[Tutor] How to refactor a simple, straightforward script into a "proper" program?
boB Stepp
robertvstepp at gmail.com
Sun Jan 5 22:25:52 EST 2020
I have been doing much pondering of yesterday's remarks. In
particular your and Alan's thoughts have me reflecting deeply.
On Sat, Jan 4, 2020 at 8:07 PM DL Neil via Tutor <tutor at python.org> wrote:
>
> On 5/01/20 12:05 PM, boB Stepp wrote:
> If you do not consider OOP to be (too) 'advanced', what may help is
> drawing a UML diagram - if UML is not in your portfolio (and to ease
> email-presentation), instead make two (equivalent to UML) lists:-
Other than some toy, play around with code programs to explore OOP, I
have yet to write anything for serious use with that style of code
organization (Unless you throw out that everything in Python is an
object, etc.). I keep circling around the OOP pool, but have yet to
leap in and try to swim.
> What is the subject of the computation? A book.
> How are you measuring its size? Nr Pages.
When I read this in the wee morning hours today, I was startled that
it made sense to consider multiple books (Or other sorts of reading
materials, per Alan.) even in light of the original simple script.
Say I was a student (I always am, just not "officially".). I could
see in light of a multiple course load, each with multiple readings to
accomplish with each having different due dates, that I (or my
mythical user) might want to keep track of all of this and how many
pages each day to read in each text. And that thinking leads to
persistent storage and upon opening the program updating the pages per
day of each text. So this along with Alan' and your comments have me
shifting my thinking from procedural code organization to OO
organization. So I keep threatening in previous comments I have made
on this list to tackle the OOP learning curve. Perhaps now is the
timely time?
So I have been thinking a lot about this today and find myself
stumbling around in a confused daze despite making lists, crude
UML-like diagrams, etc. So questions!
I think the basic unit of code organization would be a ReadingMaterial
class, choosing this name to reflect Alan's thoughts that ultimately
there might be other things to read besides paper-based books. To
keep with the current confines of the original problem as stated, an
object of this class would have the following user-entered attributes:
title, total_amount_to_read, total_amount_read, and goal_date. I
switched from "pages" to "amount" as my best wording to reflect a
potential shift from pages read to percent of item read or lines read
or whatever. This means that I will have to throw in the concept of
"units" if I do this generalization now. I know the Agile people
might say stick with today's problem (pages) and worry about other
units when one is forced to. So I may stick with pages after I hash
out more important difficulties. So back to "pages" for now.
So I am really struggling with "separation of concerns" on getting the
four needed attributes of ReadingMaterial from the user's screen to
the particular instance of this class that the user wishes to create.
First point of confusion: the creation of the ReadingMaterial object.
Should it start with (a) my_book = ReadingMaterial() or with (b)
my_book = Reading_Material(title [, total_pages_to_read,
total_pages_read, goal_date])? The brackets indicate optionally
starting with all four attributes from the get-go. In other words in
(a) the object would see to initiating the acquisition of user input
itself, while the second has some other entity taking care of user
input and then passing it into the instantiation process. Which is
the proper way to proceed? On the (a) side, ReadingMaterial knows
what it needs and it is the natural location for future additional
attributes that may need to be acquired someday. This is the
direction I am currently leaning. So the initialization of a
ReadingMaterial object might look like:
class ReadingMaterial:
def __init__(self):
self.title = _get_input(???)
self.total_pages_to_read = _get_input(???)
self.total_pages_read = _get_input(???)
self.goal_date = _get_input(???)
def _get_input(self, ???):
return input_manager.get_input(???)
Based on my previous code explorations, I need to do two forms of
input validation: (1) syntactical, so no ValueErrors result and (2)
logical: Things like the number of pages read are less than the
length of what is being read, etc. It seems to me that the
InputManager (yet to be fleshed out) should do (1) and ReadingMaterial
should do (2), especially as it cannot be done until *all* of the
input is acquired. So what about (???) ? Only ReadingMaterial knows
about the details of each attribute, especially validating the "type"
of input. Also, it seems natural that in case of type validation
exception being thrown, the resulting message to the user should be
customized to the input being asked for. That suggests to me that
such error messages should be stored in the ReadingMaterial class.
Additionally, the input prompt for each attribute to be acquired needs
to be customized appropriately as well. And if I am engaging in
"good" OOD thinking then that suggest that (???) should minimally be
the input type being requested (str -- not really an issue since
input() returns a string, int, date object, etc), the customized input
prompt and the customized error message. I presume good practice
would for the InputManager to pass the input prompt to a
DisplayManager to be printed to the user's screen and that the
InputManager would convert the user input to the requested (by
ReadingMaterial) type, syntactically validate it, if that fails send
the error message to the DisplayManager and await a new effort at
valid input. Is this how I should be thinking about this problem?
*If* I am on the right track, where does the InputManager get
instantiated? DisplayManager? Is this where in the MVC pattern, the
program's main() would instantiate a Controller and then the
Controller would be responsible for coordinating these?
Anyway this is where I am at now staring at a mostly empty split
Neovim screen, split between reading_tracker.py (Name change per Alan
~(:>)) ) and test_reading_tracker.py with
============================================================================
#!/usr/bin/env python3
"""Pages per day needed to complete a reading on time.
[Usage message]
[Third-party or custom imports]
[A list of any classes, exception, functions, and any other objects
exported by the module]
class ReadingMaterial
"""
class ReadingMaterial:
"""A class to represent reading material such as books."""
pass
if __name__ == "__main__":
pass
============================================================================
on the left side and
============================================================================
#!/usr/bin/env python3
"""Tests for reading_tracker.
[Usage message]
[Third-party or custom imports]
[A list of any classes, exception, functions, and any other objects
exported by the module]
"""
import unittest
from reading_tracker import ReadingMaterial
if __name__ == "__main__":
unittest.main()
============================================================================
on the right side.
--
boB
More information about the Tutor
mailing list