← Назад к статьям

Лучшие практики работы со ScriptRunner

За годы работы со ScriptRunner я накопил большой опыт написания скриптов для Jira. Некоторые подходы работают отлично, другие создают проблемы. В этой статье соберу лучшие практики, которые помогут писать эффективные, безопасные и поддерживаемые скрипты, а также избежать типичных ошибок.

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

Производительность критична, особенно для скриптов, которые выполняются часто (валидаторы, пост-функции, слушатели).

Кэширование объектов

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

// Плохо - получаем объект каждый раз
def user1 = ComponentAccessor.getUserManager().getUserByKey("user1")
def user2 = ComponentAccessor.getUserManager().getUserByKey("user2")

// Хорошо - получаем один раз
def userManager = ComponentAccessor.getUserManager()
def user1 = userManager.getUserByKey("user1")
def user2 = userManager.getUserByKey("user2")

Избегайте N+1 запросов

Не делайте запросы в циклах, если можно получить все данные одним запросом:

// Плохо
issues.each { issue ->
    def subTasks = issueManager.getSubTaskObjects(issue.id) // N запросов
}

// Хорошо - получаем все подзадачи одним запросом через JQL
def allSubTasks = searchService.search(user, 
    searchService.parseQuery(user, "parent in (PROJ-1, PROJ-2, ...)").query, 
    PagerFilter.getUnlimitedFilter())

Используйте DO_NOT_DISPATCH для массовых операций

При массовых обновлениях отключайте события, если они не нужны:

issueManager.updateIssue(user, issue, 
    com.atlassian.jira.event.type.EventDispatchOption.DO_NOT_DISPATCH, false)

Безопасность

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

Валидация входных данных

Всегда валидируйте входные данные перед использованием:

def userKey = params.userKey?.toString()
if (!userKey || userKey.isEmpty()) {
    return Response.status(400).entity([error: "userKey is required"]).build()
}

def user = userManager.getUserByKey(userKey)
if (!user) {
    return Response.status(404).entity([error: "User not found"]).build()
}

Проверка прав доступа

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

def permissionManager = ComponentAccessor.getPermissionManager()
if (!permissionManager.hasPermission(Permissions.ADMINISTER, issue.projectObject, user)) {
    throw new InvalidInputException("You don't have permission to perform this action")
}

Санитизация данных

При работе с пользовательским вводом очищайте данные от потенциально опасного содержимого:

def userInput = params.input?.toString()?.trim()
if (!userInput) {
    return
}

// Удаляем HTML-теги и потенциально опасные символы
def sanitized = userInput.replaceAll(/<[^>]*>/, "").replaceAll(/[<>"'&]/, "")

Обработка ошибок

Правильная обработка ошибок делает скрипты надёжными и упрощает отладку.

Всегда обрабатывайте исключения

try {
    performOperation()
} catch (Exception e) {
    log.error("Error performing operation: ${e.message}", e)
    // Не пробрасывайте исключения дальше без необходимости
    // Возвращайте понятные сообщения об ошибках
}

Используйте специфичные типы исключений

// В валидаторах
throw new InvalidInputException("Custom error message")

// В REST API
return Response.status(400).entity([error: "Invalid input"]).build()

Поддерживаемость кода

Код должен быть понятным и легко поддерживаемым.

Используйте понятные имена переменных

// Плохо
def u = ComponentAccessor.getUserManager().getUserByKey("john")
def i = issueManager.getIssueObject("PROJ-1")

// Хорошо
def userManager = ComponentAccessor.getUserManager()
def currentUser = userManager.getUserByKey("john")
def issue = issueManager.getIssueObject("PROJ-1")

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

// Получаем все открытые задачи проекта
def jql = "project = PROJ AND status != Done"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())

// Обрабатываем только задачи с высоким приоритетом
def highPriorityIssues = results.results.findAll { it.priority.name == "High" }

Выносите константы

def MAX_RETRIES = 3
def RETRY_DELAY = 1000
def BATCH_SIZE = 100

// Используйте константы вместо магических чисел
for (int i = 0; i < MAX_RETRIES; i++) {
    // ...
}

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

Вот ошибки, которые я часто вижу в скриптах:

Ошибка 1: Изменение задачи в getter вычисляемого поля

В getter нельзя изменять задачу — это вызовет бесконечные циклы:

// Плохо - НЕ ДЕЛАЙТЕ ТАК
def value = issue.getCustomFieldValue(someField)
issue.setCustomFieldValue(anotherField, "new value") // ОШИБКА!
issueManager.updateIssue(...)

// Изменения делайте только в setter или слушателях событий

Ошибка 2: Бесконечные циклы в слушателях

Слушатель обновляет задачу → генерируется событие → слушатель снова обновляет задачу:

// Плохо
def event = event
def issue = event.issue as MutableIssue
issue.setCustomFieldValue(field, "value")
issueManager.updateIssue(...) // Это вызовет новое событие!

// Хорошо - используйте флаг или проверяйте источник события
def autoUpdateField = customFieldManager.getCustomFieldObjectByName("Auto Updated")
if (issue.getCustomFieldValue(autoUpdateField) == "true") {
    return // Пропускаем автоматическое обновление
}

Ошибка 3: Игнорирование null

Всегда проверяйте значения на null:

// Плохо
def assignee = issue.assignee.displayName // Может быть NullPointerException

// Хорошо
def assignee = issue.assignee?.displayName ?: "Не назначен"

Тестирование скриптов

Всегда тестируйте скрипты перед применением на production:

  • Тестируйте на тестовых проектах
  • Проверяйте граничные случаи (null, пустые значения, большие объёмы данных)
  • Тестируйте на разных типах задач и проектах
  • Проверяйте производительность на реальных объёмах данных

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

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

  • Описание назначения скрипта
  • Входные параметры и их формат
  • Примеры использования
  • Известные ограничения

Выводы

Следуя лучшим практикам, вы создадите эффективные, безопасные и поддерживаемые скрипты. Помните о производительности, безопасности, обработке ошибок, поддерживаемости кода.

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

Если нужна помощь с написанием скриптов — свяжитесь со мной.