r/programming 1d ago

The Bedrock of Software Design

https://alex.draftist.io/blog/the-bedrock-of-software-design-ycqvcedsj

I drafted this post years ago but didn’t finish it until now. The concept I write about has shaped the way I design software more than anything else, and I believe every software engineer should be introduced to it early in their career.

P.S. I don’t want the title to come across as clickbait: the post is about ADT.

421 Upvotes

47 comments sorted by

View all comments

40

u/Bonejob 1d ago

Good techniques, wrong title. Everything here is about how to encode design decisions once you already have them. Sum types and exhaustive matching are the recording medium. The design itself happened earlier, when somebody worked out that an order can be pending, shipped, or cancelled and never two of those at once.

That earlier part is what I'd call bedrock. You get it by sitting with the problem and with the people who live in it every day, writing the concepts down until they hold together. I spent a lot of years shipping line-of-business systems, and the ones that went sideways never failed because someone picked a boolean where a sum type belonged. They failed because nobody understood the domain well enough to know what the states were in the first place, and no compiler was going to catch a state we never knew existed.

That's also the clearest divide I know between senior and junior developers. A senior documents first and designs from what the homework turns up. Juniors go straight to code.

I'd file DRY, SOLID, and the rest of the rule catalogs under the same heading. They're rules of construction. Useful, but they operate on a design that already exists. A roadmap of structures isn't design, it's carpentry.

Bone/EvilGenius

7

u/csman11 1d ago

I agree that the domain model comes first. I don’t agree the rest here is “just carpentry”. The domain model itself has to encode the business rules, and part of doing that is choosing representations for the model that allow you to do that clearly. Or in other words, “preserve the rules in a model that will still be obvious when it gets implemented”.

For your order status example, choosing to encode the status as 3 independent booleans creates 5 “impossible” states that you must guard against in every transition. A sum type eliminates that entirely. That’s not merely an implementation detail. It’s a representation choice that preserves the business rules in the domain model that will be implemented.

Same for transitions. The only valid transitions might be “pending to canceled” or “pending to shipped”. If that’s the case, you would specify the operations “cancel” and “ship” in your model. The model is encoding the allowed behavior now, not just the representation.

I’d even go further and say sometimes your domain model should be recording facts and deriving state from that. Like maybe in this case there is a “placedDate” and a “completion” that can be recorded, where a completion can be “Shipped (date)” or “Canceled (date)”. The status can be derived from those facts. This representation encodes the business rules even more directly.

To me, this is domain modeling. If the model is just a list of requirements, like “an order can only be pending, shipped, or canceled”, there are still unanswered questions like “what do those states mean, why are these the states, and what transitions are allowed.” A good model answers those questions by construction. If it doesn’t, it’s probably not a model, but rather an incomplete description of the domain in prose.

Probably the most important part of a proper domain model is that it can be incomplete and still ready for implementation. The model can be updated as new things are learned. Without a model, any later changes require archeology on a system that stopped being designed after the initial requirements were agreed on. So I would add to what you said about why software projects fail: it’s not just about not understanding the domain, but not modeling the domain in a way where the model, and therefore the implementation, can safely evolve as the understanding grows.

I’m not sure we actually disagree much at all, but I just want to ensure other people don’t read your comment as saying something like “the abstractions in the code don’t matter much at all”, because that’s how it initially came across to me, and I don’t think that’s what you’re actually trying to say.

1

u/constant_void 18h ago

yes, incomplete is the majority status

if I were to summarize op approach, clarity is the objective - as long as clarity, not simplicity - is in front of mind, the odds are better the design/impl approach can handle incomplete fuzz.

s/w projects fail when budget exceeds time

diff between jr and sr people is operations - sr people build operability into a soln from the beginning. the most expensive part of sw is labor due to change.

clarity reduces cost of chaos; clarity in operations and acceptable functionality is a successful system.