I read no such thing. (Speaking of straw men...) instead, I read him saying that the alternative to SOLID was “Write simple code.”
I think there’s room to criticize this essay, but that’s a bizarre one to lead with.
The part you missed is in the same sentence as the three words you quoted.
"Instead I suggested to write simple code using the heuristic that it 'Fits In My Head'."
The obvious criticism here is that now any developer who wants to defend their code will simply claim a given spaghetti incident easily fit in their head. The author even seems to acknowledge this line of attack in the next paragraph:
> You might ask “Whose head?” For the purpose of the heuristic I assume the owner of the head can read and write idiomatic code in whichever languages are in play, and that they are familiar with the problem domain. If they need more esoteric knowledge than that, for instance knowing which of the many undocumented internal systems we need to integrate with to get any work done, then that should be made explicit in the code so that it will fit in their head.
But that just protects against spaghetti made from esoteric know of internals. For example, in C a "big head" would still be able to justify using global variables (after all, that's idiomatic C), rolling their own buggy threading approach, and deeply nested side-effect based programming.
I'd much prefer pointing big heads to the rule of "do one thing" than to ever read a response of the category, "Well, it fits in my head."
The "bigger heads" types will use SOLID or any other argument to justify a higher number of abstractions. While the other type will do the same to justify a lesser.
At the end of the day, there is no right solution, but what is so called "cultures" that group people with similar taste and cognitive fingerprint, and provide best practices.
If I can't understand it, neither can the next guy.
slaps on hood
The thing is: that's just a bad defense. If the people who are going to be to maintaining it and working with it are saying it's bad code, it's bad. Even if this is the only developer who is going to maintain it for now (which itself is always a terrible idea), eventually someone else will take over, and the closest thing you have to that "someone else" is the rest of the current team.
I'm having a hard time imaging someone seriously making this argument, who is not also:
* Trying to establish themselves as the sole possible maintainer, so either the company can't fire them or is completely screwed if they ever leave, or:
* Is a saying the rest of their team is just too stupid to work with
In either case, this person is a massive liability, and the longer they are employed the more damage they'll do.
What if everyone but one says it fits in their head?
What if you're just talking one on one, and have no idea how many team members it fits in the head of?
What if the person who is criticizing the code will begrudgingly admit that it fits in their head, but still thinks it's too messy?
"It depends". If the one person is a junior it's maybe not a code issue (unless junior-level people are expected to maintain it). On the other hand if it's a skilled, experienced, senior person saying they can't follow it, there's likely an issue with the code.
> What if you're just talking one on one, and have no idea how many team members it fits in the head of?
> What if the person who is criticizing the code will begrudgingly admit that it fits in their head, but still thinks it's too messy?
The greater point here is is that it's about code maintainability. "Fits in your head" is merely one proxy for that, not the actual goal. There are also hundreds of other things that lower code maintainability -- including being "messy", having bad names, poor or no inline documentation, no unit tests, and being "clever" -- and all of those are things that should be discussed and fixed.
Presumably everyone involved are professionals and working in good faith. If they really can't come to consensus: sleep on it. Involve more people. Suggest alternatives. Pair up.
Someone consistently on the losing side of these discussions -- whether they feel they're constantly dumbing down their code, or that the rest of the team is writing unmaintainable spaghetti nonsense -- should probably reflect on if this is the right team for them.
Care to elaborate as to why those are strawmans?
Without that, this is a no-argument, which is probably worse than a strawman.
>and then suggests that instead we should make software as complex as possible but no more complex than the coders own comprehension
The combo (go and make "software as complex as possible but no more complex than the coders own comprehension") is suggested nowhere in the post.
The second part of the combo (make software "no more complex than the coders own comprehension") is indeed said, and is very sensible advice.
It is, however, combined with the inverse advice of "make it as complex as possible" which you claim the author combined it with: with the advice to keep it simple.
The strawman here is the fallacy that SOLID (SRtC) is clearly not saying that a software, composed of "code", should only do one thing. By that reasoning, SOLID rules out any software that provide multiple capabilities. Your editor, for example, has multiple capabilities. It can save. It can highlight. It can cut. It can paste.
So, a reasonable reading of SOLID naturally is not ruling out composing complex software using "simple single purpose code". However, OP is assuming a ridiculous reading (the strawman) that basically can only be valid for software that has only one irreducible capability.
One can read SOLID SRtC in terms of capability as "compose complex software using single purpose code".
One can read SOLID SRtC in terms of change as "changes to code should consist of one, or a sequential set of, single purpose changes".
That's not what TFA says here. It doesn't claim that SOLID says that software as in 'a full program' should do one thing.
It just says that SOLID says that a piece of code (as in a function or a class) should do one thing, which SOLID does say, and which the author of TFA disagrees with.
Perhaps the use of the word "code" (or "non-trivial code") is confusing, but the author doesn't imply the whole program with that, but the same as SOLID does (a unit of code):
"Code should fit in your head at any level of granularity, whether it is at method/function level, class/module level, components made up of classes, or entire distributed applications".
As long as you manage to keep artificial complicatedness out of your code, you will always have the complexity of the problem mirrored in your code. A common problem of ideas about object oriented programming like SOLID or Clean Code is that they have a focus on classes. If you keep your classes very simple, you will instead end up with a complex architecture, where you might have zero responsibility layers or functions that just pass on the call further to a layer down.
Code is cheap to write, and mostly debt. A software product - a working system that meets some particular need - is not. The distinction is that building a software product is much more than banging out code; it's the experience of figuring out what exactly that need is (gathering requirements, getting a system out for users to test, getting real-world feedback). Sometimes you capture that in documentation, test cases, ADRs, comments, commit messages, etc. Sometimes it's in your head, which is okay as long as you're still there. (Of course, if it's in your head and you leave, then the next set of programmers will be scared to change the system until they redo the work of figuring out what the software is, despite the code being in front of them.)
If you have that understanding about what the software does, ideally in the form of automated test cases, you can rewrite the code. So you may as well bang out the code in a way that gets you a working system and remains comprehensible. Once you're making enough changes to it that you're worried about it getting incomprehensible, proceed to rewrite it. Probably the world has changed in many ways sine you wrote it - maybe you can run the system for a lot faster and cheaper with containers in the cloud talking to a SaaS database than with your expensive IBM mainframe talking to DB2. Or perhaps the requirements are changing significantly, and starting with the old code doesn't give you much of an advantage.
Trying to keep a codebase comprehensible over the long term makes it, if you'll excuse the pun, solidify - it becomes increasingly hard to make changes that weren't anticipated by the original design, and it also requires more and more effort to just do anything. You might be able to swap out the relational database for another one, but you probably can't switch to a design where you're using a key-value store with totally different performance characteristics. And even if you do just want to change the database, you have to figure out how ten classes get dependency-injected into each other before you can start coding.
All the time you spent making the code "future-proof" so it remains forever comprehensible could have been spent simply not doing that, delivering business value, and writing new code as the need arises.
This has nothing to do with tooling, but the fact that pulling the rug under an established code base could have very unintended effects, compared to simply adding and extending functionality without touching what is already there.
By doing as the author suggests you’ll end up with either 500 broken tests or 5000 compiler errors in the best case, or in the worst case an effectively instantly legacied code base where you can’t trust anything to do what it says.
I once had to change an entire codebase’s usage of ints to uuids, which took roughly 2 whole days of fixing types and tests, even though logically it was almost equivalent. Imagine changing anything and everything to “make it do something else”.
Honestly, if you cannot change your code, you have a problem.
https://drive.google.com/file/d/0BwhCYaYDn8EgN2M5MTkwM2EtNWF...
Flipping it around, if base libraries were to be always open for modification instead of extension, then instead of writing your feature logic in your own codebase, you might be tempted to submit a pull request to React.js to add your feature logic. That sounds ridiculous but that's the equivalent of what I see a lot of new engineers do when they try to fit the feature logic they need to implement anywhere it makes sense, often in some shared library in the same codebase.
And how long would it have taken for the original author to use an IdentifierFactory instead of ints and write meaningful tests for it? Less than two days?
OCP though more commonly refers to: 1. Building small, universally useful abstractions first 2. Extending behavior of that abstraction or system by writing new code rather than changing published code directly.
This is trivial when you have a few patterns under your belt. Template factories, builders, strategies, commands. I mean, while it’s not the best idea in most cases, even just inheriting a parent class and giving a new concrete new behavior is still better than changing something fundamental to the system.
Like has been said 999 times in this thread, software isn’t black and white. You have to make choices about where you go concrete and where you abstract, and gauge that against risk factors. A somewhat complex class you expect to go away in a couple months? Make it a god class that anyone who wants to can scan through. A fundamental class that will be used by hundreds of developers and underpin most operations in a production system where 5 minutes of downtime costs tens of thousands of dollars? It’s worth the upfront cost to build with these standards.
That's still a matter of tooling. With type-checking, static analysics, and test suites, changing code doesn't have "very unintended effects".
Back in the day, without those, things were much more opaque.