Архитектура ПО

Содержание

Слайд 2

DevOps итоги Узкими местами потока создания ценности являются: Тестирование Большое время

DevOps итоги

Узкими местами потока создания ценности являются:
Тестирование
Большое время обратной связи «разработка–>тест->

дефект»
Много времени занимает «ретроспективное тестирование»
Тестирование поверхностное (не тестируются негативные сценарии)
Развертывание
Ручная доставка новых версий пользователю
Нет понятия «Релиз» системы
Эксплуатация
Нет данных о реальной работе системы на объектах
Слайд 3

Развитие функционала Время Функции Релиз Потеря контроля Этап 1(разработка) Этап 2

Развитие функционала

Время

Функции

Релиз

Потеря контроля

Этап 1(разработка)

Этап 2 (эксплуатация)

Этап 3 (загнивание)

Качество кода

Слайд 4

Качество кода Качество кода – достаточно сложное понятие. Мы рассмотрим 3

Качество кода

Качество кода – достаточно сложное понятие. Мы рассмотрим 3 параметра:
Работоспособность

– способность выполнять задачи
Модифицируемость – возможность вносить изменения без потери работоспособности (Архитектурное понятие)
Минимальное число зависимостей – взаимосвязей между частями приложения (Архитектурное понятие)
Причины падения качества кода:
Изначально некачественная разработка (при разработке добиваемся только работоспособности, не следим за архитектурой)
Затыкание дыр – быстрое устранение дефектов без анализа последствий для архитектуры проекта
Наличие «неизменяемых функций»– например подключение новой СУЛ, которой нет в доступе. Изменять такие фрагменты страшно.
Отсутствие качественного тестирования после модификации или введения нового функционала.
Введение «голубого функционала» который не нужен впоследствии и только загрязняет код
Слайд 5

Запахи плохого дизайна архитектуры Жесткость Жесткость – это характеристика программы, затрудняющая

Запахи плохого дизайна архитектуры

Жесткость
Жесткость – это характеристика программы, затрудняющая внесение

в нее изменений, даже самых простых. Дизайн жесткий, если единственное изменение вызывает целый каскад других изменений в зависимых модулях. Чем больше модулей приходится изменять, тем жестче дизайн.
Хрупкость
Хрупкость – это свойство программы повреждаться во многих местах при внесении единственного изменения. Зачастую новые проблемы возникают в частях, не имеющих концептуальной связи с той, что была изменена.
Косность
Дизайн является косным, если он содержит части, которые могли бы оказаться полезны в других системах, но усилия и риски, сопряженные с попыткой отделить эти части от оригинальной системы, слишком велики.
Вязкость
Сталкиваясь с необходимостью внести изменение, разработчик обычно находит несколько способов сделать это. Одни сохраняют дизайн, другие – нет(хак). Если сохраняющие дизайн подходы оказывается реализовать труднее, чем «хак», то вязкость дизайна высока.
Слайд 6

Ненужная сложность Дизайн попахивает ненужной сложностью, если содержит элементы неиспользуемые в

Ненужная сложность
Дизайн попахивает ненужной сложностью, если содержит элементы неиспользуемые в текущий

момент. Это часто случается, когда разработчики стараются предвидеть будущие изменения требований и вставлять в программу средства для их поддержки.
Ненужные повторения
Если в системе есть дублирующийся код, то задача ее изменения может потребовать значительных усилий. Ошибки, обнаруженные в повторяющемся блоке, должны быть исправлены во всех его копиях.
Непрозрачность
Непрозрачность – это трудность модуля для понимания. Код может быть ясным и выразительным или темным и запутанным. Код, эволюционирующий со временем, постепенно становится все более и более непрозрачным. Дабы свести непрозрачность к минимуму, нужно постоянно следить, чтобы он оставался ясным и выразительным.

Запахи плохого дизайна архитектуры

Слайд 7

Методы борьбы с ухудшением качества кода Рефакторинг – изменение внутренней структуры

Методы борьбы с ухудшением качества кода

Рефакторинг – изменение внутренней структуры

ПО без изменения его поведения.
В рефакторинг входит:
Переименование классов, функций, переменных.
Переработка алгоритмов, при условии сохранения входных и выходных данных
Переработка архитектуры
Рефакторинг проводится на разных уровнях приложения:
На уровне минимальных «единиц» кода (на уровне function или class)
На уровне крупных компонентов (на уровне бизнес логики, канального уровня, интерфейсного уровня)
На уровне всего приложения (рефакторинг архитектуры)
Слайд 8

Рефакторинг на уровне минимальных «единиц» кода Разработка кода Тестирование Рефакторинг Тестирование

Рефакторинг на уровне минимальных «единиц» кода

Разработка кода

Тестирование

Рефакторинг

Тестирование

Анализ кода

Работоспособный код

Работоспособный высокого

качества
(минимизация зависимостей, модифицуемость )

Разработка функционала

Слайд 9

Выводы Основной причиной загнивания проекта является ухудшение качества кода и «размытие»

Выводы

Основной причиной загнивания проекта является ухудшение качества кода и «размытие» архитектуры.


Признаками «размытия» архитектуры являются «запахи»
Для сохранения качества кода необходимо проводить рефакторинг на различных уровнях
Неотъемлемой частью рефакторинга является тестирование!
При этом тестирование производится многократно на каждом этапе и на каждом уровне рефакторинга.
А это значит что тестирование следует автоматизировать!!!
А возможность автоматизации тестирования зависит от архитектуры приложения и от архитектуры его компонентов.
Слайд 10

Архитектура ПО Архитектура ПО – это внутренний атрибут качества ПО и

Архитектура ПО

Архитектура ПО – это внутренний атрибут качества ПО и разрабатывается

она в зависимости от специфики разрабатываемого ПО.
Общепринятого определения понятия «Архитектура ПО» не существует.
Мы будем рассматривать Архитектуру ПО как искусство или науку проведения границ и установления зависимостей.
Факты об архитектуре ПО:
Разработка архитектуры требует времени
Поддержание архитектуры требует времени
Архитектура это долгосрочная инвестиция! Затраты на хорошую архитектуру окупятся при дальнейшей разработке проекта.
Слайд 11

Границы Границы отделяют программные элементы друг от друга и избавляют их

Границы

Границы отделяют программные элементы друг от друга и избавляют их

от необходимости знать, что находится по ту сторону.
Границы часто только умозрительны.
«Физические» воплощения границ:
Функция
Класс (понятие из ООП)
Интерфейс (java)
Программный модуль (*.h + *.c)
Компонент (скомпилированный модуль, dll и тд)
Пересечение границы – это вызов функции (метода)
Слайд 12

Зависимости Функции (классы) считаются зависимыми если одна функция вызывает другую функцию.

Зависимости

Функции (классы) считаются зависимыми если одна функция вызывает другую функцию.
Зависимость

обозначается стрелкой направленной в сторону зависимости.
void FunctA (){
m= Sum (x,y);
}
Важно понимать! Изменение поведения функции Sum окажет влияние на поведение функции FunctA
Изменение поведения функции FunctA не окажет никакого влияния на функцию Sum.

FunctA

Sum

Слайд 13

Уровень абстракции Уровень абстракции — один из способов сокрытия деталей реализации

Уровень абстракции

Уровень абстракции — один из способов сокрытия деталей реализации определенного набора

функциональных возможностей. Применяется для управления сложностью проектируемой системы при декомпозиции когда система представляется в виде иерархии уровней абстракции.

Общая структура ПО «КУПОЛ»

Канальный уровень

Канальный поток

Классы канальных устройств

Сервер БКЛов

Слайд 14

Гексагональная архитектура В приложении выделяют основные слои: Внешние интерфейсы – обеспечивают

Гексагональная архитектура

В приложении выделяют основные слои:
Внешние интерфейсы – обеспечивают взаимодействие приложения

с внешним миром (ввод / вывод данных, взаимодействие с другими системами или частями приложения)
Контроллеры – обеспечивает поток управления, преобразование данных от интерфейса в бизнес логику, взаимодействие с бизнес логикой и управление выводом.
Бизнес логика – реализует основную цель приложения, содержит сложные алгоритмы обработки данных.
При этом зависимости направлены от
внешних слоев вовнутрь.
На границах между слоями должны быть
интерфейсы

Внешние интерфейсы

Контроллеры

Бизнес логика

Зависимости

Слайд 15

Гексагональная архитектура ПО на различных уровнях Классы, компоненты и границы из

Гексагональная архитектура ПО на различных уровнях

Классы, компоненты и границы из которых

состоит приложение на различных уровнях, так же построены согласно гексагональной архитектуре.
Например поле ввода IP адреса в рамках приложения это «внешний интерфейс». А для «внешнего интерфейса» это отдельный класс, который обладает интерфейсом, логикой (определение корректности IP) контроллером.

Внешние интерфейсы

Контроллеры

Бизнес логика

Интерфейс

Границы между слоями

Контроллер

Бизнес логика

Слайд 16

Принципы построения ПО по гексагональной архитектуре Бизнес логика не должна иметь

Принципы построения ПО по гексагональной архитектуре

Бизнес логика не должна иметь внешних

зависимостей
Бизнес логика должна быть аналогом «математической функции». Другими словами иметь четкие входные данные и четкие выходные данные.
Все «внешние» взаимодействия должны производиться через контроллер
Контроллер должен быть максимально простым, вся сложная логика должна быть в бизнес логике
В общем случае контроллер не должен быть зависимым от слоя внешнего интерфеса (для встраиваемого ПО есть исключения)
Другими словами: Бизнес логика не должна ничего знать о контроллере и внешнем интерфейсе, контроллер ничего не должен знать об реализации внешнего интерфейса(быть отвязанным), внешний интерфейс ничего не должен знать о бизнес логике.
Слайд 17

Принципы SOLID SRP Принцип единой ответственности – каждый модуль должен иметь

Принципы SOLID

SRP Принцип единой ответственности – каждый модуль должен иметь только

одну причину для изменения. Иметь одну ответственность.
OCP: Open-Closed Principle — принцип открытости/закрытости. Добавление новых функций должно вводится дополнением нового кода, а не изменением старого.
LSP: Liskov Substitution Principle — принцип подстановки Барбары Лисков. Сохранения интерфейсов между компонентами.
ISP: Interface Segregation Principle — принцип разделения интерфейсов. Этот принцип призывает разработчиков программного обеспечения избегать зависимости от всего, что не используется.
DIP: Dependency Inversion Principle — принцип инверсии зависимости. Код, реализующий высокоуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали. Напротив, детали должны зависеть от политики.
Слайд 18

Принцип единой ответственности Оригинальное описание: «У класса должна быть только одна

Принцип единой ответственности

Оригинальное описание: «У класса должна быть только одна причина

для изменения.»
Другими словами у класса (функции) должна быть только одна ответственность. Класс (функция) должны выполнять только одно действие.
Пример
void FunctionF(k,x,a){
y = k*x + a; // Вычисление функции
Printf(“y = “,y );}//Вывод на экран
Следствия :
В коде не должно быть дублирования. Если есть дублирование, то необходимо от него избавиться через функцию, макрос или наследование. Первый признак дублирование – copy/paste кода.
Связанное понятие «Зацепление» - мера «Специализированности» класса или функции. Высокое зацепление – принцип соблюдается, низкое – нет.
Слайд 19

Принцип открытости/закрытости (OCP) Оригинальное звучание: Программные сущности (классы, модули, функции и

Принцип открытости/закрытости (OCP)

Оригинальное звучание: Программные сущности (классы, модули, функции и

т. п.) должны быть открыты для расширения, но закрыты для модификации.
У модулей, согласованных с принципом OCP, есть две основных характеристики.
1. Они открыты для расширения. Это означает, что поведение модуля
можно расширить. Когда требования к приложению изменяются, мы добавляем в модуль новое поведение, отвечающее изменившимся
требованиям. Иными словами, мы можем изменить состав функций
модуля.
2. Они закрыты для модификации. Расширение поведения модуля не
сопряжено с изменениями в исходном или двоичном коде других модулей ПО.
Это принцип больше относится к ООП и реализуется с помощью интерфейсов и полиморфизма.
Понятие «Связанность» - это взаимодействие между классами (модулями). Если взаимодействие производится по строго определённым интерфейсам, то связанность низкая и это хорошо. А если взаимодействие производится на уровне переменных, то связанность высокая и это плохо.
Слайд 20

Принцип подстановки Лисков (LSP) Оригинальное описание: Должна быть возможность вместо базового

Принцип подстановки Лисков (LSP)

Оригинальное описание: Должна быть возможность вместо базового типа

подставить любой его подтип.
Этот принцип относится к ООП.
Описание другими словами: Потомки базового класса должны поддерживать интерфейсы родителя.
Признаки нарушение LSP: Потомок исключает функциональность базового класса и его нельзя использовать в приложении вместо базового.
Слайд 21

Принцип инверсии зависимости (DIP) в ООП Оригинальное звучание: Модули верхнего уровня

Принцип инверсии зависимости (DIP) в ООП

Оригинальное звучание:
Модули верхнего уровня не должны

зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Класс А

Класс B

Класс А

Класс B

Интерфейс
Класса B

Слайд 22

Принцип инверсии зависимости (DIP) в Си Еще можно разделить зависимости с

Принцип инверсии зависимости (DIP) в Си
Еще можно разделить зависимости с помощью

указателей на функции. Из функции FunctionA() вызывать функцию FunctionB() через указатель, предварительно его инициализировав.

FileA.c
#include «FileB.h»
FunctionA(){
FunctionB();
……
}

FileB.c
FunctionB()
{
……
}

FileA.c
#include «FileB_interface.h»
FunctionA(){
FunctB_intf();……
}

FileB.c
FunctionB()
{
……
}

FileB_interface.c
FunctB_intf(){
FunctionB();
}

Слайд 23

Шаблоны проектирования Pattern design – наборы типовых решений часто встречающихся задач

Шаблоны проектирования

Pattern design – наборы типовых решений часто встречающихся задач при

разработке ПО. Их также называют идиомами.
Шаблонов проектирования достаточно много, они имеют разную степень абстракции и разное назначение.
Мы интуитивно применяем шаблоны, не зная что это шаблон.
Гексагональная архитектура это так же шаблон.
В рамках этой беседы шаблоны рассматривать не будем. Просто необходимо знать что они есть.
Слайд 24

Пример Задача: По внешнему тактовому сигналу (inStrob = 1) считать значение

Пример

Задача:
По внешнему тактовому сигналу (inStrob = 1) считать значение с портов

А, В, С. Вычислить сумму и подсветить соответствующий светодиод.
Светодиод зажигается 1 в советующем бите
Led_Green, Led_Yellow, Led_Red
Слайд 25

Пример решение1 Функция Handle_ABC() выполняет 5 действий

Пример решение1

Функция Handle_ABC() выполняет 5 действий

Слайд 26

Переход к гексагональной архитектуре Ввод / вывод PORTA PORTB PORTC inStrob

Переход к гексагональной архитектуре

Ввод / вывод

PORTA
PORTB
PORTC
inStrob
Led_Green
Led_Yellow
Led_Red

Контроллер

Бизнес логика

Сумма A+B+C
Определение интервала

Слайд 27

Модуль ввода /вывода (hal_un.c)

Модуль ввода /вывода (hal_un.c)

Слайд 28

Бизнес логика (logic.c)

Бизнес логика (logic.c)

Слайд 29

Контроллер

Контроллер

Слайд 30

Граф вызовов функций Handle_ABC_Contr getStrobState hal_GetStrob() hal_getA() hal_getB() hal_getC() Logic LogicCheckInterval

Граф вызовов функций

Handle_ABC_Contr

getStrobState

hal_GetStrob()

hal_getA()

hal_getB()

hal_getC()

Logic

LogicCheckInterval

LogicCalc

HandleShow

hal_HideAll()

hal_Show_GR()

hal_Show_YL()

hal_Show_RD();

Ввод / вывод

Бизнес логика

Контроллер

Слайд 31

Устойчивость к изменениям в требованиях Что изменится если произойдут изменения в

Устойчивость к изменениям в требованиях

Что изменится если произойдут изменения в требованиях…


Изменения в входных / выходных интерфейсах, например A,B,C необходимо принимать по UART.
Изменится фунция Handle_ABC_Contr (), в ней будут вызывать другие функции вместо hal_getA(), hal_getB(), hal_getС(), hal_GetStrob(). Остальное останется неизменным.
Изменения в алгоритме обработки входных значений, например A+B-C.
Изменится только функция LogicCalc(), остальное останется неизменным.
Изменятся пороги или правила индикации светодиодами, то изменится функция LogicCheckInterval(), хотя может поменяться и HandleShow()
Вывод: при изменении требований, меняется только те части, в отношении которых поменялись требования.
Слайд 32

Разработка архитектуры ПО шаг 1 Построение дерева функционала Адаптер СУЛ Взаимодействие

Разработка архитектуры ПО шаг 1

Построение дерева функционала

Адаптер СУЛ

Взаимодействие с БЛ

Опрос

входов

Определение адреса

Определение типа

Обработка протокола

Обработка команд

Взаимодействие с СУЛ

Слайд 33

Разработка архитектуры ПО шаги 2..n 2. Укрупненное разбиение на классы (по

Разработка архитектуры ПО шаги 2..n

2. Укрупненное разбиение на классы (по функционалу)
3.

Вводятся дополнительные классы для обеспечения реализации функционала (класс таймеров, HAL и тд)
3. Определение связей между классами (лучше всего UML модель диаграмма классов)
4. Для каждого полученного класса выделяются (лучше рисуются UML модели диаграмма классов):
1. Интерфейсы взаимодействия
2. Бизнес логика.
3. Контроллер (поток управления)
5. При необходимости каждый класс разбивается на более мелкие классы (функции) в соответствии с принципом единой ответственности.
Слайд 34

Выводы о применении гексагональной архитектуры Минусы Разработка архитектуры требует затрат времени

Выводы о применении гексагональной архитектуры

Минусы
Разработка архитектуры требует затрат времени
Реализация ПО

в соответствии с архитектурой требует затрат времени, увеличивает объем кода, снижает быстродействие.
Плюсы
Если придерживаться архитектуры, то ПО получается гибким, устойчивым к изменениям, появляется возможность повторного использования кода в других проектах.
Архитектура окупается при дальнейшем развитии проекта
Архитектура позволяет производить автоматизированное тестирование ПО, а следовательно позволяет чаще производить рефакторинг!!!!