Розділ 10. Складові діяльності¶
«Зроби задачу» — так узагальнено звучить більшість формулювань, коли ми говоримо про роботу з моделлю. Але просто однієї задачі не існує. Є п’ять різних дій, які ми часто називаємо одним словом і модель добре робить одні з них, посередньо інші, а одну не може зробити за нас у принципі.
У розділі 2 я говорив про декомпозицію задачі як підсилювач власного різноманіття. Цей розділ — про декомпозицію іншого роду: не того, що робимо, а того, як робимо. Розкласти доведеться саму діяльність.
П’ять дій замість однієї¶
Будь-яка інженерна задача має п’ять складових.
Зрозуміти. Скласти власну картину того, з чим маєте справу: про що взагалі ця задача і навіщо вона комусь потрібна; як влаштована та частина системи, яку доведеться міняти, і від чого вона залежить; де межі задачі — що входить, а що свідомо лишається за дужками; і що може піти не так — де крихкі місця, які наслідки, якщо зламається. З цієї картини виводяться і специфікація, і критерії перевірки, і рішення, чи готове це потрапити в систему. І ще одне рішення живе саме тут: чим цю задачу робити — руками, з моделлю як засобом чи віддати агентові. Це прикидка за законом розділу 2: чи вистачить вашого різноманіття на те, що ви збираєтесь запустити.
Специфікувати. Перекласти наше розуміння в текст, з яким можна працювати: що має вийти, чого не має статися, як ми дізнаємось, що готово. Це ті самі проксі й опис цілі з розділу 6 — тільки тепер видно, звідки вони беруться: з розуміння, і ні з чого іншого. Специфікація без картини за спиною — набір слів, які модель радо доповнить власними припущеннями.
Згенерувати. Отримати сам артефакт — код, міграцію, конфіг, тест, шматок документації. Єдина зі складових, де результат можна побачити й помацати за хвилину, — і тому саме її ми звикли вважати «роботою». Насправді це найдешевша з п’яти: варіантів тут можна отримати скільки завгодно, і жоден із них сам по собі нічого не вартий, доки не пройшов наступну складову.
Перевірити. Переконатися на двох рівнях: що згенероване відповідає специфікації — і що специфікація досі відповідає розумінню, бо в процесі часто з’ясовується, що картина була неповною. Перше роблять тести, автоматичні перевірки в CI і рев’ю; друге — тільки людина, яка пам’ятає, навіщо все це. Зелений пайплайн закриває перший рівень і нічого не каже про другий.
Інтегрувати. Вбудувати в живу систему — і в живу команду. Мерж, деплой, міграція даних, зміна в документації — це видима частина. Невидима: у голові команди має з’явитися нове знання про систему — що змінилося, чому саме так, де тепер крихке місце. Без цієї частини код у продакшні, а розуміння — ні, і наступна задача в цьому модулі почнеться з нуля. Ця складова може тягнутися тижнями, тоді як генерація — один день. Адже інтеграція — це не деплой коду, а нове знання в команді і нова поведінка в житті ваших користувачів.
Чому саме складові, а не етапи? Етапи йдуть по черзі. Складові — незалежні: жодна не виводиться з решти. Можна ідеально специфікувати те, чого не зрозумів, — специфікація буде бездоганною і хибною. Можна ідеально перевірити те, що ніколи не інтегрується. Можна згенерувати тонну коду, не перевіривши ані рядка. Кожна складова робиться або не робиться окремо, і успіх однієї нічого не каже про іншу.
З незалежності випливають дві закономірності.
Перша: у кожної складової своя крива. Вони ростуть окремо і атрофуються окремо. Інженер може за рік удвічі прискорити генерацію і водночас непомітно втратити навичку читати чужий код — бо генерація йде щодня, а на читання чужого коду і чужих рішень уваги вже не лишається. Складова, яку не живиш, виснажується — незалежно від того, як блискуче йдуть решта чотири.
Друга: ланцюг стійкий, лише коли замкнений. «Інтегрувати» — не кінець, а вхід у «зрозуміти» наступної задачі: те, що ви вбудували сьогодні, — це те, що вам доведеться розуміти завтра. Якщо коло розірване і розуміння не повертається — кожна наступна задача починається з меншого знання про систему, ніж попередня. Ви рухаєтесь уперед по кодовій базі, яка щодня стає для вас чужішою.
Руки на штурвалі¶
Авіація прожила цю історію на сорок років раніше за нас.
Автопілот з’явився не для того, щоб замінити пілота, — а щоб зняти з нього одну складову. Тримати курс і висоту годинами — робота монотонна, і машина робить її краще: рівніше, без утоми, без відволікань. Це чиста генерація: є специфікація — курс, висота, швидкість, — і автопілот її виконує. Пілоти віддали цю складову повністю і правильно зробили.
Що ж лишилося в кабіні? Пілот і далі розуміє ситуацію: погода попереду, стан борту, що означає ця вібрація. Він і далі специфікує — задає автопілоту режим і параметри. Він і далі перевіряє — зіставляє прилади з тим, що відчуває сам. І він інтегрує — вписує політ у розклад, диспетчерів, екіпаж. Автопілот забрав одну з п’яти складових. Чотири лишилися — і тримати їх стало важче. Поки руки на штурвалі, увага тримається сама: літак щосекунди вимагає її через зусилля і вібрацію. Коли курс тримає автоматика, рукам нема чого робити — а стежити за системою, яка все робить сама і добре, виявилося окремою важкою роботою, в якій людина слабка.
І ось що з’ясувалось за десятиліття. Найгірше екіпажі показують себе не тоді, коли автопілот працює, і не тоді, коли його немає. Найгірше — у момент, коли він вимикається і повертає літак людині. Пілот, який роками лише вводив параметри, раптом мусить відчувати машину руками — а складова атрофувалась, бо її не живили. Літак справний, погода прийнятна, і екіпаж все одно втрачає його, тому що розуміння ситуації давно живе не в пілоті, а в системі, яка щойно сказала «далі ви самі». В авіації для таких пілотів є невеселе прізвисько — діти пурпурової лінії: лінії маршруту на дисплеї, за якою вони звикли летіти, не питаючи, куди вона веде.
Авіація відповіла на це не відмовою від автопілота. Вона відповіла двома правилами. Перше — про режими. Автопілот має кілька рівнів автономії, від «тримай лише висоту» до «лети за маршрутом і сам заходь на посадку», і пілот у кожну секунду мусить знати, в якому режимі він зараз, що робить автоматика, а що лишилось на нього; кожне перемикання режиму в кабіні промовляють уголос. Друге — обов’язкові години ручного пілотування, щоб складова, яку віддали машині, не померла в людині зовсім.
Це і є метод цього розділу. Далі — з чого він складається.
Хто тут вирішує¶
Усередині кожної з п’яти складових є три позиції. Суб’єкт — той, хто вирішує: ставить питання, обирає, приймає. Засіб — те, через що робиться дія. Об’єкт — те, над чим вона робиться.
Візьмімо «зрозуміти» незнайомий модуль. Об’єкт — модуль. Засіб — редактор, дебагер, git blame, і тепер модель. У полі вводу ваш промпт: «покажи, де викликається ця функція». Суб’єкт — ви: питання ваше, і ви вирішуєте, яким буде наступне і коли розуміння досить, щоб рухатися далі. Модель тут — потужніший засіб, ніж усе, що було досі. Але засіб.
Тепер поміняймо в промпті кілька слів: «поясни мені цей модуль». Об’єкт той самий. Засіб той самий. А суб’єкт? Хто вирішує, що в модулі важливе, а що ні, що згадати, а що пропустити, коли пояснення досить? Модель. Питання тепер ставить собі вона сама — свої, правдоподібні, з розділу 6. Ви прочитаєте пояснення й повірите, що зрозуміли, — а розуміння сформувала не ваша голова.
Ось механізм, який робить чотири ролі з розділу 2 такими підступними: з капітана в пасажира переходять не рішенням — формулюванням. Ніхто не каже «віддаю розуміння машині». Кажуть «поясни» замість «покажи, де». І з цього моменту хід роботи задає засіб: модель видала рішення — і ви правите його, шматок за шматком, у тій послідовності, в якій вона його згенерувала. Робота кипить, але її план склали не ви.
Тому в кожній складовій питання одне: хто тут вирішує? Не «чи бере участь модель» — вона бере участь всюди, і це нормально. А хто ставить питання і хто приймає відповідь.
Карта делегування¶
Тепер сама карта. У кожної складової — свій рівень залучення моделі: одну віддаємо повністю, три — частково, одну — ніколи.
Згенерувати — повністю. Це складова, яку варто віддавати без жалю — так, як пілоти віддали утримання курсу. Модель генерує швидше, рівніше і не втомлюється. Ваша позиція тут — не писати самому «щоб не втратити навичку»: навичка писати код руками цінна рівно настільки, наскільки вона живить розуміння, а живлять його читання й перевірка, не набір. Віддаючи генерацію повністю, ви лишаєтесь суб’єктом на вході (специфікація) і на виході (перевірка) — і цього досить.
У наступних трьох складових модель — засіб, а суб’єкт — ви, і межа проходить точно посередині складової.
Специфікувати — частково. Модель може розширити ваш опис цілі в критерії приймання, знайти неврахований випадок, запропонувати структуру. Але що є ціллю, вирішуєте ви: проксі, яку сформулювала модель для себе сама, оптимізуватиме модель.
Перевірити — частково. Модель напише тести, зробить перше рев’ю, знайде очевидне. Але тест, який задумали не ви, перевіряє не ваше розуміння, а її. Тому критерії — ваші, і хоча б один тест на кожен із них теж.
Інтегрувати — частково. Модель зробить мерж, підправить конфіг, напише реліз-нотатку. Але рішення «це готове потрапити в систему» приймає той, хто відповідатиме за систему о третій ночі.
Зрозуміти — ніколи. І тут потрібне уточнення, без якого правило прочитають хибно. «Ніколи» не означає «без моделі». Модель — найкращий засіб розуміння, який траплявся інженерам: вона відповідає на питання «де», «чому», «що буде, якщо», за секунди, з будь-якої точки кодової бази. Користуйтеся щедро. Віддавати не можна інше — позицію суб’єкта: дозволити моделі вирішити, що тут важливо, і прийняти це рішення, не поставивши власного питання. Тест простий: після сесії розуміння ви можете пояснити модуль колезі без моделі під рукою? Якщо так — розуміння ваше. Якщо ні — воно лишилося в сесії і закінчиться разом із нею.
Чому саме ця складова не делегується, коли решта — так? Не тому, що модель гірше розуміє код. Часто краще. А тому, що розуміння — точка відліку для решти чотирьох. Хто розуміє, той визначає, що взагалі є задачею: де її межі, що вважати успіхом, що можна не чіпати. Специфікація, перевірка й інтеграція виконуються з позиції того, хто розуміє. Якщо ця позиція — у моделі, то всі чотири інші складові ви виконуєте з чужої позиції, і виконуєте їх як об’єкт, навіть якщо натискаєте всі кнопки самі.
Є і загальніше правило, за яким можна перевіряти будь-яку складову перед тим, як віддати. Результат складової має триматися сам — за своїм складом, а не за зовнішньою підпорою. Згенерований код тримається сам: він або проходить тести, або ні, і сесія, що його породила, для цього не потрібна. Розуміння, яке живе лише в контексті моделі, — не тримається: заберіть сесію — і в команді немає нікого, хто знає, чому модуль такий. Системи, зібрані зовнішньою силою, стоять рівно доти, доки сила на місці.
Один модуль, дві сесії¶
Задача реальна: додати експорт у CSV до модуля звітів у легасі-сервісі, який писали не ми і не цього року.
Сесія перша. Один промпт: «Ось модуль звітів. Поясни, як він працює, і додай експорт у CSV з тими ж фільтрами, що в поточному HTML-звіті». Модель дала три абзаци пояснення — читав по діагоналі, звучало правдоподібно — і чотириста рядків коду. Тести зелені, рев’ю прочитало код, а не модуль; змержили і в прод.
Через два тижні — тікет: у CSV для одного клієнта не збігаються суми з HTML-звітом. Хто в команді знає, чому? Ніхто. Модуль розуміла сесія, якої немає. Я відкрив новий чат, попросив пояснити знову — і дістав інше пояснення, теж правдоподібне. Після двох годин дебагу з’ясували: у модулі було два шляхи фільтрації — старий і новий, під фіча-атрибутом, — і CSV підключився до старого, бо модель обрала правдоподібніший із двох. Три абзаци пояснення про цей атрибут не згадували. Не тому, що модель сховала, — тому, що ніхто не спитав.
Сесія друга, інша задача в тому самому модулі, за методом. Спершу — година розуміння, модель як засіб: «покажи всі місця, де застосовуються фільтри», «чим відрізняються ці два шляхи», «хто вмикає цей атрибут і для кого». Питання мої, відповіді її, і після кожної — моє рішення, куди копати далі. За годину я міг намалювати модуль на дошці — і намалював, для себе. Потім специфікація: п’ять критеріїв, серед них — «працює для обох шляхів фільтрації, тест на кожен». Потім генерація — віддана повністю, чотириста рядків за три хвилини. Перевірка: тести на два шляхи написав сам до генерації, рев’ю зробила модель і знайшла проблему з кодуванням, якої я б не помітив. Інтеграція — сам: схема лишилася на дошці, а в документації модуля з’явився абзац — два сліди розуміння, які переживуть сесію.
Рахунок: година «зайвого» читання проти двох годин дебагу і двох тижнів роботи в проді з багом. І ще одна різниця, яку не видно в годинах: після другої сесії модуль розуміє людина в команді. Після першої — ніхто.
Код, якого ніхто не розуміє¶
Анти-патерн цього розділу — найпоширеніший із тих, що я бачу зараз, і найважчий для розпізнавання, бо виглядає як продуктивність.
Команда генерує швидко. Тікети закриваються. Код проходить тести. Але поставте будь-кому питання «чому цей модуль зроблено саме так» — і отримаєте «так згенерувалось». Пояснення коду шукають у моделі, а не в людях; рев’ю читає дифи, а не систему; онбординг нового інженера — це «спитай у чату». Розуміння системи не належить нікому.
Діагноз за правилом цього розділу: складову «зрозуміти» віддано повністю — не рішенням, а тисячею маленьких «поясни» замість «покажи». Система зібрана зовнішньою силою в неприродному порядку — код з’явився раніше за розуміння, — і тримається, доки сила на місці. А сила ця — сесія, яка живе пару годин чи днів.
Такий код не поганий. Він часто кращий за той, що написали б руками. Але він стоїть на підпорі, якої вже немає, і перша нетипова ситуація — баг, який не відтворюється, зміна вимог, що зачіпає два модулі одразу, — це момент, коли автопілот вимикається і каже команді «далі ви самі». І команда, яка роками лише вводила параметри, з’ясовує, що не може відчути машину руками.
Лікується це не переписуванням і не заборонами на генерацію. Лікується поверненням складової. Окрема сесія розуміння над кодом, який уже є: не «поясни», а «покажи, де», «чому тут два шляхи», «що зламається, якщо». Схема на дошці, абзац у документації, розмова на рев’ю про систему замість дифів. Розуміння, яке пережило сесію, — єдиний рядок у результаті роботи, який модель не може згенерувати за вас. Про решту рядків цього результату — розділ 11.
Складові масштабуються¶
Досі ми розкладали одну сесію. Та сама карта працює й рівнями вище.
Агент — наступний рівень. Від чату він відрізняється тим, що виконує кілька складових поспіль без вас: сам ставить собі проміжні задачі, сам генерує, сам перевіряє результат і рухається далі. Тобто агент — це складові, віддані оптом. Питання до нього те саме, що й до однієї сесії, тільки ціна помилки вища: хто суб’єкт у «зрозуміти»? Якщо агент сам вирішує, що в задачі важливо, а що можна пропустити, — ви віддали автопілоту не лише курс, а й розуміння погоди. Як побудувати конструкцію, що тримає агента в позиції засобу, — розділ 14.
Команда — ще один рівень. П’ять складових розподіляються між людьми: хтось розмовляє з замовником, хтось пише вимоги, хтось генерує з моделлю, хтось перевіряє, хтось вбудовує в систему. Сам розподіл нормальний — доки розуміння не зібралося в одній голові. Спокуса та сама, що й з моделлю: віддати «зрозуміти» одному, «хто в темі», а решті лишити виконання. Через пів року цей один іде у відпустку — і команда опиняється в тому самому становищі, що й інженер із модулем, який розуміла лише закрита сесія.
Механіка на всіх рівнях одна. Змінюється тільки те, кому належить розуміння, — і скільки коштує його втрата.
У вашому ремеслі¶
П’ять складових не знають, що таке код. Підставте свій матеріал.
Текст. Редактор, який пише моделі «прочитай ці інтерв’ю і напиши статтю», віддав «зрозуміти»: що в цих інтерв’ю важливе, вирішила модель. Той, хто спершу питає — «які твердження повторюються у трьох джерелах», «де вони суперечать одне одному», — а потім специфікує тезу і віддає генерацію чернетки, лишається автором. Різниця видна через місяць: перший не може відповісти читачеві на питання «звідки це», другий — може.
Аналітика. Дашборд, який модель згенерувала на прохання «покажи, що важливо в цих даних», — картина чужого розуміння. Аналітик, що спершу сформулював три питання до даних, а потім віддав побудову графіків, — суб’єкт; він знає, чого на дашборді немає, і це знання дорожче за сам дашборд.
Дизайн. «Зроби макет для цієї фічі» — генерація і розуміння в одному промпті; макет буде правдоподібним і чужим. Дизайнер, який спершу розібрав сценарій користувача — сам, з моделлю як засобом: «які стани екрана можливі», «що робить користувач, коли дані не завантажились», — а потім віддав генерацію варіантів, отримує макет, який може захистити перед командою.
Наостанок — три питання до вашої практики.
— Візьміть останню задачу з моделлю і розкладіть її на п’ять складових: у якій із них ви були суб’єктом, а в якій — уже ні?
— Чи є у вашій кодовій базі модуль, який розуміє тільки сесія, якої немає?
— І глибше: коли ви востаннє ставили моделі питання «покажи, де» замість «поясни»?
