My experience is that I run into a lot of relatively junior programmers who are concerned about clean code. Is my code clean? How do I organize my code? How do I make it clean? Should we clean up this code?
I almost never want to use the word “clean” when I’m talking about code.
These days, when someone asks me to review code, and they start talking about “clean” code, I shift the discussion to two points—code should be correct and easy to understand. I think “correct and easy to understand” is a much more useful rubric than “clean”. Obviously it’s still subjective. Code that is easy to understand for you may be hard for me to understand. Likewise, code that is correct for your use cases may be incorrect for my use cases. But it’s much easier to come to an agreement about “correct and easy to understand”, or at least communicate issues with the code using that basis.
Like, “you shouldn’t use boolean flags as parameters, you should use enums” becomes “I can‘t understand the meaning of true/false at the call site, so let’s use an enum instead”. This gives us a very clear, articulable basis for how we talk about code quality.
Of course, “correct and easy to understand” is not the be-all and end-all for describing good code. It’s just a nice substitute for the horribly vague, terribly subjective “clean”.
I think the problem with talking about understandable code is that we only talk about textual representation.
We should break out of the ide and visualize code more. I think then we would see the tangled mess we are creating. And then if we started talking about code that was cleanly visualized, we would truly have understandable code.
We need two-way sync for visualizations of all data structures and data flows. And then we need to see the actual data values inline as it flows through the system as we read the code.
Wallaby.js is a great leader in this space. I found it too hard to setup and too slow - but I’m convinced this applied to the entire stack is the future.
We draw diagrams on the whiteboard to explain every system but they are completely absent when we actually code.
> And then we need to see the actual data values inline as it flows through the system as we read the code ... Wallaby.js is a great leader in this space
It's a very under-appreciated feature, but I know what you mean.
Alongside NCrunch, Wallaby is one of my most valuable purchases. For those not familiar with the feature being referred to, as a continuous test runner one of the great benefits is that the IDE plugins show the values of anything (variables etc) in your code, live. I don't mean some kind of hover-for-intellisense, but at the end of each line you see all the values for all the variables all the time.
So in real-time, as you code, any tests covering the code you're changing are being run and you can see holistically how everything you're working on is changing as you type.
Basic test runners run continuously on the command line. Better ones run in the background of your IDE, possibly adding coverage markers etc. Wallaby goes one step further and actually decorates your code with real values live in the IDE as you work.
It's very good (though, yes, tricky to set up and a little slow).
> code should be correct and easy to understand. I think “correct and easy to understand” is a much more useful rubric than “clean”. Obviously it’s still subjective. Code that is easy to understand for you may be hard for me to understand.
There's a deeper problem here that has driven a lot of disputes over the years.
"Ease of understanding" is not a stable property of code. If you have two separate pieces of code that are easy to understand, and the only thing you do with them is combine them, your resulting one piece of code may also be easy to understand.
> Like, “you shouldn’t use boolean flags as parameters, you should use enums” becomes “I can‘t understand the meaning of true/false at the call site, so let’s use an enum instead”.
That's not the reason to use enums instead of parameters for functions, rarely will you not be able to understand what the bool arg means in:
create_user(..., is_admin: bool)
If you don't then you need a better IDE.
The reason to use enums and for that matter structs is that as requirements evolve, inevitably you will need to extend the code base and at some point `is_admin` alone won't cut it, you'll need an is_moderator etc.
This leads to two things, one is function signature pollution and unnecessary extra validation code, eg. what happens if both is_admin and is_moderator are set?
What about the functions relying on the old signature? Perhaps you make separate versions of the functions, eg. create_admin_user and create_moderator_user and keep the old one with the flag, then you can keep backward compatibility...but at the cost of an api that makes people go insane when using it.
More likely you'd then go to an enum which leads backward incompatible changes, and likely a refactor, which in the event of a library can break dependent code and in all cases is a lot more work than just using an enum in the first place.
Try to write code for the future reader and writer of the codebase. An enum will allow readers to immediately understand that there is a [normal, admin] distinction and adding a moderator would be as simple as adding a [normal, moderator, admin] in there.
Then, assuming you've written your code in a proper manner, your IDE will tell you all the places where a moderator needs to be accounted for in the code base.
If you tried to check in code with a 2-member enum I would try and stop you ("don't reinvent booleans"). Honestly this is totally fine early stage code, but I could try and mollify you with two functions, which feels more YAGNI. If we end up being wrong about that, we probably need more of a role/rights system than an enum will provide (I bet, for instance, you'll have a lot of business logic per-role that shouldn't just get stuffed in a single function).
This is what drives me nuts about seemingly simple rules like this: they let you feel like you can turn off your brain. You check all the boxes Clean Code tells you to and you feel like you're done. But you need to think systemically, and little simple maxims like "don't use bool flags" or "enums > bool flags" let engineers miss the forest for the trees over and over again.
What if you have three people in the same code base who write "easy to understand" code for them, but inconsistent with each other?
Even if those three all understand each other while writing it, this will not continue as the team changes and people have to onboard.
As such, it isn't sufficient to have a linting standard: great codebases should have a consistent mental model. "Clean" code, within the context of Bob Martin "Clean", is one consistent mental model. I don't like all of it, but it gives one coherent top-to-bottom model.
Code is a kind of language. It is entirely possible to tell if person speaks clearly, easy to understand, or not. You may even train an ML model on Wikipedia section "Simple English".
The same applies to code. It is not 100% deterministic metric, but it is relatively easy to argue and have a consensus upon.
> I don't like all of it, but it gives one coherent top-to-bottom model.
Completely agree. Bob Martin “Clean” has some really good recommendations, and some subjective advices. Normally the dev team can use this mental model as baseline and agree on which of the subjective advice to follow.
I just had an issue related to this recently, one of the senior software engineer in my team was previously a university professor with no software engineering experience. Every code review is an endless discussion, as he don’t agree with most of Martin “clean” code. So his code not only has several bad smells, but it feels like a completely different dialect. It forced us to have really basic discussions about code practices, even when to use comments (the professor likes to comment almost every line of code).
IMHO, that was an excellent talk, well presented and insightful.
I found your argument starting around 28:00 interesting. I’m not familiar with the specific tools you’re using and their idioms, so it’s possible that I’m missing some context here. As a general principle, I like to make it very clear where code is doing I/O and what is happening with any data involved. If this function receives data in one format, transforms that data into another format and finally sends that data to the database, then to me that seems like a useful separation of concerns and a clear representation of the overall behaviour. Maybe I wouldn’t choose the same name/structure as User.registration_changeset in your example, but the principle of hiding the implementation details of the data transformation seems helpful, for the same reason it’s helpful to see Repo.insert() instead of five lines of SQL at this level of the code.
It can definitely be confusing to hear "clean" when communicating in a professional setting.
That said, in the book he covers correctness and understandability in nearly every paragraph. It's all the book is about really. It's called "clean" because it's concise and a catchy book title.
If you say the book “covers correctness and understandability in nearly every paragraph” then I’m convinced we must be talking about different books.
For example, the book presents a rule for class names:
> Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info in the name of a class. A class name should not be a verb.
What a proclamation! We could connect this to correctness and understandability. But the book does not. You could say that it is harder to understand a class named “SomethingManager” because “manager” is vague, and more specific words are usually preferred. Instead, the book presents a rule, without explanation or example. Kind of like the Strunk & White book on style, and if you like Strunk & White, then we disagree on at least two books.
That’s my general complaint about the book. Too many proclamations, too much doctrine, not enough explanation or foundation.
"If a function is only called from a single place, consider inlining it.
If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.
If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.
If the work is close to purely functional, with few references to global state, try to make it completely functional.
Try to use const on both parameters and functions when the function really must be used in multiple places.
Minimize control flow complexity and “area under ifs”, favoring consistent execution paths and times over “optimally” avoiding unnecessary work."
Yeah, but read the very top of that post for his updated thoughts. The problem with manually inlining large functions is that they make code impossible to follow or reason about, which over time means that they turn into spaghetti code because people get lazy about reusing pieces from random other places in the same function.
His later addendum is important not to omit, because this is really the key to more reliable code:
> The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!).
> If a function is only called from a single place, consider inlining it.
A compiler should do this for you.
> If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.
This makes no sense to me.
> If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.
This can be useful, but only if it does not mess up code readability unless you are trying to squeeze your code into a cache.
> If the work is close to purely functional, with few references to global state, try to make it completely functional.
This makes very good sense, but it can be harder than it seems. One of the easiest ways (depending on how far down the call stack you are) you may just be able to pass the individual parts of that global state in as parameters.
> Try to use const on both parameters and functions when the function really must be used in multiple places.
Not all languages support this, but where they do this is good practice. In general: limiting scope and mutability is always an advantage.
> Minimize control flow complexity and “area under ifs”, favoring consistent execution paths and times over “optimally” avoiding unnecessary work.
Is also good advice.
In general you want to avoid nesting your ifs too deeply because at some point you lose track of what the local context is that got you there. Then it is usually better to break out a function and name it well so that that context is clear again. In general, naming things well is hard.
Point of inlining is to make it explicit that it is an ad hoc implementation for that location, so you don't have to be afraid of adding more stuff in it just to solve a local problem.
That would lead to a Big Ball of Mud if given to an inexperienced or reckless developer as i've witnessed many times in my career. The problem with such best practices/patterns/thought pieces is that they stem from highly experienced developers but given to the inexperienced they are like a hand grenade in the hands of an ape.
That’s true of every methodology used by inexperienced or reckless developers. There is no shortcut to quality or experience. But Carmack’s approach has the distinction of ultimately delivering results by orienting one towards the objective necessities of the problem at hand rather than, for instance, the never-ending spiral of arbitrary and inefficient abstractions that OO ideology commonly produces.
A common theme not only in software but other industries: Beware of people selling you advice. They are the ones who will breed dogmatic illogical cargo-cults of people whose only rebuttal when questioned is some variant of "because someone who sold me this book that claims it'll make my code better said so", and that can't be a good thing in general.
but we assume that Martin doesn't literally mean that every function in our entire application must be four lines long or less.
Unfortunately I have worked (and fortunately, briefly!) with codebases like that --- not surprisingly, in Java. In addition to the complexity of whatever the code is doing, smashing things into tiny pieces also adds its own complexity. The fact that a short function is "easily understandable" on its own, which is what a lot of proponents of this dogma argue, is useless for understanding the functioning of the whole. The latter is far more important when debugging.
Function length is ultimately meaningless. I'd rather have a 5000-line function that reads top-to-bottom, than 1000 5-line functions if it means I don't have to jump around several-dozen-level-deep callstacks and try to keep track of 30+ character long identifiers to understand how the thing works.
Have you worked with 5000 line functions? Just trying to set breakpoints in them at meaningful points is a nightmare.
Give me 1000 5-line functions any day - providing of course they have sensible names (and ideally don't cause unexpected side-effects etc., though when a function is 5-lines long, that's fairly easy to spot; in a 5000-line function, fuhgeddaboudit. My personal guideline is "it should fit on a screen" (at a window/font size you'd normally edit code at - it doesn't vary that much from dev to dev.)
> and ideally don't cause unexpected side-effects etc., though when a function is 5-lines long, that's fairly easy to spot
Not easy to spot side effects if the 5 line function calls 999 other 5 line functions, which you have to do if you replace 1 function with 1000 functions. When people start to arbitrarily break up tightly coupled implementations into tiny functions you will get a much worse mess than if they just kept it as big as it was when they developed it.
Small functions are great, but forcing people to write small functions is not great. They are two different things.
Yes I have, and some even approaching 10kl. It's actually easier when you only have one "dimension" to worry about, and can simply scroll up and down instead of having to jump around between functions, or worse, files.
That said, a lot of these huge functions were basically for implementing defined step-by-step processes, and comments delineated the sections as well as loop and condition ends.
I have, at two different companies, in fact, and 1000-line chubbers at a couple of others. The 5000 line functions weren't great, but they weren't that bad because the code was pretty straightforward, doing one thing after another top to bottom.
Over time they all got gradually broken down into smaller functions/methods.
(Disclaimer: I was not the original author of any of these.)
Hyperbole aside what can actually be said doesn't make great bumper sticker advice: functions shouldn't be too long because that's hard to understand, but they shouldn't be too short either because that's also hard to understand. I think a decent rule of thumb is if it doesn't fit on your screen it might be too long.
I have. It was mostly a big switch statement, and individual bodies could be read in sequence. It was easy to work with and you could read it by scrolling, instead of jumping around a file.
The first thing I do when refactoring spaghetti messes is to remove all useless and counter-productive functions and abstractions, so I can actually get one single, huge function containing all the process.
Only from there it becomes possible to have the broad view, reorganize and (re-)factorize the code again properly.
So if I had to choose between both, I'd take the 5000 lines function any time, since it makes my job easier.
Agree, but picking those 1000 sensible names is usually also very hard, and when people get it wrong and use bad names, sometimes it's worse than nothing.
Obviously, both of your extremes are totally unmaintainable and it doesn’t matter which somebody prefers of the two.
What I think matters in the general case is the likely mutation rate of the code, and the familiarity of its design. Both of these are speculative, subjective factors, so right-sizing a function is basically a gut feeling. It can prove wrong in hindsight and others can disagree with your choice from the get-go. So it goes.
But a library function that won’t need much maintenance and implements a well-known pattern, style, or algorithm can safely be much longer than something that’s likely to see a lot of change (especially by other people) or that’s very unusual.
I'd rather work with complex data than with complex code. Just structure the data as best as you can and then use functions that are simple to operate on that data. This will help keep code complexity down and makes it much, much easier to refactor because usually you'll be operating on a small subset of the data anyway.
I had a similar experience. There was a developer in my group that adhered to Clean Code. Four line functions made everything verbose. When he left I had to take over all of his code.
The one part of Clean Code that saved my sanity was his choice of names and flow. That made it somewhat easier to navigate.
I have experiences with other code where it was so abstract, it took much longer to figure out.
I like to go by the rule: "write code for the average developer". I think finding that balance makes it easier to grok the code and maintain it.
Clean code does emphasize that the structure of the class file and layout of functions should strive to allow for the entire class file to be read in a single pass from top to bottom.
Though, I'd agree that is quite different when someone puts in a linter that requires 5 line methods and scatters the flow of logic.
IMHO, "imperative shell with functional core" (which implies consistent levels of abstractions) is a follow up the book could really use.
To another extent, I think small and controversial rules of thumb like "keep functions short" can easily be taken out of context. A refactoring I often see that can be done when methods are poorly broken up is class variables can be converted into local variables once some of the methods are inlined.
The point of the small function is so that a developer can keep track of fewer moving parts in their head while reading. If the shattering into functions causes the class to have 30 class variables in order to support that, then it's very much a case of following the wrong heuristic. IIRC, clean code holds a highest priority on the developer only needing to keep track of 3-5 things at a given time (and pretty much everything else is in service of that goal)
You can have 1000 5-lines functions in all languages, not only java. Typescript FE projects can be as hard as java ones to read.
It seems to me that there's a lot of prejudice against java, maybe because it has been used by many low skilled devs developing poor quality codebases.
It seems to me that there's a lot of prejudice against java, maybe because it has been used by many low skilled devs developing poor quality codebases.
I don't think that's the reason. The cultures of Java, C#, and now TS/JS definitely seem to worship abstraction far too much, and as a result you end up with what's classically known as "enterprise" code. I've seen the work of "low skilled devs" in other languages like PHP, VB, etc. and that leans far on the under-abstraction side; you're far more likely to find lots of copy-pasting and kiloline-long functions (if they even bother to write more than one), yet that code tends to be easier to work with in my experience because it's largely linear and not nested.
From my decade writing software professionally and my current job search, I really question the actual demand for clean code.
That’s unfortunate because it’s my specialty and what gives me job satisfaction.
I love fixing things. I actually enjoy working on a crappy codebase that has made the company money but is now too hard to maintain/extend and needs cleaning. Adding tests, refactoring, extracting functionality to discrete functions, figuring out what the black box actually does, etc. This is what I’ve specialized in.
However, it seems to me that companies don’t actually value that. They all say they do of course, while actually being afraid of doing this because it takes more time and money. Even if they’re mature enough that survival isn’t an immediate concern anymore, there is time to clean things up, and the mess is actually slowing them down through downtime, bugs, and not being able to ship relatively trivial features/updates in less than weeks.
I also suspect that not focusing on clean code is a strategy many managers have because they can show velocity to their higher ups and get promoted before it all blows up and they’re held responsible.
So what do you all think? Is clean code really actually valuable in the eyes of organizations or will they always take the quicker and dirtier option given the choice? Will I ever find work selling code cleaning (even to companies that ask for it) or should I “rebrand” on fast and cheap code at the expense of quality?
> They all say they do of course, while actually being afraid of doing this because it takes more time and money.
That is my experience too with most places - they say that they support clean, well tested and maintainable code and then turn around and ask things to be delivered in unreasonable time resulting in quick and dirty code.
> I also suspect that not focusing on clean code is a strategy many managers have because they can show velocity to their higher ups and get promoted before it all blows up and they’re held responsible.
Indeed - furthermore, because the code lacks quality, it requires more effort to manage and (intentionally or not) helps them to demand headcount which requires more managers which results in them building hierarchies, moving up and having their fiefdom. It is a smart approach too in a perverse sense: how will you get to a position managing 100 people if you only delivered with 5 people due to your efficiency? You'll be labeled as "lacks experience managing large orgs".
So… how do you deal with this on a personal level? How do you retain any shred of sanity or enjoyment from your work?
I can’t stand self inflicted toil and the stress from constant firefighting which is invariably the result of these short term policies. It makes me want to quit programming altogether and work in something totally unrelated even though I love programming. Just not the way employers want it.
I think you can easily roll an analogy with having a plan documented on 5 napkins, bits sprinkled over 127 emails, a few doodles on a white board, 93 photos on your phone, some files in some folder and 20% in your head.
You could start execution right away! Every bit of information is readily available, sure, the numbers in those emails might change in the process but you can just document those changes on additional napkins.
But there is a stage 2 to the plan that needs to happen in 3 to 6 years. By then there will be tens of thousands of relevant emails, hundreds of napkins, you cant find the original photos and you have so much related information in your head that that important thought is buried much like the email... I'm sure I have a folder someplace on this computer.... where is the chat log?
You made promotion so stage 2 is left to the new hire. They had a wonderful resume but for some reason it takes him forever to progress. They wont argue it would have been nice if you wrote down what stage 2 involved years ago. In stead they ask for 20 more people to organize the data.
I get the exact same joys from work. Taking an old but functional and profitable codebase and cleaning it up, bringing it up to conventions and standards, refactoring. At my current job, feature work is always a thing, but I make refactoring boards and get people on board with bringing in refactor tickets every sprint, etc.
So while not 100% of my job is refactoring, a large portion is. And sometimes I convince them it’s time to rewrite one entire section or another, so a couple of sprints at a time might be dedicated to just that.
I guess I’ve gotten lucky, probably a combination of that and being outspoken about what needs to be fixed.
> So what do you all think? Is clean code really actually valuable in the eyes of organizations or will they always take the quicker and dirtier option given the choice? Will I ever find work selling code cleaning (even to companies that ask for it) or should I “rebrand” on fast and cheap code at the expense of quality?
There's many sides to this.
Even Boeing doesn't value clean code (as shown by the 737 MAX disaster where 9$/h coders were hired in India). So you can imagine how much a local non-tech company values clean code.
There's also that a lot of these companies simply... couldn't recognize clean code even if they saw it! Since they aren't competitive with top players, the caliber of programmers they attract is nowhere near the level of engineers at real tech companies. Unless these companies "luck out" with an extremely high caliber hire, they won't really know there's a better way of doing things [0]. Think places where source control is still seen as too complicated or not needed.
It would be prudent to include some data to substantiate your insinuation that some non white non European programmers did the Boeing MCAS code. The 9$ per hour wage maybe less than that of a burger flipper in the USA, but the cost of living is vastly different.Being an engineer , if you are ignoring this significant parameter then we definitely know one source of the issue .Maybe you are not an engineer but a fine arts major who “learned” coding by yourself.
I think as professionals many of us like to take pride in our work. A natural way to do that is for code to be "clean". The problem, as I see it, is that this is often a subjective metric and what we take pride in doesn't always align with what delivers tangible value to an organization (a point you alluded to).
Clean code can bring tangible value, but big rewrites taken on for that goal can often not. I personally try to be more pragmatic--what is valuable depends a lot on context and the point in life of a particular project+team+organization.
Sometimes the most valuable thing is to ship a messy code that works to get the ball rolling or prove viability of an approach. Other times it's to make sure what you deploy is bullet proof to avoid liability or expensive downtime. The value of clean code will be reflected by your team and organization's needs at that point in time.
> what is valuable depends a lot on context and the point in life of a particular project+team+organization.
For sure, and I only target companies or teams that are mature, struggle to move forward because code quality grinds things down to a halt, and that express the desire for improvement there.
However, there are very few companies that 1. Even have the awareness to realize this is where they’re at, and 2. Actually mean it. I’ve been passed over many times in favour of someone else who ships code quickly and cheaply by lowering quality. Every place I’ve ever worked at, quality is the least concern in relation to the others.
Now for a startup that has limited runway and has to find product market fit for example then it’s totally different. Focus rightly is on shipping stuff at the expense of quality because survival is on the line. In a few years, once this company has made it, they’ll look for someone to clean their codebase up.
My broader question though is whether I should keep branding myself as a “code cleaner”. Despite a (minority) of companies advertising positions for that skill, my experience has been that the vast majority simply don’t care. And those that do, don’t actually care all that much. It’s a struggle though because I get no satisfaction from shipping low quality code and eventually end up quitting out of boredom or frustration. I’m now at a point where I’m considering doing something entirely different from programming because I can’t derive any job satisfaction from writing low quality code and the resulting constant firefighting that results from it.
That’s my understanding as well, quality for the sake of it isn’t appealing.
But it is known that a quality foundation enables everything in the business, reduces waste, churn, and increases profits.
Yet when it comes down to actually doing it, nobody cares. Fast and cheap always wins over quality.
That’s not necessarily a problem overall, but it is for me because I derive no satisfaction from shipping low quality code and the constant firefighting that results.
This is a struggle because it’s the vast majority of the demand for software, which makes me want to quit the industry altogether even though I love programming and improving “legacy” codebase. But there seem to be no actual demand for this skill despite the appearances.
> I love fixing things. I actually enjoy working on a crappy codebase that has made the company money but is now too hard to maintain/extend and needs cleaning. Adding tests, refactoring, extracting functionality to discrete functions, figuring out what the black box actually does, etc. This is what I’ve specialized in.
I am thankful that people like you exist, but after having taken on that role out of necessity myself, I've mostly been left disliking the experience.
Working withing under-documented and under-tested "legacy" codebases, especially the kind where the developers got clever with design patterns, both putting them in when they make sense as well as when they didn't. Those codebases are hard to navigate and hard to refactor (outside of fully automatic renaming/extracting interfaces etc.) and even harder to change - in many cases because you're not even aware of the design assumptions or aspects of the architecture that were made by someone who is now long gone.
For example, I worked on a system where users could submit corrections to data and those could either be accepted or removed. There was a CorrectionFormService, that also was related to CorrectionEntryService, but both of those were abstract and had corresponding CorrectionFooFormService and CorrectionFooEntryService instances, as well as an additional CorrectionFooService. In the database, there also were foos and foos_corrections tables, the latter of which was related to correction_forms, sometimes with additional related tables. The problem was that the logic didn't actually fit such neat structure, so depending on whether you're working with Foo, Bar or Baz, more and more of the methods had to be overriden, as well as new ones added, none of which was actually documented. In most cases you were supposed to have the original_id column point at the foos (or whatever) table id that had the actual data, except when for some reason original_id was the same as id and instead you had something like object_id store that information. And on top of that, there were database views which were used for querying the data, where there were additional rules for the id, original_id and object_id column values, with about 10 prior Jira/Redmine issues related to how this data should work. Not only that, but there were also requirements for accepting some of the data recursively (since parent_id was needed for some of the data structures), but not in all of the cases, only when some other enum column had a particular value in a related table.
Long story short, the more you looked into it, the more details spilled out, to the point where you could not keep a full mental picture of it in mind. Some of the implementation was okay, some of it was ridden with iteration overhead and accidental complexity. Contrast getting to write new code and getting things done in hours, versus spending days if not weeks (across multiple developers, actually) working to get things done within this pre-existing setup. Maintenance work will typically take longer than developing new features and, in my personal experience, will be more mentally draining.
I find that you can’t always fix everything. But the biggest hurdle is actually getting buy in (even if there is already buy in and that’s what you were specifically hired to do)
Rewriting is tricky. It’s sometimes necessary (outdated and unmaintained language, obscure tech you can’t find people for, or sheer tech bankruptcy) but it can also not solve anything. As you implement the new version, you rediscover all the arcane business rules, edge cases, and sheer craziness that you didn’t know about and the rewrite ends up either lacking these or becomes The Mess v2 because you had to bolt all this weirdness on after the fact making the new codebase suck in a different way but overall as problematic as the one it replaced.
It will also take much longer than estimated for the same reasons.
Yeah. This fits with my reading of it as well. Periodic chunks of "yep, makes sense" scattered through a really disturbing miasma of questionable stuff and incredibly tightly bound methods sharing gigantic balls of state that must be called in an order, but with nothing that hints at or enforces that order. It adds up to a really horrific result pretty frequently.
I'm not sure how it got its status at the beginning, but I think it retains it through sheer scale - it's a gigantic book for what it actually contains. It batters you with poorly connected ideas over and over and over until you can't tell right from wrong, and in the end you're just agreeing with whatever because doing otherwise gets you nowhere and nothing but pain as you try to read more. It's like a cult brainwashing ritual.
It reminds me of the Elements of Style, which has for decades had a reputation as the book every English writer should read. In reality, it's a mix of obvious truisms ("omit needless words," gee, thanks, but how do I identify the needless ones?) and grammatical advice where the authors are so confused about the concepts that they constantly violate their own precepts. However, at least EB White was a talented author whose writing was beloved. Seems hard to say much complimentary about the sample code presented here.
I once stated a few of the author's criticisms in a forum, one of which was the 3 line function, only to get a reply from Martin himself, saying that he hadn't written that. Unfortunately for him, everybody reads it in the book, and his apostles repeat it, and it's part of his success.
I also found it to flip between "this is obvious advice" and "this is awful advice", and I think that's part of the issue. I've had multiple arguments with people where that natural motte-and-bailey led to "oh, you don't like Clean Code? so you think variables shouldn't have understandable names?"
Clean Code was one of the first books I read as a History student trying to become a self taught developer. From my point of view, coming from the rigor of historiography, the book was inconsistent and dogmatic. Still I took it as a replacement to talking with an experienced engineer, because that’s how it felt and most of the principles were fine when not taken to the extreme.
But now in my career I’ve seen awfully unnecessarily complex code in the name of the SOLID principles and others like DRY. Other times completely misunderstanding what the principles stand for and applying them in hand wavy ways.
This is a personal stretch, but even the marketing behind the principles is a bit of a red flag for me (Clean Code, SOLID, UNCLE BOB????). To me it sounds like someone made a career out of this.
I wish we had a better book to recommend to newcomers.
I'm still amused by claims that the book is dogmatic when these paragraphs are in the opening chapter:
> Consider this book a description of the Object Mentor School of Clean Code. The techniques and teachings within are the way that we practice our art. We are willing to claim that if you follow these teachings, you will enjoy the benefits that we have enjoyed, and you will learn to write code that is clean and professional. But don’t make the mistake of thinking that we are somehow “right” in any absolute sense. There are other schools and other masters that have just as much claim to professionalism as we. It would behoove you to learn from them as well.
> Indeed, many of the recommendations in this book are controversial. You will probably not agree with all of them. You might violently disagree with some of them. That’s fine. We can’t claim final authority. On the other hand, the recommendations in this book are things that we have thought long and hard about. We have learned them through decades of experience and repeated trial and error. So whether you agree or disagree, it would be a shame if you did not see, and respect, our point of view.
Generally dogmatic people don't say, in short, "We could be wrong, we don't think we are, but go see what other people have to say, too.".
The claim is not that the book is dogmatic, but that its worshippers (for lack of a better word) are. The infamous Design Patterns book, despite having a similar "disclaimer", has had a similar effect.
It's dogmatic in the sense that it often pursues some point (such as very short functions) to the level of a dogma, without much regard for the benefit accrued.
If this verbiage is only in the introduction, it will not carry much weight. People, and especially IT people like rules, that you can indiscriminately apply.
I've recommended it before (I think there's even a blog post of mine written in direct response to this article floating around somewhere): A Philosophy of Software Design is really good.
It isn't so concerned with all of the details about what exact decisions you should make ("how many lines should my function have?" etc), but rather the high-level design choices that go into those decisions. A lot of the book is spent on the philosophy of interfaces, which is the author's way of describing the boundaries between a piece of code and its caller.
It's written by John Ousterhout, of Tcl fame (among many other things) based on his own experience, but also his attempts to teach software engineering to students, and the techniques that worked well for those students in practical situations, so I think it's fairly well grounded in actual advice.
I was also thinking about this book when I read that article. It's really an excellent book and it's condensed and because it's written by an expert in teaching it's easy to grasp.
It's also recommended in this article:
"Update, 2020-12-19
After suggestions from comments below, I read A Philosophy of Software Design (2018) by John Ousterhout and found it to be a much more positive experience. I would be happy to recommend it over Clean Code".
Honestly anytime I run into someone spouting something "Uncle Bob" has tried to propagate in our industry while trying to sell books and his "knowledge" I find that it is safe to assume they are probably a perpetual junior level developer.
For example the Open Closed principle is often times used to justify a plethora of inheritance chains rather than having a more generic class that can handle cases based on some parameter. Specially when all a subclass does is modify some class attribute or change some implementation by one line. Obviously some times it's good to apply the principle, but not EVERY time it fits.
There are literally hundreds of better books for newcomers. As just one, I would mention Programming Pearls by Jon Bentley. I would say it communicates a much more healthy approach to the craft.
Good points, interesting context coming from a history student and now with some experience, I'd like to read more on your thoughts if you have a blog.
Thanks! I haven't written much really. I killed my personal site some months ago, and this is the only public blog post I have now, but it's more related to my current side-project and how I got into programming:
"And, when I was listening to them, they all sounded to me like, to me, extremely bureaucratic programming that came from the mind of somebody that has not written a lot of code, frankly."
"So... imagine that someone enters a kitchen, because they want to show you how to make a cup of coffee. As you watch carefully, they flick a switch on the wall. The switch looks like a light switch, but none of the lights in the kitchen turn on or off. Next, they open a cabinet and take down a mug, set it on the worktop, and then tap it twice with a teaspoon. They wait for thirty seconds, and finally they reach behind the refrigerator, where you can't see, and pull out a different mug, this one full of fresh coffee.
...What just happened? What was flicking the switch for? Was tapping the empty mug part of the procedure? Where did the coffee come from?
I almost never want to use the word “clean” when I’m talking about code.
These days, when someone asks me to review code, and they start talking about “clean” code, I shift the discussion to two points—code should be correct and easy to understand. I think “correct and easy to understand” is a much more useful rubric than “clean”. Obviously it’s still subjective. Code that is easy to understand for you may be hard for me to understand. Likewise, code that is correct for your use cases may be incorrect for my use cases. But it’s much easier to come to an agreement about “correct and easy to understand”, or at least communicate issues with the code using that basis.
Like, “you shouldn’t use boolean flags as parameters, you should use enums” becomes “I can‘t understand the meaning of true/false at the call site, so let’s use an enum instead”. This gives us a very clear, articulable basis for how we talk about code quality.
Of course, “correct and easy to understand” is not the be-all and end-all for describing good code. It’s just a nice substitute for the horribly vague, terribly subjective “clean”.
We should break out of the ide and visualize code more. I think then we would see the tangled mess we are creating. And then if we started talking about code that was cleanly visualized, we would truly have understandable code.
We need two-way sync for visualizations of all data structures and data flows. And then we need to see the actual data values inline as it flows through the system as we read the code.
Wallaby.js is a great leader in this space. I found it too hard to setup and too slow - but I’m convinced this applied to the entire stack is the future.
We draw diagrams on the whiteboard to explain every system but they are completely absent when we actually code.
It's a very under-appreciated feature, but I know what you mean.
Alongside NCrunch, Wallaby is one of my most valuable purchases. For those not familiar with the feature being referred to, as a continuous test runner one of the great benefits is that the IDE plugins show the values of anything (variables etc) in your code, live. I don't mean some kind of hover-for-intellisense, but at the end of each line you see all the values for all the variables all the time.
So in real-time, as you code, any tests covering the code you're changing are being run and you can see holistically how everything you're working on is changing as you type.
Basic test runners run continuously on the command line. Better ones run in the background of your IDE, possibly adding coverage markers etc. Wallaby goes one step further and actually decorates your code with real values live in the IDE as you work.
It's very good (though, yes, tricky to set up and a little slow).
There's a deeper problem here that has driven a lot of disputes over the years.
"Ease of understanding" is not a stable property of code. If you have two separate pieces of code that are easy to understand, and the only thing you do with them is combine them, your resulting one piece of code may also be easy to understand.
Or it might be next to impossible to understand.
That's not the reason to use enums instead of parameters for functions, rarely will you not be able to understand what the bool arg means in:
If you don't then you need a better IDE.The reason to use enums and for that matter structs is that as requirements evolve, inevitably you will need to extend the code base and at some point `is_admin` alone won't cut it, you'll need an is_moderator etc.
This leads to two things, one is function signature pollution and unnecessary extra validation code, eg. what happens if both is_admin and is_moderator are set?
What about the functions relying on the old signature? Perhaps you make separate versions of the functions, eg. create_admin_user and create_moderator_user and keep the old one with the flag, then you can keep backward compatibility...but at the cost of an api that makes people go insane when using it.
More likely you'd then go to an enum which leads backward incompatible changes, and likely a refactor, which in the event of a library can break dependent code and in all cases is a lot more work than just using an enum in the first place.
Try to write code for the future reader and writer of the codebase. An enum will allow readers to immediately understand that there is a [normal, admin] distinction and adding a moderator would be as simple as adding a [normal, moderator, admin] in there.
Then, assuming you've written your code in a proper manner, your IDE will tell you all the places where a moderator needs to be accounted for in the code base.
This is what drives me nuts about seemingly simple rules like this: they let you feel like you can turn off your brain. You check all the boxes Clean Code tells you to and you feel like you're done. But you need to think systemically, and little simple maxims like "don't use bool flags" or "enums > bool flags" let engineers miss the forest for the trees over and over again.
IDEs help with this of course, but it's not just the "2 options becomes 3 options" situation that recommends against them
What if you have three people in the same code base who write "easy to understand" code for them, but inconsistent with each other?
Even if those three all understand each other while writing it, this will not continue as the team changes and people have to onboard.
As such, it isn't sufficient to have a linting standard: great codebases should have a consistent mental model. "Clean" code, within the context of Bob Martin "Clean", is one consistent mental model. I don't like all of it, but it gives one coherent top-to-bottom model.
The same applies to code. It is not 100% deterministic metric, but it is relatively easy to argue and have a consensus upon.
Completely agree. Bob Martin “Clean” has some really good recommendations, and some subjective advices. Normally the dev team can use this mental model as baseline and agree on which of the subjective advice to follow.
I just had an issue related to this recently, one of the senior software engineer in my team was previously a university professor with no software engineering experience. Every code review is an endless discussion, as he don’t agree with most of Martin “clean” code. So his code not only has several bad smells, but it feels like a completely different dialect. It forced us to have really basic discussions about code practices, even when to use comments (the professor likes to comment almost every line of code).
I found your argument starting around 28:00 interesting. I’m not familiar with the specific tools you’re using and their idioms, so it’s possible that I’m missing some context here. As a general principle, I like to make it very clear where code is doing I/O and what is happening with any data involved. If this function receives data in one format, transforms that data into another format and finally sends that data to the database, then to me that seems like a useful separation of concerns and a clear representation of the overall behaviour. Maybe I wouldn’t choose the same name/structure as User.registration_changeset in your example, but the principle of hiding the implementation details of the data transformation seems helpful, for the same reason it’s helpful to see Repo.insert() instead of five lines of SQL at this level of the code.
Can’t stand being asked multiple times to change something depending on what order people review code.
I’ve learned to love auto linting for the sole reason that it instantly shuts down any discussion on styling.
That said, in the book he covers correctness and understandability in nearly every paragraph. It's all the book is about really. It's called "clean" because it's concise and a catchy book title.
For example, the book presents a rule for class names:
> Classes and objects should have noun or noun phrase names like Customer, WikiPage, Account, and AddressParser. Avoid words like Manager, Processor, Data, or Info in the name of a class. A class name should not be a verb.
What a proclamation! We could connect this to correctness and understandability. But the book does not. You could say that it is harder to understand a class named “SomethingManager” because “manager” is vague, and more specific words are usually preferred. Instead, the book presents a rule, without explanation or example. Kind of like the Strunk & White book on style, and if you like Strunk & White, then we disagree on at least two books.
That’s my general complaint about the book. Too many proclamations, too much doctrine, not enough explanation or foundation.
there is nothing obvious about this, I think it's very objective what unreadable means (mostly it's complicated without a reason)
"If a function is only called from a single place, consider inlining it.
If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.
If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.
If the work is close to purely functional, with few references to global state, try to make it completely functional.
Try to use const on both parameters and functions when the function really must be used in multiple places.
Minimize control flow complexity and “area under ifs”, favoring consistent execution paths and times over “optimally” avoiding unnecessary work."
http://number-none.com/blow/blog/programming/2014/09/26/carm...
His later addendum is important not to omit, because this is really the key to more reliable code:
> The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don.t let them slide back into impurity!).
A compiler should do this for you.
> If a function is called from multiple places, see if it is possible to arrange for the work to be done in a single place, perhaps with flags, and inline that.
This makes no sense to me.
> If there are multiple versions of a function, consider making a single function with more, possibly defaulted, parameters.
This can be useful, but only if it does not mess up code readability unless you are trying to squeeze your code into a cache.
> If the work is close to purely functional, with few references to global state, try to make it completely functional.
This makes very good sense, but it can be harder than it seems. One of the easiest ways (depending on how far down the call stack you are) you may just be able to pass the individual parts of that global state in as parameters.
> Try to use const on both parameters and functions when the function really must be used in multiple places.
Not all languages support this, but where they do this is good practice. In general: limiting scope and mutability is always an advantage.
> Minimize control flow complexity and “area under ifs”, favoring consistent execution paths and times over “optimally” avoiding unnecessary work.
Is also good advice.
In general you want to avoid nesting your ifs too deeply because at some point you lose track of what the local context is that got you there. Then it is usually better to break out a function and name it well so that that context is clear again. In general, naming things well is hard.
Point of inlining is to make it explicit that it is an ad hoc implementation for that location, so you don't have to be afraid of adding more stuff in it just to solve a local problem.
Unless it outputs source code, it can't.
The main keyword is consider. Sometimes it's a good idea, and sometimes (often) it isn't.
but we assume that Martin doesn't literally mean that every function in our entire application must be four lines long or less.
Unfortunately I have worked (and fortunately, briefly!) with codebases like that --- not surprisingly, in Java. In addition to the complexity of whatever the code is doing, smashing things into tiny pieces also adds its own complexity. The fact that a short function is "easily understandable" on its own, which is what a lot of proponents of this dogma argue, is useless for understanding the functioning of the whole. The latter is far more important when debugging.
Function length is ultimately meaningless. I'd rather have a 5000-line function that reads top-to-bottom, than 1000 5-line functions if it means I don't have to jump around several-dozen-level-deep callstacks and try to keep track of 30+ character long identifiers to understand how the thing works.
Not easy to spot side effects if the 5 line function calls 999 other 5 line functions, which you have to do if you replace 1 function with 1000 functions. When people start to arbitrarily break up tightly coupled implementations into tiny functions you will get a much worse mess than if they just kept it as big as it was when they developed it.
Small functions are great, but forcing people to write small functions is not great. They are two different things.
That said, a lot of these huge functions were basically for implementing defined step-by-step processes, and comments delineated the sections as well as loop and condition ends.
Over time they all got gradually broken down into smaller functions/methods.
(Disclaimer: I was not the original author of any of these.)
Only from there it becomes possible to have the broad view, reorganize and (re-)factorize the code again properly.
So if I had to choose between both, I'd take the 5000 lines function any time, since it makes my job easier.
What I think matters in the general case is the likely mutation rate of the code, and the familiarity of its design. Both of these are speculative, subjective factors, so right-sizing a function is basically a gut feeling. It can prove wrong in hindsight and others can disagree with your choice from the get-go. So it goes.
But a library function that won’t need much maintenance and implements a well-known pattern, style, or algorithm can safely be much longer than something that’s likely to see a lot of change (especially by other people) or that’s very unusual.
But we work with 1000 five lines functions all the time in high level languages.
All the stuff done on array/list/hash and the likes (map,fold/inject/reduce,sum,select,reject..) are five lines functions.
Why would you not want that for your own types?
The one part of Clean Code that saved my sanity was his choice of names and flow. That made it somewhat easier to navigate.
I have experiences with other code where it was so abstract, it took much longer to figure out.
I like to go by the rule: "write code for the average developer". I think finding that balance makes it easier to grok the code and maintain it.
Though, I'd agree that is quite different when someone puts in a linter that requires 5 line methods and scatters the flow of logic.
IMHO, "imperative shell with functional core" (which implies consistent levels of abstractions) is a follow up the book could really use.
To another extent, I think small and controversial rules of thumb like "keep functions short" can easily be taken out of context. A refactoring I often see that can be done when methods are poorly broken up is class variables can be converted into local variables once some of the methods are inlined.
The point of the small function is so that a developer can keep track of fewer moving parts in their head while reading. If the shattering into functions causes the class to have 30 class variables in order to support that, then it's very much a case of following the wrong heuristic. IIRC, clean code holds a highest priority on the developer only needing to keep track of 3-5 things at a given time (and pretty much everything else is in service of that goal)
Deeply nested code is read only, unless the problem is nicely modelled as recursive in some way.
It’s when your data items have children, which can have children, which can have children...
I don't think that's the reason. The cultures of Java, C#, and now TS/JS definitely seem to worship abstraction far too much, and as a result you end up with what's classically known as "enterprise" code. I've seen the work of "low skilled devs" in other languages like PHP, VB, etc. and that leans far on the under-abstraction side; you're far more likely to find lots of copy-pasting and kiloline-long functions (if they even bother to write more than one), yet that code tends to be easier to work with in my experience because it's largely linear and not nested.
That’s unfortunate because it’s my specialty and what gives me job satisfaction.
I love fixing things. I actually enjoy working on a crappy codebase that has made the company money but is now too hard to maintain/extend and needs cleaning. Adding tests, refactoring, extracting functionality to discrete functions, figuring out what the black box actually does, etc. This is what I’ve specialized in.
However, it seems to me that companies don’t actually value that. They all say they do of course, while actually being afraid of doing this because it takes more time and money. Even if they’re mature enough that survival isn’t an immediate concern anymore, there is time to clean things up, and the mess is actually slowing them down through downtime, bugs, and not being able to ship relatively trivial features/updates in less than weeks.
I also suspect that not focusing on clean code is a strategy many managers have because they can show velocity to their higher ups and get promoted before it all blows up and they’re held responsible.
So what do you all think? Is clean code really actually valuable in the eyes of organizations or will they always take the quicker and dirtier option given the choice? Will I ever find work selling code cleaning (even to companies that ask for it) or should I “rebrand” on fast and cheap code at the expense of quality?
That is my experience too with most places - they say that they support clean, well tested and maintainable code and then turn around and ask things to be delivered in unreasonable time resulting in quick and dirty code.
> I also suspect that not focusing on clean code is a strategy many managers have because they can show velocity to their higher ups and get promoted before it all blows up and they’re held responsible.
Indeed - furthermore, because the code lacks quality, it requires more effort to manage and (intentionally or not) helps them to demand headcount which requires more managers which results in them building hierarchies, moving up and having their fiefdom. It is a smart approach too in a perverse sense: how will you get to a position managing 100 people if you only delivered with 5 people due to your efficiency? You'll be labeled as "lacks experience managing large orgs".
I can’t stand self inflicted toil and the stress from constant firefighting which is invariably the result of these short term policies. It makes me want to quit programming altogether and work in something totally unrelated even though I love programming. Just not the way employers want it.
You could start execution right away! Every bit of information is readily available, sure, the numbers in those emails might change in the process but you can just document those changes on additional napkins.
But there is a stage 2 to the plan that needs to happen in 3 to 6 years. By then there will be tens of thousands of relevant emails, hundreds of napkins, you cant find the original photos and you have so much related information in your head that that important thought is buried much like the email... I'm sure I have a folder someplace on this computer.... where is the chat log?
You made promotion so stage 2 is left to the new hire. They had a wonderful resume but for some reason it takes him forever to progress. They wont argue it would have been nice if you wrote down what stage 2 involved years ago. In stead they ask for 20 more people to organize the data.
So while not 100% of my job is refactoring, a large portion is. And sometimes I convince them it’s time to rewrite one entire section or another, so a couple of sprints at a time might be dedicated to just that.
I guess I’ve gotten lucky, probably a combination of that and being outspoken about what needs to be fixed.
There's many sides to this.
Even Boeing doesn't value clean code (as shown by the 737 MAX disaster where 9$/h coders were hired in India). So you can imagine how much a local non-tech company values clean code.
There's also that a lot of these companies simply... couldn't recognize clean code even if they saw it! Since they aren't competitive with top players, the caliber of programmers they attract is nowhere near the level of engineers at real tech companies. Unless these companies "luck out" with an extremely high caliber hire, they won't really know there's a better way of doing things [0]. Think places where source control is still seen as too complicated or not needed.
[0] https://www.hanselman.com/blog/dark-matter-developers-the-un...
Clean code can bring tangible value, but big rewrites taken on for that goal can often not. I personally try to be more pragmatic--what is valuable depends a lot on context and the point in life of a particular project+team+organization.
Sometimes the most valuable thing is to ship a messy code that works to get the ball rolling or prove viability of an approach. Other times it's to make sure what you deploy is bullet proof to avoid liability or expensive downtime. The value of clean code will be reflected by your team and organization's needs at that point in time.
For sure, and I only target companies or teams that are mature, struggle to move forward because code quality grinds things down to a halt, and that express the desire for improvement there.
However, there are very few companies that 1. Even have the awareness to realize this is where they’re at, and 2. Actually mean it. I’ve been passed over many times in favour of someone else who ships code quickly and cheaply by lowering quality. Every place I’ve ever worked at, quality is the least concern in relation to the others.
Now for a startup that has limited runway and has to find product market fit for example then it’s totally different. Focus rightly is on shipping stuff at the expense of quality because survival is on the line. In a few years, once this company has made it, they’ll look for someone to clean their codebase up.
My broader question though is whether I should keep branding myself as a “code cleaner”. Despite a (minority) of companies advertising positions for that skill, my experience has been that the vast majority simply don’t care. And those that do, don’t actually care all that much. It’s a struggle though because I get no satisfaction from shipping low quality code and eventually end up quitting out of boredom or frustration. I’m now at a point where I’m considering doing something entirely different from programming because I can’t derive any job satisfaction from writing low quality code and the resulting constant firefighting that results from it.
But it is known that a quality foundation enables everything in the business, reduces waste, churn, and increases profits.
Yet when it comes down to actually doing it, nobody cares. Fast and cheap always wins over quality.
That’s not necessarily a problem overall, but it is for me because I derive no satisfaction from shipping low quality code and the constant firefighting that results.
This is a struggle because it’s the vast majority of the demand for software, which makes me want to quit the industry altogether even though I love programming and improving “legacy” codebase. But there seem to be no actual demand for this skill despite the appearances.
I am thankful that people like you exist, but after having taken on that role out of necessity myself, I've mostly been left disliking the experience.
Working withing under-documented and under-tested "legacy" codebases, especially the kind where the developers got clever with design patterns, both putting them in when they make sense as well as when they didn't. Those codebases are hard to navigate and hard to refactor (outside of fully automatic renaming/extracting interfaces etc.) and even harder to change - in many cases because you're not even aware of the design assumptions or aspects of the architecture that were made by someone who is now long gone.
For example, I worked on a system where users could submit corrections to data and those could either be accepted or removed. There was a CorrectionFormService, that also was related to CorrectionEntryService, but both of those were abstract and had corresponding CorrectionFooFormService and CorrectionFooEntryService instances, as well as an additional CorrectionFooService. In the database, there also were foos and foos_corrections tables, the latter of which was related to correction_forms, sometimes with additional related tables. The problem was that the logic didn't actually fit such neat structure, so depending on whether you're working with Foo, Bar or Baz, more and more of the methods had to be overriden, as well as new ones added, none of which was actually documented. In most cases you were supposed to have the original_id column point at the foos (or whatever) table id that had the actual data, except when for some reason original_id was the same as id and instead you had something like object_id store that information. And on top of that, there were database views which were used for querying the data, where there were additional rules for the id, original_id and object_id column values, with about 10 prior Jira/Redmine issues related to how this data should work. Not only that, but there were also requirements for accepting some of the data recursively (since parent_id was needed for some of the data structures), but not in all of the cases, only when some other enum column had a particular value in a related table.
Long story short, the more you looked into it, the more details spilled out, to the point where you could not keep a full mental picture of it in mind. Some of the implementation was okay, some of it was ridden with iteration overhead and accidental complexity. Contrast getting to write new code and getting things done in hours, versus spending days if not weeks (across multiple developers, actually) working to get things done within this pre-existing setup. Maintenance work will typically take longer than developing new features and, in my personal experience, will be more mentally draining.
Rewriting is tricky. It’s sometimes necessary (outdated and unmaintained language, obscure tech you can’t find people for, or sheer tech bankruptcy) but it can also not solve anything. As you implement the new version, you rediscover all the arcane business rules, edge cases, and sheer craziness that you didn’t know about and the rewrite ends up either lacking these or becomes The Mess v2 because you had to bolt all this weirdness on after the fact making the new codebase suck in a different way but overall as problematic as the one it replaced.
It will also take much longer than estimated for the same reasons.
I’m not super keen on rewrites in most cases.
I'm not sure how it got its status at the beginning, but I think it retains it through sheer scale - it's a gigantic book for what it actually contains. It batters you with poorly connected ideas over and over and over until you can't tell right from wrong, and in the end you're just agreeing with whatever because doing otherwise gets you nowhere and nothing but pain as you try to read more. It's like a cult brainwashing ritual.
Deleted Comment
Deleted Comment
But now in my career I’ve seen awfully unnecessarily complex code in the name of the SOLID principles and others like DRY. Other times completely misunderstanding what the principles stand for and applying them in hand wavy ways.
This is a personal stretch, but even the marketing behind the principles is a bit of a red flag for me (Clean Code, SOLID, UNCLE BOB????). To me it sounds like someone made a career out of this.
I wish we had a better book to recommend to newcomers.
> Consider this book a description of the Object Mentor School of Clean Code. The techniques and teachings within are the way that we practice our art. We are willing to claim that if you follow these teachings, you will enjoy the benefits that we have enjoyed, and you will learn to write code that is clean and professional. But don’t make the mistake of thinking that we are somehow “right” in any absolute sense. There are other schools and other masters that have just as much claim to professionalism as we. It would behoove you to learn from them as well.
> Indeed, many of the recommendations in this book are controversial. You will probably not agree with all of them. You might violently disagree with some of them. That’s fine. We can’t claim final authority. On the other hand, the recommendations in this book are things that we have thought long and hard about. We have learned them through decades of experience and repeated trial and error. So whether you agree or disagree, it would be a shame if you did not see, and respect, our point of view.
Generally dogmatic people don't say, in short, "We could be wrong, we don't think we are, but go see what other people have to say, too.".
It isn't so concerned with all of the details about what exact decisions you should make ("how many lines should my function have?" etc), but rather the high-level design choices that go into those decisions. A lot of the book is spent on the philosophy of interfaces, which is the author's way of describing the boundaries between a piece of code and its caller.
It's written by John Ousterhout, of Tcl fame (among many other things) based on his own experience, but also his attempts to teach software engineering to students, and the techniques that worked well for those students in practical situations, so I think it's fairly well grounded in actual advice.
It's also recommended in this article:
"Update, 2020-12-19 After suggestions from comments below, I read A Philosophy of Software Design (2018) by John Ousterhout and found it to be a much more positive experience. I would be happy to recommend it over Clean Code".
https://pomasearch.com/blog/why-is-this-site-named-poma-and-...
"And, when I was listening to them, they all sounded to me like, to me, extremely bureaucratic programming that came from the mind of somebody that has not written a lot of code, frankly."
...What just happened? What was flicking the switch for? Was tapping the empty mug part of the procedure? Where did the coffee come from?
That's what this code is like."
This is such a good metaphor