If you're looking to write code for more than recreational purposes, then, code quality is a an aspect of your work that you will have to give more attention. Code quality is defined by a convergence of attributes such as maintainability, reusability, readability, efficiency, error proneness, and modularity.
Let's look at some of these attributes in a bit more detail:
- Modularity -This defines how independent the different chunks of your code are from each other i.e. does making a bad change to one part of your code break everything else? You generally want the answer to that question to be no. This is similar to the concept of coupling as used in OOP (Object Oriented Programming). Code that is modular can have its constituent blocks of functionality swapped in or out without causing the whole house to come down on your head. Over time, you will start to appreciate the power of having modular code when you find yourself having to completely rework a particular feature or functionality of your code.
- Reusability - This attribute measures the degree to which parts of your code can be reused in other (entirely different) projects. The degree to which your code is reusable is largely based on how tightly it's coupled with the rest of your code base. An example of code reuse would be building a user authentication sequence for a fun social media app for your company and then being able to reuse this authentication sequence in a payroll management system without needing the rest of the code base for the social media site. A perfect real life example is that of car tyres... those babies will generally work on any car as long as they are the right size. Notice how their use on any particular car is independent of the design of the rest of the car.
- Maintainability - Like you might have guessed, this attribute measures the ease with which your code can be upgraded/altered over time without introducing new bugs. When using OOP principles, your code's maintainability is largely determined by how tightly coupled your classes are.
Coupling is the degree to which classes or objects depend on each other that is, tight coupling results when a group of classes are highly dependent on one other. This scenario arises when a class assumes too many responsibilities, or when one concern is spread over many classes rather than having its own class.
It's safe to say then, that loose coupling will yield greater code reusability and easier maintainability.
- Readability - Readable code‚??-‚??every programmer claims their code is super readable but, obviously, that's not the case. What is, readable code then? Before we define what it is, maybe the more important question is, why does readability matter anyway? When you start to work on larger projects, you'll find that your codebase grows by hundreds of lines daily which means, that in a few weeks you might have forgotten the logic behind the decisions you made. Similarly, other people viewing your code would find it impossible to understand your code if it is not readable. Hopefully, you can start to see how having unreadable code could make maintenance a nightmare.
Now, we can define readable code as being easy to understand or follow logically. There's a ton of things that you can do to make your code more readable, and you can find many of them here but generally,
- comment your code,
- name variables (both temporary and otherwise) consistently and descriptively,
- avoid extremely long lines of code (> 120 characters)
- form code groups
- DRY (Don't Repeat Yourself): If you ever find yourself copying and pasting code, STOP and think long and hard‚??-‚??you're probably doing something wrong
- YAGNI (You Ain't Gonna Need It): Avoid writing code that doesn't get used as it will cause confusion down the road
- use nesting sparingly and
- use proper spacing and/or indentation (yes, whitespace can be beautiful) and you should be well on your way to creating art!
I want to write quality code...what do I need to do?
Now that you agree that the goal of writing high quality code is worth pursuing, let's explore how we might go about changing our workflow to this effect.
Adopt Test Driven Development (TDD)
This is a programming practice (you might or might not have heard of before) that involves creating test cases that your code should be able to pass if it's written correctly. It can be viewed as a pessimistic approach to programming, however, it forces you to think about all the things that could go wrong with your code so that you can, ahead of time, plug all the loopholes that could make your code buggy.
For example, if you are asked to write code for a calculator that adds two numbers and returns their sum; there are two ways to approach this task:
Examples to follow are in Python
1. Well, the problem seems easy enough, I'll just write a function that takes two arguments and returns their sum.
So, our code from approach 1 works just fine, it returns the sum of 6 and 9 as 15...we don't need test driven development, right? WRONG! Let's consider the test driven approach for a second...
In the test driven approach, we would have to think about all the things that could trip our code up and write tests to ensure that our code caters for said scenarios. For example, in our calculator code, what if the user:
- passed two strings as arguments to our function?
- passed an integer and a string to our function?
We would then write tests for these unlikely yet apocalyptic scenarios, like so:
Let's now have a look at what the code for calculator would look like if written with the tests above in mind:
Instantly, the TDD approach produces code that can withstand varying degrees of abuse from the users and is less likely to break. It's clear now that our code from approach 1 wouldn't robustly handle these unexpected scenarios because we did not have the opportunity to consider their existence in the first place.
In many ways, TDD borrows from Murphy's law:
Anything that can go wrong, will go wrong
And herein lies the immense power of test driven development, it forces you to think about all the many ways your code could break (however unlikely)...which inherently causes you to write more robust code.
This calculator example is a rather simplistic and the tests we wrote do not cover all possible scenarios, but the concept remains the same regardless of how big your project is. Generally speaking, the more tests you write, the better, because it should, in principle, result in greater test coverage.
You should always strive to achieve 100% test coverage which essentially means that when your unit tests are run, all your lines of code are executed! If these unit tests are well written, you'll have a greater ability to catch any and all bugs‚??-‚??giving you the opportunity to fix them all and you're on your way to achieving true bug-free status.
You can read more on getting started with TDD here
as well as my article here
Use Automated Code Review tools
We've spoken at length about the many many things that qualify code as high quality, and perhaps some day, you'll understand deeply, all the aspects of code quality‚??-‚??you might even gain the ability to quickly analyse other programmers' scripts and point out the issues and fixes to said issues. Until that day comes though, I (strongly) recommend that you integrate the use of automated code review tools into your workflow.
I like to think of code review tools as sentinels; they watch over you as you write your code and every so often (pre and/or post commit, depending on the particular tool), they'll let you know how you can improve your code. There are numerous code review tools out there and you can see a list here
; I've personally been experimenting with Codacy
and Code Climate
up until this point. Codacy and code climate are both free for use with open source github repos‚??-‚??private repos are paid for. I've been happy with Codacy but I'm pretty new at this workflow as well, so, as I learn more about the different tools, I might or might not make a change.
Getting started with code review tools
After creating an account with codacy, add a public repository from your Github account. From this point on, your code will be reviewed every time you make a commit to that repository.
After the review, the results are published on a beautiful dashboard highlighting the most important aspects of your code.
Analysing the results
The code review reveals a ton of stuff about our code‚??-‚??the good, bad and ugly alike. You'll come to appreciate that the quest to write better code calls for a willingness to constantly be faced with your work's shortcomings so that you may improve it.
From the dashboards above, the first thing we see is that this particular project has been awarded a B grade‚??-‚??which isn't bad at all as a starting point.
In the bottom right, we can also see a chart of how the project's code quality has evolved over time‚??-‚??this does a pretty good job of keeping you conscious of seeking positive growth.
In addition, information about the code's security, error proneness, compatibility, style and issues is provided. The current issues with your code should be your focus because they highlight your code's vulnerabilities.
The dashboard provides a categorised breakdown of the issues; quickly giving you an idea of which area your code is most vulnerable in. By far my favorite thing about code review tools is that they (almost always) give comprehensive information about individual issues such as, why a particular issue has been flagged as such as well as what you could do to fix said issue.
In my experience, understanding why a particular code block raises an issue makes it unlikely that you'll use it again in the future. An accumulation of this understanding of various issues will undoubtedly have a positive impact on the quality of your code.
Make use of Continuous Integration (CI) tools
Attaining and/or maintaining high code quality is challenging enough as a lone wolf programmer, imagine how much harder it is as a team making changes to a central repository. Often, teams fail to ship software quickly enough because of the difficulties in reviewing the contributions of the team members.
I will be writing another article on improving the quality of your codebase as a team using CI tools/build-servers like Travis CI and Jenkins. Watch out for that.
Continuous Integration ( CI ) is a development practice that requires developers to integrate code into a shared repository several times a day. Each check-in is then verified by an automated build, allowing teams to detect problems early.
Let's wrap this up
Writing quality code is a habit more than it is a skill, and like all habits, you'll have to develop it by taking deliberate action. I appreciate that this can prove difficult, which is why you should rely on TDD and code review tools to keep you honest and encourage you to always consider multiple perspectives and adhere to the best programming practices.