主要的 Apex 编写技能,用于类生成、重构和审查。当用户提到 Apex、.cls、触发器,或要求创建/重构类(服务、选择器、领域、批处理、可排队、可调度、可调用、DTO、工具类、接口、抽象类、异常、REST 资源)时,始终激活。此技能适用于涉及 SObject CRUD、集合映射、获取相关记录、计划作业、批处理作业、触发器设计、@AuraEnabled 控制器、@RestResource 端点、自定义 REST API 或现有 Apex 代码审查的请求。
生成 Apex
使用此技能生成生产级 Apex:新类、选择器、服务、异步作业、可调用方法和触发器;以及基于证据审查现有的 .cls 或 .trigger 文件。
必需输入
在编写之前收集或推断:
- 类类型(服务、选择器、领域、批处理、可排队、可调度、可调用、触发器、触发器动作、DTO、工具类、接口、抽象类、异常、REST 资源)
- 目标对象和业务目标
- 类名(使用下面的命名表推导)
- 全新创建 vs 重构/修复;任何组织/API 约束
- 部署目标(默认为 runSpecifiedTests,并在适用时使用生成的测试)
除非指定,否则默认值:
- 共享:
with sharing(参见每种类型的共享规则) - 访问:
public(仅在托管包或@RestResource要求时使用global) - API 版本:
66.0(最低版本) - ApexDoc 注释:是
如果用户提供了清晰完整的请求,立即生成,无需不必要的来回沟通。
工作流程
所有步骤按顺序执行。不要跳过、合并或重新排序。如果受阻,停止并询问缺失的上下文。如果不适用,在报告中标记 N/A 并附上一行理由。
阶段 1 — 编写
-
发现项目约定
- 服务-选择器-领域分层、日志工具
- 现有的类/触发器和当前的触发器框架或处理程序模式
- 是否已使用触发器动作框架(TAF)
-
选择最小正确模式(参见下面的类型特定指南)
-
审查模板和资产
- 在编写之前,从
assets/读取匹配的模板(参见类型特定指南中的文件映射) - 如果该类型存在
references/示例,将其作为具体风格指南阅读 - 对于任何测试类工作,始终阅读并使用
platform-apex-test-generate技能
- 在编写之前,从
-
在护栏下编写 — 应用下面规则部分中的每一条规则
- 生成带有 ApexDoc 的
{ClassName}.cls - 生成
{ClassName}.cls-meta.xml
- 生成带有 ApexDoc 的
-
生成测试类 — 加载
platform-apex-test-generate技能以创建{ClassName}Test.cls和{ClassName}Test.cls-meta.xml。始终需要生成 Apex 测试才能部署。不加载platform-apex-test-generate技能,不得创建或编辑测试文件。
阶段 2 — 验证(在报告前必需)
编写文件是中间点,而不是终点。步骤 6 和 7 各需要一次工具调用,并产生必须出现在步骤 8 报告中的输出。在运行两个步骤并捕获其输出之前,不要总结或呈现报告。
-
运行代码分析器
- 在所有生成/更新的
.cls文件上调用 MCPrun_code_analyzer。 - 修复所有
sev0、sev1和sev2违规;重新运行直到干净。 - 将最终工具输出逐字捕获到报告中。
- 备用方案:
sf code-analyzer run --target <target>。如果两者都不可用,在报告中记录run_code_analyzer=unavailable: <error>。
- 在所有生成/更新的
-
执行 Apex 测试
- 通过
sf apex run test或 MCP 运行组织测试,包括{ClassName}Test。 - 将所有测试生成/修复/覆盖工作委托给
platform-apex-test-generate;迭代直到测试通过。 - 捕获通过/失败计数和覆盖率百分比到报告中。
- 如果不可用,在报告中记录
test_execution=unavailable: <error>。
- 通过
阶段 3 — 报告
- 报告 — 使用本文件底部的输出格式。
Analyzer行必须包含步骤 6 的实际工具输出(或在尝试调用后记录run_code_analyzer=unavailable: <reason>)。Testing行必须包含步骤 7 的实际结果(或在尝试调用后记录test_execution=unavailable: <reason>)。- 缺少任何一行的报告都是不完整的。在记录不可用之前,始终尝试工具调用。
规则
硬性约束(必须强制执行)
如果生成的代码会违反任何约束,在继续之前停止并解释问题:
| 约束 | 理由 |
|---|---|
| 将所有 SOQL 放在循环外 | 避免查询调控器限制(100 个查询) |
| 将所有 DML 放在循环外 | 避免 DML 调控器限制(150 条语句) |
| 在每个类上声明共享关键字 | 防止意外的 without sharing 默认值和数据暴露 |
| 使用自定义元数据/标签/describe 调用,而不是硬编码的 ID | 确保跨组织的可移植性 |
| 始终处理异常(记录、重新抛出或恢复) | 防止静默失败 |
| 对所有包含用户输入的动态 SOQL 使用绑定变量 | 防止 SOQL 注入 |
使用 Apex 原生集合(List、Map、Set)而不是 Java 类型 |
防止编译错误 |
| 在使用前验证方法在 Apex 中存在 | 防止依赖不存在的 API |
避免在主代码路径中使用 System.debug() |
调试语句即使日志未激活也会求值并消耗 CPU。如果主代码路径需要,使用日志框架 |
绝不使用 @future 方法 |
使用带有 System.Finalizer 的 Queueable;@future 无法链式调用,无法从 Batch 调用,且无法接受非原始类型 |
批量处理与调控器限制
- 所有公共 API 接受并处理集合;单记录重载委托给批量方法
- 在批处理/批量流程中,优先使用部分成功 DML(
Database.update(records, false))并处理SaveResult中的错误 - 使用
Map<Id, SObject>构造函数从查询结果中高效进行基于 ID 的查找 - 使用
Map<Id, List<SObject>>按父记录分组子记录;在处理前在单个循环中构建映射 - 使用
Set<Id>进行去重和成员检查;优先使用Set.contains()而不是List.contains() - 当同时需要父记录和子记录时,使用关系子查询在单个 SOQL 中获取
- 使用带有
GROUP BY的AggregateResult进行汇总计算,而不是查询并在 Apex 中计数 - 仅对实际更改的记录执行 DML — 在添加到更新列表之前,与
Trigger.oldMap或先前状态进行比较 - 使用
Limits.getQueries()、Limits.getDmlStatements()、Limits.getCpuTime()监控复杂事务中的消耗
SOQL 优化
- 使用带有适当
WHERE子句的选择性查询;尽可能在过滤器中使用索引字段(Id、Name、OwnerId、查找/主从字段、ExternalId字段、自定义索引) - SOQL 中不存在
SELECT *— 始终指定所需的确切字段 - 应用
LIMIT子句限制结果集;使用ORDER BY确保确定性结果 - 查询自定义元数据类型(以
__mdt结尾的对象)时,不要使用 SOQL — 使用内置方法({CustomMdt__mdt}.getAll().values()、getInstance()等)
缓存
- 对频繁访问但很少更改的数据使用平台缓存(
Cache.Org/Cache.Session);设置 TTL 并始终处理缓存未命中 — 缓存随时可能被逐出 - 使用
private static Map字段作为事务作用域缓存,防止在同一执行上下文中重复查询;在首次访问时惰性初始化
安全性
- 默认使用
with sharing;记录使用without sharing或inherited sharing的理由 - 在 SOQL 中使用
WITH USER_MODE,在DatabaseDML 中使用AccessLevel.USER_MODE以强制执行 CRUD/FLS - 通过允许列表或
Schema.describe验证动态字段/操作符名称 - 所有外部凭据/API 密钥使用命名凭据
- 对于
@AuraEnabled面向用户的错误,使用AuraHandledException(不包含内部细节) without sharing需要自定义权限检查- 将
without sharing逻辑隔离在专用的辅助类中;从with sharing入口点调用以限制提升访问的范围 - 通过平台加密静态加密 PII/敏感数据;绝不在调试语句、错误消息或 API 响应中暴露 PII
安全性验证
在最终确定之前,验证:CRUD/FLS 已强制执行(SOQL + DML)· 每个类上都有显式的共享关键字 · 没有硬编码的秘密或记录 ID · PII 已从日志和错误消息中排除 · 错误消息已为最终用户清理。
错误处理
- 在捕获通用
Exception之前先捕获特定异常;在消息中包含上下文 - 仅在可能抛出异常的代码周围使用
try/catch(DML、调用、JSON 解析、类型转换);避免防御性地包装简单赋值/集合操作/算术运算 - 保留异常原因链:
new CustomException('message', cause)(不要用拼接的消息替换堆栈跟踪) - 在有意义的情况下,为每个服务域提供自定义异常类
- 在
@AuraEnabled方法中,捕获异常并重新抛出为AuraHandledException - 备用方案:当没有有意义的域异常时,捕获通用
Exception并重新抛出或将其包装在保留原始原因的最小自定义异常中
空安全
- 在每个公共方法的顶部添加对空/输入的保护子句;匹配上下文风格:在私有/触发器处理程序方法中提前
return,在公共 API 中throw异常,在验证服务中使用record.addError() - 返回空集合而不是
null - 对链式属性访问使用安全导航(
?.) - 除非保证存在,否则不要内联解引用
map.get(key);先使用containsKey、赋值+空检查或安全导航 - 对默认值使用空合并(
??) - 优先使用
String.isBlank(value)而不是手动检查如value == null || value.trim().isEmpty()
常量与字面量
- 尽可能使用枚举而不是字符串常量;枚举值遵循
UPPER_SNAKE_CASE - 将重复的字面字符串/数字提取为
private static final常量或常量类 - 对面向用户的字符串使用
Label.自定义标签 - 对可配置值(阈值、映射、功能标志)使用自定义元数据
- 绝不在代码中输出 HTML 转义实体(例如
');在 Apex 字符串字面量中使用单引号'
命名约定
| 类型 | 模式 | 示例 |
|---|---|---|
| 服务 | {SObject}Service |
AccountService |
| 选择器 | {SObject}Selector |
AccountSelector |
| 领域 | {SObject}Domain |
OpportunityDomain |
| 批处理 | {Descriptive}Batch |
AccountDeduplicationBatch |
| 可排队 | {Descriptive}Queueable |
ExternalSyncQueueable |
| 可调度 | {Descriptive}Schedulable |
DailyCleanupSchedulable |
| DTO | {Descriptive}DTO |
AccountMergeRequestDTO |
| 包装器 | {Descriptive}Wrapper |
OpportunityLineWrapper |
| 工具类 | {Descriptive}Util |
StringUtil |
| 接口 | I{Descriptive} |
INotificationService |
| 抽象类 | Abstract{Descriptive} |
AbstractIntegrationService |
| 异常 | {Descriptive}Exception |
AccountServiceException |
| REST 资源 | {SObject}RestResource |
AccountRestResource |
| 触发器 | {SObject}Trigger |
AccountTrigger |
| 触发器动作 | TA_{SObject}_{Action} |
TA_Account_SetDefaults |
其他命名规则:
- 类:
PascalCase - 方法:
camelCase,以动词开头(get、create、process、validate、is、has、can) - 变量:
camelCase,描述性名词;列表使用复数名词(例如accounts、relatedContacts);映射使用{value}By{key}(例如accountsById);集合使用{noun}Ids - 常量:
UPPER_SNAKE_CASE - 使用完整的描述性名称而不是缩写(
acc、tks、rec)
ApexDoc
- 在类头部和每个
public/global方法上必需 - 包括:简要描述、
@param、@return、@throws、@example(如有帮助)
类级别格式:
/**
* 提供地理定位和地址转换的服务。
*/
public with sharing class GeolocationService { }
方法级别格式:
/**
* @param paramName 参数描述
* @return 返回值描述
* @example
* List<Account> results = AccountService.deduplicateAccounts(accountIds);
*/
代码结构与架构
- 每个类单一职责;最多 500 行 — 超出时拆分
- 提前返回:在方法顶部验证前置条件,立即返回/抛出
- 对于超过约 40 行的方法,提取私有辅助方法
- 使用依赖注入(构造函数/方法参数)以提高可测试性
- 优先使用组合和窄接口而不是深层继承;通过新实现扩展,而不是修改
- 在层边界上强制每个方法保持单一抽象级别:
| 层 | 拥有 | 绝不能包含 |
|---|---|---|
| 触发器 | 仅事件路由 | 业务逻辑、编排 |
| 处理程序/服务 | 流程控制、协调 | 内联 SOQL/DML/HTTP/解析 |
| 领域 | 业务规则、验证 | 查询、调用、持久化细节 |
| 数据/集成 | SOQL、DML、HTTP | 业务决策 |
- 禁止:混合编排与内联 SOQL/DML/HTTP 的方法;业务规则与解析内部细节混合;在一个方法中混合验证+持久化+跨系统管道
异步决策矩阵
| 场景 | 默认 | 关键特征 |
|---|---|---|
| 标准异步工作 | Queueable | 作业 ID、链式调用、非原始类型、可配置延迟(通过 AsyncOptions 最多 10 分钟)、去重签名 |
| 非常大的数据集 | Batch Apex | 分块处理,最多 5 个并发;对大型范围使用 QueryLocator |
| 现代批处理替代方案 | CursorStep(Database.Cursor) |
2000 条记录块,更高吞吐量,无 5 作业限制 |
| 定期计划 | 计划流(首选)或 Schedulable | Schedulable 有 100 作业限制;仅在需要链式调用 Batch 或复杂 Apex 逻辑时使用 |
| 作业后清理 | Finalizer(System.Finalizer) |
无论 Queueable 成功/失败都运行 |
| 长时间运行的调用 | Continuation | 每个事务最多 3 个,3 个并行 |
| 延迟超过 10 分钟 | System.scheduleBatch() |
在特定未来时间计划 Batch 作业 |
| 遗留的即发即弃 | @future |
不要在新代码中使用 — 参见硬性约束;替换为 Queueable + Finalizer |
类型特定指南
服务
- 模板:
assets/service.cls· 参考:references/AccountService.cls with sharing;无状态 — 没有public字段或可变实例状态;保持公共 API 集中,并在合理时使用static- 将所有 SOQL 委托给选择器,将 SObject 行为委托给领域
- 将业务错误包装在自定义异常中(例如
AccountServiceException)
选择器
- 模板:
assets/selector.cls· 参考:references/AccountSelector.cls inherited sharing;每个 SObject 或查询域一个- 返回
List<SObject>或Map<Id, SObject>;使用共享的基础字段列表常量(无内联重复) - 接受过滤器参数;始终包含
WITH USER_MODE
领域
- 模板:
assets/domain.cls with sharing;封装字段默认值、派生和验证- 仅操作内存中的列表;无 SOQL/DML(属于服务/选择器)
批处理
- 模板:
assets/batch.cls· 参考:references/AccountDeduplicationBatch.cls with sharing;实现Database.Batchable<SObject>(在跨块跟踪时添加Database.Stateful)start()= 查询定义;execute()= 业务逻辑;finish()= 日志/通知- 对大型数据集使用
QueryLocator;通过Database.SaveResult处理部分失败 - 通过构造函数接受过滤器参数以实现可重用性
可排队
- 模板:
assets/queueable.cls with sharing;实现Queueable,并在需要 HTTP 调用时可选实现Database.AllowsCallouts- 通过构造函数接受数据
- 添加链深度保护以防止无限链
- 可选实现
Finalizer用于恢复/清理 - 使用
AsyncOptions实现可配置延迟(最多 10 分钟)和去重签名
可调度
- 模板:
assets/schedulable.cls with sharing;execute()委托给 Queueable 或 Batch- 提供 CRON 常量和便捷的
scheduleDaily()辅助方法
DTO / 包装器
- 模板:
assets/dto.cls - 不需要共享关键字(纯数据容器)
- 简单的公共属性;无参 + 参数化构造函数;当排序重要时实现
Comparable - 对序列化/反序列化的私有/受保护内部 DTO 使用
@JsonAccess
工具类
- 模板:
assets/utility.cls - 不需要共享关键字;所有方法
public static;private构造函数 - 纯函数,无副作用;无 SOQL/DML
接口
- 模板:
assets/interface.cls - 在每个方法签名上使用 ApexDoc 定义清晰的契约
抽象类
- 模板:
assets/abstract.cls with sharing;通过virtual方法提供默认行为- 将扩展点标记为
protected virtual或protected abstract - 在 ApexDoc 中包含一个具体示例,展示如何扩展该类
自定义异常
- 模板:
assets/exception.cls - 不需要共享关键字;使用描述性名称扩展
Exception - 支持的构造函数:
()、('msg')、(cause)、('msg', cause)
触发器
- 模板:
assets/trigger.cls - 每个对象一个触发器;将所有逻辑委托给处理程序/TAF 动作类
- 包含所有相关的 DML 上下文;如果使用 TAF:
new MetadataTriggerHandler().run();
触发器动作(TAF)
- 每个关注点每个上下文一个类;实现
TriggerAction.{Context} - 通过
Trigger_Action__mdt注册(未注册的动作处于非活动状态) - 名称:
TA_{SObject}_{ActionName};优先使用字段值比较而不是静态布尔值来防止递归
可调用方法(@InvocableMethod)
- 模板:
assets/invocable.cls with sharing;内部Request/Response带有@InvocableVariable- 方法必须是
public static;非静态或单对象签名将无法编译 - 接受
List<Request>,返回List<Response>;批量处理(SOQL/DML 放在循环外) - 装饰器参数:
label(必需 — Flow Builder 显示名称)、description、category(在 Builder 中对动作分组)、callout=true(当方法进行 HTTP 调用时必需) @InvocableVariable参数:label(必需)、description、required=true/false@InvocableVariable支持:原始类型、Id、SObject、仅List<T>(不支持Map/Set/Blob);对 Flow 集合 I/O 使用List<Id>或List<SObject>字段- 始终在 Response 中包含
isSuccess、errorMessage和errorType(e.getTypeName()) - 建议在 Response 中返回错误;抛出异常会触发 Flow 故障路径 — 仅保留给不可恢复的失败
REST 资源(@RestResource)
- 模板:
assets/rest-resource.cls global with sharing;类和方法都必须为global- 版本化 URL:
@RestResource(urlMapping='/{resource}/v1/*') - 根据分支使用适当的 HTTP 状态码(
200/201/400/404/422/500);绝不要将所有错误默认设为500 - 验证输入(Id 格式:
Pattern.matches('[a-zA-Z0-9]{15,18}', value));在 SOQL 中绑定所有用户输入 - 在查询中包含
LIMIT/ORDER BY;实现分页(pageSize/offset) - 标准化的
ApiResponse包装器(success、message、data/records);内部请求/响应 DTO - 薄控制器:将业务逻辑委托给服务类
@AuraEnabled 控制器
with sharing;在所有 SOQL 中使用WITH USER_MODE- 仅对只读查询使用
@AuraEnabled(cacheable=true);对 DML 操作不设置cacheable - 捕获异常并重新抛出为
AuraHandledException,并附上用户友好的消息
输出期望
每个类的交付物:
{ClassName}.cls{ClassName}.cls-meta.xml(默认 API 版本66.0或更高,除非指定){ClassName}Test.cls(通过platform-apex-test-generate技能生成){ClassName}Test.cls-meta.xml(通过platform-apex-test-generate技能生成)
每个触发器的交付物:
{TriggerName}.trigger{TriggerName}.trigger-meta.xml(默认 API 版本66.0或更高,除非指定)
元 XML 模板:
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>{API_VERSION}</apiVersion>
<status>Active</status>
</ApexClass>
按此顺序报告:
Apex work: <摘要>
Files: <路径>
Design: <模式/框架选择>
Workflow: 所有步骤已完成(1-8);任何 N/A 已说明理由
Risks: <安全性、批量处理、异步、依赖说明>
Analyzer: <必需 — 粘贴实际的 run_code_analyzer 输出或说明 "run_code_analyzer=unavailable: <reason>">
Testing: <必需 — 粘贴实际的测试执行结果(通过/失败、覆盖率)或说明 "test_execution=unavailable: <reason>">
Deploy: <试运行或下一步>
跨技能集成
| 需求 | 委托给 |
|---|---|
| Apex 测试 / 修复失败 | platform-apex-test-generate 技能 |
| 描述对象/字段 | 元数据技能(如果可用) |
| 部署到组织 | 部署技能(如果可用) |
| Flow 调用 Apex | Flow 技能(如果可用) |
| LWC 调用 Apex | LWC 技能(如果可用) |
故障排除边界
此技能仅处理生产级 .cls/.trigger/.apex 问题:编译/解析失败、部署依赖错误、运行时调控器限制失败。对于测试执行、断言、覆盖率或 sf apex run test 失败,委托给 platform-apex-test-generate。






