Hacker News new | ask | show | jobs
by ChrisMarshallNY 1624 days ago
I find it fascinating that people are so against inheritance/polymorphism, these days.

That's one of the absolute best ways to DRY. factoring out common base classes is a classic OO exercise. It's possible to drastically reduce the size of a codebase, and the potential error exposure, by doing some simple extractions.

8 comments

To me, inheritance and polymorphism are two different things. Polymorphism is about different units implementing an interface or equivalent protocol and that rocks. Inheritance is, essentially, dumping a bunch of code into your new class, and most of the time just imposes constraints and breaks API boundaries for no good reason. After studying and doing OOP for about 5 years, I don't see the advantages of inheritance over composition. The only value I see is libraries exposing base classes that enforce behavior on user-written subclasses, stuff like React's Component or Java's HttpServlet. Seems to me we can have polymorphism without subclassing as long as the programming language has a half-decent type system.
Yeah; "favor composition over inheritance" remains good advice. "Classical" OO inheritance is brittle and often harmful.
Well, this is one of those "yes and no" situations.

The biggest argument that I hear against OO, is that "someone may misuse or misunderstand it."

I feel that this reflects the tech industry's obsession with hiring armies of relatively inexperienced developers, and then cycling through them, because we don't do what it takes to retain people.

I like composition. I use it often. It is not a "one size fits all" solution to anything; just like OO isn't.

"Reduce state" is another big rallying cry. Good advice, for algorithms, multithreaded service providers, and engines. Not so good, for UI, and, in many cases, device control.

I have spent the last couple of days, working on the login screen for the app I'm developing. It's loaded with state. That can't be avoided, and negotiating the several different states that this -seemingly- innocuous screen can have, is not for the faint of heart, but it needs to be done right, because it's the first screen our users see. It also optionally implements Sign In With Apple, which brings its own baggage. The users' experience must be absolutely frictionless, while also being very secure. The work has involved the server (PHP), the SDK (Swift), and the app, itself (also Swift). I'm not done. I keep uncovering corner cases.

I'm just not a fan of "Don't use X, because X is bad, and you're a bad programmer, if you use X." The tech industry has been dealing with this, since the GOTO wars.

Most of my projects are a hideous chimera of decades-old techniques, mixed with cutting edge stuff.

If someone wants to work on it, then they need to have their stuff together. I'm not going to "dumb it down," but I need to do a lot of documentation (I write about how I document, here: https://littlegreenviper.com/miscellany/leaving-a-legacy/).

Here's an interesting thing that happened to me, some time ago, and I decided to write about it: https://littlegreenviper.com/miscellany/swiftwater/the-curio...

> I feel that this reflects the tech industry's obsession with hiring armies of relatively inexperienced developers, and then cycling through them, because we don't do what it takes to retain people.

That is true (and it won't change, so we need to behave like it won't...), but that is not the only reason.

An inheritance hierarchy in a large program that makes sense today might not make sense in a year or two. Refactoring it is hard. Refactoring a composition-based solution is easier.

Composition-based solutions are also easier to test: inject mocks. It's a lot easier to mock a compositional dependency than it is to mock behavior that's inherited from a parent class.

Given that composition is easier to maintain and test, and that it can achieve the same functionality as inheritance, I've pretty much stopped using inheritance. And I write Java 99% of the time.

I write Apple UIKit apps. I am looking forward to using SwiftUI, which is designed to afford use of Protocol-Oriented Programming, reactive/observer stuff, and a lot of lower-state stuff. I think it would have made the work I've been doing in the last few days, much easier.

But if you use UIKit, then it's fairly important to use classic MVC (not MVVM), as that is what the SDK was specifically designed for. Trying to coerce it into other models just causes a lot of extra pain and complexity.

Also, there are models that have been specifically designed (I won't talk about which), to introduce extra complexity. These are made to allow a design to be "broken up," so that parts can be assigned to different developers.

> and it won't change, so we need to behave like it won't...

I sincerely hope that you're wrong. It's been an unmitigated disaster.

It’s possible (and IMO enjoyable) to write UIKit apps with ComposableArchitecture, a purely value-typed approach. I’ve also mostly used MVVM with UIKit since 2015, and found my apps to be stabler and easier to maintain than standard MVC apps.

In projects I lead I’ve been discouraging inheritance because I’ve found it highly detrimental to maintainability. “Base” classes invite bloat and overgeneralization, especially with inexperienced devs.

While inheritance can certainly work, especially in terms of UI development, the issue is when you are talking more abstract concepts. At that point, it can become really hard to figure out the right lines for what should be inherited vs composed.

In my experience, poorly composed code is simply easier to understand than code which poorly applies inheritance.

For me, inheritance is best used lightly. The obvious smell is when you end up with methods that don't apply to all the base classes. Or, said another way, when a super class it has a superset of capabilities for a base class.

A good example of this is Java's collections, which, for the most part, are quite good with their inheritance. However, because the base classes have mutable methods, it makes it a pain to deal with unmodifiable collections. Java's mistake is they should have had the default collection be unmodifiable and had sub classes which added mutation capabilities. List and MutableList, for example.

> After studying and doing OOP for about 5 years, I don't see the advantages of inheritance over composition.

I mean, "prefer composition over inheritance" was in the GoF book which is from 1994, and is one of the core OOP books. If you've just been studying OOP for five years, it's likely that you weren't even BORN when this was already industry-level good practice.

Universities in my country are hopelessly stuck in the early 90s :)
I don't think people are necessarily against polymorphism or factoring out common logic, but they're definitely against big class hierarchies in the old "OO" style.

Plain data structures with polymorphism via traits/interfaces/protocols seems like it's becoming the more popular way to handle these problems (I have no way to prove this, of course), and I prefer that way as well.

It's just the worst though when a dev sees two chunks of code that happen to be "shaped" the same and decide to tie them together with one abstraction. It's like seeing two different cables in a building that carry radically different signals but happen to go along the same path for awhile, and someone comes along and zip ties them together. You may have wanted to be able to route one of them completely differently, or maybe you wanted to add one more of the same cable, and now you have to cut up all the zip ties and either re-apply them (shove it into the existing abstraction), or bundle them up some other new way.

There's something to be said for preferring composition over inheritance, but in that case overly DRY code consists almost entirely of glue. As with almost anything it's a matter of choosing the right tool for the job.

The only code style advices that I've found to hold nigh universally are the following:

- The best code is no code

- Don't end classes in 'er' or 'or'

Coding paradigms are good when they let you do those things and are bad when they don't do both of those things (i.e. they result in more code or clases ending in 'er'; a class named 'Helper' is a code smell worse than sulfur dioxide)

Almost every one of my classes (in my UIKit app) is a ViewController. I don't really need to write anything more.

So they pretty much all are "er" classes. ¯\_(ツ)_/¯

So what you're saying is you've got a bunch of views and most of your programming code is spent on classes that ensures they do the right thing?

I'm not ruling out that this is an illustration of the problem with classes ending in -er.

Not saying it's great.

It's just the way you write iOS apps (at least, the original design way).

It's classic MVC. Most of the action happens in the controllers.

BTW: I totally agree that the best code I write, is the code I don't write.

Simple fix, name it HelperClass instead?
Because a lot of us learned the hard way that inheritance can very easily lead to a dark place. Changing behaviour on a grandparent object in specific circumstances related to the implementation of a child object gets really nasty.
I came to conclusion that very few people actually understand what a good OO design is and ever fewer have skills to implement it. All this talk about SOLID patterns and what is more likely to result in overengineered monstrosity than in something elegant and easy to maintain.
One way to understand OO, is to write OO code in a non-OO language (like C). When you need to basically write your own vtables, you really understand how things work.

I had to do that, in the early '90s, because I was writing an SDK that had to be implemented in C, but used object abstraction. It ended up being used for over 25 years.

I think a lot of people have been burned by inheritance done wrong.

With a lot of other techniques, if the implementation isn't perfect and you don't own the library you're using you still have a lot of power available to patch things up and make everything play nicely together. Inheritance mixes implementation details with your type checking, so in a lot of languages that can make it extremely painful to turn a body of almost-good-enough code into something that's actually usable.

You don't have to use OO though. We're using an OO-oriented language at work, but I do a lot of de-duplication of code by extracting common functions.

If needed I can delegate the specialization to an interface or in a function reference parameter (so anonymous functions can be passed) rather than in a subclass.

When implementing the interfaces I might use subclasses though, if I have some very similar variants.

Base classes tend to limit what your abstractions are though. In practice I've found that while different classes share common behaviour, it's not always so straightforward that it can be arranged into a tree. A mixin type approach, where behaviours can be attached to different classes (particularly if those behaviours don't define any new state) works much better.