Представьте, что вам дали задачу поправить часть кода. В голове возникнет много мыслей. Кто его написал? Когда? А может он — legacy? Где документация? Давайте попробуем разобраться с «наследием» основательно и со всех сторон. Поможет нам в этом вопросе Андрей Солнцев @asolntsev (http://asolntsev.github.io/), разработчик из таллинской компании Codeborne. Начнём.

— Андрей, вы знакомы с трудами Michael Feathers, например, «Working Effectively with Legacy Code»? В книге акцентируется внимание на важности тестирования и выделяется одно из ключевых отличий legacy от не legacy-кода — это наличие тестов. Вы согласны с этим мнением?

Абсолютно согласен! Скажу больше: юнит-тесты — необходимое, но недостаточное условие. И с юнит-тестами можно навалить так, что сам Геракл не разгребёт.
Что для настоящего джедая мастхав, так это:
  1. TDD — то есть тесты ДО кода.
  2. Чистый код (и чистые тесты).


Я очень люблю книгу Robert C. Martin «Clean Code» («Чистый код»). Это для меня настольная библия. Категорически всем советую. Кстати, его блог тоже великолепен.


— С чистым кодом понятно. А при чём тут TDD? Код без багов — это ведь не то же самое, что хороший код?

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

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

TDD нужен вовсе не для того, чтобы находить баги.
Тест — это первое использование твоего кода. Шерлок Холмс говорил: «Сначала собираешь факты, а уж потом выстраиваешь теорию. Иначе ты подсознательно начнёшь подтасовывать факты под свою теорию». Ты даже сам не заметишь, как это случится! Так же и с кодом: когда ты пишешь тест до кода, ты вынужден подумать о том, как его удобнее использовать, как назвать класс, метод; какие параметры передать. А если сначала написать код, то начнёшь подтасовывать тесты под него. В итоге доля тестов окажется слишком сложна, другая — трудна для исправления, а часть останется ненаписанной. И вот перед нами legacy-код собственной персоной!

— В продолжение разговора о тестировании — на ваш взгляд, какие тесты корректны и в каких ситуациях, например, юнит-тесты?

Начнём с юнит-тестов. Они необходимы в каждом проекте, тут без вариантов.

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

— А каким должно быть покрытие кода тестами? Достаточно ли 70%? Как не переборщить с «диагностикой» проекта?

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

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

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

Грубо говоря, если просроченные кредиты нужно подсвечивать красным, то логику «какие просроченные» нужно выносить в отдельный метод с юнит-тестами, а «подсвечивание» — отдельно. Ну там, css или как там ещё. И TDD стимулирует разделять эти слои, вот что замечательно. В этом его сила! А тесты — это лишь побочный эффект.

— Сколько времени нужно уделить созданию юнит-тестов, и на каком этапе проекта они играют ключевую роль?

Юнит-тесты играют ключевую роль всегда, на всех этапах. Нет такого этапа, где «пора начать писать юнит-тесты» или «можно больше не писать юнит-тесты».

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

— Как у вас выглядит процесс написания тестов?

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

Советую вот это видео, где понятно и весело показано TDD: «Пацан накодил — пацан протестил!» Не пугайтесь длины, там про юнит-тесты только первые 30 минут.



— Андрей, если TDD так полезен, почему его так мало используют?

Всё просто. TDD — это дисциплина. Это как спорт: все знают, что спорт полезен, но ленятся. Самые честные признают, что им лень, остальные находят отговорки, а особо отъявленные начинают придумывать объяснения, почему спорт, оказывается, даже вреден. Так же и с TDD.

Забавная разница в том, что люди, лежащие на диванах, не называют себя спортсменами. Тут всё честно. А кодеры не пишут тестов и при этом называют себя разработчиками. Невероятно, правда? Но заканчивается это всегда одинаково. Спортсмен, пропускающий тренировки, быстро проигрывает соревнования. Так же и код, написанный не через тесты, моментально становится legacy. Тут всё честно.

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

TDD — это теорема, которую надо доказывать каждый день.

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

Я вам расскажу одну байку. Мой брат работал в химической лаборатории тартуского университета. К ним приехала делегация откуда-то из Европы. Им всё показали: новые помещения, новое оборудование, все дела. И тут они обратили внимание на старый советский агрегат где-то в углу. Делегаты сделали круглые глаза и спросили, мол, а что ж вы его не выкинете? На что последовал ответ: «Не поверите! Потому, что он… РАБОТАЕТ!»

Тут нет единого ответа. Можно ежедневно обновлять все зависимости и относиться к этому как к необходимой гигиене. Можно даже попросить Maven или Gradle делать это автоматически. А можно сделать fork оригинальной библиотеки и сидеть двадцать лет на старой версии. Пожалуй, это зависит от зависимостей. Если я сильно завишу от Selenium, использую новые фичи, то я его часто обновляю. Если я никак не завишу от новых фич log4j, я сижу на древней версии. Всё ж работает.

Чем старше я становлюсь, тем больше склоняюсь к тому, что не стоит гнаться за обновлениями. Новый Play framework оказался не лучше, чем Struts2. Maven не лучше (и даже хуже), чем Ant. TestNG однозначно хуже JUnit (а ведь «new generation» в названии!). Hibernate3 был сложнее, чем Hibernate 2. Spring boot сложнее, чем Spring MVC. А ведь лет 10 прошло! Про новые JavaScript библиотеки я уж молчу.

Так что не всё то новьё, что обмазано солидолом.

— В вашей практике встречались подобные ситуации (дополнительно к предыдущему вопросу)? Как вы выходили из подобных ситуаций? Какой стратегии вы придерживаетесь, переписать всё (часто используется термин — greenfield) или сделать рефакторинг?

Конечно, встречались.
Да это просто вопрос организации. Если приспичило обновить какую-то зависимость, которая обратно несовместима, надо просто выделить для этого время, договориться с командой, чтобы ни у кого ничего срочного в этот момент не было, да и отрефакторить. Многие начнут плакаться: «Нам не дают на это времени». Ну не знаю, может, не так уж нужно обновляться? Было бы критично — дали бы время.

Насчёт greenfield я довольно скептично настроен.
Конечно, плох тот солдат, который не мечтает всё переписать с нуля.
Но я за всю свою жизнь ни разу не видел, чтобы систему переписали с нуля и стало лучше. Обычно получается так же или хуже. Переписывальщик начинает с большим азартом, потом сталкивается с одной проблемой, с другой; понимает, что уже не успевает… Оставляет технический долг в одном месте, в другом — и вот у вас уже legacy-код в новом проекте, хотя ещё и половины-то не написано.

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

— Может быть, существует проверенный «гибридный» вариант?

Гибридный вариант есть. Можно переписать с нуля какую-то часть системы, самую легасисястую. Причём оставить старый вариант тоже, так чтобы они работали параллельно. И переводить на новый вариант все куски системы постепенно, один за одним. Да, это требует терпения и времени, но это единственный способ. Неохота? Тогда вообще не начинай. Торопыгам тут не место.

— Как вы отличаете плохой, попадающий под определение legacy-код, от максимально «разогнанного»? Стоит ли гнаться за производительностью в ущерб будущей поддержке?

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



— Как вы думаете, legacy-код может тормозить развитие всей системы?

Конечно, может! И тормозит!
Собственно, это самое главное, что обычно тормозит. Каждое следующее изменение вносить всё сложнее, риск что-то сломать всё больше.

— Вы можете дать совет о необходимости избавления от ужасного и медленного «монстра» в проекте, если его увидите? Как на это реагировали (если такие ситуации происходили)? Изменения провели к положительно эффекту (если такие ситуации происходили)?

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

Но тут важно не уйти слишком далеко. Надо сделать чуть чище поляну, а не перепахать весь лес без оглядки. Это я видел (и делал) много раз: очередная горячая голова решает всё переписать, уходит на несколько часов/дней в жёсткий рефакторинг, вязнет, тонет и в итоге делает только хуже. В лучшем случае откатывает свои изменения и отделывается лёгкой потерей времени.

Чинить legacy-код — безусловно, крайне полезное упражнение. Отлично прокачивает скиллы аналитического мышления, рефакторинга и понимание хорошего кода. Как упражнение — советую.

НО.
Чинить legacy-код — то же самое, что рубить голову гидре. Вырастет три новых. И ещё окажется, что та первая не до конца отрубилась, и мало того, что тебя укусила, так ещё из неё вылилось что-то липкое и зелёное и запачкало всё вокруг. А потом пришёл менеджер и сказал: «Нафига ты вообще это трогал, работало же?»

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

— Вы придерживаетесь мнения, что красота кода — это ещё не показатель legacy-статуса?

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

— Можно ли утверждать, что нарушение стандартного соглашения по оформлению кода — это legacy?

Стандартные соглашения полезны, но не стоит их переоценивать. Любые соглашения о коде становятся legacy ещё быстрее, чем сам код. Их же никто никогда не читает. У лебедя, рака и щуки были верёвки стандартной длины — и сильно им это помогло?

И не стоит ради них тратить свои силы. Настройте один раз IDE всех участников проекта, чтобы она сама форматировала код как надо, и больше не думайте об этом.

— В рамках legacy мы обсудили вопросы тестирования, производительности, зависимостей в проектах, красоты кода и затронули тему развития. Ваше мнение о legacy-коде и причинах его возникновения не менялось с течением времени?

Конечно, менялось. Вначале я, как и все, думал, что плохой код пишут индусы и тупые коллеги. Или «джуниоры». Но всё не так просто.

Я не люблю слишком простые объяснения. Слишком легко свалить всё на «тех придурков». А legacy-код возникает и у самых умных людей, и в самых лучших проектах.

— И у тебя?

О да. О да!
Вот я тут вещаю, такой умный, про legacy-код, а у меня самого есть open-source проект Selenide, который legacy-кодом зарос по самое не моргну. Нет, он отлично работает, пользователи довольны, но мне его менять со временем всё сложнее. Есть тикеты, которые висят уже пару лет, потому что мне реально сложно их сделать. Поменяешь в одном месте — ломается в другом. Да, там есть интеграционные тесты, благодаря которым я не боюсь что-то сломать. Но от них нет помощи в том смысле, что менять-то код не получается.

Однажды я понял: ключевая проблема в том, что в этом проекте я изначально не писал юнит-тесты. Казалось, зачем тесты, если эта библиотека сама предназначены для написания тестов? Она и так будет протестирована вдоль и поперёк. А оказалось — вон оно что, Михалыч! Юнит-тесты нужны для избежания legacy-кода.

Ну я так и делаю: раз в полгода-год сажусь и переписываю какой-нибудь модуль к чертям собачьим. Поскольку это хобби, я прусь от этого. Но если б это было работой и выражалось в деньгах — стоило бы переписывать? Ох, не знаю…

—Как вам кажется, в каких оттенках стоит воспринимать legacy-код? В каких случаях оправдано его наличие?

У меня есть простой рецепт. Отвлекитесь на секунду от лямбд и скобочек и представьте себя на месте клиента. Это здорово прочищает мозги.

Как клиент, привозите вы свою машину в автосервис, просите посмотреть, что там стучит.
А вам механик говорит: «Я поменял то, что стучало, но у вас ещё и топливный насос дырявый, — будем менять?» Это хороший механик. Плохой механик поменял бы насос сразу. А хороший механик сообщил о проблеме и расписал варианты: можно заменить сейчас — будет стоить столько-то. Можно не менять, тогда зиму ещё доездите, но потом и все шланги полетят — выйдет втрое дороже. И вы сами как клиент решаете. Один скажет: чего два раза ходить, меняй всё! Другой скажет: «Сейчас денег нет, а мне ещё зимнюю резину нужно. Давай до весны подождём».

А знаете, что сделает самый худший механик? Промолчит. Сделает только то, что просили.

А как ведём себя мы все с вами, дорогие программисты?
Те самые горячие головы, что норовят тут же всё порефакторить — они как кто? Правильно, как плохой механик. А те «повзрослевшие», что решают оставить всё как есть? Увы, как самый худший. А что должен сделать с legacy-кодом хороший механик? А как часто мы так делаем? Вот и думайте.

— Андрей, TDD, юнит-тесты — всё это звучит хорошо, но вы же понимаете, что для 99% наших читателей это неподъёмно в силу самых разных причин, часто от них не зависящих. А можете напоследок дать простой рецепт, как бороться с legacy-кодом?

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

Люди как люди. Любят торопиться, но ведь это всегда было… Человечество любит простые рецепты… В общем, напоминают прежних… микросервисный вопрос только испортил их…

В общем, волшебного избавления от legacy-кода не будет. Но вы держитесь там, удачи, хорошего настроения!

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

Спасибо и вам за приглашение.
А я воспользуюсь положением и приглашу всех читателей в гости к нам в Таллинн. У нас красивый средневековый город, хорошее крафтовое пиво и отличные мероприятия для айтишников, куда любой желающий может прийти бесплатно: devclub.eu

Спасибо за внимание и за терпение!



Больше интересных докладов, технических, хардкорных, вы найдете в программе Joker 2016. Если вы работаете с легаси, вам стоит обратить внимание на следующие доклады:


А также еще неопубликованный доклад Вячеслава Лапина «Как я переводил большой legacy enterprise проект на Java 8 — приёмы, трюки и «подводные ками»».
Поделиться с друзьями
-->

Комментарии (61)


  1. zenkz
    26.08.2016 22:28
    +8

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

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

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


    1. martin_wanderer
      26.08.2016 23:14
      +1

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


      1. zenkz
        26.08.2016 23:47
        +3

        Код покрытый тестами и TDD — это не одно и то же.
        Да и само покрытие юнит-тестами не даёт 100% гарантии от ошибок. Ведь ошибка может быть в тесте или именно такая ситуация тестом не покрыта.
        В то же время простой и лаконичный код не покрытый тестами всё-же лучше, чем запутанный код, покрытый ими.
        Юнит-тестирование — это «защита от дурака», но не панацея.


        1. lany
          27.08.2016 00:20
          +2

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


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


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


          1. zenkz
            27.08.2016 00:28
            +1

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

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


            1. lany
              27.08.2016 10:54

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


              1. zenkz
                29.08.2016 16:43

                Для Open-source наличие юнит тестов крайне желательно. Тут даже спорить не о чем.
                К сожалению в коммерческих проектах всё не так радужно. Т.е. никто не будет тебя ругать за написание юнит-тестов и использование TDD, но тем не менее сроки обычно настолько сжаты, что разве что в говнокод не позволяют скатываться, а о юнит-тестах остаётся только мечтать.


                1. asolntsev
                  29.08.2016 19:35

                  Ещё раз повторюсь, как раз юнит-тесты позволяют укладываться в сроки и разрабатывать проект с более-менее постоянной скоростью.


                  1. softaria
                    29.08.2016 20:44

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

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


                    1. asolntsev
                      31.08.2016 00:17

                      Да, типичное видение ситуации.

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


                      1. softaria
                        31.08.2016 08:10

                        А откуда заказчик значет, что вам хватит два дня?


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

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


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

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


                        1. asolntsev
                          01.09.2016 01:00

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

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

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

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

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

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


                          1. softaria
                            01.09.2016 08:59

                            Вы скажете ему, что это невозможно


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

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

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

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


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


            1. Kane
              28.08.2016 23:02
              +1

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


        1. asolntsev
          27.08.2016 09:27
          +2

          Как я уже писал, TDD — это вообще не защита, а способ разработки.


        1. 3aicheg
          29.08.2016 09:48
          +3

          Весь покрытый тестами, абсолютно весь
          Код, который legacy, на гитхабе есть…


    1. asolntsev
      27.08.2016 09:37
      +5

      zenkz

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

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

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

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

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

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

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


      1. zenkz
        29.08.2016 16:34

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

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

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

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

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

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


        1. asolntsev
          31.08.2016 00:45

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

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

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

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

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

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

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

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

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


    1. softaria
      27.08.2016 10:52
      +4

      это удваивает расходы на разработку


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


      1. asolntsev
        27.08.2016 23:01
        +2

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

        TDD не сокращает время только там, где не надо думать. А такое бывает?


        1. softaria
          28.08.2016 09:07

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


          1. asolntsev
            28.08.2016 22:14

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


    1. onedev_link
      30.08.2016 16:11
      +1

      Но 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 ускоряет и разработку и внесение изменений. Интересно послушать другие кейсы.

      Статья прям пропитана влиянием Дяди Боба :)


      1. asolntsev
        31.08.2016 00:49
        +1

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

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


  1. lany
    26.08.2016 23:41
    +7

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


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


    1. asolntsev
      27.08.2016 09:44

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

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


      1. lany
        27.08.2016 11:17
        +8

        Вот почему ты не боишься менять 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?


        1. softaria
          27.08.2016 11:29
          +3

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


          1. lany
            27.08.2016 11:36
            +4

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


            1. softaria
              27.08.2016 12:25
              +1

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


        1. asolntsev
          27.08.2016 23:23
          -1

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

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

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

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

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

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


        1. asolntsev
          27.08.2016 23:28
          -1

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

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

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

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

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

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

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


        1. 3aicheg
          29.08.2016 10:21
          +2

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

          image


  1. RPG18
    27.08.2016 00:27
    +2

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


    1. 23derevo
      27.08.2016 14:05
      +1

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

      Тогда повседневная разработка сильно отличалась — не было CI, не было нормальных IDE, например. Времена меняются.


      1. EngineerSpock
        27.08.2016 14:11

        Бог с ними с посоветованными книгами, их советовать можно. Я, конечно, не знаю, текст слабо выражает эмоции, но чувствуется, что человек советует эти книги для джунов с таким опломбом, вот это и весело :)


        1. 23derevo
          27.08.2016 14:21

          ага, у меня те же ощущения. Эти книги можно советовать старшекурсникам и джунам, верно. Довольно глупо выглядит попытка советовать такую литературу эксперту :)


      1. softaria
        27.08.2016 14:15
        +1

        IDE таки были. Даже eclipse как раз 15 лет назад появился. А до него были Visual Age, NetBeans, Borland c-builder тот же.


        1. 23derevo
          27.08.2016 14:19

          По сравнению с современными они были — так, текстовыми редакторами. Все было другое — и возможности рефакторинга и возможности по разработке тестов и инфрастуруктуры и тестовые фреймворки.


          1. softaria
            27.08.2016 14:33
            +1

            Возможности, конечно, были ниже. Но вот какого-то драматического скачка, в развитии именно IDE, делающего какие-то книги устарешими, на мой взгляд, за последние 15 лет не было. Рефакторинг уже был, хотя и гораздо слабее.


      1. RPG18
        27.08.2016 20:51
        +1

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


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


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

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


        1. softaria
          27.08.2016 20:54

          Иногда рефакторинг — это ответ на новые требования к новым версиям продукта.


  1. slonopotamus
    27.08.2016 15:45
    +1

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


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

    Статья сводится к «Только идиоты не используют TDD и не пишут юнит-тесты. Правда я в своем проекте тоже этого не делаю». И вы это называете «без максимализма»?


    1. asolntsev
      27.08.2016 22:07
      +1

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

      Вы передёргиваете. Меня спросили, как бороться с легами-кодом, и я дал свой ответ.
      Кстати, вы невнимательно читали. Я использую TDD во всех своих проектах. Я не использовал его только в одном, том самом проекте, и то только вначале, 5 лет назад.


  1. vladislav2103
    27.08.2016 19:33
    +2

    В целом статья правильная и я согласен с автором. А вот что вы посоветуете делать когда есть legacy код который очень плохо покрывается unit тестами?


    1. asolntsev
      27.08.2016 22:13
      +1

      Зависит от ситуации.
      Если этот легаси менять особо не требуется — может, лучше не трогать.
      Если же вам приходится его менять, и это причиняет боль, то надо рефакторить и дописывать тесты. По чуть-чуть, помаленьку. Выделить кусочек, написать на него тесты, порефакторить. И так далее.


  1. amarao
    29.08.2016 13:13
    +2

    По своему опыту — в коде очень трудно начать писать тесты, если нет какого-то вмеяемого представления о том, как их писать в этом конкретном месте. То есть дописать 101ый тест после 100 существующих — легко. Написать 1ый тест — подвиг, размер которого никто даже оценить не может.

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


    1. asolntsev
      29.08.2016 14:56

      Да, верно. Ну так это можно сказать про всё на свете. И тысячное слово на узбекском легче учить, чем первое.

      Насчёт второго сценария: да, конечно, в этом и есть главная проблема легаси кода.


      1. amarao
        29.08.2016 21:06

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


        1. asolntsev
          31.08.2016 00:23

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

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


          1. amarao
            31.08.2016 15:25
            +1

            Отличные аналогии:

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

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

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

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

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


            1. asolntsev
              01.09.2016 01:05

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

              Рынок завоёвывают вовсе не те, кто выпустил продукт первым. Google, Amazon, Apple, DropBox, Chrome — они все были далеко не первыми. Но завоевали рынок.


  1. vyatsek
    29.08.2016 14:11

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

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

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

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

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

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


    1. asolntsev
      29.08.2016 15:39
      +1

      TDD и легаси проблемы совершенно разного уровня и характера

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

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

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

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

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

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

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

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

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

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

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


      1. vyatsek
        29.08.2016 16:37

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


        1. asolntsev
          31.08.2016 00:27

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

          люди говорят это от лени и нежелания. «Сам не пробовал, но осуждаю».


          1. zenkz
            31.08.2016 01:53

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

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

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


            1. asolntsev
              01.09.2016 00:57

              Спасибо за конструктивный подход.

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

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

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

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

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

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


  1. boom
    30.08.2016 16:24
    +3

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

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


    1. asolntsev
      31.08.2016 00:31

      Да, такое бывает.
      Но это просто неправильные тесты. :)
      Каждый аспект должен тестироваться одним-пятью-десятью тестами. Которые проверяют только его.
      Все остальные тесты не должны ломаться, если поменялось поведение в этом аспекте.

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

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

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