关于 platform-apex-generate
platform-apex-generate 按项目规范及安全、批量处理约束指导 Salesforce Apex 类和触发器的编写与审查,面向 Apex 开发者,并要求报告前运行分析器和测试。
主要功能
- 01生成 Apex 类和触发器
- 02发现项目编码规范
- 03遵循安全与治理限制
- 04运行代码分析器验证
- 05执行 Apex 测试并报告
适用场景
- 01构建新的 Apex 服务或触发器
- 02重构或审查 Apex 代码
- 03报告前验证生成的代码
MCP 相关
该技能要求对生成或更新的 `.cls` 文件调用 MCP `run_code_analyzer`,并允许通过 MCP 执行 Apex 测试。最终报告须记录工具输出或不可用状态。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill platform-apex-generate安装后在 Claude Code 中输入/platform-apex-generate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/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/中存在该类型的示例,请阅读并将其用作具体的风格指南 - 对于任何测试类工作,始终阅读并使用
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 方法 | 使用 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()等) - API 版本为 67.0 及以上的类中,如果使用
without sharing关键字,且运行用户没有相应的字段级或对象级安全权限,查询将会抛出异常。如果要升级 API 版本,请确保查询受到适当保护,并相应更新测试。默认情况下,只允许在有明确理由的查询中使用SYSTEM_MODE变体。
缓存
- 对于频繁访问且很少更改的数据,使用 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——对于 API 版本为 67.0 或更高的所有 Apex 类,这些都是默认设置 - 通过允许列表或
Schema.describe验证动态字段名和运算符名 - 所有外部凭据/API 密钥均使用 Named Credentials
- 对面向用户的
@AuraEnabled错误使用AuraHandledException(不包含内部详细信息) - 使用
without sharing时,必须检查 Custom Permission - 将
without sharing逻辑隔离在专用辅助类中;从with sharing入口调用,以限制提升访问权限的范围 - 通过 Platform Encryption 加密静态存储的 PII/敏感数据;绝不在调试语句、错误消息或 API 响应中暴露 PII
安全验证
完成前,请验证:已强制执行 CRUD/FLS(SOQL + DML)· 每个类都显式指定了 sharing 关键字 · 没有硬编码的机密或记录 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 min)、去重签名 |
| 超大型数据集 | 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 |
特定类型指南
服务
- 模板:
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 callout 时可选实现Database.AllowsCallouts - 通过构造函数接收数据
- 添加链深度保护,防止无限链式调用
- 可选实现
Finalizer,用于恢复/清理 - 使用
AsyncOptions配置延迟时间(最长 10 min)和去重签名
Schedulable
- 模板:
assets/schedulable.cls - 使用
with sharing;execute()将任务委托给 Queueable 或 Batch - 提供 CRON 常量和便捷的
scheduleDaily()辅助方法
DTO / Wrapper
- 模板:
assets/dto.cls - 无需 sharing 关键字(纯数据容器)
- 使用简单的公共属性;提供无参和带参构造函数;需要排序时实现
Comparable - 对参与序列化/反序列化的私有/受保护内部 DTO 使用
@JsonAccess
Utility
- 模板:
assets/utility.cls - 无需 sharing 关键字;所有方法均为
public static;使用private构造函数 - 保持纯函数特性,无副作用;不执行 SOQL/DML
Interface
- 模板:
assets/interface.cls - 定义清晰的契约,并为每个方法签名编写 ApexDoc
Abstract
- 模板:
assets/abstract.cls - 使用
with sharing;通过virtual方法提供默认行为 - 将扩展点标记为
protected virtual或protected abstract - 在 ApexDoc 中提供一个展示如何扩展此类的具体示例
Custom Exception
- 模板:
assets/exception.cls - 无需 sharing 关键字;使用描述性名称扩展
Exception - 支持的构造函数:
(),('msg'),(cause),('msg', cause)
Trigger
- 模板:
assets/trigger.cls - 每个对象使用一个触发器;将所有逻辑委托给 handler/TAF action 类
- 包含所有相关 DML 上下文;使用 TAF 时:
new MetadataTriggerHandler().run();
Trigger Action (TAF)
- 每种关注点和上下文各使用一个类;实现
TriggerAction.{Context} - 通过
Trigger_Action__mdt注册(未注册的 action 均处于非激活状态) - 命名格式:
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 集合输入/输出请使用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(通过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: <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 测试/修复失败 | platform-apex-test-generate 技能 |
| 描述对象/字段 | 元数据技能(如果可用) |
| 部署到组织 | 部署技能(如果可用) |
| Flow 调用 Apex | Flow 技能(如果可用) |
| LWC 调用 Apex | LWC 技能(如果可用) |
故障排查范围
此技能仅处理生产环境中的 .cls/.trigger/.apex 问题:编译/解析失败、部署依赖错误、运行时 governor limit 故障。对于测试执行、断言、覆盖率或 sf apex run test 失败,请委托给 platform-apex-test-generate。