Метафора системы: как повысить качество кода

Всем привет!

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

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

Определение метафоры системы

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

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

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

Википедия

Итак, метафора системы. С одной стороны, определение очень общее и не совсем понятно, как им пользоваться, с другой — широко распространенной информации по данному подходу, в отличие от других практик, например TDD, Pair Programming или CodeReview, не очень много, и постигать саму концепцию приходится скорее на практике.

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

К практике

К сожалению, без изначальных вводных подойти к теме метафоры системы довольно сложно, поэтому давайте перейдем к действиям над живым кодом. Примером послужит тот же проект, что и в статье по объему кода, который мы в компании Terrasoft разрабатываем для облегчения работы с внутренними задачами. Сразу определимся, что в данной ситуации нам интересен именно ход изменений и логика их появления, а не конкретное состояние кода в отдельно взятый момент времени. Всем, кому будет интересно обсудить технические аспекты реализации именно проекта, я с радостью отвечу на GitHub-e или по личным каналам, указанным в LinkedIn.

Первое знакомство или структура проекта

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

Я думаю, что многие из нас (а может, вообще все) при работе с такой структурой испытывают желание преобразить ее. Давайте разберемся, что тут не так. Первое, что бросается в глаза, — это плоский список, содержащий в себе названия, не объединенные общей логикой.

Класс Program лежит на уровне с классом PackageConverter и тем самым говорит, что в нашем проекте эти два понятия не разделены на организационном уровне. Т.е. первым признаком отсутствия метафоры системы является структура файлов, следующий признак уже находится на уровне кода, и замечание касается организации namespace-ов, которые используются в рамках проекта. Вот наглядный пример кода, который иллюстрирует взаимосвязь названия файла и его внутренней структуры:

В классе Program добавлено 18 namespace-ов. Вспомним распространенное правило: много зависимостей в классе — это плохо, и это говорит о наличии сильной связности в рамках данной реализации. При этом, если мы посмотрим на количество строк в коде класса, эта цифра близится к 1000, и это уже само по себе становится проблемой даже просто при навигации по нему. Давайте вернемся к вопросу введения метафоры и посмотрим, как отражается ее присутствие на коде.

Тот же проект, те же люди и класс с менее общим названием. Вместо 18 using-ов всего 4, количество строк меньше почти в 5 раз. Вы можете самостоятельно убедиться, что остальные классы проекта будут в разы меньше и проще, чем класс Program. Как же получается, что разработчики в одном и том же проекте пишут код, который так ощутимо отличается по качеству? Все дело в том, что в рамках проектирования отдельно взятой функциональности каждый применяет свои умения без ограничений, а при сведении результатов работы изначально отсутствует договоренность и общие принципы того, как это делать. Если кто-нибудь из вас встречал понятия архитектурных максим проекта — это как раз одно из их проявлений.

В ходе развития проекта и расширения команды возникла необходимость такую договоренность ввести, и давайте посмотрим на то, как доменную область можно построить уже в готовом коде. Суть нашей работы в этом проекте заключается в создании CLI Tool для решения задач CI/CD в рамках процесса разработки. Я думаю, что при изучении различных инструментов вы не раз пользовались такими инструментами, которые упрощают технологические задачи, например, в angular или react приложениях.

Предметная область

Итак, давайте посмотрим, какие понятия из нашей предметной области отсутствуют на данный момент в структуре проекта, и постараемся их добавить. Первое, что приходит мне в голову, — это отсутствие понятия Command (а ведь CLI = Command Line Interface :) ). В проекте присутствует класс CommandLineOptions — в нем сосредоточено описание всех параметров для всех команд. В результате чего любому разработчику ничего не остается, кроме как дописывать сами команды в класс, который этого не запрещает напрямую. Новые и новые строки кода добавляются в файл, в результате чего получается своеобразное ассорти, но при этом стоит отметить, что даже такое название класса однозначно определяет его назначение, и вы не встретите в проекте описание параметров команд в другом месте.

Обратите внимание, что объем этого класса не очень большой с одной стороны, а с другой — количество using-ов в нём очень умеренное. То, что внешние показатели класса не кажутся очень пугающими, обусловлено тем, что у него довольно конкретная область ответственности, заложенная в его названии. Это значит, что все разработчики одинаково интерпретируют и используют его в своей работе. Однако давайте построим связи дальше и рассмотрим, как же этот класс связан с упомянутым выше классом Program, который содержит почти 1000 строк. Если мы для описания команд имели хоть мало-мальски выделенную структурную единицу, то для реализации самой команды такой структурной единицы нет. Всю реализацию команд мы найдем в классе Program.

Проблема файлов, которые агрегируют различные классы, сродни проблеме множественной ответственности. Зачастую такие классы визуально от вас скрывают проблему структурной организации кода (стоит отметить, что для вскрытия или обнаружения такой проблемы можно использовать инструменты, встроенные в IDE, которые отобразят диаграмму классов проекта) Данная проблема очень часто встречается в проектах, где есть классы или файлы, названные с помощью общих слов, которые не уточняют их предназначение и не помогают разработчикам идентифицировать ответственность класса в изначальном плане. Например, это Tools, Utilities, Extensions, Helper и им подобные. Часто, рождаясь в проекте как временные, они могут обрастать функционалом, поскольку уже подключены в места, откуда этот функционал можно вызвать — или, что еще хуже, приводят к ситуации, когда возможности использовать полезные функции в этих классах без сложной инициализации зависимостей просто нет. Однако не стоит сразу отчаиваться, если в своем проекте вы столкнулись с данной проблемой. У таких файлов есть одна интересная особенность, о которой нужно помнить и стараться использовать при изменении структуры кода: они легко разделяются при введении метафоры всей системы или хотя бы в отдельно взятой её области. Этот эффект вызван как раз тем, что код внутри такого файла часто бывает очень слабо связан между собой ввиду того, что относится к разным задачам. В тех же местах, где использование такого кода есть, это решается с помощью приёма в рефакторинге выделения нового класса.

Глаза боятся, а руки делают

Теперь давайте попробуем ввести понятие Command в существующий код, для начала только на уровне структуры файлов и директорий. И сразу перенесем туда файл CommandLineOptions.

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

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

Стоимость и временные затраты на такое действие — ничтожно малы. Создаем класс AppListCommand и переносим из класса Program реализацию метода ShowAppList.

AppListComand

Program

Давайте рассмотрим, что произошло со структурой кода после этих операций, на которые мы потратили всего пару минут:

  • класс Program стал на 10 строк меньше;
  • класс настроек CommandLineOptions стал меньше на 4 строки;
  • функционал отображения списка настроек теперь можно использовать из других мест.

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

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

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


До
После

Вывод

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

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

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

Похожие статьи:
17 вересня Верховна Рада підтримала у першому читанні оновлену версію законопроєкту про підвищення податків. Йдеться про законопроєкт...
В статье я расскажу о том, как использовать такой инструмент, как Jira, для управления бэклогом при разработке программного обеспечения....
ТЦК реформують, у жовтні в «Резерв+» має запрацювати рекрутинг, а згодом і онлайн-відстрочки. На тлі цього DOU поспілкувався...
В ІТ-компанії Sii Ukraine (є частиною великого світового аутсорсера Sii Group) не відмовляються від своїх амбітних планів...
Нещодавно керівник Мінцифри Михайло Федоров повідомив, що Україна лідирує за кількістю розробників штучного...
Яндекс.Метрика