JAVA技术文章

2019-12-19 Apex Code Best Practi

2020-01-02  本文已影响0人  古月的小七

由于Salesforce本身的Limitation,当然也出于程序员自身的修养,我们应该在编码过程中遵循一定的规范,让我们的代码整洁高效。下面的链接是我们平时写代码时应该遵循的一些最佳实践
,我们这边整理一下以加深理解。还有就是如果你的代码之后交付时是需要打包上传到AppExchange的,如果不遵循这些最佳实践的话,你的代码是通过不了Security Review的。
Best Practice #1: Bulkify your Code
只处理一条数据的代码:

trigger accountTestTrggr on Account (before insert, before update) {

   //This only handles the first record in the Trigger.new collection
  //But if more than one Account initiated this trigger,
  //those additional records
  //will not be processed
   Account acct = Trigger.new[0];
   List<Contact> contacts = [select id, salutation, firstname, lastname, email 
              from Contact where accountId = :acct.Id];
}

处理多条数据:

trigger accountTestTrggr on Account (before insert, before update) {

   List<String> accountNames = new List<String>{};
 
   //Loop through all records in the Trigger.new collection
   for(Account a: Trigger.new){
      //Concatenate the Name and billingState into the Description field
      a.Description = a.Name + ':' + a.BillingState
   }
   
}

Best Practice #2: Avoid SOQL Queries or DML statements inside FOR Loops
查询和DML操作在循环之内:

trigger accountTestTrggr on Account (before insert, before update) {
      
   //For loop to iterate through all the incoming Account records
   for(Account a: Trigger.new) {
          
      //THIS FOLLOWING QUERY IS INEFFICIENT AND DOESN'T SCALE
      //Since the SOQL Query for related Contacts is within the FOR loop, if this trigger is initiated 
      //with more than 100 records, the trigger will exceed the trigger governor limit
      //of maximum 100 SOQL Queries.
          
      List<Contact> contacts = [select id, salutation, firstname, lastname, email 
                        from Contact where accountId = :a.Id];
          
      for(Contact c: contacts) {
         System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], 
                                         LastName[' + c.lastname +']');
         c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname;
           
         //THIS FOLLOWING DML STATEMENT IS INEFFICIENT AND DOESN'T SCALE
         //Since the UPDATE dml operation is within the FOR loop, if this trigger is initiated 
         //with more than 150 records, the trigger will exceed the trigger governor limit 
         //of 150 DML Operations maximum.
                                  
         update c;
      }       
   }
}

把查询和DML操作放在循环之外:

trigger accountTestTrggr on Account (before insert, before update) {
  //This queries all Contacts related to the incoming Account records in a single SOQL query.
  //This is also an example of how to use child relationships in SOQL
  List<Account> accountsWithContacts = [select id, name, (select id, salutation, description, 
                                                                firstname, lastname, email from Contacts) 
                                                                from Account where Id IN :Trigger.newMap.keySet()];
      
  List<Contact> contactsToUpdate = new List<Contact>{};
  // For loop to iterate through all the queried Account records 
  for(Account a: accountsWithContacts){
     // Use the child relationships dot syntax to access the related Contacts
     for(Contact c: a.Contacts){
      System.debug('Contact Id[' + c.Id + '], FirstName[' + c.firstname + '], LastName[' + c.lastname +']');
      c.Description=c.salutation + ' ' + c.firstName + ' ' + c.lastname; 
      contactsToUpdate.add(c);
     }        
   }
      
   //Now outside the FOR Loop, perform a single Update DML statement. 
   update contactsToUpdate;
}

Best Practice #3: Bulkify your Helper Methods
就是我们平常所写的一些helper方法或者是公共方法处理的要是集合形式(Array, List, Set, ect.)。
Best Practice #4: Using Collections, Streamlining Queries, and Efficient For Loops
Use collections inefficiently:

trigger accountTrigger on Account (before delete, before insert, before update) {
 
    //This code inefficiently queries the Opportunity object in two seperate queries
    List<Opportunity> opptysClosedLost = [select id, name, closedate, stagename
            from Opportunity where  
            accountId IN :Trigger.newMap.keySet() and StageName='Closed - Lost'];
    
    List<Opportunity> opptysClosedWon = [select id, name, closedate, stagename
            from Opportunity where  
            accountId IN :Trigger.newMap.keySet() and StageName='Closed - Won'];
    
    for(Account a : Trigger.new){
        
        //This code inefficiently has two inner FOR loops
        //Redundantly processes the List of Opportunity Lost
        for(Opportunity o: opptysClosedLost){   
            if(o.accountid == a.id)
               System.debug('Do more logic here...');
        }
        
        //Redundantly processes the List of Opportunity Won
        for(Opportunity o: opptysClosedWon){    
            if(o.accountid == a.id)
               System.debug('Do more logic here...');
        }
    }
}

Use collections efficiently:

trigger accountTrigger on Account (before delete, before insert, before update) {
 
     //This code efficiently queries all related Closed Lost and
     //Closed Won opportunities in a single query.
    List<Account> accountWithOpptys = [select id, name, (select id, name, closedate, 
         stagename  from Opportunities  where accountId IN :Trigger.newMap.keySet() 
         and  (StageName='Closed - Lost' or StageName = 'Closed - Won')) 
         from Account where Id IN :Trigger.newMap.keySet()];
    
    //Loop through Accounts only once
    for(Account a : accountWithOpptys){
        
         //Loop through related Opportunities only once
         for(Opportunity o: a.Opportunities){
            if(o.StageName == 'Closed - Won'){
                System.debug('Opportunity Closed Won...do some more logic here...');
            }else if(o.StageName =='Closed - Lost'){
                System.debug('Opportunity Closed Lost...do some more logic here...');
            }
         }
    
   }
}

Best Practice #5: Streamlining Multiple Triggers on the Same Object
对同一个Object最好Trigger都写在一个类里面,这样可以有效避免多次查询和DML操作。
Best Practice #6: Querying Large Data Sets
查询通常需要加上限制条件,来减少查询的数量。
未加限制条件:

//A runtime exception is thrown if this query returns 
//enough records to exceed your heap limit.
Account[] accts = [SELECT id FROM account];

加限制条件:

// Use this format for efficiency if you are executing DML statements 
// within the for loop.  Be careful not to exceed the 150 DML statement limit.

Account[] accts = new Account[];

for (List<Account> acct : [SELECT id, name FROM account
                            WHERE name LIKE 'Acme']) {
    // Your logic here
    accts.add(acct);
}
update accts;

Best Practice #7: Use of the Limits Apex Methods to Avoid Hitting Governor Limits
在处理大批量数据时我们可以通过下面的API了解我们是否超出了Limitation。

trigger accountLimitExample on Account (after delete, after insert, after update) {

    System.debug('Total Number of SOQL Queries allowed in this Apex code context: ' +  Limits.getLimitQueries());
    System.debug('Total Number of records that can be queried  in this Apex code context: ' +  Limits.getLimitDmlRows());
    System.debug('Total Number of DML statements allowed in this Apex code context: ' +  Limits.getLimitDmlStatements() );
    System.debug('Total Number of CPU usage time (in ms) allowed in this Apex code context: ' +  Limits.getLimitCpuTime());
    
   // Query the Opportunity object 
    List<Opportunity> opptys = 
        [select id, description, name, accountid,  closedate, stagename from Opportunity where accountId IN: Trigger.newMap.keySet()];
   
    System.debug('1. Number of Queries used in this Apex code so far: ' + Limits.getQueries());
    System.debug('2. Number of rows queried in this Apex code so far: ' + Limits.getDmlRows());
    System.debug('3. Number of DML statements used so far: ' +  Limits.getDmlStatements());    
    System.debug('4. Amount of CPU time (in ms) used so far: ' + Limits.getCpuTime());
    
    //NOTE:Proactively determine if there are too many Opportunities to update and avoid governor limits
    if (opptys.size() + Limits.getDMLRows() > Limits.getLimitDMLRows()) {
            System.debug('Need to stop processing to avoid hitting a governor limit. Too many related Opportunities to update in this trigger');
            System.debug('Trying to update ' + opptys.size() + ' opportunities but governor limits will only allow ' + Limits.getLimitDMLRows());
            for (Account a : Trigger.new) {
                a.addError('You are attempting to update the addresses of too many accounts at once. Please try again with fewer accounts.');
            }
    }
    
    else{
        System.debug('Continue processing. Not going to hit DML governor limits');
        System.debug('Going to update ' + opptys.size() + ' opportunities and governor limits will allow ' + Limits.getLimitDMLRows());
        for(Account a : Trigger.new){
            System.debug('Number of DML statements used so far: ' +  Limits.getDmlStatements());
            
            
            for(Opportunity o: opptys){ 
                if (o.accountid == a.id)
                   o.description = 'testing';
            }
           
        }
        update opptys;
        System.debug('Final number of DML statements used so far: ' +  Limits.getDmlStatements());
        System.debug('Final heap size: ' +  Limits.getHeapSize());
    }
}

Best Practice #8: Use @future Appropriately
@future本身Salesforce就不推荐使用,所以如果必须使用还是要慎行。
Best Practice #9: Writing Test Methods to Verify Large Datasets
我们常常会忽略写一些Governor Limit的测试类。

public class sampleTestMethodCls {

    static testMethod void testAccountTrigger(){
        
        //First, prepare 200 contacts for the test data
        Account acct = new Account(name='test account');
        insert acct;
        
        Contact[] contactsToCreate = new Contact[]{};
        for(Integer x=0; x<200;x++){
            Contact ct = new Contact(AccountId=acct.Id,lastname='test');
            contactsToCreate.add(ct);
        }
        
        //Now insert data causing an contact trigger to fire. 
        Test.startTest();
        insert contactsToCreate;
        Test.stopTest();    
    }   
}

Best Practices #10: Avoid Hardcoding IDs

//Query for the Account record types
     List<RecordType> rtypes = [Select Name, Id From RecordType 
                  where sObjectType='Account' and isActive=true];
     
     //Create a map between the Record Type Name and Id for easy retrieval
     Map<String,String> accountRecordTypes = new Map<String,String>{};
     for(RecordType rt: rtypes)
        accountRecordTypes.put(rt.Name,rt.Id);
     
      for(Account a: Trigger.new){
         
          //Use the Map collection to dynamically retrieve the Record Type Id
          //Avoid hardcoding Ids in the Apex code
          if(a.RecordTypeId==accountRecordTypes.get('Healthcare')){             
             //do some logic here.....
          }else if(a.RecordTypeId==accountRecordTypes.get('High Tech')){
             //do some logic here for a different record type...
          }
         
     } 

这是使用Map可以减少我们代码量的写法:

public static void updateAccountWithCompetitor(List<Id> acctList){
       List<Account> finalAccountList = new List<Account>();
       List<Competitor__c> compForOpptyList = [SELECT Id, Opportunity__r.AccountId FROM Competitor__c WHERE Opportunity__c IN (SELECT Id FROM Opportunity WHERE AccountId IN: acctList )];

       Map<Id, List<Competitor__c>> groupedData = new Map<Id, List<Competitor__c>>();
         for(Competitor__c record: compForOpptyList ) {
             if(groupedData.containsKey(record.Opportunity__r.AccountId)) {
                 groupedData.get(record.Opportunity__r.AccountId).add(record);
             } else {
                 List<Competitor__c> tempList = new List<Competitor__c>();
                 tempList.add(record);
                 groupedData.put(record.Opportunity__r.AccountId, tempList);
             }
         }
        for(Id accountId : acctList) {
            finalAccountList.add(new Account(
                Id = accountId,
                PwC_Has_Competitors__c = groupedData.containsKey(accountId) && groupedData.get(accountId).size()>0
            ));
            }
        if(finalAccountList.size()>0){
            update finalAccountList;
        }
    }
上一篇下一篇

猜你喜欢

热点阅读