К основному содержанию
правило официальный · GitHub

apex — правило для GitHub Copilot: стандарты Apex на Salesforce

Правило задаёт Copilot стиль кода Apex: массовые операции, имена классов и методов, проверка прав доступа. Пригодится, если вы пишете триггеры и классы на платформе Salesforce.

Установить в GitHub Copilot · Вручную: положить файлы Все способы и платформы ↓
.github/instructions/apex.instructions.md
Открыть на GitHub

Что делает apex

Файл подключается к репозиторию и дальше работает сам: Copilot подхватывает инструкции, когда вы открываете *.cls или *.trigger. Внутри — разделы про общие требования, соглашения об именовании и набор практик.

Именование расписано по суффиксам: контроллеры заканчиваются на Controller, обработчики триггеров на TriggerHandler, сервисные классы на Service, селекторы на Selector, тесты на Test, batch-классы на Batch, очереди на Queueable. Методы и переменные — camelCase, константы — UPPER_SNAKE_CASE, триггеры — имя объекта плюс Trigger.

Отдельный блок про governor limits: лимиты SOQL-запросов, DML-операций, heap size и CPU time. Советует проверять расход через System.Limits заранее, а не когда запрос уже упал. Ещё есть требования к безопасности — проверка CRUD/FLS, WITH SECURITY_ENFORCED, Security.stripInaccessible(), ключевые слова WITH SHARING и WITHOUT SHARING. Если ваш код ходит в базу без проверки прав, правило будет тянуть правки в эту сторону, и это повод проверить, не сломает ли оно существующую логику доступа.

Какие задачи решает

  • писать Apex-код, который выдерживает массовую обработку записей
  • разложить классы по слоям: сервисы, домены, селекторы
  • вынести SOQL и DML из циклов
  • проверять CRUD и FLS перед запросом к объекту
  • подобрать имена классов и методов по единому шаблону

Примеры запросов агенту

  • напиши триггерный обработчик для Account по этому правилу
  • перепиши этот класс так, чтобы он был bulkified и не упирался в governor limits
  • добавь проверку прав доступа через WITH SECURITY_ENFORCED в этот SOQL-запрос

Как установить apex

Вручную: положить файлы

.github/instructions/apex.instructions.md

Поле applyTo задаёт, к каким файлам применять

По шагам, с командами для каждой платформы: AGENTS.md и CLAUDE.md: что это, кто читает и какой выбрать

Вопросы о apex

Как установить apex в GitHub Copilot?

Скопируйте файл правила в репозиторий по пути .github/instructions/apex.instructions.md. Дальше Copilot читает его автоматически для файлов *.cls и *.trigger.

Что делает правило apex?

Задаёт Copilot требования к коду Apex: массовые операции вместо одиночных записей, единые суффиксы в именах классов, проверка CRUD/FLS и осознанная работа с governor limits.

Для каких файлов работает это правило?

Область задана в applyTo: **/*.cls и **/*.trigger. На другие файлы проекта инструкции не распространяются.

Исходный текст

apex.instructions.md · MIT
---
description: 'Guidelines and best practices for Apex development on the Salesforce Platform'
applyTo: '**/*.cls, **/*.trigger'
---

# Apex Development

## General Instructions

- Always use the latest Apex features and best practices for the Salesforce Platform.
- Write clear and concise comments for each class and method, explaining the business logic and any complex operations.
- Handle edge cases and implement proper exception handling with meaningful error messages.
- Focus on bulkification - write code that handles collections of records, not single records.
- Be mindful of governor limits and design solutions that scale efficiently.
- Implement proper separation of concerns using service layers, domain classes, and selector classes.
- Document external dependencies, integration points, and their purposes in comments.

## Naming Conventions

- **Classes**: Use `PascalCase` for class names. Name classes descriptively to reflect their purpose.
  - Controllers: suffix with `Controller` (e.g., `AccountController`)
  - Trigger Handlers: suffix with `TriggerHandler` (e.g., `AccountTriggerHandler`)
  - Service Classes: suffix with `Service` (e.g., `AccountService`)
  - Selector Classes: suffix with `Selector` (e.g., `AccountSelector`)
  - Test Classes: suffix with `Test` (e.g., `AccountServiceTest`)
  - Batch Classes: suffix with `Batch` (e.g., `AccountCleanupBatch`)
  - Queueable Classes: suffix with `Queueable` (e.g., `EmailNotificationQueueable`)

- **Methods**: Use `camelCase` for method names. Use verbs to indicate actions.
  - Good: `getActiveAccounts()`, `updateContactEmail()`, `deleteExpiredRecords()`
  - Avoid abbreviations: `getAccs()` → `getAccounts()`

- **Variables**: Use `camelCase` for variable names. Use descriptive names.
  - Good: `accountList`, `emailAddress`, `totalAmount`
  - Avoid single letters except for loop counters: `a` → `account`

- **Constants**: Use `UPPER_SNAKE_CASE` for constants.
  - Good: `MAX_BATCH_SIZE`, `DEFAULT_EMAIL_TEMPLATE`, `ERROR_MESSAGE_PREFIX`

- **Triggers**: Name triggers as `ObjectName` + trigger event (e.g., `AccountTrigger`, `ContactTrigger`)

## Best Practices

### Bulkification

- **Always write bulkified code** - Design all code to handle collections of records, not individual records.
- Avoid SOQL queries and DML statements inside loops.
- Use collections (`List<>`, `Set<>`, `Map<>`) to process multiple records efficiently.

```apex
// Good Example - Bulkified
public static void updateAccountRating(List<Account> accounts) {
    for (Account acc : accounts) {
        if (acc.AnnualRevenue > 1000000) {
            acc.Rating = 'Hot';
        }
    }
    update accounts;
}

// Bad Example - Not bulkified
public static void updateAccountRating(Account account) {
    if (account.AnnualRevenue > 1000000) {
        account.Rating = 'Hot';
        update account; // DML in a method designed for single records
    }
}
```

### Maps for O(1) Lookup

- **Use Maps for efficient lookups** - Convert lists to maps for O(1) constant-time lookups instead of O(n) list iterations.
- Use `Map<Id, SObject>` constructor to quickly convert query results to a map.
- Ideal for matching related records, lookups, and avoiding nested loops.

```apex
// Good Example - Using Map for O(1) lookup
Map<Id, Account> accountMap = new Map<Id, Account>([
    SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds
]);

for (Contact con : contacts) {
    Account acc = accountMap.get(con.AccountId);
    if (acc != null) {
        con.Industry__c = acc.Industry;
    }
}

// Bad Example - Nested loop with O(n²) complexity
List<Account> accounts = [SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds];

for (Contact con : contacts) {
    for (Account acc : accounts) {
        if (con.AccountId == acc.Id) {
            con.Industry__c = acc.Industry;
            break;
        }
    }
}

// Good Example - Map for grouping records
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();
for (Contact con : contacts) {
    if (!contactsByAccountId.containsKey(con.AccountId)) {
        contactsByAccountId.put(con.AccountId, new List<Contact>());
    }
    contactsByAccountId.get(con.AccountId).add(con);
}
```

### Governor Limits

- Be aware of Salesforce governor limits: SOQL queries (100), DML statements (150), heap size (6MB), CPU time (10s).
- **Monitor governor limits proactively** using `System.Limits` class to check consumption before hitting limits.
- Use efficient SOQL queries with selective filters and appropriate indexes.
- Implement **SOQL for loops** for processing large data sets.
- Use **Batch Apex** for operations on large data volumes (>50,000 records).
- Leverage **Platform Cache** to reduce redundant SOQL queries.

```apex
// Good Example - SOQL for loop for large data sets
public static void processLargeDataSet() {
    for (List<Account> accounts : [SELECT Id, Name FROM Account]) {
        // Process batch of 200 records
        processAccounts(accounts);
    }
}

// Good Example - Using WHERE clause to reduce query results
List<Account> accounts = [SELECT Id, Name FROM Account WHERE IsActive__c = true LIMIT 200];
```

### Security and Data Access

- **Always check CRUD/FLS permissions** before performing SOQL queries or DML operations.
- Use `WITH SECURITY_ENFORCED` in SOQL queries to enforce field-level security.
- Use `Security.stripInaccessible()` to remove fields the user cannot access.
- Implement `WITH SHARING` keyword for classes that enforce sharing rules.
- Use `WITHOUT SHARING` only when necessary and document the reason.
- Use `INHERITED SHARING` for utility classes to inherit the calling context.

```apex
// Good Example - Checking CRUD and using stripInaccessible
public with sharing class AccountService {
    public static List<Account> getAccounts() {
        if (!Schema.sObjectType.Account.isAccessible()) {
            throw new SecurityException('User does not have access to Account object');
        }

        List<Account> accounts = [SELECT Id, Name, Industry FROM Account WITH SECURITY_ENFORCED];

        SObjectAccessDecision decision = Security.stripInaccessible(
            AccessType.READABLE, accounts
        );

        return decision.getRecords();
    }
}

// Good Example - WITH SHARING for sharing rules
public with sharing class AccountController {
    // This class enforces record-level sharing
}
```

### Exception Handling

- Always use try-catch blocks for DML operations and callouts.
- Create custom exception classes for specific error scenarios.
- Log exceptions appropriately for debugging and monitoring.
- Provide meaningful error messages to users.

```apex
// Good Example - Proper exception handling
public class AccountService {
    public class AccountServiceException extends Exception {}

    public static void safeUpdate(List<Account> accounts) {
        try {
            if (!Schema.sObjectType.Account.isUpdateable()) {
                throw new AccountServiceException('User does not have permission to update accounts');
            }
            update accounts;
        } catch (DmlException e) {
            System.debug(LoggingLevel.ERROR, 'DML Error: ' + e.getMessage());
            throw new AccountServiceException('Failed to update accounts: ' + e.getMessage());
        }
    }
}
```

### SOQL Best Practices

- Use selective queries with indexed fields (`Id`, `Name`, `OwnerId`, custom indexed fields).
- Limit query results with `LIMIT` clause when appropriate.
- Use `LIMIT 1` when you only need one record.
- Avoid `SELECT *` - always specify required fields.
- Use relationship queries to minimize the number of SOQL queries.
- Order queries by indexed fields when possible.
- **Always use `String.escapeSingleQuotes()`** when using user input in SOQL queries to prevent SOQL injection attacks.
- **Check query selectivity** - Aim for >10% selectivity (filters reduce results to <10% of total records).
- Use **Query Plan** to verify query efficiency and index usage.
- Test queries with realistic data volumes to ensure performance.

```apex
// Good Example - Selective query with indexed fields
List<Account> accounts = [
    SELECT Id, Name, (SELECT Id, LastName FROM Contacts)
    FROM Account
    WHERE OwnerId = :UserInfo.getUserId()
    AND CreatedDate = THIS_MONTH
    LIMIT 100
];

// Good Example - LIMIT 1 for single record
Account account = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];

// Good Example - escapeSingleQuotes() to prevent SOQL injection
String searchTerm = String.escapeSingleQuotes(userInput);
List<Account> accounts = Database.query('SELECT Id, Name FROM Account WHERE Name LIKE \'%' + searchTerm + '%\'');

// Bad Example - Direct user input without escaping (SECURITY RISK)
List<Account> accounts = Database.query('SELECT Id, Name FROM Account WHERE Name LIKE \'%' + userInput + '%\'');

// Good Example - Selective query with indexed fields (high selectivity)
List<Account> accounts = [
    SELECT Id, Name FROM Account
    WHERE OwnerId = :UserInfo.getUserId()
    AND CreatedDate = TODAY
    LIMIT 100
];

// Bad Example - Non-selective query (scans entire table)
List<Account> accounts = [
    SELECT Id, Name FROM Account
    WHERE Description LIKE '%test%'  // Non-indexed field
];

// Check query performance in Developer Console:
// 1. Enable 'Use Query Plan' in Developer Console
// 2. Run SOQL query and review 'Query Plan' tab
// 3. Look for 'Index' usage vs 'TableScan'
// 4. Ensure selectivity > 10% for optimal performance
```

### Trigger Best Practices

- Use **one trigger per object** to maintain clarity and avoid conflicts.
- Implement trigger logic in handler classes, not directly in triggers.
- Use a trigger framework for consistent trigger management.
- Leverage trigger context variables: `Trigger.new`, `Trigger.old`, `Trigger.newMap`, `Trigger.oldMap`.
- Check trigger context: `Trigger.isBefore`, `Trigger.isAfter`, `Trigger.isInsert`, etc.

```apex
// Good Example - Trigger with handler pattern
trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
    new AccountTriggerHandler().run();
}

// Handler Class
public class AccountTriggerHandler extends TriggerHandler {
    private List<Account> newAccounts;
    private List<Account> oldAccounts;
    private Map<Id, Account> newAccountMap;
    private Map<Id, Account> oldAccountMap;

    public AccountTriggerHandler() {
        this.newAccounts = (List<Account>) Trigger.new;
        this.oldAccounts = (List<Account>) Trigger.old;
        this.newAccountMap = (Map<Id, Account>) Trigger.newMap;
        this.oldAccountMap = (Map<Id, Account>) Trigger.oldMap;
    }

    public override void beforeInsert() {
        AccountService.setDefaultValues(newAccounts);
    }

    public override void afterUpdate() {
        AccountService.handleRatingChange(newAccountMap, oldAccountMap);
    }
}
```

### Code Quality Best Practices

- **Use `isEmpty()`** - Check if collections are empty using built-in methods instead of size comparisons.
- **Use Custom Labels** - Store user-facing text in Custom Labels for internationalization and maintainability.
- **Use Constants** - Define constants for hardcoded values, error messages, and configuration values.
- **Use `String.isBlank()` and `String.isNotBlank()`** - Check for null or empty strings properly.
- **Use `String.valueOf()`** - Safely convert values to strings to avoid null pointer exceptions.
- **Use safe navigation operator `?.`** - Access properties and methods safely without null pointer exceptions.
- **Use null-coalescing operator `??`** - Provide default values for null expressions.
- **Avoid using `+` for string concatenation in loops** - Use `String.join()` for better performance.
- **Use Collection methods** - Leverage `List.clone()`, `Set.addAll()`, `Map.keySet()` for cleaner code.
- **Use ternary operators** - For simple conditional assignments to improve readability.
- **Use switch expressions** - Modern alternative to if-else chains for better readability and performance.
- **Use SObject clone methods** - Properly clone SObjects when needed to avoid unintended references.

```apex
// Good Example - Switch expression (modern Apex)
String rating = switch on account.AnnualRevenue {
    when 0 { 'Cold'; }
    when 1, 2, 3 { 'Warm'; }
    when else { 'Hot'; }
};

// Good Example - Switch on SObjectType
String objectLabel = switch on record {
    when Account a { 'Account: ' + a.Name; }
    when Contact c { 'Contact: ' + c.LastName; }
    when else { 'Unknown'; }
};

// Bad Example - if-else chain
String rating;
if (account.AnnualRevenue == 0) {
    rating = 'Cold';
} else if (account.AnnualRevenue >= 1 && account.AnnualRevenue <= 3) {
    rating = 'Warm';
} else {
    rating = 'Hot';
}

// Good Example - SObject clone methods
Account original = new Account(Name = 'Acme', Industry = 'Technology');

// Shallow clone with ID and relationships
Account clone1 = original.clone(true, true);

// Shallow clone without ID or relationships
Account clone2 = original.clone(false, false);

// Deep clone with all relationships
Account clone3 = original.deepClone(true, true, true);

// Good Example - isEmpty() instead of size comparison
if (accountList.isEmpty()) {
    System.debug('No accounts found');
}

// Bad Example - size comparison
if (accountList.size() == 0) {
    System.debug('No accounts found');
}

// Good Example - Custom Labels for user-facing text
final String ERROR_MESSAGE = System.Label.Account_Update_Error;
final String SUCCESS_MESSAGE = System.Label.Account_Update_Success;

// Bad Example - Hardcoded strings
final String ERROR_MESSAGE = 'An error occurred while updating the account';

// Good Example - Constants for configuration values
public class AccountService {
    private static final Integer MAX_RETRY_ATTEMPTS = 3;
    private static final String DEFAULT_INDUSTRY = 'Technology';
    private static final String ERROR_PREFIX = 'AccountService Error: ';

    public static void processAccounts() {
        // Use constants
        if (retryCount > MAX_RETRY_ATTEMPTS) {
            throw new AccountServiceException(ERROR_PREFIX + 'Max retries exceeded');
        }
    }
}

// Good Example - isBlank() for null and empty checks
if (String.isBlank(account.Name)) {
    account.Name = DEFAULT_NAME;
}

// Bad Example - multiple null checks
if (account.Name == null || account.Name == '') {
    account.Name = DEFAULT_NAME;
}

// Good Example - String.valueOf() for safe conversion
String accountId = String.valueOf(account.Id);
String revenue = String.valueOf(account.AnnualRevenue);

// Good Example - Safe navigation operator (?.)
String ownerName = account?.Owner?.Name;
Integer contactCount = account?.Contacts?.size();

// Bad Example - Nested null checks
String ownerName;
if (account != null && account.Owner != null) {
    ownerName = account.Owner.Name;
}

// Good Example - Null-coalescing operator (??)
String accountName = account?.Name ?? 'Unknown Account';
Integer revenue = account?.AnnualRevenue ?? 0;
String industry = account?.Industry ?? DEFAULT_INDUSTRY;

// Bad Example - Ternary with null check
String accountName = account != null && account.Name != null ? account.Name : 'Unknown Account';

// Good Example - Combining ?. and ??
String email = contact?.Email ?? contact?.Account?.Owner?.Email ?? '[email protected]';

// Good Example - String concatenation in loops
List<String> accountNames = new List<String>();
for (Account acc : accounts) {
    accountNames.add(acc.Name);
}
String result = String.join(accountNames, ', ');

// Bad Example - String concatenation in loops
String result = '';
for (Account acc : accounts) {
    result += acc.Name + ', '; // Poor performance
}

// Good Example - Ternary operator
String status = isActive ? 'Active' : 'Inactive';

// Good Example - Collection methods
List<Account> accountsCopy = accountList.clone();
Set<Id> accountIds = new Set<Id>(accountMap.keySet());
```

### Recursion Prevention

- **Use static variables** to track recursive calls and prevent infinite loops.
- Implement a **circuit breaker** pattern to stop execution after a threshold.
- Document recursion limits and potential risks.

```apex
// Good Example - Recursion prevention with static variable
public class AccountTriggerHandler extends TriggerHandler {
    private static Boolean hasRun = false;

    public override void afterUpdate() {
        if (!hasRun) {
            hasRun = true;
            AccountService.updateRelatedContacts(Trigger.newMap.keySet());
        }
    }
}

// Good Example - Circuit breaker with counter
public class OpportunityService {
    private static Integer recursionCount = 0;
    private static final Integer MAX_RECURSION_DEPTH = 5;

    public static void processOpportunity(Id oppId) {
        recursionCount++;

        if (recursionCount > MAX_RECURSION_DEPTH) {
            System.debug(LoggingLevel.ERROR, 'Max recursion depth exceeded');
            return;
        }

        try {
            // Process opportunity logic
        } finally {
            recursionCount--;
        }
    }
}
```

### Method Visibility and Encapsulation

- **Use `private` by default** - Only expose methods that need to be public.
- Use `protected` for methods that subclasses need to access.
- Use `public` only for APIs that other classes need to call.
- **Use `final` keyword** to prevent method override when appropriate.
- Mark classes as `final` if they should not be extended.

```apex
// Good Example - Proper encapsulation
public class AccountService {
    // Public API
    public static void updateAccounts(List<Account> accounts) {
        validateAccounts(accounts);
        performUpdate(accounts);
    }

    // Private helper - not exposed
    private static void validateAccounts(List<Account> accounts) {
        for (Account acc : accounts) {
            if (String.isBlank(acc.Name)) {
                throw new IllegalArgumentException('Account name is required');
            }
        }
    }

    // Private implementation - not exposed
    private static void performUpdate(List<Account> accounts) {
        update accounts;
    }
}

// Good Example - Final keyword to prevent extension
public final class UtilityHelper {
    // Cannot be extended
    public static String formatCurrency(Decimal amount) {
        return '$' + amount.setScale(2);
    }
}

// Good Example - Final method to prevent override
public virtual class BaseService {
    // Can be overridden
    public virtual void process() {
        // Implementation
    }

    // Cannot be overridden
    public final void validateInput() {
        // Critical validation that must not be changed
    }
}
```

### Design Patterns

- **Service Layer Pattern**: Encapsulate business logic in service classes.
- **Circuit Breaker Pattern**: Prevent repeated failures by stopping execution after threshold.
- **Selector Pattern**: Create dedicated classes for SOQL queries.
- **Domain Layer Pattern**: Implement domain classes for record-specific logic.
- **Trigger Handler Pattern**: Use a consistent framework for trigger management.
- **Builder Pattern**: Use for complex object construction.
- **Strategy Pattern**: For implementing different behaviors based on conditions.

```apex
// Good Example - Service Layer Pattern
public class AccountService {
    public static void updateAccountRatings(Set<Id> accountIds) {
        List<Account> accounts = AccountSelector.selectByIds(accountIds);

        for (Account acc : accounts) {
            acc.Rating = calculateRating(acc);
        }

        update accounts;
    }

    private static String calculateRating(Account acc) {
        if (acc.AnnualRevenue > 1000000) {
            return 'Hot';
        } else if (acc.AnnualRevenue > 500000) {
            return 'Warm';
        }
        return 'Cold';
    }
}

// Good Example - Circuit Breaker Pattern
public class ExternalServiceCircuitBreaker {
    private static Integer failureCount = 0;
    private static final Integer FAILURE_THRESHOLD = 3;
    private static DateTime circuitOpenedTime;
    private static final Integer RETRY_TIMEOUT_MINUTES = 5;

    public static Boolean isCircuitOpen() {
        if (circuitOpenedTime != null) {
            // Check if retry timeout has passed
            if (DateTime.now() > circuitOpenedTime.addMinutes(RETRY_TIMEOUT_MINUTES)) {
                // Reset circuit
                failureCount = 0;
                circuitOpenedTime = null;
                return false;
            }
            return true;
        }
        return failureCount >= FAILURE_THRESHOLD;
    }

    public static void recordFailure() {
        failureCount++;
        if (failureCount >= FAILURE_THRESHOLD) {
            circuitOpenedTime = DateTime.now();
            System.debug(LoggingLevel.ERROR, 'Circuit breaker opened due to failures');
        }
    }

    public static void recordSuccess() {
        failureCount = 0;
        circuitOpenedTime = null;
    }

    public static HttpResponse makeCallout(String endpoint) {
        if (isCircuitOpen()) {
            throw new CircuitBreakerException('Circuit is open. Service unavailable.');
        }

        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(endpoint);
            req.setMethod('GET');
            HttpResponse res = new Http().send(req);

            if (res.getStatusCode() == 200) {
                recordSuccess();
            } else {
                recordFailure();
            }
            return res;
        } catch (Exception e) {
            recordFailure();
            throw e;
        }
    }

    public class CircuitBreakerException extends Exception {}
}

// Good Example - Selector Pattern
public class AccountSelector {
    public static List<Account> selectByIds(Set<Id> accountIds) {
        return [
            SELECT Id, Name, AnnualRevenue, Rating
            FROM Account
            WHERE Id IN :accountIds
            WITH SECURITY_ENFORCED
        ];
    }

    public static List<Account> selectActiveAccountsWithContacts() {
        return [
            SELECT Id, Name, (SELECT Id, LastName FROM Contacts)
            FROM Account
            WHERE IsActive__c = true
            WITH SECURITY_ENFORCED
        ];
    }
}
```

### Configuration Management

#### Custom Metadata Types vs Custom Settings

- **Prefer Custom Metadata Types (CMT)** for configuration data that can be deployed.
- Use **Custom Settings** for user-specific or org-specific data that varies by environment.
- CMT is packageable, deployable, and can be used in validation rules and formulas.
- Custom Settings support hierarchy (Org, Profile, User) but are not deployable.

```apex
// Good Example - Using Custom Metadata Type
List<API_Configuration__mdt> configs = [
    SELECT Endpoint__c, Timeout__c, Max_Retries__c
    FROM API_Configuration__mdt
    WHERE DeveloperName = 'Production_API'
    LIMIT 1
];

if (!configs.isEmpty()) {
    String endpoint = configs[0].Endpoint__c;
    Integer timeout = Integer.valueOf(configs[0].Timeout__c);
}

// Good Example - Using Custom Settings (user-specific)
User_Preferences__c prefs = User_Preferences__c.getInstance(UserInfo.getUserId());
Boolean darkMode = prefs.Dark_Mode_Enabled__c;

// Good Example - Using Custom Settings (org-level)
Org_Settings__c orgSettings = Org_Settings__c.getOrgDefaults();
Integer maxRecords = Integer.valueOf(orgSettings.Max_Records_Per_Query__c);
```

#### Named Credentials and HTTP Callouts

- **Always use Named Credentials** for external API endpoints and authentication.
- Avoid hardcoding URLs, tokens, or credentials in code.
- Use `callout:NamedCredential` syntax for secure, deployable integrations.
- **Always check HTTP status codes** and handle errors gracefully.
- Set appropriate timeouts to prevent long-running callouts.
- Use `Database.AllowsCallouts` interface for Queueable and Batchable classes.

```apex
// Good Example - Using Named Credentials
public class ExternalAPIService {
    private static final String NAMED_CREDENTIAL = 'callout:External_API';
    private static final Integer TIMEOUT_MS = 120000; // 120 seconds

    public static Map<String, Object> getExternalData(String recordId) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CREDENTIAL + '/api/records/' + recordId);
        req.setMethod('GET');
        req.setTimeout(TIMEOUT_MS);
        req.setHeader('Content-Type', 'application/json');

        try {
            Http http = new Http();
            HttpResponse res = http.send(req);

            if (res.getStatusCode() == 200) {
                return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            } else if (res.getStatusCode() == 404) {
                throw new NotFoundException('Record not found: ' + recordId);
            } else if (res.getStatusCode() >= 500) {
                throw new ServiceUnavailableException('External service error: ' + res.getStatus());
            } else {
                throw new CalloutException('Unexpected response: ' + res.getStatusCode());
            }
        } catch (System.CalloutException e) {
            System.debug(LoggingLevel.ERROR, 'Callout failed: ' + e.getMessage());
            throw new ExternalAPIException('Failed to retrieve data', e);
        }
    }

    public class ExternalAPIException extends Exception {}
    public class NotFoundException extends Exception {}
    public class ServiceUnavailableException extends Exception {}
}

// Good Example - POST request with JSON body
public static String createExternalRecord(Map<String, Object> data) {
    HttpRequest req = new HttpRequest();
    req.setEndpoint(NAMED_CREDENTIAL + '/api/records');
    req.setMethod('POST');
    req.setTimeout(TIMEOUT_MS);
    req.setHeader('Content-Type', 'application/json');
    req.setBody(JSON.serialize(data));

    HttpResponse res = new Http().send(req);

    if (res.getStatusCode() == 201) {
        Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        return (String) result.get('id');
    } else {
        throw new CalloutException('Failed to create record: ' + res.getStatus());
    }
}
```

### Common Annotations

- `@AuraEnabled` - Expose methods to Lightning Web Components and Aura Components.
- `@AuraEnabled(cacheable=true)` - Enable client-side caching for read-only methods.
- `@InvocableMethod` - Make methods callable from Flow and Process Builder.
- `@InvocableVariable` - Define input/output parameters for invocable methods.
- `@TestVisible` - Expose private members to test classes only.
- `@SuppressWarnings('PMD.RuleName')` - Suppress specific PMD warnings.
- `@RemoteAction` - Expose methods for Visualforce JavaScript remoting (legacy).
- `@Future` - Execute methods asynchronously.
- `@Future(callout=true)` - Allow HTTP callouts in future methods.

```apex
// Good Example - AuraEnabled for LWC
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts() {
        return [SELECT Id, Name FROM Account WITH SECURITY_ENFORCED LIMIT 10];
    }

    @AuraEnabled
    public static void updateAccount(Id accountId, String newName) {
        Account acc = new Account(Id = accountId, Name = newName);
        update acc;
    }
}

// Good Example - InvocableMethod for Flow
public class FlowActions {
    @InvocableMethod(label='Send Email Notification' description='Sends email to account owner')
    public static List<Result> sendNotification(List<Request> requests) {
        List<Result> results = new List<Result>();

        for (Request req : requests) {
            Result result = new Result();
            try {
                // Send email logic
                result.success = true;
                result.message = 'Email sent successfully';
            } catch (Exception e) {
                result.success = false;
                result.message = e.getMessage();
            }
            results.add(result);
        }
        return results;
    }

    public class Request {
        @InvocableVariable(required=true label='Account ID')
        public Id accountId;

        @InvocableVariable(label='Email Template')
        public String templateName;
    }

    public class Result {
        @InvocableVariable
        public Boolean success;

        @InvocableVariable
        public String message;
    }
}

// Good Example - TestVisible for testing private methods
public class AccountService {
    @TestVisible
    private static Boolean validateAccountName(String name) {
        return String.isNotBlank(name) && name.length() > 3;
    }
}
```

### Asynchronous Apex

- Use **@future** methods for simple asynchronous operations and callouts.
- Use **Queueable Apex** for complex asynchronous operations that require chaining.
- Use **Batch Apex** for processing large data volumes (>50,000 records).
  - Use `Database.Stateful` to maintain state across batch executions (e.g., counters, aggregations).
  - Without `Database.Stateful`, batch classes are stateless and instance variables reset between batches.
  - Be mindful of governor limits when using stateful batches.
- Use **Scheduled Apex** for recurring operations.
  - Create a separate **Schedulable class** to schedule batch jobs.
  - Never implement both `Database.Batchable` and `Schedulable` in the same class.
- Use **Platform Events** for event-driven architecture and decoupled integrations.
  - Publish events using `EventBus.publish()` for asynchronous, fire-and-forget communication.
  - Subscribe to events using triggers on platform event objects.
  - Ideal for integrations, microservices, and cross-org communication.
- **Optimize batch size** based on processing complexity and governor limits.
  - Default batch size is 200, but can be adjusted from 1 to 2000.
  - Smaller batches (50-100) for complex processing or callouts.
  - Larger batches (200) for sim...

© github · MIT · github/awesome-copilot

Похожие правила IDE

Все правила IDE по теме «Языки программирования» →

Из того же набора732

Весь набор →
attester-verify-packages Проверяет имена пакетов и символов PyPI и npm через attester.dev до установки или импорта. Нужен для защиты от выдуманных зависимостей. ★ 40 418звёзд репо astro Правило задаёт Copilot стандарты разработки на Astro 7: островная архитектура, Content Layer API, TypeScript и серверный рендеринг по умолчанию. ★ 40 418звёзд репо aspnet-rest-apis Направляет GitHub Copilot при создании REST API на ASP.NET Core 10, сопоставляя контроллеры и Minimal API. Применяется к файлам *.cs и *.json. ★ 40 418звёзд репо arch-linux Правило задаёт рекомендации для инструкций и скриптов по Arch Linux: от обновления через pacman до проверки служб. Пригодится, когда Copilot помогает администрировать систему. ★ 40 418звёзд репо ansible Задаёт стандарты написания плейбуков Ansible: правила именования задач, формат коллекций FQCN, разделение секретов в Ansible Vault и проверки через ansible-lint. ★ 40 418звёзд репо ai-prompt-engineering-safety-best-practices Правило помогает формулировать запросы к ИИ с ясным контекстом, ограничениями и требованиями к формату. Оно также предлагает учитывать безопасность и предвзятость при создании и проверке промптов. ★ 40 418звёзд репо agents Правило задаёт стандарт для файлов кастомных агентов GitHub Copilot: какие поля обязательны, как настроить инструменты и переходы между агентами. ★ 40 418звёзд репо agent-skills Правило задаёт стандарты создания навыков для GitHub Copilot. Оно помогает правильно оформить файл SKILL.md, настроить триггеры активации и структурировать инструкции. ★ 40 418звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой