open atlas
↑ К треку
Паттерны и качество кода CP · 07 · 01

Коннасенс

Коннасенс — точная таксономия связанности: два компонента коннасны, когда изменение одного вынуждает менять другой. Два правила (предпочитай слабее, предпочитай локальнее) дают объективно сравнивать связанность двух дизайнов, а не на вкус.

CP Senior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

Ты на ревью дизайна, и кто-то говорит: «тут слишком сильная связанность». Все кивают. Но связанность как именно? Двое ревьюеров тычут в разные строки, оба называют это «связанностью», и спор растворяется во вкусовщине — твой вкус против их, и нет способа его разрешить. «Связанность» одним словом — слишком тупой инструмент, чтобы им спорить.

Коннасенс — это лекарство: словарь, который называет вид связанности и ранжирует каждый вид по тому, насколько он болит. Два куска кода коннасны, когда изменение одного вынуждает менять другой, чтобы программа осталась корректной. Как только ты можешь назвать вид — «это коннасенс позиции, а этот рефакторинг превращает его в коннасенс имени» — связанность перестаёт быть вопросом мнения и становится тем, что можно измерить и сравнить.

Цель

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

1

Коннасенс — это связанность с точным определением: два компонента коннасны, если изменение одного требует изменить другой, чтобы остаться корректным. Вот и всё — но точность важна. Она даёт бинарный тест для любых двух кусков кода: если я меняю A, обязан ли я также менять B, чтобы программа осталась корректной? Если да — они коннасны, и теперь можно задать единственные два вопроса, которые имеют значение: насколько сильный коннасенс и насколько далеко друг от друга живут эти два куска.

// Эти две строки коннасны: вызывающий и функция
// договорились, что ВТОРОЙ аргумент — это таймаут. Поменяй порядок
// в createConn — и этот вызов молча неверен, но всё ещё компилируется.
function createConn(host: string, timeoutMs: number) { /* ... */ }
createConn("db.internal", 5000);

Пока здесь ничего «неправильного» нет. Суть в том, что существует скрытая договорённость, и остальной урок — о том, как называть такие договорённости и ранжировать их, чтобы сокращать их осознанно.

2

Статический коннасенс — видимый при чтении кода — приходит в порядке силы, который можно запомнить. От слабейшего к сильнейшему: имя (два места должны согласовать имя, например функция и её вызывающий разделяют taxFor), тип (должны согласовать тип), смысл/соглашение (должны согласовать, что 0 означает «гость», а 1 — «админ» — магическое значение), позиция (должны согласовать порядок вещей, например позиционные аргументы) и алгоритм (должны согласовать общее вычисление, например обе стороны должны хешировать пароль одинаково).

// Коннасенс смысла: каждое место должно «знать», что 1 = админ.
if (user.role === 1) showAdminPanel();   // 1 означает... что?
// Коннасенс имени (слабее): договорённость — это общее имя.
if (user.role === Role.Admin) showAdminPanel();

Именованная версия — более слабый коннасенс, чем магическая 1, и именно поэтому «заменить магическое число на именованную константу» — это реальное улучшение, а не просто украшение: оно сдвигает тебя вниз по лестнице силы.

3

Динамический коннасенс — видимый только в рантайме — строго хуже, потому что договорённость невидима в исходнике. Формы: порядок выполнения (B должно выполниться после A, например open() до read()), тайминг (гонка — корректность зависит от когда, а не только от порядка), значение (несколько значений должны меняться вместе, чтобы остаться согласованными, например инвариант min/max или скидка, которая должна совпадать с итогом) и идентичность (две ссылки должны указывать на один и тот же экземпляр объекта, а не просто на равные).

// Коннасенс порядка выполнения: вызывающие обязаны вызывать это
// по порядку, и ничто в типах об этом не говорит. Переставь их —
// и оно компилируется, выполняется и портит состояние.
txn.begin();
txn.write(row);
txn.commit();   // должно быть последним — но кто это обеспечит?

Статический коннасенс можно найти, поискав по коду; динамический находишь, отлаживая прод. Вот почему зависимость от порядка или инвариант общего изменяемого значения — более громкий запах, чем позиционный аргумент: то же семейство, хуже локальность боли.

4

Два правила превращают таксономию в компас: предпочитай более слабый коннасенс и предпочитай более локальный коннасенс. Сила говорит, какой вид дороже сопровождать; локальность говорит, что тот же вид намного дешевле, когда оба конца сидят рядом. Коннасенс позиции между двумя аргументами одной приватной функции тривиален — оба конца на одной строке, которую ты контролируешь. Тот же коннасенс позиции в публичном API, который потребляют двенадцать команд, — это проект миграции. Локальность часто рычаг побольше: обычно нормально иметь сильный коннасенс внутри маленького связного модуля (это и есть связность) и катастрофа — иметь даже слабый коннасенс, пересекающий границы модулей или сервисов.

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

Разбор примера

От коннасенса позиции к коннасенсу имени. Функция с несколькими позиционными аргументами навязывает каждому вызывающему коннасенс позиции: вызывающие и функция должны согласовать порядок, и эта договорённость невидима в месте вызова.

// Коннасенс ПОЗИЦИИ (сильный) в каждом месте вызова.
function createUser(
  name: string,
  email: string,
  isAdmin: boolean,
  sendWelcome: boolean,
): User { /* ... */ }

// В месте вызова булевы нечитаемы и хрупки к порядку:
createUser("Ada", "ada@x.io", false, true);
// Поменяй местами два последних аргумента — оно всё ещё компилируется
// и молча создаёт админа, который не получит приветственное письмо.
// Перестановка в сигнатуре ломает каждого вызывающего, невидимо.

Отрефактори в объект-опции, который превращает связанность в коннасенс имени:

interface CreateUserOptions {
  name: string;
  email: string;
  isAdmin?: boolean;
  sendWelcome?: boolean;
}

function createUser(opts: CreateUserOptions): User { /* ... */ }

// Теперь договорённость по ИМЕНИ, а не по порядку:
createUser({ name: "Ada", email: "ada@x.io", sendWelcome: true });

Улучшились две вещи, и обе можно назвать. Сила: позиция → имя, ход вниз по лестнице, потому что порядок больше не несёт смысла — перестановка полей теперь no-op, а опечатка в имени поля — это ошибка компиляции вместо молчаливой подмены. Локальность не изменилась, но ты ещё и сжал договорённость: необязательные поля можно опускать, поэтому вызывающие фиксируют только те имена, которые реально используют. Вызов читается как документация. В этом весь смысл коннасенса как компаса: ты отрефакторил не «потому что объекты-опции приятнее» — ты отрефакторил, потому что перешёл от более сильной формы связанности к более слабой, и можешь защитить это перед ревьюером одним предложением.

Почему это работает

Зачем вводить целую таксономию, когда «уменьшай связанность» уже существует как совет? Потому что «уменьшай связанность» не даёт способа сравнить два дизайна, оба из которых связаны — а связаны почти все. Коннасенс даёт. Когда у двух предложений обоих есть связанность (а она есть всегда), ты можешь сказать: «у предложения A коннасенс позиции через публичный API; у предложения B коннасенс имени внутри одного модуля — B и слабее, и локальнее, поэтому побеждает B», и это объективный рейтинг, а не предпочтение. Это также останавливает перекоррекцию: расцепить всё невозможно и обычно вредно (ты бы ничего не инлайнил и ничего не разделял). Цель — никогда не нулевой коннасенс; это слабейший, самый локальный коннасенс, который всё ещё выражает реальную связь, нужную коду.

Частая ошибка

Режим отказа — относиться к коннасенсу как к академической мелочёвке: вызубрить девять форм, чтобы выигрывать споры, и потом никогда ими не пользоваться. Таксономия бесполезна как ответ на викторине и ценна только как компас для рефакторинга: ты наводишь его на реальную связанность, и он говорит, какое направление «лучше». Если ты можешь продекламировать «коннасенс смысла», но это не меняет того, что ты делаешь на ревью, ты выучил слова и упустил инструмент. Обратная ошибка так же дорога: гнаться за каждым слабым локальным коннасенсом до нуля. Коннасенс имени между функцией и её тремя вызывающими в одном файле — это норма: это здоровая связанность. Трать бюджет там, где и сила, и расстояние высоки. Компас указывает; он не приказывает тебе маршировать повсюду.

Проверь себя
Викторина

Ты заменяешь функцию с 4 позиционными аргументами на функцию, принимающую единственный объект-опции, используемую во многих местах вызова. В терминах коннасенса — что именно улучшилось и почему это объективный выигрыш, а не вопрос стиля?

Итог

Коннасенс — это связанность с определением, достаточно острым, чтобы им спорить: два компонента коннасны, когда изменение одного вынуждает менять другой, чтобы остаться корректным. Статические формы видны в исходнике и ранжируются от слабейшей к сильнейшей — имя, тип, смысл, позиция, алгоритм; динамические формы проявляются только в рантайме — порядок выполнения, тайминг, значение, идентичность — и хуже, потому что договорённость невидима, пока что-нибудь не сломается. Два правила превращают это в компас: предпочитай более слабый коннасенс и предпочитай более локальный коннасенс, причём локальность часто рычаг побольше. Рефакторинг позиционные аргументы → объект-опции — канонический ход: позиция становится именем, защитимый шаг вниз по лестнице. Пользуйся коннасенсом, чтобы объективно сравнивать связанность двух дизайнов вместо вкуса — и сопротивляйся парным режимам отказа: копить словарь как мелочёвку и гнаться за каждой слабой локальной связанностью до нуля.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.