Отладка выражений в Expression Language

Expression Language в Vega представляет собой компактный JavaScript-подобный синтаксис, встроенный в систему декларативных визуализаций. Он используется для вычисления значений сигналов, параметров, фильтров, вычисляемых полей и реактивных выражений внутри графиков.

Главная особенность — выполнение выражений происходит не в глобальном JavaScript-контексте, а в изолированной среде интерпретатора, где доступны только ограниченные переменные и функции: datum, event, signals, а также набор математических и логических операций.

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


Модель выполнения выражений

Каждое выражение в Vega компилируется в функцию интерпретатора и исполняется в момент:

  • изменения сигнала;
  • обновления данных;
  • обработки событий мыши/клавиатуры;
  • пересчёта трансформаций.

В отличие от обычного JavaScript, выражения не имеют доступа к внешним переменным и работают в строго определённой области видимости.

Ключевые источники данных внутри выражений:

  • datum — текущая строка данных
  • event — DOM-событие
  • signals — реактивные параметры визуализации

Пример выражения:

"expr": "datum.value > 10 ? 'high' : 'low'"

Основные причины ошибок в выражениях

Большинство проблем в Expression Language связано не с синтаксисом JavaScript как таковым, а с ограничениями окружения выполнения.

Неопределённые значения

Типичная ошибка:

Cannot read property 'x' of undefined

Возникает, когда datum отсутствует или структура данных не соответствует ожидаемой.

Проблемные выражения:

datum.a.b

если datum.a равен null.

Корректный подход:

datum.a && datum.a.b

или

datum.a ? datum.a.b : 0

Несовпадение сигналов

Сигналы могут быть не определены в момент первого вычисления.

signal("brush").extent[0]

Ошибка возникает, если brush ещё не создан или не инициализирован.

Более устойчивый вариант:

signal("brush") ? signal("brush").extent[0] : 0

Ошибки типов

Expression Language не выполняет строгую типизацию. Это приводит к скрытым проблемам:

  • строки вместо чисел
  • NaN после арифметических операций
  • некорректная конкатенация

Пример:

datum.value + 10

Если datum.value — строка, результат будет строковым.

Защита:

+datum.value + 10

Инструменты отладки Vega

Vega Editor

Основной инструмент диагностики выражений — Vega Editor. Он предоставляет:

  • мгновенную компиляцию спецификации;
  • отображение ошибок Expression Language;
  • интерактивное изменение сигналов;
  • инспектор данных и сигналов.

Ошибки выражений отображаются в консоли и overlay-панели.


Логирование через signals

Сигналы могут использоваться как промежуточные точки отладки.

{
  "name": "debug_value",
  "update": "datum.value"
}

Далее значение можно наблюдать через инспектор сигналов.


Использование console.log

В Expression Language доступна ограниченная функция логирования:

log(datum.value)

Однако использование log зависит от конфигурации интерпретатора Vega и может быть отключено в production-сборках.


Изоляция выражений для диагностики

Один из эффективных методов — последовательное упрощение выражения.

Исходное выражение:

datum.a > 10 && signal("enabled") ? datum.b * 2 : datum.c / datum.d

Шаги упрощения:

  1. Проверка datum:
datum.a
  1. Проверка сигнала:
signal("enabled")
  1. Разделение логики:
datum.b * 2
datum.c / datum.d

Такой подход позволяет локализовать источник ошибки.


Отладка в трансформациях Vega-Lite

Vega-Lite использует Expression Language в ограниченном контексте, особенно в трансформациях:

  • calculate
  • filter
  • window (частично)

Пример calculate:

{
  "calculate": "datum.sales * 1.2",
  "as": "adjusted_sales"
}

Частые ошибки в calculate

  1. отсутствие поля:
datum.sales * 1.2

если sales не существует

  1. строковое значение:
"100" * 1.2
  1. фильтрация по undefined:
datum.value > threshold

если threshold не определён как сигнал или поле


Поведение интерпретатора выражений

Expression Language в Vega интерпретируется, а не компилируется в нативный JavaScript код.

Это означает:

  • отсутствует полноценный stack trace;
  • ошибки часто указывают на общий тип проблемы;
  • выполнение происходит в sandbox-среде.

При ошибке интерпретатор возвращает:

  • undefined как fallback;
  • либо выбрасывает runtime error в зависимости от конфигурации.

Работа с datum и контекстом выполнения

datum является ключевой переменной, но его содержимое зависит от контекста:

  • в marks — текущий элемент данных;
  • в transforms — строка потока данных;
  • в signals — отсутствует.

Ошибка:

datum.value

в сигнале приведёт к datum is undefined.

Корректное разделение контекстов:

  • marks: datum
  • signals: signal("name")
  • events: event

Отладка событийных выражений

События мыши и клавиатуры передаются через event.

Пример:

"on": {
  "click": "event.x"
}

Типичные проблемы:

  • отсутствие ожидаемых координат
  • различия между event.x и event.clientX
  • несоответствие координатной системы

Для диагностики полезно временно возвращать объект:

event

Типичные паттерны ошибок Expression Language

1. Глубокие цепочки доступа

datum.a.b.c.d

Любой null в цепочке приводит к падению выражения.

Устойчивый вариант:

datum.a && datum.a.b && datum.a.b.c && datum.a.b.c.d

2. Деление на ноль

datum.a / datum.b

если datum.b = 0, результатом будет Infinity.


3. Смешение сигналов и datum

datum.value + signal("offset")

если offset не число, возникает неявное преобразование типов.


Практики стабилизации выражений

Явное приведение типов

Number(datum.value)

или

+datum.value

Защита от null

datum.value ? datum.value : 0

или

datum.value || 0

(с осторожностью, так как 0 также считается falsy)


Проверка сигналов

signal("x") != null ? signal("x") : 0

Реактивная отладка сигналов

Сигналы в Vega образуют граф зависимостей. Ошибка в одном сигнале может каскадно влиять на десятки выражений.

Для диагностики полезно:

  • отслеживать входящие зависимости;
  • минимизировать цепочки вычислений;
  • временно отключать производные сигналы.

Пример цепочки:

"a" -> "b" -> "c"

Ошибка в a делает некорректными b и c.


Особенности ошибок компиляции выражений

Ошибки делятся на два класса:

Синтаксические

  • отсутствующие скобки
  • некорректные операторы
  • ошибки парсинга

Пример:

datum.value >

Рантайм-ошибки

  • undefined поля
  • неверные типы
  • отсутствующие сигналы

Изоляция выражений в сложных визуализациях

В крупных спецификациях Vega выражения часто распределены по:

  • marks
  • scales
  • axes
  • signals
  • transforms

Отладка упрощается при временном разбиении:

  • удаление второстепенных marks
  • отключение transforms
  • упрощение сигналов до констант

Использование вычисляемых сигналов как инструмент диагностики

Сигналы могут выступать как промежуточные переменные:

{
  "name": "step_debug",
  "update": "datum.value * 2"
}

или цепочка:

{
  "name": "step1",
  "update": "datum.value"
},
{
  "name": "step2",
  "update": "signal('step1') * 2"
}

Такой подход позволяет локализовать момент появления ошибки.


Поведение при некорректных выражениях

При ошибке Expression Language Vega часто:

  • пропускает отрисовку элемента;
  • возвращает null в вычисление;
  • продолжает выполнение остальной визуализации.

Это создаёт эффект «тихих ошибок», когда график отображается частично некорректно без явного падения.