Handbook · Personal philosophy

How I write software.

The maxims I keep coming back to, the clean-code rules I hold myself and my code reviews to, and how I think about building software with AI. Most of these were learned the hard way, usually while on call.

← Back to nchilders.com
01 / Maxims

Guiding maxims

  1. 00You will regret complexity when on call.
  2. 01Stop falling in love with your own code.
  3. 02Everything is a trade-off. There is no “best.”
  4. 03Every line of code you write is a liability.
  5. 04Document your designs and decisions.
  6. 05Everyone hates code they didn’t write.
  7. 06Don’t add unnecessary dependencies.
  8. 07Coding standards prevent arguments.
  9. 08Write meaningful commit descriptions.
  10. 09Never stop learning new things.
  11. 10Code reviews are for spreading context.
  12. 11Always build for maintainability.
  13. 12Always ask for help when you’re stuck.
  14. 13Fix root causes, not symptoms.
  15. 14Software is never finished.
  16. 15Estimates are not promises.
  17. 16Ship early, iterate often.
  18. 17Keep. It. Simple.
02 / Clean code

Clean code principles

Core principles

  • Intentional naming. Prefer domain terms. Booleans start with is, has, or can.
  • Do one thing per function or class: a single reason to change.
  • No surprises. Follow command–query separation and avoid hidden side effects.
  • Fail loudly and early, with context-rich errors.
  • Prefer composition over inheritance.
  • Comments explain why, not what. Remove dead code. Every TODO gets an owner and a date: TODO(name, date): …

Guardrails

  • Functions stay under 25 lines, at a single level of abstraction.
  • No more than 3 parameters. Beyond that, use keyword arguments or an options object.
  • No boolean flag parameters. Split the method instead.
  • Lines under 100 characters, with formatters running on save.
  • Small, focused files, each with one public responsibility.

Encouraged

  • Tell, don’t ask. Respect the Law of Demeter.
  • Wrap third-party boundaries in adapters, and write learning tests for tricky libraries.
  • Background jobs are idempotent. Migrations are safe and reversible.
  • The Boy Scout Rule: leave the code you touch a little cleaner than you found it.

Rails

  • Thin controllers. Domain services own the business logic.
  • Name services by intent, separating reads from writes: Invoice::Finder.find versus Invoice::Upserter.call.
  • Keep business rules out of model callbacks. Prefer explicit orchestration.
  • Queries live in scopes or query objects, never inline SQL in controllers.
  • Data backfills and jobs are idempotent and transactional.
  • Avoid polymorphic type checks. Prefer duck-typed interfaces, such as a recipient_count on every recipient type.

Ember

  • Data down, actions up. Routes load data; components render and handle interaction.
  • Colocate templates, code, and tests. Keep templates lean.
  • Prefer tracked state over observers, and surface user-meaningful errors consistently.

Tests

  • Unit tests target public APIs. Integration tests cover critical paths.
  • Arrange, act, assert. Descriptive names. Don’t mock internals.
  • Use factories and builders, pin behavior at the seams, and test that jobs and backfills are idempotent.

Errors & logging

  • Raise specific errors, catch them at the seams, and translate them into API responses.
  • Include request and account IDs in logs. Never log PII.
03 / AI

Building software with AI

I use AI every day, and I want every engineer I work with to use it too. It has made writing code dramatically cheaper. It hasn't made owning code any cheaper, and nearly everything I believe about AI comes from that gap.

AI is an amplifier, not an author.

It multiplies whatever is already there. Clear standards and good tests produce great output; a messy codebase gets messier, faster.

You own every line you merge.

“The AI wrote it” is not an answer in an incident review. If you can’t explain it, you can’t ship it.

Cheap code is still a liability.

Generating a thousand lines is free. Reading, testing, securing, and maintaining them is not. Ask for the smallest change that solves the problem.

Same bar, same pipeline.

AI-written code goes through the same review, tests, CI, and QA as everything else. There is no fast lane.

Context beats clever prompts.

The standards that onboard a new engineer are the same ones that make an agent useful. Write them down once and give them to every tool.

People own the decisions.

AI can draft options and argue trade-offs. Architecture, product calls, and anything touching customer trust stay with humans.

Where it shines

  • Scaffolding and boilerplate that follows patterns already in the repo.
  • Writing tests, especially listing the edge cases I didn’t think of.
  • Getting oriented in unfamiliar code, libraries, and error messages.
  • Mechanical refactors and migrations with a clear before and after.
  • A first-pass review before a human one, and drafts of docs and commit messages.

Where I slow down

  • Authentication, authorization, payments, and anything security-sensitive.
  • Data migrations and backfills that run against production.
  • Concurrency, caching, and other bugs that only show up under load.
  • Architecture and dependency choices that the team will live with for years.
  • Anything I can’t verify myself. If I can’t check it, I don’t trust it.

How I work with agents

  • One source of truth. Team standards live in one place and sync into every tool’s instructions: CLAUDE.md, Copilot instructions, Cursor rules, and the rest.
  • Treat it like a sharp junior engineer. Fast and capable, but it needs clear direction and a careful review.
  • Assumptions out loud. When requirements are unclear, the agent asks or states its assumptions so they can be corrected before merge.
  • Small steps. Small diffs and small commits, so every change is easy to review and easy to revert.
  • Tests with every behavior change. If it changes how the software behaves, it comes with automated tests.
  • Prefer boring. Consistent, obvious solutions that match the codebase beat clever new abstractions.

Hard limits

  • Never swallow errors or rescue broad exceptions just to get the build green.
  • Never disable linters, weaken tests, or strip guardrails to pass CI.
  • Never put secrets, tokens, customer data, or PII into prompts or logs.
  • No new dependencies without a clear need. Each one adds audit and upgrade work.

These are my personal principles, not the official standards of any employer.

Like how I work?

Let's build something.