[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)