SIL — это язык программирования для автоматизации работы в Atlassian Jira и Confluence. Вы можете почитать больше про SIL вот здесь.

Я часто работаю со скриптами, написанными на SIL, и хотел бы поделиться с Вами моими мыслями по тому, как сделать код SIL «чистым».

В этой статье я сначала сформулирую правила, которыми я руководствуюсь при написании кода на SIL, а затем приведу пример кода и сделаю рефакторинг, используя эти правила.

Правила


  1. Имена структур начинайте с заглавной буквы.
  2. Добавляйте элементы массивов, используя метод AddElement.
  3. Используйте пользовательские функции(user defined routines), чтобы разбить Ваш код на логические блоки и избавиться от комментариев в коде.
  4. Выносите переиспользуемый код в библиотеки(inclusions).
  5. Объявляйте структуры перед всем остальным объявлением переменных.
  6. Давайте значимые имена структурам и переменным, в этом случае Вам не придется добавлять дополнительные комментарии к структурами и переменным.
  7. Именуйте переменные и функции в соответствии с Google Java style guide.

Теперь рассмотрим пример кода на SIL.

string USER = currentUser();
// Response
struct returnData {
    string status;
}
// Project
struct space {
    string key;
}
// Inner part with content
struct storage {
    string value;
    string representation;
}
// Part for storage
struct body {
    storage storage;
}
// Main entity for sending to Confluence
struct reqData {
    string type;
    string title;
    space space;
    body body;
}
reqData data;
data.type = "page";
data.title = "Page for issue " + key + "  " + summary + ".";
data.space.key = project;
data.body.storage.value = "<p> Author:"+userFullName(USER) + " description: "  + description + "</p>";
data.body.storage.representation = "storage";
// Create request
HttpRequest request;
HttpHeader header = httpCreateHeader("Content-Type", "application/json");
HttpHeader authHeader = httpBasicAuthHeader("admin", "admin");
request.headers += header;
request.headers += authHeader;
logPrint("WARN", request);
//POST
string JSONData = toJson(data);
logPrint("WARN", JSONData);
returnData result = httpPost("http://192.168.54.203:8090/rest/api/content/", request, JSONData);
string errMsg = httpGetErrorMessage();
logPrint("ERROR", "Last error message: " + errMsg);
logPrint("WARN", result);
string COMMENT = "Page created in Confluence " + updated + " by " + userFullName(USER) + " Status : " +result.status + "."; 
addComment(key, USER, COMMENT);
//Return Status
return result.status;

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

Сначала посмотрим на структуры. Вот один из примеров:

struct body {
    storage storage;
}

Мы видим, что «storage storage», выглядит непонятным. Было бы понятнее, если бы код был вот таким:

struct body {
    Storage storage;
}

Теперь видно, что мы определяем поле в структуре под именем storage типа Storage. Для того, чтобы мы могли написать такой код, нам необходимо сделать первую букву каждой структуры заглавной:

// Response
struct ReturnData {
    string status;
}
// Project
struct Space {
    string key;
}
// Inner part with content
struct Storage {
    string value;
    string representation;
}
// Part for storage
struct Body {
    Storage storage;
}
// Main entity for sending to Confluence
struct ReqData {
    string type;
    string title;
    Space space;
    Body body;
}

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

struct Space {
    string key;
}
struct Storage {
    string value;
    string representation;
}
struct Body {
    Storage storage;
}
struct CreateConfluencePageRequest {
    string type;
    string title;
    Space space;
    Body body;
}
struct CreateConfluencePageResponse {
    string status;
}

По моему мнению такие наименования не требуют дополнительных комментариев для структур. Также мы объявим структуры в самом начале кода.

Мы использовали Правила 1, 5, 6.

Теперь посмотрим на код после объявления структур:

string USER = currentUser();
CreateConfluencePageRequest data;
data.type = "page";
data.title = "Page for issue " + key + "  " + summary + ".";
data.space.key = project;
data.body.storage.value = "<p> Author:"+userFullName(USER) + " description: "  + description + "</p>";
data.body.storage.representation = "storage";
// Create request
HttpRequest request;
HttpHeader header = httpCreateHeader("Content-Type", "application/json");
HttpHeader authHeader = httpBasicAuthHeader("admin", "admin");
request.headers += header;
request.headers += authHeader;
logPrint("WARN", request);
//POST
string JSONData = toJson(data);
logPrint("WARN", JSONData);
CreateConfluencePageResponse result = httpPost("http://192.168.54.203:8090/rest/api/content/", request, JSONData);
string errMsg = httpGetErrorMessage();
logPrint("ERROR", "Last error message: " + errMsg);
logPrint("WARN", result);
string COMMENT = "Page created in Confluence " + updated + " by " + userFullName(USER) + " Status : " +result.status + "."; 
addComment(key, USER, COMMENT);
//Return Status
return result.status;

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

getNewConfluencePageContent();
createHttpRequest();
createConfluencePage();
addCommentToJiraIssue();

То есть сначала мы получаем содержимое новой страницы в Confluence, потом создаем http запрос, создаем страницу в Confluence и пишем комментарий в Jira запросе, что страница в Confluence создана. Теперь давайте реализуем этим функции:

function getNewConfluencePageContent() {
    CreateConfluencePageRequest data;
    data.type = "page";
    data.title = "Page for issue " + key + "  " + summary + ".";
    data.space.key = project;
    data.body.storage.value = "<p> Author:"+userFullName(currentUser()) + " description: "  + description + "</p>";
    data.body.storage.representation = "storage";
    return toJson(data);
}

function createHttpRequest() {
    HttpRequest request;
    HttpHeader header = httpCreateHeader("Content-Type", "application/json");
    HttpHeader authHeader = httpBasicAuthHeader("admin", "admin");
    request.headers = addElement(request.headers, header);
    request.headers += addElement(request.headers, authHeader);
    logPrint("WARN", request);
    return request;
}

function createConfluencePage(string pageJson) {
    HttpRequest request = createHttpRequest();
    CreateConfluencePageResponse result = httpPost("http://192.168.54.203:8090/rest/api/content/", request, pageJson);
    logPrint("ERROR", "Last error message: " + httpGetErrorMessage());
    logPrint("WARN", result);
    return result;
}

function addCommentToJiraIssue(string resultStatus) {
    string COMMENT = "Page created in Confluence " + updated + " by " + userFullName(currentUser()) + " Status : " +resultStatus + "."; 
    addComment(key, currentUser(), COMMENT);
}

string pageJson = getNewConfluencePageContent();
CreateConfluencePageResponse result = createConfluencePage(pageJson);
addCommentToJiraIssue(result.status);
return result.status;

Мы вынесли логические блоки в функции (Правило 2). Теперь мы можем просто написать код вот таким образом:

string pageJson = getNewConfluencePageContent();
CreateConfluencePageResponse result = createConfluencePage(pageJson);
addCommentToJiraIssue(result.status);
return result.status;

Код у нас содержит четыре логических блока. Сначала мы получаем json новой страницы, потом создаем страницу в Confluence, добавляем комментарий к запросу и возвращаем статус создания страницы в Confluence. Эти четыре строчки дают нам верхнеуровневое представление о том, что делает скрипт. Если мы хотим понять содержимое страницы в Confluence, то мы всегда можем перейти в getNewConfluencePageContent и посмотреть содержимое этой функции. Эта функция содержит только код для создания содержимого страницы и ничего лишнего, таким образом, изучая код функции, мы не будем отвлекаться на другую функциональность.

Мы можем предположить, что нам нужно будет создавать страницы в Confluence не только из одного скрипта. Поэтому давайте создадим файл confluence_helper.incl и вынесем все необходимые функции для создания страницы в Confluence в этот файл:

struct Space {
    string key;
}
struct Storage {
    string value;
    string representation;
}
struct Body {
    Storage storage;
}
struct CreateConfluencePageRequest {
    string type;
    string title;
    Space space;
    Body body;
}
struct CreateConfluencePageResponse {
    string status;
}

function createHttpRequest() {
    HttpRequest request;
    HttpHeader header = httpCreateHeader("Content-Type", "application/json");
    HttpHeader authHeader = httpBasicAuthHeader("admin", "admin");
    request.headers = addElement(request.headers, header);
    request.headers += addElement(request.headers, authHeader);
    logPrint("INFO", request);
    return request;
}

function createConfluencePage(string confluenceUrl, string pageJson) {
    HttpRequest request = createHttpRequest();
    CreateConfluencePageResponse result = httpPost(confluenceUrl, request, pageJson);
    logPrint("ERROR", "Last error message: " + httpGetErrorMessage());
    logPrint("INFO", result);
    return result;
}

Я сделал функцию createConfluencePage более общий, добавив в нее еще один параметр confluenceUrl. Таким образом мы выполнили Правило 4.

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

include "confluence_helper.incl";

function getNewConfluencePageContent() {
    CreateConfluencePageRequest data;
    data.type = "page";
    data.title = "Page for issue " + key + "  " + summary + ".";
    data.space.key = project;
    data.body.storage.value = "<p> Author:"+userFullName(currentUser()) + " description: "  + description + "</p>";
    data.body.storage.representation = "storage";
    return toJson(data);
}

function addCommentToJiraIssue(string resultStatus) {
    string comment = "Page created in Confluence " + updated + " by " + userFullName(currentUser()) + " Status : " +resultStatus + "."; 
    addComment(key, currentUser(), comment);
}


const string CONFLUENCE_URL = "http://192.168.54.203:8090/rest/api/content/";

string pageJson = getNewConfluencePageContent();
CreateConfluencePageResponse result = createConfluencePage(CONFLUENCE_URL, pageJson);
addCommentToJiraIssue(result.status);
return result.status;

В первой строке я включил файл confluence_helper.incl, чтобы использовать из него функцию создания страниц в Confluence.

Я сохранил адрес Confluence в константной переменной и изменил имена переменных в соответствии с Google Java notation style (Правило 7).

Мне кажется, что на этом можно остановиться с рефакторингом. В результате рефакторинга мы получили переиспользуемую функцию создания страниц в Confluence, и наш код стал более читабельным и поддерживаемым.