Tutorial 6: Review for Readability
- Contributor
- 4 days ago
- 3 min read
Code is read 10x more than written. A reviewer who optimizes for the reader does the team a long-term favor.
Step 1: The Reader Test (5 min)
When reading code, ask: "Could someone new to the team understand this in 6 months?"
If no: feedback opportunity. Not necessarily a blocker — but a signal.
You're not nitpicking style. You're protecting future maintainability.
Step 2: Naming (15 min)
Bad names tax every future reader.
✗ data
✗ x, y, z
✗ tmp
✗ result
✗ handleData
✗ doStuff
✓ activeUsers
✓ pendingOrders
✓ cachedBalance
✓ filterExpiredSessions
A function called process(data) is meaningless. validateEmailAddress(input) reads itself.
Exception: short names for short scope. for i in range(10) is fine.
Step 3: Function Length (10 min)
A 200-line function:
Holds too much in your head
Mixes concerns
Hard to test
Rule of thumb: under 30 lines.
Long function = refactoring opportunity. Comment:
"Consider splitting this into validateInput, computeResult, and notifyCaller."
Not blocking unless the function is a maintenance nightmare. Suggestion.
Step 4: Comment the Why, Not the What (10 min)
# Bad
i = i + 1 # increment i
# Good
i = i + 1 # skip the first row (header)
Code shows what. Comments add why.
If you find comments explaining what — the code probably needs renaming.
Step 5: Avoid Nesting (10 min)
# Bad
def process(items):
if items:
if items[0].is_valid:
if items[0].user:
if items[0].user.active:
return items[0].user.id
return None
# Better — early returns
def process(items):
if not items:
return None
item = items[0]
if not item.is_valid:
return None
if not item.user or not item.user.active:
return None
return item.user.id
Deep nesting = mental load. Early returns flatten.
Step 6: Single Concern (10 min)
# Bad — many things at once
def save_user(user_input):
parsed = json.loads(user_input)
if not parsed.get("email"):
raise ValueError()
user = User(name=parsed["name"], email=parsed["email"])
db.save(user)
send_email(user, "welcome")
log(f"Saved: {user.id}")
return user.id
# Better
def save_user(user_input):
parsed = parse(user_input)
validate(parsed)
user = create_user(parsed)
notify_new_user(user)
return user.id
Each function does one named thing.
Step 7: Magic Numbers (5 min)
# Bad
if user.attempts > 5:
lockout(user)
# Better
MAX_LOGIN_ATTEMPTS = 5
if user.attempts > MAX_LOGIN_ATTEMPTS:
lockout(user)
Named constants explain intent. Easier to change.
Exception: 0, 1, -1 are fine.
Step 8: Consistency (5 min)
In a file:
Mixed naming styles (getUserById and fetch_user(id))
Mixed string types (single quotes here, double there)
Mixed import styles
Reviewer flags: "let's be consistent with the rest of the file."
Linters catch most. Humans catch the rest.
Step 9: Test Readability (10 min)
Tests are documentation:
# Bad
def test_user():
assert do_thing(...) == True
# Better
def test_user_with_expired_token_returns_unauthorized():
response = client.get("/me", headers={"Authorization": "Bearer expired"})
assert response.status_code == 401
assert response.json()["error"] == "token_expired"
Name tells the story. Body confirms it.
Step 10: Don't Block on Readability Alone (5 min)
Code can be ugly and correct.
Reviewer judgment:
Correctness issue → block
Major readability issue with future maintenance impact → strong suggestion
Minor style issue → comment for the author's discretion
The goal isn't perfect code. It's code that works and future engineers can understand.
What You Just Did
Readability review: reader test, naming, length, comments, nesting, single concern, magic numbers, consistency, test names, judgment on blocking. Long-term maintainability.
Common Failure Modes
Block on every readability nit. PR never merges.
Skip readability; only correctness. Codebase rots.
"What" comments. Noise; rot when code changes.
Mixed style in same file. Cognitive overhead per line.
Inconsistent naming. Reader can't predict next pattern.


