What I generally find is that having junior developers test their code is what makes it better code. You have them test the code and they complain about how hard it is to test. I work with them to understand that if it is hard to test it is also hard to work with later and we break up the code into smaller functions, make the output of the function depend on the input where possible, simplify the interfaces, etc. and by the time they are done they end up with code that is much better and also tested. At this point I used tests to determine if code is good or bad. If you have never actually used the code you can't say if the design is good or bad and testing will force you to use it and actually confront what you have done. It is also why developers can't just hand off testing to someone else. They need to feel the pain of the code they have written and fix it to get better.
I prefer juniors. They learn this stuff today. Had a discussion in a review today with a dude who learned programming in the early '90s. He was like: I don't read the tests, I just read the code to find out if it's correct. Well, I thought, then please don't ask me what the code is supposed to do 'cos this you could've easily found in the tests. My point is: the older the coder usually the worse the code. Tests are unconcise and any "extra" layer of abstraction gets dismissed with: we didn't need this back in the day with C. Yeah, that's what your today's code looks like. 90's C 😂
@@riesigerriese666 I'd argue if more people knew how to get the performance and efficiency of 90's C in modern OOP / functional languages, the world would be a better place. Sure, the old code reads like crap, but it had to ship on a CD. It was probably a lot better tested because shipping a bug was expensive back then. Don't hate on old developers. They know what they're doing for the most part. Maybe just let Dave tell them why continuous delivery works differently and why they need automated acceptance tests.
@@riesigerriese666 "My point is: the older the coder usually the worse the code. Tests are unconcise and any "extra" layer of abstraction gets dismissed with: we didn't need this back in the day with C. Yeah, that's what your today's code looks like. 90's C" I disagree. That is independent of the age of the coder. Some programmers settle to their "perfect" tech and stop moving. Others never stopped learning and changing with the times. The coder who is thinking that all they need is Javascript+Node+VSCode is no different from the one who thought the same about Java+Maven+Netbeans/Eclipse. Or PHP, or C, or Cobol. More than 6 decades ago we got Fortran, Cobol and APL. Tech is constantly evolving since then. The ones who settle will be left behind, and that is true for young and old coders alike.
@@riesigerriese666 It kinda sounds like you're very confused with the English language so maybe don't pick on non-juniors until you get that part working ;) There's a lot of juniors that don't want to learn but they do want that $$$.
I was in a C# shop years ago and we did TDD and negative testing, for years we enjoyed 0 defects in production, but did have a hard time producing quickly. Eventually we got a new IT VP who decided our time to market was too long and started shaving testing time. Eventually production suffered and we had many episodes where the production floor came to a halt.
What is more crucial than having readable classes and functions is the cognitive load required for a developer to follow the code throughout the entire program. The key consideration is whether the code is excessively abstracted, making it difficult to understand, or if it is structured in a way that makes the location of functionality (also known as locality of behaviour) is clear and easy to follow.
@@markemerson98 the opposite of what you will find with supposed "clean code". every "clean code" codebase that ive been forced to deal with has been a mess of painful abstraction.
To be honest I now try to minimise abstraction for the sake of abstraction. If a piece of your logic is naturally separate absolutely put it in a separate class, but that class _probably_ doesn't need an interface and factory/manager classes to initialise it.
Yes. This is why I consider "write small functions/classes" bad advice. It's not that low cognitive load code has large functions/classes, but jumping around for otherwise linear tasks increases cognitive load. There's a trade off. When you do it by rule instead of reason, you can actually make it harder to understand.
@@traveller23e Abstracting to depend on interfaces helps separate out the work load in a team so more devs can work on it, leading to quicker delivery. It also makes unit testing (particular TDD) much, much easier. Surly extracting away things that have separate concerns reduces cognitive load?
9:34 The issue with extremely layered code that Java practitioners love is that you cannot see the forest for the trees. The code injection and aspect orient programming is a bane to understanding any code because of the complex layers involved. I would NOT suggest that as an example of “good” code readability.
In the future, you need to see the forest, because one of the trees in that forest will be on fire, and you need to be able to find it. Modern software development, especially on the front end, has gone completely insane, in my opinion. You can get a lot done using just simple functions. Not everything has to be a reactive object model. 🙄
"code injections prevents you from seeing the forst for the trees" DI is about interfaces. You dont need to see the implementation because it doesnt matter. The only time it matters, is for debugging and your trace should tell you which implementation was involved in the process and where the exception happened. If your code is correctly written, the faulty implementation can be used and tested in isolation to reproduce the bug and solve the issue
A lot of programmers do not believe it's worthwhile spending time fretting over what they name things and some find it difficult to name things per se. So they end up typically using generic private naming conventions called "whatever". However they frequently don't realise that if they are having difficulty finding a good name for a class, method or variable then this can be an early indication that the code they are about to write is not a good abstraction and they should consider alternative approaches. It's also true that if your proposed abstractions are a good description of elements of the real world problem domain they are supposed to model then your classes, methods and variables should more or less name themselves without you having to think about it that much. This is an acid test for good object-oriented code.
OMG a reference to Ward's C2 wiki!! That's an automatic like right there! 😂 The best compliment I ever received about my code wasn't from another developer, but rather from a business analyst. She and I were talking though a feature and I coded something quickly for her to see. She looked at the code and said, "I understand exactly what that is doing!"
Yes, I thought the same when I saw the reference. Very few developers know about C2 it seems, and what a shame that is. I discovered it nearly 20 years ago and it is an absolute treasure trove of knowledge for software engineers looking to refine their thinking and philosophy.
When I was a Junior dev I would comment like hell in my code, I thought it was useful for me and everyone else. Once I started working on code created by more senior devs / contractors I was in awe of how readable the code was (naming conventions, verb-age) and comments were minimal, normall just to explain a quirk in a third party API. It seems soo simple but using the right names for objects is hard, but worth doing right.
Naming is so important. My daughter is a cs student and I told her the most important part when writing code is to try to get meaningful names. It is both easier to read and write, it can be nearly like a text.
Good naming, whether it's for types, methods, variables or just concepts described anywhere related is half the battle towards not just writing clearly, but in actuality towards the solution to the problem as you cannot write those good names without understanding the problem space.
09:25 your "readable code" is a mess, I still have no idea what you are trying to do, just collected that it's a bunch of Java OOP jargon, no verbs, no intent, you are saying: nuts and bolts and gears to describe a clock. it's really bad.
I used to think like that. But, as Jan Bosch rightfully stated, devs tend to forget about the business part. Some code is written once and never touched again. Some code is actually never executed in production. Some code is required for exactly one customer that doesn't pay particularly well. So, if you partition your code for business value, you will find areas where you absolutely need to have very high coding standards, and there are areas where it suffices if it barely works. It's okay to write dirty code if you prototype, as long as you keep your code in production clean. This greyscale thinking also makes it a lot easier to argue with upper management why a restructuring might be feasible.
I think this is an extremely undervalued and often forgotten. Writing testable code takes longer, but you get time savings later when the system is inevitably adjusted to new business needs. But some stuff is throwaway, so writing it testable is just wasting time.
Yes, excellent point. My company is trying to move over to a mono repo, but have implemented a number of very strict checks on code quality on anything that's checked in. Which prevents us from writing quick prototypes or even from copying over legacy code and slowly refactoring it. There's no room for code that has lower business value. So everything gets treated the same priority lol
Yeah to write code that’s easy to change sometimes requires a lot of design, which isn’t feasible in some business realities. Sometimes code is actually disposable in that it’s only used once for some one off project. I think he’s talking about saas or platform development
I don't think he does. But I know the fear of writing testable code as being time consuming. I experience it in my current job with high pressure deliverables but the truth is that what takes the most time away from delivering working and stable features is actually the countless number of bugs and unfathomable side effects generated by that speedy coding attitude and also doing shortcuts in having good tools to interface with hardware (for the embedded folks out there).
The problem of partitioning code for business is that you enforce the result that your architecture will be a copy of the organization internal department scheme and that usually is HORRIBLE to mantain and horrible to scale. Develoeprs do need to understand the business but the oens that define where to partition things shoudl be senior DEVELOPERS.
About readability, "The Programmer's Brain" is a very good book that talks about code readability. Everyone's coding style is like a dialect of a specific language.
as per the CUPID axioms, ideally the team adopts an idiomatic style so you can't readily tell which person wrote it. (that's why my SQL has the commas at the start of the lines not the end... as much as it pains me)
I can't recommend The Programmers Brain highly enough. Even though it's targeted at beginning programmers, I think there's lots of value in it for developers of all levels.
@@RoamingAdhocrat Typically, that is what coding style within teams evolves into, eventually. It happens even without some enforced coding standards, simply because people reading each other's code keep borrowing from each other's coding habits until a certain degree of uniformity is reached. Still, even so, team members might be able to identify which other team member wrote a particular piece of code by just looking at it. The same way we each have our particular style when speaking, coders develop personal style traits when coding. Style, however, is about much smaller and less relevant variations than aspects relevant for readability and maintainability. _This_ is what coding standards should be about, but they unfortunately often aren't. More often, they are about some guy with more time than was good for him at a given time compiling his personal preferences good or bad, into an at least partially useless ruleset which then gets imposed upon other people and prevents change for the better.
@@krux02 Complex requirements doesn't automatically lead to a hard to read implantation. Breaking something down into smaller, simple easy to understand steps is not unusual and part of an engineers role. I know that there are some very specific domains where that might be harder to achieve, but not most business apps in my experience. One shouldn't necessary lead to the other.
I think that by code readability, we really mean that it's easy to understand what it's doing and that is certainly the hallmark of good code. Sometimes we get enamoured by the programming techniques we use and assume these techniques will result in readable, understandable code. I often find OO code, with multiple layers of abstraction that "hide information" very difficult to understand.
I thought I knew bad code, but nothing in my 20 year career prepared me for an End To End suite which used an OOP abstraction layer to change method call passed parameters using a database as a way to handle versioning, a feature which was later abandoned anyway.
"OOP abstraction layer to change method call passed parameters". Funnily enough this looks *awfully* similar to what he's building here in the 2nd half of the video. I agree with the notion of small, self-documenting methods. But no amount of compartmentalisation can fix inherently insane ideas
I've had more than one job interview abruptly terminated when I claimed that one does not write code for computers. The ones who cared to listen did agree that code was effectively a means to convey knowledge.
that's astonishing. I thought (from my hobbyist/enthusiast perspective) that writing code primarily to be readable by future maintainers was very well understood
You are unlucky to get interviewed at such companies, I've never had such weird experience. But you are lucky you haven't spend more of your precious time with them :)
My take is that when following SOLID at any cost, the code becomes a mess. One should know SOLID very well so they can understand when it is OK to not be compliant with a particular "letter" of the abbreviation. If I have to open 8 files which are 30-40 lines of code so I can eventually see the "raw" hash map or whatever data structure I am interested in what it contains and having several interfaces of any of the in reality "wrapper" and/or "pass-thru" classes/files - I think this is no longer readable and easy to maintain... regardless of it being 100% unit test covered and fully following SOLID etc
Wrappers and pass-thru are not SOLID compliant, so nothing here is SOLID's fault. I disagree, there is no reason de diverge from SOLID. But I agree that you definitly can make bad code while following SOLID to the letter. What you describe is bad code. But abstractions matter. Good abstractions are a life saver, bad abstractions kill companies.
@@RaMz00z What kill companies is market , management, regulation and govnnrment shenannigans. Abstraction cannot do that. Bad abstractions make a company SLOW. Alone they are not enough to kill a company. Obviously you want good ones, but engineers tend to overestimate how much their critical path is ALL the critical path of a company.
I have a feeling that the way people learn SOLID these days might lead to a poor outcome, as they sometimes focus too much on the HOW to apply SOLID rather than WHY and when use these techniques.
10:30 This code is a literal nightmare. Whenever I see something like this, I work hard to remove all the nonsense abstraction and just write what's needed to do what you are trying to do. For the record, as a senior engineer at Amazon with more than a decade of experience writing Java and Kotlin, I have no idea what that piece of code is supposed to be, but I do know it's bad.
It's invoking a specific method chosen by a parameter which was decoded from the value of a message. I figured it out the first try. Like "invoke mow lawn" because the parameter that was decoded based on the message "grass tall" tells us to do that.
@@stevealdrich2472 if initialisation is so small there's no point to delegate atomic operations to other methods. All of this could be inline. If you can hold all this methods in head without ide provided bookmarks or "go to definition" - ok, i am grug, i can not. They names say nothing about what its will do and why they in other methods. If they used only once in one constructor(they are, i can see it from far away) - there's no point in them.
@@stevealdrich2472 Which is absolutely terrible. If I see a function with 0 usages I delete it. And guess what, this nonsense implementation prevents the IDE (or compiler) from detecting that functions are actually used, because they're all routed through this genius obfuscation machine. This is literally the first thing a junior builds after getting their grips on a language, and then regrets it deeply two weeks later. Unless they are stuck in an eternal junior mindset of "oh wow, I only need 10 lines of code to call ALL other functions in my entire application dynamically. They say more code is bad, so I must be doing it right. Right?"
I'm sorry to say, but this is an example of bad code. Immediately coming to a solution design from the completely wrong angle. You're immediately forced to think in whatever esoteric design pattern the author has come up with in their head. Spaghetti code is the design, with data hiding and functionality encapsulation. These kinds of code architectures grow into a tangled web, and are one of the major sources of the worst type of bug : a state-related bug. The most difficult to find. The most difficult to fix. The most likely kind which requires ugly hacks. This type of code design is speculative at best, and it is this kind of code which results in companies having to rapidly make major rewrites. How many v2.0s, or v3.0s of libraries and apps do there need to be? It comes down to either A: ugly hack, B: major refactoring, or C: total rewrite (while making all the same mistakes, all over again). ...and I haven't even touched on performance.
@RockTo11 Agree this video was done too fast, there were no real take away for me (when I watched the video before). To do code-presentations, you really need to be well prepared. A lot of Continuous Deliverys videos are of high quality though. He has sth to say, which is not the case about other presenters (or maybe I am just too old).
Your example of Immutable OO is incorrect. That class is NOT immutable. The internal state (methodParams) does not only get initialized once in code, but instead changes through decodeParams() whenever invoke() is called. It could be argued that since methodParams is not emitted publicly it's therefore just an internal implementation and the object is "functionally immutable" from the perspective of the consumer, but your explanation of what the code is vs. what the code actually does isn't correct. The _object_ and its underlying data is not immutable, it absolutely does mutate, which is different from an immutable type where changes result in new copies while the original is unchanged.
I enjoyed watching that. Don't disagree with any of it. Wrote my first piece of code at 18 here i am 45 years later still writing code. :) The comments below about old programmers producing old code made me laugh. No we don't - we are continuously learning new stuff - I've lost count of how many different languages I've written code in. If you aren't learning (at any age) then you will be one of 'those' old programmers. :).
Why is "methodParams" a field in your ReactServiceMethod class, when you return it anyway from your decodeParams method? This is a race condition waiting to happen when the service method is invoked multiple times concurrently and all invocations override the methodParams array for each other.
for me, after some years - the most effective method in SE to maintain long-standing code - is to write it as simply as possible (even trivially simple) + maximum modularisation at every level. the more modules that are independent of each other - the greater the chance that even if one goes wrong, at least the others will not be affected. and by always adding another module, you can avoid the mistakes you made earlier. with large modules, you are only adding to the debt and complexity, every additional line of code is a potential bug and complication. small modules - they are easier to test and understand even written incorrectly and not in accordance with the clean code or any rules. in the end, they are also easier to rewrite :)
This is exactly correct. Don’t use classes when modules (i.e. static classes) will do. But now you have a new problem. If you create hundreds of small modules, how do you, or more importantly, other developers find the correct module to modify in the future? Naming your modules in a standard and reliable way is very important. My current approach (after 25+ years of software development) is to essentially use the same naming convention that database designers use. All logic that is within the Sales “domain” goes in the “Sales” namespace/folder. All logic that deals exclusively with orders goes in the Orders module/file. Any logic that deals exclusively with Customers in the Customers module/file. Any logic that deals with orders for a specific customer goes in the CustomerOrders module/file. The CustomerOrders module can pretty much only talk to the Customers module and the Orders module, as a general rule, but this is not strictly mandatory. Any logic that you don’t have control over and cannot modify, like hardware, OS, third party DLLs, web services, etc. gets its own module to separate it from the rest of your logic and translate that logic into your systems preferred behavior and naming conventions. Applications are built in a top-down, org chart style design where the application is at the top, and it talks to the high level modules and listens to events from those modules. These high level modules talk to and listen to lower level, more specific modules, and so on. Interfaces are only used in situations where they make the implementation easier than having no interfaces. For instance, when you have a lot of similar objects, but with different behavior, and you don’t want to write a bunch of large switch statements. Logic for chess pieces would be a good example. This is a pretty simple approach that is easy to explain to others, and easy to work with on a day to day basis. It makes the code easy to follow, makes it trivial to find the code that you need, and also makes the code easy to convert to a different platform or even a different language.
@@AftercastGames My god, I *wish* I could structure my code like this. Have one folder for some service, with a client, maybe an api class with extended QoL methods extending client functionality, and a bunch of ValueObjects to define the input shapes, outputs shapes and maybe even legal transitions of data within that module. I could literally just take the folder as it is, rip it out and make it a standalone package. But I'm forced to put all "clients" into a "clients" folder, all "rules" into a "rules" folder etc pp, completely eliminating the notion of modules, because the files belonging to each one are spread all over the codebase
10:54 nitpick: I notice that `decodeParams` and thus `invoke` is not thread-safe, as `methodParams` is mutated. 13:50 no, `methodParams` is _not_ immutable. `final` in this case just means that it (the array) cannot be reallocated (and so its size cannot change), not that its contents cannot change.
I think he means the class itself is immutable on the registry, I'm not a java guy, so don't really know, but from what I understand, is that each value in the array, is variable, while the registry addresses holding each value is imuttable (instantiated) to a permanent address. Don't quote me, I'm just spit balling, but I don't think he is wrong.
These are difficult arguments to fight because many organizations pride themselves in complexity. The philosophy is this: if you don’t write something complex you must be doing it wrong.
I used to ask myself the question "If I got called in at 2 a.m. and was told that if I can't fix it by 5 a.m. the Bank/Store/Works can't open tomorrow, how would i feel about this code?" Applying the question to my own code as well as any I was asked to modify or review, helped me concentrate my mind no end!
@@pawelhyzopski6456 That is not as easy to say when your field for eaxmple is healthcare.. and not opoenign meansw peopel will die in emergency waiting for the PACS or HIS to proccess their data. There are some moments where you NEED to do what is needed. And as soon as that is done you start doing what you SHOULD have done.
@@tiagodagostini Fair comment, and yours too, Pawel. My point was that when you write your code, you should think about how readable it is going to be in the future. ANY code may need to be changed in the future, as requirements change, and you should be thinking about that NOW. The "fix it or we can't trade tomorrow" was not meant to be taken so literally (though I have seen a case where that was the reality- NOT in code that I had written)! .
In your final example, you use a specific definition of immutable - instances of the class are only immutable from the perspective of the caller, they are not immutable internally (despite the use of "final"). You create a fixed-size array, not using array comprehension, but by allocating it and then looping and filling the blank array in. To me, that's not immutable. I realise "pure" immutability only exists at a higher level (because, von Neumann) but you could at least push the mutability down to a level below your code by using comprehension.
This code is hard to change, therefore we need more abstraction. Now it's easy to change, but nobody except me really knows what's going on so it's still hard to change.
...and I keep my job forever as it's difficult for the company to even think about firing me. I'm the only one that knows the infinite maze of abstraction of this well written repo! Come on
Sorry for the rant, but this is how I feel. Your code was only readable in this video, because each module was listed one under the other. Too often with this modular approach is that always, each method is kept in a subfolder in a subfolder, in a sepearte file, making it completely unreadable, and no one knows the scope of each method. Or if there is an error. This is the case for every single modern piece of software ever made, it all consists of a bunch of classes instantiating methods, hiding behind subfolder after subfolder and file after file. 9 times out of ten, you end up with developers being so unaware of what methods, or solutions exist in the project, they just make their own and create bloat and more classes and methods, as they scale instead of reworking or removing the old ones, because they don't know about them and they are not in front of them two read. Separate your methods catalogue them into classes, but knowing what your team mates are producing, is the whole point of readability in the first place. It's like putting a different chapter of a book in 100 books. It just imperically creates massive over bloated projects with redundant and memory hogging code. Abstraction is the enemy of modular and oo code, in my opinion. Ps if a software engineer is unable to work on multiple modules or see and understand the larger picture of a design, then they are not a software engineer. No engineer is an island.
`decodeParams` mutates the (transitive) object's state, `methodParams` field serves no purpose (maybe except for a buffer allocation, but it also makes the method thread-unsafe)
I noticed that, too. I'm surprised that no one else mentioned this or responded to your comment. (I'd say that this makes the code at least a little harder to read.)
I think people tend to get lost in the weeds by worrying about whether a piece of software "reads" nicely, when they should be asking why they need to write that piece of code to begin with. The example of code in the video triggers this instinct for me. When I see that code, my first question is to ask why the code even needs to exist in the first place, since the signal to noise ratio for doing actual business logic is quite low, and I would prefer to find an architecture and representation that made writing that code in the first place completely unnecessary. Very often, I find that people try to make code easy to change by introducing abstraction points which just increase indirection, instead of simplifying the code so that changing it is a simple matter of some minor text editing and shifting, which has the benefit of not making it harder to see how the whole system works. That is, making something easy to change should also be about making something where it is easy to predict the impact of those changes to things like memory consumption, performance, and domain overheads (how much stuff you need to keep in your head to understand the code completely).
It’s funny you should mention that, because watching the video when he was describing what the code was doing so we could see if the code was clear… I was thinking “Why on earth would you choose such a convoluted example”. There may have been a 1-to-1 mapping of what he was saying the code did and what the code read like, but that code was not implementing business logic. It was implementing a hack. Sometimes you have to interoperate with existing code in other languages, but if possible you would avoid writing that code at all. There is no business logic that requires translating into another programming language. Thats an implementation detail. It’s mess that adds to the cognitive load of someone trying to understand the codebase.
Good Code. Locality of Behaviour matters. We spend more time reading code than producing it. If i have to jump through X abstractions good night reading that. The most readable project i have worked on, was simple at the same time. Not many layers if any at all, small enough units but not too small. Usage of patterns when really needed to solve a problem.
@@djcrem00 indeed. I went thru few hundreds kb code quicker since it was in same file than try to find it scattered across multiple files. I like when it is partitioned but needs to be done well.
At this point, I try to write code in a way that will be easy to follow and modify in the future when I inevitably decide to do things a “better” way, and have to come back and figure out how I “used” to do it. 😏
What about the duty of care to produce as many billable hours as possible? As productivity approaches zero the demand for consultants approaches infinity. Bad code has been a massive boon for the industry, so it’s no wonder so much of it is promoted as “best practice” by the same industry.
One of my most favorite quotes during code review, "Your code is bad and you should feel bad too." There was not a dry eye from the laughter which ensued. You know you have a great team when everyone can laugh and learn together.
Thx, the video well delivers the message it was intended to. I can understand this message, and core way of thinking to code. Helpful message, from my prospective.
I like this "easy to change" criterion. I often say things like "easy to understand", "no wtfs found", but if those conditions are violated, then the code is almost certainly hard to change. So, "easy to change" seems like the stronger condition. Great video!
I appreciate the pragmatic approach to good code, it works, and it's reasonably easy to change. Complex code that handles complex business logic also increases difficulty of change, however doesn't always mean the code is bad if it is complex, and often high levels of error handling and redundancy increases complexity as well.
I have worked in derivative trading in finance, flight navigation systems, and consulted teams working on Genomic analysis, high-end scientific instruments, and self driving cars. So not "simple systems" I still think that these rules hold, I don't agree that "complex biz logic" forces us to write hard-to-change code, we just have to think more carefully about our designs to ensure that they remain easy to change.
@ContinuousDelivery I'm not saying that just because the business logic is complex that you can't write components of it as easy to read and easy to change, I don't see it so black and white. Complexity of the problem you are solving increases risk factors for harder to change implementations. No code or design pattern is perfect as this would assume requirements are finite and all known up front, and that requirements remain static during the life of a project. What's considered easy to change code I believe boils down to first having appropriate structure and architecture as well as the skill level and specific domain knowledge of the developer that inherits another developers imperfect code to perform said change. I'm not dismissing your experience and credentials, simply many developers often confuse complexity with maintainability and requirements dependency with rigid code.
Wholeheartedly agree. Systems are often complex because often we are writing in a larger environmental context. When you have to understand the complexities of some larger system around what you are writing, your code will always be difficult to read for a newbie to the system. However, somebody who knows the system already will see your code and know exactly what you were trying to do because they have that context.
Your code looks horrible. It's the kind of code that is spread over multiple files, has indirections at every corner and after looking at for over an hour you realize "this does nothing but call a function with two default parameters. these are hundreds of lines of code as wrapper to a singular call." I would never want to work with that. Over engineered for usecases that never actually going to happen except in your head, and when something actually has to change in this are you realize that your abstractions do not cover the reality of the situation and now you will either doctor it in or start over. The exact opposite of what this code supposedly is avoiding, but never actually in practice. OR the third option, the poor people who have to use your construct will have to add their own workarounds to be able to use your system, in turn having to take your assumptions of what the future might be ( which are always wrong ) and carry them forwards.
Just one nitpick: If the code doesn't do what it's supposed to do, it's not bad code, it's wrong code. It can be the greatest, most readable code by any standard you could imagine and can very well be good code, but if it doesn't do what you need it for, it's still wrong, just not necessarily bad.
Readability's great when you can achieve it, but I have to say your example @8:16 is very trivial. Where complex concepts and optimisations come in, variable names get shortened to acronyms because they require more words to make the distinctions between them exceedingly clear, and operations become abstruse (e.g. bitwise operations). Being descriptive and using method/function encapsulation in performance-critical inner loops become impossible, and code may need to be manually inlined (where compiler does not support auto-inlining), removing the documentative benefit of method names. All of this works against readability, and means the best you can do for readability is keep it at a somewhat higher level in your program. In critical sections, comments are all you have to keep things clear. Code cannot always self-document.
What obfuscates code for me is languages that require a lot of scaffolding code to make a feature work, or combine lots of short hand arcane syntax. It's much harder for non-techies to understand what is going on (e.g. functional tester working out equivalence partitions). It seems in the evolution of programming languages I've seen in the last 30 years, we can't get away from doing that.
I haven't given it a lot of thought but I believe the list of two important things about code is lacking that it is reasonably easy to understand at the micro and macro levels. I see this as connected to but distinct from the other two . "Clean Code" advocates will harp on the easy to change line when they often produce code with extremely poor locality of reference that is a nightmare to understand and debug based on cherry picked examples of situations where a single change in a small method leads to a desired result. I guess I'd wrap up my point in the idea that easy to change depends on the circumstances of the change and things like tiny shallow abstractions make it easier to make certain changes assuming everything in your design is perfect and you have somehow memorised where every bit of code is. To be honest I think this is inline with the video I just think that it is worth mentioning separate from the other two criteria.
I personally always keep this in mind: the client/customer _will_ change their mind, often times right up until the last possible minute. I just accept that it's their prerogative to request changes and the code will have to adapt (i.e., be easy to change).
Sometimes good code just doesn't matter, because bad code can be delivered a little bit quicker, because that's what "business" mistakenly thinks is best.
Mistakenly? Not necessarily. Like the F1 car that is supposed to fall apart 1 yard past the finish line, 'good' code satisfies the need and no more. Any more is programmer vanity and detracts from the profit, which the programmer should receive as a bonus.
@@_Mentat In the case that "Business" doesn't understand the concept of "Doing good business, IS good business", that statement doesn't hold true. But I absolutely agree with you that if the target is to build software and deliver a product that essentially self destructs 1 lap after the race, then if everytone is onboard with the the idea, then I suppose there is no problem with that. Perhaps I should value my art less, and just sell whatever I can to whomever is offering the most money? What are your thoughts on that?
@@jacquesduplessis6175 My though is all software will be over-engineered because programmers want to; I just like everyone else. I at least know it's wrong. Just don't do it so much you bankrupt the company.
@@_Mentat We've have had this saying for many years, ````````any idiot can make a system more complicated, but it takes a tru genius to simplify things. I'll leave it at that, and awaiting my next offer. 🤯
@@somestrangescotsman No. If your code base is difficult to change, the result will be many changes that only exist to fix bugs that were unintentionally created by previous changes. Virtually every code base I've ever worked on (25+ years) that had multiple developers eventually grew to the point that any change had a > 50% chance of breaking something. This is why I now advocate for both small projects and simple, straightforward coding practices. (i.e. no hidden functionality, no pointless interfaces, minimize dependencies, etc.)
In my definition, good code has always been code that is readable, and bad code is code that isn't. You see, if the code isn't readable, it's also impossible to change. And if the code isn't readable, the only way to make sure that it works correctly is to either test it on all possible input data or to have a static code analyzer that understands what the code does and can theoretically prove that it's always correct. However, verifying it with a code review by other programmers that ensures the code handles all edge cases correctly again requires the code to be readable, otherwise it cannot be done. Unreadable code rarely works correctly, because whoever wrote the code has surely made mistakes or missed problems because the code is so poorly written. And I disagree with the video's assertion that whoever wrote the code should be able to read it. You don't need to read your own code as you write it if you write a few lines at a time. This is proven by the fact that people make bugs in that code, and when you force them to re-read their own code, really read it statement by statement, they find their own bugs, but they didn't notice them when they wrote the code because they never went back to re-read what they just wrote. They think they know what they just wrote because they just wrote it. And as for the definition of readability: If I show my code to someone who knows the language and knows the frameworks I'm using, but is unable to understand what my code does, then it's not readable if and only if I could have written the same code in a different way and it would do exactly the same thing, and this time the person can figure it out with no problem. If there is no rewrite that would help that person, then the problem may be elsewhere, like my code implements a complex algorithm and the person doesn't know it, doesn't know what that algorithm is good for, and therefore can't understand the code without first reading a paper or something. But quite often I come across code and have a hard time understanding what it does, so I rewrite it. Then I show both versions to a colleague, and if the colleague says "I have no idea what this code does, but this code here does the following", and the later code is my code, then the code was unreadable before because I had the same problem, and apparently you could have written the code in a way that would have been easy to understand.
At 10:10, initParamParsers(...) can be made highly readable without the Java boilerplate in FP or using functional style. Of course, the author preferred the boilerplates
I got my CS degree in 2004. One of our first programming classes was QBasic and Unix. That class washed out about half of the candidates simply because they couldn't manage the planning, much less the logic. Who would've thought that pseudocode and flowcharting would take out so many people? Any more, though, I have to fight to get management to let me document and/or plan anything; "just code it, we're in a hurry!"
I am not a developer,but ~half of the video, I caught myself thinking that you are speaking about functional programming approach. And after a while you mentioned it. Thanks for a video, legend
Good code is code that is readable and easily extendable within the scope of anticipated changes regarding the direction in which the code will evolve. Some changes can be predicted, while others may be considered more or less likely. All this leads to certain design decisions and compromises. Therefore, it is not enough to say that readable code is code that is easy to modify without considering whether the direction of the change was taken into account during the design and possibly why it was not.
The code example you showed is far too simple to even discuss what "good code" means. I like your content but I wish you had much deeper dives in coding practices with examples that involve several classes and dependencies.
While i agree with the notion you bring here, the problem is that most examples are either context or expertize dependent, meaning majority of the programmer audience even, will not get the full context.
A lot of good points. Please note, I found some of the graphics and code examples very hard to read and see on a phone. Perhaps on laptop or TV it's easier to read.
The point about going quickly by working carefully is a great one. Just as you will get into debt by trying to build a stock portfolio by buying lots of different shares without spending time on making sure they're good quality shares, you will likewise get into technical debt if you try to add lots of features without spending time on making sure it's good quality code. This is engineering due diligence.
I think the main source of bad programs is not unsafe languages, workflow practices, time pressure, laziness, or carelessness. The main problem is constantly changing specifications. We can have designed a perfect system, following all the rules of good programming, then our client suddenly comes and asks for a feature that we never thought of before. As a result, the code that was written perfectly has to be torn apart and the tester code has to be modified. Imagine that happening many times over a long period of time and now the code that was perfectly written has become a mess.
I would say there are two problems with that argument. First, thinking a design is "perfect" even if you're not saying it seriously, is very dangerous. All designs can be improved. Second, if your code gets messy because you had to change it a lot, that means that it wasn't good code to start with because it wasn't easy to change, which would mean that adding features or implementing new requirements was not considered in the design to begin with. I think that example is a perfectly working piece of code that only does one specific thing and it's very difficult to extend, and sounds to me like it is heavily coupled. That is the opposite of good code, imo.
The problem is that you are not optimizing your code for change. I can't comment on your situation specifically, but in my career I have seen a lot of codebases that had the exact problem you describe. Usually these kind of codebases follow a lot of "best practices" and use a lot of design patterns like clean architecture, solid, etc, etc. They had all kind of fancy abstractions that looked good in the moment, but the second the specifications changed, all those abstractions either had to be thrown away and a bunch of code had to be rewritten, or a couple of hacky if statements had to be added to add the new feature, resulting in bugs in the long run. I have come to appreciate code that doesn't create unnecessary abstractions. While this is subjective, at least for me code is easier to change when there are less abstractions I have to worry about. I don't think best practices and design patterns are bad, but they are not silver bullets. In my experience, a lot of developers use patterns and best practices for the sake of using them, rather than understanding what they are good at and using them where needed.
I am not following at all, good code is meant for chaange, even unforseen or radical. So "perfect" code as you say would have no issue changing in your scenario.
My problem with the code is that I think it shouldn’t exist! 😮 To me, this looks like accidental complexity brought on by complexity elsewhere in the system (presumably a React frontend) spilling over to your Java code. The code itself is fine and most of the criticisms I see are just silly. “Don’t call an init() in a constructor”, “that naming has a code smell”, “need better separation”, blah, blah, blah. These are fantastic examples of missing the forest through the trees. The video definition is perfect. Does it do the job and is it easy to maintain? All codebases trend toward a giant ball of mud, thereby eventually failing the second point. Complexity management is how we keep code alive. Find complexity that’s not needed (e.g. accidental complexity) and root it out. That will keep the codebase alive longer.
To achieve good code, design document highlighting business rules and decisions is of equal importance. This is very useful for live applications that is in the 10-15 year lifecycle. Engineers come and go, without this document, any code is suspect, and let's be honest, everyone always has an idea to make it better, often leading to waste of resources trying to test and understand. We are not talking about simple expressions here, but business objects and their behaviors. Ofcourse, this document is worthless if it can't be easily found. Project management + SCM + KB glue is also important to reach this.
I like this suggestion, and it implies we consider the lifetime of a software product. Too often people don't consider the full implications of designing code that needs to last more than a generation of programmers. In my team, I've noticed we've begun reversing design decisions made by predecessors 2-3 years ago, because we didn't understand why they did what they did.
I can't people who write "slick", "clever", "impenetrable" code basically as an exercise in proving what geniuses they are. I always try to write straightforward, properly compartmentalised, liberally commented code...I write it as if it was to be read and maintained by someone other than myself.
Code should tell a story, and a codebase should be like a book written by one author. There are many ways to write a good book, just like there are many ways to write a good codebase.
Everyone talking about naming conventions, classes, methods, testable code. I deal with cobol code written and "maintained" for up to 40 years. Please, empathize with me.
The question seems a bit odd. You can *optimize* for performance, correctness, testability, readability, scalability, fun. These don't always have to contradict each other, e.g. testable code is often readable, correct and more fun to work with, and not necessarily slow or unscalabe. However optimizing purely for performance, you almost certainly will sacrifice readability.
What do you think about doing an experiment in large scale code review? Perhaps create a challenge and take code submissions. Then have the public vote on which they prefer between side by side pairs, like MKBHD's blind smartphone camera test?
The only thing I think you missed, is visibility. Having juniors support production is key to their growth as a good developer. Looking at your code in production and not knowing if its working or why it isn't working is crucial too. Logging is more than a 'in passing' feature of good code.
Readability and computational/memory efficiency are at odds in some specific scenarios, e.g. micropython executing on a microcontroller. A lot of programming is balancing trade-offs and making the right compromises.
I really enjoyed the content of this video and agree with most of the content within. However, I think it is a content that preaches to choir, as young or inexperienced audience can’t value much. I’d love to discuss some of these ideas with you mate, I think there is a lot gems stored in your strong and well structured mind which we could certainly benefit, especially with a lil tweak in your code examples. 💪 Nonetheless, keep going w the good job mate 🫡
The lack of checks on your code triggers me slightly. Specially because I can't see exceptions being thrown. If the "method" parameter of your "ReactServiceMethod" constructor is null for some reason, it will crash. Same for "service" and "getParameterCount".
That sounds like defensive coding? I’d rather not spend time maintaining defensive code. Good unit testing (yes TDD) will ensure that you’re never passing a null. Using the result pattern or null object pattern will mitigate having to check for nulls everywhere which makes life easier. I’d also avoid throwing exceptions too, they’re expensive and are just goto statements with knobs on.
@@leerothman2715 I would not check for nulls everywhere either. But, as far as we know, the "ReactServiceMethod" is the top level and could be part of something that can be used by others. You can never control how others will brake things. I dislike exceptions too. Only use them when I can't avoid them.
@@leerothman2715 You're both onto something. The issue here is Java's type system's inability to express nullability. While we don't want to check for states we already know our programs should never be in, we also do not want such guarantees to be upheld by something external to the code itself. It should be evident from the code itself whether or not values being null is something that should be handled. There are programming patterns that reduce the need for this, as you mentioned, but IMO most of the time rigorous use of _@NotNull_ and _@Nullable_ (which inserts assertions for annotated values) in combination with a good static analyzer (e.g., Intellij) works wonders.
Know your framework/tool/language you are using and how your code is going to be used. In this small example, getting a null is an error and the program cannot do it's (intended) work. So why have another check that will throw the same but different kind of error? And if you read the (Java) API, you'll know the "getParameterCount" does not err out and you will get a return value. So there is no need for another unnecesary check. Adding checks is adding code. And that must be done for a purpose. That kind of defensive coding only tells me you don't know your API and are too afraid to do something.
i think the language and framework you are using play a huge role too. these can force you to hurt the readability of your code and they determine which changes are practical
I would like to add to the list of stinkers.... 'code that don't belong to the level of abstraction of class or method, I mean ideally, reading a method body must be so easy as reading a fable that circumscribes its idiom to the semantic level of its domain'
While these are important metrics for good code, there are other metrics as well. For example you can implement bogo sort and it will (eventually) do what it's supposed to do, but it is obviously a bad idea to do this. You could also implement quicksort, and it would be faster, but there's probably already a fast sort in whatever standard library you're using. My top metric is not e.g. readability, it's simplicity. The worst code I see is written by smart people trying to be clever.
Good code should be readable by almost anyone? My neighbour's mom needs to be able to read my distributed job queue code for reliably sending out emails. It's gonna save me so much money in case I need to hire a gardener to do software engineering. Obviously.
Third option should be does what it's supposed to .. but also does a bunch of stuff is wasn't. Fourth option is does what it's supposed to but is almost impossibly inefficient and will eventually bring down the entire system with certainty
This is good advice 👍 one thing though is that some languages are harder to work with eg I work with c# but had to switch to python for some ML inference API. The switch from a strongly-typed compiled language to a duck-typed interpreted language brought so many complexities, and all errors became run time errors.
The end result is the same. The strongly typed language has a chaperone that points when you make a mistake. But the mistake is still you that make. It has been years sicne last time I had an error in python of an entity of wrong type reaching a location. But that is mostly the developer developing the paranoia needed for that tool (i.e the damn parameters indicate very clearly what should be passed). Strong typing is very useful helper tool, but all mistakes are always made by a developer.
I like it. It’s simple and efficient. Especially if you save those interfaces so that you can hand them to new developers to look through before actually reading the code. Sort of like looking at the database schema before looking at the actual code. Smart. 😉
I was just about to say, I favour composition over inheritance. (Coming from an early start in OOP and inheritance etc.) Good code is code now matter what the approach.
Forget about OOP. Go into some code that is nested 12 levels deep. Take the inner block that takes up 11 levels of nesting, rip it out, put it into a function and give it a name, parameters and return type that document what its purpose and expected result are. Boom, now you have 1 level of nesting in your main code, that even tells you what it's doing in more or less natural language. If you decide you want to make the function (which still has 11 levels of nesting) more readable, take its inner block that takes up 10 levels of nesting... (you'll know how to go from here)
09:10 dear god, if I would make a list of the most annoying, unreadable antipatterns, then dynamically calling methods on some arbitrary object would be easily in the top 3. Great job on completely bypassing your compiler and IDE when it comes to figuring out actual function usage 13:06 "every method is abstracted from every other". Yeah right, that's a nice "method" field you got there in your "invoke()" method. It totally doesn't refer to something that isn't part of the method signature
function that has one line + try-catch block is a good code...welp...and still...we should test agains contracts, not each private method - mindset of a person who is paid by number of lines of code written
A none technical person has no chance at understanding your code. It's outright delusional to believe any none coder understands anything about it. Even if you explain them stuff, you basically need to teach them coding. I guess it's hard for a senior developer to put himself in the mindset of a none coder.
Not necessarily. If the business guy asks you to write a program that does 8 things, in order, every time they run it, you should probably have a function somewhere that is easy to find that has 8 lines of code in it. That is something you can show the non-tech guy and he could understand pretty easily.
I want to add another bullet point on your definition of "bad code"... Bad code is also code that is hard to understand. I'm guessing that you would argue that hard to understand code is a proper subset to hard to change code, but I'm not so sure. I think a useful distinction can be made between code that is easy to change once it's understood, but is hard to understand and code that is easy to understand, but hard to change despite that understanding. For the first type, those who understand it, and likely have been working with it. for a long time, thinks its great but onboarding people is extremely difficult. That's where the whole meme "my code good, your code bad" comes from IMO.
So your way of proving readability of code to the lay person at around 8.15, was to start talking in jargon for the next 40 seconds that no lay person would have had the first clue about.
So the code of an MRI machine on its multiple levels including image processing should be readable and understandable by a radiologist, because he knows the problem domain being solved with it?
Yes. Ideally, at a high level, if your program does 8 things, in order, every time you run it, there should be a function somewhere that is relatively easy to find that has 8 lines of code in it. If your library does 12 things, somewhere there should be a file with 12 functions in it. Pretty simple, really. 🙂
You may think the previous commenters are trolling but that's really it. If you cannot explain the code in terms of what the client asked for or vice versa, then what are you even doing? Here an absurd counter example: "So yeah we took 18 months to build the thing you asked for" "Ok what can it do?" "Well uhhhh it takes some ints and floats uuhhhh and uhhhh it does like parseParamList uhhhh" "What" "Well you know computer science is complicated. We built it using OOP so there's like, private state and uhhh" "Bro I need a device that can take a series of MRI data, analyze the blood flow and produce images based on that. Do you have a function for that?" "Uhhhhh well we do use MVC, does that answer the question?"
I don't agree that 'non-technical people should be able to read your code' is a scale that the code should be measured. At no point in the development cycle should that be the criteria of judging the readability of code. Lead Technical Engineers & Architects should be the judge of it and that is where the importance of code readability ends. Because it is expected of them to have a clear, latest and in-depth understanding of the application so that they can be trusted to represent when interacting with managers and product team.
It is possible that you are missing my point, it is not that non-technical people need to care, or even have any direct stake in the readability of your code, rather it is that you could, with relative ease, be able to explain it to them so that they can read it. This is a qualitative measure of it's readability, if you need deep experience and extensive training in software development to read the code, that isn't good enough to qualify as "readable" so, sure you want tech leaders and architects to be able to read it, but you get that FOR SURE, when almost anyone can read it.
@@ContinuousDelivery Why should we dumb down the code? Readability is for people who have the capacity to understand. Non-technical people have no interest, knowledge, experience in reading or understanding code. I get your points, but I don't think code readability standards should target any non-technical person.
Ah. I remember back in college. We had a contest using STSC APL language to write "one liner" programs which did something at least somewhat useful. Impossible to understand except by decomposing bit by bit. And have a good understanding of manipulating multiple dimensional matricies. Comments? The code speaks for itself! 😂😂😂😂
Okay, I fished the first third part, I will comeback after taking a nap, his voice is too much to me, I am instantly feeling sleepy can’t even work with 2x speed. 😢
totally agree with claim that readability is subjective. espcially after watching "Maintainability And Readability | Prime Reacts" (Primeagen's reaction video on tsoding). as a programmer I find myself thinking about some problem I'm goig to solve in a cetrain perspective. it will be reflected in code (other programmer can see the problem from a different angle and would write it differently). if I come back to code I wrote after 2-5 years, and I can understand it sort of easy and modify it, I would say code is "good to maintain" subjectively
Just put it all in one function and shout down reviewers during meetings, that's what the senior devs at my workplace do, and we get paid 30k above market. RAILS 4 LIFE!
Just goes to show how subjective this is because I personally didn't think your code was great. Calling an init method from a constructor? That's the sort of thing I'd expect to see from an old C program. You also tend to simplify things. Let's be honest, easy to change code is often (but not always) less performant. So not always achievable eithout compromising something else. I totally agree that easily changeable code is the goal, but that also assumes you know future requirements upfront. Given the time, you can create code that matches your requirements of good code, but in the real world, we're tasked with getting specific functionality in place quickly.
As someone who mostly writes high-performance code, I see what you mean with good code (as defined here) often being at odds with performance. However, in many cases it's possible to get 80-90% of the performamce while sticking to easy-to-read-and-maintain code. I believe that should be the goal, only to be compromised in those cases where every little bit of performance really IS needed. A great example is the Eigen library. *Almost* best-in-class performance, and the internals are really ugly at times, but as a user it allows me to write GREAT code while getting lots of performance for free.
@@ToadalChaos That's my point, it's subjective. From my point of view, 'Good Code' is code that gets the job done without bugs. Having the requirement of "it should be easy to change" might be completely out of scope and add more complexity than is actually required, meaning it could add more things that could go wrong. Not to mention you might never need to change that code again. If it turns out the code is needing to be changed as more requirements come in, then sure, refactor it. But doing that at the outset seems wasted effort, as well as adding more unnecessary failure points. This also depends on the type of project you're working on and the time you have for the task. As for your second point, add an abstraction layer on top of the 'Ugly code' might hide it, but its still there, and someone still has to maintain it. Not saying there is anything wrong with that, but not sure what you were getting at.
If you're talking about writing code without really knowing what you need and what the requirements are until you're finished in one breath.... And in the next breath saying code that's easy to change is not performant, then I'd argue you're prematurely optimizing an area without knowing it needs to be optimized. If you do know it needs to then you know more than you're letting on. Realistically modern compilers are pretty clever. Just look at Jason Turner's C++ examples. A lot can be inlined or completely optimized out. What you write isn't translated exactly to assembly. Often times if you know your runtime or compiler you can use more clear and verbose code that is easily understood and later optimized out.
Bad Code hall of shame: "I once saw a C function that was 10,000 lines long." Pfft! Lightweights. I worked with one (you are still using it in your phone if it has 3G) that had a McCabe metric of 18,000 and had been edited several times a week, every week, for 10 years. My suggestion that we CONSIDER trying some simple refactoring was... "not well received".
What I generally find is that having junior developers test their code is what makes it better code. You have them test the code and they complain about how hard it is to test. I work with them to understand that if it is hard to test it is also hard to work with later and we break up the code into smaller functions, make the output of the function depend on the input where possible, simplify the interfaces, etc. and by the time they are done they end up with code that is much better and also tested.
At this point I used tests to determine if code is good or bad. If you have never actually used the code you can't say if the design is good or bad and testing will force you to use it and actually confront what you have done. It is also why developers can't just hand off testing to someone else. They need to feel the pain of the code they have written and fix it to get better.
I prefer juniors. They learn this stuff today.
Had a discussion in a review today with a dude who learned programming in the early '90s. He was like: I don't read the tests, I just read the code to find out if it's correct.
Well, I thought, then please don't ask me what the code is supposed to do 'cos this you could've easily found in the tests.
My point is: the older the coder usually the worse the code. Tests are unconcise and any "extra" layer of abstraction gets dismissed with: we didn't need this back in the day with C.
Yeah, that's what your today's code looks like. 90's C 😂
@@riesigerriese666 I'd argue if more people knew how to get the performance and efficiency of 90's C in modern OOP / functional languages, the world would be a better place.
Sure, the old code reads like crap, but it had to ship on a CD. It was probably a lot better tested because shipping a bug was expensive back then.
Don't hate on old developers. They know what they're doing for the most part. Maybe just let Dave tell them why continuous delivery works differently and why they need automated acceptance tests.
@@riesigerriese666 "My point is: the older the coder usually the worse the code. Tests are unconcise and any "extra" layer of abstraction gets dismissed with: we didn't need this back in the day with C.
Yeah, that's what your today's code looks like. 90's C"
I disagree. That is independent of the age of the coder. Some programmers settle to their "perfect" tech and stop moving. Others never stopped learning and changing with the times. The coder who is thinking that all they need is Javascript+Node+VSCode is no different from the one who thought the same about Java+Maven+Netbeans/Eclipse. Or PHP, or C, or Cobol. More than 6 decades ago we got Fortran, Cobol and APL. Tech is constantly evolving since then. The ones who settle will be left behind, and that is true for young and old coders alike.
@@riesigerriese666 It kinda sounds like you're very confused with the English language so maybe don't pick on non-juniors until you get that part working ;) There's a lot of juniors that don't want to learn but they do want that $$$.
I was in a C# shop years ago and we did TDD and negative testing, for years we enjoyed 0 defects in production, but did have a hard time producing quickly. Eventually we got a new IT VP who decided our time to market was too long and started shaving testing time. Eventually production suffered and we had many episodes where the production floor came to a halt.
What is more crucial than having readable classes and functions is the cognitive load required for a developer to follow the code throughout the entire program. The key consideration is whether the code is excessively abstracted, making it difficult to understand, or if it is structured in a way that makes the location of functionality (also known as locality of behaviour) is clear and easy to follow.
@@markemerson98 the opposite of what you will find with supposed "clean code". every "clean code" codebase that ive been forced to deal with has been a mess of painful abstraction.
@@HaHa-fn9iq exactly
To be honest I now try to minimise abstraction for the sake of abstraction. If a piece of your logic is naturally separate absolutely put it in a separate class, but that class _probably_ doesn't need an interface and factory/manager classes to initialise it.
Yes. This is why I consider "write small functions/classes" bad advice. It's not that low cognitive load code has large functions/classes, but jumping around for otherwise linear tasks increases cognitive load. There's a trade off. When you do it by rule instead of reason, you can actually make it harder to understand.
@@traveller23e Abstracting to depend on interfaces helps separate out the work load in a team so more devs can work on it, leading to quicker delivery. It also makes unit testing (particular TDD) much, much easier. Surly extracting away things that have separate concerns reduces cognitive load?
9:34 The issue with extremely layered code that Java practitioners love is that you cannot see the forest for the trees. The code injection and aspect orient programming is a bane to understanding any code because of the complex layers involved. I would NOT suggest that as an example of “good” code readability.
Why do you need to see the forrest exactly ?
Spoiler: you don't.
In the future, you need to see the forest, because one of the trees in that forest will be on fire, and you need to be able to find it.
Modern software development, especially on the front end, has gone completely insane, in my opinion. You can get a lot done using just simple functions. Not everything has to be a reactive object model. 🙄
"code injections prevents you from seeing the forst for the trees"
DI is about interfaces. You dont need to see the implementation because it doesnt matter.
The only time it matters, is for debugging and your trace should tell you which implementation was involved in the process and where the exception happened.
If your code is correctly written, the faulty implementation can be used and tested in isolation to reproduce the bug and solve the issue
A lot of programmers do not believe it's worthwhile spending time fretting over what they name things and some find it difficult to name things per se. So they end up typically using generic private naming conventions called "whatever". However they frequently don't realise that if they are having difficulty finding a good name for a class, method or variable then this can be an early indication that the code they are about to write is not a good abstraction and they should consider alternative approaches. It's also true that if your proposed abstractions are a good description of elements of the real world problem domain they are supposed to model then your classes, methods and variables should more or less name themselves without you having to think about it that much. This is an acid test for good object-oriented code.
This, a MILLION times this !
I've never understood programmer's aversion to vowels. Why oh why is TTLSLS better than TotalSales ?
@@stevealdrich2472 TotalSales!? That's clearly an abbreviation for the Trusted TLS Lookup Service 😁.
so f*ing true!
OMG a reference to Ward's C2 wiki!! That's an automatic like right there! 😂
The best compliment I ever received about my code wasn't from another developer, but rather from a business analyst. She and I were talking though a feature and I coded something quickly for her to see. She looked at the code and said, "I understand exactly what that is doing!"
That really sounds like high praise :)
Yes, I thought the same when I saw the reference. Very few developers know about C2 it seems, and what a shame that is. I discovered it nearly 20 years ago and it is an absolute treasure trove of knowledge for software engineers looking to refine their thinking and philosophy.
When I was a Junior dev I would comment like hell in my code, I thought it was useful for me and everyone else. Once I started working on code created by more senior devs / contractors I was in awe of how readable the code was (naming conventions, verb-age) and comments were minimal, normall just to explain a quirk in a third party API. It seems soo simple but using the right names for objects is hard, but worth doing right.
Naming is so important. My daughter is a cs student and I told her the most important part when writing code is to try to get meaningful names. It is both easier to read and write, it can be nearly like a text.
In general you only comment when it makes sense, as in dirty patch, bugs, todos etc.
Code by it self should be just as readable as harry Potter books.
Good naming, whether it's for types, methods, variables or just concepts described anywhere related is half the battle towards not just writing clearly, but in actuality towards the solution to the problem as you cannot write those good names without understanding the problem space.
you know the adage: There are only two hard things in Computer Science: cache invalidation and naming things.
09:25 your "readable code" is a mess, I still have no idea what you are trying to do, just collected that it's a bunch of Java OOP jargon, no verbs, no intent, you are saying: nuts and bolts and gears to describe a clock.
it's really bad.
I used to think like that. But, as Jan Bosch rightfully stated, devs tend to forget about the business part. Some code is written once and never touched again. Some code is actually never executed in production. Some code is required for exactly one customer that doesn't pay particularly well.
So, if you partition your code for business value, you will find areas where you absolutely need to have very high coding standards, and there are areas where it suffices if it barely works.
It's okay to write dirty code if you prototype, as long as you keep your code in production clean.
This greyscale thinking also makes it a lot easier to argue with upper management why a restructuring might be feasible.
I think this is an extremely undervalued and often forgotten. Writing testable code takes longer, but you get time savings later when the system is inevitably adjusted to new business needs. But some stuff is throwaway, so writing it testable is just wasting time.
Yes, excellent point. My company is trying to move over to a mono repo, but have implemented a number of very strict checks on code quality on anything that's checked in. Which prevents us from writing quick prototypes or even from copying over legacy code and slowly refactoring it. There's no room for code that has lower business value. So everything gets treated the same priority lol
Yeah to write code that’s easy to change sometimes requires a lot of design, which isn’t feasible in some business realities. Sometimes code is actually disposable in that it’s only used once for some one off project. I think he’s talking about saas or platform development
I don't think he does. But I know the fear of writing testable code as being time consuming. I experience it in my current job with high pressure deliverables but the truth is that what takes the most time away from delivering working and stable features is actually the countless number of bugs and unfathomable side effects generated by that speedy coding attitude and also doing shortcuts in having good tools to interface with hardware (for the embedded folks out there).
The problem of partitioning code for business is that you enforce the result that your architecture will be a copy of the organization internal department scheme and that usually is HORRIBLE to mantain and horrible to scale. Develoeprs do need to understand the business but the oens that define where to partition things shoudl be senior DEVELOPERS.
About readability, "The Programmer's Brain" is a very good book that talks about code readability. Everyone's coding style is like a dialect of a specific language.
as per the CUPID axioms, ideally the team adopts an idiomatic style so you can't readily tell which person wrote it. (that's why my SQL has the commas at the start of the lines not the end... as much as it pains me)
I can't recommend The Programmers Brain highly enough. Even though it's targeted at beginning programmers, I think there's lots of value in it for developers of all levels.
@@RoamingAdhocrat Typically, that is what coding style within teams evolves into, eventually. It happens even without some enforced coding standards, simply because people reading each other's code keep borrowing from each other's coding habits until a certain degree of uniformity is reached.
Still, even so, team members might be able to identify which other team member wrote a particular piece of code by just looking at it. The same way we each have our particular style when speaking, coders develop personal style traits when coding.
Style, however, is about much smaller and less relevant variations than aspects relevant for readability and maintainability. _This_ is what coding standards should be about, but they unfortunately often aren't. More often, they are about some guy with more time than was good for him at a given time compiling his personal preferences good or bad, into an at least partially useless ruleset which then gets imposed upon other people and prevents change for the better.
How many wtf’s per minute is normally a good indicator 😂. When you open a repo and it reads like you’d expect, that’s a good day in my book.
Hard problems to solve usually result in code with a high wtf's per minute, no matter how you solve it, because it is inherently complex.
@@krux02 Complex requirements doesn't automatically lead to a hard to read implantation. Breaking something down into smaller, simple easy to understand steps is not unusual and part of an engineers role. I know that there are some very specific domains where that might be harder to achieve, but not most business apps in my experience. One shouldn't necessary lead to the other.
I think that by code readability, we really mean that it's easy to understand what it's doing and that is certainly the hallmark of good code. Sometimes we get enamoured by the programming techniques we use and assume these techniques will result in readable, understandable code. I often find OO code, with multiple layers of abstraction that "hide information" very difficult to understand.
I find OOC to be amazingly ugly and hard to read! Been coding off-and-on for 37 years.
I dont think OOP is the problem. You can over-abstract and "hide information" all the same with cryptic and badly named functions and structs.
I thought I knew bad code, but nothing in my 20 year career prepared me for an End To End suite which used an OOP abstraction layer to change method call passed parameters using a database as a way to handle versioning, a feature which was later abandoned anyway.
Time to refactor
@@Lemmy4555 refactor isn't a strong enough word here
@@sb_dunk how about, burn it down?
"OOP abstraction layer to change method call passed parameters". Funnily enough this looks *awfully* similar to what he's building here in the 2nd half of the video. I agree with the notion of small, self-documenting methods. But no amount of compartmentalisation can fix inherently insane ideas
I've had more than one job interview abruptly terminated when I claimed that one does not write code for computers. The ones who cared to listen did agree that code was effectively a means to convey knowledge.
that's astonishing. I thought (from my hobbyist/enthusiast perspective) that writing code primarily to be readable by future maintainers was very well understood
@@RoamingAdhocrat This is the only criteria that makes sense
You are unlucky to get interviewed at such companies, I've never had such weird experience. But you are lucky you haven't spend more of your precious time with them :)
My take is that when following SOLID at any cost, the code becomes a mess. One should know SOLID very well so they can understand when it is OK to not be compliant with a particular "letter" of the abbreviation. If I have to open 8 files which are 30-40 lines of code so I can eventually see the "raw" hash map or whatever data structure I am interested in what it contains and having several interfaces of any of the in reality "wrapper" and/or "pass-thru" classes/files - I think this is no longer readable and easy to maintain... regardless of it being 100% unit test covered and fully following SOLID etc
@@miroslavisikiyski4876 agreed.
Words to make a living by, “I’d rather my program be SOLD than SOLID”
Wrappers and pass-thru are not SOLID compliant, so nothing here is SOLID's fault.
I disagree, there is no reason de diverge from SOLID. But I agree that you definitly can make bad code while following SOLID to the letter.
What you describe is bad code. But abstractions matter. Good abstractions are a life saver, bad abstractions kill companies.
@@RaMz00z What kill companies is market , management, regulation and govnnrment shenannigans. Abstraction cannot do that. Bad abstractions make a company SLOW. Alone they are not enough to kill a company. Obviously you want good ones, but engineers tend to overestimate how much their critical path is ALL the critical path of a company.
I have a feeling that the way people learn SOLID these days might lead to a poor outcome, as they sometimes focus too much on the HOW to apply SOLID rather than WHY and when use these techniques.
10:30 This code is a literal nightmare. Whenever I see something like this, I work hard to remove all the nonsense abstraction and just write what's needed to do what you are trying to do.
For the record, as a senior engineer at Amazon with more than a decade of experience writing Java and Kotlin, I have no idea what that piece of code is supposed to be, but I do know it's bad.
It's invoking a specific method chosen by a parameter which was decoded from the value of a message. I figured it out the first try. Like "invoke mow lawn" because the parameter that was decoded based on the message "grass tall" tells us to do that.
@@stevealdrich2472 if initialisation is so small there's no point to delegate atomic operations to other methods. All of this could be inline. If you can hold all this methods in head without ide provided bookmarks or "go to definition" - ok, i am grug, i can not. They names say nothing about what its will do and why they in other methods. If they used only once in one constructor(they are, i can see it from far away) - there's no point in them.
@@stevealdrich2472 Which is absolutely terrible. If I see a function with 0 usages I delete it. And guess what, this nonsense implementation prevents the IDE (or compiler) from detecting that functions are actually used, because they're all routed through this genius obfuscation machine. This is literally the first thing a junior builds after getting their grips on a language, and then regrets it deeply two weeks later. Unless they are stuck in an eternal junior mindset of "oh wow, I only need 10 lines of code to call ALL other functions in my entire application dynamically. They say more code is bad, so I must be doing it right. Right?"
I'm sorry to say, but this is an example of bad code. Immediately coming to a solution design from the completely wrong angle. You're immediately forced to think in whatever esoteric design pattern the author has come up with in their head. Spaghetti code is the design, with data hiding and functionality encapsulation. These kinds of code architectures grow into a tangled web, and are one of the major sources of the worst type of bug : a state-related bug. The most difficult to find. The most difficult to fix. The most likely kind which requires ugly hacks.
This type of code design is speculative at best, and it is this kind of code which results in companies having to rapidly make major rewrites. How many v2.0s, or v3.0s of libraries and apps do there need to be? It comes down to either A: ugly hack, B: major refactoring, or C: total rewrite (while making all the same mistakes, all over again).
...and I haven't even touched on performance.
@RockTo11 Agree this video was done too fast, there were no real take away for me (when I watched the video before). To do code-presentations, you really need to be well prepared. A lot of Continuous Deliverys videos are of high quality though. He has sth to say, which is not the case about other presenters (or maybe I am just too old).
Your example of Immutable OO is incorrect. That class is NOT immutable. The internal state (methodParams) does not only get initialized once in code, but instead changes through decodeParams() whenever invoke() is called. It could be argued that since methodParams is not emitted publicly it's therefore just an internal implementation and the object is "functionally immutable" from the perspective of the consumer, but your explanation of what the code is vs. what the code actually does isn't correct.
The _object_ and its underlying data is not immutable, it absolutely does mutate, which is different from an immutable type where changes result in new copies while the original is unchanged.
I enjoyed watching that. Don't disagree with any of it. Wrote my first piece of code at 18 here i am 45 years later still writing code. :)
The comments below about old programmers producing old code made me laugh. No we don't - we are continuously learning new stuff - I've lost count of how many different languages I've written code in. If you aren't learning (at any age) then you will be one of 'those' old programmers. :).
Why is "methodParams" a field in your ReactServiceMethod class, when you return it anyway from your decodeParams method? This is a race condition waiting to happen when the service method is invoked multiple times concurrently and all invocations override the methodParams array for each other.
for me, after some years - the most effective method in SE to maintain long-standing code - is to write it as simply as possible (even trivially simple)
+ maximum modularisation at every level.
the more modules that are independent of each other - the greater the chance that even if one goes wrong, at least the others will not be affected.
and by always adding another module, you can avoid the mistakes you made earlier.
with large modules, you are only adding to the debt and complexity, every additional line of code is a potential bug and complication.
small modules - they are easier to test and understand even written incorrectly and not in accordance with the clean code or any rules.
in the end, they are also easier to rewrite :)
Modules has a bonus. You can write them in any language on any platform. You can change them on the fly.
This is exactly correct. Don’t use classes when modules (i.e. static classes) will do.
But now you have a new problem. If you create hundreds of small modules, how do you, or more importantly, other developers find the correct module to modify in the future?
Naming your modules in a standard and reliable way is very important. My current approach (after 25+ years of software development) is to essentially use the same naming convention that database designers use. All logic that is within the Sales “domain” goes in the “Sales” namespace/folder. All logic that deals exclusively with orders goes in the Orders module/file. Any logic that deals exclusively with Customers in the Customers module/file. Any logic that deals with orders for a specific customer goes in the CustomerOrders module/file. The CustomerOrders module can pretty much only talk to the Customers module and the Orders module, as a general rule, but this is not strictly mandatory. Any logic that you don’t have control over and cannot modify, like hardware, OS, third party DLLs, web services, etc. gets its own module to separate it from the rest of your logic and translate that logic into your systems preferred behavior and naming conventions.
Applications are built in a top-down, org chart style design where the application is at the top, and it talks to the high level modules and listens to events from those modules. These high level modules talk to and listen to lower level, more specific modules, and so on.
Interfaces are only used in situations where they make the implementation easier than having no interfaces. For instance, when you have a lot of similar objects, but with different behavior, and you don’t want to write a bunch of large switch statements. Logic for chess pieces would be a good example.
This is a pretty simple approach that is easy to explain to others, and easy to work with on a day to day basis. It makes the code easy to follow, makes it trivial to find the code that you need, and also makes the code easy to convert to a different platform or even a different language.
@@AftercastGames My god, I *wish* I could structure my code like this. Have one folder for some service, with a client, maybe an api class with extended QoL methods extending client functionality, and a bunch of ValueObjects to define the input shapes, outputs shapes and maybe even legal transitions of data within that module. I could literally just take the folder as it is, rip it out and make it a standalone package.
But I'm forced to put all "clients" into a "clients" folder, all "rules" into a "rules" folder etc pp, completely eliminating the notion of modules, because the files belonging to each one are spread all over the codebase
10:54 nitpick: I notice that `decodeParams` and thus `invoke` is not thread-safe, as `methodParams` is mutated.
13:50 no, `methodParams` is _not_ immutable. `final` in this case just means that it (the array) cannot be reallocated (and so its size cannot change), not that its contents cannot change.
I think he means the class itself is immutable on the registry, I'm not a java guy, so don't really know, but from what I understand, is that each value in the array, is variable, while the registry addresses holding each value is imuttable (instantiated) to a permanent address. Don't quote me, I'm just spit balling, but I don't think he is wrong.
@@clarkmerchant2 Nothing about Java specifically. The class instance has state that is changing after constructor call so it is not immutable. Period.
These are difficult arguments to fight because many organizations pride themselves in complexity. The philosophy is this: if you don’t write something complex you must be doing it wrong.
omg, that is like the other side if KISS
I used to ask myself the question "If I got called in at 2 a.m. and was told that if I can't fix it by 5 a.m. the Bank/Store/Works can't open tomorrow, how would i feel about this code?" Applying the question to my own code as well as any I was asked to modify or review, helped me concentrate my mind no end!
You should not deliver anything in the first place. Ask a builder what would he do if he got a call from same bank that the ceiling is falling apart
@@pawelhyzopski6456 That is not as easy to say when your field for eaxmple is healthcare.. and not opoenign meansw peopel will die in emergency waiting for the PACS or HIS to proccess their data. There are some moments where you NEED to do what is needed. And as soon as that is done you start doing what you SHOULD have done.
@@tiagodagostini Fair comment, and yours too, Pawel. My point was that when you write your code, you should think about how readable it is going to be in the future. ANY code may need to be changed in the future, as requirements change, and you should be thinking about that NOW. The "fix it or we can't trade tomorrow" was not meant to be taken so literally (though I have seen a case where that was the reality- NOT in code that I had written)! .
In your final example, you use a specific definition of immutable - instances of the class are only immutable from the perspective of the caller, they are not immutable internally (despite the use of "final"). You create a fixed-size array, not using array comprehension, but by allocating it and then looping and filling the blank array in. To me, that's not immutable.
I realise "pure" immutability only exists at a higher level (because, von Neumann) but you could at least push the mutability down to a level below your code by using comprehension.
This code is hard to change, therefore we need more abstraction. Now it's easy to change, but nobody except me really knows what's going on so it's still hard to change.
...and I keep my job forever as it's difficult for the company to even think about firing me. I'm the only one that knows the infinite maze of abstraction of this well written repo!
Come on
Sorry for the rant, but this is how I feel. Your code was only readable in this video, because each module was listed one under the other. Too often with this modular approach is that always, each method is kept in a subfolder in a subfolder, in a sepearte file, making it completely unreadable, and no one knows the scope of each method. Or if there is an error.
This is the case for every single modern piece of software ever made, it all consists of a bunch of classes instantiating methods, hiding behind subfolder after subfolder and file after file. 9 times out of ten, you end up with developers being so unaware of what methods, or solutions exist in the project, they just make their own and create bloat and more classes and methods, as they scale instead of reworking or removing the old ones, because they don't know about them and they are not in front of them two read.
Separate your methods catalogue them into classes, but knowing what your team mates are producing, is the whole point of readability in the first place.
It's like putting a different chapter of a book in 100 books. It just imperically creates massive over bloated projects with redundant and memory hogging code.
Abstraction is the enemy of modular and oo code, in my opinion.
Ps if a software engineer is unable to work on multiple modules or see and understand the larger picture of a design, then they are not a software engineer.
No engineer is an island.
`decodeParams` mutates the (transitive) object's state, `methodParams` field serves no purpose (maybe except for a buffer allocation, but it also makes the method thread-unsafe)
I noticed that, too. I'm surprised that no one else mentioned this or responded to your comment. (I'd say that this makes the code at least a little harder to read.)
@@jjjtccc there is a thread about that, so it isn't no one :)
I think people tend to get lost in the weeds by worrying about whether a piece of software "reads" nicely, when they should be asking why they need to write that piece of code to begin with. The example of code in the video triggers this instinct for me. When I see that code, my first question is to ask why the code even needs to exist in the first place, since the signal to noise ratio for doing actual business logic is quite low, and I would prefer to find an architecture and representation that made writing that code in the first place completely unnecessary. Very often, I find that people try to make code easy to change by introducing abstraction points which just increase indirection, instead of simplifying the code so that changing it is a simple matter of some minor text editing and shifting, which has the benefit of not making it harder to see how the whole system works.
That is, making something easy to change should also be about making something where it is easy to predict the impact of those changes to things like memory consumption, performance, and domain overheads (how much stuff you need to keep in your head to understand the code completely).
It’s funny you should mention that, because watching the video when he was describing what the code was doing so we could see if the code was clear… I was thinking “Why on earth would you choose such a convoluted example”. There may have been a 1-to-1 mapping of what he was saying the code did and what the code read like, but that code was not implementing business logic. It was implementing a hack. Sometimes you have to interoperate with existing code in other languages, but if possible you would avoid writing that code at all.
There is no business logic that requires translating into another programming language. Thats an implementation detail. It’s mess that adds to the cognitive load of someone trying to understand the codebase.
Good Code. Locality of Behaviour matters. We spend more time reading code than producing it. If i have to jump through X abstractions good night reading that.
The most readable project i have worked on, was simple at the same time. Not many layers if any at all, small enough units but not too small. Usage of patterns when really needed to solve a problem.
@@djcrem00 indeed. I went thru few hundreds kb code quicker since it was in same file than try to find it scattered across multiple files. I like when it is partitioned but needs to be done well.
When I write code, I always say I'm writing it for my future self who could have not slept enough, or could be drunk. Everyone gets the idea :)
At this point, I try to write code in a way that will be easy to follow and modify in the future when I inevitably decide to do things a “better” way, and have to come back and figure out how I “used” to do it. 😏
@@AftercastGames we're saying more or less the same. But it seems you drink less than me in the future :)
What about the duty of care to produce as many billable hours as possible?
As productivity approaches zero the demand for consultants approaches infinity. Bad code has been a massive boon for the industry, so it’s no wonder so much of it is promoted as “best practice” by the same industry.
One of my most favorite quotes during code review, "Your code is bad and you should feel bad too."
There was not a dry eye from the laughter which ensued. You know you have a great team when everyone can laugh and learn together.
Thx, the video well delivers the message it was intended to.
I can understand this message, and core way of thinking to code.
Helpful message, from my prospective.
I like this "easy to change" criterion. I often say things like "easy to understand", "no wtfs found", but if those conditions are violated, then the code is almost certainly hard to change. So, "easy to change" seems like the stronger condition. Great video!
Thanks. Obviously I agree, I think that "Easy to Change" should be the defining characteristic of high quality in code.
The 3rd item to add to the list of bad code is "missing, incorrect, or defective comments/documentation."
I appreciate the pragmatic approach to good code, it works, and it's reasonably easy to change.
Complex code that handles complex business logic also increases difficulty of change, however doesn't always mean the code is bad if it is complex, and often high levels of error handling and redundancy increases complexity as well.
I have worked in derivative trading in finance, flight navigation systems, and consulted teams working on Genomic analysis, high-end scientific instruments, and self driving cars. So not "simple systems" I still think that these rules hold, I don't agree that "complex biz logic" forces us to write hard-to-change code, we just have to think more carefully about our designs to ensure that they remain easy to change.
@ContinuousDelivery I'm not saying that just because the business logic is complex that you can't write components of it as easy to read and easy to change, I don't see it so black and white. Complexity of the problem you are solving increases risk factors for harder to change implementations.
No code or design pattern is perfect as this would assume requirements are finite and all known up front, and that requirements remain static during the life of a project.
What's considered easy to change code I believe boils down to first having appropriate structure and architecture as well as the skill level and specific domain knowledge of the developer that inherits another developers imperfect code to perform said change.
I'm not dismissing your experience and credentials, simply many developers often confuse complexity with maintainability and requirements dependency with rigid code.
Wholeheartedly agree. Systems are often complex because often we are writing in a larger environmental context. When you have to understand the complexities of some larger system around what you are writing, your code will always be difficult to read for a newbie to the system. However, somebody who knows the system already will see your code and know exactly what you were trying to do because they have that context.
Your code looks horrible. It's the kind of code that is spread over multiple files, has indirections at every corner and after looking at for over an hour you realize "this does nothing but call a function with two default parameters. these are hundreds of lines of code as wrapper to a singular call." I would never want to work with that.
Over engineered for usecases that never actually going to happen except in your head, and when something actually has to change in this are you realize that your abstractions do not cover the reality of the situation and now you will either doctor it in or start over. The exact opposite of what this code supposedly is avoiding, but never actually in practice. OR the third option, the poor people who have to use your construct will have to add their own workarounds to be able to use your system, in turn having to take your assumptions of what the future might be ( which are always wrong ) and carry them forwards.
@@Nozdrum what a pity so few people commented the video understand what you said (
Just one nitpick: If the code doesn't do what it's supposed to do, it's not bad code, it's wrong code.
It can be the greatest, most readable code by any standard you could imagine and can very well be good code, but if it doesn't do what you need it for, it's still wrong, just not necessarily bad.
Readability's great when you can achieve it, but I have to say your example @8:16 is very trivial. Where complex concepts and optimisations come in, variable names get shortened to acronyms because they require more words to make the distinctions between them exceedingly clear, and operations become abstruse (e.g. bitwise operations). Being descriptive and using method/function encapsulation in performance-critical inner loops become impossible, and code may need to be manually inlined (where compiler does not support auto-inlining), removing the documentative benefit of method names. All of this works against readability, and means the best you can do for readability is keep it at a somewhat higher level in your program. In critical sections, comments are all you have to keep things clear. Code cannot always self-document.
What obfuscates code for me is languages that require a lot of scaffolding code to make a feature work, or combine lots of short hand arcane syntax. It's much harder for non-techies to understand what is going on (e.g. functional tester working out equivalence partitions). It seems in the evolution of programming languages I've seen in the last 30 years, we can't get away from doing that.
Agreed. These days, my C# code looks a lot like my 6502 assembly code, to be honest. 😁
Code readability. Demonstrated on examples this way: I wrote it and look! I can read it! Isn't a proof I write readable code? 😅
I haven't given it a lot of thought but I believe the list of two important things about code is lacking that it is reasonably easy to understand at the micro and macro levels. I see this as connected to but distinct from the other two . "Clean Code" advocates will harp on the easy to change line when they often produce code with extremely poor locality of reference that is a nightmare to understand and debug based on cherry picked examples of situations where a single change in a small method leads to a desired result. I guess I'd wrap up my point in the idea that easy to change depends on the circumstances of the change and things like tiny shallow abstractions make it easier to make certain changes assuming everything in your design is perfect and you have somehow memorised where every bit of code is. To be honest I think this is inline with the video I just think that it is worth mentioning separate from the other two criteria.
If you think Clean Code advocates for "tiny shallow abstractions", you clearly haven't read the book...
It advocates exactly for the opposite...
I personally always keep this in mind: the client/customer _will_ change their mind, often times right up until the last possible minute. I just accept that it's their prerogative to request changes and the code will have to adapt (i.e., be easy to change).
Sometimes good code just doesn't matter, because bad code can be delivered a little bit quicker, because that's what "business" mistakenly thinks is best.
Mistakenly? Not necessarily. Like the F1 car that is supposed to fall apart 1 yard past the finish line, 'good' code satisfies the need and no more. Any more is programmer vanity and detracts from the profit, which the programmer should receive as a bonus.
@@_Mentat In the case that "Business" doesn't understand the concept of "Doing good business, IS good business", that statement doesn't hold true. But I absolutely agree with you that if the target is to build software and deliver a product that essentially self destructs 1 lap after the race, then if everytone is onboard with the the idea, then I suppose there is no problem with that.
Perhaps I should value my art less, and just sell whatever I can to whomever is offering the most money? What are your thoughts on that?
@@jacquesduplessis6175 My though is all software will be over-engineered because programmers want to; I just like everyone else.
I at least know it's wrong.
Just don't do it so much you bankrupt the company.
@@_Mentat We've have had this saying for many years, ````````any idiot can make a system more complicated, but it takes a tru genius to simplify things. I'll leave it at that, and awaiting my next offer. 🤯
That's the definition of tech debt.
How do you measure "how easy it is to change" in an objective way?
By measuring the number of changes to the source code versus the number of “requested” changes to the source code. 😉
@@AftercastGamesso if it's easy to change, we get lots of changes and few requests? Sweet. If I don't share my code, it's gonna get no requests....
@@somestrangescotsman No. If your code base is difficult to change, the result will be many changes that only exist to fix bugs that were unintentionally created by previous changes. Virtually every code base I've ever worked on (25+ years) that had multiple developers eventually grew to the point that any change had a > 50% chance of breaking something. This is why I now advocate for both small projects and simple, straightforward coding practices. (i.e. no hidden functionality, no pointless interfaces, minimize dependencies, etc.)
In my definition, good code has always been code that is readable, and bad code is code that isn't. You see, if the code isn't readable, it's also impossible to change. And if the code isn't readable, the only way to make sure that it works correctly is to either test it on all possible input data or to have a static code analyzer that understands what the code does and can theoretically prove that it's always correct. However, verifying it with a code review by other programmers that ensures the code handles all edge cases correctly again requires the code to be readable, otherwise it cannot be done. Unreadable code rarely works correctly, because whoever wrote the code has surely made mistakes or missed problems because the code is so poorly written.
And I disagree with the video's assertion that whoever wrote the code should be able to read it. You don't need to read your own code as you write it if you write a few lines at a time. This is proven by the fact that people make bugs in that code, and when you force them to re-read their own code, really read it statement by statement, they find their own bugs, but they didn't notice them when they wrote the code because they never went back to re-read what they just wrote. They think they know what they just wrote because they just wrote it.
And as for the definition of readability: If I show my code to someone who knows the language and knows the frameworks I'm using, but is unable to understand what my code does, then it's not readable if and only if I could have written the same code in a different way and it would do exactly the same thing, and this time the person can figure it out with no problem. If there is no rewrite that would help that person, then the problem may be elsewhere, like my code implements a complex algorithm and the person doesn't know it, doesn't know what that algorithm is good for, and therefore can't understand the code without first reading a paper or something. But quite often I come across code and have a hard time understanding what it does, so I rewrite it. Then I show both versions to a colleague, and if the colleague says "I have no idea what this code does, but this code here does the following", and the later code is my code, then the code was unreadable before because I had the same problem, and apparently you could have written the code in a way that would have been easy to understand.
4:38 Do you know what "it's" means?
At 10:10, initParamParsers(...) can be made highly readable without the Java boilerplate in FP or using functional style. Of course, the author preferred the boilerplates
I got my CS degree in 2004. One of our first programming classes was QBasic and Unix. That class washed out about half of the candidates simply because they couldn't manage the planning, much less the logic. Who would've thought that pseudocode and flowcharting would take out so many people? Any more, though, I have to fight to get management to let me document and/or plan anything; "just code it, we're in a hurry!"
I am not a developer,but ~half of the video, I caught myself thinking that you are speaking about functional programming approach. And after a while you mentioned it. Thanks for a video, legend
It's not about functional programming, it's about good/bad code, that got nothing to do with functional programming.
Good code is code that is readable and easily extendable within the scope of anticipated changes regarding the direction in which the code will evolve. Some changes can be predicted, while others may be considered more or less likely. All this leads to certain design decisions and compromises. Therefore, it is not enough to say that readable code is code that is easy to modify without considering whether the direction of the change was taken into account during the design and possibly why it was not.
No timestamps?
Hmmm...
The code example you showed is far too simple to even discuss what "good code" means. I like your content but I wish you had much deeper dives in coding practices with examples that involve several classes and dependencies.
While i agree with the notion you bring here, the problem is that most examples are either context or expertize dependent, meaning majority of the programmer audience even, will not get the full context.
A lot of good points. Please note, I found some of the graphics and code examples very hard to read and see on a phone. Perhaps on laptop or TV it's easier to read.
The point about going quickly by working carefully is a great one. Just as you will get into debt by trying to build a stock portfolio by buying lots of different shares without spending time on making sure they're good quality shares, you will likewise get into technical debt if you try to add lots of features without spending time on making sure it's good quality code. This is engineering due diligence.
I think the main source of bad programs is not unsafe languages, workflow practices, time pressure, laziness, or carelessness. The main problem is constantly changing specifications. We can have designed a perfect system, following all the rules of good programming, then our client suddenly comes and asks for a feature that we never thought of before. As a result, the code that was written perfectly has to be torn apart and the tester code has to be modified. Imagine that happening many times over a long period of time and now the code that was perfectly written has become a mess.
I would say there are two problems with that argument. First, thinking a design is "perfect" even if you're not saying it seriously, is very dangerous. All designs can be improved. Second, if your code gets messy because you had to change it a lot, that means that it wasn't good code to start with because it wasn't easy to change, which would mean that adding features or implementing new requirements was not considered in the design to begin with. I think that example is a perfectly working piece of code that only does one specific thing and it's very difficult to extend, and sounds to me like it is heavily coupled. That is the opposite of good code, imo.
The problem is that you are not optimizing your code for change. I can't comment on your situation specifically, but in my career I have seen a lot of codebases that had the exact problem you describe. Usually these kind of codebases follow a lot of "best practices" and use a lot of design patterns like clean architecture, solid, etc, etc. They had all kind of fancy abstractions that looked good in the moment, but the second the specifications changed, all those abstractions either had to be thrown away and a bunch of code had to be rewritten, or a couple of hacky if statements had to be added to add the new feature, resulting in bugs in the long run. I have come to appreciate code that doesn't create unnecessary abstractions. While this is subjective, at least for me code is easier to change when there are less abstractions I have to worry about.
I don't think best practices and design patterns are bad, but they are not silver bullets. In my experience, a lot of developers use patterns and best practices for the sake of using them, rather than understanding what they are good at and using them where needed.
I am not following at all, good code is meant for chaange, even unforseen or radical.
So "perfect" code as you say would have no issue changing in your scenario.
My problem with the code is that I think it shouldn’t exist! 😮
To me, this looks like accidental complexity brought on by complexity elsewhere in the system (presumably a React frontend) spilling over to your Java code.
The code itself is fine and most of the criticisms I see are just silly. “Don’t call an init() in a constructor”, “that naming has a code smell”, “need better separation”, blah, blah, blah. These are fantastic examples of missing the forest through the trees.
The video definition is perfect. Does it do the job and is it easy to maintain?
All codebases trend toward a giant ball of mud, thereby eventually failing the second point. Complexity management is how we keep code alive. Find complexity that’s not needed (e.g. accidental complexity) and root it out. That will keep the codebase alive longer.
To achieve good code, design document highlighting business rules and decisions is of equal importance. This is very useful for live applications that is in the 10-15 year lifecycle.
Engineers come and go, without this document, any code is suspect, and let's be honest, everyone always has an idea to make it better, often leading to waste of resources trying to test and understand.
We are not talking about simple expressions here, but business objects and their behaviors.
Ofcourse, this document is worthless if it can't be easily found. Project management + SCM + KB glue is also important to reach this.
I like this suggestion, and it implies we consider the lifetime of a software product. Too often people don't consider the full implications of designing code that needs to last more than a generation of programmers.
In my team, I've noticed we've begun reversing design decisions made by predecessors 2-3 years ago, because we didn't understand why they did what they did.
I can't people who write "slick", "clever", "impenetrable" code basically as an exercise in proving what geniuses they are. I always try to write straightforward, properly compartmentalised, liberally commented code...I write it as if it was to be read and maintained by someone other than myself.
Code should tell a story, and a codebase should be like a book written by one author. There are many ways to write a good book, just like there are many ways to write a good codebase.
Everyone talking about naming conventions, classes, methods, testable code. I deal with cobol code written and "maintained" for up to 40 years. Please, empathize with me.
Do you make any sacrifices for optimization with regards to readability ?
The question seems a bit odd. You can *optimize* for performance, correctness, testability, readability, scalability, fun.
These don't always have to contradict each other, e.g. testable code is often readable, correct and more fun to work with, and not necessarily slow or unscalabe. However optimizing purely for performance, you almost certainly will sacrifice readability.
What do you think about doing an experiment in large scale code review? Perhaps create a challenge and take code submissions. Then have the public vote on which they prefer between side by side pairs, like MKBHD's blind smartphone camera test?
Great video, it reminds me the "easy to change" from pragmatic programmer book.
Awesome video, and I will watch this several times! Also, 1,000th like 🎉
The only thing I think you missed, is visibility. Having juniors support production is key to their growth as a good developer. Looking at your code in production and not knowing if its working or why it isn't working is crucial too. Logging is more than a 'in passing' feature of good code.
Helps everyone grow, not just juniors.
Readability and computational/memory efficiency are at odds in some specific scenarios, e.g. micropython executing on a microcontroller. A lot of programming is balancing trade-offs and making the right compromises.
I really enjoyed the content of this video and agree with most of the content within. However, I think it is a content that preaches to choir, as young or inexperienced audience can’t value much. I’d love to discuss some of these ideas with you mate, I think there is a lot gems stored in your strong and well structured mind which we could certainly benefit, especially with a lil tweak in your code examples. 💪
Nonetheless, keep going w the good job mate 🫡
The lack of checks on your code triggers me slightly. Specially because I can't see exceptions being thrown. If the "method" parameter of your "ReactServiceMethod" constructor is null for some reason, it will crash. Same for "service" and "getParameterCount".
That sounds like defensive coding? I’d rather not spend time maintaining defensive code. Good unit testing (yes TDD) will ensure that you’re never passing a null. Using the result pattern or null object pattern will mitigate having to check for nulls everywhere which makes life easier. I’d also avoid throwing exceptions too, they’re expensive and are just goto statements with knobs on.
@@leerothman2715 I would not check for nulls everywhere either. But, as far as we know, the "ReactServiceMethod" is the top level and could be part of something that can be used by others. You can never control how others will brake things.
I dislike exceptions too. Only use them when I can't avoid them.
@@leerothman2715 You're both onto something. The issue here is Java's type system's inability to express nullability. While we don't want to check for states we already know our programs should never be in, we also do not want such guarantees to be upheld by something external to the code itself. It should be evident from the code itself whether or not values being null is something that should be handled. There are programming patterns that reduce the need for this, as you mentioned, but IMO most of the time rigorous use of _@NotNull_ and _@Nullable_ (which inserts assertions for annotated values) in combination with a good static analyzer (e.g., Intellij) works wonders.
As soon as you start adding throws into your code, the design is bad and you should really start over.
Know your framework/tool/language you are using and how your code is going to be used.
In this small example, getting a null is an error and the program cannot do it's (intended) work. So why have another check that will throw the same but different kind of error?
And if you read the (Java) API, you'll know the "getParameterCount" does not err out and you will get a return value. So there is no need for another unnecesary check.
Adding checks is adding code. And that must be done for a purpose. That kind of defensive coding only tells me you don't know your API and are too afraid to do something.
i think the language and framework you are using play a huge role too. these can force you to hurt the readability of your code and they determine which changes are practical
I am a lone programmer, I do not use github, and the code I have written for the last 30 years has only been seen by me
I would like to add to the list of stinkers.... 'code that don't belong to the level of abstraction of class or method, I mean ideally, reading a method body must be so easy as reading a fable that circumscribes its idiom to the semantic level of its domain'
I think this is bad criteria for what constitutes good code. Under the right conditions any code base be difficult to change.
While these are important metrics for good code, there are other metrics as well.
For example you can implement bogo sort and it will (eventually) do what it's supposed to do, but it is obviously a bad idea to do this.
You could also implement quicksort, and it would be faster, but there's probably already a fast sort in whatever standard library you're using.
My top metric is not e.g. readability, it's simplicity. The worst code I see is written by smart people trying to be clever.
"The worst code I see is written by smart people trying to be clever."
I think Uncle Bob said that to the letter ^^ (I agree btw)
Good code should be readable by almost anyone?
My neighbour's mom needs to be able to read my distributed job queue code for reliably sending out emails. It's gonna save me so much money in case I need to hire a gardener to do software engineering. Obviously.
Third option should be does what it's supposed to .. but also does a bunch of stuff is wasn't. Fourth option is does what it's supposed to but is almost impossibly inefficient and will eventually bring down the entire system with certainty
This is good advice 👍 one thing though is that some languages are harder to work with eg I work with c# but had to switch to python for some ML inference API. The switch from a strongly-typed compiled language to a duck-typed interpreted language brought so many complexities, and all errors became run time errors.
The end result is the same. The strongly typed language has a chaperone that points when you make a mistake. But the mistake is still you that make. It has been years sicne last time I had an error in python of an entity of wrong type reaching a location. But that is mostly the developer developing the paranoia needed for that tool (i.e the damn parameters indicate very clearly what should be passed). Strong typing is very useful helper tool, but all mistakes are always made by a developer.
I like to play for the sake of fun a mental game, imagine your whole system coded in java interfaces.... and then implement them!
I like it. It’s simple and efficient. Especially if you save those interfaces so that you can hand them to new developers to look through before actually reading the code. Sort of like looking at the database schema before looking at the actual code. Smart. 😉
What if you don't do OOP and your code doesn't look like this?
functional decomposition can be applied to most programming styles
I was just about to say, I favour composition over inheritance. (Coming from an early start in OOP and inheritance etc.)
Good code is code now matter what the approach.
Forget about OOP. Go into some code that is nested 12 levels deep. Take the inner block that takes up 11 levels of nesting, rip it out, put it into a function and give it a name, parameters and return type that document what its purpose and expected result are. Boom, now you have 1 level of nesting in your main code, that even tells you what it's doing in more or less natural language. If you decide you want to make the function (which still has 11 levels of nesting) more readable, take its inner block that takes up 10 levels of nesting... (you'll know how to go from here)
09:10 dear god, if I would make a list of the most annoying, unreadable antipatterns, then dynamically calling methods on some arbitrary object would be easily in the top 3. Great job on completely bypassing your compiler and IDE when it comes to figuring out actual function usage
13:06 "every method is abstracted from every other". Yeah right, that's a nice "method" field you got there in your "invoke()" method. It totally doesn't refer to something that isn't part of the method signature
My brain ran into a problem need to restart
function that has one line + try-catch block is a good code...welp...and still...we should test agains contracts, not each private method - mindset of a person who is paid by number of lines of code written
c2 wiki is really a hidden gem.
I wish Microsoft created unmaintainable code. Then they wouldn’t keep shoving crummy updates down our throats.
A none technical person has no chance at understanding your code. It's outright delusional to believe any none coder understands anything about it. Even if you explain them stuff, you basically need to teach them coding. I guess it's hard for a senior developer to put himself in the mindset of a none coder.
Not necessarily. If the business guy asks you to write a program that does 8 things, in order, every time they run it, you should probably have a function somewhere that is easy to find that has 8 lines of code in it.
That is something you can show the non-tech guy and he could understand pretty easily.
I want to add another bullet point on your definition of "bad code"... Bad code is also code that is hard to understand.
I'm guessing that you would argue that hard to understand code is a proper subset to hard to change code, but I'm not so sure. I think a useful distinction can be made between code that is easy to change once it's understood, but is hard to understand and code that is easy to understand, but hard to change despite that understanding.
For the first type, those who understand it, and likely have been working with it. for a long time, thinks its great but onboarding people is extremely difficult. That's where the whole meme "my code good, your code bad" comes from IMO.
So your way of proving readability of code to the lay person at around 8.15, was to start talking in jargon for the next 40 seconds that no lay person would have had the first clue about.
8:15
So the code of an MRI machine on its multiple levels including image processing should be readable and understandable by a radiologist, because he knows the problem domain being solved with it?
Yep, exactly.
Yes. Ideally, at a high level, if your program does 8 things, in order, every time you run it, there should be a function somewhere that is relatively easy to find that has 8 lines of code in it.
If your library does 12 things, somewhere there should be a file with 12 functions in it.
Pretty simple, really. 🙂
You may think the previous commenters are trolling but that's really it. If you cannot explain the code in terms of what the client asked for or vice versa, then what are you even doing?
Here an absurd counter example:
"So yeah we took 18 months to build the thing you asked for"
"Ok what can it do?"
"Well uhhhh it takes some ints and floats uuhhhh and uhhhh it does like parseParamList uhhhh"
"What"
"Well you know computer science is complicated. We built it using OOP so there's like, private state and uhhh"
"Bro I need a device that can take a series of MRI data, analyze the blood flow and produce images based on that. Do you have a function for that?"
"Uhhhhh well we do use MVC, does that answer the question?"
@@holonaut congrats for missing the point entirely.
It was never about a developer capable of explaining.
But thank you for trying
@@marcr8181 I suspect you're the guy who writes exclusively void functions that operate on global state and thinks his code is immaculate
I don't agree that 'non-technical people should be able to read your code' is a scale that the code should be measured. At no point in the development cycle should that be the criteria of judging the readability of code. Lead Technical Engineers & Architects should be the judge of it and that is where the importance of code readability ends. Because it is expected of them to have a clear, latest and in-depth understanding of the application so that they can be trusted to represent when interacting with managers and product team.
It is possible that you are missing my point, it is not that non-technical people need to care, or even have any direct stake in the readability of your code, rather it is that you could, with relative ease, be able to explain it to them so that they can read it. This is a qualitative measure of it's readability, if you need deep experience and extensive training in software development to read the code, that isn't good enough to qualify as "readable" so, sure you want tech leaders and architects to be able to read it, but you get that FOR SURE, when almost anyone can read it.
@@ContinuousDelivery Why should we dumb down the code? Readability is for people who have the capacity to understand. Non-technical people have no interest, knowledge, experience in reading or understanding code. I get your points, but I don't think code readability standards should target any non-technical person.
Ah. I remember back in college. We had a contest using STSC APL language to write "one liner" programs which did something at least somewhat useful. Impossible to understand except by decomposing bit by bit. And have a good understanding of manipulating multiple dimensional matricies. Comments? The code speaks for itself! 😂😂😂😂
If it's difficult to change it also has poor security and so doesn't do what it's supposed to :)
1:39 it works and is easy to change 100%!!!!
So lets build a self modifying mashine code to make it impossible to understand what is going on.😂😂😂
I want that t-shirt.
Okay, I fished the first third part, I will comeback after taking a nap, his voice is too much to me, I am instantly feeling sleepy can’t even work with 2x speed. 😢
totally agree with claim that readability is subjective. espcially after watching "Maintainability And Readability | Prime Reacts" (Primeagen's reaction video on tsoding). as a programmer I find myself thinking about some problem I'm goig to solve in a cetrain perspective. it will be reflected in code (other programmer can see the problem from a different angle and would write it differently). if I come back to code I wrote after 2-5 years, and I can understand it sort of easy and modify it, I would say code is "good to maintain" subjectively
Just put it all in one function and shout down reviewers during meetings, that's what the senior devs at my workplace do, and we get paid 30k above market. RAILS 4 LIFE!
Just goes to show how subjective this is because I personally didn't think your code was great. Calling an init method from a constructor? That's the sort of thing I'd expect to see from an old C program. You also tend to simplify things. Let's be honest, easy to change code is often (but not always) less performant. So not always achievable eithout compromising something else. I totally agree that easily changeable code is the goal, but that also assumes you know future requirements upfront. Given the time, you can create code that matches your requirements of good code, but in the real world, we're tasked with getting specific functionality in place quickly.
What's wrong with calling a private method in the constructor? I see no harm in it if it helps manage the complexity.
As someone who mostly writes high-performance code, I see what you mean with good code (as defined here) often being at odds with performance.
However, in many cases it's possible to get 80-90% of the performamce while sticking to easy-to-read-and-maintain code. I believe that should be the goal, only to be compromised in those cases where every little bit of performance really IS needed.
A great example is the Eigen library. *Almost* best-in-class performance, and the internals are really ugly at times, but as a user it allows me to write GREAT code while getting lots of performance for free.
@@ToadalChaos That's my point, it's subjective. From my point of view, 'Good Code' is code that gets the job done without bugs. Having the requirement of "it should be easy to change" might be completely out of scope and add more complexity than is actually required, meaning it could add more things that could go wrong. Not to mention you might never need to change that code again. If it turns out the code is needing to be changed as more requirements come in, then sure, refactor it. But doing that at the outset seems wasted effort, as well as adding more unnecessary failure points. This also depends on the type of project you're working on and the time you have for the task. As for your second point, add an abstraction layer on top of the 'Ugly code' might hide it, but its still there, and someone still has to maintain it. Not saying there is anything wrong with that, but not sure what you were getting at.
C does not have constructors...
If you're talking about writing code without really knowing what you need and what the requirements are until you're finished in one breath....
And in the next breath saying code that's easy to change is not performant, then I'd argue you're prematurely optimizing an area without knowing it needs to be optimized. If you do know it needs to then you know more than you're letting on.
Realistically modern compilers are pretty clever. Just look at Jason Turner's C++ examples. A lot can be inlined or completely optimized out. What you write isn't translated exactly to assembly. Often times if you know your runtime or compiler you can use more clear and verbose code that is easily understood and later optimized out.
Bad Code hall of shame: "I once saw a C function that was 10,000 lines long." Pfft! Lightweights. I worked with one (you are still using it in your phone if it has 3G) that had a McCabe metric of 18,000 and had been edited several times a week, every week, for 10 years. My suggestion that we CONSIDER trying some simple refactoring was... "not well received".
A screen capture of scrolling its Control Flow Graph
ruclips.net/video/Cn_1WgrGwdA/видео.html
You on Mastodon?