6 ms·
How to Write Readable Code
- xupybd 6y agoI'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.
- emsy 6y agoInefficient software is deployed to millions of users and can cost a lot more time (and resources/energy). Also, I’m fed up by bad software eating up all the performance improvements of modern hardware. It feels like we never got faster CPUs, memory and SSDs. The demand for programming increased but the supply of capable engineers couldn’t keep up and everyone is trying to find excuses for the simple truth: most programmers are incompetent.
- kspacewalk2 6y ago>Inefficient software is deployed to millions of users and can cost a lot more time (and resources/energy). Whose time/resources/energy? Not the software vendor's. >most programmers are incompetent. You can't afford the software written exclusively by very competent programmers, optimizing for efficiency/speed/energy usage instead of development time. That'll usually cost you far more than more compute resources/time in the end.
- hedora 6y agoWith *aas, the vendor ends up paying for the electricity. Also, paying an army of incompetents to modify a codebase isn’t a sustainable development process. Eventually the code spins out of control. Worse, one of the incompetents eventually accidentally gets promoted, then rapidly hires and promotes more bozos. Ultimately, they eat the organization from the inside like a cancer. (This is well-documented, and has happened to many organizations. Search for “bozo effect” or “bozo explosion” for more information.)
- bluefirebrand 6y ago> paying an army of incompetents to modify a codebase isn’t a sustainable development process. Unfortunately it doesn't have to be sustained long term. Only until you get bought or whatever, the people at the top make their money, the army of incompetents cash in somewhat too if they are lucky, and some poor sucker is stuck with an unmaintainable mess that they just paid a premium for.
- rosmax_1337 6y agoMost situations operate on more or less cost vs efficiency basis, no matter if you are a beginner programmer making your first game in Unity or a senior dev at Google. Your code needs to be fast enough to be fast, but not fast enough that you have no time to develop anything else on your project. It seems therefore natural, that as hardware becomes stronger, developers feel less pressure to optimize. Naturally also, it is the developers fault when something is so poorly optimized that it affects the product. Because in the end, we are workers like all other kinds of workers, and we're looking to get a job done, and not done "perfectly". (unless you're hired to optimize servers at Google to the point of perfection) Also, a term I like to mention is that "instant = instant * 0.5". Meaning, if your code is fast enough that it doesn't affect the user experience in the end, and feels "fast". Making it twice as fast has literally no impact. And I doubt server computing cycles is causing energy shortages around the world. We've got other problems causing that.
- jcelerier 6y ago> Also, a term I like to mention is that "instant = instant * 0.5". Meaning, if your code is fast enough that it doesn't affect the user experience in the end, and feels "fast". Making it twice as fast has literally no impact. that makes the very flawed assumption that your program is the only one running on the computer. In practice even if your program does not appear slower, it uses more battery, other software have less CPU time, etc etc
- username90 6y agoEven more important is that it caps the number of features you can add. There are many expensive operations people like to have in text editors, using code that is 10x faster would help with many of those.
- Igelau 6y agoIt's kind of outrageous the amount of software that seems to be completely inoperable unless you have a SSD.
- xupybd 6y agoYes if you are deploying to millions you need to spend time making your code fast. The vast majority of people are making code for much smaller audiences.
- beny23 6y agoIt is very easy to write code that “works on my machine” or “works in QA” and then fails when it hits production volumes or a requirement changes to add more users/elements. Then it often becomes prohibitively expensive to just throw more servers/VMs at it. And often the “expensive” engineer changing a nested loop into a map lookup can save years of compute time in a matter of hours. Unfortunately all too often that only happens after scaling up/out without understanding why systems are slow. Don’t get me wrong, I’m not arguing for needless early optimisation but an inefficient query/algorithm can waste a lot of computing resources...
- hedora 6y agoThis is a false dichotomy. It’s easier to make code faster if it is readable and maintainable. The slowest code I’ve encountered was also unreadable and unmaintainable (otherwise someone would have already fixed the obvious performance issues).
- uglygoblin 6y agoI've had the exact same experience.
- dan-robertson 6y agoIndeed, the ways to write efficient code are driven by testing and measuring rather than trying to write code that is efficient (probably with silly micro-optimisations) from the start. The importance of readability to efficiency is that it would be good for your code to be obviously linear (or n log n) from reading it. The most significant efficiency improvements typically come from better algorithms or data structures in specific cases. Sometimes improvements to heuristics too. Lesser improvements may come from optimising hot code (eg making it better for the cache) or fundamental language changes (e.g. in a more statically typed language, field accesses may be pointer dereferences, or even better pointer addition, rather than hashtable lookups. But languages like python probably have better tuned general-purpose hashtable implementations than you’ll likely find elsewhere. Also changing default data structures from e.g. linked lists and binary trees to arrays (or something array like) and b-trees (or some other shallow tree) will likely be good)
- fpig 6y agoIn my experience, it's the opposite. Inefficiency can be orders of magnitude more expensive than the cost of the added effort to avoid that inefficiency.
- FriedrichN 6y ago> 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.
- jspash 6y agoJust curious..and I don't want to turn this into a language war, but which language was it?
- ragnese 6y agoAnd, probably more importantly, what kind of application was it and was it using a popular major framework (e.g., a web backend in Spring)?
- FriedrichN 6y agoIt was a totally custom ERP application in PHP, no frameworks. The style was very idiosyncratic to the original creator. And to top it off we did not have a versioning system or test environment, we did everything live, right there and then.
- ragnese 6y agoYikes. You weren't kidding about doing everything wrong. Just out of more curiosity, can you comment on the idiosyncratic code style? I saw in your other post that it was written in PHP 4. So that means it did have access to classes and whatnot, right?
- indentit 6y agoThis 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.
- anaerobicover 6y agoIf you're interested in more like this, check out the book "Code Complete" by Steve McConnell. It's an absolute treasure trove of detailed examinations of code quality. Like you said, it really helped me put words to my intuitions so I could discuss them with teammates.
- carapace 6y agoJust 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.)
- hyperpallium2 6y ago> 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.
- hyperpallium2 6y ago> don't mix levels of abstraction I'm uncomfortable about this because abstractions are difficult to define and demarcate - and end up being leaky anyway. So you need to change several separate functions in parallel, instead of having them in the same place. OTOH there are also some obviously different levels of abstraction, which it makes sense not to mix. Further, some such levels can be established in a particular domain, so whether good or not, they are helpful for people already trained in or familiar with that domain.
- karmakaze 6y agoMy 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."
- yawboakye 6y agoThe 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.
- shwestrick 6y ago> you should always return in an 'if' branch Interesting. This code style simply doesn't need 'else' at all. I can surely say, if I was reading a codebase that adhered to this discipline, it would be very easy to read. What are your thoughts on how difficult it is to write code in this style?
- yawboakye 6y agoSee http://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ http://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-val....
- rualca 6y ago> That is, you should always return in an `if` branch. I've had seasoned team members criticizing that approach as possibly the worst mistakes a developer can do. Their rationale is that you needlessly increase the cyclomatic complexity of a module whenever you return early, and if you care about logging you'll already have to add multiple code paths just to emit them.
- hashbig 6y ago> 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?
- splittingTimes 6y agoGod 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.
- rileymat2 6y agoAs a counter example, I was able to customize FitNesse for my needs quickly and easily. I can't remember the exact reason (it was about 8 years ago), but the change was simply adding a new class inherited from a base and using that. I did not have to change legacy code (except at the instantiation site) to do it. This was in Java which is not even my primary language. It was pretty impressive. It breaks down to basically, when you can keep the whole system in your head, that style is needlessly complex, but once it exceeds that level, then it is better.
- rualca 6y agoI disagree. I find that breaking functions into subfunctions extremely helpful to make code easier to read and understand and follow, and consequently to track and fix bugs. Extracting functions does way more than removing descriptive comments. When you extract a function, you're compartmentalizing code and adding scopes where none existed and limit contexts. When you extract a function, you're explicitly constraining a block to comply with a contract, which allows you to not care what goes below that point. You just care about pre and post-conditions, and that is more than enough to troubleshoot and fix bugs, and more importantly not add them.
- UncleMeat 6y agoThis depends. If a subcomponent only requires a small subset of the data used by a function and its input/output can be modeled well by a single simple data instance, then this often helps. This goes wrong when the subcomponents aren't actually logically distinct and you end up passing basically all of the local variables between each one.