От переводчика.

Под катом я помещаю для вас перевод статьи знаменитого и влиятельного инженера из Кремниевой Долины Дэвида Крошо (David Crawshaw), сооснователя и технического директора (CTO) компании Tailscale. Ранее Дэвид более 9 лет работал программистом-исследователем в компании Google и в настоящее время является одним из самых авторитетных практикующих специалистов по языку Go. В частности, именно Дэвид адаптировал Go для платформ iOS и Android. В статье Дэвид делится своими наблюдениями о том, какую работу программист может и должен поручать большим языковым моделям, какие подводные камни есть в этом искусстве, и как оно может развиваться в ближайшие годы. Далее - от автора.

В этом документе я обобщил мой собственный опыт, приобретённый за минувший год и связанный с использованием генеративных моделей при программировании. Не скажу, что это был пассивный процесс. Я намеренно изыскивал возможности воспользоваться БЯМ при программировании, чтобы подробнее их изучить. В результате я теперь постоянно применяю БЯМ при работе и считаю, что в сухом остатке они положительно влияют на мою продуктивность (попробовал вернуться к программированию без них — опыт не понравился).

За этой работой я смог выделить часто повторяющиеся шаги, которые можно оптимизировать, и сейчас мы с единомышленниками разрабатываем инструмент именно для этой цели, ориентированный на работу с Go: sketch.dev. Это только начало, но пока имеющийся опыт радует.

Контекст

Обычно мне любопытно узнавать о новых технологиях. Поэкспериментировав самую малость с БЯМ, я задумался, можно ли извлечь из них практическую пользу. Технология, которая (хотя бы иногда) способна выдавать отточенные ответы на каверзные вопросы, располагает к себе. Ещё увлекательнее наблюдать, как компьютер пытается по сформулированному запросу написать фрагмент программы — и серьёзно в этом прогрессирует.

На моей памяти был только один технологический переход, субъективно сравнимый с нынешним: дело было в 1995 году, когда мы впервые сконфигурировали локальную сеть с применением рабочего маршрута по умолчанию. У нас в соседней комнате стоял совместно используемый компьютер, на котором действовала программа Trumpet Winsock, и мы заменили его машиной, которая поддерживала маршрутизацию соединения через диал-ап. Тогда в один момент Интернет оказался у меня под рукой. В тот момент казалось ошеломительным иметь бесперебойный доступ к Интернету, казалось, что мы в будущем. Пожалуй, я тогда это ощущал острее многих других, привыкших к Интернету в университетах, поскольку я сразу окунулся в новейшие Интернет-технологии: веб-браузеры, JPEG, миллионы людей. Доступ к мощным БЯМ ощущается примерно так же.

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

Обзор

При повседневном программировании я обычно использую БЯМ в трёх следующих качествах:

1.     Автозавершение. Мне удаётся работать продуктивнее, если модель сама набирает за меня относительно очевидные фрагменты текста. По-видимому, в нынешнем состоянии здесь ещё есть над чем работать, но это тема для отдельного разговора. Даже стандартные решения «с полки» лучше, чем ничего. Я сам в этом убедился, пытаясь от них отказаться. Не проходило и недели, чтобы меня не начало удручать, сколько букв требуется набить хотя бы для того, чтобы получилась модель формального взаимодействия (FIM). Именно эта область более всего располагает к экспериментам.

2.     Поиск. Если у меня возникает вопрос о работе в сложной среде, например: «как мне сделать эту кнопку прозрачной средствами CSS?», то на него мне лучше всего ответит БЯМ, работающая на основе пользовательских данных — o1, sonnet 3.5, т.д. В этом она не только превзойдёт любой старомодный веб-поисковик и даже попытается распарсить ту веб-страницу, на которой я окажусь. (Иногда БЯМ ошибаются. Точно как и люди. Как-то раз я нахлобучил себе на голову туфлю и спросил двухлетнюю дочку, как ей моя шляпа. Она же не поняла шутку и как следует меня отчитала. БЯМ может что-то «перепутать», как и я в тот момент).   

3.     Программирование через чат. Это самая сложная из трёх стратегий. Именно с ней я извлекаю из БЯМ максимум пользы, но, в то же время, именно она меня более всего беспокоит. В данном случае нужно много учиться и постоянно корректировать свой подход к программированию — в принципе, мне это не нравится. Чтобы научиться таким способом извлекать из чата с БЯМ полезную информацию, требуется повозиться не меньше, чем при осваивании логарифмической линейки. Кроме того, меня раздражает непредсказуемость в работе этого сервиса, где постоянно меняется не только поведение самой программы, но и пользовательский интерфейс. Действительно, в долгосрочной перспективе я собираюсь избавиться от программирования через чат, поставить эти модели на службу разработчику, причём, чтобы взаимодействие с ними было не столь отталкивающим. Но пока я стараюсь решать эту проблему поступательно, то есть, научиться обращаться с БЯМ как можно лучше и подумать, в чём такую практику можно доработать.

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

Далее задача сводится к тому, чтобы извлечь ценность из такого программирования через чат.

Зачем вообще использовать чат

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

Таким образом, вам программирование через чат может и не подойти. Именно та разновидность программирования, которой приходится заниматься мне — это разработка продукта. В общих чертах это попытка предоставить программу пользователю через надёжный интерфейс. Поэтому я много пишу, много выбрасываю и то и дело перехожу из одного контекста в другой. Бывают дни, когда я пишу в основном на TypeScript, иногда — преимущественно на Go. В прошлом месяце я целую неделю просидел в базе кода на C++, исследуя одну идею, и мне как раз представилась возможность изучить, в каком формате на стороне сервера обрабатываются события при работе по протоколу HTTP. Я сразу везде, что-то всё время забываю и переучиваю. Если вы тратите большую часть времени не на написание кода как такового, а на перепроверку, не стал ли криптографический алгоритм после ваших оптимизаций более уязвим для атак по времени, то не думаю, что изложенные здесь мои наблюдения будут вам полезны.

БЯМ с функцией чата лучше всего справляются с вопросами экзаменационного характера

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

1.     Старайтесь не загонять БЯМ в чрезмерно сложные и неоднозначные ситуации, в которых она может запутаться и выдать вам некачественные результаты. Именно поэтому я не преуспел с чатом в рамках моей IDE. У меня в рабочем пространстве царит беспорядок, тот репозиторий, над которым я работаю, по умолчанию слишком велик, и модель в нём постоянно на что-то отвлекается. По состоянию на январь 2025 года люди значительно превосходят БЯМ в умении сосредотачиваться. Вот почему я по-прежнему работаю с БЯМ через браузер — хорошо сформулированные запросы получаются у меня только в случае, если я создаю их с чистого листа.

2.     Просите выполнить работу, которую легко проверить. Как программист, работающий с БЯМ, вы должны быть в состоянии легко прочитать код, который она написала, обдумать его и решить, хорошо ли она справилась с поставленной задачей. Можно попросить БЯМ сделать такие вещи, о которых вы никогда не попросили бы человека. Например, любой человек сочтёт оскорбительным приказ «перепиши все твои новые тесты и добавь в них <промежуточная концепция, которая, предположительно, должна сделать тесты более удобочитаемыми>». Ещё бы, ведь после этого вам придётся несколько дней жаркой перепалки с человеком, а стоит ли такая работа затраченных усилий. БЯМ же сделает такую работу за минуту и совершенно не будет вам перечить. Пользуйтесь тем, что переделать работу почти ничего не стоит.

БЯМ идеально подходит такая задача, при решении которой требуется использовать очень много распространённых библиотек (больше, чем человек даже в состоянии запомнить – так что считайте, что БЯМ проделает для вас сразу много микроисследований). Далее БЯМ должна обработать спроектированный вами интерфейс или соорудить для вас небольшой интерфейс, внятность которого вы быстро сможете оценить. После этого модель должна написать удобочитаемые тесты. Иногда для этого требуется сначала подобрать библиотеку, если вы стремитесь решить какую-то мудрёную задачу (хотя, БЯМ с открытым исходным кодом довольно хорошо справляются с подобными задачами).

Всегда требуется пропускать через компилятор тот код, который написала БЯМ, затем выполнять тесты — и уже потом уделять время на чтение этого кода. Все БЯМ могут выдавать код, который иногда не компилируется. Сравнительно качественные БЯМ очень хорошо исправляют свои ошибки. Часто для этого требуется всего лишь вставить в чат ошибку компилятора или описание отказа теста — и модель исправит код.

Не составляет труда добавлять в код новые структуры

Ежедневно приходится идти на зыбкие компромиссы, связанные с тем, чего стоит написать код, прочитать его и отрефакторить. Разберём для примера ситуацию с границами пакетов в Go. В стандартной библиотеке есть пакет “net/http”, в котором содержатся некоторые фундаментальные типы. Они предназначены для работы с кодировкой форматов, MIME-типами, т.д. Также здесь находятся HTTP-клиент и HTTP-сервер. Действительно ли это должен быть один пакет, или его можно разделить на несколько? Есть о чём поспорить! Честно говоря, в настоящий момент я не знаю, есть ли верный ответ на этот вопрос. То, что у нас сейчас есть – работает, и, спустя 15 лет работы, я по-прежнему не уверен, могла бы какая-нибудь другая конфигурация пакетов работать лучше.

Вот некоторые преимущества относительно больших пакетов: централизована документация по вызывающим функциям, легче приступать к написанию кода, упрощается рефакторинг, проще делиться вспомогательным кодом, не изобретая специально для этого надёжных интерфейсов (для чего зачастую требуется подтягивать фундаментальные типы из пакета в ещё один листовой пакет, также наполненный типами). Недостатки в том, что большой пакет сложнее читать, поскольку в нём происходит много всего и сразу (попробуйте почитать реализацию клиента net/http, не оступившись и не провалившись на несколько минут в серверный код). Например, есть у меня база кода, где в некоторых фундаментальных типах применяется библиотека C. Но отдельные элементы этой базы кода должны быть в двоичном файле, широко распространяемом по многим платформам, где технически не требуется библиотека на C. Поэтому рассчитывайте на то, что у вас в базе кода будет больше пакетов, чем ожидается, и некоторые из них нужны именно для изоляции библиотеки на C, во избежание cgo в мультиплатформенном двоичном файле.

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

  • Поскольку БЯМ лучше справляются с вопросами, сформулированными в экзаменационном стиле, модели требуется как можно больше мелких пакетов. По ним ей будет проще очертить полный, но при этом обособленный контекст некоторого куска работы. То же справедливо и для человека, именно поэтому мы и пользуемся пакетами. Однако в данном случае мы «расплачиваемся» за размер пакета дополнительной работой по типизации/латанию/систематизации кода, чтобы повысить его удобочитаемость. Когда БЯМ делает эту работу за нас и сама же получает от этого пользу (а это большой кусок работы), акценты в компромиссе смещаются. В качестве бонуса мы получаем более удобочитаемый код. 

  • Сравнительно мелкие и более многочисленные пакеты можно компилировать и тестировать в отрыве от не связанного с ними кода. Это полезно в цикле разработки с применением БЯМ, поскольку в таком случае изменения можно протестировать и скомпилировать ещё до рефакторинга других зависимых пакетов. Среда для тестирования пакетов упрощается.

Пример

Давайте проработаем пример, на котором я в совокупности продемонстрирую некоторые из обсуждённых выше идей.

Напишем инструмент резервуарной выборки для квартилей чисел с плавающей точкой.

Начнём со структуры пакета. Если бы я делал это до применения БЯМ, то мне пришлось бы выбрать тот или иной пакет потоковой статистики, в котором содержалось бы несколько алгоритмов — может быть, по одному на файл. По-видимому, не только мне в голову приходила такая мысль — существует свободно распространяемый пакет quantile, построенный именно по такой модели. Итак, теперь я хочу выделить в собственный пакет лишь этот конкретный алгоритм. Другие варианты или родственные алгоритмы можно вынести в собственный пакет.

Далее рассмотрим, что нам может дать БЯМ. Первый проход получился неплохим. Сформулировал такой промпт, дополнительно уточнив, что он нужен мне на Go, в результате получил quartile_sampler.go:

// QuartileSampler ведёт резервуарную выборку для оценки квартилей потока  
// значений float64. Здесь резервуарная выборка применяется для того, чтобы у 
// нас было репрезентативное множество при обработке больших потоков данных. 
type QuartileSampler struct {  
   reservoir []float64  
   seen      int  
   maxSize   int  
   rng       *rand.Rand  
}

// NewQuartileSampler создаёт новый инструмент выборки, в котором может содержаться до maxSize образцов.  
// Чем больше maxSize, тем более точные оценки мы получаем, но тем больше памяти при этом расходуем.  
func NewQuartileSampler(maxSize int) *QuartileSampler { … }

Базовый интерфейс тоже хорош:

// Добавляет в выборку новое значение.  
func (qs *QuartileSampler) Add(value float64) { … }

// Квартили возвращают оценочные значения для 25-й, 50-й (медиана) и 75-й перцентилей  
// тех данных, что мы пока успели рассмотреть. Если никакие данные добавлены не были, то возвращается (0,0,0).  
func (qs *QuartileSampler) Quartiles() (q1, median, q3 float64) {  
   if len(qs.reservoir) == 0 {  
       return 0, 0, 0  
   }  
    
   // Делаем копию во избежание изменения резервуара  
   sorted := make([]float64, len(qs.reservoir))  
   copy(sorted, qs.reservoir)  
   slices.Sort(sorted)  
    
   n := len(sorted)  
   q1 = percentile(sorted, 0.25)  
   median = percentile(sorted, 0.50)  
   q3 = percentile(sorted, 0.75)  
    
   return q1, median, q3  
}

Отлично! Ещё есть тесты.

Отмечу, что на этом можно было бы и остановиться. Иногда я пользуюсь генерацией кода через БЯМ как своеобразным специализированным поиском. Скажем, меня интересует резервуарная выборка, но при этом я хочу посмотреть, как алгоритм поведёт себя при каком-нибудь странном ограничении, например, при выборке взвешенных по времени данных. Можно не копаться в литературе, а немного откорректировать промпт, так, чтобы в новой реализации отслеживалась свежесть данных. Также можно было бы попросить модель указать в комментариях ссылки на источники, которые я затем мог бы проверить вручную и определить, нашла ли она какие-нибудь солидные исследования, достойные дальнейшей проработки. Бывает, я потрачу минуту, чтобы прочитать сгенерированный код, вижу в нём очевидный приём, о котором сам бы и не подумал, затем отбрасываю этот код и начинаю всё заново. Просто теперь я знаю, что такой приём возможен. Вот почему так сложно оценить материал, сгенерированный БЯМ. Да, иногда она выдаёт плохой код, буксует, совершает что-то невозможное (она навоображала себе такой элемент API Monaco, который я действительно хотел бы когда-нибудь встретить в реальности), просто тратит моё время. Но при этом я могу сэкономить многие часы, вычленив из её выдачи что-то важное, чего я раньше не знал.

Вернёмся к коду. Забавно, что первый вариант кода, предложенного БЯМ, не скомпилировался. Посреди реализации квартилей была такая строка:

n := len(sorted)

Она вполне нормальная, сортирует сегмент, определённый несколькими строками выше. Но это значение так нигде и не используется, поэтому gopls (и компилятор Go в ответ на команду go build) сразу же говорит:

declared and not used: n

Исправить это очень легко. Если я вставлю ошибку обратно в БЯМ, она её исправит. Но в данном случае я читаю код и явственно вижу, что просто могу сам удалить эту строку — что и делаю.  

Переходим к тестам. Результаты меня не удивили. В quartile_sampler_test.go:

func TestQuartileSampler(t *testing.T) {  
   tests := []struct {  
       name     string  
       data     []float64  
       wantQ1   float64  
       wantMed  float64  
       wantQ3   float64  
       epsilon  float64 // допустимая ошибка при сравнении чисел с плавающей точкой  
   }{  
       {  
           name:    "basic sequence",  
           data:    []float64{1, 2, 3, 4, 5, 6, 7},  
           wantQ1:  2,  
           wantMed: 4,  
           wantQ3:  6,  
           epsilon: 0.1,  
       },  
	…  
   }

   for _, tt := range tests {  
       t.Run(tt.name, func(t *testing.T) {  
           qs := NewQuartileSampler(100) // Для точности тестирования используем большой резервуар  
           for _, v := range tt.data {  
               qs.Add(v)  
           }

           q1, med, q3 := qs.Quartiles()  
            
           if math.Abs(q1-tt.wantQ1) > tt.epsilon {  
               t.Errorf("Q1 = %v, want %v (±%v)", q1, tt.wantQ1, tt.epsilon)  
           }  
           if math.Abs(med-tt.wantMed) > tt.epsilon {  
               t.Errorf("Median = %v, want %v (±%v)", med, tt.wantMed, tt.epsilon)  
           }  
           if math.Abs(q3-tt.wantQ3) > tt.epsilon {  
               t.Errorf("Q3 = %v, want %v (±%v)", q3, tt.wantQ3, tt.epsilon)  
           }  
       })  
   }
}

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

Во-первых, БЯМ не прогоняет эти числа через другую реализацию (насколько мне известно. Работая с многоумной БЯМ, сложно с уверенностью сказать, что именно происходит у неё за кулисами). Она сама их вычислила, а БЯМ, как известно, не очень сильны в арифметике. Поэтому тест такого рода целесообразно проводить в случае, если код написан человеком (ведь мы опираемся на вывод, полученный из другого инструмента), либо в случае, если мы решили дедовским методом сами посчитать арифметику, то подобные задачи плохо подходят для БЯМ. 

Вторая проблема — в том, что мы могли бы справиться лучше. Я счастлив жить во времена, когда программисты сами пишут себе тесты, но мы не придерживаемся в коде тестов столь же строгих стандартов, как в боевом коде. Это разумный компромисс, в сутках всё-таки всего 24 часа. Но недостаточную математическую подготовку БЯМ с успехом компенсируют за счёт энтузиазма.

Давайте попробуем ещё более качественный тест.

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

Так у нас получился новый тестовый код:

// referenceQuartiles вычисляет точные квартили для сегмента значений float64  
// при помощи линейной интерполяции, то есть, действует примерно так, как действовал бы инструмент выборки.  
func referenceQuartiles(data []float64) (q1, median, q3 float64) { … }

// compareQuartiles проверяет, действительно ли два набора квартилей отличаются друг от друга в рамках погрешности.  
// Возвращает true, если степень несовпадения остаётся в допустимых рамках, в противном случае возвращает false.  
func compareQuartiles(q1a, meda, q3a, q1b, medb, q3b, epsilon float64) bool { … }

// checkQuartiles – это тестовый помощник, сравнивающий выведенную выборку со справочной 
// реализацией и сообщающий обо всех найденных отличиях.  
func checkQuartiles(t *testing.T, data []float64, epsilon float64) {  
   t.Helper()  
    
   // Получаем справочные значения  
   wantQ1, wantMed, wantQ3 := referenceQuartiles(data)  
    
   // Получаем значения выборки, и для точности используем большой резервуар  
   qs := NewQuartileSampler(1000)  
   for _, v := range data {  
       qs.Add(v)  
   }  
   gotQ1, gotMed, gotQ3 := qs.Quartiles()  
    
   if !compareQuartiles(gotQ1, gotMed, gotQ3, wantQ1, wantMed, wantQ3, epsilon) {  
       t.Errorf("Quartiles mismatch:\ngot  (q1=%v, med=%v, q3=%v)\nwant (q1=%v, med=%v, q3=%v)\nepsilon=%v",  
           gotQ1, gotMed, gotQ3, wantQ1, wantMed, wantQ3, epsilon)  
   }  
}

Далее я переработал исходный тест, показанный выше, так, чтобы в нём использовались checkQuartiles — и у нас получилось что-то новенькое:

func FuzzQuartileSampler(f *testing.F) {  
   // Добавляем корпус значений для посева  
   f.Add([]float64{1, 2, 3, 4, 5})  
    
   f.Fuzz(func(t *testing.T, data []float64) {  
       // При фаззинге используем увеличенную погрешность, поскольку разброс крайних значений может оказаться шире  
       checkQuartiles(t, data, 0.2)  
   })  
}

Это смешно, потому что неверно. У меня работает инструмент gopls, который сразу же сообщает:

fuzzing arguments can only have the following types:
    string, bool, float32, float64,
    int, int8, int16, int32, int64,
    uint, uint8, uint16, uint32, uint64,
    []byte

Если передать эту ошибку обратно БЯМ, она повторно сгенерирует фаззинг-тест, так, что на этот раз он выстраивается вокруг функции func(t *testing.T, data []byte), которая при помощи math.Float64frombits извлекает из сегмента данных числа с плавающей точкой. Взаимодействия такого рода лишний раз стимулируют нас автоматизировать обратную связь от инструментов. Всё, что требовалось модели — это очевидное сообщение об ошибке, на его основе она делала успехи в полезной работе. Я не требовался.

Экспресс-анализ истории наших с БЯМ чатов за последние несколько недель показывает, что более чем в 80% случаев ошибки имеют инструментальный характер. БЯМ может прогрессировать без всяких моих подсказок (но, как я упоминал выше, это ни в коей мере не нельзя считать авторитетным количественным анализом). Примерно в половине случаев модель может решить проблему без моего вмешательства, я выступаю лишь в роли посредника.

Куда мы движемся? Тесты становятся лучше, возможно, даже не столь DRY

Лет 25 назад среди программистов было движение, объединённое призывом «не повторяйся» (don’t repeat yourself, сокращённо DRY). Как часто бывает в случаях с краткими запоминающимися принципами, которые преподаются старшеклассникам, это движение зашло слишком далеко. Чтобы абстрагировать фрагмент кода и создать условия для его повторного использования, приходится нести немало издержек, требуется создавать промежуточные абстракции, которые нужно заучивать. В обособленный таким образом код нужно вносить фичи, благодаря которым этот код становится максимально полезен самому широкому кругу людей. Таким образом, мы впадаем в зависимость от библиотек, наполненных бесполезными деталями, которые мешают сосредоточиться.

В последние 10-15 лет наблюдался гораздо более взвешенный подход к коду: многие программисты усвоили, что лучше повторно реализовать концепцию, если обеспечивать разделяемость выходит дороже, чем написать и поддерживать отдельный код. Я теперь гораздо реже стал писать в код-ревью «этого не стоит делать, разделите реализации» (И это к лучшему, поскольку никому в самом деле не нравится слышать такие вещи, когда вся работа уже сделана). Программисты постепенно совершенствуются в искусстве компромиссов.

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

Я считаю, что эта тенденция лучше всего просматривается на уровне обёрток REST API, специфичных для различных языков. В API любой крупной компании найдутся десятки таких примеров (обычно низкокачественных). Авторы таких обёрток на самом деле не используют своих реализаций в каких-то конкретных целях, а пытаются вписать все сучки и задоринки некоторого API в большой и сложный интерфейс. Даже если это сделано хорошо, на мой взгляд, было бы проще посмотреть документацию REST (обычно это набор curl-команд) и реализовать языковую обёртку для того 1% кода API, который меня действительно интересует. Так значительно сокращается объём материала по API, который приходится изучить заранее, а также упрощается чтение и осмысление кода в дальнейшем — как для меня, так и для других программистов, которым придётся с ним работать.

Например, в рамках моего недавнего проекта на sketch.dev я реализовал на Go обёртку для Gemini API. Даже притом, что существует официальная обёртка на Go, бережно сработанная знатоками языка, которым определённо не всё равно, в неё требуется хорошенько вчитываться, чтобы понять:

$ go doc -all genai | wc -l  
    1155

В первой версии моя упрощённая исходная обёртка состояла всего из 200 строк кода — один метод, три типа. Прочитать всю реализацию — это 20% от работы, требуемой на прочтение документации по официальному пакету. Если же вы решите покопаться в этой реализации, то обнаружите, что она – сама по себе обёртка, в которой заключена другая реализация, состоящая в основном из сгенерированного кода, и в ней полно всяких proto, grpc и т.д. А мне только-то и требовалось выполнить cURL и разобрать объект JSON.

Рано или поздно в проекте наступает момент, когда Gemini становится основой всего проекта, в котором используются почти все до единой возможности Gemini, а выстраивание на основе gRPC хорошо согласуется с использованием системы телеметрии, задействованной где-то ещё в вашей организации, там, где уже следует использовать большую официальную обёртку. Но, как правило, такая работа требует гораздо больше времени, как на подготовительном, так и на рутинном этапе. Учитывая, что нам почти всегда требуется лишь тончайший срез любого API, с которым приходится сегодня работать, такие собственные клиенты, в основном написанные на мощностях GPU, значительно эффективнее при решении любой задачи.

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

Автоматизация всего, что подмечено выше: sketch.dev

Как программист я инстинктивно стараюсь поставить компьютеры себе на службу. Из БЯМ можно извлечь массу пользы, как же перепоручить компьютеру работу с моделью?

Думаю, для решения этой задачи важнее всего не слишком обобщать. Решаешь конкретную задачу, а потом постепенно расширяешься. Поэтому не стоит сразу делать универсальный UI для программирования через чат, добиваясь, чтобы этот интерфейс в равной степени хорошо работал и с COBOL, и с Haskell. Лучше сосредоточиться на конкретном окружении. Я программирую в основном на Go, поэтому мне легко представить такой список для моих коллег по Go:

  • Что-то вроде песочницы для Go, выстроенной вокруг редактирования пакета и выполнения тестов

  • Чтобы в этом был интерфейс для чата, в котором можно редактировать код

  • Небольшая среда на основе UNIX, в которой можно было бы выполнять go get и go test

  • Интеграция с goimport-ами

  • Интеграция с gopls

  • Автоматическая обратная связь от модели: при редактировании модели выполнять go get, go build, go test, получать обратную связь по недостающим пакетам, ошибкам компилятора, отказам тестов, так, чтобы можно было сообщать эту информацию модели и добиваться, чтобы она автоматически исправляла ошибки

Мы с коллегами собрались и написали ранний прототип такого инструмента: sketch.dev.

Мы стремились не создать «веб-IDE», а оспорить само мнение, что программирование через чат в принципе сводимо к «работе в IDE». IDE — это совокупности инструментов, специально подобранные для людей. Это деликатная среда, в которой мне известно, что происходит. Но я не хочу, чтобы БЯМ расплевала свой первый черновик по всей ветке, над которой я сейчас работаю. Притом, что БЯМ в конечном итоге — это инструмент разработки, для неё требуется собственная IDE, через которую мы будем получать обратную связь и обеспечивать эффективную работу модели.

Иными словами, мы не пытались встраивать goimport-ы в скетч ради того, чтобы ими пользовались люди, а стремились приблизить код Go к виду, готовому для компиляции, и для этого использовали автоматические сигналы. Таким образом компилятор сможет предоставлять БЯМ более качественную обратную связь об ошибках, одновременно управляя моделью. Таким образом,  sketch.dev правильнее понимать как «интегрированную среду для разработки на Go, ориентированную на БЯМ».

Всё это очень свежие разработки, и многое ещё предстоит сделать — например, интегрировать инструмент с git, чтобы можно было загружать для редактирования уже существующие пакеты, а отредактированные результаты затем сбрасывать в ветку. Нужно лучше протестировать обратную связь. Наладить взаимодействие с консолью. (Если получен ответ «запусти sed», то нужно запустить sed, будь ты человек или БЯМ.) Пока мы продолжаем исследования, но уверены, что, если сосредоточиться на создании среды для определённого рода программирования, то получится лучше, чем при попытке создать универсальный инструмент.

Комментарии (26)


  1. boopiz
    24.01.2025 22:46

    всё, что "ничего не стоит" содержит и нулевую ценность. именно по этому, если ничего не стоит переделать, то и ценность вопроса нулевая, как и ценность специалиста

    все эти "автозавершения" и прочие мнимые плюшки из серии "как сделать кнопку прозрачной" теряют свою эффективность на фоне затрат ревизии сгенерированного набора символов (пока ещё не кода)

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

    в итоге, для ревизии сгенерированной кучи кода непонятоого качества и, возможно, с "закладками" (ведь модели учат на чем то. и кто вам даст гарантию, что учат правильно. ведь контроля за этим нет) вам потребуется гораздо бОльший уровень компетенций и опыта непосредственного личного написания кола и участия в разнонаправленных проектах (откуда же ему взяться у новичков и, даже разработчика среднего кровня) чем, если бы задача выполнялась специалистом более низкого уровня. в этом случае он (специалист) и приобретал тот самый опыт через поиск информации, общение с коллегами и тд.

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


    1. olku
      24.01.2025 22:46

      Подтверждает гипотезу что контролирующая система должна быть умнее контролируемой. Но возникает соблазн однажды поменять роли и присягнуть ИИ. Ведь вкалывать должны роботы, а не человек.


    1. MainEditor0
      24.01.2025 22:46

      Немного самопиара своей статьи, которая смежна к теме: https://habr.com/ru/articles/875022/


    1. CentariumV
      24.01.2025 22:46

      Вопрос в том, а для чего нужна ревизия и не сможет ли с ней справится БЭМ. Если будет обнаружен баг и БЭМ сможет его найти, то ревизия не нужна. Также как и если нужно будет внедрить новый функционал и БЭМ сможет это сделать. А так даже во время ревизии чужого кода, созданного человеком часто спрашиваю у БЭМ - «можешь доходчиво объяснить, что тут делается и зачем, отрефакторить код или объяснить, почему такой - то баг?». И БЭМ часто выдает очень даже неплохой результат


  1. Dron007
    24.01.2025 22:46

    Мне кажется, тут мы просто находимся в очень переходном моменте, когда рано делать выводы. Вспомните какой код генерировала ChatGPT 3, постоянно забывающая контекст, и можно сравнить с тем же DeepSeek R1.

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

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

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

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

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


    1. boopiz
      24.01.2025 22:46

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

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


    1. gmtd
      24.01.2025 22:46

      Помню как на WebAudio, который не изучал, с помощью ИИ написал проигрыватель мелодий, играл с реверберацией, подавлял щелчки, но в итоге я ничего не приобрёл.

      Вы можете взять пистолет и заполучить деньги, которые не заработали. ИИ - такой же инструмент. "Приобретете" вы что-то или нет зависит от того, как вы будете его использовать.

      Не надо на ИИ сваливать лень, некомпетенцию и плохие намерения того, кто им пытается пользоваться.


      1. karmael
        24.01.2025 22:46

        я совершенно искренне не понимаю, как вы что то там "разбираетесь", используя инструмент буквально созданный для того что бы не погружаться в суть?


        1. gmtd
          24.01.2025 22:46

          Не понял

          Я задаю чатботу вопрос - "в чем разница между CSS custom properties и CSS variables"

          Кто из нас не погружается в суть?


          1. karmael
            24.01.2025 22:46

            никто


          1. karmael
            24.01.2025 22:46

            я искренне не понимаю, зачем чатбот для этого вопроса, гугл вряд ли ответит, что например "потому что так захотел мики-маус", а вот за чатбот вы уверены?


            1. gmtd
              24.01.2025 22:46

              Я эмпирически оцениваю достоверность ответа и использую полученную информацию для сужения дальнейшего поиска по первоисточникам, если они меня интересуют


              1. karmael
                24.01.2025 22:46

                как вы можете оценивать ответы если отдали эрудицию на аутсорс чатботу?


                1. gmtd
                  24.01.2025 22:46

                  По-моему, это вы свою эрудицию кому-то на аутсорс отдали

                  Не превращайте комментарии к статье в чатик


                  1. karmael
                    24.01.2025 22:46

                    ну подождите, вы буквально просите чатбот почитать за вас документацию


              1. Mr_Cheater
                24.01.2025 22:46

                Эмпирически. А откуда взяться этому опыту, если все общение с ии будет сводиться к копипасте?


          1. karmael
            24.01.2025 22:46

            а самое главное то, что никто не так и не открыл документацию, не вы ни чатбот


    1. rukhi7
      24.01.2025 22:46

      Возьмём, к примеру, компилятор в машинный код. Мы же его не перепроверяем как правило

      Очень интересная аналогия. Но может быть дело в том что компилятор проверили до нас те кто разработали компилятор и очень тщательно проверили, а также проверили (правда косвенно) те кто пользовался этим компилятором десятки лет.

      Есть все таки принципиальная разница между работой компилятора и работой ИИ, компилятор выдает постоянный воспроизводимый результат, то что выдает ИИ в большей или меньшей степени не предсказуемо и подвержено изменениям во временем. Интересно что изменчивость результатов от ИИ ставит под вопрос возможности его проверки - надо как то сравнивать разные ответы от ИИ, как это делать? Очевидный ответ нужен ИИ-2 чтобы проверить ИИ, кто проверит ИИ-2? ... Конечно существуют и разрабатываются разные методики оценки результатов работы ИИ и они объективные, но все равно очень сложные и тоже основаны на вероятностном подходе, то есть оставляют вероятность ошибки.


      1. Green__Hat
        24.01.2025 22:46

        ИИ тоже проверяют. Там и кнопочки одобрямс-осуждамс имеются. Имхо, на данный момент они для красоты, но то пока. Скоро всем миром косяки в новом "чудо-компиляторе" повычистим, может и десятка лет не понадобится.

        Очень свежая штука, оттого и сыр-бор. Мы еще за шесть пальцев его доругать не успели, а оно уже на PhD сдает


        1. rukhi7
          24.01.2025 22:46

          ИИ тоже проверяют. Там и кнопочки одобрямс-осуждамс имеются.

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

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


        1. rukhi7
          24.01.2025 22:46

          а оно уже на PhD сдает

          на PhD сдает не ИИ а условный Интернет который все "знает". ИИ это вероятностный алгоритм который позволяет сильно сократить время поиска в этой мусорной куче данных и знаний и того что похоже на знания и отфильтровать одно от другого ну и оформить-подать под нужным соусом. Вообще говоря неизвестно насколько эта сдача на PhD чистый эксперимент, а не PR-акция, это же очень сложно до конца проверить.


      1. Dron007
        24.01.2025 22:46

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

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


        1. rukhi7
          24.01.2025 22:46

          у пользователя есть набор явных требований и для программы существует набор неявных требований,

          когда то лет аж 20 назад, насколько я помню эта концепция звучала примерно так:

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

          Я не думаю что в этом плане что-то сильно изменилось с тех пор.

          Я боюсь что если в Интернете нет какого-то решения ИИ сам его не придумает, а даже если придумает человек это решение должен будет понять, а чтобы что-то новое понять надо знать много старого (в смысле того что известно), нужно иметь базу знаний.

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


          1. Dron007
            24.01.2025 22:46

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

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


            1. rukhi7
              24.01.2025 22:46

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

              Я представляю себе какого бы франкенштейна сотворили эти товарищи при работе с ИИ если бы они в конце всех мучений смогли бы всетаки получить результат (а это вполне вероятно), ведь они бы заставили бы ИИ сделать так чтобы метод достижения результата отражал их нереальную логику достижения этого результата, но в глубине бы работал механизм который все делает чтобы результат все таки получить. Дело в том что ИИ же это безотказный исполнитель, если вы хотите получить решение с вашими собственными тараканами вы его тоже получите!

              Золотая рыбка исполняет желания буквально, не пытаясь переубедить пользователя.


              1. Dron007
                24.01.2025 22:46

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