💾 Archived View for dioskouroi.xyz › thread › 29396515 captured on 2021-12-03 at 14:04:38. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-11-30)
-=-=-=-=-=-=-
________________________________________________________________________________
I found out that some fellow engineers do not want to write maintainable code, they instead want to write code that:
- follows SOLID principles
- follows Clean Code principles
- follows Hexagonal architecture principles
- ...
Sometimes such principles do lead to maintainable code, but, most of the time they don't (at least in my limited working experience of around 10 years). An example: If you duplicate code because at the end it seems to be the proper way to write a piece of functionality that will be easier to maintain in the future (and most importantly, it communicates clearly its intention)... well, that's a no-go for some fellow engineers because, somehow, that violates all or some of the principles they have read in some blog post owned by internet celebrities.
Yeah, obsession with DRY to the detriment of readability and maintainability does happen. If you need to write another script like the one you wrote yesterday (but slightly different, and you don't know exactly how different), then starting with a copy&paste is a valid strategy. As you go on you will realize the common points of both scripts, and _then_ you can DRY them both. Or not, if the common parts are too few to bother. Creating complex class hierarchies to "promote reuse" before there even is a _case for reuse_ could be seen as premature optimization or YAGNI.
On the flip side, in my experience, people with certain amount of years under their belts tend to treat all those principles in a way they should be, ie. as inspiration instead of as law.
My personal pet-peeve is when the obsession with DRY carries over to testing. Testing is not the place for DRY. Reading a test should be like reading a recipe: do A then do B, then do C, then expect result X. When you start abstracting the setup to the nth degree it becomes much harder to understand the test because you have to click around the code to following the abstractions like a trail of bread crumbs, when really what you want is for the stack trace to say "error on line 123" and the cause to easily found near line 123. Tests shouldn't be dry, they should be wet.
This is something I've seen described as "optimizing for deletion" which is the only way to have true modularity.
Yes! And deleting large amounts of code from a massive tightly-coupled monolith is a great way to learn that first-hand. I like to say that you learn a lot more from dealing with a legacy monolith than you do by implementing the latest clean patterns. It eliminates a lot of cargo-cult mentality and lets you really see what patterns make the difference.
From your example, it sounds like your gripe is more with engineers that follow a given principle dogmatically, rather than applying it judiciously based on context.
Although I think there is some value in principles like these, I am always wary of anyone that's overly absolute in their thinking.
The key is this: If you have to change one, do you have to change all copies? The problem is not duplicated code; the problem is duplicated concepts.
DRY was a response to cut-and-paste programming, not thoughtful application of the same (or similar) code to different problems.
The problem often arises when two pieces of code _happen to_ look the same, but are conceptually doing two different things.
I have seen far too many codebases where a junior engineer took it upon himself to DRY-ify two similar looking bits of code, only to have to later go start adding knobs and if-branches to handle differing evolution between the two problem domains.
If the original piece of code had some logic bug, sure, it will likely be necessary to go fix that bug in all of the duplicated spots. But if the code evolves in one place due to the problem domain changing slightly, that rarely implies that the duplicated code needs to change.
I once worked with a principal who applied a dogmatic view of the 12 Factor App and developed a framework based on those principles. It was to be the One Framework to Rule Them All, and _the_ framework upon which our entire portfolio's application infrastructure was to be based because it would make everybody's lives so much easier and novice programmers could be productive right away, etc.
Maintaining code in it was a hassle. Code was scattered to the four winds in a required directory structure. But because simply importing modules wasn't allowed -- modules had to be structured as functions that accepted a "dependency manifest" containing all the modules they depended on -- it was difficult to figure out where your implementations lived. So instead of being able to read the code and figure out what was going on, you had to navigate this tangled web of dependencies and guess or search for where some of them lived.
Thankfully, with the arrival of a new system architect, cooler heads started to prevail and the company abandoned plans to go "all in" on this solution.
From the opening of _Structure and Interpretation of Computer Programs_ [0] there is the famous aphorism, "Programs are meant to be read by humans and only incidentally for computers to execute."
I've been programming for over twenty years. Try as I might to produce code that expresses the problem eloquently and succinctly to unfold the solution in the readers' understanding as they skim through the source... it has rarely ever worked. Firstly you cannot please everyone. And secondly, programs are not structured for pedagogy.
Writing maintainable code is a communication skill but I find the best skills are writing, speaking, and illustrating concepts in prose, specifications, white board sessions, chats, etc.
The technicalities of ensuring code follows some kind of style guide, design principles, etc plays a big part. But nothing will explain "why" or the big picture stuff quite like a specification or blog post in my experience.
[0]
https://en.wikipedia.org/wiki/Structure_and_Interpretation_o...
_Update_: added missing link
Author here. Looks like we both have a similar length of experience. I wouldn't give up this battle yet, because
> Firstly you cannot please everyone
> And secondly, programs are not structured for pedagogy
My theory is that you only need to please a couple of maintainers that work with you, not everyone. That's why I proposed a test with 2 colleagues at the end. It could potentially be spiced up with 2 colleagues of different levels. I believe this can act as one of those 20% effort to get 80% there, but definitely don't claim to have proof of this.
I think it is possible to produce code that conveys everything the spec would. The problem is the same as with Donald Knuth literate programming: to use it, you now require your coders to be both great programmers AND great writers. And these two traits rarely coincide in a single person.
How often have you seen an algorithm expressed with such grace that it appears boringly obvious?
There is a perverse quality that I see in mostly junior engineers of not wanting the complicated thing to appear simple. I think it's probably a result of some ego and accomplishment and wanting others to know it was challenging. I'm not sure exactly but I've seen it a lot.
U know $perl ?
Where are these juniors graduating from? At my school, asking students to even use python was like asking them to do Microsofts taxes for the year.
The problem is "writing maintainable code" doesn't stroke the ego quite like "writing impressive code"
The challenge is, how do you teach it?
Like many writers, many coders think their code _is_ readable and are resistant to feedback. They literally do not see the problem.
You can institute all the rules and guidelines you want, but they see it as friction and overhead and only do it because they are forced to.
Plain real world experience. So one way is:
1. Learn all the guidelines/rules/principles out there (or at least the ones that appear to be the most important or popular ones)
2. Apply them in real projects (and keep working on such projects for at least N years)
3. Realize how painful is to maintain some of your solutions. Unlearn what you have learnt
Point 1 is important (otherwise you may miss other people's good solutions). Point 3 is important (self-critic, self-introspection, let your ego go away)... but for me the most important one is point 2: unfortunately most fellow engineers out there jump from job to job after 1 or 2 years and they don't end up maintaining their own code (so, they usually don't get to experience point 3).
Where N is at least 3, in my limited experience.
A big issue seems to be that what is "readable" to one person isn't necessarily "readable" to someone else, and you end up with endless yakyak over whether the braces should be inline with the `if` statement or not.
My take is that some compromise is key: Yes, there are some standards that objectively make code easier to understand. But you should also learn to read code that isn't written exactly as you'd like it to be. And try to stay consistent with the overall style.
The first step is to agree on what it is.
I've never actually seen rules or guidelines that were:
* Non-trivial - e.g. not about spaces or line lengths or something.
* Objective and specific - e.g. with a vague principle like "single responsibility" it doesn't take much to trigger an argument about what constitutes a responsibility.
* Actually _made_ it more maintainable - I remember some of the DDD guidelines were non-trivial, objective and specific but they 3-5xed the SLOC.
I tried writing some of my own, but it's a lot of work, doesn't generalize very easily and is as likely to spark an argument as it is to actually help.
But those are "writing techniques" and not the actual writing / stories. As long as you're having a "conversation" with a codebase by continually reading and editing code you know what parts of the code flow well and are expressive and what parts are not. I think a discussion about the eloquence of a codebase should be had in continuity, parallel to all other individual tasks.
Actually the article illustrates some of the criteria like if the code answers "how/what/why" questions. From my own experience the codebases where the code didn't answer those questions were the worst.
That's who you need to let go. People resistant to feedback are bad for any role in any company, and if they have their heads so far up their... that they think _they_ know how the code _looks for someone else_ better than _that someone_ else, they really shouldn't have been hired in the first place.
It's politeness first, and skill second.
Most people don't lack skill per se; they just don't give a shit about you.
I really don't believe that - it does not match my experience at all. It's just that there are a lot of conflicting forces at play. It's taken my whole career to get better at balancing those forces, and it changes for each scenario. Building software is a people problem, and people (like software) are hard.
I've found that documentation is one of the first things to go when time-constrained in Sprints. The PM/PO or Team Lead will pay lip service to the idea of allotted time to documentation writing for each Story, but don't enforce that during planning. Nor do they call it out during Retros.
Heck, there's some devs, senior and junior, who don't realize the value of standard branch naming, ticket references in commit messages, etc etc. Or they'll pay lip service (again) to a conventions standard, but still continue to work the old way over and over.
The common thread among devs that I've spoken to is that they feel they aren't talented enough to write good docs, that they don't have time, and that they don't know what to write about. I feel like the team lead and higher-ups need to focus on enforcing standards for a few sprints to get everyone working on the same page.
Yes, writing maintainable code requires the ability to explain.
There is far far more to "communication skills" than explaining yourself very well, stuff like negotiation and persuasion and conflict resolution. Most of those are less relevant to writing code as much as to getting it accepted.
In other words, code should be eloquent, it should be as easy as possible to understand why it exists and why the way it is written is correct (or give information as to why the author thought it was correct).
Likewise, with regard to modularity, I like the take on codebases that optimize for deletion and not abstraction (after all, abstraction is only a tool, not an objective).
These two together make up for the best code IMO.
It's about good abstraction.
"If a piece of code could be abstracted, it'll eventually be extracted."
Communication? Maybe. But in my mind it's more of a user experience. That is, someone else picks up your "product" (i.e., code), how easy - or not - is it for them to engage with that product?
Software development is communication; writing code is a side-activity.
I just started using @coincircle
https://coincircle.com/l/2Q_TQamlA5
If you have one group of coders writing new functionality, and another doing maintenance work on the application, you have diverging interests. The "new functionality" people have an incentive to rush stuff, because bugfixes won't be their responsibility anyway. The most cynical way to do this is writing a lot of new functionality, put it on CV, and leave for another company.
A great place to manage your crypto is
@coincircle
https://coincircle.com/l/2Q_TQamlA5
I interviewed someone from China once who wrote code almost as I write prose.
FirstWeCreateAVariableCalledX.AssignItValueY().PassYtoPvalue();
Now imagine 10,000 lines of code written like this and done so in a way that was thoroughly impressive. The creativity alone was startling. I would do it a disservice to further explain.
Yes, her object names were ridiculously long, but it was the most readable code ever. It was like she combined the code and documentation together. I'd seen this neither before nor since, and it was strange and beautiful.