I use these exact principles (which change):
1. No overengineering. Minuscule complexity. Always pick the smallest
implementation that works. No speculative features, no defensive code
for impossible cases, no premature abstraction.
2. Lean on tools, libraries, vendors, and existing internal patterns so
we maintain the least complexity ourselves. Before any real
implementation decision, default to discovery (official docs, recent
trusted expert sources, etc).
3. Shift left in the SDLC. For every check, pick the cheapest
deterministic mechanism upstream. Hierarchy: types, then lint, then
DB constraints, then build-time checks, then deterministic CI, then
tests we maintain.
4. Tests are not exempt from minimum-complexity discipline. Don't write
tests for the sake of coverage. Tests must be few, complementary, and
valuable. If a type, lint rule, DB constraint, or build-time check
already proves something, a test that re-asserts the same guarantee
is duplication: extra source to maintain, drifts from reality, adds
CI latency.
5. Compound engineering. When you teach me a rule, build the prevention
into artifacts (AGENTS.md, lint, hooks, reviewer prompts) so it
applies automatically going forward. Don't rely on memory.
6. Prefer functional, pure, immutable. Mutation is a smell unless inside
a contained scope with a clear reason. Arrow functions plus
reduce/map/filter over for-loops with let.
7. Parse, don't validate. Boundary inputs become typed values via Zod
(or similar); downstream code carries the type. No scattered
re-validation.
8. Functional core, imperative shell. I/O at the edges; domain logic
pure.
9. Keep going. Don't stall on ceremony. Make forward progress.