Index

Design Before the Pattern Name

Boolean Parameters Hide Different Operations

This method call is legal:

$exporter->execute($accountId, true, true);

It is also semantically wrong.

The first boolean in the retained exporter meant “source billing details from the effective payer.” The payload still targeted the original account and kept that account’s operational fields. The second boolean meant “the source’s billing mode is an authoritative instruction.”

An effective-payer export described who pays. It was not allowed to change the original account’s billing mode. The true, true combination therefore had to be neutralized by a later guard.

Two booleans made four mechanical states. The domain had three valid intents. That mismatch is a design signal stronger than the usual complaint that boolean arguments are hard to read.

Two words conceal four contracts

Named arguments make the call less cryptic:

$exporter->execute(
    accountId: $accountId,
    effective: true,
    billingModeAuthoritative: true,
);

Now the contradiction is visible, but it is still representable.

Write the matrix before choosing a replacement:

Effective sourceAuthoritative modeApparent meaningValid
nonoroutine primary refreshyes
noyesexplicit primary mode changeyes
yesnoeffective-payer refreshyes
yesyeseffective payer changes primary modeno

The last row is not merely an unusual option. It crosses two kinds of authority.

The retained correction makes that rule explicit:

authoritative mode = not effective source and requested authority

Clamping prevents the bad effect. It also admits that the method signature accepts a state the operation cannot honor.

Separate target, source, and authority

The word effective hid more than a lookup choice.

In the retained flow, three identities could matter:

target account
    receives the exported profile

operational source
    supplies identity and ordinary account fields

billing source
    supplies invoice details from the effective payer

For an effective-payer refresh, the target and operational source remained the original account. Only the billing source changed. The payload also recorded the payer relationship.

The authority flag answered a different question:

Does the billing-mode field report current receiver state,
or instruct the receiver to change it?

Routine synchronization read the receiving system’s current mode and echoed it. That prevented a profile refresh from overwriting a decision already owned by the receiver. An explicit change on the primary account carried source authority instead.

If the receiver had no account to read, the source value was a fallback, not an instruction. The receiver still had to preserve existing state if one existed.

These are protocol decisions. Calling them true and false does not make them small.

The failure was an authority bug

Retained history records why the second flag appeared. After a legacy account table was removed, routine profile exports could overwrite the receiving system’s existing billing mode.

The first repair did two things:

  • routine exports echoed the receiver’s current mode; and
  • explicit source changes carried an authority marker.

It also propagated the marker to both primary and effective exports. A follow-up narrowed the rule: only a primary export may carry billing-mode authority. Focused tests now prove an explicit primary change remains authoritative and an effective export cannot become authoritative even if its input context asks for it.

That sequence is useful because it shows the cost of an unnamed flag pair. Each boolean was locally understandable. Their interaction encoded an ownership rule which was discovered one correction later.

The artifacts do not retain an affected-account count, duration, monetary impact, or production outcome. They establish the overwrite mechanism, authority correction, and invalid combination—not its scale.

Name the three intents

The callers were not thinking in flag tuples. They were doing one of three things:

refresh the primary profile
publish an explicit primary mode change
refresh billing details from the effective payer

An API can state those intents directly:

$exports->refreshPrimaryProfile($accountId);
$exports->publishPrimaryModeChange($accountId);
$exports->refreshFromEffectivePayer($accountId);

Separate methods fit when each operation has distinct authorization, payload, or side-effect rules.

A validated intent type fits when the operations share enough transport and mapping machinery to travel through one pipeline:

enum ProfileExportIntent
{
    case RoutinePrimaryRefresh;
    case ExplicitPrimaryModeChange;
    case EffectivePayerRefresh;
}

The pipeline derives source and authority from the intent. Callers cannot ask for a fourth state because there is no enum case for it.

This is stronger than renaming booleans:

execute($id, useEffectivePayer: true, allowModeChange: true);

Named arguments explain the invalid sentence. A closed intent vocabulary makes the sentence impossible to write.

The retained implementation still uses booleans and clamps the invalid pair. The named API is a proposed refactor, not a historical outcome.

Use policies when the axes really are independent

Not every pair of flags should become separate methods.

Suppose one decision chooses a profile source and another independently chooses an output format. Every combination may be valid:

primary + JSON
primary + CSV
effective payer + JSON
effective payer + CSV

Two enums or policy objects may express that product better than four methods.

The test is composability:

  1. Does each axis have meaning on its own?
  2. Is every combination valid?
  3. Can one policy change without changing the other’s authority?
  4. Do callers choose both decisions independently?

If yes, represent two policies. If no, model the valid combined intents.

In the retained case, source selection and mode authority were not independent. Effective source prohibited source authority. A combined intent therefore matched the domain more closely than two public switches.

Trace callers by reason, not argument

Before changing the signature, inventory calls in a table:

Caller eventCurrent flagsIntended operationRequired authority
scheduled profile refreshfalse, falseroutine primary refreshreceiver
primary mode changedfalse, trueexplicit mode changesource
payer relationship refreshtrue, falseeffective-payer refreshreceiver
effective event with authority markertrue, trueinvalid requestnone

This is more useful than searching for true and false. It tells you why the call exists and which replacement it should use.

Compatibility can be staged:

old entry point
    -> validate flag pair
    -> translate to named intent
    -> invoke new pipeline

Add telemetry or a hard failure for the invalid pair before removing the wrapper. Silent normalization is safe only when discarding the flag cannot hide a caller defect. An authority request which is ignored deserves visibility.

Keep booleans where two states are one operation

A boolean is not automatically bad design.

includeArchived on a local read may describe one bounded query option. ascending may be clear inside a private sorting helper. A checkbox naturally arrives as a boolean at the delivery boundary.

The cost rises when the flag changes:

  • who owns a field;
  • which identity receives an effect;
  • whether work is a command or a query;
  • which errors are possible;
  • whether persistence occurs;
  • whether an operation may be retried; or
  • what result the caller should expect.

At that point, the two branches deserve the same design attention as two public methods.

Boolean count is only a prompt. One authority flag can be more consequential than five presentation options.

Test the semantic matrix

The public reproduction executes all four synthetic flag pairs. It proves that effective=true, authoritative=true collapses to effective, non-authoritative behavior. It then derives the same policies from three named intents and verifies that none can combine effective source with mode authority.

The fixture models the contract. It does not run the private exporter or prove that the proposed enum would reduce defects.

For production code, test more than the returned marker:

  • the original target identity never changes;
  • operational fields remain from the original account;
  • billing fields switch only for the effective-payer intent;
  • routine exports echo receiver-owned mode;
  • explicit primary change carries source authority;
  • missing receiver data remains a non-authoritative fallback; and
  • an invalid legacy tuple is reported or rejected.

A green test for execute($id, true, false) is not enough if nobody can say what those arguments promise.

Flags are compact syntax. They are poor names for different authority contracts. When a method has to erase one combination, stop documenting the booleans and name the operations the system actually permits.