[Tutor] How to refactor a simple, straightforward script into a "proper" program?
boB Stepp
robertvstepp at gmail.com
Wed Jan 1 21:38:20 EST 2020
On Wed, Jan 1, 2020 at 12:30 PM boB Stepp <robertvstepp at gmail.com> wrote:
> <version1.py => A simple throwaway script>
> ============================================================================
> #!/usr/bin/env python3
> """Calculate the number of pages per day needed to read a book by a
> given date."""
>
> from datetime import date
>
> COUNT_TODAY = 1
>
> num_pages_in_book = int(input("How many pages are there in your book? "))
> num_pages_read = int(input("How many pages have you read? "))
> today_date = date.today()
> goal_date = date.fromisoformat(
> input("What is your date to finish the book (yyyy-mm-dd)? ")
> )
>
> days_to_goal = (goal_date - today_date).days
> pages_per_day = (num_pages_in_book - num_pages_read) / (days_to_goal +
> COUNT_TODAY)
> print(
> "\nYou must read",
> pages_per_day,
> "pages each day (starting today) to reach your goal.",
> )
> ============================================================================
>
> As this script is I believe it to be easy to read and understand by an
> outside reader. For my original purposes it quickly gave me what I
> wanted out of it. But if I wanted to "give it away" to the general
> public there are some things that bother me:
>
> 1) There is no error checking. If someone deliberately or mistakenly
> enters incorrect input the program will terminate with a ValueError
> exception.
> 2) There are no checks for logic errors. For instance, a person
> could enter a goal date that is prior to today or enter a date in the
> future that falls well beyond any human's possible lifespan (with
> current technology).
> 3) There is no formatting of output. It is possible to generate
> float output with many decimal places, something most users would not
> want to see. And does it make any sense to have non-integral numbers
> of pages anyway?
> 4) There are duplicated patterns of input in the code as is. Surely
> that duplication could be removed?
> 5) A minor, but bothersome quibble: If the reader need only read one
> page per day, the program would display an annoying "... 1.0 pages
> ...", which is grammatically incorrect.
> 6) There are no tests, which makes it more difficult to grow/maintain
> the program in the future.
> 7) The user must restart the program each time he/she wishes to try
> out different goal dates.
> 8) [Optional] There are no type annotations.
OK, I have made some progress, but this has been for me a more
difficult exercise than I thought it would be. I feel that in my
efforts to be DRY that I am coding as if I were wet behind my ears!
Anyway, here is what I currently have:
<version2.py => Not all goals accomplished -- yet.>
============================================================================
#!/usr/bin/env python3
"""Calculate the number of pages per day needed to read a book by a
given date."""
from datetime import date
def get_input(str_converter, msg):
"""Prompt user with a message and return user input with the
correct data type."""
while True:
try:
return str_converter(input(msg))
except ValueError:
if str_converter == int:
print("\nPlease enter a positive integer!")
elif str_converter == date.fromisoformat:
print("\nPlease enter a valid date in the following
format yyyy-mm-dd!")
def get_input_params():
"""Collect all needed input parameters."""
str_converters = [int, int, date.fromisoformat]
input_msgs = [
"How many pages are there in your book? ",
"How many pages have you read? ",
"What is your date to finish the book (yyyy-mm-dd)? ",
]
num_pages_in_book, num_pages_read, goal_date = map(
get_input, str_converters, input_msgs
)
days_to_goal = (goal_date - date.today()).days
return num_pages_in_book, num_pages_read, days_to_goal
def calc_pages_per_day(num_pages_in_book, num_pages_read, days_to_goal):
"""Return number of pages to read each day to attain goal."""
COUNT_TODAY = 1
pages_per_day = (num_pages_in_book - num_pages_read) /
(days_to_goal + COUNT_TODAY)
return pages_per_day
def main():
"""Run program and display results."""
input_params = get_input_params()
print(
"\nYou must read",
calc_pages_per_day(*input_params),
"pages each day (starting today) to reach your goal.",
)
if __name__ == "__main__":
main()
============================================================================
I think I have removed all "perceived" duplication, but I had to
struggle to get to this point. I have never used the map() function
before, but it was the only way I was able to avoid writing multiple
calls to get_input() as separate lines.
ValueErrors are now handled, but I don't have checks in place yet for
input that does not raise an unhandled exception, but would cause
erroneous results, such as negative numbers of days, in the past
dates, etc.
I *think* the code still reads reasonably well, but is not as
straightforward to understand as the original simple script.
I have not written tests yet (Bad boB!). Nor have I addressed better
display formatting.
I still think that you guys would do something remarkably easier, so I
am awaiting your comments with bated breath.
HAPPY NEW YEAR TO YOU AND YOURS!!!
--
boB
More information about the Tutor
mailing list