Here is some advice that you should keep in mind while writing your code. These tips should make it easier for you to write readable and maintainable code. The most important thing about computer code is, that you will be able to understand it even after a longer period of time. Also, you will probably read more code than write it, so make sure it makes sense to both, a different person and yourself 6 months from now.
Use meaningful names for variables and functions. Have a look at the following example:
def a(h, w): result h*w
The above function most likely takes the height and the width of an object and calculates the rectangular area of this object and returns it. This function might even come with a comment explaining what it does. But just imagine you are at the bottom of your code and there is just this function used as such:
if a(5, 12) < 35: print("Too small!")
What was the use of the function a() again? Do I really need to look it up? This is even worse when you import the functions from a different file, then you cannot just scroll up to have a look.
Try to avoid such a vague name pattern and use descriptive names that make sense.
def area_rectangle(height, width): result height*width if area_rectangle(5, 12) < 35: print("Too small!")
If you now look at the code, it does make more sense, even without any comments. Also, if you have several functions with a similar goal, you may want to use a certain naming patters such as area_rectangle() and area_circle which makes it clear by their names that they have some similarities. The same applies to the names of variables. r is not the best variable name for the radius of a circle, therefore, use radius_circle instead, or something else that is more descriptive.
You may even want to add the measurement unit inside the name such as battery_level_mV so you will immediately see that this value is given in mili-volts instead of volts.
Avoid hard-coded values
When you implement some code with numbers or strings inside that code, you could either type the values you want into the code, or you could use a variable instead. If you implement the code by typing the actual values, these values are called hard-coded as you cannot easily change them.
Why is this bad? When you have a value that changes during the development phase, you will need to find this value in the code on each instance you use this value, and replace it. This can be a very time-consuming process while hard-coded values do not have any real advantage.
In some programming languages, you can implement these values as constants which are variables whose value cannot be changed. In Python, constants do not exist so it is common practice to use a variable written entirely in capital letters.
MIN_VOLTAGE = 1.2 MAX_VOLTAGE = 1.7 PI = 3.141592 SOFTWARE = "Python" SOFTWARE_VERSION = "2.7.17" DEFAULT_STATE = False
In case the value will change inside the code, you should use a simple variable instead of a constant. The advantages are that you can easily change the value of these constants without searching in the code and you can see in the code what this value actually means. If there are hard-coded values, these numbers do not always make sense. Compare the following:
if voltage_mV > 1.2: print("Enough power left!")
if voltage_mV > MIN_VOLTAGE: print("Enough power left!")
The second version of that code is more likely to make sense to the reader than the version with hard-coded values.
The use of comments in code is often discussed among developers. Why using them? Why not using them? When to use them? There is no absolute answer to this topic, but here are some guidelines that you can apply.
Don’t understand this wrongly. Comments are great to describe the functionality of your code, but you should still try to limit the number of comments that you insert into your code. Why? Because it clutters the code and it can be misleading. Why can it be misleading? Well, the code is the part that actually does what is says, so if there is a change in the code, the code will still tell the truth, but when the code changes, the comments are often untouched. This means the comment tells something different as the code executes. In that way, the code can lie.
Have a look at the following example:
if light_sensor_mV > 300: # sets day_time to True is_day_time = True else: # sets day_time to False is_day_time = False
Now it can occur, that you may change the sensor, or make a different circuit which then inverses the behavior of the sensor. You change your code so it does work properly again. But as you only verify if the code is correct, you may not realize that the comments are not matching what the code does:
if light_sensor_mV < 300: # sets day_time to True is_day_time = False else: # sets day_time to False is_day_time = True
The issue is, that when you re-visit your own code after several months, you forget that you changed the code and then you only see that the comments and the code do not match. What does that mean? is the code wrong? Do you need to change the code? So you can now either trust the comment and change the code or you can trust your code, not knowing if it worked or not, and change the comment. This creates some ambiguity that is unnecessary and can easily be solved by removing these comments. There, it helps that your variable names and function names have a good descriptive name so that you don’t need any comments to figure out what they mean.
To make it short: Comments can lie, code cannot lie.
Write meaningful comments
But this does not mean you should never use any comments. The goal is to avoid unnecessary comments. A comment is appropriate if it adds value to the code.
There are situations where a code is simply not necessary:
# set radius to zero radius = 0 # set center to (0, 0) center_x = 0 center_y = 0 def print_hello(): # prints "Hello" print("Hello")
The above code contains comments that are totally unnecessary. Except from the risk that the comments could be misleading, these comments here do not add value. Some developers would go so far to say that, if you have the urge to write a comment to explain the code, the code itself is not well written. I would not go so far, but I would recommend thinking if that comment is really useful or not.
Comments that can add value include explanation why you set a certain value, explain the general functionality of a complex function or add notes to remind you to add a new feature later. Some text editors even highlight the word TODO for that matter.
max_battery_voltage_mV = 3300 min_battery_voltage_mV = 2700 battery_voltage_mV = voltsensor_battery.read() # check if voltage is enough to drive back if battery_voltage_mV > min_battery_voltage_mV: # TODO: implement functions to continue print("Continue driving!") else: # TODO: implement functions to drive back print("Driving back!")
You can see that the above code has a comment explaining why there is a check of the voltage and there are comments explaining what needs to be done in the near future. When these features are implemented, these TODO comments should be removed.
Before writing a comment, think whether it adds value to your code or not.
When testing your code or testing a new feature, you will likely comment out some code to see how the program reacts to these specific lines. This is totally fine while developing, but when there are still lines of code that are commented out after you finished your work, this will also lead to confusion. People will most likely not use this code as it is not needed, why else would it have been commented out earlier? At the same time, people will not remove this code, because why else was this code not deleted earlier? This code might be needed later? Maybe not? Other people don’t know. And you also will nit remember after a longer break.
Some people recommend to remove the code as soon as you don’t need it anymore, to avoid this confusion. By using a version control system like git, the code that has been removed is never really lost anyways. Other developers don’t like the idea of radically removing code, they might need it later. So if you want to keep the code ready to re-integrate again, add a comment explaining why you put that code into comment. Yes, this will clutter the code even more, but at least there is no confusion about why there is code into comment.
When adding some test code such as additional print statements, you may also add a comment to explain that this code is for debugging only. This makes it easier to clean up your work when it is finished and working.
This might sound obvious, but sometimes people tend to forget to implement some specific code as a function. This might happen, because the code is just a few lines that are repeated and implementing a function seems a little bit too ambitious. Then, you want to change that code and then you need to change it several times inside your code. If this code was implemented in a function, you could easily change it one single time and then it would be fine again.
For example if you want to calculate the distance between two points:
from math import sqrt point1 = (2, 5) # in cm point2 = (3, 7) # in cm distance1 = math.sqrt((poin2 - point1)**2 + (poin2 - point1)**2) point3 = (5 -2) # in cm point4 = (0, 3) # in cm distance = math.sqrt((point4 - point3)**2 + (point4 - point3)**2)
In the above code, the coordinated of the points are indicated in centimeters but you want to have the distance in meters. You now need to add the conversion twice:
from math import sqrt point1 = (2, 5) # in cm point2 = (3, 7) # in cm distance1 = math.sqrt((poin2 - point1)**2 + (poin2 - point1)**2) distance2_m = distance1/100 # add conversion here point3 = (5 -2) # in cm point4 = (0, 3) # in cm distance = math.sqrt((point4 - point3)**2 + (point4 - point3)**2) distance2_m = distance2/100 # add conversion here
If you implemented a function for this, you would simply need to add the unit conversion once and the actual code would be much cleaner:
from math import sqrt def calculate_distance(point1, point2): distance = math.sqrt((poin2 - point1)**2 + (poin2 - point1)**2) distance_meters = distance/100 return distance point1 = (2, 5) # in cm point2 = (3, 7) # in cm distance2_m = calculate_distance(point1, point2) point3 = (5 -2) # in cm point4 = (0, 3) # in cm distance2_m = calculate_distance(point3, point4)
The function does not much more than the actual code, but it is easier to maintain. You can simply add or remove parts of the function and then the changes are consistent for each time you use this function. Functions are a powerful tool, so don’t hesitate to use them. Also, using functions allows you to use a meaningful name that might be more descriptive than the actual formulas used in the code.
Separate different levels of abstraction
What is a level of abstraction? When writing code, you might realize that some code is more abstract than other code. By abstract, I mean that some code is closer to the actual hardware than other pieces of code.
For example, if you have a micro-controller and you change the state of one pin from LOW to HIGH, this would be considered very low level behavior as this is very close to the real hardware and is not abstract at all.
Instead, if you use a function that simply sends a message through the serial port or just use the print() function, this is considered to be very abstract code as you do not need to worry which bits and bytes are changed in order to make your message appear on the screen or on another device. This code would be very abstract and it would probably be implemented through a set of lower level functions that do the work for you.
motor_enable_pin = True # low level code print("Motor enabled!") # high level code
The actual advice is to keep same level of abstraction together. No high level code together with low level code in one function. How to do that? Implement a function that makes an action more abstract. In the above example, there is the changing of the voltage level of a hardware pin next to a print statement. In this case, you may want to re-organize the code as follows:
def main(): enable_motor() # high level code def enable_motor(): enable_motor_pin() # mid level code print("Motor enabled!") # mid level code def enable_motor_pin(): motor_enable_pin = True # low level code
Now, in the above code, there are three functions that are responsible to enable the motor and to write a message on the screen. You may not find this very intuitive as you actually write much more code which is not doing more than the two lines earlier, but in the end, the code is more structured and easier to read.
In the end, you could say that, by writing more, you end up reading less. You only need to look at the abstraction level that you actually are interested in. If you only want to make sure the message you write on the screen is correct, you do not need to worry about which pin has been enabled or which other actions have been taking in order to enable the pin. Of course, the above example is very short. In a real world example, each function will contain more code and then the use of functions of different abstraction levels will make even more sense.
Keep it clean
Avoid the “Quick and Dirty” approach as you probably end up rewriting it several times and then spend more time as if you had done it the “Nice and Clean” way.
By “Quick and Dirty”, I mean to ignore all the advice above just because it is too much work to think of good variable and function names and not putting any value adding comments because “you know what it does”. You might end up with code that will be used more often than you could imagine and you end up wondering why this code is such a mess.
I once was told to write some code the “Quick and Dirty” way because the code should do a small task and then I would not have to bother with that code ever again. I ended up working over 8 month with that same code and I wished I just made it “Nice and Clean” in the first place instead of trying to improve it each time that I re-open the code.
If you are not sue about certain topics, you can go back to Object-Oriented Programming.