Modules -> OOP -> Case study: Time -> Prototyping versus planning

Prototyping versus planning


Related code
class Time:
    """Represents the time of day.
    attributes: hour, minute, second
    """

    def set(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    def print(self):
        print(f"{self.hour:02}:{self.minute:02}:{self.second:02}")

    def add(self, other):
        sum = Time()
        sum.hour = self.hour + other.hour
        sum.minute = self.minute + other.minute
        sum.second = self.second + other.second

        if sum.second >= 60:
            sum.second -= 60
            sum.minute += 1
        if sum.minute >= 60:
            sum.minute -= 60
            sum.hour += 1

        return sum

    def increment(self, seconds):
        self.second += seconds

        while self.second >= 60:
            self.second -= 60
            self.minute += 1

        while self.minute >= 60:
            self.minute -= 60
            self.hour += 1

start = Time()
start.set(9, 45, 0)

duration = Time()
duration.set(2, 30, 0)

divmod

divmod is a built-in function that takes two arguments and returns a pair of values representing the quotient and remainder of the division operation. The syntax is:

divmod(a, b)

Here, a is the numerator, and b is the denominator. The function returns a tuple (q, r) where q is the quotient (the result of the integer division) and r is the remainder.

>>> divmod(10, 3)
(3, 1)

In this example, divmod(10, 3) calculates the quotient and remainder of dividing 10 by 3. The result is a tuple (3, 1), where 3 is the quotient and 1 is the remainder.

So, divmod is same as floor division and modulus combined.

>>> divmod(10, 3) == (10 // 3, 10 % 3)
True

Prototyping versus planning

The development plan I am demonstrating is called "prototype and patch". For each method, I wrote a prototype that performed the basic calculation and then tested it, patching errors along the way.

This approach can be effective, especially if you don't yet have a deep understanding of the problem. But incremental corrections can generate code that is unnecessarily complicated, since it deals with many special cases, and unreliable, since it is hard to know if you have found all the errors.

An alternative is designed development, in which high-level insight into the problem can make the programming much easier.

For example, we can convert Time objects to integers and take advantage of the fact that the computer knows how to do integer arithmetic.

Here is a method that converts time to integer:

class Time:
    ...
    def to_seconds(self):
        minutes = self.hour * 60 + self.minute
        seconds = minutes * 60 + self.second
        return seconds
...

And here is a function that converts integer to a Time:

def seconds_to_time(seconds):
    time = Time()
    minutes, time.second = divmod(seconds, 60)
    time.hour, time.minute = divmod(minutes, 60)
    return time

Since seconds_to_time takes an integer (seconds), and not Time instance, it is not reasonable to be part of the Time class. So we define it as a function.

Once you are convinced they are correct, you can use them to rewrite add and increment:

class Time:
    ...
    def add(self, other):
        seconds = self.to_seconds() + other.to_seconds()
        return seconds_to_time(seconds)

    def increment(self, seconds):
        seconds += self.to_int()
        return seconds_to_time(seconds)
...

Exercises


Rewrite increment using time_to_int and int_to_time.