Its an old T-Shirt, but "Always write your code as if the person coming after you is a violent psychopath who knows where you live" comes to mind.
Locality of behavior is underrated
This resonates. I am a vastly experienced software engineer who landed in a team with a Prima Donna coder who made my life a misery. I was all about logging, observability, simple readable code and he was all about the latest programming paradigm and "clean code". My code was full of logging and error handling so I could write meaningful messages into the log about what went wrong and why. He would delete that stuff. I concluded the guy had just never been responsible for operations, rarely had to sift through log files and was never in the hot seat when systems went down. He lived in a "clean code" bubble.
His description of finding a string somewhere in the codebase and working your way up, lines up 100% with my day to day. Polymorphism does screw it up. Happy to know I'm not the only one that starts every problem looking for some string in the codebase :D
The underlying problem with all the 'advice' books is that those assume that by following certain practices a good product is going to emerge almost magically. It is as if we expected that pushing bolts and rivets in a specific way and aligning beams in another way would allow to build a bridge without ever seeing one. Call it 'clean engineering'.
So true - supporting code in production has beaten out of me any intent of doing fancy abstractions and depth. I hope you continue to speak to this in plain principles.
Write simple functions. Keep things together. Avoid premature abstraction. The end.
I'd pay to see you debate Uncle Bob on clean code. Make it happen!
This makes me want to write a blog post on software craftsmanship
Great video. A little bit of feedback on the editing: personally I found the "morph" cuts very distracting, I think simple hard cuts work better.
16:44 "The value of maintainable code is writting code so that when bugs happen (and they will happen) you can find them and you can fix them (and write test so they don't happen again)" Loved this quote
I've rewritten several C# and C++ code in Ada and it changed the way I think and write code. It forces you to think really hard about your program's specifications. Before you can write a single line of executable code, you model your data structures in terms of types and their values. In turn, those values have constraints such as ranges, precision, number of bits, etc. Those types are grouped into "packages" and are included with your functions and procedures. When properly specified, it becomes very hard to write buggy code because the compiler checks your values at compile time and or runtime. The common fence post error and buffer overflow bugs simply don't occur when your types are properly specified. Those same constraints also serve as metadata, allowing the compiler to perform optimizations that would be impossible in other languages. The efficiency rivals C and C++ in speed and size. The resulting code is incredibly easy to understand and maintain, since it has a Pascal-like syntax and its specifications are built-in. You can revisit your code months later and immediately resume where you left off.
Clean Code seemed quite dogmatic and extreme to me but it really helped me take a closer look and consideration of the quality and clarity of my code base. Take all the advice you get from the veterans of the trade and apply only what makes sense to you at the time, if you just apply everything you hear it will most certainly be mis-applied and cause problems. Maybe you should have Uncle Bob on to discuss your concerns. I would watch that for sure.
TLDR: if a change in one place requires making a change somewhere else, then those places are coupled. Otherwise, they're not coupled. It's easier to think of the codebase as a graph structure with variables as nodes and edges as operations/functions connecting them. Having any part of the graph unreachable because it's a disconnected component is useless because it could never be called (like code in a file that can never be called by the main process). But having every node fully connected is unmaintainable: there are too many places where something affects something else and could have a possible bug. The happy medium is finding a fully-reachable yet minimally-spanning subgraph where all desired outputs are possible yet the number of connections where a bug could exist is minimized. Then other techniques like type safety, automated testing and good documentation can kick in to protect those minimal connection points that need to exist.
First time seeing one of you videos, but I liked it a lot. I think you really hit the nail on the head with something that I've been trying to explain to myself and others for years, but never had the words to do it. Thank you. Two ways I often think of the bad code I see: "Oh, that developer was trying to be clever, but failed," or, "That dev was trying to use that new hot coding technique, but it sucks. Why?" I feel like so much of it comes down to developer ego. Devs that are just chill and down to earth often write the easiest to maintain code.
How i explain wacka mole is ... "seperating the puppies does not mean cut the puppies into little pieces and give me all their paws. Its the same with code" this mental image is mortifying and gets the point of what something being "whole" means.
I strongly identify with the idea that a software team should limit the set of "things" that create meaningless arguments. Clean code rules certainly fall in that bucket. And I totally agree that applying clean code on any appreciable scale on a real large codebase leads to disparate and hard to understand code. Clean code is part of a set of coding advice (possibly stemming from OOP in my opinion) which advocates for an abstraction-and-refactoring-first approach, which is doomed to fail because one does not infact understand the true nature of the problem at the outset (if indeed ever). This is a lesson I had to learn after being a clean code, abstracting zealot. HOWEVER, the opposite end of the extreme is also a problem. I've seen code written by senior devs which gives no thought to structure at all. This is code consisting of huge functions, where logic for new requirements is just tacked in the middle somewhere, without thinking if this is a well defined business rule or process or invariant which should be pulled and out and shared with an existing usecase. Then one gets bugs where business rules are not being applied consistently. This is still better than lots of little functions, because it is easier to trace backwards (or use a debugger and trace forwards) through big functions, but it is not ideal. Perhaps the ideal "book" is one which looks at the problem from both extremes, and explains why there are not hard and fast rules but rather tools in the toolbelt: sometimes you use a hammer, other times a screwdriver. But I also can't help but wonder if the only way one learns is through experience and a bit of humility.
True indeed. Separation of concern is very important and a major pain to deal with. Another common one I see is over-engineering/future proofing, large amounts of classes and concepts that are there for future ”what ifs” that never comes but now that code needs to be maintained. Its such a classic programmer thing to do, cant just solve the issue at hand but need to build something fancier that the next set of developers will have to deal with. Also having more than one way of doing something, usually a nightmare and unless it is absolutely needed to support both ways its not worth it. Another one is premature optimization, kind of falls under future proofing. But lots of devs love to do ”smart” things in the name of performance. In the end, the actual performance issue customers face are somewhere else and the cool optimisation is never useful only prone to errors and a nightmare to maintain
Wait, I dont get it. In the last clean code example you were referring to, it's suggested to add the "special case" to the Expense class, instead of checking for the exceptional case outside it, why does this break the whole "don't make whack-a-moles" thing you mentioned? What's wrong with checking the private implementation of that class? If the error is in the expense amount, isn't it natural to then go check how that amount is calculated? Honestly, this entire thing sounded like someone who has a bunch of shit to vent, and just finding any excuse to vent.
@TitoSpadini