Google Apps Script: Как эффективно использовать глобальные переменные?

Что такое глобальные переменные и зачем они нужны?

Глобальные переменные в Google Apps Script — это переменные, объявленные вне тела любой функции. Они доступны для чтения и изменения из любого места в вашем скрипте, включая все функции и, при определенных условиях, даже между различными выполнениями скрипта (хотя такое поведение не гарантировано и зависит от жизненного цикла выполнения).

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

Область видимости переменных: глобальная vs. локальная

Ключевое отличие глобальных переменных от локальных заключается в их области видимости:

  • Глобальные переменные: Объявлены вне функций. Доступны из любой функции в пределах одного файла .gs или всего проекта (в зависимости от контекста выполнения).
  • Локальные переменные: Объявлены внутри функции (с использованием var, let или const). Доступны только в пределах той функции, где они объявлены, и вложенных в нее блоках.

Понимание этой разницы критично для предотвращения конфликтов имен и управления состоянием приложения.

Примеры использования глобальных переменных для упрощения кода

Представим сценарий обработки данных из Google Analytics API. Идентификатор представления (View ID) и, возможно, диапазон дат часто используются в нескольких запросах.

// Глобальная переменная для View ID
const GA_VIEW_ID = 'ga:123456789';

/**
 * Получает данные о сессиях.
 * @returns {GoogleAppsScript.AnalyticsReporting.Schema.Report | null} Отчет API или null при ошибке.
 */
function getSessionData() {
  try {
    // Используем глобальную переменную GA_VIEW_ID
    const reportRequest = AnalyticsReporting.newReportRequest();
    // ... настройка запроса с использованием GA_VIEW_ID ...
    const report = AnalyticsReporting.Reports.batchGet(reportRequest).getReports()[0];
    Logger.log('Отчет по сессиям получен.');
    return report;
  } catch (e) {
    Logger.log('Ошибка получения данных о сессиях: ' + e);
    return null;
  }
}

/**
 * Получает данные о пользователях.
 * @returns {GoogleAppsScript.AnalyticsReporting.Schema.Report | null} Отчет API или null при ошибке.
 */
function getUserData() {
  try {
    // Используем ту же глобальную переменную GA_VIEW_ID
    const reportRequest = AnalyticsReporting.newReportRequest();
    // ... настройка другого запроса с использованием GA_VIEW_ID ...
    const report = AnalyticsReporting.Reports.batchGet(reportRequest).getReports()[0];
    Logger.log('Отчет по пользователям получен.');
    return report;
  } catch (e) {
    Logger.log('Ошибка получения данных о пользователях: ' + e);
    return null;
  }
}

В этом примере GA_VIEW_ID объявлена один раз и используется в обеих функциях, что избавляет от необходимости передавать ее как параметр или объявлять локально в каждой функции.

Объявление и инициализация глобальных переменных

Способы объявления глобальных переменных в Apps Script

Глобальные переменные объявляются вне функций с использованием ключевых слов var, let или const. Рекомендуется использовать const для значений, которые не должны изменяться после инициализации (например, константы конфигурации), и let для переменных, значение которых может меняться во время выполнения скрипта.

// Объявление константы
const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID_HERE';

// Объявление изменяемой глобальной переменной
let reportCache = null;

// Использование var (менее предпочтительно из-за особенностей области видимости)
var lastRunTimestamp = null;

Рекомендации по именованию глобальных переменных

Для улучшения читаемости кода придерживайтесь следующих соглашений:

  • Константы: Используйте UPPER_SNAKE_CASE (все буквы заглавные, слова разделены подчеркиванием). Пример: API_ENDPOINT, DEFAULT_TIMEOUT.
  • Изменяемые глобальные переменные: Используйте camelCase. Пример: userDataCache, lastProcessedRow.
  • Выбирайте осмысленные имена, отражающие назначение переменной.

Инициализация глобальных переменных: когда и как?

Глобальные переменные инициализируются в момент загрузки скрипта, до выполнения какой-либо функции. Это означает, что вы можете присвоить им начальные значения прямо при объявлении.

// Инициализация строкой
const REPORT_NAME = 'Monthly Performance Report';

// Инициализация числом
const MAX_RETRIES = 3;

// Инициализация результатом вызова функции Apps Script (например, получение активного документа)
// Важно: Это значение будет определено один раз при загрузке скрипта.
const ACTIVE_SPREADSHEET = SpreadsheetApp.getActiveSpreadsheet();
const UI = SpreadsheetApp.getUi();

// Инициализация null для последующего заполнения (например, кэш)
let cachedData = null;

Инициализация сложными объектами или результатами вызовов API при объявлении может замедлить загрузку скрипта. В таких случаях предпочтительнее использовать ленивую инициализацию внутри функций при первом обращении.

Эффективное использование глобальных переменных

Глобальные переменные для хранения конфигурационных параметров

Это одно из самых частых и оправданных применений. Параметры, такие как ID документов, URL внешних сервисов, имена листов, ключи API (с осторожностью!), удобно хранить в глобальных константах.

// ID документа для отчетов
const REPORT_SPREADSHEET_ID = '...';
// Имя листа с исходными данными
const SOURCE_DATA_SHEET_NAME = 'Raw Data';
// URL внешнего API
const CRM_API_ENDPOINT = 'https://api.examplecrm.com/v2/';
// Настройки отчета
const REPORT_SETTINGS = {
  includeCharts: true,
  emailRecipient: 'manager@example.com'
};

/**
 * Генерирует отчет на основе глобальных настроек.
 */
function generateReport() {
  const ss = SpreadsheetApp.openById(REPORT_SPREADSHEET_ID);
  const sourceSheet = ss.getSheetByName(SOURCE_DATA_SHEET_NAME);
  // ... логика обработки данных ...
  if (REPORT_SETTINGS.includeCharts) {
    // ... создание диаграмм ...
  }
  // ... отправка отчета на REPORT_SETTINGS.emailRecipient ...
}

Использование глобальных переменных для кэширования данных

Если ваш скрипт многократно обращается к одним и тем же данным в течение одного выполнения (например, читает большой диапазон из Google Sheets или получает данные из внешнего API), кэширование в глобальной переменной может значительно повысить производительность, сократив количество дорогостоящих вызовов.

/** @type {Object[] | null} */
let productCatalogCache = null;

/**
 * Получает каталог продуктов, используя кэш.
 * @returns {Object[]} Массив объектов продуктов.
 */
function getProductCatalog() {
  if (productCatalogCache === null) {
    Logger.log('Кэш каталога пуст. Загрузка данных...');
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Products');
    // Предполагаем, что данные в формате [ [id, name, price], ... ]
    const data = sheet.getDataRange().getValues(); 
    const headers = data.shift(); // Удаляем заголовки
    productCatalogCache = data.map(row => ({
      id: row[0],
      name: row[1],
      price: row[2]
    }));
  }
  return productCatalogCache;
}

/**
 * Обрабатывает заказы, используя каталог продуктов.
 */
function processOrders() {
  const catalog = getProductCatalog(); // Получаем данные (из кэша, если уже загружены)
  // ... логика обработки заказов с использованием catalog ...
  const specificProduct = catalog.find(p => p.id === 'XYZ');
  Logger.log(`Цена продукта XYZ: ${specificProduct?.price}`);
}

/**
 * Выполняет другую операцию, также использующую каталог.
 */
function generateInventoryReport() {
  const catalog = getProductCatalog(); // Снова получаем данные (из кэша)
  // ... логика генерации отчета ...
  Logger.log(`Всего продуктов в каталоге: ${catalog.length}`);
}
Реклама

Важно помнить, что такой кэш живет только в рамках одного выполнения скрипта.

Применение глобальных переменных для обмена данными между функциями

Иногда необходимо передать состояние или результат работы одной функции в другую без явной передачи через аргументы. Глобальные переменные могут служить этим «общим пространством», но использовать этот подход следует с осторожностью.

/** @type {string | null} */
let currentStatus = null;

/**
 * Выполняет первый шаг обработки.
 */
function stepOne() {
  Logger.log('Выполнение шага 1...');
  // ... какая-то работа ...
  currentStatus = 'Шаг 1 выполнен успешно';
}

/**
 * Выполняет второй шаг, зависящий от статуса первого.
 */
function stepTwo() {
  if (currentStatus === 'Шаг 1 выполнен успешно') {
    Logger.log('Выполнение шага 2...');
    // ... какая-то работа ...
    currentStatus = 'Шаг 2 выполнен успешно';
  } else {
    Logger.log('Шаг 1 не был выполнен, пропуск шага 2.');
    currentStatus = 'Ошибка на шаге 1';
  }
}

/**
 * Основная функция рабочего процесса.
 */
function runWorkflow() {
  currentStatus = 'Начало';
  stepOne();
  stepTwo();
  Logger.log(`Итоговый статус: ${currentStatus}`);
}

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

Ограничения и потенциальные проблемы при использовании глобальных переменных

Проблемы с читабельностью и поддержкой кода при злоупотреблении глобальными переменными

Чрезмерное использование глобальных переменных делает код «неявным». Становится сложно отследить, где и как изменяется переменная, что затрудняет отладку и рефакторинг. Функции становятся менее предсказуемыми, так как их поведение зависит от внешнего состояния, а не только от входных аргументов.

Риски возникновения конфликтов имен

В больших проектах с множеством файлов .gs или при использовании библиотек возрастает риск случайного переопределения глобальной переменной, если имена выбраны недостаточно уникально. Это может привести к трудноуловимым ошибкам.

Альтернативы глобальным переменным: паттерны проектирования и best practices

Вместо широкого использования глобальных переменных рассмотрите следующие подходы:

  1. Передача через параметры функций: Самый явный и контролируемый способ обмена данными.

  2. Объекты конфигурации: Создайте объект, содержащий все настройки, и передавайте его в функции.

  3. Классы и модули: Инкапсулируйте данные и логику внутри классов. В Apps Script можно имитировать модульную структуру с помощью объектов или пространств имен.

  4. PropertiesService: Для хранения конфигурации, особенно чувствительных данных (API ключи, пароли) и значений, которые должны сохраняться между выполнениями скрипта. PropertiesService обеспечивает безопасное хранилище.

    // Сохранение API ключа
    PropertiesService.getScriptProperties().setProperty('API_KEY', 'SECRET_KEY');
    
    // Чтение API ключа
    const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY');
    
  5. Ленивая инициализация: Инициализируйте ресурсы только тогда, когда они действительно нужны, часто внутри функций.

Практические примеры и сценарии использования глобальных переменных

Пример 1: Глобальная переменная для URL spreadsheet документа

Удобно хранить URL или ID часто используемого документа в глобальной константе.

/** @const {string} */
const CENTRAL_DATA_SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';

/**
 * Читает данные из центральной таблицы.
 * @returns {any[][]} Двумерный массив данных.
 */
function readCentralData() {
  try {
    const ss = SpreadsheetApp.openById(CENTRAL_DATA_SPREADSHEET_ID);
    const sheet = ss.getActiveSheet();
    return sheet.getDataRange().getValues();
  } catch (e) {
    Logger.log(`Ошибка чтения данных из ${CENTRAL_DATA_SPREADSHEET_ID}: ${e}`);
    return [];
  }
}

Пример 2: Использование глобальной переменной для API key

Внимание: Хранить чувствительные API ключи напрямую в коде (даже в глобальных переменных) небезопасно. Используйте PropertiesService для таких данных. Этот пример демонстрирует концепцию, но для реальных ключей используйте PropertiesService.

// Пример с НЕ ЧУВСТВИТЕЛЬНЫМ ключом или ID
/** @const {string} */
const PUBLIC_API_IDENTIFIER = 'pub-analytics-123';

// ДЛЯ ЧУВСТВИТЕЛЬНЫХ КЛЮЧЕЙ - ПРАВИЛЬНЫЙ ПОДХОД:
/**
 * Получает API ключ из Script Properties.
 * @returns {string | null} API ключ или null, если не найден.
 */
function getApiKey_() {
  return PropertiesService.getScriptProperties().getProperty('MY_SECURE_API_KEY');
}

/**
 * Выполняет запрос к API.
 */
function callExternalApi() {
  const apiKey = getApiKey_();
  if (!apiKey) {
    Logger.log('API ключ не настроен в Script Properties.');
    return;
  }
  const url = `https://api.service.com/data?apiKey=${apiKey}&id=${PUBLIC_API_IDENTIFIER}`;
  // ... выполнение запроса с UrlFetchApp ...
}

Пример 3: Кэширование данных из spreadsheet с использованием глобальной переменной

Повторим пример с кэшированием, акцентируя внимание на глобальной переменной как хранилище кэша.

/** @type {Map<string, number> | null} */
let currencyRatesCache = null;

/** 
 * Загружает или возвращает из кэша курсы валют.
 * @returns {Map<string, number>} Map, где ключ - код валюты (напр., 'USD'), значение - курс.
 */
function getCurrencyRates_() {
  if (currencyRatesCache === null) {
    Logger.log('Загрузка курсов валют...');
    currencyRatesCache = new Map();
    try {
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Currency Rates');
      const data = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();
      data.forEach(row => {
        if (row[0] && typeof row[1] === 'number') {
          currencyRatesCache.set(row[0].toString().toUpperCase(), row[1]);
        }
      });
    } catch (e) {
      Logger.log(`Ошибка загрузки курсов валют: ${e}`);
      // Возвращаем пустой Map или обрабатываем ошибку иначе
      currencyRatesCache = new Map(); 
    }
  }
  return currencyRatesCache;
}

/**
 * Конвертирует сумму в базовую валюту.
 * @param {number} amount Сумма.
 * @param {string} currencyCode Код валюты (напр., 'USD').
 * @returns {number} Конвертированная сумма или NaN при ошибке.
 */
function convertToRub(amount, currencyCode) {
  const rates = getCurrencyRates_(); // Используем кэш
  const rate = rates.get(currencyCode.toUpperCase());
  if (rate) {
    return amount * rate;
  } else {
    Logger.log(`Курс для ${currencyCode} не найден.`);
    return NaN;
  }
}

// Пример использования
function calculateTotalRevenue() {
  const salesData = [['USD', 100], ['EUR', 50], ['USD', 75]]; // Пример данных
  let totalRub = 0;
  for (const sale of salesData) {
    totalRub += convertToRub(sale[1], sale[0]);
  }
  Logger.log(`Общая выручка в RUB: ${totalRub.toFixed(2)}`);

  // Еще один вызов для демонстрации кэширования
  const additionalSale = convertToRub(200, 'USD');
  Logger.log(`Доп. продажа в RUB: ${additionalSale.toFixed(2)}`); // Курсы будут взяты из кэша
}

Использование глобальных переменных может быть эффективным инструментом в Google Apps Script при разумном подходе. Они отлично подходят для констант конфигурации и простого кэширования в рамках одного выполнения. Однако избегайте злоупотребления ими для управления сложным состоянием и всегда отдавайте предпочтение PropertiesService для хранения чувствительных данных.


Добавить комментарий