Чистый код в SIL

    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, и наш код стал более читабельным и поддерживаемым.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 9

      0

      Не соглашусь со вторым пунктом.


      Для добавления к массиву удобнее использовать нотацию +=. Об этом же говорится в документации. Причём это должно работать как для добавления отдельного элемента, так и для слияния с другим массивом с таким же типом данных.


      Функцию стоит использовать, если нужно добавить элемент, отсутствующий в массиве (с помощью arrayAddElementIfNotExist), или другие array routines для последующей модификации массива.

        0
        Если будет вот такой код:
        data += "new data";

        data это string или string []? Непонятно. Нужно будет смотреть объявление переменной и тратить на это лишнее время. Если таких строк будет много, то это будет отвлекать.
        Если будет вот такая запись:
        data = addElement(data, "new data");

        сразу понятно, что data это string [].
        Конечно, можно договориться, что всем переменным типа массив добавлять Arr (dataArr) или добавлять s в конец (datas такого нет, поэтому придется что-то другое придумать). Но мне кажется, проще просто сделать код прозрачным. Да мы напишем чуть больше, но будет понятнее.
          0

          data как название переменной противоречит пункту 6 — непонятно, что это за "данные" и в каком контексте используются. Всегда дописывать Arr или List необязательно, но человекопонятное название поможет понять, что это за тип данных (userNames, groups, issueKeys, etc.).

            0
            Любой рефакторинг субъективен. Из моего опыта для прозрачности нужно использовать addElement.
        +1
        Красавчик! Только первичное авторство кода бы написал, что первый день с SIL
          0
          )))))
          0
          Для запроса и ответа создания страницы в Confluence можно использовать одну и туже структуру. Только в запросе полей меньше заполнять. А из ответа можно получить полезные вещи: код страницы и ссылку на страницу, чтобы потом можно было в задаче линк сделать.
            0
            Если использовать одну структуру, то нужно ей дать какое-то название, из которого будет понятно, что это и запрос и ответ.
              0
              Просто ConfluenceApiData если любите длинные названия

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

          Самое читаемое