By Gaurav Singh
I recently read Clean Code, written by the prolific Robert C Martin, also known as ‘Uncle Bob’. It was one of the most delightful and eye-opening experiences of my life as a programmer. There were lots of amazing takeaways from the book and I highly recommend it to anyone who wants to improve their craft in coding.
Why this post?
Master programmers think of systems as stories to be told rather than programs to be written. — Robert C Martin.
Though it is not possible to summarise the entire book in a blog post, I wanted to list some learnings that can improve the quality of the code that we write.
This post is not an attempt to explain each and every one of them (you can Google, or better yet, pick up the book!). It is intended to help programmers understand an important truth:
“Know! What you Don’t know, you don’t know”
With that food for thought, let’s get started…
Universal principles to follow: 🌏
There are some oft-repeated principles which are so universal in nature that they deserve a special mention.
- Avoid duplication anywhere in code (Famously known as the DRY principle — Don’t Repeat Yourself )
- Follow SOLID principles to write clean classes and well organised APIs
- Follow a design pattern if it fits the problem space that you are trying to solve. This instantly makes your code much more accessible to fellow programmers
- Follow the Law of Demeter to enable less coupling and more cohesion
Law of Demeter says that a method f of a class C should only call the methods of these:
• An object created by f
• An object passed as an argument to f
• An object held in an instance variable of C
- Follow the three laws of Test-Driven Development (TDD), and keep your test code as clean as production code.
First Law: You may not write production code until you have written a failing unit test.
Second Law: You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
Third Law: You may not write more production code than is sufficient to pass the currently failing test.
A Function should do one thing only and do it really well
Certain tips for writing effective functions:
- Avoid passing boolean into a function, this is a hint that func has an
ifstatement within which causes it to do more than one thing.
- Functions should either do something or answer something, but not both. This ensures a function does not have hidden side effects. For example, a func named
isPresent()should only return a bool and not do any other operations.
- Prefer Exceptions to returning Error Codes and extract error handling
try catchinto their own function.
- Avoid output arguments. Function, if it has to, should change state of its owning object
- Code should always be separated with blank lines to club logical blocks together. Think of different lines of code as thoughts and then always think of organising similar thoughts together
- Each function should read like a newspaper; every function’s implementation follows its call and has less vertical density
- Don’t return null
Object and data structures:
- Variables should be private so that we can change their type or implementation when required. There is no need to add getters/setter to each variable to expose them as public.
- Hiding implementation is not just a matter of putting a layer of functions between the variables. Hiding implementation is about abstractions! We do not want to expose details of data but rather express data as abstract terms
- Try and extract try catch blocks into separate well named functions. Having them mixed with other code just confuses the structure of the program. This is in line with the ‘Function should do one thing’ rule.
Error handling is one thing.😅
- Prefer returning Exceptions instead of Error Codes.
- Each exception that you throw should provide enough context to determine the source and location of an error.
- Variables should be declared as close to their usage as possible.
- Keep variables private and only expose necessary interactions as well defined abstractions. Avoid senseless getters and setters which expose all variables unnecessarily.
The only truly good comment is the comment you found a way not to write.
- Don’t use a comment when you can use a well named Function or a Variable
- Any comment which forces you to look into another module for meaning has failed miserably in communicating and is not worth it at all.
“Don’t comment bad code, rewrite it”
- Brian W. Kernighan and P. J. Plaugher
- Always wrap third party code/API to minimise your dependency on it. Allow for the freedom to move to a different one in future without changing the consuming code.
Code organisation and design:
- Nearly all code is read left to right and top to bottom. Each line represents an expression or a clause, and each group of lines represents a complete thought. Those thoughts should be separated from each other with blank lines
- Local variables should be declared as close to their usage as possible.
- Instance variables should be declared at top of the class since in a well defined class they would be used by multiple functions
- If one function calls another, they should be vertically close, and the caller should be above the callee, if at all possible. This gives the program a natural flow.
- Try to follow the The Principle of Least Surprise: any function or class should implement the behaviours that another programmer could reasonably expect.
- It is NOT necessary to do a Big Design Up Front(BDUF). In fact, BDUF is even harmful because it inhibits adapting to change. This can be due to the psychological resistance to discarding prior effort or the way architecture choices influence subsequent thinking about the design.
According to Kent Beck, a design is “simple” if it follows these rules (in order of importance:
• Runs all the tests
• Contains no duplication
• Expresses the intent of the programmer
• Minimises the number of classes and methods
Clean tests should follow F.I.R.S.T principles:
- Fast: ⏩ Tests should be fast. They should run quickly. When tests run slow, you won’t want to run them frequently
- Independent: 🆓 Tests should not depend on each other. One test should not set up the conditions for the next test. You should be able to run each test independently and run the tests in any order you like.
- Repeatable: 🔁 Tests should be repeatable in any environment. They should run in the production environment, the QA environment, and even on your laptop while riding home on the train without a network.
- Self-Validating: ✅ The tests should have a boolean output. Either they pass or fail. You should not have to read through a log file to tell whether the tests pass.
- Timely: ⏲The tests need to be written in a timely fashion.
Apart from this:
- Ensure you write tests just for a single concept. Doing too many things in a test is usually a sign that it needs to be broken down.
That’s all folks!
To be fair, it’s going to take loads of practice and many other books to drill these principles in my subconscious. But the more you read, the more you’ll learn. And yes, practise, practise, practice.
If you found this interesting, please do share. If you have any thoughts, feedback or comments, please reply.
Till next time, happy learning and coding. Cheers! 😁
There are plenty of code-related challenges to solve at Gojek. If you write clean code and want to take a stab at what we have to offer, check out gojek.jobs and grab this chance to make a dent in Southeast Asia.