Exception/error handling is a very common process. In fact, it is one of the most prominent topics when learning a new programming language. Exception handling helps programmers to manage adverse conditions and ensures that the software/application can recover from any expected or unexpected failures. Often, programmers ignore the importance of exception handling, which results in:
The first step in exception handling is to ensure that all the probable areas should be equipped with an appropriate exception handling logic. As commonly stated "at minimum, all publicly accessible methods should have an appropriate try/catch". It solves the first point mentioned earlier, that is, erroneous screens/messages being visible to end users.
Let's focus on the second and third points now.
In Apex, database transactions are automatically committed; that is, any database transaction will be committed only if there are no unhandled exceptions.
Similar to various other programming languages, Apex has a top-level exception class as well that can handle any type of exception (excluding exceptions related to governor limits).
Now, let's take a look at a very simple example. Let's take a controller method, which saves an account:
public class GenerateAccountController {
Account NewAccount {get; set;}
//..some awesome variable to be used by class
public GenerateAccountController(){
NewAccount = new Account();
}
//..some awesome code here
public void createAccount(){
insert NewAccount;
}
}
In the preceding example, it is quite evident that any exception thrown while inserting an account will result in a non-user-friendly error message being displayed to the user, as shown in the following figure:
As per the first point discussed earlier, we should avoid erroneous screens for end users. So, let's wrap the code in the try-catch block, as shown in the following code:
public class GenerateAccountController {
public Account NewAccount {get; set;}
public GenerateAccountController(){
NewAccount = new Account();
}
public void createAccount(){
try{
insert NewAccount;
}
catch(Exception ex){
}
}
}
This is a fantastic example of Visualforce's clever error handling and notification process, even though the catch block is empty in code. Here, any validation rule that is attached to a field will result in an error being displayed at the top as well as at the field level automatically (if apex:inputField
is used).
It is a good start, but it only handles validation errors appropriately. Let's modify our code even further and try to create a contact as soon as an account is created:
public class GenerateAccountController { public Account NewAccount {get; set;} public GenerateAccountController(){ NewAccount = new Account(); } public void createAccount(){ try{ insert NewAccount; Contact c; //intentionally contact c is not initialized insert c; } catch(Exception ex){ } } }
In the preceding code, we will get an error while inserting the contact because we did not initialize the contact object. When the preceding controller is executed, we will notice that:
This is a classic example of error hiding. We are hiding the errors and not handling them. This anti-pattern signifies improper exception handling processes. Sometimes, error hiding can be unavoidable. For example, in the case of unexpected errors or web service integrations, where there can be myriads of errors and there is no need to handle them separately. In cases where we need to hide an error, we can either show a generic message to end users or log it. But, as a good practice, any known or expected error should be handled appropriately to ensure system stability and user-friendliness.
As we can see in the preceding code snippet, this is the behavior that we definitely do not want. Either accounts and contacts should be saved together or nothing should be saved. So, we modify our code to instruct the Force.com platform to not commit any data unless the entire operation is completed successfully:
public class GenerateAccountController { public Account NewAccount {get; set;} public GenerateAccountController(){ NewAccount = new Account(); } public void createAccount(){ System.savepoint sp = Database.setSavepoint(); try{ insert NewAccount; Contact c; insert c; } catch(Exception ex){ Database.rollback(sp); } } }
We have to consider the following limitations while using transactions in Apex:
This additional code helps ensure that the entire operation, that is, the insertion of the account and contact, are treated as one transaction. In case of a failure, the entire transaction is rolled back. Only when the entire transaction is successfully executed, Apex auto-commits the entire transaction. Now, when we run this code, we will see that the page refreshes but no account is created. However, we are still not displaying any error message. In the following code, we added the logic to display the error message in the catch block:
public class GenerateAccountController { public Account NewAccount {get; set;} public String ContactLimit {get; set;} public GenerateAccountController(){ NewAccount = new Account(); } public void createAccount(){ System.savepoint sp = Database.setSavepoint(); try{ //insert NewAccount; if(Newaccount.Type == 'Prospect'){ Contact c = new Contact(LastName = NewAccount.Name, Phone = NewAccount.Phone, Limit__c = Integer.valueOf(ContactLimit), Accountid = NewAccount.id); insert c; } } catch(Exception ex){ /* rollback transaction */ Database.rollback(sp); /* display error message */ ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, ex.getMessage()); System.debug(ex); Apexpages.addMessage(msg); } } }
Now, after the execution, the user will see the following message on the page:
The preceding code fulfills all the three issues that we discussed at the start of this topic to demonstrate how to avoid the error hiding anti-pattern.