Introduction
I like my code to be predictable. When I give it a certain state and press certain buttons, I want to maximize how often it does the same thing. It’s easier to debug, it gets into weird states less often, and I find myself more inclined to cover all paths. It’s also much easier to test.
Pathways
At any given point in a codebase there’s a theoretically limited set of pathways that could be taken from there.
To keep my code predictable, I try to narrow the possible pathways while increasing the covered pathways. I’ve developed several techniques to do this. They often include declarative programming.
Techniques
State machines
State machines are particularly effective at achieving this goal in the context of interaction design. They ensure that all available actions a user can take and when they can take them are fully covered, while also protecting against “rogue” interactions or behavior.
I do this in Typescript with React and XState: State machines in frontend dev
No exceptions
Results-based programming, such as the Result type in Rust or fp-ts’s Either type, is also useful for capturing this goal in “logic pipelines,” such as handling or parsing incoming or outgoing data, or chaining different types of business logic.
Neverthrow and fp-ts are good options.
API definitions
Starting early in my career, I experimented with OpenAPI to describe my APIs. This allowed me to generate both backend and frontend code. Not allowing myself to stray from this code enforces a contract on both sides. At Palantir we had Conjure, and I’m experimenting with GraphQL at RCKIVE.
Business logic
It’s been more difficult to apply these principles to business logic itself. One solution I’ve been considering is using state machines to model the state of a data entity, such as a subscription that can transition into a canceled state.
Ideally, a solution like this would eventually allow non-coding users to define the business logic state graph. A server could host an SCXML definition of the business logic, which the frontend could use to determine possible actions for any given data state.
This obviously comes with the many dangers of allowing non-programmers to interact with actual logic. So I haven’t implemented this quite yet.
Testing
Model-based testing
I haven’t quite dived into this yet, but I’d love to test out XState’s model-based testing.