Clean code and SOLID
SOLID is five object-oriented design principles that lead to code that's easy to change and test. Combined with the fundamentals of clean code, they are the foundation of maintainable Java services at ProfessNet.
S — Single Responsibility
A class has a single reason to change. If it does too much (logic + persistence
- email), split it.
// bad — the service does everything
class ReportService {
void generate() { /* data + format + save + send */ }
}
// good — responsibilities separated
class ReportBuilder { Report build(ScanResult r) { ... } }
class ReportStorage { void save(Report r) { ... } }
class ReportNotifier { void notify(Report r) { ... } }
O — Open/Closed
Classes open for extension, closed for modification. We add new variants
through new implementations, not through if/else growing endlessly.
interface Exporter { String export(Report r); }
class JsonExporter implements Exporter { /* ... */ }
class CsvExporter implements Exporter { /* ... */ }
// a new format = a new class, without touching the existing ones
L — Liskov Substitution
A subtype must work everywhere the base type does — without surprises. We don't override a method so that it breaks the contract (e.g. throws where the base one doesn't).
ProfessNet standard: if a subclass must throw
UnsupportedOperationExceptionfrom a base method, that's a sign the hierarchy is wrong — rethink the interfaces.
I — Interface Segregation
Small, specialized interfaces instead of a single "fat" one. A client should not depend on methods it does not use.
// bad — the interface forces you to implement everything
interface ProbeOps { void scan(); void export(); void archive(); }
// good — separated roles
interface Scannable { void scan(); }
interface Exportable { String export(); }
D — Dependency Inversion
We depend on abstractions, not on concretes. High-level classes don't create dependencies themselves — they receive them through the constructor.
// good — a dependency on the interface, injected
class InventoryService {
private final ProbeRepository repository; // an interface, not a concrete class
InventoryService(ProbeRepository repository) {
this.repository = repository;
}
}
Clean-code practices
| Principle | What it means |
|---|---|
| Expressive names | runScan instead of proc, no magic numbers |
| Short methods | one method = one task |
| No duplication (DRY) | shared code extracted, not copied |
| Immutability | record, final fields, no setters where possible |
| Few parameters | more than 3-4 → wrap in an object |
// magic number → named constant
private static final int STALE_AFTER_MINUTES = 1440;
if (probe.minutesSinceLastSeen() > STALE_AFTER_MINUTES) {
probe.markStale();
}
Tip: SOLID is guidance, not dogma. Don't introduce an abstraction "just in case" for a single implementation — that's YAGNI. Refactor when a second implementation appears or a real need to change arises.
The five SOLID principles plus the discipline of clean code give services you change without fear and test without gymnastics. It's an investment that pays off with every new requirement.