- •Введение
- •1. Цели и задачи архитектора информационных систем
- •1.1. Цели создания информационных систем
- •1.2. Базовые структуры информационных систем
- •1.3. Уровни реализации архитектуры ИС
- •1.4. Атрибуты качества информационных систем
- •1.5. Сопровождение бизнес-ориентированных ИС
- •2.2. Структурное программирование
- •2. Парадигмы программирования
- •2.1. Модульное программирование
- •2.4. Функциональное программирование
- •2.5. Общие тенденции развития парадигм программирования
- •3. Чистая архитектура
- •3.1. Кричащая архитектура
- •3.2. Принципы SOLID
- •3.2.1. Принцип единственной ответственности
- •3.2.2. Принцип открытости/закрытости
- •3.2.3. Принцип подстановки Б. Лисков
- •3.2.4. Принцип разделения интерфейсов
- •3.2.5. Принцип инверсии зависимостей
- •3.3. Программные компоненты и принципы их организации
- •3.3.1. Диаграмма компонентов
- •3.3.2. Принципы взаимосвязи компонентов
- •3.3.3. Принцип эквивалентности повторного использования и выпусков
- •3.3.4. Принцип согласованного изменения
- •3.3.5. Принцип совместного повторного использования
- •3.3.6. Сравнение принципов взаимосвязи компонентов
- •3.4. Компонентный подход как способ динамического изменения архитектуры ИС
- •3.4.1. Принцип ацикличности зависимостей
- •3.4.2. Принцип устойчивых зависимостей
- •3.4.3. Принцип устойчивости абстракций
- •3.4.4. Графическая интерпретация принципов SDP и SAP
- •3.5. Вычислительные архитектуры ИС
- •3.5.1. Клиент-серверная архитектура
- •3.5.2. Трёхзвенная архитектура
- •3.5.3. Микросервисная архитектура
- •3.6. Шаблоны и антишаблоны проектирования ИС
- •3.6.1. Шаблоны проектирования
- •3.6.2. Антишаблоны проектирования
2. Парадигмы программирования
Сложность информационных систем напрямую зависит от сложно-
сти решаемых с помощью таких систем производственных и бизнес-
ориентированных задач. Таким образом, можно утверждать, что ИС с высокой объективной сложностью являются более ценными, чем сис-
темы с малой сложностью, так как они позволяют автоматизировать более комплексные процессы. Проблему представляет в первую оче-
редь неоправданно высокая динамика роста сложности при добавлении в систему нового функционала.
В исторической ретроспективе снижение динамики роста сложно-
сти ИС можно показать, рассмотрев доминировавшие в разное время парадигмы программирования и проведя их сравнительный анализ [25].
2.1. Модульное программирование
Первым этапом в борьбе со сложностью разрабатываемых про-
грамм можно считать появление парадигмы модульного программиро-
вания [26]. Идея о том, что весь исходный код программы целесообраз-
но разделять на обособленно компилируемые модули, появилась в кон-
це 1950-х годов в таких языках программирования, как Фортран II и
Кобол.
Необходимость разделения всего программного кода на отдельные модули в первую очередь была связана с большим временем компиля-
ции программ. Появление ошибки компиляции в любой части про-
граммы приводило к тому, что весь программный код после исправле-
ния ошибки приходилось компилировать повторно. Обособленно ком-
пилирующиеся модули позволили уменьшить время компиляции, так как при возникновении ошибки требовалось выполнять сборку лишь
17
одного модуля, а не всей программы в целом. Кроме того, модульное программирование позволило упростить и сам процесс поиска ошибки за счёт локализации области поиска: ошибку теперь следовало искать внутри модуля, а не во всем программном коде.
В дальнейшем модульное программирование проникло и в архи-
тектуру информационных систем: выделение модулей и жёсткое выде-
ление архитектурных границ ИС все так же позволяет с высокой скоро-
стью выполнять локализацию ошибок, несмотря на объёмы программ-
ного кода в современных ИС.
2.2. Структурное программирование
Концепция структурного программирования появилась в конце
1960-х – начале 1970-х годов и представляла собой дальнейшее разви-
тие идей модульного программирования [25, 27]. Парадигма структур-
ного программирования утверждает, что реализация любых конечных алгоритмов возможна без использования операторов безусловного пе-
рехода. Для описания алгоритма работы программы могут быть ис-
пользованы блок-схемы. Блок-схема, в свою очередь, может быть опи-
сана с помощью направленного графа. В таком графе шаги алгоритма могут быть представлены в виде узлов, а направление переходов – в
виде рёбер направленного графа.
В основе структурного программирования лежат следующие базо-
вые операторы:
–«последовательность» порождает из одного узла графа одно ребро;
–«ветвление» порождает из одного узла графа два ребра;
–«цикл» порождает из одного ребра два ребра, одно из которых
«возвращается» в исходный узел.
18
При этом каждый из этих операторов имеет лишь один вход.
Используемый в модульном программировании оператор безус-
ловного перехода «goto» приводит к расширению числа входов каждого из вышеупомянутых операторов на несколько порядков (в переделе число входов не ограничено). Очевидно, что направленный граф с уз-
лами, имеющими не более трёх рёбер, анализировать значительно про-
ще, чем граф, в котором на одно ребро приходится в пределе бесконеч-
ное число рёбер.
Помимо отказа от оператора безусловного перехода структурное программирование усилило и концепцию модульности, введя следую-
щие два принципа разработки ИС:
–в программе базовые конструкции вкладывают друг в друга произвольным образом, другие средства последовательного управления программой не предусматриваются;
–повторяющиеся фрагменты программы оформляются в виде подпрограмм, состоящих из заголовка с параметрами (сигнату-
ра методов) и тела подпрограммы.
При реализации технологии структурного программирования в ос-
новной программе оставляют только общую часть алгоритма, а детали его реализации вписывают в код подпрограммы.
Таким образом, структурное программирование позволяет:
–осуществлять разработку программного обеспечения методом
«сверху-вниз» (ИС описывается с помощью крупных логиче-
ских блоков, которые впоследствии уточняются – то есть под-
вергаются декомпозиции), что упрощает восприятие каждого отдельного этапа разработки;
–значительно сократить число вариантов построения програм-
мы, тем самым уменьшив её вариативную сложность;
19
–выполнять независимую отладку и тестирование отдельных фрагментов программы;
–упростить анализ программного кода за счёт близкого распо-
ложения логически взаимосвязанных операций и удалённого расположения слабосвязанных;
–повысить производительность за счёт повторного использова-
ния отдельных частей системы (вместо их повторной реализа-
ции).
Несмотря на вышеупомянутые преимущества, структурная пара-
дигма программирования имеет и свои недостатки.
Во-первых, данные и операции над ними не имеют явной связи, а
потому могут располагаться далеко друг от друга (аналогично логиче-
ски связанным операциям при использовании оператора «goto»). Такое расположение в значительной степени усложняет разработку сложных ИС, так как зачастую неясно, в какой части ИС реализована логика об-
работки того или иного элемента данных.
Во-вторых, структурное программирование не фокусируется на структурах данных, а, следовательно, имеет большие сложности в опи-
сании сложноструктурированных объектов материального мира. Такие объекты при структурном программировании зачастую описываются с помощью неименованной упорядоченной последовательности прими-
тивных типов данных. Так, например, процедура, осуществляющая те-
лефонный звонок некоторому человеку, может иметь следующий вид:
procedure MakePhoneCall(name: string; age: int)
Из приведённого примера нельзя сделать вывод о том, что это за человек (например, является ли он преподавателем или студентом). Бо-
лее того, с высокой точностью даже нельзя утверждать, что name и age
являются характеристиками человека, а не заказываемого им по теле20
фону автомобиля такси, у которого name – марка автомобиля, а age ука-
зывает на год его выпуска. Очевидно, что различные трактовки одного и того же метода усложняют восприятие программного кода.
Кроме того, сложность описания объектов материального мира влечёт за собой также и сложность модификации программного кода: в
случае, если очередное функциональное требование к ИС повлечёт за собой изменение параметров человека (например, он будет характери-
зоваться не именем, а фамилией, именем и отчеством), то изменения потребуется скрупулёзно вносить по всему программному коду (так как простого способа понять, где имя человека, а где наименование марки авто, при структурном программировании нет).
2.3.Объектно-ориентированное программирование
Внастоящее время наиболее распространённым подходом к про-
граммированию является объектно-ориентированное программирова-
ние (ООП) [2, 25, 27]. Первое воплощение парадигма ООП получила в
1967 году в языке Симула и получила дальнейшее развитие в языке
Smalltalk в 1970-х годах.
В основе объектно-ориентированного программирования лежит понятие «объекта» как некоторой структуры данных, моделирующей объекты реального мира (объекты предметной области). Принципиаль-
ное отличие ООП от парадигмы структурного программирования за-
ключается в том, что объекты описываются не только набором характе-
ристик, но и своим поведением, представленным в виде процедур и функций (методов).
Объекты в ООП объединяются в «классы». Класс – это общее опи-
сание некоторой однотипной группы объектов. Класс можно также описывать как шаблон для создания однотипных объектов. Все одно-
21
типные объекты будут иметь один и тот же набор характеристик и сходное поведение. Так, например, группа автомобилей (класс «Авто-
мобиль») может характеризоваться своей маркой, количеством дверей,
годом выпуска (характеристики автомобиля) и алгоритмом запуска двигателя (поведение автомобиля). При этом каждый конкретный ав-
томобиль (объект класса «Автомобиль») будет иметь свои значения характеристик (<Газ Волга, 4 двери, 1992>, <Газ Волга, 4 двери, 1996>, <Лада Калина, 5 дверей, 2018> и т.д.).
Один класс может наследовать другой. Принцип наследования позволяет на основе классов «родители» создавать классы «потомки»
(«наследники»). Классы-наследники имеют те же характеристики и по-
ведение, что и классы-родители, но при этом они могут иметь и свои дополнительные (уточняющие) характеристики и более конкретное по-
ведение. Ниже для примера рассмотрены три класса: «Животное» (ро-
дительский класс), «Собака» и «Змея» (классы-потомки).
1. Класс «Животное», характеризующийся именем и возрастом, а
также имеющий поведение «Питаться».
2.На основе класса «Животное» можно создать класс «Собака»,
который помимо имени и возраста может характеризоваться ещё количеством ног и длиной хвоста. В классе «Собака» также мож-
но конкретизировать процесс «Питание»: «Собака», в силу отсут-
ствия у неё резцов, питается слабо пережёванными кусками мяса.
3.Наследником класса «Животное» может быть класс «Змея». У
неё есть имя и возраст (как у любого животного), но нет ног, как у класса «Собака». При этом отличительной чертой класса
«Змея» (т.е. её уточняющей характеристикой) может быть при-
знак, является ли она ядовитой (классу «Собака» такой признак не нужен, так как ядовитых собак не существует). «Змея», как и
22
«Собака», тоже умеет питаться, но сам процесс отличается: до-
быча поглощается классом «Змея» целиком.
Наследование является очень гибким механизмом расширения функционала ИС без необходимости анализа всего программного кода приложения.
Другим принципом ООП является полиморфизм, который был продемонстрирован выше на примере метода «Питаться». ООП позво-
ляет в классах-наследниках создавать методы, имеющие такое же на-
звание, как и методы родителя. Полиморфизм позволяет либо полно-
стью изменять поведение базового класса (т. е. класса-родителя), либо
(что предпочтительнее) уточнять его. Так, можно сказать, что «Живот-
ное» при питании открывает рот и получает энергию из пищи, «Змея» открывает рот, проглатывает добычу, получает энергию, «Собака» открывает рот, отрывает кусок мяса, слабо пережёвывает пищу, получает энергию.
Еще одним принципом ООП является механизм инкапсуляции,
позволяющий скрывать детали реализации того или иного поведения внутри класса. Так, для того чтобы покормить «Собаку», классу «Чело-
век» нет необходимости знать о структуре желудочно-кишечного трак-
та своего питомца. Нюансы реализации ЖКТ в классе «Собака», оче-
видно, усложнят процесс кормления. В будущем при необходимости изменения отдельных аспектов данного процесса (например, добавле-
ние в корм тех или иных лекарственных средств) могут в значительной степени увеличить время внесения исправлений (когда программист вместо решения поставленной задачи будет осваивать курс биологии).
К основным принципам ООП также следует отнести принцип аб-
стракции, т. е. использование для описания объектов реального мира только тех его характеристик, которые необходимы для решения по23
