Не кажется ли вам, что ИИ снизил ценность операционных знаний, типа «как сделать ту или иную вещь на том или ином языке программирования», но повысил ценность архитектурного мышления и умения правильно декомпозировать код, то есть делить его на модули. Причём принципы этого деления никак не изменились с 70-х годов. И даже наоборот теперь им нужно следовать более строго и щепетильно, иначе ИИ (вы же всё равно прибегаете к его помощи хотя бы для каркаса) наворотит такую безграничную вайб-лапшу, что распутать человеческими мозгами будет невозможно.
Закон Конвея
Аж в 1967 году программист Мевил Конвей, работавший над компиляторами и операционными системами, сформулировал закон (по типу законов Мерфи) — «Организации, проектирующие системы вынуждены создавать проекты, которые копируют структуры коммуникаций в этих организациях», т.е. фактически, структура ПО повторяет структуру компании, в которой оно создано. Так получается естественным образом и грамотный архитектор должен держать это в голове — либо архитектуру подгонять под команду, либо наоборот форматировать команду под архитектуру, иначе трудно будет получить ожидаемый результат.
Если в современные команды ворвался ИИ на правах джуниор-разработчика, то фактически он изменил структуру команд, что должно изменить и структуру кода. Пока ИИ удаётся удовлетворительно справляться с небольшими замкнутыми кусками кода. Логично, что и система должна дробиться на более мелкие и обособленные куски кода. Но на какие именно, вероятно, ИИ не может решить окончательно — поскольку по закону Конвея это зависит от структуры команды не меньше, чем от технических параметров.
Меньше зацепления, больше связности
К счастью для нас принципы разделения (или декомпозиции или сегрегации) кода были сформулированы даже раньше закона Конвея — начиная с 60-х появляются термины «связность» (cohesion) и «зацепление» (coupling). Последнее, кстати говоря, вполне позволительно перевести как «совокупление», что делает термин ещё звучнее и понятнее — совокупление программных модулей — зрелище жалкое и отталкивающее, его следует избегать всеми доступными средствами. Это такая ситуация когда один модуль вызывает методы другого, а другой первого (ну или взаимно пользуются константами и свойствами), что делает их по сути неразделимыми. Связность — иная ситуация — это когда модуль сфокусирован на решении единственной задачи и не использует перекрёстных зависимостей и предоставляет стандартизированное API для общения с собой.
Если на примерах.
Банальная задача — нужно поменять пользователю пароль залогировать это и отправить емейл с уведомлением. Как сделает неопытный программист (я двадцатилетней давности)
// App.code * .code — это любой язык // сделаем объект в котором запомним ID-шечку, это ж ООП let user = new User(userId) user.setPassword(pass) // и всё // а уж в User.code // естественно, в контексте экземпляра User — там же все данные есть user.setPassword(pass) => { // захэшируем пароль, может даже с солью, закинем в БД // закинем в БД лог-запись, чтоб два раза не вставать // и вызовем отправку почты — это ж каждый раз надо делать при смене пароля }
Не, ну для отправки е-мейла, конечно, напишем все таки отдельный модуль — мы ж за модульность, просто вызывать будем его в экземпляре User’a:
// это строчка из user.setPassword(), this — это user // мейлер сам достанет почту из user’a // и имя достанет, если надо красивое обращение написать mailer.sendMail(this, “Вам изменили. Наверное, пароль”)
Здесь у User’a низкая связность — задачи, которые он делает слабо связаны между собой — он и в БД лезет в разные таблицы, и логирует, и пароли хэширует и лезет в мейлер. Понадобится ещё и СМС отправлять — допишем.
У обоих модулей сильное зацепление — юзер знает каким именно образом информируется пользователь, мейлер знает как устроен пользователь, если кто-то вдруг у юзера переименует поле firstName в name (что вообще-то стрёмно) то посыпятся эксепшены в модуле мейлер, а юзер будет себя хорошо чувствовать.
Как сделаю я сейчас — наделаю файликов. Развязываем зацепление, фокусируем связность.
UsersRepo.code // только лазает в БД только в таблицу юзеров User.code // запись о юзере из бд UserLogRepo.code // лазает в БД только в таблицу логов MailService.code // отправляет текст на e-mail адрес, о юзерах не слышал даже UsersService.code // совмещает всё, делая нужную работу
И уже в UsersService будет метод setPassword(userId, pass) в котором
let user = UsersRepo.findById(userId) // упадёт, если юзер не существует UsersRepo.savePass(userId, pass) let event = this.formatEvent(user) // сдесь же в сервисе формируем запись лога UserLogRepo.save(event) let message = this.formatMessage(user) // как-то там формируем текст почты MailService.send(user.email, message)
Так выходит даже многословнее, но каждый отдельный модуль становится существенно проще, они не знают друг о друге, за исключением UsersService который знает обо всех, но при этом остальные модули никак на сервис не завязаны и ничего о нём не знают. Такую разработку уже можно распараллелить — отдать …Repo знатоку SQL, отдать мейлер тому кто слышал про POP3 и IMAP. А UsersService, тому кто общается с менеджерами. И такое даже безопаснее скормить ИИ. (Причём это не единственный способ разделения кода — какие то методы можно перетащить из сервиса в самого юзера. Можно сделать формирование месседжей отдельным классом или внедрить в другой класс — код от этого не потеряет модульность, просто изменится характер некоторых связей)
Таким образом мы осуществили декомпозицию с целью уменьшить зацепление и повысить связность. Мы «управляли сложностью» — мы не уменьшили её. Парадоксальным образом нам даже пришлось её повысить, чтобы упростить разработку. Мы раздробили монолитную сложность на множество несложных изолированных компонентов и структурировали их связи.
Декомпозиция — это важно
«Меньше зацепления, больше связности» это настолько фундаментальная цель, что, например, распространённые в ООП принципы SOLID — это фактически инструкция как достичь целевого уровня связности/зацепления. Эта целевая задача в явном виде входит в правила GRASP или в принципы «Чистой Архитектуры» (Clean Architecture). Это всегда было важно и помогало строить большие системы. А с появлением ИИ стало еще важнее.
Итог
Декомпозируйте свой код. Принимайте при этом во внимание не только известные паттерны, но и свои административно-организационные особенности. Это позволит не только другим разработчикам понимать что происходит, но и проще отлаживаться, и самому «въехать» спустя пару месяцев, и ИИ напрячь в локальной замкнутой области кода.
Может ли декомпозировать сам ИИ? Может, если явно попросить. Но без учёта вашей организационной структуры. Но и её можно уточнить, однако промпт придётся усложнять. Плюс к тому же ИИ может потерять связи между модулями или «забыть» правильные названия свойств и методов. Поэтому будьте бдительны и осторожны, если даёте ему больше одного файла!
PS
Если решите прокомментировать публикацию — оставьте, по возможности, мнение — «повышает ли ИИ значимость знаний по архитектуре?».
Источник: habr.com



























