Является ли это ограничение тестовой разработки (и Agile в целом) практически актуальным?

30

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

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

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

Эта мысль может быть применена ко всем гибким методам, которые выполняются небольшими шагами, а не только к TDD. Есть ли в этой предложенной аналогии между TDD и локальной оптимизацией серьезные недостатки?

Фрэнк Пуффер
источник
Вы имеете в виду методику TDD, называемую триангуляцией ? Под «приемлемым решением» вы подразумеваете правильное или поддерживаемое / элегантное / удобочитаемое?
guillaume31
6
Я думаю, что это настоящая проблема. Так как это только мое мнение, я не буду писать ответ. Но да, поскольку TDD рекламируется как практика проектирования , недостатком является то, что он может привести либо к локальным максимумам, либо к отсутствию решения вообще. Я бы сказал, что в целом TDD НЕ подходит для алгоритмического проектирования. См. Соответствующее обсуждение ограничений TDD: решение Sudoku с TDD , в котором Рон Джеффрис делает задницу сам себе, бегая по кругу и «занимаясь TDD», в то время как Питер Норвиг предоставляет реальное решение, фактически зная о предмете,
Андрес Ф.
5
Другими словами, я бы предложил (надеюсь) неоспоримое утверждение о том, что TDD хорош для минимизации количества классов, которые вы пишете в «известных» задачах, и, следовательно, для создания более чистого и простого кода, но не подходит для алгоритмических задач или для сложных задач, где на самом деле смотреть на картину в целом и обладать знанием предметной области более полезно, чем писать фрагментарные тесты и «открывать» код, который вы должны написать.
Андрес Ф.
2
Проблема существует, но не ограничивается TDD или даже Agile. Изменяющиеся требования, которые означают, что дизайн ранее написанного программного обеспечения должен меняться, происходят постоянно.
RemcoGerlich
@ guillaume31: Не обязательно триангуляция, но любая техника, использующая итерации на уровне исходного кода. Под приемлемым решением я подразумеваю решение, которое проходит все испытания и может поддерживаться на достаточно хорошем уровне.
Фрэнк

Ответы:

8

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

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

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

Я могу представить себе ситуацию, когда это может произойти в реальности: когда вы выбираете неправильную архитектуру таким образом, вам необходимо заново воссоздать все существующие тесты с нуля. Допустим, вы начинаете реализовывать свои первые 20 тестовых примеров на языке программирования X в операционной системе A. К сожалению, требование 21 включает в себя необходимость выполнения всей программы в операционной системе B, где X недоступно. Таким образом, вам нужно отбросить большую часть своей работы и переопределения на языке Y. (Конечно, вы не выбросили бы код полностью, а перенесли бы его на новый язык и систему.)

Это учит нас, даже при использовании TDD, это хорошая идея, чтобы сделать общий анализ и дизайн заранее. Это, однако, также верно для любого другого подхода, поэтому я не рассматриваю это как внутреннюю проблему TDD. И для большинства реальных задач программирования вы можете просто выбрать стандартную архитектуру (например, язык программирования X, операционная система Y, система баз данных Z на аппаратном XYZ), и вы можете быть относительно уверены, что итеративная или гибкая методология, такая как TDD не приведет вас в тупик.

Цитируя Роберта Харви: «Вы не можете вырастить архитектуру из модульных тестов». Или pdr: «TDD не только помогает мне прийти к лучшему окончательному дизайну, но и помогает получить меньше попыток».

Так на самом деле то, что вы написали

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

может стать реальностью - если вы выберете неправильную архитектуру, вы, скорее всего, не достигнете нужного решения оттуда.

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

Док Браун
источник
20

Я не думаю, что у TDD есть проблема локальных максимумов. Код, который вы пишете, может, как вы правильно заметили, но именно поэтому рефакторинг (переписывание кода без изменения функциональности) на месте. По сути, по мере увеличения количества ваших тестов вы можете переписывать значительную часть вашей объектной модели, если вам нужно, сохраняя поведение неизменным благодаря тестам. Тесты устанавливают инвариантные истины о вашей системе, которые, следовательно, должны быть действительными как в локальных, так и в абсолютных максимумах.

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

  1. Проблема полноты : сколько тестов необходимо, чтобы полностью описать систему? Является ли «кодирование примерами» полным способом описания системы?

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

  3. Проблема тестового повреждения : чтобы сделать утверждения тестируемыми, нам может потребоваться написать неоптимальный код (например, менее производительный). Как мы пишем тесты, чтобы код был настолько хорош, насколько это возможно?


Отредактировано с учетом комментария: вот пример исключения локального максимума для «двойной» функции посредством рефакторинга

Тест 1: когда вход равен 0, вернуть ноль

Реализация:

function double(x) {
  return 0; // simplest possible code that passes tests
}

Рефакторинг: не требуется

Тест 2: когда ввод 1, вернуть 2

Реализация:

function double(x) {
  return x==0?0:2; // local maximum
}

Рефакторинг: не требуется

Тест 3: когда на входе 2, вернуть 4

Реализация:

function double(x) {
  return x==0?0:x==2?4:2; // needs refactoring
}

Рефакторинг:

function double(x) {
  return x*2; // new maximum
}
Sklivvz
источник
1
Что я испытал, так это то, что мой первый дизайн работал только для некоторых простых случаев, и позже я понял, что мне нужно более общее решение. Для разработки более общего решения потребовалось больше тестов, в то время как оригинальные тесты для особых случаев больше не будут работать. Я считаю приемлемым (временно) удалить эти тесты, пока я разрабатываю более общее решение, добавляя их обратно, как только время будет готово.
5gon12eder
3
Я не уверен, что рефакторинг - это способ обобщить код (конечно, за пределами искусственного пространства «шаблонов проектирования») или избежать локальных максимумов. Рефакторинг приводит в порядок код, но не поможет вам найти лучшее решение.
Андрес Ф.
2
@Sklivvz Понятно, но я не думаю, что это работает так, как примеры игрушек, подобные тем, которые вы опубликовали. Кроме того, вам помогло то, что ваша функция была названа «double»; таким образом, вы уже знали ответ. TDD определенно помогает, когда вы более или менее знаете ответ, но хотите написать его «чисто». Это не поможет обнаружить алгоритмы или написать действительно сложный код. Вот почему Рон Джеффрис не смог решить судоку таким образом; Вы не можете реализовать алгоритм, с которым вы не знакомы, из-за того, что TDD делает его неясным.
Андрес Ф.
1
@VaughnCato Хорошо, теперь я в состоянии либо доверять тебе, либо быть скептиком (что было бы грубо, поэтому давайте не будем этого делать). Скажем так, по моему опыту, это не работает, как вы говорите. Я никогда не видел достаточно сложный алгоритм, разработанный из TDD. Может быть, мой опыт слишком ограничен :)
Андрес Ф.
2
@Sklivvz «Пока вы можете писать соответствующие тесты» - это как раз то, что мне нужно, чтобы задать вопрос. То, что я говорю, это то, что вы часто не можете . Думать об алгоритме или решателе не проще, если сначала писать тесты . Сначала вы должны посмотреть на всю картину . Попытка сценариев, конечно, необходима, но обратите внимание, что TDD не о написании сценариев: TDD о тест-драйве дизайна ! Вы не можете управлять дизайном решателя судоку (или нового решателя для другой игры), сначала написав тесты. В качестве неофициального доказательства (которого недостаточно): Джеффрис не мог.
Андрес Ф.
13

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

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

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

candied_orange
источник
13

В своем ответе @Sklivvz убедительно доказал, что проблемы не существует.

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

Йорг Миттаг
источник
8

Могут ли методы TDD и Agile обещать оптимальное решение? (Или даже «хорошее» решение?)

Не совсем. Но это не их цель.

Эти методы просто обеспечивают «безопасный переход» из одного состояния в другое, признавая, что изменения являются трудоемкими, трудными и рискованными. И смысл обоих методов заключается в том, чтобы приложение и код были жизнеспособными и проверенными на соответствие требованиям быстрее и более регулярно.

... [TDD] выступает против разработки программного обеспечения, которая позволяет добавлять программное обеспечение, которое не соответствует требованиям ... Кент Бек, которому приписывают разработку или "переоткрытие" методики, заявил в 2003 году, что TDD поощряет простое проектирует и внушает доверие. ( Википедия )

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

Что касается Agile процессов:

Рабочее программное обеспечение является основной мерой прогресса ... В конце каждой итерации заинтересованные стороны и представитель заказчика рассматривают прогресс и переоценивают приоритеты с целью оптимизации возврата инвестиций ( Википедия )

Ловкость не ищет оптимального решения ; просто рабочее решение с целью оптимизации ROI . Он обещает рабочее решение скорее раньше , чем позже ; не "оптимальный".

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

Оптимумы в разработке программного обеспечения - нечеткие, движущиеся цели. Требования, как правило, меняются и изобилуют секретами, которые возникают только из-за вашего смущения в конференц-зале, полном боссов вашего босса. А «внутреннее совершенство» архитектуры и кодирования решения оценивается разделенными и субъективными мнениями ваших коллег и вашего управляющего руководителя - никто из которых не может ничего знать о хорошем программном обеспечении.

По крайней мере, методы TDD и Agile признают трудности и пытаются оптимизировать две вещи, которые являются объективными и измеримыми: « Работает», «Не работает» и « Рано или поздно».

И даже если у нас есть «работающие» и «скорее» в качестве объективных показателей, ваша способность оптимизировать их в первую очередь зависит от навыков и опыта команды.


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

так далее..

Будет ли каждая из этих вещей на самом деле приводить к оптимальным решениям - это еще один замечательный вопрос!

svidgen
источник
1
Правда, но я не писал, что цель TDD или любого другого метода разработки программного обеспечения - это оптимальное решение в смысле глобального оптимума. Меня беспокоит только то, что методологии, основанные на небольших итерациях на уровне исходного кода, могут вообще не найти приемлемого (достаточно хорошего) решения
Фрэнк
@Frank Мой ответ предназначен для охвата как локальных, так и глобальных оптимумов. И в любом случае ответ таков: «Нет, это не то, для чего предназначены эти стратегии - они предназначены для повышения рентабельности инвестиций и снижения рисков». ... или что-то типа того. И это отчасти связано с тем, что получает ответ Йорг: «Оптимумы» являются движущимися целями. ... я бы даже сделал еще один шаг вперед; они не только движутся цели, но они не являются полностью объективными или измеримыми.
svidgen
@FrankPuffer Может быть, это стоит добавить. Но суть в том, что вы спрашиваете, достигают ли эти две вещи чего-то, чего они совсем не предназначены или не предназначены для достижения. Более того, вы спрашиваете, могут ли они достичь чего-то, что нельзя даже измерить или проверить.
svidgen
@FrankPuffer Bah. Я попытался обновить свой ответ, чтобы сказать это лучше. Я не уверен, что сделал это лучше или хуже! ... Но мне нужно выйти из SE.SE и вернуться к работе.
svidgen
Этот ответ в порядке, но проблема, с которой я столкнулся (как и с некоторыми другими ответами), заключается в том, что «снижение риска и повышение рентабельности» не всегда являются лучшими целями. На самом деле они сами по себе не являются целями. Когда вам нужно что-то сделать, снижение риска не поможет. Иногда относительно ненаправленные маленькие шаги, как в TDD, не сработают - вы минимизируете риск, но в конечном итоге вы не получите ничего полезного.
Андрес Ф.
4

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

В реальном мире ваши тесты в основном выполняют и проверяют бизнес-правила:

Например, если клиент 30 лет не курит с женой и двумя детьми, категория «премиум» - «x» и т. Д.

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

То, что вы на самом деле создали, - это сеть безопасности, так что при добавлении нового метода расчета для определенной категории клиентов все старые правила внезапно не нарушаются и дают неправильный ответ. Сеть безопасности еще более полезна, если первым шагом отладки является создание теста (или серии тестов), который воспроизводит ошибку до написания кода для исправления ошибки. Затем, через год, если кто-то случайно воссоздает исходную ошибку, модульный тест будет прерван до того, как код будет даже проверен. Да, TDD допускает одно: теперь вы можете с большой уверенностью выполнять рефакторинг и убирать вещи. но это не должно быть важной частью вашей работы.

mcottle
источник
1
Во-первых, когда я прочитал ваш ответ, я подумал: «Да, это ключевой момент». Но после переосмысления вопроса снова я подумал, что он не обязательно такой абстрактный или нереальный. Если слепо выбрать совершенно неправильную архитектуру, TDD не решит это, не после 1000 итераций.
Док Браун
@Doc Браун Согласен, это не решит эту проблему. Но он предоставит вам набор тестов, которые реализуют каждое предположение и бизнес-правило, чтобы вы могли многократно улучшать архитектуру. Архитектура настолько плохая, что ее нужно переписать, чтобы исправить, очень редко (я надеюсь), и даже в этом крайнем случае модульные тесты бизнес-правил были бы хорошей отправной точкой.
Маккотт
Когда я говорю «неправильная архитектура», я имею в виду случаи, когда нужно отбросить существующий набор тестов. Вы читали мой ответ?
Док Браун
@DocBrown - Да, я сделал. Если вы имели в виду «неправильная архитектура», что означает «изменить весь набор тестов», возможно, вам следовало бы сказать это. Изменение архитектуры не означает, что вам придется отбрасывать все тесты, если они основаны на бизнес-правилах. Возможно, вам придется изменить их все, чтобы они поддерживали любые новые интерфейсы, которые вы создаете, и даже полностью переписать некоторые из них, но бизнес-правила не будут заменены техническими изменениями, поэтому тесты останутся. Конечно, инвестиции в модульные тесты не должны быть признаны недействительными из-за маловероятной возможности полного уничтожения архитектуры
mcottle
Конечно, даже если нужно переписать каждый тест на новом языке программирования, не нужно выбрасывать все, по крайней мере, можно портировать существующую логику. И я согласен с вами на 100% для крупных реальных проектов, предположения в этом вопросе нереальны.
Док Браун
3

Я не думаю, что это мешает. В большинстве команд нет ни одного человека, способного предложить оптимальное решение, даже если вы написали его на доске. TDD / Agile не будет мешать им.

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

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

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

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

JeffO
источник
0

Может быть обманчиво применять математическую концепцию типа «локальный оптимум» к разработке программного обеспечения. Использование таких терминов делает разработку программного обеспечения гораздо более измеримой и научной, чем на самом деле. Даже если для кода существовал «оптимум», у нас нет способа его измерить и, следовательно, нет способа узнать, достигли ли мы его.

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

JacquesB
источник
Но была ли это слишком сильная реакция? Это, безусловно, помогает во многих случаях, когда строгое предварительное планирование оказалось громоздким и дорогостоящим. Тем не менее, некоторые проблемы с программным обеспечением должны решаться как математическая проблема с предварительным проектированием. Вы не можете TDD их. Вы можете использовать TDD UI и общий дизайн Photoshop, но вы не можете использовать TDD его алгоритмы. Они не являются тривиальными примерами, такими как получение «sum», «double» или «pow» в типичных примерах TDD [1]). Вы, вероятно, не можете дразнить новый фильтр изображений из-за написания некоторых тестовых сценариев; Вы обязательно должны сесть и написать и понять формулы.
Андрес Ф.
2
[1] На самом деле, я уверен fibonacci, что то , что я видел в качестве примера / учебника по TDD, является более или менее ложью. Я готов поспорить, что никто не «обнаружил» Фибоначчи или другие подобные серии с помощью TDD. Каждый начинает с того, что уже знает фибоначчи, которые обманывают. Если вы попытаетесь выяснить это с помощью TDD, вы, вероятно, дойдете до тупика, о котором спрашивал OP: вы никогда не сможете обобщить ряд, просто написав больше тестов и рефакторинг - вы должны применить математические рассуждения!
Андрес Ф.
Два замечания: (1) Вы правы, что это может быть обманчиво. Но я не писал, что TDD - это то же самое, что математическая оптимизация. Я просто использовал это как аналогию или модель. Я действительно считаю, что математика может (и должна) применяться почти ко всему, если вы знаете о различиях между моделью и реальной вещью. (2) Наука (научная работа) обычно даже менее предсказуема, чем разработка программного обеспечения. И я бы даже сказал, что разработка программного обеспечения больше похожа на научную работу, чем на ремесло. Ремесла обычно требуют гораздо больше рутинной работы.
Фрэнк
@AndresF .: TDD не означает, что вам не нужно ни думать, ни придумывать. Это просто означает, что вы пишете тест, прежде чем писать реализацию. Вы можете сделать это с помощью алгоритмов.
JacquesB
@FrankPuffer: Хорошо, так какое же измеримое значение имеет «локальный оптимум» в разработке программного обеспечения?
JacquesB