Skip to content
C.W.K.
Stream
Lesson 03 of 05 · published

Some Apps Don't Have the Feature

~12 min · divergence, design, judgment, records

Level 0Loose Parts
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

A Boolean That Removes Two Columns

The queue kernel assumes every workshop reviews its work: a delegation carries whether review is on and which reviewer was assigned, and there is machinery for running rounds and recording verdicts.

One workshop has none of it. Its work is curating a memory vault, and its owner ruled that being present in the session is the review — a second opinion from another model on somebody's own memories is not a quality gate, it is a stranger's edit. So that workshop has no reviewer columns, no review verbs, and no review machinery.

The shared builder expresses this as a parameter. Passing false omits two columns; the workshop's table is exactly the shape it always had. No fork, no subclass, no dead columns carrying nulls forever as a monument to a feature this app declined.

Divergence With Reasons Is Not Drift

That workshop diverges in several other places too, and every one of them is recorded with its reason. It keeps its own storage approach because its access pattern genuinely differs. It uses the opposite direction of a name canonicalization because adopting the shared one would flip the meaning of identifiers it has already stored. It has no brief auto-commit because its briefs are written by a different mechanism.

Those entries sit in the adoption ledger as dashes with explanations attached, and the ledger says out loud what that means: converge only with a concrete reason, never for uniformity. Which is the opposite of how consolidation projects usually behave — the pull toward making the table all checkmarks is strong, and every unexplained dash looks like unfinished work.

A dash with a reason is a decision; a dash without one is a to-do. They look identical in the table and behave completely differently over time. The unexplained one gets "fixed" by a future contributor who reads it as an omission, and the deliberate difference is destroyed by somebody being helpful. Write the reason in the cell, not in a commit message.

Where the Parameter Stops Being Enough

There is a limit, and it is worth naming so the technique is not overapplied. A boolean that omits two columns is fine. A second boolean that changes which transitions are legal, a third that alters the response shape, a fourth that picks between two storage strategies — each is individually defensible and collectively they turn the shared module into a configuration language for a family of half-similar behaviors.

The rough test: a parameter should remove or name something, not choose behavior. Removing a column is removal. Naming the subject column is naming. Selecting between two claim algorithms is choosing behavior, and that is a signal that two different things are wearing one name and should be two modules — or that one of them belongs to the app.

Code

Omission as a parameter, and the point where the technique breaks·python
# GOOD: the parameter REMOVES a feature the app declined.
# The workshop with no reviewer gets exactly its historical table.
review = (
    "  review_on INTEGER NOT NULL DEFAULT 1,\n"
    "  review_brain TEXT,\n"
    if review_cols else ""
)

# GOOD: the parameter NAMES something the kernel refuses to know.
f"  {subject_col} TEXT NOT NULL,\n"


# BAD: the parameter CHOOSES BEHAVIOR. Read the accumulation, not
# any single line - each addition was reasonable when it was made.
def acquire_claim(subject, *, strategy="partial-index",
                  on_conflict="raise", lease_mode="strict",
                  audit=True, allow_reentrant=False):
    if strategy == "partial-index":
        ...
    elif strategy == "advisory-lock":     # a second app's algorithm,
        ...                               # wearing the first one's name
    elif strategy == "optimistic":        # a third
        ...
    #
    # Six parameters, and the honest reading is that this function is
    # now three functions and a config language. The shared module has
    # become the place where apps express how they differ, which is
    # exactly backwards - it exists to hold what they share.


# The refactor that recovers it: the KERNEL keeps the mechanism it
# actually shares, and the choosing moves to the app.
def acquire_claim_ok(subject: str, *, conflict_policy) -> Claim:
    """One algorithm - the partial unique index. What happens when the
    database refuses a second open claim is the APP's decision, so it
    arrives as a callable rather than as a string to switch on."""
    try:
        return _insert_claim(subject)
    except IntegrityError:
        return conflict_policy(subject)   # the app decides: raise,
                                          # wait, or break the claim

External links

Exercise

Find a shared function in your codebase with four or more parameters and classify each one: does it NAME something, REMOVE something, or CHOOSE a behavior? Count the third category. Then, for each behavior-choosing parameter, write what would have to move to the caller for it to disappear.
Hint
A behavior-choosing parameter is usually a string or enum immediately followed by a branch on its value. The refactor that removes it is almost always the same: accept a callable or an object instead of a name, and let the caller supply the behavior rather than select it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.