Cohesion (связность) — это метрика, показывающая, насколько тесно связаны элементы внутри одного модуля или класса. По данным Wikipedia, высокая связность является признаком хорошо спроектированного модуля, где все методы и поля работают над одной задачей. Cohesion напрямую влияет на поддерживаемость кода и противопоставляется coupling — связанности между модулями.
Главное
Cohesion (связность, зацепление) — метрика, которая оценивает, насколько методы, поля и свойства внутри одного класса или модуля логически связаны между собой. Высокосвязный модуль выполняет одну задачу и содержит только те элементы, которые необходимы для её выполнения. Низкосвязный модуль пытается делать несколько вещей одновременно — методы слабо связаны по смыслу.
В контексте объектно-ориентированного программирования cohesion тесно связан с Single Responsibility Principle (S). Если класс имеет одну чёткую ответственность, его cohesion, как правило, высок. Если класс занимается и UI, и бизнес-логикой, и работой с сетью — cohesion низкий, и такой класс стоит разбить на несколько отдельных классов с более узкой ответственностью.
Понимание cohesion помогает разработчику принимать решение о рефакторинге. Когда вы видите, что в классе есть метод, который не использует поля класса, это сигнал низкой связности. Такой метод либо лишний в классе, либо класс неправильно спроектирован. Стремление к высокой связности — это постоянная работа по улучшению архитектуры на каждом уровне кода.
В программной инженерии выделяют семь уровней cohesion, расположенных от наихудшего к наилучшему. Понимание этой шкалы позволяет объективно оценить качество модуля и понять, в каком направлении двигаться при рефакторинге. Чем выше уровень, тем более поддерживаемым и понятным будет код.
Случайная (coincidental) — наихудший уровень, когда элементы в модуле сгруппированы случайно, без какой-либо логической связи. Пример: класс Utilities, в котором собраны методы форматирования даты, отправки email и расчёта скидки. Такой класс невозможно понять без чтения всех методов, и изменение одного метода может сломать другие только потому, что они находятся рядом.
Логическая (logical) связность — элементы выполняют логически связанные, но разные по сути задачи. Класс с методами parseJSON, parseXML и parseCSV логически связан темой «парсинг», но каждый метод делает принципиально разную работу. Проблема: при добавлении нового формата (YAML) класс растёт, и его интерфейс становится раздутым.
Временная (temporal) связность — элементы сгруппированы по времени выполнения. Класс AppInitializer, который настраивает базу данных, загружает конфиг, инициализирует аналитику — всё это происходит при запуске приложения, но сами задачи не связаны между собой. Лучше разделить их на отдельные Initializer для каждой зоны ответственности.
Процедурная (procedural) связность возникает, когда элементы объединены последовательностью выполнения. Модуль «Обработка заказа» содержит методы validateCart, processPayment, sendConfirmation — каждый метод вызывается строго после предыдущего. Это лучше случайной или логической связности, но всё ещё не идеал: каждый шаг может быть вынесен в отдельный модуль.
Коммуникационная (communicational) связность — элементы работают с одними и теми же данными. Класс UserService с методами getUser, updateUser, deleteUser объединён общей сущностью User. Это существенно лучше процедурной связности: класс имеет чёткую предметную область. Большинство Repository классов в мобильных проектах имеют коммуникационную связность.
Функциональная (functional) связность — наивысший уровень, когда каждый элемент модуля участвует в выполнении одной задачи. Класс PasswordValidator с единственным методом validate, который проверяет длину, наличие символов и сложность пароля — пример функциональной связности. Если такой класс меняется, то только потому, что изменились правила валидации паролей.
Достижение функциональной связности — основная цель архитектурного рефакторинга. Каждый класс должен иметь ровно одну причину для изменения. В мобильной разработке функциональная связность достигается через выделение отдельных Use Cases, кастомных View, форматтеров и валидаторов. Каждый такой класс — законченный строительный блок с чёткой зоной ответственности.
Cohesion и coupling — две стороны одного качества. Чем выше cohesion внутри модуля, тем, как правило, ниже coupling между модулями. Хорошо спроектированная система одновременно стремится к высокой связности внутри и слабой связанности снаружи. Это правило признаётся фундаментальным в программной инженерии с 1970-х годов.
Соотношение cohesion-coupling можно представить как баланс. Если разработчик жертвует cohesion, объединяя в одном классе несколько задач, соседние модули получают больше зависимостей — им приходится обращаться к этому перегруженному классу для разных целей, что увеличивает coupling. И наоборот, разбиение на мелкие высокосвязные классы уменьшает количество точек взаимодействия между модулями.
На практике это означает: когда вы выделяете новый класс с функциональной связностью, вы одновременно избавляете другие модули от необходимости знать детали его реализации. Например, выделив EncryptionManager в отдельный класс с функциональной связностью, вы даёте другим модулям простой интерфейс encrypt/decrypt без необходимости понимать детали алгоритма шифрования.
// Низкая связность — класс делает всё сразу
class UserManager {
fun fetchAndSaveUser(id: String) { }
fun parseUserJson(json: String): User { }
fun displayUserName(user: User): String { }
fun validateEmail(email: String): Boolean { }
}
// Высокая связность — каждый класс решает одну задачу
class UserRepository {
fun fetchUser(id: String): User { }
}
class UserJsonParser {
fun parse(json: String): User { }
}
class UserNameFormatter {
fun format(user: User): String { }
}
class EmailValidator {
fun isValid(email: String): Boolean { }
}
Пример показывает разницу: UserManager имеет логическую связность — все методы про пользователей, но каждый делает принципиально разную работу. После рефакторинга каждый класс имеет функциональную связность, а coupling снижается, потому что другие модули зависят только от нужного им класса, а не от всего UserManager.
LCOM (Lack of Cohesion of Methods) — наиболее известная метрика для измерения связности класса. LCOM подсчитывает, сколько пар методов не используют общие поля. Значение 0 означает идеальную связность (все методы работают с одними полями), высокое значение — низкую связность. LCOM4 (усовершенствованная версия) учитывает транзитивные связи через другие методы.
В Android-разработке метрики связности можно получить через Detekt с правилом TooManyFunctions. Классы с десятком методов, использующих разные группы полей, скорее всего имеют низкую связность. В iOS SwiftLint имеет правило file_length и function_body_length — косвенные индикаторы: длинные файлы и методы часто сигнализируют о низком cohesion.
Ручной способ оценки: задайте вопрос «Этот класс изменится по одной причине или по нескольким?» Если вы можете назвать более одной независимой причины — класс имеет низкую связность. Второй тест: «Можно ли разделить этот класс на два независимых класса?» Если да — делайте это. Регулярная проверка cohesion на код-ревью предотвращает появление God классов и снижает технический долг.
Первый шаг — применить Single Responsibility Principle. Каждый класс должен иметь одну чёткую ответственность. Если в классе есть метод, который не относится к его основной задаче, вынесите его в отдельный класс. Техника Extract Class или Extract Delegate в IDE автоматизирует этот процесс. После выделения проверьте, стал ли исходный класс более сфокусированным.
Второй шаг — использовать паттерн Facade для упрощения интерфейса. Если класс предоставляет 20 методов, из которых клиенты используют только 3-4, возможно, у класса низкая связность — он предоставляет слишком много разной функциональности. Сгруппируйте методы по темам и выделите отдельные классы для каждой группы, а исходный класс сделайте фасадом или удалите.
Третий шаг — обратить внимание на группы полей. Если у класса есть поля, которые используются только частью методов — это индикатор низкого cohesion. Разделите класс по группам полей. Например, если класс содержит поля userRepository, networkClient и analyticsTracker, но методы первой группы используют только userRepository, а второй — networkClient — это два разных класса.
Четвёртый шаг — избегать создания «утилитных» классов с произвольными static методами. Каждый static метод, находящийся в классе Utils или Helpers — кандидат на выделение в специализированный класс. FormatUtils.dateToString лучше перенести в DateFormatter, а ValidationUtils.isValidEmail — в EmailValidator. Это повышает cohesion каждого класса и делает код самодокументируемым.
Часто задаваемые вопросы
Почти всегда. Функциональная связность делает код понятным и предсказуемым. Однако доведение до крайности может привести к избыточному дроблению: когда для каждой операции создаётся отдельный класс, и архитектура становится излишне сложной. Баланс — несколько классов на фичу, каждый с функциональной связностью.
Cohesion — метрика внутренней согласованности одного модуля или класса. Modularity — архитектурный принцип, при котором приложение разбивается на физические модули. Высокий cohesion является целью при проектировании как отдельных классов, так и целых модулей.
Detekt для Android и Xcode Analyzer для iOS подсвечивают классы с подозрительно большим количеством методов или полей. IntelliJ IDEA и AppCode имеют визуализацию зависимостей — вы можете увидеть граф связей и обнаружить классы с низкой связностью. SonarQube считает метрики LCOM автоматически.
Да. Интерфейс с методами connect, disconnect и isConnected имеет высокую связность — все методы относятся к управлению соединением. Интерфейс с методами connect, parseData и renderUI имеет низкую связность. Принцип Interface Segregation (SOLID) требует создавать узкоспециализированные интерфейсы с высокой связностью.
Задайте три вопроса: можно ли описать назначение класса одним предложением? Все ли методы поддерживают это назначение? Есть ли в классе поля, которые не используются частью методов? Если ответ на любой вопрос отрицательный — cohesion низкий, и класс стоит разделить.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также