С приходом ИИ сильно вырос соблазн заниматься простым человеческим вайб-кодингом: пишешь себе промпты, копируешь готовый код, вставляешь в проект - и готово! И бизнес рад, и времени меньше уходит… В чем подвох? Пожалуй, в том, что таким образом все меньше полезной информации оседает в голове.
В этой статье я хочу поделиться своим методом, с помощью которого я внедряю новые фичи в проект, над которым работаю, при этом получая новые знания и опыт (как в старые добрые времена), затрачивая гораздо меньше времени.
В настоящее время я занимаюсь разработкой фронтенда в над starpx.com - это приложение, позволяющее просматривать и редактировать Deepsky-фотографии, поэтому для статьи взял оттуда пару личных кейсов.
Возможно, описываемые в статье технологии покажутся кому-то "детскими" и простыми, но главное - это принцип взаимодействия с ними человека, который про них только слышал, но не использовал.

TL;DR

Мой процесс внедрения новых (в первую очередь - для меня) фичей выглядит так:

  1. Тренируй насмотренность

  2. Изучи с чем имеешь дело

  3. Спроси ИИ и проанализируй ответ

  4. Внедряй

1. Тренируй насмотренность

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

Личный кейс № 1: тайлинг

Задача: сделать разделение изображения на фрагменты небольшого разрешения (тайлы), которые при размещении рядом друг с другом составляют само изображение. При этом, должно поддерживаться неограниченное количество уровней тайлинга, фрагменты должны подгружаться по мере увеличения степени зумирования, а итоговое изображение должно собираться в SVG (на этом срезалось пару библиотек, которые строят изображение в canvas).

Участок звездного неба, обработанный в StarPX
Участок звездного неба, обработанный в StarPX

Джуном я бы побежал решать задачу "в лоб", нагородил бы условий и кастомных событий и, скорее всего забыл бы про оптимизацию.
Теперь, в более зрелом возрасте и имея насмотренность, я могу добавить к условиям оптимизацию: при увеличении или перемещении области просмотра должны подгружаться только те тайлы, которые находятся на экране в данный момент. Как это отследить? Ну, я слышал про такую штуку, как IntersectionObserver, но ни разу ее не применял. Было бы круто поработать с ней!

Личный кейс № 2: диаграммы

Вряд ли человек, не увлекающийся астрономией или астрофотографией, знает, что звезды имеют свои характеристики и даже диаграммы, их описывающие. Так, диаграмма Герцшпрунга-Рассела отражает зависимость между спектральными классами или показателями цветовой температуры звёзд и их абсолютными звёздными величинами, а диаграмма светимости показывает зависимость силы излучения для определенных частот световых волн. Разумеется, уважающий себя сервис должен уметь отображать эти диаграммы.
Учитывая, что в проекте для отрисовки overlay-слоев уже использовалась библиотека D3.js, глупо было бы использовать еще одну отдельно для графиков.

Диаграмма Герцшпрунга - Рассела
Диаграмма Герцшпрунга - Рассела

Личный кейс № 3: ускорить загрузку информации об объектах звездного неба

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

  1. IndexedDB - идеально, чтобы сохранить информацию об объектах звездного неба в табличном виде.

  2. Worker'ы - чтобы делать это в отдельном потоке, невидимо для пользователя.

Объекты в IndexedDB
Объекты в IndexedDB

Сразу в голове звучит: “О! Так я же читал про эту библиотеку/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)
    }
  }
})

Выводы

В описанном подходе я вижу следующие плюсы:

  1. Вы приобретаете опыт и знания, а не просто копипастите ответы LLM к себе в проект

  2. Вы не тратите время на рутину - ее за вас делает ИИ, на вас - архитектура и внедрение

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

Конечно, можно (и даже нужно) добавить к перечисленным шагам этап ревью, но как минимум это лучше, чем бездумные Ctrl+C - Ctrl-V.

Спасибо, что дочитали, буду рад конструктивным комментариям!

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


  1. sabaton222
    12.05.2025 11:24

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

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


    1. shsv382 Автор
      12.05.2025 11:24

      Спасибо за комментарий. Но речь не о границах дозволенного. Здесь я скорее говорю о другом. Представьте две крайности: первая - полностью положиться на ИИ-ассистенов и не вникать в результат их выдачи, типа "работает - не трогай". Результат - ничего не усваивается, не остается в голове, опыт только для галочки, что в свою очередь влечет проблемы с отладкой, поддержкой и масштабированием такого кода. Вторая крайность - это всë делать самому, что в современных условиях, я считаю, просто глупо. Да, для себя в познавательных целях можно, но не на коммерческой основе, где время=деньги.

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

      П.с. заголовок тоже от нейронки. Текст полностью авторский


    1. vacoo
      12.05.2025 11:24

      Генерация кода от генерации изображения отличается тем что изображение можно сразу оценить. А код имеет очень много вариантов как он будет работать если это не хелло ворлд. Плюс еще баги.

      Я сторонник того чтобы обращатся к ИИ для генерации идей для решения проблем.


    1. QuantumBoy
      12.05.2025 11:24

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