Describe what makes code high quality, measure complexity and duplication, recognise smells and apply refactorings, use modern Java to express intent, automate quality gates, run effective code reviews and measure test strength beyond coverage
Code quality is the degree to which source code is easy to read, understand, change and trust. It is the finest-grained level of maintainability: architecture decides where changes land; code quality decides how expensive each landing is.
Figure 1 — Code quality pillars
Attribute
Question
Indicator
Readable
Can someone else follow it quickly?
Review time, naming, function size
Simple
Is it as simple as the problem allows?
Cognitive complexity, nesting
Correct
Does it do the right thing, provably?
Tests, mutation score, types
Consistent
Does it look like the rest of the codebase?
Formatter, conventions
Secure
Is it free of common weaknesses?
SAST findings
Table 1 — Attributes
Code-quality vocabulary
Meaning
Cognitive complexity
How hard code is to understand (nesting, breaks in flow)
Cyclomatic complexity
Number of independent paths
Code smell
Surface symptom of a deeper design problem
Refactoring
Behaviour-preserving change to structure
Mutation score
% of injected faults detected by tests
Table 2 — Terms
2Why It Exists
Low-quality code effect
Evidence at ShopNow
Slower reviews
Complex PRs: 3× review time
More defects
Top-complexity methods had 5× defect density
Fear of change
Engineers avoid touching applyPromotions
Onboarding friction
New engineers ask the same questions
Table 3 — Why code quality matters
Figure 2 — Cognitive complexity of checkout methods
3Core Concepts — Code Quality
3.1 Readability and Naming
Guideline
Example
Names reveal intent
isEligibleForFreeShipping(cart)
Functions do one thing at one level of abstraction
checkout() calls validate, price, reserve, pay
Avoid boolean parameters
ship(order, Priority.EXPRESS) not ship(order, true)
Comments explain why, not what
"// GST rounding per notification 12/2025"
Table 4 — Readability
3.2 Simplicity
Practice
Why
Guard clauses instead of deep nesting
Linear reading
No speculative generality
YAGNI
Prefer composition
Simpler than inheritance hierarchies
Immutable data
Fewer states to reason about
Table 5 — Simplicity
3.3 Smells and Refactorings
Figure 3 — Code smells and refactorings
4How It Works Internally — Quality Gates
Figure 4 — Automated quality gates
Gate
Rule at ShopNow
Formatting
Spotless check fails build
Static analysis
Error Prone + NullAway errors fail; Sonar "new code" gate
Complexity
New methods cognitive complexity ≤ 15
Duplication
New code duplication < 3%
Tests
New code coverage ≥ 80% + mutation score tracked for domain modules
Security
No new high SAST findings
Table 6 — Gates on new code ("clean as you code")
4ADeep Dive — Test Quality
Figure 5 — Coverage target vs meaningful tests
Technique
Detail
Mutation testing (PIT)
Changes code (e.g., > to >=); tests should fail
Property-based tests (jqwik)
Invariants over many generated inputs (e.g., price never negative)
Test naming
rejects_expired_coupon_even_if_stackable
Arrange-Act-Assert
One behaviour per test
Table 7 — Strength over volume
5APIs / Features / Syntax
JAVA
// Before: nested, primitive-typed, hard to follow (cognitive complexity 48)double apply(Cart c, List<Map<String,Object>> promos, boolean member) { /* ...120 lines... */}// After: small functions, domain types, sealed promotion kinds, pattern matchingpublic Money applyPromotions(Cart cart, List<Promotion> promotions, Customer customer) {return promotions.stream() .filter(p -> p.isApplicableTo(cart, customer)) .map(p -> discountFor(p, cart)) .reduce(Money.zero(cart.currency()), Money::plus) .min(cart.subtotal()); // discount never exceeds subtotal}private Money discountFor(Promotion p, Cart cart) {return switch (p) { case PercentageOff po -> cart.subtotal().times(po.percent()); case FlatOff fo -> fo.amount(); case BuyXGetY bxgy -> bxgy.freeItemsValue(cart); case FreeShipping _ -> cart.shippingFee(); };}
The 120-line method (cognitive complexity 48, 5 defects in a year) was refactored in six small PRs behind characterisation tests: extract methods, introduce Money and sealed Promotion types, replace conditionals with pattern matching. Complexity fell to 9; defects in the following year: 0; a new promotion type now takes one class and one switch case (the compiler finds the switch).
6.2 Null-safety
NullAway on the checkout module found 23 potential NPEs at compile time; production NPEs in checkout dropped from ~6/month to 0.
6.3 Reviews that focus on design
After automating formatting and common bugs, review comments on style fell by 70% and comments on naming, boundaries and tests rose. Median review time dropped from 26 to 9 hours.
6.4 Worked example — code review checklist
Question
Why
Does the name say what it does?
Understandability
Is this in the right module?
Architecture
What happens on failure?
Correctness
Is there a test for the behaviour, not just the lines?
Test strength
Is anything clever that could be plain?
Simplicity
Does it change a public contract?
Compatibility
Table 8 — Human review focus
6.5 Worked example — mutation testing result
Module
Line coverage
Mutation score
Action
pricing
91%
84%
Good
promotions
88%
52%
Many assert-free tests — strengthen
refunds
64%
61%
Add edge-case tests
Table 9 — Coverage alone misled on promotions
6.6 Worked example — refactoring sequence
Step
Refactoring
1
Characterisation tests for current behaviour
2
Extract Method for each block
3
Introduce Parameter Object (PricingRequest)
4
Replace primitives with value objects
5
Replace conditional with polymorphism/pattern matching
6
Remove dead code
Table 10 — Small, behaviour-preserving steps
6.7 Worked example — interview whiteboard
Prompt
Answer outline
"How do you keep code quality high across 60 teams?"
"Define quality as readable, simple, correct, consistent and secure. Automate what machines judge: formatter, Error Prone/NullAway, Sonar gates on new code, complexity and duplication limits, SAST. Humans review design, names, boundaries and tests. Measure test strength with mutation testing and property-based tests. Use modern Java — records, sealed types, pattern matching — to encode intent. Refactor hotspots in small, test-protected steps and share templates and examples."
Table 11 — Structured answer
6.8 Worked example — modern Java for quality
Feature
Quality benefit
Records
Immutable data, no boilerplate, clear intent
Sealed interfaces + switch patterns
Exhaustive handling, compiler-checked
var (judiciously)
Less noise when type is obvious
Text blocks
Readable SQL/JSON in tests
Virtual threads
Simple blocking code instead of callback chains
Table 12 — Language features that reduce cognitive load
6.12 Worked example — code quality metrics per team
Team
New-code gate pass rate
Median review time
Mutation score (domain)
checkout
97%
9 h
81%
orders
89%
14 h
66%
catalog
95%
7 h
78%
Table 16 — Comparative, not punitive
6.13 Worked example — when not to refactor
Situation
Reason
Code scheduled for replacement next quarter
Wasted effort
No tests and high risk
Add tests first
Unclear requirements
Wait until design settles
Table 17 — Refactor with purpose
6.14 Worked example — error handling quality
Poor
Better
catch (Exception e) {}
Catch specific, add context, rethrow or handle
Exceptions for control flow
Result types or explicit states
Generic RuntimeException
Domain exceptions (CouponExpiredException)
Table 18 — Failure paths are code too
6.15 Worked example — naming improvements
Before
After
data, info, obj
shippingQuote, sellerProfile
process()
reserveInventory()
flag2
isGiftWrapped
Manager, Helper, Util classes
Named by responsibility: CouponValidator
Table 19 — Names are the cheapest documentation
6.16 Worked example — static analysis findings (quarter)
Tool
Findings on new code
Fixed before merge
Error Prone
214
100%
NullAway
96
100%
Sonar bugs
41
100%
Sonar code smells
380
88% (rest accepted with reason)
Table 20 — Machines catch the routine issues
7Common Problems & Mistakes
Mistake
Fix
Style debates in reviews
Formatter + linters
Coverage as the goal
Mutation/property tests
Big refactoring PRs
Small steps with tests
Premature abstraction
Wait for the third case
Gates on legacy code blocking all work
Gate new code only
Ignoring null-safety
NullAway / annotations
Table 21 — Mistakes and fixes
8Performance & Production Considerations
Consideration
Practice
Build time of analysis
Incremental analysis; heavy scans nightly
Mutation testing cost
Run on changed domain modules
Readable vs fast code
Optimise only measured hotspots; document why
Table 22 — Operating quality gates
Quality gates have a cost that developers feel on every build, so keep them fast or they will be bypassed. Run the formatter check and Error Prone with NullAway in the compile step, where they add seconds, and let SonarQube analyse only the changed code in pull requests, leaving full-branch scans and heavyweight SAST for a nightly job. Mutation testing with PIT is the most expensive check; run it incrementally on changed classes in domain modules such as pricing and promotions, and full runs weekly. Watch the build time of the pipeline as a metric in its own right: once pull-request builds creep past about 15 minutes, teams start batching changes into bigger PRs, which makes reviews worse. On hot paths, readable code occasionally yields to faster code; when it does, measure first with JFR or JMH and record the reason in a comment next to the optimisation.
9Architecture & Design Considerations
Guideline
Detail
Templates and golden paths
New services start clean
Shared value types
Money, Ids in a stable library
Quality gates in platform CI
Consistent across teams
Code quality supports architecture quality
Small modules make small code possible
Table 23 — Design guidance
10Interview Questions & Answers
Q1BasicConcept
What is code quality?
Code quality is the degree to which source code is easy to read, understand, change and trust. It is the finest-grained level of maintainability: architecture decides where a change lands, and code quality decides how expensive that landing is. In practice it has five attributes: readable, with names and small functions that reveal intent; simple, with no more complexity than the problem needs; correct, provably, through types and strong tests; consistent with the rest of the codebase; and secure against common weaknesses. Each has an indicator, such as review time, cognitive complexity, mutation score or SAST findings. At ShopNow, the most complex checkout methods had five times the defect density of the rest.
Q2BasicConcept
What is a code smell?
A code smell is a surface symptom in code that suggests a deeper design problem, a term popularised by Martin Fowler. It is not a bug, and the code may work perfectly, but it signals that change will be harder or riskier than it should be. Common examples are long methods, large classes, long parameter lists, duplicated business rules, primitive obsession, feature envy, where a method uses another class's data more than its own, and boolean flag parameters. Each smell suggests a refactoring: Extract Method, Introduce Parameter Object, replace primitives with value objects such as Money, or replace conditionals with polymorphism or pattern matching. Static analysis flags many smells; reviewers judge which matter.
Q3IntermediateTrade-off
Cognitive vs cyclomatic complexity?
Cyclomatic complexity counts the number of independent paths through a method, essentially one plus the number of decision points, so it estimates how many test cases are needed to cover it. Cognitive complexity, defined by SonarSource, measures how hard the code is for a human to understand: it penalises nesting more heavily, counts breaks in linear flow such as loops, jumps and recursion, and ignores structures that read easily, such as a flat switch. A ten-case switch has high cyclomatic but low cognitive complexity, while three nested ifs inside a loop score the opposite. For maintainability, cognitive complexity is the better gate; ShopNow limits new methods to 15.
Q4IntermediateTricky
Why is coverage insufficient?
Coverage only shows that code was executed by a test, not that the test would fail if the code were wrong. A test can call a method and assert nothing, or assert only that the result is not null, and still produce 90% line coverage. Mutation testing exposes this: a tool such as PIT makes small changes to the code, for example turning greater-than into greater-or-equal, and checks whether any test fails. At ShopNow the promotions module had 88% line coverage but only a 52% mutation score, because many tests had no meaningful assertions. Combine coverage on new code with mutation scores and property-based tests on domain modules.
Q5Senior EngineerConcept
What should code review focus on?
Code review should focus on what machines cannot judge: design, naming, module boundaries, failure handling and whether tests verify behaviour. Formatting, style and common bug patterns should already be enforced by Spotless, Error Prone, NullAway and Sonar, so reviewers do not spend attention on them. Good review questions are whether the name says what the code does, whether the change sits in the right module, what happens on failure, whether there is a test for the behaviour rather than just the lines, whether anything clever could be plain, and whether a public contract changes. At ShopNow, automating style cut style comments by 70% and median review time from 26 to 9 hours.
Q6Senior EngineerCode
How do sealed types help quality?
Sealed types let you declare a closed set of subtypes, for example a sealed Promotion interface permitting only PercentageOff, FlatOff, BuyXGetY and FreeShipping. Combined with pattern matching in a switch expression, the compiler checks exhaustiveness: the switch needs no default branch, and if someone adds a new promotion type, every switch that does not handle it fails to compile. That turns a class of runtime bugs, the forgotten case, into compile errors and makes the domain model explicit in the code. Records as the permitted subtypes add immutable, boilerplate-free data. At ShopNow, adding a promotion type now takes one class and one switch case, and the compiler finds the switch.
Q7Technical ArchitectScenario
How do you apply gates to legacy code?
Apply the gates to new and changed code only, an approach often called clean as you code. If gates measured the whole legacy codebase, every build would fail and teams would either disable the checks or stop working. Instead, configure SonarQube's new-code period so that only lines added or modified in the pull request must meet complexity, duplication, coverage and security rules, while the existing debt is tracked separately. Over time, the code people actually touch improves, which is where improvement pays off. Pair it with targeted refactoring of hotspots, the files that change most and have the most defects, rather than an unfocused clean-up of rarely changed code.
Q8Technical ArchitectTricky
When is duplication acceptable?
Duplication is acceptable when code looks similar but represents different concepts that will change for different reasons. Merging them creates the wrong abstraction: a shared method full of flags and special cases that couples two parts of the system, which is worse than the duplication it removed. For example, a customer return window and a seller payout hold period may share the same date arithmetic today, yet they belong to different teams and change for different reasons. Copy-pasted business rules, by contrast, must be consolidated, because a fix applied to one copy will be missed in the other. A useful rule of thumb is to wait for the third occurrence before abstracting.
Q9Solution ArchitectDesign
How do you scale code quality across teams?
Make quality the default of the platform rather than the effort of each team. Put the gates in shared CI templates so all teams get the same formatter, Error Prone and NullAway rules and Sonar new-code thresholds automatically. Start every new service from a golden-path template that is already clean, and provide shared libraries for value types such as Money and identifiers. Publish per-team metrics, such as gate pass rate, median review time and mutation score on domain modules, and use them comparatively to find where help is needed, not to punish. Back it with shared examples, review guidelines and communities of practice, so standards spread through people as well as tools.
Q10Solution ArchitectScenario
How do you refactor safely?
Refactor in small, behaviour-preserving steps protected by tests. If the code has no tests, first write characterisation tests that capture its current behaviour, even where that behaviour looks wrong, so any accidental change is detected. Then apply one refactoring per pull request, such as Extract Method, Introduce Parameter Object, replacing primitives with value objects or replacing conditionals with pattern matching, keeping each change easy to review and revert. When behaviour must change as well, do it separately and behind a feature flag. ShopNow refactored its 120-line applyPromotions method in six small PRs, reducing cognitive complexity from 48 to 9 with zero defects the following year.
Newsletter
Get new tutorials every week
New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.
Your subscription could not be saved. Please try again.
Almost done! Check your inbox and click the confirmation link to complete your subscription.