Во многих системах первым признаком неприятностей служит запись сообщения в какой-нибудь журнал. Обычно это журнал регистрации ошибок. Так что окажите себе услугу: организуйте процесс таким образом, чтобы с первого же дня разработки, как только что-то появляется в журнале ошибок, вас будили среди ночи телефонным звонком. Если во время системного тестирования вы сможете смоделировать нагрузку на свою систему, чистый журнал ошибок, скорее всего, засвидетельствует надежность вашей системы, а «грязный» послужит первым сигналом тревоги.
Распределенные системы создают дополнительный уровень сложности. Вы должны решить, как действовать в случае отказа внешних зависимостей. Если система сильно распределена, это может происходить часто. Учтите это обстоятельство при выборе политики ведения журнала.
В целом, лучшим свидетельством того, что все в порядке, служит регулярное появление сообщений низкого приоритета. Меня устраивает, когда на каждое существенное событие в приложении появляется примерно одно сообщение уровня 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 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 позволило нам ввести измененную систему индексирования, в которой используется SortedList, а ключом служит объем трат наших покупателей. И, если абстрагироваться от данного примера, намного важнее, что следование DRY помогло найти и исправить узкое место. Пиши мы код по принципу WET, сделать это было бы труднее.
Когда программисты и тестировщики сотрудничаютДжанет Грегори
Когда тестировщики и программисты начинают сотрудничать, происходят чудеса. Меньше времени уходит на игру в пинг-понг дефектами в системе отслеживания дефектов. Меньше времени тратится на обсуждение того, является ли поведение ошибкой или новой функцией, а больше — на разработку качественного программного обеспечения, отвечающего ожиданиям заказчиков. Существует много возможностей наладить сотрудничество еще до того, как начнется написание кода.
Тестировщики могут помочь заказчикам написать приемочные тесты на языке их предметной области с помощью таких инструментов, как Fit (Framework for Integrated Test). Если передать эти тесты программистам перед тем, как они начнут писать код, те смогут применить практику разработки на основе приемочного тестирования (acceptance test-driven development, ATDD). Программисты пишут фреймворки для прогона тестов, а потом код с проверкой на прохождение этих тестов. Далее эти тесты входят в набор регрессионных тестов. При такой организации сотрудничества функциональное тестирование проходит быстро, и остается больше времени на экспериментальное тестирование граничных условий или сценариев более изощренных рабочих процессов.
Можно пойти еще дальше. Будучи тестировщиком, я могу изложить свои соображения по тестированию еще до того, как программисты начнут писать код новой функции. Если я интересуюсь соображениями программистов, они почти всегда предоставляют мне сведения, позволяющие мне добиться лучшего покрытия кода или сократить затраты времени на ненужные тесты. Часто нам удавалось предотвратить появление дефектов за счет того, что тесты проясняют многие первоначальные идеи. Например, в одном из проектов, где я участвовала, я передала программистам Fit-тесты, которые показывали, какие результаты ожидаются при поиске по маске. Программист до этого твердо намеревался реализовать только поиск по конкретным словам. Нам удалось пообщаться с заказчиком, и мы смогли договориться о правильной интерпретации поиска до того, как начать писать код. В результате мы предотвратили возникновение дефекта и сэкономили всем уйму времени.
Программисты могут сотрудничать с тестировщиками и в деле автоматизации. Они знакомы с хорошей практикой написания кода и способны помочь тестировщикам создать надежный комплекс автоматизированных тестов, что послужит интересам всей команды. Мне часто приходилось видеть, как проекты по автоматизации тестирования завершались неудачей из-за неумелого проектирования тестов. Либо тесты пытаются проверить слишком многое, либо тестировщики недостаточно разбираются в технологии, чтобы сделать тесты независимыми от кода. Тестировщики часто создают узкие места, поэтому полезно, когда программисты работают с ними вместе над такими задачами, как автоматизация. Определив вместе с тестировщиками, что можно протестировать на раннем этапе — возможно, с помощью какого-нибудь простого инструмента, — программисты получают дополнительный канал обратной связи, что в конечном итоге позволяет им создать более качественный код.
Если тестировщики перестанут думать лишь о том, как бы им сломать программу или найти ошибки в коде разработчиков, то и программисты перестанут считать, что тестировщики стараются только «достать» их, и будут более склонны к сотрудничеству. Когда программисты понимают, что они отвечают за качество своего кода, легкость его тестирования становится естественным дополнительным качеством, и команда может совместно автоматизировать больше регрессионных тестов. Таково чудо успешной групповой работы.