Domain Model Pitfalls in OOP and FP

(2020. 08. 31.)

The encouraged domain modeling patterns of both Object-oriented Programming (OOP) and Functional Programming (FP) will eventually break our model of reality. Let’s see how.

Old-school OOP encourages shoehorning all the alternatives under subclasses of a common interface, and users operating in an alternative-agnostic way. FP prefers encoding the alternatives explicitly and publicly as Algebraic Data Types (ADTs), which are kind of enums on steroids.1

What code modifications are convenient? Subclasses make it easy to add a new case - we just add a new subclass, and implement the required methods locally. ADTs make it easy to add a new operation - we add a new function, and cover the existing cases locally.

What modifications are cumbersome? Adding a new method to all subclasses spreads out over the source code, leading to a shotgun diff. Similarly, adding a new case to ADTs needs touching all the different operations on that ADT.

What programming patterns are incentivised by these extremes then?

Subclasses whisper that you can add more cases any time, and they will nicely fit under this uniform interface. ADTs whisper that you can add new operations any time, and your original choice of modeling reality will be suitable to perform them all.

But our models of the reality are just that - incomplete models, bound to fail if stressed too much.

As more subclasses are added, it is more likely that one of them won’t conform nicely to the existing interface. Then we try to fix by adding a partial method which is unsupported on some subclasses, or by hacks like downcasting (Webdriver anyone?).

As more operations are demanded, it is more likely that the existing partitioning of the world into the nice distinct cases won’t suit that operation anymore. Then, as a fix, we can introduce more specific cases, or make existing ones more general - leading to ambiguity, bloat and mental load for the existing operations.

This ancient Chienese categorization of animals - likely made up by Borges - illustrates the end result of those piling hacks with shocking accuracy.

  1. those that belong to the Emperor,
  2. embalmed ones,
  3. those that are trained,
  4. suckling pigs,
  5. mermaids,
  6. fabulous ones,
  7. stray dogs,
  8. those included in the present classification,
  9. those that tremble as if they were mad,
  10. innumerable ones,
  11. those drawn with a very fine camelhair brush,
  12. others,
  13. those that have just broken a flower vase,
  14. those that from a long way off look like flies.

What should we do then?

OOP lures us with the false promise that everything is uniform under the Sun. FP lures us with the false promise that we are smart enough and able to precisely model reality. I assume we are naturally drawn to one extreme or the other based on our mindset. For example, I tend to think in edge-cases, so the explicitness and non-uniform representation of ADTs appeal to me.

What should we do then? There are two main factors - economic realities of programming, and personal tendencies (when contrasted with team preferences). Be aware of the pitfalls of your modeling, realize when the burden of maintainability exceeds the cost of a refactoring, and then act. Be aware of your natural tendencies, and slightly counterbalance. Don’t oversimplify, but don’t overcomplicate either.

  1. It was observed that a subclassing and ADTs are duals: they encode the same information and can perform the same work. They differ in the way of representation, and what maintenance work is convenient to perform on them. Alternative subclasses correspond to alternative cases. Pattern-matching (or folding) an ADT corresponds to a Visitor on the subclasses.↩︎