Vasu Patel logoVasu Patel
← Clean by Default: Coding Standards
All chapters in this track
  1. 01Chapter 1: Why Coding Standards Matter: Readability, Onboarding, Defects, Consistency at Scale
  2. 02Chapter 2: Naming Conventions
  3. 03Chapter 3: Formatting and Style
  4. 04Chapter 4: Comments and Self-Documenting Code
  5. 05Chapter 5: Error Handling Conventions
  6. 06Chapter 6: Code and Project Organization (Files, Modules, Packages)
  7. 07Chapter 7: Writing Testable Code
  8. 08Chapter 8: Commit Hygiene: Atomic Commits, Messages, PR-Sized Changes
  9. 09Chapter 9: Language-Specific Idioms and Anti-Patterns Across TypeScript and Python
  10. 10Chapter 10: Security-Conscious Basics: Input Validation, Secrets Handling, Injection Risks
  11. 11Chapter 11: Enforcing Standards Automatically: Linters, Formatters, CI Gates
  12. 12Chapter 12: Creating, Documenting, and Evolving a Team Style Guide (Exceptions and Legacy Code)
  13. 13Chapter 13: Worked Example: Refactoring a Messy Function to Standard
  14. 14Chapter 14: Worked Example: Drafting a Style Guide Section From Scratch
  15. 15Chapter 15: Common Interview Questions About Coding Standards and Code Quality

Chapter 12 of 15

Chapter 12: Creating, Documenting, and Evolving a Team Style Guide (Exceptions and Legacy Code)

Why this matters

Every standard in this book only produces real value once it's actually documented, agreed upon, and consistently applied by an entire team — a set of unwritten conventions that live only in senior engineers' heads doesn't scale past a handful of people, and creates exactly the "tribal knowledge" onboarding friction Chapter 1 identified as one of standards' core justifications. This chapter covers building, evolving, and — just as importantly — knowing when to deliberately deviate from a real, living style guide.

Starting a style guide: don't invent from scratch

[BEGINNER] Writing a style guide entirely from first principles is both unnecessary and wasteful — mature, well-reasoned style guides already exist for most languages (Google's style guides, Airbnb's JavaScript/TypeScript guide, PEP 8 for Python) and cover the vast majority of formatting and naming decisions competently. The practical approach: adopt an established guide as a starting baseline, then document only the specific deviations and additions your team actually needs, rather than re-deriving hundreds of individual rules from scratch.

# Our Team's Style Guide

We follow [Airbnb's TypeScript Style Guide](link) as our baseline, with the
following team-specific additions and deviations:

## Deviations from Airbnb's guide
- We allow `for...of` loops where Airbnb's guide prefers array methods,
  specifically for performance-critical hot paths (see: PERF-102 incident).

## Team-specific additions (not covered by the baseline guide)
- All API route handlers must validate request bodies using our `validateSchema`
  utility before touching business logic (see Chapter 10, input validation).
- Database queries must use parameterized queries exclusively — see Chapter 10.

This approach means the style guide document itself stays small and focused specifically on what makes your team/codebase distinctive, rather than duplicating (and inevitably drifting out of sync with) a comprehensive external reference.

Documenting the "why," not just the "what," for every rule

[SENIOR INSIGHT] A style guide entry that states a rule without its rationale invites exactly the "arbitrary bureaucracy" framing Chapter 1 opened by rejecting — and, more practically, a team that doesn't understand why a rule exists cannot correctly judge when an exception is warranted (the very next topic in this chapter). Every rule should be documented with enough context that a new engineer reading it understands not just what to do, but why, ideally linked to a concrete example or historical incident where relevant:

## Rule: Always use parameterized queries for SQL

**Why:** String-concatenated SQL queries are vulnerable to SQL injection
(see Chapter 10). This isn't a hypothetical risk — see INC-3312, where a
concatenated query in the legacy reporting module allowed unauthorized
data access.

**What to do instead:** Use the query builder's parameter binding, e.g.,
`db.query("SELECT * FROM users WHERE id = ?", [userId])`.

Handling legitimate exceptions

[BEGINNER] No standard should be treated as an inviolable law — every rule in this book has legitimate edge cases where following it would cause more harm than good, and a mature style guide explicitly acknowledges this rather than pretending otherwise. The discipline that keeps exceptions from eroding the standard entirely: every exception must be explicit, justified in writing, and scoped as narrowly as possible — never a silent, undocumented deviation.

// eslint-disable-next-line no-explicit-any -- Third-party library's type
// definitions are incomplete/incorrect for this specific callback signature;
// tracked in TICKET-4471 for removal once the upstream types are fixed.
function handleLegacyCallback(data: any) { ... }

[IN PRACTICE] This inline-comment-plus-linter-suppression pattern accomplishes several things at once: it makes the exception visible in the code itself (not hidden in a separate document nobody reads), it requires the engineer to articulate a specific justification (preventing casual, unjustified suppressions), and it's searchable (grep for eslint-disable reveals every documented exception across the codebase at once) — turning what could be an invisible, uncontrolled erosion of the standard into a visible, trackable, and ideally temporary one.

Legacy code: a pragmatic, boundary-based approach

[SENIOR INSIGHT] A common, paralyzing mistake when introducing new standards to an established codebase: insisting every existing line immediately comply, which is usually impractical and can indefinitely block unrelated work. The standard pragmatic approach, sometimes called the "boy scout rule" (leave the code a little better than you found it) combined with a boundary-based requirement: new code must comply with current standards; changed code should be brought into compliance as part of the change; unchanged legacy code is left alone until it's actually touched for some other reason.

Policy, stated explicitly in the style guide:
1. NEW files: must fully comply with current standards.
2. MODIFIED files: the specific lines being changed should comply; you are
   NOT required to fix unrelated pre-existing violations elsewhere in the
   same file (though doing so in a SEPARATE commit is welcomed).
3. UNTOUCHED legacy files: no action required until they're next modified
   for an unrelated reason.

This policy prevents two real failure modes simultaneously: engineers being blocked from shipping unrelated work by an unrelated wall of legacy violations, and legacy code being frozen in a permanently non-compliant state with no realistic path to ever improving (since it improves incrementally, every time it's touched for any reason, rather than requiring a dedicated, large-scale remediation effort that competes with feature work for priority and often loses).

Evolving the style guide over time

[BEGINNER] A style guide is a living document, not a one-time artifact — new patterns emerge, past decisions turn out to be mistaken, and languages/tools evolve. Treat proposed style guide changes with the same process as any other significant team decision: proposed explicitly (often as a pull request against the style guide document itself, allowing comments and discussion the same way code review would), discussed, and — critically — once adopted, applied consistently going forward, with the legacy-code policy above governing how (and whether) it retroactively applies to existing code.

Common mistakes

  1. Writing an entire style guide from scratch instead of adopting an established baseline and documenting only genuine deviations — wasteful, and likely to under-cover edge cases a mature, battle-tested guide already handles.
  2. Documenting rules without their rationale, producing a style guide that reads as arbitrary bureaucracy and that a team can't reliably use to judge legitimate exceptions.
  3. Allowing silent, undocumented deviations from a standard — every exception should be visible, justified in writing, and ideally trackable/searchable (like an inline lint-suppression comment with a reason).
  4. Requiring full retroactive compliance across an entire legacy codebase before any new work can proceed — an unrealistic bar that either blocks unrelated work indefinitely or leads to the standard being abandoned; the boundary-based (new/modified/untouched) policy is the practical, sustainable alternative.

Checklist: building and maintaining a team style guide

  1. Adopt an established style guide as your baseline, documenting only your team's specific deviations and additions rather than writing every rule from scratch.
  2. Document the "why" behind every rule, ideally linked to a concrete example or past incident — this is what lets the team correctly judge future exceptions.
  3. Require every exception to a standard to be explicit, justified in writing, and as narrowly scoped as possible (e.g., an inline suppression comment with a reason), never a silent deviation.
  4. Apply a boundary-based policy to legacy code: new code must comply, modified code should be brought into compliance for the lines actually touched, untouched legacy code is left alone until it's next modified.
  5. Treat the style guide as a living document, evolving it through the same kind of explicit, discussed proposal process used for other significant team decisions.

Discussion / interview questions

  1. "Would you write your team's style guide entirely from scratch? Why or why not?" Strong answer: No — adopt an established, mature style guide (Airbnb, Google, PEP 8) as a baseline and document only genuine team-specific deviations and additions; writing everything from scratch is wasteful and likely to miss edge cases a mature guide already handles well.

  2. "Why should a style guide document the rationale behind each rule, not just the rule itself?" Strong answer: Without the rationale, a rule reads as arbitrary bureaucracy, and — more practically — a team that doesn't understand why a rule exists can't correctly judge when a genuine exception is warranted, since that judgment requires understanding the underlying trade-off the rule is protecting against.

  3. "How would you introduce a new coding standard to a large, existing codebase without blocking all unrelated work?" Strong answer: Apply a boundary-based policy — require full compliance in new code, bring modified code into compliance for the specific lines being touched, and leave untouched legacy code alone until it's next modified for some other reason — avoiding both blocking unrelated work and permanently freezing legacy code in a non-compliant state.

  4. "How should exceptions to a coding standard be handled, so they don't gradually erode the standard's value?" Strong answer: Every exception should be explicit and visible (e.g., an inline lint-suppression comment), justified in writing with a specific reason, and scoped as narrowly as possible — never a silent, undocumented deviation that other engineers can't discover or reconsider later.

Chapter summary / cheat sheet

  • Adopt an established style guide as a baseline; document only your team's genuine deviations and additions, rather than writing every rule from scratch.
  • Document the "why" behind every rule, not just the rule itself — this is what enables correct judgment about future exceptions.
  • Every exception must be explicit, justified in writing, and narrowly scoped — never silent or undocumented.
  • Apply a boundary-based policy to legacy code (new complies fully, modified complies for touched lines, untouched is left alone) rather than demanding unrealistic full retroactive compliance.
  • Treat the style guide as a living document, evolved through an explicit, discussed proposal process — not a one-time, frozen artifact.