677 comments

[ 5.9 ms ] story [ 408 ms ] thread
Java is still popular. JDK 17 looks as much like ML (e.g. oCAML) as they can make it as fast they can make it without breaking things.
Also, the tooling and compiler speed aren't fucked like they are in Scala or Kotlin. I like Kotlin, especially the null-safety, but the ecosystem's quality is kinda shoddy. Everything is just faster and less buggy in Java.
Martin is, and always has been, a plagiarist, ghost-written, clueless, idiot, with a way of convincing other know-nothings that he knew something. At one time he tried to set up a reputation on StackOverflow, and was rapidly seen off.

Toxic. Avoid.

Yes let's please do this. I'm tried of this book being brought up at work.

My clean code book:

* Put logic closest to where it needs to live (feature folders)

* WET (Write everything twice), figure out the abstraction after you need something a 3rd time

* Realize there's no such thing as "clean code"

> WET (Write everything twice), figure out the abstraction after you need something a 3rd time

so much this. it is _much_ easier to refactor copy pasta code, than to entangle a mess of "clean code abstractions" for things that isn't even needed _once_. Premature Abstraction is the biggest problem in my eyes.

Write Code. Mostly functions. Not too much.

I think where DRY trips people up is when you have what I call "incidental repetition". Basically, two bits of logic seem to do exactly the same thing, but the contexts are slightly different. So you make a nice abstraction that works well until you need to modify the functionality in one context and not the other...
So much this. Especially early in project's life when you aren't sure what the contexts/data model/etc really need to be, so much logic looks the same. It becomes so hard to untangle later.
If you mostly deduplicate by writing functions, fixing this problem is never very hard: duplicate the function, rename it and change the call-site.

The interesting thing about DRY is that opinions about it seem to depend on what project you’ve worked on most recently: I inherited a codebase written by people skeptical of DRY, and we had a lot of bugs that resulted from “essential duplication”. Other people inherit code written by “architecture astronauts”, and assume that the grass is greener on the WET side of the fence.

Personally, having been in both situations, I’d almost always prefer to untangle a bad abstraction rather than maintain a WET codebase.

Conversely, fixing duplication is never hard. Just move the duplicated code into a function. Going in reverse can be much tougher if the function has become an abstraction, where you have to figure out what path each function call was actually taking.

Or, put another way: I'd much rather deal with duplication than with coupling problems.

The issue I have is that duplication is a coupling problem, but there’s no evidence in the coupled parts of the code that the coupling exists. It can be ok on a single-developer project or if confined to a single file, but my experience is that it’s a major contributor to unreliability, especially when all the original authors of the code have left.
I think the opposite is true. Bad abstractions can be automatically removed with copy/paste and constant propagation. N pieces of code that are mostly the same but have subtle differences have no automatic way to combine them into a single function.
N pieces of code that are mostly the same but have subtle differences isn't repetition and probably shouldn't be combined into a single function, especially if the process of doing so it non-obvious.
I think they mean if the code does the same thing but has syntax differences. Variables are named differently, one uses a for loop while the other uses functional list operations, etc.
Often it is. Earlier in this thread I used the example of a unit system - one example of there there can be a ton of repetition to remove, but there are fundamental differences between liters and meters that make removing the duplication hard if you didn't realize upfront you had a problem. Once you get it right converting meters to chains isn't that hard (I wonder how many reading even know chain was a unit of measure - much less have any idea how big it is), but there are a ton of choices to make it work.
> N pieces of code that are mostly the same but have subtle differences isn’t repetition

Often, they are.

IME, a very common pattern is divergent copypasta where – because there is no connection once the copying occurs – a bug is noticed and fixed in one place and not in others, later noticed separately and fixed a slighly different way in some of the others, in still others a different thing that needs done in the same function gets inserted in between parts of the copypasta, etc. IT’s still essentially duplication – more over its still the same logical function being performed different places, but in slightly different ways, creating a singificant multiple on maintainance cost, which – not literal code duplication in and of itself, is the actual problem addressed with DRY, which is explicitly not about duplication of code but single source of truth of knowledge: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”. Divergent implementations of the same logical function are different representations of the same knowledge.

You never know if these subtle differences were intentional to begin with. It might have been the same once upon a time, but then during an emergency outage someone makes a quick fix in one place but forgets to update all other copies of this code.

Repeating what others already mentioned, often it can be the same thing but written in a slightly different way. Even basic stuff like string formatting vs string concatenation can make it non-obvious that two pieces of code are copies.

> Conversely, fixing duplication is never hard. Just move the duplicated code into a function.

I think the single biggest factor determining the difficulty of a code change is the size of the codebase. Codebases with a lot of duplication are larger, and the scale itself makes them harder to deal with. You may not even realize when duplication exists because it may be spread throughout the code. It may be difficult to tell which code is a duplicate, which is different for arbitrary reasons, and which is different in subtle but important ways.

Once you get to a huge sprawl of code that has a ton of mostly-pointless repetition, it is a nightmare to tame it. I would much rather be handed a smaller but more intertwined codebase that only says something once.

The problem with duplication it is hard to spot and fix. Converting liters to ml or quarts isn't hard, but the factors are different, and there is also other units. If you only do a few of these isn't a big deal, but if you suddenly realize that you have tons of different conversions scattered around and you really need to implement a good unit conversion system it will be really hard to retrofit everything. Note that even if you have a literToml, Literto Quart and MileToKm functions retrofitting the right system will be hard. (Where I work we have gone through 4 different designs of a uber unit system module before we actually got all the requirements right, and each transition was a major problem)
Fixing duplication is never hard because by nature, duplicated code will drift over time even if it shouldn't have. So it's technically not "duplicate" anymore even if they are supposed to do the exact same thing.

Fixing a bad abstraction is only hard because there's some weird culture about not tearing down abstractions. Rip them apart and toss them on the heap. It's a million times easier than finding duplicate code that has inadvertently drifted apart over time.

There are certain abstractions that can cause real problems: XML-driven DI, bad ORMs, code generators, etc. But, in general, I agree: people are generally too unwilling to refactor aggressively.
If you find a bug in the duplicated part and has no idea that it was actually duplicated (or even if you do, where are they?), you still have multiple lurking bugs around.
Yep. Re-use is difficult. When you overdo it you cause a whole new set of problems. I once watched a person write a python decorator that tried unify HTTP header based caching with ad hoc application caching done using memcached.

When I asked exactly what they were trying to accomplish they kept saying "they didn't want caching in two places". I think anyone with experience can see that these items are unrelated beyond both having the word "cache"

This is what was actually hanging them up ... the language. Particularly the word "cache". I've seen this trap walked into over and over. Where a person tries to "unify" logic simply because two sets of logic contain some of the same words.

Then you copy function and modify one place? I don't get what is so hard about it. The IDE will even tell you about all places where function is called, there is no way to miss one.
> it is _much_ easier to refactor copy pasta code

So long as it remains identical. Refactoring almost identical code requires lots of extremely detailed staring to determine whether or not two things are subtly different. Especially if you don't have good test coverage to start with.

> requires lots of extremely detailed staring

Yeah, nah. There are diffing tools.

problem is you only diff if things look different
I personally love playing the game called "reconcile these very-important-but-utterly-without-tests sequences of gnarly regexes that were years ago copy-pasted in seven places and since then refactored, but only in three of the seven places, and in separate efforts each time".
"Write Code. Mostly Functions. Not too much" made my day and is excellent advice.
>it is _much_ easier to refactor copy pasta code

I totally agree assuming that there will be time to get to the second pass of the "write everything twice" approach...some of my least favorite refactoring work has been on older code that was liberally copy-pasted by well-intentioned developers expecting a chance to come back through later but who never get the chance. All too often the winds of corporate decision making will change and send attention elsewhere at the wrong moment, and all those copy pasted bits will slowly but surely drift apart as unfamiliar new developers come through making small tweaks.

I worked on a small team with a very "code bro" culture. No toxic, but definitely making non-PC jokes. We would often say "Ask your doctor about Premature Abstractuation" or "Bad news, dr. says this code has abstractual dysfunction" in code reviews when someone would build an AbstractFactoryFactoryTemplateConstructor for a one-off item.

When we got absorbed by a larger team and were going to have to "merge" our code review / git history into a larger org's repos, we learned that a sister team had gotten in big trouble with the language cops in HR when they discovered similar language in their git commit history. This brings back memories of my team panicked over trying to rewrite a huge amount of git history and code review stuff to sanitize our language before we were caught too.

If you were "caught", what would happen? You probably have a couple of zoom calls and get forced to watch sensitivity training videos. Who cares.
Wait whaat? I am not from USA and hail from a much more blunt culture, what's wrong with those 2 statements?

To me they're harmless jokes but maybe someone will point out they're ableist?

It wasn't just these phrases, but we took them out to be safe (thinking that them being jokes about male... "performance", like from pharma ads on TV with an old man throwing a football through a tire swing).

The biggest thing is that we used non-PC language like "retarded" very casually, not to mention casually swearing in commit messages e.g. "un-fucking up my prior commit". Our sister team got in trouble for "swears in the git commit history", so we wanted to get ahead of that if possible.

In a healthy company culture, we'd just say "okay we'll stop using these terms", but the effort was made to erase their existence because this was a company where non-engineering people (e.g. how well managers and HR liked you) was a big factor in getting promoted.

Once I realized how messed up that whole situation was, I left as fast as I could.

Those aren't particularly bad, but dick jokes have no place in a professional workplace imo. I'm so very weary of it. And yes, in the past I was part of the problem. Then I grew up.
> Write Code. Mostly functions. Not too much.

Think about data structures (types) first. Mostly immutable structures. Then add your functions working on those structures. Not too many.

OMG. This is exactly my experience after trying to write code first for 10+ years. (Yes, I am a terrible [car] driver, and a totally average programmer!)

"Bad programmers worry about the code. Good programmers worry about data structures and their relationships." - Linus Torvalds

He wasn't kidding!

And the bit about "immutable structures". I doubted for infinity-number-of-years ("oh, waste of memory/malloc!"). Then suddenly your code needs to be multi-threaded. Now, immutable structures looks genius!

> it is _much_ easier to refactor copy pasta code,

Its easy to refactor if its nondivergent copypasta and you do it everywhere it is used not later than the third iteration.

If the refactoring gets delayed, the code diverges because different bugs are noticed and fixed (or thr same bug is noticed and fixed different ways) in different iterations, and there are dozens of instances across the code base (possibly in different projects because it was copypastad across projects rather than refactored into a reusable library), the code has in many cases gotten intermixed with code addressing other concerns...

There's a problem with being overly zealous. It's entirely possible to write bad code, either being overly dry or copy paste galore. I think we are prone to these zealous rules because they are concrete. We want an "objective" measure to judge whether something is good or not.

DRY and WET are terms often used as objective measures of implementations, but that doesn't mean that they are rock solid foundations. What does it mean for something to be "repeated"? Without claiming to have TheGreatAnswer™, some things come to mind.

Chaining methods can be very expressive, easy to follow and maintain. They also lead to a lot of repetition. In an effort to be "DRY", some might embark on a misguided effort to combine them. Maybe start replacing

  `map(x => x).reduce(y, z => v)` 
with

  `mapReduce(x => x, (y,z) => v)`
This would be a bad idea, also known as Suck™.

But there may equally be situations where consolidation makes sense. For example, if we're in an ORM helper class and we're always querying the database for an object like so

  `objectContext.Orders.Select(e => e.id = y).Include(e => e.Customers).Include(e => e.Bills).Include(e => e.AwesomeDogs)...`
then it with make sense to consolidate that into

  `orderIncludingCustomersBillsAndDogs(id) => ...`
My $0.02:

Don't needlessly copy-pastes that which is abstractable.

Don't over abstract at the cost of simplicity and flexibility.

Don't be a zealot.

How do you deal with other colleagues that have all the energy and time to push for these practices and I feel makes things worse than the current state?
Explain that the wrong abstraction makes code more complicated than copy-paste and that before you can start factoring out common code you need to be sure the relationship is fundamental and not coincidental.
And get ignored or laughed at. Some people know the rules and nothing but.
> Realize there's no such thing as "clean code"

You can disagree over what exactly is clean code. But you will learn to distinguish what dirty code is when you try to maintain it.

As a person that has had to maintain dirty code over the years, hearing someone saying dirty code doesn't exist is really frustrating. Noone wants to clean up your code, but doing it is better than allowing the code to become unmaintainable, that's why people bring up that book. If you do not care about what clean code is, stop making life difficult for people that do.

> hearing someone saying dirty code doesn't exist is really frustrating

Not sure why you're being downvoted, but as an unrelated aside, the quote you're responding to literally did not say this.

> that's why people bring up that book

I think the point is that following that book does not really lead to Clean Code.

> I think the point is that following that book does not really lead to Clean Code.

And that is why I started saying "You can disagree over what exactly is clean code". Different projects have different requirements. Working on some casual website is not the same as working on a pacemaker firmware.

Yeah, I don't know that we're disagreeing here. For me, there's definitely good code and bad code. I read OP more as saying perfect is the enemy of good, not that all code is good code.

I'd say if you want good code in your working environment, set up a process for requiring it. Automated testing and deployment, and code reviews would be the way to go. Reading Clean Code won't get you there.

I think it's more that clean code doesn't exist because there's no objective measure of this (and those services that claim there are are just as dangerous as Clean Code, the book); anyone can come along and find something about the code that could be tidied up. And legacy is legacy, it's a different problem space to the one a greenfield project exists in.

> As a person that has to maintain dirty code

This is a strange credential to present and then use as a basis to be offended. Are you saying that you have dirty code and have to keep it dirty?

The counterintuitive aspect of this problem that acts as a trap of the less pragmatic people, is that an objective measure is not always necessary.

Let's say you are a feeding a dog. You can estimate what amount of food is dog too little, and what amount of dog food is too much... but now, some jerk comes around and tells you they're going to feed the dog the next time. You agree.

You check the plate, and there's very little food in it. So you say: "hey, you should add more dog food".

Then, the jerk reacts by putting an excessive amount of food in it, just to fuck with you. Then you say "that's too much food!"... So then the jerk reacts saying "you should tell me exactly how many dog pellets I should put on the plate".

Have you ever had to count dog pellets individually to feed a dog? no. You haven't, you have never done it, yet you have fed dogs for years without problems just using a good enough estimate of how much a dog can eat.

Just to please the fucking jerk, you take the approximate amount dog food you regularly feed the dog every day, count the pellets, and say: "there, you fuck, 153 dog pellets".

But the jerk is not happy yet. Just to really fuck with you, the guy will challenge you and say: "so what happens if I feed the dog 152 pellets, or 154... see? you are full of shit". Then you have to explain the jerk that 153 was never important, what's important is the approximate amount of dog food. But the jerk doesn't think that way, the jerk wants a fucking exact number so they can keep fighting you...

Then the jerk will probably say that a dog pellet is not a proper unit of mass, and then the jerk will say that nutrients are not equally distributed in dog pellets, and the bullshit will go on and on and on.

And if you are ever done discussing the optimal number of pellets then there will be another discussion about the frequency in which you feed the dog, and you will probably end up talking about spacetime and atomic clocks and the NTP protocol and relativity and inertial frames just to please the jerk whose objective is just to waste your time until you give up trying to enforce good practices.

And this is how the anti-maintainability jerks operate, by getting into endless debates about how an objective measure of things is required, when in reality, it's not fucking needed and it never was. Estimation is fine.

Just like you won't feed a dog a fucking barrel of dog food you won't create a function that is 5 thousand lines of code long because it's equally nonsensical.

So in the end, what do you do? you say: this many lines of code in a function is too fucking much, don't write functions this long. Why? because I say so, end of debate, fuck it.

There's a formal concept for this in philosophy, but that's up to you to figure out.

Another way to say (I think) the same is:

The cost of analysis outweighs the benefit.

In this case the cost of counting food pellets versus the benefit of precision; but in software generally it is the high cost of (accurate) estimating versus the benefit of just getting on with it.

It is also, to some degree, the impossibility of estimating given that the task is given to someone who is an expert at writing code, not estimating. Coding expertise give insight into how long coding tasks take, but that it the least critical component of estimating a task. Writing code and seeing if it works is the best estimate; sometimes the prototype works first time, never always.

best example of this: https://xkcd.com/1445/

> Just to please the fucking jerk

That's probably mistake number one right there.

There's another formal concept for this in philosophy, related to wrestling with pigs.

Just trying to illustrate why it does not make sense to even get started down that path and the conclusion reflects this.
I agree.

Despite approaching this from completely different angles I think we're roughly pointing in the same direction.

A pragmatist would allow for a strong baseline of good practices and automated rules, with some room for discretion where it counts. That way, no one gets bogged down in exhausting debates, and people who work better with strict constraints can avoid ambiguity, but you can still bend the rules with some justification. I don't like all of the rules, but it's easy to fall back on the tooling than it is to hash out another method length debate.

As far as maintaining code goes, I've had the (mis)fortune of dealing with overly abstracted messes and un-architected spaghetti. I'm not sure which is worse, but what I rarely have to deal with now is all the crap that is auto-formatted away.

If I'm involved in any debate, it's around architecture and figuring out how to write better code by properly understanding what we're trying to achieve, rather than trying to lay down Clean Code style design patterns as if they were Rubocop rules.

Well, if there is a problem, you can identify it, discuss it and address it, instead of ignoring it entirely which is what some people do. Do what works for your team.

Excessive abstractions, leaky abstractions and such are indeed a problem. The principle or least power works there (given a choice of solutions, use the least flexible/powerful to solve your problem).

This is why I never talk software architecture with anyone on the internet anymore. It’s always an intellectual race to the bottom with someone who insists on a rigid process for things that are absolutely obvious to anyone with experience and genuine concern for the state of the codebase.
I think this ties in to something I've been thinking, though it might be project specific.

Good code should be written to be easy to delete.

'Clever' abstractions work against this. We should be less precious about our code and realise it will probably need to change beyond all recognition multiple times. Code should do things simply so the consequences of deleting it are immediately obvious. I think your recommendations fit with this.

Aligns with my current meta-principle, which is that good code is malleable (easily modified, which includes deletion). A lot of design principles simply describe this principle from different angles. Readable code is easy to modify because you can understand it. Terse code is more easily modified because there’s less of it (unless you’ve sacrificed readability for terseness). SRP limits the scope of changes and thus enhances modifiability. Code with tests is easier to modify because you can refactor with less fear. Immutability makes code easier to modify because you don’t have to worry about state changes affecting disparate parts of the program.

Etc... etc...

(Not saying that this is the only quality of good code or that you won’t have to trade some of the above for performance or whatnot at times).

The unpleasant implication of this is that code has a tendency towards becoming worse over time. Because the code that is good enough to be easy to delete or change is, and the code that is too bad to be touched remains.
One of John Carmack's maxims is "Fight code entropy." As simple as that is it carries a lot of meaning imo.
Ah hahha. I love WET. I always say the only way to write something correctly is to re-write it.
That's not what WET means. The GP is saying you shouldn't isolate logic in a function until you've cut-and-pasted the logic in at least two places and plan to do so in a third.
> WET (Write everything twice)

In practice (time pressure) you might end up duplicating it many times, at which point it becomes difficult to refactor.

If it's really abstractable it shouldn't be difficult to refactor. It should literally be a substitution. If it's not, then you have varied cases that you'd have to go back and tinker with the abstraction to support.

It's a similar design and planning principle to building sidewalks. You have buildings but you don't know exactly the best paths between everything and how to correctly path things out. You can come up with your own design but people will end up ignoring them if they don't fit their needs. Ultimately, you put some obvious direct connection side walks and then wait to see the paths people take. You've now established where you need connections and how they need to be formed.

I do a lot of prototyping work and if I had to sit down and think out a clean abstraction everytime I wanted to get for a functional prototype, I'd never have a functional prototype--plus I'd waste a lot of cognitive capacity on an abstraction instead of solving the problem my code is addressing. It's best, from my experience, to save that time and write messy code but tuck in budget to refactor later (the key is you have to actually refactor later not just say you will).

Once you've built your prototype, iterated on it several times had people break designs forcing hacked out solutions, and now have something you don't touch often, you usually know what most the product/service needs to look like. You then abstract that out and get 80-90% of what you need if there's real demand.

The expanded features beyond that can be costly if they require significant redesign but at that point, you hopefully have a stable enough product it can warrant continued investment to refactor. If it doesn't, you saved yourself a lot of time and energy worrying trying to create a good abstract design that tends to fail multiple times at early stages. There's a balance point of knowing when to build technical debt, when to pay it off, and when to nullify it.

Again, the critical trick is you have to actually pay off the tech debt if that time comes. The product investor can't look bright eyed and linearly extrapolate progress so far thinking they saved a boatload of capital, they have to understand shortcuts were taken and the rationale was to fix them if serious money came along or chuck them in the bin if not.

> If it's really abstractable it shouldn't be difficult to refactor. It should literally be a substitution.

This is overstated. Not all abstractions are obvious substitutions. To elaborate: languages vary in their syntax, typing, and scope. So what might be an 'easy' substitution and one language might not be easy in another.

> * WET (Write everything twice), figure out the abstraction after you need something a 3rd time

There are two opposite situations. One is when several things are viewed as one thing while they're actually different (too much abstraction), and another, where a thing is viewed as different things, when it's actually a single one (when code is just copied over).

In my experience, the best way to solve this is to better analyse and understand the requirements. Do these two pieces of code look the same because they actually mean thing in the meaning of the product? Or they just happen to look the same at this particular moment in time, and can continue to develop in completely different directions as the product grows?

Solving the former is generally way uglier/more obnoxious IMO than solving the latter, esp. if you were not the person who designed the former.
WET is great until JSON token parsing breaks and a junior dev fixes it in one place and then I am fixing the same exact problem somewhere else and moving it into a shared file. If it's the exact same functionality, move it into a service/helper.
This is what I'm doing even while creating new code. There's a few instances for example where the "execution" is down to a single argument - one of "activate", "reactivate" and "deactivate". But I've made them into three distinct, separate code paths so that I can work error and feedback messages into everything without adding complexity via arguments.

I mean yes it's more verbose, BUT it's also super clear and obvious what things do, and they do not leak the underlying implementation.

repeat after me:

Document.

Your.

Shit.

everything else can do one. Just fucking write documentation as if you're the poor bastard trying to maintain this code with no context or time.

Documentation is rarely adequately maintained, and nothing enforces that it stay accurate and maintained.

Comments in code can lie (they're not functional); can be misplaced (in most languages, they're not attached to the code they document in any enforced way); are most-frequently used to describe things that wouldn't require documenting if they were just named properly; are often little more than noise. Code comments should be exceedingly-rare, and only used to describe exception situations or logic that can't be made more clear through the use of better identifiers or better-composed functions.

External documentation is usually out-of-sight, out-of-mind. Over time, it diverges from reality, to the point that it's usually misleading or wrong. It's not visible in the code (and this isn't an argument in favor of in-code comments). Maintaining it is a burden. There's no agreed-upon standard for how to present or navigate it.

The best way to document things is to name identifiers well, write functions that are well-composed and small enough to understand, stick to single-responsibility principles.

API documentation is important and valuable, especially when your IDE can provide it readily at the point of use. Whenever possible, it should be part of the source code in a formal way, using annotations or other mechanisms tied to the code it describes. I wish more languages would formally include annotation mechanisms for this specific use case.

> Documentation is rarely adequately maintained,

yes, and the solution is to actually document.

> wouldn't require documenting if they were just named properly

I mean not really. Having decent names for things tells us what you are doing, but not why.

> only used to describe exception situations

Again, just imagine if that wasn't the case. Imagine people had the empathy to actually do a professional job?

> The best way to document things is to name identifiers well, write functions that are well-composed and small enough to understand, stick to single-responsibility principles.

No, thats the best way to code.

The best way to document is to imagine you are a new developer to the code base, and any information they should know is where they need it. Like your code, you need to test your documentation.

I know you don't _like_ documenting, but thats not the point. Its about being professional, just imagine if an engineer didn't _like_ doing certification of their stuff? they'd loose their license. Sure you could work it out later, but thats not the point. You are paid to be professional, not a magician.

(comment deleted)
> I know you don't _like_ documenting, but thats not the point.

On the contrary, I like writing documentation. What I can't stand is unnecessary documentation; documentation of obvious things; documentation as a replacement for self-documenting code; documentation for a vague ill-defined future use-case that probably won't exist (YAGNI); and many of the other documentation failures I've already mentioned. It has nothing to do with my professionalism. It has to do with wasting time and energy and using it as a crutch in place of more valuable knowledge transfer mechanisms.

Self-documenting code is a complete fantasy for everything but completely trivial logic.
It's not. Good quality codebases do exist, and generally are fairly well self-documented.

Typings, well-named functions and variables, consistent APIs and once in a while, a comment to summarize rationales and point out edge cases, and a README file to explain the setup and entry points.

That is generally all you need in terms of documentation. I've dealt with plenty of codebases like these and they're a delight to learn.

> they're a delight to learn

Boto3, its massive. The only reason its tolerable is that its got a large amount of accurate documentation. On the face of it, its an utter arse, because the "client" section of the library is deeply unpythonic (not in the oh its not pretty sense, as it its barely OO and uses capitals for argument names)

If that was "self documenting" I wouldn't be using it.

Take flask for example, its usable because its got good documentation and its well thought out.

but, as I keep on having to tell people. Good function names and tying is not documentation. These tell you what but not why. You don't need documentation if you are working on the same codebase day in day out. But if you have to pick it up again after 6months, you'll wish that you had properly documented it.

I get that you are proud that you don't need documentation. I don't need documentation, I want it because it makes my life 500% easier.

What are you talking about? Boto3 is extremely well documented, and is absolutely not self-documenting. It's a mostly autogenerated API, and a horrible example to use for this.

I don't think you understand what my post is trying to convey. Documentation isn't a sin, it's great when necessary. In the case of boto, it's obviously necessary. In the case of Flask (a framework where you're not going to read the code), it's obviously necessary.

You'll notice, if you read through the Flask codebase, that it's only documented in terms of user-facing functions, which end up in the official end-user documentation. In that sense, the codebase itself is in fact mostly self-documenting.

There's a difference between end-user documentation and developer-facing documentation.

> Put logic closest to where it needs to live (feature folders)

Can you say more about this?

I think I may have stumbled on a similar insight myself. In a side project (a roguelike game), I've been experimenting with a design that treats features as first-class, composable design units. Here is a list of the subfolder called game-features in the source tree:

  actions
  collision
  control
  death
  destructibility
  game-feature.lisp
  hearing
  kinematics
  log
  lore
  package.lisp
  rendering
  sight
  simulation
  transform
An extract from the docstring of the entire game-feature package:

  "A Game Feature is responsible for providing components, events,
  event handlers, queries and other utilities implementing a given
  aspect of the game. It's primarily a organization tool for gameplay code.
  
  Each individual Game Feature is represented by a class inheriting
  from `SAAT/GF:GAME-FEATURE'. To make use of a Game Feature,
  an object of such class should be created, preferably in a
  system description (see `SAAT/DI').
  This way, all rules of the game are determined by a collection of
  Game Features loaded in a given game.
  
  Game Features may depend on other Game Features; this is represented
  through dependencies of their classes."
The project is still very much work-in-progress (procrastinating on HN doesn't leave me much time to work on it), and most of the above features are nowhere near completion, but I found the design to be mostly sound. Each game feature provides code that implements its own concerns, and exports various functions and data structures for other game features to use. This is an inversion of traditional design, and is more similar to the ECS pattern, except I bucket all conceptually related things in one place. ECS Components and Systems, utility code, event definitions, etc. that implement a single conceptual game aspect live in the same folder. Inter-feature dependencies are made explicit, and game "superstructure" is designed to allow GFs to wire themselves into appropriate places in the event loop, datastore, etc. - so in game startup code, I just declare which features I want to have enabled.

(Each feature also gets its set of integration tests that use synthetic scenarios to verify a particular aspect of the game works as I want it to.)

One negative side effect of this design is that the execution order of handlers for any given event is hard to determine from code. That's because, to have game features easily compose, GFs can request particular ordering themselves (e.g. "death" can demand its event handler to be executed after "destructibility" but before "log") - so at startup, I get an ordering preference graph that I reconcile and linearize (via topological sorting). I work around this and related issues by adding debug utilities - e.g. some extra code that can, after game startup, generate a PlantUML/GraphViz picture of all events, event handlers, and their ordering.

(I apologize for a long comment, it's a bit of work I always wanted to talk about with someone, but never got around to. The source of the game isn't public right now because I'm afraid of airing my hot garbage code.)

Sounds interesting. Is the game open source? Published anywhere?
Not at this moment. I intend to publish it under GPLv3, after I get the MVP done.
I'd be interested in how you attempt this. Is it all in lisp?

It might be hard to integrate related things, e.g. physical simulation/kinematics <- related to collisions, and maybe sight/hearing <- related to rendering; Which is all great if information flows one way, as a tree, but maybe complicated if it's a graph with intercommunication.

I thought about this before, and figured maybe the design could be initially very loose (and inefficient), but then a constraint-solver could wire things up as needed, i.e. pre-calculate concerns/dependencies.

Another idea, since you mention "logs" as a GF: AOP - using " join points" to declaratively annotate code. This better handles code that is less of a "module" (appropriate for functions and libraries) and more of a cross-cutting "aspect" like logging. This can also get hairy though: could you treated "(bad-path) exception handling" as an aspect? what about "security"?

> I'd be interested in how you attempt this. Is it all in lisp?

Yes, it's all in Common Lisp.

> It might be hard to integrate related things, e.g. physical simulation/kinematics <- related to collisions, and maybe sight/hearing <- related to rendering;

It is, and I'm cheating a bit here. One simplification is that I'm writing a primarily text-based roguelike, so I don't have to bother with a lot of issues common to real-time 3D games. I can pick and choose the level of details I want to go (e.g. whether to handle collisions at a tile granularity, or to introduce sub-tile coordinates and maybe even some kind of object shape representation).

> Which is all great if information flows one way, as a tree, but maybe complicated if it's a graph with intercommunication.

The overall simulation architecture I'm exploring in this project is strongly event-based. The "game frame" is basically pulling events from a queue and executing them until the queue is empty, at which point the frame is considered concluded, and simulation time advances forward. It doesn't use a fixed time step - instead, when a simulation frame starts, the code looks through "actions" scheduled for game "actors" to find the one that will complete soonest, and moves the simulation clock to the completion time of that action. Then the action completion handler fires, which is the proper start of frame - completion handler will queue some events, and handlers of those events will queue those events, and the code just keeps processing events until the queue empties again, completing the simulation frame.

Structure-wise, simulation GF defines the concept of "start frame" and "end frame" (as events), "game clock" (as query) and ability to shift it (as event handler), but it's the actions GF that contains the computation of next action time. So, simulation GF knows how to tell and move time, but actions GF tells it where to move it to.

This is all supported by an overcomplicated event loop that lets GFs provide hints for handler ordering, but also separates each event handling process into four chains: pre-commit, commit, post-commit and abort. Pre-commit handlers fire first, filling event structure with data and performing validation. Then, commit handlers apply direct consequences of event to the real world - they alter the gameplay state. Then, post-commit handlers process further consequences of an event "actually happening". Alternatively, abort handlers process situations when an event was rejected during earlier chains. All of them can enqueue further events to be processed this frame.

So, for example, when you fire a gun, pre-commit handlers will ensure you're able to do it, and reject the event if you can't. If the event is rejected, abort chain will handle informing you that you failed to fire. Otherwise, the commit handlers will instantiate an appropriate projectile. Post-commit handlers may spawn events related to the weapon being fired, such as informing nearby enemies about the discharge.

This means that e.g. if I want to implement "ammunition" feature, I can make an appropriate GF that attaches a pre-commit handler to fire event - checking if you have bullets left and rejecting the event if you don't (events rejected in pre-commit stage are considered to "have never happened"), and a post-commit handler on the same event to decrease your ammo count. The GF is also responsible for defining appropriate components that store ammo count, so that (in classical ECS style) your "gun" entity can use it to keep track of ammunition. It also provides code for querying the current count, for other GFs that may care about it for some reason (and the UI rendering code).

> I thought about this before, and figured maybe the design could be initially very loose (and inefficient), but then a constraint-solver could wire things up as needed...

I've gone down roads similar to this. Long story short - the architecture solves for a lower priority class of problem, w/r to games, so it doesn't pay a great dividend, and you add a combination of boilerplate and dynamism that slows down development.

Your top issue in the runtime game loop is always with concurrency and synchronization logic - e.g. A spawns before B, if A's hitbox overlaps with B, is the first frame that a collision event occurs the frame of spawning or one frame after? That's the kind of issue that is hard to catch, occurs not often, and often has some kind of catastrophic impact if handled wrongly. But the actual effect of the event is usually a one-liner like "set a stun timer" - there is nothing to test with respect to the event itself! The perceived behavior is intimately coupled to when its processing occurs and when the effects are "felt" elsewhere in the loop - everything's tied to some kind of clock, whether it's the CPU clock, the rendered frame, turn-taking, or an abstracted timer. These kinds of bugs are a matter of bad specification, rather than bad implementation, so they resist automated testing mightily.

The most straightforward solution is, failing pure functions, to write more inline code(there is a John Carmack posting on inline code that I often use as a reference point). Enforce a static order of events as often as possible. Then debugging is always a matter of "does A happen before B?" It's there in the source code, and you don't need tooling to spot the issue.

The other part of this is, how do you load and initialize the scene? And that's a data problem that does call for more complex dependency management - but again, most games will aim to solve it statically in the build process of the game's assets, and reduce the amount of game state being serialized to save games, reducing the complexity surface of everything related to saves(versioning, corruption, etc). With a roguelike there is more of an impetus to build a lot of dynamic assets(dungeon maps, item placements etc.) which leads to a larger serialization footprint. But ultimately the focus of all of this is on getting the data to a place where you can bring it back up and run queries on it, and that's the kind of thing where you could theoretically use SQLite and have a very flexible runtime data model with a robust query system - but fully exploiting it wouldn't have the level of performance that's expected for a game.

Now, where can your system make sense? Where the game loop is actually dynamic in its function - i.e. modding APIs. But this tends to be a thing you approach gradually and grudgingly, because modders aren't any better at solving concurrency bugs and they are less incentivized to play nice with other mods, so they will always default to hacking in something that stomps the state, creating intermittent race conditions. So in practice you are likely to just have specific feature points where an API can exist(e.g. add a new "on hit" behavior that conditionally changes the one-liner), and those might impose some generalized concurrency logic.

The other thing that might help is to have a language that actually understands that you want to do this decoupling and has the tooling built in to do constraint logic programming and enforce the "musts" and "cannots" at source level. I don't know of a language that really addresses this well for the use case of game loops - it entails having a whole general-purpose language already and then also this other feature. Big project.

I've been taking the approach instead of aiming to develop "little languages" that compose well for certain kinds of features - e.g. instead of programming a finite state machine by hand for each type of NPC, devise a subcategory of state machines that I could describe as a one-liner, with chunks of fixed-function behavior and a bit of programmability. Instead of a universal graphics...

Thanks for the detailed evaluation. I'll start by reiterating that the project is a typical tile-based roguelike, so some of the concerns you mention in the second paragraph don't apply. Everything runs sequentially and deterministically - though the actual order of execution may not be apparent from the code itself. I mitigate it to an extent by adding introspection features, like e.g. code that dumps PlantUML graphs showing the actual order of execution of event handlers, or their relationship with events (e.g. which handlers can send what subsequent events).

I'll also add that this is an experimental hobby project, used to explore various programming techniques and architecture ideas, so I don't care about most constraints under which commercial game studios operate.

> The perceived behavior is intimately coupled to when its processing occurs and when the effects are "felt" elsewhere in the loop - everything's tied to some kind of clock, whether it's the CPU clock, the rendered frame, turn-taking, or an abstracted timer. These kinds of bugs are a matter of bad specification, rather than bad implementation, so they resist automated testing mightily.

Since day one of the project, the core feature was to be able to run headless automated gameplay tests. That is, input and output are isolated by design. Every "game feature" (GF) I develop comes with automated tests; each such test starts up a minimal game core with fake (or null) input and output, the GF under test, and all GFs on which it depends, and then executes faked scenarios. So far, at least for minor things, it works out OK. I expect I might hit a wall when there are enough interacting GFs that I won't be able to correctly map desired scenarios to actual event execution orders. We'll see what happens when I reach that point.

> that's the kind of thing where you could theoretically use SQLite and have a very flexible runtime data model with a robust query system - but fully exploiting it wouldn't have the level of performance that's expected for a game.

Funny you should mention that.

The other big weird thing about this project is that it uses SQLite for runtime game state. That is, entities are database rows, components are database tables, and the canonical gameplay state at any given point is stored in an in-memory SQLite database. This makes saving/loading a non-issue - I just use SQLite's Backup API to dump the game state to disk, and then read it back.

Performance-wise, I tested this approach extensively up front, by timing artificial reads and writes in expected patterns, including simulating a situation in which I pull map and entities data in a given range to render them on screen. SQLite turned out to be much faster than I expected. On my machine, I could easily get 60FPS out of that with minimum optimization work - but it did consume most of the frame time. Given that I'm writing a ASCII-style, turn(ish) roguelike, I don't actually need to query all that data 60 times per second, so this is quite acceptable performance - but I wouldn't try that with a real-time game.

> The other thing that might help is to have a language that actually understands that you want to do this decoupling and has the tooling built in to do constraint logic programming and enforce the "musts" and "cannots" at source level. I don't know of a language that really addresses this well for the use case of game loops - it entails having a whole general-purpose language already and then also this other feature. Big project.

Or a Lisp project. While I currently do constraint resolution at runtime, it's not hard to move it to compile time. I just didn't bother with it yet. Nice thing about Common Lisp is that the distinction between "compilation/loading" and "runtime" is somewhat arbitrary - any code I can execute in the latter, I can execute in the former. If I have a fu...

Sounds like you may be getting close to an ideal result, at least for this project! :) Nice on the use of SQLite - I agree that it's right in the ballpark of usability if you're just occasionally editing or doing simple turn-taking.

When you create gameplay tests, one of the major limitations is in testing data. Many games end up with "playground" levels that validate the major game mechanics because they have no easier way of specifying what is, in essence, a data bug like "jump height is too short to cross gap". Now, of course you can engineer some kind of test, but it starts to become either a reiteration of the data (useless) or an AI programming problem that could be inverted into "give me the set of values that have solutions fitting these constraints" (which then isn't really a "test" but a redefinition of the medium, in the same way that a procedural level is a "solution" for a valid level).

It's this latter point that forms the basis of many of the "little languages". If you hardcode the constraints, then more of the data resides in a sweet spot by default and the runtime is dealing with less generality, so it also becomes easier to validate. One of my favorite examples of this is the light style language in Quake 1: https://quakewiki.org/wiki/lightstyle

It's just a short character string that sequences some brightness changes in a linear scale at a fixed rate. So it's "data," but it's not data encoded in something bulky like a bunch of floating point values. It's of precisely the granularity demanded by the problem, and much easier to edit as a result.

A short step up from that is something like MML: https://en.wikipedia.org/wiki/Music_Macro_Language - now there is a mostly-trivial parsing step involved, but again, it's "to the point" - it assumes features around scale and rhythm that allow it to be compact. You can actually do better than MML by encoding an assumption of "playing in key" and "key change" - then you can barf nearly any sequence of scale degrees into the keyboard and it'll be inoffensive, if not great music. Likewise, you could define rhythm in terms of rhythmic textures over time - sparse, held, arpeggiated, etc. - and so not really have to define the music note by note, making it easy to add new arrangements.

With AI, a similar thing can apply - define a tighter structure and the simpler thing falls out. A lot of game AI FSMs will follow a general pattern of "run this sequenced behavior unless something of a higher priority interrupts it". So encode the sequence, then hardcode the interruption modes, then figure out if they need to be parameterized into e.g. multiple sequences, if they need to retain a memory scratchpad and resume, etc. A lot of the headache of generalizing AI is in discovering needs for new scratchpads, if just to do something like a cooldown timer on a behavior or to retain a target destination. It means that your memory allocation per entity is dependent on how smart they have to be, which depends on the AI's program. It's not so bad if you are in something as dynamic as a Lisp, but problematic in the typical usages of ECS where part of the point is to systematize memory allocation.

With painting what you're looking for is a structuring metaphor for classes of images. Most systems of illustration have structuring metaphors of some kind specifically for defining proportions - they start with simple ratios and primitive shapes, and then use those as the construction lines for more detailed elements which subdivide the shapes again with another set of ratios. This is the conceptual basis of the common "6-8 heads of height" tip used in figure drawing -...

Thanks for the detailed explanation!

I don't think I can respond properly and ask the many more questions I have in a HN thread that's already this aged, so if you'd like to talk more, feel free to hit me up (my contact details are in my profile).

I read Clean Code in 2010 and trying out and applying some of the principles really helped to make my code more maintainable. Now over 10 years later I have come to realize that you cannot set up too many rules on how to structure and write code. It is like forcing all authors to apply the same writing style or all artists to draw their paintings with the exact same technique. With that analogy in mind, I think that one of the biggest contributors to messy code is having a lot of developers, all with different preferences, working in the same code base. Just imagine having 100 different writers trying to write a book, this is the challenge we are trying to address.
I'm not sure that's really true. Any publication with 100 different writers almost certainly has some kind of style guide that they all have to follow.
> figure out the abstraction after you need something a 3rd time

That's still too much of a "rule".

Whenever I feel (or know) two functions are similar, the factors that determine if I should merge them:

- I see significant benefit too doing so, usually the benefit of a utility that saves writing the same thing in the future, or debugging the same/similar code repeatedly.

- How likely the code is to diverge. Sometimes I just mark things for de-duping, but leave it around a while to see if one of the functions change.

- The function is big enough it cannot just be in-lined where it is called, and the benefit of de-duplication is not outweighed by added complexity to the call stack.

I’ve never heard the term WET before but that’s exactly what I do.

The other key thing I think is not to over-engineer abstractions you don’t need yet. But to try and leave ‘seams’ where it’s obvious how to tease code about if you need to start building abstractions.

> WET

One of the most difficult to argue comments in code reviews: “let’s make it generic in case we need it some place else”. First of all, chances that we need it some place else aren’t exactly high, unless you are writing a library and code explicitly design to be shared. And even if such need arises, chances of getting it right generalizing from one example are slim.

Regarding the book though, I have participated in one of the workshops with the author and he seemed to be in favor of WER and against “architecting” levels of abstraction before having concrete examples.

My experience interviewing recently a number of consultants with only a few years experience was the more they mumbled clean code the less they knew what they were doing.
Clean Code is fine. It's a little dated, as you would expect, and for the most part, everything of value in it has been assimilated into the best practices and osmotic ether that pervades software development now. It's effectively all the same stuff as you see in Refactoring or Code Complete or Pragmatic Programmer.

I suspect a lot of backlash against it centers around Uncle Bob's less than progressive political and social stances in recent years.

TFA really seems to be saying that there's no such thing as clean code, so don't even bother trying.
I never read Clean Code and know nothing about its author so I'm willing to trust you on the first part, but the second paragraph is really uncalled for IMO. The article is long and gives precise examples of its issues with the book. Assuming an ulterior motive is unwarranted.
I don't think it is uncalled for when there have been several instances of boycotts organized for that reason.

Nobody is that upset about anodyne OOP design patterns

Yeah I agree with the author, and I would go further, it's a nice list of reasons why Uncle Bob is insufferable.

Because of stuff like this:

> Martin's reasoning is rather that a Boolean argument means that a function does more than one thing, which it shouldn't.

Really? Really? Not even for dependency injection? Or, you know, you should duplicate your function into two very similar things to have one with the flag and another one without. Oh but DRY. Sure.

> . He says that an ideal function has zero arguments (but still no side effects??), and that a function with just three arguments is confusing and difficult to test

Again, really?

I find it funny who treats him as a guru. Or maybe that's the right way to treat him, like those self-help gurus with meaningless guidance and whishy-washy feel-good statements.

> Every function in this program was just two, or three, or four lines long. Each was transparently obvious. Each told a story. And each led you to the next in a compelling order.

Wow, illumination! Salvation! Right here!

Until, of course you have to actually maintain this and has to chase down 3 or 4 levels of functions deep what is it that the code is actually doing. And think of function signature for every minor thing. And passing all the arguments you need (ignoring that "perfect functions have zero arguments" above - good luck with that)

Again, it sounds like self-help BS and not much more than that.

It's actually a good marketing trick. He can sell something slightly different and more "pure" and make promises on it and then sell books, trainings and merchandises.

That's what the wellness industry do all the time.

> Really? Really? Not even for dependency injection? Or, you know, you should duplicate your function into two very similar things to have one with the flag and another one without. Oh but DRY. Sure.

I'm not sure dependency injection has anything to do with boolean flags or method args. I think the key point here is that he is a proponent of object oriented programming. I think he touches on dependency injection later in the book, but it's been a while since I've read it. He suggests your dependencies get passed at object initialization, not passed as method options. That let's you mock stuff without needing to make any modifications to the method that uses that dependency easily.

> Until, of course you have to actually maintain this and has to chase down 3 or 4 levels of functions deep what is it that the code is actually doing. And think of function signature for every minor thing. And passing all the arguments you need (ignoring that "perfect functions have zero arguments" above - good luck with that)

I myself find it easier to read and understand simple functions than large ones with multiple indentation levels. Also, it definitely does not make sense to pass many arguments along with those many small functions. He recommends making them object instance properties so that you don't need to do that.

It may not be for everyone, but I'll take reading code that follows his principles instead of code that had no thought about design put into it any day of the week. It's not some dogmatic law that should be followed in all cases, but to me it's a set of pretty great ideas to keep in mind to lay out code that is easy to maintain and test.

> I'm not sure dependency injection has anything to do with boolean flags or method args.

DI can be abused as a way to get around long function signatures. "I don't take a lot of arguments here" (I'm just inside of an object that has ten other dependencies injected in). Welcome back to testing hell.

Yea... that could probably turn ugly in a lot of cases. I think the flow of object creation and dependency injection would be the important part to handle well in a case with a lot of dependencies. I think the dependencies should be passed down through objects in one direction. So if your object (a) that works on objects (b) that have a lot of dependencies, that first outer object (a) is responsible for injecting dependencies into those 2nd objects (b). So if you have a mocked dependency, you pass that when initializing object a, and object a is responsible for injecting that mocked dependency into object b.

A disclaimer, I'm an sre, so definitely not the most expert proponent of oop.

> I myself find it easier to read and understand simple functions than large ones with multiple indentation levels

A CRUD app might get away with this, but anything more complex needs a lot of different variables and has complex code.

> Until, of course you have to actually maintain this and has to chase down 3 or 4 levels of functions deep what is it that the code is actually doing.

The art is to chain your short functions like a paragraph, not to nest them a mile deep, where the "shortness" is purely an illusion and the outer ones are doing tons of things by calling the inner ones.

That's a lot harder, though.

But it fits much better with the spirit of "don't have a lot of args for your functions" - if you're making deeply nested calls, you're gonna have to pass all the arguments the inner ones need through the outer ones. Or else do something to obfuscate how much you're passing around (global deps/state, crazy amounts of deep DI, etc...) which doesn't make testing any easier.

The boolean issue is probably the one that's caused me the most pain. That contradiction with DRY has actually had me go back and forth between repeating myself and using a flag, wasting a ton of time on something incredibly pointless to be thinking that hard about. I feel like the best thing for my career would have been to not read that book right when I started my first professional programing job.
It might make sense to divide your function with boolean flag into two functions and extract common code into third private function. Or may be it'll make things ugly.

I treat those books as something to show me how other people do things. I learn from it and I add it to my skill book. Then I'll apply it and see if I like it. If I don't like it in this particular case, I'll not apply it. IMO it's all about having insight into every possible solution. If you can implement something 10 different ways, you can choose the best one among them, but you have to learn those 10 different ways first.

It's been a while since I've read it, but I think to handle boolean flag type logic well he suggests to rely on object subclassing instead. So, for an example that uses a dry run flag for scary operations, you can have your normal object (a) and all of its methods that actually perform those scary operations. Then you can subclass that object (a) to create a dry run subclass (b). That object b, can override only the methods that perform the scary operations that you want to dry run while at the same time using all of its non scary methods. That would let you avoid having if dry_run == true; then dry_run_method() else scary_method() scattered in lots of different methods.
10 or so years ago when I first got into development I looked to people like Martin's for how I should write code.

But I had more and more difficulty reconciling bizarrely optimistic patterns with reality. This from the article perfectly sums it up:

>Martin says that functions should not be large enough to hold nested control structures (conditionals and loops); equivalently, they should not be indented to more than two levels.

Back then as now I could not understand how one person can make such confident and unambiguous statements about business logic across the spectrum of use cases and applications.

It's one thing to say how something should be written in ideal circumstances, it's another to essentially say code is spaghetti garbage because it doesn't precisely align to a very specific dogma.

Can you provide a source where he said that? Or did he actually say something more like "thats when I consider refactoring it"?

My recollection of the clean code book and Fowler books were very much “I think these are smells, but smells in the code are also fine”

Note: Robert Martin and Martin Fowler are different people. Are you saying Fowler said this?

It's directly from the article, and Clean Code was one of the first books I purchased.

Fixed the typo, thanks.

The article is not written by Robert Martin, so that doesn’t necessarily establish he said that. You also implied Fowler said it. Thanks for clarifying.

I believe Robert Martin did say this, but there was probably a preface from the book that didn’t make it into the article, so the quote in the article may be a bit out of context.

This is the point that I have the most trouble understanding in critiques of Fowler, Bob, and all writers who write about coding: in my reading, I had always assumed that they were writing about the perfect-world ideal that needs to be balanced with real-world situations. There's a certain level of bluster and over-confidence required in that type of technical writing that I understood to be a necessary evil in order to get points across. After all, a book full of qualifications will fail to inspire confidence in its own advice.
This is true only for people first coming to development. If you're just starting your journey, you are likely looking for quantifiable absolutes as to what is good and what isn't.

After you're a bit more seasoned, I think qualified comments are probably far more welcome than absolutes.

> After all, a book full of qualifications will fail to inspire confidence in its own advice.

I don't think that's true at all. One of the old 'erlang bibles' is "learn you some erlang" and it full of qualifications titled "don't drink the kool-aid" (notably not there in the haskell inspiration for the book). It does not fail to inspire confidence to have qualifications scattered throughout and to me it actually gives me MORE confidence that the content is applicable and the tradeoffs are worth it.

https://learnyousomeerlang.com/introduction#about-this-tutor...

> it's another to essentially say code is spaghetti garbage because it doesn't precisely align to a very specific dogma

Does it say that though?

All codebases are spaghetti garbage, but some are useful.

    Now, some people will claim that having 8-character indentations makes
    the code move too far to the right, and makes it hard to read on a
    80-character terminal screen.  The answer to that is that if you need
    more than 3 levels of indentation, you're screwed anyway, and should fix
    your program.
https://www.kernel.org/doc/Documentation/process/coding-styl...
One mistake I think people like the author make is treating these books as some sort of bible that you must follow to the letter. People who evangelised TDD were the worst offenders of this. "You HAVE to do it like this, it's what the book says!"

You're not supposed to take it literally for every project, these are concepts that you need to adapt to your needs. In that sense I think the book still holds up.

(comment deleted)
But that's what some people are doing - they take this book as the programming bible.
Sure, but it doesn't mean the book itself is bad. It's that beginners should be aware that what's "right" differs from project to project.
I understand that the people that follow Clean Code religiously are annoying, but the author seems to be doing the same thing in reverse: because some advice is nuanced or doesn't apply all the time then we should stop recommending the book and forget it altogether.
Its not just that it doesn't always apply. Its that the absracted rules are not useful as stand alone guides to developing code, although they are presented as such. Its the entire purpose of the book isn't it? The argument against this book isn't that books about code style and rules are bad. Its that this one is bad. And its often recommended as core reading material to new developers (several examples of that in this thread). I've read several code style / guide books over the last decade. This is one of the few I put down fairly early on because it just didn't seem very good.
Agreed. It’s one of the best books on programming there is. Like any book, probably 20% I don’t agree with. But 80% of it is gold.
For me this maps so clearly to the Dreyfus model of skill acquisition. Novices need strict rules to guide their behavior. Experts are able to use intuition they have developed. When something new comes along, everyone seems like a novice for a little while.

The Dreyfus model identifies 5 skill levels:

Novice

Wants to achieve a goal, and not particularly interested in learning. Requires context free rules to follow. When something unexpected happens will get stuck.

Advanced Beginner

Beginning to break away from fixed rules. Can accomplish tasks on own, but still has difficulty troubleshooting. Wants information fast.

Competent

Developed a conceptual model of task environment. Able to troubleshoot. Beginning to solve novel problems. Seeks out and solve problems. Shows initiative and resourcefulness. May still have trouble determining which details to focus on when solving a problem.

Proficient

Needs the big picture. Able to reflect on approach in order to perform better next time. Learns from experience of others. Applies maxims and patterns.

Expert

Primary source of knowledge and information in a field. Constantly look for better ways of doing things. Write books and articles and does the lecture circuit. Work from intuition. Knows the difference between irrelevant and important details.

> Primary source of knowledge and information in a field. Constantly look for better ways of doing things. Write books and articles and does the lecture circuit.

Meh. I'm probably being picky, but it doesn't surprise me that a Thought Leader would put themselves and what they do as Thought Leader in the Expert category. I see them more as running along a parallel track. They write books and run consulting companies and speak at conferences and create a brand, and then there are those of us who get good at writing code because we do it every day, year after year. Kind of exactly the difference between sports commentators and athletes.

The problem is that the book presents things that are at best 60/40 issues as hard rules, which leads novices++ follow them to the detriment of everything else.
Yup. I see the book as guide to a general goal, not a specific objective that can be defined. To actually reach that goal is sometimes completely impossible and in many other cases it introduces too much complexity.

However, in most cases heading towards that goal is a beneficial thing--you just have to recognize when you're getting too close and bogging down in complying with every detail.

I still consider it the best programming book I've ever read.

Uncle Bob himself acts like it is a bible, so if you buy into the rest of his crap then you'll likely buy into that too.

If treated as guidelines you are correct Clean Code is only eh instead of garbage. But taken in the full context of how it is presented/intended to be taken by the author it is damaging to the industry.

I've read his blog and watched his videos. While his attitude comes off as evangelical, his actual advice is very often "Do it when it makes sense", "There are exceptions - use engineering judgment", etc.

In no way is he treating his book as a bible.

The whole point of "You HAVE to do it like this, it's what the book says!" is to sell more books or consulting.

I agree: Clean Coders and TDDers are cut from the same cloth.

I agree. Trying to apply the lessons in there leads to code that is more difficult to read and reason about. Making it "read like a book" and keeping functions short sound good on the surface but they lead to lines getting taken up entirely by function names and a nightmare of tracking call after call after call.

It's been years since I've read the book and I'm still having trouble with the bad ideas from there because they're so well stuck with me that I feel like I'm doing things wrong if I don't follow the guidelines in there. Sometimes I'll actually write something in a sensible way, change it to the Clean Code way, and then revert it back to where it was when I realize my own code is confusing me when written like that.

This isn't just a Robert C Martin issue. It's a cultural issue. People need to stop shaming others if their code doesn't align with Clean Code. People need to stop preaching from the book.

I make my code "read like a book" with a line comment for each algorithmic step inside a function, and adding line-ending comments to clarify. So functions are just containers of steps designed to reduce repetition, increase visibility, and minimize data passing and globals.
Finally! I'm glad to hear I'm not the only one. I've gone against 'Clean Code' zealots that end up writing painfully warped abstractions in the effort to adhere to what is in this book. It's OK to duplicate code in places where the abstractions are far enough apart that the alternative is worse. I've had developers use the 'partial' feature in C# to meet Martin's length restrictions to the point where I have to look through 10-15 files to see the full class. The examples in this post are excellent examples of the flaws in Martin's absolutism.
Seriously? That's an abuse of partial and just a way of following the rules without actually following them. That code must have been fun to navigate...
Many years ago I worked on a project that had a hard “no hard coded values” rule, as requested by the customer. The team routinely wrote the equivalent to

    const char_a = “a”;
And I couldn’t get my manager to understand why this was a problem.
Clearly it is still a hardcoded value! It fails the requirement. Instead there should be a factory that loads a file that reads in the "a" to the variable, nested down under 6 layers of abstraction spread across a dozen files.
thats too enterprisey, at a startup you would write it like this:

const charA = (false+"")[1];

That has three constants.
is this better?

const charA = (![]+[])[+!+[]];

[] is still a constant.
True, but we can both agree that this is a better constant than "a". Much better job security in that code.. unless you get fired for writing it, that is
I cannot even parse this. What is going on here?
Saw this just the other day. I was at a loss to know what to say. :(
This gets to intent.

What, in the code base, does char_a mean? Is it append_command? Is it used in a..z for all lowercase? Maybe an all_lowercase is needed instead.

I know that it's an "A". I don't know why that matters to the codebase. Now, there are cases where it is obvious to even beginners, and I'm fine with it as magic characters, but I've seen cases where people were confused to why 1000 was in a `for(int i = 0; i<1000; i++)`. Is 1000 arbitrary in that case, or is it based on a defunct business requirement in 2008? Will it break if we change it to 5000 because our computers are faster now?

Can I please forward your contact info to my developer? Maybe you can do a better job convincing him haha ;)
You were never alone Juggles. We've been here with you the whole time.

I have witnessed more people bend over backwards and do the most insane things in the name of avoiding "Uncle Bob's" baleful stare.

It turns out that following "Uncle Sassy's" rules will get you a lot further.

1. Understand your problem fully

2. Understand your constraints fully

3. Understand not just where you are but where you are headed

4. Write code that takes the above 3 into account and make sensible decisions. When something feels wrong ... don't do it.

Quality issues are far more often planning, product management, strategic issues than something as easily remedied as the code itself.

>Write code that takes the above 3 into account and make sensible decisions. When something feels wrong ... don't do it.

The problem is that people often need specifics to guide them when they're less experienced. Something that "feels wrong" is usually due to vast experience being incorporated into your subconscious aesthetic judgement. But you can't rely on your subconscious until you've had enough trials to hone your senses. Hard rules can and often are overapplied, but its usually better than the opposite case of someone without good judgement attempting to make unguided judgement calls.

You are right, but also I think the problem discussed in the article is that some of these hard rules are questionable. DRY for example: as a hard rule it leads to overly complex and hard to maintain code because of bad and/or useless abstractions everywhere (as illustrated in TFA). It needs either good experience to sense if they "feel good" like you say, or otherwise proven repetitions to reveal a relevant abstraction.
"How do you develop good software? First, be a good software developer. Then develop some software."

The problem with all these lists is that they require a sense of judgement that can only be learnt from experience, never from checklists. That's why Uncle Bob's advice is simultaneously so correct, and yet so dangerous with the wrong fingers on the keyboard.

The profession needs a stronger culture of apprenticeship.

In between learning the principles incorrectly from books, and learning them inefficiently at the school of hard knocks, there's a middle path of learning them from a decent mentor.

The problem is that there is a huge amount of “senior” devs who only got that title for having been around and useless for a long time. It is the best for all to not have them mentoring anyone.

But otherwise I agree, it’s just hard to recognize good programmers.

Also, good programmers don't necessarily make good mentors.

But I imagine these problems aren't unique to the software industry. It can't be the case that every blacksmith was both a good blacksmith and a good mentor, and yet the system of apprenticeship successfully passed down knowledge from generation to generation for a long time. Maybe the problem is our old friend social media, and how it turns all of us into snobs with imposter syndrome, so few of us feel willing and able to be a mentor.

Agreed.

That's why my advice to junior programmers is, pay attention to how you feel while working on your project - especially, when you're getting irritated. In particular:

- When you feel you're just mindlessly repeating the same thing over and over, with minor changes - there's probably a structure to your code that you're manually unrolling.

- As you spend time figuring out what a piece of code does, try to make note of what made gaining understanding hard, and what could be done to make it easier. Similarly, when modifying/extending code, or writing tests, make note of what took most effort.

- When you fix a bug, spend some time thinking what caused it, and how could the code be rewritten to make similar bugs impossible to happen (or at least very hard to introduce accidentally).

Not everything that annoys you is a problem with the code (especially when one's unfamiliar with the codebase or the tooling, the annoyance tends to come from lack of understanding). Not everything should be fixed, even if it's obviously a code smell. But I found that, when I paid attention to those negative feelings, I eventually learned ways to avoid them up front - various heuristics that yield code which is easier to understand and has less bugs.

As for following advice from books, I think the best way is to test the advice given by just applying it (whether in a real or purpose-built toy project) and, again, observing whether sticking to it makes you more or less angry over time (and why). Code is an incredibly flexible medium of expression - but it's not infinitely flexible. It will push back on you when you're doing things the wrong way.

That's a great set of tips, thanks for sharing.

I like the idea of trying to over-apply a rule on a toy project, so you can get a sense of where it helps and where it doesn't. For example, "build Conway's Game of Life without any conditional branches" or "build FizzBuzz where each function can have only one line of code".

> When you feel you're just mindlessly repeating the same thing over and over, with minor changes - there's probably a structure to your code that you're manually unrolling.

Casey has a good blog post about this where he explains his compression-oriented programming, which is a progressive approach, instead of designing things up front.

https://caseymuratori.com/blog_0016

I read that a while ago. It's a great article, I second the recommendation! I also love the term, "compression-oriented programming", it clicked in my mind pretty much the moment I saw it.
The right advice to give new hires, especially junior ones, is to explain to them that in order to have a good first PR they should read this Wikipedia page first:

https://en.wikipedia.org/wiki/Code_smell

Also helpful are code guidelines like the one that Google made for Python:

https://google.github.io/styleguide/pyguide.html

Then when their first PR opens up, you can just point to the places where they didn't quite get it right and everyone learns faster. Mentorship helps too, but much of software is self-learning and an hour a week with a mentor doesn't change that.

Yeah to some degree. I am in that 25 years of experience range. The software I write today looks much more like year 1 than year 12. The questions I ask in meetings I would have considered "silly questions" 10 years ago. Turns out there was a lot of common sense I was talked out of along the way.

Most people already know what makes sense. It's the absurdity of office culture, JR/SR titles, and perverse incentive that convinces them to walk in the exact opposite direction. Uncle Bob is the culmination of that absurdity. Codified instructions that are easily digested by the lemmings on their way to the cliff's edge.

I've seen juniors with better judgment than "seniors".

But they were not in the same comp bracket. And I don't think they gravitated in the same ecosystems so to speak.

> 1. Understand your problem fully

> 2. Understand your constraints fully

These two fall under requirements gathering. It's so often forgotten that software has a specific purpose, a specific set of things it needs to do, and that it should be crafted with those in mind.

> 3. Understand not just where you are but where you are headed

And this is the part that breaks down so often. Because software is simultaneously so easy and so hard to change, people fall into traps both left and right, assuming some dimension of extensibility that never turns out to be important, or assuming something is totally constant when it is not.

I think the best advice here is that YAGNI, don't add functionality for extension unless your requirements gathering suggests you are going to need it. If you have experience building a thing, your spider senses will perk up. If you don't have experience building the thing, can you get some people on your team that do? Or at least ask them? If that is not possible, you want to prototype and fail fast. Be prepared to junk some code along the way.

If you start out not knowing any of these things, and also never junking any code along the way, what are the actual odds you got it right?

>These two fall under requirements gathering. It's so often forgotten that software has a specific purpose, a specific set of things it needs to do, and that it should be crafted with those in mind.

I wish more developers would actually gather requirements and check if the proposed solution actually solves whatever they are trying to do.

I think part of the problem is that often we don't use what we work on, so we focus too much in the technical details, but we forget what the user actually needs and what workflow would be better.

In my previous job, clients were always asking for changes or new features (they paid dev hours for it) and would come with a solution. But I always asked what was the actual problem, and many times, there was a solution that would solve the problem in a better way

I've also never agreed completely with Uncle Bob. I was an OOP zealot for maybe a decade, and I'm now I'm a Rust convert. The biggest "feature" of Rust is that is probably brought semi-functional concepts to the "OOP masses." I found that, with Rust, I spent far more time solving the problem at hand...

Instead of solving how I am going to solve the problem at hand ("Clean Coding"). What a fucking waste of time, my brain power, and my lifetime keystrokes[1].

I'm starting to see that OOP is more suited to programming literal business logic. The best use for the tool is when you actually have a "Person", "Customer" and "Employee" entities that have to follow some form of business rules.

In contradiction to your "Uncle Sassy's" rules, I'm starting to understand where "Uncle Beck" was coming from:

1. Make it work.

2. Make it right.

3. Make it fast.

The amount of understanding that you can garner from make something work leads very strongly into figuring out the best way to make it right. And you shouldn't be making anything fast, unless you have a profiler and other measurements telling you to do so.

"Clean Coding" just perpetuates all the broken promises of OOP.

[1]: https://www.hanselman.com/blog/do-they-deserve-the-gift-of-y...

Simula, arguably the first (or at least one of the earliest) OOP languages, was written to simulate industrial processes, where each object was one machine (or station, or similar) in a manufacturing chain.

So, yes, it was very much designed for when you have entities interacting, each entity modeled by a class, and then having one (or more) object instantiations of that class interacting with each other.

> and make sensible decisions

well there goes the entire tech industry

I started OOP in '96 and I was never able to wrap my head around the code these "Clean Code" zealots produced.

Case in point: Bob Martin's "Video Store" example.

My best guess is that clean code, to them, was as little code on the screen as possible, not necessarily "intention revealing code either", instead everything is abstracted until it looks like it does nothing.

SOLID code is a very misleading name for a technique that seems to shred the code into confetti.

I personally don't feel all that productive spending like half my time just navigating the code rather than actually reading it, but maybe it's just me.

> I personally don't feel all that productive spending like half my time just navigating the code rather than actually reading it

Had this problem at a previous job - main dev before I got there was extremely into the whole "small methods" cult and you'd regularly need to open 5-10 files just to track down what something did. Made life - and the code - a nightmare.

I have had the experience of trying to understand how a feature in a C++ project worked (both Audacity and Aegisub I think) only to find that I actually could not find where anything was implemented, because everything was just a glue that called another piece of glue.

Also sat in their IRC channel for months and the lead developer was constantly discussing how he'd refactor it to be cleaner but never seemed to add code that did something.

My last company was very into Clean Code, to the point where all new hires were expected to do a book club on it.

My personal take away was that there were a few good ideas, all horribly mangled. The most painful one I remember was his treatment of the Law of Demeter, which, as I recall, was so shallow that he didn't even really even thoroughly explain what the law was trying to accomplish. (Long story short, bounded contexts don't mean much if you're allowed to ignore the boundaries.) So most everyone who read the book came to earnestly believe that the Law of Demeter is about period-to-semicolon ratios, and proceeded to convert something like

  val frobnitz = Frobnitz.builder()
      .withPixieDust()
      .withMayonnaise()
      .withTarget(worstTweetEver)
      .build();
into

  var frobnitzBuilder = Frobnitz.builder();
  frobnitzBuilder = frobnitzBuilder.withPixieDust();
  frobnitzBuilder = frobnitzBuilder.withMayonnaise();
  frobnitzBuilder = frobnitzBuilder.withTarget(worstTweetEver);
  val frobnitz = frobnitzBuilder.build();

and somehow convince themselves that doing this was producing tangible business value, and congratulate themselves for substantially improving the long-term maintainability of the code.

Meanwhile, violations of the actual Law of Demeter ran rampant. They just had more semicolons.

I love how this is clearly a contextual recommendation. I'm not a software developer, but a data scientist. In pandas, to write your manipulations in this chained methods fashing is highly encouraged IMO. It's even called "pandorable" code
TBH, the only context where I've seen people express a strong preference for the non-chained option is under the charismatic influence of Uncle Bob's baleful stare.

Otherwise, it seems opinions typically vary between a strong preference for chaining, and rather aggressive feelings of ¯\_(ツ)_/¯

The latter example (without the redundant assignments) is preferred by people who do a lot of line-by-line debugging. While most IDEs allow you to set a breakpoint in the middle of an expression, that's still more complicated and error prone than setting one for a line.

I've been on a team that outlawed method chaining specifically because it was more difficult to debug. Even though I'm more of a method-chainer myself, I have taken to writing unchained code when I am working on a larger team.

  var frobnitzBuilder = Frobnitz.builder();
  frobnitzBuilder.withPixieDust();
  frobnitzBuilder.withMayonnaise();
  frobnitzBuilder.withTarget(worstTweetEver);
  val frobnitz = frobnitzBuilder.build();
...is undeniably easier to step-through debug than the chained version.
Might depend on the debugger? The main ones I've used also let me go through the chained version one at a time, including seeing intermediate values.
On that note, I've never seen an explanation of Law of Demeter that made any kind of sense to me. Both the descriptions I read and the actual uses I've seen boiled down to the type of transformation you just described, which is very much pointless.

> Long story short, bounded contexts don't mean much if you're allowed to ignore the boundaries.

I'd like to read more. Do you know of a source that covers this properly?

If you really want to dig into it, perhaps a book on domain-driven design? That's where I pulled the term "bounded context" from.

My own personal oversimplification, probably grossly misleading, is that DDD is what you get when you take the Law of Demeter and run as far with it as you can.

Thanks.

I have Evans' book on my bookshelf. I understand it's the book on DDD, right? I tried to read it a while ago, I got through about one third of it before putting it away. Maybe I should revisit it.

Three things that go well together:

  - Law of Demeter
  - Tell, Don’t Ask
  - narrow interfaces
> Law of Demeter

"Don't go digging into objects" pretty much.

Talk to directly linked objects and tell them what you need done, and let them deal with their linked objects. Don't assume that you know what is and always will be involved in doing something on dependent objects of the one you're interacting with.

E.g. lets say you have a Shipment object that contains information of something that is to be shipped somewhere. If you want to change the delivery address, you should consider telling the shipment to do that rather than exposing an Address and let clients muck around with that directly, because the latter means that now if you need to add extra logic if the delivery address changes there's a chance the changes leaks all over the place (e.g. you decide to automate your customs declarations, and they need to change if the destination country changes; or delivery costs needs to updated).

You'll of course, as always, find people that takes this way too far. But the general principle is pretty much just to consider where it makes sense to hide linked objects behind a special purpose interface vs. exposing them to clients.

As for why this is useful:

If objects are allowed to talk to friends of friends, that greatly increases the level of interdependency among objects, which, in turn, increases the number of ancillary changes you might need to make in order to ensure all the code talking to some object remains compatible with its interface.

More subtly, it's also a code smell that suggests that, regardless of the presence of objects and classes, the actual structure and behavior of the code is more procedural/imperative than object-oriented. Which may or may not be a big deal - the importance of adhering to a paradigm is a decision only you can make for yourself.

Talk to directly linked objects and tell them what you need done, and let them deal with their linked objects. Don't assume that you know what is and always will be involved in doing something on dependent objects of the one you're interacting with.

IMHO, this is one of those ideas you have to consider on its merits for each project.

My own starting point is usually that I probably don’t want to drill into the internals of an entity that are implementation details at a lower level of abstraction than the entity’s own interface. That’s breaking through the abstraction and defeating the point of having a defined interface.

However, there can also be relationships between entities on the same level, for example if we’re dealing with domain concepts that have some sort of hierarchical relationship, and then each entity might expose a link to parent or child entities as part of its interface. In that case, I find it clearer to write things like

    if (entity.parent.interesting_property !== REQUIRED_VALUE) {
        abort_invalid_operation();
    }
instead of

    let parent_entity = entities.find(entity.parent_id);
    if (parent_entity.interesting_property !== REQUIRED_VALUE) {
        abort_invalid_operation();
    }
and this kind of pattern might arise often when we’re navigating the entity relationships, perhaps finding something that needs to be changed and then checking several different constraints on the overall system before allowing that change.

The “downside” of this is that we can no longer test the original entity’s interface in isolation with unit tests. However, if the logic requires navigating the relationships like this, the reality is that individual entities aren’t independent in that sense anyway, so have we really lost anything of value here?

I find that writing a test suite at the level of the overall set of entities and their relationships — which is evidently the smallest semantically meaningful data set if we need logic like the example above — works fine as an alternative to dogmatically trying to test the interface for a single entity entirely in isolation. The code for each test just sets up the store of entities and adds the specific instances and relationships I want for each test, which makes each test scenario nicely transparent. This style also ensures the tests only depend on real code, not stand-ins like mocks or stubs.

I don't think the two versions are relevant to Law of Demeter. One example has pointers/references in a strong tree and another has indexed ones, but neither is embracing LoD more or less than the other.

This would be a more relevant example:

parent_entity.children.remove(this)

vs

parent_entity.remove_child(this)

...Where remove_child() would handle removing the entity from `children` directly, and also perhaps busting a cache, or notifying the other children that the heirarchy has changed, etc etc.

Going back to your original case, you _could_ argue that LoD would advise you to create a method on entity which returns the parent, but I think that would fall under encapsulation. If you did that though, you could hide the implementation detail of whether `parent` is a reference or an ID on the actual object, which is what most ORMs will do for you.

FWIW, I was writing JavaScript in that example, so `entity.parent` might have been implemented internally as a function anyway:

    get parent() {
        return this.entities.find(this.parent_id);
    }
I don’t think whether we write `entity.parent` or `entity.parent()` really matters to the argument, though.

In any case, I see what you’re getting at. Perhaps a better way of expressing the distinction I was trying to make is whether the nested object that is being accessed in a chain can be freely used without violating any invariants of the immediate object. If not, as in your example where removing a child has additional consequences, it is probably unwise to expose it directly through the immediate object’s interface.

Yes, its a great case for making the actual `children` collection private so that mutation must go through the interface methods instead. But still, iteration over the children is a likely use case, so you are left with either exposing the original object or returning a copy of the array (potentially slower, though this might not matter depending).
That problem could potentially be solved if the programming language supports returning some form of immutable reference/proxy/cursor that allows a caller to examine the container but without being able to change anything. Unfortunately, many popular languages don’t enforce transitive immutability in that situation, so even returning an “immutable” version of the container doesn’t prevent mutation of its contained values in those languages. Score a few more points for the languages with immutability-by-default or with robust ownership semantics and support for transitive immutability…
Very true. JS has object freezing but that would affect the class's own mutations. On the other hand you could make a single copy upon mutation, freeze it, and then return the frozen one for iteration if you wanted to. Kind of going a bit far though imho.
Ah, but what if children is some kind of List or Collection which can be data-bound? By Liskov's substition principle, you ought to be able to pass it to a Collection-modifying routine and have it function correctly. If the parent must be called the children member should be private, or else the collection should implement eventing and the two methods should have the same effect (and ideally you'd remove one).
That takes us back up to viardh's concluding remark from earlier in the thread:

> You'll of course, as always, find people that takes this way too far. But the general principle is pretty much just to consider where it makes sense to hide linked objects behind a special purpose interface vs. exposing them to clients.

I would say that if you're using a ViewModel object that will be data-bound, then you're sort of outside the realm of the Law of Demeter. It's really more meant to concern objects that implement business logic, not ones that are meant to be fairly dumb data containers.

On the other hand, if it is one that is allowed to implement business logic, then I'd say, yeah, making the children member public in the first place is violating the law. You want to keep that hidden and supply a remove_child() method instead, so that you can retain the ability to change the rules around when and how children are removed, without violating LSP in the process.

In the other branch I touched on this- iterating children still being a likely use case after all, you have the option of exposing the original or making a copy which could have perf impacts.

But honestly best to not preoptimize, I would probably do

private _children : Entity[];

get children() { return this._children.slice(); }

And reconsider the potential mutation risk later if the profiler says it matters.

It can be, though there are some interesting philosophical issues there.

The example that I always keep coming back to is Smalltalk, which is the only language I know of that represents pure object-oriented programming. Similar to how, for the longest time, Haskell was more-or-less the only purely functional language. Anyway, in Smalltalk you generally would not do that. You'd tell the object to iterate its own children, and give it some block (Smalltalk equivalent of an anonymous function) that tells it what to do with them.

Looping over a data structure from the outside is, if you want to get really zealous about it, is more of a procedural and/or functional way of doing things.

Indeed! It's the visitor pattern and since we're talking about a tree that would probably be useful here.
Agree that the transformation described is pointless.

A more interesting statement, but I am not sure it is exactly equivalent to the law of Demeter:

Distinguish first between immutable data structures (and I'd group lambdas with them), and objects. An Object is something more than just a mutable data structure, one wants to also fold in the idea that some of these objects exist in a global namespace providing a named mutable state to the entire rest of the program. And to the extent that one thinks about threads one thinks about objects as providing a shared-state multithread story that requires synchronization, and all of that.

Given that distinction, one has a model of an application as kind of a factory floor, there are widgets (data structures and side-effect-free functions) flowing between Machines (big-o Objects) which process them, translate them, and perform side-effecting I/O and such.

Quasi-law-of-Demeter: in computing you have the power to also send a Machine down a conveyor belt, and build other Machines which can respond to that.[1] This is a tremendous power and it comes with tremendous responsibility. Think a system which has something like "Hey, we're gonna have an Application store a StorageMechanism and then in the live application we can swap out, without rebooting, a MySQL StorageMechanism for a local SQLite Storage Mechanism, or a MeticulouslyLoggedMySQL Storage Mechanism which is exactly like the MySQL one except it also logs every little thing it does to stdout. So when our application is misbehaving we can turn on logging while it's misbehaving and if those logs aren't enough we can at least sever the connection with the live database and start up a new node while we debug the old one and it thinks it's still doing some useful work."

The signature of this is being identified by this quasi-Law-of-Demeter as this "myApplication.getStorageMechanism().saveRecord(myRecord)" chaining. The problem is not the chaining itself; the idea would be just as wrong with the more verbose "StorageMechanism s = myApp.getStorageMechanism(); s.saveRecord(myRecord)" type of flow. The problem is just that this superpower is really quite powerful and YAGNI principles apply here: you probably don't need the ability for an application to hot-swap storage mechanisms this way.[2]

Bounded contexts[3] are kind of a red herring here, they are extremely handy but I would not apply the concept in this context.

1. FWIW this idea is being shamelessly stolen from Haskell where the conveyor belt model is an "Arrow" approach to computing and the idea that a machine can flow down a conveyor belt requires some extra structure, "ArrowApply", which is precisely equivalent to a Monad. So the quasi-law-of-Demeter actually says "avoid monads when possible", hah.

2. And of course you may run into an exception to it and that's fine, if you are aware of what you are doing.

3. Put simply a bounded context is the programming idea of "namespace" -- a space in which the same terms/words/names have a different meaning than in some other space -- applied to business-level speech. Domain-driven design is basically saying "the words that users use to describe the application, should also be the words that programmers use to describe it." So like in original-Twitter the posts to twitter were not called tweets, but now that this is the common name for them, DDD says "you should absolutely create a migration from the StatusUpdate table to the Tweet table, this will save you incalculable time in the long-run because a programmer may start to think of StatusUpdates as having some attributes which users don't associate with Tweets while users might think of Tweets as having other properties like 'the tweet I am replying to' which programmers don't think the StatusUpdates should have... and if you're not talking the same language then every single interaction consists of f...

I think following some ideas in the book, but ignoring others like the ones applicable for the law of demeter can be a recipe for a mess. The book is very opinionated, but if followed well I think it can produce pretty dead simple code. But at the same time, just like with any coding, experience plays massively into how well code is written. Code can be written well when using his methods or when ignoring his methods and it can be written badly when trying to follow some of his methods or when not using his methods at all.
Every time that I see a builder pattern, I see a failure in adopting modern programming languages. Use named parameters, for f*cks sake!

  const frobnitz = new Frobnitz({pixieDust: true, mayonnaise: true, target: worstTweetEver});
>his treatment of the Law of Demeter, which, as I recall, was so shallow that he didn't even really even thoroughly explain what the law was trying to accomplish.

oof. I mean, yeah, at least explain what the main thing you’re talking about is about, right? This is a pet peeve.

I've gone through java code where i need to open 15 different files, with one lined pieces of code, just to find out it's a "hello world" class.

I like abstraction as much as the next guy but this is closer to obfuscation than abstraction.

Spaghetti code is bad, but lasagna code is just as bad IMO.
(comment deleted)
At a previous company, there was a Clean Code OOP zealot. I heard him discussing with another colleague about the need to split up a function because it was too long (it was 10 lines). I said, from the sidelines, "yes, because nothing enhances readability like splitting a 10 line function into 10, 1-line functions". He didn't realize I was being sarcastic and nodded in agreement that it would be much better that way.
> where I have to look through 10-15 files to see the full class

The Magento 2 codebase is a good example of this. It's both well written and horrible at the same time. Everything is so spread out into constituent technical components, that the code loses the "narrative" of what's going on.

What do you say to convince someone? It’s tricky to review a large carefully abstracted PR that introduces a bunch of new logic and config with something like: “just copy paste lol”
Sometimes you really just do need a 500 line function.
Nah mate, you never do. Nor 500 1-liners.
Yes, it's the first working implementation before good boundaries are not yet known. After a while it becomes familiar and natural conceptual boundaries arise that leads to 'factoring' and shouldn't require 'refactoring' because you prematurely guessed the wrong boundaries.

I'm all for the 100-200 line working version--can't say I've had a 500. I did once have a single SQL query that was about 2 full pages pushing the limits of DB2 (needed multiple PTFs just to execute it)--the size was largely from heuristic scope reductions. In the end, it did something in about 3 minutes that had no previous solution.

In C# and .NET specifically, we find ourselves having a plethora of services when they are "human-readable" and short.

A service has 3 "helper" services it calls, which may, in turn have helper services, or worse, depend on a shared repo project.

The only solution I have found is to move these helpers into their own project, and mark the helpers as internal. This achieves 2 things:

1. The "sub-services" are not confused as stand-alone and only the "main/parent" service can be called. 2. The "module" can now be deployed independently if micro-services ever become a necessity.

I would like feedback on this approach. I do honestly thing files over 100 lines long are unreadable trash, and we have achieved a lot be re-using modular services.

We are 1.5 years into a project and our code re-use is sky-rocketing, which allows us to keep errors low.

Of course, a lot of dependencies also make testing difficult, but allow easier mocks if there are no globals.

>I would like feedback on this approach. I do honestly thing files over 100 lines long are unreadable trash

Dunno if this is the feedback you are after, but I would try to not be such an absolutist. There is no reason that a great 100 line long file becomes unreadable trash if you add one line.

I mean feedback on a way to abstract away helper services. As far as file length, I realize that this is subjective, and the 100 lines number is pulled out of thing air, but extremely long files are generally difficult to read and context gets lost.
Putting shared context in another file makes it harder to read though. Files should be big enough to represent some reasonable context, for more complicated things that necessary creates a big shared context you want bigger files and simpler things smaller files.

A thing that can be perfectly encapsulated in a 1000 line file with a small clean interface is much much better than splitting that up into 20 files 100 lines each calling each other.

... and here I was thinking I was alone!
> It's OK to duplicate code in places where the abstractions are far enough apart that the alternative is worse.

I don't recall where I picked up from, but the best advice I've heard on this is a "Rule of 3". You don't have a "pattern" to abstract until you reach (at least) three duplicates. ("Two is a coincidence, three is pattern. Coincidences happen all the time.") I've found it can be a useful rule of thumb to prevent "premature abstraction" (an understandable relative of "premature optimization"). It is surprising sometimes the things you find out about the abstraction only happen when you reach that third duplicate (variables or control flow decisions, for instance, that seem constants in two places for instance; or a higher level idea of why the code is duplicated that isn't clear from two very far apart points but is clearer when you can "triangulate" what their center is).

I'm coming to think that the rule of three is important within a fairly constrained context, but that other principle is worthwhile when you're working across contexts.

For example, when I did work at a microservices shop, I was deeply dissatisfied with the way the shared utility library influenced our code. A lot of what was in there was fairly throw-away and would not have been difficult to copy/paste, even to four or more different locations. And the shared nature of the library meant that any change to it was quite expensive. Technically, maybe, but, more importantly, socially. Any change to some corner of the library needed to be negotiated with every other team that was using that part of the library. The risk of the discussion spiraling away into an interminable series of bikesheddy meetings was always hiding in the shadows. So, if it was possible to leave the library function unchanged and get what you needed with a hack, teams tended to choose the hack. The effects of this phenomenon accumulated, over time, to create quite a mess.

An old senior colleague of mine used to insist that if i added a script to the project, i had to document it on the wiki. So i just didn't add my scripts to the project.
I'd argue if the code was "fairly throw-away" it probably did not meet the "Rule of 3" by the time it was included in the shared library in the first place.
I don't hate the rule of 3. But i think it's missing the point.

You want to extract common code if it's the same now, and will always be the same in the future. If it's not going to be the same and you extract it, you now have the pain of making it do two things, or splitting. But if it is going to be the same and you don't extract it, you have the risk of only updating one copy, and then having the other copy do the wrong thing.

For example, i have a program where one component gets data and writes it to files of a certain format in a certain directory, and another component reads those files and processes the data. The code for deciding where the directory is, and what the columns in the files are, must be the same, otherwise the programs cannot do their job. Even though there are only two uses of that code, it makes sense to extract them.

Once you think about it this way, you see that extraction also serves a documentation function. It says that the two call sites of the shared code are related to each other in some fundamental way.

Taking this approach, i might even extract code that is only used once! In my example, if the files contain dates, or other structured data, then it makes sense to have the matching formatting and parsing functions extracted and placed right next to each other, to highlight the fact that they are intimately related.

The rule of three is a guideline or principle, not a strict rule. There's nothing about it that misses the point. If, from your experience and judgement, the code can be reused, reuse it. Don't duplicate it (copy/paste or write it a second time). If, from your experience and judgement, it oughtn't be reused, but you later see that you were wrong, refactor.

In your example, it's about modularity. The directory logic makes sense as its own module. If you wrote the code that way from the start, and had already decoupled it from the writer, then reuse is obvious. But if the code were tightly coupled (embedded in some fashion) within the writer, than rewriting it would be the obvious step because reuse wouldn't be practical without refactoring. And unless you can see how to refactor it already, then writing it the second time (or third) can help you discover the actual structure you want/need.

As people become more experienced programmers, the good ones at least, already tend to use modular designs and keep things decoupled which promotes reuse versus copy/paste. In that case, the rule of three gets used less often by them because they have fewer occurrences of real duplication.

I think the point you and a lot of other commenters make is that applying hard and fast rules without referring to context is simply wrong. Surely if all we had to do was apply the rules, somebody would have long ago written a program to write programs. ;-)
You have a point for extracting exact duplicates that you know will remain the same.

But the point of the rule of 3 remains. Humans do a horrible job of abstracting from one or two examples, and the act of encountering an abstraction makes code harder to understand.

> You want to extract common code if it's the same now, and will always be the same in the future.

I suppose I take that as a presumption before the Rule of 3 applies. I generally assume/take for granted that all "exact duplicates" that "will always be the same in future" are going to be a single shared function anyway. The duplication I'm concerned about when I think the Rule of 3 comes into play is the duplicated but diverging. ("I need to do this thing like X does it, but…")

If it's a simple divergence, you can add a flag sometimes, but the Rule of 3 suggests that sometimes duplicating it and diverging it that second time "is just fine" (avoiding potentially "flag soup") until you have a better handle on the pattern for why you are diverging it, what abstraction you might be missing in this code.

The Go proverb is "A little copying is better than a little dependency." Also don't deduplicate 'text' because it's the same, deduplicate implementations if they match in both mechanism 'what' it does as well as their semantic usage. Sometimes the same thing is done with different intents which can naturally diverge and the premature deduplication is debt.
> “premature abstraction”

Also known as, “AbstractMagicObjectFactoryBuilderImpl” that builds exactly one (1) factory type that generates exactly (1) object type with no more than 2 options passed into the builder and 0 options passed into the factory. :-)

Partial classes is an ugly hack to mix human and machine generated source code. IMHO it should be avoided
I think the problem is not the book itself, but people thinking that all the rules apply to all the code, al the time. A length restriction is interesting because it makes you think if maybe you should spit your function into more than one, as you might be doing too much in one place. Now, if splitting will make things worse, then just don't.
There seems to be a lot of overlap between the Clean Coders and the Neo Coders [0]. I wish we could get rid of both.

[0] People who strive for "The One" architecture that will allow any change no matter what. Seriously, abstraction out the wazoo!

Honestly. If you're getting data from a bar code scanner and you think, "we should handle the case where we get data from a hot air balloon!" because ... what if?, you should retire.

I like to say "the machine that does everything, does nothing".
The problem is that `partial` in C# should never even have been considered as a "solution" to write small, maintainable classes. AFAIK partial was introduced for code-behind files, not to structure human written code.

Anyways, you are not alone with that experience - a common mistake I see, no matter what language or framework, is that people fall for the fallacy "separation into files" is the same as "separation of concerns".

> It's OK to duplicate code in places where the abstractions are far enough apart that the alternative is worse.

Something I’ve mentioned to my direct reports during code reviews: Sometimes, code is duplicated because it just so happens to do something similar.

However, these are independent widgets and changes to one should not affect the other; in other words, not suitable for abstraction.

This type of reasoning requires understanding the problem domain (i.e., use-case and the business functionality ).

People to people dealt this fate ...

What is mostly surprising I find most of developers are trying to obey the "rules". Code containing even minuscule duplication must be DRYied, everyone agrees that code must be clean and professional.

Yet it is never enough, bugs are showing up and stuff that was written by others is always bad.

I start thinking that 'Uncle Bob' and 'Clean code' zealots are actually harmful, because it prevents people from taking two steps back and thinking about what they are doing. Making microservices/components/classes/functions that end up never reused and making DRY holy grail.

Personally I am YAGNI > DRY and a lot of times you are not going to need small functions or magic abstractions.

> I've had developers use the 'partial' feature in C# to meet Martin's length restrictions

That is not the fault of this book or any book. The problem is people treating the guidelines as rituals instead of understanding their purpose.

I don’t disagree with the overall message or choice of examples behind this post, but one paragraph stuck out to me:

> Martin says that it should be possible to read a single source file from top to bottom as narrative, with the level of abstraction in each function descending as we read on, each function calling out to others further down. This is far from universally relevant. Many source files, I would even say most source files, cannot be neatly hierarchised in this way.

The relevance is a fair criticism but most programs in most languages can in fact be hierarchized this way, with the small number of mutually interdependent code safely separated. Many functional languages actually enforce this.

As an F# developer it can be very painful to read C# programs even though I often find C# files very elegant and readable: it just seems like a book, presented out of order, and without page numbers. Whereas an .fsproj file provides a robust reading order.

> "with the level of abstraction in each function descending as we read on, each function calling out to others further down." ...

> Many functional languages actually enforce this.

Don't they enforce the opposite? In ML languages (I don't know F# but I thought it was an ML dialect), you can generally only call functions that were defined previously.

Of course, having a clear hierarchy is nice whether it goes from most to least abstract, or the other way around, but I think Martin is recommending the opposite from what you are used to.

Hmm, perhaps I am misreading this? Your understanding of ML languages is correct. I have always found “Uncle Bob” condescending and obnoxious so I can’t speak to the actual source material.

I am putting more emphasis on the “reading top-to-bottom” aspect and less on the level of abstraction itself (might be why I’m misreading it). My understanding was that Bob sez a function shouldn’t call any “helper” functions until the helpers have been defined - if it did, you wouldn’t be able to “read” it. But with your comment, maybe he meant that you should define your lower-level functions as prototypes, implement the higher-level functions completely, then fill in the details for the lower functions at the bottom. Which is situationally useful but yeah, overkill as a hard rule.

In ML and F# you can certainly call interfaces before providing an implementation, as long as you define the interface first. Whereas in C# you can define the interface last and call it all you want beforehand. This is what I find confusing, to the point of being bad practice in most cases.

So even if I misread specifically what (the post said) Bob was saying, I think the overall idea is what Bob had in mind.

> I am putting more emphasis on the “reading top-to-bottom” aspect and less on the level of abstraction itself (might be why I’m misreading it). My understanding was that Bob sez a function shouldn’t call any “helper” functions until the helpers have been defined - if it did, you wouldn’t be able to “read” it. But with your comment, maybe he meant that you should define your lower-level functions as prototypes, implement the higher-level functions completely, then fill in the details for the lower functions at the bottom.

Neither really, he's saying the higher-level abstractions go at the top of the file, meaning the helpers go at the bottom and get used before they're defined. No mention of prototypes that I remember.

Personally, I've never liked that advice either - I always put the helpers at the top to build up the grammar, then the work is actually done at the bottom.

It seems your idea is precisely the opposite of Robert Martin's. What he is advocating for is starting out the file with the high-level abstractions first, without any of the messy details. So, at the top level you'd have a function that says `DoTheThing() { doFirstPart(); doSecondPart();}`,then reading along you'd find out what doFirstPart() and doSecondPart() mean (note that I've used imperative style names, but that was a random choice on my part).

Personally I prefer this style, even though I dislike many other ideas in Clean Code.

The requirement to define some function name before you can call it is specific to a few languages, typically older ones. I don't think it's very relevant in this context, and there are usually ways around it (such as putting all declarations in header files in C and C++, so that the actual source file technically begins with the declarations from the compiler's perspective, but doesn't actually from a programmer perspective (it just begins with #include "header").

ML languages are not functional ("functional" in the original sense of the word - pure functional). They are impure and thus don't enforce it.
As someone who almost exclusively uses functional languages: don’t do this. This kind of pedantic gatekeeping is not only obnoxious... it’s totally inaccurate! Which makes it 100x as obnoxious.

“Functional” means “functions are first-class citizens in the language” and typically mean a lot of core language features designed around easily creasing android manipulating functions as ordinary objects (so C#, C++, and Python don’t really count, even with more recent bells-and-whistles). Typically there is a strong emphasis on recursive definitions. But trying to pretend “functional programming languages” are anything particularly specific is just a recipe for dumb arguments. And of course, outside of the entry point itself, it is quite possible (even desirable) to write side-effect-free idiomatic ISO C.

The “original” functional language was LISP, which is impure as C and not even statically-typed - and for a long time certain Lisp/Scheme folks would gatekeep about how a language wasn’t a Real Functional Language if it wasn’t homoiconic and didn’t have fancy macros. (And what’s with those weird ivory tower Miranda programmers?) In fact, I think the “gate has swung,” so to speak, to the point that people downplay the certain advantages of Lisps over ML/Haskells/etc.

> for a long time certain Lisp/Scheme folks would gatekeep about how a language wasn’t a Real Functional Language if it wasn’t homoiconic and didn’t have fancy macros

This never happened. You are confusing functional with the 2000s "X is a good enough Lisp" controversy, which had nothing to do with functional programming.

> “Functional” means “functions are first-class citizens in the language”

No, the word function has a clearly defined meaning. I don't know where you get your strange ideas from - you need to look at original sources. The word "functional" did not become part of the jargon until the 1990s. Even into the 1980s most people referred to this paradigm as "applicative" (as in procedure application), which is a lot more appropriate. The big problem with the Lisp community is that early on, when everyone used the words "procedures" or "subroutines," they decided to start calling them "functions," even though they could have side effects. This is probably the reason why people started trying to appropriate "functional" as an adjective from the ML and Haskell communities into their own languages. A lot of people assume that if you can write a for loop as a map, it makes it "functional." What you end up with is a bunch of inappropriate cargo-culting by people who do not understand the basics of functional programming.

Term "functional" has been watered down and has different meanings now. However, the original meaning comes from mathematics and is still in use. You might not like it, but that's how it is - hence why I deliberately disambiguated it in my response to make it clear.

This has also nothing to do with gatekeeping.

If you disagree with me, maybe you should go to Wikipedia first and change it there, because by what you say, Wikipedia does it wrong too.

> In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.

https://en.wikipedia.org/wiki/Functional_programming

And functions links to:

> The subroutine may return a computed value to its caller (its return value), or provide various result values or output parameters. Indeed, a common use of subroutines is to implement mathematical functions, in which the purpose of the subroutine is purely to compute one or more results whose values are entirely determined by the arguments passed to the subroutine. (Examples might include computing the logarithm of a number or the determinant of a matrix.) This type is called a function.

> In programming languages such as C, C++, and C#, subroutines may also simply be called functions (not to be confused with mathematical functions or functional programming, which are different concepts).

https://en.wikipedia.org/wiki/Subroutine

> In ML languages, you can generally only call functions that were defined previously.

Hum... At least not in Haskell.

Starting with the mostly dependent code makes a large difference in readability. It's much better to open your file and see what are the overall functions. The alternative is browsing to find it, even when it's on the bottom. Since you read functions from the top to the bottom, locating the bottom of the function isn't much of a help to read it.

1 - The dependency order does not imply on any ordering in abstraction. Both can change in opposite directions just as well as on the same.

We follow this approach closely - the problem is that people confuse helper services for first-order services and call them directly leading to confusion. I don't know how to avoid this without moving the "main" service to a separate project and having `internal` helper services. DI for class libraries in .NET Core is also hacky if you don't want to import every single service explicitly.
Is there a reason why private/internal qualifiers aren’t sufficient? Possibly within the same namespace / partial class if you want to break it up?

As I type this out, I suppose “people don’t use access modifiers when they should” is a defensible reason.... I also think the InternalsVisibleTo attribute should be used more widely for testing.

You can't make a helper private if you move it out to a separate service. The risk of making it public is if people think the helper should be used directly, and not through the parent service.

Internal REQUIRES that the service be moved out to a class library project, which seems like overkill in a lot of cases.

It is interesting that he uses a fitnesse example.

Years ago we started using fitnesse at a place I was working, and we needed something that was not included, I think it was being able to make a table of http basic auth tests/requests.

The code base seems large and complex at first, but I was able to very quickly add this feature with minimal changes and was pretty confident it was correct. Also, I had little experience in Java at the time. All in all it was a pretty big success.

Fitnesse is Uncle Bob's baby so it makes sense to use that example, he can't get through a book without talking about it at length.
Interesting, is probably the wrong, word. I should say interesting to me, because I had a different experience with it. And it was not any sort of theoretical analysis, it was a feature I needed to get done.
This is an interesting article because as I was reading Martin's suggestions I agreed with every single one of them. 5 lines of code per function is ideal. Non-nested whenever possible. Don't mix query/pure and commands/impure. Then I got to the code examples and they were dreadful. Those member variables should be readonly.

Using Martin's suggestion with Functional Hexagonal Architecture would lead to beautiful code. I know because that's what I've been writing for the past 3 years.

> But mixed into the chapter there are more questionable assertions. Martin says that Boolean flag arguments are bad practice, which I agree with, because an unadorned true or false in source code is opaque and unclear versus an explicit IS_SUITE or IS_NOT_SUITE... but Martin's reasoning is rather that a Boolean argument means that a function does more than one thing, which it shouldn't.

I see how this can be polemic because most code is littered w/ flags, but I tend to agree that boolean flags can be an anti-pattern (even though it's apparently idiomatic in some languages).

Usually the flag is there to introduce a branching condition (effectively breaking "a function should do one thing") but don't carry any semantic on it's own. I find the same can be achieved w/ polymorphism and/or pattern-matching, the benefit being now your behaviour is part of the data model (the first argument) which is easier to reason about, document, and extend to new cases (don't need to keep passing flags down the call chain).

As anything, I don't think we can say "I recommend / don't recommend X book", all knowledge and experience is useful. Just use your judgment and don't treat programming books as a holy book.

> As anything, I don't think we can say "I recommend / don't recommend X book", all knowledge and experience is useful. Just use your judgment and don't treat programming books as a holy book.

People don't want to go through the trouble of reading several opposing points of view and synthesize that using their own personal experience. They want to have a book tell them everything they need to do and follow that blindly, and if that ever bites them back then that book was clearly trash. This is the POV the article seems to be written from IMHO.

Not even that, this book gets recommended to newbies who don't yet have the experience to read it critically like that.
As far as the boolean flag argument goes, I've seen it justified in terms of data-oriented design, where you want to lift your data dependencies to the top level as much as possible. If a function branches on some argument, and further up the stack that argument is constant, maybe you didn't need that branch at all if only you could invoke the right logic directly.

Notably, this argument has very little to do with readability. I do prefer consolidating data and extracting data dependencies -- I think it makes it easier to get a big-picture view, as in Brook's "Show me your spreadsheets" -- but this argument is rooted specifically in not making the machine do redundant work.

> Usually the flag is there to introduce a branching condition (effectively breaking "a function should do one thing")...

But if you don't let the function branch, then the parent function is going to have to decide which of two different functions to call. Which is going to require the parent function to branch. Sooner or later, someone has to branch. Put the branch where it makes the most sense, that is, where the logical "one-ness" of the function is preserved even with the branch.

> I find the same can be achieved w/ polymorphism and/or pattern-matching, the benefit being now your behaviour is part of the data model (the first argument) which is easier to reason about, document, and extend to new cases (don't need to keep passing flags down the call chain).

You just moved the branch. Polymorphism means that you moved the branch to the point of construction of the object. (And that's a perfectly fine way to do it, in some cases. It's a horrible way to try to deal with all branches, though.) Pattern-matching means that you moved the branch to when you created the data. (Again, that can be a perfectly fine way to do it, in some cases.)

Another thing Martin advocates for is not putting your name in comments, e.g. "Fixed a bug here; there could still be problematic interactions with subsystem foo -- ericb". He says, "Source control systems are very good at remembering who added what, when." (p. 68, 2009 edition)

Rubbish! Multiple times I've had to track down the original author of code that was auto-refactored, reformatted, changed locations, changed source control, etc. "git blame" and such are useless in these cases; it ends up being a game of Whodunit that involves hours of search, Slack pings, and what not. Just put your name in the comment, if it's documenting something substantial and is the result of your own research and struggle. And if you're in such a position, allow and encourage your colleagues to do this too.

What is even the downside of adding a few extra characters to the end of a comment to show who wrote it?

And has Martin ever even worked on large, non-greenfield projects? That's the only way I could see anyone professing such idealism.

> What is even the downside of adding a few extra characters to the end of a comment to show who wrote it?

You're right - in fact we should do this for every line of code, so that we know of whom to ask questions!

    func main() { // jen20
        fmt.Println("Hello World") // ziml77
    } // jen20
What's the downside of adding a few extra characters!?

Of course, this view is already available to people: `git blame` - and it's the same for comments, so there is no need.

The exception is "notes to future self" during the development of a feature (to be removed before review), in which case the most useful place for them to appear is at the _start_ of the comment with a marker:

    // TODO(jen20): also implement function X for type Y
Then they are easy to find...
What you've shown is code clutter and reductio ad absurdum to what I wrote in the top-level comment. I am speaking of architectural comments, bug-fixes, and especially in service architecture where unusual and catastrophic interactions might happen (or have happened) with code that's not under your control.
>You're right - in fact we should do this for every line of code, so that we know of whom to ask questions!

if you want to live in this kind of absolutism, I would rather have the name on every line than no comments at all.

Good news then: git blame already does this, and modern editors show it all the time (via copying Code Lens features from Visual Studio).

Install the appropriate extension for GitHub and you can have it there too, with no extra effort or maintenance burden of duplicative metadata.

Better put such a long explanation there that your name isn't needed any more. Because if it is your name that makes the difference chances are that you have left the company by the time someone comes across that comment and needs access to your brain.
Sometimes what is interesting is that you have found that another engineer—whom you might not know in a large enough organization—has put time and thought into the code you are working on, and you can be a lot more efficient and less likely to break things if you can talk to that engineer first. It's not always the comment itself.
Sure, but in IT the rule of assumption should be that the code will outlive the coder. If being able to talk to other engineers is going to make the difference (instead of just being an optimization) then you already have problems.
Talking to other engineers is necessary when your codebase, organization, and architecture is large enough that changes can have far-reaching effects. I would say, in fact, that it's the most important distinction between a junior and senior engineer.

Let me give you a real-world example. Facebook used to have a service called BCF‡; it was basically a "lookup" service for a given host, where you could do forward- or reverse-lookups of a hostname, or a class of hosts, and get information about their physical location, network properties, hardware configuration, and so forth.

This code was old. It had survived the transition from SVN to Git, and I'm sure it has in some form survived the transition from Git to Mercurial, though that was after my time. It had also been moved several times as no team formally owned the service. It was originally slapped together under extreme pressure by a few engineers, basically a "hackathon." Despite that, it was so useful that it had been adopted by pretty much every team that touched infrastructure, which at the time I became involved was ~300 engineers.

I was working on a service that made extensive use of the data in BCF. There was a problem with one of its Thrift RPCs which required a bugfix. This bug had plagued users of the service for several years, but because the code was so old and hoary—it didn't even have auto-generation of its Thrift bindings—nobody had bothered to fix it. Instead, every team who used this RPC had coded around it, or (worse still) skipped the binding and queried the backing database directly.

Well, I was determined to fix the bug. "git blame" showed a bot. No problem, let's go back before that commit...another bot. Before that, a human! Cool, let's reach out—no, turns out he'd done some code formatting on it. Before that—whoops, the beginning of the code history! OK, so check the old SVN repo. Five contributors over its history. I pinged each and every one who was still at the company—they hadn't written it. Finally got ahold of somebody who said, "Oh yeah, Samir‡ wrote that, ask him." I looked up and realized I could see the back of Samir's head, because he sat 10m away. It took two hours to get to that point, and 5 minutes to sort out what I needed from him without literally bringing down the site. I fixed the bug.

Every single one of those things, of course, defied the "rule of assumption." But every single one of those things was done under a specific kind of duress: keeping the company running and the features rolling. The real world is messy, and putting a little extra in your comments and leaving threads for future engineers is an enormously powerful lubricant.

‡ names changed to protect the guilty

I think you just bolstered my 'then you already have problems' argument ;)
Every company has these problems, my friend. Like I said, good comments are lubrication against the problems that will arise in sufficiently large, churning code.
Yes, that is exactly the thesis of the classic paper "Programming as Theory Building"[0], that it is the theory in the human's head that we pay for and that the complete history of the code is often not enough to effectively modify a program.

[0]https://pages.cs.wisc.edu/~remzi/Naur.pdf

Or have gotten busy since. I worked at Coinbase in 2019 and saw a comment at the top of a file saying that something should probably be changed. I git-blamed and saw it was written six years earlier by Brian Armstrong.
I think most of what martin says is rubbish, but this is not. I have never had `git blame` fail...ever. I know what user is responsible for every line of code. Doing this is contemporaneous. Its right up there with commenting out blocks of code so you don't lose them.
I don't know what to say, this is a real problem I have encountered in actual production code multiple times. Any code that lives longer than your company's source control of choice, style of choice, or code structure of choice is vulnerable. Moreover, what's the harm? It's more information, not less.
>Moreover, what's the harm? It's more information, not less

If your code is outliving your source control system you got bigger problems than whatever is in your comments. I can confidently say that in 25 years in this industry this isn't something I've encountered. So probably sufficiently low enough probability to safely ignore.

>Moreover, what's the harm? It's more information, not less.

Too much information is every bit as bad as too little.

> I can confidently say that in 25 years in this industry this isn't something I've encountered.

I've been in the industry less than half that time and it's happened to me. blame will tell you the last person/commit to touch that line of code. To find out when it was originally written, I may have to (in some cases) manually binary search the history of the file.

why? Just go to the commit hash in the blame and run blame again.

All in all it has a negligible impact on the readability of the code. It's mostly aesthetic for me. It's ugly and only solves far fetched problems.

Do you scratch your name and SSN into the side of your car? What if your title blows away in the wind on the same day that city hall burns down destroying all ownership records?

Good idea. TIL.

(I'm embarrassed I didn't think of it before)

Was going to post the exact same thing. I make use of this repeated git blame method all the time, and for everyone who is just learning this for the first time, you'll actually want to write `git blame <commit>~` to go back one commit from the commit hash in the blame, because otherwise you'll still get the same results on the line you're looking at.

Also, if you're using GitHub, their Blame view also a button that looks like a stack of rectangles between the commit information and the code. Clicking that will essentially do the same thing command-line git operation above.

git blame --ignore-rev helps with filtering out meaningless changes in commits.
If you're a CLI user, you should check out tig, which is a terminal UI for git. In the blame view, you can select a line and press "," to go to the blame view for the parent of the commit blamed for that line.

This lets you quickly traverse through the history of a file. Another good tool for digging into the history is the -S flag to "git log", which finds commits that added or removed a certain string (there's -G that searches for a regex, too).

If you prefer a GUI tool, DeepGit[0] is a very nice tool that allows you to do some pretty amazing code archeology. I use this all the time for figuring out how legacy code evolved over time.

[0] https://www.syntevo.com/deepgit/

> If your code is outliving your source control system you got bigger problems than whatever is in your comments.

You've never been at a company that finds it needs to upgrade from $old_version_control_sytem to $newer_version_control_system?

Because I've never seen that done as a big-bang rollout, it's always been "oh that code lives in the new system now, and we only kept 6 months of history"

Even just having an architect manually move folders around in the version control system has broken the history.

>You've never been at a company that finds it needs to upgrade from $old_version_control_sytem to $newer_version_control_system?

I've done this maybe a dozen times. Its always been big bang and I've never lost any history I didn't choose to lose. In fact I just did this last week for a 10 year old project that moved from SVN to GIT.

If you work somewhere where someone tells you this isn't possible consider finding a new job or alternatively become their new source control lord with your new found powers. Moving things between SCM systems is about the easiest thing you can do. Its all sequentially structured and verifiable. The tools just work everytime.

Code never gets moved or refactored by someone other than the original author?
So then you checkout at the refactor commit and look through the blame to continue searching. If you have to repeat this more than a few times then the person has probably left the company or hasn't touched the code in years so its better to understand it yourself before modifying.
I'm embarrassed I didn't think of that before.
No worries, after typing it out I definitely feel like there should be an easier way to say "show me the stack of commits that touched this line(s) of code", and I'm sure some git wizard has a fancy one liner that could more easily do that.
If it gets moved then the blame will tell me who moved it, It will also tell me what the hash was before it was moved. That hash will have all the original information. Same for the refactor case.
(comment deleted)
The parent's comment holds when reformatting, especially in languages with suspect formatting practices like golang, where visibility rules are dictated by the case of the first letter (wat?) or how it attempts to align groups of constants or struct fields depending on the length of the longest field name. Ends up in completely unnecessary changes that divert away from the main diff.
It does require some rigor in your version control practices, but I'm happily "git blame"-ing twenty year old code every now and then.
If you have to track down the author, then it is already bad. The code should not hope that the author never finds a new job.
A 1000 times this. We never use git blame - who cares? The code should be self-explanatory, and if it's not, the author doesn't remember why they did it 5 years down the line either.
I rarely want the author name. What I want is the commit message explaining the changes, and I want to see what the code used to do.

Code without history is nearly unsupportable without reverse engineering it all.

But sometimes it is bad, and not fixable within the author's control. I occasionally leave author notes, as a shortcut. If I'm no longer here, yeah you gotta figure it all out the hard way. But if I am, I can probably save you a week, maybe a month. And obviously if its something you can succintly describe, you'd just leave a comment. This is the domain of "Based on being here a few years on a few teams, and three services between this one, a few migrations etc etc". Some business problems have a lot of baggage that aren't easily documented or described, its the hard thing about professional development especially in a changing business. There's also cases where I _didnt'_ author the code, but did purposefully not change something that looks like it should be changed. In those cases, without my name comment, git blame wouldn't point you to me. YMMV.
I think your comment is controversial, for a number of reasons. One, I think nobody should own code. Code should be obvious, tested, documented and reviewed (bringing the number of people involved to at least two), the story behind it should be either in the git comments or referenced to e.g. a task management system. Code ownership just creates islands.

I mean by all means assign a "domain expert" to a PART of your code, but no individual segment of code should belong to anyone.

Second: There's something to be said about avoiding churn. Everybody loves refactoring and rewriting code, present company included, but it muddles the version control waters. I've seen a few github projects where the guidelines stated not to create PRs for minor refactorings, because they create churn and version control noise.

Anyway, that's all "ideal world" thinking, I know in practice it doesn't work like that.

Maybe not exclusive ownership, but there are always going to be those more familiar with a section of code that others.

It's not really efficient to insist everyone know the codebase equally, especially with larger codebases.

I never found people adding a name useful.

Either the code is recent (in which case 'git blame' works better since someone changing a few characters may or may not decide to add their name to the file) or it's old and the author has either left the company or has forgotten practically everything about the code.

Let's not throw the baby out with the bathwater. We can still measure how quickly new (average) developers become proficient, average team velocity over time, and a host of other metrics that tell us if we are increasing or decreasing the quality of our code over time. Ignoring it all because it's somewhat subjective is selfish and bad for your business.

Leave off the word "clean" or whatever... DO have metrics and don't ignore them. You have people on your team that make it easier for the others, and people who take their "wins" at the expense of their teammates' productivity.

(comment deleted)
> output arguments are to be avoided in favour of return values

what is an output argument?

That's a C/C++ trick where a location to dump the output is presented as an argument to the function. This makes functions un-pure and leads to all kind of nastiness such as buffer overruns and such if you are not very careful.

sprintf(buffer,"formatstring", args)

'buffer' is an output argument.

Not only in C land; C# has "ref" (pass by reference, usually implying you want to overwrite it) and "out" (like ref but you _must_ set it in all code paths). Both are a bit of a code smell and you're nearly always better off with tuples.

Unfortunately in C land for all sorts of important system APIs you have to use output arguments.

(comment deleted)
It's wrong to call output parameters a "C/C++ trick" because the concept really has nothing to do with C, C++, buffer overruns, purity, or "other nastiness".

The idea is that the caller tells the function its calling where to store results, rather than returning the results as values.

For example, Ada and Pascal both have 'out' parameters:

    https://stackoverflow.com/questions/3003480/the-use-of-in-out-in-ada#3004067

    https://freepascal.org/docs-html/ref/refsu66.html#x182-20600014.4.3

    http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-6-1.html
Theoretically, other than different calling syntax, there's conceptually no difference between "out" parameters and returning values.

In practice, though, many languages (C, C++, Java, Python, ...) support "out" parameters accidentally by passing references to non-constant objects, and that's where things get ugly.

(comment deleted)
An output argument is when you pass an argument to a function, the function makes changes, and after returning you examine the argument you passed to see what happened.

Example: the caller could pass an empty list, and the method adds items to the list.

Why not return the list? Well, maybe the method computes more things than just the list.

> Why not return the list? Well, maybe the method computes more things than just the list.

Or in C you want to allocate the list yourself in a particular way and the method should not concern with doing the allocation itself. And the return value is usually the error status/code since C doesn't have exceptions.

An output argument (or parameter) is assigned a result. In Pascal, for instance, a procedure like ReadInteger(n) would assign the result to n. In C (which does not have variable parameters) you need to pass the address of the argument, so the function call is instead ReadInteger(&n). The example function ReadInteger has a side effect so it is therefor preferable to use an output parameter rather than to return a result.
"Promote I/O to management (where it can't do any damage)" is the actionably good thing i've taken from Brandon Rhoades' talk based on this: https://www.youtube.com/watch?v=DJtef410XaM

Living in a world where people regularly write single functions that: 1. loads data from a hardcoded string path of file location 2. does all the analysis inside the same loop that the file content iteration happens in and 3. plots the results ... that cleavage plane is a meaningfully good one.

The rest of the ideas fall into "all things in moderation, including moderation", and can and should be special-cased judiciously as long as you know what you're doing. But oh god please can we stop writing _that_ function already.

This is the first time I've heard of this book. I certainly agree some of these recommendations are way off the mark.

One guideline I've always tried to keep in mind is that statistically speaking, the number of bugs in a function goes way up when the code exceeds a page or so in length. I try to keep that in mind. I still routinely write functions well over a page in length but I give them extra care when they do, lots of comments and I make sure there's a "narrative flow" to the logic.

4 line functions everywhere is insanity. yes, you should aim for short functions that do one thing, but in the real world readability and maintainability would suffer greatly if you fragment everything down to an arbitrarily small number.
The big one to keep an eye on is cyclomatic complexity with respect to function length. Just 3 conditional statements in your code gives you no less than 8 ways through your code and it only goes up from there.

All of these 'clean code' style systems have the same flaw. People follow them without understanding why the system was made. It is why you see companies put in ping pong tables, but no one uses them. They saw what someone else was doing and they were successful so they copy them. Not understanding why the ping pong table was there. They ignore the reason the chesterton's fence was built. Which is just as important if you are removing it. Clean code by itself is 'ok'. I personally am not very good at that particular style of coding. I do like that it makes things very nice to decompose into testing units.

A downside to this style of coding is it can hide complexity with an even more complex framework. It seems to have a nasty side effect of smearing the code across dozens of functions/methods which is harder in some ways to get the 'big picture'. You can wander into a meeting and say 'my method has CC of 1' but the realty is that thing is called at the bottom of a for loop, inside of 2 other if conditions. But you 'pass' because your function is short.

Number of bugs per line also goes way up when the average length of functions goes below 5, and the effect in most studies is larger than the effect of too large functions.
The problem with Clean Code is also the problem with saying to ignore Clean Code. If you treat everything as a dogmatic rule on how to do things, you're going to have a bad time.

Because, they're more like guidelines. If you try not to repeat yourself, you'll generally wind up with better code. If you try to make your methods short, you'll generally wind up with better code.

However, if you abuse partial just to meet some arbitrary length requirement, then you haven't really understood the reason for the guideline.

But the problem isn't so much because the book has a mix of good and bad recommendations. We as an evolutionary race have been pretty good at selectively filtering out bad recommendations over the long term.

The problem is that Uncle Bob has a delusional cult following (that he deliberately cultivated), which takes everything he says at face value, and are willing to drown out any dissenting voices with a non-stop barrage of bullshit platitudes.

There are plenty of ideas in Clean Code that are great, and there are plenty that are terrible...but the religiosity of adherence to it prevents of from separating the two.

Uncle "Literally who?" Bob claims you should separate your code into as many small functions spread across as many classes as you can and makes a living selling (proverbial) shovels. John Carmack says you should keep functions long and have the business logic all be encapsulated together for mental cohesion. Carmack makes a living writing software.
Uncle Bob makes a living selling snake oil. Which one should we listen to?
I work with long functions right now. It does not give mental cohesion. Instead, it makes it difficult to figure out what author intended to happen.
Or perhaps the length of the function is orthogonal to the quality of the author's code. Make the function as long as necessary to be readable and maintainable by the people most likely to read and maintain it. But that's not a very sellable snippet, nor a rule that can be grokked in 5 minutes.
Short functions make it much harder for bugs to hide.
Bugs favorite place to hide is in interfaces.
Long functions make it much harder for bugs to hide.

See what I did there?

The type of software John writes is different (much more conceptually challenging), and I don't recall him being as big of a proponent of TDD (which is the biggest benefit to small functions).

I think the right answer depends on a number of other factors.

On the spectrum you've described, I'm progressively shifting from Uncle Bob's end to Carmack's the further I get into my career. I think of it as code density. I've found that high density code is often easier to grok because there's less ceremony to keep in my head (e.g. many long method names that may or may not be named well, jumping around a bunch of files). Of course, there's a point at which code becomes so dense that it again becomes difficult to grok.
Carmack is literally the top .1% (or higher) of ability and experience. Not to mention has mostly worked in a field with different constraints than most. I don't think looking to him for general development advice is all that useful.
I think the exact opposite.

Read the doom source code and you can see that he didn't mess around with trying to put everything into some nonsense function just because he has some part of a larger function that can be scoped and named.

The way he wrote programs even back then is very direct. You don't have to jump around into lots of different functions and files for no reason. There aren't many specialized data structures or overly clever syntax tricks to prove how smart he is.

There also aren't attempts to write overly general libraries with the idea that they will be reused 100 times in the future. Everything just does what it needs to do directly.

But why should any of that be aspirational to an "average" developer? It's like learning how to do mathematics by copying Terrence Tao's patterns of behavior. Perhaps Carmack's output is more a function of the programmer than an indication of good practice for average devs.
I guess I'm still not being clear - when you read John Carmack's programs you realize that it just isn't necessary to do complex nonsense.

If you take a look at the doom source code you realize this isn't the cutting edge of mathematics, he is cranking out great software by avoiding all that and using high school algebra (literally and figuratively) instead.

While other people spin their wheels sweating over following snake oil bob's pamphlet Carmack is making programs that people want, source code that people want and work that stands the test of time.

The circumstances he operated under while writing Doom were much different than most people encounter. The couple of people working with him on the code were all experts in their field and have a complete understanding of the problem space. What you seem to be identifying as indications of unnecessary complexity of modern development practices might really just be accident of circumstance and the individual skill of the contributors at the time. It is a mistake to look at the behaviors of the unusually talented few and see takeaways to apply more broadly.
He wrote doom by himself and worked with people on later projects. You can look at the source yourself. What he himself said is the exact opposite of what you are saying - because he was able to write things directly he was able to experiment a lot. He was able to get the easier things working and go through trial and error on more difficult aspects.

If you would actually read through some of his work you would see that it a refreshingly simple way to do thing. No trying to hide that global data isn't global, no unnecessary indirection etc. etc.

> What you seem to be identifying as indications of unnecessary complexity of modern development practices might really just be accident of circumstance and the individual skill of the contributors at the time

I have no idea what this is supposed to mean.

> It is a mistake to look at the behaviors of the unusually talented few and see takeaways to apply more broadly.

This is the point you are trying to make but you just keep repeating it without backing it up in any way.

John Carmack used his skill to do things in a simple and direct way. Anyone can start to imitate that immediately. There is no invisible magic going on, he just doesn't subscribe to a bunch of snake oil nonsense that distracts people from writing the parts of their program that actually do things.

>This is the point you are trying to make but you just keep repeating it without backing it up in any way.

Do I really need to back up the claim that what applies to the rare talents doesn't automatically apply to everyone? That should be the default assumption unless proven otherwise.

> Do I really need to back up the claim that what applies to the rare talents doesn't automatically apply to everyone? That should be the default assumption unless proven otherwise.

You do actually, yes

This makes me think you are just ignoring what I'm actually saying. Read some of his source code like I mentioned multiple times and tell me this somehow only applies to John Carmack. There is no reason anyone couldn't program that way, but people have their head filled with nonsense and get distracted thinking they need unnecessary overhead. John Carmack's programs are incredibly simple and clear. Why would someone try to emulate this charlatan who doesn't make anything when they could mimic Carmack?

This isn't saying everyone can drive as fast as a race car driver, it's saying that you should at least go in the same direction if you want to get to the same place.

>Read some of his source code like I mentioned multiple times and tell me this somehow only applies to John Carmack.

I've read the Quake 3 Arena source code before. I wasn't impressed. In fact, it was what I would consider "bad code". Granted, his constraints were different so I don't judge him or his code for it. It turns out you cannot generalize from very specific scenarios with unique constraints, as I have been saying. I really don't see what general principles you think you can glean from the source code written by one unusually capable man in a mad rush to be the first to deliver a revolutionary gaming experience. The fact that you think you can is rather puzzling.

If you were so unimpressed, why do you think Carmack is in the 0.1% of programming skill?

> In fact, it was what I would consider "bad code".

lol, I don't think history agrees with you.

> The fact that you think you can is rather puzzling.

It shouldn't be puzzling since I gave you half a dozen examples of pitfalls that Carmack doesn't fall in to.

Let's reiterate: Carmack doesn't do any of the bob martin snake oil bullshit and he writes very simple, clear, direct programs that were used as foundations for multiple companies and reused over and over for decades after. He demonstrates what exceptional programming looks like through simplicity and ignoring nonsense silver bullets from charlatans.

You can keep saying how baffled and confused you are, but you haven't actually given any examples or anything concrete at all.

The biggest tell that Bob Martin isn't operating from direct experience and first principals is his 180 from object-oriented to functional programming. You don't get a dichotomy like that if you derived your opinions. The principals that functional programming is built on, what actually provides benefit to the program, would have gradually permeated the earlier teachings, they wouldn't just come out of nowhere and flip the entire ideology.

He's selling a product. He'll say what sounds convincing and he won't say things that are true but unpopular (or boring). I don't think it's deliberate but the end result is similar.

One point I do think is relevant though, is that smarter people and better programmers can cope with more complexity in one place. Carmack can probably cope with a lot more state/lines/whatever within a single function, whereas chunking everything up into a form digestible by 5-year-olds is sometimes necessary for your code to be approached by a junior. But the consequence of making dostoevsky digestible by children is that you either remove a significant amount of the content, or the book becomes hundreds of thousands of pages long. And it raises the obvious question, do you want your 747 designed by juniors?

Carmack's writing on the proper length of functions (although he expresses it in terms of when to inline a function): http://number-none.com/blow/john_carmack_on_inlined_code.htm....

2014 HN discussion: https://news.ycombinator.com/item?id=8374345

A choice quote from Carmack:

> The function that is least likely to cause a problem is one that doesn't exist, which is the benefit of inlining it. If a function is only called in a single place, the decision is fairly simple.

> In almost all cases, code duplication is a greater evil than whatever second order problems arise from functions being called in different circumstances, so I would rarely advocate duplicating code to avoid a function, but in a lot of cases you can still avoid the function by flagging an operation to be performed at the properly controlled time. For instance, having one check in the player think code for health <= 0 && !killed is almost certain to spawn less bugs than having KillPlayer() called in 20 different places.

I happen to agree with you and have posted in various HN threads over the years about the research on this, which (for what it's worth) showed that longer functions were less error prone. However, the snarky and nasty way that you made the point makes the comment a bad one for HN, no matter how right you are. Can you please not post that way? We're trying for something quite different here: https://news.ycombinator.com/newsguidelines.html.

It's even more important to stick to the site guidelines when you're right, because otherwise you discredit the truth and give people a reason to reject it, which harms all of us.

https://hn.algolia.com/?dateRange=all&page=0&prefix=true&sor...

Most of these types of books approach things from the wrong direction. Any recommendation should look at the way well designed, maintainable systems are actually written and draw their conclusions from there. Otherwise you allow too much theorizing to sneak in. Lots of good options to choose from and everyone will have their own pet projects, but something like SQLite is probably exemplary of what a small development team could aim for, Postgres or some sort of game engine would maybe be good for a larger example (maybe some of the big open source projects from major web companies would be better, I don't know).

There are books that have done something like this[0], but they are a bit high level. There is room for something at a lower level.

[0]: http://aosabook.org/en/index.html for example.

I would say Code Complete is one such book.
I’d say “Stop recommending Clean Code to noobs! It’s dangerous”

Because Clean Code is really a good book once you have enough experience to understand when each idea works and when does not. And why.

And noobs tend to over-engineer, so any book like CC or design patterns give them additional excuse and reason to over engineer and make a mess.

Robert Martin and his aura always struck me as odd. In part because of how revered he always was at organizations I worked. Senior developers would use his work to end arguments, and many code reviews discussions would be judged by how closely they adhere to Clean Code.

Of course reading Clean Code left me more confused than enlightened due precisely to what he presents as good examples of Code. The author of the article really does hit the nail on the head about Martin's code style - it's borderline unreadable a lot of times.

Who the f. even is Robert Martin?! What has he built? As far as I am able to see he is famous and revered because he is famous and revered.

I think part of his appeal lies in his utter certainty that he is correct. This is also the problem with him.
He ran a consultancy and knew how to pump out books into a world of programmers that wanted books

I was around in the early 90s through to the early 2000s when a lot of the ideas came about slowly got morphed by consultants who were selling this stuff to companies as essentially "religion". The nuanced thoughts of a lot of the people who had most of the original core ideas is mostly lost.

It's a tricky situation, at the core of things, there are some really good ideas, but the messaging by people like "uncle bob" seem to fail to communicate the mindset in a way that develops thinking programmers. Mainly because him, and people like Ron Jerfferies, really didn't actually build anything serious once they became consultants and started giving out all these edicts. If you watched them on forums/blogs at the time, they were really not that good. There were lots of people who were building real things and had great perspectives, but their nuanced perspectives were never really captured into books, and it would be hard to as it is more about the mentality of using ideas and principles and making good pragmatic choices and adapting things and not being limited by "rules" but about incorporating the essence of the ideas into your thinking processes.

So many of those people walked away from a lot of those communities when it morphed into "Agile" and started being dominated by the consultants.