Hacker News new | ask | show | jobs
by geofft 1921 days ago
What's the alternative here? If you had to change a codebase's usage of ints to uuids, should the original author have used dependency inversion and required an IdentifierFactory that was ints at the time so you could just swap out the implementation? And if they did - why wouldn't they have just used UUIDs in the first place? You're betting on the fact that the original author anticipated a particular avenue of further change, but also made the wrong decision for their initial implementation, which seems like the wrong bet. If they made the wrong decision, they almost certainly didn't anticipate the need for another one, either.

And how long would it have taken for the original author to use an IdentifierFactory instead of ints and write meaningful tests for it? Less than two days?

2 comments

In the uuid case, the person had no choice. Remember, these are principles, not laws, and at some point your system is making concrete choices. Choosing UUIDs isn’t necessarily a OO design problem. He was just highlighting how expensive it can be if you require changes to your fundamental classes to extend or change behavior. In the identifier type case, it’s rare that folks abstract this stuff away. Though: I do know a LOT of systems that use synthetic identifiers for this exact purpose, as larger enterprises tend to deal with many more different identifier types from different integrations, from a DB type that can’t hold new identifiers, because IDs need to be sharper/distributed etc. So yeah, it’s a principal, and one should choose if it’s worth the upfront cost for its benefits.

OCP though more commonly refers to: 1. Building small, universally useful abstractions first 2. Extending behavior of that abstraction or system by writing new code rather than changing published code directly.

This is trivial when you have a few patterns under your belt. Template factories, builders, strategies, commands. I mean, while it’s not the best idea in most cases, even just inheriting a parent class and giving a new concrete new behavior is still better than changing something fundamental to the system.

Like has been said 999 times in this thread, software isn’t black and white. You have to make choices about where you go concrete and where you abstract, and gauge that against risk factors. A somewhat complex class you expect to go away in a couple months? Make it a god class that anyone who wants to can scan through. A fundamental class that will be used by hundreds of developers and underpin most operations in a production system where 5 minutes of downtime costs tens of thousands of dollars? It’s worth the upfront cost to build with these standards.

Changing ints to UUIDs is a classic example of the Primitive Obsession smell, and the solution is to wrap primitives in a type representing their semantic meaning, such as “ID,” or “PrimaryKey,” not to use a factory. That way, when you need to change the underlying type from int to UUID, you only need to do it in one place.
Indeed. Unfortunately, in some languages – like Java or C#, it is harder to do without incurring a significant cost (boxing/unboxing) than in languages that allow type aliases/typedefs.
In theory, yes, but in practice performance is dominated by network and (less often) algorithms. The cost of boxing/unboxing doesn’t even register except in rare cases, which can be specifically coded for.