关于 salesforce-apex-quality
面向 Salesforce Apex 类、触发器和测试的代码审查清单,帮助开发者按明确规则检查 governor limit、安全性、查询方式和测试质量。
主要功能
- 01检查循环中的 SOQL 与 DML
- 02验证 sharing 和 CRUD/FLS
- 03防止 SOQL 注入
- 04现代 Apex 写法建议
- 05正向、负向及批量测试检查
- 06触发器架构规则
适用场景
- 01合并前审查 Apex 服务
- 02查找循环中的 SOQL 或 DML
- 03检查面向 UI 的方法是否验证 CRUD/FLS
- 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