Введение
Почему Наш Дизайн Такой НЕУДАЧНЫЙ?
Что приходит на ум, когда вы слышите слово хаос? Возможно, вы думаете о шумной фондовой бирже или о своей кухне по утрам — все запутано и перемешано. Когда вы думаете о слове порядок, возможно, вы думаете о пустой комнате, безмятежной и спокойной. Однако для ученых хаос характеризуется однородностью (sameness), а порядок — сложностью (difference).
Например, ухоженный сад — это упорядоченная система. Садоводы определяют границы дорожками и забором, размечают клумбы или огороды. Со временем сад развивается, становясь все богаче и гуще; но без целенаправленных усилий сад разрастется. Сорняки и травы будут заглушать другие растения, покрывая дорожки, пока в конце концов все их части не станут снова такими же — дикими и неуправляемыми.
Программные системы тоже склонны к хаосу. Когда мы впервые начинаем строить новую систему, у нас есть грандиозные идеи, что наш код будет чистым и хорошо упорядоченным, но со временем мы обнаруживаем, что он включает ненужные и оборванные элементы кода и заканчивается запутанной трясиной менеджеров классов и утилитных модулей. Мы обнаруживаем, что наша разумная многослойная архитектура рухнула сама по себе, как какая то безделушка. Хаотические программные системы характеризуются одинаковостью функций: обработчики API, которые знают предметную область и отправляют электронную почту и выполняют регистрацию; классы «бизнес-логики», которые не выполняют вычислений, но выполняют ввод-вывод; и все вместе со всем остальным, так что изменение любой части системы чревато опасностью. Это настолько распространено, что у разработчиков программного обеспечения есть собственный термин для обозначения хаоса: антипаттерн "the Big Ball of Mud" (Большой шар грязи или нелитературно по русски говнокод :) ) ([bbom_image]).

| Говнокод — естественное состояние программного обеспечения, так же как болезнь — естественное состояние вашего сада. Чтобы предотвратить коллапс, нужны энергия и направление. |
К счастью, методы, позволяющие избежать этого, не сложны.
Инкапсуляции и абстракции
Инкапсуляция и абстракция — это инструменты, к которым мы все как программисты инстинктивно стремимся, даже если не все используем именно эти слова. Позвольте нам ненадолго остановиться на них, поскольку они являются постоянной фоновой темой книги.
Термин инкапсуляция охватывает две тесно связанные идеи: упрощение поведения и скрытие данных. В этом обсуждении мы используем первое. Мы инкапсулируем поведение, определяя задачу, которую необходимо выполнить в нашем коде, и передаём эту задачу четко определенному объекту или функции. И называем этот объект или функцию абстракцией.
Взгляните на следующие два фрагмента кода Python:
import json
from urllib.request import urlopen
from urllib.parse import urlencode
params = dict(q='Sausages', format='json')
handle = urlopen('http://api.duckduckgo.com' + '?' + urlencode(params))
raw_text = handle.read().decode('utf8')
parsed = json.loads(raw_text)
results = parsed['RelatedTopics']
for r in results:
if 'Text' in r:
print(r['FirstURL'] + ' - ' + r['Text'])
import requests
params = dict(q='Sausages', format='json')
parsed = requests.get('http://api.duckduckgo.com/', params=params).json()
results = parsed['RelatedTopics']
for r in results:
if 'Text' in r:
print(r['FirstURL'] + ' - ' + r['Text'])
Оба кода делают одно и то же: они отправляют закодированные в форме значения на URL-адрес, чтобы использовать API поисковой системы. Но второе проще читать и понимать, потому что оно работает на более высоком уровне абстракции.
Мы можем сделать еще один шаг вперед, определив и назвав задачу, которую нам хотелось бы, чтобы код выполнял для нас, и используем еще более высокоуровневую абстракцию, чтобы сделать ее явной:
import duckduckpy
for r in duckduckpy.query('Sausages').related_topics:
print(r.first_url, ' - ', r.text)
Инкапсуляция поведения с помощью абстракций-это мощный инструмент для того, чтобы сделать код более выразительным, более тестируемым и более простым в обслуживании.
| В литературе, посвященной объектно-ориентированному (ОО) миру, одна из классических характеристик этого подхода называется responsibility-driven design; в нем используются слова roles (роли) и responsibilities (обязанности), а не tasks (задачи). Главное — думать о коде с точки зрения поведения, а не с точки зрения данных или алгоритмов.[1] |
Большинство шаблонов в этой книге связаны с выбором абстракции, поэтому вы увидите множество примеров в каждой главе. Кроме того, [chapter_03_abstractions] конкретно обсуждает некоторые общие эвристики для выбора абстракций.
Многоуровневое представление
Инкапсуляция и абстракция помогают нам скрывать детали и защищать целостность наших данных, но нам также необходимо помнить о взаимодействии между нашими объектами и функциями. Когда одна функция, модуль или объект использует другую, мы говорим, что одна depends on (зависима) от другой. Эти зависимости образуют своего рода сеть или граф.
В большом комке грязи зависимости выходят из-под контроля (как вы видели в [bbom_image]). Изменение одного узла графа становится затруднительным, поскольку оно может повлиять на многие другие части системы. Слоистые архитектуры являются одним из способов решения этой проблемы. В многоуровневой архитектуре мы разделяем наш код на отдельные категории или роли и вводим правила касающеся того, какие категории кода могут вызывать друг друга.
Одним из наиболее распространенных примеров является трехслойная архитектура, показанная на рис. [layered_architecture1].

Многоуровневая архитектура является, пожалуй, наиболее распространенным шаблоном для построения business software — коммерческого ПО. В этой модели у нас есть компоненты пользовательского интерфейса, которые могут быть веб-страницей, API или командной строкой; эти компоненты пользовательского интерфейса взаимодействуют со слоем бизнес-логики, который содержит наши бизнес-правила и наши рабочие процессы; и, наконец, у нас есть уровень базы данных, который отвечает за хранение и извлечение данных.
До конца этой книги мы будем систематически выворачивать эту модель наизнанку, следуя одному простому принципу.
The Dependency Inversion Principle (Принцип инверсии зависимостей)
Возможно, вы уже знакомы с принципом инверсии зависимостей (DIP), потому что это D в SOLID. [2]
К сожалению, мы не можем проиллюстрировать DIP, используя три небольших листинга кода, как мы это делали для инкапсуляции. Однако вся [Часть1] по сути представляет собой отработанный пример реализации DIP во всем приложении, так что вы получите множество конкретных примеров.
А пока можно поговорить о формальном определении DIP:
Модули высокого уровня не должны зависеть от модулей низкого уровня. И то и другое должно зависеть от абстракций.
Абстракции не должны зависеть от деталей. Вместо этого детали должны зависеть от абстракций.
Но что это значит? Давайте разберемся по крупицам.
Модули высокого уровня это код, который действительно волнует вашу организацию. Возможно, вы работаете в фармацевтической компании, и ваши высокоуровневые модули имеют дело с пациентами и испытаниями. Возможно, вы работаете в банке, и ваши высокоуровневые модули управляют сделками и биржами. Высокоуровневые модули программной системы-это функции, классы и пакеты, которые имеют дело с нашими концепциями реального мира.
Напротив, низкоуровневые модули — это код, который вашей организации не важен. Маловероятно, что ваш отдел кадров будет в восторге от файловых систем или сетевых сокетов. Нечасто вы обсуждаете SMTP, HTTP или AMQP со своим финансовым отделом. Для наших нетехнических заинтересованных сторон эти низкоуровневые концепции не интересны и не актуальны. Все, что их волнует, — это правильность работы высокоуровневых концепций. Если расчет заработной платы выполняется вовремя, вашему бизнесу вряд ли будет важно, выполняется ли это задание cron или временная функция, выполняемая в Kubernetes.
Depends on (зависит от) не обязательно означает imports или calls, а скорее несёт более общую идею о том, что один модуль knows about (знает о) или needs (нуждается в) другом модуле.
И мы уже упоминали abstractions: это упрощенные интерфейсы, которые инкапсулируют поведение, подобно тому, как наш модуль duckduckgo инкапсулирует API поисковой системы.
Все проблемы в информатике можно решить, добавив еще один косвеный уровень.
Итак, первая часть DIP говорит, что наш бизнес и код не должны зависеть от технических деталей; вместо этого оба должны использовать абстракции.
Почему? В широком смысле, потому что мы хотим иметь возможность изменять их независимо друг от друга. Модули высокого уровня должны быть легко изменены в соответствии с потребностями бизнеса. Низкоуровневые модули (детали) часто на практике сложнее изменить: подумайте о рефакторинге для изменения имени функции по сравнению с определением, тестированием и развертыванием миграции базы данных для изменения имени столбца. Мы не хотим, чтобы изменения бизнес-логики замедлялись, потому что они тесно связаны с деталями инфраструктуры низкого уровня. Но точно так же важно иметь возможность изменять детали инфраструктуры, когда это необходимо (например, подумайте о сегментировании базы данных), без необходимости вносить изменения в бизнес-уровень. Добавление абстракции между ними (знаменитый дополнительный слой косвенности) позволяет им изменяться (более) независимо друг от друга.
Вторая часть еще более загадочна. «Абстракции не должны зависеть от деталей» кажется достаточно ясным, но «Детали должны зависеть от абстракций» трудно себе представить. Как мы можем получить абстракцию, которая не зависит от деталей, которые она абстрагирует? К тому времени, когда мы дойдем до [chapter_04_service_layer], у нас будет конкретный пример, который должен прояснить все это.
Место для Всей Нашей Бизнес-логики: Модель Предметной Области (The Domain Model)
Но прежде чем мы сможем вывернуть нашу трехуровневую архитектуру наизнанку, нам нужно больше поговорить об этом среднем слое: высокоуровневых модулях или бизнес-логике. Одна из наиболее распространенных причин, по которой наши проекты идут "как-то не так", заключается в том, что бизнес-логика распространяется по всем слоям нашего приложения, что затрудняет ее идентификацию, понимание и изменение.
[chapter_01_domain_model] показывает, как построить бизнес-уровень с помощью шаблона Domain Model. Остальные шаблоны в [part1] показывают, как мы можем сохранить модель предметной области легко изменяемой и свободной от низкоуровневых проблем, выбирая правильные абстракции и постоянно применяя DIP.

Комментарии
Отправить комментарий