Salesforce Best Practices
Configuration and Development
Salesforce projects require configuring your application and sometimes developing code. To ensure performance, maintainability and platform limits, you need to do the right configuration. It is not because "it works" that it is correctly configured. At any time, your configuration could be audited by Salesforce or a consulting partner. If you already know the best practices, you will have less rework after the review. This page is a list of configuration and development best practices, we don't cover business or project scope, just pure platform setup and code.
You can quickly jump to the right section through these links.
General Best Practices
Three "Good Old" Principles.
- Adopt VS Adapt:
- It's more Adopt BEFORE Adapt using SFDC.
- Consider adopting the most standard functionality and using the most easy technology that don't require development skills (such as Formula, Workflow, Validation rule...).
- Consider Adapting when any of the standard components doesn't meet the business needs; keep in mind that every custom development are expensive to develop & to maintain.
- Challenge the business team to adopt the Salesforce behavior instead of redeveloping the old CRM system as identical (risk to fall into the same problems as the old one).
- Re Use before Buy before Make:
- Try to reuse the existing behavior first, by challenging user's team / product owner to adopt it.
- If it is not possible, have a look on other solution available on the AppExchange (with possible adapting) and assess costs with a full custom solution.
- KISS: Keep It Simple, Stupid!
- More the code is complex, more it is to understand and expensive to maintain.
- Don't design features that were not asked (avoid the 'feature creep').
Data Model Design
- Reuse the maximum standard objects of the platform.
- Be aware of the behavior for these objects, especially on Lead (that becomes an Account and a Contact) and Opportunity (stage, probability...).
- Be careful of relationship type you want to implement, sometimes lookup is enough but try to identify future needs to select the right relation (for example when a field of the parent object should be updated -by workflow- when the child changes, it's only possible with a master-detail relationship...).
- Be careful when choosing the right field type and name as when they're deployed it's a high risk to modify them.
- Consider making a field required when he's updatable by both the UI and web service or other apex code.
- Identify the different kinds of users/actors, their relation (visibility of records), their rights (profile).
- Field API Name should be as short as possible. Avoid using underscores, use a capital letter for each word, and abbreviate words.
- Create a default record type when creating an object. This will prevent data migration later when you will need to manage record types
- Ensure you have Field Level Securities giving access to all fields for a technical profile, to be able to backup all your data
- Custom Settings: Optimize the length of your text fields. You have limited storage, even if you put data with a shorter length it will really be estimated with full length.
- Visibility: Think twice before defining Org Wide Defaults for external users as "public" on an object.
- When defining an existing field as required, ensure (or migrate) existing records have no empty value for that field
- If "Strictly enforce picklist values" option enabled, picklist values must be aligned with your data
Integration Design
- Identify the volume of data exchanged.
- Identify the direction: inbound, outbound.
- Identify the type: sync / async.
- Identify the integration process: batch, oneshot.
- Favor inbound message using the standard Enterprise / Partner API as custom inbound or outbound are expensive to develop (except for outbound call using the workflow).
Logic Design
- Match each need with available Force.com technology to build the logic (Validation Rule, Workflow, Approval Process...).
- Manage configuration limits: number of workflows, of filtered lookups...
- Use Apex code in a last consideration.
- Any item configured should have comments in the description attribute
- Enforce naming conventions
Code - general
- Keep the right visibility. Use "with sharing" for your class, or document why you would run without sharing
- Don't code if you can configure it. Allowed exception is related to performance.
- Comment your code
- Do not hard code IDs. If you need some invariant Ids in some circumstance you can put them in a System Label
- Keep your code small. This will better manage the 3Mb limit, make you think on an optimized algorithm, and most of the time make your code faster. Ex: is it necessary to define a temporary variable that will be used only once?
- Always code for performance.
- Ensure system.debug() is consistent to the context (ex: do not log "after insert" if you are in a "before insert" trigger)
- Avoid multi-line tests. This could have negative impact on code coverage if part of the test is false.
- Remove commented code to have clean classes
- Never use try..catch when the purpose is only to hide errors (with no code in the catch, or just a system debug). The user or admin should be notified by the error
- Do not have a different behavior when testing vs when running (spaghetti code using if(System.Test.isRunningTest())
- Apex Callouts - Do not hard code credentials in the code. Use named credentials as endpoints to simplify authenticated Apex callouts and manage credentials through the Setup user interface.
- Do not hardcode email addresses
- Put outside the FOR loops invariants. Ex: Contact.sObjectType.getDescribe() or Schema.SObjectType.Contact.fields.getMap();
- Do not call @future methods inside FOR loops. These methods should be bulkified and called after the loop.
- Use only one return statement par method.
- Remove unnecessary local variables, empty blocks and empty methods
SOQL in Apex
- Avoid SOQL queries statements inside loops (for, while..)
- Leverage SOQL capability to reduce Apex code
- Capability to order results, filter, query multiple objects in 1 query through relationships
- Capability to aggregate date and do computation (select SUM()...)
- Put the result directly in a map instead of doing this in a for..loop
- Nested for..loop with a SOQL query in the container loop can implicitly manage batch records, reducing heap consumption and increasing speed (because of smaller lists). for(Account[] accs:[Select Name from Account limit 10000])for(Account a:accs){do something}
- Make efficient SOQL queries. Ex: [select Name from Profile where id in (select ProfileId from User where Id=:UserInfo.getUserId())] could be rewritten as [select Name from Profile where Id=:UserInfo.getProfileId()]
- Limit returned records. If a query is returning multiple records, you must add "limit xxx" at the end of the query to manage governor limits
- Whenever it is possible, try to use 1 query for multiple records on the same object, instead of 1 query per record.
- Do not query on fields that you will not use
- When querying users, don't forget to filter on the isActive field
- To filter your query, use the DeveloperName field instead of the Name field when the queried object has both (i.e.: Group, RecordType...)
- When doing a query on RecordType object, filter on SObject name as you can have the same recordType developerName across multiple objects
- Reduce joins in queries. If you need an id of the record from a lookup, do not code select theLookup__r.id from theObject__c. Prefer this one: select theLookup__c from theObject__c
DML
- Use a DML for a list of records instead of 1 DML per record (bulk)
- Do not perform DML inside of loops
Triggers
- Don't forget that trigger handle up to 200 records per execution.
- Don't put the logic (incl. SOQL or DML) in the trigger, use a handler class.
- Don't push trigger.New to the handler class method, you should filter on records that are in related scope. You will increase performance, reduce governor limits consumption, and limit regression risks when updating the class.
- Create 1 trigger per event and per object. Multiple triggers for the same event of an object would not ensure that the execution order would always be the same, giving unpredictable behavior in production. Managing multiple events in the same trigger would reduce maintainability with spaghetti code.
- Put bypass logic in the trigger for each handler class. This would prevent running some code for some users. For instance, a technical user doing data migration will have improved performance if some business logic is not triggered. For this purpose, you should use PAD framework. Ensure that each bypass keyword used in triggers is defined in the bypass multi-picklist definition.
- Always optimize. Even if you think it is not needed, as your trigger can be run from another trigger, which can reach governor limits.
- Beware of over optimization side effects; when optimizing SOQL queries and DMLs, you will probably use structured maps indexed by IDs. This consumes a lot in memory as triggers are limited in heap size and each trigger in the transaction can consume much heap. Free memory if such structures are not needed after usage.
Controllers
- Bind your pages with the code using "Page.VisualforcePageName" instead of "new PageReference('/apex/VisualforcePageName')". Impact and dependencies will be better managed (ex: when renaming the page), and this is the only way to be compatible with CRSF protection.
- Reduce ViewState to get better performance.
- Secure your code (SOQL injection, etc.). If you leverage an URL parameter you should make sure that it is of the expected type with a correct value.
Test Classes
- Prefer creating multiple testMethods in a test class instead of one big method. Benefit is maintainability
- Avoid using seealldata=true in test classes to avoid conflict (locking contention) with real production data
- Always assert() in test methods
- Leverage a @testSetup method to reduce execution time and increase maintainability
- Test the limits (bulk)
- Use test.startTest() after data preparation and test.stopTest() before asserts
- It is better to do Test Driven Development. It is more productive development.
- Prefer target 85% code coverage per class
Visualforce
- Keep you page light (few fields, simple DOM, avoid frameworks), for performance and usage.
- Remove commented code (<!-- ... -->) to increase maintainability and improve server parsing performance
- Do not hardcode access to Visualforce Pages (value="/apex/PageName") as it will break when CRSF is enabled. Use either {!$Page.PageName} or {!URLFOR($Page.PageName)}
- Reduce frameworks usage to keep the page light and performant (RAM footprint, network impact, CPU consumption...)
- Manage errors. An error can still occure, warn the user with putting one of the following tags on the top of your page: <apex:pageMessages /> or <apex:messages />
Profiles
- Keep the number of profiles low. Use permission sets if one profile as a few more permissions than another existing profile.
- Remove unused custom profiles
- Profiles should not be API enabled, except when really necessary, for security reasons.
Roles
- Keep your role hierarchy simple. If you have one role per user, and some roles without users, you probably have a design issue
Validation Rules
- Don't use hard coded IDs
- Put a bypass system in the logic
- When adding a Validation Rule, migrate existing records that don't match this rule.
- Keep the quantity of VR low to manage performance
- Define the error message at the field location instead of at the top of the page.
- Remove inactive Validation Rules
Workflow rules and actions
- Don't use hard coded IDs
- Put a bypass system in the logic
- Don't create multiple workflows with the same rule
- Remove Workflow actions that are not used by workflow rules
- Remove rules that have no actions
- Keep the quantity of WR low to manage performance
- Remove inactive workflows
- Don't update fields if the current value is already correct
Process Builder
- Put a bypass logic within each test
- Don't update fields if the current value is already correct
Static Resources
- Do not host credentials (security risk)
- Use the description field to tell where the resource is being used
- Optimize picture compression and size. Use the appropriate file format.
Custom Labels
- Avoid duplicate labels with the same value
Naming conventions
- Avoid using underscores
- Classes: Use UpperCamelCase
- Variables and methods: use camelCase with the first letter in lowercase
- Test classes: Append "Test" to the name of the covered class
- Trigger: [ObjectName][Event]. Example: AccountBeforeInsert