1. 세션 요약 - 빈칸 채우기
(기본 원칙) 좋은 추상화는 함수나 컴포넌트의 이름과 파라미터, 반환 값, 즉 _______만으로도 _______을 예측할 수 있다. 이러한 예측 가능한 코드는 개발자가 코드를 읽을 때 다른 부분으로 시선을 이동하거나 내부 구현을 확인하느라 발생하는 인지적 부담을 크게 줄여준다.
('예측 가능한 인터페이스' 패턴 전문가 5단계)
1.
요구사항을 확인하고, 구현해야 하는 대상의 _______을 파악한다.
2.
구현해야 하는 대상의 본질을 생각해본 뒤, 이를 내가 알고 있는 _______와 매칭해본다.
3.
매칭된 일반해가 마땅히 가지고 있어야 하는 인터페이스를 먼저 떠올리고, 그 중 필요한 인터페이스를 추가한다. 예측 가능성을 위해 가급적 _______ / _______의 관례를 따른다.
4.
구현에 인터페이스를 맞추지 않고, 인터페이스에 맞춰 구현을 추가한다. 이 과정에서 인터페이스는 최대한 단순하게 유지하고 구체적인 로직은 _______하도록 한다.
5.
구현을 완료한 뒤 형태만으로도 동작이 예상되고 내부 구현이 궁금해지지 않는지, 즉 _______한지 점검해본다.
2. 세션 목표
개발을 하다보면 인터페이스 만으로 내용을 파악할 수 없어 머리 아프게 코드를 한참 들여다봐야 하는 경우가 생깁니다
•
함수나 컴포넌트가 예상과 다르게 작동하여 내부 구현을 확인해야 하는 경우가 발생함
•
컴포넌트마다 다른 인터페이스 패턴을 사용해 학습 비용이 증가함
•
도메인에 지나치게 종속된 인터페이스로 인해 재사용성이 저하됨
이번 세션에서 다루는 내용을 잘 이해하면,
•
함수 이름과 파라미터만으로도 동작을 예상할 수 있는 인터페이스를 설계할 수 있게 되어, 시점 이동이 줄어들고 인지 부하를 낮출 수 있게 됩니다.
•
코드를 파악해야 하는 시간이 줄어들어 업무 효율이 증가하고, 제품의 유지보수 비용이 감소합니다.
3. 인접 개념
•
•
DRY(Don't Repeat Yourself) - 중복 제거와 재사용성
•
HTML 표준 속성명 - value, onChange, disabled 등
•
Composition over Inheritance - 상속보다는 조합
•
Interface Segregation Principle(ISP) - 인터페이스 분리 원칙
•
Archetype - 원형(Input / Toggle / Selector / List / Dialog / Form / Router / …)
4. 멘탈 모델 & 시각화 자료
5. '인터페이스 일반화' 패턴이란?
도메인에 종속되지 않은 범용적인 인터페이스를 설계하여, 시점 이동 없이 코드가 예측 가능하도록 만드는 패턴
특정 비즈니스 로직이나 도메인 용어를 제거하고, 컴포넌트의 본질적인 역할과 동작만을 추상화하여 내부를 깊게 살펴보지 않더라도 의도와 동작이 잘 드러나도록 개선합니다.
인터페이스 일반화는 특정 도메인에 종속되지 않고 "행동의 본질"을 드러내는 보편적인 API를 설계하는 것입니다. 핵심은 "같은 것은 같게" 만들어서, 어디서든 예측 가능한 사용법을 제공하는 거예요.
예를 들어, Modal이든 Popup이든 Dialog든 본질은 "무언가를 보여주고 숨기는 것"이죠. 그럼 모두 같은 방식(open()/close())으로 동작해야 합니다. 어떤 건 show()고 어떤 건 display()이면 안되는거죠.
많은 개발자가 컴포넌트를 만들 때 "재사용성"에 집착합니다. 하지만 진짜 중요한 건 "예측 가능성"이에요.
처음 보는 라이브러리를 쓴다고 생각해보세요.
문서를 읽기도 전에 "아, Modal이니까 당연히 open prop이 있겠지?"라고 예상할 수 있다면? 그게 바로 예측 가능한 인터페이스의 힘입니다. showProductModal() 같은 특수한 이름 대신 그냥 open()이라고 하면, 모든 개발자가 설명 없이도 사용법을 압니다.
새로운 도메인이 추가되어도 문제없어요. 오늘은 ProductModal, 내일은 UserModal, 모레는 OrderModal이 추가되어도 모두 같은 open()/close() 인터페이스를 쓰니까요.
즉, 인터페이스 일반화는 단순히 코드를 예쁘게 만드는 일이 아닙니다. 이것은 팀 동료를 위한 인지 부하 관리 기술입니다.
Universal Language - 도메인을 넘어서는 보편적 언어
개발 세계에는 이미 합의된 "보편적 언어"가 있습니다. 이걸 사용하면 설명이 필요 없어요. 가급적 표준을 따르는 것이 소통 비용을 줄이고 예측 가능성을 높여줄 수 있습니다.
🌍 Actions: open/close, show/hide, enable/disable
🌍 States: loading/success/error, active/inactive, valid/invalid
🌍 Events: onChange, onSubmit, onCancel, onComplete
Plain Text
복사
이런 단어들은 어떤 도메인에서든 같은 의미로 통합니다. 굳이 startProductLoading, beginUserFetch 같은 걸 만들 필요가 없어요. 그냥 loading이면 충분하죠.
대상의 본질을 바라보기
함수 이름을 지을 때도 "이게 본질적으로 뭐하는 거지?"를 생각해보세요.
// 🤔 이 셋의 공통점은?
handleProductSelection(productId)
handleUserChoice(userId)
handleOrderPick(orderId)
// 💡 본질: "무언가를 선택하는 행위"
onSelect(id) // 도메인(Product/User/Order)은 벗겨내고
// 행동(Select)의 본질만 남김
TypeScript
복사
실제 코드에서는 이렇게 쓰면 됩니다:
// Don't ❌
handleSomeProduct({
clickProductSelection: (productId) => {}
})
// Do ✅
handleSomeProduct({
onSelect: (productId) => {}
})
TypeScript
복사
왜 onSelect가 더 나을까요? 이 패턴을 한 번 배운 개발자는 다른 컴포넌트에서도 onSelect를 찾을 거예요. 학습 전이가 일어나는 거죠.
‘추상화 레벨’ 맞추기
인터페이스를 설계할 때는 모든 메서드가 같은 추상화 레벨에 있어야 합니다.
// Set A
interface DataManager {
fetch()
update()
delete()
saveUserProfile() // 🤔 너무 구체적. 나머지는 일반 동작
}
// Set B
interface Modal {
open()
close()
minimize()
openWithProductData() // 🤔 도메인 특화. 나머지는 추상 동작
}
// Set C
interface Form {
validate()
submit()
reset()
validateEmailFormat() // 🤔 특정 필드 검증. 나머지는 폼 전체 동작
}
TypeScript
복사
각 세트에서 하나씩 이상한 게 보이시나요? 나머지는 일반적인 동작을 나타내는데, 하나만 특정 도메인이나 구체적인 동작을 나타내고 있죠.
언제, 그리고 어떻게 (When and How to Apply)
이 패턴은 강력하지만, 모든 곳에 동일한 기준으로 적용할 필요는 없습니다. 비용 대비 효용이 높은 곳에 집중하는 것이 현명합니다.
•
디자인 시스템 컴포넌트: Button, Modal, Input 등 앱 전반에서 재사용되는 핵심 UI 조각들.
•
공유 모듈: 여러 팀이나 프로젝트에서 함께 사용하는 기능.
•
반복되는 패턴: 앱 내에서 2~3회 이상 유사한 형태로 구현된 기능.
(예시: Modal 컴포넌트)
'유저 삭제 확인', '상품 정보 보기' 등 다양한 곳에서 쓰일 모달은 '열고 닫는' 본질에만 집중한 일반적인 인터페이스(isOpen, onClose)를 가져야 합니다.
내용은 사용처에서 자유롭게 채워넣도록 책임을 위임하는 것이 이상적입니다.
// 👍 Good: Archetype(Dialog)을 정의하고 표준 인터페이스 사용
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
}
function Modal({ isOpen, onClose, children }: ModalProps) {
if (!isOpen) return null;
// ... 복잡한 렌더링 로직 ...
}
// [사용처]
// 도메인 로직은 사용처에서 합성(Composition)
<Modal isOpen={isConfirmOpen} onClose={handleCancel}>
<h2>정말 삭제하시겠습니까?</h2>
<AsyncButton onClick={handleDeleteUser}>삭제</AsyncButton>
</Modal>
TypeScript
복사
•
일회성 컴포넌트: 특정 이벤트 페이지의 배너처럼 한번 쓰이고 버려질 것이 명확한 UI.
•
매우 특수한 도메인: 다른 곳에서 재사용될 가능성이 거의 없는 고유한 비즈니스 로직 덩어리.
(예시: MarketingCampaignBanner_2025_Fall_Event)
이 배너는 2025년 가을 이벤트 페이지에서만 사용됩니다. 굳이 value, onChange 같은 인터페이스로 일반화하기보다, 필요한 데이터를 직접 가져와 빠르게 구현하고 이벤트 종료 후 쉽게 제거하는 것이 더 효율적입니다.
모든 것을 일반화하려는 시도는 오버 엔지니어링으로 이어질 수 있습니다.
// 굳이 일반화할 필요 없음
import { useFallEventData } from './hooks/useFallEventData';
function MarketingCampaignBanner_2025_Fall_Event() {
const { title, link, promotionCode } = useFallEventData();
if (!title) return null;
return (
<div className="campaign-banner">
<h1>{title}</h1>
<a href={link}>
이벤트 참여하기 (쿠폰: {promotionCode})
</a>
</div>
);
}
TypeScript
복사
6. 전문가들의 '인터페이스 일반화' 패턴
6-1. 미니 과제 - Before
6-2. 전문가 영상
1.
요구사항을 확인하고, 구현해야 하는 대상의 동작을 파악한다.
•
ex. “디자인 보니까 가운데는 월을 표시하고, 좌우 화살표로 월을 선택할 수 있어야 하는구나.”
2.
구현해야 하는 대상의 본질을 생각해본 뒤, 이를 내가 알고 있는 일반해와 매칭해본다.
•
ex. “내가 알고 있는 것 중에 뭐랑 닮았지? Input? Modal? Slider? Calendar? Form?”
3.
매칭된 일반해가 마땅히 가지고 있어야 하는 인터페이스를 먼저 떠올리고, 그 중 필요한 인터페이스를 추가한다. 예측 가능성을 위해 가급적 웹 표준 / 주요 라이브러리의 관례를 따른다.
•
ex. “월을 선택하는 거니까 input[type=’date’]랑 비슷한 인터페이스를 써야겠다. 그러면 value, onChange, min, max 같은게 있어야겠네.”
•
ex. “프론트엔드 CRUD의 대부분은 Table 아니면 Form이야. 이건 Form으로 구현하면 돼.”
◦
“상태의 흐름은 위에서 아래로(GET), 아래에서 위로 흐르는 것 하나(POST) 총 두 방향이 있다. 개발자는 결국 이 두 가지 방향만 잘 구현하면 돼.”
◦
“그렇다면 Form(onSubmit) → FieldGroup(values, onChange) → Field(value, onChange) 의 흐름을 기본으로 그 위에 비즈니스 로직을 얹으면 되겠다”
4.
구현에 인터페이스를 맞추지 않고, 인터페이스에 맞춰 구현을 추가한다.
•
ex. "컴포넌트 인터페이스는 단순하게 유지하고 구체적인 로직은 사용처에서 처리해야 해. MonthSelector는 value와 onChange만 노출하고, 구체적인 로직은 사용처에서 onChange 콜백 안에 필요한 비즈니스 로직을 인라인으로 작성해야지."
5.
구현을 완료한 뒤 형태만으로도 동작이 예상되고 내부 구현이 궁금해지지 않는지, 즉 예측 가능한지 점검해본다.
6-3. 미니 과제 - After
7. Wrap-up
이제 도입부의 빈칸을 채워보세요!
(기본 원칙) 좋은 추상화는 함수나 컴포넌트의 이름과 파라미터, 반환 값, 즉 인터페이스만으로도 내부 동작을 예측할 수 있다. 이러한 예측 가능한 코드는 개발자가 코드를 읽을 때 다른 부분으로 시선을 이동하거나 내부 구현을 확인하느라 발생하는 인지적 부담을 크게 줄여준다.
('예측 가능한 인터페이스' 패턴 전문가 5단계)
1.
요구사항을 확인하고, 구현해야 하는 대상의 동작을 파악한다.
•
ex. “디자인 보니까 가운데는 월을 표시하고, 좌우 화살표로 월을 선택할 수 있어야 하는구나.”
2.
구현해야 하는 대상의 본질을 생각해본 뒤, 이를 내가 알고 있는 일반해와 매칭해본다.
•
ex. “내가 알고 있는 것 중에 뭐랑 닮았지? Input? Modal? Slider? Calendar? Form?”
3.
매칭된 일반해가 마땅히 가지고 있어야 하는 인터페이스를 먼저 떠올리고, 그 중 필요한 인터페이스를 추가한다. 예측 가능성을 위해 가급적 웹 표준 / 주요 라이브러리의 관례를 따른다.
•
ex. “월을 선택하는 거니까 input[type=’date’]랑 비슷한 인터페이스를 써야겠다. 그러면 value, onChange, min, max 같은게 있어야겠네.”
•
ex. “프론트엔드 CRUD의 대부분은 Table 아니면 Form이야. 이건 Form으로 구현하면 돼.”
◦
“상태의 흐름은 위에서 아래로(GET), 아래에서 위로 흐르는 것 하나(POST) 총 두 방향이 있다. 개발자는 결국 이 두 가지 방향만 잘 구현하면 돼.”
◦
“그렇다면 Form(onSubmit) → FieldGroup(values, onChange) → Field(value, onChange) 의 흐름을 기본으로 그 위에 비즈니스 로직을 얹으면 되겠다”
4.
구현에 인터페이스를 맞추지 않고, 인터페이스에 맞춰 구현을 추가한다. 이 과정에서 인터페이스는 최대한 단순하게 유지하고 구체적인 로직은 사용처에서 처리하도록 한다.
•
ex. "컴포넌트 인터페이스는 단순하게 유지하고 구체적인 로직은 사용처에서 처리해야 해. MonthSelector는 value와 onChange만 노출하고, 구체적인 로직은 사용처에서 onChange 콜백 안에 필요한 비즈니스 로직을 인라인으로 작성해야지."
5.
구현을 완료한 뒤 형태만으로도 동작이 예상되고 내부 구현이 궁금해지지 않는지, 즉 예측 가능한지 점검해본다.



