Write Clean Classes Like a PRO
7 essential tips to produce maintainable and intention-revealing classes
50% OFF - The Complete TDD course
Are you ready to master clean code, testing and Test-Driven Development (TDD)?
I recently launched a complete TDD course containing everything you need to craft high-quality software.
Now there is a 50% OFF for the course
Get instant access by clicking here.
Motivation
Writing software is hard. But reading it shouldn’t be. Clean code isn’t just prettier. It’s easier to change, easier to test, and easier to trust.
When your classes are small, focused, and intention-revealing, your whole codebase becomes easier to work with. Clean classes lead to clean code.
Here are 6 quick principles I live by that help write clean classes:
Use Nouns for Naming
Classes represent things - or things that do things - so name them using nouns or noun phrases. Avoid using verbs for classes. This makes it easier for others to quickly understand the responsibilities of your classes.
Write code for humans to read, not just for machines to execute.
Avoid Getters and Setters
Getters and setters are evil. Period. Here’s 3 reasons why:
Violate the “Tell, Don’t Ask” principle: Don’t ask for the information you need to do the work. Ask the object that has the information to do the work for you.
Violate the “Encapsulation” principle: Objects should own their behavior and protect their own state.
Exposes implementation details: If we want to access one object from inside another, we're depending on how that object is built. If internals change, our code breaks and we have to fix it.
Hide internal state, and only expose what’s necessary. Keep fields private, and expose them via clear, intention-revealing methods.
❌ player.setHealth(100)
✅ player.increaseHealth(20)
The only exceptions are classes that are purely data-transfer objects (DTOs).
Expose a Single API
If your class isn’t coordinating others (i.e., it’s not a service or orchestrator), keep the API tight. A class should act like a tool: one entry point, one clear responsibility. It means one public method, and optionally many other private methods.
If you expose multiple public methods that do unrelated things, it's a strong signal that your class is doing too much. It’s time to split it into smaller, more focused classes.
Arrange Methods by Call Order
Nothing is worse than scrolling up and down in the code to understand the logic flow. Order class methods by how they’re called. This makes your code read like a story:
Small change, big difference.
Favor Composition Over Inheritance
Avoid using inheritance for code reuse. When we start learning programming, inheritance seems like a cool way to reuse functions and remove duplications.
But after a time, we realize it just leads to tight coupling and fragile code bases. Use composition instead. It will make the design more flexible.
Only use inheritance if there’s a clear is-a relationship between your domain objects.
Use Functions in JavaScript
JavaScript is a functional-first language at its core. It treats functions as first-class citizens. So try to stick with functions, instead of classes.
Functions are lightweight, composable, and easier to test. They don't carry hidden state or lifecycle baggage like classes often do.
For most use cases like data transformation, business logic, or even configuration, simple, pure functions are clearer and more maintainable.
Don’t Create Test for All Classes
Avoid creating a test class for each production class:
❌ XTest for XClass
❌ YTest for YClass
❌ ZTest for ZClass
It couples your tests to the implementation details. It makes them fragile. Fragile tests frequently break during refactoring. If your tests break during refactoring then they don't have much value.
Instead, you should test your internal classes via the public APIs. By doing so you end up with a robust test suite that aids refactoring.
Conclusion
Clean classes are the foundation of readable, maintainable software. They make your codebase a joy to work with, and easy to reason about.
To learn more Clean Code and testing best practices, check out my recently launched TDD course which includes:
The fundamentals of Test-Driven Development
Three real-world TDD examples in C#, TypeScript and Rust
The most essential Clean Code tips
The two schools of testing with the 5 types of mocks
Using TDD to design high-quality software
Testing legacy code
Refactoring best practices
more direct setters and getters can be useful at times too, mostly for higher level manipulation, they aren't inherently evil, but delaying the implementation till you actually need them should be the way I think
for a single player game these may not be needed unless a specific quest/story calls for that, but in VTT it's basically impossible to omit due to the freedom required by GM to manipulate the whole world, manipulating health only with damage and healing may get annoying
Even though a lot of these might be fundamental aspects of classes that several software developers already practice, I absolutely appreciate the tip about having the functions in your class appear in the order you are likely to call them. I agree that jumping up and down is irritating.