Comments 13
Я правильно понимаю, что можно напрямую обращаться к автору статьи? :)
Не имею возможности сейчас подробно анализировать ни код в книге, ни ваш код. При поверхностном же прочтении бросаются в глаза некоторые моменты. Сначала хотел их поместить в один коммент, но он получился слишком сложным, да и формулировать не всегда получается достаточно быстро. Поэтому буду излагать их по одному. Вот первый.
TDD/BDD «с моками» нужно для того, чтобы одной методологией (TDD) покрыть все стадии разработки: анализ требований – дизайн – реализация, без единого разрыва :) на up-front проектирование. Как это сделать без моков — я вариантов не встречал (если знаете, поделитесь, кстати).
По сути, вы пользуетесь результатами этого процесса, когда предлагаете свой дизайн:
Не имею возможности сейчас подробно анализировать ни код в книге, ни ваш код. При поверхностном же прочтении бросаются в глаза некоторые моменты. Сначала хотел их поместить в один коммент, но он получился слишком сложным, да и формулировать не всегда получается достаточно быстро. Поэтому буду излагать их по одному. Вот первый.
TDD/BDD «с моками» нужно для того, чтобы одной методологией (TDD) покрыть все стадии разработки: анализ требований – дизайн – реализация, без единого разрыва :) на up-front проектирование. Как это сделать без моков — я вариантов не встречал (если знаете, поделитесь, кстати).
По сути, вы пользуетесь результатами этого процесса, когда предлагаете свой дизайн:
Оно (приложение) принимает события от сервера и реагирует на них некоторыми командами, поддерживая внутреннюю state машину ≤…> И это по сути всё.Как бы вы смогли сделать так обобщенное утверждение, не исследуя отдельные частные случаи (что сделали авторы книги в тестах с моками)? Если бы эти частные случаи не были бы разобраны достаточно подробно (в тестах с моками), как бы вы могли быть уверены, что ваше архитектурное решение правильное? Ну, и т.д. То есть, с одной стороны имеем открыто показанное (почти) во всех подробностях решение, иллюстрирующее общий метод, а с другой — из шляпы фокусника извлеченное лучшее (это, надеюсь, мы еще обсудим) решение. Собственно, вопрос: вы предлагаете какую-то свою методологию построения систем? Если да, в чем ее отличие от TDD/BDD, которые по сути следуют по «классическому» пути ООП (найди функциональность — найди (или создай) объект, в который эту функциональность надо поместить) на уровне анализа отдельных (а не обобщенных) требований.
0
Вопрос по больше части сводится к top-down vs bottom-up подходу к разработке. Mockist подход действительно помогает при top-down, т.к. позволяет «мочить» несущественные детали. Я бы не сказал, что один из подходов позволяет решать задачи проектирования лучше другого. Лично я больше тяготею к классическому bottom-up, но также понимаю людей, которые предпочитают top-down.
При разработке top-down без моков действительно никак. Но при этом эти моки не обязательно оставлять после того как реализация готова. Такие тесты можно отрефакторить и заменить тестами без моков, что я собственно и сделал в статье.
При разработке top-down без моков действительно никак. Но при этом эти моки не обязательно оставлять после того как реализация готова. Такие тесты можно отрефакторить и заменить тестами без моков, что я собственно и сделал в статье.
0
По первому абзацу можно понять, что вы противопоставляете эти два подхода. «Полноценный» TDD позволяет сочетать (как всегда и было по классике, кстати): сверху-вниз «проектируем» систему (в данном случае с помощью моков выделяя и распределяя ответственность), затем снизу-вверх реализуем (имея разбивку на объекты и структуру каждого объекта, прорабатываем необходимые детали реализации с помощью «классического» TDD). Разве нет?
Второй абзац окончательно запутывает: так, вы не против разработки с моками? Вы просто призываете их удалить после того, как «реализация готова»?
Второй абзац окончательно запутывает: так, вы не против разработки с моками? Вы просто призываете их удалить после того, как «реализация готова»?
0
Классика — это всегда bottom-up, Кент Бек и ко не писали про моки в оригинале, лондонская школа выработалась позже. Сочетать то, что описано в книге с классикой кстати возможно, но не так как вы описали (и это соответственно не будет полноценным top-down). Можно начать с набросков доменной модели (без тестов), затем после того как структура более-менее понятна — написать первый end-to-end тест и прокладывать себе путь к его исполнению путем классического bottom-up. Получится эдакий двух-уровневый TDD (как описано в книге), но без моков и без преждевременного распределения ответственностей. Проблема в top-down подходе в том, что если вы неверно выделелили эти ответственности, то отрефакторить их довольно сложно, т.к. из-за моков тесты становятся завязаны на детали имплементации.
вы не против разработки с моками? Вы просто призываете их удалить после того, как «реализация готова»?Это я к тому, что если вы большой приверженец подхода сверзу-вниз, то это тоже не повод оставлять моки в конечной имплементации.
0
Классика — это всегда bottom-up, Кент Бек и ко не писали про моки в оригинале, лондонская школа выработалась позже.А, вы о классике TDD? Я не сразу понял. Я-то о классике программирования :) — в книжках еще про структурных подход можно почитать.
Сочетать то, что описано в книге с классикой кстати возможно, но не так как вы описали (и это соответственно не будет полноценным top-down).Я бегло поискал по книге — они действительно об этом не пишут! То, как я это описал, работает — я так практикую уже несколько лет. Осталось выяснить, откуда я это взял :) Не сам же придумал! Был уверен, что вычитал данный подход именно в «Growing…»
Можно начать с набросков доменной модели (без тестов), затем после того как структура более-менее понятна — написать первый end-to-end тест и прокладывать себе путь к его исполнению путем классического bottom-up. Получится эдакий двух-уровневый TDD (как описано в книге), но без моков и без преждевременного распределения ответственностей. Проблема в top-down подходе в том, что если вы неверно выделелили эти ответственности, то отрефакторить их довольно сложно, т.к. из-за моков тесты становятся завязаны на детали имплементации.На практике часто все происходит с точностью до наоборот. Если «набросать структуру», то очень легко ошибиться, и ошибка часто выявляется уже на поздних стадиях: когда снизу-вверх «поднялись» до верхних уровней и вдруг оказалось, что модуль A должен использовать объекты из модуля B, но ничего про модуль B не знает, а чтобы узнал — нужно изменить (часто просто поломать) всю сложившуюся структуру. Очень огорчает и приводит к большим временным затратам.
Когда же проектирование идет не в голове/доске/листочках бумаги, а в тестах (где конкретные требования фиксируются — специфицируются) такого практически не бывает. Распределение ответственности там происходит непосредственно по факту: данный объект должен реализовать такое-то требование? — для этого необходимо выполнить такую-то работу — кто ее может сделать? — если объекта нет, вводим мок и на следующем шаге разработки углубляемся в него; если объект уже есть — пишем классику и начинаем подъем вверх. Ошибки аналогичные описанной выше (не учли, что для выполнения некоторой работы нужен еще один объект) случаются здесь гораздо реже (потому что разбираем не абстрактно-общие понятия, а частные случаи и не в абстрактном пространстве, а в реальном коде), а когда случаются — такие ошибки выявляется достаточно быстро, часто уже на следующем уровне детализации, но в любом случае еще при движении вниз (при реализации замоченного объекта выясняется, что он не в состоянии реализовать предъявляемые требования) — когда время на детальную проработку структуры системы еще не потрачено и менять ее относительно легко.
Тесты с моками далеко не всегда оказываются такими уж хрупкими — если их использовать как спецификацию выявленных требований, а не пытаться отразить все аспекты поведения проектируемой системы. Если не включать в них лишних деталей, то даже если при анализе ошибся (что не мудрено), то изменить тест для исправления оказывается не такой уж сложной/трудоемкой задачей. …Хотя это не всегда просто и (судя по всему) нужен опыт — шишок много уже набито, и они продолжают набиваться :) Средства разработки (фреймворки для mock-ования:) хоть и развиваются, но отстают существенно, по крайней мере, не помогая в той степени, в какой могли бы… а во многом и затрудняя, да…
0
Если «набросать структуру», то очень легко ошибитьсяСтурктура как раз-таки должна быть легковесной (отсюда слово «набросать»), чтобы ее можно было легко менять. Итерировать дизайн нужно в любом случае, разница в том, есть ли у вас при этом тесты и если есть — насколько хрупкие они.
Тесты с моками далеко не всегда оказываются такими уж хрупкимиТесты с моками не хрупки только когда они заменяют собой external systems (bus, БД и т.д). Если они мочат внутренности доменной модели — они завязываются на детали имплементации и значит становятся хрупкими. Сторонники mockist подхода (как минимум те, кого я встречал, включая авторов GOOS книги) не делают такого разделения и как правило мочат всё подряд — и внешние системы и внутренности самой доменной модели.
+1
Тесты с моками не хрупки только когда они заменяют собой external systems (bus, БД и т.д).Это как раз тот случай, когда моки использовать не следует! Если, конечно, вы о моках говорите, а не о Test Doubles в общем виде.
Стурктура как раз-таки должна быть легковесной (отсюда слово «набросать»), чтобы ее можно было легко менять.Да понятно, что все должны быть богатыми и здоровыми. Вопрос в том, как этого добиться. Как работает TDD с моками и без, почему это так, где тут плюсы, а где минусы и т.д. — более-менее понятно (если этим долго и упорно заниматься). А вот откуда возьмется легковесная и легко изменяемая на протяжении всей жизни программной системы структура — не ясно. Может быть, она должна присниться (как якобы было с таблицей и Менделеевым)? Или нужно «съесть зубы» на архитектурах программного обеспечения? Или изучить все существующие архитектурные/дизайнерские паттерны? Развить в себе интуицию до уровня вангования чтобы наперед знать все возможные последствия того или иного принятого решения? Нужно познать смысл жизни? :) Я пока не увидел ответа, который мог бы работать для любого/обычного человека. Или, может быть, наоборот: не надо уделять этому вопросу лишнего внимания? «набросал» что первое пришло в голову и отдал кодерами — пусть сами мучаются со своей кодобазой, если через полгода выясниться, что эта простенькая структура не отвечает необходимым требованиям?
Итерировать дизайн нужно в любом случае, разница в том, есть ли у вас при этом тесты и если есть — насколько хрупкие они.Создается впечатление, что вы понимаете TDD как тестирование. И тесты для вас — какой-то ненужный артефакт, мешающий процессу разработки: закончили разработку — запустили (условно) один раз тесты, чтобы убедиться что все правильно — забыли про них.
Если же относиться к TDD как к Test-Drivent Development-у (я выделил ключевое слово), то в идеале тесты являются формализованными (точно в необходимой степени) и автоматически проверяемыми спецификациями на код. Их поломка должна показывать, что изменились наши требования — и это очень важный момент. Если же наши тесты ломаются по каким-то другим причинам, и нам это не помогает обнаружить проблему, а мешает развивать систему — значит мы не умеем правильно писать тесты.
+1
найди функциональность — найди (или создай) объект, в который эту функциональность надо поместить
Идеологи ООП с вами не согласны. «Правильно» все таки наоборот.
0
По поводу того, что предложенное вами альтернативное решение проще. Диаграмма действительно выглядит проще. Но это не значит, что вся система проще исходной. Можно же сделать диаграмму и из одного элемента, спрятав внутри тонны километровых спагетти :) Собственно, основной «гипотезой» ООП относительно борьбы со сложностью является (слегка утрирую) утверждение «много простых объектов — лучше, чем мало сложных».
Действительно ли ваша система проще? Мне пока вы не смогли это продемонстрировать. Понимаю, что для точного ответа на вопрос что проще, надо смотреть и сравнивать код. Для меня, как «стороннего» читателя выделить на это время проблематично. Может быть, вы могли бы предъявить какие-то более веские обоснования, что ваше решение действительно лучше?
Другая сторона вопроса: за счет чего достигается простота? Используете ли вы аналогичные внешние библиотеки/фреймворки или же часть «черной работы» за вас уже сделано и за счет этого удается упростить ваш код?
И есть еще третья сторона: почему авторы ввели все эти (как следует из вашей критики — ненужные/лишние) объекты? К примеру, за счет чего их «цепочка» из четырех классов приводящих к AuctionEvent у вас превращается всего лишь в одно такое событие? Это моки заставили авторов наделать столько классов (что сомнительно) или (что более вероятно) они принимали во внимание какие-то аспекты, которые вы по каким-то причинам откинули?
Действительно ли ваша система проще? Мне пока вы не смогли это продемонстрировать. Понимаю, что для точного ответа на вопрос что проще, надо смотреть и сравнивать код. Для меня, как «стороннего» читателя выделить на это время проблематично. Может быть, вы могли бы предъявить какие-то более веские обоснования, что ваше решение действительно лучше?
Другая сторона вопроса: за счет чего достигается простота? Используете ли вы аналогичные внешние библиотеки/фреймворки или же часть «черной работы» за вас уже сделано и за счет этого удается упростить ваш код?
И есть еще третья сторона: почему авторы ввели все эти (как следует из вашей критики — ненужные/лишние) объекты? К примеру, за счет чего их «цепочка» из четырех классов приводящих к AuctionEvent у вас превращается всего лишь в одно такое событие? Это моки заставили авторов наделать столько классов (что сомнительно) или (что более вероятно) они принимали во внимание какие-то аспекты, которые вы по каким-то причинам откинули?
0
Действительно ли ваша система проще? Мне пока вы не смогли это продемонстрировать.Мне кажется, это довольно легко увидеть глядя на диаграммы + код. В альтернативной версии в два раза меньше классов и интерфейсов (при этом код внутри самих классов либо такой же по размеру, либо меньше), плюс он не имеет недостатков, описанных в статье, таких как наличие циклических зависимостей.
Фукнциональность проекта идентична оригиналу, как по части самого кода так и по части тестов его покрывающих, фреймворки (кроме UI) не используются.
Это моки заставили авторов наделать столько классов (что сомнительно) или (что более вероятно) они принимали во внимание какие-то аспекты, которые вы по каким-то причинам откинули?Опять же, это легко проверить посмотрев на код.
0
Откуда у авторов книги взялись циклические зависимости?
Если разобрать предлагаемый ими подход, то он состоит из двух этапов: разработка (как это раньше называлось) сверху-вниз (outside-in, если не ошибаюсь, в их терминологии?) и снизу-вверх (inside-out). Моки нужны на первом — чтобы выделять коллабораторов и иметь возможность написать работающий тест. И при этом, по идее, должны возникать только односторонние зависимости: от разрабатываемой системы (System Under Test …но лучше все же называть ее System Under Development?) к сотрудникам (collaborators), которые в будущем должны будут обеспечить требующуюся ей функциональность.
Иногда, впрочем, возникает соблазн передать в сотрудника разрабатываемую систему, чтобы первый сам извлек из нее какую-то необходимую ему информацию. Обычно это приводит к плохим последствиям. Авторы книги попали именно в такую ловушку? Или откуда еще взялись циклы?
Без ответа на этот вопрос — опять же — возникает ощущение фокусничества: возможно, компоненты действительно должны взаимодействовать «в обе стороны», а вы просто прячете это за возможностями какого-нибудь фреймворка.
Если разобрать предлагаемый ими подход, то он состоит из двух этапов: разработка (как это раньше называлось) сверху-вниз (outside-in, если не ошибаюсь, в их терминологии?) и снизу-вверх (inside-out). Моки нужны на первом — чтобы выделять коллабораторов и иметь возможность написать работающий тест. И при этом, по идее, должны возникать только односторонние зависимости: от разрабатываемой системы (System Under Test …но лучше все же называть ее System Under Development?) к сотрудникам (collaborators), которые в будущем должны будут обеспечить требующуюся ей функциональность.
Иногда, впрочем, возникает соблазн передать в сотрудника разрабатываемую систему, чтобы первый сам извлек из нее какую-то необходимую ему информацию. Обычно это приводит к плохим последствиям. Авторы книги попали именно в такую ловушку? Или откуда еще взялись циклы?
Без ответа на этот вопрос — опять же — возникает ощущение фокусничества: возможно, компоненты действительно должны взаимодействовать «в обе стороны», а вы просто прячете это за возможностями какого-нибудь фреймворка.
0
Чем руководствовались авторы сказать трудно. В книге кстати заметно как они испытывают трудности с циклами, т.к. возникают проблемы при «собирании» всех взаимодействующих классов воедино в composition root.
возможно, компоненты действительно должны взаимодействовать «в обе стороны», а вы просто прячете это за возможностями какого-нибудь фреймворкаКак я уже упомянул, код проекта довольно несложен, никаких фреймворков за исключением UI не используется. Должны или нет взаимодействовать в обе стороны — на мой вгляд неверная постановка вопроса. Нужно смотреть на то, можно ли сделать так, чтобы они работали только в одну сторону. Если можно — значит так и нужно делать.
0
Sign up to leave a comment.
Отзыв на книгу Growing Object-Oriented Software, Guided by Tests