Распределенные системы создают дополнительный уровень сложности. Вы должны решить, как действовать в случае отказа внешних зависимостей. Если система сильно распределена, это может происходить часто. Учтите это обстоятельство при выборе политики ведения журнала.
В целом, лучшим свидетельством того, что все в порядке, служит регулярное появление сообщений низкого приоритета. Меня устраивает, когда на каждое существенное событие в приложении появляется примерно одно сообщение уровня 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));