[HN Gopher] Premature Abstraction
___________________________________________________________________
Premature Abstraction
Author : arendjr
Score : 82 points
Date : 2024-07-18 07:49 UTC (15 hours ago)
(HTM) web link (arendjr.nl)
(TXT) w3m dump (arendjr.nl)
| Zambyte wrote:
| Anyone who wants to do a deep dive into understanding effective
| abstractions, I _highly_ recommend SICP. The full book[0] and
| lectures[1] are available online for free. You don 't have to
| know Scheme to follow along.
|
| [0] https://mitp-content-
| server.mit.edu/books/content/sectbyfn/b...
|
| [1] https://ocw.mit.edu/courses/6-001-structure-and-
| interpretati...
| morkalork wrote:
| >You don't have to know Scheme to follow along.
|
| Is a hilarious understatement. No, you don't need to know it
| when you start the book but when you finish, you certainly
| will.
| sevensor wrote:
| Although I agree with the recommendations, I cringe at the
| definition of abstraction. In a sane world, abstraction doesn't
| mean defining classes so much as it means identifying important
| unifying concepts. DRYing your code by moving a method to a
| common base class isn't abstraction in any important way, it's
| just adding a level of indirection. In fact, I'd argue that this
| example is the opposite of abstraction: it's concretion. Now
| every subclass relies implicitly on a particular implementation
| of that shared method. Not that doing this is never useful, but
| it's a mistake to call it abstraction when it's nothing of the
| sort. No wonder people complain that their abstractions leak.
| withinboredom wrote:
| I'm currently dealing with a codebase that does this to a
| ridiculous extent. Like, literally, every change affects the
| entire project because everything is made of base-classes mixed
| in weird ways. Every concrete object inherits multiple base
| classes and no individual behavior. Imagine something like
| this: class Book extends ShelfableItem,
| Pagable, Authored, Readable, BaseBook {}
|
| It's absolutely insane.
| patmorgan23 wrote:
| Oof. I know this is an example but all of those look like
| they would be better served as interfaces.
| withinboredom wrote:
| Yes. Did I mention there are interfaces too, with almost
| the same name, but it's only used for the base classes.
| (Yes, there is only one implementation of all interfaces).
| dakiol wrote:
| It depends. Of course we don't see the whole picture
| because it's just an example by OP, but I also find weird
| that the de facto solution to abstract classes is:
| interfaces. Sometimes, duplication is better than
| interfaces.
| favorited wrote:
| Even if they were interfaces, it screams of the "model your
| code after physical objects" approach, where a system has 1
| enormous "Book" type which represents all the things you
| can do with a physical book.
|
| It seems unlikely that the same type should be "Shelfable"
| and "Readable" / "Pagable," because they describe distinct
| sets of operations. When a book is on a shelf, you can't
| page through it. If you "read" a book on a shelf, you only
| see the title, author, and maybe some pull quotes.
| neonsunset wrote:
| Except usually the name of every type the Book inherits from
| or implements is so long it has to be put on a separate line.
| sevnin wrote:
| If by abstraction you mean identifying unifying concepts then I
| cant understand how you reasoned yourself into thinking that
| identifying a common method and sharing it between multiple
| classes by the means of the super class is not abstraction. You
| have identified a commonality - the common code, common method.
| By your definition it's abstraction.
| mannykannot wrote:
| I came to say more-or-less the same thing. The author is making
| some valid points, but the moral is that premature
| _reification_ of abstract concepts may be harmful, especially
| if there is something vague about them.
| aswerty wrote:
| > Post-Architecture is a method of defining architecture
| incrementally, rather than designing it upfront
|
| For anyone else wondering what it means.
|
| I'm going to be honest, almost all architecture I've seen out in
| the wild has followed a more incremental approach. But then again
| everywhere I've worked hasn't separated the architecture/coding
| roles.
| tracker1 wrote:
| If you work with C# or Java in a lot of places, such as Banking
| in particular, you'll definitely see a lot of up-front
| architecture and excess abstractions early on.
| recursivedoubts wrote:
| My general take (and w/ the caveat that every system is
| different) is as follows:
|
| - procedural code to enter into the system (and perhaps that's
| all you need)
|
| - object oriented code for domain modeling
|
| - functional code for data structure transformations & some light
| custom control flow implementation (but not too much)
|
| I like the imperative shell, functional core pattern quite a bit,
| and focusing on data structures is great advice as well. The
| anti-OO trend in the industry has been richly earned by the OO
| architecture astronauts[1], but the idea of gathering a data
| structure and the operations on that data structure in a single
| place, with data hiding, is a good one, particularly for domain
| modeling.
|
| In general I think we are maturing as an industry, recognizing
| that various approaches have their strengths and weaknesses and a
| good software engineer can mix and match them when building a
| successful software project.
|
| There is no silver bullet. If only someone had told us that years
| ago!
|
| [1] - https://www.joelonsoftware.com/2001/04/21/dont-let-
| architect...
| kingofthehill98 wrote:
| That's the strategy I always take when designing a system.
| Funny that I never thought about it before, I think most PHP
| developers will relate to that aswell.
|
| - Procedural single point entrance into the system (network ->
| public/index.php, cli -> bin/console)
|
| - OOP core for business logic, heavily borrowed (copied) from
| Java OOP model
|
| - Functional code inside classes/methods whenever possible
| (callables/closures, array_map/filter/reduce/walk,
| illuminate/collections, etc.)
| bunderbunder wrote:
| OO code for domain modeling might be, to date, the single
| greatest source of disillusionment in my career.
|
| There are absolutely use cases where it works very well. GUI
| toolkits come to mind. But for general line-of-business domain
| modeling, I keep noticing two big mismatches between the OO
| paradigm and the problem at hand. First and foremost, allowing
| subtyping into your business domain model is a trap. The
| problem is that your business rules are subject to change, you
| likely have limited or even no control over how they change,
| and the people who do get to make those decisions don't know
| and don't care about the Liskov Substitution Principle. In
| short, using one of the headline features of OOP for business
| domain modeling exposes you to outsize risk of being forced to
| start doing it wrong, regardless of your intentions or skill
| level. (Incidentally, this phenomenon is just a specific
| example of premature abstraction being the root of all evil.)
|
| And then, second, dynamic dispatch makes it harder for
| newcomers to figure out the business logic by reading the code.
| It creates a bit of a catch-22 situation where figuring out
| which methods will run when - an essential part of
| understanding how the code behaves - almost requires already
| knowing how the code works. Not actually, of course, but
| reading unfamiliar code that uses dynamic dispatch is and
| advanced skill, and nobody enjoys it. Also, this problem can
| easily be mitigated with documentation. But that solution is
| unsatisfying. Just using procedural code and banging out
| whatever boilerplate you need to get things working using
| static dispatch creates less additional work than what it takes
| to write and maintain satisfying documentation for an object-
| oriented codebase, and comes with the added advantage that it
| _cannot_ fall out of sync with what the code actually does.
|
| Incidentally, Donald Knuth made a similar observation in his
| interview in the book _Coders at Work_. He expressed
| dissatisfaction with OOP on the grounds that, for the purposes
| of maintainability, he found code reuse to be less valuable
| than modifiability and readability.
| sqeaky wrote:
| This is a strong argument against inheritance, but that isn't
| everything about OOP. Just one well supported advanced
| abstraction (That I would also argue should be rarely used.)
|
| I would argue that just having strong type system and
| bundling methods with data gets you the vast majority of the
| usefulness of OOP. Liskov, Open/Closed, Message Passing, and
| other theoretical abstractions be damned.
|
| EDIT - Where are the good places to use inheritance?
|
| There are only a few I can think.
|
| One is when you are trying to create a system that inverts
| dependencies by allowing a plugin system or follows some sort
| of nuanced workflow that others might want to "hook into".
| But that isn't the only way to do that, maybe other ways
| would be better like passing in functors.
|
| Another situation I have seen recently is when creating a
| kind of data or messages that differ only by type and maybe a
| few small pieces of behavior and they are all known up front.
| moth-fuzz wrote:
| > One is when you are trying to create a system that
| inverts dependencies by allowing a plugin system or follows
| some sort of nuanced workflow that others might want to
| "hook into".
|
| I'm fairly certain that's _the_ use case of inheritance -
| at least in the Simula tradition. Classes as a means of
| lifetime management, moving parts that have well defined
| steps of operation (methods), and interchangeable parts
| (subtypes) which you can more or less slot into the larger
| system (polymorphism).
|
| It's easier to think about classes not as nouns, but as
| verbs over time (or rather, bounded by time): at a specific
| moment in the assembly line, call this particular method,
| at another moment, call that other method...
|
| Object oriented programming in the Simula tradition I would
| even go as far as to say is just best practices in
| structured/procedural programming taken to their logical
| extremes.
| jancsika wrote:
| > Classes as a means of lifetime management
|
| Wrt plugin systems: at least as the class level, are
| classes really a means of lifetime management in
| practice?
|
| IIRC, audio plugin APIs follow the shell command pattern
| of memory management for loading new classes-- the user
| dynamically loads a library into a running instance of an
| application, and there it stays until the application
| exits.
|
| And even if plugin systems as implemented are actually
| unloading classes, the user is almost always just
| restarting the app to make sure it took. :)
|
| Edit: clarification
| recursivedoubts wrote:
| Agree 100%: static typing (for code completion) +
| method/data bundling is the major win in OO, and it rarely
| gets talked about for whatever reason.
|
| It's unfortunate that inheritance became such a major focus
| of practical OO languages. Would love to see a composition-
| first OO language. Might have its own problems, but would
| at least be interesting.
| 1propionyl wrote:
| Go, Rust, Zig, etc all support static typing and
| method/data bundling without any explicit language
| support for implementation inheritance (interface
| inheritance in general and especially when structural
| rather than nominal is not nearly as much of an issue and
| doesn't create strict tree hierarchies).
|
| Rust has support for variance and subtupint so perhaps
| it's not as pure of an example, but it's pretty heavily
| restricted.
|
| Zig's support for method/data bundling being used for
| "objects" isn't even first class so I wouldn't call it OO
| (object- _oriented_ ) so much as object-orientation-
| capable with less fuss than if one wanted to build their
| own objects system in C.
| sqeaky wrote:
| Even in C++ the last time I thought I might need
| inheritance I made a simple class/struct with a few
| members that were `std::function` instances. Instead of
| needing inheritance this worked and I managed to keep
| type safety checks on all function return and parameter
| types. Once upon a time this would have been weird
| function pointers and `void*` with dangerous casts. Last
| month when I did it, there were just lambdas passed to
| typesafe constructors.
| jerf wrote:
| Whether you'd call "composition-first" is probably asking
| for a big argument about what "composition first" really
| means, but Go is certainly a language that syntactically
| privileges a particular type of composition over
| inheritance. It doesn't even have syntax for inheritance,
| and frankly even manually implementing it is rather a
| pain (best I've ever done requires you to pass the
| "object" as a separate parameter to every method call...
| and, yes, I said that correctly, to every _method_ call).
|
| I'm not ready to try to stake a position on the top of
| some "composition first" hill because the syntactic
| composition it supports is not something I use all the
| time. It's an occasional convenience more than a
| fundamental primitive in the language, the way
| inheritance is in inheritance-based languages. Most of
| the composition is just done through methods that happen
| to use in composed-in values, but it is generally not
| particularly supported by syntax.
| j45 wrote:
| The real world definitely isn't OO all the time.
|
| Maybe more functional and event based.
|
| The oscillation between the two as what's in favour is also
| humorous.
|
| The right thing for the right need for the present and near
| future, especially the newer the codebase, and the greater
| the need to learn, is often the way to consider pursuit.
| temporarely wrote:
| > The real world definitely isn't
|
| Ironically, (ime/o!h;) it turns out this the "root of evil"
| plaguing _abstraction_ : thinking that software
| architecture must map to "the real world".
| epgui wrote:
| I think you have it backwards.
|
| Software exists in the real world, and is used to solve
| real world problem. In building software we inevitably
| invent or use abstractions to represent or effect real
| world things. Abstractions that make it easier to do this
| are good, abstractions that make it harder to do this are
| just getting in the way.
| temporarely wrote:
| 1 - (Application) System exist in the real world, not
| software. Software exists in machines.
|
| 2 - Computing is used to solve real world problem.
|
| 3 - "In building software we inevitably invent or use
| abstractions to represent or effect real world things."
| Here is the problem where we part company.
|
| 4 - Abstractions that inform computing systems are indeed
| useful.
|
| [edit+ps]
|
| self disclosure: I've reached 'architectural orbit'
| numerous times in my career. 30 years later, I am sharing
| a subtle point. Effective software models _cutout
| attributes_ of real world elements of the problem domain.
| All attempt to "model the world" end in tears.
| epgui wrote:
| This reads like nonsense to me, so I can only assume the
| disagreement is semantic.
| j45 wrote:
| For me, software and tech that is for someone, exists to
| work for people, who are end users.
|
| End users and customers don't exist to serve at the
| leisure and pleasure of software and it's creators.
|
| Making people work harder than they need to operate
| software is selfish.
|
| DevOps and DevEx is important, but if no one uses it with
| those being great, the Customer and their experience are
| often lost and never gained.
|
| Learning to model something flexible enough for absorbing
| and quickly implementing the early customer feedback that
| is relevant is critical to boring things like retention.
|
| Helping customers earn enough to eat every month, helps
| the tool makers earn enough to eat every month.
| pjc50 wrote:
| > dynamic dispatch makes it harder for newcomers to figure
| out the business logic by reading the code
|
| This is definitely a potential problem, but I note that you
| can also get into this mess without OO in any language that
| lets you put a (reference to) function in a variable. Or, god
| help you, operator overloading.
| beryilma wrote:
| [delayed]
| sqeaky wrote:
| I hadn't thought about it explicitly like this before, and I
| think I agree. My more nebulous thought process was something
| like:
|
| 1. Try to solve the problem purely functionally.
|
| 2. If that failed because of data issue, model the data with
| objects and simple operations in an OOP style or well thought
| collection of arrays (game devs answer to OOP causing memory
| and caching problems, but the end result programming is similar
| to OOP thought process)
|
| 3. If that is can't happen because some external restriction is
| impose use the minimal amount of procedural logic to solve the
| problem and round off as many sharp corners as is practical
| until it is unlikely any on the team gets cut.
|
| Logically that is very close to inversion of thought process
| and ordering of operations to what you suggested. But I think
| we would recognize each others attempts to pick a design
| paradigm in code.
|
| Now I want to think about this more. Is there some underlying
| principle here? Is this some kind of underlying principle?
| Where do domain specific languages fit in? Do other paradigms
| fit in? What are the bounds of this pattern, where does this
| process fail?
| epgui wrote:
| If you think OOP is particularly well-suited to domain
| modelling, you should try the FP approach to domain modelling.
|
| You will never be able to unsee the complexity inherent to OOP.
| jprete wrote:
| Do you happen to have a link to an example or explanation?
| arendjr wrote:
| The keyword to search for is algebraic data types, which
| are common in functional languages, but for instance Rust
| also has them.
|
| Here is an example comparing C# with F#, where the latter
| also algebraic data types:
| https://blog.ploeh.dk/2016/11/28/easy-domain-modelling-
| with-...
| neonsunset wrote:
| The syntax has improved quite substantially since 2016
| for pattern matching at C#'s end, and it's very easy to
| model ADTs with records (and the experience of using them
| even before that was decent with methods accepting
| lambdas).
|
| Today, you write it in a similar way you would write a
| match in Rust.
| epgui wrote:
| The other commenter is _not technically wrong_ to point at
| "algebraic data types", but I don't think that answer is
| helpful at all. It's like saying the answer to data
| modelling is tuples.
|
| I would instead recommend searching for "functional
| programming and domain driven design".
| Dwedit wrote:
| When you write a procedure that has to maintain an internal
| state between calls, changing it into a class makes sense. As
| for the name, you change the verb (write) into a noun (writer),
| and you now have a name for the class.
|
| C# will silently create hidden closure classes for you when you
| use lambdas or yield.
| arendjr wrote:
| Just know that if you do this, you're injecting statefulness
| in the center of wherever it was this procedure was being
| used. If your entire system already has statefulness
| everywhere, nobody will bet an eye. But if you want to have
| any chance at creating a functional core or island, it's the
| opposite of what you should be doing.
| alex-robbins wrote:
| When you write a procedure that has to maintain an internal
| state between calls, stopping what you're doing and switching
| to functional programming makes sense.
| gwbas1c wrote:
| > These things might be good architectures, they will certainly
| benefit the developers that use them, but they are not, I
| repeat, not, a good substitute for the messiah riding his white
| ass into Jerusalem, or world peace. No, Microsoft, computers
| are not suddenly going to start reading our minds and doing
| what we want automatically just because everyone in the world
| has to have a Passport account.
|
| Priceless.
| t43562 wrote:
| I like to start with a fairly unambitious bit of procedural code
| and gradually introduce abstractions when it starts to get
| complicated or repetitious.
|
| Straight code becomes functions, occasionally a group of
| functions cry out to become a class.
|
| In C++ this is a huge effort to do - change hurts more there. In
| python it's much less painful and I end up with a program that is
| some imperfect composite of object oriented with functions. Next
| week when I want it to do more I move it further down the road to
| structure.
|
| I also like keeping side effects in their own ghettos and
| extracting everything else out of those if possible but I'm not a
| big functional programming person - it's just about testing.
| Testing things with side effects is a pain.
| tracker1 wrote:
| I find that JS/TS also lends itself towards this in terms of
| Node/Deno/Bun usage for apps. You can have a file/module that
| simply exports a function, a collection of functions, a class,
| etc. It's easy to keep it simple and then combine with a mix of
| procedural, functional and oo concepts as best fits the use
| case.
| HdS84 wrote:
| Yes, I'm doing that a lot, too. I'm often astounded how hostile
| some languages are to later changes - e.g. java always feels
| resistant to change, while dotnet and especially python are
| more amenable. I.e. I totally transformed a program from
| function to oo in python without much sweat - would have been a
| total pain in dotnet or java
| k__ wrote:
| Isn't that mostly a question of composability?
| jimmaswell wrote:
| Fine blog post overall, but the author fell to premature
| abstraction themselves in declaring that little Foo class bad.
| It's entirely too generalized for me to say anything negative
| about at all. Depending on the context, a tiny class like that
| could be completely sensible or utterly unnecessary.
| epgui wrote:
| The discussion of procedural code doesn't make sense to me,
| because it seems to mix together some orthogonal concepts.
|
| Procedural is not the opposite of object-oriented (nor is it
| particularly contrasting); idiomatic OOP is procedural to a large
| degree. Effective functional programming happens when you ditch
| the procedural approach in favour of a more declarative approach.
| commandlinefan wrote:
| > The real problem with the class Foo above is that it is utterly
| and entirely unnecessary
|
| I see this sentiment a _lot_ in anti-OO rants, and the problem is
| that the ranter is missing the point of OO _entirely_. Hard to
| fault them, since missing the point of OO entirely is pretty
| common but... if you're creating classes as dumb-data wrappers
| and reflexively creating getters and setters for all of your
| private variables then yes what you're doing _is_ utterly and
| entirely unnecessary, but you're not doing object-oriented design
| at all. The idea, all the way back to the creation of OO, was to
| expose actions and hide data. If you're adding a lot of syntax
| just to turn around and expose your data, you're just doing
| procedural programming with a redundant syntax.
| HdS84 wrote:
| As someone who dies a lot of python, TS, dotnet and java - I
| disagree. The problem of dotnet and java is that everything is
| a object. And for many cases, I don't need that object at all,
| it can be a static class - but honestly, the python concept of
| a module fits a lot better. It's a grouping of functions in a
| module, not a class holding functions.
| resters wrote:
| A well chosen abstraction dramatically simplifies code and
| understanding, and a poorly chosen abstraction has the opposite
| effect.
|
| When building a system some choices about abstractions can be
| made early, before the problem domain is fully understood.
| Sometimes they stand the test of time, other times they need to
| be reworked. Being aware of this and mindful of the importance of
| good abstractions is key to good system design.
| feoren wrote:
| I know HN doesn't like quibbles about site design, but I'm
| literally having difficulty reading the article due to the font
| size being forced to be at least 1.3vw. Zooming out doesn't
| decrease the font size! Downvote if this is boring, but (a) I've
| never seen a site that did that before, so it's just notable from
| a "Daily WTF" kind of perspective, and (b) just in case the
| submitter is on HN: it's actually preventing me from reading the
| content (without changing it in DevTools anyway).
| m3kw9 wrote:
| Here's the scenario, hot shot intern comes in, calls a meeting to
| use generics so things can be done "easier". He does a good job
| at presenting it and its value, the dumbass team lead oks it.
| Fast forward 1 week everyone complains behind on how much pain it
| is to use
| MisterBastahrd wrote:
| There's no greater joy in life than jumping through an abstract
| object, an object interface, and a factory method only to find
| out that the factory only services one object.
| phkahler wrote:
| >> Often, an abstraction doesn't truly hide the data structures
| underneath, but it is bound by the limitations of the initial
| data structure(s) used to implement it. You want to refactor and
| use a new data structure? Chances are you need a new abstraction.
|
| Data structures _are abstractions_ :-)
| scotty79 wrote:
| I would love a language that has this gradual evolutional
| abstracting as a core concern. That makes it easy. Where you can
| start from simplest imperative code and easily abstract it as the
| need for this arises.
|
| For example a language that requires "this." or "self." prefix is
| not such language because you can't easily turn a script or a
| function into a method of some object.
| aliasxneo wrote:
| I'm not "formally" trained in software engineering and am
| primarily self-taught. This area, in particular, has been
| confusing to me over the years, especially after consuming so
| many contradictory blog posts.
|
| I tried to model DDD in a recent Golang project and am mostly
| unhappy with the result. Perhaps in my eagerness, I fell into the
| trap of premature abstraction, but there's not anything in
| particular that I can point to that might lead to that
| conclusion. It just feels like the overall code base is more
| challenging to read and has a ton of indirection. The feeling is
| made worse when I consider how much extra time I spent trying to
| be authentic in implementing it.
|
| Now, I'm starting a new project, and I'm left in this uncertain
| state, not knowing what to do. Is there a fine balance? How is it
| achieved? I appreciate the author's attempt at bringing clarity,
| but I honestly walked away feeling even more confident that I
| don't understand how this all works out.
___________________________________________________________________
(page generated 2024-07-18 23:05 UTC)