Writing clean, concise code is a lot harder than it sounds. It requires experience and a good understanding of how code hangs together. The larger the system, the bigger the challenge.
Software development is a tricky business. We spend most of our day breaking down complex problems into smaller tasks and then we write machine code. We often have to analyse code that someone else has written. We need to read so much documentation in order to get acquainted with a new API. Every day is an uphill battle against a new framework, language or library. In the end, we write our own code, solve the problem and move on to the next task.
Writing clean, concise code is a lot harder than it sounds. It requires experience and a good understanding of how code hangs together. The larger the system, the bigger the challenge. As a developer, there is nothing worse than having to support and troubleshoot badly written code. Sometimes, it’s us that write that code. I can attest that I have produced some code ‘gems’, and I’m sure I’ve written some awful code that’s still running on a server in a basement somewhere. I’ve worked hard, learnt from my mistakes and I hope that I’ve come a long way since those darker days. I also understand that I have a long road ahead of me, and I strive to learn and improve my coding skills every day.
Over the years I’ve picked up some good practices, tools and standards to help me become a “Clean Coder”. By the way, the principles of clean code have been thoroughly covered by Robert C Martin (also known as Uncle Bob) in his two homonymous and highly recommended books:
In this post I’ll try to touch upon some of the basic Clean Code principles and show you how to write clean, readable and easily maintainable code. Your future self, your colleagues and successors will be certainly grateful for it!
Lots of things have been written on the subject of naming in programming, but there’s nothing that captures this better than this quote by Phil Carlton:
“There are only two hard things in Computer Science: cache invalidation and naming things.”
Working with badly named code can seriously affect your productivity and your ability to fix bugs or add features. All code should be self-describing with meaningful and easy to understand naming. There’s a big difference between:
var x = 3; and
var maximumLoginAttempts = 3;
The larger the scope, the more important to get the name right. Don’t concatenate your variable and function names, you’re not going to run out of bytes! But self-describing code is just about understanding the intent. It’s also about refactoring and improving your code flow. Imagine if you did a standard “Search & Replace” for all references of variable “x” or even worse “a” or “b“. A world of fun awaits as you undo your changes and revert to manually changing all instances of that variable.
Camel-case, Pascal-case, any case. If you chose a method, you’ll have to stick with it. Having consistency across you code makes things predictable and easier to understand. When you work alone, this can be easy to enforce. However, this is a much bigger challenge in the context of a bigger team. Fortunately, there are tools that can help enforce consistency. I use different tools such as StyleCop, JSHint, JSLint, JSCS (all open-source and free) and they work great. Make sure that the whole team arrives at a consensus on which rules work, or you’ll just end up back where you started. StyleCop for example tends to be overly eager (aggressive) in the way that your code should look, and some of the rules don’t make sense. Feel free to run through many trial-and-errors until you’ve refined the config enough to meet your needs. Again, it’s important to discuss this with the rest of the team. Other IDEs will have similar tools but if there’s none that can support your technology stack, just write them down somewhere and try to enforce them during code reviews or pull request (PR) acceptances.
Code comments tend to be a sensitive subject and I’ve seen people defending both sides. I’m with Uncle Bob on this one. Unless your comments are treated as active code (with reviews, PRs etc) then they should be avoided. Code comments add noise to your code, distract you from your task and, in most cases, tend to be unrelated and out-of-date. Describing what the code does is the responsibility of the code itself. As the code evolves, the comments go stale and developers rarely go back to fix inconsistencies. Apology comments of the style: “//TODO: hacky workaround, will fix later – CM”, are all too common and they sound more like an apology than a valid comment. If someone took time to write the comment, they could have taken a bit longer to come up with a proper fix. Finally, there are always exceptions to every rule and for me comments are useful when used to document public APIs. If you’re interested in reading more about code comments, have a read here.
Your code should read like a well-written story. The code should have a natural flow (a bit like Behaviour-Driven Developer) where each method describes an operation that has an entry and exit point. Each method should add to the overall story and written in such a way that it would make sense even to non-programmers. Some people believe that methods that describe a “story” should be clustered together, but this may come in conflict with some tools (Resharper, StyleCop) or design patterns (the scissors rule). You just need to decide what works better for you, but in my experience when working through code to trace an issue, it’s much easier when I have everything closely together rather than having to jump in random places in the code to follow the workflow.
S.O.L.I.D. is your friend
I won’t try to explain or redefine what S.O.L.I.D. is, as you can read about it here. The important thing is that by applying the S.O.L.I.D. principles, you’re also working towards Clean Code nirvana. Building upon the rules and best practices described by S.O.L.I.D., your code and the overall system become easy to maintain and extend over time. The guidelines will help you reduce or eliminate code smells and make code refactoring easier.
Following S.O.L.I.D., the next item on the agenda is size. Class size and method size both matter. Would you prefer a 3000-line class over a 250, and respectively a 150-line method over a 15-line one? Uncle Bob has this to say when it comes to method lengths:
“The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that. Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.”
Some may claim that this promotes fragmentation and can lead to large projects with lots of files, but this is usually an issue for younger, inexperienced developers. With experience, the fear of oversized, overcomplicated projects is replaced by the understanding of how one can leverage the S.O.L.I.D. architecture to create both a testable and maintainable solution.
Writing “clever” code
There’s usually more than one way to solve a problem. The same idea is valid for writing a routine, method or function. Some people enjoy making their lives and the lives of those working with them difficult by writing “clever” code. Yes, it works, and yes, they’ve managed to compress 10 lines of code into 2 by using short variable names and nested Lamda expressions inside an anonymous function. Unfortunately, this will not be as testable as you may think. Whenever I’m presented with a similar dilemma, simple always wins, hands down. I believe that Brian Kernigham’s words capture this better than me:
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” Keep your code simple and easy to read and maintain.
Test-driven Development (TDD) or testing in general
TDD and testing are not directly related to clean code. Even the act of making your code testable will force you to think about the design and patterns to follow. TDD, on its own, is a subject that the development community can be particularly passionate about. However, the overall moral of the story is this: no software can be considered resilient, adaptable and manageable if there are no tests to cover it. It doesn’t really matter if you practice TDD religiously or if you write your tests (unit and integration) after the fact. Testing is an invaluable discipline and will ensure that the code functions in a predictable way and generates the expected output. There have been lots of books, posts and articles written about the importance of testing so I hope that your code has the right test coverage.
Some of the things I’ve written about are common sense and readily available knowledge. Some of you may have learned the hard way, and some may have escaped you until now. It’s never too late to start writing clean code and it should be your goal to improve every single day. If you find yourself writing the same code you did a year ago, or even 6 months ago, then you should definitely consider changing your priorities and look at how you can improve. There are many resources available to help you improve and the community is there to support you, so join me in this quest to rid the world of technical debt and write beautiful, functional code.