Tip #192: Defensive script writing

(Today’s tip is actually two-in-one but we’ll get to that.)

Over the years as a CRM jackofalltrades I learned that to create truly reusable, bug-free and resilient systems is very important to write your scripts as if the next team member, who comes after you, dedicated their entire career to destroying your life’s work.

Consider the task: add a new Age attribute to the contact record and set it’s value based on birthdate attribute. Easy, right?

function setAge() {
  Xrm.Page.getAttribute("foo_age").setValue(
    calculateAge(
      Xrm.Page.getAttribute("birthdate").getValue()
    )
  );
}

function BirthdateOnChange() {
  setAge();
}

Some time later users report the following message being displayed every time they enter birthdate field:
Script error message

As it turns out, one of the junior customizers was given a task to create a new form which is a simplified version of the existing one. After opening the form and using Save As function, they’ve got the exact replica including form scripts and events. Then they proceeded to cleanup the layout as specified by the business and that included removing Age attribute. And they did not test because the existing form they copied works perfectly.

How this fragment could have been written so it does not crumble when a user sneezes? One of the options:

function setAge() {
  var ageAttr = Xrm.Page.getAttribute("foo_age");
  var bdAttr = Xrm.Page.getAttribute("birthdate");
  if(ageAttr != null && bdAttr != null) {
    ageAttr.setValue(
      calculateAge(bdAttr.getValue()));
    )
  );
}

function BirthdateOnChange() {
  setAge();
}

That way, code at least does not bring the form down when one of the attributes is removed. It’s also considered a good practice to explicitly add dependencies for the specific event handler (BirthdateOnChange in our example):
Event Handler Dependencies

One may argue that the example is trivial and bug should have been picked up by testing. Indeed, it’s a simplification but the best practice has always been to program defensively as if the next in line is malicious, ignorant or both.

As for the bonus tip, here is how to calculate age in javascript:

function calculateAge(bd) {
    if (!bd)
        return null;

    var today = new Date();

    var bmonth = bd.getMonth();
    var bday = bd.getDate();
    var nowmonth = today.getMonth();
    var nowday = today.getDate();

    var age = today.getFullYear() - bd.getFullYear();
    // take into account if this year birthday 
    // is later in the year
    if (bmonth > nowmonth 
      || (bmonth == nowmonth && bday > nowday)) {
        age = age - 1;
    }

    return age;
}

4 thoughts on “Tip #192: Defensive script writing

  1. Chris Groh, Hitachi Solutions says:

    You need to be careful though. Image a scenario where setting the age field is important – all the sudden it is getting set to null. Now, you’re in a scenario where it is harder to troubleshoot. Or worse, users don’t realize things are going wrong because they aren’t getting that error alert. It passes through testing without anyone noticing the issue and goes to production. Now your report filtering isn’t working correctly and you’re going to have to trace the issue all the way back to the source then need to write an SSIS or scribe job to fix the affected records.

    Sometimes bombing out and letting the user see that error alert when something unexpected happens *IS* the defensive answer.

    Though if it is extremely important something is set, the best answer may be to make sure that the logic is implemented in a plugin.

    • Good points, Chris, thanks for chiming in. As you understand, the example is trivialised and the main point was not to assume anything and not to let things just happen, to be always in control. To ensure field presence, declaring dependencies is one of best strategies but it is rarely used. I probably didn’t express the intent the best way possible, looking at code, indeed, better option would have been

        if(ageAttr == null) {
            Xrm.Utility.alertDialog('Dude, where is my age?');
        }
      

      As far as plugins are concerned, their usability from UX point of view can be somewhat limited. When user enters a birthdate, they want to see age calculated there and then, and not after saving the record. (This requirement is not imaginary, we have it on one deployment).

      Edit: completely slipped my mind. Sometimes you don’t control what goes on the form. ISV hat on, you want your code as defensive as possible because ability to apply a fix will be out of your hands (to a degree).

  2. Bruno says:

    In my humble opinion I consider the best practice adding the field as dependence, including a try catch also. I don’t think hiding an exception would be a great choice, anyway, that was an awesome tip, thank you!

    • Try/catch is another very useful technique, thanks for reminding, Bruno. I like the discussion, I think the goal is achieved – to get people thinking about code they write. What’s important is that very time you put a ‘.’ in your reference, think if the left of the dot can be null or undefined and then take an action: ignore, catch, test, whatever is your preferred defense mechanism.

Leave a Reply

Your email address will not be published. Required fields are marked *