Error Cases
One thing we have to be careful about when coding is the point where we may reach invalid input, or cases that won’t work in our program. These cases need to be carefully considered when developing software, otherwise serious issues can occur.
For instance, one case we’ve not thought about is division by zero, which is impossible, and will cause errors if unchecked. For instance, if we had tried to run 10 / 0
in our previous program, Python would crash, reporting the error float division by zero
. However, in a real application, this uncaught error could crash the whole system - which is never a good thing to happen.
Note that the code above checks if our second number is zero only when we’re running a divide. We make sure to keep prompting for input until we get a non-zero number to divide by.
Cleaning Up Our Logic
This section is actually a very quick example, hence the snippet opposed to the whole file. We aim to clean up the code to make it easier to maintain and work with.
We refactor the logic to not bother with reassigning False
to isZero
(which it already must be, otherwise we wouldn’t continue in the while
loop). We also remove the repeated case of setting isInvalidInput = False
, by moving the assignment out of the if
statement.
Don’t Repeat Yourself
There is a well well known principle in Computer Programming called Don’t Repeat Yourself, or DRY. Repeating your code - be it copy and pasting, or just following similar patterns - is never a good habit to fall into. The repetition means that in the future you will have to update various codeblocks if i.e. you find a bug with the code, or you need to add new functionality. When you start repeating code more than twice, this becomes a maintainability nightmare. Therefore, when you find yourself doing the same thing twice, it is always a good idea to take a step back and think how you can make the pattern of code you’re using, repeatable.
For instance, in our previous program, we are repeating code to take in numbers:
This code can much more easily be translated into a function (function: a named section of code to perform some task) which allows us to turn the pattern we’re using into a repeatable task.
Functions allow us to define (hence def
) a named section of code (such as get_number_from_user
) which we can perform some useful task with. This then allows us to reuse it in multiple places, which saves code repetition and makes your program more easy to understand and work with. However, in order to produce truly useful code, we want to be able to get data out of functions, such as if the user has entered input. This is done via the return
statement, and will return a variable (i.e. number
, result
) or a value (i.e. True
, 100
).
In the following code snippet we have converted our pattern for taking numbers as input into a function get_number_from_user
.
We move the main pattern into the function, and then replace it by a call to the function, which is done by the function name followed by parentheses ()
, which informs Python that we’re using a function. We use an assignment because we know that the get_number_from_user
function returns a value.
Exercises:
- Is this all we need to do? Check if there is anywhere else we’ve taken in numbers that we need to convert into calls to
get_number_from_user
.
Abstracting Common Patterns into Functions
However, we need to make this function more generic. Remember how in previous versions of the code, we would prompt the user with something more useful, such as Enter the second number:
? To do this, we need to pass in something to each function, via something called parameters (parameter: a variable that can be passed into a function). Parameters allow us to give variables to functions, which in turn let our functions do more interesting things.
In this code snippet, we pass in the string that we want to be output, so it will then prompt the user something more obvious. This is done via a parameter, which allows us to specify from our code what we want to be prompted, instead of using the same string.
You Get a Function, You Get a Function, Everyone Gets a Function!
Now we get to cleaning up more of the code. In this segment, we will turn more bits of repeated code into functions in order to remove any repetitions.
We follow our previous example, and move the code for getting a string input into the function get_string_from_user
.
We take the functionality for get_result
and move it into its own function so that in the future, we are able to calculate an operation much more easily.
We also take the whole chunk of code to take get_operation_from_user
out, including its loop. This allows us to take the large error checking block, and move it into its own component. One thing to note is that at the end of the function, we have the statement return operation, number2
; this allows us to return multiple values, and is an incredibly useful feature in Python. In this example, we return both the operation
that we were given by the user, as well as the value of number2
. Now, why do we do this? When we pass in our number2
value (from our main loop) into the function, Python creates a copy of that variable. This means that when we then update it, Python only updates our copy, and not the previous value - which may not seem useful, but in the future we’ll see that it’s important. Therefore, we need to make sure that we return the new value of number2
, which can be done in the same return
statement.
Because we’ve returned multiple values, we also need to make sure that we collect them on the other side. This is done via the same syntax - operation, number2 = get_operation_from_user(number2)
- where we inform Python we have multiple values via the function.
Exercises:
- Try and return more than two values from a function. Does it work the same?