How to Write Readable Code
> Avoid configurable functions
I used to work at a place where they had a substantial amount of legacy, they kept two systems up to date, a current one and a new one. In my time working there they still didn't phase out the old system and the new one was already a system full of legacy and they were planning a new system (I wonder if they're running three systems now).
Many of their functions had +10 arguments which were poorly documented so they had to be deciphered from the function bodies who were sometimes thousands of lines. I was fortunate enough to be granted my own project so I didn't spend too much time working with it, but when I did I had no idea what I was doing.
I was very happy when I left, they practically begged me to stay but I just simply couldn't deal with it (and there were plenty of other issues as well). It was very effective at teaching me ways of how you can bungle up every aspect of your IT.
My best notes for code clarity is to be in the frame of mind of the next person reading this that begins with no context. To that end it's best to let code read well from the top and at each level like good prose, with only as many words as the the importance of the parts.
Apply the "Principle of least astonishment" aggressively in all aspects, variable/function/argument/class naming, arguments/mutation, return type variations. Make it so reading the call site of the function lets the reader accurately guess what it does and not need to look into how it does it, or if it does something else too/sometimes.
All of that is useful but doesn't help if the overall structure of decomposition from top to bottom is poorly chosen: reflect and reconsider. "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."
This explained a few things very nicely which I had in my mind for what makes readable code, but had never grasped well enough to be able to describe it so neatly. So thank you to the author and the submitter - I have bookmarked it and will be able to refer to it when I am writing code or reviewing pull requests :)
Specifically, I always have in mind that a function should "only do one thing", so having multiple layers of abstraction in the same function would be picked up by that rule generally, but I like the explanation given with "if a welcome email has not already been sent, send a welcome email". This approach also makes it easier to test parts of code in isolation.
I've run into programmers that always want to make code faster and more "efficient". What is wrong with that thinking is that computing resources are cheap but programmers are not cheap. Most of the time the more efficient option is readable and maintainable code.
God do I hate it when a function is broken down into sub functions that are only called once for the sake of readability. And then these sub functions themselves are broken down too until you have Bob Martins magical 5 to 10 line functions.
On the surface that looks like clarity, but when do you have to look at that code? Often when you need to fix an issue or extend a functionality. You need to understand what's going on in detail.
When you debug a workflow or Algorithm in a code base like this you have to jump from one function to the next and suddenly you are 6 layers deep and totally lost the context. It is so much harder to grasp code structured like this compared to simple linear flows where each step is marked by inline comments.
> Develop a sense for clarity
Ah, I thought this would define "readable code", but it's "how to write" it. The title, literally.
missing step: understand WTF you are doing.
Many small functions also create complexity, though hadn't considered stacktrace documentation:
> [small functions] It’s easier to tell what the program was “thinking” when you look at a stack trace or run a debugger.
[don't mix levels of abstraction] also explodes the number of functions. I've seen this reasoning before, it makes sense, but I'm not convinced yet. I think if there's some substantial, genuine work done at each level, it's helpful. But just a sequence of calls doesn't help.
Nice bit on incidental duplication:
> The point of DRY isn’t to run a manual compression process on the codebase, it’s to avoid a dependency where two parts of the code need to be manually kept in sync.
In writing, clarity and simplicity go together. Of course, it takes longer to write a short letter. And, code is not writing.
The practical/actionable advice from the article are these 4: (1) don’t prematurely optimize (2) avoid configurable functions (3) don’t break out functions (4) don’t mix levels. Mainly because they’re via negativa (Don’ts) and allow no room for misunderstanding or misapplication.
As I’ve grown older I’ve come to hate functions of a certain length, and especially how conditions are used. I think the authors gets at this with the “avoid configurable functions” but if I were allowed to give it a new heading I’d choose “avoid conditions.” This is in keeping with the need for the law to be via negativa. But conditions are powerful language features, you say, and I totally agree. I think they should be used for the dual purpose of (1) dispatching commands (switch/case) and (2) eliminating/refining inputs. That is, you should always return in an `if` branch.
> Reading well-regarded code can give you a sense of what good can look like.
What are some good looking code one could read, regardless of the language?
Just a nit (that I think is actually pretty important) DRY is a repetition (with less rigor) of the formal concept of refactoring. ("Programming Pearls" by Jon L. Bentley is a worthy tome for more info on refactoring among other things.)