SIL — это язык программирования для автоматизации работы в Atlassian Jira и Confluence. Вы можете почитать больше про SIL вот здесь.
Я часто работаю со скриптами, написанными на SIL, и хотел бы поделиться с Вами моими мыслями по тому, как сделать код SIL «чистым».
В этой статье я сначала сформулирую правила, которыми я руководствуюсь при написании кода на SIL, а затем приведу пример кода и сделаю рефакторинг, используя эти правила.
Теперь рассмотрим пример кода на SIL.
Посмотрев на код достаточно сложно быстро определить, что делает код. Комментарии в коде тоже не очень помогают. Давайте попробуем изменить код в соответствии с правилами выше.
Сначала посмотрим на структуры. Вот один из примеров:
Мы видим, что «storage storage», выглядит непонятным. Было бы понятнее, если бы код был вот таким:
Теперь видно, что мы определяем поле в структуре под именем storage типа Storage. Для того, чтобы мы могли написать такой код, нам необходимо сделать первую букву каждой структуры заглавной:
Теперь давайте переименуем структуры таким образом, чтобы из названия структуры было бы понятно для чего она нужна, и нам не пришлось бы добавлять комментарии:
По моему мнению такие наименования не требуют дополнительных комментариев для структур. Также мы объявим структуры в самом начале кода.
Мы использовали Правила 1, 5, 6.
Теперь посмотрим на код после объявления структур:
Посмотрев на код мы не можем быстро понять, что происходит в коде. Давайте сначала попробуем разбить код на логические блоки:
То есть сначала мы получаем содержимое новой страницы в Confluence, потом создаем http запрос, создаем страницу в Confluence и пишем комментарий в Jira запросе, что страница в Confluence создана. Теперь давайте реализуем этим функции:
Мы вынесли логические блоки в функции (Правило 2). Теперь мы можем просто написать код вот таким образом:
Код у нас содержит четыре логических блока. Сначала мы получаем json новой страницы, потом создаем страницу в Confluence, добавляем комментарий к запросу и возвращаем статус создания страницы в Confluence. Эти четыре строчки дают нам верхнеуровневое представление о том, что делает скрипт. Если мы хотим понять содержимое страницы в Confluence, то мы всегда можем перейти в getNewConfluencePageContent и посмотреть содержимое этой функции. Эта функция содержит только код для создания содержимого страницы и ничего лишнего, таким образом, изучая код функции, мы не будем отвлекаться на другую функциональность.
Мы можем предположить, что нам нужно будет создавать страницы в Confluence не только из одного скрипта. Поэтому давайте создадим файл confluence_helper.incl и вынесем все необходимые функции для создания страницы в Confluence в этот файл:
Я сделал функцию createConfluencePage более общий, добавив в нее еще один параметр confluenceUrl. Таким образом мы выполнили Правило 4.
Теперь наш основной скрипт будет выглядеть вот таким образом:
В первой строке я включил файл confluence_helper.incl, чтобы использовать из него функцию создания страниц в Confluence.
Я сохранил адрес Confluence в константной переменной и изменил имена переменных в соответствии с Google Java notation style (Правило 7).
Мне кажется, что на этом можно остановиться с рефакторингом. В результате рефакторинга мы получили переиспользуемую функцию создания страниц в Confluence, и наш код стал более читабельным и поддерживаемым.
Я часто работаю со скриптами, написанными на SIL, и хотел бы поделиться с Вами моими мыслями по тому, как сделать код SIL «чистым».
В этой статье я сначала сформулирую правила, которыми я руководствуюсь при написании кода на SIL, а затем приведу пример кода и сделаю рефакторинг, используя эти правила.
Правила
- Имена структур начинайте с заглавной буквы.
- Добавляйте элементы массивов, используя метод AddElement.
- Используйте пользовательские функции(user defined routines), чтобы разбить Ваш код на логические блоки и избавиться от комментариев в коде.
- Выносите переиспользуемый код в библиотеки(inclusions).
- Объявляйте структуры перед всем остальным объявлением переменных.
- Давайте значимые имена структурам и переменным, в этом случае Вам не придется добавлять дополнительные комментарии к структурами и переменным.
- Именуйте переменные и функции в соответствии с 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, и наш код стал более читабельным и поддерживаемым.
vmm86
Не соглашусь со вторым пунктом.
Для добавления к массиву удобнее использовать нотацию +=. Об этом же говорится в документации. Причём это должно работать как для добавления отдельного элемента, так и для слияния с другим массивом с таким же типом данных.
Функцию стоит использовать, если нужно добавить элемент, отсутствующий в массиве (с помощью arrayAddElementIfNotExist), или другие array routines для последующей модификации массива.
aleme Автор
Если будет вот такой код:
data это string или string []? Непонятно. Нужно будет смотреть объявление переменной и тратить на это лишнее время. Если таких строк будет много, то это будет отвлекать.
Если будет вот такая запись:
сразу понятно, что data это string [].
Конечно, можно договориться, что всем переменным типа массив добавлять Arr (dataArr) или добавлять s в конец (datas такого нет, поэтому придется что-то другое придумать). Но мне кажется, проще просто сделать код прозрачным. Да мы напишем чуть больше, но будет понятнее.
vmm86
data как название переменной противоречит пункту 6 — непонятно, что это за "данные" и в каком контексте используются. Всегда дописывать Arr или List необязательно, но человекопонятное название поможет понять, что это за тип данных (userNames, groups, issueKeys, etc.).
aleme Автор
Любой рефакторинг субъективен. Из моего опыта для прозрачности нужно использовать addElement.