Chapter 17: Systematic Application Guide
What You’ll Learn
- 8 checkpoints for consistent JBCT application
- Quick reference tables for immediate lookup
- Violation -> Fix patterns for common mistakes
- Application order for new code and reviews
Prerequisites: Chapter 16: Project Structure
The Checkpoint Approach
JBCT application works through checkpoints - specific moments during coding where you pause and verify compliance. Each checkpoint has:
- Trigger: When to apply this checkpoint
- Rules: What to check
- Fixes: How to correct violations
The goal is 100% compliance through systematic verification, not heroic effort.
CHECKPOINT 1: Before Writing Any Lambda
Trigger: You’re about to write ->
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| L1 | Is method reference possible? | Use Type::method |
| L2 | Does lambda have braces {}? |
Extract to named method |
| L3 | Nested monadic operations inside? | Extract inner operation |
| L4 | Control flow (if/switch/try)? | Extract to named method |
| L5 | Multiple statements? | Extract to named method |
Allowed Lambda Forms (Exhaustive)
// Method references (ALWAYS PREFERRED)
.map(Email::new)
.flatMap(this::validate)
// Single-value expression (no braces)
.map(value -> expression)
.filter(s -> !s.isBlank())
// Multi-value expression (no braces)
.map((a, b) -> new Pair(a, b))
Extraction Pattern
// BEFORE (violation)
.map(data -> {
cache.put(key, data);
log.info("Cached: {}", data);
return data.size();
})
// AFTER (compliant)
.map(this::cacheAndCount)
private int cacheAndCount(Data data) {
cache.put(key, data);
log.info("Cached: {}", data);
return data.size();
}
CHECKPOINT 2: Choosing Return Type
Trigger: Writing a method signature
Decision Tree
Can this operation fail?
|-- NO: Can the value be absent?
| |-- NO -> return T
| |-- YES -> return Option<T>
|-- YES: Is it async/IO?
|-- NO -> return Result<T>
|-- YES -> return Promise<T>
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| R1 | Does Result always succeed? | Change to T |
| R2 | Is Option always present? | Change to T |
| R3 | Using Promise<Result<T>>? |
Use Promise<T> only |
| R4 | Returning Void? | Use Unit |
| R5 | Returning null? | Use Option<T> |
CHECKPOINT 3: Writing Factory Methods
Trigger: Creating construction logic for a type
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| F1 | Name follows TypeName.typeName()? |
Rename |
| F2 | Validation happens at construction? | Move to factory |
| F3 | Return type matches validation needs? | Apply Checkpoint 2 |
| F4 | Constructor exposed publicly? | Make factory only entry |
Pattern
public record Email(String value) {
// Factory with validation -> Result<T>
public static Result<Email> email(String raw) {
return Verify.ensure(raw, Verify.Is::present)
.map(String::trim)
.filter(INVALID_EMAIL, s -> PATTERN.matcher(s).matches())
.map(Email::new);
}
}
public record Config(DbUrl url, DbPassword pass) {
// Factory without validation -> T
public static Config config(DbUrl url, DbPassword pass) {
return new Config(url, pass);
}
}
CHECKPOINT 4: Designing a Class/Interface
Trigger: Creating a new type
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| D1 | What zone does this belong to? | Place in correct package |
| D2 | Does it mix I/O with domain logic? | Split into separate types |
| D3 | Are primitives used for domain concepts? | Extract value objects |
| D4 | Does naming match the zone? | Adjust naming style |
Zone Placement
Zone A (Entry): Controllers, handlers, main
-> Business action verbs: handleRegistration(), processOrder()
Zone B (Domain): Use cases, value objects, domain services
-> Domain vocabulary: Email.email(), ValidRequest.validRequest()
Zone C (Infrastructure): DB, external APIs, config loading
-> Technical names: loadAllGenerations(), saveUser(), readFile()
CHECKPOINT 5: Writing Monadic Chains
Trigger: Chaining .map()/.flatMap()/.filter() etc.
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| M1 | Single pattern per method? | Extract mixed patterns |
| M2 | Chain length <= 5 steps? | Split into composed methods |
| M3 | Side effects only in terminal ops? | Move to .onSuccess()/.onFailure() |
| M4 | Logging mixed with logic? | Move logging to appropriate layer |
Pattern Separation
// VIOLATION: Mixing Sequencer + Fork-Join
return validate(request)
.flatMap(req -> Result.all(
checkInventory(req),
validatePayment(req)
).map((inv, pay) -> proceed(req)));
// FIX: Extract Fork-Join
return validate(request)
.flatMap(this::validateOrder)
.flatMap(this::processOrder);
private Result<ValidRequest> validateOrder(ValidRequest req) {
return Result.all(checkInventory(req), validatePayment(req))
.map((inv, pay) -> req);
}
CHECKPOINT 6: Adding Logging
Trigger: Adding log statements
Rules to Check
| Rule | Check | Fix |
|---|---|---|
| G1 | Is logging conditional on data? | Remove condition |
| G2 | Logger passed as parameter? | Move logging to owner |
| G3 | Logging in pure transformation? | Move to terminal operation |
| G4 | Duplicate logging across layers? | Single layer logs |
Ownership Pattern
// VIOLATION: Caller logs for callee
cache.refresh()
.onSuccess(count -> log.debug("Refreshed {}", count));
// FIX: Cache owns its logging
// In GenerationCache:
public Result<Integer> refresh() {
return doRefresh()
.onSuccess(count -> log.debug("Refreshed {}", count));
}
CHECKPOINT 7: Review Completeness
Trigger: Before submitting code for review
Verification Checklist
- [ ] Every lambda checked against L1-L5
- [ ] Every return type checked against R1-R5
- [ ] Every factory method checked against F1-F4
- [ ] Every new type checked against D1-D4
- [ ] Every monadic chain checked against M1-M4
- [ ] Every log statement checked against G1-G4
- [ ] No FQCNs in code (use imports)
- [ ] Test names follow
methodName_outcome_condition
CHECKPOINT 8: Implementation Patterns
Trigger: Choosing implementation style
Nested Record vs Lambda Pattern
Does implementation need state beyond parameters?
|-- YES -> Use nested record pattern
|-- NO: Is it a functional interface?
|-- YES -> Use lambda pattern
|-- NO -> Use nested record pattern
Nested record pattern (state needed):
static GenerationCache generationCache(WorkConfig config) {
record generationCache(
AtomicReference<Instant> lastRefreshTime,
ConcurrentMap<String, Generation> cache,
WorkConfig config
) implements GenerationCache {
@Override
public Result<Unit> refresh() { ... }
}
return new generationCache(
new AtomicReference<>(Instant.EPOCH),
new ConcurrentHashMap<>(),
config
);
}
Lambda pattern (functional interface, no state):
static Step validate(Validator validator) {
return ctx -> validator.validate(ctx.input());
}
Quick Reference: Violation -> Fix Patterns
| Violation | Detection | Fix |
|---|---|---|
| Multi-statement lambda | { } with multiple lines |
Extract to method |
| Nested monadic ops | .flatMap(x -> y.map(...)) |
Extract inner to method |
| Always-succeeding Result | Result.success(new X()) |
Return X directly |
| Mixed I/O and domain | File/DB ops in domain class | Split to adapter |
| Primitive obsession | String url, int poolSize |
Create value object |
| Conditional logging | if (x) log.debug() |
Remove condition |
| Logger as parameter | method(Logger log) |
Move logging to owner |
Application Order
When Implementing New Code
- Design phase: Apply Checkpoint 4 (class design)
- Signature phase: Apply Checkpoint 2 (return types), Checkpoint 3 (factories)
- Implementation phase: Apply Checkpoint 1 (lambdas), Checkpoint 5 (chains), Checkpoint 8 (patterns)
- Polish phase: Apply Checkpoint 6 (logging)
- Completion phase: Apply Checkpoint 7 (review)
When Reviewing Existing Code
- Scan for lambdas (
->) - apply Checkpoint 1 - Scan for return types - apply Checkpoint 2
- Scan for factories - apply Checkpoint 3
- Scan for mixed responsibilities - apply Checkpoint 4
- Scan for logging - apply Checkpoint 6
- Final verification - apply Checkpoint 7
Key Takeaways
- 8 checkpoints - Cover all aspects of JBCT
- Apply systematically - Violations caught early, not in review
- Quick reference tables - For immediate lookup
- Order matters - Design -> Signature -> Implementation -> Polish
- 100% compliance - Achievable through systematic verification
Exercises
See Appendix B for exercises on:
- Exercise 6.1: Checkpoint application practice
- Exercise 6.3: Code review using checkpoints
What’s Next
Chapter 18 covers migration strategies for adopting JBCT in existing codebases.