Code Architecture: Decreasing bugs by decreasing code surface
This post arose from seeing the same type of code question, same type of bug over and over and feeling that the acceptable solutions don't really address the problem: Code structure is causing us headaches by having too many lines in which to hide bugs.
Sample use case: Query a table and grab X number of fields values from it.
Example 1: Familiar ServiceNow Code
try {
var choices = new GlideRecord('sys_choice');
choices.addQuery('element','u_function');
choices.addQuery('name','incident');
choices.addQuery('inactive',false);
choices.query();
var data = [];
while (choices.next()) {
data.push({
'label': choices.getValue('label'),
'dependent_value': choices.getValue('dependent_value'),
'name': choices.getVaue('name')
});
}
} catch (error) {
//do something
}
In the sndev slack channel code as the one above is the norm. It goes through reviews, best practices, and any issues will undoubtedly get an answer. Slack channel is certainly the very best place to ask code questions. go there if you are not already a member.
The problem that is not immediately visible is that its structure inherently leads to too many logical units crammed into one space, creating more lines than needed, thereby increasing the chance for bugs to hide. The basic principle is that the more involved a while(gr.next()) loop grows, the more space there is for bugs.
The solution is to decrease code surface by introducing functions to the repetitive nature of ServiceNow APIs such as breaking the code in the following form:
- Create the glide record
- query the glide record
- loop
- functions to execute within the loop
- a try catch
Example 2: Refactored Example 1 (Outside functions names and hardcoded parameters, this point-free style is in our Production Instance)
function dependentChoices(element) {
return DBTable('sys_choice')
.queryWhileCompose([valuesForFields(['label', 'dependent_value','name'])])
('element='+ element +'^name=incident^inactive=false');
}
Example 2 has decrease code space, increased scalability through function queryWhileCompose that accepts any number of functions to execute against each glide record. Made new GlideRecord and while isolated to DBTable: they won't be found anywhere else in code. And allow for trivial updates of functionality.
Though I tend to prefer point-free coding as shown in example 1, that LISP-like style can be a mouthful to swallow. In that case the more familiar temporary variables helps while keeping the code concise.
Example 3: Using temporary variables to break down the process
var fieldNames = ['label', 'dependent_value'];
var getValues = valuesForFields(fieldNames);
var query = 'element=u_function^name=incident^inactive=false';
var choices = DBTable('sys_choice')
.queryWhileCompose([getValues])(query);
Example 3: Adding more functionality
var fieldNames = ['label', 'dependent_value'];
var getValues = valuesForFields(fieldNames);
var alertBusinessOwner = alertBusinessOwnerOfNewActiveChoice(eventName);
var query = 'element=u_function^name=incident^inactive=false';
var getDependentChoices = [getValues, activateIfDeactive, alertBusinessOwner, logMetaDataChange];
var choices = DBTable('sys_choice')
.queryWhileCompose(getDependentChoices)(query);
In the example above, there have been 3 additional functions added: activateIfDeactive, alertBusinessOwner, logMetaDataChange. Each handling single responsibility. If an error is introduce it must be one of those functions. this makes the function easier to digest. It readily informs the reader what is happening, expelling all ambiguity while being drastically less code in which to hide bugs.
Summary
Bugs love large surfaces, the more code available the greater the chances are for bugs. If you find yourself geographically navigating code, somethings is wrong. It is extremely difficult to debug a large surface of code.
https://www.servicenow.com/community/developer-articles/code-architecture-decreasing-bugs-by-decreasing-code-surface/ta-p/2322256