🛸 Hitchhiker's Guide — Phase 05: Code Quality and Engineering Discipline
Read this if: You write code that passes tests but gets rejected in code review — or you've been on a project where every PR battle is about style rather than correctness. By the end you should understand the three-layer automated quality stack every serious Apache project runs, know what each layer catches (and what it misses), and be able to write a structured code review comment that will actually get addressed instead of ignored.
0. The 30-second mental model
Three quality layers. Each catches a different class of problem:
Layer 1 — Style (Checkstyle)
"Does the code look consistent across all contributors?"
Catches: wrong names, long lines, missing Javadoc, magic numbers, unused imports
Misses: anything about what the code does
Layer 2 — Static Analysis (SpotBugs)
"Does the bytecode have recognizable bug patterns?"
Catches: null dereferences, resource leaks, mutable state exposure, deprecated APIs
Misses: logic bugs, wrong algorithms, performance regressions
Layer 3 — Human Review (dev@ mailing list / GitHub review)
"Is the design sound? Is the API contract right? Are edge cases handled?"
Catches: everything layers 1-2 miss
Misses: nothing — but it's slow and expensive
All three complement each other. A green Checkstyle + SpotBugs build doesn't mean the code is correct. A thorough human review doesn't mean the formatting is consistent. Run all three.
1. Checkstyle
1.1 What it does
Checkstyle parses the Java source into an AST (Abstract Syntax Tree) and walks each node, checking it against a set of configured rules. It runs during the Maven build via maven-checkstyle-plugin.
The configuration file (checkstyle.xml) specifies which rules to enforce and with what parameters. Projects either:
- Extend a well-known style (Google Java Style, Sun Coding Conventions)
- Write project-specific rules, often derived from one of the above
Checkstyle fails the build when failsOnError=true (the default for mvn checkstyle:check).
1.2 Rules You Must Know
| Rule | What it catches | Example violation |
|---|---|---|
ConstantName | static final fields not in UPPER_SNAKE_CASE | static final int maxRetries = 3 |
UnusedImports | Imported types not referenced in the file | import java.util.ArrayList; never used |
MagicNumber | Numeric literals other than -1, 0, 1, 2 | Thread.sleep(5000) |
MissingJavadocMethod | Public methods without a Javadoc comment | public void submit(Runnable task) with no /** |
LineLength | Lines longer than the configured max (typically 100) | A log message with 3 concatenations on one line |
VisibilityModifier | Non-private instance fields | protected String name; in a concrete class |
ModifierOrder | Modifiers in wrong order | static public final instead of public static final |
1.3 Running in Maven
# Check only — does not compile, just style checks
mvn checkstyle:check
# Check during the verify phase (standard CI mode)
mvn verify
# Check and generate an HTML report (does not fail build)
mvn checkstyle:checkstyle
# Report at: target/reports/checkstyle.html
1.4 Suppressing False Positives
Two mechanisms:
Inline suppression (for one-off exceptions):
// CHECKSTYLE:OFF MagicNumber
private static final int FIBONACCI_MULTIPLIER = 0x9e3779b97;
// CHECKSTYLE:ON MagicNumber
Suppression XML (for systematic exceptions, e.g., test classes):
<!-- suppressions.xml -->
<suppressions>
<suppress checks="MagicNumber" files=".*Test\.java"/>
</suppressions>
Use suppressions sparingly. A rule with 100 suppressions is not enforced — it's just noise.
2. SpotBugs
2.1 How it works
SpotBugs analyzes compiled bytecode (.class files), not source code. It applies ~400 bug patterns — each is a recognizable sequence of bytecode instructions or a data-flow property that typically indicates a bug.
Because it analyzes bytecode, SpotBugs can catch bugs that exist after inlining, optimization, and type erasure — things Checkstyle never sees.
2.2 Bug Categories
| Category prefix | Meaning | Examples |
|---|---|---|
NP_ | Null pointer dereference | NP_NULL_ON_SOME_PATH, NP_ALWAYS_NULL |
EI_ | Expose internal representation | EI_EXPOSE_REP, EI_EXPOSE_REP2 |
MS_ | Mutable static field | MS_MUTABLE_COLLECTION, MS_SHOULD_BE_FINAL |
DM_ | Deprecated method / bad idiom | DM_NUMBER_CTOR, DM_STRING_CTOR |
RCN_ | Redundant null check | RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE |
OS_ | Open stream / resource leak | OS_OPEN_STREAM, OS_OPEN_STREAM_EXCEPTION_PATH |
IS2_ | Inconsistent synchronization | IS2_INCONSISTENT_SYNC |
2.3 Confidence Levels
| Level | Meaning | Should you fix it? |
|---|---|---|
| HIGH | Very likely a real bug | Always |
| MEDIUM | Possibly a bug, but context needed | Usually yes |
| LOW | Speculative; many false positives | Review, but filter in CI |
Apache projects typically set CI to fail only on HIGH confidence bugs. MEDIUM is reported but not blocking. LOW is usually suppressed.
2.4 Running SpotBugs in Maven
# Check — fails build on threshold violations
mvn spotbugs:check
# Generate HTML report without failing
mvn spotbugs:spotbugs
# Report: target/spotbugsXml.xml + target/spotbugs.html (with spotbugs-maven-plugin)
# Launch the SpotBugs GUI viewer
mvn spotbugs:gui
2.5 The Four Bugs You Must Know
NP_NULL_ON_SOME_PATH — a code path exists where a method is called on a potentially-null reference:
// BUGGY: Map.get() returns null when the key is absent
return properties.get(key).trim(); // NPE if key not present
// FIX:
String value = properties.get(key);
return (value != null) ? value.trim() : null;
EI_EXPOSE_REP — a getter returns a direct reference to an internal mutable object:
// BUGGY: caller can mutate the internal map
public Map<String, String> getProperties() { return properties; }
// FIX:
public Map<String, String> getProperties() {
return Collections.unmodifiableMap(properties);
}
MS_MUTABLE_COLLECTION — a static final field holds a mutable collection:
// BUGGY: 'final' prevents reassignment, but the list can still be mutated
public static final List<String> REQUIRED_KEYS = new ArrayList<>();
// FIX:
public static final List<String> REQUIRED_KEYS = List.of("host", "port", "db.name");
DM_NUMBER_CTOR — using a deprecated new Integer(int) constructor:
// BUGGY: deprecated since Java 9, removed in Java 17
Integer boxed = new Integer(value);
// FIX:
Integer boxed = Integer.valueOf(value); // uses cached instances for -128..127
3. Code Review in Apache Projects
3.1 The Review Workflow
Apache projects do code review on GitHub Pull Requests and (for controversial changes) on the dev@ mailing list. The workflow:
- Contributor opens a PR with a JIRA issue number in the title
- Committers review: automated CI runs first, then humans review after CI is green
- Reviewer leaves comments — questions, suggestions, required fixes
- Contributor addresses each comment with either a fix or a reasoned disagreement
- A committer with write access merges when they're satisfied (often signaled by "+1")
3.2 What Makes a Good Review
A good review is:
- Specific: point to the exact line and explain the concern
- Actionable: "consider using
Collections.unmodifiableMap" not "this is bad" - Prioritized: distinguish blockers (correctness, API contract) from suggestions (readability) from nits (style)
- Respectful: the tone is collegial, not dismissive — the contributor wrote this on their own time
A bad review is:
- "This is wrong" without explaining why
- Demanding a full rewrite because the reviewer would have designed it differently
- Mixing 10 style nits with 1 real bug — the author can't tell which matters
3.3 The Review Rubric
Use this checklist when reviewing a patch:
| Category | Questions to ask |
|---|---|
| Correctness | Does this handle null inputs? What happens at the boundary (n=0, empty list)? Does this race condition exist under concurrent access? |
| Tests | Are there tests? Do they cover the failure case? Does the test actually exercise the code path it claims to? |
| API contract | Does this change break binary or source compatibility? Is the Javadoc accurate? Is the method name precise? |
| Performance | Is there a better data structure for this access pattern? Is there an unnecessary copy or allocation in the hot path? |
| Clarity | What does the comment "trust me" mean? Can a new contributor understand this in 6 months without the original author? |
3.4 Severity Labels
BLOCKER — prevents merge; correctness or API regression
MAJOR — should be fixed before merge; may not be correctness but is important
MINOR — should be addressed; not urgent
NIT — trivial style, can be ignored or fixed in a follow-up
QUESTION — asking for clarification, not requesting a change
4. What Automated Tools Miss
This is what you say in interviews:
- Logic errors: Checkstyle and SpotBugs don't know if your algorithm is wrong
- Missing tests: SpotBugs won't tell you the null path isn't tested
- Wrong abstraction: The API might work but be impossible to extend
- Performance regression: SpotBugs can flag
O(n²)patterns in some cases, but not always - Concurrency bugs: SpotBugs catches some, but subtle races require manual analysis (or tools like ThreadSanitizer)
The conclusion: automated tools shrink the review surface so humans can focus on what matters.
5. Interview Questions — Concepts You Must Explain Out Loud
| Topic | One-line answer |
|---|---|
| What is the difference between Checkstyle and SpotBugs? | Checkstyle enforces source-level style conventions by walking the AST; SpotBugs detects likely bugs by analyzing compiled bytecode using dataflow patterns. They complement each other — neither catches what the other does. |
| How does SpotBugs detect null dereferences? | It performs interprocedural dataflow analysis on bytecode, tracking which references might be null at each program point and flagging any dereference where the null path is possible. |
What is EI_EXPOSE_REP and how do you fix it? | The class returns a direct reference to an internal mutable field. A caller can then mutate the object directly, bypassing any invariants the class maintains. Fix: return Collections.unmodifiableMap() or a defensive copy. |
Why is public static final List<String> FOO = new ArrayList<>() a bug? | The final keyword only prevents reassignment of the variable — it does not make the list immutable. Any caller can still call FOO.add(...) or FOO.clear(). Use List.of() or Collections.unmodifiableList(). |
| What makes a -1 code review comment? | The change introduces a correctness regression, breaks the API contract (binary or source incompatibility), has no tests for the changed behavior, or violates a legal/license requirement. Style preferences are suggestions, not blockers. |
| How do you handle 50 Checkstyle violations in a legacy file you're modifying? | Fix only the violations in the lines you're changing. Create a JIRA issue for the rest. Adding a suppressions.xml entry for the file temporarily is acceptable if the team agrees — but every suppression should have a ticket to remove it. |
| What's the difference between HIGH and MEDIUM confidence in SpotBugs? | HIGH means SpotBugs is confident this is a real bug based on the bytecode pattern. MEDIUM means the pattern matches but context might explain it away (false positive). CI typically fails only on HIGH; MEDIUM is reported but not blocking. |
How does Checkstyle MagicNumber help maintainability? | Named constants explain intent. Thread.sleep(5000) tells you nothing; Thread.sleep(RETRY_DELAY_MS) tells you exactly what the number means and makes it trivial to change the delay in one place. |
6. References
- Checkstyle — Available Checks
- maven-checkstyle-plugin
- SpotBugs Bug Descriptions
- spotbugs-maven-plugin
- Google Java Style Guide
- Apache Spark Code Review Guidelines — real-world Apache review culture
- Apache Kafka Contributing Guide — review checklist used by a production Apache project