This advice boils down to "write good code." Unfortunately, practical advice that goes beyond that is really hard.
Software engineering is hard because everything is a trade off! Should I add another layer of abstraction or just add another optional parameter? Should I add flexibility for future requirements or simplify the code so it's obvious to a junior programmer?
The answer to almost every question is, "It depends!"
What are the skills of the dev team? How much time do you have? Do you already have a lot of tech debt? Are you building a prototype or a safety-critical system?
Worse, many answers depend on unknowable facts: How will requirements change in the future? Will we ever need this functionality? Did we bet on the right ecosystem?
The best software engineers can balance all these questions, even the ones they don't have answers to, and come up with a design that works. The fact that we, as an industry, can build multi-million-line programs is a minor miracle. The wonder isn't that so many projects fail but that any succeed.
If anyone were to ask me for advice--which I notice they're not--I would just say two things:
> This advice boils down to "write good code." Unfortunately, practical advice that goes beyond that is really hard.
I don't think that's true. I think a better way to phrase it is "avoid being clever if you don't need to be." Sure, you could write that algorithm as three nested list comprehensions, but does that accomplish anything other than showing off? Do you really need that metaprogramming or would a simple function suffice? The worst code I've ever had to deal with was never written by juniors, it was written by guys that knew fancy template metaprogramming in C++ and would create a nightmare for everyone else to deal with because they wanted to show how smart they were.
> Sure, you could write that algorithm as three nested list comprehensions, but does that accomplish anything other than showing off?
Ask different programmers this and you'll get different answers. One group will say that the list comprehensions reduces ceremony and makes the essential business logic clear, and writing out a loop longhand would serve no purpose other than showing off your knowledge of implementation trivia. Another group would say that writing the code without using list comprehensions makes it simpler, and the other group is just showing off their knowledge of language features.
One of the annoying things about "code simplicity" is that it is a subjective trait. Whether a piece of code is simple to understand depends very heavily on what the reader is used to, and that's not something that the writer of the code has much control over.
That has pretty much the same problem. Assembly programs have low "cognitive load" by that definition, but few would say that assembly programs are simpler.
I wouldn't say so. When you're on assembly programs, you hold things like "register values" in your head as well as other low-level things. It's not only about abstractions and language features.
And unlike registers and memory addresses, you don't really have to juggle regular variables in your head unless the program has a lot of unnecessary mutability, or really bad variable names.
x64 has 16 general-purpose registers. "Much lower" starts somewhere around a half. I say, touching 32 variables in a given scope is not "half complicated", it's sabotage.
Not really, assembly has much more cognitive load. Try reading more than a trivial chunk of assembly and you'll see you can get overwhelmed pretty quickly by the amount of information you have to remember. You have to memorize code paths, which register or address has what, offsets of structures, juggle registers.
My friends call this the complexity hump. Pretty much everyone goes through this phase. The faster you get out of it the better. Some stay there their whole career.
A massive cohort of programmers, some at very high positions that are highly analytically minded, do not understand this. It’s not for a lack of intelligence, but curiosity and empathy (as in the dictionary definition - the theory of mind stuff).
My current best way I try to explain this is: go ahead, assume you’re 10% or even 200% smarter than others - a true 10x engineer. It doesn’t matter, because complexity scales not linearly but exponentially. The difference between big brain and small brain is minuscule in face of the omnipresent beast of complexity. It’s like surviving a terminal desease - an athlete might live longer but you’re still gonna die. And complexity creeps in simply as a byproduct of doing anything, so ignoring it will result in more, just like more code leads to more bugs.
I think a big part of the problem is the inherited collective ego of software being birthed by computer science. If you go look at early software, early Unix, whatever, there is such a tremendous volume of really clever things going on. Little DSLs, weird experiments that bled into POSIX. All the effort devoted to grammars and compilers and making Hard Problems look Easy.
I sometimes get frustrated that my colleagues don't know how to read an awk script, or that they think it's just a fact of life that they have to delete their git repository and perform a fresh clone because that's just the way npm is sometimes. Am I asking for too much? But then I remember that we have built all of this on impossibly tall ivory towers of complexity. It's almost unfathomable.
Almost every project is someone's vice in this vein form of worship we call software development. We tell ourselves that we've moved past this phase, we've seen what complexity really is. But can we actually? Ever? Are we just fooling ourselves?
It always seemed to me that it's ideal to use sophisticated tools to build mundane and interesting solutions. But every solution is just a tool for someone else. So much of what I love about what we do is hopelessly couples to that very complexity I despise. How can we reconcile this?
Out of sight, out of mind, I suppose.
I think the real problem is that we consider this all one big profession when in reality it's as varied as the crafts: electricians, plumbers, metalworkers, laborers, carpenters, mechanics, engineers, architects, painters, roofers, landscapers...
> But can we actually? Ever? Are we just fooling ourselves?
I don’t have any hopes of the deepest theoretical complexity problems will be solved. However, let’s not be too defeatist. Look back 10-20-30 years. The things that survive in the long run (longer than a ~5y hype-cycle) are almost always an improvement compared to the past. Some genies can’t be put back in the bottle - good ideas, models and abstractions fit that bill, in my view. I’m hopeful.
> Out of sight, out of mind, I suppose.
Yes but this isn’t always bad. Things like schedulers, compilers, query planners should be out of mind. Or at least it’s the best way we can compartmentalize complexity today. It’s a last resort, so you shouldn’t pick immature and highly complex deps at the same time. Fortunately, we have mature tech that does a lot of heavy lifting, even if magical.
If you only have grugs, you will never solve the difficult problems, though. There's no binary advice to be given around complexity - whether to avoid it or to embrace it, I think.
Most programming is not really a difficult problem, in fact it is rather boring. That is why 200IQ programmers are constantly making it more complex than necessary. They need a challenge. Plus, it gives them authority.
In a way yes. If we only settled for mature tech ever, we wouldn’t get anywhere. However, even for new ideas and abstractions complexity should always be avoided. All else equal, less complexity is better.
Yup, moving from 40 hour weeks to 168 hour weeks (if the body supports) or a 4x hiring spree (if they integrate into the team) seems like nothing for some complicated crap I've seen.
Then, there are a lot of programmers who are completely unaware of these tradeoffs, and simply follow some trend or methodology without any thought of their own. 20 lines of code split over 4 microservices? Sure, why not? "It's supposed to make development more flexible and improve the developer experience". Cherish coworkers who actually care about their craft, not all shops have them.
I'm wading through this level of craziness right now, and it is slowly dawning on me that it was done on purpose, to stretch out a couple of weeks of programming to years and years. A technically legal form of corruption.
Everyone here seems to be labouring under the assumption that we all want to write good code, we just can't precisely agree on what good looks like.
No, sadly, there are people out there writing bad code on purpose.
A bit late reply here, so I don't know if you'll see it. But, how sure are you that it was done intentionally? Incompetence is pretty is to argue, but intentionally making something bad?
The extremely vehement push-back after any suggestion of using less code[1] in the future. Any talk of efficiency or lean techniques were instantly shut down, with a distinct undertone of nervousness. As in: “He knows! We’ve been found out!”
For reference this is in public service where it’s easy to get away with this kind of thing, even for decades or until retirement.
To clarify: this is the softest form of corruption. There’s no brown paper bags with wads of cash in them exchanging hands. It’s a passive exploit of a disinterested organisation with too much money that’s happy to pay for developers to write verbose code for years on end with no business purpose.
[1] To put some numbers against this: recently I was tasked with migrating a data feed API to the cloud. It has about six Visual Studio projects: multiple tiers of APIs calling each other over the network. Roughly 3K likes of incredibly repetitive code written by hand. I replaced it with 70 lines of code. It was faster to do that than to write the infrastructure templates for such a complex architecture.
In my experience, incompetence has been the underlying cause for a lot that seems like malice or something more deliberate. Especially when combined with social aspects like not wanting to lose face, or having an ego.
I suppose that the transition between incompetence and malice isn't exactly black and white either, or mutually exclusive for that matter.
I've seen complacency too. People who know better, but instead of arguing that point, they go along the path of least resistance. "I get my paycheck either way"-kind of mindset.
I have yet to experience someone deliberately turning 70 lines of code into 3k lines. But, I've certainly seen that exact same thing happen. Almost surprising how similar it sounds. I should check how many lines it is, but if I would guess, it was 1k lines of code that could be done in 10. Which is what you get when you manually write a API client and objects, and you've split out the 10 lines of code into 4 separate micro-services (this isn't an exaggeration). I'm 99% sure that this was a case of not understanding why it was a terrible approach, and it being driven by consultants who are happy to explore some unsuited approach just for the CV-padding of it, if nothing else.
Deliberate feigned incompetence to make a problem worse, or job safety. I've yet to actually experience and confirm this for myself.
What you describe as "He knows! We've been found out!" might be entirely true, but it could also reflect being found out as in the impostor syndrome.. or "... that we have no clue", etc.
What you're saying about everything being a trade off really resonates with me.
I don't think I really "grasped" software engineering until a more experienced friend told me, a couple decades ago, that every abstraction is also has trade offs and hidden costs.
To me it seemed ludicrous, since abstractions were "obviously good", but ruminating on that and observing closely the code I was maintaining taught me that it was true. Everything is indeed a trade off...
oh yeah i remember my first interface factory factory singletons with variable dependency injection aritys built with a flywheel pattern. pretty sure i got fired for that one.
I really don't see how it's just "write good code." In fact the opening line is about removing code being better than writing more.
Maybe it boils down to KISS but IMO it goes deeper than that. The lesson that code is a liability not an asset is not represented in either "be good" or "be simple."
Maybe you're right. Obviously, if this advice helps people, then I should just back off.
It's true that removing code is better than adding it, and that code is a liability. And I admit that I needed to hear that early in my career.
But the article acknowledges that "over-adherence to [that] dogma can be counter productive". Great--so how can I tell if I've gone too far? How do I know if I should write more code or less code? How do I know if my code is "dumb" enough? The article does not help with that.
Saying that "code is a liability" is not actionable advice.
for me I try to find the simplest solution that fulfils the requirement I have for the task on hand.
No fancy abstractions/ design patterns- most of the time you don't need them, and when you do you certainly need them in another way, completely different from the one you chose.
Abstraction based on sample size one and "foresight" is a sure mark of a noob :)
Haskell is incredible for refactoring. And if the team is composed of people with the outlook of the post author, the code will be usually be easy to understand. One ought not conflate the difficulty of understanding code written in an exotic language with which one is unfamiliar, and the difficulty of understanding code which is extremely abstract and unnecessarily clever. The latter sort can be written in any language.
Perl (without dependencies) works awesomely well as a replacement for bash in scripts, in my experience. Unlike Python, chances that it will break the next month (or the next decade) are virtually nil.
Python without dependencies will also work everywhere basically forever. Hell, most Python 2 is valid Python 3, but it's been over a decade now - Python 3 is the default system Python in most everything.
Software engineering is hard because everything is a trade off! Should I add another layer of abstraction or just add another optional parameter? Should I add flexibility for future requirements or simplify the code so it's obvious to a junior programmer?
The answer to almost every question is, "It depends!"
What are the skills of the dev team? How much time do you have? Do you already have a lot of tech debt? Are you building a prototype or a safety-critical system?
Worse, many answers depend on unknowable facts: How will requirements change in the future? Will we ever need this functionality? Did we bet on the right ecosystem?
The best software engineers can balance all these questions, even the ones they don't have answers to, and come up with a design that works. The fact that we, as an industry, can build multi-million-line programs is a minor miracle. The wonder isn't that so many projects fail but that any succeed.
If anyone were to ask me for advice--which I notice they're not--I would just say two things:
1. Write lots of programs.
2. Work with good software engineers.