🛸 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

RuleWhat it catchesExample violation
ConstantNamestatic final fields not in UPPER_SNAKE_CASEstatic final int maxRetries = 3
UnusedImportsImported types not referenced in the fileimport java.util.ArrayList; never used
MagicNumberNumeric literals other than -1, 0, 1, 2Thread.sleep(5000)
MissingJavadocMethodPublic methods without a Javadoc commentpublic void submit(Runnable task) with no /**
LineLengthLines longer than the configured max (typically 100)A log message with 3 concatenations on one line
VisibilityModifierNon-private instance fieldsprotected String name; in a concrete class
ModifierOrderModifiers in wrong orderstatic 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 prefixMeaningExamples
NP_Null pointer dereferenceNP_NULL_ON_SOME_PATH, NP_ALWAYS_NULL
EI_Expose internal representationEI_EXPOSE_REP, EI_EXPOSE_REP2
MS_Mutable static fieldMS_MUTABLE_COLLECTION, MS_SHOULD_BE_FINAL
DM_Deprecated method / bad idiomDM_NUMBER_CTOR, DM_STRING_CTOR
RCN_Redundant null checkRCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE
OS_Open stream / resource leakOS_OPEN_STREAM, OS_OPEN_STREAM_EXCEPTION_PATH
IS2_Inconsistent synchronizationIS2_INCONSISTENT_SYNC

2.3 Confidence Levels

LevelMeaningShould you fix it?
HIGHVery likely a real bugAlways
MEDIUMPossibly a bug, but context neededUsually yes
LOWSpeculative; many false positivesReview, 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:

  1. Contributor opens a PR with a JIRA issue number in the title
  2. Committers review: automated CI runs first, then humans review after CI is green
  3. Reviewer leaves comments — questions, suggestions, required fixes
  4. Contributor addresses each comment with either a fix or a reasoned disagreement
  5. 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:

CategoryQuestions to ask
CorrectnessDoes this handle null inputs? What happens at the boundary (n=0, empty list)? Does this race condition exist under concurrent access?
TestsAre there tests? Do they cover the failure case? Does the test actually exercise the code path it claims to?
API contractDoes this change break binary or source compatibility? Is the Javadoc accurate? Is the method name precise?
PerformanceIs there a better data structure for this access pattern? Is there an unnecessary copy or allocation in the hot path?
ClarityWhat 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

TopicOne-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