Pull to refresh

Comments 61

Спасибо за статью — очень интересно написано.
Но TDD и юнит-тестирование не являются серебрянной пулей от появления легаси-кода.

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

И ещё пара слов про TDD: Я не понимаю людей, для которых TDD — это икона идеального процесса разработки.
Это хороший подход в определённых условиях, и он оправдан для критически-важных модулей, API и в случае, если в проекте мало или вообще нет QA инженеров.
А слепое использование TDD как раз и приводит к проблемам в архитектуре. Ведь часто программист пишет тесты, а потом начинает подгонять под них код, при этом не задумываясь о картине в целом. И опять-же мало кто из заказчиков готов платить за TDD, т.к. это удваивает расходы на разработку. (Хоть и значительно сокращает их на поддержку — но об этом никто не думает)
TDD не гарантирует, что не получится legacy, но код тесами не покрытый — legacy однозначно. Потому что при исправлении нет возможности понять, сломал ты что-нибудь или нет.
Код покрытый тестами и TDD — это не одно и то же.
Да и само покрытие юнит-тестами не даёт 100% гарантии от ошибок. Ведь ошибка может быть в тесте или именно такая ситуация тестом не покрыта.
В то же время простой и лаконичный код не покрытый тестами всё-же лучше, чем запутанный код, покрытый ими.
Юнит-тестирование — это «защита от дурака», но не панацея.

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


Практика показывает, что даже тривиальные методы со временем легко могут стать нетривиальными. Скажем, простой пример из моей библиотеки StreamEx: метод AbstractStreamEx.forEach(). Когда-то это был просто делегат к стандартному Java 8 Stream.forEach(). Казалось бы, тупо тестировать делегат. Оригинальный метод протестирован разработчиками OpenJDK вдоль и поперёк, да и если там бага, то вряд ли меня, как разработчика библиотеки это колышет. А в простом делегате сломаться ничего не может.


Однако за время разработки метод сильно поменялся. Во-первых, появилась поддержка пользовательских ForkJoinPool'ов. Это надо обработать вручную, так как стандартное Stream API этого не умеет (ветка context.fjp). Во-вторых, появился быстрый путь, который иногда позволяет не создавать Java 8 стрим вообще (в некоторых сценариях это существенно ускоряет работу). Конечно, когда этот метод менялся, писались новые тесты. Но при этом весьма важно было, что хоть какие-то тесты уже были на каждый метод. Ведь подобные изменения (например, поддержка пользовательских пулов) затрагивают сразу кучу методов, трудно сразу их все исправить и ещё и на все написать тесты. Так легко стать жертвой копипасты или фигурную скобку не там поставить или ещё что-нибудь. А когда есть старые тесты, покрывающие тривиальную реализацию, я могу быть хоть немного уверен, что новые фичи не меняют поведение в старых сценариях.

Отличный пример с forEach! Спасибо! Конечно код с тестами лучше чем код без тестов. Я думаю с этим все согласятся.

А если не секрет, то что у вас за проект (в смысле консалтинг/работа на заказчика или коробочное решение/работа на свою компанию или open-source)?
Просто из моего опыта (в основном работа на заказчика/консалтинг) — почти никто не хотел платить даже за банальное покрытие критических частей системы юнит-тестами, не говоря уже о TDD.
Всем нужно быстро и с минимальным бюджетом.

Это конкретно опенсорсный любительский проект и он покрыт тестами на 98.6%. Вообще на последних работах, где я работал, написание юнит-тестов исключительно приветствуется.

Для Open-source наличие юнит тестов крайне желательно. Тут даже спорить не о чем.
К сожалению в коммерческих проектах всё не так радужно. Т.е. никто не будет тебя ругать за написание юнит-тестов и использование TDD, но тем не менее сроки обычно настолько сжаты, что разве что в говнокод не позволяют скатываться, а о юнит-тестах остаётся только мечтать.
Ещё раз повторюсь, как раз юнит-тесты позволяют укладываться в сроки и разрабатывать проект с более-менее постоянной скоростью.
Не все заказчики это понимают. Иногда в ход идёт вот такая двойка:
1) «Соображения моего бизнеса требуют, чтобы мы показали первую версию в след. понедельник. На качество пофиг. Не успеем — проекта не будет.»
2) «Так всё же уже работает, осталось только пофиксить баги. Вот вам на это ещё два дня — я щедрый»

Разумеется, правильно будет не работать с такими. Но не у всех и не всегда есть выбор.
Да, типичное видение ситуации.

А откуда заказчик значет, что вам хватит два дня?
А что если он скажет «вот вам один день»? А «полдня»?
Что вы тогда ему скажете?
Ну вот, а почему вы про тесты то же самое не говорите?
А откуда заказчик значет, что вам хватит два дня?


Он полагается на своё «профессиональное» чутье :)

А что если он скажет «вот вам один день»? А «полдня»?
Что вы тогда ему скажете?
Ну вот, а почему вы про тесты то же самое не говорите?


У меня, к счастью, больше нет таких заказчиков.
Когда были, то приходилось тратить до 70% времени на переговоры.

Моя мысль не в том, что это правильно. А в том, что часто TDD и другие полезные практики не используются под давлением таких вот горе-заказчиков.
Он полагается на своё «профессиональное» чутье :)

А где же ваша профессиональная оценка?

У меня, к счастью, больше нет таких заказчиков.

Вы таки уходите от ответа.
Вы скажете ему, что это невозможно. Кто же вам мешает сказать то же самое про тесты?
Только не говорите мне, что он этого не потерпит, уйдёт к другому. Не уйдёт. Потерпит.

часто TDD и другие полезные практики не используются под давлением таких вот горе-заказчиков.

Мысль понятна.
А моя мысль в том, что это лишь отговорка.
Очень удобно всё свалить на горе-заказчика.
Кто хочет — ищет способ. Кто не хочет — ищет причину.
Вы скажете ему, что это невозможно


Это зависит от моих целей. Прямо сейчас я скажу, что не заинтересован делать проект на таких условиях.
А вот 14 лет назад, когда мы с парой друзей начинали свой бизнес я отвечал иначе. Я соглашался на любые условия, потому что для меня жизненно важно было иметь заказчиков. Любых. Хороших тогда у меня не было.

Только не говорите мне, что он этого не потерпит, уйдёт к другому. Не уйдёт. Потерпит..

Кто-то потерпит, кто-то уйдёт.
Когда вам нет смысла цепляться за любого заказчика любой ценой, это для вас неважно. Уйдёт так уйдёт.

Очень удобно всё свалить на горе-заказчика.


Если вы о «мыши плакали, кололись, но продолжали есть кактус», то соглашусь. Но бывает и такая ситуация: «Здесь и сейчас мне нужен этот заказчик и я буду играть по его правилам. Как только ситуация позволит, я это изменю.»

Такая ситуация мне не понятна. Как можно понять, что код работает, если он не протестирован? На мой взгляд, писать код без тестов — это просто не профессионально.

Как я уже писал, TDD — это вообще не защита, а способ разработки.
Весь покрытый тестами, абсолютно весь
Код, который legacy, на гитхабе есть…
zenkz

TDD и юнит-тестирование не являются серебрянной пулей
Спасибо, кэп!
Ничто на свете не является серебряной пулей. Серебряных пуль в принципе не бывает.

А слепое использование TDD как раз и приводит к проблемам
Спасибо, кэп!
Слепое следование чему угодно приводит к проблемам. С этим невозможно не согласиться.

TDD — это икона идеального процесса разработки
Не совсем так. TDD — не икона. У него есть свои нюансы. И я вполне допускаю, что могут быть средства получше TDD. Просто их пока нет. Просто человечество пока не придумало ничего лучше. Ничего, даже близко стоящего по эффективности.

Ведь часто программист пишет тесты, а потом начинает подгонять под них код
Браво, браво, изящный троллинг! Я восхищён.

Да, вообще-то вся идея как раз в том и состоит, чтобы подгонять код под тесты. А вы произносите это таким тоном, как будто это плохо. Изящно. Давайте попробуем ещё варианты: «Подгонять код под спецификацию». «Подгонять код под требования клиента». «Подгонять код под архитектуру». Хорошо звучит, сочно.

Мало кто из заказчиков готов платить за TDD, т.к. это удваивает расходы на разработку.

Распространённый страх.
На это есть простой ответ. Этот вопрос не надо задавать заказчику. Врач не спрашивает, нужно ли ему стерилизовать инструменты перед операцией. Кузнец не спрашивает, раздувать ли ему меха. Это ваш профессионализм, при чём тут заказчик? Делайте так, как считаете нужным, иначе у вас нет причины называть себя профессионалом.
Зачем же так агрессивно?
Никто же не говорит, что TDD — это плохо.
Основным плюсом TDD является то, что этот подход позволяет сразу писать тестируемый код.
Основным минусом, как я уже упомянул выше, является то, что прохождение тестов для некоторых программистов становится выше, чем хороший алгоритм и лаконичный код.
Проблема подгонки кода под тесты очень важная, поэтому вы зря иронизируете по этому поводу. Ведь чтобы получился хороший код — нужно написать хорошие тесты. А если тесты написаны плохо, то программист может довести разработку до «зелёного» статуса и успокоиться на этом.
Конечно, любой инструмент можно использовать неправильно. Можно писать хороший код вообще без автоматических тестов, а можно написать ужасный код с TDD (а можно и наоборот).

Отдельно хочу поспорить про «Подгонять код под спецификацию». «Подгонять код под требования клиента». «Подгонять код под архитектуру» — это тоже плохо.
Спецификация должна быть живой, и если в ходе разработки появились идеи как улучшить результат, то можно поменять спецификацию.

Подгонять код под клиента — это основа костылестроения. Да, мы все делаем это и в этом нет ничего страшного, но часто именно из-за хотелок клиента код превращается в лапшу, в которой, действительно, без TDD ничего не изменишь не сломав.

Подгонять код под архитектуру — это тоже странно. Если вам это приходится делать, то скорее всего проблема в архитектуре (она недостаточно гибкая или не подходит под текущий проект). (Тут я именно про «подгонять», т.к. любой код должен быть изначально написан с оглядкой на архитектуру приложения — будет странно писать JSON-RPC, если все остальные сервисы используют REST)

По поводу готовности заказчика платить за TDD. Профессионализм тут не причём. Это бизнес. К примеру компания участвует в конкурс на разработку ПО. В компании А работают профессионалы, поэтому они заложили в цену TDD, а в компании Б работают обычные программисты, и они решили писать без TDD и вообще без юнит-тестов (что позволило им заложить минимальные цены). Угадайте кто выиграет конкурс?

Не хочу вас обидеть, но к вам наверно тяжело на работу устроиться… Очень уж много максимализма в ваших суждениях о TDD. Но тем не менее спасибо за вашу статью и комментарии… Это действитеьно интересная тема для обсуждения…

Основным минусом, как я уже упомянул выше, является то, что прохождение тестов для некоторых программистов становится выше, чем хороший алгоритм и лаконичный код.

Да не верю я! Ну не бывает такого.
Практику TDD осваивают в первую очередь те, кто заботится о качестве кода и хорошей архитектуре. Они ищут, как этого добиться, и находят один хороший способ — TDD. Я в жизни не поверю, что есть раздолбаи, которым пофиг качество кода, но которые заботятся о тестах.

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

Ну так блин, а кто вам мешает сказать про тесты то же самое? Тесты должны быть живыми, и если в ходе разработки появились идеи как улучшить результат, то можно поменять тесты.

Профессионализм тут не причём. Это бизнес.

Да, хороший пример. Проблема с конкурсами действительно есть. Мы, например, проиграли несколько конкурсов больших государственных компаний.

Но мы нашли простое решение. Мы теперь ищем заказчиков не из госсектора, а таких, которые заинтересованы в результате. Которые уже обожглись на конкурсах на основе низких цен, и теперь смотрят не только на цену, но и на качество. Таких заказчиков немало, ох немало. Все же уже пообжигались за время существования IT-индустрии. И такие заказчики гораздо интереснее. Они знают, чего хотят. Они хотят добиться результата, а не просто потратить бюджет.

к вам наверно тяжело на работу устроиться

Как посмотреть. В некотором роде даже проще, чем во многие другие фирмы. Мы не требуем знания каких-то специальных технологий. Мы не требуем какого-то особенного опыта. Мы не просим дипломы, сертификаты и прочие бумажки. Мы готовы научить всему, что умеем. Мы ждём только ответственного отношения к результату своей работы. Но — да, тестовое задание со сложным кодом или без тестов имеет нулевые шансы на успех. Это, по-вашему, сложно? По-моему, это логично.
это удваивает расходы на разработку


Только на маленьких проектах. На долгоживущих получается более медленный старт, и здесь расходы выше. Но потом они «отбиваются» за счет сохранения постоянной скорости разработки новых версий. В проекте без TDD функция расходов на внесение измнений от времени обычно становится экспоненциальной.
softaria Спасибо, хотя я и с этим не согласен. TDD не удваивает расходы на разработку, даже и в маленьких проектах.
Все наивно полагают, что основное время разработчика уходит на набор кода. Поэтому больше букв — больше времени.
Но на самом деле мы все умеет печатать буквы и жмыкать шорткаты очень быстро. Основное время уходит не на печатанье, а на думанье. А TDD как раз помогает думать. Поэтому оно сокращает общее время.

TDD не сокращает время только там, где не надо думать. А такое бывает?
Все же есть какой-то размер проекта, где TDD избыточен. Крайний случай — я пишу одноразовый скрипт на перле, который выброшу после обработки одного текстового файла. В скрипте 5 строк.
А вот где граница между таким случаем и проектами, где TDD полезен каждый определяет по собственному опыту.
Для меня граница проходит примерно по проекту размером в человекомесяц, писанному одним человеком и без перспектив развития.
Да, есть какая-то граница, за которой TDD не нужен.
Но мне кажется, она зависит вовсе не от размера проекта. И для однострочечного метода можно и нужно написать тест(ы). А вот для скрипта на перле — … наверное, никто никогда не пишет. Даже если он в 100500 строк.
Но TDD и юнит-тестирование не являются серебрянной пулей от появления легаси-кода.

Есть несколько определений legacy code. Одно приведено в статье: «Код не покрытый юнит-тестами». Мне ещё нравится другое определение: «Код, который страшно изменять».

Если использовать первое определение легаси кода то TDD является именно что серебряной пулей, потому что изначально не появляется никакого не покрытого тестами поведения.

Костыли появляются потому что в систему становится страшно вносить изменения. Полностью покрытая юнит-тестами система убивает этот страх и вместо костылей вы получаете возможность проводить нормальный рефакторинг. Полностью покрыть юнит-тестами систему, сделать её тестопригодной, легче и лучше всего с помощью TDD.

А слепое использование TDD как раз и приводит к проблемам в архитектуре

Можете привести примеры? Обратная связь от кода наоборот должна помогать построить правильный дизайн системы.

Michael C. Feathers: “Code without tests is bad code. It doesn't matter how well written it is; it doesn't matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behavior of our code quickly and verifiably. Without them, we really don't know if our code is getting better or worse.”

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

Тоже хочу пример. Любой. Обычно, TDD ускоряет и разработку и внесение изменений. Интересно послушать другие кейсы.

Статья прям пропитана влиянием Дяди Боба :)
Кстати, да, определение «Код, который страшно изменять» мне нравится ещё больше.
Потому, что покрыт он или покрыт тестыми — это всё-таки косвенный признак, а вот «страшно менять» — это уже конкретно проблема.

P.S. Не, книжку Дяди Боба я прочитал позже, чем начал строчить на хабре статьи про тесты. :)

Тесты — это хорошо, спору нет. Но превращать TDD в икону, по-моему, неправильно. И code-first подход хорошо работает. Писать тест вперёд полезно при багфиксинге, когда уже есть API, который в определённых условиях работает неправильно. Тогда ты убеждаешься, что тест действительно воспроизводит проблему, и потом убеждаешься, что тест стал проходить, то есть проблема исправлена. Однако писать тесты на API, которого ещё нет, я как-то не вижу большого смысла. Я ещё могу понять написать интерфейсы, на них сделать тесты, которые хотя бы компилируются, а потом к ним прикрутить реализацию. А на пустом месте начинать с тестов странно.


Довод, что тесты помогут сделать API удобным, мне кажется несостоятельным. В процессе развития API удобное легко перерастает в неудобное. Причём это может случиться как за несколько часов разработки, так и через несколько лет. Ключ к успеху в том, что не надо бояться изменить API. Даже если ещё вчера оно казалось классным, а сегодня выплыли проблемы, надо не бояться брать и переделывать. Если не боишься, то TDD ничего не решает. Написал код, написал тест, показалось неудобно — переделал код. Если переделывать боишься и будешь тесты подгонять под код, то TDD тоже ничего не решает. Написал тесты, написал код, который подогнан под тесты. Завтра понял, что чего-то в API не учёл, стало всё неудобно, надо переделывать и тесты и код, а ты боишься.

lany тесты помогают сделать API удобным, потому что ты пишешь тест ДО того, как писать API. И в этот момент ты физически вынужден продумать, каким оно должно быть, чтобы его было удобно использовать. Здесь и сейчас, реально в коде, а не на бумажке. Бумажка всегда далека от жизни.

API, конечно, будет постоянно меняться. Так юнит-тесты как раз тем и хороши, что они позволяют не бояться менять код.
А если у тебя нет тестов, что даёт тебе основание не бояться? Ведь любое изменение может всё сломать. Вот почему ты не боишься менять API?
Вот почему ты не боишься менять API?

Наверно, потому что я смелый и уверенный в себе. Но уж точно не потому что есть юнит-тесты. Юнит тесты позволяют не бояться переделывать реализацию. Но при переделке API юнит-тесты тоже приходится переделывать.


Необязательно было выделять "ДО" капсом, я и так понял посыл из интервью. Я как раз и говорю, что особо не верю в это. Даже если написать юнит-тесты до кода, необязательно получится с первого раза хорошо (скорее нет, чем да). И переделывать всё равно придётся. Кроме того, не стоит забывать, что API — это двусторонний контракт. Во-первых, у него есть пользователи, а во-вторых, реализации (часто одна, но иногда и много). И удобство реализаций не менее важно, чем удобство использования. Давай простой пример рассмотрим, ты пишешь движок регулярных выражений. Пишем тест как удобно (реализации ещё нет):


String myTestString = "blahblahfoo";
assertTrue(MyRegexp.matches(myTestString, "(foo|bar|baz)$"));
assertFalse(MyRegexp.matches(myTestString, "^(foo|bar|baz)"));

Отлично, мы поняли, что нам нужно API из одного статического метода MyRegexp.matches. Потом мы начинаем реализовывать этот метод и понимаем, что в реализации есть два выделенных этапа. Первый — компиляция регулярного выражения (70% времени), а второй — применение его к конкретной строке (30% времени). С придуманным нами удобным API разделить эти два процесса весьма трудно и получится криво. Видали код java.util.Scanner? Там даже LRU-кэш сделали в подобной ситуации, хотя могли просто API сделать умнее. То есть наш удобный с точки зрения использования API оказался совсем неудобным с точки зрения реализации. Было бы лучше для переиспользования компилированное состояние хранить в инстансе, например, так:


String myTestString = "blahblahfoo";
assertTrue(new MyRegexp("(foo|bar|baz)$").matches(myTestString));
assertFalse(new MyRegexp("^(foo|bar|baz)").matches(myTestString));

Отлично, пишем реализацию дальше. Тут мы заметили, что некоторые группы регекспов принципиально отличаются друг от друга. Например, если регексп — это простой литерал, хотелось бы сделать какой-то отдельный класс MyLiteralRegexp, который существенно проще внутри, жрёт меньше памяти и работает быстрее, чем полноценный регексп. Значит, MyRegexp должен быть интерфейсом с несколькими реализациями и new MyRegexp уже не сработает, нужен фабричный метод, который по выражению поймёт, какую реализацию подставить. И нам ещё раз приходится переписывать тест:


String myTestString = "blahblahfoo";
assertTrue(MyRegexp.compile("(foo|bar|baz)$").matches(myTestString));
assertFalse(MyRegexp.compile("^(foo|bar|baz)").matches(myTestString));

Ну и как нам TDD помогло спроектировать хорошее API?

TDD не отменяет необходимости думать о реализации. Он лишь заставлет думать и об удобстве исрользования.
Важны оба аспекта. TDD помогает в одном. Вы критикуете его за то, что не помогает во втором. Но ведь он — не серебряная пуля, чтобы решать абсолютно все проблемы.

Нет, вы не поняли. На мой взгляд, TDD не решает никакую проблему. Утверждается, что TDD помогает спроектировать хорошее API. Я в своём ответе спорю именно с этим утверждением, ни про какое второе я не говорю.

Хорошее API — это такое, которое удобно использовать и практично реализовывать. TDD помогает в первом аспекте и не помогает во втором. В этом моя мысль.
Наверно, потому что я смелый и уверенный в себе. Но уж точно не потому что есть юнит-тесты.

Серьёзно? Это без шуток?
Тогда вы очень опасный человек. Вы путаете смелость и безрассудство. Это ведь то же самое, что ходить по стройке без каски, ездить непристёгнутым и крутить провода под напряжением голыми руками. Потому, что вы смелый и уверенный.

Такие люди самые опасные в нашей профессии. Именно они всё ломают. Они ж смелые.

Ваш пример с `MyRegexp` хороший. Но он как раз ничего плохого про TDD не доказывает.
Да, требования меняются, пожелалки заказчика меняются, и приходится менять API. Это обычное дело.
Ваш API по сути прошёл через три стадии развития:
1. Релиз 1:
MyRegexp.matches

2. Релиз 2:
new MyRegexp()

3. Релиз 3:
MyRegexp.compile()

(предыдущий комментарий случайно отослался)

Наверно, потому что я смелый и уверенный в себе. Но уж точно не потому что есть юнит-тесты.

lany Серьёзно? Это без шуток?
Тогда вы очень опасный человек. Вы путаете смелость и безрассудство. Это ведь то же самое, что ходить по стройке без каски, ездить непристёгнутым и крутить провода под напряжением голыми руками. Потому, что вы смелый и уверенный.

Такие люди самые опасные в нашей профессии. Именно они всё ломают. Они ж смелые.

Ваш пример с `MyRegexp` хороший. Но он как раз ничего плохого про TDD не доказывает.
Да, требования меняются, пожелалки заказчика меняются, и приходится менять API. Это обычное дело.
Ваш API по сути прошёл через три стадии развития:
1. Релиз 1: MyRegexp.matches()
2. Релиз 2: new MyRegexp()
3. Релиз 3: MyRegexp.compile()

И каждую из них вы могли разрабатывать с помощью TDD.
И на каждой из стадий API мог получиться лучше или хуже. С TDD обычно лучше, чем без. В данном примере это неочевидно, потому что на каждом стадии альтернатив-то практически и нет.

Почему нет? Всё в порядке, всё так и бывает. Не вижу никаких противоречий.
Наверно, потому что я смелый и уверенный в себе.

image

Я бы советовал бы прочитать/перечитать следующую литературу:
Кент Бек "Экстремальное программирование".
На вопрос почему XP дает хорошие результаты отвечает Стив Макконнел в "Совершенный код".
Если вы считаете, что умеете писать тесты, то Кори Сандлер, Том Баджетт, Гленфорд Майерс "Искусство тестирования программ".

вы все откуда беретесь с этими советами и с книжками пятнадцатилентней давности? Думаете, asolntsev все это не читал? :)

Тогда повседневная разработка сильно отличалась — не было CI, не было нормальных IDE, например. Времена меняются.
Бог с ними с посоветованными книгами, их советовать можно. Я, конечно, не знаю, текст слабо выражает эмоции, но чувствуется, что человек советует эти книги для джунов с таким опломбом, вот это и весело :)
ага, у меня те же ощущения. Эти книги можно советовать старшекурсникам и джунам, верно. Довольно глупо выглядит попытка советовать такую литературу эксперту :)
IDE таки были. Даже eclipse как раз 15 лет назад появился. А до него были Visual Age, NetBeans, Borland c-builder тот же.
По сравнению с современными они были — так, текстовыми редакторами. Все было другое — и возможности рефакторинга и возможности по разработке тестов и инфрастуруктуры и тестовые фреймворки.
Возможности, конечно, были ниже. Но вот какого-то драматического скачка, в развитии именно IDE, делающего какие-то книги устарешими, на мой взгляд, за последние 15 лет не было. Рефакторинг уже был, хотя и гораздо слабее.

Он вскользь упоминает о необходимости другого вида тестирования. Модульное тестирование обеспечивает в эффективность в среднем 30%. Что-то в этом отношении поменялось, после того как Макконнел опубликовал эти цифры?


Так же он недостаточно говорил о рефакторинге кода. В XP рефакторинг включается в итерацию. В TDD тоже используется рефакторинг. Рефакторинг кода во время разработки, это выплата процентов по техническому долгу. Все об этом знают, но как выразился asolntsev:


Это как спорт: все знают, что спорт полезен, но ленятся.

Почитай комментарии, все свелось к обсуждению "писать тесты до" или "писать тесты после".

Иногда рефакторинг — это ответ на новые требования к новым версиям продукта.
Представьте, что вам дали задачу поправить часть кода. В голове возникнет много мыслей. Кто его написал? Когда? А может он — legacy? Где документация? Давайте попробуем разобраться с «наследием» основательно и со всех сторон.


Ну и где в тексте про все это?

Статья сводится к «Только идиоты не используют TDD и не пишут юнит-тесты. Правда я в своем проекте тоже этого не делаю». И вы это называете «без максимализма»?
slonopotamus Вы не забывайте, это интервью. Так часто бывает в интервью: журналист подготовил один план, а по ходу дела беседа вывела совсем на другую орбиту. Думаю, это хорошо, и журналисты к этому готовы. Их первоначальный план — лишь примерный. Фраза «без максимализма» тоже из того примерного плана.

Вы передёргиваете. Меня спросили, как бороться с легами-кодом, и я дал свой ответ.
Кстати, вы невнимательно читали. Я использую TDD во всех своих проектах. Я не использовал его только в одном, том самом проекте, и то только вначале, 5 лет назад.
В целом статья правильная и я согласен с автором. А вот что вы посоветуете делать когда есть legacy код который очень плохо покрывается unit тестами?
Зависит от ситуации.
Если этот легаси менять особо не требуется — может, лучше не трогать.
Если же вам приходится его менять, и это причиняет боль, то надо рефакторить и дописывать тесты. По чуть-чуть, помаленьку. Выделить кусочек, написать на него тесты, порефакторить. И так далее.
По своему опыту — в коде очень трудно начать писать тесты, если нет какого-то вмеяемого представления о том, как их писать в этом конкретном месте. То есть дописать 101ый тест после 100 существующих — легко. Написать 1ый тест — подвиг, размер которого никто даже оценить не может.

Второй сценарий — чужой проект, в который в нужном месте что-то дописываешь. Например, «ещё один тег в xml'ку». А где и как описать это в тестах — совершенно неподъёмная задача, требующая иногда больше усилий, чем оригинальная.
Да, верно. Ну так это можно сказать про всё на свете. И тысячное слово на узбекском легче учить, чем первое.

Насчёт второго сценария: да, конечно, в этом и есть главная проблема легаси кода.
Тут проблема в другом — тесты всё равно идут как «пёрышки», сколько бы про их важность не говорили. Потому что код тестов ничего полезного для продукта не делает, он помогает только его сопровождению (то есть работает на нужды бэк-офиса). Если цейтнот, нет ресурсов или мотивации, то «нужды бэкофиса» с лёгкостью пропускаются — пишется только код для продакшена.
Всё в ваших руках.
Про «код тестов ничего полезного не делает»: всё это полная чушь. Это типичный страх тех, кто даже не пробовал объяснить заказчику, зачем это нужно.

Сами посудите, ни один пассажир такси не скажет, что «правила дорожного движения ничего полезного не делает, давайте их не соблюдать» или «ремень безопасности не везёт меня на самолёт, а у меня цейтнот — давайте его не пристёгивать». И ни один водитель не скажет: «У меня цейтнот и нет мотивации, поэтому пофиг, поедем через болото». Всё в ваших руках.
Отличные аналогии:

«забей на правила, припаркуйся поближе к парадной, мне эту глыбу 30 метров на себе переть не хочется». И таксист может подъехать поближе по тротуару в нарушение ПДД.

«какой нафиг ремень сломан и не поеду? У нас дедлайн горит, поехали так».

«в объезд 50 км смысла ехать нет — когда приедем, будет уже всё равно. Либо пробуем тут проехать, либо всё, сворачиваем лавочку».

Хотя на самом деле ситуация ещё более тонкая: если спецификация меняется очень динамически, то тесты осложняют изменение спецификации, то есть мешают огружать minimally viable product. Качество растёт, сроки удлинняются.

Безусловно, тесты хорошо и важно, но не в ущерб рыночным преимуществам компании. Звучит как «обмажься дерьмом», но рынок — это рынок.
Понимаю, это очень распространённое представление.
Но это всего лишь стереотипы. Жуткие стереотипы.
На рынке, конечно, все торопятся, но никто не хочет облажаться перед конкурентами, выпустив дырявое бажное приложение. Никто не хочет быстро выйти на рынок, а потом завязнуть в своём спагетти.

Рынок завоёвывают вовсе не те, кто выпустил продукт первым. Google, Amazon, Apple, DropBox, Chrome — они все были далеко не первыми. Но завоевали рынок.
TDD — это про разработку (test driven DEVELOPMENT). Это способ создания кода таким, чтобы он был НЕ legacy. Таким, чтобы его было возможно поддерживать: отлаживать, исправлять, рефакторить, дорабатывать. На данный момент это единственный известный человечеству способ. Всё остальное — от лукавого, не дайте себя обмануть.
TDD и легаси проблемы совершенно разного уровня и характера. Legacy — это когда при помощи текущего кода делают не свойственную ему задачу и никакое TDD и тестирование от этого не спасет.

Безусловно, нужно стремиться к максимальному покрытию. Все эти разговоры про 30% или 70% покрытие — от непонимания. Надо стремиться к 100% (хоть это и недостижимый предел).

Всем известно, что 100% покрытие невозможно, но не все правильно понимают, почему. Вовсе не потому, что «надо тестировать только критичное», «незачем тестировать геттеры» и «у нас есть дела поважнее».

100% покрытие тестами еще один миф, покрывать тестами нужно по заранее известным критериям: важность, проблемность, трудность обнаружения ошибки. Стремиться к 100% покрытию напоминает продавцов БАД. Метрика покрытия тестами просто некоторый индикатор кода, не более.

Ты пишешь красный тест 10 минут, делаешь его зелёным 10 минут, рефакторишь 10 минут. Никто, конечно, не замеряет это время точно — иногда это 5:30:0, а иногда 180:5:60. Неважно. Важно, что ты с таким же темпом, с предсказуемой скоростью сможешь менять код и через месяц, и через год
теоретический бред. Это возможно лишь тогда, когда допиливается мини-фича к текущему фреймворку. Когда приходится реализовывать новый кейс на текущем коде, в первую очередь важно понять что укладывается в текущий дизайн и логику, а что нет. А уж потом писать тесты.

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

TDD — это вообще не проблема. :)

Да, конечно, легаси-код — это проблема, а TDD — один из способов с ней бороться. Да, он не спасёт на 100% (особенно если не дружишь с головой), но может сильно помочь (особенно если дружишь с головой).

Стремиться к 100% покрытию напоминает продавцов БАД.

Конечно, метрика — это просто некоторый индикатор.
И слепо стремиться к 100% покрытию, конечно, не стоит. Но вы посмотрите на это с другой стороны.
Вы пишите какую-то логику? Она делает что-то полезное для клиента? Клиент за неё заплатил? Она может сломаться? Клиент расстроится? Значит, надо тестировать.
Получить деньги за фичу и не протестировать её — это же просто непрофессионально, обман.
Как вам понравится, если дантист просверлит вам зуб и не проверит, всё ли там теперь правильно встало на свои места?
Как вам понравится, если дантист скажет, что этот зуб не очень критичный и проблемный, вот я и решил не проверять.

теоретический бред.

Очень даже практические рассуждения. У нас вся компания работает так ежедневно и очень даже успешно.
А вот теоретическим бредом я называю рассуждения о том, почему TDD не работает, от людей, которые сами его не используют.

в первую очередь важно понять что укладывается в текущий дизайн и логику

Ну, спасибо, кэп.
Да, перед тем, как что-то делать, надо сначала понять.

Вообще хороший дизайн и программы делают люди, а не методологии.

Ага, баги, плохой дизайн и легаси код тоже делают люди, а не методологии.
Люди несовершенны, люди допускают ошибки. Человеческий мозг не способен заранее всё продумать и запланировать.

Поэтому людям надо помочь. Надо дать инструмент, который поможет им преодолеть свои слабости — как колесо, плуг и револьвер. TDD тоже такой инструмент. Я допускаю, что TDD не единственный и могут быть инструменты в сто раз круче. Но пока их нет. Человевечество пока не придумало такого инструмента.
TDD — один из способов с ней бороться
Ерунда. Чтобы побороть легаси, необходимо сначала найти и выделить наиболее проблемные участки кода, которые необходимо переделать и выкатить. Написание тестов один из инструментов, не более.
Вы пишите какую-то логику? Она делает что-то полезное для клиента? Клиент за неё заплатил? Она может сломаться? Клиент расстроится?
Все очень и очень сильно зависит. На некоторые фейлы или некорректную работу можно забить. Тесты все лишь проверка контракта и песочница для предотвращения повтороного проявление одних и тех же дефектов.
Поэтому людям надо помочь. Надо дать инструмент, который поможет им преодолеть свои слабости — как колесо, плуг и револьвер.
Это примерно как посыл продавана БАДов, любой инструмент имеет граничные условия применения, преимущества и недостатки.
Преимущества как правило понятны, а вот с недостатками все сложнее и о них говорят мало.
Преимущества как правило понятны, а вот с недостатками все сложнее и о них говорят мало.
А я с удовольствием поговорю о недостатках TDD.
Я не утверждаю, что их нет.
Но я утвеждаю, что те «недостатки», что люди обычно называют — полная чушь:
  • придётся потратить времени вдвое больше
  • заказчик не согласен за это платить
  • ...

люди говорят это от лени и нежелания. «Сам не пробовал, но осуждаю».
Добавлю несколько проблем (вернее целей достигнув которых TDD можно будет легко внедрить):
— научиться писать хорошие тесты (особенно 1ые 10-20 тестов в системе).
— понять что и как нужно тестировать (основной функционал, граничные значения, заведомо неверные данные и т.д.)
— убедить себя, что это не «лишняя» работа
— убедить заказчика, что это нужно
— работать в команде с компетентными разработчиками или знать как научить их использовать DI, интерфейсы (а лучше весь SOLID), моки и прочее…

Также нужно помнить что тесты — это тоже код, который нужно поддерживать.
Небольшое изменение в корне системы может сломать десятки, сотни, тысячи тестов и чтобы их починить может понадобиться во много раз больше времени, чем на само изменение. (Но тут же и плюс TDD и юнит-тестирования в целом — вы будете уверены на [процент покрытия тестами]% что ваши изменения не сломают что-то важное.)

И всё же, какие недостатки TDD видите вы? (Тем более у вас опыта много больше моего в этой области)
Лично для меня самое сложное было — это начать (пункт 1,2 из моего списка).
Спасибо за конструктивный подход.

Отлично сформулировано. Да, этих целей надо достичь. Да, как и в любом деле, сложно сделать первый шаг, сложно написать первые 10 тестов.

Небольшое изменение в корне системы может сломать десятки, сотни, тысячи тестов

Не-не, это не так.
Как я уже писал выше, каждый аспект должен быть проверен один-двумя-пятью-N тестами.
Меняется аспект — должны сломаться только эти N тестов.
Если ломается 1000 тестов — это неправильные тесты.

И всё же, какие недостатки TDD видите вы?

Так всё, что вижу, я сформулировал в статье.
TDD — это дисциплина, её надо придерживаться каждый день. Недостаточно просто прочитать статью и понять. Надо каждый день. И каждый день очень легко забыться и начать фигачить код без тестов.
Но это не минус, это так с любой дисциплиной.

Ну, можно называть такой минут, что TDD не гарантирует отсутствие ошибок. То и дело приходит соблазн забить на юнит-тесты и написать UI-тест, потому что после юнит-тестов всё равно придётся руками прокликать.
Но опять же, это не минус. Ничто на свете не гарантирует отсутствие ошибок.
Какое-то негативное к себе отношение у меня оставило TDD после использования. На одном из больших проектов писали активно юнит-тесты, было их 1800 штук, так вот больше всего убивало, что добавление маленькой логики (типа: а давайте юзеру добавим поле phone) по сути должно было реализовываться за 2ч — ломало 10-15 тестов… (т.к. изменялся интерфейс ключевой энтити проекта, а это и валидации, и сохраняторы, отображаторы и еще куча мест, где мог использоваться пользователь) и реализация была уже 3-4ч.

Плюс ко всему часто были ситуации, когда тесты работали, а бизнесс логика — нет. В итоге юнит-тестов стали писать меньше, больше ацептанс тестов, а позже вообще отказались — писали только ацептанс тесты. Да, они выполняются по 2ч, но! они написаны по кейсам бизнесс-логики и полностью отвечают спецификации, и в итоге не такие хрупкие, т.к. таким тестам всеравно как именно реализована фича — она просто должна работать, в тоже время TDD — заботится о том как именно реализовано что-то и не решает того, что это не работает вместе или не отвечает тркбованиям бизнесс логики.
Да, такое бывает.
Но это просто неправильные тесты. :)
Каждый аспект должен тестироваться одним-пятью-десятью тестами. Которые проверяют только его.
Все остальные тесты не должны ломаться, если поменялось поведение в этом аспекте.

Т.е. если меняктся валидация поля phone, должны сломаться только те N тестов, которые проверяют валидацию phone.
Если писать тесты так, то описанной вами проблемы не будет.

были ситуации, когда тесты работали, а бизнесс логика — нет.

Это тоже распространённая проблема, и диагноз тот же: неправильные тесты.
Тут надо сесть и разобраться, как так получилось, что тесты зелёные, а логика не работает. Значит, тесты не проверяли эту логику. Надо их пересмотреть.
Sign up to leave a comment.