5 ms·
It's not the number of abstractions, but the quality of abstractions. If abstractions reduce cognitive load, they are good. If abstractions increase cognitive l
by cc_ashby 3y ago
It's not the number of abstractions, but the quality of abstractions. If abstractions reduce cognitive load, they are good. If abstractions increase cognitive load, they are bad. Often times good abstractions are obvious within the problem domain.
To this end, I think Haskell, in its pursuit of the purest, can lead to incredibly-high cognitive load abstractions that make reading code daunting. It feels good when writing it, but feels awful when reading it. Sometimes applying the DRY principle can be bad if the thing you are generalizing will lead to higher cognitive load for people referencing it.
Also I think abstractions vary in quality depending on the language the abstraction is embedded in. Functional paradigms in C++ are thorny to work with; even though functional paradigms are good abstractions in general, if the language doesn't support it first-class (i.e. the cognitive load of using them is high), then they are not good abstractions.
You can also consider maps in a language like Clojure, where maps are almost "zero-class"; it's like the language was built for maps. Nested maps have incredibly low cognitive load in Clojure (not only because the syntax supports it, but also because functions are standardized). Nested maps in Common Lisp, however, are not as nice as those in Clojure; you have to import non-standard libraries in order to deal with them (and even more obscure libraries to support Clojure-like read macros).
The key question is: when someone else reads this code, how much do they need to "reconstruct" the context? Can they get away with not reconstructing the context? And if they need to, how fast can they do so, via appropriate naming, comments, and types?
- koboll 3y agoThe key determinant, imo, is precisely and comprehensively descriptive names Even if this leads to humourously Java-esque code, it's worth it An abstraction that has one purpose and is named to make that purpose crystal clear is a good abstraction
- lukeramsden 3y ago> Even if this leads to humourously Java-esque code, it's worth it Some people seem to get mad about the verbose naming common in Java - but it’s one of the biggest blessings I’ve ever experienced. If you name things after what they do, and that name is stupid, then it’s the quickest indicator of bad design I’ve ever seen. Good design is where every name is patently obvious and encompasses the entire purpose of the class / record / method / whatever.
- TeMPOraL 3y agoI think it's not the verbose names that are the problem in stereotypical Java-esque names, but rather the large amount of "scaffolding" classes that implement design patterns - a wealth of middleware that becomes apparent thanks to the verbose naming. That is, it's not the ProcessedWidget::QueryStructuralIntegrityPercent() that's the issue - it's the associated WidgetFactory, ProcessedWidgetBuilder, ProcessedWidgetProcessingManagerDelegateProxy, etc. that bloat the code and tax cognitive memory, and which exist only because the language isn't (or used to not be) expressive enough to express those patterns without giving them a name.
- peterashford 3y agoThere's nothing like ProcessedWidgetProcessingManagerDelegateProxy in Java or the standard library. I feel that Java gets whacked with a cudgel that should be aimed at middleware authors.
- TeMPOraL 3y agoThere's nothing in JavaScript forcing you to use 500 dependencies across 100 000 folders either. Opinions on a programming language are often, for better or worse, opinions on the language plus its community/ecosystem. And correct me if I'm wrong, but isn't the company responsible for maintaining Java (Sun, now Oracle) also one of the worst offenders in terms of naming things in its middleware?
- eyelidlessness 3y ago> If you name things after what they do, and that name is stupid, then it’s the quickest indicator of bad design I’ve ever seen. I agree it’s a smell, but I’d caution that it’s also very situational. Sometimes the domain has a recognizable category of like-things which don’t have a name for their likeness within the same domain, and choosing even a stupid-sounding name is a good start towards choosing a better name. Other times, a targeted portion of an existing codebase might have a common theme which doesn’t necessarily line up with the domain, but needs to be named something to untangle what outlived its WET shelf life. You can recognize it’s a stable abstraction, you can recognize it's entirely divorced from any domain concern except by happenstance. You just have to give it a name, or let it be an increasingly unnecessary burden. It’s easier to take such a hard line on the design quality implications of naming when you have a vocabulary to reference and/or compose, or when you have extant design attitudes relatively aligned with that principle. It’s especially difficult to apply the principle in systems with extant design problems of this nature, because whatever established or emergent abstractions do exist might not align with any principle you’d apply, and you can’t move them in that direction without some intermediate step no matter how flawed. You have to name what is before you name what should be.
- throwuwu 3y agoDoesn’t matter how good your names are if your control flow is incomprehensible or if the data representation is some bloated or mangled monstrosity.
- i_am_a_peasant 3y agoand every time i complain about names during code reviews i get accused of bike shedding
- AlexCoventry 3y agoThe values you're espousing here are the core lesson I took from John Ousterhout's A Philosophy of Software Design.
- valcron1000 3y ago> To this end, I think Haskell, in its pursuit of the purest, can lead to incredibly-high cognitive load abstractions that make reading code daunting. Hard disagree. The Haskell abstractions in the standard library are (mostly) great. Classes like `Monoid` or `Functor` as as basic as they get (in the sense that they describe a very small set of behaviors) and include laws. Of course, user defined classes can incur in high cognitive load, but that is applicable to any language that supports abstraction.
- TeMPOraL 3y agoI feel the problem is more that Haskell is one of those languages that push against the unmovable barrier of dimensionality. Yes, monads and related ideas that get increasingly adopted by other languages are great. They let you express some things more clearly than before - especially when it comes to cross-cutting concerns like error handling - but only to a point. At some point, you're trying to optimize readability of code across multiple concerns, each having a good claim to being of prime importance. But you can't. You can try and invent increasingly obscure mathematical abstractions to make some concerns special cases of a more general one - but in the end, there's only so much you can cram into the same piece of plaintext. You have to trade readability for some readers (e.g. those interested in "golden path" business rules) against readability for others (e.g. those interested in error propagation). Or, you can double down on further abstractions and make everything unreadable for everyone equally :). The underlying problem is partially about plaintext format itself, but mostly about the fact that we always work on the same, single representation, and demand it to be everything for everyone. I feel the only way to progress is to finally give up the constraint of directly reading and writing the final "single source of truth" code. It will definitely simplify things day-to-day, as it'll allow you to plain ignore and hide things you don't care about at a particular moment, instead of trying to keep them concise with design patterns, clever syntax, advanced algebra, etc.
- tikhonj 3y agoAbstractions, like so many other things, can be "difficult" along two axes: they can be hard to learn, or they can be hard to use. The former increase cognitive load as you're learning them, sure, but they can also pay off massively once you do—abstract math is hard but also lets us manage complexity and express ideas we would not be able to handle otherwise. On the other hand, lots of abstractions aren't particularly hard to learn (usually because they aren't very abstract!), but either don't do much or carry an ongoing amount of complexity as you use them. It doesn't matter how well you've learned about pointers or malloc + free, non-trivial code using them requires a lot of care and is easy to mess up. (This is a controversial point, but it should be clear given the number of bugs and security vulnerabilities we find due to memory safety issues in real code!) I see it as a trade-off between abstract thought and working memory. You can spend time up-front to reduce the amount of details you need to keep in your head as you go along, or you can continue to juggle more details to learn less up-front. The problem, of course, is that most people don't differentiate between the two. In some sense, it's only fair: if an abstraction carries some up-front cognitive cost as you're learning it, how do you know it'll get better? Do you even have the time to learn something new right this moment? But, ultimately, it's a massive difference and avoiding abstractions that are hard up-front is self-defeating in the long term. Haskell abstractions mostly fall in the former camp: hard to learn, but powerful once you have. I've worked with some pretty poor Haskell code and the abstractions that seemed hard when I was a beginner were exactly what made it easier to understand messy code! Turns out that expressive types and effect management mean that I don't have to carefully understand which parts of the code can affect each other indirectly and which parts can't; it's all explicit in the structure of the code. I had to learn the language and the concepts to understand it but, once I did, I could read it directly from the program rather than needing to simulate the code in my head. I've found that, most of the time, the KISS design philosophy is heavily weighted in the opposite direction: it's all about avoiding concepts and abstractions that a reader would need to learn, but at the expense of having everybody keep more details in their head as they're writing and reading the code. The small pieces might each be easier to understand, but there are a whole bunch more pieces to track for the same amount of logic! The problem with a blanket "how hard will somebody else find this code to read?" is that so much of it rides on familiarity rather than anything fundamental to the code. And that's what matters in any specific situation, to be sure... but it doesn't answer the broader question of how we should be writing (and reading) code. After all, even if familiarity dominates in the immediate short term, it might still be worth up-front learning to have a better experience in the medium and long term.
- wudangmonk 3y agoI think abstraction is promoted way too much in programming. People use lisp macros as an example of too much abstraction since it allows you to write a DSL that only you understand yet they celebrate the poor man's DSL they create using their OOP abstractions. I like the math homework approach to programming. First you get in there with a vague idea of what you need to do, you bang your head against it until it starts to make some sense. You then move on to more complicated examples only to realize that what you thought you knew was wrong. After going through many of these iterations you might start to notice some patterns common to all examples and if you're lucky they might turn out to be true. This is rare and only happens when you have a very good understanding of the problem.
- kazinator 3y agos/People use/People who have never written a line of code in it use/
- vlzdr 3y agoI fully agree with you. You mentioned that sometimes applying the DRY principle is a bad thing. Really, people often adhere to the DRY principle far too dogmatically. It’s better to have some duplication than to end up with a wrong abstraction. I like the AHA principle much more. It suggests: - Avoid Hasty Abstractions - Prefer duplication over the wrong abstraction because duplication is far cheaper than the wrong abstraction I found it here: https://kentcdodds.com/blog/aha-programming https://kentcdodds.com/blog/aha-programming. The point about duplication being better than wrong abstraction is made here: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction. And the principle doesn’t suggest avoiding abstractions altogether. It’s really only about avoiding the hasty ones. Wait with the abstraction until you feel it’s necessary or when duplication itself becomes a problem. At that point, you’ll have a clearer understanding of how the abstraction should function.
- rightbyte 3y ago> people often adhere to the DRY principle far too dogmatically This is a problem with all rule of thumbs for programmers. It is like we as a group are far more dogmatic than normal. Or our rules are worse. Or both. Dunno.
- ughitsaaron 3y agoI really liked the way Sussman discussed repetition in the SICP lectures. Something like, “If you find yourself repeating the something, that’s often a hint that there could be a useful abstraction.” It’s a far less prescriptive (and more appropriately flexible) way of expressing the same general idea than DRY.
- kmoser 3y ago> The key question is: when someone else reads this code, how much do they need to "reconstruct" the context? Can they get away with not reconstructing the context? And if they need to, how fast can they do so, via appropriate naming, comments, and types? That is indeed the key question, but the answer will have a lot to do with their experience as a developer, their knowledge of the domain, and their familiarity with the code. I'll take poor quality abstractions any day, provided they are coupled with amazing documentation, even if in the form of well written comments, that describe the reasoning behind them.
- EVa5I7bHFq9mnYK 3y agoI always told my devs: imagine the dumbest programmer you ever knew. Take me, your manager, for example. Will I be able to understand and fix/modify your code 10 years down the road when you all have left?
- sebazzz 3y agoThat pretty much excludes using any npm library like redux or webpack.
- ughitsaaron 3y agoI was coming here thinking the same thing. The important thing isn’t abstractions in general, it’s whether those abstractions are useful (defined here as reducing the cognitive load of a reader, but there’s probably dozens of other meaningful definitions of a “useful” abstraction). That can depend on a number of factors, e.g. the particular programming idioms, flavor, style, etc. of a team; the quality of documentation and onboarding of new engineers to a team; the specific task at hand, etc.