Start / Articles / Pattern-Based Code Review

From Subjective Opinions to Systematic Analysis: Pattern-Based Code Review

How structural patterns transform code review from art into engineering


The Problem with Traditional Code Review

Code review discussions often devolve into debates about style, naming preferences, and subjective “readability.” Two experienced developers can look at the same function and have completely opposite opinions about whether it’s “clean” or “messy.”

This subjectivity creates real problems:

What if code review could be as systematic as running a test suite?


The Key Insight: Patterns Enable Decomposition

When code follows well-defined structural patterns, something remarkable happens: functions become decomposable into discrete, analyzable parts.

Consider a function that follows no particular pattern. To review it, you must:

Now consider a function that implements exactly one pattern from a known catalog. Suddenly you can:

Patterns transform code review from holistic judgment into component analysis.


The Pattern Catalog: A Reviewer’s Toolkit

Every function implements exactly one of these patterns:

1. Leaf

The atomic unit – a function that does one thing with no internal steps.

Business Leaf: Pure computation, no I/O

public Money calculateDiscount(Money price, double discountRate) {
    return price.multiply(discountRate);
}

Adapter Leaf: I/O operation that bridges to external systems

public User findByEmail(String email) throws DatabaseException {
    try (var conn = dataSource.getConnection();
         var stmt = conn.prepareStatement(FIND_BY_EMAIL_SQL)) {
        stmt.setString(1, email);
        var rs = stmt.executeQuery();
        return rs.next() ? mapToUser(rs) : null;
    } catch (SQLException e) {
        throw new DatabaseException("Failed to find user by email", e);
    }
}

2. Sequencer

A chain of 2-5 dependent steps where each step’s output feeds the next.

public OrderConfirmation processOrder(OrderRequest request) {
    ValidatedOrder validated = validateOrder(request);
    InventoryReservation reservation = reserveInventory(validated);
    PaymentResult payment = processPayment(reservation);
    return confirmOrder(payment);
}

3. Fork-Join

Parallel independent operations combined into a single result.

public Dashboard loadDashboard(Long userId) {
    CompletableFuture<UserProfile> profileFuture = 
        CompletableFuture.supplyAsync(() -> userService.getProfile(userId));
    CompletableFuture<List<Order>> ordersFuture = 
        CompletableFuture.supplyAsync(() -> orderService.getRecentOrders(userId));
    CompletableFuture<List<Notification>> notificationsFuture = 
        CompletableFuture.supplyAsync(() -> notificationService.getUnread(userId));

    CompletableFuture.allOf(profileFuture, ordersFuture, notificationsFuture).join();

    return new Dashboard(
        profileFuture.join(),
        ordersFuture.join(),
        notificationsFuture.join()
    );
}

4. Condition

Branching based on a discriminator – if/else or switch.

public double calculateShippingRate(Order order) {
    if (order.isPremiumCustomer()) {
        return calculatePremiumRate(order);
    } else if (order.getWeight() > HEAVY_THRESHOLD) {
        return calculateHeavyItemRate(order);
    } else {
        return calculateStandardRate(order);
    }
}

5. Iteration

Collection processing via loops or streams.

public List<OrderSummary> getPendingOrderSummaries(List<Order> orders) {
    return orders.stream()
        .filter(Order::isPending)
        .map(this::toSummary)
        .collect(Collectors.toList());
}

Pattern-Based Review: The Checklist Approach

Once you recognize a function’s pattern, you apply pattern-specific review criteria. This replaces vague “is it readable?” with concrete checklists.

Reviewing a Leaf

Reviewing a Sequencer

Reviewing a Fork-Join

Reviewing a Condition

Reviewing an Iteration


The Meta-Review: Pattern Violations

Beyond pattern-specific checks, reviewers should watch for structural violations:

Mixed Patterns

A function that starts as a Sequencer but contains an inline Fork-Join:

// BAD: Mixed patterns -- Sequencer contains inline Fork-Join
public OrderResult processOrder(OrderRequest request) {
    ValidatedOrder validated = validateOrder(request);
    
    // Suddenly doing parallel work inline
    CompletableFuture<User> userFuture = 
        CompletableFuture.supplyAsync(() -> userService.getUser(validated.getUserId()));
    CompletableFuture<Product> productFuture = 
        CompletableFuture.supplyAsync(() -> productService.getProduct(validated.getProductId()));
    CompletableFuture.allOf(userFuture, productFuture).join();
    
    OrderContext context = new OrderContext(userFuture.join(), productFuture.join());
    return finalizeOrder(context);
}

Review feedback: “Extract the parallel fetch into a separate method like fetchOrderContext().”

// GOOD: Clean -- Sequencer with extracted Fork-Join
public OrderResult processOrder(OrderRequest request) {
    ValidatedOrder validated = validateOrder(request);
    OrderContext context = fetchOrderContext(validated);  // Fork-Join hidden here
    return finalizeOrder(context);
}

private OrderContext fetchOrderContext(ValidatedOrder validated) {
    // Fork-Join pattern in its own method
    CompletableFuture<User> userFuture = 
        CompletableFuture.supplyAsync(() -> userService.getUser(validated.getUserId()));
    CompletableFuture<Product> productFuture = 
        CompletableFuture.supplyAsync(() -> productService.getProduct(validated.getProductId()));
    CompletableFuture.allOf(userFuture, productFuture).join();
    
    return new OrderContext(userFuture.join(), productFuture.join());
}

Violated Abstraction Levels

Mixing high-level orchestration with low-level details:

// BAD: Mixed abstraction levels
public void processUserRegistration(RegistrationRequest request) {
    // High-level step
    User user = createUser(request);
    
    // Suddenly low-level details
    String welcomeHtml = "<html><body><h1>Welcome " + user.getName() + "!</h1>"
        + "<p>Your account has been created.</p>"
        + "<a href='" + baseUrl + "/verify?token=" + user.getVerificationToken() + "'>Verify</a>"
        + "</body></html>";
    
    emailService.send(user.getEmail(), "Welcome!", welcomeHtml);
    
    // Back to high-level
    auditService.logRegistration(user);
}

Review feedback: “Extract email content generation to a separate method or template.”

// GOOD: Clean -- consistent abstraction level
public void processUserRegistration(RegistrationRequest request) {
    User user = createUser(request);
    sendWelcomeEmail(user);
    auditService.logRegistration(user);
}

private void sendWelcomeEmail(User user) {
    String content = emailTemplates.renderWelcome(user);
    emailService.send(user.getEmail(), "Welcome!", content);
}

Incorrect Pattern Choice

Using Sequencer when Fork-Join is appropriate (sequential operations that are actually independent):

// BAD: Sequential when parallel is possible
public ReportData gatherReportData(Long userId) {
    UserProfile profile = userService.getProfile(userId);      // 200ms
    List<Order> orders = orderService.getOrders(userId);       // 300ms  
    AccountBalance balance = accountService.getBalance(userId); // 150ms
    // Total: 650ms sequential
    
    return new ReportData(profile, orders, balance);
}

Review feedback: “These fetches are independent – use parallel execution to reduce latency.”

// GOOD: Parallel execution for independent operations
public ReportData gatherReportData(Long userId) {
    CompletableFuture<UserProfile> profileFuture = 
        CompletableFuture.supplyAsync(() -> userService.getProfile(userId));
    CompletableFuture<List<Order>> ordersFuture = 
        CompletableFuture.supplyAsync(() -> orderService.getOrders(userId));
    CompletableFuture<AccountBalance> balanceFuture = 
        CompletableFuture.supplyAsync(() -> accountService.getBalance(userId));
    
    CompletableFuture.allOf(profileFuture, ordersFuture, balanceFuture).join();
    // Total: ~300ms parallel
    
    return new ReportData(
        profileFuture.join(), 
        ordersFuture.join(), 
        balanceFuture.join()
    );
}

The “God Method” – Multiple Patterns Jumbled Together

// BAD: Multiple patterns mixed together
public Invoice generateInvoice(Long orderId) {
    Order order = orderRepository.findById(orderId);  // Leaf
    if (order == null) {                              // Condition starts
        throw new OrderNotFoundException(orderId);
    }
    
    List<InvoiceLine> lines = new ArrayList<>();      // Iteration starts
    for (LineItem item : order.getItems()) {
        Product product = productService.getProduct(item.getProductId());  // Another Leaf
        if (product.isTaxable()) {                    // Nested Condition
            lines.add(createTaxableLine(item, product));
        } else {
            lines.add(createNonTaxableLine(item, product));
        }
    }
    
    double subtotal = 0;                              // Another Iteration
    for (InvoiceLine line : lines) {
        subtotal += line.getAmount();
    }
    
    if (order.hasDiscount()) {                        // Another Condition
        subtotal = applyDiscount(subtotal, order.getDiscount());
    }
    
    return new Invoice(order.getId(), lines, subtotal);
}

Review feedback: “This method mixes at least 4 patterns. Extract: fetchOrder(), buildInvoiceLines(), calculateSubtotal(), applyDiscountIfPresent().”

// GOOD: Clean -- one pattern per method, composed as Sequencer
public Invoice generateInvoice(Long orderId) {
    Order order = fetchOrder(orderId);
    List<InvoiceLine> lines = buildInvoiceLines(order);
    double subtotal = calculateSubtotal(lines);
    double finalAmount = applyDiscountIfPresent(subtotal, order);
    return new Invoice(order.getId(), lines, finalAmount);
}

private Order fetchOrder(Long orderId) { /* Leaf */ }
private List<InvoiceLine> buildInvoiceLines(Order order) { /* Iteration */ }
private double calculateSubtotal(List<InvoiceLine> lines) { /* Iteration */ }
private double applyDiscountIfPresent(double subtotal, Order order) { /* Condition */ }

Practical Review Workflow

Step 1: Pattern Recognition

Before reading implementation details, identify the pattern:

Step 2: Count the Patterns

If you identify more than one pattern in a single method, that’s already a finding. Flag it for extraction.

Step 3: Apply Pattern Checklist

Use the appropriate checklist. Check each criterion mechanically.

Step 4: Verify Abstraction Consistency

Read through the method body. Are all statements at the same level of abstraction? High-level orchestration shouldn’t mix with low-level implementation details.

Step 5: Check Error Handling Consistency


Common Review Comments by Pattern

Having a vocabulary of pattern-based feedback makes reviews faster and more actionable:

Leaf

Sequencer

Fork-Join

Condition

Iteration

Mixed Patterns


Benefits of Pattern-Based Review

For Reviewers

For Authors

For Teams


Implementing Pattern-Based Review

Start Small

  1. Introduce the five patterns in a team meeting
  2. Start identifying patterns in PR descriptions: “This method is a Sequencer with 4 steps”
  3. Use pattern vocabulary in review comments

Create Team Standards

Build Into Process

Evolve and Refine


Conclusion

Traditional code review asks: “Is this code good?” – a question with infinite subjective answers.

Pattern-based code review asks: “Is this a valid Sequencer? Does this Fork-Join have independent branches? Is this Leaf truly atomic?” – questions with objective, verifiable answers.

When you recognize structural patterns, review transforms from art appreciation into engineering inspection. Every function has a recognizable shape. Every shape has specific criteria. Every criterion has a clear pass/fail answer.

The result: faster reviews, better feedback, and code that improves systematically rather than accidentally.

The patterns aren’t new – you’ve been writing Sequencers, Fork-Joins, and Iterations your whole career. What’s new is naming them, recognizing them explicitly, and using that recognition to make code review objective and systematic.


Want to dive deeper into pattern-based code structure? Check out Java Backend Coding Technology for a complete methodology built on these principles.