Найти тему
Дед Мазай на Котлине

Просто о сложном коде

Связность и связанность
Связность и связанность

Делал презентацию для выступления на внутреннем митапе на нашем IT-заводе. Попробую перевести её в формат статьи. Но получится текста сильно меньше, чем наговорил на митапе.

Связность и связанность кода - это два идущих рука об руку понятия, с помощью которых можно определять, насколько качественной явлется кодовая база проекта.

Отличие в произнесении этих двух понятий - всего в одной букве 'н' и это очень запутывает, особенно, когда пытаешься использовать эти термины при общении голосом. Как следствие, многие пытаются использовать вместо этих двух понятий синонимы, что запутывает желающих разобраться в них ещё больше.

Связность - это взаимосвязь элементов кода (функций, классов, модулей и т.д.).
Связность бывает:
- функциональная (элементы служат выполнению приложением одной общей для них функции),
- логическая (один элемент использует данные, предоставляемые другим элементом),
- последовательная (элементы должны быть выполнены последовательно друг за другом),
- процедурная (элементы должны быть выполнены в строгом порядке),
- веременная (элементы должны быть выполнены в одно время).

Высокая связность - когда элементы, между которыми есть связность, находятся в одном компоненте кода. Высокая связаность считается хорошим признаком.

Пример с классами пакета employee, имеющими высокую связность
Пример с классами пакета employee, имеющими высокую связность

Низкая связность - когда элементы, между которыми нет связности, находятся в одном компоненте. Это считается признаком кода плохого качества.

Пример с классами пакета controller, имеющими низкую связность
Пример с классами пакета controller, имеющими низкую связность

Что делать, чтобы было хорошо?

Элементы, имеющие низкую связность, должны быть разнесены в разные компоненты:
- пары поле - метод с низкой связностью должны быть вынесены в отдельные классы,
- классы с низкой связностью должны быть вынесены в отдельные пакеты.

Связанность - когда элементы с низкой связностью имеют связи (вызывают методы друг друга) или находятся в одном компоненте (см. выше пример с пакетом controller).

Понятие связанности используется совместно с таким понятиями как:
- абстрактность,
- нестабильность,
- коннасценция.

Абстрактность - это отношение числа абстрактных классов к конкретным.
Абстрактные классы помогают уменьшить связанность в коде. Но слишком большое значение абстрактности затрудняет разработчикам отслеживание взаимных связей компонентов программы.

Нестабильность определяется по формуле:

I = Ce/(Ce + Ca)
Ce - эфферентность (исходящие связи класса)
Ca - афферентность (входящие связи класса)
Чем выше нестабильность, тем легче кодовая база ломается при внесении изменений.

Между абстрактностью и нестабильностью должен быть баланс.
Сильное отклонение в сторону преобладания абстрактности переносит код в зону бесполезности: слишком абстрактный код создаёт трудности при его использовании.
Сильное отклонение в сторону нестабильности переводит код в зону мучений: слишком большая доля реализации и недостаточная абстрактность приводят к хрупкости кода и сложности в сопровождении.

Конасценция. Два компонента считаются конасцентными, если изменния, внесённые в один из них, потребуют модификации другого для поддержания общей работоспособности системы.

В некоторых источниках отмечают, что связанность - это устаревшее понятие, введёное учёными в 1970-х годах. Более современным считается использовать вместо него термин конасценция.
Характеристики коннасценции:
- сила,
- локальность,
- степень.

Сила конасценции - это лёгкость, с которой в код могут быть внесены изменения (выполнен рефакторинг).

В порядке возрастания сложности рефакторинга кода выделяют следующие виды коннасценции:
- статические – связанность на уровне исходного кода,
- динамические – связанность на уровне выполнения.

Статические конасценции разделяются на конасценции (в порядке возрастания ложности рефакторинга):

  • имени (пример: несколько функций завязаны на один enum),
  • типа (одна функция зависит от типа данных, возвращаемых дугой функцией),
  • смысла (статические константы),
  • алгоритма (на одном конце алгоритм расшифровки сообщения зависит от алгоритма шифрования на другом конце),
  • позиции (пример из java: нужно соблюдать позицию передаваемых в метод аргументов).

Динамические конасценции разделяются на конасценции (в порядке возрастания ложности рефакторинга):

  • исполнения (порядок выполнения имеет значение),
  • синхронности (в многопоточном коде),
  • значений (значения разных элементов зависят друг от друга. Пример: транзакционность),
  • идентичности (несколько элементов работают с одной структурой. Например, с очередью).

Локальность конасценции. Зависит от того, как близко друг от друга в коде находятся связанные элементы.

Чем дальше друг от друга находятся такие элементы и, чем сильнее будет конасценция, тем больше будет ущерб от связанности элементов.
Примеры:
- 2 микросервиса смотрят в одну базу данных (завязаны на её структуру),
- интеграционные тесты зависят от какого-то эталонного наполнения тестовой базы данных.

Степень конасценции - это количество классов, на которые она влияет.

Степень конасценции может увеличиваться с разрастанием кодовой базы.

Пример:
На начальном этапе разработки у нас всего несколько интеграционных тестов и они зависят от эталонного наполнения тестовой базы данных. Мы можем не видеть в этом проблемы, так как степень конасценции пока невелика. Однако проблема есть, так как в коде присутствует динамическая конасценция значения - вторая по неприятным поседствиям конасценция. Хуже неё только динамическая конасценция идентичности.
При разрастании кодовой базы будет увеличиваться количество интеграционных тестов и в каждом из них нам нужно будет учитывать, что база данных должна сохранять своё эталонное состояние, несмотря ни на что. Т.е. степень конасценции будет возрастать. С возрастанием степени конасценции однажды наступает момент, когда уже будет

-4

и переписать старые тесты. И это будет печально.

Что делать с конасценцией? Возможны два пути.

Путь падавана:

1. Не думать о ней и жить дальше.
2. Когда станет совсем невозможно работать - перейти на другой проект. А ещё лучше - перейти на новый проект и с самого начала, основательно разложить там те же грабли, что были и на прошлом проекте.
3. Повторять пункты 1 и 2 до выхода на пенсию.

Путь джедая:

1. Сводить общую коннасценцию к минимуму за счёт разбиения системы на инкапсулированные элементы (классы, пакеты, модули, приложения).
2. Минимизировать коннасценцию, пересекающую границы инкапсуляции.
3. Сильные формы коннасценции переделывать в как можно более слабые. Например, статическую. логическую конасценцию

const val TRUE: Int = 1
const val FALSE: Int = 0

Заменить на статическую конасценцию типа: вместо объявления констант TRUE и FALSE использовать везде значения true и false типа Boolean.

4. По мере увеличения расстояния между элементами использовать более слабые формы коннасценции.