224 comments

[ 3.4 ms ] story [ 242 ms ] thread
The famous book about Clean Code was a big Oxymoron, because the examples in it pages was in Java.
I work with clean java, and it's a charm!
As a one time java dev, I see what you are saying, but modern java is ok. Nowadays, its more on the dev team than the language.
Java is a fairly clean language. It may be verbose, but that is it.
It depends where one gets their beans from.
This article really buries the most important lesson about refactoring:

> I didn’t talk to the person who wrote it. I rewrote the code and checked it in without their input. Even if it was an improvement (which I don’t believe anymore), this is a terrible way to go about it. A healthy engineering team is constantly building trust. Rewriting your teammate’s code without a discussion is a huge blow to your ability to effectively collaborate on a codebase together.

Also (2020).

Why one should ask to the original developer if the code can be modified?

If I see a code I want to change, I do it. I don't ask for permission. And colleagues can do the same with code I wrote. We trust each other.

If they were still there, why wouldn't you engage them about a modification you think was better than the original? Everyone learns in that situation.
they may not be 'on that project' any more, and if the burden of support is on you at that point, and you're the one dealing with the code, and they're not, them saying "meh, it was good enough" doesn't really help. I've hit that before, and mgrs have said "devX said it's good enough, they wrote it before, don't try to change stuff". Well.. they wrote it 2 years ago when there were 300 customer records, not the 14200 today, and they're not fielding support issues from people complaining about delays now.

There's plenty of reasons to not engage with someone. There's also plenty of reasons why it may make sense, and I think it's highly context dependent. For me, it's about 50/50. And... in the cases where I reach out, it's about 50/50 as to whether they have any time/inclination/memory/ability to help anyway.

Because it wasn’t necessarily an improvement on the original code. A discussion should have happened either during the original commit or before the refactoring commit.
Trust would also include to consider that there might be a reason for why the code is the way it is, even if it is not directly obvious.

A brief chat can then help to clarify things.

Two reasons, one technical, one social. Firstly, the original developer probably spent more hours thinking over and working on that bit of code than you have -- so maybe they had a good reason for doing it the way they did. You won't know unless you ask them. Secondly, I think it's a fairly natural and common human behaviour that when you spend a lot of time working on or making something that you feel some degree of satisfaction, pride and investment in the result. Even in a cooperative joint endeavour that is striving to avoid an idea of individual "ownership" of parts of the project, I think it can be helpful sometimes to acknowledge that feeling of investment by consulting the original author before walking in and radically changing something they did.
I've heard this many times... 'take ownership of your code' ... but the problem is the vast majority of devs work for corporations who own the code, and unless you're a shareholder that code certainly isn't yours, it's corporate property, that's the deal in most employment scenarios.

Realistically, people doing such work care very little about writing 'great code' because they know they have no real 'ownership'. There hopes for higher pay and recognition rely on climbing the corporate ladder by whatever means available. Team member, team leader, division manager, VP of whatever, etc. Blame bad results on someone else, that's the normal tactic for these types. Don't hold up production over code quality concerns, because delays in pushing product to market upset the shareholder board, which they see as lost profits.

The whole notion of a 'skilled technical individual who takes pride in their work because they own it' sounds like some awful corporate in-house propaganda campaign to be honest. And this accounts for much of the current mass exodus from the corporate workforce, I imagine.

I didn't say "take pride in your code because you own it", I said that because you feel some pride in what you've made you feel some sense of investment in it. And I wasn't trying to speak to legal ownership but to the kind of psychological feeling that might explain people getting a bit cranky if you waltz in and rewrite things. That doesn't happen to everybody or in all situations, but I don't think it's that rare.

The code I write is corporately owned in the legal sense but I still feel some attachment to it and care about good workmanship.

The other developer usually has reasons why they designed it that way. If you just rewrite other people's code, it makes it seem like you think there is One True Way to writing code and that you're smarter than them.

I'd ask first if they agree with a certain improvement. It shows I value them as an engineer and they might bring up critical information that I'm missing, making my "improvement" actually worse. It doesn't hurt to talk to people.

I just assume that they got one pass at it. Agile!!
I agree Agile steers a team towards hastily bodged together code.
The student said to the teacher:

"This fence is in the way and obstructing the flow, it should be removed"

The teacher said to the student:

"If you can tell my why someone made the fence in the first place, I will allow you to remove it."

Note that this has nothing to do with "code ownership". If you worked for me and randomly changed code that you did not like, I would fire you.

> If you worked for me and randomly changed code that you did not like, I would fire you.

From one extreme to the other? People make mistakes, educate them instead of brutally punishing them. Talk it through - isn't this exactly the mistake that was made by the developer (they didn't talk to their peers)? If they do not respond to feedback, that can eventually lead to a firing.

Of course I would talk to a junior dev right out of college and tell them these kind of "drive by" refactorings were counter productive and dangerously susceptible to the introduction of lack of context bugs... ONCE. The hiring process is too expensive and time consuming to punt on a junior dev or intern for such a transgression.

However, if the behavior persisted then yes, I would fire them.

If anyone sees code that they hate they are free to submit an issue and I will be happy to review it and prioritize it with the other work to be done.

If they hate code that smells so much that they want to volunteer their time to refactor it, then they can participate in code reviews for me instead.

> I would fire you

Ha ha ha. That's not how it works with me.

If I ever see you again rewrite someone else's code without asking, I will fire you on the spot.
(comment deleted)
Ironic. You learned nothing from the article.
i'd fire you for micromanagement, mismanagement and encouraging bad practices.
Years ago (2005?) I worked at a call center. Most agents were on one coast - data center was in another coast. Agents in our call center complained about speed. When they'd pull up an account, it was taking seconds... sometimes 15-20 seconds... to pull up the account.

This was all a bespoke CRM system. I poked in the code (it was something I had access to) and noticed that ... the entirety of the whole screen was duped - the output of the client's history was embedded in an HTML comment tag. So... if the client had, say, 3 years of info/comments, it was rendered, then rendered again in HTML comments. It was a single line duplicating the entirety of the info. It was obviously a debug remnant. I removed it. I tried to talk to the original developer beforehand - he was on the phone, and kept waving me off. I emailed him. No answer. I committed it and got it to a testing server. The test guy immediately loved the speed improvement, and we got it out to the floor. There were about ... 50-70 agents live at any one time, and everyone's hold/wait times were cut by 20-30% overnight. Clients and agents were happier. I found out later that, over time, we cut bandwidth bills by a measurable amount.

I was raked over the coals and nearly fired for that. "unprofessional", "insulting", etc. It was primarily because I'd made someone else look bad. Other people on the team also made changes now and then to each others' code without asking permission, when it was needed/obvious (emergencies/etc). It was a simple oversight in removing some debug code, and it made a huge difference (it had been tested and had other eyes on it as well - this wasn't "change prod by hand without telling anyone").

Still bugs me to this day (as you can tell).

You mailed the original developer, he didn't answer. And you were fired for doing your job right. What you must conclude is that they don't care about quality but about ego. They have a philosophy that don't favor quality. You should be glad they fired you.
I wasn't fired, just... the threat was raised. That original dev left soon after (in no way related to that - he'd been looking around for a while from what I heard). It was a strange situation because in the group of... 8 or 9 of us, I was the last one in, and got along OK with everyone, except him.
The point your parent post was making was that regardless of whether or not you actually got fired doesn't actually matter. The signal you should be paying attention to (which you are surely aware of, since you say this episode still bothers you) is that the environment was a toxic one so if they did fire you they would have been doing you a favor. At least from a silver linings point of view.
A lot of people attach their ego to the work that they produce. I did when I was younger and would try and defend poor choices because I had made them. They were my children. Once I put that aside and separated my ego from my code, I got much more comfortable pointing to my own mistakes and learning from them. I am a far better engineer and teammate because of that.
There's a big difference between fixing a problem that's been in production for two months, and rewriting code your coworker pushed two hours ago. For one thing, your coworker might be still working on the next iteration of the code, and adding an unnecessary diff could easily cause hours of wasted time.
The reasonable thing to do is often to add the original developer as a code change reviewer. They should know about what the code is doing and about any pitfalls involved. If they don't like the changes, then it is easy to take the discussion then.
While that is undeniably an important issue, personally, I would pick this as the more important point in this particular context:

"My code traded the ability to change requirements for reduced duplication, and it was not a good trade. For example, we later needed many special cases and behaviors for different handles on different shapes."

The reason for my choice of this point is that provides a direct, technical counter to one of the simplistic technical rules on which the Clean Code movement is founded, and so might make a impression on someone who has, heretofore, regarded the case made by Clean Code proponents as irrefutable.

That's also why I say "in this particular context", as in the broader scope of software development practices generally, the interpersonal dynamics of the organization are almost always more important than coding style.

And... > It was already late at night.

Probably not the best time to refactor someone else's code that currently works.

This blog is essentially a journey of self discovery arriving at https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction

"duplication is far cheaper than the wrong abstraction."

Totally agree!

I think this is a journey a lot of software engineers go through. I can say I was walking down this path in the past.

The thing I hate most is when some creates the "new way of doing things". Then fails to convert most of the existing code which is "doing things the old way" even when it's clear that it is the same thing.
Which then devolves into five ways of doing things after a few more iterations. Kitchen sink architecture.
The "new way of doing things" is risky and might not turn out so good after all.

If you treat the new way as an experiment, and only gradually convert the rest of the code, you're in for a smoother ride.

Please no, this is how you end up with 20 different experiments and zero consistency in everything. Gradual migrations are fine because it makes project managers happy but it requires a commitment to actually follow through with it. If there's a chance that you won't actually have dev time to finish the migration then just don't do it. It can't be that important then.

It's totally fine to try out an experiment in a branch and deploy to QA but don't deploy it to prod until you're sure.

> Please no, this is how you end up with 20 different experiments and zero consistency in everything.

This comes from a lack of discipline in the org, not from the parent's (IMO) correct guidance that an org should undertake architectural changes with pilots in isolated parts of the code base.

The pitfall there - and I myself am guilty of this - is that you only apply it halfway, so instead of fixing the problem (if there even is one), you're left with two different approaches to the same problem, meaning you added a second problem as well as a lot of overhead because you're not maintaining two different solutions.
That's essentially the lava flow anti-pattern. You have remnants of old architect/tools/philosophy left in the old code while the new code is being built with the new hotness. Invariably everyone has to learn the old stuff to maintain the system as well as learn the new stuff to keep building.
(comment deleted)
> The moral of this story? Don't get trapped by the sunk cost fallacy.

Most engineers seem to have the wrong takeaway from this post. Every abstraction goes from right to wrong eventually. The answer is not to not abstract, the answer is to ruthlessly tear down abstractions when they go from right to wrong, which people seem to have trouble doing.

I think the other major problem with abstractions is that they have a cost and no one seems to like to discuss that. Even the right abstraction has a cost.

The typical SE path I'm familiar with looks like:

1. Write _anything_ that works. Dependencies, code quality don't matter. Code is idiosyncratic but can be understood by a reviewer.

2. Apply abstractions _everywhere_. Re-implement data structures and algorithms (maybe unknowingly). Code is now very hard to understand.

3. Figure out one is completely unable to update or even maintain code written 6 months ago. Rewritten code suddenly becomes clearer, one begins to think about programming as an craft and not just hacking things on a keyboard until you get the desired result. Dependencies, judicious comments, code quality and a sane terseness become important. The journey starts here.

Personally, step 1 was very short as I learned to program on the job. I was responsible for code, and so I needed to get it together quick. A language like Python makes 2 very easy, and 3 came very quickly too since, as I mentioned, I was responsible for the code (one man team).

I have met people who work at large institutions who are stuck at 1. Others with degrees in CS who are stuck at 2. Some just "get it" and go straight to 3. But typically, the real journey starts when you need to go on with work but your prior self is preventing you from being efficient. You need to get rid of that prior self's work to move on.

This is why I tend to avoid refactoring code as often I find just having a common pattern in code that's been adopted either officially or unofficially by the dev team being more important than trying to 'clean it up.' Sure, I say delete commented out code since we have git and other VCS today that makes this habit unnecessary but if a piece of code doesn't use LINQ style queries to parse lists and uses for loops instead, I just leave it be.
That was a great article!

I really appreciate it, when people write from hard-won personal experience.

For myself, I write in Swift, and it’s quite possible to write totally inscrutable code in Swift. I am guilty of making code harder to read, and less grokkable, in my “cleanup” sweeps.

I am currently doing a bit of navel-gazing, on this very topic. I have gotten into the habit of writing very “swifty” code, and am thinking that I should probably back off a bit from that.

(comment deleted)
There was a discussion a couple of years ago on this article with 500+ comments, for those interested: https://news.ycombinator.com/item?id=22022466
No thanks, we're doing it all over again here. This time will be better!
The mantra of every disasterous rewrite.
I think it will be better if we redo it in another language:

Acest articol este interesant

in the old version the highest rated comment was 19 lines split over 7 paragraphs and contained internal lists! The highest rated comment at this time on our version is only 11 lines over 4 paragraphs! A win for our side already!!
(comment deleted)
This seems close to illustrating a thought I've been toying with turning into a blog post but need some more examples of before I do:

"Abstract over data, not behaviour"

This seems to fall into the trap of abstracting over behaviour.

The best other example I've thought of for this is the 'generic repository pattern' common in C# and I'd guess Java. Just because CRUD on types is all similar behaviour you can't really build a generic/abstracted way of doing it, because at some point you need different behaviour in some update/insert depending on how the domain concept should act and then you're in a world of pain with a crappy abstraction.

One's code actually 'doing something' isn't a sign of unclean code, it's why one writes it. Stop trying to build abstractions that simplify the doing of what your code does.

Ah, thank you. I never managed to put it quite clearly as that.

But thinking back and considering the intuition I built around when an abstraction makes sense and when it's just going to be unhelpful cruft, boils down to this.

Abstracting over behavior gets messy very quickly. Special cases will probably arise on the next requirement change. Unless they're already there and you missed the subtle interaction. And if you didn't, and actually handled that properly, your abstraction will have a lot of hooks and bells and whistles to support the different behaviors, and it'll just be hell to maintain.

Data can change underneath you, sure, but I think we as humans have a much better intuition about concrete things rather than algorithms, so it's easier to abstract data in a useful way. Which is why I think that works better.

The abstraction is the standard case and should invite custom behaviour to be added dynamically. This extra behaviour should be added by the caller / user of the abstraction.

Especially in a CRUD situation, every custom procedure will probably have a few basic steps that stay the same. All you usually need is a pre and a post hook.

See, but that's exactly what I'm saying does not work.

It's tempting to think that for e.g. a CRUD situation, pre- and post-hooks are all you need.

And it may be that way at the beginning, although hardly so, except for the simplest applications. But very soon, you start to run into things like (not an exhaustive list, but all are things I actually encountered while trying to do precisely what you're saying, many years ago):

* Transactions, when multiple things have to happen atomically. Your pre-hook must start a transaction, and your post-hook must commit it. But what if there's an error somewhere? Python and JS don't have RAII, so you need some kind of catch block to abort the transaction. Where does that happen?

* Tricky validation, e.g. needing to do queries against the data store to check the request is well-formed. So if you're using an event-based language, your pre-hook also needs to be asynchronous.

* Data transformations (what the user sends will hardly be what needs to end up in your data store), so your pre-hook needs to be able to return a new object.

* Tracing across service calls, so you need to pass some kind of request ID as well to your hooks.

* An "update" request needs to return something (e.g. an ID) to avoid another round trip. But sometimes it needs to return more stuff, so your post hook must be able to return data. Oh, but your ORM or DB usually returns the created object, so you'd like to use that instead of re-fetching in your post-hook, so now your post-hook must accept that as well.

These just keep coming up. The first three in particular are usually guaranteed to happen before the first release of the product, since requirements always change. Soon you have an unmaintainable monstrosity. A little copy-paste is tame in comparison.

Some of those could perhaps be solved by an "around" hook, in addition to pre- and post-hooks, like advice[1] in Emacs. Although in OOP maybe that would be represented by a decorator class (or a subclass) that overrides the method and calls the original method?

[1] https://www.gnu.org/software/emacs/manual/html_node/elisp/Ad...

https://en.wikipedia.org/wiki/Aspect-oriented_programming

A further elaboration on that idea. I like it, but have used it in a disciplined way. There's some valid criticism of its ability to obfuscate a program. Particularly with the ability to add and remove advice dynamically, making static analysis (to determine why something happens) practically impossible.

Exactly right, transactions being the killer/most common one.

And the proposed solutions of hooks, AOP etc all start down the road of scattering actual logic across files and methods making the code harder to reason about which seems to be the worst part of overeager abstraction.

The library Automapper from C# is another place I run into this a lot. At its core it simply maps from domain to dto properties but you soon end up needing awful unwieldy configs and magic. I'd rather have a few hundred lines of obvious mapping than ever work with automapper again.

In this case the abstraction should be designed so as to invite/welcome custom functionality. The domain/entity repo should extend a generic crud repo--as they usually do in my experience.
Absolutely – I was sad that the post didn't point out the better (correct?) way to clean up that code:

leave the objects alone, but write pure helper functions that implement the most common expressions inside the mathy parts, and have all the similar-looking objects use those helper functions.

Uncle Bob fooled many developers to join his sect. I am much happier since I've discovered that there's not only "one true path" and OOP, SOLID and Clean Code are not the means to everything in software.
He makes money whenever he convinces someone he's right, of course he's convinced many to join his sect.
This post is not related to Uncle Bob's book, which is not even mentioned. The term "clean" here is subjectively interpreted by the writer of the post.
It's ambiguous really. Clean architecture refers to architecture that is structured in such a way (mostly by relying on tests) that one can "clean" out crufty portions of the code and not feel anxiety because one's tests should give one peace of mind. Whether or not the author was referring to the clean code as part of a larger clean architecture or just that the code wasn't crufty in general isn't clear. I would have preferred that the author used a different term (or that Uncle Bob had chosen something less common himself).
"Firstly, I didn’t talk to the person who wrote it. I rewrote the code and checked it in without their input. Even if it was an improvement (which I don’t believe anymore), this is a terrible way to go about it."

While undeniably true, this feels completely orthogonal to the question of clean code. You could ruffle someone's feathers in the exact same way by taking code into the opposite direction.

"Goodbye, Clean Code [...] Don’t be a clean code zealot"

I agree with choosing pragmatism over purism. But the clickbaity title exercises a pattern I've seen hundreds of times, to which I have frankly become a bit allergic over the years: "X considered harmful! ...I mean, if you're overdoing X". Not being a healthy food zealot doesn't equal "goodbye, healthy food".

(comment deleted)
HN has a lot of people who seem to benefit from these types of “common sense” articles that bury the lede, judging by the fact they appear on the front page so frequently.

(Putting common sense in scare quotes to acknowledge that this is new information to some, no matter how obvious to others)

Is it really the case? Could it maybe be that these clickbeity titles are upvoted and comment on more, regardless of the questionable added value to the community "burried" deep inside?
I feel that a lot of discussions on those clickbaity titles are based on the title alone
I'd suspect it's more a matter of people enjoying the sense of validation they get from an article which tells them that the low standards they're accustomed to from work are good, actually.
I'd put it differently: HackerNews has an immune system to defend against low-quality submissions, including articles with clickbait titles, but it's imperfect. (As dang puts it, we're trying for something different than internet default here. [0])

[0] https://news.ycombinator.com/item?id=29153668

Yeah, "don't overdo X" is always true by definition. That is what "overdo" means. Likewise "don't be an X zealot" is always true by definition (based on how we use the term zealot in modern English).

That said, despite the clickbait title, I think these sorts of posts make a valid point because people often do in fact take heuristics like DRY to mean "never repeat any code under any circumstances and any code repetition is per se bad."

Well... you're right. But humans have a tendency to become zealots. They (we) take a good thing, and make it The One True Thing(TM). It happens with many people with many different things. It seems to just be human nature.

So it may be good to remind ourselves "Hey, this is just a thing. Use it when it makes sense, and don't overdo it."

> While undeniably true, this feels completely orthogonal to the question of clean code.

I think this mentality and the Clean Code Movement (with capital Cs) actually have a lot in common.

Much of the Clean Code movement is occupied with the idea that there is one, single, right way of doing things. Anyone who does things differently is doing things the wrong way.

There is a subset of programmers who are attracted to these movements because it can feel like a free license to feel superior to your peers. Once you understand all of the rules and intricacies of the Clean Code Movement, it can feel like a free pass to push the Clean Code rules on to others and the codebase. No need for code review or consultation, because you've already decided that the Clean Code Way is the right way and therefore there isn't anything to discuss.

> Much of the Clean Code movement is occupied with the idea that there is one, single, right way of doing things

Well if this is the case then they certainly didn't get it from the Clean Code book itself. The books very explicitly makes the point that there's no one right way and that the guidelines in the book won't apply to every situation.

That's in the first chapter. I'll quote it here because it seems many of the objectors to the book haven't read it or haven't read this part:

> Consider this book a description of the Object Mentor School of Clean Code. The techniques and teachings within are the way that we practice our art. We are willing to claim that if you follow these teachings, you will enjoy the benefits that we have enjoyed, and you will learn to write code that is clean and professional. But don't make the mistake of thinking that we are somehow "right" in any absolute sense. There are other schools and other masters that have just as much claim to professionalism as we. It would behoove you to learn from them as well. [emphasis in original]

> No need for code review or consultation, because you've already decided that the Clean Code Way is the right way and therefore there isn't anything to discuss.

I liken the CC movement to religion - it has good intentions, but when taken too far you end up with a holier-than-thou attitude.

> this feels completely orthogonal to the question of clean code.

For me, one of the interesting points of the article is not just that the replacement code was inferior, it's the recognition that the emotional compulsion to "clean up the dirty code" was a problem in itself.

Sure, as you say, if you refactored it in the other direction in this would also be problematic -- but there aren't many people who feel a visceral emotional compulsion to refactor code to make it less "clean". (Maybe brainfuck aficionados? Anyway, a rare breed.)

So I think there's something about the concept of "neat/clean code" that asymmetrically/directionally produces this error, and I took the "goodbye" to be giving up on this compulsive attachment to the concept of "all code must be cleaned", rather than saying that the concept of clean code shouldn't be used anywhere. The money quote being:

> Am I saying that you should write “dirty” code? No. I suggest to think deeply about what you mean when you say “clean” or “dirty”. Do you get a feeling of revolt? Righteousness? Beauty? Elegance? How sure are you that you can name the concrete engineering outcomes corresponding to those qualities? How exactly do they affect the way the code is written and modified?

It's really more about identifying the feelings that "dirty" code produces and realizing that they might lead you astray.

> "X considered harmful! ...I mean, if you're overdoing X"

I'm 100% on board with the general objection to this kind of thing, it's a pet peeve that I share. I just didn't get triggered by this one :)

"For me, one of the interesting points of the article is not just that the replacement code was inferior, it's the recognition that the emotional compulsion to "clean up the dirty code" was a problem in itself."

That's certainly a valid observation and a good point. I think the article would benefit if it the author analyzed this aspect more.

They author didn't focus on the rewriting without checking with their coworker part of the lesson, and I'm not sure why you are. It felt like they added it because it was part of the truth and mentioning is honest, but the bulk of the article was on the actual code changes he made, why they thought it was a good software engineering as a their younger self and why they no longer think it was good software engineering now.

I really don't see your point about the article being linkbait-y at all.

Agreed! The article seems more geared towards a lesson teaching teamwork and not being a code cowboy than about clean code.
The thing that bothers me about conversations in opposition to "best practices" or "clean" code" is that it misses the pragmatic aspect of those concepts.

WHY do we write clean code?

Because the value of software is its ability to CHANGE. Otherwise we would stick with fixed circuits. Best practices and clean code don't exist in a vacuum and they are not floating abstractions that developers should throw around as if everyone is on the same page with respects to what they mean.

They are a set of quality standards that allow for the rapid development, adaptability and ease of maintenance as a codebase evolves over time.

Clean code does not exist to slow or hinder development, or to handicap junior developers. It exists to maintain a team's velocity as the scope and scale of the project increases. Clean code is one of the crucial tools in the toolbox for avoiding death march projects. It it is not the only tool: an efficient management process, feedback loop, QA cycle, deployment process etc. are all critical as well. It is a team effort and code quality is the part that rests directly on the shoulders of the developers.

The one and only part of the blog/article that I agree with is the part about refactoring without making it a teaching moment for the dev who wrote it. That part implies they weren't doing code reviews, another crucial element in the quality control of the product being developed.

It blows my mind that these conversations are so often misunderstood. Us old timey senior devs have a professional obligation to make sure that the younger generation understands these concepts. Why do we see so many trendy medium articles and blog posts that seem to want to throw decades of hard learned lessons out the window? I blame the older generation for failing the younger ones. This stuff is not self-evident and has to be taught.

In the article the author said that his “cleaner” version had less duplicate code, but was harder to adapt to new usecases. His point is that clean looking code is sometimes harder to change, because while cleaning people introduce abstractions or assumptions that hinder new usecases rather than facilitating them. The right way to keep code maintainable is not always self evident, and not necessarily always clean (which I interpret as SOLID, deduplicated).
I didn't miss that, I just chose not to address it for brevity.

The problem that highlights is both a process and an experience problem. It indicates that he did not have a clear picture of the development road-map for the project, which suggests there are silos of information. There were a lot of hints about this actually. The lack of code reviews (that's where the original dev's opportunity for quality improvement should have been caught and addressed), the fact that a manager would chastise him for trying to improve code quality and the fact that he made poor design choices when trying to improve said quality really does point to a dysfunctional process.

Pointing the finger at code quality here is failing to understand the difference between the trees and the forest.

His example makes perfect sense... when the "naive" implementation duplicates stuff over 100 or 200 LOC.

I've had joyful experience of working with duplicate code that spanned over multiple thousands of lines. I still fondly remember a PHP script generating multiple xml files, supporting different versions of format. (It was an e-commerce format for exporting and exchanging products data). And with multiple inconsistencies among this copy-pasta that have accumulated over time.

The problem here is the boiling frog syndrome. Duplication rarely starts with thousands of lines. It grows over time, and the longer you put up with it, the harder it becomes to roll it back. But as long as it grows slowly, the frog feels comfortable.

Clean code approach may get overzealous, the potential for fanaticism may even be sort of inherent to this approach (I agree), but it stems for acknowledging this simple, sad truth. The truth is that complexity of naive code is kind of like cancer. If you don't detect it before it actually begins to hurt, it's not great news.

I'm from the older generation. I don't agree with a lot of what's in clean code. Particularly the focus on micro unit tests. Nor do I like the moralising tone of the books author in general. Also flexibility comes at a cost. A codebase where everything is easy to change becomes complex in aggregate, such that nothing is easy to change.
Don't confuse Robert Martin's book "Clean Code" with the broader concept of clean code.

"A codebase where everything is easy to change becomes complex in aggregate"

What you're describing there is a code smell that is commonly referred to as "Speculative generality" and was written about in a book by Martin Fowler called "Refactoring, improving the design of existing code." Which, incidentally is a great companion book to Robert Martin's Clean Code.

Speaking of trendy medium articles, here is one that I wrote on the concept of clean code that might help better understand what I mean when I use the term:

https://medium.com/@gspencley/what-does-clean-code-mean-anyw...

Overly abstracted code can be difficult to read, comprehend, maintain and reason about.

Thousands of small methods spread through hundreds of FactoryFactoryFactories and Builders and Adapters and Visitors can become a maintenance nightmare.

Overly abstracted code, or code that is a maintenance nightmare, is not clean code.

It's almost like you didn't even ready my comment, or completely glossed over the "why do we write clean code" part. Clean code is code that is easy to understand and maintain. If you're describing the opposite then you're not describing clean code.

Still, if the math itself was repetitive it can be moved into a library/module that the shape classes call into.
"first you learn the value of abstraction, then you learn the cost of abstraction, then you're ready to engineer"

- Kent Beck

Premature abstraction is the root of all evil.
There are only two hard things in Computer Science: cache invalidation and to know when not to use abstraction.
But what about naming things
Follows by symmetry. Proof is left as an exercise for the reader.
Thats an off by one error which naturally is included in all CS laws.
Maybe the wrong thing was invalidated from the cache of hard things in computer science.
There are only two hard problems in computer science: cache invalidation, naming things and off-by-one errors.
I liked this article about "semantic compression"[0] by Casey Muratori very much. He also posted some video lectures fairly recently about what he calls "non pessimisation" (a term that I personally didn't read anywhere else so far), which is mostly concerned with performance, but the idea is to simply avoid and isolate abstraction and indirection and programming in a style that adheres to what some people describe as mechanical sympathy.

It basically comes down to:

1. Write the code that makes the computer do what it needs to do to achieve a specific goal. No more and no less.

2. Refactor ("compress") the code by deriving re-used (implicit) structures and functions.

He puts a lot of emphasis on those steps being done in order and on deferring the second step

Note that this is from someone who writes games/engines/tooling so there is a large, real pressure to write simple and reasonably performant, optimizable code. He would also prefer that most of programming would be done in this way.

[0] https://caseymuratori.com/blog_0015

I cost myself a position recently arguing about this.

I had a first round interview that went well, and was given a take home and the problem was very simple, dead simple. In turn I tried to keep the code as simple as possible, with only necessary abstractions.

The second interview was with an entirely different person who seemed displeased I didn’t bloat the solution with all sorts of enterprise patterns, dependencies and conventions. I was kinda backed into a wall and while attempting to explain my reasons I ended up in an argument with the interviewer and it was clear we did not see eye to eye. He would later challenge my ability to perform in a patronizing tone and I was sure the opportunity was dead

It wasn’t the only thing that went wrong with that interview, but I feel that disagreement was the biggest killer.

Looks to me like you dodged a bullet.
Yes, but it is hard to find teams who are not into abstracting for abstracting sake.
It is a good way to sort out prospects, then.

I know people who actually believe a typedef for a pointer or atomic type is a Good Idea.

That would be your everyday experience. No reason to not be picky right now. I would go crazy having to work with over-abstract code for OOP religions sake
>In turn I tried to keep the code as simple as possible, with only necessary abstractions.

I always talked the talk and walked the walk for the interviews. I know people love Solid, design patterns, Dry, OOP, Restful, Clean Code, complicated architectures. So I am always willing to talk about these and even sprinkle the discussion with the most obscure design patterns they may never heard of.

Yes, I do consider some of these being fads or even detrimental to good software but my goal was to be employed, not to convince people that there is not only one True Way.

Having duplication does not mean your code is not clean. Sometimes, having duplication is actually cleaner, and easier to understand. Removing duplication may introduce complexities, and force developers to untangle the additional logic that was introduced to remove dupes. Good duplication means code just happened to be the same in a few places (but it can be potentially different). It's fine and easy to read. On the other hand, you may have code that MUST be the same in a few places, and updating it in one place but not others would break stuff. Those dupes must be removed.
IME abstractions generally begin with the latter case. Perhaps there's one or two corner cases that the abstraction also covers, but it seems justifiable at the time because the core of the code really ought to work the same across all cases. Then you slowly start adding corner cases, or you change your new feature slightly so that the abstraction has to change. Little by little you end up with this insanely complicated (and unclean) abstraction which is arguably significantly worse than simply duplicating the code would have been in the first place. I make this remark because I think that "the code needs to be the same in a few places" may not be a sufficiently strong reason for writing an abstraction right away. Even in these cases, sometimes it's ok to leave duplication in your codebase for a little while while you let the feature you're working on shake itself out, then come back and see if the abstraction is worth writing. That's the only way to break the cycle.
An issue in the duplicated approach is keeping track of the different blocks once they diverge enough.

In the best case scenario they all slighty change over time without forcing complexity on each other. But it becomes a problem as changes that should be breaking only break part of the code, and the rest can go unfixed as nobody remembers all the linked bits.

It would be critical for instance if the duplicated bits were involved in invoicing procedures, and one in five remained unchanged while the other got updated.

I'm not necessarily claiming that all abstractions are bad. I'm say that (as a general rule) engineers should be more willing to duplicate code.
Would you go with a simple comment/tag to denote both piece of code are identical but not abstracted away on purpose ?
I do this

// @note this code is duped in ../../some/other/file.code

I need to write an emacs helper to duplicate and tag in one go.
Some IDE's and external tools have duplication detection; I mean it probably won't be enough for people to go 'if I fix it here, I should fix it there' if it applies to both locations, but still.
It's not duplication unless the intent is exactly the same between the disparate code fragments.
>just happened to be the same >MUST be the same

This difference is something important to stress.

> and force developers to untangle the additional logic that was introduced to remove dupes.

Additional logic is only needed when code is almost duplicated, and that is where the question arises of whether what is happening is best viewed as a different “version” of some common process, or a different basic process that has similarities.

Actual duplication doesn't require additional logic to remove duplication; and even near dupes often don’t require distinct (i.e., branching) logic if the language offers the right abstraction facilities.

(comment deleted)
(comment deleted)
(comment deleted)
As an amateur programmer I do feel the same about my LaTeX projects. I would try to DRY my code into reuseable chunks, but eventually every use case is slightly different, and LaTeX is a terrible language to work with if you want refactoring
(comment deleted)
Clean code vs dirty code is not a problem here. Rewriting someone else's code (no matter how dirty) without telling or consulting was the bigger mistake. This can poison professional relationships for life.
that relationship arguably deserves to be poisoned, it's a relationship between toddlers instead of engineers.
You're lucky! Once upon a time I had a team lead that had the same priorities as OP. Code cleanliness was more important than the architecture of the solution. It was infuriating.
When we don’t feel confident in our code, it is tempting to attach our sense of self-worth and professional pride to something that can be measured.

I don't think assuming people who disagree with you lack confidence and are "compensating" is an effective way to reach an audience.

Rewriting your teammate’s code

There should be no such thing as "your teammates code", there is only your team's code. If the changes were improvements they should be welcomed by the team, if they worsen the code they should be unwelcomed - independent of who first authored the lines.

There is absolutely judgement involved in refactoring, code replication, and choosing the right abstraction. But this article doesn't offer much wisdom that helps with those decisions.

You should 100% talk to the original author before rewriting though. Not doing so communicates that you think you know better than them, so much better than them that you don't even need to talk to them to understand why they wrote it the way they did, and the tradeoffs. This is very arrogant and would most likely bother the original developer, which is bad for team dynamics.
I mostly agree with you but I've found that in certain (smaller) teams with a high level of trust and experience it can be fine to do this without a heads up.
I disagree. Most of the time I'm intrigued by the changes someone makes to my code. It's like a musician getting their song covered. There's a creative interpretation, often something I didn't see.

This one was a bad example. But past 5 or so years of experience, when someone has experience with other languages and coding styles, it's more interesting and I often learn a lot from them.

Maybe we're envisioning different scenarios here. If someone just committed code yesterday and is still working on it, absolutely discuss with them first. I'm imagining a scenario where someone comes across duplicated code months or years later. What if the original author is gone? Code needs to be clear enough to understand after the original author is gone.

Change for change's sake may be bad for team dynamics. But if the change is one that everyone agrees is for the better, no one should be offended by the improvement.

To be honest, i found these values touted, to be the signal of the worst kind of subjective code, leading to endless "battles" between subjective "better" code styles and bitter endless "but mine is more logic"-debates where the subjectiveness of ones opinion is not even perceived.

I had programers on lousy toolchains argue for the 9000 codeline one-file copy paste monolith, because it was "objectively" easier to debug.

There is always a teammates code, who will be different.

You might be projecting. That read like the authors personal experience, one that you might reasonably suspect others have shared.
There are always judgement calls to be made. Some of this is throwing out the baby with the bathwater. You don't want to be so rigid that you can't violate the rules for good reason. Also, refactoring someones code is not necessarily a bad thing per se, though it is better to come up with a style that everyone agrees with.
The author has took the wrong lesson from this experience. The issue was surely almost exclusively that done first year coder decided to change someone else's code without talking to them first. I don't think it would have mattered if the committed code was literally the worst code ever written (providing it wasn't going to delete the internet). The author refers to this but by calling the article "good bye clean code" they have completely missed the point.
As a bit of a tangent.

I've been a bit of an apologist.

I thought maybe people just took clean code a bit too far, that people were being too dogmatic about what was basically sound advice. Then I found Uncle Bob's old website the other night and I can no longer deny he's had a finger in this.

> _Anything not testable is useless._

> Here's the thing. If you can't test it, you don't know that it works. If you don't know that it works, then it's useless. If you have a requirement and cannot prove that you have met it, then it is not a requirement.

- http://www.butunclebob.com/ArticleS.UncleBob.AgilePeopleStil...

If these are the vibes you're putting out, your followers are going to be zealots. They're going to be frothing at the mouth whenever someone proposes alternative paradigms.

And yet, at least for the quoted part, he is right.

It requires some massive balls to deploy code to production while not even seeing if it works for any case.

If the answer to "how do you know it works?" is "trust my judgement bro" then I will not, in fact, trust their judgement.

I do agree he has a finger in the zealotry and he should probably have modulated how he expressed himself.

You know, they sent people to the moon using software written before the unit test paradigm.
Way, way back in the day, one of the best applications I've ever written had no tests. That application is probably in use today, in fact, even though its life is measured in decades. My takeaway is that tests are important, but good code is still more important.
What I really wanted to say is that there is more than one way of knowing whether code works. Unit testing is one paradigm.

Just because Uncle Bob, seller of unit testing software and TDD-books will hear of no other doesn't mean that this is the One True Way.

There's more to testing than unit testing.

I haven't actually read his site, I'm going by what was quoted.

Might be that he meant "unit tested" when he said "tested", I guess.

One of the best engineering (and life) tips I’ve learned is to beware anyone selling silver bullets
"let it go"! Everyone senior, lead, etc needs to understand this if they want a productive team.