7 Tips on Writing Better and More Readable Code
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
When people first start learning programming, it is often the logic part of it that is the fascinating and challenging bit. What is often overlooked are the more nuanced aspects of programming such as variable naming, code structure, documentation, etc. The aim of this post is to introduce a few simple tips on these aspects that are easy to implement, which will hopefully make you write better and more readable code.
1. Write Descriptive Function and Variable Names
Many beginners, and also professional programmers, sometimes have a tendency to name their functions and variables with non-descriptive names. The educational material available is partially to blame here. Many tutorials and books when introducing functions and variables don't name them properly, calling functions "f" and variables "x" and "y" for example. One of the brilliant aspects of having names for variables and functions is that the programmer can convey some information on what role that variable or function plays in the program. A function called "f" does not give us any information on what it does. Same goes for "x" and "y." Another common occurrence (which is also often found in educational material) is having variables with unnecessary "fluff" in the name, such as "my_number" or "the_answer." What is it that makes the number belong to the programmer or the answer being the answer?
Sometimes it is also very tempting to name your variables in the context of the program domain and not the problem domain, e.g., naming your function parameters "input" and your return value "output" or "return_value." This doesn't give away any useful information that we don't already know. Of course, the variables first seen right after a function declaration are inputs. Same with whatever comes after a return statement, it must be an output.
Example of Bad Naming Style
def list_func(inputs): temp = 0 for input in inputs: temp += input output = temp return output
Instead of doing the above mistakes one can use the chance to name things to convey the meaning of the program instead, naming functions and variables in relation to the problem they're trying to solve.
It is also sometimes useful to mention units in variable names. In my personal experience, this has been especially useful with variables representing time, e.g., "ms_to_sleep", "hours_to_work", etc. Failing to have a full grasp of units used can, in the end, be very costly.
Example of Good Variable Naming
def sum(nums): cur_sum = 0 for num in nums: cur_sum += num return cur_sum
2. Remove Comments
While we're on the subject of readability, there is another technique that can be utilised to improve code, namely removing comments. Most code is over commented, and you might wonder why that is a bad thing. The problem is that comments need to be maintained, just as code does. But code breaks or shows us unexpected behaviour if it's wrong. A comment, on the other hand, won't break the program if it's wrong or false, leading to being misleading over time.
Therefore comments can be dangerous at explaining code since they're not responsible for any of its behaviour. Luckily we have this wonderful tool to explain what code is doing, namely the programming language we're programming in. If we write readable code in our programming language, we shouldn't have much need for comments as the code would be self describing. Whenever you feel the urge to write comments on what your code is doing, try to think of a way you can explain this in the code instead, such as naming variables differently or extracting some code into a separate function.
A general rule of thumb is to comment on why the code is as it is, not what it is doing.
Example of Bad Commenting
# A function that takes a list of numbers and outputs # the sum. def list_func(inputs): # We use temp to contain our sum temp = 0 # Loop over each number in the inputs for input in inputs: # Add our current number to our sum temp += input # Assign our final sum to the output output = temp # Return our output return output
Instead of having each comment mirroring each line of code, it is much more useful to put comments where one anticipates that readers of the code will be confused, and commenting why the code is written the way it is.
Example of Good Commenting
def sum(nums): # Instead of writing our own function we could use # Python's inbuilt 'sum' function here but we want # to use this function as an example for an online # article. cur_sum = 0 for num in nums: cur_sum += num return cur_sum
3. Document Classes and Functions
Documenting classes and functions would be the exception to the rule above. Comments are in general unnecessary, but one place where they have their use is in documenting higher level concepts. Sometimes you can't fully fit what a function does in its name without typing out a whole sentence, making the function name overly verbose. For someone reading the code, it is very helpful if functions and classes have some documentation, either in the form of comments or as a docstring. This saves the reader time as they don't have to read through the whole function or class to figure out what it does and what its parameters and output are.
For dynamically typed languages this is especially important. In statically typed languages functions have a signature which specifies what its input types should be and what the output type is. This is not the case in dynamically typed languages where the types of the parameters are not explicitly given. If the function isn't documented one usually has to read through at least the beginning of it to understand what 'shape' the parameters should have. A good function documentation that specifies what types the parameters are as well as the output, perhaps even with an example, saves the reader of having to figure this out and they can instead trust the documentation. Google's Python style guide is a good example of this.
4. Write Helper Functions
Functions are probably most known for avoiding code duplication and reuse, but they can also be used to aid code readability. As a general rule, having a function of length longer than the height of your screen makes it unwieldy and hard to read. The reader would have to keep variables from the beginning of the function in their head whilst they are reading the rest of it, and it is hard to get a good picture of the code. This is where helper functions come in. Usually, a big function like the one mentioned above would have multiple code 'sections' where each section would do something slightly different from the rest of the code. Each of these sections can be turned into a helper function that has a descriptive name. This would aid in readability for the big function. Instead of being long and unwieldy it would be short and snappy with easy to read function calls instead.
5. Follow Code Conventions
When being a beginner programmer, one might not pay too much attention to coding conventions. However, on collaborative projects they are essential. Coding style and conventions help readers of code by indicating what the code does. E.g., it is quite common to have class names have its first letter be upper case and then use camelCase for the rest of the name. This convention makes it easy to spot classes.
Conventions also make the code look the same throughout a whole project so a reader won't have to 'readjust' every time they look at a different part. They also allow a consistent editor setup. For example, it is quite common to have a maximum line length in projects, prohibiting any lines of code to exceeding n characters. This allows people working on the project to set up their editors so they can have two files open at the same time side by side. If this limit suddenly isn't enforced in a file, it becomes very annoying as one has to readjust the editor to see the whole line.
It doesn't matter too much what the convention is as long as there is one and that it is consistently followed. For example, it would be very annoying if one programmer used snake_case for variables while another used camelCase on the same project. An easy way to find good conventions is to see if there is a standard one for the language, such as Python's PEP8. They usually also come with linters for editors so that it will complain if the convention isn't being followed. Google also has conventions for the languages they use.
6. Write Tests
One aspect that isn't taught to beginners early on is that you should test your code. In larger projects, this is more important as tests both act as a piece of documentation for the code as well as providing confidence that the code behaves as it does. But even on single person projects, they are useful as they allow refactoring to be done more easily and act as a confidence booster.
A test for code is simple a script that tests one aspect of your code, usually a function or a class. A test for a function would explain what it tests and set up input variables to the function and have defined an expected output. The test then calls the function under test with its defined input variables, captures the answer and checks that it is as expected. For example, if we're testing a function that adds two numbers together we could write a test that's called 'TestAddIntegers' which calls the function with the numbers 2 and 3 and checks that the answer is 5.
However, usually, it is not enough to have just one test per function. What is important to test is all the edge cases and rarer cases. Can the function add negative numbers? Can it add floats? Does it throw an error if we give it different types, e.g., strings?
There is a lot of debate on how much one should test one's code and which parts and in what way. This deserves a blog post of its own. There seems to be good consensus though that computer programs should have some form of tests.
7. Use Whitespace and Ordering to your Advantage
One aspect of programming that is rarely mentioned is how statements are ordered and arranged. I like to think of programming as writing prose. When writing we have structures and techniques that help us communicate with the reader. We have individual words, sentences, paragraphs, punctuation, and other punctuation that help us convey our idea to the reader. In code, we have different structures. We have functions, objects, statements, assignments, expressions, and most importantly, whitespace. These structures are different to what we use when writing prose, but we should use them with the same purpose, to communicate effectively what our code does. In the same way that we wouldn't introduce more than one idea in a paragraph, we shouldn't have two unrelated statements be executed in the same block of code.
A good metric to think of when ordering statements, is that one should minimise variables' lifespans. A variable's lifespan is the number of lines between the first and last time it was used. Reading code with long variable lifespans is hard as one has to keep variables in one's mind for a long time, which increases the cognitive load. If a variable instead is used in a 10 line long block, for example, it is much easier to keep track of, and one doesn't need to think of it after that block. Doing this well is sometimes difficult but adding helper functions and whitespace in the right places helps.
I hope these tips on writing better and more readable code were useful and that you will keep them in mind the next time you program. If you have any tips of your own for improving code readability, I'd love to hear about them in the comments.
Any other feedback is also very welcome. I might write posts about each of these aspects in more detail at a later time, so any good tips or suggestions on what you want to know more about will be much appreciated.