Chain of Responsibility
Every new role drops a line into checkAccess. The function has grown past 40 lines of if (user.role === '...'), and every team that adds a role edits the same file.
- ▸A 40-line
checkAccess(user, file)grows anotherifevery release, the same file shows up in every team's diff. - ▸Pull each
ifinto a class with onenextfield; the chain's order moves to where you writeapp.use(...). - ▸Same shape, two semantics: first claimer wins (Python
try/except) or every link runs pre-next-post (middleware pipelines).
Open checkAccess(user, file):
function checkAccess(user, file) { if (user.banned) return false; if (file.public) return true; if (file.ownerId === user.id) return true; if (user.role === 'admin') return true; if (user.role === 'auditor' && file.dept === user.dept) return true; if (user.role === 'editor' && file.editable) return true; // ...nine more arms added over six releases... return false; }
Each if is one team's contribution. The audit team added its line; editor added the next; share-link slotted one in further down. Every release's diff touches this file.
Three problems hide in the shape:
1. Order is part of the contract, invisible in the syntax. if (user.banned) return false only works because it runs before if (user.role === 'admin') return true. Swap them and a banned admin gets through.
2. A new check edits a growing file. The cost of a new role isn't 'one new line', it's 'one new line in a file every team has been editing for six months'.
3. The trailing return false is silent. A permission you forgot to write returns 'deny' with no message. Nobody notices until a user complains.
The shape isn't unique to access. Validation cascades, content-negotiation, error recovery, exception handling, anywhere a request gets routed by 'who can answer this?' grows the same if-ladder.