[Тестирование IT-систем, Совершенный код] Код без тестов — легаси (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Если вы работаете в IT, то о легаси вы слышите часто — обычно с множеством негативных коннотаций. Понятно, что это не «хороший код», но какой? Может старый, может не поддерживаемый или не обновляемый, а может просто чужой? Есть ли «полноценное» определение «легаси», на которое можно ссылаться? А когда разберемся — что нам делать с легаси? Попробуем разобраться. Спойлер: выводы неочевидны.
Автор — Николас Карло, веб-разработчик в Busbud (Монреаль, Канада). Специализируется на легаси. В свободное время организует митап Software Crafters и помогает с конференциями SoCraTes Canada и The Legacy of SoCraTes.Данная статья была скомпилирована (и отредактирована) их двух статей Николаса: «What is Legacy Code? Is it code without tests?» и «The key points of Working Effectively with Legacy Code». Показалось логичным рассказать о том, что такое легаси, а потом — как с ним работать.Что такое «легаси»?Возможно, если вы задавались этим вопросом, то встречали определение от Майкла Физерса. Майкл выпустил книгу «Working Effectively with Legacy Code» в 2004 году, но она до сих пор актуальна. Комикс это отлично иллюстрирует.
В своей книге Майкл пишет своё определение:
«Для меня легаси — это просто код без тестов».
Почему Физерс так считает? Потому что по его многолетнему опыту без тестов обычно трудно узнать всё, что код умеет. Если тестов нет, то для понимания, что код делает, вам нужно внимательно его прочитать, воспроизвести программу в своей голове и представить все возможные сценарии. Потом вы поменяете код и нужно снова представить все сценарии. Или проверить их вручную, но всегда есть шанс что-то сломать.Это хорошее определение: чаще всего тесты отсутствуют, так что это хорошее начало. Но это ещё не всё — есть нюансы.Код с тестами также может быть легаси. Если вы читаете тесты, но не можете понять, что должен делать код — они отстой. Плохие тесты только мешают: тестируемый код так же трудно отрефакторить, как если бы у него не было тестов, а может даже и сложнее!Тестов может и не быть, но код всё ещё легко можно отрефакторить. Возможно, вы поддерживаете небольшую кодовую базу без тестов, которую легко понять и рефакторить. Хотя, по моему опыту, это аномалия. Эту кодовую базу можно было бы проверить, но отсутствие автоматизированных тестов всё же не позволяет квалифицировать его как легаси.Перейдём к моему определению легаси.
Легаси — это ценный код, который вы боитесь менять.
Например, мы ищем первопричину ошибки или выясняете, куда вставить свою функцию. Мы хотим поменять код, но это трудно, потому что непонятно как не нарушить существующее поведение. Готово — у нас легаси!Но есть нюансы.Мы переоцениваем сложность незнакомого кода. Поэтому мы думаем, что код, который писали не мы — устаревший. Это работает и с нашими прошлыми проектами, когда мы не можем понять, что закладывали и имели в виду, когда писали эту мешанину на экране.Хорошие тесты помогают легко менять незнакомый код. А плохие тесты не помогают. Отсюда и определение Физерса. С легаси помогает время. Парадоксально: обычно время превращает любой код в легаси, но чтобы его понять нам также помогает время. Если вы начали работать над легаси и это трудно — подождите. Да, большая часть кода ужасна, но вы привыкнете и лучше поймете его причуды и особенности.Легаси не виновато в том, что оно такое. Большая часть кода ужасна, потому что это результат работы многих людей в течение долгого времени с противоречивыми требованиями и под давлением дедлайнов. Это Рецепт Устаревшего Кода™. Когда мало времени и недостаточно знаний — рождаются костыли (ну вы знаете). В конце концов, мы достигнем состояния, когда каждое движение приводит к ошибке, а реализация любой функции занимает целую вечность.А теперь один из важнейших нюансов.
Легаси — это код, который мы изо всех сил пытаемся понять, чтобы поменять.
Легаси — это личная точка зрения. Устаревший код может стать проблемой для каждого разработчика команды. Какой-то код может показаться сложным, потому что мы его ещё не поняли, а какой-то понимаем, но всё равно чувствуем себя некомфортно, когда рефакторим. Но субъективное ощущение «легаси» зависит от нашего понимания кода, и наших чувств по поводу его изменения. Часто люди этого не понимают.В итоге мы получаем, что легаси это:
- код без тестов;
- который мы пытаемся понять, чтобы отрефакторить;
- но боимся.
Что же делать?Как же эффективно работать с легаси?Легаси — код, который мы пытаемся понять, чтобы отрефакторить. Задача рефакторинга в том, чтобы сохранить существующее поведение кода. Как без тестов мы будем уверены, что ничего не сломали? Нам нужна обратная связь. Автоматизированная обратная связь — ещё лучше.Добавить тесты, а затем внести измененияЛогично, что если добавить тесты, они помогут его «прощупать» и он перестанет быть устаревшим. Поэтому первое, что нужно сделать — написать тесты. Только тогда мы будем в безопасности, чтобы рефакторить код.Но чтобы запустить тесты, мы должны поменять код. Возникает парадокс легаси. Мы обречены? Нет. Поменяем как можно меньше кода для тестов:
- Определим точки изменения — «швы».
- Разорвём зависимости.
- Напишем тесты.
- Внесём изменения.
- Отрефакторим.
Первые два пункта самые сложные, а как только доберёмся до тестов, мы знаем, что делать.Найти «швы» для разрыва зависимостейОбычно когда мы добавляем тесты к легаси возникает «проблема зависимостей»: код, который мы хотим протестировать, не может работать, потому что ему нужно что-то сложное для тестирования. Иногда это соединение с базой данных, иногда вызов на сторонний сервер, а иногда — параметр, который сложно создать. А чаще всё и сразу.Чтобы протестировать код, нужно разбить эти зависимости в тестах. Для этого необходимо выявить «швы».
«Шов» — место, где можно изменить поведение программы, не меняя код.
«Швы» бывают разные. Если это объектно-ориентированный ЯП, то обычно это объект, например, в JavaScript.
export class DatabaseConnector {
// A lot of code…
connect() {
// Perform some calls to connect to the DB.
}
}
Допустим, метод connect() вызывает проблемы, когда мы пытаемся поместить код в тесты. Получается, что весь класс — это «шов», который можно поменять. Можно расширить этот класс в тестах, чтобы предотвратить его подключение к реальной БД.
class FakeDatabaseConnector extends DatabaseConnector {
connect() {
// Override the problematic calls to the DB
console.log("Connect to the DB")
}
}
Есть и другие виды швов. Если язык позволяет изменять поведение кода без изменения исходного кода, у нас есть точка входа в написание тестов. Кстати о тестах…Напишем unit-тестыДискуссии о лучших практиках тестирования обычно перерастают в холивары. Применять принцип пирамиды тестов, и писать максимум unit-тестов? Или использовать «Кубок тестирования» и писать в основном интеграционные?Почему советы такие противоречивые? Потому что у них нет единого определения того, что такое «unit». Одни люди говорят об «интеграционных тестах» и тестируют всю библиотеку, а другие тестируют каждый класс по отдельности.Чтобы избежать путаницы, Майкл даёт четкое определение того, что такое НЕ unit-тест:
- он не работает быстро (< 100ms / test);
- он взаимодействует с инфраструктурой, например, базой данных, сетью, файловой системой, переменными;
Напишите максимум тестов, которые обладают этими 2 качествами, при этом неважно, как вы их назовёте.Иногда трудно написать такие тесты, потому что не понятно, что код должен делать. Тогда используйте специальную технику.Тесты для определения характеристик
Это тесты, которые формализуют фактическое поведение части кода.
Вместо того чтобы писать комплексные модульные тесты, мы фиксируем текущее поведение кода — делаем снимок того, что он делает. Тест гарантирует, что это поведение не изменится!Это мощная техника, потому что:
- В большинстве систем то, что код делает важнее того, что он должен делать.
- Мы можем быстро покрыть легаси с помощью этих тестов. Так мы подстрахуемся для рефакторинга.
Этот метод также называют «Approval Testing» («тестированием одобрения»), «Snapshot Testing» или «Golden Master».Но обычно на всё это очень мало времени.Когда совсем нет времени на рефакторингНесколько советов, если предыдущие не подходят.Большие куски кода обладают «гравитацией» и привлекают ещё больше кода. «Теория разбитых окон» в действии: небольшой беспорядок влечёт за собой беспорядок серьёзнее. Если класс уже содержит 2000 строк, то какая разница, что вы добавите еще 3 if оператора и будете поддерживать класс длиной в 2010 строк?Это всего лишь 3 if: тяжело себя убедить, что нужно потратить на них 2 дня, хотя и должны. Что делать, если действительно нет времени писать тесты для этого класса? Используйте техники Sprout (прорастание), Wrap (обёртывание) и скретч-рефакторинг.SproutНапишите код в другом месте, сделайте один тест, определите, где вы должны вызвать этот код из существующего кода (точка вставки), и вызовите свой код из легаси.Рассмотрим на примере:
class TransactionGate {
// … a lot of code
postEntries(entries) {
for (let entry of entries) {
entry.postDate()
}
// … a lot of code
transactionBundle.getListManager().add(entries)
}
// … a lot of code
}
Допустим, нам нужно убрать дубли файла entries, но postEntries() трудно проверить — нет на это времени. Мы можем «прорастить» код где-то ещё, например, в новом методе uniqueEntries(). Этот новый метод легко протестировать, потому что он изолирован. Затем вставим вызов этого метода в существующий, не проверенный код.
class TransactionGate {
// … a lot of code
uniqueEntries(entries) {
// Some clever logic to dedupe entries, fully tested!
}
postEntries(entries) {
const uniqueEntries = this.uniqueEntries(entries)
for (let entry of uniqueEntries) {
entry.postDate()
}
// … a lot of code
transactionBundle.getListManager().add(uniqueEntries)
}
// … a lot of code
}
Минимальные изменения, минимальный риск. Можете «вырастить» один метод, целый класс или что-то ещё, что изолирует новый код.WrapМожно «обернуть» изменение, если оно должно произойти до или после существующего кода.
- Переименуем старый метод, который хотим обернуть.
- Создадим новый с тем же именем и подписью, что и старый.
- Вызовем старый метод из нового.
- Поместим новую логику до/после вызова другого метода.
Эту новую логику можно проверить, потому что старый метод — это «шов», который можно изменить в тестах. Помните предыдущий код?
class TransactionGate {
// … a lot of code
postEntries(entries) {
for (let entry of entries) {
entry.postDate()
}
// … a lot of code
transactionBundle.getListManager().add(entries)
}
// … a lot of code
}
Ещё один способ решить эту проблему — это обернуть её, поэтому мы переходим к postEntries(), списку записей, из которых мы удалили дубли.
class TransactionGate {
// … a lot of code
postEntries(entries) {
// Some clever logic to retrieve unique entries
this.postEntriesThatAreUnique(uniqueEntries)
}
postEntriesThatAreUnique(entries) {
for (let entry of entries) {
entry.postDate()
}
// … a lot of code
transactionBundle.getListManager().add(entries)
}
// … a lot of code
}
В тестах мы бы изменили проблему postEntriesThatAreUnique(), чтобы проверить, работает ли логика удаления дубликатов. Разница может быть ещё больше.
class TransactionGate {
// … a lot of code
+ postEntries(entries) {
+ // Some clever logic to retrieve unique entries
+ this.postEntriesThatAreUnique(uniqueEntries)
+ }
+ postEntriesThatAreUnique(entries) {
- postEntries(entries) {
for (let entry of entries) {
entry.postDate()
}
// … a lot of code
transactionBundle.getListManager().add(entries)
}
// … a lot of code
}
Эти методы не идеальны, и у них есть недостатки. Но это полезные инструменты при работе с легаси. А при необходимости можно даже немного нарушить правила.Скретч-рефакторингСложно работать с кодом, который мы не писали, без тестов и с плохой документацией. Чтобы «выбраться» нам нужно разбить зависимости, написать тесты. Но с чего вообще начинать, когда код непонятен? Хорошая техника — скретч-рефакторинг.Его цель в том, чтобы ознакомиться с кодом, а не менять. Мы «играем» с кодом столько, сколько захотим: извлекаем функции, упрощаем, переименовываем переменные. Как только сделаем, всё, что нам нужно — откатим всё обратно и начнём с правильных тестов.ВыводыЛегаси будет везде, где бы вы ни работали, в каждой кодовой базе. Можно сопротивляться и чувствовать себя плохо, когда вы застряли в нём. А можно рассматривать это как возможность. Работа со старым кодом это очень ценный навык, его надо изучать теоретически (почитайте книгу «Working Effectively with Legacy Code») и практиковать в ежедневных задачах.Похожие и интересные статьи:
- Как мы выпиливали Realm.
- Как мы накосячили пока делали Бриллиантовый чекаут™ 9 месяцев, а планировали 2.
- Как заблокировать приложение с помощью runBlocking.
- О том, над чем в целом мы тут работаем: монолит, монолит, опять монолит.
- Как у нас работает QA-команда.
- Кратко об истории Open Source — просто развлечься (да и статья хорошая).
Больше новостей про разработку в Додо Пицце я пишу в канале Dodo Pizza Mobile. Также подписывайтесь на чат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на канал Dodo Engineering, где мы постим всё, что с нами интересного происходит.А если хочешь присоединиться к нам в Dodo Engineering, то будем рады — сейчас у нас открыты вакансии iOS-разработчиков (а ещё для Android, frontend, SRE и других).
===========
Источник:
habr.com
===========
===========
Автор оригинала: Nicolas Carlo
===========Похожие новости:
- [Firefox, Тестирование IT-систем, Проектирование и рефакторинг, Браузеры, Софт] Разработчики Firefox закрыли тикет 17-летней давности
- [Тестирование IT-систем, Системное администрирование, DevOps] Хаос на практике: зачем ломать production?
- [Тестирование веб-сервисов, Тестирование мобильных приложений] Видео с Kolesa QA Meetup 3.0: QAцентризм, подготовка данных к тестам и независимые моки
- [Программирование, Java, Совершенный код, C#, Kotlin] Лучший язык программирования
- [Совершенный код, .NET, API, C#, Микросервисы] Паттерн CQRS: теория и практика в рамках ASP.Net Core 5
- [Программирование, Совершенный код] Что такое хороший код? Считаем звёзды
- [Тестирование IT-систем, Python, Карьера в IT-индустрии, VueJS] Дайджест митапов и практикумов: Frontend, Web, QA и не только
- [Тестирование IT-систем, Тестирование веб-сервисов, Kotlin] Kotlin. Автоматизация тестирования (Часть 2). Kotest. Deep Diving
- [Тестирование IT-систем, Подготовка технической документации] Чек-лист тестирования требований
- [Тестирование IT-систем, IT-инфраструктура, API, DevOps] Что такое системы API Management
Теги для поиска: #_testirovanie_itsistem (Тестирование IT-систем), #_sovershennyj_kod (Совершенный код), #_testy (тесты), #_legasi (легаси), #_legacy, #_junit (юнит), #_blog_kompanii_dodo_engineering (
Блог компании Dodo Engineering
), #_testirovanie_itsistem (
Тестирование IT-систем
), #_sovershennyj_kod (
Совершенный код
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:19
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Если вы работаете в IT, то о легаси вы слышите часто — обычно с множеством негативных коннотаций. Понятно, что это не «хороший код», но какой? Может старый, может не поддерживаемый или не обновляемый, а может просто чужой? Есть ли «полноценное» определение «легаси», на которое можно ссылаться? А когда разберемся — что нам делать с легаси? Попробуем разобраться. Спойлер: выводы неочевидны. Автор — Николас Карло, веб-разработчик в Busbud (Монреаль, Канада). Специализируется на легаси. В свободное время организует митап Software Crafters и помогает с конференциями SoCraTes Canada и The Legacy of SoCraTes.Данная статья была скомпилирована (и отредактирована) их двух статей Николаса: «What is Legacy Code? Is it code without tests?» и «The key points of Working Effectively with Legacy Code». Показалось логичным рассказать о том, что такое легаси, а потом — как с ним работать.Что такое «легаси»?Возможно, если вы задавались этим вопросом, то встречали определение от Майкла Физерса. Майкл выпустил книгу «Working Effectively with Legacy Code» в 2004 году, но она до сих пор актуальна. Комикс это отлично иллюстрирует. В своей книге Майкл пишет своё определение: «Для меня легаси — это просто код без тестов».
Легаси — это ценный код, который вы боитесь менять.
Легаси — это код, который мы изо всех сил пытаемся понять, чтобы поменять.
«Шов» — место, где можно изменить поведение программы, не меняя код.
export class DatabaseConnector {
// A lot of code… connect() { // Perform some calls to connect to the DB. } } class FakeDatabaseConnector extends DatabaseConnector {
connect() { // Override the problematic calls to the DB console.log("Connect to the DB") } }
Это тесты, которые формализуют фактическое поведение части кода.
class TransactionGate {
// … a lot of code postEntries(entries) { for (let entry of entries) { entry.postDate() } // … a lot of code transactionBundle.getListManager().add(entries) } // … a lot of code } class TransactionGate {
// … a lot of code uniqueEntries(entries) { // Some clever logic to dedupe entries, fully tested! } postEntries(entries) { const uniqueEntries = this.uniqueEntries(entries) for (let entry of uniqueEntries) { entry.postDate() } // … a lot of code transactionBundle.getListManager().add(uniqueEntries) } // … a lot of code }
class TransactionGate {
// … a lot of code postEntries(entries) { for (let entry of entries) { entry.postDate() } // … a lot of code transactionBundle.getListManager().add(entries) } // … a lot of code } class TransactionGate {
// … a lot of code postEntries(entries) { // Some clever logic to retrieve unique entries this.postEntriesThatAreUnique(uniqueEntries) } postEntriesThatAreUnique(entries) { for (let entry of entries) { entry.postDate() } // … a lot of code transactionBundle.getListManager().add(entries) } // … a lot of code } class TransactionGate {
// … a lot of code + postEntries(entries) { + // Some clever logic to retrieve unique entries + this.postEntriesThatAreUnique(uniqueEntries) + } + postEntriesThatAreUnique(entries) { - postEntries(entries) { for (let entry of entries) { entry.postDate() } // … a lot of code transactionBundle.getListManager().add(entries) } // … a lot of code }
Больше новостей про разработку в Додо Пицце я пишу в канале Dodo Pizza Mobile. Также подписывайтесь на чат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на канал Dodo Engineering, где мы постим всё, что с нами интересного происходит.А если хочешь присоединиться к нам в Dodo Engineering, то будем рады — сейчас у нас открыты вакансии iOS-разработчиков (а ещё для Android, frontend, SRE и других).
=========== Источник: habr.com =========== =========== Автор оригинала: Nicolas Carlo ===========Похожие новости:
Блог компании Dodo Engineering ), #_testirovanie_itsistem ( Тестирование IT-систем ), #_sovershennyj_kod ( Совершенный код ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:19
Часовой пояс: UTC + 5