[HN Gopher] Death by design patterns, or On the cognitive load o...
       ___________________________________________________________________
        
       Death by design patterns, or On the cognitive load of abstractions
       in the code
        
       Author : alentred
       Score  : 92 points
       Date   : 2023-05-29 21:04 UTC (1 hours ago)
        
 (HTM) web link (alentred.medium.com)
 (TXT) w3m dump (alentred.medium.com)
        
       | waiseristy wrote:
       | Otherwise known as "your CS degree has shockingly little to do
       | with this god forsaken login page we're designing"
       | 
       | I've had so many long conversations with early-mid career
       | developers that the stupid code being written will have a shelf
       | life about as long as a head of lettuce. And that the "best"
       | solution is typically the simplest, shortest, and can be written
       | the quickest.
        
       | ngngngng wrote:
       | One of the strangest ironies of my career is that the smartest
       | developers often write the worst code. Their perfect memories
       | enable them to effortlessly work with infinite layers of
       | abstraction and write the most clever solutions imaginable to any
       | problem.
        
         | baconforce wrote:
         | a lot of it is hubris as well, they just assume that others
         | will be able to understand it because it's easy and intuitive
         | to them, and if others cannot understand it they must not be
         | good developers
        
         | ldjkfkdsjnv wrote:
         | If they work with developers of a similar intelligence its not
         | a problem, and even has massive returns
        
         | bqmjjx0kac wrote:
         | And that's why my shitty memory is kind of a super power.
         | Except for the way it makes everything else difficult.
        
         | aleksiy123 wrote:
         | Gotta dumb yourself down to write your best code sometimes.
         | 
         | In this vain this often posted article is fun
         | https://grugbrain.dev/
        
         | Instantix wrote:
         | Are you sure those "smartest developers" would be still smart
         | if they have to maintain someone else abstract code? Easy to
         | memorize layers of abstraction when it follow your own logic
         | and you build it step by step.
        
           | Hermitian909 wrote:
           | Still happens. I work in a codebase that is definitely on the
           | upper end of complexity for the industry. ~18 months ago we
           | hired a very smart developer who had one of the more rapid
           | onboardings to our codebase I've seen, he was very productive
           | in under a month. It turned out he was _too_ productive and
           | people were starting to find new layers being added to the
           | code base. A few of the more senior folks had to come in and
           | shut him down.
        
           | leetrout wrote:
           | Bingo. It's different when you grow up with it.
        
         | tester756 wrote:
         | how do you define smartest developers?
        
           | intelVISA wrote:
           | ability to solve Hard Problems rather than create them
        
         | mjevans wrote:
         | Long ago I read something like...
         | 
         | A debugger needs to be smarter than the person who wrote the
         | code being debugged.
         | 
         | If someone is at their smartest when writing a section of code,
         | they are thus unable to successfully debug problems involving
         | that code.
        
           | [deleted]
        
           | alwaysbeconsing wrote:
           | Indeed; this is called Kernighan's Law:
           | 
           | > Everyone knows that debugging is twice as hard as writing a
           | program in the first place. So if you're as clever as you can
           | be when you write it, how will you ever debug it?
           | 
           | https://en.wikiquote.org/wiki/Brian_Kernighan
        
         | analog31 wrote:
         | Being a savant at creating and then solving a particular kind
         | of puzzle is certainly a form of smartness. Figuring out
         | whether it's a form that you want to have involved in your
         | business is another form of smartness.
         | 
         | The principal-agent problem looms large.
        
         | biorach wrote:
         | Nope, those are not the smartest developers. Those are the
         | cleverest developers.
         | 
         | The _best_ developers solve the hard problems without writing
         | the worst code.
        
       | epolanski wrote:
       | I find these arguments going back in circles and concluding
       | nothing.
       | 
       | All teams are different, and a good rule of thumb is to write
       | code the least skilled or experienced person can understand on
       | its own without guidance. Since teams vary so do these limits
       | change. I had teams where it would've been odd to even try use
       | any abstraction, let alone algebraic data types, and teams
       | practicing lots of abstraction and architectural patterns all on
       | top of functional effects. The teams widely different in
       | proficiency of the best and weakest members and so did the
       | codebase patterns.
       | 
       | It's like in sports, different teams demand different approaches
       | because the qualities and proficiencies differ, there are no
       | universal truths but solutions that fit better different teams.
        
         | pmontra wrote:
         | I write code that I can understand when I'll come back to it
         | after one year or two. Sometimes I don't understand what my old
         | code does and I realize that I failed. It wasn't simple enough.
        
         | m000 wrote:
         | > All teams are different, and a good rule of thumb is to write
         | code the least skilled or experienced person can understand on
         | its own without guidance.
         | 
         | This. Also, if your team develops on framework X and people are
         | hired based on that, stick to the damn patterns used by your
         | framework!
         | 
         | I had once worked with a developer that one day unilateraly
         | decided that the abstractions provided by the framework we used
         | were not good enough. Instead, we should use the abstractions
         | from a "design pattern" described in a shitty blog post they
         | dug up, and "everyone should read". There wasn't enough
         | pushback, so we had ended up with some seriously unmaintainable
         | code.
        
       | m_0x wrote:
       | Author provides a good example. Linear scripts are a horror to
       | maintain but a project with too many abstractions are a horror to
       | understand.
       | 
       | I'd rather maintain the latter because I just need to understand
       | the code once and figuring out abstraction layers is fun at the
       | cost of my employer's dime.
       | 
       | > the older version better
       | 
       | Better in what aspect? Better to maintain? Better to debug?
       | Better in solving the business needs? If the answer is all of
       | them then congrats you have poorly done refactoring.
        
       | BrissyCoder wrote:
       | Starting around the mid 2010s I think, I started seeing needless
       | use of "Design Patterns" all over the place (mostly by CS
       | graduates). Factories, adapters, observers in places where it was
       | just plain overkill and needless extra lines of useless code.
       | 
       | I started referring to it as cargo-cult programming.
        
       | zmmmmm wrote:
       | One thing I have to say about this is that the fact they did this
       | with a bunch of Python code is probably a huge part of why it
       | didn't go well. Abstracted Python code drives me crazy because,
       | in part because of the dynamic and typed nature of Python, the
       | support is so poor for then navigating around it afterwards.
       | 
       | Compare to Java and with single keystrokes I can navigate up the
       | type hierarchy and bring up a full display of every class
       | implementing an interface. With a couple more keystrokes I can
       | rename a function in the base class and update all the
       | implementors, globally across hundreds of thousands of lines of
       | code. And in Eclipse this happens nearly instantly with 100%
       | fidelity (I assume in other IDEs too). I can move around the
       | codebase with so much ease. As soon as I hit Python code like
       | this I'm completely lost.
       | 
       | I see people complaining all the time about IDEs (if you need an
       | IDE then your code is bad) and then also about design patterns
       | and abstraction and I do see a connection in that yes, if you
       | need to scale complexity there is a fundamental aspect of that
       | whereby you need a more structured approach to how you are
       | building things and the underlying tools that are supporting
       | that.
        
       | srajabi wrote:
       | No abstractions turns to spaghetti Too many abstractions turns to
       | spaghetti
       | 
       | I don't think the answer is one or the other at all like the blog
       | post is implying. I think his previous conclusion of a threshold
       | makes more sense. Perhaps though they have chosen the wrong
       | abstractions and hence the confusion
        
         | usrbinbash wrote:
         | The thing is, even if its spaghetti, I can follow the
         | individual noodles. I can carefully pull them out of the pile
         | and lay them out all neat and separated.
         | 
         | With "cleverely abstracted" code, I cannot do that. Because
         | there are no noodles any more. There are small pieces of
         | noodles, all in different sizes, spread out over 200 plates,
         | that reside on 140 tables in 40 different rooms, some of which
         | are in another building. And there are now 20 types of sauce,
         | and 14 types of parmesan, but those are not neatly separated,
         | but spread evenly all over everything, and so it all tastes
         | awful. And each individual plate _still_ manages to be
         | spaghetti.
         | 
         | Okay, yes, I may have strechted out the food-analogy a bit.
         | 
         | But that's pretty much how I feel when digging through over-
         | engineered codebases.
        
           | lisasays wrote:
           | But hey, it's serverless and scales to zero.
           | 
           | And some guy banged it out over the weekend, which means he's
           | a rock star.
        
             | intelVISA wrote:
             | Nobody else can match (understand) his velocity. He's the
             | best.
        
           | Tyr42 wrote:
           | I usually hear lasagna code if it's got too many codes, and
           | ravioli code if it's too packetized.
        
         | mathisfun123 wrote:
         | >No abstractions turns to spaghetti Too many abstractions turns
         | to spaghetti
         | 
         | That's because abstraction is a myth perpetuated by the likes
         | of Sussman (ie academics that never wrote real code) and in
         | reality the only thing that matters is PSPACE [?] EXPTIME.
        
         | candiddevmike wrote:
         | One could argue there are different degrees of ugly and
         | beautiful spaghetti though.
        
         | Falkon1313 wrote:
         | Yes, it's just the new style of spaghetti, this time with
         | lipstick and a bow tie.
         | 
         | One notable difference is that now the GOTOs are implicit and
         | only in your head, so it's notably even harder to comprehend
         | than the old style of spaghetti.
         | 
         | Also often adds in some code hidden in comments
         | ('annotations'), some squirreled away in complex configuration
         | files, and some other code where what actually gets executed
         | depends on concatenated string variables, and maybe for good
         | measure some places where which code gets executed (and how)
         | implicitly depends on the filepath it is under, and now we have
         | a mess even a debugger can't help with.
         | 
         | It's nice to see more people finally getting disillusioned with
         | these trends.
        
       | epgui wrote:
       | I'm just a guy, so take what I say with a grain of salt.
       | 
       | This problem is broadly solved by a combination of domain-driven
       | design (DDD) and functional programming (FP).
       | 
       | Yes, I know you're tired of hearing about FP and you think FP
       | engineers are all white bearded monad salespeople. I don't care.
       | 
       | Odds are, if you're an engineer, you're not doing purely abstract
       | work: your code has some correspondence to "stuff in the real
       | world". FP is unencumbered by the class/object model and you can
       | abstract over anything trivially using plain old functions: don't
       | "choose" your abstractions, use these, and your domain models, to
       | figure out what the correct/real(est) abstractions are. It's not
       | so much about too much or too little abstractions, it's about how
       | suitable the abstractions are.
        
         | leetrout wrote:
         | Speaking for myself I think FP is going to have its moment and
         | it will be when average devs can ignore the syntax and theories
         | in the abstract and just solve problems.
         | 
         | Pretty much everything is better with FP and walking a
         | reasonable line when showing benefits _without_ immediately
         | diving into all the esoteric parts wins people.
         | 
         | We need the MVC of FP type of trendy bandwagon. The FP rails.
         | 
         | I am optimistic about Roc and hope Rust also continues to help
         | make FP style programming more commonplace even if I still dont
         | know how to implement a monad.
        
         | avindroth wrote:
         | I like FP, but I don't think the situation is solved by FP.
         | There's a lot more room for discussion of abstraction and
         | complexity within FP.
        
         | intelVISA wrote:
         | Functional is generally accepted as The Best Abstraction imo
        
       | Scubabear68 wrote:
       | It took us a long time as an industry to recognize that
       | popularity of "design patterns" was due to problems with
       | languages. And the best fix is to improve the language.
        
       | [deleted]
        
       | zokier wrote:
       | > Now I introduce abstractions only if I decide that the future
       | reader could confidently modify the code without looking at the
       | implementations
       | 
       | Going full no true scotsman, but is it really an abstraction if
       | that property does not hold true? Isn't the author essentially
       | arguing against leaky abstractions, which should self-evidently
       | be a bad idea? The problem of course is that developers do not
       | always have the prescience to predict if an abstraction will be
       | leaky or not.
        
       | 0xCAP wrote:
       | I've had the pleasure to work with an immensely talented
       | developer who would spam unnecessary patterns all over the
       | codebase, to the point where it required several coffees just to
       | go through a single branch of domain logic. Even the IDs of his
       | aggregates were classes.
       | 
       | At the same time, though, I feel very disheartening that at my
       | current company devs ignore what even "aggregate" means, and
       | don't have any kind of clue about patterns and higher concepts in
       | general. I really miss smart and educated people in IT.
        
         | tester756 wrote:
         | >Even the IDs of his aggregates were classes.
         | 
         | People were arguing for such a thing because e.g
         | 
         | if you have 2 guids then you can write code which handles two
         | completely unrelated guids
         | 
         | like
         | 
         | `student.Id == car.Id`
         | 
         | meanwhile if you have types like StudentId and CarId then you'd
         | have compilation error
         | 
         | While I'm not a fan of this, then the concept is sound.
        
           | 0xCAP wrote:
           | Yes, this is a philosophical issue that lies within our
           | attempts to model reality in our systems. What defines
           | identity? A dog named Brian is not the same as a man named
           | Brian.
           | 
           | For value objects, surely Leibniz's philosophy applies: if a
           | theory describes two situations as being distinct, and yet
           | also implies that there is no conceivable way, empirically,
           | to tell them apart, then that theory contains some
           | superfluous and arbitrary elements that ought to be removed.
           | 
           | For entities, it's much more complex.
           | 
           | I still feel some kind of way when seeing classes (with
           | methods!) being used for IDs, though.
        
             | tcfhgj wrote:
             | > still feel some kind of way when seeing classes (with
             | methods!) being used for IDs, though.
             | 
             | Depends on the language. In many languages strings are
             | classes in general, so it makes sense that a new type for a
             | strong is is one as well
        
         | biorach wrote:
         | > an immensely talented developer who would spam unnecessary
         | patterns all over the codebase
         | 
         | He may have been talented but unnecessary patterns are the
         | hallmarks of a bad programmer
        
       | rightbyte wrote:
       | He does not mention that "linear" code are much more grep-able
       | than fancy pancy code. Even such simple things as error messages
       | as string literals in the code makes your life so much easier.
       | 
       | A degenerate case of grep-able code is e.g. virtual methods for
       | some class and derived types that share name with methods of
       | another class and derived types.
       | 
       | I also distastes code with so much dynamicness that you have no
       | clue where stuff happens. I really like long functions that do
       | many things one thing after another.
        
       | samsquire wrote:
       | This article made me think of this blog post:
       | 
       | https://mikehadlow.blogspot.com/2012/05/configuration-comple...
       | 
       | I prefer the old version or the pre over-refactored code better
       | too, I can at least understand it.
       | 
       | I've noticed that when control flow is abstracted away at runtime
       | for flexibility of future changes just in case we need to handle
       | other cases in the future, the code starts to become difficult to
       | reason about.
       | 
       | When people extract everything out until you have hundreds of low
       | signal-to-noise files.
       | 
       | I think it's a reaction to team based software delivery, where
       | multiple people need to work on different layers independently
       | and separately, but when you're working solo, a dense codebase
       | you can fit in your head is a lot more enjoyable.
       | 
       | Maybe the size of the codebase determines this.
        
         | lmm wrote:
         | > I've noticed that when control flow is abstracted away at
         | runtime for flexibility of future changes just in case we need
         | to handle other cases in the future, the code starts to become
         | difficult to reason about.
         | 
         | Control flow is a big thing to watch for. IME an abstraction
         | that's defined in terms of plain functions and values, and lets
         | you use your own control flow, is often a good one; one that
         | requires you to supply a bunch of callbacks and let it take
         | over the control flow is often a bad one.
        
       | swayvil wrote:
       | Simple code is like the Zen ideal. Direct seeing, direct
       | apprehension. It beats any abstraction. It's right there.
       | 
       | Like, we could almost have our ideal UI be a dozen lines of code
       | (in some kind of CLIish dealy) , exposed for twiddling.
       | 
       | It would be the ultimate in informativeness, control and
       | flexibility.
        
       | thex10 wrote:
       | OP, I think you'd really enjoy the book "A Philosophy of Software
       | Design" by John Ousterhout. It discusses this topic at length.
       | With respect to reducing complexity, you pretty much drew the
       | same conclusion he did.
        
         | avindroth wrote:
         | I came into this thread thinking of recommending this book. Are
         | there any other books like it that discuss managing complexity,
         | maybe not just for software engineering?
        
       | hu3 wrote:
       | Worst offender for me is when I can't access the implementation
       | by just clicking on the type or variable, because it is an
       | interface.
       | 
       | For example with dependency injection in languages where devs put
       | Interfaces in constructors and some magic injects the Class that
       | satisfies that Interface at runtime. When you click the type or
       | variable, the IDE opens the Interface file which is just a bunch
       | of function signatures, no implementation. It's infuriating when
       | all you want is to see some concrete code. You just want to see
       | exactly what code is being executed and then carry on with your
       | day.
       | 
       | I get the benefits of dependency injection but most of the times
       | a single class implements that interface and yet i'm forced to
       | scavenge the code to find it. I also know some tooling is
       | powerful enough to list the Classes that satisfy that Interface.
       | But it's not perfect and it's still a layer of indirection adding
       | to my cognitive load.
        
       | smitty1e wrote:
       | I don't have the large code base experience, so I'm talking a bit
       | out of line.
       | 
       | However, contra OO, I submit that keeping the data orthogonal to
       | the methods is a win.
       | 
       | Restated, I haven't seen much win with objectifying everything
       | and having a huge object inheritance tree. Probably useful in
       | some domain.
       | 
       | Recently looking at botocore. There are no classes for AWS
       | services; just a "database" of JSON documents enumerating
       | "shapes" and "operations", and then some wrapper classes that
       | dynamically instantiate classes where needed.
       | 
       | Feels as though AWS may be on to something, though doubtless
       | their server-side code is non-trivial.
        
         | leetrout wrote:
         | If you havent read it already it may be worth reading why the
         | AWS libs are built that way. Definitely a problem at a certain
         | scale.
         | 
         | And if you havent seen it already you would probably appreciate
         | Project Cambria from Ink and Switch.
         | 
         | https://www.inkandswitch.com/cambria/
         | 
         | Editing to add: and "schema driven development" with tools like
         | Buf
         | 
         | https://buf.build/
        
       | khazhoux wrote:
       | In C++ the over-abstraction starts in your first lessons. I know
       | and understand all the arguments, but I will never type this:
       | std::cout << "You have " << itemCount << " items of type " <<
       | itemType << "." << std::endl;
       | 
       | When I could do this:                   printf("You have %d items
       | of type %s.\n", itemCount, itemType.c_str());
        
         | waiseristy wrote:
         | C++, OOP, and whatever crazy async shit is going on in JS
         | ruined the conceptual model of "code" for going on 2
         | generations of programmers.
         | 
         | We do students a disservice teaching them how to "code" first.
         | And not instead how to solve problems with code
        
           | aswanson wrote:
           | Yeah, that async stuff is brutal.
        
             | throw_m239339 wrote:
             | It's funny. I started with Flash, thus Action Script as a
             | designer and evented programming felt so natural to me,
             | especially the observer pattern, so much that I had issues
             | when dealing with thread safety in Java.
        
             | waiseristy wrote:
             | Great for solving the typical problems encountered in a web
             | browser.
             | 
             | But a real fucked up mental model to bake into an entire
             | industry's worth of developers
        
               | jimkoen wrote:
               | To me this seems overexaggerated. Most people I went to
               | university with already struggled to understand async
               | code, without having done web development before. And for
               | those with web development background it was pretty easy
               | to adjust.
               | 
               | I also don't know any school, college or university that
               | starts their programming courses in a webbrowser. Typical
               | languages in Germany for a first semester algorithms/data
               | structures class are either Java or C++.
        
           | QuantumSeed wrote:
           | I would go a step further and teach them how to read code
           | before teaching them how to write it
        
         | pmontra wrote:
         | Many languages have string interpolation, which looks more
         | similar to C++'s << than to printf                 puts "You
         | have #{itemCount} items of type #{itemType}."
         | print(f"You have {itemCount} items of type {itemType}.")
         | console.log(`You have ${itemCount} items of type ${itemType}.`)
        
         | LaLaLand122 wrote:
         | I will never type this:                   printf("You have %d
         | items of type %s.\n", itemCount, itemType.c_str());
         | 
         | When I could do this:                   std::println("You have
         | {} items of type {}.", itemCount, itemType);
         | https://en.cppreference.com/w/cpp/io/print
        
           | khazhoux wrote:
           | That's perfect -- as easy to write and to read as printf. I
           | feel like an idiot for not using it all these years.
           | 
           | Edit: Oh, it's in the C++23 standard. _Only took them 38
           | years to think of this!_ ;-)
        
       | avindroth wrote:
       | 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 wrote:
         | The 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
        
         | AlexCoventry wrote:
         | The values you're espousing here are the core lesson I took
         | from John Ousterhout's _A Philosophy of Software Design_.
        
       | taneq wrote:
       | The cleverest code is, in my experience, the simplest. Anything
       | else isn't clever, it's just showing off.
        
       ___________________________________________________________________
       (page generated 2023-05-29 23:00 UTC)