- «Нам важен готовый продукт — плевать на плохой код»
- «Нам важен поддерживаемый продукт — пусть это и будет долго»
Это вечный спор между «говнокодом» и «скоростью разработки», менеджерами и разработчиками. Ошибаются обе стороны. Это два конца одной палки. На этой палке я бы написал красными буквами «ошибки руководителя разработки». Кому интересная эта тема — добро пожаловать под кат. А еще под катом много букв об архитектуре ПО, микросервисах и здравом смысле.
Вообще эта статья началась с обсуждения Agile Manifesto в facebook… Тема обсуждения была примерно следующей — «готовый продукт важнее чем люди и их взаимодействие». В целом я понимаю боль автора и боль всех участвовавших в комментариях. Еще я понимаю что словом «Продукт» часто называют то, что продуктом не является. И это порождает последствия и проблемы в реальных программных решениях.
Продукт (в нашем случае программный продукт) — это бренд и маркетинг, продажи, постподажная поддержка, реклама и предпродажная работа, и конечно наш код. Процесс, называемый словом «бизнес» — обеспечивает сращивание этих частей воедино, и это сращивание приносит деньги.
Код (наше программное обеспечение, ПО) — это часть продукта. Как и другие части она должна легко скрещиваться с остальными частями и подстраиваться под них. Под понятием «легко» — я подразумеваю следующий набор: «быстро», «не титаническим трудом», «без внезапных последствий».
Многие ошибаются и говорят что качество кода здесь решающий момент. Обычно все сводиться к тому, что «говнокод можно быстро написать и запустить», а «хороший код не имеет внезапных последствий». Нет. Решающий момент — это возможность это ПО менять. Ведь процесс скрещивания ПО с остальными частями бизнеса — это и есть его изменение.
Когда маркетологи просят сохранять utm-метки зареганного юзера, когда нужно отправлять емейлы определенной верстки, когда нужно добавить в ваш интернет-магазин фичу «а с этим покупают еще» — это и есть процесс интеграции ПО с остальными частями бизнеса. Именно этот процесс должен происходить быстро, просто, без внезапных багов. И не нужно изобретать велосипед — все уже придумано до нас.
Модульность
Если Вы не приверженец SOA/микросервисов и пишите монолит — сделайте его модульным. Зачем это нужно? Вот набор основных пунктов:
- 1 фича — 1 модуль. Можно легко и быстро написать с нуля «из грязи и веток» — то, что требуется для бизнеса сейчас. Когда возникнет нужда это поддерживать — можно легко изменить код модуля для большей читаемости, сохранив интерфейс.
- Компоновка. Хороший модуль — не вещь в себе. Его можно переиспользовать и компоновать. Вспоминаем bash и pipe ;-) «cat /etc/passwd | grep root | tr ':' ' ' | awk '{print $2}'» — вот он пример модульности и компоновки. Unix-way называется.
- Легко выкинуть/выключить. Если маркетинг ошибся (а в среднем 70% действий маркетинга — это пробы, ошибки, снова пробы) — можно легко выкинуть лишний кусок.
Как это поможет быстро и надежно делать фичи? Ну во первых писать с нуля маленький модуль — это и правда быстро. Во вторых — имея возможность компоновки — через полгода-год большую часть писать вообще не придется — просто скомпоновать и чуть-чуть дописать (новый маленький модуль). Про отсутствие титанического труда есть единственное условие — опишите архитектуру модуля и их взаимодействия (компоновки) в документации и строго ей следуйте, не усложняйте ее сразу и не изменяйте потом. Возьмите за основу известный всем фреймворк. Если этого придерживаться и не выходить за рамки продуманной арихектуры — титанический труд не потребуется. Вы же не пересобираете grep каждый раз когда что то делаете в консоли.
Про баги и предсказуемость — модуль живущий в рамках себя и имеющий простой неизменный интерфейс очень легко тестировать. Я не говорю о написании unit-тестов прямо сразу. В первый момент это может быть ручное тестирование. Прибегать к автоматике следует только когда есть нужда запускать ее каждый раз — т.е. модуль активно меняется внутри.
SOA/микросервисы
Эта та же самая модульность и правила те же, только более гибкая. Разница в том, что модули теперь называются сервисы и взаимодействуют не вызовом публичных методов а по сети передачи данных. Правила те же, но с добавлением нового — оно делает жизнь бизнеса еще легче. Вот полный список:
- 1 фича — 1 сервис.
- Компоновка.
- Легко выкинуть/выключить.
- Стандартные протоколы взаимодействия. Это огромный плюс SOA — ведь по стандартному протоколу вы можете легко добавить в ваше решение сторонее ПО. Образно — использование Radius, OAuth2 — позволяет легко авторизовывать своих пользователей в стороннем продукте. Использование стандартны форматов данных (json, xml) интегрироваться с различными ентерпраз решениями, например с каким то готовым движком интернет магазина. Чтобы это было не тяжким трудом — читаем выше (1 сервис — 1 фича, компоновка, следовать единой схеме)
Разработать и продумать SOA архитектуру, выбрать протоколы и форматы сложнее чем разработать модульный монолит, но большой плюс SOA — это то, что все велосипеды придуманы до нас — нужно только выбрать.
Какое это имеет отношение к слову «Продукт»?
На самом деле одно единственное — простая и гибкая архитектура помогает строить продукт и делает из IT надежную часть бизнеса, а не «слабое звено компании».
Иметь возможность быстро выпускать изменения с предсказуемым поведением, без постоянного прироста затрат — основное требование к команде разработки. Конечно есть и другие требования, они свои в каждом бизнесе, но они относятся более к деталям работы, а не к сути.
«Кому нужна архитектурочка»?
Ответ так же прост как и вопрос — не надо путать понятие архитектуры и «красивого кода». Красивый код нужен разработчикам, чтобы его было легко править, можно было им мериться с коллегами. Простая и гибкая архитектура — заказчику, чтобы его требования к разработке выполнялись.
Архитектура — это как набор спичечных коробков. В них можно спички положить, можно траву. Можно наколеночный код, можно красивый. Она ни к чему не обязывает. Если вы такой спичечный коробок сделаете, что ничего кроме спички в него не засунуть, то конечно этот коробок — фигня полная.
То, как же нам «передавать данные в шаблоны из контроллеров» — в виде массивов или объектов-коллекций — в большинстве случаев не имеет вообще никакого отношения к архитектуре ПО.
Так же не нужно относить к архитектуре выбор языка на котором вы пишите — на любом полноценном языке программирования можно создать хорошую архитектуру, а можно и плохую.
Как создавать хорошую архитектуру?
Работа по продумыванию взаимодействия и компоновки модулей/сервисов — по большей части основана на выборе. Чтобы выбирать — нужно знать стек технологий с которыми вы работаете. Выбирать наиболее простые.
Знать стек технологий — это не значит «я знаю кунг-фу, карате и другие крутые слова». Знать стек нужно с начала, а не с конца — многие вещи родились 30 лет назад и работают отлично, выполняя свои функции до сих пор. Конечно всегда хочется взять что-то новое и попробовать, поучиться — но «пробовать и учиться» — это для Junior/Middle разработчика, не для архитектора.
Знать стек технологий нужно детально и изнутри. Всегда задайте себе вопрос — «почему я выбрал это?». Если вы ответили на этот вопрос «я с этим знаком больше» — значит у вас недостаточно опыта для трезвого выбора решения, нужно изучать технологии.
Если вы ответили «кроме этого решения — альтернатив нет» — есть два варианта:
- Вы не знаете о существовании альтернатив — ищите их! Ну уж за 40 лет IT решений изобрели огромную массу на самые разные случаи.
- Вы делаете что-то странное. Если до вас так никто не делал — наверное были причины не делать именно так. Не думайте, что у вас уникальный проект и прочее — если вы не строите Токамак — то он не уникальный в общем смысле. Просто с ним что-то не так.
Что делать если вам достался уже готовый проект с пятнадцатилетним legacy? В нем нет модульности, нет сервисов, а те что есть — далеко не микро. Идите по стратегии разбиения монолита. От большого старого монолита удобно идти к микросервисам. Выносите атомарные части во вне. Начните с вынесения технически сложных вещей — это сразу минимизирует баги и даст время на позитивную разработку.
Микросервис — это как функциональное программирование. Только не функция, а сервис. Он прост, атомарен. Когда в микросервисе есть только одна функция — например предоставлять OAuth — он только это и должен делать, БД должна быть вне него. Или сервис для ресайза картинок — только ресайзит их. Не нужно в нем же их хранить.
Итог
Итог наверное в том, что холивар «красивый код»/«быстрое решение» в целом ни о чем. Базируется он обычно на нерасширяемой архитектуре.
Архитектура — не самое важное в ПО. Ее не нужно развивать и разрабатывать — это работа на 3-5 дней. Сесть, подумать, описать. И еще на полгода-год для руководителя — научить всех ее придерживаться.
Архитектура — не серебряная пуля. Она не делает ни фич, но может сделать баги. Грамотная архитектура — позволяет делать фичи быстро на коленке рядом с остальным красивым кодом и не портить общую картину. Компоновать, переиспользовать. Неграмотная — приводит к внезапным проблемам и долгой разработке. Ее отсутствие — обычно к тому же что и неграмотная.
О чем статья? По сути ни о чем. Она для тех кто продолжает спорить — «красивый код» vs «быстрое решение».
Комментарии (19)
primepix
14.03.2016 19:10Имхо ваша дилемма решается вводом метрик (kpi). Красивый код пишется ведь не из-за эстетической причины (если мы говорим про бизнес), а из-за того, что приложение будет дешевле в поддержке. При внедренной методологии разработки, сборе метрик и некотором опыте вы сможете прикинуть стоимость решения используя разные подходы. Собственно это и даст руководящему составу обоснование выбора того или иного пути. А что думают конечные разработчики, если честно не важно, главное, чтоб им работалось комфортно, закрывались необходимые потребности, в общем. чтоб были удовлетворены и счастливы :)
Ostrovski
14.03.2016 20:38+1Так вроде статья же о том, что красивый код не имеет ничего общего с ускорением решения, если под капотом хорошая архитектура. Т.е. можно
наговнокодитьзапилить с нуля прототип модуля X, он удобно встроится в существующем окружении и будет себе работать, пока нужен. А что внутри, пофиг. Главное — что он был встроен быстро. А еще главное — что также быстро может быть выпилен из системы. А красивый код нужен скорее в монолите — когда чем лучше код, тем меньше жди от него неприятностей, т.к. все со всем связано.
P.S. Я тоже люблю красивый код.primepix
14.03.2016 21:22я всегда думал, что красивый код это и есть хорошая архитектура, как в глобальных так и в микро-масштабах
Ostrovski
14.03.2016 22:22+1Имхо, архитектура — это про декомпозицию и взаимодействие. Не важно на каком уровне. И на уровне одного модуля/сервиса/приложения — это "хороший код" (паттерны там всякие). А когда речь идет о комплексном многоуровневом проекте — архитектура уже далека от кода, она оперирует подсистемами (их интерфейсами и порядком взаимодействия). И в случае, если последняя хороша, то чистотой кода в ее компонентах иногда можно пренебречь в угоду скорости разработки.
piromanlynx
14.03.2016 23:15+1Суть архитектуры — разделять код. модуль от модуля, сервис от сервиса. Хороший код, от времянок и
говнокода. Тогда архитектура помогает разрабатывать.
По поводу масштабов — вопрос размера решения. Если оно маленькое — архитектура работает на уровне модулей. Если большое — на уровне сервисов.
Т.е. не нужно делать ее многоуровневой. Какие могут быть модули в сервисе? никаких — он одну фичу делает. одну функцию. Как может быть архитектура внутри модуля в монолите? тоже никакой.
Вложенные модули в модули и прочее — адское зло и в целом противоречит идеям композии, переиспользования. А в целом — еще и порождает массовые зависимости между модулями. Чего в хорошей архитектуре быть вообще не должно.piromanlynx
14.03.2016 23:18+1Ну а что там внутри модуля — это дело конкретной реализации конкретной задачи. Хоть там белые мыши на трубе играют. Не важно в целом. На это кстати целый термин в ООП придумали. Инкапсуляция.
А композиция в терминах архитекутуры — это несколько расширенное понятие полиморфизма.
piromanlynx
14.03.2016 23:21+1Не только в монолите — он нужен там где его реально будут менять. т.е. пусть у вас модуль или сервис который постоянно переписывается — он должен быть "красив", прост и понятен. Верстка та же, стили и прочее — оно постоянно меняется. там должен быть порядок.
А вот из каких тряпок и грязи собрали модуль отправки писем — не важно, главное что отправлял в соответствии с ТЗ. Если он верно написан — шаблонов писем в нем не будет. И менять там впринципе нечего — почта она вон 40 лет не меняется.Mendel
15.03.2016 00:25-1Чтобы в него по 10 лет не залазить он изначально должен быть написан добротно.
Да, если потом поменялись стили, подходы и определеный АПИ, то можно только обертку поменять не заглядывая во внутрянку, пока пхп7 не отменит конструкторы пхп4стайл. Но изначально он должен быть красивым.
Иначе у нас и иньекций будет, и первое же расширение протокола всё сломает что не успело сломаться после того как добавили четыре атача одновременно и т.п.piromanlynx
15.03.2016 11:23Чтобы в него по 10 лет не залазить он изначально должен быть написан добротно.
Без явных багов и "красивый код" — понятия очень разные и обычно не пересекающиеся
только обертку поменять не заглядывая во внутрянку
Я там на 3 абзаца расписал, что обертку менять вообще не нужно. Плохая архитектура — если ее нужно постоянно менять…
пока пхп7 не отменит конструкторы пхп4стайл
Ну живет отдельный сервис у вас нас php4. Он же жить вам не мешает — пусть живет и дальше. Нужно будет в нем правки делать — и php обновим и говнокод перепишем. Работает — не трож. Тем более можно и не трогать. Трогают обычно от желания поучиться вместо того чтобы поработать.
расширение протокола всё сломает
Ну вот я привел пример почты — протокол 40 лет не меняется. Нормальные люди его сделали. Грамотные в архитектуре. SMTP. Потом появился ESMTP — но он не противоречит старому и целиком и полностью его поддерживает.
Это больше к вопросу выбора решений — не нужно выбирать сырое, что еще много раз поменяется.
Askofen
16.03.2016 00:27Вся проблема заключается в том, что даже в простых приложениях часто невозможно представить одну фичу одним модулем. Предположим, речь идет о такой простой функции, как добавление нового Клиента в какое-нибудь бизнес-приложение. Такая фича потребует создание новой формы, изменений в слое доступа к данным и в базе данных. Возможно, потребуются небольшие изменения и в слое бизнес-логики.
В приведенном примере одна простая фича затронула сразу 3-4 подсистемы. Более сложные фичи в более сложных приложениях могут затронуть большее количество подсистем, и изменения могут оказаться не такими простыми. Так что рекомендация "1 фича = 1 модуль" — это лишь благое, но нежизнеспособное пожелание.piromanlynx
16.03.2016 00:52Совсем нет. Вы просто сходу представляете нерасширяему систему (MVC например).
Что такое добавить нового клиента в приложение? Идем с конца: нам нужна сущность клиента, нам нужна возможность его добавить (форма), нам нужны внешние ключи/связи.
Вспоминаем, что база это не то куда надогадить, а мы понимаем ее как "микросервис для хранения данных". Окей.
Итого: 2 задачи, 2 фичи, 2 правки:
- Добавить таблицу с юзерами в БД (правка в микросервисе "СУБД", CREATE TABLE, CRAETE VIEW)
- Нужен интерфейс (список, форма добавления/редактирования, валидация, само сохранение) — чисто новый микросервис, ничем на прочее не завязанный. На запись в БД имеет права только в свою табличку clients. На чтение — только из готовых VIEW (чтоб связи выбрат — клиент-менеджер, клиент-партнер). VIEW как известно — это интерфейс (читаем wikipedia).
Как пускать туда манагеров в этот микросервис? Ну да, у нас же нормальная архитектура и композиция — авторизация у нас централизована по токену.
Как показать все в едином интерфейсе? Ну да, у нас же все по уму — мы ssi используем и proxy_pass. Внешне — все в одном UI, не внешне — хоть в разных ЦОД.
Вы просто начинаете с того, что у вас монолит на MVC — там да, проблема. Но не надо закладываться на то, что "мы сразу с нуля будем делать плохо"piromanlynx
16.03.2016 00:55О том как представить "СУБД как микросервис" можно отдельную статью написать. Но суть простая:
- Читать могут все и только из VIEW — сохраняем обратно совместимый интерфейс
- Создавать новые записи может только кто-то один
- Изменять записи может только кто-то один (возможно он же и их создатель — так даже лучше)
- Удалять записи может только кто-то один (возможно он же и создатель, и редактор)
"все" и "кто-то" один — я имею ввиду микросервисы.piromanlynx
16.03.2016 00:56С модульой архитектурой — все абсолютно тоже самое. Только не SSI, а технология на уровне фреймворка
Mendel
16.03.2016 02:35А чем вам MVC мешает делать модульно?
Добавление таблиц и т.п. это миграции. Это вообще отдельная песня.
Новая ответственность, новая сущность, новая моделька, отдельный класс, вероятно в отдельной папке отдельного модуля (как настроишь среду). Контроллер для CRUD? Наследуемся от стандартного класса (да, академический контроллер это не контроллер в бытовом понимании а его экшн, но базовый класс под CRUD-контроллер это удобно и практично. Я вообще такие вещи простой строчкой в конфиге предпочитаю делать, без кода). Нужны вьювы для CRUD? Написали вьювы. Всё собственно. Права со сценариями/ролями мы пристроили еще в модельку.
Телодвижений вроде как аж целых четыре — сотворить модельку, сотворить и запустить миграцию, сотворить шаблоны, сотворить контроллер в виде одной строчки. Но по смыслу тоже самое что и в микросервисах — создаем структуру таблицы, пишем бизнеслогику (модель) пишем вьювы. А обвеска везде одинакова по сложности.
Тот же профиль, только в продукт птицефабрики.piromanlynx
16.03.2016 11:27MVC не мешает. Обычно MVC используют криво. Моделиз юзают из других модулей, вьюхи и прочее
Askofen
16.03.2016 13:03+2Дело не в том, как Вы организуете доступ к базе данных — через микросервис или посредством слоя доступа к данным, а в том, что Вы понимаете под словом фича (feature). Обычно под feature понимают некоторую пользовательскую функцию, т.е. особенность приложения, которая интересна пользователю. Когда приложение рассматривается в виде набора (или дерева) фич, то при этом осуществляется функциональная декомпозиция приложения с точки зрения пользователя. Но эта декомпозиция никак не совпадает со структурной декомпозицией того же самого приложения, которое осуществляет инженер вне зависимости от того, какая архитектура, какие принципы заложены в основе этой технической декомпозиции: будь то разбиение по слоям, микросервисы, MVC и т.д. и т.п.
Взглядов на одно и тоже приложение может быть несколько. И нужно четко понимать различия между этими взглядами, в том числе, различие между взглядом пользователя и взглядом инженера.
gonzalez187
17.03.2016 12:01Получается, «серебряная пуля» для развивающихся продуктов, имеющих сложности с программной подсистемой, — это рефакторинг архитектуры?
На первом этапе, выводя продукт на рынок, мы можем не знать о большинстве требований/условий функционирования, мы имеем ограниченный бюджет, компентенции, время, чтобы продумать все как следует => выбираем не самое лучшее архитектурное решение, но позволяющее быстро заработать. Так образуется технический долг…
Тогда:
если продукт пользуется спросом мы можем позволить себе провести рефакторинг архитектуры, избавиться от технического долга и обеспечить развитие на долгие годы
если продукт не пользуется спросом, то технический долг списывается
piromanlynx
За опечатки в личку — всем спасибо!