Pull to refresh
7
0
Андрей Коломенский @onedev_link

Founder, Executive Partner at LeadStartup

Send message
Что определяет качество выражения предметной области в коде?
Если граф плох для быстрой, автоматизированной проверки, это является явным маркером сильного связывания. Что приводит к жесткости, хрупкости, непереносимости, нарушению SRP и OCP.

Название должны ясно выражать намерения. Вы можете привести пример из класса, состоящего из 3-4-5 зависимостей, я сделаю из него класс с 1-2 зависимостями, причем они будут лучше выражать намерения чем те зависимости. Есть исключения, многопоточность, Observer-ы, но в целом задача решается.
Работа с Legacy это отдельный большой топик. Если тестов нет, то обычно применяется Golden Master и верхнеуровневые Acceptance тесты. Далее в зависимости от стратегии, самый простой вариант — писать новые классы с явным контролем зависимостей, изменяемые части системы постепенно выносить в отдельные классы и покрывать тестами.

Если есть тесты, но они интегрированные, коротко алгоритм следующий:

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

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

Тем самым мы:
  • Увеличили количество элементов в системе
  • Повысили количество абстракции
  • Лучше прояснили намерения
  • Вероятней всего обеспечили SRP и OCP
  • Сделали возможным написание изолированных тестов с разделением на тесты контракта и коллаборации


Иногда, особенно если используется сервис-локатор, происходит просто взрыв новых, самых разнообразных классов. Просто аккуратно распихиваем ответственности по своим местам для SRP и OCP.

Есть много нюансов. В частности DDD помогает лучше сформировать выражения намерений, а также еще хочется добавить что наибольший результат этот подход дает для продуктов которые уже нашли свою бизнес–модель. Стартапам не нужно поддерживать продуктивность разработки на постоянном уровне.
Логические соображения описаны мастерами в книгах и выступлениях, я привел их в статье, а также привел свои аргументы. Могу сконтачить с J.B.Rainsberger, Кент Беку можно написать в фейсбук.
Да, именно. Я предлагаю жесткую дисциплину: ни одной строчки кода без быстрой, автоматизированной проверки его поведения. Я предлагаю писать код так чтобы QA обнаруживал 0 дефектов. TDD — единственная дисциплина которая может позволить реализовать это.

Это тяжело. Это очень тяжело. И это профессионально.

> Есть какие-то аргументы в пользу этой точки зрения?
Опыт индустрии за последние 25 лет. Попробуйте пройти bowling kata game, это будет как один из примеров как это работает.
> Берешь и меняешь. Мы тут обсуждаем работу взрослых людей, профессионалов, или выезд детсада на пикник? Что это еще за «набраться храбрости»? «Не могу фичу внедрить, мне страшно»? Инфантилизм какой-то возведенный в абсолют, уж простите.

Обычно так: «Для того чтобы правильно имплементировать это требование нужно изменить структуру 2х модулей. Один модуль связан с адресацией, второй с контрактами. Есть интеграционные тесты, но они тестируют только ограниченное количество вариаций. Если я где-то накосячу, а QA не обнаружит дефект то у компани из-за меня будут проблемы, очень вероятно финансовые, а мне это не нужно. Сделаю-ка я чуть-чуть по-другому.»

Альтернативное мышление в виде «Я проведу рефакторинг, а QA пусть ищет дефекты» не лучше.

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

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

В западных странах в нормальную компанию на позицию Senior бекэнд разработки не возьмут без навыков написания юнит-тестов. В Silicon Valley это ~99% компаний практикующие гибкие подходы к разработке, большая часть также использует TDD.
Юнит-тесты являются основой, на которой строится качественная архитектура.

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

Я уверен, существуют исключения, но как правило в среде, где рефакторинг означает «я поменяю структуру системы без изменения поведения, и возможно внесу в систему явные и скрытые дефекты» вынуждает разработчиков обходить проблемные места стороной, особенно под давлением сроков. Результат — загнивание системы и замедление разработки.

Проверьте, попробуйте написать юнит-тест на ваш текущий модуль, с полной изоляцией зависимостей. Любые сложности с которыми вы столкнетесь, будут связаны с одним из этих свойств. Обычно жесткость (сильная связанность) будет основной проблемой.
Юнит–тесты, написанные через TDD, позволяют моему мозгу принимать более качественные архитектурные решения, а также улучшать дизайн системы. Без них — анрил, особенно в командной разработке сложной системы.
4 Rules of Simple Design рассказывает о приоритизации. Без тестов безопасный рефакторинг невозможен, следовательно невозможно устранение дублирования и прояснение намерений.

Вы правильно говорите, нужно двигаться по циклу Red — Green — Refactor.
Я правильно понимаю, что бот-«бухгалтер» никак не поможет мне понять, что является доходом на УСН а что нет??? (бывают платежи, которые не учитываются, кроме того есть возвраты, которые учитываются как отрицательные доходы и т.п. ) — ведь именно это, в первую очередь, помогает экономить на налогах, если я вообще веду какую-то экономическую деятельность и рассчитываю на прибыль. Т.е. нужно сначала где-то это все самому посчитать и потом лишь ввести общую сумму в этом блестящем боте??? Но ведь это смешно — каша из топора! Таких калькуляторов уже полно.
Вы даже не формируете платежки и никак не помогаете с отправкой отчетности.

Я не говорю, что если чуть расширю свою деятельность, добавится патент или сменю СНО или появятся сотрудники и т.п. Перечисленные вами облачные бухгалтерии легко и быстро сделают доработку на подсказку когда лучше платить взносы (а я знаю что ребята-разрабы из этих сервисов мониторят конкурентов), а вот чат-бот до этих сервисов не вырастет никогда. Ведь ценность сервисов как раз в том, что они полностью закрывают мою потребность в бухгалтере и не требуют от меня знания налогового и бух. учета, т.к. все операции автоматически разбираются в зависимости от типа и моей СНО и других особенностей.

Кроме того, налоговое планирование само по себе имеет смысл только при рассмотрении каждой конкретной ситуации. Для этого работает служба сопровождения клиентов, в т.ч. онлайн-конультант и Служба консалтинга, которые по запросу клиента могут предложить ему наиболее «эффективный» вариант, в том числе и по учету взносов в уменьшение налога, и в целом по организации бизнеса. (В мое дело точно работает, насчет эльбы — вроде, но не уверен)
Кроме того в Мое дело и эльбе рассчитаны суммы налогов и лишь предложены, а редактировать их можно вдоль и поперек, если так хочется и создавать платежки + сразу отправлять в свой банк по интеграции.

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

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

Статья прям пропитана влиянием Дяди Боба :)
Хорошая архитектура – та, что соответствует своему назначению, т.е. позволяет решать поставленные перед ней задачи.


Можете рассказать как вы измеряете качество архитектуры?
Я предполагал что код метода `send` находится прямо внутри метода отправки сообщений пользователей, у вас всё верно. Мои аргументы предназначались для случая когда отправка выполнялась прямо в методе.

а в случае test-first мы должны проверить что сервису БД были переданные данные на запись

Верно.

Тестирование по принципу black-box означает тестирование реакции на воздействие абстрагированное от внутренней реализации.

Вот кстати я не до конца понимаю этот момент. Если мы мочим реализацию public методов зависимостей, которые потом использует
тест, это считается black box или нет? С одной стороны мы предполагаем что должно происходить внутри метода, с другой стороны нас это не интересует, так как нам важно получить нужный assert и не важно как метод это будет делать.
Выносить рассылку в отдельную функцию не совсем разумно, особенно если это единственное место в проекте где есть рассылка

1. Не разумно выносить в функцию? Вам необходимо вводить отдельный класс, обязанностью которого будет осуществление рассылки и подключать его как зависимость.
2. Не единственное: тесты тоже будут использовать этот метод. Это также аргумент в сторону тех кто не хочет создавать интерфейсы для только одной реализации. Stubs тоже будут использовать этот интерфейс.

Если вы пишите тесты перед реализацией то реализация представляет собой чистейший черный ящик, потому что реализации ещё нет.
Ведь с такой трактовкой, как ваша, настоящая программистская работа, where rubber hits the ground, где мы пишем алгоритм, происходит именно на стадии рефакторинга.


Нет. Рефакторинг подразумевает изменение структуры программы без изменения её поведения. На стадии рефакторинга вы не можете писать новые тесты по определению. Настоящая работа происходит на всех этапах.

… что непонятно, а после скольких тестов и захардкоженных ветвлений, нужно этим рефакторингом заниматься


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

Не будем лукавить — каждую новую строчку в эту череду if-oв, вообще говоря, добавлять проще, чем вот это общее «а+b».


Не проще, потому что у вас дедлайн и вам платят за разработку ценного функционала.

TDD это строгая дисциплина и она требует определенного последовательного процесса работы. Я сам прошел через это. «у меня метод уже полностью в голове реализован и он точно будет корректен, зачем писать эту пачку бесполезных тестов?». Лень. Медленные интегрированные тесты, когда желания писать много тестов нет вообще. И минимальная реализация с хардкодом при первом тесте, это же для дураков ведь, да? Я же умный, могу писать правильную реализацию сразу и очнуться через пол-часа с десятью интегрированными тестами, которые не принуждают дизайн системы быть удобным для тестирования, не дают по нему никакой обратной связи и не отлавливают все ошибки в коде. Зато «сэкономил» 15 минут, о да.
Спасибо за формальную верификацию, изучу вопрос.

Зафиксировать столько, сколько нужно раньше реализации — не получается.

Почему бы не фиксировать одно требование за раз?

черт, промазал
Нет, в данном случае не пройдет (другой тест проверяет что функция должна вернуть 4), но это очень важный момент!

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

Помните, вы пишите тесты перед реализацией для того чтобы облегчить себе жизнь. Вам не нужно тестировать в стиле «если функция работает с (1,1) то не факт что она будет работать с (100,100), напишу ка я тест». Вы понимаете что вам для удобства и удовлетворения требований бизнеса необходимо от метода конкретное поведение. Вы его фиксируете тестом. Если хотите протестировать ещё и пограничные состояния вы делаете ставку и пишите тест. Если тест в будущем сломается вы выиграли. Если нет, то тест оказался бесполезным. В любом случае тест должен обязательно провалиться, иначе это стопроцентно бесполезный тест.
При проектировании метода вы пишите первый тест, где на входном значении (2,2) вы ожидаете 4. В реализации вы хардкодите return 4. Это третий принцип TDD. Вы пишите только тот минимальный код реализации, чтобы текущий тест прошел вместе со всеми остальными. Потом вы пишите ещё один тест с входными значениями (1,0) ожидая return 1, и уже пишите код сложения.

Ещё один момент: чтобы не словить ситуацию когда вы подбираете и бесконечно пишите бесполезные тесты вроде (1,0), (1,1), (1,2) и т.д, тест после написания обязательно должен провалиться (второй принцип TDD). Если тест после написания не проваливается значит он не нужен.
1

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity