Skip to content
Back to skills

Readable Testable Code

ASecurity

이 프로젝트의 개발 철학(단일 책임 원칙 + 읽기 쉽고 테스트하기 쉬운 코드). 코드를 작성·수정·리팩터링·리뷰할 때, 함수/컴포넌트/모듈의 구조나 책임 분리, 추상화 도입 여부, 테스트 작성 방식을 판단해야 할 때 반드시 먼저 적용한다.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 13, 2026
ai-agentsreactapi

Works with

  • api

Security analysis

A100/100

Scanned October 13, 2026

npx -y skills add yuntaengtaeng/snitch --skill readable-testable-code --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Readable Testable Code?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Readable Testable Code
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yuntaengtaeng-readable-testable-code/badge)](https://www.skillsdirectory.com/skills/yuntaengtaeng-readable-testable-code)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: readable-testable-code
description: 이 프로젝트의 개발 철학(단일 책임 원칙 + 읽기 쉽고 테스트하기 쉬운 코드). 코드를 작성·수정·리팩터링·리뷰할 때, 함수/컴포넌트/모듈의 구조나 책임 분리, 추상화 도입 여부, 테스트 작성 방식을 판단해야 할 때 반드시 먼저 적용한다.
---

# Readable & Testable Code Philosophy

이 프로젝트의 개발 철학은 **단일 책임 원칙**과 **읽기 쉽고 테스트하기 쉬운 코드**다.

코드를 작성하거나 수정할 때 다음 질문을 가장 먼저 확인한다.

> **이 코드는 하나의 이유로만 변경되는가?**  
> **이 코드는 읽는 사람이 편한가?**  
> **이 코드는 테스트하기 쉬운가?**

특정 아키텍처나 디자인 패턴을 따르는 것보다 이것들을 우선한다.

---

## 0. 단일 책임 원칙

하나의 함수, 컴포넌트, 모듈은 **하나의 책임**만 가진다.
책임이란 "변경될 이유"다. 변경될 이유가 둘 이상이라면 책임이 섞여 있다는 신호다.

### 판단 기준

- 이 코드를 한 문장으로 설명할 때 "그리고"가 들어가는가?
- 서로 관련 없는 요구사항 변경이 같은 코드를 수정하게 만드는가?
- 이름이 하는 일을 모두 담지 못해 모호해지는가? (`handleData`, `process`, `utils`)

책임 분리는 읽기 쉬움과 테스트 용이성의 출발점이다.
단, 분리 자체가 목적이 아니다. 나눈 결과가 더 읽기 어렵다면 나누지 않는다 (→ 7장).

---

## 1. 읽는 사람이 편한 코드

코드는 작성하는 시간보다 읽히는 시간이 더 길다.

따라서 코드의 의도가 구현을 따라가야만 이해되는 구조보다,
코드 자체에서 자연스럽게 드러나는 구조를 선호한다.

### 판단 기준

코드를 읽었을 때 다음 질문에 쉽게 답할 수 있어야 한다.

- 이 코드가 **왜 존재하는가?**
- 무엇을 **입력**으로 받는가?
- 무엇을 **결과**로 만드는가?
- 어떤 **상태를 변경**하는가?
- 어떤 **외부 의존성**을 사용하는가?
- 실패하면 **어떤 일이 발생하는가?**

이 질문에 답하기 위해 여러 파일을 계속 이동하거나
구현 세부사항을 깊게 추적해야 한다면 구조를 다시 검토한다.

### 이름으로 의도를 설명한다

주석으로 구현을 설명하기 전에 이름으로 의도를 표현한다.

```ts
// Bad
const result = data.filter((v) => v.status === 'ACTIVE');

// Better
const activeUsers = users.filter(isActiveUser);
```

코드를 읽는 사람이 구현을 해석하는 것이 아니라
**업무의 흐름을 읽을 수 있도록 한다.**

---

## 2. 테스트하기 쉬운 코드

테스트 작성의 어려움은 종종 설계에 대한 피드백이다.

테스트를 작성하면서 복잡함이 느껴진다면 단순히
"테스트 코드가 귀찮다"고 판단하지 않는다.

먼저 프로덕션 코드의 구조를 의심한다.

### 이런 신호를 주의한다

테스트 하나를 작성하기 위해

- mock이 지나치게 많이 필요하다.
- setup 코드가 테스트 본문보다 길어진다.
- 작은 동작 하나를 검증하는데 많은 객체가 필요하다.
- 네트워크, storage, timer, browser API 등이 모두 따라온다.
- private 구현을 직접 테스트하고 싶어진다.
- 작은 구현 변경으로 관련 없는 테스트가 많이 깨진다.
- 어떤 결과를 검증해야 하는지 명확하지 않다.

이런 상황에서는 다음을 질문한다.

> 이 코드가 너무 많은 책임을 가지고 있지 않은가?

> 순수한 로직과 외부 의존성이 섞여 있지 않은가?

> 입력과 출력이 명확하지 않은가?

> 구현 세부사항을 외부에서 너무 많이 알아야 하지 않는가?

---

## 3. 순수 로직과 Side Effect를 구분한다

가능하면 계산과 판단은 순수 함수로 만든다.

```ts
function calculateDiscount(
  price: number,
  discountRate: number,
) {
  return price * (1 - discountRate);
}
```

이런 코드는 테스트가 단순하다.

```ts
expect(calculateDiscount(10_000, 0.1))
  .toBe(9_000);
```

반면 아래와 같은 작업은 Side Effect다.

- API 호출
- localStorage
- Date / Timer
- DOM
- analytics
- navigation
- 파일 접근
- 전역 상태 변경

Side Effect를 없애려고 하지 않는다.

대신 **핵심 판단 로직과 Side Effect의 경계를 명확하게 만든다.**

---

## 4. 의존성을 숨기지 않는다

함수가 외부 환경에 많이 의존할수록 테스트하기 어려워진다.

```ts
async function loadUser() {
  const token = localStorage.getItem('token');
  const response = await fetch('/api/user', {
    headers: { Authorization: token ?? '' },
  });

  return response.json();
}
```

필요하다면 의존성을 밖에서 전달할 수 있는 구조를 고려한다.

```ts
async function loadUser(
  token: string,
  fetchUser: (token: string) => Promise<User>,
) {
  return fetchUser(token);
}
```

단, **테스트 가능성만을 위해 모든 것을 추상화하지 않는다.**

추상화 자체가 코드 이해 비용을 증가시킨다면 오히려 좋지 않은 설계일 수 있다.

---

## 5. React에서는 UI와 판단 로직을 구분한다

컴포넌트가 다음 모든 역할을 동시에 수행하지 않도록 주의한다.

- 데이터 요청
- 데이터 변환
- 비즈니스 규칙 판단
- 상태 관리
- 이벤트 처리
- UI 렌더링

컴포넌트가 복잡해졌다면 먼저 질문한다.

> UI를 그리는 책임과 판단하는 책임을 분리하면 더 이해하기 쉬운가?

예를 들어:

```ts
function canPurchase(
  user: User,
  product: Product,
) {
  return user.isVerified && product.stock > 0;
}
```

컴포넌트에서는 결과를 사용한다.

```tsx
const purchasable = canPurchase(user, product);

return (
  <Button disabled={!purchasable}>
    구매하기
  </Button>
);
```

이렇게 하면 중요한 규칙을 React 렌더링 구조와 독립적으로 테스트할 수 있다.

---

## 6. 테스트는 구현이 아니라 행동을 검증한다

가능하면 다음을 테스트한다.

> "내부에서 어떻게 구현했는가?"

보다

> "주어진 상황에서 어떤 행동을 하는가?"

예를 들어 함수 내부에서 어떤 helper가 몇 번 호출됐는지를 지나치게 검증하면
리팩터링만 해도 테스트가 깨질 수 있다.

사용자 또는 호출자가 관찰할 수 있는 결과를 우선 검증한다.

```ts
expect(calculatePrice(cart)).toBe(12_000);
```

구현 세부사항에 강하게 결합된 테스트보다
**행동에 결합된 테스트를 선호한다.**

---

## 7. 테스트하기 쉽다는 이유만으로 과설계하지 않는다

테스트 가능성은 설계를 판단하는 신호이지 절대적인 목표가 아니다.

다음과 같은 코드를 만들지 않는다.

```text
Interface
  ↓
Factory
  ↓
Adapter
  ↓
Service
  ↓
Wrapper
  ↓
실제 함수
```

단순한 함수 하나로 충분하다면 함수 하나를 사용한다.

추상화는 다음과 같은 실제 이유가 있을 때 도입한다.

- 책임을 분리할 필요가 있다.
- 외부 시스템과 경계를 만들 필요가 있다.
- 여러 구현을 교체해야 한다.
- 복잡한 로직을 독립적으로 다뤄야 한다.
- 테스트에서 실제로 제어해야 하는 의존성이 있다.

**테스트를 위해 구조를 복잡하게 만드는 것은 목적이 아니다.**

---

# Agent Decision Rule

코드를 작성하거나 수정하기 전에 다음 순서로 판단한다.

**1. Readability**

> 처음 보는 개발자가 이 코드의 의도를 쉽게 이해할 수 있는가?

**2. Responsibility**

> 하나의 함수, 컴포넌트, 모듈이 너무 많은 이유로 변경되고 있지 않은가?

**3. Testability**

> 중요한 행동을 테스트하려면 얼마나 많은 준비가 필요한가?

**4. Dependency**

> 핵심 로직이 React, 브라우저 API, 네트워크 같은 외부 환경에 불필요하게 묶여 있지 않은가?

**5. Simplicity**

> 이를 개선한다는 이유로 오히려 더 많은 추상화와 파일을 만들고 있지는 않은가?

판단 우선순위는 다음과 같다.

```text
의도가 명확한 코드
    ↓
책임이 명확한 코드
    ↓
테스트하기 쉬운 코드
    ↓
필요한 만큼의 추상화
```

패턴을 적용하는 것이 목표가 아니다.

**읽기 어렵거나 테스트하기 어렵다는 사실을 설계에 대한 피드백으로 사용한다.**

---

## 최종 체크

코드를 완료하기 전에 스스로 묻는다.

> **하나의 책임만 가지는가?**

> **읽는 사람이 편한가?**

> **이 코드의 중요한 행동을 테스트하기 쉬운가?**

하나라도 명확하게 "아니다"라면,
코드를 더 추가하기 전에 구조를 다시 검토한다.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.