6 ms·
"Clean code" needs a re-brand. It seems like constantly, over my 25+ year career, I'm seeing never-ending push back against design patterns, abstractions, de-du
by gspencley 3y ago
"Clean code" needs a re-brand. It seems like constantly, over my 25+ year career, I'm seeing never-ending push back against design patterns, abstractions, de-duplication etc.
There are always the same reasons:
- Abstractions make code more complex
- We don't have time to write clean code
- It's all just your opinions and preferences
The purpose of clean code is to make code SIMPLER, and easier to maintain as requirements change. The value of software is precisely its ability to change over time. Otherwise we could stick to fixed circuits which are FAR easier and cheaper to implement and maintain.
Therefore, if you did not achieve these goals with your refactor, and your boss can make a persuasive argument that your refactor made it HARDER to maintain as requirements change into the future ... then your refactored code wasn't "clean", now was it?
As for not discussing the refactor with the original developer, that's outside the scope of clean code. Has nothing to do with whether or not clean code is good or bad, or whether your changes were "cleaner" or not. That's a process and etiquette discussion. Just because you did a dick move and went cowboy doesn't say anything about whether or not clean code is valuable.
- foobarian 3y agoOne thing I learned in industry is that the best cleanly written code does not survive contact with product. Sometimes the technical debt is encoded in the product requirements themselves, and no amount of refactoring or best practices can fix it, only mitigate the pain.
- gspencley 3y agoSure. We see that all the time. Error handling and security are often afterthoughts, for example. We engineers tend to get "commissioned" to build quick proof of concepts and once the business sees their vision come to reality they don't want it to be thrown out and redone with architectural consideration, they want to rush it to market as the "MVP" as quick as humanly possible. This is a reality of our industry that we need to manage and find ways to cope with. When it comes to certain architectural decisions, there are things that are very hard to change later on, and sometimes you don't know how the product will need to scale, or what challenges the engineering team will be faced with a year, two years, 5 years down the line. This is where we need to make our best guesses and we can't always get it right. When it comes to the rest, making the code as simple and easy to change is the best we've got. I was recently reminded by someone I look up to and admire that not everyone has the "gift" of being able to think "top down." This came up because I am often frustrated by the claim that "we don't have time to write this cleanly." It's a frustrating claim to me, because I don't find that writing short, single responsibility code that is well labelled takes any time at all. I also spent time learning how to work TDD so I can write unit tests as part of the development and design process rather than having to go back and write tests after the fact. But a lot of developers build "bottom up", where they write really long functions or methods, or dump everything into a single file and then think about separation of concerns, and layers and isolated "components" later on and thus need to spend a lot of lengthy time refactoring after the fact. That initial "bottom up" process is the "getting it to work" part of the process, which is fun for most devs. But if they then need to refactor after it is tedious and laborious and not fun at all. And I think this is probably where the vast majority of the push-back and friction amongst devs really comes from if we look at it from a human point of view. If you enjoy the process of DESIGNING software, and thinking top down, you won't find refactoring to be a chore or time consuming. You will understand how a well chosen abstraction can SIMPLIFY rather than complicate. But if you like to solve problems primarily, and to build things you haven't built before, and by "building" it means getting something that "works" up and running quickly ... then you don't want to spend your time reading books on design patterns, or refactoring your PR because the Principal told you it's not up to standards.
- pif 3y ago> I don't find that writing short, single responsibility code that is well labelled takes any time at all. Writing it takes no time; designing it does. > That initial "bottom up" process is the "getting it to work" part of the process, which is fun for most devs. It's not about having fun: it's about getting the functionality to the first users, and then, finally, you can get some slack and have fun and refactor your code for future changes. But functionality is what sells: clean code is invisible to the users and thus to the business. Clean code is for us developers, and only for us. We have the right to be egoist and work for our own sake from time to time, and a working functionality is what will let the business close an eye over it for some time.
- gspencley 3y ago> It's not about having fun: it's about getting the functionality to the first users, and then, finally, you can get some slack and have fun and refactor your code for future changes. You're speaking to business motivation, which I intentionally did not touch upon. Everything you said is valid. What I'm speaking about is developer push-back against "clean code." And when a developer is advocating for the business, then it is business push-back. Business push-back is valid because a) their business needs are valid and b) it is very difficult to correlate code quality with business value. There is a correlation, because developer velocity and ability to grow the feature-set and fix defects quickly and efficiently benefits the business as it reduces cost over time and benefits users. But that benefit is long-term and not immediately visible by end users. The business doesn't always want to pay for this up front which is their prerogative, and then we circle back to what you said about developers doing it in order to make our own lives easier because we know the day is going to come when the business gets frustrated that velocity was extremely high at the beginning of the project life-cycle but has ground to a halt. In any event, developer push back is real, and is not tied to business values but to personal developer values. I see code comments (which IMO are smells and point to smells) that say things like "// no point in extracting this anonymous function as it is only a few lines long" ... meanwhile doing so would have taken no extra time at all and it is buried in the middle of a 300 line Promise chain or rjxs stream etc.
- 3y ago
- fauigerzigerk 3y agoI think the pushback is against certain recipes that seem too absolutist. "Clean Code" is the title of a famous book that defines clean (among other things) as: "No Duplication Duplication is the primary enemy of a well-designed system. It represents additional work, additional risk, and additional unnecessary complexity." So according to this definition, removing duplication is synomymous with simplification, which is simply incorrect. Removing duplication is the introduction of a dependency. If this dependency is a good model of the problem then this deduplication is a good abstraction and may also be a simplification. Otherwise it's just compression in the guise of abstraction. [Edit] Actually, this quote is a reference to Kent Beck’s Simple Design that appears in Clean Code.
- no_wizard 3y agoI'm not really sure I know what you mean by "compression in the guise of abstraction". Re-usable code is a great way to isolate a discrete piece of logic / functionality to test and use in a repeatable manner. A sharable module is the simplest and often smallest form of abstraction.
- fauigerzigerk 3y agoReusing a function C in functions A and B makes A and B dependent on C. If the definition of C changes, the definitions of A and B also change. So this is more than reusing some lines of code. It's a statement that you want A and B to change automatically whenever C changes. If this dependency is introduced purely out of a desire to reuse the lines of code that make up C, then I'm calling it compression. In my view, this is a bad and misleading form of abstraction if you can call it that at all.
- rewmie 3y ago> Reusing a function C in functions A and B makes A and B dependent on C. If the definition of C changes, the definitions of A and B also change. To pile onto this example, in some cases the mastermind behind these blind deduplication changes doesn't notice that the somewhat similar code blocks reside in entirely different modules. Refactoring these code blocks into a shared function ends up introducing a build time dependency where previously there was none, and as a result at best your project takes longer to build because independent modules are now directly dependent or at worsr you just introduced cyclic dependencies.
- wredue 3y agoWhat a giant pile of “no true Scotsman” The purpose of clean code and the results of practicing it, it turns out, are vastly different. The reason for that is because people are generally incapable of predicting any future state of their code even if they have the requirements in front of them. Clean code necessitates that dreadful question of “what if”, which is a horrific Pandora’s box of never getting your shit out the door. It is nearly always better to code for what you know, and to have a person experienced in the domain to otherwise guide the architecture.
- gspencley 3y agoI never made the argument of "no true developer." I pointed to specific reasons cited when push-back is given, and then explained why I disagree with those points. > Clean code necessitates that dreadful question of “what if”, Nope. Not even a little. "Clean code" can be as simple as keeping your moving parts isolated and loosely coupled so that they are easier to debug, to reason about and to reuse in new contexts if needed. What you're talking about, the "what if" game ... is what Martin Fowler termed "Speculative Generality" in his book "Refactoring" when cataloguing code smells and sources of complexity. In other words, the exact opposite of what I'm talking about.
- wredue 3y agoSorry, but your entire post is a “no true Scotsman”. >if the code is not maintainable, then it’s not real clean code” If this isn’t a word for word prime example of “no true Scotsman”, I don’t know what is.
- secondcoming 3y agoYup, Clean Code is to make the life of $NEXT_PERSON easier. The value put on this varies from company to company. Some require it, others will balk at not releasing a feature because you want to tidy the code up from being prototype quality.
- SonicScrub 3y agoIt's a good idea to remember that $NEXT_PERSON could quite possibly you a year from now, when you've probably forgotten what you were on about previously. There are selfish reasons to clean code as well!
- hkwerf 3y ago> Otherwise we could stick to fixed circuits which are FAR easier and cheaper to implement and maintain. While this is not the key point of your post, this in particular is also not the case. With even a little complexity, it quickly becomes much easier and cheaper to implement functionality in Software on a generic CPU than in fixed circuits. For example, we often integrate simple CPU in products that otherwise are fixed circuits, just to perform single functions as they would be too resource intensive and hard to verify. The CPU then becomes a slave to the surrounding circuit.
- simiones 3y ago"Clean code" can mean two things: (1) someone's subjective feeling that a certain piece of code is nice, and (2) following the (majority of) the guidelines in the book Clean Code and related. What people have started feeling, myself included, is that (2) and (1) are fundamentally different in many places. That is, that following some of the guidelines of Clean Code the book produces code that is very much not clean. So sure, everyone agrees that clean code is better, almost by definition. There are indeed valid (usually business/time) reasons to go for code you know is messy, but those are all external - in an ideal world, everyone wants to write clean code. But, increasingly, people don't feel that Clean Code is a good way to achieve that. In fact, many of the patterns in Clean Code have ended up being considered sources of messy code by many people - over-abstraction and de-duplication perhaps chief amongst them.
- brtkdotse 3y agoAfter numerous failed attempts at reading Clean Code, I'd say (1) and (2) are very much at odds.
- meheleventyone 3y agoI think the broader issue is that there is little valuable empirical evidence that the stuff people say is clean code actually makes things simpler. Likewise I find it unlikely that there is one way to make things simpler and its the way associated with the book. It's all vibes.
- 4star3star 3y agoI like to keep in mind a companion rule along with DRY (don't repeat yourself): Don't make the reader repeat themself, i.e. re-read your code multiple times after you've cleverly repackaged it.
- spion 3y agoIf you don't mind, I'd like to propose an alternative list: - Most of our abstractions are bad, even those we think are good. The good ones are largely already in our libraries (although even that isn't always the case unfortunately). - Bad abstractions are a time sink. - Writing code is also about communicating with other humans (on the team), so what they think about communication matters a lot more than what I think
- mostlylurks 3y ago> Bad abstractions are a time sink Yes, but contrary to the general perception, I fend them to be far less of a time sink than duplicated code. It takes almost no time at all to bypass or rework a bad abstraction, but code duplication necessitates either a literal multiplication of your efforts wherever you need to add features to duplicated code, or to refactor the duplication into a non-duplicated form, which also takes a lot longer than dealing with a bad abstraction. Abstractions are cheap and disposable, even if they're bad. Duplication is expensive and hard to get rid of.
- spion 3y agoAbstractions are not cheap and disposable. If 5 places use one abstraction, it takes at least (1) 5x the effort to dispose of them compared to changing one of those places. (1) At least 5, because sometimes its worse. Example: lets say you are using a bad ORM which in addition doesn't expose its connection pool in any reusable way (most don't). You want to introduce a good ORM, but you can't because duplicating the size of the connection pool is too expensive. Additionally, because the ORM triggers events when objects are created and modified, and database state relies on that, you cannot use the other ORM for object creation and modification unless you replace them all at once. So you would have to refactor your code to introduce your own data access layer that has implementations for both ORMs, then switch over all at once. In contrast, introducing deduplication later is somewhat easier. You can build an abstraction and test it with 1 or 2 of the existing parts of the code, then migrate the rest incrementally instead of all-at-once. It will also prevent some bad (expensive) aspects of the design due to the requirement that it must be incrementally adoptable (must be able to have a reusable connection pool, demand a more reliable change events system etc) and as a corollary it will also be incrementally removable.
- __loam 3y agoPhilosophy of Software Design is a much better book discussing these ideas than clean code. Part of the problem is uncle Bob's book is now tied up with the idea of clean code, but his book sucks.
- PH95VuimJjqBqy 3y agoPeople never think about complexity as a tool, but it's as much of a tool as the lowly assumption. If anything, they're two sides of the same coin. You can make things simple by making assumptions, you can enable cool things by making things complex. the thing about assumptions is that they're dangerous. easy example: assuming the timezone in a db column vastly simplifies things but if you're ever wrong things can go terribly wrong. Which is why some of the most dangerous developers on your team are the ones who either don't _respect_ assumptions or don't even realize they're building in assumptions. The flip side is true for complexity. You can absolutely harness complexity to great effect, but if you have developers who don't realize they're building in complexity or don't respect the cost of that complexity, you have dangerous developers.