skip to content

Why is column-aligning consecutive assignments or declarations ("horizontal alignment") generally discouraged, and what is the modern rationale behind line-length limits?

level: middleimportance: should knowfreq 45%

answer

  1. Alignment → rename repads block → diff/blame noise
  2. Column pulls eye to values, hides names
  3. Formatters don't maintain hand alignment
  4. 80 = punch card; today = side-by-side diffs
  5. Deep indent / long line = extract

basics

~20 s

Aligning values into a neat column looks tidy but breaks the moment a name changes, producing large diffs and constant re-alignment. Line limits exist mainly so code fits side-by-side diffs and review windows without horizontal scrolling.

solid answer

~50 s

**Horizontal alignment** — padding with spaces so that `=`, types, or trailing comments line up in a column — is discouraged for three reasons: (1) it draws the eye down the column of *values*, hiding the name–value pairing that actually matters; (2) renaming one identifier forces re-padding every line in the block, so a one-line semantic change lands as a ten-line diff that pollutes blame and creates merge conflicts; (3) almost no auto-formatter maintains it, so it decays. Preferred: no padding, one space around operators, and if a list is unreadable unaligned, the list is too long. **Line length**: the historical 80 columns came from punch cards and terminals. The surviving rationale is human and tooling-based — comfortable reading width, side-by-side diff panes, code-review UIs, split editors, terminals, and web/mobile diff views all truncate or wrap long lines. Teams typically settle on 80–120 and, crucially, let a formatter enforce it rather than argue per line.

code

text · 9 lines
text
// Hand-aligned: renaming 'id' forces re-padding of BOTH other lines
int    id        = 1;
String firstName = "Ada";
long   createdAt = 0;

// Unaligned: a rename touches exactly one line in the diff
int id = 1;
String firstName = "Ada";
long createdAt = 0;

go deeper

for a junior

Say alignment looks neat but breaks on any rename and creates big diffs; keep lines short enough to read without scrolling sideways.

for a middle

Explain the diff/blame/merge-conflict cost concretely and give the modern line-length rationale (side-by-side diffs, review UIs, reading ergonomics) rather than the punch-card history alone.

for a senior

Separate indentation, intra-line spacing, and alignment as distinct concerns; note the gofmt counterexample (alignment is fine when a tool owns it) and prefer auto-wrap over hard-fail linting.

for a principal

Position it as automation policy: any formatting concern a machine can re-derive should never consume human review or CI-failure budget, and repeated limit violations should be read as design signals.

### Horizontal formatting: the three levers Horizontal layout has three separate concerns that are often conflated: 1. **Indentation** — expressing block nesting/hierarchy. 2. **Intra-line spacing** — spaces around operators, after commas, around parentheses. 3. **Alignment** — extra padding so tokens on consecutive lines form a vertical column. The first two are near-universally agreed to be valuable. The third is the contested one. ### What horizontal alignment is Given declarations of differing name lengths, alignment inserts padding so the `=` (or the type, or the trailing comment) lands in the same column on every line: ``` int id = 1; String firstName = "Ada"; long createdAt = 0; ``` versus unaligned: ``` int id = 1; String firstName = "Ada"; long createdAt = 0; ``` ### Why alignment is discouraged - **It emphasizes the wrong axis.** The reader's eye follows the aligned column — the list of *types* or *values* — which encourages reading the block as "a column of values" and skipping the names those values belong to. The name–value pairing is the information; the column is decoration. - **It creates false diffs (the decisive practical argument).** Rename `id` to `identifier`, and the padding on *every* line in the block must change to keep the column. In version control, that renders as a multi-line change for a one-token edit. Consequences: noisy code review, blame attributing unrelated lines to the renaming commit, and a sharply higher chance of merge conflicts when two branches touch the same aligned block. - **Tools don't preserve it.** Opinionated formatters generally strip or ignore hand-inserted alignment padding, so an aligned block silently degrades to a half-aligned mess on the next format-on-save. Anything a formatter won't maintain is not a sustainable convention. - **It hides a smell.** Martin's framing: if the list is long enough that alignment feels necessary to read it, the *list is too long* — the class has too many fields, or the function has too many locals. **Counterpoint worth acknowledging:** in some communities (notably gofmt aligning Go struct fields and tags, or table-like literal data such as a lookup table or a parameterized test matrix) alignment genuinely helps, and there the formatter itself maintains it — which neutralizes the diff-noise objection. The rule of thumb: alignment is acceptable exactly when a tool owns it; hand-maintained alignment is not. ### Indentation Indentation encodes scope hierarchy so the reader can perceive nesting without parsing braces. Points that matter: - **Consistency beats the tabs-vs-spaces choice.** The argument is famous and low-value; pick one, encode it in `.editorconfig` and the formatter, stop discussing it. (Tabs allow per-developer width and help some screen-reader/low-vision users; spaces guarantee identical rendering everywhere. Both are defensible.) - **Don't collapse short blocks onto one line.** Writing a whole conditional or short function on a single line hides the structure indentation exists to reveal; expand it. - **Deep indentation is a signal, not a formatting problem.** Four or five nesting levels means the code needs guard clauses, early returns, or extraction — reformatting won't fix it. ### Line length: history and the surviving reasons The 80-column convention descends from the 80-column punch card and the 80×24 terminal. Those constraints are gone; the reasons that survived are: - **Reading ergonomics.** Typography guidance on line measure suggests roughly 50–100 characters per line is comfortable; very long lines make the eye lose its place returning to the next line's start. - **Side-by-side views.** Code review diffs, three-way merge tools, and split editor panes each get roughly half a screen. Lines beyond ~100–120 characters wrap or require horizontal scrolling exactly where careful reading matters most. - **Non-editor surfaces.** Terminals, log-style patch output, web diff views, chat pastes, and mobile browsers all truncate. - **It is a pressure valve.** A line-length cap indirectly discourages deep nesting, long parameter lists, and chained expressions that do too much — hitting the limit is often a hint to extract a variable or a function. Common settings: 80 (Python's PEP 8, older C/Java guides), 100 (Kotlin official style, many Java teams), 120 (many enterprise Java/C# teams), and Prettier's default 80 as a *soft* wrapping target rather than a hard error. Note the distinction: some tools **hard-fail** on over-length lines (a linter rule), while formatters **wrap automatically** — the latter is far less friction because it never blocks on something a machine can fix. ### Practical guidance - Let a formatter own wrapping and spacing; never hand-wrap to satisfy a linter if the formatter can do it. - Allow sensible exemptions: long URLs in comments, long string literals, generated code, import lines — most linters support per-rule ignores for exactly these. - Treat repeated limit violations in one area as a design signal, not a formatting nuisance.

  • Is there any case where column alignment is fine?
    Yes — when a formatter owns it (for example gofmt aligning struct fields and tags) or for genuinely table-like data such as a lookup table or a parameterized test matrix. The objection is to *hand-maintained* alignment, whose cost is diff noise and decay; if a tool re-derives it deterministically on every save, that cost disappears.
  • Should line length be a hard build failure or an auto-wrap?
    Prefer auto-wrap by a formatter. A hard-fail linter rule makes a human do work a machine could do, and it blocks CI on something with no semantic content. Keep hard failures for cases the formatter genuinely cannot fix, and allow exemptions for URLs, long literals, and generated code.
  • Tabs or spaces — how should a team decide?
    Either is defensible (tabs allow per-reader width and help some accessibility setups; spaces render identically everywhere). What matters is picking one, encoding it in `.editorconfig` plus the formatter, and ending the discussion — mixed indentation is the only genuinely bad outcome.

Hand-aligned columns are like hand-kerning a paragraph with spaces: it looks sharp until one word changes and you must re-space the whole block — which is exactly why typesetting is left to the machine.

context