Chapter 02 — One Thing Well

Do One Thing Well

Tenet one: write programs that do one thing and do it well. McIlroy wasn’t asking for programs to be trivial. He was asking them to have a single, clear job. The payoff is composability: a program that does one thing can be handed off to another program that does a different thing.

This is easiest to see in the shell. Here’s a pipeline:

bash~/tutorials/
grep "error" server.log | sort | uniq -c | sort -rn

Four tools. grep filters lines that contain the word “error.” sort puts them in alphabetical order so duplicates land next to each other. uniq -c counts consecutive identical lines and prepends the count. The second sort -rn re-sorts by that count, descending, so the most common errors surface first.

None of these tools knows about the others. grep doesn’t know there’s a sort coming. sort doesn’t know what the input means. But together, they produce a sorted, deduplicated count of every unique error in a log file. That’s useful output from four tools that each do exactly one thing.

Applied to CSS

A class should do one thing. .btn establishes the base button: shape, padding, cursor, font behavior. That’s its job. It doesn’t set the color or the size. Those are the jobs of modifier classes.

css~/tutorials/
/* Does one thing: establishes button shape, padding, cursor, and base font behavior */
.btn {
  display: inline-flex;
  align-items: center;
  padding: 0.5em 1.25em;
  border: 2px solid transparent;
  border-radius: 4px;
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

/* Does one thing: applies the brand color treatment */
.btn-primary {
  background-color: var(--color-moss);
  border-color: var(--color-moss);
  color: var(--color-parchment);
}

/* Does one thing: adjusts the size */
.btn-lg {
  font-size: 1.125rem;
  padding: 0.75em 1.75em;
}

Usage:

html~/tutorials/
<button class="btn btn-primary btn-lg">Get in touch</button>

The base class is immutable. You don’t override it, you extend it. If .btn-primary has a bug, you fix .btn-primary. The base class and every other modifier are untouched.

Here’s what the alternative looks like after six months. The real cost isn’t the name. It’s the duplication: when .header-cta-button needs to change, you find it in _header.scss and fix it there, then notice .button-red-large should change too, and now you’re hunting across files.

css~/tutorials/
.button-red-large { ... }
.button-blue-small { ... }
.button-green-medium-outlined { ... }
.header-cta-button { ... } /* one-off, lives in _header.scss, duplicates most of .button-red-large */

That’s not a naming problem. It’s a single-responsibility problem. Each class is trying to encode every decision about a button into one name. When requirements change, you add a new class. The list grows and nothing is reusable.

Applied to JavaScript

A function that does one thing is easy to name. If you can’t name a function clearly in a few words, it’s doing too many things.

js~/tutorials/
// Hard to name because it does everything
function handleFormSubmit(event) {
  event.preventDefault();
  const email = document.querySelector('#email').value.trim();
  if (!email || !email.includes('@')) {
    document.querySelector('.error').textContent = 'Please enter a valid email.';
    document.querySelector('.error').hidden = false;
    return;
  }
  fetch('/api/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
  })
    .then(res => res.json())
    .then(data => {
      document.querySelector('.success').hidden = false;
      document.querySelector('.error').hidden = true;
    })
    .catch(() => {
      document.querySelector('.error').textContent = 'Something went wrong.';
      document.querySelector('.error').hidden = false;
    });
}

handleFormSubmit is doing at least five things: getting the email value, validating it, showing error messages, making the API call, and updating the DOM on success or failure. It’s named for the event, not for what it actually does. That’s a warning sign.

Refactored into functions that each do one thing:

js~/tutorials/
function getEmailValue() {
  return document.querySelector('#email').value.trim();
}

function isValidEmail(email) {
  return email.length > 0 && email.includes('@');
}

function showError(message) {
  const el = document.querySelector('.error');
  el.textContent = message;
  el.hidden = false;
}

function showSuccess() {
  document.querySelector('.success').hidden = false;
  document.querySelector('.error').hidden = true;
}

async function subscribeEmail(email) {
  const res = await fetch('/api/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
  });
  return res.json();
}

async function handleFormSubmit(event) {
  event.preventDefault();
  const email = getEmailValue();
  if (!isValidEmail(email)) {
    showError('Please enter a valid email.');
    return;
  }
  try {
    await subscribeEmail(email);
    showSuccess();
  } catch {
    showError('Something went wrong.');
  }
}

isValidEmail can be unit-tested without touching the DOM. showError can be reused anywhere a form needs to surface a message. subscribeEmail can be swapped out when the API changes. The orchestration logic in handleFormSubmit is now readable in plain English. Each function can be understood in isolation because each one does exactly one thing.

getEmailValue() is a one-liner, and someone will want to inline it. Name it anyway: handleFormSubmit reads without you tracking down what document.querySelector('#email').value.trim() actually means.

Applied to React

The same principle scales up. A <SubscribeForm> component that fetches its own data, validates input, renders feedback, and handles errors inline is four components pretending to be one. The refactored version delegates:

jsx~/tutorials/
function SubscribeForm() {
  const { subscribe, status } = useSubscribe();
  return (
    <form onSubmit={subscribe}>
      <EmailInput />
      <SubmitButton disabled={status === 'loading'} />
      {status === 'error' && <ErrorMessage />}
    </form>
  );
}

SubscribeForm orchestrates; it doesn’t implement. EmailInput can be reused anywhere an email field appears.

Reference