Code Smells
On this page · 3 min read
Revised October 19, 2026. What changed
A code smell is a reason to investigate, not a proof that a design is wrong. Ask what change is becoming difficult, which invariant is obscure, and whether a smaller refactoring makes that behavior easier to verify.
Extract the decision that actually varies
Two methods that scan the same list for different conditions may share a predicate. Java collections expose streams; a List itself has no filter method. A potentially absent result belongs in Optional<Integer>, not an invented sentinel.
static Optional<Integer> first(List<Integer> values, Predicate<Integer> test) {
return values.stream().filter(test).findFirst();
}
Call first(List.of(1, 4, 6), x -> x % 2 == 0). The result contains 4. For an all-odd list it is empty. A caller can choose an explicit fallback or handle absence; calling get() without checking merely moves the failure.
Large methods and long parameter lists
Length is a clue, not a universal threshold. A long table of independent rules may be easier to inspect than a maze of tiny wrappers. Split a method when separate responsibilities have separate reasons to change. Group parameters when they form a meaningful value with shared validation, not merely to reduce the visible argument count.
For an address, a value object can enforce required fields and avoid swapping two same-typed arguments. A bag of unrelated options that bypasses those checks is not an improvement. There is no useful universal API limit derived from a memory-span experiment.
State and ownership
Global mutable state makes dependencies and test order harder to see. Prefer explicit ownership and pass the dependency that a calculation actually needs. A pure utility function or an immutable constant does not have the same problem as a process-wide mutable registry.
Inheritance and public contracts
Extending a collection merely to reuse storage can expose operations your domain cannot support. Composition lets a domain object expose only its valid operations. Inheritance can still be appropriate when the subtype preserves the parent contract. Judge substitutability, not whether a class happens to contain getters or static methods.
Refactor against observable behavior
Before changing a suspected smell, name a concrete failure: duplicate rules drift, invalid states can be constructed, or a feature touches unrelated modules. Write a behavior check for that failure, make one refactoring, and retain the public contract. Abstraction has a cost too.
The workbench includes the complete LegacyExamples.java program, imports, assertions, and a run command: java LegacyExamples.java with JDK 17 or later. The fragments below are extracted from that same tested source.