Building better programs (Part 1)
Goals
You might be wondering why the word "goals" is used instead of just the word "goal", as what more could one want than correctness? As it turns out, a lot.
Correctness
Make your programs work correctly.
A program that is clear is easier not just for others to understand but for you to understand it in the weeks and months after you wrote it.
Clarity
Make your programs understandable.
It is harder to make a program clear when it is complex, so where possible, make it simpler by breaking it into smaller subtasks.
Modularity
Break your program into reusable pieces.
To take full advantage of our work, we ideally don't have to keep reinventing the wheel. Where possible, we make our work general so it is more widely applicable.
Extensibility
Make functions general enough to be used again.
All these goals support correctness by reducing the chance of introducing errors and increasing the chance of finding any that do exist.
How to make your programs clear
In the example of our simple code, where the output was formed by exchanging the first and middle characters in the input, our diagram assumed that the string was long enough that a first and middle character could be extracted. This does not work for strings of length less than 2. The code we created results in an error when the input is the empty string and returns the string repeated twice when the input is a string of length one. How could a clearer program help us avoid these problems?
Determining if a program is clear
Questions a reader might ask:
- What does this program do?
What makes a program clear and easy to understand? The same principles apply as in any kind of writing: aim between too terse and too verbose, and convey meaning whenever and however you can. It should be clear what the program does. - What values are used as input to this function?
The problem with the simple code can be prevented by making clear what values can be used as the input. We could prohibit use of the empty string. - What steps are used to accomplish the goal?
To be able to see whether a program is doing what it should requires knowing what steps are intended. - Why are these values being calculated?
Knowing the roles of various variable computed along the way is also required to determine if the program is doing what it should.
If you can't understand what a program is supposed to be doing, it is quite possible that it is not performing as intended.
Caution!
If the answers are not clear to you, there is a good chance that the program is not correct.
This suggest giving lots of comments to provide explanations. However, a short note is often clearer than a long-winded verbose one. Ideally, the code itself is written clearly enough to make the intention obvious with comments added judiciously as needed. If you aren't sure about whether a comment is needed or not, err on the side of providing too much information, not too little.
Achieving balance in use of comments:
- Where possible, use the code itself to convey meaning.
- Use comments as a supplement where necessary.
- Err on the side of excess.
How to write clear code
So how does one write clear code? For starters, choose identifiers that convey meaning; a meaningless name is a wasted opportunity. Choose meaningful names for variables, constants, and functions.
The same principle applies to choosing data types. Choose good data types. Is the answer a Boolean? Then use a Boolean! Use the simplest choice that gets the job done.
Even though you can read your code and know whether 7 represents the number of days in a week or the number of dwarves, your reader would appreciate reading a meaningful name. Use constants and avoid “magic” numbers.
Organize your code and put all imported modules and constant declarations first. This makes it easier on the reader by putting such information in a place that is easy to find.
Use blank lines and make visual “pauses” between tasks. Blank lines can be used as dividers to break code up into units, similar to the idea of grouping sentences into paragraphs. But still be prepared to add comments, as not all meaning can be conveyed in code alone.
Caution
Sometimes comments are needed to achieve clarity.
How to use comments
For example, comments can be a succinct way of letting the reader know the meaning and purpose of variables and constants, as well as types used, such as restricting inputs to nonempty strings. Comments are also crucial in showing the reader the “big picture” view of what is being done when and why. This can easily be achieved by using subtasks from the planning stage, as in our blackboard work, as comments in the program or function.
Comments for every program
- Meaning and types of variables and constants (as needed).
- Subtasks and steps in solving the problem.
Specifying the purpose of a function gives a clear way of explaining how the output is related to the inputs. Comments should also specify preconditions. You can think of these as the promise made by anyone using the function, guaranteeing properties of the inputs. Postconditions are like a promise made to the user of the function, specifying the results of using the function.
Comments for every function
- Purpose, relating output to inputs.
- Preconditions: restrictions on types and values of inputs.
- Postconditions: output type and side effects.
How to make your programs modular
As an example of modularity, consider the record label example, where we had to find the area of the record label with outer diameter 4 inches and inner hole diameter 0.25 inches. The task comes down to finding the area of the inner circle and substracting it from the area of the outer circle. The function relied on the subtask of finding the area of a circle. A function that helps in another function is known as a "helper function".
Good habit
Use helper functions for subtasks.
Using helper functions
Planning out a program consists of breaking it into smaller subtasks. Create your program by breaking tasks into subtasks. You can think of the process as identifying and putting together a "wishlist" of functions you wish you had available such as for repeated tasks. For example, finding the area of a circle in the record label example. You might also wish to have a function for the purposes of clarity, where looking at the details of an intermediate calculation could distract from the “big picture” of these order of steps being taken to reach the goal. In short, create functions for repeated tasks and clarity. Once you have assembled a wishlist, make your wishes come true. If there is no built-in function that does what you want, create your own helper functions.
How to make your programs extensible
Helper functions can often be reused in other programs. Ideally anything that you write has the potential for reuse. For example, in our room painting example, we had to find the area of the walls of 20 rooms, each 6×8 with 10-foot ceilings. We originally wrote a function with the number of rooms as the parameter. We can make it much more general, and hence more widely applicable, by also allowing the length, width, and height of the rooms to vary. Whenever you write a function, ask yourself whether there are more parameters to add to make the code more general.
Good habit
Use parameters to make functions more general.
Checklist
What we've discussed is summarized in this list.
Better program checklist
- Are types and meanings of all variables and constants clear?
Ensure that variables and constants have clear types and meanings. - Are there any “magic” numbers?
If there are numbers without apparent meaning, replace them with constants. - Is the organization and use of blank lines clear?
A program can also be made clearer by good organization and separation using blank lines. - Are the steps indicated using comments?
The plans we make on a blackboard before writing the program should also be included. - Do functions have comments explaining the purpose, preconditions, and postconditions?
Comments on functions should spell out the purpose, preconditions, and postconditions. - Are there helper functions for repeated and other tasks?
Don't forget to use helper functions where useful. - Are there more parameters that could be added to a function?
Add parameters where possible to make functions more extensible.