Clean Code

Coding Principles for Better Code Quality

After 15 years of writing code, I keep coming back to the same ten principles. Not because they're trendy. Because every production incident I've lived th...

20 Jul 2024

Coding Principles for Better Code Quality

After 15 years of writing code, I keep coming back to the same ten principles. Not because they're trendy. Because every production incident I've lived through traces back to violating one of them.

Here are the ones that actually matter.

1. Follow code standards

Every language has conventions. Python has PEP 8. Java has Google's style guide. TypeScript has established patterns for naming, formatting, and structure.

Pick a standard. Enforce it with a linter. Stop arguing about tabs vs. spaces in code reviews. Consistency across a codebase matters more than any individual preference.

2. Comment the why, not the what

Bad comments describe what the code does. The code already does that. Good comments explain why a decision was made — the constraint, the trade-off, the business rule that isn't obvious.

Keep documentation up to date. Stale comments are worse than no comments because they actively mislead.

3. Build for failure

Your code will receive bad input. The network will drop. The database will be slow. Plan for it.

Use structured error handling. Log meaningful context — not just "error occurred" but what operation failed, with what inputs, and in what state. The difference between a 5-minute fix and a 5-hour investigation is the quality of your error context.

4. Apply SOLID

Single Responsibility. Open-Closed. Liskov Substitution. Interface Segregation. Dependency Inversion. Five principles that prevent your codebase from turning into a tightly coupled mess.

You don't need to memorize the academic definitions. Just ask: "If I change this class, how many other things break?" If the answer is "a lot," you're violating SOLID somewhere.

5. Make testing easy

If your code is hard to test, it's probably too coupled. Testable code has clear inputs, clear outputs, and minimal hidden dependencies.

Write tests that catch real bugs, not tests that hit coverage numbers. A test suite that passes while bugs ship is security theater.

6. Abstract, but not too much

Abstraction hides complexity behind a simple interface. Good abstraction lets you swap implementations without touching callers.

Too much abstraction hides everything. You end up with five layers of indirection to do a database query. Abstract when you have a real need, not when you anticipate one.

7. Use design patterns wisely

Patterns solve specific problems. Factory, Strategy, Observer — each has a sweet spot.

The danger is pattern fever. Don't create a AbstractSingletonProxyFactoryBean when a function would do. Use patterns when they simplify. Remove them when they don't.

8. Minimize global state

Global variables are invisible dependencies. Every function that reads or writes global state is secretly coupled to every other function that does the same.

Prefer local state. Pass data through parameters. Use dependency injection. Keep functions pure when you can.

9. Refactor continuously

Refactoring isn't a separate phase. It's part of every task. When you touch code, leave it a little better than you found it.

Technical debt compounds like financial debt. Small regular payments keep it manageable. Ignoring it leads to bankruptcy — a codebase no one wants to touch.

10. Build security in

Validate all input. Sanitize output. Use parameterized queries. Encrypt sensitive data. Apply the principle of least privilege.

Security isn't a feature you add at the end. It's a constraint you design around from the start. Every shortcut becomes a vulnerability.

The thread that connects them

These principles aren't independent rules. They reinforce each other. SOLID makes testing easier. Code standards make refactoring safer. Good error handling makes debugging possible.

You won't follow all of them perfectly all the time. That's fine. The point is to recognize when you're violating one and make a conscious decision about the trade-off.