Выбрать главу

Распределенные системы создают дополнительный уровень сложности. Вы должны решить, как действовать в случае отказа внешних зависимостей. Если система сильно распределена, это может происходить часто. Учтите это обстоятельство при выборе политики ведения журнала.

В целом, лучшим свидетельством того, что все в порядке, служит регулярное появление сообщений низкого приоритета. Меня устраивает, когда на каждое существенное событие в приложении появляется примерно одно сообщение уровня INFO.

Слишком подробный журнал говорит о том, что систему трудно контролировать во время эксплуатации. Если вы исходите из того, что журнал ошибок должен оставаться пустым, вам будет гораздо легче разобраться в проблеме, когда в нем что-то все-таки появится.

WET размазывает узкие места производительности

Кирк Пеппердин

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

Рассмотрим некоторую функциональность нашей системы (назовем ее X), являющуюся узким местом для процессора. Допустим, что функция X потребляет 30 % мощности процессора. Теперь предположим, что у функции X есть 10 различных реализаций. В среднем каждая реализация потребляет 3 % процессорного времени. Поскольку такой уровень использования процессора не вызывает беспокойства, при беглом анализе можно не заметить, что эта функция создает узкое место. Но допустим, что мы каким-то образом выяснили, что функция X — узкое место. Тогда ставится задача найти и исправить каждую реализацию. Для WET у нас есть 10 разных реализаций, которые нужно найти и исправить. В случае DRY мы сразу увидим загрузку процессора в 30 %, а размер кода для исправления будет в 10 раз меньше. К тому же не потребуется выискивать все многочисленные реализации.

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

public class UsageExample {

   private ArrayList<Customer> allCustomers = new ArrayList<Customer>();

   //…

   public ArrayList<Customer> findCustomersThatSpendAtLeast(Money amount) {

     ArrayList<Customer> customersOfInterest = new ArrayList<Customer>();

     for (Customer customer: allCustomers) {

       if (customer.spendsAtLeast(amount))

         customersOfInterest.add(customer);

     }

     return customersOfInterest;

 }

}

Сделав эту коллекцию напрямую доступной клиентам, мы нарушили принцип инкапсуляции. Это не только снижает потенциал рефакторинга, но и заставляет пользователей кода нарушать DRY, поскольку каждому из них придется заново реализовывать потенциально идентичный запрос. Такой ситуации легко избежать, если убрать открытые коллекции из API. В данном примере можно ввести новый предметно-ориентированный тип коллекции с именем CustomerList. Этот класс семантически лучше согласован с предметной областью. Естественным образом он станет местом, в котором содержатся все наши запросы.

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

public class CustomerList {

   private ArrayList<Customer> customers = new ArrayList<Customer>();

   private SortedList<Customer> customersSortedBySpendingLevel =

     new SortedList<Customer>();

   //…

   public CustomerList findCustomersThatSpendAtLeast(Money amount) {

      return new CustomerList(

         customersSortedBySpendingLevel.elementsLargerThan(amount));