Ranjgith
The answer is simple: craftsmanship.
There are two parts of learning this craftsmanship: knowledge and work - knowledge about principles, patterns, practices and heuristics that a craftsman needs and practicing the same again and again to get better at it.
In many cases, bad code even brings the company down – let me explain, if you are a programmer for say more than a year, you have probably been slow down by someone else's messy code. Over the period of time, the messy code slows us down even further as every change or feature addition we make requires us to understand some twists, tangles and knots which breaks the code one way or other.
As the mess builds up, productivity of the team goes down – how does this actually happen? Why does a piece of code turn bad so easily? In most of the retrospect, the main reason is the schedule for the release - but who to blame? It's you! because the managers look for us for the commitments and promises they make. Moreover, we should keep them educated about the risks and messes the timeline which they propose could make.
"How do I write Clean Code?" – Good question! but we must need to know what clean code actually is to write a clean code. The definition varies from person to person and it is tough to define what a clean code actually is. Let me try to put it into like this – It should be - elegant, efficient, readable, simple, direct and crisp. Yes, I know it is really much more than that!
There are different rules and principles which we can follow to deliver a better code.
Names are everywhere in software development – variables, functions, arguments, classes, packages and even the software has a name. There are simple rules we can follow to have a better name.
Use intention-revealing names. There is a good saying in programming – if a name requires a comment, then the name does not reveal the intent. A name should always answer - why it exists, what it does and how it is used.
Avoid Disinformation. We as programmers should avoid false clues that obscure the meaning of code. It takes a long time for us to spot the difference between RealTimeConversionEngineForHandlingOfStrings and RealTimeConversionEngineForStorageOfStrings? Using inconsistent spellings and words is dis-information. Also, we should avoid naming variables as l and e – main reasons being l looks similar to 1 in most of the fonts and variable e is most common letter in English makes it harder during search.
Make Meaningful Distinctions. Noise words are meaningless distinction and redundant. If you have a class named Product, and other classes named ProductInfo and ProductData - although Info and Data are two different words, they mean the same here - same as a, an and the.
Use Pronounceable Names. There are some methods of shrinking the variable names like genymdhms (generate date, year, month, day, hour, minute and second). This helps no one - the function name can be as simple as generateTimestamp.
Classes can be named as a noun or noun phrase names - Customer, Account and avoid using Data, Info, Manager, Processor as they are verbs
Method or function name should have verb or verb phrase names like paymentGenerator, saveFile or deleteAccount. Usually prefixes like set, get and is are allowed for accessors.
Don't be sarcastic or cute. Don't use puns – Naming a function as handGranade() does not help anyone - deleteItems() would be a proper name. Say what you mean and mean what you say.
Pick one word per concept - words like get, fetch and retrieve convey same meaning, if you are sticking to a word or following a certain standard, try to stick to that for similar functions– say getNames(), getLicense() instead of getNames() and fetchLicense()
Use solution domain names. If you are using a well-established solution or algorithm, that already has a name, please use the name. For instance, use mergeSort() instead of sort()
Yeah! Choosing good names is the hardest thing as it requires good descriptive skills and a shared culture at least within the team. Follow some of these rules that improve readability of your code to maximum extent.
First rule for a function is it has to be small! The second rule is that the functions should be smaller than that. We all might have written functions from 10 lines to 1000 lines.
Blocks and indenting – This imply that the blocks withing the if, else, for and while statements should be one line lone - most probably a function call. Not only it keeps the enclosing function small, it acts as a documentary value if the function name is functionally descriptive.
Does one thing – Functions should always do one thing – what the function name states and should do that well. This also leads to more-abstraction of the code which helps code reuse easy and eliminates code duplication.
The Stepdown Rule – We always want our code to read like a top-down narrative.
Switch statements – they usually do N things - which almost breaks single responsibility of the function that beholds. But we can bury the switch statements in low- level classes and ensure that it is never repeated - or use abstract factory at specific times.
Use descriptive, consistent names and conservative with arguments. Arguments are hard. They must be minimized as possible- they are even harder in testing point of view.
Should not create side effects - let's say that we write a function - checkPassword() of the current requirement which initializes a database and validates a password, if in future if someone working on the code uses this method -it might cause some undesirable side effects. Moreover, the previous implementation violates the single use for a function.
A well-placed comment is very much helpful in all the cases. But the ideal code will not need any comments! The proper use of comments is to compensate for our failure to express ourself in code. Always when commenting, think yourself whether you could express the same in code.
Some comments like Legal comments (Copyrights, Creator of the code), informative comments like explanation of intent behind the design decisions, TODO comments, warnings about consequences of changes are really necessary to the code.
But, most of the comments fall into this category called bad comments – Why? Sometimes we tend to add redundant comments, misleading ones, commented out code, too much information and leave the auto-generated comments.
Now a days, we have auto-formatters available for almost all the programming languages - so it is preferred to use code formatters to keep the code following some standard formatting.
Procedural code (code with data structures) makes it easy to add new functions without changing the existing data types and Object-Oriented code on the other hand makes it easy to add new classes without changing existing functions. The compliment is also true. Procedural code makes it hard to add new data structures as all functions must change. OO code makes it hard to add new functions because all classes must change.
Train Wrecks - This is what happens when the law of Demeter is not followed. A code is called as a train wreck when it looks like a bunch of coupled train cars.
The Law of Demeter - module must not know about the innards of the objects it manipulates. A Simplified version of the law is as follows- a method f of a class C should only call the methods of these:
Hybrids - We sometimes tend to use hybrids - half data structure and half object - which makes harder to add functions and add new data structures. We should avoid creating them.
In a given system, we will sometimes want flexibility to add new data types and we prefer using objects, and other times, we want flexibility to add new behaviours, so in that part we prefer data types and procedures. Good programmers understand these issues without prejudice and choose the best option for the job at hand.
Use Exceptions rather than return codes - In return codes, the caller must check for errors immediately after the call- which unfortunately is easy to forget. For this reason, it is better to throw an exception when we encounter an error.
Write Try-Catch Statements - In a way, try blocks are like transactions, your catch has to leave the program in a consistent state, no matter what happens in try.
Provide context with exceptions - create informative error messages and pass them along with the exceptions
Avoid uncaught exceptions, don't return or pass null in terms of error. Clean code is readable but also should be robust. One of the ways of clean- robust code is to have a good error handler
We, at some point in our programming journey would have heard about the term - test driven development. The three laws of TDD are,
Keep the tests clean - dirty tests are equal to no tests. Test code also requires thought, design and care like production code. Readability is what that makes tests clean.
Clean tests follow five rules that forms the acronym F.I.R.S.T - Fast, Independent, Repeatable, Self-Validating and Timely.
I hope this article will act as a good reference material for your steps towards writing better code. All the discussed practices are taken and referred from the book, Clean Code by Robert C. Martin.
This is just the tip of the iceberg. There are lot of topics yet to be discussed like Classes, Systems, Successive Refinement, Refactoring - which I will cover in another blogpost.
Enthusiastic Engineer! 😎