I3.13.2unrepresentable illegal statesdesign

If the data model can hold an illegal combination, it will

Aliases: make illegal unrepresentable · dirty combination · extra product

What it is

Legal states are nodes on the machine: “unsubmitted / submitting / success / failure”, for example. If the data structure can also hold “success and failure”, “submitting and saved and cancelled” — combinations that belong to no node — those combinations will appear in the run. Appearance is not a long shot. It is what the structure allows: given assignment, concurrency, deserialization, the combination will be written. Making illegal states unrepresentable is making the set of nodes the entirety of what the type can express, not a wish in a document.

This leaf is “if it can hold it, it will”. Why people often blow the combination space open with a cluster of booleans is the next cut.

Why it happens

The possible values of the data make a space. The machine’s legal nodes are a subset of that space. The difference is illegal states. Once the difference is non-empty, every write path — a user act, a network callback, a read-back from disk, another optimistic assign — is a chance to fall into it. Defence by convention (“these two fields should not both be true”) dies at the first missed assignment. After it dies there is no type error, only a behaviour error: the screen paints a mixture never designed, later transitions look for an edge on a graph that has no such node, and become undefined transitions.

So an illegal state is not “occasionally dirty data”. It is the product of modelling a superset. The extra dimensions the model grew will be filled at runtime. Shrink the space — one enum instead of a cluster of independently flippable flags, mutually exclusive records instead of optional fields that can all be filled — and the difference goes empty. Illegal states move from “please do not write this” to “cannot be written”.

Where it stops holding

External protocols, historical database columns, logs will leave old shapes; deserialization must face combinations that were legal and are illegal now. That is adaptation at the boundary: fold them at the door into today’s legal node or an explicit “corrupt, needs repair”. Do not feed the old shape straight to the current machine. Debuggers and internal probes may see a wider space than the UI; that layer is not user state. An intentional superset (a draft object allowed to miss fields) should make “incomplete” itself a legal node, not impersonate submitted with missing fields. In distribution, two legal snapshots merged may assemble an illegal — a reason to promote the merge unit to the constraint, not a reason to let the superset live on.

Applying it

  • List legal nodes as one enum or a set of mutually exclusive records, so objects in storage and in memory can only be one of them.
  • At deserialization and network-decode doors, judge unknown combinations as error and stop in a repairable state. Do not “fill existing fields as best we can”.
  • In review, a comment “these two fields must not both be true” is a type waiting to be collapsed.
  • How to check: fuzz or hand-write a saved object: success=true and failed=true together, or progress=100 and status=pending. If the object can be created and rendered, the space is larger than the node set. Collapse to a single enum and pour the same bytes: it should fail at the door, not paint “a finished spinner”. Read a deprecated combination from an old file: it should enter repair, not be treated as a current node.

Related

  • Same group: I3.13.1 Every legal transition must have a defined outcome · I3.13.3 Independent booleans mint illegal states that a single enum would forbid · I3.13.4 The UI must be a projection of the state machine, not a visual guess
  • Nearby: I3.11 Sync conflict merge · I3.09 Optimistic update rollback
  • Search terms: illegal state · unrepresentable · make illegal states unrepresentable

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/I3.13.2