5 ms·
Simplicity Before Generality, Use Before Reuse
- bootsz 9y agoThis really hits home: > How much of what software engineers now do is still devoted to the accidental, as opposed to the essential? Far, far too much
- Derbasti 9y agoThis is especially true for libraries. A minimal, simple library is much better than a sprawling, "convenient", but more complex library.
- makapuf 9y agoOr -God forbid- framework.
- nnq 9y ago> People do not on the whole pay for — or need — generality: they tend to have a specific situation, and it is a solution to that specific situation that has value Yeah, but otoh, most progress in science and tech has been made by sort-of-tricking people into paying for stuff that solve a slightly more interesting/general problem than the original boring problem they wanted solved. So at least when it doesn't drown everyone in paralyzing complexity, we need to remember to keep that "hacky rascality" alive and slightly "screw" our clients at the expense of developing some general and reusable solutions (hopefully as open-source libraries :P) that will probably matter more than whatever "important" problems they want solving ;)
- jerf 9y ago"slightly more interesting/general problem" I'd say there's no great sin in solving a slightly more general problem than what you currently face. In fact, that's often net simpler almost immediately; it's not long before a function to format a URL is simpler than simply manipulating a URL every time. (Even ignoring the simpler issues.) The problem arises when you have something like "Let's see... I need a database... let's write an ORM." Whoops. (Speaking from harsh experience there.)
- bullen 9y agoGreat article! We need more "practical rational knowledge" infused arguments in a sea of over-engineered "download and configure" nightmares!
- dgreensp 9y agoThis article really hits home in terms of how people sometimes think about React components, and how I’d like them to think about them. I sometimes hear, from other engineers working on a complex React app, that the point of React’s components is that every component is written to be “reusable.” Every component. This is used as a justification for “going general” immediately when writing a component. In reality, there is real work to make a one-off component into a reusable component, and that work can only be done in the context of having (or at least realistically imagining, not dreaming up as an exercise) multiple use sites. What React components actually provide are the same things that modules and functions provide: encapsulation; limited dependencies; a calling convention. When you write a function, you try to keep the arguments to a minimum; you might say it makes the function more “reusable,” but there is still a big difference between a library function that is designed to be used from anywhere, and a private helper function that is not. React components are “reusable” the way functions and modules are reusable. A better word might be “usable.” :) You can reason about the use, even if there is only one use. Making something “reusable” means making it general, and we’ve all had the experience that the article points to of taking some code that was intended to be general, but in fact is used in only one place, and remaking it so that it’s actually possible to use in a second place. That said, I have many times in programming succeeded in boiling something down to its essence in advance, and writing the general thing first, followed by the specific thing. I suspect this is actually more about simplicity than generality, however. I’m thinking of cases of “bottom-up” development where you write the primitives and then the higher-level parts in terms of the primitives. Actually, maybe it is about generality too, and when you are testing out your primitives in different combinations, you are doing the work required for good generality (seeing lots of different cases and basing your decisions on that).
- curyous 9y agoSo true. Premature generality is a big problem.
- jandrese 9y agoIMHO this is one of those things that only experience can help with. Nobody wants to be the guy adopting a single-use codebase to a somewhat different problem and having to rewrite half of it, but overgeneralizing from the start leads to complex and bloated programs that are hard to deploy anywhere--where the effort to deploy it is about half as much as it would have required to write a custom solution from scratch... My personal philosophy is to keep the dependencies to a minimum and make judgement calls on simplicity versus generality. I probably err on the side of simplicity a little too often, but I also get asked to deploy solutions on non-internet connected devices fairly often. Solutions that you can deploy out of a tarball on a base CentOS 5.5 machine are good sometimes.
- whack 9y agoSorry for ranting. I've been reading articles like this one for many years now, and I've certainly learnt a lot through them. Lately though, it feels as though we as a profession are just going around in circles, not making any real progress. More and more, these articles remind me of schoolyard wisdom, the kind people love to throw around because they sound wise. "Winners never quit." "Quitters never win". "Quit while you're ahead". Taken individually, they all sound perfectly reasonable and wise. Put them together, and clearly there's something wrong. This article strikes me in the same vein. "YAGNI. Keep your code as simple as possible. Focus on your code as it is right now, and not what's going to happen in the future". "The only constant is change. Business requirements are always in flux. The mark of good code is the ability to change its behavior without overhauling everything". Taken individually, they both sound perfectly reasonable, but put them together, and something is clearly wrong. Maybe it's time we accepted that there is no one right way. No one all-important metric or rule that trumps everything else. That simplicity is important, that conciseness is important, that DRY is important, that being able to adapt to future changes is important. That good code finds a balance between all of the above, without sacrificing one on the altar of another. That instead of writing articles trying to elevate one over another by attacking strawmen, we should instead present realistic code examples, thorough which we can have a grounded discussion on how best to find the balance between all the different qualities that comprise good code. It's not going to be as easy or fun as simply declaring "X considered harmful", but it's going to help elevate the quality of our discussion, and help our profession progress to the next level.
- Veedrac 9y ago> Taken individually, they both sound perfectly reasonable, but put them together, and something is clearly wrong. Which makes sense; the former is a rebuttal of the latter. The C or Go philosophy is fundamentally at odds with the C++ or Haskell philosophy.
- makapuf 9y agoId say that this article is trying to be too general and give advice for a too broad case. It would be better to focus on real world cases, solve them and try to find some slightly generalised advice </meta>
- tome 9y agoI'd like to take this opportunity to flesh out some ideas I've had regarding the difference between the concepts "generic" and "general". "Oh, I think we need the ability to do this kind of thing someday" falls under "general", giving more and more functionality to a component. "Oh, there are several things about this piece that we don't actually need to know" falls under "generic", allowing the code to generally be simpler. In Haskell traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b) is more generic than traverse :: Int -> IO Float -> [Int] -> IO [Float] and therefore strictly better (unless there are reasons the latter could be optimized but not the former).
- TheCoelacanth 9y agoThey are definitely some downsides to the first definition. For instance, the error messages will often be much harder to read. For the second one you might get something straight forward like it expected `[Int]` but the actual type was `Int`. For the first one you will probably get something about not being able to deduce some long constraint for some long type. That might be possible to improve with better tooling, but for now it is a very real downside. I think that especially for application code (as opposed to library code), it is a mistake to make your type signature far more generic than you need right away, especially because it is usually easy to switch to the more generic type declaration later without changing much existing code.
- tome 9y agoThis is a good point. I think there's some merit in doing traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b) mySpecialTraverse :: Int -> IO Float -> [Int] -> IO [Float] mySpecialTraverse = traverse in some instances.