The Elements of Style is a short book of rules for writing English, published long before anyone wrote software, and it remains a better guide to naming and structuring code than most books written for the purpose. This is not a coincidence. Both disciplines are about conveying a model to a reader who will not have you there to explain it, under a constraint of limited attention.
What follows is the translation, in our words. The originals are worth reading directly; they are shorter than this.
Cut what carries no weight
Strunk's most-quoted rule is about needless words. In prose the target is padding — phrases that occupy space without changing meaning. In code the equivalent is worse than padding, because a needless abstraction is not merely inert; the reader must first understand it in order to discover that it does nothing.
The everyday version:
if (user.isActive() == true) {
return true;
} else {
return false;
}
against
return user.isActive();
Everyone agrees about this one, which is why it is the least interesting example. The expensive version is structural:
interface PaymentGateway { ... }
class PaymentGatewayImpl implements PaymentGateway { ... }
class PaymentGatewayFactory { ... }
with exactly one implementation, one factory, and no second implementation planned. Three files and a layer of indirection to express what one class expressed. The interface is defensible the moment a second implementation exists, or the moment you need to stub it and your language cannot stub a class. Before then it is a needless word with a build step.
The same rule catches:
- The wrapper that forwards every call unchanged and adds one convenience method.
- The configuration option nobody has ever set to anything but the default.
- The
try/catchthat catches an exception, logs it, and rethrows it unchanged, so the log now contains the same stack twice. - Comments restating the line beneath them.
// increment iabovei++is not documentation; it is a word that must be maintained.
The test is not "is this line short". Strunk was explicit that concision does not mean brevity for its own sake — it means every element does work. A long function that does one thing plainly is better prose than a dense one-liner the reader must decompile.
Name the actor and name the act
The rule about the active voice is about restoring the missing agent. "Mistakes were made" hides who made them. Code hides agents constantly.
The literal form is a name that describes a state of affairs rather than an action:
dataProcessing() → parseInvoiceRows()
handleUser() → suspendUserAccount()
doStuff(config) → applyRetentionPolicy(config)
handle, process, manage, perform and do are the software equivalent of the passive: they name the fact that something happens without naming what. A function whose best available name is processData is usually a function that does several unrelated things, and the naming difficulty is the diagnosis rather than the problem.
The deeper form is about verb and object. A good function name contains both: what it does, to what. validate() is half a name. validateShippingAddress() is a whole one. If you cannot supply the object without a paragraph, the function has more than one.
The corollary applies to booleans and to types. flag, status, data, info, temp and result are placeholders that survived to production. So is a class called UserManager, which manages users the way a Department of Things deals with things.
Prefer the specific to the general
Strunk's rule is to use definite, concrete language: the reader remembers the particular and forgets the abstract. In code this is a claim about types.
def send(target, payload, options=None)
against
def send_invoice_email(customer: Customer, invoice: Invoice, *, cc_accounts: bool = False)
The general version accepts anything and documents nothing. Every caller must read the body to learn what options may contain, and every future change to options is invisible to the compiler and to the reader. The specific version states its contract in the signature, which is where a reader looks first.
The same instinct rejects the untyped bag — Map<String, Object>, dict, a JSON blob threaded through six layers — in favour of a named structure with named fields. It rejects a string where an enum belongs. It rejects int for a quantity that has units, and it certainly rejects a float for money, which has its own failure mode.
Generality is not free and it is not neutral. Every type parameter, every hook, every "for future use" argument is a promise to future readers that the extra capability is used somewhere. When it is not, you have written the software equivalent of a vague word: something that gestures at meaning without committing to any.
Say what is, not what is not
Positive statements are easier to hold in the head than negative ones, and negations compound catastrophically.
if (!user.isNotVerified() && !config.disableChecks)
Two negations in a condition, one of them buried in a method name, and the reader is now doing boolean algebra to establish what the happy path is. Written positively:
if (user.isVerified() && config.checksEnabled)
The rule extends to flags. A field named disableFoo produces disableFoo = false at every call site that wants normal behaviour, which reads as a double negative in configuration. Name it fooEnabled.
It also extends to guard clauses, which are the one place a negation earns its keep: if (order == null) return; at the top is a negative sentence that exists precisely so the rest of the function can be positive.
The last thing read is the thing remembered
Strunk's advice to place the emphatic word at the end of the sentence has a direct structural analogue. Readers give disproportionate weight to a function's final lines, and to the last argument in a signature.
This argues for guard clauses and early returns as a matter of emphasis, not just nesting depth. Compare a function that opens with four validation branches and ends on the work it exists to do, against one that opens with the work and trails off through error handling. Both are correct. Only the first ends on its point.
It argues for parameter order that puts context first and subject last, so call sites read as sentences: writeTo(stream, record), not write(record, stream). And it argues against the trailing block of cleanup, logging and metrics that means the last thing a reader sees in your payment function is a call to a stats collector.
The function is the paragraph
Strunk makes the paragraph the unit of composition: one topic, developed, then a break. The function is the same unit. It should hold one idea at one level of abstraction, and the level matters as much as the count.
The common failure is not length; it is mixing altitudes. A function that opens by deciding a business rule, then drops to string formatting, then does a null check, then calls a repository, is a paragraph that changes subject three times. The fix is rarely to split it at an arbitrary line count. It is to lift each altitude into its own named step, so the top level reads as an outline of the operation and each name is a heading.
Writing is rewriting
The rule everybody skips. Strunk and White treat revision as the main event rather than a tidy-up, and the reason applies exactly to code: you cannot know the right structure until you have written a wrong one and read it back.
In practice this means the first working version is a draft, and treating it as finished is the single most common cause of code that is technically correct and permanently unpleasant to work in. The revision pass is where duplicated logic becomes one function, where the four-argument signature loses two arguments to a struct, where the variable that was tmp2 acquires a name, and where the abstraction you invented in the first hour gets deleted because the second hour proved it wrong.
Tests are what make this affordable. Without them, revision is risk, so nobody revises, so the draft ships. That is the actual argument for a test suite: not defect count, but that it lets you rewrite.
Where the analogy breaks
Prose has one audience and one reading. Code has two: the human and the machine, and the machine's requirements occasionally override the human's. A hot loop unrolled by hand is bad prose and correct engineering. A named intermediate variable that clarifies a formula may or may not survive the optimiser, and in the rare cases where it does not, the optimiser wins.
The honest formulation is that clarity is the default and every departure from it needs a reason you can state — a measurement, a platform constraint, a correctness requirement. "It felt faster" is not one of those, and neither is the pleasure of a clever line. Strunk had a rule about that too.