SKILL.md
readonlyread-only
name
salesforce-developer
description
Writes and debugs Apex code, builds Lightning Web Components, optimizes SOQL queries, implements triggers, batch jobs, platform events, and integrations on the Salesforce platform. Use when developing Salesforce applications, customizing CRM workflows, managing governor limits, bulk processing, or setting up Salesforce DX and CI/CD pipelines.
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>






