Responsive Advertisement

Getting rid of try-catch from apex

Have you written try catch in your helper classes, to capture expected errors and handled it ? Then you will also agree the exception handling is a repeated piece of code every class has. It is a pain in back to cover in test class. It becomes a nightmare if the class has less lines, and you are stuck with the uncovered catch blocks. So, today I am sharing an approach that I tried.

Is a and Has a

Is a and Has a are the concepts from Object Oriented programming. Is a is from Inheritance, Has a is from composition.
I will not discuss much about it, but we are going to use the Is a concept here.

Logger Class

Most projects has their implementation of logger. I am going to use the logger class that I created in the custom settings to switch your logs on and off. Basically I moved the code execution and try catch into the logger class itself. When I want to execute some method which might throw exception, I call my logger to watch it and handle any exception that may occur.

public static Object watch(
String className,
String methodName,
Map<String, Object> args
) {
Loggable loggable = (Loggable) Type.forName(className)
.newInstance();
try {
return loggable.call(methodName, args);
} catch (Exception exc) {
Logger logger = new Logger(className);
logger.error(exc.getMessage());
return exc;
}
}

The watch method accepts three parameters. Name of the class, name of the method and key-value pair of arguments that I need to pass into the method execution. I am using the Type class to dynamically create an instance of the class which I want to execute. The classes which can be called by logger has to implement the Loggable interface. I will talk more about it later in the post. But basically it has the call method which is implemented by the class I want to call.

An example class implementing Loggable
public with sharing class AccountService implements Loggable {
public Account getAccountByAccountNumber(String accountNumber) {
return [
SELECT Id, AccountNumber Name
FROM Account
WHERE AccountNumber = :accountNumber
WITH SECURITY_ENFORCED
];
}
// bad code(query without limits and security)
public Account getAllAccounts() {
return [SELECT Id, AccountNumber Name FROM Account];
}

public Object call(String methodName, Map<String, Object> args) {
switch on methodName {
when 'getAccountByAccountNumber' {
return this.getAccountByAccountNumber(
(String) args.get('accountNumber')
);
}
when 'getAllAccounts' {
return this.getAllAccounts();
}
when else {
return null;
}
}
}
}

Basically when a method name is passed as string, I am calling the actual method and passing arguments from the named arguments map after typecasting. I got the idea while I was working on a Queueable adding itself to the queue to do a different task. I am using the same concept there as well except for the arguments.

If you are a JavaScript developer like me, you can relate this easily as everything in JavaScript is an object.

Finally the Loggable interface. Loggable is a copy of apex Callable interface, though I did not inherit from it just to make sure, it is only used in logger context.

public interface Loggable {
Object call(String action, Map<String, Object> argsByName);
}

Now that I have everything setup, lets see an how to call logger to watch a method.

@IsTest
private class AccountService_Test {
@IsTest
static void getAccountByAccountNumberTest() {
insert new Account(Name = 'Test Company',
AccountNumber = '12345');
Map<String, String> params = new Map<String, String>();
params.put('accountNumber', '12345');
Test.startTest();
Account account = (Account) Logger.watch(
'AccountService',
'getAccountByAccountNumber',
params
);
Test.stopTest();
System.assertNotEquals(
null,
account,
'Expected an account to be returned for
the given account number'
);
System.assertEquals(
'12345',
account.AccountNumber,
'Expected the account number to match "12345"'
);
}
}

Let me know if this was helpful. Share your suggestions and comments.

Post a Comment

0 Comments