Skip to main content
the readable codebase

Variables and Methods: Names That Eliminate the Need to Read the Implementation

5 min read Chapter 8 of 27

Variables and Methods: Names That Eliminate the Need to Read the Implementation

The Smell

The logistics platform’s InventoryService contains this method:

public void process(List<Map<String, Object>> data, boolean flag) {
    for (Map<String, Object> item : data) {
        String val = (String) item.get("type");
        int qty = (int) item.get("quantity");
        Object ref = item.get("ref");
        // ... 40 more lines
    }
}

Every variable in this method forces the reader to hold a question: What is data? What does flag control? What is val? What does ref refer to? Each question consumes a working memory slot. By the time the reader reaches line 6, they have accumulated four unanswered questions and have not yet encountered the actual logic.

This pattern appears 34 times in the logistics codebase. A search for variables named data, result, temp, info, val, obj, or flag across all service classes returns 127 matches. Each match is a point where the next reader will pause, re-read surrounding code, or scroll to a declaration to understand what a variable holds.

The Cognitive Cost

A variable named shipmentWeight costs zero working memory. The reader sees it, understands it, and moves on. The concept is decompressed instantly.

A variable named val costs one working memory slot. The reader must either remember what was assigned to val three lines ago, or scroll back to check. That slot stays occupied until val leaves scope or the reader finishes reading the method. In a method with four poorly named variables, four working memory slots are consumed by tracking names, leaving zero for tracking logic.

A method named findActiveCarriersForRoute costs zero working memory at the call site. The caller knows what it does without opening it. The return type confirms it (List<Carrier>), and the name explains the filter criteria.

A method named getCarriers is ambiguous. All carriers? Active carriers? Carriers for a specific route? The reader must open the implementation. Opening the implementation adds one more context switch, one more file to keep in memory, one more thread of reasoning to maintain.

The Before

// HARD TO READ: Every name forces the reader to check the implementation.

public class InventoryService {

    public Map<String, Object> process(String id, boolean flag) {
        // Reader must check: what does 'id' identify? A warehouse? A product? A shipment?
        var entity = repo.findById(id);
        if (entity == null) {
            return Collections.emptyMap();
        }

        // Reader must check: what does 'flag' mean?
        var result = new HashMap<String, Object>();
        var items = getItems(id);  // Reader must check: items of what?

        for (var item : items) {
            var val = calculate(item, flag);  // Reader must check: calculate what?
            var key = item.get("code");       // Reader must check: code of what?
            result.put((String) key, val);
        }

        // Reader must check: what is in 'result'? A map from what to what?
        return process2(result, entity);
        // Reader must check: what does process2 do that process didn't finish?
    }

    private List<Map<String, Object>> getItems(String id) { /* ... */ }
    private Object calculate(Map<String, Object> item, boolean flag) { /* ... */ }
    private Map<String, Object> process2(Map<String, Object> data,
                                          Object entity) { /* ... */ }
}

Count the questions a reader must answer to understand this method: What does id identify? What does flag control? What entity is being found? What items are being retrieved? What is being calculated? What is process2? That is six questions, each consuming a working memory slot. The method is 15 lines long, but its cognitive cost exceeds methods three times its length.

The Fix

// READABLE: Every name answers the question the reader would ask.
// No reader needs to open the implementation to understand the call site.

public class InventoryService {

    public WarehouseStockSummary recalculateStock(WarehouseId warehouseId,
                                                   StockCountMode mode) {
        Warehouse warehouse = warehouses.findOrFail(warehouseId);

        List<InventoryItem> inventoryItems =
            inventoryItems.findByWarehouse(warehouseId);

        Map<ProductCode, StockLevel> stockByProduct = inventoryItems.stream()
            .collect(Collectors.toMap(
                InventoryItem::productCode,
                item -> calculateStockLevel(item, mode)
            ));

        return applyWarehouseAdjustments(stockByProduct, warehouse);
    }

    private StockLevel calculateStockLevel(InventoryItem item,
                                            StockCountMode mode) {
        return switch (mode) {
            case PHYSICAL -> StockLevel.fromPhysicalCount(item);
            case AVAILABLE -> StockLevel.fromAvailableStock(item);
        };
    }

    private WarehouseStockSummary applyWarehouseAdjustments(
            Map<ProductCode, StockLevel> stockByProduct,
            Warehouse warehouse) {
        // warehouse-specific adjustments (reserved stock, damaged goods)
        return new WarehouseStockSummary(warehouse.id(), stockByProduct);
    }
}

The changes:

  • idwarehouseId: The reader knows what is being identified without checking the repository call.
  • flagStockCountMode mode: The boolean is replaced by an enum that names the two modes. At the call site, StockCountMode.PHYSICAL is self-documenting; true is not.
  • entitywarehouse: The reader knows the type without checking the variable declaration.
  • itemsinventoryItems: Unambiguous. Not shipment items, not order items.
  • valStockLevel (a typed return value): The type documents the value.
  • resultstockByProduct: The name describes the structure and its key.
  • process2applyWarehouseAdjustments: The name describes exactly what transformation happens.
  • getItemsfindByWarehouse: The method name describes the query, not the return shape.

The refactored version has the same number of lines. The cognitive complexity score is similar. The readability difference is not structural. It is semantic. Every name communicates its purpose. No reader needs to open the implementation of any called method to understand what this method does.

The Rule

A variable or method name is good if a reader can predict what it contains or does without reading the implementation or the surrounding three lines. If a name requires the reader to check the assignment, open the method body, or read a comment to understand it, rename it. In code reviews, apply this test: cover the right-hand side of an assignment or the body of a method. Can you predict what it does from the name alone? If not, the name needs work.