美文网首页JAVA技术文章
2019-12-19 Apex Code Best Practi

2019-12-19 Apex Code Best Practi

作者: 古月的小七 | 来源:发表于2020-01-02 16:39 被阅读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;
            }
        }
    

    相关文章

      网友评论

        本文标题:2019-12-19 Apex Code Best Practi

        本文链接:https://www.haomeiwen.com/subject/lkhmnctx.html