Best Practices
Essential best practices for Salesforce development in this repository.
Code Quality
General Principles
Write Clean, Readable Code
- Use meaningful variable and method names
- Keep methods short and focused
- Add comments for complex logic
- Follow consistent formatting
DRY (Don't Repeat Yourself)
- Extract reusable logic into helper methods
- Create utility classes for common operations
- Use inheritance and composition wisely
SOLID Principles
- Single Responsibility: One class, one purpose
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Subtypes must be substitutable
- Interface Segregation: Many specific interfaces
- Dependency Inversion: Depend on abstractions
Lightning Web Components
Component Design
Good:
javascript
// Small, focused component
export default class AccountName extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: "$recordId", fields: ["Account.Name"] })
account;
get accountName() {
return this.account.data?.fields.Name.value;
}
}Bad:
javascript
// Too many responsibilities
export default class AccountEverything extends LightningElement {
// Handles display, editing, deleting, related records, etc.
// 500+ lines of code
}Property Naming
Good:
javascript
export default class MyComponent extends LightningElement {
@api recordId; // Public API
selectedItem; // Private property
isLoading = false; // Boolean with 'is' prefix
errorMessage; // Clear naming
}Bad:
javascript
export default class MyComponent extends LightningElement {
@api rid; // Unclear abbreviation
item; // Too generic
loading; // Missing 'is' prefix
err; // Unclear abbreviation
}Error Handling
Good:
javascript
async loadData() {
this.isLoading = true;
this.error = undefined;
try {
this.data = await getData({ recordId: this.recordId });
} catch (error) {
this.error = this.normalizeError(error);
this.showToast('Error', this.error.message, 'error');
} finally {
this.isLoading = false;
}
}
normalizeError(error) {
if (error.body?.message) {
return { message: error.body.message };
}
return { message: 'An unexpected error occurred' };
}Bad:
javascript
async loadData() {
this.data = await getData({ recordId: this.recordId });
// No error handling, no loading state
}Reactive Properties
Good:
javascript
export default class AccountDisplay extends LightningElement {
@track filters = {
industry: "",
minRevenue: 0,
};
handleIndustryChange(event) {
// Triggers reactivity by creating new object
this.filters = {
...this.filters,
industry: event.target.value,
};
}
}Bad:
javascript
export default class AccountDisplay extends LightningElement {
@track filters = {
industry: "",
minRevenue: 0,
};
handleIndustryChange(event) {
// Mutation doesn't trigger reactivity
this.filters.industry = event.target.value;
}
}Apex Best Practices
Bulkification
Good:
apex
public class AccountService {
public static void updateIndustries(List<Account> accounts, String industry) {
for (Account acc : accounts) {
acc.Industry = industry;
}
update accounts; // Single DML
}
}Bad:
apex
public class AccountService {
public static void updateIndustries(List<Account> accounts, String industry) {
for (Account acc : accounts) {
acc.Industry = industry;
update acc; // DML in loop - NEVER DO THIS
}
}
}SOQL Best Practices
Good:
apex
public static List<Account> getAccountsWithContacts(Set<Id> accountIds) {
// Single query with relationship
return [
SELECT Id, Name,
(SELECT Id, Name FROM Contacts)
FROM Account
WHERE Id IN :accountIds
];
}Bad:
apex
public static List<Account> getAccountsWithContacts(Set<Id> accountIds) {
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
for (Account acc : accounts) {
// SOQL in loop - NEVER DO THIS
List<Contact> contacts = [SELECT Id, Name FROM Contact WHERE AccountId = :acc.Id];
// Can't assign to relationship anyway
}
return accounts;
}Sharing and Security
Good:
apex
// Explicitly declare sharing
public with sharing class AccountService {
public static List<Account> getAccounts() {
// Respects user's sharing rules
return [SELECT Id, Name FROM Account];
}
}
// Use without sharing only when necessary
public without sharing class SystemAccountService {
// Used for system-level operations only
public static List<Account> getAllAccountsSystemMode() {
return [SELECT Id, Name FROM Account];
}
}Bad:
apex
// No sharing declaration - inherited from caller (unclear)
public class AccountService {
public static List<Account> getAccounts() {
return [SELECT Id, Name FROM Account];
}
}Field-Level Security
Good:
apex
public with sharing class AccountController {
@AuraEnabled
public static List<Account> getAccounts() {
// Check FLS
if (!Schema.sObjectType.Account.fields.AnnualRevenue.isAccessible()) {
throw new AuraHandledException('Insufficient permissions');
}
return [
SELECT Id, Name, AnnualRevenue
FROM Account
LIMIT 10
];
}
}Alternative: Use Security.stripInaccessible
apex
public with sharing class AccountController {
@AuraEnabled
public static List<Account> getAccounts() {
List<Account> accounts = [
SELECT Id, Name, AnnualRevenue, Industry
FROM Account
LIMIT 10
];
// Automatically removes fields user can't access
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
accounts
);
return decision.getRecords();
}
}Exception Handling
Good:
apex
public class AccountService {
public class AccountServiceException extends Exception {}
public static void updateAccount(Account acc) {
try {
update acc;
} catch (DmlException e) {
System.debug(LoggingLevel.ERROR, 'Error updating account: ' + e.getMessage());
throw new AccountServiceException('Failed to update account: ' + e.getDmlMessage(0));
}
}
}Bad:
apex
public class AccountService {
public static void updateAccount(Account acc) {
try {
update acc;
} catch (Exception e) {
// Swallowing exception - NEVER DO THIS
System.debug('Error: ' + e.getMessage());
}
}
}Testing Best Practices
Test Data
Good:
apex
@isTest
private class AccountServiceTest {
@TestSetup
static void setup() {
// Create test data once for all test methods
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(
Name = 'Test Account ' + i,
Industry = 'Technology'
));
}
insert accounts;
}
@isTest
static void testGetAccounts() {
Test.startTest();
List<Account> accounts = AccountService.getActiveAccounts(10);
Test.stopTest();
Assert.areEqual(10, accounts.size(), 'Should return 10 accounts');
}
}Bad:
apex
@isTest
private class AccountServiceTest {
@isTest
static void testGetAccounts() {
// Creating data in every test method (inefficient)
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name = 'Test ' + i));
}
insert accounts;
// Test code
}
}Assertions
Good:
apex
@isTest
static void testAccountCreation() {
Account acc = new Account(Name = 'Test Account');
insert acc;
Account inserted = [SELECT Id, Name, IsActive__c FROM Account WHERE Id = :acc.Id];
// Clear, descriptive assertions
Assert.areEqual('Test Account', inserted.Name, 'Account name should match');
Assert.isTrue(inserted.IsActive__c, 'New accounts should be active by default');
}Bad:
apex
@isTest
static void testAccountCreation() {
Account acc = new Account(Name = 'Test Account');
insert acc;
Account inserted = [SELECT Id, Name FROM Account WHERE Id = :acc.Id];
// Generic assertion
Assert.isNotNull(inserted);
}Git and Version Control
Commit Messages
Good:
Add account search functionality
- Implement AccountController.searchAccounts method
- Create accountSearch LWC component
- Add Jest tests for search component
- Update documentationBad:
fixed stuffBranch Naming
Good:
feature/account-search
bugfix/contact-email-validation
hotfix/production-error-123Bad:
new-branch
fix
maciej-branchPull Requests
Good PR:
- Clear title describing the change
- Detailed description of what and why
- Links to related issues
- Screenshots for UI changes
- Tests included and passing
- Code review requested
Bad PR:
- No description
- Hundreds of files changed
- No tests
- Failing CI checks
CI/CD Best Practices
Pre-Deployment Checklist
- [ ] All tests passing locally
- [ ] Code reviewed by peer
- [ ] No hardcoded values or credentials
- [ ] Documentation updated
- [ ] CI/CD pipeline green
- [ ] Validated in sandbox
- [ ] Rollback plan documented
Metadata Management
Good:
xml
<!-- Clear, well-organized metadata -->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>Environment Variables
Good:
apex
// Use Custom Metadata or Custom Settings
Integration_Setting__mdt setting = [
SELECT Endpoint__c, API_Key__c
FROM Integration_Setting__mdt
WHERE DeveloperName = 'Production'
LIMIT 1
];
String endpoint = setting.Endpoint__c;Bad:
apex
// Hardcoded values
String endpoint = 'https://api.example.com/prod';
String apiKey = 'abc123secret';Performance Best Practices
LWC Performance
Good:
javascript
// Cache wire results
@wire(getAccounts)
accounts;
// Use getters for computed values
get hasAccounts() {
return this.accounts?.data?.length > 0;
}Bad:
javascript
// Imperative calls on every render
renderedCallback() {
getAccounts()
.then(result => {
this.accounts = result;
});
}Apex Performance
Good:
apex
// Use selective queries
List<Account> accounts = [
SELECT Id, Name
FROM Account
WHERE Industry = 'Technology'
AND CreatedDate = LAST_N_DAYS:30
LIMIT 100
];Bad:
apex
// Querying all records
List<Account> accounts = [SELECT Id, Name FROM Account];
// Filtering in code
List<Account> filtered = new List<Account>();
for (Account acc : accounts) {
if (acc.Industry == 'Technology') {
filtered.add(acc);
}
}Documentation Best Practices
Code Comments
- Explain why, not what
- Keep comments up-to-date
- Use JSDoc/JavaDoc format
README Files
- Clear setup instructions
- Prerequisites listed
- Examples included
API Documentation
- Document all public methods
- Include parameter descriptions
- Provide usage examples
Security Best Practices
CRUD/FLS Checks
- Always check permissions
- Use
with sharing - Validate user input
SOQL Injection Prevention
apex// Good - using binding String accountName = 'Test'; List<Account> accounts = [ SELECT Id FROM Account WHERE Name = :accountName ]; // Bad - string concatenation String query = 'SELECT Id FROM Account WHERE Name = \'' + accountName + '\'';XSS Prevention
- Sanitize user input
- Use platform encoding
- Validate data types
Next Steps
- LWC Examples - See examples in practice
- Apex Examples - Server-side patterns
- Testing Guide - Testing best practices