salesforce-apex-quality

작성자: github

Salesforce 개발을 위한 Apex 코드 품질 가드레일. 벌크 안전 규칙(루프 내 SOQL/DML 금지), 공유 모델 요구사항, CRUD/FLS 보안, SOQL…을 적용합니다.

npx skills add https://github.com/github/awesome-copilot --skill salesforce-apex-quality

Salesforce Apex Quality Guardrails

Apply these checks to every Apex class, trigger, and test file you write or review.

Step 1 — Governor Limit Safety Check

Scan for these patterns before declaring any Apex file acceptable:

SOQL and DML in Loops — Automatic Fail

// ❌ NEVER — causes LimitException at scale
for (Account a : accounts) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :a.Id]; // SOQL in loop
    update a; // DML in loop
}

// ✅ ALWAYS — collect, then query/update once
Set<Id> accountIds = new Map<Id, Account>(accounts).keySet();
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}
update accounts; // DML once, outside the loop

Rule: if you see [SELECT or Database.query, insert, update, delete, upsert, merge inside a for loop body — stop and refactor before proceeding.

Step 2 — Sharing Model Verification

Every class must declare its sharing intent explicitly. Undeclared sharing inherits from the caller — unpredictable behaviour.

DeclarationWhen to use
public with sharing class FooDefault for all service, handler, selector, and controller classes
public without sharing class FooOnly when the class must run elevated (e.g. system-level logging, trigger bypass). Requires a code comment explaining why.
public inherited sharing class FooFramework entry points that should respect the caller's sharing context

If a class does not have one of these three declarations, add it before writing anything else.

Step 3 — CRUD / FLS Enforcement

Apex code that reads or writes records on behalf of a user must verify object and field access. The platform does not enforce FLS or CRUD automatically in Apex.

// Check before querying a field
if (!Schema.sObjectType.Contact.fields.Email.isAccessible()) {
    throw new System.NoAccessException();
}

// Or use WITH USER_MODE in SOQL (API 56.0+)
List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE AccountId = :accId WITH USER_MODE];

// Or use Database.query with AccessLevel
List<Contact> contacts = Database.query('SELECT Id, Email FROM Contact', AccessLevel.USER_MODE);

Rule: any Apex method callable from a UI component, REST endpoint, or @InvocableMethod must enforce CRUD/FLS. Internal service methods called only from trusted contexts may use with sharing instead.

Step 4 — SOQL Injection Prevention

// ❌ NEVER — concatenates user input into SOQL string
String soql = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';

// ✅ ALWAYS — bind variable
String soql = [SELECT Id FROM Account WHERE Name = :userInput];

// ✅ For dynamic SOQL with user-controlled field names — validate against a whitelist
Set<String> allowedFields = new Set<String>{'Name', 'Industry', 'AnnualRevenue'};
if (!allowedFields.contains(userInput)) {
    throw new IllegalArgumentException('Field not permitted: ' + userInput);
}

Step 5 — Modern Apex Idioms

Prefer current language features (API 62.0 / Winter '25+):

Old patternModern replacement
if (obj != null) { x = obj.Field__c; }x = obj?.Field__c;
x = (y != null) ? y : defaultVal;x = y ?? defaultVal;
System.assertEquals(expected, actual)Assert.areEqual(expected, actual)
System.assert(condition)Assert.isTrue(condition)
[SELECT ... WHERE ...] with no sharing context[SELECT ... WHERE ... WITH USER_MODE]

Step 6 — PNB Test Coverage Checklist

Every feature must be tested across all three paths. Missing any one of these is a quality failure:

Positive Path

  • Expected input → expected output.
  • Assert the exact field values, record counts, or return values — not just that no exception was thrown.

Negative Path

  • Invalid input, null values, empty collections, and error conditions.
  • Assert that exceptions are thrown with the correct type and message.
  • Assert that no records were mutated when the operation should have failed cleanly.

Bulk Path

  • Insert/update/delete 200–251 records in a single test transaction.
  • Assert that all records processed correctly — no partial failures from governor limits.
  • Use Test.startTest() / Test.stopTest() to isolate governor limit counters for async work.

Test Class Rules

@isTest(SeeAllData=false)   // Required — no exceptions without a documented reason
private class AccountServiceTest {

    @TestSetup
    static void makeData() {
        // Create all test data here — use a factory if one exists in the project
    }

    @isTest
    static void givenValidInput_whenProcessAccounts_thenFieldsUpdated() {
        // Positive path
        List<Account> accounts = [SELECT Id FROM Account LIMIT 10];
        Test.startTest();
        AccountService.processAccounts(accounts);
        Test.stopTest();
        // Assert meaningful outcomes — not just no exception
        List<Account> updated = [SELECT Status__c FROM Account WHERE Id IN :accounts];
        Assert.areEqual('Processed', updated[0].Status__c, 'Status should be Processed');
    }
}

Step 7 — Trigger Architecture Checklist

  • One trigger per object. If a second trigger exists, consolidate into the handler.
  • Trigger body contains only: context checks, handler invocation, and routing logic.
  • No business logic, SOQL, or DML directly in the trigger body.
  • If a trigger framework (Trigger Actions Framework, ff-apex-common, custom base class) is already in use — extend it. Do not create a parallel pattern.
  • Handler class is with sharing unless the trigger requires elevated access.

Quick Reference — Hardcoded Anti-Patterns Summary

PatternAction
SOQL inside for loopRefactor: query before the loop, operate on collections
DML inside for loopRefactor: collect mutations, DML once after the loop
Class missing sharing declarationAdd with sharing (or document why without sharing)
escape="false" on user data (VF)Remove — auto-escaping enforces XSS prevention
Empty catch blockAdd logging and appropriate re-throw or error handling
String-concatenated SOQL with user inputReplace with bind variable or whitelist validation
Test with no assertionAdd a meaningful Assert.* call
System.assert / System.assertEquals styleUpgrade to Assert.isTrue / Assert.areEqual
Hardcoded record ID ('001...')Replace with queried or inserted test record ID

github의 다른 스킬

console-rendering
github
Go에서 struct 태그 기반 콘솔 렌더링 시스템 사용 지침
official
acquire-codebase-knowledge
github
사용자가 기존 코드베이스에 대한 매핑, 문서화, 또는 온보딩을 명시적으로 요청할 때 이 스킬을 사용하세요. "이 코드베이스를 매핑해줘", "문서화해줘"와 같은 프롬프트에서 트리거됩니다.
official
acreadiness-assess
github
현재 리포
official
acreadiness-generate-instructions
github
AgentRC 명령어를 통해 맞춤형 AI 에이전트 지침 파일을 생성합니다. .github/copilot-instructions.md 파일을 생성합니다(기본값, VS Code의 Copilot에 권장됨).
official
acreadiness-policy
github
사용자가 AgentRC 정책을 선택, 작성 또는 적용할 수 있도록 지원합니다. 정책은 관련 없는 검사를 비활성화하고, 영향/수준을 재정의하며, 설정을 통해 준비 상태 점수를 사용자 지정합니다.
official
add-educational-comments
github
코드 파일에 교육용 주석을 추가하여 효과적인 학습 자료로 변환합니다. 설명의 깊이와 어조를 세 가지 설정 가능한 지식 수준(초급, 중급, 고급)에 맞게 조정합니다. 파일이 제공되지 않으면 자동으로 요청하며, 빠른 선택을 위해 번호 목록 매칭을 제공합니다. 교육용 주석만을 사용하여 파일을 최대 125%까지 확장합니다(엄격한 제한: 새 줄 400개, 1,000줄 초과 파일의 경우 300개). 파일 인코딩, 들여쓰기 스타일, 구문 정확성 등을 유지합니다.
official
adobe-illustrator-scripting
github
Adobe Illustrator 자동화 스크립트를 ExtendScript(JavaScript/JSX)로 작성, 디버깅 및 최적화합니다. 스크립트를 생성하거나 수정하여 조작할 때 사용합니다.
official
agent-governance
github
선언적 정책, 의도 분류, AI 에이전트 도구 접근 및 행동 제어를 위한 감사 추적. 구성 가능한 거버넌스 정책은 허용/차단된 도구, 콘텐츠 필터, 속도 제한, 승인 요구 사항을 정의하며, 코드가 아닌 구성으로 저장됨. 의미론적 의도 분류는 패턴 기반 신호를 사용하여 도구 실행 전에 위험한 프롬프트(데이터 유출, 권한 상승, 프롬프트 인젝션)를 탐지함. 도구 수준 거버넌스 데코레이터는 함수에서 정책을 적용함...
official