[Из песочницы] Ловушка CMS
В конец 2013 года Maxim Chernyak написал замечательную статью в которой подчеркивал исключительную важность поддержки архитектуры приложений настолько простой, насколько это возможно. Удивлен что на хабре до сих пор не было перевода, предлагаю ознакомиться с переводом данной статьи. Также прошу сообщать о всех возможных опечатках и неточностях перевода.ПреамбулаМного лет назад у нас было приложение Ruby on Rails. Все начиналось с объектов. Некоторые выступали прототипами для других объектов. Другие требовали множество связанных с ними частей, частей этих частей и т.д. Насколько много? Пожалуй, одним прототипам известно. Эти прототипы должны были иметь интерфейс для администрирования, но смена логики работы одного прототипа могла привести к цепной реакции в остальных частях. Любое изменение объектов и их прототипов пролегали через связанную сеть из различных моделей. Сложность интерфейса для администрирования быстро взлетела до небес. Дошло до того момента, когда прототипы должны были стать сериализуемыми, сохраняющими фрагменты своей логики. С этого момента каждая фича становилась предметом очень трудной реализации, и, в конечном счете, приложение скатилось к состоянию, когда модификация и доработка стали практически невозможны. Было такое чувство, будто CMS навязывала себя в качестве посредника между фичей и ее реализацией, подобно системам, ориентированным исключительно на бизнес-логику, с нагромождением высокоуровневых абстракций.Думаете это была худшая часть проекта? Это был еще только минимально жизнеспособный продукт в новом стартапе.
Незапланированная CMS Природа программирования призывает нас побаловать себя решением головоломок и моделированием абстрактных концепций. Это страсть, которая заставляет нас упускать из виду надвигающуюся опасность. Благодаря нашим смутным субъективным предположениям, мы уже находимся на пути к ловушке создания чрезмерно сложных систем. Ловушке CMS. Мы страдаем от последствий попадания в эту ловушку, таких как «выгорание», потерю энтузиазма, сорванные сроки, неудачи в бизнесе, но, кажется, никогда не будем говорить об этой ошибке напрямую. Где то около кулера, более опытный коллега замечает, что вы усложняете вещи. Где то в IRC вас высмеивают за вопрос о сложной системе объектов для проекта, который, вероятнее всего, так и не увидит света. Тем не менее, никто не может четко объяснить, что лежит в основе этого процесса. Эти замечания — вот и все что мы знаем об этой проблеме, и люди, в конечном счете, узнают об этом на своей шкуре. Вот почему я бы хотел пролить немного света на это явление. Для начала вот небольшой список причин, как, на мой взгляд, определить находитесь ли вы на пути к этой ловушке.Ловушка CMS это состояние веб-приложения в котором разработка CMS создает помехи для создания контента этой CMS.
Если вы, как и я, создаете стартап, вы должны знать, что эта ловушка особенно опасна на ранней стадии разработки. Только небольшой процент компаний играет в долгую игру, и со временем, их проблемы переходят уже в другую плоскость. Хотя эти компании могут также попасть в эту ловушку, для них, вероятнее всего, это будет уже не так критично, даже если они преследовали это направление намеренно. Здесь я бы хотел сосредоточиться в гораздо более разнообразном вопросе: именно на небольших компаниях. Проблема станет очевидной, как только вы открыли двери вашего проекта для клиентов. Они начинают использовать ваш продукт и предоставлять вам реальную аналитику и обратную связь. С этого момента проект больше не будет управляться вашим шестым чувством, вы должны будете опираться на реальные данные, свидетельствующие о том, как действовать дальше, какой функционал реализовывать в будущем. Это время, когда все ваши архитектурные решения проходят испытание. Реальность беспощадна, она не избавит вас от болезненной истины, когда происходит закат вашей многообещающей архитектуры приложения. Вы могли, вы хотели бы провести рефакторинг, но уже слишком поздно, так как вам уже нужно создавать новый функционал, а его реализация становится все сложнее и сложнее, вниз по спирали падающей продуктивности.
Я скажу себе потом «Спасибо» «Большинство наших предположений пережили свою бесполезность»— Marshall McLuhan
Проще говоря, мы любим проектирование системы. Как только мы формируем некоторое понимание проблемы, мы бросаемся к нашим /(?: whiteboards|moleskines|mindmaps|editors)/ и начинаем определять сущности и взаимодействия между ними. Это то, что мы делаем лучше всего. Мы беремся за некоторые из наиболее фундаментальных решений о проекте. Затем, тщательно изложив наши предположения, фиксируем изменения. Нам нравиться думать, что мы сеем мудрость и гибкость с наших ранних решений, и будем благодарить себя позже. Казалось бы, что может пойти не так со всеми этими точками расширения и хорошо представленными сущностями? Реальность в том, что, скорее всего, эти ранние решения скорее ограничат наши будущие наработки, нежели дадут нам простор. Грядет день, когда мы встретим нашего старого друга, наивного «себя в прошлом», смотрящего на нас из редактора, гордо улыбаясь. Этот благонамеренный человек провел часы, дни и недели отправляя все наши усилия в бездну предполагаемой архитектуры, едва ли имея представление о реальных проблемах, с которыми мы столкнемся после запуска. В настоящее время мы застряли во всем этом «полезном» коде. Это как если бы вы решили приготовить салат, но вместо отдельных ингредиентов, разложенных перед вами, все что бы вы имели — это другой салат, приготовленный незнакомцем, в котором вы сейчас вынуждены копаться, в надежде найти ингредиенты, которые вам нужны.
В программировании, вы в прошлом — не кто иной, как незнакомец с кучей багов.
Или в том же духе — представьте, что вы только что вернулись к компьютеру и обнаружили что ваше приложение реорганизовано каким-то невежественным незнакомцем таким образом, что имеет мало общего с реальным предназначением проекта. Это не очень отличается от того, когда мы находим части системы, смоделированные в прошлом. Разве вы хотите иметь дело со всем этим мусором, а не просто двигаться вперед, в соответствии с тем, как это диктуется потребностями бизнеса? Если применить все это к моей личной истории, то в конце концов я понял, что с каждой новой фичей я проводил все больше времени, выясняя как вписать ее в существующую структуру фреймворка, вместо того чтобы проектировать фичу. Как вы уже догадались, я очень себя благодарил за то, что был так внимателен.
C < RUD «Труднее читать код, чем его писать»— Joel Spolsky
Говорить об архитектуре, это как говорить о самом коде. Это не совсем то, что мы не сможем иметь ввиду в будущем, просто в этом вопросе, вполне вероятно, шансы не в нашу пользу. Код легко писать и трудно изменять или удалять. Каждая строка, которую мы с легким сердцем бросаем в дело, в конечном счете будет дразнить нас: «Угадай, что пойдет не так, если ты прикоснешься ко мне? (: trollface)». Архитектурные решения, так же как и код, легко создавать и очень трудно изменять. И если в коде эта проблема смягчается тестированием, то в архитектуре нас ничего не спасает. Единственная мера качества, которая у нас есть, это количество боли, которое мы чувствуем, когда работаем над новой функцией, но что-то менять в архитектуре к тому времени, зачастую, слишком поздно. Плохая архитектура может погубить весь бизнес, несмотря на то, что весь код будет покрыт тестами на 100%.
Сигнал тревоги Как и в большинстве ловушек, также и в нашем случае нет конкретного способа узнать, что вы на пути к ней. Лучшее, на что вы можете рассчитывать это когда окружающие вещи «предупредят» вас о предстоящей опасности. Ниже я перечислю некоторые из них, по своему опыту. Если вы замечаете эти признаки на раннем этапе разработки, они должны, по крайней мере, вызвать у вас подозрение.Ранний консерватизм «Государство, которое не в силах ничего изменить, не способно себя сохранить»— Edmund Burke
Допустим, вы столкнулись с новой фичей и реализуете ее, чтобы быть реальным yak shave. Вы понимаете, что реализация требует большого рефакторинга, и вы ищите способы его избежать. Существует тонкая грань между тем, для чего вы делаете это — вы делаете это из соображений эффективности или вы делаете это, потому что застряли в нагромождениях legacy-архитектуры. Может быть, ваши ранние предполагаемые проектные решения встают на пути нынешних реальных потребностей бизнеса? Возможно, вы создавали второпях слишком много, и теперь это только вопрос времени — когда вы попадетесь в ловушку, и ваш проект останется парализованным? Не поймите меня неправильно, зачастую, консерватизм является здоровой защитой от излишней сложности, это стандартная практика опытного разработчика. Проблема в том, когда консерватизма слишком много именно на ранних этапах разработки проекта. Это точно должно вызывать подозрение.
Синдром Drupal«а «Хмм, у нас различные правила ценообразования для различных продуктов, мы должны найти решение для администратора, чтобы он мог задавать эти правила в админке. Может быть, мы должны хранить код в базе данных и затем запускать его?»
Это классический признак того, что вы на пути к ловушке. Вы пытаетесь придумать способ, чтобы задавать через админку некую логику, которая затем должна быть сохранена в базе данных. Если это не касалось интерфейса админки, возможно это было сделано с помощью всего лишь нескольких строк кода. Тем не менее, сейчас мы говорим о создании моделей цен, ассоциированных с правилами и о сложности этого. Для расширения возможностей цен, которые ранее, возможно, были реализованы с помощью одной-двух строк кода, теперь придется создавать миграции, формы, валидацию и все остальное. Вам действительно нужен интерфейс для правил ценообразования в админке, в настоящий момент?
Seed«ы слабы Как бы вы реализовали 10 категорий для размещения в них ваших товаров? Типичный ответ предполагает создание модели Category, а затем написание сценария (seed), который развернет 10 предписанных категорий, которые будут ассоциированы с продуктами. Затем вам необходимо будет убедиться, что каждый разработчик развернул этот сценарий. Конечно, кроме того, не забывайте о запуске его в продакшене. При каждом деплое. При каждом обновлении. И при настройке новой машины. И при выполнении тестов. И, естественно, если вы внесли изменения в этот самый сценарий.Если на ранней стадии ваше приложение полагается на множество таких сценариев, вы уже на скользком пути. Вещи, которые на данный момент можно описать константой, не должны быть смоделированы как сущности баз данных, но я вернусь к этому позже.Все дороги ведут в Мордор «Один — не просто реализация бизнес-логики»— Boromir
Этот пункт несколько похож на ранний консерватизм, но разница все же есть. Бывали ли вы когда ни будь напуганы тривиальной задачей? Спросите себя: будет ли эта задача настолько же пугающей, если будет реализована как отдельный компонент, вне проекта? Если ответ да, смотрите под ноги, потому что вы можете попасть в ловушку. Реализация функционала в хорошо спроектированных системах не должна быть более трудной, чем ее реализация в изоляции — как отдельного компонента.
«Фантомные боли» Иногда ловушку CMS можно узнать по присутствии «фантомных болей», которые вытекают из скрытых последствий развивающегося CMS. Например, на самом деле, вам никогда и не нужно было удалять реализованные вами категории, но потому что вы реализовали их как редактируемые администратором записи в базе данных, вы вдруг думаете, что будет, если эти категории удалят из базы данных? Ваша архитектура взяла на себя смелость заставить думать вас о сценарии, работа которого на самом деле не нужна и не реальна. В конечном счете это и называется «фантомными болями».Предотвращение Во всех перечисленных выше симптомах есть что то общее. Они все являются результатом ранних предположений, которые приводят к сложности системы. В этом случае полезно ответить на вопрос «Что такое сложная система?» и «Как писать программы, не делая ошибочных ранних предположений?».В контексте данной статьи, скажем, что сложная система представляет собой сеть узлов, которая состоит из большего количества узлов, чем вы обычно можете держать в своей голове. Очевидно, чтобы получить систему с низким уровнем сложности, нужно сократить количество узлов и соединений. Что касается последнего вопроса, то это как раз то, что привело меня к его решению. Чтобы писать программы без подобных проблем вы должны в первую очередь избегать динамической генерации данных во время runtime’а.Позвольте мне объяснить. Будучи напуганным сложностью системы, я обнаружил, что существует принцип, который должен стать основополагающим, во всех принятых решениях. Давайте назовем этот принцип keep it static, stupid (делай это статичным), что представляется целесообразным, потому что на самом деле это не более чем архитектурно-известный риф, на пути к созданию простых вещей.Создание неизменных вещей — архитектурный эквивалент избегания преждевременной оптимизации.
Красотой этого принципа является то, что он применим на каждом уровне абстракции, независимо от того о чем мы говорим — представления, код или базы данных. Сама по себе идея проста — если есть сомнения — сделайте это статичным. Это легче понять на конкретном примере, на различных уровнях типичного Rails-приложения.
Это может быть решено с помощью класса? Ранее в этой статье я упоминал правила ценообразования. Это общая проблема, где каждый продукт может вычисляться по разным правилам ценообразования. Цена может зависеть от количества, текущего пользователя (программы лояльности), истории заказа, купонов и многих других вещей. Чтобы избежать попадания в ловушку CMS я предостерегаю вас избегать построения этих видов на ранней стадии во время выполнения. Напишите класс схемы ценообразования. Используйте шаблон Strategy. Сделайте ваш алгоритм заменяемым на уровне кода. Определите правила ценообразования на вашем языке программирования. Таким образом, сложность этой логики будет отображена непосредственно в коде, и не потянет целый слой абстракции.Языки программирования уже имеют такие замечательные инструменты как условия и циклы. Зачем переопределять их на более высоком уровне абстракции? Этих инструментов более чем достаточно, чтобы построить сложную ценовую логику напрямую, путем написания кода. Если у вас есть несколько алгоритмов ценообразования записанных в виде подключаемых объектов, не стесняйтесь — дайте администратору выбрать один из них, может быть даже «заполнить пробелы», добавляя ключевые особенности в ваш алгоритм, но развивать эту функциональность постепенно, по мере необходимости. Создать свой интерфейс администрирования со временем, добавлять все больше и больше гибкости во время выполнения в ваши объекты-стратегии. Помните, вы всегда можете сделать статичные / жестко закодированные вещи динамичными, но наоборот — далеко не всегда. Все, что регулируется в момент выполнения, вводит сложность в каждом решении, с этого момента, и увеличивает шансы на ошибки, которые вы не можете предвидеть, даже в, казалось бы, не связанных между собой частях вашего приложения.
Это может быть решено с помощью статичной страницы? Допустим, вы перечисляете на некоторой странице объекты, которые должен видеть клиент. Этими объектами могут быть продукты, фотографии, файлы, либо что-то еще, что вы делаете. Теперь вы, наверное, решили, что на каждом элементе должно быть название, описание, фото, возможно автор или бренд. Вы разделили свои сущности на поля с данными, и решили создать модели, поддерживаемые базой данных. Это как раз тот момент, где я хотел бы предложить остановиться и подумать, есть ли у вас достаточный повод, почему это должна быть не статичная страница. Статическое шаблонное представление означает, что для того чтобы изменить положение вещей, вы должны изменить представление и развернуть его, да, но это также означает что вам не придется писать контроллеры, миграции, формы, интерфейс для администрирования и т.д. На самом деле, отчасти, у вас уже есть интерфейс для администрирования, если вы используете GitHub. Это не редактирование в режиме реального времени, но лучше чем ничего. Пользователи спокойно могут редактировать представления через форму на GitHub«e.Это становится еще более важной проблемой, если вас просят перечислить категории на основе определенных правил. При динамическом подходе, это сразу заставит вас создать сеть связанных моделей, просто для того чтобы отрендерить шаблон. Заметим, как мало вы знаете в этот момент о будущих потребностях бизнеса, и как вы будете их предполагать. Заметим так же, как быстро и легко просто сесть и захардкодить эту страницу. Так же как и в случае с паттерном «стратегия» — мы всегда сможем придать этой странице динамическое содержание в будущем, когда на то возникнут реальные потребности. Если вы строите динамическую систему прямо сейчас, скорее всего вы ей и ограничитесь.
Это может быть решено с помощью константы? Возвращаясь к вопросу о сценарии, этот вопрос довольно прост. Вы создаете категории. Эти категории предопределены. Вместо добавления модели, таблицы и сценария для деплоя, почему бы просто не сделать эти категории константой с массивом? Код позволяет хранить статические данные без участия базы данных. Используйте это, и ждите того момента, когда вам действительно будет необходимо редактирование категорий во время выполнения. Когда это наступит, вы всегда сможете извлекать категории из базы данных. Если у вас есть категории, которые никогда не меняются и некоторые, которые должны изменяться в админке, вы можете не трогать предыдущие категории. Вы можете оставить их в константе, читать их оттуда, и таким образом избегать seed«ов. На самом деле это мой маленький секрет. Я не люблю данных таких сценариев. Сейчас наше приложение работает «из коробки» на машине любого нового разработчика. Если вы загрузите наш код на свой dev-компьютер, приложение просто запустится и будет работать. Вот почему я всегда говорю: если не знаем — хардкодим.Это может быть решено с помощью строки в базе данных? Допустим, на данный момент приложение работает очень хорошо и у вас есть все необходимые модели для базы данных. Вам нужно отобразить в свободной форме текст, который может отличаться от одной сущности к другой (например, разный текст для каждого продукта), а также может содержать различные интерполяции из различных источников. В соответствии с принципом вы не должны думать о моделировании этого текста через классы. Во-первых, спросите себя — можно ли просто позволить администратору задавать текст для каждого продукта. Но подождите, сказали бы вы. Если значение получено из других источников, почему администратор должен заполнять их вручную? Должен ли он искать и вводить их каждый раз? Кажется, мы что то упустили. Что-ж, расслабимся и рассмотрим включение возможности добавлять сниппеты. Возможно, стоит добавить просто текстовое поле свободной формы, которое предоставляет некоторый заранее заполненный текст для администратора. Когда вы думаете что вам нужно структурировать данные для сохранения чего то очень «гибкого», то лучше используйте обычную строку с canned snippets.Подводя итог «У каждой сложной проблемы есть простое, всем понятное неправильное решение»— H.L. Mencken
В то время как текст выше представляет из себя хороший базовый принцип, он не может являться решением всех проблем, которые возникают с CMS-подобными решениями. Когда вам поручили разработку очень гибкой CMS, естественно, вы стараетесь делать нечто подобное. Когда вас просят разработать приложение с чем-то вроде Drupal, вы находитесь в совершенно иной области, где ловушка CMS может угрожать постоянно. Тем не менее, даже в этих случаях будут возникать вопросы, следует ли делать что-то более или менее динамичным, и я призываю каждого разработчика склоняться к менее динамичному варианту. Вы будете делать услугу не только для себя же самого в будущем, но и для следующего разработчика, который гораздо продуктивней нарежет кусок статичного HTML и добавит некий динамический контент, чем будет пытаться понять дымящуюся груду предполагаемой архитектуры, с большим количеством «движущихся» частей.Важно также отметить, что я не осуждаю расширение архитектуры. Это хорошо, до определенной степени, это линия которую мы рисуем на песке от случая к случаю. Я призываю вас подумать о том, где провести эту линию каждый раз при реализации чего-то.
Возвращаясь к моей истории, она закончилась годичным застоем и последующими очень неохотными обновлениями всего приложения. В конце концов, вышеупомянутые «прототипы» были понижены до неизменяемых классов и с течением времени стали носить декларативный характер, благодаря естественно развивающейся внутренней DSL. Смотря на эти файлы сегодня, тяжело представить как бы я реализовывал интерфейс для администрирования со всеми этими «движущимися» частями во время выполнения. Не смотря на то что мы потратили год, я до сих пор рад, что мы стиснули зубы и провели рефакторинг. Было больно, но теперь эта ошибка далеко позади.
Если вы не знаете точно, что вы делаете (а обратное маловероятно), предпочитайте неизменяемость. Попробуйте приложить дополнительные усилия, для определения того, какие части вашей бизнес логики можно оставить в коде. Если сомневаетесь — оставляйте в коде. Делая это, убедитесь, что вы следовали советам и рекомендациям: никогда не повторяйте постоянные данные, используйте композицию, внедрение зависимостей, наследование — все, чтобы убедиться, что вы соблюдаете единый принцип ответственности и поддерживаете единство управления.И самое главное, не запутайтесь в слишком долгих рассуждениях — пусть история разворачивается естественным путем.
Оригинал статьи: hakunin.com/cms-trap