추상화와 유연성은 양립할 수 있는가?
스프린트 미션 4는 바닐라 JS로 회원가입/로그인 인증 폼을 만들고, 입력 값을 검증해서 에러 메시지를 보여주는 과제였다.
처음엔 “폼 검증 로직을 잘 나눠서 구현해보자” 정도였는데, 제출 피드백을 받고 React로 마이그레이션까지 해보니 질문이 하나로 좁혀졌다.
추상화와 유연성은 함께 가져갈 수 있을까?
이 글은 내가 했던 설계가 왜 유연하지 않았는지, 그리고 그걸 어떤 관점으로 다시 정리했는지 기록한 내용이다.
내가 말하는 “추상화”와 “유연성”의 기준
이번 경험에서 애매했던 건 단어 자체가 아니라 “판단 기준”이었다. 그래서 기준부터 정리했다.
- 추상화: 구현을 감추는 게 목적이 아니라, 변경이 일어나는 지점을 경계로 분리하는 작업
- 유연성: 요구사항이 바뀌었을 때 수정이 한 지점에서 끝나고, 연쇄적으로 깨지지 않는 성질
즉, “설정으로 다 가능해 보이는 구조”가 유연한 게 아니라 “변경 비용이 국소화되는 구조”가 유연한 구조다.
1차 시도: 바닐라 JS에서 데이터로 폼을 생성하는 구조
요구사항 자체는 단순했다.
- 회원가입 / 로그인 폼 구현
- 각 input 값 검증
- 실패 시 에러 메시지 출력
여기서 나는 “필드 정의만 있으면 렌더링/검증이 자동으로 따라오게 만들자”로 방향을 잡았다.
그래서 인증 필드를 데이터로 정의하고, 그 데이터를 기반으로 UI 생성 + 검증까지 처리하게 만들었다.
DEFAULT_VALID_FIELDS = {
index: number,
class: string,
description: string,
discriptionStyle: string,
isVisibility: Boolean,
rules: [
{
element: string,
innerContents: string,
label: {
innerContents: string,
class: string,
},
attribute: {},
options: [{ value: string, name: string }],
checkValue: (value: string) => ({
isValid: boolean,
message: string
})
}
]
}[]당시에는 “데이터 기반으로 재사용 가능하게 만들었다”라고 생각했다.
피드백: DEFAULT_VALID_FIELDS에 대한 의존성이 너무 강하다
제출 후 받은 핵심 피드백은 이거였다.
DEFAULT_VALID_FIELDS에 대한 의존성이 너무 강하다.
피드백을 내 코드 관점으로 풀면 이런 상태였다.
- 폼 구조(UI), 검증 규칙(로직), 에러 메시지(UI), 검증 함수(비즈니스 로직)까지 전부 한 객체에 섞여 있다
- 객체 구조가 조금만 바뀌어도 렌더링/검증/에러 처리 코드가 같이 흔들린다
- 필드 하나 추가/수정이 “데이터만 바꾸면 끝”이 아니라, 결국 여러 코드가 함께 수정된다
내 의도는 “데이터로 유연하게 만들기”였는데, 실제론 하나의 데이터에 모든 책임을 몰아넣은 결합 구조가 됐다.
2차 시도: React로 옮기면 해결될까?
피드백 이후 “이 패턴을 React로 옮기면 나아지지 않을까?”라고 생각했다.
- 이벤트 바인딩/상태 관리 등은 React가 더 안정적으로 처리한다
- 필드 정의를 데이터로 관리하는 건 유지한다
- 렌더링/리렌더링 관점에서 구조가 정리될 수 있다
그래서 바닐라 JS 아이디어를 React로 옮기면서 FIELDS 구조를 만들었다.
FIELDS = {
index: number;
rules: {
element: string;
isVisibility?: boolean;
ContainerAttribute?: {};
label?: {
contents: string;
};
attribute: {};
checkValue: (value, allValues) => { isValid: boolean; message: string };
}[];
}[];React 컴포넌트는 이 데이터를 받아서
- 어떤 요소를 만들지
- 어떤 label을 붙일지
- 어떤 속성을 부여할지
- 어떤 규칙으로 검증할지
를 한 번에 처리했다.
결과: 문제가 그대로 반복됐다
React로 옮겼는데도 본질은 바뀌지 않았다.
FIELDS가 바뀌면 컴포넌트 곳곳이 같이 수정된다- 검증 규칙을 조금만 바꾸고 싶어도
FIELDS구조부터 손대야 한다 - 특정 필드(비밀번호)에만 필요한 UI 옵션까지
rules에 같이 들어가서 데이터가 점점 비대해진다 checkValue가 필드 정의에 묶여서 다른 곳에서 재사용하기 애매하다
정리하면 FIELDS는 “스키마”가 아니라 God Object에 가까워졌다.
- UI를 어떻게 그릴지
- 로직을 어떻게 검증할지
- 특정 UI 기능(비밀번호 토글)까지
전부 한 덩어리로 뭉쳐 있었고, 그 결과는 “유연해 보이지만 변경에 약한 구조”였다.
내가 놓친 핵심: “데이터로 추상화” = “책임 분리”가 아니었다
멘토가 추천해준 키워드(OOP, Container/Presentational, 디자인 패턴, 3-Layers)를 따라가면서 공통점을 하나로 묶을 수 있었다.
- 책임 분리
- 계층 구조(단방향 의존성)
내 구조는 겉으론 분리돼 보였지만 실제론 계층이 없었다.
내가 만들고 싶었던 계층
-
스펙(데이터) 레벨
- “필드가 무엇인지”만 정의
-
로직 레벨
- “이 값을 어떻게 검증하는지”를 순수 함수로 관리
-
UI 레벨
- “그걸 어떻게 보여주는지”에 집중
실제 내가 만든 계층(이상하게 섞인 상태)
FIELDS안에 스펙 + 로직 + UI 옵션이 모두 들어감- 컴포넌트가 그걸 받아서 렌더링 + 검증 + 상태 처리까지 한 번에 수행함
분리는 “파일/상수” 기준으로 한 게 아니라 “책임/경계” 기준으로 해야 했는데, 나는 그걸 놓쳤다.
정리: 추상화는 경계를 만드는 일이고, 유연성은 변경을 국소화하는 성질이다
이번 경험에서 내가 얻은 결론은 이거다.
- 추상화는 “데이터로 다 밀어 넣는 것”이 아니라, 변경이 일어나는 지점에 경계를 세우는 것이다
- 유연성은 “어떤 것도 설정으로 가능하게 만드는 것”이 아니라, 수정이 필요한 순간에 연쇄 수정이 발생하지 않는 것이다
- 올바른 추상화(경계가 있는 추상화)는 유연성을 떨어뜨리는 게 아니라 오히려 유연성을 만든다
다음에 내가 해볼 방향
다음엔 아예 구조를 이렇게 가져가려고 한다.
- 필드 스펙은 “표현”만 가진다 (라벨, 타입, id 같은 최소 정보)
- 검증 함수는 필드 밖으로 빼서 “순수 함수”로 둔다
- UI는 범용 컴포넌트(Input, ErrorMessage 같은)로 나누고
- Container에서 “스펙 + 검증 조합”을 구성한다
즉, 설정 하나로 만능을 만들기보다, 조합 가능한 단위들로 나눠서 확장하는 쪽으로 가보려 한다.
팀 프로젝트가 끝나면 스프린트 미션 6을 이어서 하면서, 이번에 정리한 “경계/계층/의존성” 관점을 코드에 더 명확히 녹여볼 생각이다.