mcpField
S
salesforce-apex-quality

salesforce-apex-quality

github.com/github测试与质量40.5k884 次安装1 天前更新
代码仓库

关于 salesforce-apex-quality

面向 Salesforce Apex 类、触发器和测试的代码审查清单,帮助开发者按明确规则检查 governor limit、安全性、查询方式和测试质量。

主要功能

  1. 01检查循环中的 SOQL 与 DML
  2. 02验证 sharing 和 CRUD/FLS
  3. 03防止 SOQL 注入
  4. 04现代 Apex 写法建议
  5. 05正向、负向及批量测试检查
  6. 06触发器架构规则

适用场景

  1. 01合并前审查 Apex 服务
  2. 02查找循环中的 SOQL 或 DML
  3. 03检查面向 UI 的方法是否验证 CRUD/FLS
  4. 04评估批量操作的测试覆盖

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/github/awesome-copilot --skill salesforce-apex-quality

安装后在 Claude Code 中输入/salesforce-apex-quality确认已加载。

详细介绍

Salesforce Apex 质量防护准则

对你在编写或审查的每个 Apex 类、触发器和测试文件应用这些检查。

步骤 1 —— 调控器限制安全检查

在判定任何 Apex 文件可接受之前,先扫描以下模式:

循环中的 SOQL 和 DML —— 自动失败
// ❌ NEVER — causes LimitException at scale
for (Account a : accounts) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :a.Id]; // SOQL in loop
    update a; // DML in loop
}

// ✅ ALWAYS — collect, then query/update once
Set<Id> accountIds = new Map<Id, Account>(accounts).keySet();
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}
update accounts; // DML once, outside the loop

规则:如果你在 for 循环体内看到 [SELECT 或 Database.query、insert、update、delete、upsert、merge——立即停止,先重构再继续。

步骤 2 —— 共享模型验证

每个类都必须显式声明其共享意图。未声明的共享会从调用方继承——行为不可预测。

声明何时使用
public with sharing class Foo所有服务、处理器、选择器和控制器类的默认选择
public without sharing class Foo仅当该类必须以提升权限运行(例如系统级日志记录、触发器绕过)。需要添加代码注释说明原因。
public inherited sharing class Foo应遵循调用方共享上下文的框架入口点

如果某个类没有上述三种声明中的任何一种,在编写其他任何内容之前先添加它。

步骤 3 —— CRUD / FLS 强制执行

代表用户读取或写入记录的 Apex 代码必须验证对象和字段访问权限。平台在 Apex 中不会自动强制执行 FLS 或 CRUD。

// Check before querying a field
if (!Schema.sObjectType.Contact.fields.Email.isAccessible()) {
    throw new System.NoAccessException();
}

// Or use WITH USER_MODE in SOQL (API 56.0+)
List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE AccountId = :accId WITH USER_MODE];

// Or use Database.query with AccessLevel
List<Contact> contacts = Database.query('SELECT Id, Email FROM Contact', AccessLevel.USER_MODE);

规则:任何可从 UI 组件、REST 端点或 @InvocableMethod 调用的 Apex 方法必须强制执行 CRUD/FLS。仅从受信任上下文调用的内部服务方法可以改用 with sharing。

步骤 4 —— SOQL 注入防护

// ❌ NEVER — concatenates user input into SOQL string
String soql = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';

// ✅ ALWAYS — bind variable
String soql = [SELECT Id FROM Account WHERE Name = :userInput];

// ✅ For dynamic SOQL with user-controlled field names — validate against a whitelist
Set<String> allowedFields = new Set<String>{'Name', 'Industry', 'AnnualRevenue'};
if (!allowedFields.contains(userInput)) {
    throw new IllegalArgumentException('Field not permitted: ' + userInput);
}

步骤 5 —— 现代 Apex 惯用法

优先使用当前语言特性(API 62.0 / Winter '25+):

旧模式现代替代写法
if (obj != null) { x = obj.Field__c; }x = obj?.Field__c;
x = (y != null) ? y : defaultVal;x = y ?? defaultVal;
System.assertEquals(expected, actual)Assert.areEqual(expected, actual)
System.assert(condition)Assert.isTrue(condition)
[SELECT ... WHERE ...] 无共享上下文[SELECT ... WHERE ... WITH USER_MODE]

步骤 6 —— PNB 测试覆盖检查清单

每个功能都必须覆盖全部三条路径进行测试。缺少任何一条都是质量不合格:

正向路径
  • 预期输入 → 预期输出。
  • 断言确切的字段值、记录数或返回值——而不仅仅是断言没有抛出异常。
负向路径
  • 无效输入、null 值、空集合和错误条件。
  • 断言抛出的异常具有正确的类型和消息。
  • 断言在操作应当干净地失败时,没有记录被修改。
批量路径
  • 在单个测试事务中插入/更新/删除 200–251 条记录。
  • 断言所有记录都正确处理——没有因调控器限制导致的部分失败。
  • 使用 Test.startTest() / Test.stopTest() 隔离异步工作的调控器限制计数器。
测试类规则
@isTest(SeeAllData=false)   // Required — no exceptions without a documented reason
private class AccountServiceTest {

    @TestSetup
    static void makeData() {
        // Create all test data here — use a factory if one exists in the project
    }

    @isTest
    static void givenValidInput_whenProcessAccounts_thenFieldsUpdated() {
        // Positive path
        List<Account> accounts = [SELECT Id FROM Account LIMIT 10];
        Test.startTest();
        AccountService.processAccounts(accounts);
        Test.stopTest();
        // Assert meaningful outcomes — not just no exception
        List<Account> updated = [SELECT Status__c FROM Account WHERE Id IN :accounts];
        Assert.areEqual('Processed', updated[0].Status__c, 'Status should be Processed');
    }
}

步骤 7 —— 触发器架构检查清单

  • 每个对象一个触发器。如果存在第二个触发器,将其合并到处理器中。
  • 触发器主体仅包含:上下文检查、处理器调用和路由逻辑。
  • 触发器主体中不得直接包含业务逻辑、SOQL 或 DML。
  • 如果已经在使用触发器框架(Trigger Actions Framework、ff-apex-common、自定义基类)——对其进行扩展。不要创建并行模式。
  • 处理器类为 with sharing,除非触发器需要提升权限。

快速参考 — 硬编码反模式摘要

模式操作
for 循环内的 SOQL重构:在循环前查询,对集合进行操作
for 循环内的 DML重构:收集变更,在循环后执行一次 DML
类缺少共享声明添加 with sharing(或说明为何使用 without sharing)
用户数据上的 escape="false"(VF)移除 — 自动转义可强制执行 XSS 防护
空的 catch 块添加日志记录以及适当的重新抛出或错误处理
与用户输入进行字符串拼接的 SOQL替换为绑定变量或白名单验证
没有断言的测试添加有意义的 Assert.* 调用
System.assert / System.assertEquals 风格升级为 Assert.isTrue / Assert.areEqual
硬编码记录 ID('001...')替换为查询或插入的测试记录 ID

评论

支持 Markdown