5 ms·
The author presents a series of strawman arguments to debunk SOLID, and then suggests that instead we should make software as complex as possible but no more co
by krona 6y ago
The author presents a series of strawman arguments to debunk SOLID, and then suggests that instead we should make software as complex as possible but no more complex than the coders own comprehension. In my experience this commonly how incomprehensible codebases evolve; the code is comprehensible right up to the point that it isn't, and by that point it's too late to change anything.
- rootsofallevil 6y agothat's an interesting take, what would you consider strawman arguments in the article?
- sorokod 6y agoNot the person you asked but, I would say that the expectation that SOLID provides or should provide, unambiguous guidance
- refactor_master 6y agoFor example, the open-close principle. The author blames this advice on tooling of the 90s and proposes instead “Change the code to make it do something else”. This has nothing to do with tooling, but the fact that pulling the rug under an established code base could have very unintended effects, compared to simply adding and extending functionality without touching what is already there. By doing as the author suggests you’ll end up with either 500 broken tests or 5000 compiler errors in the best case, or in the worst case an effectively instantly legacied code base where you can’t trust anything to do what it says. I once had to change an entire codebase’s usage of ints to uuids, which took roughly 2 whole days of fixing types and tests, even though logically it was almost equivalent. Imagine changing anything and everything to “make it do something else”.
- garethrowlands 6y agoIt has a fair bit to do with tooling. For example, C++ suffers from the fragile base class problem and some changes can cause long compile times. Nowadays, we have tests and deployment pipelines that are explicitly designed to let us make and deploy changes safely. Honestly, if you cannot change your code, you have a problem.
- chillacy 6y agoThe OCP is imo poorly named, but it has far bigger implications than the post acknowledges. For one, it implies the concept of abstraction layers. In particular, base libraries should provide abstractions at the level of the library. In this way it's able to achieve being "closed for modification but open for extension". https://drive.google.com/file/d/0BwhCYaYDn8EgN2M5MTkwM2EtNWFkZC00ZTI3LWFjZTUtNTFhZGZiYmUzODc1/view https://drive.google.com/file/d/0BwhCYaYDn8EgN2M5MTkwM2EtNWF... Flipping it around, if base libraries were to be always open for modification instead of extension, then instead of writing your feature logic in your own codebase, you might be tempted to submit a pull request to React.js to add your feature logic. That sounds ridiculous but that's the equivalent of what I see a lot of new engineers do when they try to fit the feature logic they need to implement anywhere it makes sense, often in some shared library in the same codebase.
- geofft 6y agoWhat's the alternative here? If you had to change a codebase's usage of ints to uuids, should the original author have used dependency inversion and required an IdentifierFactory that was ints at the time so you could just swap out the implementation? And if they did - why wouldn't they have just used UUIDs in the first place? You're betting on the fact that the original author anticipated a particular avenue of further change, but also made the wrong decision for their initial implementation, which seems like the wrong bet. If they made the wrong decision, they almost certainly didn't anticipate the need for another one, either. And how long would it have taken for the original author to use an IdentifierFactory instead of ints and write meaningful tests for it? Less than two days?
- jdlshore 6y agoChanging ints to UUIDs is a classic example of the Primitive Obsession smell, and the solution is to wrap primitives in a type representing their semantic meaning, such as “ID,” or “PrimaryKey,” not to use a factory. That way, when you need to change the underlying type from int to UUID, you only need to do it in one place.
- shock 6y agoIndeed. Unfortunately, in some languages – like Java or C#, it is harder to do without incurring a significant cost (boxing/unboxing) than in languages that allow type aliases/typedefs.
- jdlshore 6y agoIn theory, yes, but in practice performance is dominated by network and (less often) algorithms. The cost of boxing/unboxing doesn’t even register except in rare cases, which can be specifically coded for.
- razorfen 6y agoIn the uuid case, the person had no choice. Remember, these are principles, not laws, and at some point your system is making concrete choices. Choosing UUIDs isn’t necessarily a OO design problem. He was just highlighting how expensive it can be if you require changes to your fundamental classes to extend or change behavior. In the identifier type case, it’s rare that folks abstract this stuff away. Though: I do know a LOT of systems that use synthetic identifiers for this exact purpose, as larger enterprises tend to deal with many more different identifier types from different integrations, from a DB type that can’t hold new identifiers, because IDs need to be sharper/distributed etc. So yeah, it’s a principal, and one should choose if it’s worth the upfront cost for its benefits. OCP though more commonly refers to: 1. Building small, universally useful abstractions first 2. Extending behavior of that abstraction or system by writing new code rather than changing published code directly. This is trivial when you have a few patterns under your belt. Template factories, builders, strategies, commands. I mean, while it’s not the best idea in most cases, even just inheriting a parent class and giving a new concrete new behavior is still better than changing something fundamental to the system. Like has been said 999 times in this thread, software isn’t black and white. You have to make choices about where you go concrete and where you abstract, and gauge that against risk factors. A somewhat complex class you expect to go away in a couple months? Make it a god class that anyone who wants to can scan through. A fundamental class that will be used by hundreds of developers and underpin most operations in a production system where 5 minutes of downtime costs tens of thousands of dollars? It’s worth the upfront cost to build with these standards.
- coldtea 6y ago>This has nothing to do with tooling, but the fact that pulling the rug under an established code base could have very unintended effects, compared to simply adding and extending functionality without touching what is already there. That's still a matter of tooling. With type-checking, static analysics, and test suites, changing code doesn't have "very unintended effects". Back in the day, without those, things were much more opaque.
- tanseydavid 6y agoHis entire screed on single responsibility principle spins around on semantics.
- jdlshore 6y ago> he suggests we should make code as complex as possible I read no such thing. (Speaking of straw men...) instead, I read him saying that the alternative to SOLID was “Write simple code.” I think there’s room to criticize this essay, but that’s a bizarre one to lead with.
- jancsika 6y ago> instead, I read him saying that the alternative to SOLID was “Write simple code.” The part you missed is in the same sentence as the three words you quoted. "Instead I suggested to write simple code using the heuristic that it 'Fits In My Head'." The obvious criticism here is that now any developer who wants to defend their code will simply claim a given spaghetti incident easily fit in their head. The author even seems to acknowledge this line of attack in the next paragraph: > You might ask “Whose head?” For the purpose of the heuristic I assume the owner of the head can read and write idiomatic code in whichever languages are in play, and that they are familiar with the problem domain. If they need more esoteric knowledge than that, for instance knowing which of the many undocumented internal systems we need to integrate with to get any work done, then that should be made explicit in the code so that it will fit in their head. But that just protects against spaghetti made from esoteric know of internals. For example, in C a "big head" would still be able to justify using global variables (after all, that's idiomatic C), rolling their own buggy threading approach, and deeply nested side-effect based programming. I'd much prefer pointing big heads to the rule of "do one thing" than to ever read a response of the category, "Well, it fits in my head."
- lupire 6y agoNot defending the article at all, but "whose head" should be at least a code reviewer's head, ideally two.
- alfonsodev 6y agoI believe that there are people that can handle more abstractions in their working memory, maybe up to 6 or 7 layers of abstraction, and there are people that can't go further than 3, before feeling lost. The "bigger heads" types will use SOLID or any other argument to justify a higher number of abstractions. While the other type will do the same to justify a lesser. At the end of the day, there is no right solution, but what is so called "cultures" that group people with similar taste and cognitive fingerprint, and provide best practices.
- geofft 6y agoIn my opinion, that's in fact exactly how you should be developing your software. Code is cheap to write, and mostly debt. A software product - a working system that meets some particular need - is not. The distinction is that building a software product is much more than banging out code; it's the experience of figuring out what exactly that need is (gathering requirements, getting a system out for users to test, getting real-world feedback). Sometimes you capture that in documentation, test cases, ADRs, comments, commit messages, etc. Sometimes it's in your head, which is okay as long as you're still there. (Of course, if it's in your head and you leave, then the next set of programmers will be scared to change the system until they redo the work of figuring out what the software is, despite the code being in front of them.) If you have that understanding about what the software does, ideally in the form of automated test cases, you can rewrite the code. So you may as well bang out the code in a way that gets you a working system and remains comprehensible. Once you're making enough changes to it that you're worried about it getting incomprehensible, proceed to rewrite it. Probably the world has changed in many ways sine you wrote it - maybe you can run the system for a lot faster and cheaper with containers in the cloud talking to a SaaS database than with your expensive IBM mainframe talking to DB2. Or perhaps the requirements are changing significantly, and starting with the old code doesn't give you much of an advantage. Trying to keep a codebase comprehensible over the long term makes it, if you'll excuse the pun, solidify - it becomes increasingly hard to make changes that weren't anticipated by the original design, and it also requires more and more effort to just do anything. You might be able to swap out the relational database for another one, but you probably can't switch to a design where you're using a key-value store with totally different performance characteristics. And even if you do just want to change the database, you have to figure out how ten classes get dependency-injected into each other before you can start coding. All the time you spent making the code "future-proof" so it remains forever comprehensible could have been spent simply not doing that, delivering business value, and writing new code as the need arises.
- intricatedetail 6y agoWriting complex and convoluted code is a strategy used by some developers who bill by day or an hour, so then they have to spend extra time to "understand" or "refactor". I've seen this so many times. One even got visibly angry when I politely pointed out his solution could be vastly simplified or even skipped as the client was considering different options.
- legulere 6y agoThat's the point where you need refactoring. The basic problem is that you cannot look into the future and clearly see what exactly you are going to need (if you are lucky enough to implement something along a specification, you should us that knowledge though!). As long as you manage to keep artificial complicatedness out of your code, you will always have the complexity of the problem mirrored in your code. A common problem of ideas about object oriented programming like SOLID or Clean Code is that they have a focus on classes. If you keep your classes very simple, you will instead end up with a complex architecture, where you might have zero responsibility layers or functions that just pass on the call further to a layer down.
- coldtea 6y ago>The author presents a series of strawman arguments to debunk SOLID Care to elaborate as to why those are strawmans? Without that, this is a no-argument, which is probably worse than a strawman. >and then suggests that instead we should make software as complex as possible but no more complex than the coders own comprehension The combo (go and make "software as complex as possible but no more complex than the coders own comprehension") is suggested nowhere in the post. The second part of the combo (make software "no more complex than the coders own comprehension") is indeed said, and is very sensible advice. It is, however, combined with the inverse advice of "make it as complex as possible" which you claim the author combined it with: with the advice to keep it simple.
- eternalban 6y ago> The Single Responsibility Principle says that code should only do one thing. Another framing is that it should have “one reason to change”. I called this the “Pointlessly Vague Principle”. What is one thing anyway? Is ETL – Extract-Transform-Load – one thing (a DataProcessor) or three things? Any non-trivial code can have any number of reasons to change, which may or may not include the one you had in mind, so again this doesn’t make much sense to me. The strawman here is the fallacy that SOLID (SRtC) is clearly not saying that a software, composed of "code", should only do one thing. By that reasoning, SOLID rules out any software that provide multiple capabilities. Your editor, for example, has multiple capabilities. It can save. It can highlight. It can cut. It can paste. So, a reasonable reading of SOLID naturally is not ruling out composing complex software using "simple single purpose code". However, OP is assuming a ridiculous reading (the strawman) that basically can only be valid for software that has only one irreducible capability. One can read SOLID SRtC in terms of capability as "compose complex software using single purpose code". One can read SOLID SRtC in terms of change as "changes to code should consist of one, or a sequential set of, single purpose changes".
- coldtea 6y ago>The strawman here is the fallacy that SOLID (SRtC) is clearly not saying that a software, composed of "code", should only do one thing. By that reasoning, SOLID rules out any software that provide multiple capabilities. Your editor, for example, has multiple capabilities. It can save. It can highlight. It can cut. It can paste. That's not what TFA says here. It doesn't claim that SOLID says that software as in 'a full program' should do one thing. It just says that SOLID says that a piece of code (as in a function or a class) should do one thing, which SOLID does say, and which the author of TFA disagrees with. Perhaps the use of the word "code" (or "non-trivial code") is confusing, but the author doesn't imply the whole program with that, but the same as SOLID does (a unit of code): "Code should fit in your head at any level of granularity, whether it is at method/function level, class/module level, components made up of classes, or entire distributed applications".