С приходом ИИ сильно вырос соблазн заниматься простым человеческим вайб-кодингом: пишешь себе промпты, копируешь готовый код, вставляешь в проект - и готово! И бизнес рад, и времени меньше уходит… В чем подвох? Пожалуй, в том, что таким образом все меньше полезной информации оседает в голове.
В этой статье я хочу поделиться своим методом, с помощью которого я внедряю новые фичи в проект, над которым работаю, при этом получая новые знания и опыт (как в старые добрые времена), затрачивая гораздо меньше времени.
В настоящее время я занимаюсь разработкой фронтенда в над starpx.com - это приложение, позволяющее просматривать и редактировать Deepsky-фотографии, поэтому для статьи взял оттуда пару личных кейсов.
Возможно, описываемые в статье технологии покажутся кому-то "детскими" и простыми, но главное - это принцип взаимодействия с ними человека, который про них только слышал, но не использовал.
TL;DR
Мой процесс внедрения новых (в первую очередь - для меня) фичей выглядит так:
Тренируй насмотренность
Изучи с чем имеешь дело
Спроси ИИ и проанализируй ответ
Внедряй
1. Тренируй насмотренность
Во все времена одним из признаков хорошего специалиста является насмотренность. Быть в тренде, читать про новые технологии - это как правило хорошего тона любого инженера. Ибо если ты сам себе землекоп с лопатой, то, не прочитав про экскаватор, ты не будешь его применять.
Для ознакомления с тем, “чем живет” твоя предметная область, не нужно много времени и сил, достаточно посмотреть доклад с конференции или почитать статью вместо листания ленты в кофе-брейк - чтобы хотя бы было представление.
И вот тебе прилетает новая задача, при виде которой обычный человек скорее всего пойдет гуглить на пару часов, далее пару дней обдумывания, дейлики, созвоны, асинхронные консультации и тд и тп. Но не в нашем случае - у нас есть насмотренность.
Личный кейс № 1: тайлинг
Задача: сделать разделение изображения на фрагменты небольшого разрешения (тайлы), которые при размещении рядом друг с другом составляют само изображение. При этом, должно поддерживаться неограниченное количество уровней тайлинга, фрагменты должны подгружаться по мере увеличения степени зумирования, а итоговое изображение должно собираться в SVG (на этом срезалось пару библиотек, которые строят изображение в canvas).

Джуном я бы побежал решать задачу "в лоб", нагородил бы условий и кастомных событий и, скорее всего забыл бы про оптимизацию.
Теперь, в более зрелом возрасте и имея насмотренность, я могу добавить к условиям оптимизацию: при увеличении или перемещении области просмотра должны подгружаться только те тайлы, которые находятся на экране в данный момент. Как это отследить? Ну, я слышал про такую штуку, как IntersectionObserver, но ни разу ее не применял. Было бы круто поработать с ней!
Личный кейс № 2: диаграммы
Вряд ли человек, не увлекающийся астрономией или астрофотографией, знает, что звезды имеют свои характеристики и даже диаграммы, их описывающие. Так, диаграмма Герцшпрунга-Рассела отражает зависимость между спектральными классами или показателями цветовой температуры звёзд и их абсолютными звёздными величинами, а диаграмма светимости показывает зависимость силы излучения для определенных частот световых волн. Разумеется, уважающий себя сервис должен уметь отображать эти диаграммы.
Учитывая, что в проекте для отрисовки overlay-слоев уже использовалась библиотека D3.js, глупо было бы использовать еще одну отдельно для графиков.

Личный кейс № 3: ускорить загрузку информации об объектах звездного неба
При открытии пост-обработанного изображения у нас есть overlay-фигуры, размечающие различные космические объекты: галактики, туманности, звезды и т.д. Разумеется, разметить абсолютно все объекты даже на отдельном участке звездного неба невозможно - будет каша. Но самые значительные видны и подписаны. Мало того: при нажатии на них можно посмотреть более подробную информацию, в том числе диаграммы.
Пожалуй, любому придет в голову идея с кешированием. И, не имея насмотренности, я бы скорее всего побежал бы костылить сохранение этой информации в localStorage.
Под действием алкоголя насмотренности же, я вспомнил про такие классные штуки, как:
IndexedDB - идеально, чтобы сохранить информацию об объектах звездного неба в табличном виде.
Worker'ы - чтобы делать это в отдельном потоке, невидимо для пользователя.

Сразу в голове звучит: “О! Так я же читал про эту библиотеку/API на прошлой неделе, можно применить!” Ну и продолжение - “…вобью-ка в промпт…” В редких случаях это сработает, но лучше…
2. Изучи с чем имеешь дело
Гораздо более эффективным будет сначала изучить технологию, которую собираешься использовать, на достаточном уровне, чтобы а) можно было сделать все самому - чтобы в голове отложилось б) поправить код ИИ - чтобы не тратить время на бесконечные копипасты говнокода, который в лучшем случае работать не будет, а в худшем будет немасштабируемым и неотлаживаемым. Найдите туториал, посмотрите рабочие примеры, пройдите мини-курс. Наша задача - изучить на ДОСТАТОЧНОМ уровне, не погружаясь в детали на несколько недель. Здесь уместно вспомнить про принцип Парето (80/20).
Для первого кейса с тайлингом, который фактически не зависел от других библиотек моего проекта, я сделал мини-полигон в виде большого VueJS компонента, единственного отображаемого на странице. Остальные так или иначе использовали подключенные библиотеки приложения.
В изучении IntersectionObserver мне хорошо помог проект doka.guide - я немного потренировался, понял, что к чему, и "зачел" себе это знание.
Ознакомление с библиотекой D3.js проходил с использованием руководства здесь - я не большой фанат обучающих видео, мне ближе классические учебники с примерами.
По IndexedDB и Worker API я прошелся по официальной документации и статьям на хабре (благодарю всех авторов, коих я, к сожалению, не запомнил).
После того, как я потрогал все нужные технологии руками и убедился, что могу все сделать сам, я...не стал все делать сам, а пошел к нейросетям.
3. Спроси ИИ и проанализируй ответ
Как только чувствуешь, что можешь сделать самостоятельно, но при этом ясно представляешь, что это будет долго и нудно - можешь подключать ИИ-ассистента. Скорее всего, он быстро накидает “скелет” того, что тебе нужно, а благодаря своим знаниям, приобретенным на предыдущем шаге, ты легко сможешь переделать сгенерированный шаблон в рабочий код, который станет частью твоего проекта. Если есть возможность не вставлять код сразу в проект - лучше этого не делать. Вместо этого, сделать полностью рабочий прототип, возможно используя отдельное окружение. В целом код, сгенерированный ИИ, похож на полуфабрикат, типа пельменей: вряд ли их можно съесть, не сварив. Так же с кодом: не работает - “вари”, работает - внедряй.
К сожалению, данную статью я решил написать уже после успешного внедрения описываемых решений, поэтому прикрепить код, выданный LLM, не могу - он ушел в историю. Могу сказать только, что с моими задачами на удивление неплохо справлялся GigaChat от Сбера - и это очень приятный момент, что отечественные разработки становятся по-настоящему конкурентоспособными.
4. Внедряй
На последнем этапе переходим к внедрению уже готового кода в наш проект. Здесь мы подключаем зависимости, переводим имена переменных в Namespace проекта (в идеале это сделать уже в п. 3), пишем необходимые экспорты и импорты.
В кейсе с тайлингом я получил почти сразу рабочую обертку IntersectionObserver для тайлов, которые были на экране. Все, что мне оставалось, это проверить работу моего компонента и просто перенести его в проект.
Код диаграмм работал, но не совсем корректно - были проблемы с масштабом диаграммы и с использованием неподдерживаемых методов библиотеки D3.js, которые были заменены в новой версии библиотеки. К счастью, я изучил библиотеку D3.js и легко смог поправить код, чтобы он работал так, как надо, и рисовал то, что требуется.
В работе с IndexedDB LLM выдал мне рабочий composable-костяк для работы с IndexedDB, в котором править пришлось разве что имена базы данных и хранилища:
export interface StarData {
id: string
info: any // тут секрет =)
}
const DB_NAME = 'starDB'
const DB_VERSION = 1
const STORE_NAME = 'stars_info'
function openDB(): Promise {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION)
request. => reject(request.error)
request.onsuccess = () => {
resolve(request.result)
}
request.onupgradeneeded = () => {
const db = request.result
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'id' })
}
}
})
}
export async function getStarData(id: string): Promise {
const db = await openDB()
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readonly')
const store = tx.objectStore(STORE_NAME)
const request = store.get(id)
request.onsuccess = () => resolve(request.result)
request. => reject(request.error)
})
}
export async function setStarData(data: StarData): Promise {
const db = await openDB()
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite')
const store = tx.objectStore(STORE_NAME)
const request = store.put(data)
request.onsuccess = () => resolve()
request. => reject(request.error)
})
}
Аналогичная ситуация с Worker'ом - почти с первого раза рабочий код, редактировал только обмен информацией между потоками и идентификацию объектов для записи в БД:
/// <reference lib="webworker" />
import { getStarData, setStarData, StarData } from './IndexedDB'
import fastHash from '@/core/utils/FastHash'
const fetchAdditionalInfo = async (url: string) => {
const response = await fetch(url)
const info = await response.json()
return info
}
interface Star {
id: string
// rest fields
}
interface WorkerRequest {
stars: Star[]
}
interface WorkerResponse {
id: string
// info?: any - здесь прилетает контент
ok?: boolean
error?: string
}
self.addEventListener('message', async (event: MessageEvent<WorkerRequest>) => {
const { stars } = event.data
for (const star of stars) {
const id = fastHash(star.id)
try {
let starData = await getStarData(id)
if (!starData) {
const info = await fetchAdditionalInfo()
starData = { id, info }
await setStarData(starData)
}
const response: WorkerResponse = { id, ok: true }
self.postMessage(response)
} catch (error: any) {
const response: WorkerResponse = { id, ok: false, error: error.message }
self.postMessage(response)
}
}
})
Выводы
В описанном подходе я вижу следующие плюсы:
Вы приобретаете опыт и знания, а не просто копипастите ответы LLM к себе в проект
Вы не тратите время на рутину - ее за вас делает ИИ, на вас - архитектура и внедрение
Вы тратите гораздо меньше времени на отладку, благодаря тому, что понимаете, как работает технология, которую вы хотите внедрить.
Конечно, можно (и даже нужно) добавить к перечисленным шагам этап ревью, но как минимум это лучше, чем бездумные Ctrl+C - Ctrl-V.
Спасибо, что дочитали, буду рад конструктивным комментариям!
sabaton222
Интересная статья, поднимающая важные вопросы о роли ИИ в творческих и технических процессах. Однако нельзя не заметить иронии в том, что автор, критикуя использование нейросетей для генерации кода (аргументируя это потерей контроля, качества или "души" разработки), сам прибегает к помощи ИИ для создания визуальной составляющей — обложки статьи. Возникает вопрос: чем принципиально отличается генерация кода от генерации дизайна? Может быть, в дизайне допускается большая доля эксперимента, а в коде — нет? Или же здесь проявляется двойственность нашего восприятия технологий: то, что кажется "второстепенным" (например, обложка), мы охотнее делегируем алгоритмам, а в "важном" (код) стремимся сохранить человеческий контроль?
Ваша позиция заставляет задуматься о границах приемлемого использования ИИ. Но, возможно, именно этот когнитивный диссонанс — лучший повод для дискуссии: как найти баланс между эффективностью нейросетей и сохранением смысловой, творческой целостности в разных областях? Спасибо за материал, который, даже непреднамеренно, демонстрирует, насколько неоднозначна и сложна эта тема.
shsv382 Автор
Спасибо за комментарий. Но речь не о границах дозволенного. Здесь я скорее говорю о другом. Представьте две крайности: первая - полностью положиться на ИИ-ассистенов и не вникать в результат их выдачи, типа "работает - не трогай". Результат - ничего не усваивается, не остается в голове, опыт только для галочки, что в свою очередь влечет проблемы с отладкой, поддержкой и масштабированием такого кода. Вторая крайность - это всë делать самому, что в современных условиях, я считаю, просто глупо. Да, для себя в познавательных целях можно, но не на коммерческой основе, где время=деньги.
И свой подход я преподношу как некую золотую середину, сочетающую плюсы обеих этих крайностей.
П.с. заголовок тоже от нейронки. Текст полностью авторский
vacoo
Генерация кода от генерации изображения отличается тем что изображение можно сразу оценить. А код имеет очень много вариантов как он будет работать если это не хелло ворлд. Плюс еще баги.
Я сторонник того чтобы обращатся к ИИ для генерации идей для решения проблем.
QuantumBoy
Как по мне, изображение не требует "поддерживаемости" и является законченным результатом. Код - это формализованный процесс, то есть он подразумевает задачу поддержки и модификации (в идеале -за прогнозируемое конечное время)