Коннасенс
Коннасенс — точная таксономия связанности: два компонента коннасны, когда изменение одного вынуждает менять другой. Два правила (предпочитай слабее, предпочитай локальнее) дают объективно сравнивать связанность двух дизайнов, а не на вкус.
Ты на ревью дизайна, и кто-то говорит: «тут слишком сильная связанность». Все кивают. Но связанность как именно? Двое ревьюеров тычут в разные строки, оба называют это «связанностью», и спор растворяется во вкусовщине — твой вкус против их, и нет способа его разрешить. «Связанность» одним словом — слишком тупой инструмент, чтобы им спорить.
Коннасенс — это лекарство: словарь, который называет вид связанности и ранжирует каждый вид по тому, насколько он болит. Два куска кода коннасны, когда изменение одного вынуждает менять другой, чтобы программа осталась корректной. Как только ты можешь назвать вид — «это коннасенс позиции, а этот рефакторинг превращает его в коннасенс имени» — связанность перестаёт быть вопросом мнения и становится тем, что можно измерить и сравнить.
После этого урока ты можешь дать определение коннасенса и сформулировать его два правила (слабее лучше, чем сильнее; локальнее лучше, чем дальше); назвать частые статические формы (имя, тип, смысл, позиция, алгоритм) и динамические формы (порядок выполнения, тайминг, значение, идентичность); отрефакторить вызов с позиционными аргументами в объект-опции и объяснить, почему это объективно более слабая связанность; и не попасться в ловушку отношения к коннасенсу как к мелочёвке вместо конкретного компаса для рефакторинга.
Коннасенс — это связанность с точным определением: два компонента коннасны, если изменение одного требует изменить другой, чтобы остаться корректным. Вот и всё — но точность важна. Она даёт бинарный тест для любых двух кусков кода: если я меняю A, обязан ли я также менять B, чтобы программа осталась корректной? Если да — они коннасны, и теперь можно задать единственные два вопроса, которые имеют значение: насколько сильный коннасенс и насколько далеко друг от друга живут эти два куска.
// Эти две строки коннасны: вызывающий и функция
// договорились, что ВТОРОЙ аргумент — это таймаут. Поменяй порядок
// в createConn — и этот вызов молча неверен, но всё ещё компилируется.
function createConn(host: string, timeoutMs: number) { /* ... */ }
createConn("db.internal", 5000);Пока здесь ничего «неправильного» нет. Суть в том, что существует скрытая договорённость, и остальной урок — о том, как называть такие договорённости и ранжировать их, чтобы сокращать их осознанно.
Статический коннасенс — видимый при чтении кода — приходит в порядке силы, который можно запомнить. От слабейшего к сильнейшему: имя (два места должны согласовать имя, например функция и её вызывающий разделяют taxFor), тип (должны согласовать тип), смысл/соглашение (должны согласовать, что 0 означает «гость», а 1 — «админ» — магическое значение), позиция (должны согласовать порядок вещей, например позиционные аргументы) и алгоритм (должны согласовать общее вычисление, например обе стороны должны хешировать пароль одинаково).
// Коннасенс смысла: каждое место должно «знать», что 1 = админ.
if (user.role === 1) showAdminPanel(); // 1 означает... что?
// Коннасенс имени (слабее): договорённость — это общее имя.
if (user.role === Role.Admin) showAdminPanel();Именованная версия — более слабый коннасенс, чем магическая 1, и именно поэтому «заменить магическое число на именованную константу» — это реальное улучшение, а не просто украшение: оно сдвигает тебя вниз по лестнице силы.
Динамический коннасенс — видимый только в рантайме — строго хуже, потому что договорённость невидима в исходнике. Формы: порядок выполнения (B должно выполниться после A, например open() до read()), тайминг (гонка — корректность зависит от когда, а не только от порядка), значение (несколько значений должны меняться вместе, чтобы остаться согласованными, например инвариант min/max или скидка, которая должна совпадать с итогом) и идентичность (две ссылки должны указывать на один и тот же экземпляр объекта, а не просто на равные).
// Коннасенс порядка выполнения: вызывающие обязаны вызывать это
// по порядку, и ничто в типах об этом не говорит. Переставь их —
// и оно компилируется, выполняется и портит состояние.
txn.begin();
txn.write(row);
txn.commit(); // должно быть последним — но кто это обеспечит?Статический коннасенс можно найти, поискав по коду; динамический находишь, отлаживая прод. Вот почему зависимость от порядка или инвариант общего изменяемого значения — более громкий запах, чем позиционный аргумент: то же семейство, хуже локальность боли.
Два правила превращают таксономию в компас: предпочитай более слабый коннасенс и предпочитай более локальный коннасенс. Сила говорит, какой вид дороже сопровождать; локальность говорит, что тот же вид намного дешевле, когда оба конца сидят рядом. Коннасенс позиции между двумя аргументами одной приватной функции тривиален — оба конца на одной строке, которую ты контролируешь. Тот же коннасенс позиции в публичном 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-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.