11 ms·
Over the years I've seen many more unmaintainable atrocities caused by diligent adherence to these principles than I ever have because of ignorance of them. Al
by ewjordan 10y ago
Over the years I've seen many more unmaintainable atrocities caused by diligent adherence to these principles than I ever have because of ignorance of them.
All the horrid Java libraries that we hated working with 10 years ago were created because of slavish devotion to the single responsibility principle, short methods, DRY, and testability. The absolute worst codebases I've ever seen sprung from an overaggressive concern for extensibility.
I do agree with the guiding principle, that when you're writing code, it should be written for other people to read and work with. But sometimes that actually does mean that a class should represent an object that does more than one thing, and you shouldn't fight it. Sometimes a gritty algorithm really does make the most sense if it's laid out as a 40 line function in one place rather than spread across 5 different classes so each part is independently swappable and testable. Sometimes you really don't need extensibility, you know that up-front, and building it in just pisses off everyone that ever has to touch your code because now they have to go through five or six "Find implementing classes" dances to find the actual code that runs when your interface methods (of which, of course, there's only a single implementation) are called. Don't even get me started on abuses of dependency injection when it's not necessary...
Part of me is glad that these concepts are so commonly discussed, because they really are good things to consider, and can make code much tidier and easier to work with in the best case. But it takes a lot of experience to know when and where you should and shouldn't follow these "rules", and it tends to be much easier to unwind a novice's spaghetti code and tidy it up than it is to pick apart a poorly conceived tangle of abstraction and put it back together in a way that makes sense.
- k2xl 10y agoCouldn't agree more. One of the reasons I enjoyed learning and writing Go was that it sort of encouraged keeping things "tight" Often times programmers look at lines of code or number of classes written as a source of pride, when we really should be looking at these things as expenditures.
- blakecaldwell 10y agoAgree. I like Go for the same reason. With Java, there's a weird pressure to spend a week designing class hierarchies and to set up all the factories and abstract factories. In Go - uh, you can do it, but you're going against the grain.
- fh973 10y agoAren't class hierarchies just 90s OOP code and not specific to Java.
- vonmoltke 10y agoI have been writing Java for 4.5 years and have never felt such pressure. In fact, I have never written a factory and use inheritance sparingly. The problem of Java is the culture of the enterprise bros who worship at the altar of the Holy OOP, not the language itself.
- collyw 10y agoI agree, I take more price these days in removing lines of code while keeping the same functionality. Less to maintain. Less places for bugs to hide. Again its a principle and there are exceptions, but provided you realize that its a good one to follow.
- shados 10y agoyeah, DRY and "less code is better" are to concepts that should be very loose guidelines and nothing more. "The wrong abstraction is worse than no abstraction" comes to mind here. Very simple, repeated code that is unlikely to change (or unlikely to change everywhere at once because things are unrelated and just happen to do the same thing... -for now-) is much easier to read and maintain than short, clever, DRYed up code.
- leetrout 10y agoI agree with this 100%. Nothing wrecks my mental model of code like poor abstractions and even bad naming choices, too. I'm big on linting code though... For me the more everyone's code looks the same the easier it is to read without doing mental gymnastics.
- deleted 10y ago[deleted]
- makapuf 10y agoI agree about the dangers of over abstraction but you're implying it derives from the principles, however the lines The KISS principle states that most systems work best if they are kept simple rather than made complex Therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided. YAGNI is a practice encouraging to purely focus on the simplest things that make your software work. made me think that it clearly warns against overengineering. (edit:formatting)
- marcosdumay 10y agoI've seen complex atrocities created by blindly following KISS and YAGNI. It just takes a non-obvious path, and a few months of code and requirements evolution.
- Mtinie 10y agoI'll counter that comment with the following: Blindly following anything -- especially nuanced, process-driven ideological frameworks -- is a recipe for a disaster. KISS and YAGNI are examples of processes that require the person who is using them to actually think through the implications within the context of the problem being solved. KISS and YAGNI did not cause the atrocities that you've seen. Those complex beasts of code existed because the team fundamentally didn't understand what they were doing. It's like "agile" software development. Just declaring that you're following an agile methodology and scheduling stand-ups does not mean that you're going to be successful. It takes someone (and usually, many someones) who understand how to iteratively design and develop to be successful. > It just takes a non-obvious path, and a few months of code and requirements evolution. Can you expand on this comment? I'm interested in understanding what you mean by "non-obvious path".
- marcosdumay 10y ago> Blindly following anything -- especially nuanced, process-driven ideological frameworks -- is a recipe for a disaster. Yep. That's the point, that's why I pointed it was blind. Many people blindly follow and impose that kind of ideology. Most people that even mention YAGNI outside of a classroom are blind followers (for DRY things are more diverse). By non-obvious path I mean things (requirement, underlining systems, user base size) changing in a way that was not unquestionably and blatantly obvious.
- blakecaldwell 10y agoNailed it.
- highCs 10y agoSo I have a theory that may explains things here. Here it is. There is 3 levels of programmers. Level 1 is the beginner. A level 1 programmer is barely able to make a complete app or lib. His work simply rarely works. The code has no sense, is full of bugs, it's a complete mess, and the user has a very poor experience (if an experience at all). Then comes the level 2 programmer. The novice. The novice learnt about OOP, design patterns, DRY, single responsability principle, importance of testing, etc. The only problem with the level 2 programmer is that he overdoes everything. He is victim of overengineering. He is able to make a complete app with a decent UX and the quality of the code is dramatically better, but his productivity is low. Really, really low. Everything takes month. Every single change is a project. 10 actual lines of codes need to be changed? That's 10 classes, 250 lines of unit tests, it requires 3 classes to be refactored, and on and on. Finally, there is the level 3 programmer. The level 3 programmer is a programmer that does level 2 programming in a smarter way. His first principle is less code. That doesn't mean the code must be complete shit, but the level 3 engineer understand the real enemy and that all problems grow with it exponentially: code. The level 3 programmer knows, understand and apply the principles of the level 2 programmer, but he just does the right amount of them. Not too much. If he has to, the level 3 programmer gonna err on the side of the level 1 code and will avoid as much as he can level 2 code. That's because level 1 code cost less to fix than level 2 code. Now here comes my point: a level 2 programmer read and write articles that is about how to not be a level 1 programmer.
- the_other 10y agoPlease consider replacing all the "he"s with a word less gender-defining ("they", "s/he", "ve" etc). The entire gender spectrum can code if they want to and they should feel included rather than excluded.
- triplesec 10y agoThis is a really good point. We really do want to encourage women in this field, and it's a shame that dudes are downvoting this (and I'm male).
- 10y ago
- wyldfire 10y ago> Sometimes a gritty algorithm really does make the most sense if it's laid out as a 40 line function in one place rather than spread across 5 different classes so each part is independently swappable and testable. I agree with you, but it feels a little like a false dichotomy. You can pretty easily decompose a long function into multiple smaller ones, within the same file. ~40 lines is close to the breaking point IMO. If it grows much beyond that, you can probably see groups of 10+ lines of code that have some sane subset of inputs/outputs. Toss those lines in another function, give it a name, make sure it's not exported outside of this file. If the 40+ line function gets reduced to a 10-20 line one with a few more stack frames, it is probably worth it.
- peterloveslucy 10y ago40 lines is excessive, I prefer simple methods that take care of the if's and link them together serially. AFAICS this is the way the brain works. /** * get the leaf node as a string * @param obj the json object to operate on * @param path the dot path to use * @return the leaf node if present <b>and textual</b>, otherwise null */ public static String leafString(ObjectNode obj, String path) { JsonNode leaf = leaf(obj, path); return leaf != null && leaf.isTextual() ? leaf.asText() : null; }
- halostatue 10y agoThat mostly depends on where the algorithm is used. If it’s in a tight loop, those extra stack frames are deadly and completely not worth it.
- jblow 10y agoWhat size of codebase are you talking about? If it's 100,000 lines of code, and you break stuff every 40 lines, you have now introduced 2500 procedures many of which don't really need to exist. But because they do exist, anyone who comes along now has to understand this complex but invisible webbing that ties the procedures together -- who calls who, when and under what conditions does this procedure make sense, etc. It introduces a HUGE amount of extra complexity into the job of understanding the program. (Also you'll find the program takes much longer to compile, link, etc, harming workflow). I regularly have procedures that are many hundreds of lines, sometimes thousands of lines (The Witness has a procedure in it that is about 8000 lines). And I get really a lot done, relatively speaking. So I would encourage folks out there to question this 40-line idea. See also what John Carmack has to say about this: http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html http://number-none.com/blow/blog/programming/2014/09/26/carm...
- ap22213 10y agoWhich libraries are you talking about? It would be fun to check them out. Anyway, it's all about UX. Certain types of programmers will want a certain types of interfaces. A well designed library will have been crafted to meet those programmers specific use cases.
- crdoconnor 10y ago>All the horrid Java libraries that we hated working with 10 years ago were created because of slavish devotion to the single responsibility principle, short methods, DRY, and testability. The absolute worst codebases I've ever seen sprung from an overaggressive concern for extensibility. Slavish adherence to these principles (with the exception of DRY) violates one of my core principles: write the least code possible to solve a problem. The single responsibility principle in particular isn't what I'd consider a fundamental principle of good code since it often can lead to writing an awful lot of unnecessary boilerplate. Short methods are similar (if a method is called only once I often consider that a code smell). I think the culture of Java is also partly responsible for this: grand architectural designs are valued over terseness and simplicity. A system with stack traces as high as your arm are seen as something to be proud of.
- riboflava 10y agoI go back and forth on the singly-called function as a code smell. It really depends. What annoys me are the private method refactors that then can't be unit tested unless the 'private' keyword is removed. For an instance of how it depends, see the classic Forth example for implementing a washing machine driver: ... : RINSE FAUCETS OPEN TILL-FULL FAUCETS CLOSE ; ... : WASHER WASH SPIN RINSE SPIN ; Then a single call to WASHER is all that happens when the user presses the start button. Function calls should make things clearer, not less clear because of some other principle (like DRY or single responsibility or testability etc.). That's ultimately where I think the JavaLand culture has gone wrong, all these abstractions (many of them caused by being forced to live in a Kingdom of Nouns) that are sometimes quite useful in the large are always a pain in the small, but many times the small is all that is needed. Big projects are slowly learning they don't need to be so big, they can instead be a set of independent smaller projects, but it'll take more time.
- chimprich 10y ago>if a method is called only once I often consider that a code smell Well I certainly disagree; it often makes sense to separate out a singly-called function if for no other reason than to be able to unit test that functionality individually.
- ArkyBeagle 10y agoIt really sounds like you're proposing(?) or objecting to the lack of (?) code locality - feature X is all in one or two or three nicely isolated places. Over-concern for extensibility is just a really big problem.
- phn 10y agoI think the problem is guidelines and principles are just that, guides. You cannot turn off your critical thinking just because you read somewhere that you should do things a certain way. The problem, of course, is that your critical thinking needs experience before you truly become capable of deciding if a particular principle should be followed or broken in a particular case. In short, I think guidelines help, but you have to walk the path to know the path.
- chimprich 10y agoA lot of those problems are quite dependent on your choice of language. Java, and similar languages, implicitly bias programmers in favour of building multi-layered abstracted monstrosities. Inheritance and interfaces can be powerful weapons for evil. Lots of the original Java libraries pushed people in the wrong direction. I'd agree that finding the right level of abstraction is hard, easy to misjudge even for an experienced programmer with YAGNI in mind, and that you can do more damage by over-abstracting than not abstracting. Most programmers experience this the hard way sooner or later. I'd disagree that having objects/functions do more than one thing or not be able to be pulled apart into individually testable units can be a good thing - except in very rare cases, or to compromise with existing legacy code. Would you mind giving an example of where an object should do more than one thing?
- amai 10y agoThis explains most of these horrible Java libraries: http://product.hubspot.com/blog/bid/7271/premature-flexibilization-is-the-root-of-whatever-evil-is-left http://product.hubspot.com/blog/bid/7271/premature-flexibili...
- lamontcg 10y agoLately I've been ripping out crappy abstractions that were all about creating DRY code but instead made the code incredibly hard to follow. Even though the resultant code has a bit of duplication, its much easier to read, reason about, test and maintain. The redundant parts are typically in areas that are unlikely to change. I might try to come up with a really clean abstraction later in order to re-DRY the code, but lately my experience is that a shitty abstraction created entirely for the purpose of DRY'ing code is vastly worse than just allowing a bit of code duplication. DRY only for the sake of DRY is terrible.