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.
[](https://www.skillsdirectory.com/skills/yuntaengtaeng-readable-testable-code)
---
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
의도가 명확한 코드
↓
책임이 명확한 코드
↓
테스트하기 쉬운 코드
↓
필요한 만큼의 추상화
```
패턴을 적용하는 것이 목표가 아니다.
**읽기 어렵거나 테스트하기 어렵다는 사실을 설계에 대한 피드백으로 사용한다.**
---
## 최종 체크
코드를 완료하기 전에 스스로 묻는다.
> **하나의 책임만 가지는가?**
> **읽는 사람이 편한가?**
> **이 코드의 중요한 행동을 테스트하기 쉬운가?**
하나라도 명확하게 "아니다"라면,
코드를 더 추가하기 전에 구조를 다시 검토한다.