SKILL.md
唯讀
名稱
salesforce-developer
描述
撰寫與除錯 Apex 程式碼、建立 Lightning Web Components、最佳化 SOQL 查詢、實作觸發器、批次作業、平台事件及 Salesforce 平台整合。適用於開發 Salesforce 應用程式、自訂 CRM 工作流程、管理 Governor Limits、大量資料處理,或設定 Salesforce DX 與 CI/CD 管線。
Salesforce Developer
核心工作流程
- 分析需求 - 了解業務需求、資料模型、Governor Limits、可擴展性
- 設計解決方案 - 選擇宣告式或程式化方式、規劃大量資料處理、設計整合
- 實作 - 撰寫 Apex 類別、LWC 元件、SOQL 查詢,並遵循最佳實務
- 驗證 Governor Limits - 確認 SOQL/DML 次數、Heap 大小及 CPU 時間在平台限制內,再繼續進行
- 徹底測試 - 撰寫測試類別,達到 90% 以上程式碼覆蓋率,測試大量資料情境(200 筆記錄批次)
- 部署 - 使用 Salesforce DX、Scratch Org、CI/CD 進行中繼資料部署
參考指南
根據情境載入詳細指引:
| 主題 | 參考文件 | 載入時機 |
|---|---|---|
| Apex 開發 | references/apex-development.md |
類別、觸發器、非同步模式、批次處理 |
| Lightning Web Components | references/lightning-web-components.md |
LWC 框架、元件設計、事件、Wire Service |
| SOQL/SOSL | references/soql-sosl.md |
查詢最佳化、關聯、Governor Limits |
| 整合模式 | references/integration-patterns.md |
REST/SOAP API、平台事件、外部服務 |
| 部署與 DevOps | references/deployment-devops.md |
Salesforce DX、CI/CD、Scratch Org、Metadata API |
限制
必須執行
- 將 Apex 程式碼大量化 — 在迴圈前收集 ID/記錄,在迴圈外執行查詢/DML
- 撰寫測試類別,程式碼覆蓋率至少 90%,並包含大量資料情境
- 使用具選擇性的 SOQL 查詢,搭配已建立索引的欄位;善用關聯查詢
- 對長時間執行的工作使用適當的非同步處理(Batch、Queueable、Future)
- 實作適當的錯誤處理與記錄;使用
Database.update(scope, false)處理部分成功 - 使用 Salesforce DX 進行原始碼驅動開發與中繼資料部署
禁止事項
- 在迴圈內執行 SOQL/DML(違反 Governor Limits — 請參考下方大量化觸發器模式)
- 在程式碼中硬編碼 ID 或憑證
- 建立沒有保護機制的遞迴觸發器
- 跳過欄位層級安全性與共用規則檢查
- 使用已棄用的 Salesforce API 或元件
程式碼模式
大量化觸發器(正確模式)
// 正確:收集 ID,在迴圈外查詢一次
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsert(Trigger.new);
}
public class AccountTriggerHandler {
public static void handleBeforeInsert(List<Account> newAccounts) {
Set<Id> parentIds = new Set<Id>();
for (Account acc : newAccounts) {
if (acc.ParentId != null) parentIds.add(acc.ParentId);
}
Map<Id, Account> parentMap = new Map<Id, Account>(
[SELECT Id, Name FROM Account WHERE Id IN :parentIds]
);
for (Account acc : newAccounts) {
if (acc.ParentId != null && parentMap.containsKey(acc.ParentId)) {
acc.Description = 'Child of: ' + parentMap.get(acc.ParentId).Name;
}
}
}
}
// 錯誤:在迴圈內執行 SOQL — 違反 Governor Limits
trigger AccountTrigger on Account (before insert) {
for (Account acc : Trigger.new) {
Account parent = [SELECT Id, Name FROM Account WHERE Id = :acc.ParentId]; // 錯誤
acc.Description = 'Child of: ' + parent.Name;
}
}
Batch Apex
public class ContactBatchUpdate implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id, Email FROM Contact WHERE Email = null]);
}
public void execute(Database.BatchableContext bc, List<Contact> scope) {
for (Contact c : scope) {
c.Email = 'unknown@example.com';
}
Database.update(scope, false); // 允許部分成功
}
public void finish(Database.BatchableContext bc) {
// 發送通知或串接下一個批次
}
}
// 執行:Database.executeBatch(new ContactBatchUpdate(), 200);
測試類別
@IsTest
private class AccountTriggerHandlerTest {
@TestSetup
static void makeData() {
Account parent = new Account(Name = 'Parent Co');
insert parent;
Account child = new Account(Name = 'Child Co', ParentId = parent.Id);
insert child;
}
@IsTest
static void testBulkInsert() {
Account parent = [SELECT Id FROM Account WHERE Name = 'Parent Co' LIMIT 1];
List<Account> children = new List<Account>();
for (Integer i = 0; i < 200; i++) {
children.add(new Account(Name = 'Child ' + i, ParentId = parent.Id));
}
Test.startTest();
insert children;
Test.stopTest();
List<Account> updated = [SELECT Description FROM Account WHERE ParentId = :parent.Id];
System.assert(!updated.isEmpty(), '子記錄應設定描述');
System.assert(updated[0].Description.startsWith('Child of:'), '描述格式不符');
}
}
SOQL 最佳實務
// 選擇性查詢 — 在 WHERE 子句中使用已建立索引的欄位
List<Opportunity> opps = [
SELECT Id, Name, Amount, StageName
FROM Opportunity
WHERE AccountId IN :accountIds // 已建立索引的欄位
AND CloseDate >= :Date.today() // 已建立索引的欄位
ORDER BY CloseDate ASC
LIMIT 200
];
// 關聯查詢,避免額外往返
List<Account> accounts = [
SELECT Id, Name,
(SELECT Id, LastName, Email FROM Contacts WHERE Email != null)
FROM Account
WHERE Id IN :accountIds
];
Lightning Web Component(計數器範例)
<!-- counterComponent.html -->
<template>
<lightning-card title="計數器">
<div class="slds-p-around_medium">
<p>計數:{count}</p>
<lightning-button label="增加" onclick={handleIncrement}></lightning-button>
</div>
</lightning-card>
</template>
// counterComponent.js
import { LightningElement, track } from 'lwc';
export default class CounterComponent extends LightningElement {
@track count = 0;
handleIncrement() {
this.count += 1;
}
}
<!-- counterComponent.js-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>




