State
An audio player's play() method starts with switch (this.mode), and so does pause(), and so does lock(). Four modes today, one switch per method, three methods, a fifth mode lands and you're touching every method to add the same branch.
- ▸Three methods, four modes, one
switch (this.mode)at the top of every method, the same conditional ladder copy-pasted three times. - ▸Rotate the grid: each mode becomes a class holding its row, the host shrinks to a delegating slot.
- ▸
Ready.play()returnsnew Playing(), the legal edges of the machine are exactly the lines the source actually writes.
Open the audio player file. Three event methods, each one starts the same way:
class AudioPlayer { play() { switch (this.mode) { case 'ready': /* start playback, position = 0 */ break; case 'playing': /* already playing, no-op */ break; case 'paused': /* resume from pausedAt */ break; case 'locked': /* refuse, screen is locked */ break; } } pause() { switch (this.mode) { case 'ready': /* refuse, nothing to pause */ break; case 'playing': /* stash elapsedMs, stop output */ break; case 'paused': /* already paused, no-op */ break; case 'locked': /* refuse */ break; } } lock() { switch (this.mode) { /* four arms, same shape */ } } }
Three methods × four modes = twelve cells of behavior, smeared down the file in three identical-shaped switches. The cells aren't accidents, they're a 2D table. Rows: methods. Columns: modes. The file is presenting the table one row at a time, copy-pasted.
Add a fifth mode, buffering. The diff lights up every method body the same way: a new case in play, a new case in pause, a new case in lock. Three edits, one mental change. The cost of a new mode is multiplied by the method count.
The smell isn't the switch itself. It's that the same switch lives at the top of every public method. A class with one mode flag will eventually grow this shape, every event-handling method has to ask 'where am I?' before it can decide what to do.