Live data from Hacker News

Premature Abstraction

arendjr.nl

101–110 of 116 posts

Re: Premature Abstraction

#102
post #15

Earlier quoted context omitted.

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 i…

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 goo…

Inheritance is just plain a great way to model a lot of relationships, in my experience, because a lot of things are most easily thought of as "x is a kind of y". I am perennially baffled that people shit on inheritance so much, because I think it's incredibly useful. I find myself often missing inheritance when working in Rust, for example.

Re: Premature Abstraction

#103
post #15

Earlier quoted context omitted.

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 goo…

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.

It's not unfortunate happenstance, it's by definition.

Dynamic dispatch is the defining feature of object-oriented programming. In dynamically typed languages such as Smalltalk, you can get there with duck typing. But a statically typed language needs a statically typed mechanism for dynamic dispatch, and that requires some way of saying, "Y is a particular kind of X, so all members of X are also in Y." Which is - again by definition - inheritance.

You could remove - or refuse to use - the inheritance (or, equivalently for some purposes, duck typing). But that would also prevent the use of dynamic dispatch, so what you're doing would bee be procedural programming, not OOP, even if you're using an object-oriented language to do it.

Re: Premature Abstraction

#104

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…

Look, I'm going to catch flak for this but at the end of the day the main problem is that Java and C++, the most popular OOP languages, are just bad programming languages.

There are OOP languages out there, most of them older than Java and C++, that actually provide a much better set of knobs and handles for writing sane OO programs.

Java is finally getting a bit better thanks to a lot of market pressure and good ideas from Kotlin. C++ will probably be a mess forever.

Re: Premature Abstraction

#105
post #21

Earlier quoted context omitted.

> 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.

Operator overloading isn't to blame. If I overload shift-left ` That said, unless you are writing a math library or some container, there's not many good uses for operator overloading.

Custom operators of any kind are definitely a problem for learners. I think people just fixate on overloading because that's the only kind of operator customization available in the most popular languages.

The particular problem is that search engines tend to have terrible support for searching for arbitrary sequences of non-alphabetic characters.

Re: Premature Abstraction

#106

Earlier quoted context omitted.

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.

It's not unfortunate happenstance, it's by definition. Dynamic dispatch is the defining feature of object-oriented programming. In dynamically typed languages such as Smalltalk, you can get there with duck typing. But a statically typed language needs a statically typed mechanism for dynamic dispatch, and that requires some way of saying, "Y is a particular kind of X, so all members of X are also in Y." Which is - ag…

> Dynamic dispatch is the defining feature of object-oriented programming.

Message passing is the defining feature of object-oriented programming. Dynamic dispatch can be achieved using message passing, but message passing is more than dynamic dispatch.

Ultimately, static typing is incongruent with object-oriented programming. Messages are able to be invented at runtime, so it is impossible to apply types statically. At best you can have an Objective-C-like situation where you can statically type the non-OO parts of the language, while still allowing the messages to evade the type system.

Re: Premature Abstraction

#107
Wonderful write-up. One way I really try to avoid premature abstractions is co-locating code wherever it is used. If you don't try to put everything in a shared lib or utils, you keep the surface area small. Putting a function in the same file (or same folder) makes it clear that it is meant to be used in that folder only. You have to be diligent about your imports though and be sure you don't have some crazy relative paths. Then, if you find yourself with that same function in lots of places in your code, you might have an stumbled upon an abstraction. That's when you put it in the shared lib or utils folder. But maybe not, maybe that abstraction should stay in a nested folder because it is specifically used for a subset of problems. Again, that's to avoid over-abstracting. If you are only using it for 3 use cases that are all within the same parent folder path (just different sub-folders), then only co-locate it as far up in the file tree as is absolutely necessary for keeping the import simple. Again, it requires due diligence, but the compartmentalization of the folder structure feels elegant in its simplicity.

Re: Premature Abstraction

#108
In my experience, I've always found the devil to be in the [late] details.

I have learned (the hard way), that, no matter how far I go down the rabbithole, in architecture, I am never a match for Reality.

I. Just. Can't. Plan. For. Everything.

I've learned to embrace the suck, so to speak. I admit that I don't know how things will turn out, once I get into the bush, so I try to design flexible architectures.

Flexibility sometimes comes as abstractions; forming natural "pivot points," but it can also come from leaving some stuff to be "dealt with later," and segmenting the architecture well enough to allow these to be truly autonomous. That can mean a modular architecture, with whitebox "APIs," between components.

People are correct in saying that OO can lead to insane codeballs, but I find that this is usually because someone thought they had something that would work for everything, and slammed into some devilish details, down the road, and didn't want to undo the work they already did.

I have learned to be willing to throw out weeks worth of work, if I determine the architecture was a bad bet. It happens a lot less, these days, than it used to. Hurts like hell, but I've found that it is often beneficial to do stuff I don't want to do. A couple of years ago, I threw out an almost-complete app, because it was too far off the beam. The rewrite works great, and has been shipping since January.

Anyway, I have my experience, and it often seems to be very different from that of others. I tend to be the one going back into my code, months, or years, after I wrote it, so I've learned to leave stuff that I like; not what someone else says it should be like, or that lets me tick off a square in Buzzword Bingo.

My stuff works, ships, and lasts (sometimes, for decades).

Re: Premature Abstraction

#109
> /// BAD: This mutates `input`, which may be unexpected by the caller.

> [...]

> /// GOOD: `input` is preserved and a new object is created for the output.

Neither of these are good or bad without knowing their context and understanding their tradeoffs. In particular, sometimes you want to mutate an existing object instead of duplicating it, especially if it's a big object that takes awhile to duplicate.

Re: Premature Abstraction

#110
post #15

Earlier quoted context omitted.

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 goo…

Inheritance is just plain a great way to model a lot of relationships, in my experience, because a lot of things are most easily thought of as "x is a kind of y". I am perennially baffled that people shit on inheritance so much, because I think it's incredibly useful. I find myself often missing inheritance when working in Rust, for example.

Implementation inheritance often leads to code that is just awful to read. If class C extends class B and class B extends class A, then to find out what `new C().foo()` actually does, you need to read through the whole C-B-A hierarchy, bottom to top. If `A.foo()` calls `this.bar()`, you have to start again, from the bottom of the hierarchy. With an inheritance hierarchy of depth n, every method call could be going any of n different places. With an interface, there's a single level of indirection. With composition, the code simply tells you what happens next.

If class A and class B both implement interface X, and B wants to borrow code from A, it should just call A's methods—ideally, static methods, but B can keep an instance of A if it wants. Explicit is better than implicit.

Also, I dislike ontological statements like "x is a kind of y." What does that mean? Typically, it's a claim about behaviour: "x offers method w and satisfies invariant v". But the actual blueprint here is an interface, (w,v)—not another object y. The waters get even muddier when we start talking about "is-a" vs "has-a" relationships. It feels like OOP is trying to unhelpfully distance us from what's actually going on with our code. Under the hood, inheritance is no more than syntactic sugar for composition. I think that OOP's focus on the ontological philosophy of inheritance is the reason why it led to so much bad AbstractObserverStrategyFactory-style code.

Post reply on HN