loading...
Cover image for Clean code, dirty code, human code

Clean code, dirty code, human code

d_ir profile image Daniel Irvine 🏳️‍🌈 ・4 min read

Cover photo by Dan Wayman on Unsplash

Last week, Dan Abramov posted a very personal and humbling blog post entitled Goodbye, Clean Code.

I saw a tweet about this in my timeline and, being a long-term proponent of “clean” code, TDD and things of that ilk, I was naturally concerned. Here’s what I replied with.

I dislike Twitter because it’s so hard to find any nuance to arguments. So in this post I’ll explain what I mean by human code.

It’s easier to blame code than it is ourselves

I think it’s wonderful that Dan is blogging about deeply personal experiences in his career.

Many programmers who become team leads will have had a similar experience to the one he is describing. That time when your colleague wrote some code that you didn’t like so you rewrote it, because you wanted your codebase to be the best it could possible be. Then all hell broke loose. You offended your colleague, you made it awkward for the rest of the team, and your boss had to step in and sort it out.

At some point it dawns on you that being a team lead means leading from the back. That your team will only ever go as fast as the slowest person on your team, and your job is to help everyone level-up, not just yourself.

There were a bunch of people who replied to Dan’s tweet about his blog post with the same comment. Isn’t it interesting how common this experience is in tech?

Human code

We are getting to the crux of what I mean by human code. It is code that has been written with a people first approach.

I am unsure who first said the following expression, but I first heard it at the SoCraTes 2019 unconference. (Please let me know who said this, if you know!)

The two hardest problems in computer science are people, and convincing people that people are the hardest problem in computer science.

Isn’t that a wonderful saying? In my work as a software consultant helping businesses solve their software problems, almost always the biggest problem I see is interpersonal issues that stem from disagreements about project direction and structure.

Clever code

Another problem here is what does “clean” mean? It does not mean the shortest code, or the code with the most intelligent abstraction.

Take the acronym DRY (Don’t Repeat Yourself), which people misunderstand all the time, and then invent other acronyms like WET or AHA. We don’t need these acronyms. DRY is fine. It’s a topic that deserves a whole blog post on its own, but for now let me just say that there’s another term that helps understand the issue.

It’s the term clever code. I like this term because it brings to mind the image of the lone wolf, “10x” coder who is trying to prove themselves better than everyone else around them. This behavior is toxic. Clever code is toxic because it takes a disproportionate amount of time to read and maintain it. Clever code is a ticking time bomb.

And by the way, there’s no judgement here from me, because clever code is my default mode when I’m working alone. But I write much better code when I’m pairing with people. Working with others is a great way to block clever code from ever appearing.

So that is where the idea of clean code over clever code comes from.

But...

Clean code is dirty code!

Many of us in the software crafters community decided long ago to stop using the word “clean” to describe our code.

The problem is that by saying “clean” we are implicitly stating that some code is “dirty”. This can be very shaming for people. Particularly for beginners, it’s an example of the kind of word that leads to imposter syndrome, and a feeling that your code just isn’t code enough.

If you’re following the principle of human code then you want to avoid anything that could possible trigger negative responses in your colleagues, and that includes using the word clean.

It was Tobias Goeschel who first introduced me to the term clear code as an improvement on clean code. When I discussed this post with him, he reminded me that there’s a further problem with clean, and that’s the illusory binary distinction of clean vs dirty. All the code we write involves trade offs, and it’s not helpful to believe that there is always one right way of doing things.

Just like how DRY code isn’t a binary thing either. People hate on DRY because they believe it is a binary thing—it’s either DRY or it isn’t—but in reality DRY is just a gentle nudge in the right direction of code quality.

Yes, I care about code quality. But I also care about people.

Let’s stop judging each other. Let’s work together to create awesome software. 🤗

Posted on by:

d_ir profile

Daniel Irvine 🏳️‍🌈

@d_ir

I’m the author of Mastering React Test-Driven Development, available now from Packt. I run the Queer Code London meetup.

Discussion

pic
Editor guide
 

Yeah, honestly, I once got sucked into the clean code rabbit hole, and it nearly ruined my ability to program. I'm not exaggerating. When you become preoccupied with writing clean code obsessively, you redirect your attention from solving the problem at hand.

Nowadays, my process is simple:

  1. Write the code that gets the job done, no matter how ugly it may be, unless a better alternative is reasonably doable without investing more time.

  2. Refactor the code to make it easier to read and/or more performant.

That's it. I think that's all you really need.

 

I hate how it's so simple, yet I still find myself in that "rabbit hole". 🤦‍♂️

 

Yeah, I feel ya. And I know exactly how debilitating it can be when you spend hours searching for a "clean" solution to a problem, when you know you're capable of coding up a "dirty" approach in less time.

More often than I'd like, I'm too pedantic for my own good. It ruins my productivity. I should really work on that.

It puts into perspective why people "iterate" on some code. Initial implementation is expected to be dirty, but that's okay because "iteration" is a thing.

Perhaps that's where my pernicious obsession with "clean code" comes from: I would believe that "iteration" takes up more time than if I had just done it properly the first time. In reality, of course, I end up spending much more time than needed.

I can't quite recall what exactly, if anything, got me into that mindset to begin with, but it happened. Ngl, I'm still kinda dealing with it. There's nothing fundamentally wrong with writing clean code, but I think it's definitely a distraction and should only be a secondary concern, once you have something that at least works. Most of the time, people jump the gun and head straight for the Clean Code promised land.

 

I agree with your approach which involves those two steps. But I do the refactoring if there are enough time to do so. Currently I work alone on both front-end and back-end in a project that has a lot of modules; sometimes there aren't enough time to refactor certain codes in my project.

 

It's easy to drive guidances like DRY and "Clean Code" much farther than they were meant to. After all, in his book "Clean Code", Uncle Bob wrote:

"In general programmers are pretty smart people. Smart people sometimes like to show off their smarts by demonstrating their mental juggling abilities. After all, if you can reliably remember that r is the lower-cased version of the URL with the host and scheme removed, then you must clearly be very smart. One difference between a smart programmer and a professional programmer is that the professional understands that clarity is king. Professionals use their powers for good and write code that others can understand."

 

I definitely like the idea of switching 'clean' code to 'clear' code. Another term I've been using is semantic code. You see semantic used a lot when referring to HTML, and why using a div for everything is not helpful. I also think the same applies to our code.

 

So you are arguing that "clean code" is implicitly stating that some code is “dirty”, and suggest to use "clear code". The same argument could now be made that some other code is "unclear", "muddy", whatever => you "shamed" a person. In other words: if you believe in the ideology that people are fragile and easily offended (lets call this "fragile development"), then the same "offense" can be achieved with the words "clear code".

 

Not quite. Firstly this has nothing to do with being fragile or easily offended, it’s got everything to do with the skill of being able to communicate feedback about quality issues in a way that keeps the recipient of that feedback on board with the message you’re trying to get across. Admittedly this skill doesn’t come easy to many developers, since it’s one of those hard people skills, but it’s an important skill that mature developers will nurture and develop over time.

Secondly, having taught many bootcamp grads as a coach and mentor, I’ve always found that saying ”This code isn’t clear to me” gets a much better response than ”This code isn’t clean”.

One is a statement of fact that puts both teacher and student on an equal footing. The other is an unnecessary judgement.

 

saying ”This code isn’t clear to me” gets a much better response than ”This code isn’t clean”.

I'm so glad you highlighted this difference with an example from a human:human interaction 👏

I think that's something we often forget, that these terms and principles are meant to help us communicate more effectively with the human readers of our code, not (or not primarily) with the machine and our internal ego.

 

I really like that perspective. Thank you for sharing.

 

When code sucks, we should not say it doesn't. No need to be mean, but politeness cannot be used to erase the truth. Besides, how can a beginner learn to code better if we are too scared to point the problems in their code? To be human is to err, learn, and do better. Blocking critisicm because someone feeling might get hurt is doing them a huge disservice.

 

I don’t disagree. I used to feel this way. It’s just that at some point I realised that at this low level of line-by-line scrutiny it doesn’t really matter. A more important axis is time. Does the codebase improve over time? Does their coding style ‘improve’ over time? I say improve in quotes because what I really mean is does their style become more like my subjective idea of style over time?

I’d rather teach people to code like me by showing them my code than by spending time/energy poking holes in theirs.

 

I get your point, and I agree about style - Style is a matter of personal preference. There are also different "schools of thought" each with their own, respectable, set of principles. We should not fight a war to "convert" people with other principles to hold our own.

But there exist code that is objectively bad. It's not a matter of style, it's not a matter of different principles, it's a matter of lack of principles.

When code cannot be read by human beings, when it misleads and hides the developer intent, it's an objectively hard code to maintain. When code contain wrong abstractions, when it is littered with unnecessary code duplication, when it contains huge spaghetti methods, when it is riddled with hidden dependencies, when it is so brittle that touching some part breaks some distant completely irrelevant other part... It's just plain dirty code, filled with bad practices.

Just like there are good principles, there are also bad practices.

And we should not be afraid to say it.

 

I love it!

Human code > clear code > clever code > dirty code

 

if It gets the job done, then there’s really no point in arguing about it.

Unless there is some job you think is missing, verbosity, performance, testabillity, future proof?

If that’s the case it can be explained in an objective unaccusatory tone.

I often leave optional‘ comments in PR‘s where I point out verbosity semantics, and other nice to have things, but I make it clear they are optional.

And then I say we’ instead of you when I point out things I want changed.

Saying we’ and shaping change requests as questions or requests is a great way to review in a more casual way, bonus points for remembering to include a why or a link that describes what you are getting at.

Ofc. this goes both ways, as PR makers we gotta remember, this is not a war, the reviewer is there to help. Regardless of how they might accidently come across.

 

Thank you for this.

It’s probably just a convenient excuse to be lazy, but I very rarely comment on PRs. Only if I spot an obvious functional error or code that will perform badly. Anything else can be improved in a later PR, if necessary.

I prefer to pair/mob when I can so that code can be improved at the moment it’s written.

 

As with most things, there's all sorts of intersections and cases to consider on this spectrum and what is refactoring for simple clarity/cleanliness versus refactoring for good architecture and reduction of tech debt.

We've all inherited a code base, or written one (like me), where the authors leaned towards 'it works' and wrote code that quickly achieved the intended goal at the time, and then was grown to handle various new requirements. At each iteration a new 'solution' was derived to add widgets 1, 2 and 3 and the code marched forward.

When we, those who come after, then take our turn, we find ourselves deep in the technical debt that was left behind. In those situations, it feels certain that a refactoring like the one Dan Abramov describes would have made our job easier. At some point, someone with enough context and sense for the direction of the project could have reorganized the code in a way that is demonstrably better, that establishes a pattern for extension that we, the people having to add widget4, would be able to quickly grok and build upon, leaving a well ordered and easily understandable chunk of code for the next developer.

Then there's code like Aleksandr below proposes - code that was written quick and dirty, but then thoughtfully refactored for readability or performance - better method names, smaller methods, fewer allocations, added some caching, etc. And this can be great, all that is necessary, except sometimes it's the overall design and structure that becomes limiting regardless of how clearly this chunk attains the localized goal - it may do the same thing almost as well as other code that was written somewhere else or perhaps it could have been segregated or composed in a way that made it easier for similar components to reuse in the future, or it could be clear and quick to write, but it doesn't fit in with patterns established everywhere else (hey, this thing is talking directly to the database, but we have a repository for that!)

Pernicious smells and debts can creep in this way. You can find yourself with a whole lot of code that does what it's supposed to do, and is clear in that purpose, but still is difficult to extend without becoming an expert in the entire system. Or you can end up making improvements that only help there instead of everywhere like there.

There can be value to slowing down and defensively refactoring out repetition across a code base or being more thoughtful in the design, balanced against over engineering. That's the art, choosing enough good design that you leave the hints and the intention for the expansion, so when it's time to grow the system those people looking in with fresh eyes can see where and how it can be most easily extended.

For example, if I can see that a system I'm building has a clear place where a chunk of functionality could be abstracted out, say in a strategy pattern, even if there's only one such strategy now, I may still elect to take a small amount of time to build in this pattern now, because I have the context and understanding to know that this piece is replaceable. Later someone else looking to build a new feature may be determining how difficult it will be and come across this 'hint' and think - "oh, that piece there is 'swappable', which means my feature can simply implement a new strategy there and away I go". It may mean the difference between a feature being 'feasible' or 'a lot of work'. Though on the other hand, someone may never come back to this code and need that extension point, so it's a judgment call - but my feeling is that over time, without refactoring for extension or pulling out the commonality, systems become hard to reason about how to extend them and about how hard that extension will be to implement.

 

Be pragmatic. A lot of code does not survive the first year of its existence. Writing clean code at least gives it a chance but sometimes getting things up and running is more important than over-engineering it (which is what clean code fetish can escalate into).

I like to think in terms of disposable code. Lets be real, if it's frontend code, it's going to be replaced in under 2 years. Also most of your team will have moved on and be replaced with a few senior full stack guys that have not yet reached the ripe age of 28 and will be likely to want to try whatever is fashionable. Exaggerating here of course but front end code has a short shelf life in my experience. I don't care about how clean it is but it should be testable, robust, and work as advertised. These three together typically amount to the same thing but they are a bit easier to reason about than "elegance" or whatever.

For backend code, do something simple and conservative that is likely to be still valid in five years because a lot of backend code never gets retired. So doing it right is more important. If it gets retired, it's because something went wrong. Replacing the backend in a company tends to be disruptive and risky for the company. A lot of money in our industry is in patching up existing backends.

 

Hey, Daniel!

Thank you for sharing this with us.

If you’re following the principle of human code then you want to avoid anything that could possible trigger negative responses in your colleagues, and that includes using the word clean.

So true!

 
 

I love the idea of human code.
I was once contributing to a project mostly written as clever code. Not so clever after all. It was very hard to understand all the bits and bytes. Hence, let's focus on the people!

Thanks for sharing your thoughts, Daniel

 

The whole "a colleague wrote some code that you didn’t like so you rewrote it" is something I have seen numerous times in my career. As a team lead, I struggle with explain to the team member why is is NOT a good idea. Don't get me started on the numerous example of "clever" code that I have had to review.

 

Yep, I did it. Hopefully we all do it just once and never repeat the same mistake again 🤣

 

I've been thinking in a similar direction about too-narrow definitions of "good code". I've found it good to distinguish between goals of efficiency, readability, flexibility, and brevity.

A few weeks ago wrote this post elaborating:
dev.to/wanderingstan/good-code-bal...

 

Thanks for sharing the post. I really like these four distinctions. Any model that helps us think/reason/communicate more clearly about the more non-trivial aspects of our code is a win for teams.

 

The key is to be pragmatic about writing clean code.

Generally I ask myself 'if I, or someone with the domain knowledge, comes back to this code in 3 months time will we understand it?' If the answer is yes than probably the code is clean (clear) in the true sense.

Sometimes we have to accept some duplication in favour of readability, but then I tend to equate clean code to readable code reflecting the language of the business it is built for so maybe I'm biased :-)

 

I have a question. I'm in a team that doesn't follow any standard, we just write as we think is well. This may be is a company problem who never said us that we must follow this convention but, leaving that aside, how I can say to my fellows "Hey our work will be easier if we write following this convention, standard or avoiding the dirty code" without say dirty and without offending anyone?

 

There is so much to say about this topic. First thing that comes to mind is do you think introducing coding standards would be possible? If your immediate reaction is that this would be difficult, then why? For example is it that you think no one would ever agree to a standard? If that’s the case, start with just a few rules that you know everyone will agree with. Perhaps have a weekly meeting to discuss new rules. If even this seems difficult, then perhaps you have to work on building trust with your teammates so that they become more open to persuasion.

None of that is going to happen overnight, so at the same time you need to figure out a way to make peace with the code you dislike that is invariably going to be added to your repositories in the meantime 🤣

 

I thinks building trust is the best path. Today we have a meting, will se what happen. Thanks dude!

 
  1. The quote is generally attributed to Phil Karlton. With a variation that's been added:

There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors.

  1. More money is spent on maintaining software than on writing new systems.

    It is more expensive to write and deploy crappy code than it is to write and deploy code that is "good" code. "good" depends on the context. But you have to write code knowing that someone else will be reading and understanding and changing it in the future. That's where most of the costs lie.

  2. What you're generally talking about is the field of software engineering That is (IMNSHO), the coding implementation is just one part of the process of producing a part of an entire system that works correctly, is robust (handles failures appropriately), and is maintainable. Furthermore, the implementation happens in a context where time and resources cost money.

If you expect that the system you're working on will generally be thrown away in about a year, then the cost of doing line-by-line code reviews so that everyone on the team agrees and works to a particular standard is going to be much less than a system that you want to survive for a number of years.

If you are working on an airplane guidance system then the cost/benefit of code reviews is much different. Not only does your code have to be absolutely correct, when (not 'if') someone needs to change your code, it better be straightforward to make changes and not be so convoluted and brittle that any change will break it.

IOW, context matters. The cost/benefit analysis should guide you to what level of what standards are needed. Sometimes line-by-line code reviews are totally worth it and required. Sometimes they're not.

Version 1.0 of a system is much easier than version 3.5: in version 3.5 you have to deal with all of the mistakes that were made in all of the previous versions and all of the things learned since then and all of the changes in the world/business/area that have happened since then.

People have been thinking about and studying this and writing about this for decades. When developers start to grasp this, they have taken the first step to moving from "coders" to "software engineers."

 

I’m not sure if you’re comment is directed specifically at me (the author), but just in case it is:

The quote I’m talking about is not the infamous one you’ve given an attribution for. I’m talking about the variant which speaks to the larger of our industry’s problems today--i.e., people. One of the reasons this quote is magical is because it shows how outdated the original is.

Software engineering principles are generally not a match for today’s programmers given the industries they work in. If you haven’t already, I suggest you read Software Craftsmanship by Pete McBreen which will make clear some of the advances in thinking about software development in the last 25 years or so.

My point with this article is two-fold:

  1. To highlight that it’s extremely important to pay attention to messaging when you’re in a position of authority.
  2. To highlight that, for those of us who care about the notion of clean code, our thinking has evolved since the days that Kent Beck, Martin Fowler et al first showed us how to write better code.
 

You're right -- I haven't read Software Craftmanship yet. But now I will.

But I also think that maybe my definition of "Software Engineering" is more expansive. My view of Software Engineering includes people and their behaviors and wants and needs and humanness as a major part.

Teamwork is a critical part of pretty much every software engineering process. And so if the leader of a team is not a good manager or leader, that negatively impacts that part of the process. If the leader of a development team isn't able to determine what level of code review is appropriate -- if the leader is insisting on line-by-line reviews with very rigid rubrics and that is not appropriate, then that will negatively impact the team and the code developed. That is all part of the software engineering process.

So I totally agree with you that

"it’s extremely important to pay attention to messaging when you’re in a position of authority"

because that is part of software engineering. Everything involved in producing and deploying software -- whether it's writing it new from scratch or working with code that someone else wrote 5 days ago or 5 weeks ago or 5 years or 15 years... -- is part of software engineering to me. It's not just about writing code -- it's much larger than that.

Writing clean code is just one part.
And people are always part of the process. (developers, users, stakeholders, investors, ...)

 

Human Code > Clear/Semantic code > Clever code > Dirty code

I like that. I guess I personally go from "clever" to "human", depending on the situation. Most often in the middle ground.

 

I think this is a vital message really.

It's much too easy to get bogged down in some sort of perfection mode, when your job is really to solve a problem in a usable way. I have had peers who spent weeks refactoring and rewriting code to solve a problem or add a feature, which should have taken a few hours to complete. They got stuck in making it perfect in their minds.

You might even call it premature optimization. You still need to keep good practice in mind as you are working, since you don't want to make your life difficult later, but there is no way you can know how to make it perfectly "clean", until you are further along in the process. Spending too much time on perfection early on, is pretty much YAGNI.

I'm old school, I really think the simplicity of "make it work, make it right, make it fast" gives you the best of both worlds.

If you avoid the pedantic need to make is perfect the first time, or make your teams code all conform to your clean ideal (which is pretty much an impossible task), then you will also cause much less friction, because every good team refactors each others code, as they come across it, but rewriting all that code is not a team exercise. You might as well be doing it yourself. You lone wolf you.

 

My code is so muddy it needs a good wash, gets rubber gloves out (the pink ones)

 

I digress from the topic but I can tell that you are of the ENFP personality type. Thanks for the clear thoughts.

 

Good guess but not quite! 🤣

 

code quality != clean code != clever code
As long as it is maintainable and readable for the people I left with, it shall be fine with me :)

 

Let alone for other people, I think, even for ourselves, the code should be readable and maintainable. Say we leave our code for several months and then return. If we don't struggle to understand the code we left, then it's fine at least for us.

 

this is great ! lets stop judging code, distros, ides AND os's and just create and share beautiful new things !

 

I don't know ho said it, but "everybody can write code that a computer can understand, the hard thing is to write code that other developers can understand". :)

 

Thank you for taking the time to make this content.

 

Wow, very Taoist approach.

It's fascinating to me, how much coding is spiritual.

 

This post is strongly on point and awesome. Permit me to repurpose it has an audio-visual content and you will be credited for the job well done. Am I permitted?

Thanks in advance.

 

Send me a direct message and we can discuss it. Thanks.

 

It seems to me that Dan Abramov wrote exactly what you did. Maybe he used different "poetry" but the "findings" are the same (to me).