Development

“Подмоченный” код сложнее оптимизировать

(В оригинале – WET Dilutes Performance Bottlenecks)

Важность принципа DRY (Не повторяйся, Don’t Repeat Yourself) в том, что он реализует идею, согласно которой каждый фрагмент знаний представлен лишь единожды. Другими словами, каждое знание должно быть реализовано один и только один раз. Противоположность ему – принцип WET (Реализуй каждый раз, Write Every Time). (В английском здесь игра слов – аббревиатуры WET и DRY имеют еще и смысловое значение «мокрый» и «сухой»). WET код – код, в котором знание закодировано в нескольких реализациях. Влияние DRY и WET на производительность станет ясным после рассмотрения их влияния на профилирование.

Давайте предположим, что одна из функций нашей системы, скажем, X, является «узким местом» по производительности. Пусть, например, она расходует 30% процессорного времени. И теперь предположим, что она реализована в коде в десяти различных местах. В среднем, каждая реализация будет тратить около 3% процессорного времени. И вот тут мы можем просто не заметить, что данная функциональность является узким местом. Однако если мы даже каким-то образом и распознали узкое место, нам потребуется найти и исправить все десять реализаций. Будь наш код написан по принципу DRY, мы бы, во-первых, ясно увидели, что на функциональность расходуется 30% процессорного времени, а во-вторых, исправлять бы нам пришлось тоже лишь единственное место в коде. И да, нам бы не надо было тратить время на то, чтобы сначала найти все десять мест, как это было бы нужно в WET коде.

Есть один общий случай, когда часто нарушается принцип DRY – это использование множеств. Общей техникой реализации запроса является проход по всему множеству и применение запроса к каждому из элементов:

public class UsageExample {
    private ArrayList allCustomers = new ArrayList();
    // ...
    public ArrayList findCustomersThatSpendAtLeast(Money amount) {
        ArrayList customersOfInterest = new ArrayList(); 
        for (Customer customer: allCustomers) {
            if (customer.spendsAtLeast(amount)) customersOfInterest.add(customer);
        }
    return customersOfInterest; 
    }
}

Открывая это множество клиентам, мы нарушаем инкапсуляцию. И это не только ограничивает нас в возможностях рефакторинга, а также вынуждает пользователей нашего кода нарушить DRY, поскольку наиболее вероятно, что каждому из них придется еще раз реализовать практически аналогичный запрос. Ситуации легко избежать, убрав «сырое» множество из API. В данном случае мы предоставим новый, специфичный для предметной области тип CustomerList . Данный класс послужит «домом» для всех наших запросов.
Этот новый тип легко позволит нам отслеживать факт, являются ли запросы «узким местом» или нет. Включив запросы в класс, мы избавимся от необходимости открывать представления вроде ArrayList клиентам. Это дает нам свободу изменять реализации без страха нарушить контракт с клиентом:

public class CustomerList {
    private ArrayList customers = new ArrayList();
    private SortedList customersSortedBySpendingLevel = new SortedList(); // ...
    public CustomerList findCustomersThatSpendAtLeast(Money amount) {
        return new CustomerList(customersSortedBySpendingLevel.elementsLargerThan(amount)); }
}

public class UsageExample {
    public static void main(String[] args) {
        CustomerList customers = new CustomerList();
        // ...
        CustomerList customersOfInterest = customers.findCustomersThatSpendAtLeast(someMinimalAmount); 
        // ...
    } 
}

В данном примере следование принципу DRY позволило нам предоставить альтернативную схему индексирования при помощи отсортированного списка. Но более важно то, что следование принципу DRY помогло нам найти и устранить узкое место в производительности, что было бы труднее, будь код написан по принципу WET.

Автор оригинала – Kirk Pepperdine