Код больше не пишут

Глава 4. Поменять одно, не сломав всё

Часть II. Заплатка и организм9 мин

04

Поменять одно, не сломав всё

Стойка администратора в маникюрном салоне вечером: журнал записи, радиотелефон, полузакрытый ноутбук, за дверью пустые столы мастеров.иллюстрация
Администратор просит, чтобы клиентки могли указывать несколько телефонов. Изменение затронет семь звеньев системы.

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

Агент справляется минуты за три. В форме появляется кнопка «добавить ещё телефон», в базе данных появляется место для второго и третьего номера, администратор проверяет, что всё сохраняется и открывается, и радостно закрывает задачу. Всё работает так, как просили.

Недели через две выясняется, что напоминания перестали приходить примерно трети клиенток. Ещё через неделю одна из мастеров звонит клиентке по второму номеру, который оказался номером её мужа, и узнаёт о семейной жизни клиентки больше, чем та хотела бы. В конце месяца у бухгалтера не открывается выгрузка: программа, которую бухгалтер когда-то настроил для обработки таблицы, ожидала в колонке «телефон» один номер и не знает, что делать со списком.

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


Разработчики называют изменение кода словом diff, от английского difference, «разница»: это список отличий между тем, как программа выглядела до правки и как выглядит после, примерно как режим исправлений в текстовом редакторе, где зачёркнутое показано красным, а добавленное зелёным. Когда вы просите модель что-нибудь поменять, она производит именно такой список отличий и старается сделать его хорошим: аккуратным, понятным, решающим поставленную задачу.

Языковая модель по своей природе очень хорошо решает задачу «сделай изменение X». Настоящая инженерная задача почти всегда формулируется иначе: сделай изменение X так, чтобы после него вся система осталась целой. Между этими двумя формулировками лежит почти вся профессия, и маникюрный салон показывает, где именно.

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

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

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

Есть ещё вопрос прав. Раньше мастер видел телефон клиентки, потому что ему надо иногда предупредить о переносе. Должен ли мастер видеть все три номера, включая номер мужа? Скорее всего, нет, но правило об этом никто не записал, потому что до сегодняшнего дня второго номера не существовало.

А ещё телефон клиентки показывается в календаре мастера, в карточке клиентки, в панели администратора, в письме с подтверждением записи и в чеке, и каждое из этих мест надо научить показывать список или выбирать из него один номер.

Остаются проверки. У программы были автоматические тесты, которые проверяли, что телефон сохраняется и что напоминание уходит, и написаны они были для одного телефона. Если после изменения они упали, это хорошая новость, потому что так система сообщила о проблеме. Если агент, увидев упавшие тесты, поправил их так, чтобы они снова проходили, новость плохая, и об этом будет отдельная глава.

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


Сама проблема стара как программирование. В 1978 году на конференции в Чикаго трое исследователей, Стивен Яу, Джеймс Коллофелло и Т. Макгрегор, представили работу о том, что они назвали эффектом ряби: изменение в одной части программы расходится кругами по остальным, как круги от камня по воде, и чем сложнее программа, тем дальше эти круги уходят. Они даже придумали способ заранее оценивать, насколько широко разойдётся рябь от конкретной правки. С тех пор умение предсказывать, куда пойдёт рябь, считается одним из главных признаков опытного инженера. Новичок видит место, которое просили поменять. Опытный инженер видит, что ещё сдвинется вместе с ним.

Есть у этой проблемы и более поздняя и более жестокая формулировка. Инженер Google Хайрам Райт заметил, что если у программы достаточно много пользователей, то кто-нибудь из них обязательно будет зависеть от любой её особенности, которую можно наблюдать снаружи, независимо от того, обещали эту особенность или нет. Выгрузку для бухгалтера никто не обещал делать в формате «один телефон в колонке», так просто получилось, но бухгалтер на это опирался, и изменение сломало договорённость, которой официально не существовало. Программисты называют это законом Хайрама и вспоминают его всякий раз, когда безобидная правка ломает что-нибудь в соседнем отделе.


Серверная: восемь одинаковых стоек в ряд, у одной приоткрыта дверца и огоньки горят иначе, техник уходит к выходу.иллюстрация
Knight Capital, 1 августа 2012 года: новый код поставили на семь серверов из восьми, и за сорок пять минут компания потеряла больше 460 миллионов долларов.

Самая дорогая иллюстрация того, как выглядит одно маленькое изменение в большой системе, случилась первого августа 2012 года с американской компанией Knight Capital, одним из крупнейших тогда посредников на фондовом рынке. Нью-Йоркская биржа запускала новую программу для частных инвесторов, и Knight написала для неё новый код. В торговой системе компании давно лежал старый кусок кода под названием Power Peg, которым перестали пользоваться ещё в 2003 году, но так и не удалили. Когда-то в нём был счётчик, который следил, сколько акций по заявке уже куплено, и останавливал отправку приказов, как только заявка была исполнена. В 2005 году этот счётчик перенесли в другое место программы, а проверить, работает ли после этого Power Peg, никому не пришло в голову, потому что им всё равно никто не пользовался. Новый код, как выяснила потом комиссия по ценным бумагам США, заново использовал переключатель, которым раньше включался этот старый кусок. Сам по себе это разумный ход: переключатель есть, он свободен, почему бы им не воспользоваться.

Новый код нужно было установить на восемь серверов. Техник установил его на семь. Никто этого не заметил, потому что никакой проверки, которая сравнила бы восемь серверов между собой, не было. Утром первого августа, когда открылись торги, переключатель включили, и на семи серверах заработал новый код, а на восьмом проснулся старый Power Peg, который получил настоящие заказы клиентов и начал их исполнять, но уже без счётчика, который когда-то велел ему остановиться. Чтобы исполнить 212 клиентских заявок, система за сорок пять минут отправила на рынок больше четырёх миллионов приказов и купила и продала в общей сложности 397 миллионов акций. К моменту, когда её остановили, компания потеряла больше 460 миллионов долларов, у неё начались проблемы с собственным капиталом, требуемым по закону, а меньше чем через год она слилась с другой торговой фирмой и перестала существовать как самостоятельная компания.

В этой истории нет ни одного глупого решения, если рассматривать каждое отдельно. Новый код, судя по всему, был написан правильно. Переиспользовать свободный переключатель — обычная практика. Техник ошибся так, как ошибаются все люди. Катастрофа возникла на стыках: между изменением 2005 года и кодом, который после него никто не проверял, между новым кодом и старым, который забыли удалить, между переключателем и его прошлым значением, между восемью серверами, которые должны были быть одинаковыми. Каждое звено по отдельности выглядело исправным, а вместе они обрушили компанию.

ОпытСорок пять минут Knight Capital
Восьмой сервер, до которого техник не дошёл, и сверка серверов, которой у компании не было. Итоговые цифры из разбора комиссии по ценным бумагам США.

Knight Capital разорилась за сорок пять минут, но чаще сеть рвётся медленно, и тогда это похоже скорее на хроническую болезнь, чем на аварию. В июне 2026 года я разбирал устройство одной программной компании, в которую к тому времени вложили около полутора миллионов долларов. Жалоба у владельцев была одна, и формулировали они её почти дословно одинаково: каждая новая функция ломает старую. Интерфейс и серверная часть за несколько лет превратились в зоопарк, где у каждого зверя свои повадки, и никто уже не мог сказать, где у данных источник правды: одна и та же цифра жила в трёх местах и в каждом была немного своя. Никакого единственного плохого решения я там не нашёл. Там были сотни разумных изменений, каждое из которых сделали, не спросив, что сдвинется по соседству, и за полтора миллиона долларов компания купила себе систему, которую страшно трогать.


Языковые модели особенно уязвимы именно в этом месте, и причина вполне техническая. Модель видит то, что ей показали: файлы, которые попали ей на глаза, описание задачи, результат последней проверки. Всё остальное для неё не существует. Если служба напоминаний лежит в другом файле, до которого модель не дошла, она не знает, что служба читает телефон из старой ячейки. Если выгрузку для бухгалтера делает отдельная программа на компьютере самого бухгалтера, модель не узнает о ней никогда. Хорошие агенты умеют искать по всему проекту, и с каждым годом ищут лучше, но искать они будут то, что им велели, и остановятся, когда задача будет выглядеть решённой.

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

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

Большинство людей, которые сегодня впервые садятся собирать программу с помощью ИИ, о существовании этого вопроса не знают, и это не их вина: раньше, чтобы о нём узнать, надо было несколько лет писать код и регулярно получать по рукам.