About 5 years ago, I wrote a post titled “10% Development, 90% Maintenance“. That was my view on software development and still is today. Was frustrated by some code review recently and was thinking of how to communicate my penchant for maintainable code simply to both developers and non-developers. Came up with 3Cs for coding – Consistency, Context, Continuity.
1ST C – CONSISTENCY. This focuses mainly on coding style, standards and output. If your code is consistent in the way it is written, its directory structure, the naming of the variables/functions, its behaviour/output and etc., it makes life easier for others and wastes less time for everyone (including yourself). Examples of official coding standards for programming languages are PSR-12 for PHP and PEP 8 for Python.
Consistency reduces cognitive friction. Imagine trying to read thru a newspaper article with a lot of spelling/grammatical mistakes, missing spaces and a mix of British/American/Australian English. You will probably be too hung up by the errors to focus on the article itself.
// sample MESSY Code function test(id){ if ( id == 1) console.log('One'); if (2 === id ) { console.log("TWO"); } }
Consistency reduces time-consuming guesswork. If you are consistently naming your variables using camelCase convention and I need to search for variables related to a person’s ID, I would just need to search for “personId”, and not variations like “person_id”, “PERSON_ID”, “personID”, “Person-ID”, or even waste time trying to come up with a Regular Expression pattern like /person[-_]?id/i
. And this is just for searching – replacing would be another beast. For the sample API response below in JSON format, how shall a new key related to a person’s ID be named?
{ "snake_case": 1, "camelCase": 2, "PascalCase": 3, "kebab-case-sounds-tasty": 4, "CAPS": 5, "sUpErCaLiFrAgIlIsTiCeXpIaLiDoCiOuS": 6 }
Consistency reduces unwanted surprises. Imagine manually typing/copying/pasting/running 10+ commands in an exact sequence each time a Docker image needs to be built/pushed, each time an application needs to be deployed, each time you need to SSH into a bastion host by supplying a MFA code to an AWS SSM command so as to connect to an AWS RDS database, etc. The possibility of mistakes and different errors/results would be very high. Automation (e.g. use of GitHub Actions for CI/CD) and provision of user-friendly scripts (e.g. shell scripts, Composer/NPM/Poetry scripts) would be useful in ensuring consistent results each time.
2ND C – CONTEXT. Context gives meaning to code and helps others understand the rationale behind. Woe to the newly hired developer who deletes a line of code cos it seems unnecessary but it turns out that the deleted line affects some obscure logic spread across 10 other files in 5 different folders ๐
Context can be preserved via docblocks. These are specially formatted comments that use annotations to document specific segments of code, typically variables, functions and classes. Examples of docblock standards would be JSDoc for JavaScript, phpDocumentor for PHP and apiDoc for documenting REST APIs.
function z(q, r) { // Imagine reading 1000 lines to try to understand what the method does, // what it takes in for input and what output it returns } /** * Compute height based on aspect ratio * * @param {float} width - Width in pixels. * @param {float} aspectRatio - If 16:9, this value will be 0.5625 (9 / 16). * @returns {float} Corresponding height. */ function computeHeight(width, aspectRatio) { // Imagine it's still 1000 lines but you can skip reading cos of the // docblock above :) }
Context can be preserved by comments. Unlike docblocks that only precede variables/functions/classes, comments can be sprinkled anywhere. Comments can be used to explain the logic behind a for
loop, mention that a piece of code is linked to a GitHub issue, add a todo task, document a bug, etc.
document.querySelector('video').addEventListener( 'webkitfullscreenchange', function (event) { // GitHub issue #123: webkitendfullscreen doesn't work in macOS Safari // Must add listener on video element itself, not the document console.log(event); } );
Context can be preserved by documentation. By documentation, I mean plain text files written in Markdown format that are committed with the source code in the same repository. Word documents, PDFs, GitHub wikis and Google Drive documents do not count cos these may need installation of free/paid software/accounts to open, the links/access may be lost when the person who created the documents leaves the company, and are stored separately from the code (what happens to the wikis if the company moves from Bitbucket to GitHub?). In contrast, the Markdown files follow the source code wherever it goes. A simple example would be the standard README.md
and CHANGELOG.md
in open-source projects.
# Sample README for a project This is stored as `README.md` in the root of the source code repository. Paths mentioned here are relative to the root of the repository. ## Installation - Clone this repo. - Run `npm install`. ## Deployment - The code is deployed using GitHub Actions. See `.github/workflows` folder. ## Workflow - This section explains the customer journey and the architecture.
3RD C – CONTINUITY. Continuity aims to make handovers smoother, easier and complete. This largely involves the imparting of institutional knowledge. Code that has a poorly designed architecture or an overly complicated/convulated workflow, will make the handover difficult, that is if there is even an handover. When was the last time you saw an iPad shipped with an instruction manual? Remember the KISS principle – “Keep It Simple, Stupid”.
Continuity involves increasing the bus factor. Bus factor refers to the minimum no. of developers working on the project that will get knocked down by a bus before the project comes to a complete halt. If the project has a bus factor of 1, all the domain knowledge is stored in a single developer’s brain – if he leaves, no one will be able to continue the project. This extends to troubleshooting as well, with a special mention on the use of frameworks where things automagically work due to Convention over Configuration, making it hard to trace problems when they arise, especially for developers who just use them without understanding how they work (which is why I favoured Zend Framework a lot, now Laminas Project, as its approach is Configuration over Convention, similar to the “Explicit is better than implicit” aphorism in PEP 20 โ The Zen of Python).
Continuity involves understanding of human resources (HR). Unlike our grandparents’ era, employees nowadays seldom work for a company for life, i.e. decades, and that is if the company even lasts that long. A developer may not be assigned to a project for life either. Simply put, not many developers will have the luxury of walking down a corridor to ask advice from an elderly developer on a piece of code that he wrote 34 years ago, or inheriting mum’s COBOL codebase ๐
Continuity involves professionalism. This cuts across all trades, from the road sweeper who diligently covers all his assigned areas rain or shine, to the hawker who cooks every plate of food to exact standards, to the front desk manager who continues to serve the customer with a smile even after getting a tight slap from the customer for no reason. You are paid by your company to do your job. Even if you have your own company, you are being paid by your clients. It behoves you therefore to do your level best despite your emotions and ensure that your code can be easily maintained even after you have left the company.
That covers all the 3Cs – Consistency, Context, Continuity. And frankly speaking, a common “D” is needed to accomplish them – Discipline. But, that’s another topic for another day, ad huc ๐
[UPDATE 28 FEB 2022]
Additional Readings:
- Discipline Makes Strong Developers: “Discipline. Discipline! I repeat it because the mere presence of a great source control system doesn’t obligate anyone to use it in a structured, rational way. No. That takes discipline.”
- Optimize for Simplicity First: “If it’s slow but readable, I can make it fast. If it’s broken but readable, I can make it work. If it’s impossible to understand, then I have to spend hours trying to understand what the abomination is supposed to do in the first place.”
- Don’t Be Clever: Instead of clever code like
return i > 0 ? i << 2 : ~(i << 2) + 1;
, write clear code likereturn Math.abs(i * 4);
. - Martin Fowler: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Brian Kernighan: "Everyone knows that debugging is twice as hard as writing a program in the first place."
- Why Good Programmers Are Lazy and Dumb