TDD — «утка». Введение в Quack-Driven Development. Современная waterfowl-методология для программистов

Original author: Jason Hutchens
  • Translation


Предлагаю к ознакомлению вольный перевод статьи Джейсона Хатчерса «TDD(1) is ‘canard. Tell it to the Duck» или дословно: «TDD — Утка. Скажи это утке». (2)

Автор критикует «ортодоксальный» TDD подход к разработке и предлагает альтернативный.

И, да, в заголовке статьи нет опечатки (3).




Отчасти, цель жизни — веселье. Обычно, возможность сконцентрироваться на творческой стороне программирования доставляет программистам радость. Поэтому, Ruby создан, чтобы сделать программистов счастливыми.
Юкихиро Мацумото (также известный как «Matz»), 2000.


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


Умер ли TDD?


Я следил за дискуссией на тему "Is TDD dead?" с большим интересом, поскольку долго боролся с идеями, которые были резюмированы в виде "3-х основных правил TDD от дядюшки Боба":

  1. Запрещено писать какой-либо код кроме того, что нужен для прохождения проваленных тестов
  2. Запрещено писать дополнительные тесты, если текущего набора хватает для провала (проверка на компилируемость также является тестом)
  3. Запрещено писать дополнительный код, кроме кода, достаточного для прохода текущего проваленного теста.


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

Тренировка: игра в боулинг

Дядюшка Боб демонстрирует три правила TDD, показывая как реализовать механизм подсчёта очков в игре в боулинг. Давайте я вам продемонстрирую подход Дяюдшки Боба, четко следуя трем правилам TTD: (4):

  • Проверить что вы можете создать экземпляр игры в боулинг (провалено).
  • Реализовать пустой класс игры в боулинг (пройдено).
  • Проверить что созданный экземпляр возвращает 0 очков, при постоянном попадании шара в желоб (провалено).
  • Реализовать функцию подсчёта очков, которая всегда возвращает 0 (пройдено).
  • Проверить что количество очков равно 20 если на каждом ходе сбивается 1 кегля (провалено).
  • Изменить функцию подсчёта очков: возвращать сумму сбитых кеглей (пройдено).
  • Проверить что счёт корректно вычисляется при броске спэра (дополнительного броска) (провалено).
  • … (и так далее)


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

Зачем же тестировать?


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

  1. Документация. Для обеспечения понимания другими программистами. (Документация устаревает. Тесты не устаревают если вы следите, чтобы они проходили. Также, они описывают код в виде примеров его использования, которые являются лучшей документацией для программиста.)
  2. Дизайн. Зафиксировать требования к продукту и использовать их в качестве границ для разработки.
  3. Защищённость. Сделать так, чтобы другим программистам было проще делать всё правильно.
  4. Отладка. Воспроизводить ошибки до исправления их первопричин. (Всегда лучше написать тест, который воспроизводит ошибку, чем производить традиционную отладку, поскольку это гарантирует, что данный вид ошибки не появится вновь.)
  5. Соглашение. Договориться о форматах ввода/вывода функций.
  6. Безопасность. Увеличить доверие к продукту при использовании динамических, бестиповых языков.
  7. Мотивация. Пометить небольшой фронт работ, которым вы собираетесь заниматься дальше.(Когда не знаешь как подступиться новому проекту, с чего начать, красный значок напротив теста подскажет, что можно сделать здесь и сейчас (правильно — сделать так, чтобы рядом с тестом стоял зелёный значок)).
  8. Фокус. Предотвратить возникновение раздутой или излишне абстрактной реализации.
  9. Путеводитель. Определить финишную черту, чтобы вы знать, когда работу можно считать завершённой.
  10. Эффективность. Использовать время, выделенное на ручное тестирование, с толком. (Зачастую сборка и запуск приложения на несколько порядков сложнее, чем запуск тестов. Вы же не хотите ждать 20 минут, пока идёт компиляция вашей игры для консоли и её перенос в SDK, чтобы увидеть, как она тут же падает на старте. Тест выявил бы ошибку значительно быстрее.)


Большинство из описанных выше причин писать тесты не связаны, явным образом, с тремя правилами TDD и фазой рефакторинга из цикла «красный-зеленый-рефакторинг» (5).

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

Тесты в качестве документации

… часто говорят, что тесты являются наилучшей формой технической документации.

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

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

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


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

Введение в Quack-Driven Development


Quack-Driven Development, или QDD, позволяет программисту выразить требования, соглашения и намерения внутри самой реализации.

Это современная методология «водоплавающих» (waterfowl) для разработки программного обеспечения.

Фундаментом QDD является идея переноса метода утенка в код, при помощи замены комментариев в коде на тестируемые утверждения. Она (идея) имеет простую философию, которая не ограничивает, а информирует нас, как делать работу:

  • Понимайте задачу.
    Не путайте задачу и её решение. Всегда будьте уверены, что вы делаете по настоящему важные вещи. Прочтите «Are Your Lights On» для вдохновения.
  • Доверяйте своим предположениям.
    Лучше делать проверку корректности данных в интерфейсе. Не загромождайте бизнес-логику из-за того, что вы не доверяете данным.
  • Уважайте читателя.
    Пишите код, который смогут прочесть другие программисты и вы сами (через год). Пишите код, который вам нравится читать. Показывайте, а не рассказывайте.

Следующая далее реализация QDD в Ruby, является доказательством правильности концепции QDD. Она позволяет программистам «поговорить с уткой» в коде, используя «утиный смайлик» (Q<), и демонстрирует, как я бы использовать QDD подход, чтобы реализовать пример игры в боулинг дядюшки Боба. Приступим.

Заложим основу приложения

class Game
  def initialize
    @frames = []
  end
  Q< "принимает количество сбитых кегель"
  def roll(pins)
    Q< "вызывается каждый раз, когда игрок бросает шар"
  end
  Q< "возвращает итоговый счёт игры"
  def score
    Q< "вызывается только в конце игры"
  end
end

  • Хорошенько подумаем, прежде чем делать набросок реализации.
  • Четыре «утки» внедрены в код. Они задают четыре требования явным и проверяемым образом.


Зададим «уток»

class Game
  Q< :roll do
    before "принимает количество сбитых кегель" do
      expect(pins).to be_a(FixNum)
      expect(1..10).to cover(pins)
    end
 
    within "вызывается каждый раз, когда игрок бросает шар" do
      expect(@frames).to be_a(Array)
      expect(1..10).to cover(@frames.length)
    end
  end
  Q< :score do
    within "вызывается только в конце игры" do
      expect(@frames.length).to eq(10)
    end
    after "возвращает итоговый счёт игры" do
      expect(retval).to be_a(FixNum)
      expect(0..300).to cover(retval)
    end
  end
end

  • «Утки» написаны в том же классе, но могут быть сохранены и в отдельном файле.
  • Они могут быть определены для запуска перед вызовом метода, внутри вызова метода или после того, как метод был выполнен.
  • Они выполняются в области видимости метода, который проходит тестирование, поэтому локальные переменные, определенные в теле метода, доступны внутри «уток».
  • Они используют rspec-ожидания для проверки состояния.

Добавим реализацию

class Game
  def initialize
    @frames = []
  end
  Q< "принимает количество сбитых кегель"
  def roll(pins)
    Q< "вызывается каждый раз, когда игрок бросает шар"
 
    @frames << Frame.new if _start_new_frame?
    Q< "фрейм, которому необходим бросок должен быть тут"
    Q< "если мы на 10-м фрейме,фрйем может быть завершенным"
    @frames.each do |frame|
      frame.bonus(pins) if frame.needs_bonus_roll?
    end
 
    @frames.last.roll(pins) if @frames.last.needs_roll?
  end
  Q< "возвращает итоговый счёт игры"
  def score
    Q< "вызывается только в конце игры"
    @frames.map(&:score).reduce(:+)
  end
  private
  def _start_new_frame?
    @frames.size == 0 ||
      @frames.size < 10 && !@frames.last.needs_roll?
  end
end

class Frame
  def initialize
    @rolls = [] ; @bonus_rolls = []
  end
  Q< "возвращает, ждет ли этот фрейм броска"
  def needs_roll?
    !_strike? && @rolls.size < 2
  end
  Q< "принимает количество сбитых кегель"
  def roll(pins)
    Q< "вызывается только когда фрейм ждет броска"
    @rolls << pins
  end
  Q< "возврвщает ждёт ли фрейм бонусного броска"
  def needs_bonus?
    _strike? && @bonus_rolls.size < 2 ||
      _spare? && @bonus_rolls.size < 1
  end
  Q< "принимает количество сбитых кегель"
  def bonus_roll(pins)
    Q< "вызывается только когда фрейм ждет бонусного броска"
    @bonus_roll << pins
  end
  Q< "возвращает итоговый счёт по фрейму"
  def score
    Q< "вызывается только когда больше нет бросков"
    @rolls.reduce(:+) + @bonus_rolls.reduce(0, :+)
  end
  private
  def _strike?
    @rolls.first == 10
  end
  def _spare?
    @rolls.reduce(:+) == 10
  end
end

  • Обратите внимание, что, параллельно с уточнением и развитием реализации, было добавлено больше «уток». Я не показал их определения для краткости.
  • Код получился таким из эстетических побуждений. Например, я бы особо отметил, что Game#score рассчитывается путем суммирования баллов, набранных в каждом фрейме. Я сделал так не потому, что так код выглядит более универсальным и повторно используемым, и не потому, что так его проще проверять, а потому, что я буду объяснять суть этого кода коллеге именно так, как он (код) уже написан.
  • Размышляя о реализации в том же ключе, можно обнаружить, что механизм подсчёт очков в реальной жизни содержит уродливый хак: игрок может бросать шар до трех раз в десятом фрейме. Не стоит реализовывать этот хак в коде, так как, очевидно, что каждый фрейм стостоит из двух различных видов бросков. Броски текущего фрейма и бонусные броски, которые будут реализованы в будущем, когда текущий фрейм будет завершен. Такое разделение также было продиктовано эстетикой и служит для более четкого понимания реализации.

Напишем несколько примеров

describe Game do
  let(:game) { Game.new }
  it “возвратит 0 если шар ушёл в желоб” do
    20.times { game.roll(0) }
    expect(game.score).to eq(0)
  end
 
  it "возвратит 20 если все кегли забиты" do
    20.times { game.roll(1) }
    expect(game.score).to eq(20)
  end
  it "учитывает спэры" do
    game.roll(5)
    game.roll(5)
    game.roll(3)
    17.times { game.roll(0) }
    expect(game.score).to eq(16)
  end
  it "учитывает броски" do
    game.roll(10)
    game.roll(3)
    game.roll(4)
    16.times { game.roll(0) }
    expect(game.score).to eq(24)
  end
  it "возвратит 300 в случае идеальной игры" do
    12.times { game.roll(10) }
    expect(game.score).to eq(300)
  end
  it “должна работать с игрой, заданной в примере” do
    [1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6].each do |pins|
      game.roll(pins)
    end
    expect(game.score).to eq(133)
  end
  it “не должна работать, если игра некорректна” do
    expect(game.score).to quack("вызывается только в конце игры")
    expect(game.roll(11)).to quack("принимает количество сбитых кегель")
    expect(game.roll(5.5)).to quack("принимает количество сбитых кегель")
    expect(game.roll(nil)).to quack
    expect(30.times { game.roll(0) }).to quack
  end
end


  • Это всего лишь стандартные rspec тесты, но они также проверяют «уток».
  • Они могут быть записаны до, после или вперемешку с реализацией.
  • Первые пять тестов являются зеркальным отражением тестов из тренировки дядюшки Боба. Их задача — управлять реализацией, в не проверять правильность исходного кода.
  • Мы также проверили пример, который был дан в требованиях к тренировке — некорректное использование тоже обрабатывается.


«Утки» в рабочей среде

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

А почему бы и нет! Поведение «уток» может быть настроено произвольным образом:

  • Выбросить исключение: Выбросить исключение. Поведение по умолчанию.
  • Зажурналировать: Зажурналировать ошибку и продолжить.
  • Отладить: Перейти в отладчик.
  • Игнорировать: Ничего не делать вообще.

«Уток» также легко можно закомментировать, превращая их в мертвых уток, как в следующем примере:

class Game
  #< "принимает количество сбитых кегель"
  def roll(pins)
    #< "вызывается каждый раз, когда игрок бросает шар"
  end
  #< "возвращает итоговый счёт игры"
  def score
    #< "вызывается только в конце игры"
  end
end

Обратите внимание, что даже «мертвые утки» служат комментариями. Являются ли комментарии, которые вы пишете в коде сегодня, всего лишь «мёртвыми утками», которые ждут своего часа, чтобы «вернуться к жизни» посредством QDD?

Итоги


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

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

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

Три правила QDD
  1. Создайте скелет реализации с «утками», которые фиксируют требования, интерфейсные соглашения и намерения программиста.
  2. Пишите тесты, которые проверяют «уток» и охватывают макет и пишите реализацию, чтобы эти тесты проходили. Не важно в каком порядке.
  3. Запустите «уток» «жить» в рабочую среду, предоставляя своим клиентам реальные примеры, а не ваши мудрёные спецификации!

Преимущества QDD

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

И, как бонус, утиный смайлик, просто, очень симпатичный.

Я рекомендую вам сегодня же добавить QDD-подход к вашей методологии «водоплавающих», так как он прекрасно дополняет утиную типизацию, избиение утки(6) и метод утёнка!

Q< «Тестирование никогда не превратится в „утку“!»



  1. TDD — Test Driven Development — техника разработки ПО через тестирование.
  2. Игра слов: «утка» — ложный слух, в первом случае, резиновая уточка для разговоров — во втором.
  3. Очередная игра слов: Каскадная модель разработки ПО (waterfall) превращается в «методологию водоплавающих птиц» (waterfowl).
  4. Чтобы легче понять о чём идет речь, ознакомьтесь с презентацией BowlingGameKata.ppt
  5. Подробнее в разделе «Стиль разработки» ru.wikipedia.org/wiki/Разработка_через_тестирование
  6. У нас используется выражение обезьяний патч.

Share post

Comments 21

    +4
    Лучше делать проверку корректности данных в интерфейсе. Не загромождайте бизнес-логику из-за того, что вы не доверяете данным.


    Это он серьезно? Или я что-то в контексте упустил?
    Как можно валидацию данных перемещать в UI и не делать в слое бизнес-логики?
      0
      Да, серьёзно. Посмотрите — там справа указаны комментарии автора к этому пункту. Но про UI вы уже сами надумали.
        +1
        Как раз ваш перевод точен и передает только то, что написал автор. Слово в слово.

        И я таки не согласен с выносом проверки данных из бизнес-логики.
        Доверять данным нельзя по определению, и проверять их надо на всех возможных уровнях, иначе аукнется рано или поздно большой (или маленькой) дырой в security.
          0
          С этим полностью соглашусь. На днях перечитывал «24 смертных греха компьютерной безопасности» (М. Ховард, Д. Лебланк, Дж. Вьега) и осознал, что лишняя паранояй никогда не будет лишней. Возможно, автор имел ввиду проверку на входах модулей/компонентов, которая позволит обеспечить «безопасную зону» внутри него.
            +1
            Автор имел в виду, что всё должно быть в меру.

            Разработка спецификации на компонент — большое дело, понять что в него может и не может приходить — нетривиальная задача и, конечно же, она должна быть сделана, если вы хотите добиться того, чтобы всё работало.

            А вот защищать компонент от самого себя — не стоит, проверки тоже являются кодом и в них тоже могут быть ошибки. Более того, их обилие может скрыть реальные ошибки и/или породить беспечность (а нафиг мне знать что в этой переменной? в случае чего проверка выкинет ошибку… только вот беда: если вы даже не знаете пришло ли вам число или строка, то вы легко можете пропустить, скажем, отрицательное число, которое вам всё программу развалит).
            0
            Имелось в виду, что проверять данные должен тот модуль системы, который их принимает. Например, форма. В любом адекватном движке форм (Симфони, Зенд, моем кастомном ;-) валидаторы вставляются прямо в форму, а в контроллере их нет. Если говорить о декстопах, то ситуация там должна быть аналогичной, хотя это и не практикуется в классике.

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

            Надеюсь удалось донести мысль :)
              0
              Контроллер — это никак не автономный код, который принимает неконтроллируемые значения.

              Это зависит исключительно от декомпоновки приложения. Например, в asp.net mvc контроллер как раз принимает на вход значения извне.

              Но дело даже не в этом. Идея защитного программирования в том, что любой код, вызов которого вы не контролируете, может быть рано или поздно вызван не так, как вы ожидаете, и вы должны от этого защищаться. В современных «традиционных» приложениях границ безопасности намного больше одной. Например, для многослойного веб-приложения здесь будет:
              • клиентская валидация (нужна исключительно для обеспечения комфорта пользователя)
              • валидация на входе в серверную часть (потому что js всегда можно отключить + http-запрос вообще можно послать откуда угодно)
              • валидация на входе в апп-сервер (если приложение имеет более одного слоя)
              • валидация на входе в объект бизнес-логики (потому что никто никогда не знает, откуда именно он вызван и насколько аккуратен он был)
              • валидация в БД — если мы реально параноики или если у нас нет уверенности, что в БД пишут только через наш же серверный слой
                0
                Подождите, причем тут клиент и БД. Речь во-первых о коде, который работает в рамках одного процесса. Это как бы не явно подразумевается. Для БД наш бэкэнд — как раз источник неконтроллируемых данных. Тоже самое и в связке киент-сервер. Но когда речь идет о «теле сайта», т.е. все, что выполняется в рамках одного процесса PHP/Ruby/Python/anyCGI и при этом написано для этого сайта, а не является отдельной библиотекой (т.е. view, controller – да, стороннее ORM — нет) – все эти части не просто могут, а должны доверять друг другу (оставляя проверки только на входе), иначе весь ваш код будут сполшные повторяющиеся проверки, а это плохо (если не атом, медицина, космос). Опять же не уверен, что мысль донес, но постарался.
                  0
                  Это как бы не явно подразумевается.

                  Кем это подразумевается? Или это методология QDD работает только и исключительно в таких условиях?

                  Но вообще то, что вы пытаетесь донести — это концепция security boundary, гласящая, что проверки выполняются один раз на пересечении публичного интерфейса модуля, а все внутренние по отношению к модулю объекты могут проверками не заниматься. Что характерно, у меня именно так и написано, иначе бы проверка была реально в каждом классе/методе/функции. Я просто привел наиболее типичные варианты расположения security boundary.

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

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

                    Не важно, сколько людей пишут. Важно, где граница автономного (от сайта) модуля — библиотеки. Про security boundary — это именно то, что пытаюсь донести и я, и автор. Проверили на входе и юзаем дальше где надо в рамках единого кода. Каждый вход в библиотеку — это новый вход и новые проверки. Контроллер бить библиотекой не может и проверок в нем быть не должно, кроме случая отхода от ООП и MVC, когда контроллер напрямую работает с GET-параметрами, куками или другими данными, приходящими отпользователя. Подробнее тут написал.
                      0
                      Не вырывайте фраз из контекста. Связь вашего комментария с цитатой моего предложения мне не понятна.

                      Связь очень простая: вы пишете, что «подразумевается, что весь код в одном процессе», а я спрашиваю, кем это подразумевается.

                      Не важно, сколько людей пишут.

                      Важно-важно. Чем больше людей, тем более вероятность ошибки.

                      Контроллер бить библиотекой не может и проверок в нем быть не должно, кроме случая отхода от ООП и MVC

                      Давайте не будем начинать холивар за правильные реализации MVC? Просто поверьте мне, что бывают другие декомпоновки, и они не менее «канонические».

                      Вы-то, возможно, и пытаетесь донести идею security boundary, но в посте явно написано «Лучше делать проверку корректности данных в интерфейсе. Не загромождайте бизнес-логику из-за того, что вы не доверяете данным» (хотя многие проверки — это часть бизнес-логики), и вот эта формулировка — очень спорна.
                0
                >Если автономный код принимает на вход неконтроллируемые значения, он должен их проверить. В противном случае проверять данные нет смысла, если вы пишете не космос, атом или медицину. Контроллер — это никак не автономный код, который принимает неконтроллируемые значения. А вот слой интерфейса — это именно оно.

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

                    То есть проверять данные? ;)
                      0
                      Ну да.
                    0
                    Входные данные для контроллера поставляется компонент, который их принимает от юзера. Если это форма — то это объект, который представляет эту форму. Если это список с фильтрами и сортировками — то это объект, представляющий список. При этом контроллер сам его инициализирует и запускает в работу. Еще вариант, что параметры проверяет на валидность роутер, когда речь о парсинге URL (ну вон как ID поста на хабре). Кстати роутер сам же и 404 должен отдать при ошибке, без участия контроллера. Когда ни формы, ни списка, ни роута как таковоого нет, проверка происходит в контроллере, потому что он становится точкой входа. Но это как бы относительно редко используется *ну прямая передача get-параметров из ссылки).
            +10
            «Может лучше продолжать тестировать наш код в прямом эфире, во время того как пользователи взаимодействуют с нашим программным продуктом?»

            Я думаю, лучше перевести эту фразу так:

            «Я никогда не читал про Бертрана Мейера и Design-by-contract поэтому решил сделать свою реализацию смешивающую документацию, контракты и тест в одном понятии „утка“»
              0
              Кстати, порекомендуйте литературу от Мейера, пожалуйста.
                0
                Я читал про Design by contract миллиард лет назад и уже не помню какого автора, так что могу только порекомендовать поискать самостоятельно про Design by contract и язык Эйфель (кстати, DBC — это зарегистрированная торговая марка, поэтому MS, например, называет у себя тоже самое Code contracts)
              0
              Хм, а как же правило «не писать специальный код для тестов» в продакшене?
                +3
                Кто нибудь знает, что делать с подобной информацией? ООП умерло, TDD умерла, DDD умерла, DI умер, XP умерло, Agile давно умер, SOLID умер, FP тоже умерло?

                Только криворукие спагетти-программисты вечные! На них и уповаем.

                Only users with full accounts can post comments. Log in, please.