| "I'll document the right way to call it." | If the docs have to warn callers, the interface is wrong. Change the signature so the wrong call won't compile or won't typecheck. (97/55) |
"I'll add a bool strict parameter — easier than two methods." | A flag that flips the meaning of an operation is two operations wearing one name. Split it; give the caller real vocabulary. (97/19) |
| "I'll expose the field with a getter and setter — callers know what to do." | You've pushed the business rule into every caller. Encapsulate the behavior on the type that owns the state. (97/32) |
| "These two call sites do the same four lines — extract a shared helper." | Same shape today, different pressures tomorrow. Localize until a real shared concept emerges and earns a name. (97/7) |
"It takes a string — callers can pass whatever." | Strings and floats are an invitation to pass the wrong thing. A named type closes the door on the Mars-Orbiter class of bugs. (97/65) |
| "The state is implicit — callers will know the order to call methods." | Implicit state means callers can call ship before pay. Make the state a type or guard every operation that depends on it. (97/84) |
"I'll mark everything final / sealed to keep my options open." | Locked-down APIs are untestable for the code that uses them. Write a test of a caller before you decide what to seal. (97/35) |
"Callers can if on the type tag — it's only three cases." | Three cases become thirty, scattered across every caller. Move the choice inside the type with polymorphism. (97/59) |
| "If they pass bad input, they'll see a clear error message." | Errors are a sign of broken communication, not a feature. Eliminate the error condition or accept the common formats. (97/66) |
| "It's an internal API — the rules don't apply." | Internal today is exposed tomorrow, and the wrong-use bugs accumulate either way. The rules apply. (97/55) |
| "This class is the right home for it — it's already imported here." | Wedging a second concern onto a class that's already imported gives every caller a surface they don't all need. The exported surface should have one reason for callers to depend on it; split it. (97/76) |
| "I'll add another thin method that just forwards to an internal." | Shallow modules pay no abstraction tax. Make the module deep — fewer, more-powerful methods that hide implementation, not more thin pass-throughs. (Ousterhout/DeepModules) |
"I'll throw NotFound if the file doesn't exist on delete." | Define the error out of existence: idempotent delete, clamping substring, Option<T> lookup. Fewer error paths the caller has to remember. (Ousterhout/DefineErrorsOutOfExistence) |
| "I'll subclass and override the method to throw — callers shouldn't use it." | Subtype must be substitutable. If the override breaks caller assumptions, the hierarchy is wrong. Prefer composition. (Liskov/LSP) |
"I'll validate the input and pass the raw string downstream." | Parse, don't validate. Return a parsed domain type from the boundary; downstream code receives the proven shape, not the raw primitive. (King/ParseDontValidate) |
| "Callers will only use the documented behavior — internals can change freely." | Hyrum's Law: any observable behavior will be depended on by someone. Reason about the new API as if its current observable behavior were private. (Hyrum/Law) |