Clean SIL Code

SIL is a programming language for automating work in Atlassian Jira and Confluence. You can read more about SIL here .

I often work with scripts written in SIL, and would like to share with you my thoughts on how to make SIL code β€œclean”.

In this article, I will first formulate the rules that guide me when writing SIL code, and then give an example of code and do refactoring using these rules.

rules


  1. Names structures begin with a capital letter.
  2. Add array elements using the AddElement method .
  3. Use user defined routines to break your code into logical blocks and get rid of comments in the code.
  4. Bring reusable code to libraries ( inclusions ).
  5. Declare structures before the rest of the variable declaration.
  6. Give meaningful names to structures and variables, in this case you do not have to add additional comments to structures and variables.
  7. Name variables and functions according to the Google Java style guide .

Now let's look at an example of SIL code.

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;

Looking at the code is quite difficult to quickly determine what the code is doing. Comments in the code do not help much either. Let's try changing the code according to the rules above.

First look at the structure. Here is one example:

struct body {
    storage storage;
}

We see that β€œstorage storage" looks incomprehensible. It would be clearer if the code were like this:

struct body {
    Storage storage;
}

Now we see that we define a field in the structure under the name storage of type Storage. In order for us to write such code, we need to make the first letter of each structure capitalized:

// 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;
}

Now let's rename the structure so that the name of the structure makes it clear why it is needed, and we would not have to add comments:

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;
}

In my opinion, such names do not require additional comments for structures. We will also declare structures at the very beginning of the code.

We used Rules 1, 5, 6.

Now let's look at the code after the declaration of structures:

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;

Looking at the code, we cannot quickly understand what is happening in the code. Let's first try to break the code into logical blocks:

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

That is, first we get the contents of the new page in Confluence, then create an http request, create a page in Confluence and write a comment in the Jira request that the page in Confluence is created. Now let's implement these functions:

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;

We removed the logical blocks in the function (Rule 2). Now we can simply write the code like this:

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

Our code contains four logical blocks. First we get the json of the new page, then create the page in Confluence, add a comment to the request and return the page creation status to Confluence. These four lines give us a top-level view of what the script does. If we want to understand the contents of the page in Confluence, then we can always go to getNewConfluencePageContent and see the contents of this function. This function contains only code for creating the content of the page and nothing more, thus, while studying the function code, we will not be distracted by other functionality.

We can assume that we will need to create pages in Confluence not only from one script. Therefore, let's create the confluence_helper.incl file and put out all the necessary functions for creating a page in Confluence to this file:

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;
}

I made the createConfluencePage function more general by adding another confluenceUrl parameter to it. Thus, we followed Rule 4.

Now our main script will look like this:

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;

In the first line, I included the confluence_helper.incl file to use the page creation function in Confluence from it.

I saved the Confluence address in a constant variable and changed the variable names in accordance with Google Java notation style (Rule 7).

It seems to me that this can be stopped with refactoring. As a result of refactoring, we got a reusable page creation function in Confluence, and our code became more readable and supported.

All Articles