Best Practices for Writing ScriptRunner Behaviours for JIRA
By Sean Jason Byrne
Atlassian Team Senior Engineer
During a recent project, our team was tasked with making sense out of an enormous and very ugly chunk of code written for the Behaviours plugin for JIRA. Behaviours started life as an independent add-on for JIRA. The modern implementation is included in the Script Runner add-on for JIRA.
The intent of the code was simple: The code was used to hide or show fields on a page. The user would select from one field (a select list) and then, depending on what the user selected, other fields would either be hidden or shown on the page. Simple.
Unfortunately, the code we were handed made this simple task very complex. Wading through this monolith of complex code made me yearn for something simple and intuitive. I could see what the original programmer was looking to do. I could also see a much simpler way to code it and test it.
In this blog I want to share some basic coding techniques that can help keep your life simpler. I also want to demonstrate how Behaviours for JIRA can get extremely complex if you don’t follow these techniques.
The Multi-Select Example
In this multi-select example, the program needed to allow the user to select multiple choices from 27 different options. Each choice would result in the field associated with that option either being visible or hidden. While this example might appear to be simple, it is actually vey complicated.
To illustrate this, let’s pretend there were only two options from which to select instead of 27: Option A and Option B. I like to use a truth table when I think of these things. With just these two options we end up with four possibilities:
Option A Option B
Selected Not Selected
Not Selected Selected
Not Selected Not Selected
As this truth table illustrates, two selection options yields four possible combinations of those options. Mathematically, this is calculated by 2 to the power of however many options you have, in this case 22. Which means that with 27 possible options we would have 227 possible combinations, i.e. 134,217,728 possible combinations!
Add into this mix some misbehaving code and you have a testing nightmare on your hands.
What are some of the basics of what makes good code “good”?
Let’s look at an example of some “good” code:
Why is this “good” code? This is “good” code because it…
• Uses descriptive names for the variables. The goal here is to name the fields in a way that would allow someone else to intuitively figure out what the variable actually means.
For example, take a look at line 14 above. A variable is labeled “isCatSelected.” This is a clear, descriptive name for this field. It is intuitively obvious that this field tells us if the user has selected “Cat” or not (from a selection box). In contrast, if the variable was labeled “Cat,” the meaning would be less clear…and if it had been labeled “Animal3” then it would not be clear at all.
Using the word “is” for Boolean-type variables (i.e. true, false, yes, no) is just a good way to communicate the meaning of what that variable is capturing and whether or not it should be true or false.
For example, let’s look at a variable such as “isItRaining.” When it is set like this, “isItRaining = yes,” the “is” makes it very clear what this variable means.
Using descriptive names not only makes the code much easier to update, it also makes it easier for you to notice logic errors. For example, take a look at line 24. This line says that if cat, dog or fish is selected, the “pet leash laws” field will be visible (note: for some reason the imported code base uses the term “setHidden” when it really should say “setIsVisible”). Someone who is debugging this code could easily see that an error has probably been made, as “pet leash laws” generally do not apply to fish!
• Takes advantage of proven modules of code. When you are making your code, ask yourself: am I reinventing the wheel? Does this code already exist in the libraries that are available through the import statements? After all, there’s really no need to build everything from scratch every time you want to create some code. Instead, you can (and should!) use a pre-made framework as much as possible to do the work for you.
To do this, simply use the word “import” to import each desired set of tools into your code. In this example, lines 1 through 7 are all “import” statements that enable us to make use of some of the many tried and tested pre-made sets of tools that are readily available for developers.
Keep in mind, though, that even when you’re importing ready-made code, you still need to define the variables within your code. That’s why, in this example, lines 9 through 20 all begin with “def” – these are the definitions that the Behaviours plugin needs in order to make use of imported modules. With this type of programming, “defining” a variable creates the variable so that it can be used.
• Keeps it simple. Good code is easy to read because it is grouped into sections, with blank lines and spacing to enhance readability. Then, within each section, each line is devoted to just doing one simple thing. In this example, the first section covers all of the imported tools. The next section has all of the variable definitions, separated into sub-sections based on category.
Finally, after everything has been declared up front, lines 22 to 24 do the actual work. There really are only two steps here: Find out what the user has selected, and apply this selection to determining what is visible. When the page loads up, this is the code that is waiting to be activated. The user makes a selection from the multi-select boxes. The code looks to see what has been selected, and then pulls references to the other fields that we may want to show—such as the pet license field—and determines if those other fields should be hidden or visible.
• Uses good coding technique. For example, look at this OR statement and how drastically it simplifies this code. Line 22 is a good example of an OR statement, where the || symbol means “OR.” It reads:
petLicenseField.setHidden(isDogSelected || isCatSelected)
If either dog or cat is selected as an option, then the pet license field will be visible (remember, the quirk in this code framework says “setHidden” when it should say “setIsVisible”). Adding another pet to the list is simple, and could look like this:
petLicenseField.setHidden(isDogSelected || isCatSelected || isKangarooSelected)
This approach is straightforward, easy to follow and easy to maintain.
What does code look like when it is less than ideal?
Here is a small example of some code that…misbehaves. It is difficult to change and difficult to adapt to new changes. Code like this will suck time and resources from your organization:
How can you quickly identify “bad” code? “Bad” code is…
• Hard to read. In this example, the coder did not use descriptive names for the variables, leaving one to wonder what “conditions A through N” refer to, and how these conditions relate to whatever is on the page in JIRA. Yes, there might be something on the page called “Condition A.” But chances are there is not, rendering these names completely devoid of human meaning.
• Overly complex. While it appears to be simple to use “if statements”—if this then do that—when multiple checkboxes are involved, having an “if statement” for each checkbox in the multi-select is actually not simple at all. This results in fields being set and re-set over and over again. For example, just in this small section of the code, conditions A through D are set in lines 23-27 with one “if statement,” and then again in lines 31-34 with another “if statement”.
• Monolithic. If you have a very, very long piece of code, chances are it isn’t good. Modularity is a key to good code. Each piece of code should do one simple job, and that is it. Then each modular piece of code can plug into other code like a Lego block to help make the larger code.
• Not based on the math. Good code addresses the core mathematics of the problem being solved. If the math isn’t addressed in a clear way, the code will be flaky at best.
Best practices for writing Behaviours for JIRA, or any code, are all about simplicity and readability. The bottom line is, good code is elegant, easy to read, intuitive to understand, exploits pre-made frameworks, and gets the job done in as few steps as possible.
Keeping these coding techniques in mind when you write a Behaviour for JIRA will buy you a lot more reliability and peace of mind, saving, time, money and effort.