Skip to content

noncommunicado/cupid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 

Repository files navigation

CUPID

This is russian translation.

Original post: https://dannorth.net/cupid-for-joyful-coding
Author: https://mas.to/@tastapod

Автор перевода: https://t.me/noncommunicado
Подписывайтесь: https://t.me/Dev4Devs

Введение

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

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

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

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

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

Свойства CUPID

  • Компонуемость (Composable) : хорошо сочетается с другими
  • Философия Unix (Unix philosophy) : хорошо решает одну задачу
  • Предсказуемость (Predictable) : делает то, что вы ожидаете
  • Идиоматичность (Idiomatic) : кажется естественным
  • Доменность (Domain-based) : предметная область решения моделирует предметную область проблемы при помощи языка и структуры

Преамбула: когда-то, давным-давно…

У вас было такое, что читая незнакомый чужой код, вы можете его легко понять? Структура, наименование объектов, ход мыслей автора, всё выглядит очень знакомо. На вашем лице появляется улыбка. «Мне все понятно!» — думаете вы.

Мне повезло испытать это несколько раз за тридцатилетнюю карьеру, и каждый раз это приносило мне удовольствие. Первый раз это было в начале 1990-х — я хорошо это помню. Я изучал огромную кодовую базу на языке C, которая выполняла сложную обработку изображений для цифровой печати. В Someone Else’s Code™ была ошибка, и я должен был её отследить и исправить. Я помню это чувство, как начинающего программиста: смесь страха и боязни показать себя как непрофессионала, которым тогда и был.

Моя IDE (vi + ctags) позволила мне перейти к определениям функций из мест вызовов, и через несколько минут я зашел далеко по цепочке вызовов, будучи уверенным, что знаю, на что смотрю. Я быстро нашел виновника, которым была простая логическая ошибка, внес изменения, сделал сборку и протестировал. Все это было без автоматического тестирования, только с помощью Makefiles. TDD я узнал только, почти, через 10 лет, и в любом случае в C не было таких инструментов.

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

  • Нашел и исправил ошибку.
  • В то же время, не внес никаких неприятных сюрпризов.

Joyful Software

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

В своей основополагающей книге «Рефакторинг» Мартин Фаулер пишет:

“Каждый дурак может написать код, понятный компьютеру. Хороший программист пишет код, который могут понять другие люди.”

Refactoring, Martin Fowler with Kent Beck, 1996

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

Хотя «понятность» может быть благородным стремлением, это не такая уж высокая планка!Примерно в то же время, когда Мартин писал о рефакторинге, пионер вычислительной техники Ричард П. Габриэль описал идею кода, который можно сделать пригодным для жизни:

«Обитаемость — это характеристика исходного кода, которая позволяет [людям] понимать его конструкцию и намерения, а также изменять его комфортно и уверенно. «Обитаемость делает место пригодным для жизни, как дом».

— Habitability and Piecemeal Growth, Patterns of Software pp. 7-16, Richard P. Gabriel

Это больше похоже на то, к чему нужно стремиться. Как было бы здорово чувствовать себя комфортно и уверенно, изменяя чужой код? И если мы можем сделать код пригодным для жизни, что насчет удовольствия? Возможно ли, чтобы работа с кодовой базой приносила удовольствие?

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

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

Свойства превыше принципов

Когда я начал формулировать своё мнение про пять принципов SOLID, я представлял себе замену каждого из них чем-то, что я нахожу более полезным или актуальным. Вскоре я понял, что сама идея этих принципов была проблематичной. Принципы подобны правилам: вы либо соблюдаете их, либо нет. Это приводит к появлению узкого круга последователей, а не общества людей с общими ценностями.

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

Свойства свойств

Так как же нам выбрать такие свойства?Что делает свойство более или менее полезным?Я выбрал три свойства свойств CUPID:

  • практичность
  • человечность
  • многослойность

Практичность

Чтобы быть практичными, свойства должны быть:

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

Человечность

Чтобы быть человечными, свойства должны читаться с точки зрения людей, а не кода. CUPID — это про то, как чувствуется работа с кодом, а не абстрактное описание кода как такового. Например, хотя философия Unix «делать хорошо что-то одно» может звучать как принцип единой ответственности, первый касается того, как вы используете ПО, а второй — как написан код.

Многослойность

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

Компонуемость

Программой с удобным интерфейсом хочется пользоваться снова и снова. Существует несколько характеристик которые делают код более или менее компонуемым, но они не являются необходимыми или достаточными для предоставления каких-либо гарантий. В каждом случае мы можем найти контрпримеры с обеих сторон, подумаем об этом как о полезной эвристике. Больше - не обязательно лучше; должен быть компромисс.

Малая область применения

Вам потребуется меньше времени, чтобы изучить код с ограниченным API (ограниченным по области применения). В таком коде меньше вероятность чему-то пойти "не так", или возникнуть конфликту с другим кодом, который вы используете. Вернемся к убывающей отдаче: Если ваши API слишком ограничены в области применения, вам придется использовать несколько таких API вместе, и знание "правильных комбинаций" для типичных сценариев использования становится "особым знанием", а это препятствие для пользователя. Так, работа над гранулярностью API более трудна, чем кажется. Между фрагментированностью и раздутостью существует некая золотая середина «идеальной» связанности.

Самодокументируемость

Самодокументируемый код легко читать и править. Легко найти нужный компонент и определить требуемое место в коде. Мне нравится одна модель, как из давнего open-source проекта XStream, наличие 2-х минутного туториала, а еще и 10-ти минутного, и полной документации. Это дает мне возможность разбираться "небольшими порциями", понять, что это не для меня, на раннем этапе.

Часто, я начинал создавать класс придумывая самодокументируемое название, а IDE подсказывало мне, что такое название уже занято. Обычно оказывалось, что кому-то другому пришла в голову такая-же идея, и я случайно обнаружил их код, потому что мы выбрали одинаковые названия. Это было не просто совпадение, мы свободно владели одной предметной областью (доменом), что повышало вероятность того, что мы выберем похожие имена. Так обычно случается, когда вы пишите доменно-ориентированный код.

Минимум зависимостей

Чем меньше зависимостей в коде, тем меньше несовместимостей версий или билиотек. Я написал свой первый open-source проект, XJB, на Java, используя популярный фреймворк логирования log4j. Коллега подсказал, что это создает зависимость не только от log4j как библиотеки, но и от конкретной её версии. Мне это даже в голову не приходило: почему кто-то должен беспокоиться о совместимости библиотеки логирования в моём коде? Так что мы убрали эту зависимость и целый другой проект, который делал интересные вещи с Java dynamic proxy.

Философия Unix

Unix и я примерно одного возраста; мы оба начали в 1969 году, и Unix стал самой распространенной ОС на планете. В 1990-х годах у каждого серьезного производителя компьютерного оборудования был свой Unix, пока ключевые варианты с открытым исходным кодом, Linux и FreeBSD, не стали повсеместными. В наши дни он запускает почти все бизнес-серверы, как облачные, так и локальные; он работает во встроенных системах и сетевых устройствах; он лежит в основе операционных систем MacOS и Android; он даже поставляется как дополнительная подсистема с Microsoft Windows!

Простая, последовательная модель

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

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

Аналогично, команда cat выводит (объединяет) содержимое одного или нескольких файлов, grep выбирает текст, который соответствует заданному шаблону, sed заменяет текстовые шаблоны и т.д. В командной строке Unix есть мощная концепция «конвейеров», которые присоединяют вывод одной команды в качестве ввода к следующей, создавая конвейер выбора, преобразования, фильтрации, сортировки и т.д. Вы можете писать сложные программы обработки текста и данных, основанные на составлении нескольких хорошо продуманных команд, каждая из которых делает что-то одно, и делает это хорошо.

Единая цель против Единой ответственности

На первый взгляд это похоже на принцип единой ответственности (Single Responsibility Principle), и с некоторыми интерпретациями SRP есть пересечение. Но "делать хорошо что-то одно" — это взгляд снаружи; это свойство иметь конкретную, четко определенную и всеобъемлющую цель. SRP — это взгляд изнутри: он касается организации кода.

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

"Компонент UI" - это частый пример, где SRP требует, разделения бизнес логики и рендеринга компонентов. Разделение этих элементов - рутина по работе с идентичными полями моделей. Главная опасность - преждевременная оптимизация, которая может затруднить естественное разделение задач, возникающих по мере роста кодовой базы, и по мере появления компонентов "хорошо решающих одну задачу" и более подходящих под доменную модель приложения. По мере роста любой кодовой базы, приходит время для смысловой декомпозиции. Однако, свойство Компонуемости (возможность быть составным) и доменно-ориентированная структура - лучшие индикаторы как и когда вносить такие структурные изменения.

Предсказуемость поведения

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

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

Выполнение "как ожидается"

Первое из четырех правил простого дизайна Кента Бека заключается в том, что код «проходит все тесты».Это должно быть верно даже тогда, когда нет тестов! Предполагаемое поведение предсказуемого кода должно быть очевидно из его структуры и наименования.Если нет автоматизированных тестов, их создание должно быть простым. Майкл Фезерс называет это характеризационным тестированием. По его словам:

"Когда система публикуется, она, в некотором смысле, становится собственной спецификацией." - Майкл Фезерс

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

Детерменированность

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

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

  • Устойчивость - это ширина или полнота охватываемых ситуаций. Ограничения и крайние случаи должны быть очевидны.
  • Стабильность - это функционирование согласно ожиданиям. Мы должны каждый раз получать одни и те же результаты.
  • Стойкость - это то, насколько хорошо мы справляемся с ситуациями, которые мы не описываем; неожиданные изменения в среде выполнения или вводимых данных.

Наблюдаемость

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

Оснащение кода специальными средствами с самого начала позволяет собирать ценные данные, чтобы понять его поведение во время выполнения. Я предлагаю модель из четырех этапов — с двумя дополнительными! — которая выглядит следующим образом:

  1. Оснащение - программное обеспечение, собирает свои метрики.
  2. Телеметрия - делает эту информацию доступной, будь то путем опроса объекта — или принимая сообщения.
  3. Мониторинг - это получение, накопление оснащения и его отображение.
  4. Оповещение - это реакция на отслеживаемые данные или закономерности в данных.
  5. Прогнозирование - использование данных для предугадывания событий.
  6. Адаптация - это динамическое изменение системы, либо для предотвращения, либо для восстановления после инцидента.

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

Идиоматичность

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

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

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

В этом контексте ваша целевая аудитория:

  • знакомый с языком, его библиотеками, его набором инструментов и его экосистемой
  • опытный программист, который понимает разработку программного обеспечения
  • пытающийся выполнить работу!

Языковые идиомы

Код должен соответствовать идиомам языка программирования. В некоторых языках есть стандарты того, как должен выглядеть код, что позволяет легко оценить, насколько идиоматичен ваш код. Есть и менее строгие языки, возлагающие на вас ответственность «выбрать стиль» и затем придерживаться его. Go и Python — два примера категоричных языков.

Программисты на Python используют термин "pythonic" для описания идиоматичного кода. Есть пасхалка, которая появляется, если вы используете import this из Python REPL или запустите python -m this в терминале. Выведится список афоризмов “The Zen of Python”, который включает строку, передающую дух идиоматичного кода: «Должен быть один — и желательно только один — очевидный способ сделать это».

Язык Go поставляется с встроенным форматтером кода gofmt, который делает весь исходный код одинаковым. Это одним махом устраняет любые разногласия по поводу отступов, размещения фигурных скобок или других синтаксических особенностей. Это означает, что любые примеры кода, которые вы видите в библиотечных документах или руководствах, выглядят согласованными. У них даже есть документ под названием Effective Go, который демонстрирует идиоматичный Golang, выходящий за рамки определения языка.

На другом конце спектра находятся такие языки, как Scala, Ruby, JavaScript и почтенный Perl. Эти языки намеренно являются многопарадигменными; Perl придумал аббревиатуру TIMTOWTDI — «there is more than one way to do it» — произносится как «Тим Тоади». Вы можете писать функциональный, процедурный или объектно-ориентированный код в большинстве из них, что создает поверхностную кривую обучения, независимо от того, какой язык вы знаете.

Для чего-то столь простого, как обработка последовательности значений, большинство этих языков позволяют:

  • использовать итератор
  • использовать индексированный цикл for
  • использовать условный цикл while
  • использовать конвейер функций («map-reduce»)
  • написать функцию с хвостовой рекурсией

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

Идиомы встречаются на всех уровнях детализации: именование функций, типов, параметров, модулей; структура кода; структура модулей; выбор инструментов; выбор зависимостей; способ управления зависимостями и т.д.

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

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

Локальные идиомы

Если в языке нет консенсуса по поводу стиля или есть несколько альтернатив, вам и вашей команде решать, что такое «хорошо», и вводить ограничения и рекомендации для согласованности. Эти ограничения могут быть простыми, как общие правила форматирования кода в вашей IDE, инструменты «build cop», которые используют lint и сканируют код.

Записи архитектурных решений (Architecture Decision Records), или ADR, являются отличным способом документировать ваш выбор относительно стиля и идиом. Это не менее «значимые технические решения», чем любое другое архитектурное обсуждение.

Доменность (предметная область)

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

Язык предметной области

Языки программирования и их библиотеки полны конструкций из области "computer science": Hash Map, Linked List, Tree Set, Database Connection и т.д. У них есть базовые типы, включающие целые числа, строки, символы, булевы значения. Вы можете объявить чью-то фамилию как string[30], что вполне может быть способом её хранения, но определение типа с именем Surname будет более понятным. Этот тип может иметь операции, свойства или правила, связанные с фамилией. Многие ошибки в банковском программном обеспечении связаны с представлением денежных сумм в виде значений с плавающей точкой. Опытные программисты в финансах определяют тип Money как сочитание Currency (валюта) и Amount (количество), т.е. как составной тип.

Правильное именование типов и операций — это не только отлавливание или предотвращение ошибок, но и облегчение навигации в коде. Я сделал это своим вкладом в «97 вещей, которые должен знать каждый программист», как «Code in the Language of the Domain» (программирование на языке предметной области).

Одним из критериев успеха с доменно-ориентированным кодом является то, что случайный наблюдатель не может сказать, обсуждают ли люди код или предметную область. Я столкнулся с этим однажды в электронной торговой системе, где финансовый аналитик обсуждал сложную логику ценообразования торговли с двумя программистами. Я думал, что они обсуждают правила ценообразования, но они тыкали пальцем в монитор, и аналитик объяснял программистам алгоритм, который был записан в коде точно как в его формуле! Единственным когнитивным расстоянием между предметной областью и кодом были некоторые синтаксическая отличия!

Структура предметной области

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

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

Фреймворк Ruby on Rails популяризировал этот подход в начале 2000-х годов, встроив его в свой инструментарий, и широкое распространение Rails означало, что многие более поздние фреймворки скопировали эту идею. CUPID независим от языков и фреймворков, но Rails представляет собой полезный пример для понимания разницы между структурой на основе правил фреймворка и на основе предметной области.

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

app
├── assets
│   ├── config
│   ├── images
│   └── stylesheets
├── channels
│   └── application_cable
├── controllers
│   └── concerns
├── helpers
├── javascript
│   └── controllers
├── jobs
├── mailers
├── models
│   └── concerns
└── views
    └── layouts

Представьте приложение для управления больницей с разделом для записи пациентов. Этот макет предполагает, что нам понадобится как минимум:

  • модель, которая сопоставляется с базой данных (mapping)
  • представление, которое отображает запись пациента на экране
  • контроллер, который является посредником между представлениями и моделями

Затем есть место для helpers, assets и нескольких других концепций фреймворка, таких как model concerns или controller concerns, mailers, jobs, channels и, возможно, JavaScript controller, который будет работать с вашим Ruby controller. Каждый из этих артефактов живет в отдельном каталоге, хотя они семантически тесно интегрированы.

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

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

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

Границы предметной области

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

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

Подводя итоги

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

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

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

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