> Good code is code that can be understood by a junior engineer. Great code can be understood by a first year CS freshman. The best code is no code at all.
This a thousand times. Having empathy for future devs, maintenance, and bug fixes is so important.
Amen. Something bizarre I have noticed though in junior-almost-senior engineers is that they pride themselves in obfuscating and writing "highly complex" logic, with no documentation. It's almost like they are demonstrating their new abilities in the worst way possible. I have been dealing with one of these engineers recently, and they have expressed to me that they love writing <highly problematic, confusing code> because it's so terse. It's been a point of friction, actually, because I have been trying to get other engineers to help on the software they have been contributing to, but it is nearly indecipherable without the original author's help.
> Something bizarre I have noticed though in junior-almost-senior engineers is that they pride themselves in obfuscating and writing "highly complex" logic
I think it happens because most measures of code quality are quite fuzzy, but brevity (which is valuable, other things being equal) is relatively objective. "Have I made the code shorter?" is a much easier question to answer than "Have I made the code easier to understand, modify and maintain?"
> "Have I made the code shorter?" is a much easier question to answer than "Have I made the code easier to understand, modify and maintain?"
This is something that comes with experience though, and I think a lot of people don't truly grok this until they are trying to maintain their own terse/clever code written months/years earlier. Nothing is quite as humbling as doing `git blame` on some crappy code only to see your own name there.
I think another place people can end up here is if they don't know what the compiler is doing under the hood, it's easy to assume the shortest code will perform the fastest or something like that. "Presumably this cool trick avoids these extra steps" type of things.
Before I had written much assembly, I used to think ifs to avoid assignments was smart. Turns out avoiding branching is better for both testability and performance.
A similar thing I've noticed a trend of recently in frontend react codebases is overuse of memoization. It seems as though people don't realize how it works and that it is often _less_ performant than just doing some low cost computation on each render (like a comparison or basic math).
Amen to that too. That's probably the most complicated part of being a manager or tech lead. You have those amazing junior-almost-senior engineers that could be way more productive and yet deliver better code, purely by "doing less", but the over-engineering gets in the way. You know they could be top-contributors, so you don't want them to leave. But at the same time it's very tiring!
I noticed that they put a lot of their self-worth in the sophistication of their code, so it's difficult to criticise without making them feel bad. You need alternative methods of getting them to "see the light" and write code that's more understandable and maintainable by others.
What alternative methods have you found to get them to "see the light"? I've found myself wishing they'd do therapy, but that doesn't help and can't be expressed.
Mostly public feedback (for only the good things, of course). Put their "good" code on code samples, documentation, code guidelines, tell the team "look everyone please do it like person X did here". It will surprise them in a positive way.
Also on PRs try to point to their own work as sample of how to do things better. This doesn't hurt the ego much, because the role model is themselves.
Also, I feel like most of the time this is an impostor-syndrome/perfectionism issue that also happens with other workers too, so HR can give tips on how to deal with those issues in a more sensible way and tell you what you can or can't say.
That hurt me to read! Have you communicated this to your manager? Might be worthwhile to have a decision "from the top" that is essentially: all logging is good, so long as it doesn't hurt performance or contain PII/PHI.
three things i find to be true of every web app i work on:
1. good logging is the most important part of the app. whatever the app is meant to do is secondary. the app should be a logging app first, and then a backend service to sell widgets second.
2. assume performance requirements for request latency and transactions per second will be at least 3x whatever the product owner tells you at the start of the project and plan accordingly. never trust any suggestion that you can 'ignore performance for now'.
Boundaries between anything. I recently was dealing with an issue in a Jenkins pipeline, where I didn't realise that state was being serialized to string form between job stages until I explicitly logged it out. The thing that was a list in the previous stage was suddenly a string, but then Groovy would happily accept the join method on a string because it's still an iterable. Auuugh.
I wish more managers and business stakeholders investigated this more carefully. Team members of this type add a shadow overhead that impacts velocity dramatically. It’s always visible to average competent devs on the team, but can be invisible to managers who don’t investigate as to why only one person is particularly productive on the team. Most people won’t go to their bosses and say ‘so and so writes overly complicated code that’s making my life a living hell’.
In fact, I think a third-party auditor would be a valuable service for dev teams to utilize at least once a year. Totally neutral party that can come in and say ‘We’re pretty sure the codebase is too complex and we noticed the commits came from so and so’. The business value here is you can root-cause velocity issues that can come from decent people who need to be reigned in (not necessarily fired).
I’m literally prepared to pay to have these people objectively assessed.
I would do this auditing job, no joke. A kind of "code-smell" service, that can yield problematic areas, along with a report of engineers that could use additional guidance/training/reigning-in would be super valuable from a manager's perspective. And because it's a neutral party, they can feel good that there's no politics.
One challenging bit about this service would definitely be quantifying improvements. Since the problem is somewhat hidden by nature, you would almost need testimonials from other engineers on the team.
Another way to remove finger-pointing is to identify features that should be reasonably easy to implement, but for whatever reason don’t get done in time, or worse, don’t get done well (end results being bad).
If a team was tasked to make a simple landing page for example, and it was oddly hard or time consuming for an average team member, it would be good to dig into why. If the answer is ‘you should see the boilerplate involved, or the deploy process ...’, then you can make a neutral analysis as to the cause.
Testimonials aren't a bad thing though. I think every dev should have their code read and evaluated by at least one other person, although getting as many people as possible to read it would be best. If code legibility to help team members understand, debug, and improve upon the code is essential, the best metric to use for code quality would be their collective feedback on said code.
> Totally neutral party that can come in and say ‘We’re pretty sure the codebase is too complex and we noticed the commits came from so and so’.
This sounds like it'd reduce psychological safety on the team, to have someone without the project context come in and criticize your engineers. The morale decline of such a choice could outweigh the benefits.
It’s a suggestion. Generally, code complexity is created by someone that is actually pretty knowledgeable and competent. A straight confrontation won’t easily neutralize such a person in a discussion. They will know how to defend. If they also have peers they are close with, those friends will also negligently condone it with a simple ‘I don’t see anything wrong with that implementation’.
It’s a tough one, so I don’t even know where to begin other than an independent arbiter. Anyhow, I agree with you that it is a delicate matter from an emotional perspective (even though the underlying can be a reasonably objective matter).
The same goes for junior writers who think that complex sentences and words are a sign of superiority, and later discover that the real (and bigger) challenge is writing clearly.
Along the same lines, I recently reviewed a junior engineer's design document and pointed out to them a diagram showing the actors, their roles and interactions would have saved two pages of dense, complex text, and made the solution clearer.
I think there's an element of pridefulness too, in having the ability to manage dense and intricate stuff at all. They're very smart, and it makes them feel good to be able exercise that and juggle and retain so much context at once. And they don't realize how fragile that juggling is, that it's going to take a ton of effort for them or other people to come back to it.
I think this is more prevalent for some languages/stacks than others, too; there's definitely a cultural aspect fostered the language owners or whoever the leaders are.
> Something bizarre I have noticed though in junior-almost-senior engineers is that they pride themselves in obfuscating and writing "highly complex" logic, with no documentation
I've noticed this too. One thing I've had mild success with is the concept that a particular programming document (especially in functional programming) is really a series of mini-documents. Each mini-document has function-level comments, a signature, body, and returns that tell part of the story of what that function does. The minute that the collective of those fail me and I find myself reverse engineering code, then we have failed the team and cost the company money.
Some complicated things must be done, especially at the size and scale of our products, but complex things are painted with a fine veneer of interfaces and documentation.
I think another exercise that can help is putting junior engineers front and center to architecture. Whether it's exposing them to review, the review process of a Senior engineers design, or putting them front and center to design implications. I've seen having to figure out the difference between a controller and a service cause some really positive abstract thinking that puts people on the order of thinking for the group rather than their own merits.
I worked with a guy very much in that vein. He had enough years of experience to call himself senior, but it was clear his actual skill level was halfway between junior and senior at best. He wrote the most clever, fancy, opinionated code I've seen in a while, and he wrote a lot of it. I weep for the programmers that will come along in a year or so that have to figure it out.
Does that matter. A team with a senior/junior separation should have in place a system of peer review where a code not understandable to a peer will not get merged upstream. There would be a CI system with a linter that forbids abusing syntax for writing dense/obscure code. If a code requires documentation there should be in place a doc-coverage tool which forbids new undocumented code from being merged upstream.
If these systems are not in place, and a senior developer can get away with writing overly complex code, then that is the fault of management, not the developer.
It goes too far though. The virtue of simplicity needs to be balanced against the virtue of making proper use of advanced language features.
A first-year student is unlikely to understand C++ template metaprogramming, or just about any Haskell code, but that's not to say they should always be avoided in production code.
> The best code is no code at all
This can be interpreted as advice to avoid the 'inner-platform effect' anti-pattern. Good advice, but personally I'd rather express it in terms of the inner-platform effect.
IME good commenting alleviates a lot of the “problems” with using complex language features. I’m thinking redis style comments (see here[0] for antirez’s philosophy on the issue). If you’re doing something that’s not immediately obvious, explain what you’re doing! That way others can verify it during review, and when someone is reading the code later they can read the comment to understand what’s happening rather than having to parse the code. IMO this applies just as much to simple constructs as to complex ones. Big for loop? Throw a comment at the top telling me what it does so I don’t have to read it when I’m skimming later. Better yet use `map` with a well-named function. Either way, provide a semantically meaningful summary of what’s happening.
> If you’re doing something that’s not immediately obvious, explain what you’re doing!
Agreed. Comments have their place, and some code is unavoidably involved, just by the nature of unavoidable complexity. The solution isn't always to write simple code. If it were, we wouldn't bother studying clever and efficient algorithms.
Also, it's important to ensure comments are updated when code is changed. I don't know who originally said it: Stale and inaccurate comments are no longer comments, they're lies.
That blog post looks worth reading properly, I admit so far I've only skimmed it.
> A first-year student is unlikely to understand C++ template metaprogramming, or just about any Haskell code, but that's not to say they should always be avoided in production code.
They're just the people to read a book on the topic and try to use it everywhere...
This sentiment is exactly why programming in an org-chart is so much different than programming as an individual.
Don't apply corporate best practice designed to withstand turnover to personal programming - you're leaving abstraction and efficiency on the table.
The better code for your own projects is almost definitely inscrutable to a newcomer a lot of the time. It's okay for there to be prerequisites to understanding.
I agree with this in principle but in practice code I write that’s quick & easy and not very readable is usually not understandable by me in a few months either. So if it’s a personal project I hope to last I still want to keep it simple with my code.
I'm not saying to hack away and make a mess necessarily.
Sometimes the simplest solution also requires learning and building upon other concepts. Or sometimes, a simple interface is written around a complicated core.
For example: The OP's quote is used time and time again to argue against FP concepts in industry - a newcomer doesn't know the first principles, so by the OP's folksy razor[1], that code isn't as good as less abstract code that doesn't require learning a new concept or two once and for all.
[1] Folksy razors are the essence of every principle-ish-level engineer's methodology I've run into. Corporations value the ability to remove all human agency & decision-making from software development where possible.
> Corporations value the ability to remove all human agency & decision-making from software development where possible.
Corporations value the ability to continue as an operating entity and make changes to the code after the proponent of Kleisli arrows and lenses has departed for greener pastures.
Doesn't mean I have to respect for be sympathetic to it. It is just organized stupidity at scale.
That said, it's hard not to play the game and buy in. I just write my vanilla Java, say right-sounding things in meetings, and somehow get Paid despite barely doing a thing.
Corporate software development is a great career tbh - instead of paying me to use Kleisli arrows for the company's gain, the company effectively pays me to use Kleisli arrows on my own IP lmao. Gotta love all that frothy waste that's produced by Worse is Better. Waste that the average dev can now reap thanks to the boom in remote work!
I’m not sure, you can become the newcomer yourself when you have to come back to parts of your code base months later. My experience writing simpler code has been pretty successful to respond to customers wanting random new features.
I disagree strongly with it on multiple fronts. That concern should be secondary to your program actually doing its job well. Your customer will literally not care how elegant or ugly your code is; they just see the end result. And when the program fails them, it really doesn't matter to them whether your juniors understand the code or the error. Moreover, not every abstraction is (or can be expected to be) accessible to an entry level engineer. Some technologies just take a long time to master, and doing that can also require higher-level abstractions.
So I would say great code is code that:
1. Does its job well (robustly, performantly, etc. in whatever proportion is applicable)
2. Is maintainable by engineers with reasonable expertise in the tooling
in that order. If you can manage all that and make it accessible to your junior devs, that will of course make your code greater. But don't lose sight of what your customers care about. Your business isn't there to make you feel good about maintaining code, it's to provide customers with value.
It really does matter to the customer if the junior understands it when the code fails though.
Easy to understand code can be more quickly patched and repaired by anyone on the team. If you don't need to call in the senior who built it two years ago to repair it, and you can have someone do it right away, it is better for the customer.
I never said it doesn't matter. I said it matters. What I'm saying is the scenario you're portraying literally cannot play out pretty much by definition (and also empirically, from what I've seen) unless you accept that code readability for juniors is secondary to program quality. When you make readability your primary concern, it comes at the cost of fixing certain bugs and design issues... precisely because the best solutions may not t be trivial or easy to understand by the junior folks 100% of the time. So you never get into your purported state where everything was well designed and implemented in the first place and now you have to worry about getting a junior to fix a bug. Everything ends up clunky from the get-go and you never get a high-quality, robust program at all. Just something of mediocre quality with a ton of patches from devs of all level to get something like 85% working, shipping with know issues you could've avoided if you hadn't artificially restricted yourself and tied your hands behind your back for the sake of the juniors.
It depends on what you're optimizing for. I like to think of it this way. A good "programmer" can take ideas and turn them into working software that is performant enough, meets all of the requirements, etc. This is a mostly static operation. A good "engineer" can take ideas and turn them into working software that can be changed, updated, and maintained for years to decades by multiple programmers.
There are code bases at my current employer that are entirely "ok" and still being worked on from before I was able to spell my name. Projects that have had continued development for >20 years by armies of engineers and the code is still readable and simple to understand.
As with all software engineering, it’s all about trade offs and context.
That performant code that you wrote maybe at the expense of readability? It could very well become bad code when you leave the company and it falls to a junior engineer to modify it to fit some changing business requirement. Or, there’s a bug in the code and the amount of time it takes to fix it is a direct function of how quickly and completely that junior engineer can understand the code.
For me, the hard part is knowing when and how to make that trade off. I’ve definitely erred on both sides often enough.
I strongly disagree. You're packing a bunch of different metrics of quality into a single bullet and somehow suggesting those are separate than the second bullet point. Readability is just as dependent a metric as the others. If you make things that are hard to read, I can guarantee they are not going to be robust, as well as likely not performant.
In my experience, the easiest to maintain code is very often the most efficient and robust as well, because people haven't felt the need to hack around it at every corner.
This assumes you are absolutely certain you know what the code should do. And that it does what you think it does. Hence while you might think “performs its task” is an easily defined I’d disagree. I’d take clear code that wasn’t working over code that was hard to reason about and somehow worked every day.
No because I’d fix the easy to fix code, and make it work correctly. The other code is useful for sure. I mean people have built billion dollar businesses on crap software that barely works. And very rarely do they manage to fix them... I’m just suggesting I have a preference for what I’d rather work on. You might like code that works and is impossible to understand and sits there surrounded by an even more obtuse test suite (if at all), but it’s not something I enjoy is what I was saying. To some degree this is inevitable but I think it’s always worth trying to fight the good fight.
Most likely the code I write has a bug in it. Or, at the time of writing, the customer requirement is fuzzy. Or, I have a limited grasp of the problem domain. Even if it is not any of the above, most likely there will be a change in a business requirement that impacts the code.
So whenever possible, I opt to write code that is either stupidly obvious, trivially testable, or easily replaceable.
How many entry level engineers come onto a project per year? 5? Is onboarding them onto the project is a deliberate and streamlined way such an undue burden that you must change your programming style to avoid it?
But, I do not know if this metric is quite 'complete'. Because, I am very sure, wrapping concepts in mind is more difficult than understanding the code.
I am not saying the code cannot be made better or more clear. But, it also depends on who you are writing to. Somebody who is not familiar with certain style of programming cannot easily read the code of certain level of complexity.
When I was hacking away my first big program, I could not write functions. Or find reading functions easy. The whole thing was a big wall of glorified assembly sewn together by labels. I am not sure why I was like that then, but I found concepts 'functions' and recursion or any other conceptual stuff really hard. My code was, in its own twisted way, 'most simple' and utterly unreadable.
I find the same sort of difficulties while reading some FP snippets. I confess it was a very short affair, but I had some difficulty reading it and even when I understood, I could not just write or think code in the same style.
There are ways to make your code better, your intentions clear but 'can be understood by a first year CS freshman' is bit abstract criterion.
It is kind of like, vocabulary and prose. You can make your prose clear. But, people have to work on the vocabulary on their own.
> The best code is no code at all.
This is completely agreeable.
Edit : Changed some poor word choices. Added an analogy.
Absolutely, you need to care for that future dev who's an idiot when writing code today because that's going to be you in a week or two when you've forgotten all about it.
I agree with having empathy for future devs, but I think it only goes so far. I've often seen junior engineers unable to differentiate between code they don't understand and bad code.
Usually they end up thinking they can do a better job, decide to rewrite the thing from scratch, and take 10x longer to rewrite it than they thought it would take. And accomplish nothing in the end, because the thing they rewrote worked in the first place.
I think of it as writing code for the computer/compiler, rather than for human readers. If the computer "understands" the code, you think you're done.
I real life working on a team with ever changing code, that is the barest rank minimum.
As a young programmer, I thought I was a master when I got the code to work. Now I know that is just the start. Making it readable and changeable is where real mastery lies.
This is very hard to convey to young fools as I used to be.
Thats what I've noticed with TDD advocates - the amount of code required is enormous and distracts from the flow of control. Everything replaced with mocks and stubs and there's objects replaced with instances from global scope, not very good.
I've been fighting this at my company lately. We have a CDF that _clearly_ rewards people who write really advanced Ruby code. We have a lot of working, but not perfectly architected code that people come back through, pull it out into a module, and add a bunch of "included" and meta-programming.
It works. I look at their code and think "that's neat", but you added 0 functionality while making it hard for the lowest half of the engineers to work with. You could have accomplished the same thing with hard-reference to a class.
Someone please translate that to Latin and start plastering that on office walls so people take it more seriously. It’s going to save all of our mental health in the long run.
It's been seven years since I've tried writing any Latin, so you should assume this is butchered. (edit: I think it's less butchered now)
codex bonus a discipulo prendatur
codex magnus a novo prendatur
codex optimus nullus est
Part of the problem is I couldn't find any good word for "code". "Codex" sounds cool but may not be the best fit here.
EDIT: Forgot a word. Also, I think "prendere" is better for "understood" here than "scire", which is more like "to know".
EDIT2: My friend suggested using the subjunctive for "comprehend" so that it's "may be comprehended" instead of "is comprehended". Also I got the tense wrong initially and I think that's fixed now.
EDIT3: "Ablative agents" are a thing. This is a rough language. Thanks, James.
EDIT4: prendar -> prendatur; aka "oops, should have used third person"
That's a good find, but I was unsure of whether "program" is semantically equivalent to "code" here. Plus I'm tempted to leave codex since it sounds so good.
This a thousand times. Having empathy for future devs, maintenance, and bug fixes is so important.