Что такое глобальные переменные и зачем они нужны?
Глобальные переменные в 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
Вместо широкого использования глобальных переменных рассмотрите следующие подходы:
-
Передача через параметры функций: Самый явный и контролируемый способ обмена данными.
-
Объекты конфигурации: Создайте объект, содержащий все настройки, и передавайте его в функции.
-
Классы и модули: Инкапсулируйте данные и логику внутри классов. В Apps Script можно имитировать модульную структуру с помощью объектов или пространств имен.
-
PropertiesService: Для хранения конфигурации, особенно чувствительных данных (API ключи, пароли) и значений, которые должны сохраняться между выполнениями скрипта.PropertiesServiceобеспечивает безопасное хранилище.// Сохранение API ключа PropertiesService.getScriptProperties().setProperty('API_KEY', 'SECRET_KEY'); // Чтение API ключа const apiKey = PropertiesService.getScriptProperties().getProperty('API_KEY'); -
Ленивая инициализация: Инициализируйте ресурсы только тогда, когда они действительно нужны, часто внутри функций.
Практические примеры и сценарии использования глобальных переменных
Пример 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 для хранения чувствительных данных.