As developers, we should all strive to write code that’s clean and maintainable. Here are a couple of tips that have helped me progress as a developer.
Descriptive Names
It’s all fun and games to have your variable name be x, but come back to your same code 2 months later, and you’ll spend an embarrassingly long amount of time trying to figure out what that variable stands for.
Here’s an example:
def x(a): y = { 'name': 'Mark', 'age': 45, 'gender': 'male' } z = { 'name': 'Susan', 'age': 23, 'gender': 'female' } a.append(y) a.append(z) b = [] x(b)
At a quick glance, do you know what this code is doing? And how about with this code with better variable names?
def add_people_to_list(current_list_of_people): mark = { 'name': 'Mark', 'age': 45, 'gender': 'male' } susan = { 'name': 'Susan', 'age': 23, 'gender': 'female' } current_list_of_people.append(mark) current_list_of_people.append(susan) current_list_of_people = [] add_people_to_list(current_list_of_people)
It’s a lot more readable, isn’t it? So, make sure to aim for nice, descriptive names for your functions and variables. Your future self will thank you.
Documentation
Pretty self-explanatory. Aim to have documentation in your projects throughout. For instance, let’s use the same function from above.
def add_people_to_list(current_list_of_people): """ This function will add Mark's and Susan's dictionary information to the input list provided. """ mark = { 'name': 'Mark', 'age': 45, 'gender': 'male' } susan = { 'name': 'Susan', 'age': 23, 'gender': 'female' } current_list_of_people.append(mark) current_list_of_people.append(susan) current_list_of_people = [] add_people_to_list(current_list_of_people)
Now, I don’t have to analyze the entire code to get a general sense of what’s happening. I can just look at the documentation and keep in mind that Mark and Susan’s dictionary information are being added to the list I provide to the function.
Single Purpose
Aim for your functions to perform one and only thing really well. Here’s an example:
def get_names(): """ This function will retrieve all the names from the database. """ connection = connect_to_database() if connection.failed: connection = create_database_and_table() connection.populate_table() names = connection.get_names() return names
This function’s description mentions that it’ll retrieve all the names from the database, but it’s actually doing much more than that. It attempts to connect to a database, and if the connection fails, it creates a table, populates it, then finally queries the populated table and returns the names.
Now, if I visited this code a few weeks later, purged the database, and ran the function, I’d probably scratch my head for a while wondering why names are still being returned.
So, in essence, keep your functions simple and aimed at accomplishing one and only one thing.
Repetition
Whenever you’re writing code, and you seem to be writing the same lines of code with minor modifications, it might be time to make it a function. For instance, take a look at this code:
change_working_directory_to_root_path() create_folder('My Folder') change_directory('My Folder') log_message('Successfully created the folder "My Folder"') change_working_directory_to_root_path() create_folder('My New Folder') change_directory('My New Folder') log_message('Successfully created the folder "My New Folder"') change_working_directory_to_root_path() create_folder('My New New Folder') change_directory('My New New Folder') log_message('Successfully created the folder "My New New Folder"')
Doesn’t it look hideous? How about if we made it a function like so?
def create_folder_in_root_path(folder_name): """ Creates folder specified in root path and logs a success message. """ change_working_directory_to_root_path() create_folder(folder_name) change_directory(folder_name) log_message(f'Successfully created the folder {folder_name}') create_folder_in_root_path('My Folder') create_folder_in_root_path('My New Folder') create_folder_in_root_path('My New New Folder')
Isn’t this much cleaner? Also, what if you wanted to stop logging the success message? Wouldn’t you have to modify it in every single line? With a function, one change, and you’re good. So, if you find yourself writing very similar code, consider making it a function.
Redundant Commenting
Commenting code and documenting what it is doing is good, but there’s a limit to everything. For instance, take a look at this function:
def calculate_tax_to_pay(person): """ This function will calculate the amount of tax a person has to pay. """ tax_bracket = get_tax_bracket_of_person(person) # This will return the tax bracket of the person. tax_rate = tax_bracket * 5 # This will multiply the tax bracket by 5. salary = get_salary(person) # This will get the person's salary. tax_to_pay = salary * tax_rate # This will multiply the salary by the tax rate. return tax_to_pay # This is the tax the person has to pay.
The comments on the right of each line of code are redundant. The code with its descriptive variable names and functions are enough to understand what’s happening. Adding the comments on the side just make the code more congested and more difficult to read.
So, going back to the previous point above, strive to have code that’s descriptive with meaningful variable names and functions. Code that’s well written seldom requires excessive commenting.
Magic Numbers and Strings
Don’t be fooled by the name. They’re not really magical; they’re actually totally unmagical as they can be a nightmare. Here’s a small example:
def calculate_sales_tax(price): """ This function will calculate the sales tax based on the price provided. """ return price * 6.25 / 100
If you’re outside Massachusetts, you’ll have no idea why the price is being multiplied by 6.25 and divided by 100. It’ll be much better for everyone if the function instead was written like so:
def calculate_sales_tax(price): """ This function will calculate the sales tax based on the price provided. """ massachusetts_sales_tax_percentage_in_decimal = 0.0625 return price * massachusetts_sales_tax_percentage_in_decimal
Now all numbers have meaning behind them, so debugging will be much easier. If instead the code was left as is before, and the code is sent off to a consultancy overseas, the consultancy may have a tough time understanding what 6.25 is. Well, to be fair, in this simple example, chances are that they won’t, but you get the point.
Here’s another example:
color_of_car = (255, 0, 0)
If you don’t have experience with RGB tuples, you won’t know what color that is immediately. How about now?
red = (255, 0, 0) color_of_car = red
Much easier to understand, right?
Global Variables
Global variables are sometimes okay, but they can also be bad. Most of the time though, they’re not that good. Consider this chunk of code:
name = "Mr. Robot" def get_name(): name = "Marcus" global name find_information_of_person(name)
This is a small example, so you can get a sense of what the variable name is referring to inside the function.
However, imagine you’re working with huge functions with multiple of them being called sequentially. Say you find a bug, and you’re trying to figure out what caused it. Debugging this will be a nightmare because your function and global scope both use the variable ‘name’.
So, aim to have as few global variables as possible.
Practice
Writing bad code is a prerequisite for writing good code. So, it’s okay to write bad code. But it’s not okay to always write bad code. So, write more code and try to think of ways of improving it. Over time, you’ll naturally start applying the tips mentioned above.
Nothing beats practice and having experience itself teach you best practices and what works for you.
Conclusion
And that should about sum it up for this post. Hope the tips help you out in writing cleaner code and becoming a better software developer.
As always, feel free to contact me if you have any questions.