Calculation Writing Tutorial - 3.  The importance of Indenting and checking NULL values

Version 8

    Verified Product Versions

    LANDESK Service Desk 7.6LANDESK Service Desk 7.7.xLANDESK Service Desk 7.8.xLANDESK Service Desk 2016.xLANDESK Asset Central 2016.x

    Introduction

    This document is the third in a set of tutorials on writing calculations in Service Desk.  Each topic can be read independently but for an introduction to calculations please see the following document: Calculation Writing Tutorial - 1. What is a calculation? What is Boo?.

     

    Intended audience

    Some experience in object and process design is recommended, as well as the basic concept of calculations as covered in the first tutorial (linked above).

     

    The importance of Indenting

    The term indenting refers to the number of spaces or TABs at the start of a line of code.  In Boo indents are used to determine how blocks of code are arranged.

     

    When an indent is required

    All calculations use at least one indent for the code that appears beyond the first two lines.  In the blank template calculation displayed in the calculation editor this indent is a single TAB (or in versions prior to 7.5 where TAB was not possible in the editor the indent was two spaces).

     

    In a simple calculation like all those in the previous tutorials all the calculation code is in one "block", which is to say that all the lines of code are to be executed in order without exception.  However in the following example (the same as the last example from the previous tutorial) a new block of code is used within the "if" statement.  A longer indent is required to tell Boo that those specific lines of code are only to be executed when the "if" statement condition is true:

     

    import System
    static def GetAttributeValue(Incident):
              Value = 'Hello ' + Incident.RaiseUser.Title
              if Incident.Category.FullName == 'Hardware':
                        Value += '.  This is a hardware issue!'
                        Value += ' And here is more text appended.'
              return Value
    

     

    A very easy rule to follow is: If a line of code ends with a colon ":" the next line(s) should be indented further than that line.  If only one line of code is needed after a colon you can put this on the same line as was done in the previous examples.  In those cases the following line should have the same indent.

     

    What is considered an indent?

    Any number of TABs, spaces, or a combination of both is a valid indent.  When increasing the indent there is no limit on which combination of TABs and spaces you use or how many of each, meaning that all of the following are valid indents.  The following example shows valid indenting (although the code itself is meaningless!):

     

    import System
    static def GetAttributeValue(Incident):
         if Something == SomethingElse:
              Something == AnotherThing
              SomethingElse == YetAnotherThing
              if Calculations == Awesome:
                   Life = Good
         return Something
    

     

    This is valid and uses a single TAB for each indent (the TABs may have been converted to 5 spaces in this document!).  This is the best practice you should try to follow.  The following variation is just as valid, although not as easy to read:

     

    import System
    static def GetAttributeValue(Incident):
     if Something == SomethingElse:
                    Something == AnotherThing
                    SomethingElse == YetAnotherThing
                    if Calculations == Awesome:
                     Life = Good
     return Something
    

     

    This will be hard to read because the indents aren't obvious or consistent.

     

    In most cases your last line will be "return <something>" which should be back at the first indent regardless of what has happened before it otherwise the return won't always be executed and the calculation could return strange results.

     

    Checking for NULL values

    The most common mistake made when writing calculations is not checking for NULL values.  This section explains what this means and why it is important.

     

    What is a NULL value?

    The term NULL in programming means a value that is "not set" or "blank".  In Service Desk terms you can say that the Category is NULL on an incident if the dropdown is empty and a value hasn't been selected.

     

    The impact NULL values have on calculations

    When the value of something is NULL it makes its use in calculations very limited and trying to do something with a NULL value that can't be done will cause the calculation to fail with the error "Object reference not set to an instance of an object".  Additionally most of the time you won't see the error, you'll just see the calculation return a blank value.

     

    There are two main limitations on NULL values in calculations:

     

    1. If a relationship such as Category is NULL you cannot even attempt to access any attributes below it such as Category.FullName.
    2. Some attribute data types such as DateTime store a NULL so these cannot be accessed either.

     

    Examples

    Consider the following calculation:

     

    import System
    static def GetAttributeValue(Incident):
              Value = 'Hello ' + Incident.RaiseUser.Title
              if Incident.Category.FullName == 'Hardware':
                        Value += '.  This is a hardware issue!'
              return Value
    

     

    This calculation references two one-to-one relationship: RaiseUser and Category.  In the case of Category it checks the FullName attribute which is a String to check it matches 'Hardware'.  However if the Category relationship is not set then the FullName attribute below it is also not set and this will cause the calculation to fail.

     

    Here is a modified calculation that checks before it tries to compare the value:

     

    import System
    static def GetAttributeValue(Incident):
              Value = 'Hello ' + Incident.RaiseUser.Title
              if Incident.Category != null:
                        if Incident.Category.FullName == 'Hardware':
                                  Value += '.  This is a hardware issue!'
              return Value
    

     

    This calculation adds the line 'if Incident.Category != null:' so the code below it is only processed if it isn't NULL (which should always be in lowercase in the calculation itself).  The final line of code 'return Value' will be processed regardless because it is back at the same indent as the "if" statement.

     

    Note that the "if" statement checked against Incident.Category and NOT against Incident.Category.FullName as this in itself would cause the calculation to fail if the relationship is NULL.

     

    The following variation is also valid:

     

    import System
    static def GetAttributeValue(Incident):
              Value = 'Hello ' + Incident.RaiseUser.Title
              if Incident.Category != null and Incident.Category.FullName == 'Hardware':
                        Value += '.  This is a hardware issue!'
              return Value
    

     

    This combines the "if" statements and is valid because if the first comparison is false the second comparison is not checked.  Depending on what you are checking the first or second variation may suit better but both are perfectly valid in most cases.  When using this method ensure that the null check is always the first comparison.

     

    In these examples the RaiseUser is not checked and could also cause the calculation to fail because it attempts to references the Title attribute below the relationship.  However there is only need to check a relationship is set if there is a chance it could be left blank and here we will assume the RaiseUser is always set before the calculation runs.  The same logic can be applied to any other attribute or relationship that you know will always be set before the calculation is processed, but if you are not sure then it is better to be safe and check it in the calculation.

     

    Checking lots of attributes in one calculation

    If you have a lot of attributes referenced in a calculation there is no easy way to check them all for NULLs at once and you must check each one in turn.  Consider this calculation:

     

    import System
    static def GetAttributeValue(Incident):
              Value = 'Customer: ' + Incident.Customer.Title
              Value += '\r\n Category: ' + Incident.Category.FullName
              Value += '\r\n Response Level: ' + Incident.ResponseLevel.Title
              Value += '\r\n Incident Owner: ' + Incident.CurrentAssignment.User.Title
              return Value
    

     

    Note: "\r\n" means a line break and will be covered more in another tutorial.

     

    This calculation references the Customer, Category, ResponseLevel and CurrentAssignment.User relationships.  If any one of those is not set the entire calculation will fail.  To make sure it does not fail we can create a variable for each of the items we want to list and then set the variable if each item is set.  As follows:

     

    import System
    static def GetAttributeValue(Incident):
              TheCustomer = ''
              if Incident.Customer != null: TheCustomer = Incident.Customer.Title
    
              TheCategory = ''
              if Incident.Category != null: TheCategory = Incident.Category.FullName
    
              TheResponseLevel = ''
              if Incident.ResponseLevel != null: TheResponseLevel = Incident.ResponseLevel.Title
    
              TheOwner = ''
              if Incident.CurrentAssignment != null:
                        if Incident.CurrentAssignment.User != null:
                                  TheOwner = Incident.CurrentAssignment.User.Title
    
              Value = 'Customer: ' + TheCustomer
              Value += '\r\n Category: ' + TheCategory
              Value += '\r\n Response Level: ' + TheResponseLevel
              Value += '\r\n Incident Owner: ' + TheOwner
              return Value
    

     

    For each item we have created a variable and set it to '' (two single quotes) which is a blank value but different to a NULL value.  You could also set the variables to a value such as 'Not Set' rather than a blank string.  In the case of the Incident Owner we had to check both levels of relationship are set before attempting to access the attribute at the end of the chain.