03. SoC 논쟁과 컴포넌트 혁명
03. SoC 논쟁과 컴포넌트 혁명
이 문서에서 배우는 것
- 기존 SoC(관심사의 분리)가 실제로는 무엇을 분리하고 있었는지
- React의 JSX가 이 원칙을 "위반"한 것인지 "재정의"한 것인지
- 기술별 분리 vs 기능별 분리의 실질적 차이
- 비판이 사라진 4단계 과정 — 기술 커뮤니티가 관점을 바꾸는 패턴
PM의 요구사항
"알림 뱃지의 색상을 빨간색에서 파란색으로 바꾸고, 99+ 넘으면 '99+'로 표시해주세요."
이 작업을 하려면 파일을 몇 개 열어야 할까요? 기존 방식과 React 방식의 차이를 살펴보겠습니다.
기존 SoC: 기술별 분리 (2013년까지의 "상식")
2013년까지 웹 개발의 황금률이 있었습니다:
"HTML, CSS, JavaScript를 분리하라"
index.html — 구조 (Structure)
style.css — 표현 (Presentation)
app.js — 동작 (Behavior)이것을 관심사의 분리(Separation of Concerns, SoC) 라고 불렀습니다. 모든 웹 개발 교과서, 블로그, 컨퍼런스 발표에서 이 원칙을 강조했습니다.
구체적으로 어떤 모습이었나
프로젝트 구조 (기존 SoC):
my-app/
├── index.html ← 모든 HTML이 여기
├── style.css ← 모든 CSS가 여기
└── app.js ← 모든 JavaScript가 여기index.html
<!-- 알림 뱃지 -->
<span id="notification-badge" class="badge hidden">0</span>
<!-- 채팅 뱃지 -->
<span id="chat-badge" class="badge hidden">0</span>
<!-- 장바구니 뱃지 -->
<span id="cart-badge" class="badge hidden">0</span>
<!-- ... 페이지의 다른 모든 HTML도 여기에 ... -->style.css
/* 뱃지 공통 스타일 */
.badge {
background: red;
color: white;
border-radius: 50%;
padding: 2px 6px;
font-size: 12px;
}
.badge.hidden {
display: none;
}
/* ... 페이지의 다른 모든 CSS도 여기에 ... */app.js
// 알림 뱃지 로직
function updateNotificationBadge(count) {
var el = document.getElementById('notification-badge');
el.textContent = count > 99 ? '99+' : count;
el.classList.toggle('hidden', count === 0);
}
// 채팅 뱃지 로직
function updateChatBadge(count) {
var el = document.getElementById('chat-badge');
el.textContent = count > 99 ? '99+' : count;
el.classList.toggle('hidden', count === 0);
}
// ... 페이지의 다른 모든 JS도 여기에 ...React의 JSX가 등장했을 때
// Badge.jsx — HTML과 JS가 한 파일에!
function Badge({ count }) {
if (count === 0) return null;
return (
<span className="badge">
{count > 99 ? '99+' : count}
</span>
);
}커뮤니티의 반응
2013년 개발자 커뮤니티:
"이건 SoC 위반이다!"
"HTML과 JavaScript를 섞다니, 2000년대 PHP 스파게티로 돌아가는 건가?"
"10년 동안 '분리하라'고 배웠는데 이걸 부정하는 거야?"
"관심사의 분리 원칙을 모르는 사람들이 만든 것 같다"이 비판은 논리적으로 보였습니다. 하지만 정말 기존 방식이 "관심사"를 분리하고 있었을까요?
기존 방식의 현실: "분리"의 환상
PM의 요구사항을 다시 봅시다:
"알림 뱃지의 색상을 빨간색에서 파란색으로 바꾸고, 99+ 넘으면 '99+'로 표시해주세요."
기존 SoC 방식에서 이 작업을 하려면
1단계: index.html 열기
<!-- 이 id를 기반으로 CSS와 JS가 연결됨 -->
<span id="notification-badge" class="badge hidden">0</span>"badge" 클래스가 쓰이네. CSS에서 이 클래스를 찾아야겠다.
2단계: style.css 열기
.badge {
background: red; /* ← 이걸 blue로 바꿔야 함 */
color: white;
border-radius: 50%;
padding: 2px 6px;
font-size: 12px;
}잠깐, .badge를 바꾸면 채팅 뱃지랑 장바구니 뱃지도 바뀌잖아? 알림 뱃지만 바꾸려면...
/* 알림 뱃지만을 위한 새 규칙 추가 */
#notification-badge {
background: blue; /* 알림만 파란색 */
}3단계: app.js 열기
function updateNotificationBadge(count) {
var el = document.getElementById('notification-badge');
el.textContent = count > 99 ? '99+' : count; // 이미 있네... 확인 완료
el.classList.toggle('hidden', count === 0);
}결과: 뱃지 하나를 수정하는데 파일 3개를 열었습니다
badge 색상 변경:
index.html → 구조 확인 (id, class 확인)
style.css → 스타일 수정 (다른 badge에 영향 없는지 확인)
app.js → 로직 확인 (99+ 표시가 이미 있는지)
badge 삭제:
index.html → <span> 태그 삭제
style.css → .badge 규칙... 다른 곳에서 쓰이니까 못 지움
app.js → updateNotificationBadge 함수 삭제
→ 이 함수를 호출하는 다른 곳도 찾아서 삭제
badge 재사용 (다른 페이지에서):
about.html → HTML 복사
style.css → 이미 있음 (다행)
about.js → JS 로직 복사... 또는 app.js를 about.html에서도 로드진짜 문제를 시각화
"분리"된 3개 파일이 실제로는 이렇게 연결되어 있음:
index.html style.css app.js
┌──────────┐ ┌──────────┐ ┌──────────┐
│ id="noti-│──────▶│ #noti- │ │ getElem │
│ badge" │ │ badge │ │ entById( │
│ │ │ { blue } │ │ "noti- │
│ class= │──────▶│ │ │ badge") │
│ "badge" │ │ .badge │◀──────│ .classList│
│ │ │ { ... } │ │ .toggle( │
│ │ │ │ │ "hidden")│
└──────────┘ └──────────┘ └──────────┘
│ ▲ │
│ │ │
└───────────────────┴──────────────────┘
id, class, 선택자로 서로를 강하게 참조
"기술별로 분리한 것이지, 관심사별로 분리한 게 아니었다"React의 SoC: 기능별 분리
React 방식의 프로젝트 구조
프로젝트 구조 (React SoC):
src/
├── components/
│ ├── Badge.jsx ← 뱃지 관련 모든 것
│ ├── Badge.css ← (선택) 뱃지 스타일
│ ├── Chat.jsx ← 채팅 관련 모든 것
│ ├── Chat.css
│ ├── Feed.jsx ← 피드 관련 모든 것
│ └── Feed.cssBadge 컴포넌트
// Badge.jsx — 뱃지에 관한 모든 "관심사"가 한 곳에
function Badge({ count, color = 'red' }) {
if (count === 0) return null;
const displayCount = count > 99 ? '99+' : count;
return (
<span
className="badge"
style={{ backgroundColor: color }}
>
{displayCount}
</span>
);
}/* Badge.css — 이 파일은 Badge만을 위한 스타일 */
.badge {
color: white;
border-radius: 50%;
padding: 2px 6px;
font-size: 12px;
}같은 PM 요구사항을 React로 처리하면
"알림 뱃지의 색상을 빨간색에서 파란색으로 바꾸고, 99+ 넘으면 '99+'로 표시해주세요."
// Badge.jsx 하나만 열면 됨
// 사용하는 쪽에서:
<Badge count={notificationCount} color="blue" /> {/* 알림: 파란색 */}
<Badge count={chatCount} /> {/* 채팅: 기본 빨간색 */}
<Badge count={cartCount} color="green" /> {/* 장바구니: 초록색 */}파일 1개만 열면 됩니다.
비교: 기술별 분리 vs 기능별 분리
| 작업 | 기존 SoC (기술별) | React SoC (기능별) |
|---|---|---|
| badge 수정 | 3개 파일 열기 (html + css + js) | 1개 파일 열기 (Badge.jsx) |
| badge 삭제 | 3개 파일에서 관련 코드 제거 + 다른 곳에서 참조 확인 | 1개 파일 삭제 + import 제거 |
| badge 재사용 | HTML 복사, JS 로직 복사 또는 공유 | <Badge count={3} /> |
| badge가 다른 곳에 영향 | class명 충돌 가능 | 컴포넌트가 격리됨 |
| 새 개발자가 이해 | 3개 파일을 오가며 연관 관계 파악 | Badge.jsx 하나 읽으면 됨 |
시각적 비교
기존 SoC: 기술별 분리
┌───────────────────────────────────────────────┐
│ index.html │
│ badge HTML + chat HTML + feed HTML + ... │
├───────────────────────────────────────────────┤
│ style.css │
│ badge CSS + chat CSS + feed CSS + ... │
├───────────────────────────────────────────────┤
│ app.js │
│ badge JS + chat JS + feed JS + ... │
└───────────────────────────────────────────────┘
→ "badge"를 이해하려면 3개 파일을 열고 관련 부분을 찾아야 함
React SoC: 기능별 분리
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Badge.jsx │ │ Chat.jsx │ │ Feed.jsx │
│ │ │ │ │ │
│ badge HTML │ │ chat HTML │ │ feed HTML │
│ badge CSS │ │ chat CSS │ │ feed CSS │
│ badge JS │ │ chat JS │ │ feed JS │
└──────────────┘ └──────────────┘ └──────────────┘
→ "badge"를 이해하려면 Badge.jsx 하나만 열면 됨비판이 사라진 4단계
React의 JSX에 대한 비판이 어떻게 사라졌는지, 그 과정은 기술 커뮤니티가 관점을 바꾸는 전형적인 패턴을 보여줍니다.
1단계: 써보지 않고 비판 (2013)
"JSX는 SoC 위반이다"
"HTML과 JS를 섞는 건 안티패턴이다"
"PHP 스파게티 코드의 재림이다"
→ 대부분 React를 실제로 사용해보지 않은 상태에서의 비판
→ "기존 지식"에 기반한 직관적 거부감커뮤니티 감정 그래프 (2013):
긍정 ██░░░░░░░░ 20%
부정 ████████░░ 80%2단계: 실제 사용팀의 경험 공유 (2014)
Facebook: "뉴스피드 버그 50% 감소"
Instagram: "전면 재작성 후 개발 속도 2배"
Khan Academy: "React로 전환한 후 새 기능 추가가 훨씬 쉬워졌다"
Airbnb: "React 도입 후 프론트엔드 버그가 크게 줄었다"
→ "이론적으로 나쁘다" vs "실제로 좋았다"
→ 경험이 이론을 이기기 시작블로그 포스트와 컨퍼런스 발표가 쏟아졌습니다:
"React: Rethinking Best Practices" — Pete Hunt, JSConf 2013
→ "우리가 '분리'라고 부른 건 기술의 분리였지,
관심사의 분리가 아니었습니다"커뮤니티 감정 그래프 (2014):
긍정 █████░░░░░ 50%
부정 █████░░░░░ 50%3단계: 깨달음 (2015)
"우리가 지킨 SoC는 기술의 분리(Separation of Technologies)였지,
관심사의 분리(Separation of Concerns)가 아니었다"이 문장이 커뮤니티에 퍼지면서 관점의 전환이 일어났습니다:
기존 이해:
관심사 = 기술 (HTML은 구조, CSS는 표현, JS는 동작)
→ 기술별로 파일을 나누면 관심사가 분리된다
새로운 이해:
관심사 = 기능 (뱃지, 채팅, 피드, 장바구니)
→ 기능별로 파일을 나누면 관심사가 분리된다
→ 하나의 기능을 위한 HTML+CSS+JS는 같은 관심사커뮤니티 감정 그래프 (2015):
긍정 ███████░░░ 70%
부정 ███░░░░░░░ 30%4단계: 업계 표준이 됨 (2016~)
React뿐만 아니라 다른 프레임워크들도 같은 패턴을 채택했습니다:
Vue — Single File Components (2015~)
<!-- Badge.vue — HTML + CSS + JS가 하나의 파일에 -->
<template>
<span v-if="count > 0" class="badge" :style="{ backgroundColor: color }">
{{ count > 99 ? '99+' : count }}
</span>
</template>
<script>
export default {
props: {
count: Number,
color: { type: String, default: 'red' }
}
}
</script>
<style scoped>
.badge {
color: white;
border-radius: 50%;
padding: 2px 6px;
}
</style>Svelte — 태생부터 컴포넌트 단위 (2016~)
<!-- Badge.svelte -->
<script>
export let count = 0;
export let color = 'red';
$: displayCount = count > 99 ? '99+' : count;
</script>
{#if count > 0}
<span class="badge" style="background-color: {color}">
{displayCount}
</span>
{/if}
<style>
.badge {
color: white;
border-radius: 50%;
padding: 2px 6px;
}
</style>Angular — Components (2016~, Angular 2+)
// badge.component.ts
@Component({
selector: 'app-badge',
template: `
<span *ngIf="count > 0" class="badge"
[style.backgroundColor]="color">
{{ count > 99 ? '99+' : count }}
</span>
`,
styles: [`
.badge {
color: white;
border-radius: 50%;
padding: 2px 6px;
}
`]
})
export class BadgeComponent {
@Input() count = 0;
@Input() color = 'red';
}커뮤니티 감정 그래프 (2016~):
긍정 █████████░ 90%
부정 █░░░░░░░░░ 10%
→ "컴포넌트 기반 개발"이 업계 표준이 됨
→ "JSX가 SoC를 위반한다"는 비판은 거의 사라짐4단계 과정을 시간순으로 정리
2013 ────────────────────────────────────────────── "이건 SoC 위반!"
│ (써보지 않고 비판)
│
2014 ────────────────────────────────────────────── "실제로 써봤더니..."
│ (경험 공유)
│
2015 ────────────────────────────────────────────── "우리가 틀렸다"
│ (관점 전환)
│
2016~ ────────────────────────────────────────────── 업계 표준
│ (Vue, Svelte 등 동일 패턴)
▼
이 패턴은 기술 역사에서 반복됩니다:
- "Goto 문은 해로운가?" (1968, Dijkstra)
- "OOP가 정말 은탄환인가?" (1990s)
- "함수형 프로그래밍이 주류가 될 수 있는가?" (2010s)
→ 기존 "상식"이 재검토되는 과정은 늘 비슷한 경로를 밟음실제 개발에서의 영향
코드 리뷰의 변화
기존 방식의 코드 리뷰:
PR #42: "뱃지에 새 색상 옵션 추가"
변경 파일: index.html, style.css, app.js, about.html, about.js
리뷰어: "이 5개 파일의 변경이 정합성이 맞는지 확인해야 해..."
React 방식의 코드 리뷰:
PR #42: "뱃지에 새 색상 옵션 추가"
변경 파일: Badge.jsx
리뷰어: "Badge 컴포넌트만 보면 되네."팀 협업의 변화
기존 방식:
"CSS 담당자가 .badge 클래스 바꿨는데
다른 페이지에서 깨졌어요"
→ 기술별 분업이 기능을 가로지르는 문제
React 방식:
"저는 채팅 컴포넌트 담당, 너는 피드 컴포넌트 담당"
→ 기능별 분업이 자연스러움
→ 내 컴포넌트가 네 컴포넌트에 영향을 줄 수 없음시니어 개발자의 한마디
후배: "그러면 HTML, CSS, JS를 분리하는 건 이제 나쁜 건가요?"
시니어: "나쁜 게 아니야. 맥락에 따라 다른 거지. 정적 웹사이트나 간단한 페이지라면 기술별 분리가 충분해. 하지만 인터랙션이 복잡한 앱 — 상태가 많고, 같은 UI가 여러 곳에 재사용되고, 팀이 기능별로 나뉘어 일하는 환경에서는 컴포넌트 단위가 훨씬 실용적이야."
후배: "결국 '관심사'를 어떻게 정의하느냐의 문제네요?"
시니어: "정확해. SoC 원칙 자체는 여전히 유효해. 다만 '관심사'가 HTML/CSS/JS라는 기술이 아니라, Badge/Chat/Feed라는 기능이 된 거야. 원칙은 같고, 적용 방식이 진화한 거지."
핵심 정리
- 기존 SoC의 본질: HTML/CSS/JS를 분리한 것은 "기술의 분리"였지 "관심사의 분리"가 아니었다
- 숨겨진 결합: 분리된 3개 파일이 id, class, 선택자로 서로를 강하게 참조 — 하나를 수정하면 나머지도 확인 필요
- React의 재정의: 관심사 = 기능(Badge, Chat, Feed), 하나의 기능에 필요한 HTML+CSS+JS는 같은 관심사
- 실용적 이점: 수정 시 1파일, 삭제 시 1파일 삭제+import 제거, 재사용은
<Badge />한 줄 - 비판의 소멸 과정: 써보지 않고 비판 → 경험 공유 → 관점 전환 → 업계 표준
- SoC 원칙은 유효: 원칙 자체가 바뀐 게 아니라, "관심사"의 정의가 진화한 것
다음 문서에서는 더 넓은 시각에서 PHP → AJAX → React → Server Components로 이어지는 웹 개발의 나선형 진화를 살펴봅니다.