Skip to content

Null handling

Null Handling is the practice of protecting against null inputs to methods within the engines and adapters.

Null inputs can throw errors that are unhelpful to the user, typically a object is not set to an instance of an object exception, which does not provide the user with much information on how to resolve this problem within their chosen UI.

As such, it is good practice to ensure all of the inputs to a method are valid before trying to run operations on them. Take the following method as an example.

public static string GetName(BH.oM.Environment.Elements.Panel panel)
{
    string name = "";
    name += panel.Name + " ";
    name += panel.Construction.Name;
    return name;
}

If panel is null, then the line name += panel.Name + " "; will throw a NullReferenceException as you cannot get the Name property of an object with no data associated to it (null). This may then confuse the user. Therefore, we should check whether the panel is null and tell the user before using it.

public static string GetName(BH.oM.Environment.Elements.Panel panel)
{
    if(panel == null)
    {
        BH.Engine.Reflection.Compute.RecordError("Panel cannot be null when querying the name. The panel should have data associated to it and be a valid instantiation of the object."); //Suitable error message that helps the user understand what's going on
        return ""; //A suitable return - you could `return null;` here instead if needed
    }

    string name = "";
    name += panel.Name + " ";
    name += panel.Construction.Name;
    return name;
}

The return from a null check should be appropriate for the return object type. For complex objects (e.g. a BHoM object return type, such as a Panel or Bar), returning null should be appropriate, as empty objects (such as return new Panel();) will likely cause more problems down the line if the object is not null, but has no data. For primitive types (e.g. string, int) then returning a suitable default is appropriate, such as an empty string (""). For numbers (int, double, etc.), returning a number should be carefully considered. 0 may be a valid response to the method that the downstream workflow will rely on, so consider returning negative numbers (e.g. -1) instead, or numbers outside the realm of reality for the equation (such as 1e10 or -1e10 for large and small numbers respectively). The same is for bool return types, consider what true or false may imply further down the line and return the appropriate response. For collections, empty collections are appropriate.

The final decision for what the return should be will reside with the relevant toolkit lead, who should take into consideration the expected use cases and user stories.

The error message should also convey to the user which bit of the data is null and what they need to fix it. Consider the above example, the panel may not be null but the Construction property might be. Therefore panel.Construction.Name will also throw a NullReferenceException.

IsNull

For complex objects, with multiple properties to check, you may wish to implement an IsNull check query method, which takes the object and checks all of the nested data to check if any of it is null and returns a true or false and an error message if anything was null. An example of this can be seen in the Structure_Engine IsNull method which checks objects and their complex properties. This is useful for areas where the entire object must have valid data, but may not be appropriate for other instances. It is toolkit lead and developer discretion as to which way null checks should be handled in a given method.

Cheat Sheet

The following cheat sheet can be used as a guideline for what should be the default return type if a null check has failed for different types. This is not the definitive list, and many occasions may do something different with suitable justification. But if in doubt, the following can be used and would be accepted in 99 cases out of 100.

Return type Return value
int, decimal -1 or 0 - whichever is the most appropriate downstream
double double.NaN or -1 or 0 - whichever is the most appropriate downstream
float float.Nan or -1 or 0 - whichever is the most appropriate downstream
string "" or null - whichever is the most appropriate downstream
bool false or true - whichever is the most appropriate downstream (will depend on what the method is doing, e.g. a query for HasConstruction could return false appropriately because a null object cannot have a construction)
List or other IEnumerable Empty list (new List<object>();) or null
Complex object (e.g. a BHoMObject such as Panel or Bar null