Обновить

Комментарии 18

Изменение требований должно «ломать» код

Чё? Автор, вроде, не совсем новичок, судя по страничке about, но это за гранью. Жертвовать обратной совместимостью ради непоймичего — это сразу на выход.

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

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

Мне кажется, вы не поняли мысль автора. Изменение бизнес-требований должно ломать код, чтобы компилятор вам подсказал, где его надо исправить. Скажем, если добавить новое значение в enum в C#, компилятор не подскажем места, где этот enum проверяется. В отличие от F#, который покажет, где надо дописать новый вариант.

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

А мне кажется, что если вы внимательно перечитаете пример автора, то вы увидите, что проблема «забыл про вновб добавленное» решена вызовом List.iter на коллекции, вместо хардкода. Это имеет какое-то отношение к типам? — Нет.

Зато замена типа ContactInformation — на внезапно список (sic!) — преступление как перед всеми, кто этот тип использовал, так и перед всеми, кто его собирается использовать в будущем (теперь туда поместится пять имейлов и ни одного адреса).

Типы — иногда — штука удобная, но вот такие бобмартиновские примеры даже ярого адепта отвадят.

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

По крайней мере, не вижу внутренних противоречий в статье.

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

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

Сиё обсуждение происходит уже не в первый раз, и как я понял, для C#-еров такой подход выглядит экстремально, но тут просто необходимо учитывать устройство F#. Язык провоцирует нас создавать много типов с очень узкой сферой применения (<= домену в тдд). Если этому противиться, можно оказаться в ситуации, когда каждая правка будет сопровождаться философскими рассуждениями о том, каким эхом наши действия отразятся в вечности. Долго в режиме “симулятор тайп-астронавта: вид сбоку” существовать не получается, поэтому неизбежно начинается эрозия качества и прочий “реализьм”. Оба варианта мне не нравятся.

Понятно, что в случае библиотечных (ну или просто очень больших) решений у подхода ломай -> чини -> повтори будут трудности. Придётся изворачиваться, работать через type extrensions, быть может отказаться от алгебраических типов, чтобы не сталкиваться с перспективой неработающих конструкторов и т.д. Однако, во-первых, до проекта таких размеров надо ещё дожить, во-вторых, даже на больших кодовых базах большая часть кода существует в изолированных карманах, которым “библиотечный” уровень обязательств без надобности.

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

Вы правильные вещи говорите, особенно про:

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

Проблема в том, что чувак не просто девелопер, но еще и архитектор, а вдобавок — консультант. К его словам прислушиваются (наверное). А тут такая фигня.

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

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

Да это не про слом обратной совместимости, это вообще про другое это другой уровень, тут обратная совместимость никак не сломается

Применительно к F# ожидания надо скорректировать. Мы действительно до поры игнорируем обратную совместимость как несущественный фактор и не испытываем по этому поводу угрызений совести, так как эта схема работает на дистанциях значительно больших, чем это можно было представить по опыту C#. Лишь когда припрёт, мы начинаем танцы с бубнами. Причём этими танцами владеет очень ограниченный набор лиц/ Большинство из них явно выходят из аудитории, с которой работает Влашин как автор блога.

// Опыта общения с ним в роли консультанта не имею, и не знаю, что он говорит при личном общении.

Ну как так-то? Я вот тут попиливаю свой язык с завтипами, компилируемый в BEAM. В данный момент воюю с hot reload’ом, из-за которого в своё время Армстронг и Вирдинг отказались от типизации в эрланге.

Задача, в принципе, решаемая — но только до того момента, пока не меняются типы данных. Даже локальные.

Я очень хорошо понимаю вашу аргументацию, большое спасибо, что не поленились рассказать о кухне. Причины тоже понятны: у ваших типов обычно очень узкая доменная область применения, они почти все локальные, поэтому их в подавляющем большинстве случаев можно считать «деталями реализации». Так?

Всё так. Чем уже сфера ответственности, тем меньше шансов что-то сломать на широком фронте.

По хотрелоаду вполне возможно, что вы пытаетесь взять планку, которую F# не взял. У нас в REPL изменённый тип – это новый тип. С точки зрения компилятора он не эквивалентен старому, поэтому их нельзя просто так смешивать (за исключением случаев апкаста к общему предку). Часто приходится пересобирать зависимые модули и т.д. Кложуристы при столкновение с этим начинают выть, но после обретения некоторого опыта желания компилятора можно предвосхищать.

С рекордами приблизительно тоже самое происходит. Если добавить в некий тип поле B, то все конструкции вида { record with A = 42 } (новое поле не задействовано!) в зависимых dll начнут падать в рантайме, пока их не пересоберут с обновлённой либой. Код при этом останется идентичным. То есть проблему не решили by design, но существенных потерь от этого никто не понёс.

Ага, понимаю. Иногда лучше иметь внятное, ограниченное и работающее решение, чем формально безупречное, но непонятное и кривое.

По хотрелоаду вполне возможно, что вы пытаетесь взять планку, которую F# не взял. У нас в REPL изменённый тип – это новый тип.

В BEAM понятие hot reload — не про REPL, а про продакшн :) Возможность загружать новую версию кода без остановки приложения.

И нет, никаких планок я не взял (пока). Если разработчик был столь любезен, что не поломал обратную совместимость в типах — мне удалось добиться рабочего релоада. Но если состояние процесса раньше было типа User, а теперь ExtendedUser — всё ломается, и я не могу придумать, как это починить. Скорее всего, это действительно либо просто невозможно, либо нецелесообразно.

Все возможно. Только начинать надо с фундаментальных классических трудов, а не с новомодных веяний.

Типичный хабракомментарий: напыщенный, глубокомысленный, пустой и безнадёжно бесполезный.

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

В общем, хабр во всей его красе: профессионализма — ноль, одни надутые щеки и общие слова.

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

Это вот тот самый «фундаментальный классический труд», разрекламированный выше? Понимаю, сам рыбак.

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

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

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

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

А теперь давайте рассмотрим тот же самый пример, но на завтипах. (Шутка, не нужно его рассматривать, там всё сложно.)

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

вы удивительно* четко сформулировали вопрос, на который я хотел вам ответить:

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

ответ на который тоже:

банальщина из пятого класса церковно-приходской школы.

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

вызывающий код НЕ должен понять, можно Енкодеру (моя терминология) передать среди прочих новый параметр, это НЕ его задача! вызывающий код НЕ должен передавать параметров которые сломают работу со старыми параметрами! Это же очевидно, если вы добавляете параметры в конце посылки, то клиент может их просто не читать, например! Соответственно, это накладывает определенное требование И НА всех и любого клиента, и это требование уровня проектирования системы и предусмотренной возможности расширения этой системы. Если не была заложена возможность расширения системы на этапе проектирования, то сделать уже что-то потом в этом направлении действительно сложно, хотя иногда выкрутиться получается, используя другие возможности системы специальным образом.

Требование уровня проектирования системы - это то, что клиент должен уметь игнорировать неизвестные ему параметры каким-то образом (каким есть варианты, вы их должны знать!), это же элементарно согласитесь! Клиент должен работать только с тем что знает, другими словами. Поэтому старый код просто НЕ должен рухнуть увидев новый параметр, а новый код может работать так же как старый без этих новых параметров, это тоже вполне очевидно если надо поддерживать и старый и новый одновременно.

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

*удивительно - что не зря вам плюс поставил, ожидая четкой формулировки этого, интересного мне вопроса!

Может вам будет интересно, после этой статьи: проверка на предрасположенность студента к поиску... меня вообще слили здесь на Хабре когда-то, не так давно. Можете проверить - следующая статья помечена как "Из песочницы", так что у вас еще не предел здесь. Я поэтому понимаю ваши эмоции и игнорирую их, но вы и думаете интересно!

Давайте-ка вернемся к истокам. Я утверждал:

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

И тут ворвались вы, в белом плаще и на вороном коне:

Все возможно. Только начинать надо с фундаментальных классических трудов, а не с новомодных веяний.

И тут, спустя некоторое количество комментариев, выясняется, что:

Соответственно, это накладывает определенное требование И НА всех и любого клиента, и это требование уровня проектирования системы и предусмотренной возможности расширения этой системы.

О как. Требование-то, оказывается, значительно жёстче, чем у меня (я всего-то просил тип в одном месте сохранить). Вы можете себе позволить рассказывать клиентам (пользователям вашего кода) — как им жить и по каким канонам писать код. Ну что ж, в таких условиях и студент напишет хотрелоад (а я даже написал, вместо демагогии про «правильные архитектуры»).

Может вам будет интересно, […] на Хабре

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

Это два перпендикулярных подхода к разработке. Раздел скорее по принципу императивный vs функциональный. Во втором случае правильная программа (о чем как раз статья) обладает свойством полноты, то есть корректно написанный код покрывает все множество корректных входных значений. В итоге всевозможные эджкейсы, для проверки которых, по мнению некоторых, и придумали ТДД, становятся не нужны.

Код программы, который компилируется и является доказательством ее корректности)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации