Документирование кода

Документирование кода является важной частью разработки картографических приложений на Mapbox GL JS. По мере роста проекта увеличивается количество источников данных, слоёв, обработчиков событий, пользовательских компонентов и интеграций со сторонними сервисами. Отсутствие качественной документации приводит к усложнению сопровождения, увеличению времени внедрения новых разработчиков и повышению вероятности ошибок.

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


Уровни документации в проектах Mapbox GL JS

Документирование обычно разделяется на несколько уровней:

Документация проекта

Описывает архитектуру приложения:

  • структуру каталогов;
  • используемые библиотеки;
  • процесс сборки;
  • систему хранения геоданных;
  • правила именования объектов;
  • особенности развертывания.

Пример:

src/
├── map/
│   ├── layers/
│   ├── sources/
│   └── controls/
├── services/
├── utils/
└── styles/

Такое описание помогает быстро понять организацию проекта.


Документация модулей

Каждый модуль должен иметь описание назначения.

Пример файла:

// layers/buildings.js

/**
 * Создание слоя трехмерных зданий.
 * Используется после полной загрузки стиля карты.
 */
export function addBuildingsLayer(map) {
    // ...
}

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


Документация функций

Наиболее распространённый уровень документирования.

Пример:

/**
 * Добавляет источник GeoJSON на карту.
 *
 * @param {mapboxgl.Map} map Экземпляр карты.
 * @param {string} sourceId Идентификатор источника.
 * @param {Object} data GeoJSON объект.
 */
function addGeoJsonSource(map, sourceId, data) {
    map.addSource(sourceId, {
        type: 'geojson',
        data
    });
}

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

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

Использование стандарта JSDoc

Для JavaScript-проектов наиболее популярным форматом документирования является JSDoc.

Mapbox GL JS активно применяется в крупных проектах, поэтому использование JSDoc значительно упрощает поддержку кода.

Базовый синтаксис

/**
 * Создает слой маркеров.
 */
function createMarkersLayer() {
}

Документирование параметров

/**
 * Перемещает карту в указанную точку.
 *
 * @param {[number, number]} coordinates Координаты [lng, lat].
 * @param {number} zoom Уровень масштабирования.
 */
function flyToLocation(coordinates, zoom) {
    map.flyTo({
        center: coordinates,
        zoom
    });
}

Документирование возвращаемого значения

/**
 * Возвращает центр карты.
 *
 * @returns {[number, number]}
 */
function getMapCenter() {
    const center = map.getCenter();

    return [center.lng, center.lat];
}

Документирование объектов

/**
 * @typedef {Object} LayerConfig
 * @property {string} id Идентификатор слоя.
 * @property {string} source Источник данных.
 * @property {string} color Цвет объектов.
 */

Использование типов особенно полезно при работе с многочисленными настройками слоёв.


Документирование создания карты

Инициализация карты является ключевой частью приложения и требует подробного описания.

Пример:

/**
 * Создает экземпляр карты.
 *
 * @param {string} containerId ID HTML-контейнера.
 * @returns {mapboxgl.Map}
 */
function createMap(containerId) {
    return new mapboxgl.Map({
        container: containerId,
        style: 'mapbox://styles/mapbox/streets-v12',
        center: [37.6173, 55.7558],
        zoom: 10
    });
}

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


Документирование источников данных

Источники данных являются фундаментом любого картографического приложения.

Пример:

/**
 * Добавляет источник данных с объектами недвижимости.
 *
 * Источник обновляется каждые 5 минут через API.
 */
map.addSource('properties', {
    type: 'geojson',
    data: propertiesData
});

Подобные комментарии помогают понять происхождение данных.


Описание структуры GeoJSON

Особенно важно документировать пользовательские свойства объектов.

/**
 * Структура объекта недвижимости:
 *
 * {
 *   "type": "Feature",
 *   "properties": {
 *      "id": Number,
 *      "price": Number,
 *      "status": String
 *   }
 * }
 */

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


Документирование слоёв

Карта может содержать десятки слоёв.

Каждый слой должен иметь описание назначения.

Пример:

/**
 * Слой отображения автомобильных дорог.
 */
map.addLayer({
    id: 'roads',
    type: 'line',
    source: 'roads-source'
});

Документирование зависимостей между слоями

Часто порядок добавления слоёв имеет критическое значение.

/**
 * Добавляется после слоя дорог,
 * чтобы подписи отображались поверх линий.
 */
map.addLayer({
    id: 'road-labels',
    type: 'symbol',
    source: 'roads-source'
});

Такой комментарий предотвращает ошибки при рефакторинге.


Документирование выражений Mapbox

Mapbox GL JS активно использует Expressions.

Сложные выражения желательно сопровождать пояснениями.

Плохой пример:

'circle-radius': [
    'interpolate',
    ['linear'],
    ['zoom'],
    5, 2,
    15, 20
]

Хороший пример:

/**
 * Радиус маркера растет по мере увеличения масштаба:
 * zoom 5  -> radius 2
 * zoom 15 -> radius 20
 */
'circle-radius': [
    'interpolate',
    ['linear'],
    ['zoom'],
    5, 2,
    15, 20
]

Документирование обработчиков событий

События составляют значительную часть логики Mapbox-приложений.

Пример:

/**
 * Отображение карточки объекта при клике.
 */
map.on('click', 'buildings-layer', (event) => {
    const feature = event.features[0];

    showBuildingInfo(feature);
});

Документирование сложной логики событий

/**
 * При наведении:
 * 1. Изменяется курсор.
 * 2. Подсвечивается объект.
 * 3. Показывается всплывающая подсказка.
 */
map.on('mouseenter', 'restaurants-layer', () => {
    map.getCanvas().style.cursor = 'pointer';
});

Комментарий позволяет быстро понять полный сценарий взаимодействия.


Документирование пользовательских контролов

Во многих проектах создаются собственные элементы управления картой.

Пример:

/**
 * Контрол переключения тематических слоев.
 */
class LayerSwitcherControl {
    onAdd(map) {
        this.map = map;
    }

    onRemove() {
    }
}

Документирование публичного API контролов

/**
 * Включает слой.
 *
 * @param {string} layerId
 */
enableLayer(layerId) {
}

Каждый публичный метод должен иметь описание поведения.


Документирование асинхронного кода

Картографические приложения часто получают данные через API.

Пример:

/**
 * Загружает объекты с сервера
 * и обновляет источник карты.
 */
async function loadFeatures() {
    const response = await fetch('/api/features');
    const data = await response.json();

    map.getSource('features').setData(data);
}

Документирование ошибок

/**
 * Возможные ошибки:
 * - недоступность сервера;
 * - некорректный GeoJSON;
 * - отсутствие источника данных.
 */

Такие пояснения облегчают сопровождение системы.


Документирование пользовательских типов данных

Для крупных приложений рекомендуется формализовать структуры данных.

Пример:

/**
 * @typedef {Object} RestaurantFeature
 *
 * @property {number} id
 * @property {string} name
 * @property {string} category
 * @property {number} rating
 */

После этого тип можно использовать в комментариях:

/**
 * @param {RestaurantFeature} restaurant
 */
function createPopupContent(restaurant) {
}

Документирование конфигурации карты

Настройки приложения желательно выносить в отдельные объекты и документировать.

Пример:

/**
 * Конфигурация карты.
 */
const MAP_CONFIG = {
    center: [37.6173, 55.7558],
    zoom: 10,
    maxZoom: 18,
    minZoom: 3
};

Документирование ограничений

/**
 * Максимальный масштаб ограничен значением 18,
 * поскольку более высокие уровни недоступны
 * в используемом наборе тайлов.
 */

Важно объяснять причины ограничений.


Документирование стилей

Стилизация слоёв часто становится сложной частью проекта.

Пример:

/**
 * Цвет определяется уровнем загруженности дорог:
 *
 * green  - свободно
 * yellow - средняя загрузка
 * red    - пробка
 */

После этого следует соответствующее выражение Mapbox.


Документирование архитектурных решений

Комментарии должны фиксировать важные проектные решения.

Пример:

/**
 * Используется единый GeoJSON-источник,
 * поскольку обновление нескольких источников
 * значительно увеличивает нагрузку на браузер.
 */

Такая информация имеет большую ценность, чем описание очевидных строк кода.


Антипаттерны документирования

Комментарии, повторяющие код

Плохой пример:

// Создаем карту
const map = new mapboxgl.Map();

Комментарий не добавляет новой информации.


Устаревшая документация

Плохой пример:

/**
 * Загружает XML данные.
 */

Фактический код:

fetch('/api/data.json');

Несоответствие документации опаснее полного отсутствия комментариев.


Избыточные комментарии

Плохой пример:

// Получаем центр карты
const center = map.getCenter();

// Выводим центр
console.log(center);

Очевидные действия не требуют пояснений.


Автоматическая генерация документации

JSDoc позволяет автоматически формировать документацию проекта.

Пример документированного класса:

/**
 * Управляет отображением пользовательских маркеров.
 */
class MarkerManager {

    /**
     * Создает новый менеджер маркеров.
     *
     * @param {mapboxgl.Map} map
     */
    constructor(map) {
        this.map = map;
    }

    /**
     * Добавляет маркер.
     *
     * @param {[number, number]} coordinates
     */
    addMarker(coordinates) {
    }

    /**
     * Удаляет все маркеры.
     */
    clear() {
    }
}

На основе таких комментариев можно автоматически получить полноценную справочную документацию.


Практические рекомендации

Документировать причины, а не очевидные действия

Наиболее полезный комментарий:

/**
 * Используется кластеризация,
 * поскольку набор данных содержит
 * более 50 000 объектов.
 */

Документировать публичные интерфейсы

Обязательному описанию подлежат:

  • экспортируемые функции;
  • классы;
  • методы классов;
  • пользовательские типы;
  • конфигурационные объекты;
  • события;
  • контракты API.

Поддерживать документацию вместе с кодом

Любое изменение:

  • структуры GeoJSON;
  • настроек слоя;
  • сигнатуры функции;
  • конфигурации карты;

должно сопровождаться обновлением соответствующей документации.


Соблюдать единый стиль

Пример единообразного оформления:

/**
 * Краткое описание.
 *
 * @param {Type} param Описание параметра.
 * @returns {Type} Описание результата.
 */

Единый формат делает проект значительно более понятным и облегчает навигацию по коду даже при большом количестве картографических компонентов, источников данных и пользовательских модулей, характерных для приложений на Mapbox GL JS.