Writing Simple Programs

A machine showing the Input, Process, Output (IPO) model used for writing simple programs.

One of the best approaches I've found while learning is to write simple programs. I'm using the word simple, in this case, to mean the programming problems will have a minimal number of inputs, a linear process, and a limited number of outputs.

Starting with simple problems is like building a simple structure. It still requires tools and a plan, but if you make a mistake, it's not a big problem; you can fix it or even start over without losing a ton of time and work. This kind of approach provides timely feedback, helping us adjust and learn from our mistakes.

Larger projects are tempting in many ways because we want to move fast and build something that'll get us paid. The problem is that instead of attempting to build a birdhouse, you're attempting to build a real house. It's much more complicated and the consequences for poor design and build choices are a lot harder to learn from, partly due to the feedback times are a lot longer, and correcting mistakes can be time-intensive and expensive.

We learn through struggle, but we need to reach for just the right amount, we call it desirable difficulty, optimizing growth and learning potential.

We're starting small to learn planning, the tools, and the craft. When we're ready for the big projects, there will still be plenty of learning to do, but we'll be better equipped to handle the challenges because we've built a solid foundation with excellent habits.

Understanding the Problem

It's tempting to look at the problem statement and jump straight into our code editor and start working through the solution. While simple programs, like the one we are going to see here, don't appear to require a lot of planning, the habits we establish are essential as we delve into more complex problems.

Let's look at the following problem:

A user needs a program to convert miles into kilometers.

Step 1: Write It Down

One great suggestion I've seen is to write out what this program needs to do?

For example:
It'll take a number in miles and output that number in kilometers.

OK, but let's clarify a bit more.

  • What's the formula for converting miles to kilometers?
  • What should we do with decimal places?
  • What should it look like when the program starts?
  • What should it look like when the program is finished?
  • What should happen if the program doesn't work? Like if someone puts something that's not a number?

Keeping these questions in mind, let's see if we can write out a concise, useful, problem statement:

Create a "miles to kilometers" calculator. The program should prompt the user for a length in miles, calculate the length in kilometers, and output the new length. It should only accept numbers and let the user know if they make a mistake.

Step 2: Create Testable Examples

Another great suggestion is to write out some testable examples. This helps us better visualize the process and provides solid examples to validate our program.

Examples:

  • if a user inputs 5 miles, the output should be 8.04 kilometers.
  • if a user inputs 16.5 miles, the output should be 26.55 kilometers.
  • if a user inputs "Mike" miles, the output should be "Please only use numbers."

Step 3: Break It Apart

Many simple programs follow the "input, process, output" (IPO) pattern. It's useful in breaking down a problem statement to get a concrete idea of what's required to solve it programmatically.

  • The input must be a number that represents a length in miles. We need a way to validate that it is a number and handle the error if its blank or something else is entered that is not a number.
  • The process for converting miles into kilometers is to multiply the length value by 1.60934. In this case, we can use an approximate value rounded to the nearest 100th place like in our testable examples above.
  • The output will either be a length in kilometers or a helpful error message.

Code It Up

We've done the work to understand the problem and design a process for getting the desired output from an input. Now let's write the program. The examples are written in Python but can be written in any language.

# input
length_in_miles = input("Provide a length in miles: ")

# process
def calculate_kilometers(length):
    return int(length) * 1.60934

# output
print(calculate_kilometers(length_in_miles))

miles-to-kilometers.py

If you tried to run this code, you got the following error:

Test 1: input 5 expect 8.05

TypeError: can't multiply sequence by non-int of type 'float'

FAILED. Boo!

It appears we are trying to run our calculation on the wrong data type, it's a string, and we need an integer. Let's fix this by making sure only an integer can be used in our calculation.

# process
def calculate_kilometers(length):
    # length in converted to an integer
    return int(length) * 1.60934

Now my first test case is working, sort of. Yay!

Test 1: input 5 expect 8.05

Provide the length in miles: 5
8.0467

FAILED. Wait, what?

This is great, but while planning this out, we wanted the value to be rounded to the 100th place. Let's fix that.

# process
def calculate_kilometers(length):
    # I've wrapped the whole return expression in round()
    return round(int(length) * 1.60934, 2)

By wrapping the return expression in the round() method and passing it an argument of 2, we get a nice clean value rounded to the 100th place.

Test 1: input 5 expect 8.05

Provide the length in miles: 5
8.05

PASSED. Hooray!

Now let's run the next test case. We passed a value of 16.5, but it didn't work; we expected a value of 26.55. This is the error message:

Test 2: input 16.5 expect 26.55

ValueError: invalid literal for int() with base 10: '16.5'

FAILED. Oh no!

In Python, a ValueError is raised when the function receives an argument that's the correct type but an inappropriate value. In this case, we passed a floating-point number and not an integer. Let's change the code so that our function accepts floating-point numbers.

# process
def calculate_kilometers(length):
    # By wrapping our length in float() we can actually use decimals AND integers. 
    return round(float(length) * 1.60934, 2)

This time we used the float() method instead of the int() method. Let's make sure it works with our first two test cases.

Test 1: input value 5 expect 8.05

Provide the length in miles: 5
8.05

PASSED. Yay!

Test 2: input value 16.5 expect 26.55

Provide the length in miles: 16.5
26.55

PASSED. We rock!

We are making good progress. Let's move to our final test case.

Test 3: input value "Mike" expect "Please only use numbers."

ValueError: could not convert string to float: 'Mike'

FAILED. Why?

The error message tells us that our function cannot convert a string to afloat. We need some way to handle strings and return a message if a string is used. In this particular case, we need a watch to handle the error and provide a more useful error message.

# process
def calculate_kilometers(length):
	# We've include a try except block to handle the error
    try:
        return round(float(length) * 1.60934, 2)
    except ValueError as ve:
        return "Please only use numbers"

The try-except block allows us a tidy way to handle exceptions, which are a type of error in Python. If you'd like to read more about them and handling errors, read the official documentation.

When we rerun our tests, they all pass.
Test 3: input "Mike" expect "Please only use numbers"

Provide the length in miles: Mike
Please only use numbers

PASS. Finally!!

Our final program works, it meets all the specifications we set, and all the tests pass.

# input
length_in_miles = input("Provide a length in miles: ")

# process
def calculate_kilometers(length):
	# We've include a try except block to handle the error
    try:
        return round(float(length) * 1.60934, 2)
    except ValueError as ve:
        return "Please only use numbers"

# output
print(calculate_kilometers(length_in_miles))

def calculate_kilometers(length):
# We've include a try except block to handle the error
try:
return round(float(length) * 1.60934, 2)
except ValueError as ve:
return "Please only use numbers"

Summary

In this article, we learned how to write simple programs by learning how to better understand the problem our program intends to solve. We wrote the problem out and broke it down. We wrote test cases to ensure it worked the way we designed it. Finally, we wrote the program, testing and debugging as we went.

Take it Further

This is a great place to stop for now. We solved the problem but, are there things we could do to learn even more? What about building a user interface, or converting Kilometers back into Miles? How can we automate our testing? Do you think you could write this in a new language? What about JavaScript or Go?

Here is an example with some automated test:

import unittest

# input
# length_in_miles = input("Provide a length in miles: ")

# process
def calculate_kilometers(length):
    try:
        return round(float(length) * 1.60934, 2)
    except:
        return "Please only use numbers"

# output
# print(calculate_kilometers(length_in_miles))

class TestCalc(unittest.TestCase):
    def test_1(self):
        self.assertEqual(calculate_kilometers(5), 8.05)

    def test_2(self):
        self.assertEqual(calculate_kilometers(16.5), 26.55)

    def test_3(self):
        self.assertEqual(calculate_kilometers("Mike"), "Please only use numbers")

if __name__ == "__main__":
    unittest.main()

Here is an example with HTML, CSS, and JavaScript:

Come up with more of these "simple" programs to write. The more practice you get going through these steps, the more comfortable you'll be writing programs.