Community코딩 & 개발github.com

Allyansayz/agent-skills

모호한 이름, 안전하지 않은 타입, 중복 로직, 혼재된 책임, 매직 넘버 및 취약한 오류 처리를 읽기 쉽고 유지 관리 가능하며 강력한 타입의 코드로 대체하여 TypeScript 코드 품질을 향상시킵니다. 기존 TypeScript 애플리케이션, 특히 영업 CRM 시스템을 검토, 리팩토링, 디버깅 또는 확장할 때 사용하세요.

agent-skills란 무엇인가요?

이 스킬은 AI 에이전트(예: Claude Code, Cursor, Codex)가 TypeScript 코드의 품질을 자동으로 분석하고 개선하도록 설계된 전문 가이드입니다. 주요 목표는 기존 코드를 더 이해하기 쉽고, 유지 관리하기 쉬우며, 테스트 가능하고 확장 가능하게 만드는 것입니다. 이 스킬은 모호한 변수명(vs. 도메인 특화 이름), 안전하지 않은 `any` 타입(vs. 명시적 타입 및 유니온), 단일 함수 내의 혼재된 책임(vs. 관심사 분리), 설명되지 않은 매직 넘버(vs. 명명된 상수), 무시되는 오류(vs. 명시적 오류 처리)와 같은 일반적인 문제를 식별하고 수정하는 방법을 구체적인 예제와 함께 제시합니다. 특히 CRM 시스템 컨텍스트에서 `Lead`, `Contact`, `Opportunity`, `SalesRepresentative`와 같은 명확한 비즈니스 개념을 사용하도록 장려합니다. 또한, 스킬은 리팩토링 시 기존 동작을 변경하지 않고 보존해야 한다는 핵심 원칙을 강조하며, `createOrUpdateLead`와 같은 실제적이고 문제가 있는 코드 예제를 통해 단계별 리팩토링 워크플로우를 제공합니다. 이 스킬은 모든 TypeScript 프로젝트에 적용 가능하며, 개발자가 도메인 언어를 사용하여 코드의 가독성과 신뢰성을 높이는 데 중점을 둡니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aliyansayz/agent-skills/tree/HEAD/typescript-code-quality

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

컨텍스트 기반 코드 품질 — TypeScript 에이전트 스킬

1. 목적

기존 코드를 더 쉽게 이해하고, 유지 관리하고, 테스트하고, 확장할 수 있도록 하는 TypeScript 코드 품질 에이전트 역할을 합니다.

단순히 코드를 복잡하게 보이게 만들기 위해 리팩토링하지 마십시오. 개발자가 다음을 이해할 수 있도록 코드를 개선하십시오.

  • 각 함수가 수행하는 작업.
  • 비즈니스 규칙이 존재하는 이유.
  • 각 변수가 나타내는 것.
  • 함수가 기대하고 반환하는 데이터.
  • 데이터가 없거나 유효하지 않을 때 발생하는 상황.
  • 애플리케이션의 어떤 부분이 비즈니스 로직, 데이터 액세스 및 사용자 상호 작용을 담당하는지.
  • 변경 사항이 기존 애플리케이션 동작에 미치는 영향.

모호한 변수 및 함수 이름 대신 도메인별 언어를 사용하십시오.

Sales CRM 코드로 작업할 때는 Lead, Contact, Opportunity, SalesRepresentative, LeadStatus, PipelineStage 및 FollowUpDate와 같은 명확한 비즈니스 개념을 사용하십시오.

목표는 이해할 수 있고, 강력한 타입을 가지며, 안정적이고, 테스트 가능하며, 유지 관리 가능한 소프트웨어입니다.

2. 활성화 시점

다음과 같은 경우 이 스킬을 사용하십시오.

  • TypeScript 코드에 충분한 컨텍스트 없이 x, data, obj, temp, thing 또는 result와 같은 변수가 포함된 경우.
  • 함수에 process(), handle(), manage() 또는 doStuff()와 같은 모호한 이름이 있는 경우.
  • 함수가 유효성 검사, 비즈니스 계산, 데이터베이스 업데이트, 알림 및 로깅을 모두 한 번에 수행하는 경우.
  • 애플리케이션이 any, 안전하지 않은 타입 어설션, 설명되지 않은 문자열 또는 매직 넘버에 의존하는 경우.
  • 비즈니스 규칙이 여러 위치에서 중복되는 경우.
  • 오류가 자동으로 무시되거나 오해의 소지가 있는 성공 응답으로 변환되는 경우.
  • 함수에 깊게 중첩된 조건문이나 과도한 분기가 포함된 경우.
  • CRM 용어가 구성 요소, API 엔드포인트 및 데이터베이스 모델에서 일관되지 않은 경우.
  • 실제 데이터베이스나 외부 서비스에 연결하지 않고는 코드를 테스트하기 어려운 경우.
  • 개발자가 의도된 동작을 변경하지 않고 기존 코드를 개선하려는 경우.

3. 핵심 원칙

3.1 도메인별 이름 선호

이름은 애플리케이션에서의 의미를 설명해야 합니다.

권장:

const leadId = "lead_123";
const salesRepresentativeId = "user_456";
const nextFollowUpDate = new Date();
const qualifiedLeadCount = 12;

지양:

const id = "lead_123";
const user = "user_456";
const date = new Date();
const count = 12;

짧은 이름은 i와 같이 작은 범위 내에서 의미가 명확한 경우 허용됩니다. 모든 이름을 기계적으로 확장하지 마십시오. 추가 컨텍스트가 이해를 향상시킬 때 이름을 확장하십시오.

3.2 TypeScript를 사용하여 비즈니스 개념 표현

구조화되지 않은 객체보다 명시적 타입, 인터페이스, 판별 유니온 및 명명된 도메인 모델을 선호하십시오.

탈출구로 any를 사용하지 마십시오. 진정으로 신뢰할 수 없는 데이터에는 unknown을 사용하고 사용하기 전에 유효성을 검사하십시오.

3.3 책임 분리

각 함수 또는 모듈은 명확한 주요 책임을 가져야 합니다.

CRM 서비스는 하나의 함수에서 HTTP 응답을 구성하고, UI 구성 요소를 조작하고, 이메일을 보내고, 데이터베이스 쿼리를 실행해서는 안 됩니다.

3.4 의도된 동작 유지

리팩토링하기 전에 기존 비즈니스 규칙과 예상 결과를 식별하십시오.

다음을 자동으로 변경하지 마십시오.

  • 리드 자격 기준.
  • 파이프라인 전환 규칙.
  • 영업 담당자 할당.
  • 후속 일정 규칙.
  • 중복 감지 동작.
  • 권한 및 액세스 제어.
  • API 응답 형태.
  • 데이터베이스 의미 체계.

기존 동작이 올바르지 않거나 모호한 것으로 보이면, 동작 변경을 리팩토링으로 위장하는 대신 문제를 명시적으로 식별하십시오.

3.5 불필요한 추상화보다 단순한 코드 선호

실제 문제를 해결하지 않는 한 설계 패턴, 클래스, 제네릭, 팩토리 또는 추가 종속성을 도입하지 마십시오.

설명적인 이름을 가진 간단한 함수가 불필요하게 정교한 추상화보다 나은 경우가 많습니다.


4. 잘못 작성된 Sales CRM 예제

다음 코드는 의도적으로 문제가 있습니다. 리드 관리 애플리케이션에서 일반적인 코드 품질 문제를 보여줍니다.

4.1 이전: 잘못 작성된 코드

// bad-crm.ts

export async function go(d: any, db: any, mail: any) {
    try {
        let x = d.name;
        let e = d.email;
        let p = d.phone || "";
        let s = d.status || "new";
        let v = Number(d.value || 0);
        let u = d.user || null;

        if (!x || !e) {
            return { ok: false, msg: "bad data" };
        }

        let old = await db.leads.findOne({ email: e });

        if (old && old.status != "lost") {
            old.v = old.v + v;
            old.s = v > 5000 ? "hot" : old.s;
            old.p = p || old.p;
            await db.leads.update(old.id, old);

            if (old.s == "hot") {
                await mail.send(
                    old.email,
                    "Hot lead",
                    "Lead is hot!"
                );
            }

            return { ok: true, data: old };
        }

        let a = {
            name: x,
            email: e,
            phone: p,
            status: s,
            value: v,
            user: u,
            created: new Date(),
            updated: new Date(),
        };

        let r = await db.leads.insert(a);

        if (v > 5000) {
            await mail.send(
                e,
                "Hot lead",
                "Lead is hot!"
            );
        }

        return { ok: true, data: r };
    } catch (e) {
        console.log(e);
        return { ok: false, msg: "error" };
    }
}

4.2 에이전트가 식별해야 할 문제

문제예시중요성개선 사항
불명확한 함수 이름go()비즈니스 작업을 전달하지 않음동작을 정확히 설명한다면 createOrUpdateLead() 사용
불명확한 변수d, x, e, p, s, v, u, a, r개발자는 각 변수의 목적을 유추해야 함도메인별 이름 사용
안전하지 않은 타입입력, 데이터베이스, 이메일 서비스에 any 사용TypeScript의 많은 실수 포착 기능 제거인터페이스 및 명시적 입력 타입 정의
숨겨진 비즈니스 규칙v > 5000자격 기준이 설명되지 않음명명된 상수 또는 문서화된 비즈니스 규칙 정의
일관성 없는 용어status, hot, lost리드 상태와 자격은 다른 개념을 나타낼 수 있음명시적인 리드 상태 모델 정의
혼재된 책임하나의 함수가 유효성 검사, 쿼리, 업데이트, 생성, 이메일 전송, 오류 처리를 수행개별 동작을 테스트하기 어려움애플리케이션, 도메인, 인프라 책임 분리
잠재적 데이터 불일치이메일 실패 전에 데이터베이스 업데이트가 성공할 수 있음리드가 저장되었음에도 호출자가 오류를 받을 수 있음알림 실패 동작을 명시적으로 정의
약한 유효성 검사x와 e만 확인잘못된 이메일 주소, 잘못된 값, 잘못된 형식의 필드가 시스템에 유입될 수 있음입력 및 비즈니스 불변 조건 검증
암시적 상태 규칙상태가 "lost"가 아닌 경우에만 기존 리드 업데이트이 규칙의 의미가 숨겨져 있음업데이트 정책 명명 및 문서화
불명확한 지속성 계약db.leads.findOne() 및 .update()가 문서화되지 않은 형태 사용데이터베이스 가정을 확인하기 어려움리포지토리 인터페이스 정의
일반 오류 응답"error"유용한 컨텍스트를 잃고 문제 해결을 복잡하게 함예상 실패를 명시적으로 처리하고 예상치 못한 오류는 적절히 로깅
일관성 없는 응답{ ok, msg } 및 { ok, data }호출자는 각 경우에 어떤 필드가 있는지 유추해야 함판별된 결과 타입 또는 일관된 애플리케이션 오류 모델 사용
가변적이고 느슨한 형태의 데이터old.v, old.s, old.p필드 이름이 명백한 지속 모델과 일치하지 않음일관된 Lead 타입 정의
불명확한 중복 정책이메일로 기존 레코드 병합병합의 정확한 의미가 숨겨져 있음업데이트 가능한 필드와 값 집계 방법 문서화
테스트하기 어려운 의존성데이터베이스 및 이메일 서비스가 any로 전달됨계약 및 실패 사례가 컴파일러에 의해 확인되지 않음좁은 인터페이스 정의 및 의존성 주입

중요: 이것은 실제 프로덕션 CRM 구현이 아닌 예시용입니다. 리팩토링은 먼저 기존 CRM의 실제 동작, 데이터 모델, 지속성 의미 체계 및 비즈니스 규칙을 확인해야 합니다.


5. 리팩토링 워크플로우

기존 TypeScript 코드를 개선할 때 이 워크플로우를 따르십시오.

1단계: 컨텍스트 이해

수정하기 전에 관련 코드를 검사하십시오.

식별:

  • 함수의 목적.
  • 호출자 및 의존성.
  • 입력 및 출력 형식.
  • 데이터베이스 스키마 또는 리포지토리 계약.
  • 도메인 엔티티 및 비즈니스 용어.
  • 기존 유효성 검사 및 권한 부여 규칙.
  • 현재 테스트 및 예상 동작.
  • 오류 처리 및 부작용.

필요시 주변 모듈을 읽으십시오. 함수 이름만으로 함수의 완전한 계약을 유추하지 마십시오.

2단계: 현재 동작 기록

기존 로직을 평이한 언어로 설명하십시오.

위 예제의 관찰된 동작은 다음과 같습니다.

  1. 입력에서 리드 세부 정보를 읽습니다.
  2. 이름이나 이메일이 누락된 경우 요청을 거부합니다.
  3. 이메일로 기존 리드를 찾습니다.
  4. 일치하는 리드가 있고 상태가 "lost"가 아니면 제출된 값을 기존 값에 추가합니다.
  5. 결과 값이 임계값을 초과하면 해당 기존 리드를 "hot"으로 표시합니다.
  6. 새 전화번호가 제공되면 기존 전화번호를 대체합니다.
  7. 레코드를 업데이트하고 결과 상태가 "hot"이면 알림을 보냅니다.
  8. 그렇지 않으면 타임스탬프와 함께 새 레코드를 생성합니다.
  9. 제출된 값이 임계값을 초과하면 리드에 알립니다.
  10. 성공 또는 실패 객체를 반환합니다.

이 설명은 관찰된 구현 동작과 의도된 비즈니스 동작을 의도적으로 구분합니다. 예를 들어, 코드는 한 알림 경로에 대해 기존 레코드의 결과 값을 테스트하고 다른 경로에 대해 제출된 값을 테스트합니다. 이러한 규칙을 동등하게 취급하지 마십시오.

3단계: 이름 개선

일반 이름을 해당 역할을 표현하는 이름으로 바꾸십시오.

잘못된 이름더 나은 이름
gocreateOrUpdateLead
dleadInput
xleadName, 또는 catch 블록의 error
eemailAddress
pphoneNumber
sleadStatus
vleadValue
usalesRepresentativeId
oldexistingLead
anewLead
rcreatedLead
dbleadRepository 또는 databaseClient (실제 역할에 따라)
mailemailNotificationService

개선된 이름은 값을 정확히 나타내야 합니다. 값을 해당 유형으로 잘못 레이블링하지 마십시오(예: salesRepresentative는 ID가 아닌 객체를 의미).

4단계: 타입 추출

암시적인 타입 가정을 명시적인 TypeScript 구문으로 변환하십시오.

다음에서 시작:

let old: any = await db.leads.findOne({ email: e });

다음으로 진행:

const existingLead: Lead | null = await leadRepository.findByEmail(emailAddress);

다음을 정의해야 할 수도 있습니다:

interface Lead {
  id: string;
  name: string;
  email: string;
  phone: string;
  status: LeadStatus;
  value: number;
  salesRepresentativeId: string | null;
  createdAt: Date;
  updatedAt: Date;
}

type LeadStatus = "new" | "contacted" | "qualified" | "hot" | "lost" | "closed";

5단계: 명시적 오류 처리

함수가 실패할 수 있는 방법을 나열하십시오.

실패 모드현재 동작개선된 동작
누락된 입력 필드"bad data" 반환유효성 검사 오류를 명시적으로 나열
잘못된 이메일 형식확인 안 함유효성 검사 단계에서 거부
마이그레이션되지 않은 기존 리드자동 업데이트정책 문서화 또는 비즈니스 규칙으로 명시
데이터베이스 연결 오류잡히지 않은 예외를 "error"로 변환복구 전략 정의 (재시도, 서킷 브레이커, 오류 반환)
알림 전송 실패무시알림 실패 동작 정의 (기록 및 계속 / 롤백 / 재시도 대기열)

6단계: 책임 분리

함수의 현재 책임을 나열하고 더 작은 집중 함수로 분리하십시오.

go() 함수는 다음을 수행합니다:

  1. 입력 추출
  2. 유효성 검사
  3. 데이터베이스 쿼리
  4. 비즈니스 규칙 평가 (병합/업데이트 정책)
  5. 데이터베이스 업데이트
  6. 데이터베이스 생성
  7. 알림 발송
  8. 오류 처리
  9. 응답 구성

이러한 책임은 별도의 계층에 속합니다:

  • 컨트롤러/핸들러 - 입력 수집, 응답 구성
  • 서비스 - 비즈니스 규칙 조정
  • 리포지토리 - 데이터베이스와 상호 작용
  • 알림 서비스 - 이메일 발송
  • 유효성 검사기 - 입력 및 상태 검증

리팩토링 후:

export async function createOrUpdateLead(
  input: LeadInput,
  leadRepository: LeadRepository,
  notificationService: NotificationService
): Promise<CreateOrUpdateLeadResult> {
  // 의존성을 주입하고 좁은 추상화를 사용하는 진입점 함수
}

7단계: 의존성 정의

컴파일러가 계약을 강제할 수 있도록 각 종속성에 대한 명시적 타입을 정의하십시오.

interface LeadRepository {
  findByEmail(email: string): Promise<Lead | null>;
  update(lead: Lead): Promise<Lead>;
  insert(lead: NewLead): Promise<Lead>;
}

interface NotificationService {
  sendEmail(to: string, subject: string, body: string): Promise<NotificationResult>;
}

type NotificationResult = { sent: true; id: string } | { sent: false; reason: string };

8단계: 일관된 결과 타입

모든 경로에서 안정적인 구조를 반환하십시오.

type CreateOrUpdateLeadResult =
  | { success: true; lead: Lead }
  | { success: false; error: LeadError };

type LeadError = ValidationError | DuplicateError | DatabaseError | NotificationError;

9단계: 명확한 비즈니스 규칙

매직 넘버를 명명된 상수로 바꾸고 정책 문서화를 추가하십시오.

// 비즈니스 규칙: 리드 가치가 이 금액을 초과하면 자격을 얻습니다.
const LEAD_QUALIFICATION_THRESHOLD = 5000;

10단계: 존재하는 경우 기존 테스트 유지

기존 테스트를 수정하지 마십시오. 리팩토링된 코드가 동일한 입력 및 출력 계약을 따르는지 확인하십시오. 새 테스트가 필요한 경우 추가하되 기존 테스트를 변경하지 마십시오.


6. 일반 CRM 코드 안티패턴

이러한 패턴은 CRM 시스템 코드에서 자주 나타납니다.

안티패턴설명예시해결책
범용 리드 처리기단일 함수가 모든 리드 작업 처리handleLead(req)리드 작업당 전용 함수: createLead, updateLead, qualifyLead, assignLead
암시적 상태 머신if/else 체인에 숨겨진 전환 규칙if (s === "old" && v > 500) { ... }명시적 LeadStatus 타입 및 유한 상태 머신
마법의 문자열 비교하드코딩된 리터럴과의 느슨한 상태 비교lead.status === "active"열거형 상수 또는 const 객체 사용
만능 엔티티단일 타입이 모든 리드 관련 데이터 보유Lead에 50개 이상의 선택적 필드역할별 엔티티: LeadInput, LeadRecord, LeadView
스파게티 파이프라인리드의 단계 정의가 여러 곳에 분산일부 규칙은 컨트롤러, 일부는 서비스, 일부는 데이터베이스 트리거단일 책임을 가진 중앙 집중식 파이프라인 정의
숨겨진 감사 추적변경 사항을 명시적으로 기록하지 않고 제자리 업데이트lead.value = newValue명시적인 LeadEvent 또는 AuditEntry 모델

7. TypeScript 모범 사례

7.1 명시적 타입 및 인터페이스

CRUD 작업 이상의 의미를 가진 객체에 type보다 interface를 선호하십시오. 인터페이스는 확장 가능하고 선언 병합을 지원합니다.

가능할 때마다 옵셔널 속성보다 판별된 유니온을 선호하십시오. 판별된 유니온은 존재하지 않는 필드에 대한 액세스를 컴파일 시간에 방지합니다.

// Better
interface Lead {
  id: string;
  status: LeadStatus;
  value: number;
}

type LeadStatus = "new" | "contacted" | "qualified" | "hot" | "lost" | "closed";

// Rather than
interface Lead {
  id: string;
  status: string;
  value: number;
}

7.2 제네릭 제약 조건

할 수 있을 때 제네릭을 사용하되, 필요할 때만 사용하십시오. 지나치게 일반화된 코드보다 단순한 코드가 더 나은 경우가 많습니다.

제네릭이 실제 값을 추가하는 경우:

function getById<T extends { id: string }>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

코드를 더 복잡하게 만드는 경우 사용하지 마십시오:

// Avoid unless truly needed
const createLead = <T extends LeadInput, R extends LeadRepository>(input: T, repo: R) => { ... }

7.3 Nullish 처리

명시적인 null 반환보다 T | undefined를 선호하십시오. 선택적 체이닝(?.) 및 nullish 병합(??)을 사용하여 nullish 값의 전파를 방지하십시오. 관계형 데이터베이스에서 누락된 리소스 또는 선택적 필드에 null을 예약하십시오.

const lead = await repository.findById(id);
const representativeName = lead?.representative?.name ?? "Unassigned";

7.4 선언적 파이프라인

명령형 분기 및 루프 대신 배열 메서드(map, filter, flatMap, reduce)를 선호하십시오.

// Better
const qualifiedLeads = leads
  .filter(lead => lead.value >= LEAD_QUALIFICATION_THRESHOLD)
  .map(lead => lead.email);

// Rather than
const qualifiedLeads: string[] = [];
for (const lead of leads) {
  if (lead.value >= 5000) {
    qualifiedLeads.push(lead.email);
  }
}

7.5 타입 가드

런타임 유효성 검사를 위해 사용자 정의 타입 가드를 정의하십시오.

function isLead(value: unknown): value is Lead {
  return (
    typeof value === "object" &&
    value !== null &&
    "status" in value &&
    "value" in value
  );
}

8. 사용자와의 상호 작용

이 스킬은 일반적인 TypeScript 코드 품질 검토 및 리팩토링 작업을 위해 설계되었습니다. 특정 코드베이스 컨텍스트 없이 작동합니다.

이 스킬을 효과적으로 사용하기 위해 다음 프롬프트를 사용하십시오.

특정 리팩토링을 시작하기 전에 항상 함수의 기존 동작을 이해하고 있는지 확인하십시오. 코드 검토, 디버깅 세션, 리팩토링 및 기존 TypeScript 코드 확장 시 이 스킬을 활성화하십시오.

"기존 동작을 변경하지 않는다"는 원칙은 항상 최우선입니다.

9. 라이선스

MIT

관련 스킬