ArchitectMind
  1. Home
  2. Tutorials
  3. Book 8 · Architecture Quality Attributes
  4. Volume VI
  5. Code Quality
Book 8 · Volume VI — Maintainability, Modifiability & Testability · Topic 1.3

Code Quality

Readable, simple, correct and consistent code

11 min readStage 3 · Systems10 interview questions
Chapter
Chapter 1 — Maintainability
Prerequisites
Topics 1.1, 1.2
You will be able to
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
Covers
Code quality
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts — Code Quality
  4. 4. How It Works Internally — Quality Gates
  5. 4A. Deep Dive — Test Quality
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

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.

Code quality pillars
Figure 1 — Code quality pillars
AttributeQuestionIndicator
ReadableCan someone else follow it quickly?Review time, naming, function size
SimpleIs it as simple as the problem allows?Cognitive complexity, nesting
CorrectDoes it do the right thing, provably?Tests, mutation score, types
ConsistentDoes it look like the rest of the codebase?Formatter, conventions
SecureIs it free of common weaknesses?SAST findings
Table 1 — Attributes
Code-quality vocabularyMeaning
Cognitive complexityHow hard code is to understand (nesting, breaks in flow)
Cyclomatic complexityNumber of independent paths
Code smellSurface symptom of a deeper design problem
RefactoringBehaviour-preserving change to structure
Mutation score% of injected faults detected by tests
Table 2 — Terms

2Why It Exists

Low-quality code effectEvidence at ShopNow
Slower reviewsComplex PRs: 3× review time
More defectsTop-complexity methods had 5× defect density
Fear of changeEngineers avoid touching applyPromotions
Onboarding frictionNew engineers ask the same questions
Table 3 — Why code quality matters
Cognitive complexity of checkout methods
Figure 2 — Cognitive complexity of checkout methods

3Core Concepts — Code Quality

3.1 Readability and Naming

GuidelineExample
Names reveal intentisEligibleForFreeShipping(cart)
Functions do one thing at one level of abstractioncheckout() calls validate, price, reserve, pay
Avoid boolean parametersship(order, Priority.EXPRESS) not ship(order, true)
Comments explain why, not what"// GST rounding per notification 12/2025"
Table 4 — Readability

3.2 Simplicity

PracticeWhy
Guard clauses instead of deep nestingLinear reading
No speculative generalityYAGNI
Prefer compositionSimpler than inheritance hierarchies
Immutable dataFewer states to reason about
Table 5 — Simplicity

3.3 Smells and Refactorings

Code smells and refactorings
Figure 3 — Code smells and refactorings

4How It Works Internally — Quality Gates

Automated quality gates
Figure 4 — Automated quality gates
GateRule at ShopNow
FormattingSpotless check fails build
Static analysisError Prone + NullAway errors fail; Sonar "new code" gate
ComplexityNew methods cognitive complexity ≤ 15
DuplicationNew code duplication < 3%
TestsNew code coverage ≥ 80% + mutation score tracked for domain modules
SecurityNo new high SAST findings
Table 6 — Gates on new code ("clean as you code")

4ADeep Dive — Test Quality

Coverage target vs meaningful tests
Figure 5 — Coverage target vs meaningful tests
TechniqueDetail
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 namingrejects_expired_coupon_even_if_stackable
Arrange-Act-AssertOne 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 matching
public 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();
    };
}
KOTLIN
// build.gradle.kts: quality gates
plugins {
    id("com.diffplug.spotless") version "7.0.2"
    id("net.ltgt.errorprone") version "4.1.0"
}
spotless { java { googleJavaFormat() } }
dependencies {
    errorprone("com.google.errorprone:error_prone_core:2.36.0")
    errorprone("com.uber.nullaway:nullaway:0.12.3")
}
tasks.withType<JavaCompile>().configureEach {
    options.errorprone.option("NullAway:AnnotatedPackages", "com.shopnow")
}
JAVA
// Property-based test: discounts never exceed subtotal
@Property
void discountNeverExceedsSubtotal(@ForAll("carts") Cart cart,
                                  @ForAll("promos") List<Promotion> promos) {
    Money discount = engine.applyPromotions(cart, promos, Customer.guest());
    assertThat(discount.isGreaterThan(cart.subtotal())).isFalse();
}

6Practical Examples

6.1 applyPromotions refactoring

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

QuestionWhy
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

ModuleLine coverageMutation scoreAction
pricing91%84%Good
promotions88%52%Many assert-free tests — strengthen
refunds64%61%Add edge-case tests
Table 9 — Coverage alone misled on promotions

6.6 Worked example — refactoring sequence

StepRefactoring
1Characterisation tests for current behaviour
2Extract Method for each block
3Introduce Parameter Object (PricingRequest)
4Replace primitives with value objects
5Replace conditional with polymorphism/pattern matching
6Remove dead code
Table 10 — Small, behaviour-preserving steps

6.7 Worked example — interview whiteboard

PromptAnswer 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

FeatureQuality benefit
RecordsImmutable data, no boilerplate, clear intent
Sealed interfaces + switch patternsExhaustive handling, compiler-checked
var (judiciously)Less noise when type is obvious
Text blocksReadable SQL/JSON in tests
Virtual threadsSimple blocking code instead of callback chains
Table 12 — Language features that reduce cognitive load

6.9 Worked example — duplication types

TypeAction
Copy-paste of business rulesConsolidate (high priority)
Similar-looking but different conceptsKeep separate (avoid wrong abstraction)
BoilerplateRecords, generators
Table 13 — Not all duplication is bad

6.10 Worked example — quality gate failure

PRGateResult
#9123 adds shipping ruleNew method complexity 22Blocked → split into strategy per courier
#9140 adds DTODuplicate mapping code 6%Blocked → use MapStruct mapper
Table 14 — Gates prevent new debt

6.11 Worked example — readable tests

PoorBetter
test1()refund_is_rejected_when_order_older_than_30_days()
10 asserts on unrelated fieldsOne behaviour, focused asserts
Magic setup dataBuilders: anOrder().deliveredDaysAgo(31).build()
Table 15 — Tests as documentation

6.12 Worked example — code quality metrics per team

TeamNew-code gate pass rateMedian review timeMutation score (domain)
checkout97%9 h81%
orders89%14 h66%
catalog95%7 h78%
Table 16 — Comparative, not punitive

6.13 Worked example — when not to refactor

SituationReason
Code scheduled for replacement next quarterWasted effort
No tests and high riskAdd tests first
Unclear requirementsWait until design settles
Table 17 — Refactor with purpose

6.14 Worked example — error handling quality

PoorBetter
catch (Exception e) {}Catch specific, add context, rethrow or handle
Exceptions for control flowResult types or explicit states
Generic RuntimeExceptionDomain exceptions (CouponExpiredException)
Table 18 — Failure paths are code too

6.15 Worked example — naming improvements

BeforeAfter
data, info, objshippingQuote, sellerProfile
process()reserveInventory()
flag2isGiftWrapped
Manager, Helper, Util classesNamed by responsibility: CouponValidator
Table 19 — Names are the cheapest documentation

6.16 Worked example — static analysis findings (quarter)

ToolFindings on new codeFixed before merge
Error Prone214100%
NullAway96100%
Sonar bugs41100%
Sonar code smells38088% (rest accepted with reason)
Table 20 — Machines catch the routine issues

7Common Problems & Mistakes

MistakeFix
Style debates in reviewsFormatter + linters
Coverage as the goalMutation/property tests
Big refactoring PRsSmall steps with tests
Premature abstractionWait for the third case
Gates on legacy code blocking all workGate new code only
Ignoring null-safetyNullAway / annotations
Table 21 — Mistakes and fixes

8Performance & Production Considerations

ConsiderationPractice
Build time of analysisIncremental analysis; heavy scans nightly
Mutation testing costRun on changed domain modules
Readable vs fast codeOptimise 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

GuidelineDetail
Templates and golden pathsNew services start clean
Shared value typesMoney, Ids in a stable library
Quality gates in platform CIConsistent across teams
Code quality supports architecture qualitySmall 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.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 8

Architecture Quality Attributes

Volume I · Topic 1.1

Quality Attributes

Quality Attributes & Architectural Thinking

Volume I · Topic 1.2

Why Quality Attributes Matter

Quality Attributes & Architectural Thinking

Volume I · Topic 1.3

Quality Attribute Categories

Quality Attributes & Architectural Thinking

Volume II · Topic 1.1

Performance

Performance & Capacity

Volume II · Topic 1.2

Latency Components

Performance & Capacity

Volume II · Topic 1.3

Tail Latency

Performance & Capacity

Volume III · Topic 1.1

Scalability

Scalability & Elasticity

Volume III · Topic 1.2

Vertical Scaling

Scalability & Elasticity

Volume III · Topic 1.3

Horizontal Scaling

Scalability & Elasticity

Volume IV · Topic 1.1

Availability Fundamentals

Availability, Reliability & Resilience

Volume IV · Topic 1.2

Availability Calculation

Availability, Reliability & Resilience

Volume IV · Topic 1.3

Nines of Availability

Availability, Reliability & Resilience

Volume V · Topic 1.1

Security Requirements

Security, Privacy & Compliance

Volume V · Topic 1.2

Security Architecture

Security, Privacy & Compliance

Volume V · Topic 1.3

Threat Modeling

Security, Privacy & Compliance

Volume VI · Topic 1.1

Maintainability

Maintainability, Modifiability & Testability

Volume VI · Topic 1.2

Technical Debt

Maintainability, Modifiability & Testability

Volume VII · Topic 1.1

What Is Operability?

Operability, Observability & Deployability

Volume VII · Topic 1.2

Operational Readiness

Operability, Observability & Deployability

Volume VII · Topic 1.3

Production Readiness Checklist

Operability, Observability & Deployability

Volume VIII · Topic 1.1

Why Evaluate Architecture?

Quality Attribute Trade-offs & Architecture Evaluation

Volume VIII · Topic 1.2

Architecture Evaluation Methods

Quality Attribute Trade-offs & Architecture Evaluation

Volume VIII · Topic 1.3

ATAM

Quality Attribute Trade-offs & Architecture Evaluation