Saturday, January 1, 2011

Converting 10-Lines of Apex code to a 1-line Validation Rule Formula

Code clean-up is what I'm doing these days ... lots of code clean-up. One of our Salesforce.com orgs (we have sixteen of them) currently has 72% test coverage in production. I'm not sure how the previous administrators were able to install code below the 75% threshold, but they managed. I'm tasked with getting that code cleaned up, so I can deploy a new release.

While looking for areas to improve code coverage, I stumbled upon this trigger:

trigger checkAccountPhoneNumberBiBu on Account (before insert, before update) {
   for (Account account : Trigger.new) {
      if (account.Phone==null) continue;
      Pattern p = Pattern.compile('[-() ]');
      String sPhone = p.matcher(account.Phone).replaceAll('');
      // check length without punctuation
      if (sPhone.length() != 10) account.Phone.addError(' Phone number must have 3 digit area code and 7 digit number');
      p = Pattern.compile('\\d');
      sPhone = p.matcher(sPhone).replaceAll('');
      if (sPhone.length() > 0) account.Phone.addError('Phone number must be formatted as (999)999-9999');
   }
}


This trigger looks at the value entered in the "Phone" field before an Account record is inserted or updated; if the phone field is not in the (999)999-9999 format, it errors out and notifies the user to enter the phone # in the proper format.

In addition to this Apex code, the developer also had to write a testmethod to ensure coverage of the trigger. His code was only getting 67% test coverage (which is what brought the trigger to my attention in the first place).

As I started looking at what I needed to add to the testmethod to ensure 100% coverage, I realized it would be easier to just get rid of the trigger altogether, and replace it with a Validation Rule. That 10 lines of Apex code was reduced to a 1-line formula in a validation rule:

NOT(REGEX(Phone, "\\D*?(\\d\\D*?){10}"))

Here's the validation rule in the UI:



Simpler is always better. Any time you can minimize your code (or get rid of it altogether), you make your org simpler, smaller and eaiser to maintain. I'm a fan of that.

Another benefit of implementing this as a validation rule is that I'm able to control where the error message is displayed. Previously, the trigger method displayed the error message at the top of the detail record page layout.  With validation rules, you can display error messages either at the top of the screen or at exact field where the error was made; I generally prefer the latter.

Want to see this and other useful validation rules? Check out the Useful Validation Formulas guide by Salesforce.com.

7 comments:

  1. Great job JP. Another great example of why writing Apex should be a last resort.

    ReplyDelete
  2. Very cool, JP. Got any good REGEX reference sources you would care to share? That is a knowledge gap for me, and I would love to learn more bout how to make use of it.

    ReplyDelete
  3. Totally agree with the premise here - go declarative if you can, fall back to programmatic if you can not (though arguably regular expressions straddle the two somewhat).

    Just wanted to make a slight correction to this statement:

    "Another benefit of implementing this as a validation rule is that I'm able to control where the error message is displayed. Previously, the trigger method displayed the error message at the top of the detail record page layout. "

    If that last part is true then we have an issue because the syntax in your sample demonstrates that placing errors at the field level is possible in triggers:

    "if (sPhone.length() > 0) account.Phone.addError('Phone number must be formatted as (999)999-9999');"

    Certainly it is fair to say that with a validation rule you can control placement without modification to code but a developer can control placement of trigger-based validation errors in the same manner one can with validation formulas - or at least they should be. Please respond if that's not the case with your implementation.

    One other point about the code for those that can't move their logic to validation formulas is that such trigger based validations should be done in the after event, not before. The reason is that the only ordering guarantee is that before comes before after and thus other before triggers could invalidate the validation or likewise satisfy the validation.

    Also your code, though I know it's being retired, also has things inside loops that need not be there (regex pattern compilations). I mention these items at least in part to help affirm your position that avoiding code if reasonably possible is a very good thing.

    Finally, just because your logic is in a validation formula doesn't mean you should necessarily drop your test (not sure if you were planning to or not). Having a test is a good way to make sure your logic is as you expect and also that no one "accidentally" deactivates or deletes your validation rule :)

    ReplyDelete
  4. What about our friends across the pond that have a phone number like this: (+44) 0 1932 582 000 ?

    I don't really need the answer....just playing the devils advocate ;-)

    Very nice though.

    ReplyDelete
  5. During "change sets" beta release in 2010 you didn't need a test case to deploy a trigger on live site, so it makes sense that there were other loop holes when visual force first came out a few years ago or that test cases may not always been required.

    ReplyDelete
  6. We had a similar trigger in one of our orgs. The trigger did have a purpose that we were only aware of because the developer that wrote it was still there.

    The validation rule was not displaying errors when a user went to a contact clicked on the account look up button and choose to add a new account. Weird scenario I know but I bet you that is why that trigger existed for you too and you may have broke it by removing the trigger.

    ReplyDelete
  7. Good Regex examples:
    regexlib.com/
    Regex Tool:
    http://sourceforge.net/projects/regulator/

    ReplyDelete