主要的 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 — 撰寫
-
探索專案慣例
- Service-Selector-Domain 分層、日誌工具
- 現有類別/觸發程式與目前的觸發程式框架或處理器模式
- 是否已使用 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 無法鏈結、無法從批次呼叫,也無法接受非基本類型 |
批量化與控管限制
- 所有公開 API 接受並處理集合;單一記錄多載委派給批量方法
- 在批次/大量流程中,偏好部分成功 DML(
Database.update(records, false))並處理SaveResult以取得錯誤 - 使用
Map<Id, SObject>建構子從查詢結果進行高效的 ID 查詢 - 使用
Map<Id, List<SObject>>按父項分組子記錄;在處理前於單一迴圈中建立映射 - 使用
Set<Id>進行重複資料刪除與成員資格檢查;偏好Set.contains()而非List.contains() - 使用關聯子查詢在單一 SOQL 中同時擷取父項與子記錄(當兩者都需要時)
- 使用
AggregateResult搭配GROUP BY進行彙總計算,而非在 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並重新拋出,或將其包裝在保留原始原因的最小自訂例外中。
空值安全
- 在每個公開方法的頂部為 null/空輸入添加防護子句;根據上下文匹配風格:在私有/觸發處理器方法中
return提前返回,在公開 API 中throw例外,在驗證服務中使用record.addError() - 返回空集合而非
null - 使用安全導覽運算子(
?.)進行鏈結屬性存取 - 除非保證存在,否則不要內聯解參考
map.get(key);先使用containsKey、指派+空值檢查或安全導覽 - 使用 null 合併運算子(
??)處理預設值 - 偏好
String.isBlank(value)而非手動檢查如value == null || value.trim().isEmpty()
常數與字面值
- 盡可能使用列舉而非字串常數;列舉值遵循
UPPER_SNAKE_CASE - 將重複的字串/數字提取為
private static final常數或常數類別 - 使用者面向字串使用
Label.自訂標籤 - 可設定的值(閾值、對應、功能旗標)使用自訂中繼資料
- 絕不在程式碼中輸出 HTML 跳脫實體(例如
');在 Apex 字串字面值中使用單引號'
命名慣例
| 類型 | 模式 | 範例 |
|---|---|---|
| Service | {SObject}Service |
AccountService |
| Selector | {SObject}Selector |
AccountSelector |
| Domain | {SObject}Domain |
OpportunityDomain |
| Batch | {Descriptive}Batch |
AccountDeduplicationBatch |
| Queueable | {Descriptive}Queueable |
ExternalSyncQueueable |
| Schedulable | {Descriptive}Schedulable |
DailyCleanupSchedulable |
| DTO | {Descriptive}DTO |
AccountMergeRequestDTO |
| Wrapper | {Descriptive}Wrapper |
OpportunityLineWrapper |
| Utility | {Descriptive}Util |
StringUtil |
| Interface | I{Descriptive} |
INotificationService |
| Abstract | Abstract{Descriptive} |
AbstractIntegrationService |
| Exception | {Descriptive}Exception |
AccountServiceException |
| REST Resource | {SObject}RestResource |
AccountRestResource |
| Trigger | {SObject}Trigger |
AccountTrigger |
| Trigger Action | 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 行的方法提取私有輔助方法
- 使用依賴注入(建構子/方法參數)以利測試
- 偏好組合與狹窄介面而非深度繼承;透過新實作而非修改來擴展
- 在跨層邊界的方法中強制執行單一層級抽象:
| 層級 | 擁有 | 不得包含 |
|---|---|---|
| Trigger | 僅事件路由 | 業務邏輯、協調 |
| Handler/Service | 流程控制、協調 | 內聯 SOQL/DML/HTTP/解析 |
| Domain | 業務規則、驗證 | 查詢、呼叫、持久化細節 |
| Data/Integration | SOQL、DML、HTTP | 業務決策 |
- 不允許:混合協調與內聯 SOQL/DML/HTTP 的方法;業務規則與解析內部細節混合;在一個方法中混合驗證 + 持久化 + 跨系統管道
非同步決策矩陣
| 情境 | 預設 | 關鍵特性 |
|---|---|---|
| 標準非同步工作 | Queueable | 工作 ID、鏈結、非基本類型、可設定的延遲(透過 AsyncOptions 最多 10 分鐘)、重複簽章 |
| 非常大的資料集 | Batch Apex | 區塊處理,最多 5 個並行;對大型範圍使用 QueryLocator |
| 現代批次替代方案 | CursorStep(Database.Cursor) |
2000 筆記錄區塊,更高吞吐量,無 5 工作限制 |
| 週期性排程 | Scheduled Flow(偏好)或 Schedulable | Schedulable 有 100 工作限制;僅在需要鏈結到批次或需要複雜 Apex 邏輯時使用 |
| 工作後清理 | Finalizer(System.Finalizer) |
無論 Queueable 成功/失敗都會執行 |
| 長時間執行的呼叫 | Continuation | 每個交易最多 3 個,3 個並行 |
| 延遲超過 10 分鐘 | System.scheduleBatch() |
在特定未來時間排程批次工作 |
| 舊版 fire-and-forget | @future |
請勿在新程式碼中使用 — 請參閱硬性停止限制;以 Queueable + Finalizer 取代 |
各類型指引
Service
- 範本:
assets/service.cls· 參考:references/AccountService.cls with sharing;無狀態 — 無public欄位或可變的實例狀態;保持公開 API 專注,並在合理情況下設為static- 將所有 SOQL 委派給 Selector,將 SObject 行為委派給 Domain
- 將業務錯誤包裝在自訂例外中(例如
AccountServiceException)
Selector
- 範本:
assets/selector.cls· 參考:references/AccountSelector.cls inherited sharing;每個 SObject 或查詢領域一個- 返回
List<SObject>或Map<Id, SObject>;使用共用的基礎欄位清單常數(無內聯重複) - 接受篩選參數;務必包含
WITH USER_MODE
Domain
- 範本:
assets/domain.cls with sharing;封裝欄位預設值、衍生與驗證- 僅對記憶體中的清單進行操作;無 SOQL/DML(屬於 Service/Selector)
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 分鐘)與重複簽章
Schedulable
- 範本:
assets/schedulable.cls with sharing;execute()委派給 Queueable 或 Batch- 提供 CRON 常數與便利的
scheduleDaily()輔助方法
DTO / Wrapper
- 範本:
assets/dto.cls - 不需要共用關鍵字(純資料容器)
- 簡單的公開屬性;無參數 + 參數化建構子;當排序重要時實作
Comparable - 對序列化/反序列化的私有/受保護內部 DTO 使用
@JsonAccess
Utility
- 範本:
assets/utility.cls - 不需要共用關鍵字;所有方法為
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 - 不需要共用關鍵字;使用描述性名稱擴充
Exception - 支援的建構子:
()、('msg')、(cause)、('msg', cause)
Trigger
- 範本:
assets/trigger.cls - 每個物件一個觸發程式;將所有邏輯委派給處理器/TAF 動作類別
- 包含所有相關的 DML 上下文;如果使用 TAF:
new MetadataTriggerHandler().run();
Trigger Action (TAF)
- 每個關注點每個上下文一個類別;實作
TriggerAction.{Context} - 透過
Trigger_Action__mdt註冊(未註冊的動作處於非啟用狀態) - 名稱:
TA_{SObject}_{ActionName};偏好欄位值比較而非靜態布林值來處理遞迴
Invocable Method (@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);使用List<Id>或List<SObject>欄位進行 Flow 集合 I/O- 務必在 Response 中包含
isSuccess、errorMessage與errorType(e.getTypeName()) - 在 Response 中返回錯誤(建議);拋出例外會觸發 Flow Fault 路徑 — 僅保留給不可復原的失敗
REST Resource (@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 Controller
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或更高,除非指定)
Meta 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 技能 |
| 描述物件/欄位 | metadata 技能(如果可用) |
| 部署到組織 | deploy 技能(如果可用) |
| Flow 呼叫 Apex | Flow 技能(如果可用) |
| LWC 呼叫 Apex | LWC 技能(如果可用) |
疑難排解邊界
此技能僅處理生產 .cls/.trigger/.apex 問題:編譯/解析失敗、部署相依性錯誤、執行時期控管限制失敗。對於測試執行、斷言、覆蓋率或 sf apex run test 失敗,請委派給 platform-apex-test-generate。






