![Cover image for In Defense of Clean Code: 100+ pieces of timeless advice from Uncle Bob](https://media.dev.to/cdn-cgi/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ke0h8jc0i3dg9gum1o8.jpeg)
Clean Code by Robert C. Martin is the most recommended programming book of all time. Search any list of “top books for software engineers,” and you...
For further actions, you may consider blocking this person and/or reporting abuse
Thanks for the thoughtful comment!
I've seen this go both ways, good and bad. An interesting result of not using a wrapper API around a third-party API is that if you don't do it, you're likely to stay stuck on that library forever (which might be ok in some cases!).
I've seen this happen in a few companies now with things like date libraries (Moment.js, Day.js, date-fns, homegrown library, etc.), currency-formatting libraries, and XHR libraries (jQuery ajax, axios, etc.). The third-party code gets so interwoven with all the other source code that it becomes impossible to migrate away from one library and onto another. Or, not for the faint of heart, months of effort to migrate.
I think the lessons I've learned from those experiences line up pretty well with the advice in Clean Code, which would be to either 1) use a thin API wrapper over the third-party API, or 2) try to limit how much of your codebase knows about the third-party library, since that will make swapping technologies easier. And those two ideas are interesting because the first idea is really just a solution to the second idea! So however you approach it, the second idea seems to be the core principle here.
It's definitely exciting to see the native APIs get better where all three of the examples I gave will soon become a non-issue!
Libraries with types tend to get very interwoven - function libraries such as date-fns generally do not. There is absolutely nothing won by wrapping somebody else's function in your own function - it's the exact same situation, now you're just coupled to that instead. Switching from a function you wrote, or from a function you imported, is the exact same amount of work.
Even with some classes, wrapping them might not mitigate the problem and can actually make matters worse - it really depends... See this:
youtu.be/tqqH_Ib_gDc
I watched his video, and it's interesting, but I don't think his argument is quite right. He basically says throughout the video not to use abstraction because you're going to do it wrong. So he's really arguing against doing abstraction badly.
Some of his key points were:
Both of those ideas aren't criticisms of abstraction though, those are criticisms of doing abstraction badly.
To solve the first problem, just... don't add the third-party library's implementation details into your abstractions's interface. Leaking the implementation details defeats the whole purpose of abstraction.
For the second problem, it may be true in some cases that a breaking change in the third-party library's API will cause a breaking change in your abstraction layer, but it's not necessarily true. It may be very possible to write a good abstraction that can handle the third-party library's breaking change while not actually changing the interface that your abstraction creates. That's the beauty of abstraction: the rest of your app doesn't know about the changed implementation details.
His last point is great though about abstraction being hard to get right and that getting the interface right the first time is important.
See also in your article
It's been my observation that people will usually find ways to avoid doing hard things.
lots of JavaScript code is full of anonymous inline functions (of any size). Among other things it avoids having to name the function, effectively forcing any future reader of the code having to fully mentally parse the function body to divine the function's intended purpose without even a hint from a function name.
similarly if people are convinced that it is "hard to get the right abstraction on the first try" they often aren't going to invest any effort in an attempt.
Perhaps focusing on abstraction is a misdirection.
Chapter 7 Error Handling was written by Michael Feathers, Author of Working Effectively with Legacy Code
All he was suggesting was to "map" the errors/exceptions that are specific to the API to application specific exceptions that the application knows how to handle.
That being said I put forward the perspective that (the right) boundaries are more important than "abstractions". And ultimately even the "right" boundaries are subject to which objectives are perceived to have the highest value.
Data-Oriented Design: What's wrong? — Mapping the problem
Robert C. Martin is clearly in the "good at providing a human oriented representation of the problem in source code" camp and TDD is a key part of his platform.
People often pursue "testing to attempt to show correctness.”
But Michael Feathers makes the observation:
That thought and reflection also iteratively revises the boundaries towards optimal testability.
This seems to be in direct contradiction to DHH's Test-induced design damage. As a framework designer he's a proponent of "Rails is your application". He's primarily interested in speed of initial implementation and is perfectly content to rely on integrated tests to just verify product correctness — but not challenge the quality of design (the design of Rails doesn't change and comes with pre-imposed boundaries).
Robert C. Martin's view is informed by the Hexagonal architecture were:
So going back to the out-of-context over-generalization:
If the semantics between the contract and the third-party API are sufficiently different expect to implement an anti-corruption layer.
When it comes to external APIs it can be worth looking into contract tests.
Is it easy to identify optimal boundaries?
Of course not.
Andy Kinslow, Software Engineering 1968, p.21
Eric Evans, Domain Driven Design 2004, p.196
Startups don’t have stable business rules - so boundaries are constantly in flux
Adam Ralph, Finding your service boundaries 2019
i.e. it's rare to get it right off the bat.
I don't agree. The criticism is that of abstracting the wrong things. The third-party library you're using is already an abstraction of something - and there could be reasons to further abstract that, but often there isn't, and just avoiding coupling, in my opinion is definitely not the right motivation.
Even if you don't leak implementation details, you're going to "leak concepts" - ideas from the underlying library are likely going to bleed into your domain, even if things like types and interfaces do not.
Some reasons I might choose to abstract would be:
The library is really complex and does a lot more than I need - in that case, I can avoid direct coupling to a complex library by hiding it behind a simple interface.
The library doesn't quite do everything I need - in that case, I can build an abstraction that adds in the missing details, and again avoid direct coupling to something that wasn't quite what I needed in the first place.
On the other hand, why would I abstract something if it's already (more or less) exactly what the project needs? If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea - if anything changes, it's practically guaranteed to break my abstraction.
I think that's the case he's talking about in the video.
Every line of code, whether that's your code or library code, adds complexity: every line of code is a liability, so every line of code needs to have a specific, measured reason to exist.
In my experience, the most successful projects are always the ones with less complexity.
So it has to be a conscious, case-by-case decision, in my opinion.
This is assuming a one-to-one distinct abstraction to concrete library relationship. That type of alignment isn't necessarily the best way to move forward.
I think it's more important to evaluate if complexity is managed appropriately. In my view OOD invariably adds complexity in order to manage complexity — it can work but it often isn't a slam dunk.
That's pretty much a given. Guidelines tend to be a starting point, not some absolute truth.
What ultimately devalued the video for me was the example - why would there be a need for a concrete messaging abstraction? The actual goal is to have the application logic be "ignorant" of the messaging solution that is being used to handle messages. This idea is similar to Persistence Ignorance (PI):
"The Application" will only need to send a finite number of message types, and receive a finite number of message types. Worst case each send-type has its own function into the infrastructure and the application exposes a separate function for each receive-type. The application is only interested in providing the data for outgoing messages and extracting the data from the incoming messages. The application really doesn't care what happens on the other side of the application boundary.
Quote
Rabbit MQ has no business being inside the application boundary.
To use J. B. Rainsberger's terminology:
FYI: Mock Roles, not Objects
Quote
The "swap out" argument is specious but isolation is the prize.
Isolation is the pre-requisite to being able to leverage fast micro-tests — "tests to detect change", i.e. establish rapid feedback as we refactor the code to reduce the volatility in the marginal cost of adding the next feature.
Doesn't this HOW/DMZ/HZ stuff slow us down?
In the beginning perhaps but again J.B. Rainsberger explains The Scam:
He acknowledges that "the scam" is initially incredibly seductive but eventually there comes the point where the cost of continuing is higher than the cost of starting again.
So the initial investment is aimed at going well for long enough, so you'll beat fast all the time.
Perhaps that article's author simply needed a more appropriate title:
Kevlin Henny stated it this way in reference to the Dreyfus model of skill acquisition:
So it needs to be stated that simply itemizing "Clean Code Guidelines" — without any context - can really only serve novice and advanced beginner developers. To truly advance, one needs to understand the prevailing assumptions and circumstances that tend to arise that give credibility to these guidelines so that it becomes possible to disregard the guidelines outside of their implied, applicable context.
To be clear, a competent developer doesn't just simply disregard the guidelines but needs to be capable of formulating a valid argument of why the guidelines do not apply in a particular context.
At the time I found Designing Object-Oriented C++ Applications Using the Booch Method (1995) and Agile Software Development, Principles, Patterns, and Practices (2002) quite useful, as well as his blog (as long as you keep your critical thinking firmly engaged — don't take anything at face value).
That said, I find to this day that he has failed to reconcile his growing admiration of Clojure (at least since 2010) with his continued evangelism for the class-based object-oriented development methodology. I'm inclined to believe this is entirely motivated by "professional reasons".
absolutely love this comment!
as annoying and incoherent it can seem for a novice seeing experts that do not strictly follow the rules all the times, in the end it is really just that. Context and experience enable you to understand if the rule makes sense in that specific context or not.
rules are easy and necessary, and fundamental at the beginning, the more you grow and the more you understand, the more flexible you can become.
it kinda reminded me of the quote from Pablo Picasso:
Love it! The Dreyfus model of skill acquisition is one of my favorites. The whole original research paper is well worth the read: apps.dtic.mil/sti/pdfs/ADA084551.pdf
Keeping in mind:
Perhaps you'll enjoy Dan North's Dreyfus Squared pattern/hypothesis.
Very good read!
Thanks for the summary! Now, to balance things a bit, here's what I don't like about the book:
It often put the responsibility of bad code on the coder. In my experience, it's more the environment which push developers to do "bad code". The environment include: not enough knowledge about the domain (gaining knowledge about what we're building often comes with a first draft of "bad code"), pressure from the stakeholders, company culture...
Weird idea which are not backed by... anything empirical. Example in chapter 1: a developer needs to have a "code-sense", some sort of artist's sixth sense, to know what clean code is. I'm sorry: what?
The code shouldn't have one responsibility, but enough responsibility to be coherent. It's very similar, arguably, but I've seen too many class with one functions 'cause it needs to have one responsibility. I think that's the result of Martin's absolutism: it's the truth, everybody needs to follow it, who care about the context?
To quote him: "FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL. THEY SHOULD DO IT ONLY."
I'd say: it's not always true.
DRY is not about code duplication, it's about duplication of knowledge. Still, Martin puts DRY and code duplication together. Reading The Pragmatic Programmer (the book which coined DRY) will teach you much more about that than Clean Code.
The examples: it follows Java never ending verbosity, and some of them are seriously questionable. I mean what's a "SetupTeardownIncluder"?
I don't think it's a waste of time to read it, but I think it might be confusing for beginners, because of this weird mix of good advice and questionable ones.
I would recommend The Pragmatic Programmer or Refactoring (from Fowler) more than Clean code.
Yet another reference:
Aside: Development by Slogan
Ironically:
Perhaps at times he gets more caught up in his slogans than the essential messages.
Kevlin Henney regularly likes to haul this one out for some of his talks.
I tend to agree because they have already proven to be more timeless (and less context sensitive) because both of them have successfully moved to a 2nd edition after an already long period of relevance:
The Pragmatic Programmer (1999, 2019)
Refactoring (2000, 2018).
For anyone not familiar with Dave Thomas:
Fowler about Workflows of Refactoring. The 2nd edition uses JavaScript:
… but as such it still uses a predominantly class-oriented style (which in my personal opinion isn't the sweet spot for JS — but this is about mainstream development in general, not JS specifically).
The Pragmatic Programmer is a great book! That's also one I'd recommend to everyone.
Refactoring is good as well, although it's a pretty dry read and can be hard to slog through sometimes. The advice is all great, but reading through all the refactoring examples isn't exactly exciting.
A lot of debate around the book seems to be in its context
gofmt
and save a lot of time and energy on pointless debates related to this!In short, the book is written as advice for OO Web applications where it makes many good points. Taking the advice out of its context results in cargo cults and code which is not fit for its domain
This reminded a lot of the Ada style guide (yes, it is a thing!)
About functions
Yes, in theory, but there are many cases where a function with side effects makes the code smoother and clearer. The typical example is a lexical analyzer function that returns the current token and move to the next one. Yes, you can replace it with a procedure, but having a function is much more convenient, so
Absolutely. Being pragmatic about these guidelines is the key. Not every rule applies in every situation, and wisdom is knowing how to apply these ideas appropriately.
Thanks for the summary, very informative and useful.
You’re welcome, thanks for reading!
Yeah, that one should be qualified as a rule of thumb.
This video is worth watching:
youtu.be/tqqH_Ib_gDc
Talks about the pros and cons, when to abstract and when not to.
Same response here: dev.to/thawkin3/comment/1k6hm
But yes, as with most everything, the answer is often "it depends." Abstraction is a valuable tool. The hard part is learning how to do it correctly and knowing when it's an appropriate solution and when it's not.
Thank you!
Thanks for reading!
Uncle BOB. Is it.
Do you think it's still worth it to buy the book?
Yes, absolutely!
Amazing summary. Thank you for taking the time to write it. Very useful.
Thank you Davide!
Awesome summary! Thanks a lot!
Thanks Bojan!
It's true after I refactored a bunch of rot projects.
Great summary!
Thank you!
Sure thing, thanks for reading!