> But these traits are in some ways at odds with each other. The most simple code is probably not the most testable. All those interfaces and injected dependencies make for convenient testing, but have a cost in terms of simplicity.
Exactly
The same code snippet might be good in one context and an annoyance in another context
Here's where "generic rules" fail. Like python's avoidance of lambdas and favouring just having functions. Well, but, depending on the context, they make a lot of sense.
"Oh but lambdas are less readable" not if you need 20 different short functions and you need to combine them. Too implicit and it's hard to read, too explicit it gets boring and also hard to read.
20 short functions definitely sound as though they should be explicit. Named, documented, testable. 1 or 2 you could get away with being implicit. 20 requires a lot of understanding as to what's going on!
Do you test every line of code you write separately? Probably not. You test a function that has 5 lines of code.
Same for anonymous functions. You test the functions that use them and that's usually enough. If not, then that is a good indicator of separating them out into named functions.
This is the wrong way to think about it, though. A function encapsulates some behaviour, regardless of how short or long it is. You don't test each line (directly); you test each function's mode of operation. So a function with one if statement in it potentially needs two (happy path) tests.
I don't think we disagree. It's just that a lambda doesn't change this. If the lambda itself also contains a branch, then yeah, you should probably test the outer function with at least 2 inputs.
I agree, except I think that a lamdba is an arbitrary line to draw for that as well. Why not stop at the main function and give it a load of inputs? A five line lamdba looks a lot like a named function, just harder to test, reuse, and debug in a stack trace.
> 20 short functions definitely sound as though they should be explicit.
And you are right that it's pretty arbitrary when/if a lambda should specifically be tested or not. But the number of lambdas in a project isn't really a good factor to make that decision - it's individual for each function that contains a lambda.
Well, I more meant that if they're "short" as opposed to "20 character one-liners" then it sounds as though they should be tested, whether or not they're defined using lambda syntax.
* if anyone not get it, ts/js can declare a key-value lambda like:
let myFunc = {f1: () => , f2: () => } and so on. It can be called by string key like myFunc["f1"](), and all it's props can be get by Object.keys(myFunc) so it can be randomized easily.
Not that I am taking a stance here, but you didn't really engage with the GP saying that having functions instead of lambdas allows one to name, test and document them: in fact, they might not be called f1 to f20, in fact they might have docstrings (I guess one could comment lambdas, but see it rarely) and you couldn't test lambdas unless you assign them, i.e. give them a name i.e. turn them into functions in all but name.
So here's some TypeScript code I just made up, with a lot of lambdas. It's somewhat typical of code I write all the time.
books
.join(authors, book => book.author, author => author.id)
.filter(([book, author]) => author.lastName === searchText)
.map((book, author) => `The Book ${book.title}, by ${author.fullName()}, has ${book.chapters.count()} chapters, totaling ${book.chapters.sum(chapter => chapter.pages.count())} pages.`);
There are 5 lambda functions in there. Can you tell what the code is doing? Is it correct? Yes, and yes. This is the kind of code that, in my opinion, doesn't need tests at all, nor comments. You can look at it and understand what it's doing and know that it is doing it correctly, as long as it compiles. If it's mission critical, you should test that the join really should be on book.author and author.id, but you need to know the correct answer to write the test, so why not just look at the code and verify it's correct? If your answer is "because another change might break it": no, it won't! Given the preconditions, that code is correct, and no other code can effectively break (and still have your code compile) it without lying to the Type-checker. If someone breaks that code, it's because they're intentionally changing it to do something else, so they'd redline any tests you wrote for it anyway.
It sounds like you're suggesting this code should be more like:
/// <verboseDocs purpose="Verbosity">
/// <purpose> Gets the author of a book
/// <returns> The author of the given book
/// <inputs>
/// <input param="book">The book to get the author of
/// </inputs>
/// </verboseDocs>
function getAuthorOfBook(book: Book) {
return book.author;
}
@testMethod()
function canGetAuthorOfBook() {
const mockBook: Book = {
author: 'Test Author',
title: 'Verbosity',
...
};
Assert.areEqual('Test Author', getAuthorOfBook(mockBook);
}
...
/// <verboseDocs>
/// <purpose> Takes a last name and returns a function that returns true if the given (Book, Author) tuple contains an author whose last name matches the given string of the outer function.
/// ...
function getBookAuthorPairPredicateFromAuthorLastName(lastName: string) {
return function(pair: [Book, Author])
return pair[1].lastName === lastName;
}
}
And on and on and on, still needing the original code, but just a lot more obfuscated:
Now I have no idea what the hell this code is doing and whether it's correct or not. Note that to avoid lambdas in the filter (filtering by a value in the closure) we need to write a function that returns the predicate we want to test on, instead of just sticking the right thing in the right place to start with. All to avoid a single "=>".
I appreciate the large amount of effort you went to to get your point across; that helps with actual discussion, which is awesome.
The context I was talking in mentioned Python not having lambdas. It does have lamdbas (there's even a keyword) so I assumed it meant larger, multi-line lambdas. The code you wrote (glueing together a few mini-lambdas) I would not expect to test independently. I'd feel bad that you can't use SQLAlchemy's even nicer syntax, but I certainly wouldn't expect a test for each of those little things.
Coding is a highly subjective and creative endeavor. "Clean code" is akin to "well written" for writers. Sure, you can analyze and even be able to define some good practices, but because we are always creating something new that has never done before, and the field is infinitely complex, no rules can be set in stone and applied across everything.
In my opinion there's nothing wrong with calling code clean, we don't have to analyze every line of code we write. There's just too many things to consider and viewpoints to take, we'd be trapped in analysis paralysis forever, as you can continue it to no end. There's never a point where the analysis will be "done". You just have to be able to recognize when the analysis reaches a point of diminishing returns, and when moving on is the better option. Sometimes just calling it "clean" is enough.
I'm not giving up on clean code, but at the last 5 years I pursuit separation of pure function and unpure function more than clean code. It makes easier to debug / unit tests.
Writing is a great metaphor: there aren't any hard-and-fast rules and there's certainly a subjective quality to it... but also, some writing is clearly better and some writing is clearly worse. Maybe there is no such thing as "good" writing because writing can be "good" in different ways or because we can never fully define what "good" means, but that doesn't mean that all writing is equal or that "everything is a tradeoff"—and, like you said, it doesn't mean we need to deeply analyze every piece of writing to draw useful conclusions about it.
My view is that while there is definitely a subjective aspect to quality—whether in code or in writing—a larger portion is objective, just hard to formalize or quantify. Just because we can't measure something directly or because we can't all agree about it in every instance doesn't mean it's subjective. There are a lot of things we can't know exactly but that are still objective, so why not writing or code quality as well?
> There are a lot of things we can't know exactly but that are still objective
Hmmmmmm not sure about that. AFAIK anything that we either can't measure objectively, or don't yet know how to measure objectively, is a subjective measurement, even if that measurement is subject to an objective apparatus (observer effect experiments; the measurement is subject to the circumstances of measurement)
Totally open to being wrong and you being right tho. Any examples come to mind for you?
> some writing is clearly better and some writing is clearly worse
This is a presumption, and one that I don't share
Like music and other forms of art, different styles appeal differently to different audiences. Many people find the language that 8 year olds write in to be terrible, and some people find it endearing and honest and meaningful. Software is no different in my opinion
Writing would be a great metaphore only if you write the code alone and when you declared it finished, you never need to come back and add new things/remove old things or fix bugs in it.
I don't see how that invalidates the metaphor. For any version of code you can evaluate clean-ness. Writing is also not immutable, professional authors make hundreds of versions before publishing.
Of course books get 1 major release. So while ability to maintain and change is much more important in software, that is by far not the only factor for clean-ness, and I'd say hardly the most important.
> Writing is a great metaphor: there aren't any hard-and-fast rules and there's certainly a subjective quality to it... but also, some writing is clearly better and some writing is clearly worse. Maybe there is no such thing as "good" writing because writing can be "good" in different ways or because we can never fully define what "good" means
There are different writing styles (and so guide styles associated) for different objectives. Eg, journalism, with a pyramid model designed and refined to ease the process of assimilating information which is different from the playscript style model designed to give general instructions on how to flesh out and enact events.
Both these styles (I intentionally chose for their more obvious rules and usage) have different hard-and-fast rules and applying one instead of the other wouldn't work.
Now, there will be a subjective quality but there are also objective qualities to writings produced with these styles (structure, count of words, choice of wording for description, timeline of events reported, etc.).
Now, of course regarding novels, storytelling, the newyorker, writings that are intended to be read, alone, to tell a story, to convey more than facts and the most objective description of reality yada yada.
And then there are technical manuals. I think code is a technical manual for two kind of audiences: humans and computers, so compromises are made and rules for clarity and brevity can be layout.
> There's just too many things to consider and viewpoints to take, we'd be trapped in analysis paralysis forever, as you can continue it to no end. There's never a point where the analysis will be "done".
I agree with you there - but this makes it even more bizarre that there are now tools (e.g. SonarQube) trying to automate this analysis. Of course, linters are a undeniably useful, but stuffing arbitrary rules into a tool which then gives grades to your code based on a strict interpretation of those rules is a bridge too far. I mean, can you really take software that uses terms like "blocker code smell" seriously?!
I have no experience with SonarQube. I guess it's like a linter that's slightly higher level? In that case I'd guess the appeal is that it's reducing the cost of often repeated analysis that is usually done manually, not that the tool itself would make your code great to read.
If that's what it is then I think such tools are "a good servant but a bad master". It does not replace manual analysis, but can be used well to reduce costs of removing common code smells. It still requires a human to work as a judge.
an important thing to realize with this analogy is the difference in purpose between code and writing. Writing is meant to be read whereas code is meant to specify a program. For people who read code like writing this makes sense but when the focus is more on the resulting program the concerns change a lot.
Like writing, code gets better with mulling over most parts (for a long time) and rewriting a lot after insights were properly formed. When you start something, you are exploring; how can you expect that that code will be anywhere close to an ideal situation that time? People say refactoring but in my opinion that is not radical enough; you will be able to redo problems you encountered by maybe completely re-architecting the solution. This luxury is not really a thing many people can afford ofcourse; we just make stuff work well enough to get paid. And it shows.
Theres definitely clean code, theres just no rule book for how to define it or course you can teach for how to write it. Some people are just better at the art of writing simple software. You definitely know it when you see it.
I agree. The article (well the little I read of its unreadably thin font) doesn't even really argue that there's no such thing as clean code, just that it's not very well defined.
Well sure, but that's like saying "there's no such thing as beauty" or "there's no such thing as a well-written book". Clearly bullshit. They might be somewhat subjective and have no mathematical definition but they clearly exist.
The author isn't arguing against the existence of "good" code or "clean" code but rather advocating for us to try to articulate what is good (or bad) about code because "clean" is vague and encompasses too many things.
In response to:
> You definitely know it when you see it.
the author suggests:
> It’s good to build that intuition, but we can’t just stop there. We need to dig deeper beyond those feelings to understand and articulate why we think the code is good.
In my 5th role where I write code, and I have dealt with so many different approaches and definitions of "best practice" that at this point I just roll over to whatever the generally agreed approach is at my current org and mimic it as best as possible.
In one organization, I have even had two seniors go back and forth telling me to remove what the other senior told me to put in.
The entire process of defining "clean code" seems painfully arbitrary.
Similarly, the existence of so many subjective and unarticulated views on "code correctness" has left me in a place where the only certainty is: there is no certainty.
If your best practices are so great, make a sound business case for them. Uncle Bob did no such thing.
> In one organization, I have even had two seniors go back and forth telling me to remove what the other senior told me to put in.
From: You
To: Senior1, Senior2, Senior1s_Manager, Senior2s_Manager, Your_Manager
Subject: Duke it out between yourselves
Senior1, you told me to put in Feature1, Feature2, and Feature3. A while later, Senior2 told me to take them out.
Senior2, you told me to put in Feature4, Feature5, and Feature6. A while later, Senior1 told me to take them out.
My job is to implement worthwhile features, not to be an implement in your quarrel over which features are worthwhile to implement. Duke that out between yourselves, and only then give me the ones you can agree on to implement. Thank you.
Your_Manager, I've looked over my job description. It says my job is to implement worthwhile features, not to be an implement in other people's quarrels. Yours says your job is to back me up in situations like this.
Senior1s_Manager and Senior2s_Manager, maybe Senior1's and Senior2's job descriptions need to be clarified with regards to which features each of them is empowered to request or veto.
Just because "clean" connects to a lot of concepts doesn't mean "clean" code is meaningless, any more than clean dishes. A plate smeared with tar is not clean, no matter if the tar is sterile, and a plate which has been sprayed with salmonella isn't clean, even if you can't see the contaminant. These are different dimensions of "clean", but still very much meaningful.
I personally like "clean" as a term for code quality. We've all seen how focusing on easily measurable metrics (like code coverage) can help, but in the hands of the saboteur or the clueless it just becomes a meaningless number. "Clean", instead, focuses on the ultimately human judgement of how to make code which is fit for purpose.
It was never intended as a definitive _thing_. I don't think anyone believes it has an exact definition. Just like good writing. There is no canonical good. Shakespeare and Hemingway are both "good" but absurdly different. But still, "clean" is a useful abstract concept for us to aim for in each context we encounter. At the end of the day, we need some kind of way of talking about "desirable" or "good" code. And I feel like 'clean' is as good as any other word.
The author lists a litany of traits that could be considered "good" things to aim for, but most of them are pretty granular, fuzzy, or superseded by others. The author says:
Words like ‘encapsulated’, ‘testable’, ‘mockable’, ‘reusable’ have meanings that we can all agree on. When we use more specific words that describe the various code traits that affect our project then we can be sure that we’re all on the same page.
Unfortunately, however, we're never really on the same page. Even these more precise terms are very context specific and amorphous. "Testable" may mean granularly accessible, modular, mockable, idempotent, or quick to spin-up for CI, or only a subset of these. Highly dependent on testing approach too (unit, TDD, BDD, E2E...)
This is why I like thinking in terms of tenets: broad concepts that are applicable to everything but enable context-specific meaning. E.g.
- Reliable
- Efficient
- Maintainable
- Usable
With these tenets, for example, we can then *contextualize* and find the "cleanest" code for whatever particular application we're working on. If we want to break these broad tenets down further then we can, and I hold the position that this _is_ very useful:
Most of these are independently VERY context-specific. Being time-efficient is about the available hardware, interfaces, UX, etc. Being accessible or usable is about WHO is going to use or access your abstractions and underlying implementation.
To achieve these tenets, there are many principles and approaches to draw on to help us find the 'clean' solution each time: E.g. The Law of Demeter (LoD), SOLID, The abstraction principle, Functional programming, etc. These exist as tools to help us get closer and closer to the unreachable (yet desirable) goal of "clean".
So, I suppose I agree with the author but only semantically. Cleanliness does not have a fixed definition, but it is still useful to talk about as a goal or absolute to aim for. A bit like all manner of other superlatives.
> Readable, Understandable, Simple, Performant, Safe; But these traits are in some ways at odds with each other.
No, they are rarely at odds with each other. Readable code leads towards performance and safety naturally. Writing readable and simple code means that you understand the problem domain well, and the amount of abstraction needed for tackling the particular problem at hand. Readable code is also much easier to analyse for performance and safety.
After suffering in the coding world for a bit now, the only clean code I need is the code I can easily change or use. Code formatting, style, naming, but even architecture and all forms of best practice cargo culting I do not care at all, if it does not make things easier.
The company I worked for had a separate team, kind of a startup, and these guys were all in on discussions how to do things cleanly -- is it a code smell? -- but everything they did was overengineered and borderline useless.
If the things you do to make code cleaner make it more difficult to change things, or even to use it, you are doing it wrong. And I've done things wrong plenty and mostly still do, but I want to improve and what developers usually emphasize does not make things easier.
"What Is Clean Code?
There are probably as many definitions as there are programmers."
Robert C. Martin - Clean Code A Handbook of Agile Software Craftsmanship - p. 7
Clean code is code that does what you expect it to do without many surprises. It is simple, not clever. Effortless to follow. Each part handles one idea at a time, at the same abstraction level. Doesn't force you to mentally juggle many balls at the same time.
The code often tells you a story, it communicates how the programmer (author) described the problem, the solutions and the trade-offs. Very similar to writing.
Maybe it is a vague term. But it is not an excuse to write "bad" code. Perhaps every team need to have a "definition of clean code" for themselves.
An entirely subjective measure. I want to solve problems, not read code. I can read your code and understand it, but then still have to solve it to understand what problem you are solving.
In my work history, I've never come across code like this, especially "effortless to follow". All the codebases I've worked with have been head scratch causing balls of mud.
Am I unlucky or is what you are describing the rare exception?
Most large software projects have some bad code in it. If it’s all bad, then there is probably an architectural problem, poor code review practices, and/or corners are being cut to meet deadlines.
>but that lofty height of code described is an extreme rarity.
I would say lofty height only possible in following conditions:
A model of reality / problem domain that the company has created themselves that does not have to integrate with any other company, or have to follow any laws or regulations.
For example let us suppose you create a company for users to send messages to each other using your app. You can totally control everything in your environment - just your app, your definition of users, you definition of messages. But once you need to connect your app to other apps doing similar things but differently than your model you are going to hit edge cases, and if you have to think about making your app work on multiple environments and there are problems as there generally are you will start to be less lofty, and then after you have been going a while you try to enter a market with regulations, or regulation is handed down affecting your app.
Things are getting less lofty quickly at that point.
There definitely are codebases that are easy to follow.
But it requires a team that is committed to aggressively refactoring even the smallest of code smells, usually before even committing it. It also requires a team that is dedicated to its professionalism and not bend into a manager's will of refactoring being a time waste.
But when your team is committed to code quality, holy hell is it satisfying, easy and fast to work with. It really is like night and day. If you have not experienced the difference - I'm sorry to say - then you've just not worked with a high-quality team.
Same even for code base my only job was to cleanup, it would be clean 1 week then split back into mess, sometimes by my own doing. The problem is always the same: there is money for result and result is time-sensitive, no money for form and form takes time. If I have to sacrifice a bit of form to reach a result on time, I'm afraid I'll do it rather than fire a colleague because we can't pay him and do beautiful code :s
Most corporate codebases I've encountered are, to put it mildly, unpleasant. In most cases, you've got dozens of people with varying skill and style maintaining the stuff over a decade or more. New business requirements are patched on top, without ever considering the whole architecture.
The cleanest codebases are those where the project has a focused goal and is maintained by a few developers with a low churn rate.
What you can easily follow though is largely a function of your programming background. An experienced Haskell programmer will have no issue following all kinds of “.” and “$” usages, but one who is not experienced with Haskell, even if they learn what the syntax means, will have trouble following it.
I think how clearly you write and comment can matter just as much. I'm proud to say I've had situations where people with near zero programing experience were able to make some quick changes to get programs I'd written in perl working again following sudden changes. I think the more you know the easier it gets, but a reasonably capable end user with little to no experience in programing can surprise you if you take the time to try keeping things as easy to follow as possible. I do it because I can't trust myself to remember what I was doing even with projects completed only a few months ago.
When I interview candidates during recruiting I often ask the open ended question “what is good code for you” as it gets people talking. There is rarely a wrong answer. Usually, there is a difference in answers between more junior and more senior programmers. More senior candidates focus more on the readability & maintainability, while more junior programmers focus on accuracy, speed, following style-guides etc.
For me “good code” is code that is easy to change. Most things follow from that: easier to understand makes it easier to change. Do 1 thing (SRP), makes it easier to change, etc..
> When I interview candidates during recruiting I often ask the open ended question “what is good code for you” as it gets people talking. There is rarely a wrong answer.
I don't mean to be snarky, but if there is rarely a wrong answer to this question, then it doesn't sound like a good interview question to me. Having been through a bunch of interviews, I don't understand the obsession over clean code. I would understand it better if these questions helped pass/fail candidates or rank candidates (to help decide salary or position inside company). But it doesn't seem to be the case. It seems like the feel-good talky-talk over clean code in interviews is nothing but a waste of time (with "rarely a wrong answer").
No worries. I should’ve included it more clearly in my comment. I use the question to gauge seniority and experience to help determine salary proposal.
As I mention, there is typically a difference in the answer of a junior programmer (or someone who’s used to being a solo-dev) vs a senior dev that worked on larger projects with bigger teams.
Moreover, the question is about _good_ code, not clean code.
> Perhaps every team need to have a "definition of clean code" for themselves.
All of these concepts are attempts to codify practices that worked really well for specific people in specific circumstances. Like Egg Shen, we take what we want and leave the rest.
> Doesn't force you to mentally juggle many balls at the same time.
Coincidentally, this is how I define complexity colloquially for my own purposes. It is extremely general, stupidly practical, and literally rooted in brain chemistry.
Applying this to programming is still a delicate art, since it depends on what the reader of the code wants to do. Most people focus on clean modules (e.g. a 100 LOC unit testable data structure), but that's only helpful when the next person wants to modify or replace a single module, which is generally the easy part. Most times when I'm groking a new code base is spent understanding data flow across a complex hierarchy of modules – usually, the dumber and flatter, the easier this task becomes.
In any case, developers should get out of their nerd bubbles a little more. YES there are metrics that exist outside our field, but no, we gotta follow these outdated gurus who havent coded in a while, fetish-size complexity, come from a background incomprehensible to you and still stuck in old ways.
What are these metrics you ask? Here's a suggestion:
The number of neurons in your brain that light up in response to dissecting a codebase. Because you get frustrated when everything seems important, (something that happens when you dissect hardcore DRY-ish code).
Preach it. I feel like you took the words right out of my mouth. I left my last job because I couldn’t keep sitting and listening to the bullshit about how everything had to be built the same, citing “uncle Bob” every.single.day… coerced into long, drawn out PRs that oscillated between “I don’t like how much you’re changing, can you break it up?” to “what’s the reason for this change?” — lol you literally asked me to break it up. I dunno wish them the best, but god that place was a drag. I knew it was a red flag when I logged into the rabbitmq cluster and immediately notices there was no TLS and when I tried to talk about it I got waved off and asked what was assigned to me, 3 months later I open a 2 line PR and the shadow architect goes “wait, we’re not using TLS?” “Nope” “did you mention this before?” “Yep” SMH
And yet, everyone will agree:
void dothing1(Args..){...}
//old version of dothing1 the programmer keeps around to copy code from later
// which should never be used
void dothing1_backup(Args..){...}
is not clean.
People often strive for explicit and complete inclusive definitions for concepts better defined by what they are not. The former is more powerful when it applies, but the latter case is much more common.
Language is often about being able to take shortcuts, to infer meaning, to talk about things in the abstract an everyone understand what you mean.
Clean Code is just a concept, it’s up to you to define it within your culture or community.
I think most developers will have a basic grasp of the difference between clean and ugly code, you just might need to discuss the finer details as you learn.
Because it’s not well defined globally doesn’t mean there’s no such thing or it doesn’t exist.
The job of the engineer is to make complexity simple, even when your target market is other engineers.
The complexifiers and verbose Vogons persist heavily today though, as well as project management processes that create an infested myriad of complexity from shallow code to legions of dependencies and asset flipping rather than an effort to be close to the actual standards that run things, abstraction overuse is a major problem.
Simplicity takes a professional to achieve and maintain. Simplicity and simple parts lead to "clean" code.
> make complexity simple, even when your target market is other engineers
including and especially if the target market is other engineers.
Simplicity though is achieved with additional effort. It almost always requires additional effort to make the implementation simpler while having the same outcome.
It's why my personal motto is "Think more, write less". This, however, is not something that's encouraged in software companies. You are supposed to be sitting there and typing non-stop, that's what they call a "software engineering job".
In fact I never call any code "clean" because the term is vague. All that matters is how simple it is and thus how maintainable and performant, though occasionally the two can contradict each other, but most of the time they don't. Say, a complex caching algorithm can improve performance, so that's an exception. But most of the time simple is a synonym of both maintainable and fast. Can't think of anything else that matters, has real practical consequences and is not just hand waving around "common practices".
But, I think there are cases where it's quite obvious that one solution is "cleaner" than the other. Sometimes a refactor is just strictly better in all ways.
I once had a service that wrapped a core algorithm in our code, but the code for the server and the code for that algorithm were all jumbled together. It became hard to write tests for the algorithm because the server wanted sockets and a specific protocol and blah blah blah.
So, I simply separated them - the server instantiated the algorithm and called simple functions in it. Tests could do the same - it was strictly better in every sense - it made each part easier to reason about, test, read, understand... And with no real performance difference. "Clean"
> There’s no such thing as clean code.
> ‘Clean’ isn’t a measure of anything useful. Code can’t be clean simply because ‘clean’ doesn’t describe anything about code.
Ehhhh. There is absolutely such a thing as clean code. But yes; what there isn't a way to measure code cleanliness (although there's lots of surrogate measures; see every linter) which means there's also no way to render it into a dogma...
...and to (perhaps) stick my foot in it, that's something that gets harder to grok the less important subjective human experience is to you. IMHO, this is part of why Ruby code has an easier time being cleaner (and can reach high levels of "clean") - subjective human experience is baked into the language.
(note: yes, you can absolutely write dumpster fires in Ruby, arguably easier than you can in Java; and you can absolutely write very clean code on Java and any other language)
> I’ve come to the conclusion that often when we describe code as ‘clean’ when we think it’s good but we’re not entirely sure why. It just feels like the right solution.
Yes, absolutely. There's a lot of things in popular wisdom that are signposts for "there's more here if you pay attention".
> absolve you from having to justify that with more concrete rationale.
...Why in the hell do you need absolution here in the first place?! What are you being absolved from?
(shameless quote: justice only matters to the just)
> you don’t really need clean code, you need _____ code
No. That's the trap of Goodhart's Law. You need something outside of your metrics so that your metrics don't become your target.
> No. That's the trap of Goodhart's Law. You need something outside of your metrics so that your metrics don't become your target.
How can you make a metric out of "we aren't really sure what the direction of change for this feature is going to be, so it should be easy to refactor"? and why would that metric be any more likely to fall victim to Goodhart's Law than "the code should be clean"?
"Easy to refactor", AFAIK, can be approximated via LoC within functions and classes; dimensionality of functions; cyclomatic complexity; etc. Those are the metrics; you can produce reasonable and grounded numerical measurements of them.
But, if you were to just optimize for those metrics, I bet you'd run into issues, whereas if you use those metrics as part of your process for determining if it's easy to refactor, then yeah, I'd expect you to be less vulnerable to Goodhart's Law.
"Easy to refactor" is a goal, not a metric; low cyclomatic complexity is a metric, not a goal. IMO.
The fact that everyone can come up with his own definition of what "clean" is supposed to mean regarding code, tells us something very important about it:
It has no intrinsic, defined meaning in the context of code.
Saying code is "clean" is like saying food is "tasty"...its a personal opinion, not a defined term.
Strong disagree. It's defined meaning in the context of code is a subjective experience of the coder of "clean", which carries elements of "satisfying", among other things.
Maybe: It has no intrinsic, defined meaning in the context of the computing machine.
> is a subjective experience of the coder of "clean"
My subjective experience of grapefruits is that they are yucky. That tells others that I don't like grapefruits, but it tells them nothing about the grapefruit.
It tells them nothing about, what exactly in the interplay between the chemical composition of grapefruit juice, the genetic programming behind my tastebuds, and the personal experiences which shaped my individual perception of "tastyness vs. yuckyness", makes them "yucky" for me.
But there is agreed-upon tasty food, so there is some kind of intrinsic, shared meaning of the word, which is also true of code.
It's not objective, but there is a consensus among peers.
Honestly, it is like art, but these analogies kind of fall flat since we're not all "artists" in the traditional sense, usually. The reality is, for a painter, there are things that come close to objectively "good" works, a lot more so than for the layperson, and an experienced painter can walk you through all the different "types" of "good" art, just like an experienced coder can walk you through all the different "types" of "good" code.
Just because we can't write down in a few pithy words what "clean code" is doesn't mean it doesn't exist, it just further confirms something we've all known for a long time now; code is art.
Yes we do, but "this tastes terrible" still doesn't tell me what that means without additional context. It could be terrible because its spoiled. It could be terrible because the person doesn't like that food.
Same with "clean code". I think alot of people would agree that a 10000 line program where all the code lives in the main function is not "clean". But that doesn't mean this is what someone means when he says "this isn't clean code".
From the list at the link, I would argue that the following are mostly orthogonal of, and certainly not requirements from, clean code:
1. Performant
2. Safe
3. Scaleable
4. Easy to delete
---
Performant: I can write you a nice clean bubble sort implementation, which will obviously not be performant.
Safe: You could write a nice clean server which reads input from a network socket and executes it via `system(...)`. This would not be safe.
Scaleable: That's not even properly defined. How would you "scale" my device driver? Or my microcontroller logic? ... does that mean those can never be clean?
Easy to delete: To delete and replace? To delete for building tests for other parts of the code? Didn't even get that.
I'm glad he brings up the much-maligned Singleton pattern as an object for debate - or beauty being in the eye of the beholder. "Clean" to me involves a range of classes and singletons (or better, all static code) in as close an approximation to use as they're a actually used in the final code. If something happens once only in your code, it's dumb to make it a class instance. If it happens twice but you know you will never need it again, same. If it happens repeatedly, refactor.
Clean code can be repetitive or concise. Readability is important, but only part of cleanliness. When you look at bad or dirty code you can tell immediately, because the logic is loose; it seems to try to handle edge cases at the end of a logic block, or catch errors that lead you to wonder why should this code ever encounter that error if the other code it's referring to is stable? Clean code displays confidence in knowing that edge cases are managed before they get to user functions.
394 comments
[ 2.6 ms ] story [ 280 ms ] threadExactly
The same code snippet might be good in one context and an annoyance in another context
Here's where "generic rules" fail. Like python's avoidance of lambdas and favouring just having functions. Well, but, depending on the context, they make a lot of sense.
"Oh but lambdas are less readable" not if you need 20 different short functions and you need to combine them. Too implicit and it's hard to read, too explicit it gets boring and also hard to read.
Same for anonymous functions. You test the functions that use them and that's usually enough. If not, then that is a good indicator of separating them out into named functions.
> 20 short functions definitely sound as though they should be explicit.
And you are right that it's pretty arbitrary when/if a lambda should specifically be tested or not. But the number of lambdas in a project isn't really a good factor to make that decision - it's individual for each function that contains a lambda.
My solution: array of lambdas
Your solution: array of references to functions named f1 .. f20.
Which is cleaner?
* if anyone not get it, ts/js can declare a key-value lambda like:
let myFunc = {f1: () => , f2: () => } and so on. It can be called by string key like myFunc["f1"](), and all it's props can be get by Object.keys(myFunc) so it can be randomized easily.
That's why you write functions like this:
So much meaning provided by the name ;)It sounds like you're suggesting this code should be more like:
... And on and on and on, still needing the original code, but just a lot more obfuscated: Now I have no idea what the hell this code is doing and whether it's correct or not. Note that to avoid lambdas in the filter (filtering by a value in the closure) we need to write a function that returns the predicate we want to test on, instead of just sticking the right thing in the right place to start with. All to avoid a single "=>".What circle of hell are we in!?
The context I was talking in mentioned Python not having lambdas. It does have lamdbas (there's even a keyword) so I assumed it meant larger, multi-line lambdas. The code you wrote (glueing together a few mini-lambdas) I would not expect to test independently. I'd feel bad that you can't use SQLAlchemy's even nicer syntax, but I certainly wouldn't expect a test for each of those little things.
In my opinion there's nothing wrong with calling code clean, we don't have to analyze every line of code we write. There's just too many things to consider and viewpoints to take, we'd be trapped in analysis paralysis forever, as you can continue it to no end. There's never a point where the analysis will be "done". You just have to be able to recognize when the analysis reaches a point of diminishing returns, and when moving on is the better option. Sometimes just calling it "clean" is enough.
My view is that while there is definitely a subjective aspect to quality—whether in code or in writing—a larger portion is objective, just hard to formalize or quantify. Just because we can't measure something directly or because we can't all agree about it in every instance doesn't mean it's subjective. There are a lot of things we can't know exactly but that are still objective, so why not writing or code quality as well?
Hmmmmmm not sure about that. AFAIK anything that we either can't measure objectively, or don't yet know how to measure objectively, is a subjective measurement, even if that measurement is subject to an objective apparatus (observer effect experiments; the measurement is subject to the circumstances of measurement)
Totally open to being wrong and you being right tho. Any examples come to mind for you?
Like music and other forms of art, different styles appeal differently to different audiences. Many people find the language that 8 year olds write in to be terrible, and some people find it endearing and honest and meaningful. Software is no different in my opinion
Audience always matters
Of course books get 1 major release. So while ability to maintain and change is much more important in software, that is by far not the only factor for clean-ness, and I'd say hardly the most important.
There are different writing styles (and so guide styles associated) for different objectives. Eg, journalism, with a pyramid model designed and refined to ease the process of assimilating information which is different from the playscript style model designed to give general instructions on how to flesh out and enact events.
Both these styles (I intentionally chose for their more obvious rules and usage) have different hard-and-fast rules and applying one instead of the other wouldn't work.
Now, there will be a subjective quality but there are also objective qualities to writings produced with these styles (structure, count of words, choice of wording for description, timeline of events reported, etc.).
Now, of course regarding novels, storytelling, the newyorker, writings that are intended to be read, alone, to tell a story, to convey more than facts and the most objective description of reality yada yada.
And then there are technical manuals. I think code is a technical manual for two kind of audiences: humans and computers, so compromises are made and rules for clarity and brevity can be layout.
I agree with you there - but this makes it even more bizarre that there are now tools (e.g. SonarQube) trying to automate this analysis. Of course, linters are a undeniably useful, but stuffing arbitrary rules into a tool which then gives grades to your code based on a strict interpretation of those rules is a bridge too far. I mean, can you really take software that uses terms like "blocker code smell" seriously?!
If that's what it is then I think such tools are "a good servant but a bad master". It does not replace manual analysis, but can be used well to reduce costs of removing common code smells. It still requires a human to work as a judge.
- the code complies with coding conventions in force
- it is correct
- it is not inefficient
I would really love coworkers stick to these three criteria during reviews.
Well sure, but that's like saying "there's no such thing as beauty" or "there's no such thing as a well-written book". Clearly bullshit. They might be somewhat subjective and have no mathematical definition but they clearly exist.
Try pressing F9.
In response to: > You definitely know it when you see it. the author suggests: > It’s good to build that intuition, but we can’t just stop there. We need to dig deeper beyond those feelings to understand and articulate why we think the code is good.
Maintaining low viscosity (resistance to local change) is a super useful concept for me when thinking about clean code and good architecture.
I think it's a pretty good signifier of elegance, decoupling, lack of spaghetti etc.
1: http://iihm.imag.fr/blanch/ens/2010-2011/M1/EIHM/cours/1998-...
In one organization, I have even had two seniors go back and forth telling me to remove what the other senior told me to put in.
The entire process of defining "clean code" seems painfully arbitrary.
If your best practices are so great, make a sound business case for them. Uncle Bob did no such thing.
From: You
To: Senior1, Senior2, Senior1s_Manager, Senior2s_Manager, Your_Manager
Subject: Duke it out between yourselves
Senior1, you told me to put in Feature1, Feature2, and Feature3. A while later, Senior2 told me to take them out.
Senior2, you told me to put in Feature4, Feature5, and Feature6. A while later, Senior1 told me to take them out.
My job is to implement worthwhile features, not to be an implement in your quarrel over which features are worthwhile to implement. Duke that out between yourselves, and only then give me the ones you can agree on to implement. Thank you.
Your_Manager, I've looked over my job description. It says my job is to implement worthwhile features, not to be an implement in other people's quarrels. Yours says your job is to back me up in situations like this.
Senior1s_Manager and Senior2s_Manager, maybe Senior1's and Senior2's job descriptions need to be clarified with regards to which features each of them is empowered to request or veto.
I personally like "clean" as a term for code quality. We've all seen how focusing on easily measurable metrics (like code coverage) can help, but in the hands of the saboteur or the clueless it just becomes a meaningless number. "Clean", instead, focuses on the ultimately human judgement of how to make code which is fit for purpose.
The author lists a litany of traits that could be considered "good" things to aim for, but most of them are pretty granular, fuzzy, or superseded by others. The author says:
Words like ‘encapsulated’, ‘testable’, ‘mockable’, ‘reusable’ have meanings that we can all agree on. When we use more specific words that describe the various code traits that affect our project then we can be sure that we’re all on the same page.
Unfortunately, however, we're never really on the same page. Even these more precise terms are very context specific and amorphous. "Testable" may mean granularly accessible, modular, mockable, idempotent, or quick to spin-up for CI, or only a subset of these. Highly dependent on testing approach too (unit, TDD, BDD, E2E...)
This is why I like thinking in terms of tenets: broad concepts that are applicable to everything but enable context-specific meaning. E.g.
With these tenets, for example, we can then *contextualize* and find the "cleanest" code for whatever particular application we're working on. If we want to break these broad tenets down further then we can, and I hold the position that this _is_ very useful: Most of these are independently VERY context-specific. Being time-efficient is about the available hardware, interfaces, UX, etc. Being accessible or usable is about WHO is going to use or access your abstractions and underlying implementation.To achieve these tenets, there are many principles and approaches to draw on to help us find the 'clean' solution each time: E.g. The Law of Demeter (LoD), SOLID, The abstraction principle, Functional programming, etc. These exist as tools to help us get closer and closer to the unreachable (yet desirable) goal of "clean".
So, I suppose I agree with the author but only semantically. Cleanliness does not have a fixed definition, but it is still useful to talk about as a goal or absolute to aim for. A bit like all manner of other superlatives.
No, they are rarely at odds with each other. Readable code leads towards performance and safety naturally. Writing readable and simple code means that you understand the problem domain well, and the amount of abstraction needed for tackling the particular problem at hand. Readable code is also much easier to analyse for performance and safety.
The company I worked for had a separate team, kind of a startup, and these guys were all in on discussions how to do things cleanly -- is it a code smell? -- but everything they did was overengineered and borderline useless.
If the things you do to make code cleaner make it more difficult to change things, or even to use it, you are doing it wrong. And I've done things wrong plenty and mostly still do, but I want to improve and what developers usually emphasize does not make things easier.
The code often tells you a story, it communicates how the programmer (author) described the problem, the solutions and the trade-offs. Very similar to writing.
Maybe it is a vague term. But it is not an excuse to write "bad" code. Perhaps every team need to have a "definition of clean code" for themselves.
This is the crux of it for me. I want to read code not solve code. If I have to "figure out what's going on" then it's not great code.
Am I unlucky or is what you are describing the rare exception?
100% recommend having your own hobby project so you can reach it.
I would say lofty height only possible in following conditions:
A model of reality / problem domain that the company has created themselves that does not have to integrate with any other company, or have to follow any laws or regulations.
For example let us suppose you create a company for users to send messages to each other using your app. You can totally control everything in your environment - just your app, your definition of users, you definition of messages. But once you need to connect your app to other apps doing similar things but differently than your model you are going to hit edge cases, and if you have to think about making your app work on multiple environments and there are problems as there generally are you will start to be less lofty, and then after you have been going a while you try to enter a market with regulations, or regulation is handed down affecting your app.
Things are getting less lofty quickly at that point.
on edit: grammar
But it requires a team that is committed to aggressively refactoring even the smallest of code smells, usually before even committing it. It also requires a team that is dedicated to its professionalism and not bend into a manager's will of refactoring being a time waste.
But when your team is committed to code quality, holy hell is it satisfying, easy and fast to work with. It really is like night and day. If you have not experienced the difference - I'm sorry to say - then you've just not worked with a high-quality team.
Often they were small dev teams in a small company that have high standards and given the trust & freedom to do things.
I think it boils down to how much the programmer actually cares and is given the necessary time to do a good job.
P.S: Been programming only for 6-7 years so can't say if my experience is any indicator.
The cleanest codebases are those where the project has a focused goal and is maintained by a few developers with a low churn rate.
https://github.com/bilibili/vlc/blob/master/modules/video_fi...
https://github.com/bilibili/vlc/blob/master/modules/video_fi...
It sure does.
Unfortunately, the genre is quite often Lovecraftian Horror.
When I interview candidates during recruiting I often ask the open ended question “what is good code for you” as it gets people talking. There is rarely a wrong answer. Usually, there is a difference in answers between more junior and more senior programmers. More senior candidates focus more on the readability & maintainability, while more junior programmers focus on accuracy, speed, following style-guides etc.
For me “good code” is code that is easy to change. Most things follow from that: easier to understand makes it easier to change. Do 1 thing (SRP), makes it easier to change, etc..
I don't mean to be snarky, but if there is rarely a wrong answer to this question, then it doesn't sound like a good interview question to me. Having been through a bunch of interviews, I don't understand the obsession over clean code. I would understand it better if these questions helped pass/fail candidates or rank candidates (to help decide salary or position inside company). But it doesn't seem to be the case. It seems like the feel-good talky-talk over clean code in interviews is nothing but a waste of time (with "rarely a wrong answer").
No worries. I should’ve included it more clearly in my comment. I use the question to gauge seniority and experience to help determine salary proposal.
As I mention, there is typically a difference in the answer of a junior programmer (or someone who’s used to being a solo-dev) vs a senior dev that worked on larger projects with bigger teams.
Moreover, the question is about _good_ code, not clean code.
All of these concepts are attempts to codify practices that worked really well for specific people in specific circumstances. Like Egg Shen, we take what we want and leave the rest.
> Doesn't force you to mentally juggle many balls at the same time.
Coincidentally, this is how I define complexity colloquially for my own purposes. It is extremely general, stupidly practical, and literally rooted in brain chemistry.
Applying this to programming is still a delicate art, since it depends on what the reader of the code wants to do. Most people focus on clean modules (e.g. a 100 LOC unit testable data structure), but that's only helpful when the next person wants to modify or replace a single module, which is generally the easy part. Most times when I'm groking a new code base is spent understanding data flow across a complex hierarchy of modules – usually, the dumber and flatter, the easier this task becomes.
What are these metrics you ask? Here's a suggestion: The number of neurons in your brain that light up in response to dissecting a codebase. Because you get frustrated when everything seems important, (something that happens when you dissect hardcore DRY-ish code).
//old version of dothing1 the programmer keeps around to copy code from later // which should never be used void dothing1_backup(Args..){...}
is not clean.
People often strive for explicit and complete inclusive definitions for concepts better defined by what they are not. The former is more powerful when it applies, but the latter case is much more common.
Clean Code is just a concept, it’s up to you to define it within your culture or community.
I think most developers will have a basic grasp of the difference between clean and ugly code, you just might need to discuss the finer details as you learn.
Because it’s not well defined globally doesn’t mean there’s no such thing or it doesn’t exist.
The job of the engineer is to make complexity simple, even when your target market is other engineers.
The complexifiers and verbose Vogons persist heavily today though, as well as project management processes that create an infested myriad of complexity from shallow code to legions of dependencies and asset flipping rather than an effort to be close to the actual standards that run things, abstraction overuse is a major problem.
Simplicity takes a professional to achieve and maintain. Simplicity and simple parts lead to "clean" code.
> make complexity simple, even when your target market is other engineers
including and especially if the target market is other engineers.
Simplicity though is achieved with additional effort. It almost always requires additional effort to make the implementation simpler while having the same outcome.
It's why my personal motto is "Think more, write less". This, however, is not something that's encouraged in software companies. You are supposed to be sitting there and typing non-stop, that's what they call a "software engineering job".
In fact I never call any code "clean" because the term is vague. All that matters is how simple it is and thus how maintainable and performant, though occasionally the two can contradict each other, but most of the time they don't. Say, a complex caching algorithm can improve performance, so that's an exception. But most of the time simple is a synonym of both maintainable and fast. Can't think of anything else that matters, has real practical consequences and is not just hand waving around "common practices".
But, I think there are cases where it's quite obvious that one solution is "cleaner" than the other. Sometimes a refactor is just strictly better in all ways.
I once had a service that wrapped a core algorithm in our code, but the code for the server and the code for that algorithm were all jumbled together. It became hard to write tests for the algorithm because the server wanted sockets and a specific protocol and blah blah blah.
So, I simply separated them - the server instantiated the algorithm and called simple functions in it. Tests could do the same - it was strictly better in every sense - it made each part easier to reason about, test, read, understand... And with no real performance difference. "Clean"
Apart from that you can only hope to avoid adding too much dirt.
Ehhhh. There is absolutely such a thing as clean code. But yes; what there isn't a way to measure code cleanliness (although there's lots of surrogate measures; see every linter) which means there's also no way to render it into a dogma...
...and to (perhaps) stick my foot in it, that's something that gets harder to grok the less important subjective human experience is to you. IMHO, this is part of why Ruby code has an easier time being cleaner (and can reach high levels of "clean") - subjective human experience is baked into the language.
(note: yes, you can absolutely write dumpster fires in Ruby, arguably easier than you can in Java; and you can absolutely write very clean code on Java and any other language)
> I’ve come to the conclusion that often when we describe code as ‘clean’ when we think it’s good but we’re not entirely sure why. It just feels like the right solution.
Yes, absolutely. There's a lot of things in popular wisdom that are signposts for "there's more here if you pay attention".
> absolve you from having to justify that with more concrete rationale.
...Why in the hell do you need absolution here in the first place?! What are you being absolved from?
(shameless quote: justice only matters to the just)
> you don’t really need clean code, you need _____ code
No. That's the trap of Goodhart's Law. You need something outside of your metrics so that your metrics don't become your target.
Well, in several epistemologies the second admission means there's no such a thing (or that, it might as well not exist).
How can you make a metric out of "we aren't really sure what the direction of change for this feature is going to be, so it should be easy to refactor"? and why would that metric be any more likely to fall victim to Goodhart's Law than "the code should be clean"?
But, if you were to just optimize for those metrics, I bet you'd run into issues, whereas if you use those metrics as part of your process for determining if it's easy to refactor, then yeah, I'd expect you to be less vulnerable to Goodhart's Law.
"Easy to refactor" is a goal, not a metric; low cyclomatic complexity is a metric, not a goal. IMO.
It has no intrinsic, defined meaning in the context of code.
Saying code is "clean" is like saying food is "tasty"...its a personal opinion, not a defined term.
Maybe: It has no intrinsic, defined meaning in the context of the computing machine.
My subjective experience of grapefruits is that they are yucky. That tells others that I don't like grapefruits, but it tells them nothing about the grapefruit.
It tells them nothing about, what exactly in the interplay between the chemical composition of grapefruit juice, the genetic programming behind my tastebuds, and the personal experiences which shaped my individual perception of "tastyness vs. yuckyness", makes them "yucky" for me.
It's not objective, but there is a consensus among peers.
Honestly, it is like art, but these analogies kind of fall flat since we're not all "artists" in the traditional sense, usually. The reality is, for a painter, there are things that come close to objectively "good" works, a lot more so than for the layperson, and an experienced painter can walk you through all the different "types" of "good" art, just like an experienced coder can walk you through all the different "types" of "good" code.
Just because we can't write down in a few pithy words what "clean code" is doesn't mean it doesn't exist, it just further confirms something we've all known for a long time now; code is art.
Same with "clean code". I think alot of people would agree that a 10000 line program where all the code lives in the main function is not "clean". But that doesn't mean this is what someone means when he says "this isn't clean code".
1. Performant
2. Safe
3. Scaleable
4. Easy to delete
---
Performant: I can write you a nice clean bubble sort implementation, which will obviously not be performant.
Safe: You could write a nice clean server which reads input from a network socket and executes it via `system(...)`. This would not be safe.
Scaleable: That's not even properly defined. How would you "scale" my device driver? Or my microcontroller logic? ... does that mean those can never be clean?
Easy to delete: To delete and replace? To delete for building tests for other parts of the code? Didn't even get that.
Clean code can be repetitive or concise. Readability is important, but only part of cleanliness. When you look at bad or dirty code you can tell immediately, because the logic is loose; it seems to try to handle edge cases at the end of a logic block, or catch errors that lead you to wonder why should this code ever encounter that error if the other code it's referring to is stable? Clean code displays confidence in knowing that edge cases are managed before they get to user functions.