Редакторы и IDE VS Code: архитектура, расширения, отладка, remote и devcontainers
0%

VS Code: архитектура, расширения, отладка, remote и devcontainers

VS Code: архитектура, расширения, отладка, remote и devcontainers

VS Code — редактор, который выиграл рынок. По ежегодным опросам Stack Overflow им пользуется порядка 70–75 % профессиональных разработчиков — доля, которой не было ни у одного инструмента за всю историю индустрии. Само по себе это не аргумент в его пользу: популярность измеряет распространение, а не качество. Но это повод разобраться, почему так вышло и где проходят настоящие границы применимости.

Короткий ответ: VS Code угадал уровень абстракции. Он не пытается быть IDE для конкретного языка и не пытается быть чистым текстовым редактором. Он — оболочка вокруг протоколов: LSP отвечает за язык, DAP за отладку, а сам редактор занимается текстом, UI и склейкой. Всё остальное — следствие. Разберём его как инженерную систему; контекст про слои редактора и роль LSP — в обзоре трека.

Часть 1. Архитектура

Откуда взялась многопроцессность

Предшественником VS Code по духу был Atom от GitHub — тоже Electron, тоже расширяемость через JavaScript. Atom проиграл по одной ключевой причине: пакеты в нём исполнялись в том же процессе, что и интерфейс. Любое расширение с синхронным циклом или тяжёлым regex подвешивало курсор. Пользователь видел «редактор тормозит», а виноват был плагин, поставленный три недели назад и забытый.

VS Code с самого начала сделал противоположный выбор: расширения живут в отдельном процессе, Extension Host, и общаются с интерфейсом только асинхронными сообщениями. Это стоило удобства авторам расширений — API целиком построен на промисах, синхронно дёрнуть UI невозможно — но купило главное свойство: чужой код не может заблокировать ввод текста.

Процессная модель VS Code

Отсюда практическое следствие: тормоза бывают трёх разных сортов, и лечатся они по-разному.

Симптом Где искать Чем диагностировать
Лаг при наборе, дёрганый скролл renderer: декорации, огромный файл, minimap отключить minimap и editor.renderWhitespace
Автодополнение приходит через секунду extension host или языковой сервер Developer: Show Running Extensions
Старт окна 15+ секунд активация расширений Developer: Startup Performance, Help: Start Extension Bisect
«Висит» при сохранении форматтер, ФС-watcher, антивирус files.watcherExclude, точечный formatOnSave

Extension Bisect — недооценённая команда: бинарный поиск по установленным расширениям (отключает половину и спрашивает «стало лучше?»). За 4–5 итераций находит виновника из сорока.

Monaco и модель текста

Редакторская часть — Monaco — доступна отдельной библиотекой; на ней сделаны и веб-версия VS Code, и редакторы внутри чужих продуктов. Три её решения определяют поведение. Виртуализация: в DOM живут только видимые строки плюс запас, поэтому файл на 200 тысяч строк открывается приемлемо — но языковые фичи в нём отключаются по порогам вроде editor.maxTokenizationLineLength. Двухслойная подсветка: быстрая приблизительная по TextMate-грамматикам плюс точная семантическая от языкового сервера — вот почему цвет иногда «доуточняется» через полсекунды. Асинхронность всего: автодополнение, hover, подсказки типов — запросы за границу процесса, поэтому VS Code принципиально не может гарантировать мгновенную подсказку, и весь UI спроектирован так, чтобы её отсутствие не ломало работу.

Сравните с подходом JetBrains, где семантическая модель живёт в том же процессе, что и UI, и доступна синхронно. Это даёт другие возможности рефакторинга и другую цену — подробно в следующей статье.

Ленивая активация: главный механизм производительности

Установленные расширения по умолчанию не загружены. Каждое объявляет условия активации, и VS Code исполняет его код только когда условие выполнилось.

Значимые на практике условия из справочника activation events:

Событие Когда срабатывает Комментарий
onLanguage:python открыт файл на Python правильный выбор для языковых расширений
workspaceContains:**/go.mod в проекте есть такой файл активация «по признаку проекта», очень дёшево
onCommand:ext.doThing вызвана команда подставляется автоматически для contributes.commands
onStartupFinished после отрисовки окна приемлемо
* немедленно при старте красный флаг, тормозит всех

Оценивая расширение перед установкой, откройте вкладку с манифестом и посмотрите activationEvents. Звёздочка означает загрузку всегда и во всех окнах — даже если вы правите YAML, а расширение про Kotlin. Три таких — и старт вырастает на несколько секунд.

Заметьте общий вектор всей истории продукта: LSP отдал наружу языки (2016), DAP — отладчики (2018), Remote Development — файловую систему (2019), спецификация devcontainer — окружение (2022). Ядро осталось маленьким, а экосистема выросла. Это и есть ответ на «почему выиграл именно он».

Часть 2. Расширения

Два способа что-то добавить

API делится на две принципиально разные части, и их постоянно путают. Contribution points — декларации в package.json: никакого кода, вы пишете JSON, и VS Code добавляет пункт меню, тему, сниппет, настройку, грамматику. Дёшево, безопасно и не требует активации. Программный API — модуль vscode, доступный только внутри extension host: window, workspace, languages, debug, commands; полная поверхность — в справочнике.

Осмысленный минимум — счётчик TODO в статус-баре. В package.json объявляем "activationEvents": ["onStartupFinished"], "main" и команду в contributes.commands; вся остальная логика — здесь:

import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  // Элемент статус-бара создаём один раз и переиспользуем
  const item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
  context.subscriptions.push(item);

  const update = async () => {
    const pattern = new RegExp(
      vscode.workspace.getConfiguration('todoCounter').get<string>('pattern', 'TODO'), 'g');
    const files = await vscode.workspace.findFiles(
      '**/*.{ts,js,go,py}', '**/{node_modules,dist,.git}/**', 5000);

    let total = 0;
    for (const uri of files) {
      // workspace.fs, а не node:fs — работает и в контейнере, и по SSH, и в браузере
      const bytes = await vscode.workspace.fs.readFile(uri);
      total += (Buffer.from(bytes).toString('utf8').match(pattern) ?? []).length;
    }
    item.text = `$(checklist) TODO: ${total}`;
    item.show();
  };

  context.subscriptions.push(
    vscode.commands.registerCommand('todoCounter.count', update),
    // Пересчитываем при сохранении, а не на каждое нажатие клавиши
    vscode.workspace.onDidSaveTextDocument(() => update())
  );
  update();
}

Три вещи показаны намеренно. context.subscriptions — всё, что создаёт ресурс, обязано быть туда положено, иначе после десятка Reload Window накопятся висящие слушатели и окно начнёт тормозить; это самая частая ошибка в самописных расширениях. vscode.workspace.fs вместо node:fs — прямой fs работает, только когда файлы физически рядом. И никакой работы в activate() сверх необходимого: всё, что можно отложить, откладывается. Упаковка — через npx vsce package, получившийся .vsix можно раздавать внутри компании (code --install-extension file.vsix) без публикации в Marketplace.

Marketplace, Open VSX и лицензии

Здесь начинается неприятная часть, о которой обычно молчат. Официальный VS Code — это проприетарная сборка открытого кода. Репозиторий microsoft/vscode под MIT, но бинарник Microsoft собирает со своим брендингом, телеметрией и лицензией, ограничивающей доступ к Marketplace продуктами Microsoft.

Отсюда: VSCodium — сборка того же кода без телеметрии и брендинга, Marketplace ей недоступен, вместо него Open VSX под управлением Eclipse Foundation. Часть популярных расширений там отсутствует или отстаёт — особенно расширения самой Microsoft: C++, C#, Pylance, Remote-SSH, Dev Containers; их лицензия разрешает использование только в продуктах Microsoft. Вывод прагматика: нужны Pylance или Remote-SSH — берите официальную сборку и принимайте лицензию; критична открытость — берите VSCodium, но заранее проверьте, что ваш язык там нормально поддержан. Для компаний есть и юридический нюанс: массовое обращение к Marketplace из сторонних сборок нарушает условия использования и при аудите иногда всплывает.

Безопасность: расширение — это чужой код на вашей машине

Расширение исполняется с правами вашего пользователя: может читать ~/.ssh и .env, ходить в сеть, запускать процессы. Серьёзного аудита Marketplace не проводит; известны случаи typosquatting — публикации расширений с именами вроде «Prettier — Code formatter» от постороннего издателя.

Что реально помогает: Workspace Trust (security.workspace.trust.enabled, включён по умолчанию) — открытие чужого репозитория в ограниченном режиме блокирует задачи и большинство расширений; не отключайте его «чтобы не спрашивал», это единственная преграда между git clone от незнакомца и исполнением кода. Профили — отдельный набор расширений под контекст; профиль «аудит чужого кода» с тремя расширениями это хорошая гигиена. И банальное: проверять издателя, число установок и дату обновления.

Рабочая эмпирика по количеству — 10–15 расширений на профиль: два-три языковых, одно про Git, одно про тесты, форматтер, остальное вкусовщина. Всё, чем не пользовались месяц, удаляется. Каждое лишнее — память, время старта и риск конфликта форматтеров (классика: Prettier и встроенный форматтер спорят за editor.defaultFormatter, и файл форматируется по-разному в зависимости от того, кто выиграл гонку).

Часть 3. Конфигурация

Иерархия настроек

Настройки разрешаются по слоям, каждый следующий перекрывает предыдущий:

default → user → remote (конкретный удалённый хост)
        → workspace (.vscode/settings.json) → folder (multi-root) → language-specific

Порядок объясняет половину «загадочного поведения». «Почему у меня табы, хотя я поставил пробелы?» — скорее всего, в репозитории лежит .vscode/settings.json.

Рабочий settings.json

Не «конфиг мечты», а база, которая имеет смысл почти всем. Комментарии разрешены — файл читается как JSONC.

{
  "editor.fontFamily": "JetBrains Mono, Menlo, Consolas, monospace",
  "editor.rulers": [88, 120],           // визуальная граница длины строки
  "editor.minimap.enabled": false,      // экономит место и такты renderer
  "editor.stickyScroll.enabled": true,  // сигнатура функции прилипает к верху — очень полезно
  "editor.linkedEditing": true,         // переименование парного HTML-тега
  "editor.formatOnSave": true,
  "editor.formatOnPaste": false,        // почти всегда мешает
  "editor.codeActionsOnSave": {
    "source.organizeImports": "explicit",
    "source.fixAll.eslint": "explicit"
  },

  "files.trimTrailingWhitespace": true,
  "files.insertFinalNewline": true,
  "files.eol": "\n",
  // watcherExclude — прямое лекарство от 100% CPU на больших проектах
  "files.watcherExclude": {
    "**/node_modules/**": true, "**/.git/objects/**": true,
    "**/target/**": true, "**/dist/**": true
  },
  "search.exclude": { "**/node_modules": true, "**/*.lock": true, "**/dist": true },

  // Языковые переопределения — самый недоиспользуемый механизм в конфиге
  "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.tabSize": 4 },
  "[go]":     { "editor.defaultFormatter": "golang.go", "editor.insertSpaces": false },
  "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.tabSize": 2 },
  "[markdown]": { "editor.wordWrap": "on" },

  "terminal.integrated.scrollback": 20000,
  "git.autofetch": true,
  "diffEditor.ignoreTrimWhitespace": false,   // иначе прячет реальные различия
  "telemetry.telemetryLevel": "error",
  "workbench.editor.enablePreview": false,    // файлы открываются «насовсем», а не курсивом
  "window.autoDetectColorScheme": true
}

Два пункта заслуживают отдельного слова. files.watcherExclude — самое частое лекарство от «VS Code жрёт процессор»: на проектах с крупным node_modules или target наблюдение за ФС порождает десятки тысяч дескрипторов, а на Linux можно упереться в fs.inotify.max_user_watches. workbench.editor.enablePreview: false — мелочь, меняющая ощущение от работы: по умолчанию файл, открытый одним кликом, показывается курсивом и заменяется следующим, так что десять переходов подряд оставляют одну вкладку. Кому-то это нравится, кого-то бесит; знать про переключатель полезно всем.

Клавиши и when-контексты

keybindings.json — не просто список сочетаний: каждое привязывается к контексту, и это превращает клавиатуру в модальную систему без модальности.

[
  // «Прогнать тесты» — только когда открыт файл на Python
  { "key": "ctrl+k ctrl+t", "command": "workbench.action.tasks.runTask",
    "args": "pytest: текущий файл", "when": "editorLangId == python" },
  // Escape в терминале возвращает фокус в редактор
  { "key": "escape", "command": "workbench.action.focusActiveEditorGroup",
    "when": "terminalFocus" },
  // Минус снимает встроенную привязку — единственный способ переопределить без конфликта
  { "key": "ctrl+p", "command": "-workbench.action.quickOpen" },
  { "key": "ctrl+p", "command": "workbench.action.quickOpenPreviousRecentlyUsedEditor" },
  // Навигация по диагностике — то, чем пользуешься весь день
  { "key": "alt+j", "command": "editor.action.marker.next", "when": "editorFocus" },
  { "key": "alt+k", "command": "editor.action.marker.prev", "when": "editorFocus" }
]

Приходящим из Vim разумно взять VSCodeVim или neovim-интеграцию плюс "vim.handleKeys" для точечного возврата отдельных сочетаний обратно VS Code. Что вы при этом теряете и приобретаете — разобрано в статьях про Vim и Neovim.

tasks.json: не забывайте, что он есть

Задачи — встроенный запускатор команд с разбором вывода. Главная ценность в problemMatcher: ошибки компилятора превращаются в кликабельные записи в панели Problems.

{
  "version": "2.0.0",
  "tasks": [
    { "label": "build", "type": "shell", "command": "go build ./...",
      "group": { "kind": "build", "isDefault": true },
      "problemMatcher": ["$go"],           // встроенный матчер под формат ошибок Go
      "presentation": { "reveal": "silent", "panel": "shared" } },

    { "label": "test: watch", "type": "shell", "command": "npx vitest --watch",
      "isBackground": true,
      "problemMatcher": {
        "owner": "typescript",
        "fileLocation": ["relative", "${workspaceFolder}"],
        "pattern": {
          "regexp": "^\\s*(.*):(\\d+):(\\d+)\\s+-\\s+(error|warning):\\s+(.*)$",
          "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5 },
        // Без background-секции VS Code считает watch-задачу зависшей
        "background": { "activeOnStart": true,
          "beginsPattern": "RERUN", "endsPattern": "Waiting for file changes" }
      } }
  ]
}

Часть 4. Отладка

Debug Adapter Protocol

Отладка устроена так же, как языковая поддержка: редактор ничего не знает про конкретный отладчик. Он говорит на DAP — JSON-протоколе поверх stdio, а debug adapter переводит сообщения в команды gdb, delve, debugpy, инспектора Node.js или чего угодно ещё.

Понимание цепочки экономит часы. «Отладчик не останавливается на точке» всегда сводится к одному из трёх: адаптер не получил setBreakpoints (не тот файл), адаптер не смог сопоставить файл с тем, что видит рантайм (source maps или пути внутри контейнера), либо рантайм просто не исполняет этот код.

launch.json на реальных примерах

{
  "version": "0.2.0",
  "configurations": [
    { // Node/TypeScript через tsx — без отдельного шага сборки
      "name": "Node: текущий файл", "type": "node", "request": "launch",
      "runtimeExecutable": "npx", "runtimeArgs": ["tsx"], "program": "${file}",
      "console": "integratedTerminal", "envFile": "${workspaceFolder}/.env.local",
      "skipFiles": ["<node_internals>/**", "**/node_modules/**"] },

    { // Подключение к запущенному процессу — основной режим для докера
      "name": "Node: attach к контейнеру", "type": "node", "request": "attach",
      "address": "localhost", "port": 9229, "restart": true,
      "localRoot": "${workspaceFolder}",
      "remoteRoot": "/app" },              // ключевая строка: сопоставление путей

    { "name": "Python: pytest текущий файл", "type": "debugpy", "request": "launch",
      "module": "pytest", "args": ["${file}", "-vv", "-x"],
      "console": "integratedTerminal",
      "justMyCode": false },               // иначе не зайти внутрь библиотек

    { "name": "Go: сервис с флагами", "type": "go", "request": "launch", "mode": "auto",
      "program": "${workspaceFolder}/cmd/api",
      "args": ["--config", "configs/dev.yaml"], "env": { "LOG_LEVEL": "debug" } }
  ],
  "compounds": [
    { "name": "Full stack", "stopAll": true,
      "configurations": ["Go: сервис с флагами", "Node: текущий файл"] }
  ]
}

Про skipFiles и justMyCode стоит сказать отдельно: по умолчанию отладчик прячет внутренности рантайма и библиотек — удобно, пока вы не отлаживаете именно библиотеку. Тогда justMyCode: false (Python) или пустой skipFiles (Node) обязательны, иначе шаг внутрь функции молча проваливается наружу.

Точки останова, которыми не пользуются, а зря

Тип Как поставить Зачем
Условная правый клик на точке → Edit Breakpoint → Expression остановиться на 5000-й итерации, а не жать F5 пять тысяч раз
По счётчику там же, Hit Count «останови на десятом вызове»
Logpoint правый клик → Logpoint, текст с {выражение} печать в консоль без правки кода
По исключению панель Breakpoints, галочки поймать место броска, а не место, где его поймали
По данным зависит от адаптера: есть в C++, C# остановиться при изменении переменной

Logpoints стоит попробовать всем, кто отлаживает логами: тот же поток печати, но без правки исходников — а значит, без риска закоммитить отладочный вывод и без пересборки.

Часть 5. Remote Development

Самая недооценённая функция VS Code. Идея: разделить редактор на две половины и растащить их по разным машинам. Локально остаётся то, что рисует пиксели, удалённо — всё, что трогает файлы.

Разделение VS Code на локальную и удалённую половины

Это не «редактирование по сети» вроде SSHFS или rmate. Языковой сервер работает на удалённой машине, рядом с исходниками и зависимостями, поэтому автодополнение и переход к определению остаются мгновенными независимо от канала: по сети едут не файлы, а результаты запросов — на порядок меньше трафика. Реализаций одного механизма четыре: Remote-SSH (мощный сервер, GPU-машина, доступ во внутреннюю сеть), Dev Containers (воспроизводимое окружение проекта), WSL (Windows-машина с Linux-тулчейном) и Tunnels / vscode.dev (доступ к своей машине из браузера без проброса портов).

extensionKind: главный источник путаницы

Каждое расширение объявляет сторону: ["ui"] — только локально, ["workspace"] — только удалённо, оба значения — предпочитает первое, умеет второе. Если что-то «сломалось при переходе на remote», в девяти случаях из десяти расширение оказалось не на той стороне. Лечится в пользовательских настройках:

{
  "remote.extensionKind": {
    "ms-azuretools.vscode-docker": ["ui"],   // докер стоит на моей машине — расширение локально
    "esbenp.prettier-vscode": ["workspace"]  // форматтер — обязательно рядом с кодом
  }
}

Практика Remote-SSH

Host devbox
    HostName 10.0.4.17
    User marat
    ForwardAgent yes
    # Мультиплексирование: второе и последующие подключения мгновенны
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 10m
    # Не отваливаться при простое — VS Code этого очень не любит
    ServerAliveInterval 30
    ServerAliveCountMax 6

При первом подключении на сервер выкачивается VS Code Server — сотни мегабайт в $HOME, что на машинах с квотой на домашний каталог оказывается сюрпризом (управляется через remote.SSH.serverInstallPath). Сервер требует glibc не ниже определённой версии: на старых CentOS/RHEL свежий клиент просто не подключится. Зато проброс портов автоматический — подняли сервер на :3000 внутри, VS Code заметит и предложит открыть localhost:3000 локально; это одна из самых удобных мелочей во всём продукте. Ключи при ForwardAgent yes остаются локально — не копируйте приватные ключи на dev-сервер.

Экономика: remote окупается, когда сборка тяжёлая, а ноутбук — нет. Компиляция монорепозитория на 32-ядерной машине против MacBook Air — это разница между двумя минутами и двадцатью, плюс исходники и доступы физически не покидают контур. Не окупается при плохом канале: задержка выше ~150 мс делает работу с терминалом некомфортной, а обрыв связи означает потерю несохранённого состояния.

Часть 6. Dev Containers

«У меня работает» — не шутка, а системная стоимость. Новый разработчик тратит от полудня до трёх дней на настройку окружения; версии Python расходятся между машинами; кто-то поставил Node 18, а CI собирает на 22. Dev Containers переносят описание окружения в репозиторий, где ему и место. Спецификация containers.dev с 2022 года открыта и не привязана к VS Code — её поддерживают GitHub Codespaces, JetBrains Gateway и отдельный CLI.

// .devcontainer/devcontainer.json
{
  "name": "api-service",
  "dockerComposeFile": "../docker-compose.dev.yml",
  "service": "app",
  "workspaceFolder": "/workspace",

  // Features — переиспользуемые куски установки вместо копипасты в Dockerfile
  "features": {
    "ghcr.io/devcontainers/features/go:1": { "version": "1.23" },
    "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "customizations": {
    "vscode": {
      // Эти расширения ставятся ВНУТРЬ контейнера автоматически
      "extensions": ["golang.go", "ms-azuretools.vscode-docker", "eamodio.gitlens"],
      "settings": { "go.toolsManagement.checkForUpdates": "off" }
    }
  },
  "forwardPorts": [8080, 5432],
  "portsAttributes": {
    "8080": { "label": "API", "onAutoForward": "notify" },
    "5432": { "label": "Postgres", "onAutoForward": "silent" }
  },

  // Четыре разные фазы жизненного цикла — их постоянно путают
  "onCreateCommand": "go env -w GOFLAGS=-mod=mod",
  "updateContentCommand": "go mod download",
  "postCreateCommand": "make dev-setup",
  "postStartCommand": "git config --global --add safe.directory /workspace",

  "mounts": ["source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind"],
  "remoteUser": "vscode",
  "containerEnv": { "TZ": "Europe/Moscow" }
}

Различие фаз — источник большинства неработающих конфигов. Правило простое: всё, что зависит от файлов проекта, идёт в postCreateCommand; всё, что нужно при каждом запуске, — в postStartCommand.

Compose и производительность

Одного контейнера почти никогда не хватает — нужны БД, кэш, очередь.

# docker-compose.dev.yml
services:
  app:
    build: { context: ., dockerfile: .devcontainer/Dockerfile }
    volumes:
      - ..:/workspace:cached
      # Кэши и зависимости — в именованные тома, НЕ в bind mount: иначе очень медленно
      - go-build-cache:/home/vscode/.cache/go-build
      - go-mod-cache:/go/pkg/mod
    command: sleep infinity     # контейнер просто живёт, работу ведёт VS Code
    depends_on: [db, redis]
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/app
  db:
    image: postgres:16-alpine
    environment: { POSTGRES_USER: dev, POSTGRES_PASSWORD: dev, POSTGRES_DB: app }
    volumes: [pgdata:/var/lib/postgresql/data]
  redis:
    image: redis:7-alpine

volumes: { pgdata: , go-build-cache: , go-mod-cache: }

command: sleep infinity — обязательная деталь: контейнер разработки не должен запускать приложение, иначе он завершится при падении процесса и утащит за собой сессию.

На Linux devcontainer работает почти без накладных расходов — bind mount там нативный. На macOS и Windows файловая система пробрасывается через виртуализацию, и разница драматическая: npm ci в bind mount идёт в разы дольше, чем в именованном томе. Рецепты по убыванию эффекта: (1) каталоги зависимостей и кэшей — в именованные тома, как выше; (2) на macOS включить VirtioFS в Docker Desktop; (3) флаг :cached для исходников; (4) радикальный вариант clone in volume — репозиторий целиком живёт в томе Docker, максимальная скорость ценой недоступности файлов локальным инструментам.

CLI и связь с CI

devcontainer CLI позволяет использовать то же описание вне редактора — и это превращает devcontainer из «удобства для IDE» в единый источник правды об окружении:

npm i -g @devcontainers/cli
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . go test ./...   # ровно то окружение, что у разработчика

В GitHub Actions это даёт CI, гарантированно совпадающий с локальной средой — шаг devcontainers/ci@v0.3 с imageName, cacheFrom и runCmd: make ci. Больше про воспроизводимость окружений — в треке DevOps.

Часть 7. Честное сравнение

VS Code — не универсально лучший выбор.

Критерий VS Code JetBrains Neovim
Время до первой строки кода минуты десятки минут часы или дни
Тяжёлые рефакторинги в Java/C# слабо эталон отрасли слабо
Отладка хорошая, единообразная через DAP глубже, особенно JVM и .NET требует настройки, DAP через плагины
Монорепозиторий на 10 млн строк приемлемо тяжело, индексация долгая лучше всех, ничего не индексирует
Потребление памяти 0,5–2 ГБ 2–8 ГБ десятки МБ
Remote и контейнеры эталон отрасли Gateway, слабее и менее стабильно нативно, через SSH и tmux
Веб-версия полноценная ограниченная нет
Настройка под себя JSON, широко, но не глубоко GUI, глубоко в рамках модели без границ, ценой времени
Цена бесплатно 100–800 USD в год бесплатно

Где VS Code честно слаб. Крупные Java- и C#-проекты: расширения существуют и работают, но рефакторинг вроде «извлечь интерфейс из класса с сорока использованиями в двенадцати модулях» в JetBrains делается надёжно, а здесь — с оговорками; причина архитектурная, LSP описывает ограниченный набор операций, и сложные семантические преобразования в него ложатся плохо. Очень большие файлы: мегабайтный лог откроется, но с отключёнными фичами. Память под нагрузкой: три окна по три языковых сервера — несколько гигабайт, на машине с 8 ГБ тесно. Единообразие UX: каждое расширение придумывает свои настройки, команды и панели; в JetBrains функциональность интегрирована и предсказуема, здесь вы собираете IDE из деталей разных производителей — и швы видны.

Итоговое сравнение всех инструментов трека — в отдельной статье.

Типичные ошибки

  1. Ставить расширение «на всякий случай». Ревизия раз в квартал: удалить всё, чем не пользовались.
  2. Отключать Workspace Trust. Экономит два клика, открывает исполнение произвольного кода при клонировании чужого репозитория.
  3. Не класть .vscode/settings.json и extensions.json в репозиторий. Командные настройки формата и рекомендованных расширений экономят часы споров в ревью. А вот launch.json с личными путями и .env туда не надо — им место в .gitignore.
  4. Игнорировать files.watcherExclude — классическая причина «непонятной» нагрузки на CPU.
  5. Отлаживать console.log, когда есть logpoints — тот же результат без правки кода.
  6. Bind mount для node_modules в devcontainer на macOS — самая частая причина «контейнеры тормозят, вернёмся к локальной установке».
  7. Путать фазы жизненного цикла devcontainer: npm ci в onCreateCommand не сработает, исходников там ещё может не быть.
  8. Считать remote «редактированием по сети». Непонимание того, что языковой сервер работает удалённо, порождает неверные ожидания от канала и неправильно расставленный extensionKind.

Мини-итог

VS Code силён не функциями, а архитектурой: маленькое ядро, вынесенные наружу LSP и DAP, изолированный extension host, ленивая активация. Extension host в отдельном процессе — причина, по которой чужой код не подвешивает набор текста; цена — полностью асинхронный API. Расширения имеют реальную стоимость (старт, память, безопасность), поэтому держите 10–15 штук, смотрите activationEvents и не отключайте Workspace Trust. Настройки разрешаются слоями default → user → remote → workspace → folder → language, и этим объясняется половина «загадочного поведения». Отладка — это DAP: умение читать цепочку setBreakpointsstoppedstackTrace превращает «не останавливается на точке» из мистики в диагностику. Remote Development разрезает редактор по границе «пиксели / файлы» и остаётся лучшей технологией в своём классе, а Dev Containers переносят окружение в репозиторий и через CLI распространяются на CI — с главным подводным камнем в виде производительности файловой системы на macOS и Windows. Слабые места честны и известны: тяжёлые рефакторинги в статически типизированных экосистемах, очень большие файлы, память.

Источники

Что дальше

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

JetBrains IDE: индексация, рефакторинги, инспекции, когда они окупаются

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов