# The virtue of incremental development

Tags: misc

Here’s today challenge: can you write a command-line tool that allows converting to and from various measurements units?

For instance, you could input “3 miles in meters” and get “4828.03”.

I submitted this challenge to my Python students last weekend, asking them to write the code from scratch. 1

1 hour later, something miraculous happened that I never would have expect.

But let me tell you the full story.

# Getting started #

I told the students that they could start by writing some “exploratory code”.

“Just hard-code anything you have to and keep everything in the `main()` function “, I said.

After a few discussions, we agreed to only write code that converted kilometers to miles, and that we’ll read the values from the command line.

Here’s what we came up with:

``````import sys

def main():
kilometers = float(sys.argv)
miles = kilometers / 1.609
print(f"{.2f}", miles)

if __name__ == "__main__":
main()
``````

I then pointed out that the code was not generic. Indeed, “kilometers”, “miles” and “1.609” are hard-coded there.

# Naming a new function #

The students understood there was a three-parameters function waiting to be written. So we went to the drawing board and after a while we decided to have a function called `convert(value, unit_in, unit_out)`.

Note that we did not make any assumption about the body of the function. We just wanted to see how `main()` could become more generic, and we were still allowed to hard-code parts of the code:

``````def convert(value, unit_in, unit_out):
coefficient = 1 / 1.609
result = value * coefficient
return result

def main():
value = float(sys.argv)
unit_in = sys.argv
unit_out = sys.argv

result = convert(value, unit_in, unit_out)
print(f"{.2f}", result)
``````

Some notes:

• The `main()` function is now completely generic, and we probably won’t need to change it.
• The signature of the `convert` function almost dictated the command-line syntax:
``````def convert(value, unit_in, unit_out):
...
``````
``````# Usage: convert.py value unit_in unit_out
\$ python3 convert.py 2 meters miles
``````

# Computing the coefficient #

Now it was time to get rid of the hard-coded coefficient. This time finding a function name was easier:

``````def get_coefficient(unit_in, unit_out):
...
``````

Then we tried to figure out how to implement it. We knew we would be needing a dictionary, but the structure of it was unknown.

“Back to the drawing board”, I said. “Let’s write down what the dictionary should look like”.

Here’s our first attempt:

``````units =  {
"km": { "miles": 1/1.609, "meters": 1/1000, ....},
"yards": { "miles": 1/1760, "meters": ..., "km": ...}
...
}
``````

“This won’t do”, I said. “Look at what happens if we add a new measurement unit, such as `feet`”.

We’ll have to:

• add a new ‘feet’ key to the `units` dictionary,
• compute all the coefficient to convert from `feet` to all the other units,
• and add a `feet` key to all the other dictionaries

There has to be a better way!

After a short brainstorming session, we decided to limit ourselves to distance measurements, and to always convert to SI units first.

So we draw the new structure of the `units` dictionary:

``````# Coefficients convert from "meters"
distances = {
"km": 1/1000,
"yards": 1.094,
"miles": 1/1609,
}
``````

And then we thought about the algorithm. We found three possibilities:

• If we want to convert from meters, we just have to look up the coefficient in the dictionary
• If we want to convert to meters, we can look up the coefficient in the dictionary and return its inverse
• Otherwise, we combine the two procedures above and return the product of the two coefficients.

“This is looking good”, I said. “Let’s try to implement the algorithm but just for the first case and see what happens”.

# Testing the algorithm #

I showed my students how they could use Python’s interpreter to check the get_coefficient() function was working properly.

We quickly managed to get the first case working:

``````def get_coefficient(unit_in, unit_out):
# FIX ME: only works with distances for now
# Coefficients to convert from "meters"
distances = {
"km": 1/1000,
"yards": 1.094,
"miles": 1/1609,
}
if unit_in == "m":
return distances[unit_out]
``````
``````>>> import conversion
>>> conversion.get_coefficient("m", "km")
0.001
>>> conversion.get_coefficient("m", "yards")
1.094
``````

“Cool, this works”, I said. “Let’s see what happens when the input value is not in meters:”

``````def get_coefficient(unit_in, unit_out):
# FIX ME: only works with distances for now
# Coefficients to convert from "meters"
distances = {
"km": 1/1000,
"yards": 1.094,
"miles": 1/1609,
}
if unit_in == "m":
return distances[unit_out]
else:
reciprocal_coefficient = 1 / distances[unit_in]
return reciprocal_coefficient * distances[unit_out]
``````
``````>>> import conversion
>>> conversion.get_coefficient("miles", "yards")
1760
``````

“Look how readable the code is”, I said. “We have a value that’s called `reciprocal_coefficient` and we get it by calling 1 over something else. Isn’t this nice?”.

# The miracle #

I then pointed out that the ‘else’ after the return was unnecessary.

``````def get_coefficient(unit_in, unit_out):
# FIX ME: only works with distances for now
# Coefficients to convert from "meters"
distances = {
"km": 1/1000,
"yards": 1.094,
"miles": 1/1609,
}
if unit_in == "m":
return distances[unit_out]
reciprocal_coefficient = 1 / distances[unit_in]
return reciprocal_coefficient * distances[unit_out]
``````

And then it happened. “Hey”, one of the students said, “what if we added meters in the distances dictionary with `1` as value? We could get rid of the first `if` too!”.

“Let’s do it”, I said:

``````def get_coefficient(unit_in, unit_out):
# FIX ME: only works with distances for now
distances = {
"m": 1,
"km": 1/1000,
"yards": 1.094,
"miles": 1/1609,
}
reciprocal_coefficient = 1 / distances[unit_in]
return reciprocal_coefficient * distances[unit_out]
``````
``````>>> import conversion
>>> conversion.get_coefficient("m", "m")
1
>>> conversion.get_coefficient("km", "m")
1000
>>> conversion.get_coefficient("m", "yards")
1760
``````

And of course, this works. When `meters` is either `unit_in` or `unit_out`, all operations will involve multiplying or dividing by 1.

That was a really nice surprise for several reasons:

• One, when I thought about the problem alone, before starting the workshop, I was pretty sure I would need a much more complex data structure.
• Two, one of the students just refused to believe the code would work, even after having seen it in action in the interpreter ;)
• Three, we killed one comment :)

# Lessons learned #

We found a beautiful algorithm and a nice data structure, not by trying to solve everything at once, but by slowly building up more and more generic code, getting rid of hard-coded values one after the other, and by carefully thinking about naming.

I hope you find this approach useful, and I highly suggest you try using it next time you implement a new feature.

Cheers!

1. I’m using mob programming during my Python classes. It works really well. ↩︎

Thanks for reading this far :)

I'd love to hear what you have to say, so please feel free to leave a comment below, or read the contact page for more ways to get in touch with me.

Note that to get notified when new articles are published, you can either:

Cheers!