Home » Blog » Eliminating Duplicate Salesforce Code via Reuse

Eliminating Duplicate Salesforce Code via Reuse

One of the primary sources of software defects is duplication, sometimes referred to as Copy and Paste programming. When code or design is copied into multiple places, it’s only a matter of time before one instance of the code will be modified and the others missed – resulting in defects. Even if a developer does remember to update all instances of the duplicated logic, and manages to do so without typos or other errors, multiple changes take time and effort. An important software development principle is, Don’t Repeat Yourself.

A simple way to avoid duplication in your Apex code is to define “Base” classes. For example, if you find yourself adding the same or similar methods to your page controllers, extending those controllers from a base class that contains the method in a single place may be a good solution. Below are some example base classes and typical methods we have found useful on various projects.

Base Model

The model classes include logic and values specific to the business, not a specific application or interface. In the example below you’ll notice code for current object setup & modification, record validation, and object accessibility.

/**
 * This is the parent class for models. Model classes should contain
 *  the data and business rules for the application. String constants for record
 *  types, pick list values, SOQL queries, etc. should be defined here for easy 
 *  maintenance.
 * 
 *  A BaseModel represents a single SObject record in the system. This record
 *  must be provided by the derived class. Methods include validation, CRUD 
 *  access inquiries, save, and validation.
 * 

 *  Validate must be provided by the derived class.
 * 

 *  By default, save does an upsert on the current record. It can be overridden
 *  for specialized save requirements.
 */
public abstract with sharing class BaseModel {
  //--------------------------------------------------------------------------
  // Properties
  /** Id for the current object. When changed, sObj is reset as well */
  public Id theId {
    get; 
    set {
      if (value != theId) {
        theId = value;
        sObj = null;
      }
    }
  }
   
  /** The object with ID = theId */
  public SObject sObj {
    get {
      if (null == sObj) {
        sObj = String.isEmpty(theId) ? theType.newSObject() :
          Database.query('SELECT ' + String.join(new List(fields), ',') + 
            ' FROM ' + theType + ' WHERE Id = :theId LIMIT 1');
      }
      return sObj;
    } 
    private set;
  }
   
  //--------------------------------------------------------------------------
  // Methods
  /**
   * @param theType required
   * @param theId optional Salesforce Id of the object
   */
  public BaseModel(SObjectType theType, Id theId) {
    FC.preCondition(null != theType, 'BaseModel() - theType is required');
     
    this.theType = theType;
    this.theId = theId;
  }
   
  public void addFields(SObjectField[] fields) {
    FC.preCondition(null != fields, 'BaseModel.addFields() - fields are required');
     
    for (SObjectField f : fields) {
      final DescribeFieldResult d = f.getDescribe();
      this.fields.add(d.getName());
    }
    this.sObj = null;  // force requery
  }
   
  public void addFields(FieldSet fieldSet) {
    FC.preCondition(null != fieldSet, 'BaseModel.addFields() - fieldSet is required');
     
    for (FieldSetMember m : fieldSet.getFields()) {
      this.fields.add(m.fieldPath);
    }
    this.sObj = null;  // force requery
  }
   
  /** validates, then saves the object */
  public virtual void save() {
    if (this.validate(sObj)) {
      //throw new FC.ValidationException('just testing...');
      upsert sObj;
    }
  }
   
  public abstract Boolean validate(SObject obj);
   
  public virtual Boolean validate() {
    return this.validate(sObj);
  }
   
  //--------------------------------------------------------------------------
  // Accessibility Methods
  public virtual Boolean canView() {
    return objDescribe.isAccessible();
  }
   
  public virtual Boolean canInsert() {
    return objDescribe.isCreateable();
  }
   
  public virtual Boolean canUpdate() {
    return objDescribe.isUpdateable();
  }
   
  public virtual Boolean canDelete() {
    return objDescribe.isDeletable();
  }

  //--------------------------------------------------------------------------
  // Helpers
  private SObjectType theType;
   
  private Set fields = new Set { 'Id' };
   
  private DescribeSObjectResult objDescribe {
    get {
      if (null == objDescribe) {
        objDescribe = sObj.getSObjectType().getDescribe();
      }
      return objDescribe;
    }
    private set;
  }
}

Base Controller

VisualForce page controllers can benefit from shared code to handle common controller patterns, action functions, utilities, diagnostics, exception handling, authentication, and accessibility.

/**
 * This is the parent class for controllers.
 *  This base class provides an SObject record (through it's model), 
 *  authentication, saving, CRUD accessibility, and several diagnostic methods 
 *  and properties.
 * 
 *  Exceptions should be passed through the handleException method, in order to
 *  track the last exception. That method can also be overridden, for more control.
 */
public virtual with sharing class BaseController {
  //--------------------------------------------------------------------------
  // Properties
  public Boolean debugging { get { return FC.debugging; }}
  public String debugMessage { get; set; }
  public String successMessage { get; set; }
   
  public BaseModel model; 
   
  // Exception handling. Check a controller's lastException in unit tests
  public Exception lastException;
  public virtual void handleException(Exception ex) {
    lastException = ex;
    ApexPages.addMessages(ex);
  }
 
  public Boolean isAuthenticated { get { return User.isAuthenticated; }}
   
  //--------------------------------------------------------------------------
  // Methods
  public PageReference authenticate() {
    return User.isAuthenticated ? null : FC.redirect(Page.Login);
  }
   
  /**
   * save the record and return true if successful. On failure, 
   * provide rollback and standard exception handling.
   */
  public Boolean save() {
    FC.invariant(null != model, 'BaseController.save() - missing model');
     
    Savepoint save = Database.setSavepoint();
    try {
      model.save();
      return true;
    } catch (Exception e) {
      handleException(e);
      Database.rollback(save);
      return false;
    }
  }
   
  /** generic, NOOP method for use by VF; leave the body of this blank */
  public void noop() {
  }
   
  //--------------------------------------------------------------------------
  // Accessibility Methods
  public Boolean canView { get { return model.canView(); } }
  public Boolean canInsert { get { return model.canInsert(); } }
  public Boolean canUpdate { get { return model.canUpdate(); } }
  public Boolean canDelete { get { return model.canDelete(); } }
}

Base Handler

We’re fans of Tony Scott’s ITrigger pattern. When implementing this interface, it’s handy to extend a base handler so you don’t have to implement every function.

/**
 * Base implementation of ITrigger. Derive your handler from this class, then
 * override methods as needed.
 */
public virtual class BaseHandler {
  //--------------------------------------------------------------------------
  // Methods
  public virtual void bulkBefore() {}
  public virtual void bulkAfter() {}
  public virtual void beforeInsert(SObject so) {}
  public virtual void beforeUpdate(SObject oldSo, SObject so) {}
  public virtual void beforeDelete(SObject so) {}
  public virtual void afterInsert(SObject so) {}
  public virtual void afterUpdate(SObject oldSo, SObject so) {}
  public virtual void afterDelete(SObject so) {}
  public virtual void afterUndelete(SObject so) {}
  public virtual void andFinally() {}
}

Base Test

Testing is a great application we’ve found for defining a base class. We try to make writing tests easier by utilizing lots of utilities and helpers.

/**
 * This class should be extended by all test classes. It contains utilities and 
 * methods for use in unit tests
 */
public virtual class BaseTest {
  //--------------------------------------------------------------------------
  // Properties
  public static BaseTest t;
   
  /** a default user to use in System.runAs() */
  public User adminUser {
    get {
      if (null == adminUser) {
        // all test code should execute under a user we can control so as to avoid
        // surprises when deploying to different environments.
        UserRole[] roles = [SELECT Id FROM UserRole WHERE DeveloperName = 'System_Administrator'];
        if (roles.isEmpty()) {
          roles.add(new UserRole(DeveloperName = 'System_Administrator', Name = 'r0'));
          insert roles;
        }
         
        adminUser = newUser('admin@fc.com');
        adminUser.UserRoleId = roles[0].Id;
        insert adminUser;
      }
      return adminUser;
    }
    private set;
  }

  //--------------------------------------------------------------------------
  // Methods
  /**
   * unit test initialization
   * 
   * Put your global initializtion within this method and provide overrides
   * in classes that require additional initialization.
   */
  protected virtual void init() {
    debugging = true;
  }
   
  /** use this instead of Test.startTest() to provide extra functionality */
  public void start() {
    init();
    Test.startTest();
  }
   
  /** use this instead of Test.stopTest() to provide extra functionality */
  public void stop() {
    Test.stopTest();
  }
   
  /** allows get/set of the global 'debugging' flag */
  public Boolean debugging {
    get {
      return (null != FC__c.getInstance()) ? FC__c.getInstance().debugging__c : false;
    }
     
    set {
      FC__c prefs = FC__c.getInstance();
      if (null == prefs) { prefs = new FC__c(); }
      prefs.debugging__c = value;
      upsert prefs;
    } 
  }

  public User newUser(String username) {
    final Id profileId = [SELECT Id FROM Profile WHERE Name = 'System Administrator'].Id;
    return newUser(username, profileId);
  }

  //--------------------------------------------------------------------------
  // Helpers
  private User newUser(String username, Id profileId) {
    return new User(
      ProfileId = [SELECT Id FROM Profile WHERE Id = :profileId LIMIT 1].Id,
      LastName = 'last',
      Email = 'user@fc.com',
      Username = username + System.currentTimeMillis(),
      CompanyName = 'sf',
      Title = 'title',
      Alias = 'alias',
      TimeZoneSidKey = 'America/Los_Angeles',
      EmailEncodingKey = 'UTF-8',  
      LanguageLocaleKey = 'en_US', 
      LocaleSidKey = 'en_US'
    );
  }
}

Conclusion

Consolidating code into base classes will help you minimize defects and make maintenance much easier. We hope these examples have given you some inspiration you can apply to your code base!