关于 generating-apex
用于生成和审查面向生产环境的 Salesforce Apex 的技能,涵盖类、触发器和异步任务等。它规定编写约定,并要求在汇报前执行代码分析和 Apex 测试。
主要功能
- 01Apex 类和触发器编写指南
- 02发现项目既有约定
- 03审阅模板和参考示例
- 04安全与治理限制规范
- 05代码分析验证
- 06执行 Apex 测试
适用场景
- 01创建 Apex 服务、选择器或异步任务
- 02审查现有 Apex 类或触发器
- 03为部署准备 Apex 变更
MCP 相关
该技能要求调用 `run_code_analyzer` MCP 工具分析生成或更新的 Apex 文件;也允许通过 MCP 执行 Apex 测试。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill generating-apex安装后在 Claude Code 中输入/generating-apex确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CREDITS.mdassets/abstract.clsbatch.clsdomain.clsdto.clsexception.clsinterface.clsinvocable.clsqueueable.clsrest-resource.clsschedulable.clsselector.clsservice.clstrigger.clsutility.clsreferences/AccountDeduplicationBatch.clsAccountSelector.clsAccountService.cls详细介绍
生成 Apex
将此技能用于生产级 Apex:新建类、选择器、服务、异步作业、可调用方法和触发器;以及基于证据审查现有 .cls 或 .trigger。
必需输入
编写前先收集或推断以下信息:
- 类类型(服务、选择器、领域、批处理、队列、可调度、可调用、触发器、触发器操作、DTO、工具类、接口、抽象类、异常、REST 资源)
- 目标对象和业务目标
- 类名(使用下方的命名表推导)
- 全新开发还是重构/修复;组织或 API 限制
- 部署目标(默认使用 runSpecifiedTests,并在适用时使用生成的测试)
除非另有说明,默认值如下:
- 共享模式:
with sharing(参见下方各类型的共享规则) - 访问级别:
public(仅在托管包或@RestResource要求时使用global) - API 版本:
66.0(最低版本) - ApexDoc 注释:是
如果用户提供了清晰、完整的请求,立即生成,无需不必要的来回沟通。
工作流
所有步骤必须依次执行。不得跳过、合并或调整顺序。如果遇到阻碍,请停止并询问缺失的上下文。如果不适用,请标记为 N/A,并在报告中用一句话说明原因。
阶段 1 — 编写
-
了解项目约定
- 服务-选择器-领域分层、日志工具
- 现有类/触发器以及当前的触发器框架或处理程序模式
- 是否已在使用 Trigger Actions Framework(TAF)
-
选择最精简且正确的模式(参见下方的类型专属指南)
-
查看模板和资源
- 编写前读取
assets/中匹配的模板(文件映射参见类型专属指南) - 如果该类型存在
references/示例,请将其作为具体的风格指南阅读 - 对于任何测试类相关工作,始终读取并使用
generating-apex-test技能
- 编写前读取
-
遵循防护规则编写 —— 应用下方规则部分中的每一项规则
- 生成带有 ApexDoc 的
{ClassName}.cls - 生成
{ClassName}.cls-meta.xml
- 生成带有 ApexDoc 的
-
生成测试类 —— 加载
generating-apex-test技能,以创建{ClassName}Test.cls和{ClassName}Test.cls-meta.xml。部署时始终必须生成 Apex 测试。未加载generating-apex-test技能来生成测试之前,不得创建或编辑任何测试文件。
阶段 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。 - 将所有测试生成/修复/覆盖率工作委托给
generating-apex-test;反复执行,直到测试通过。 - 为报告捕获通过/失败数量和覆盖率百分比。
- 如果不可用,请在报告中记录
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 方法 | 使用 Queueable 和 System.Finalizer;@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()等)
缓存
- 对频繁访问且很少变更的数据使用 Platform Cache(
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 密钥均使用 Named Credentials
- 使用
AuraHandledException返回面向用户的@AuraEnabled错误(不包含内部细节) - 使用
without sharing时必须检查自定义权限 - 将
without sharing逻辑隔离在专用辅助类中;从with sharing入口调用,以限制提升权限的作用范围 - 使用 Platform Encryption 加密静态存储的 PII/敏感数据;绝不在调试语句、错误消息或 API 响应中暴露 PII
安全验证
完成前,请验证:已强制执行 CRUD/FLS(SOQL + DML)· 每个类都显式声明了共享关键字 · 未硬编码机密或记录 ID · 日志和错误消息中不包含 PII · 已清理面向最终用户的错误消息。
错误处理
- 在通用
Exception前捕获特定异常;在消息中包含上下文 - 仅在可能引发异常的代码(DML、调用、JSON 解析、类型转换)周围使用
try/catch;避免对简单赋值、集合操作和算术运算进行防御性包装 - 保留异常原因链:
new CustomException('message', cause)(不要将堆栈跟踪替换为拼接后的消息) - 在有意义时,为每个服务领域提供一个自定义异常类
- 在
@AuraEnabled方法中,捕获异常并将其重新抛出为AuraHandledException - 备用方案:如果没有有意义的领域异常,则捕获通用
Exception,并选择重新抛出该异常,或将其包装在能保留原始原因的最简自定义异常中。
空值安全
- 在每个公共方法开头添加空值/空输入保护子句;根据上下文匹配风格:在私有方法/触发器处理程序中提前
return,在公共 API 中抛出异常,在验证服务中使用record.addError() - 返回空集合,而不是
null - 对链式属性访问使用安全导航(
?.) - 除非能确保存在,否则绝不要内联解引用
map.get(key);应先使用containsKey、赋值后检查空值,或使用安全导航 - 使用空值合并运算符(
??)提供默认值 - 优先使用
String.isBlank(value),而不是value == null || value.trim().isEmpty()之类的手动检查
常量与字面量
- 尽可能使用枚举而非字符串常量;枚举值遵循
UPPER_SNAKE_CASE - 将重复出现的字符串/数字字面量提取为
private static final常量或常量类 - 用户可见字符串使用
Label.自定义标签 - 可配置值(阈值、映射、功能标志)使用 Custom Metadata
- 代码中绝不要输出 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
类级格式:
/**
* Provides services for geolocation and address conversion.
*/
public with sharing class GeolocationService { }
方法级格式:
/**
* @param paramName Description of the parameter
* @return Description of the return value
* @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 个作业的限制 |
| 定期计划 | Scheduled Flow(首选)或 Schedulable | Schedulable 的作业数上限为 100;仅在需要链式调用 Batch 或复杂 Apex 逻辑时使用 |
| 作业完成后的清理 | Finalizer(System.Finalizer) | 无论 Queueable 成功还是失败都会运行 |
| 长时间运行的外部调用 | Continuation | 每个事务最多 3 个,并行执行 3 个 |
| 延迟超过 10 分钟 | System.scheduleBatch() | 在指定的未来时间安排 Batch 作业 |
| 旧式即发即弃模式 | @future | 不要在新代码中使用——参见硬性限制;改用 Queueable + Finalizer |
按类型分类的指导
Service
- 模板:
assets/service.cls· 参考:references/AccountService.cls - 使用
with sharing;保持无状态——不使用public字段或可变实例状态;保持公共 API 专注,并在合理情况下使用static - 将所有 SOQL 委托给 Selectors,将 SObject 行为委托给 Domains
- 将业务错误包装在自定义异常中(例如
AccountServiceException)
Selector
- 模板:
assets/selector.cls· 参考:references/AccountSelector.cls - 使用
inherited sharing;每个 SObject 或查询领域对应一个 Selector - 返回
List<SObject>或Map<Id, SObject>;使用共享的基础字段列表常量(不要内联重复定义) - 接受筛选参数;始终包含
WITH USER_MODE
Domain
- 模板:
assets/domain.cls - 使用
with sharing;封装字段默认值、派生逻辑和验证 - 仅操作内存中的列表;不执行 SOQL/DML(应由 Services/Selectors 负责)
Batch
- 模板:
assets/batch.cls· 参考:references/AccountDeduplicationBatch.cls - 使用
with sharing;实现Database.Batchable<SObject>(跨块跟踪状态时添加Database.Stateful) start()= 查询定义;execute()= 业务逻辑;finish()= 日志记录/通知- 大型数据集使用
QueryLocator;通过Database.SaveResult处理部分失败 - 通过构造函数接受筛选参数,以便重复使用
Queueable
- 模板:
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 - 无需共享关键字(纯数据容器)
- 使用简单的 public 属性;提供无参和带参构造函数;需要排序时实现
Comparable - 对要序列化/反序列化的 private/protected 内部 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;内部定义包含@InvocableVariable的Request/Response - 方法必须为
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 集合输入/输出请使用List<Id>或List<SObject>字段- 始终在 Response 中包含
isSuccess、errorMessage和errorType(e.getTypeName()) - 建议在 Response 中返回错误;抛出异常会触发 Flow Fault 路径,仅在无法恢复的故障时使用
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 - 控制器保持精简:将业务逻辑委托给 Service 类
@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(使用generating-apex-test技能生成){ClassName}Test.cls-meta.xml(使用generating-apex-test技能生成)
每个触发器的交付物:
{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: <summary>
Files: <paths>
Design: <pattern / framework choices>
Workflow: all steps completed (1-8); any N/A justified
Risks: <security, bulkification, async, dependency notes>
Analyzer: <REQUIRED -- paste actual run_code_analyzer output or state "run_code_analyzer=unavailable: <reason>">
Testing: <REQUIRED -- paste actual test execution results (pass/fail, coverage) or state "test_execution=unavailable: <reason>">
Deploy: <dry-run or next step>
跨技能集成
| 需求 | 委派给 |
|---|---|
| Apex 测试/修复失败 | generating-apex-test 技能 |
| 描述对象/字段 | metadata 技能(如果可用) |
| 部署到 org | deploy 技能(如果可用) |
| Flow 调用 Apex | Flow 技能(如果可用) |
| LWC 调用 Apex | LWC 技能(如果可用) |
故障排查范围
此技能仅处理生产环境中的 .cls/.trigger/.apex 问题:编译/解析失败、部署依赖错误、运行时 governor limit 错误。对于测试执行、断言、覆盖率或 sf apex run test 失败,请委派给 generating-apex-test。