1 / 2

03. SoC 논쟁과 컴포넌트 혁명

예상 시간: 5분

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.css

Badge 컴포넌트

// 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라는 기능이 된 거야. 원칙은 같고, 적용 방식이 진화한 거지."


핵심 정리

  1. 기존 SoC의 본질: HTML/CSS/JS를 분리한 것은 "기술의 분리"였지 "관심사의 분리"가 아니었다
  2. 숨겨진 결합: 분리된 3개 파일이 id, class, 선택자로 서로를 강하게 참조 — 하나를 수정하면 나머지도 확인 필요
  3. React의 재정의: 관심사 = 기능(Badge, Chat, Feed), 하나의 기능에 필요한 HTML+CSS+JS는 같은 관심사
  4. 실용적 이점: 수정 시 1파일, 삭제 시 1파일 삭제+import 제거, 재사용은 <Badge /> 한 줄
  5. 비판의 소멸 과정: 써보지 않고 비판 → 경험 공유 → 관점 전환 → 업계 표준
  6. SoC 원칙은 유효: 원칙 자체가 바뀐 게 아니라, "관심사"의 정의가 진화한 것

다음 문서에서는 더 넓은 시각에서 PHP → AJAX → React → Server Components로 이어지는 웹 개발의 나선형 진화를 살펴봅니다.