Practice exercises for If You Can't Enumerate the Cases, You're Guessing.
The article introduces two tools for reasoning about boolean logic during refactoring: truth tables (for verifying equivalence) and De Morgan's laws (for rewriting predicates into readable form). These exercises let you practice both.
pnpm installEach file in src/exercises/ exports two functions: original (read-only reference) and refactored (a stub for you to implement). Read the original, understand what it does, then write an equivalent but cleaner version in refactored. The tests run against your refactored function and cover every boolean combination (the full truth table).
pnpm test:watch # re-runs on save
pnpm test # single runAll tests start failing ("Not implemented yet"). Implement refactored, save, and vitest tells you whether your version matches the original's behavior. Each test case names the full input combination, so a failure looks like:
FAIL returns true when isModerator=true, isAuthor=false, isLocked=true
That tells you exactly which row of the truth table your refactor broke.
The logic is correct but written in "reasons to reject" form (negated disjunctions, nested !). Apply De Morgan to convert them into readable "list of requirements" form.
| # | File | What to do |
|---|---|---|
| 01 | 01-submit-enabled.js |
!(!T || !E || S) : push the negation inward |
| 02 | 02-can-publish.js |
Same pattern, different domain |
| 03 | 03-deny-login.js |
!(A && B) : flip to a disjunction |
| 04 | 04-show-banner.js |
!(!F || D) : two predicates |
| 05 | 05-valid-input.js |
Nested negations with an inner || group |
Multiple if blocks that both return true, or a boolean operator that looks like it could be simplified. Some of these can be safely collapsed. Some cannot. The tests will tell you which.
| # | File | What to watch for |
|---|---|---|
| 06 | 06-can-edit-document.js |
Two paths to edit access; do they share the same gate? |
| 07 | 07-can-delete-post.js |
Moderators vs. authors: same question, different domain |
| 08 | 08-can-access-resource.js |
Three privilege levels; the repeated !isClassified looks factorable |
| 09 | 09-should-retry.js |
XOR via !== on booleans; can you simplify it to ||? |
Someone already applied the distributive law and made the code harder to read. Undo it.
| # | File | What to do |
|---|---|---|
| 10 | 10-can-merge.js |
(A || B) && (A || C) : recover the simpler form |
Each file in src/equivalence/ presents two functions: an original and a proposed refactor. Your job: build the truth table by hand, figure out which rows return true for each function, and fill in the row numbers. The tests verify your predictions against the actual outputs.
pnpm test:equivalence:watch # re-runs on save
pnpm test:equivalence # single runThe exercise file looks like this:
export function original({ P, G, S }) {
if (P) return true;
if (G && !S) return true;
return false;
}
export function refactored({ P, G, S }) {
return (P || G) && !S;
}
// YOUR WORK: which rows return true?
export const originalTrueRows = [];
export const refactoredTrueRows = [];The row numbers correspond to the standard truth table ordering listed in each file's comment header. Fill in both arrays with the row indices where you predict true, then run the tests. If your predictions are correct, you'll also see an automatic equivalence summary: either the two functions agree on all rows, or the test output names exactly which rows they disagree on.
| # | File | What's being compared |
|---|---|---|
| 01 | 01-edit-access.js |
Branch merge: P || (G && !S) vs. (P || G) && !S |
| 02 | 02-submit-form.js |
De Morgan's: !(!T || !E || S) vs. T && E && !S |
| 03 | 03-retry-logic.js |
XOR vs. OR: (T !== E) && R vs. (T || E) && R |
| 04 | 04-merge-pr.js |
Distributive law: A || (C && !B) vs. (A || C) && (A || !B) |