<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>🧪이대현의 실험실🔬</title>
        <link>https://velog.io/</link>
        <description>도전을 멈추지 않는 개발자</description>
        <lastBuildDate>Wed, 07 Oct 2026 11:13:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>🧪이대현의 실험실🔬</title>
            <url>https://velog.velcdn.com/images/daehyun_lee/profile/63690b22-bf68-4cfb-bacc-59d229aa14f2/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 🧪이대현의 실험실🔬. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/daehyun_lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[객체는 무엇을 기억하고, 무엇을 할 수 있을까? | UML 연산과 객체의 책임]]></title>
            <link>https://velog.io/@daehyun_lee/%EA%B0%9D%EC%B2%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%84-%EA%B8%B0%EC%96%B5%ED%95%98%EA%B3%A0-%EB%AC%B4%EC%97%87%EC%9D%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-UML-%EC%97%B0%EC%82%B0%EA%B3%BC-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%B1%85%EC%9E%84-0xjtola9</link>
            <guid>https://velog.io/@daehyun_lee/%EA%B0%9D%EC%B2%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%84-%EA%B8%B0%EC%96%B5%ED%95%98%EA%B3%A0-%EB%AC%B4%EC%97%87%EC%9D%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-UML-%EC%97%B0%EC%82%B0%EA%B3%BC-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%B1%85%EC%9E%84-0xjtola9</guid>
            <pubDate>Wed, 07 Oct 2026 11:13:17 GMT</pubDate>
            <description><![CDATA[<h1 id="객체는-무엇을-기억하고-무엇을-할-수-있을까--uml-연산과-객체의-책임">객체는 무엇을 기억하고, 무엇을 할 수 있을까? | UML 연산과 객체의 책임</h1>
<p>앞에서 클래스를 공부하면서 <strong>속성(Attribute)</strong>을 배웠다.</p>
<p>속성은 객체가 기억해야 하는 정보였다.</p>
<p>예를 들어 학생 객체라면 다음과 같은 정보를 가질 수 있다.</p>
<pre><code class="language-text">Student
-----------------
id
name
department
year</code></pre>
<p>그런데 객체가 정보만 가지고 있어서는 프로그램에서 아무 일도 일어나지 않는다.</p>
<p>학생은 수강 신청을 할 수도 있고,
자동차는 이동할 수도 있고,
도형은 이동하거나 크기를 변경할 수도 있다.</p>
<p>즉 객체에는</p>
<pre><code class="language-text">객체가 기억하는 것
+
객체가 할 수 있는 것</code></pre>
<p>이 함께 존재한다.</p>
<p>UML에서는 이를 각각</p>
<pre><code class="language-text">속성(Attribute)
연산(Operation)</code></pre>
<p>으로 표현한다.</p>
<p>이번에는 이 중 <strong>연산(Operation)</strong>이 무엇이고,
어떻게 찾아내며,
어떻게 UML 클래스 다이어그램으로 표현하는지를 정리해본다.</p>
<hr>
<h1 id="1-연산operation이란">1. 연산(Operation)이란?</h1>
<p>연산은 쉽게 말하면</p>
<blockquote>
<p><strong>객체가 외부에 제공하는 기능</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어 사각형 객체가 있다고 해보자.</p>
<p>사각형은 자신의 위치와 색상 같은 정보를 가지고 있을 수 있다.</p>
<pre><code class="language-text">Rectangle
-----------------
leftTop
rightBottom
lineColor
surfaceColor</code></pre>
<p>이것들은 <strong>속성</strong>이다.</p>
<p>그런데 사각형은 단순히 정보를 저장하기만 하는 것이 아니다.</p>
<pre><code class="language-text">확대한다.
축소한다.
이동한다.
색상을 변경한다.
넓이를 계산한다.</code></pre>
<p>같은 일을 할 수 있다.</p>
<p>이것들이 바로 <strong>연산</strong>이다.</p>
<p>따라서 클래스는 크게 다음처럼 볼 수 있다.</p>
<pre><code class="language-text">┌──────────────────────┐
│      Rectangle       │
├──────────────────────┤
│ leftTop              │ ← 속성
│ rightBottom          │
│ lineColor            │
│ surfaceColor         │
├──────────────────────┤
│ zoomIn()             │ ← 연산
│ zoomOut()            │
│ move()               │
│ setLineColor()       │
│ getArea()            │
└──────────────────────┘</code></pre>
<p>정리하면</p>
<pre><code class="language-text">Attribute
= 객체가 알고 있는 것

Operation
= 객체가 할 수 있는 것</code></pre>
<p>이라고 생각하면 된다.</p>
<hr>
<h1 id="2-같은-연산-이름이라도-클래스마다-동작은-다를-수-있다">2. 같은 연산 이름이라도 클래스마다 동작은 다를 수 있다</h1>
<p>예를 들어 <code>확대()</code>라는 기능을 생각해보자.</p>
<p>사각형에도 확대 기능이 있고 원에도 확대 기능이 있다.</p>
<pre><code class="language-text">Rectangle
+ zoomIn()

Circle
+ zoomIn()</code></pre>
<p>둘 다 이름은 <code>zoomIn()</code>이다.</p>
<p>하지만 실제 구현은 다르다.</p>
<p>사각형이라면</p>
<pre><code class="language-text">leftTop
rightBottom</code></pre>
<p>두 점 사이의 거리를 조정할 수 있다.</p>
<p>반면 원이라면</p>
<pre><code class="language-text">radius</code></pre>
<p>를 증가시키면 된다.</p>
<p>즉</p>
<pre><code class="language-text">Rectangle.zoomIn()
≠
Circle.zoomIn()</code></pre>
<p>구현 방법은 다를 수 있다.</p>
<p>하지만 사용자 입장에서는 둘 다</p>
<pre><code class="language-text">&quot;도형을 확대한다.&quot;</code></pre>
<p>라는 동일한 기능으로 이해할 수 있다.</p>
<p>이 개념은 이후 <strong>상속과 다형성</strong>을 이해할 때도 중요해진다.</p>
<hr>
<h1 id="3-uml에서-연산은-어떻게-표현할까">3. UML에서 연산은 어떻게 표현할까?</h1>
<p>클래스 다이어그램에서 클래스는 일반적으로 세 부분으로 나뉜다.</p>
<pre><code class="language-text">┌─────────────────────────┐
│       클래스 이름        │
├─────────────────────────┤
│          속성            │
├─────────────────────────┤
│          연산            │
└─────────────────────────┘</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">┌───────────────────────────────┐
│           Rectangle           │
├───────────────────────────────┤
│ - leftTop : Point             │
│ - rightBottom : Point         │
│ - lineColor : Color           │
├───────────────────────────────┤
│ + zoomIn(ratio : Real) : void │
│ + move(delta : Point) : void  │
│ + getArea() : Real            │
└───────────────────────────────┘</code></pre>
<p>처럼 표현한다.</p>
<p>연산의 일반적인 UML 표현은 다음과 같다.</p>
<pre><code class="language-text">가시성 연산이름(매개변수 : 타입) : 반환타입</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">+ zoomIn(ratio : Real) : void</code></pre>
<p>를 하나씩 뜯어보면</p>
<pre><code class="language-text">+           → public
zoomIn      → 연산 이름
ratio       → 매개변수 이름
Real        → 매개변수 타입
void        → 반환값 없음</code></pre>
<p>이다.</p>
<hr>
<h1 id="4-가시성은-속성에서-배운-것과-같다">4. 가시성은 속성에서 배운 것과 같다</h1>
<p>앞의 속성에서도 등장했던</p>
<pre><code class="language-text">+
-
#
~</code></pre>
<p>가 연산에도 그대로 사용된다.</p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>+</code></td>
<td>public</td>
</tr>
<tr>
<td><code>-</code></td>
<td>private</td>
</tr>
<tr>
<td><code>#</code></td>
<td>protected</td>
</tr>
<tr>
<td><code>~</code></td>
<td>package</td>
</tr>
</tbody></table>
<p>예를 들어</p>
<pre><code class="language-text">+ getArea() : Real</code></pre>
<p>이면 외부에서 접근할 수 있는 public 연산이다.</p>
<p>반면</p>
<pre><code class="language-text">- calculateArea() : Real</code></pre>
<p>이라면 클래스 내부에서 사용하기 위한 private 연산이라고 볼 수 있다.</p>
<p>즉 <strong>가시성은 속성뿐 아니라 연산에도 적용된다.</strong></p>
<hr>
<h1 id="5-매개변수와-반환-타입도-중요하다">5. 매개변수와 반환 타입도 중요하다</h1>
<p>다음 두 연산을 비교해보자.</p>
<pre><code class="language-text">setColor(c : Color) : void
getColor() : Color</code></pre>
<p>첫 번째는</p>
<pre><code class="language-text">Color를 입력받는다.
→ 반환값은 없다.</code></pre>
<p>두 번째는</p>
<pre><code class="language-text">입력값은 없다.
→ Color를 반환한다.</code></pre>
<p>따라서 UML만 보고도</p>
<pre><code class="language-text">어떤 정보를 입력받는가?
어떤 결과를 반환하는가?</code></pre>
<p>를 알 수 있다.</p>
<p>다만 <strong>분석 단계에서는 모든 구현 세부사항을 처음부터 적을 필요는 없다.</strong></p>
<p>초기 분석에서는</p>
<pre><code class="language-text">move()
resize()
calculateArea()</code></pre>
<p>처럼 핵심 기능을 먼저 찾고,</p>
<p>설계가 구체화되면서</p>
<pre><code class="language-text">move(delta : Point) : void
resize(ratio : Real) : void
calculateArea() : Real</code></pre>
<p>처럼 상세화할 수 있다.</p>
<hr>
<h1 id="6-operation과-method는-같은-말일까">6. Operation과 Method는 같은 말일까?</h1>
<p>여기서 헷갈렸던 부분이 하나 있다.</p>
<p>Java에서는 보통</p>
<pre><code class="language-java">public void move(Point delta) {
    ...
}</code></pre>
<p>를 <strong>메서드(Method)</strong>라고 한다.</p>
<p>그런데 UML에서는 왜 연산(Operation)이라고 할까?</p>
<p>둘은 비슷하지만 관점이 조금 다르다.</p>
<pre><code class="language-text">Operation
→ 객체가 제공해야 하는 기능

Method
→ 그 기능을 실제 프로그래밍 언어로 구현한 코드</code></pre>
<p>예를 들어 UML에서</p>
<pre><code class="language-text">+ move(delta : Point) : void</code></pre>
<p>라고 정의했다면,</p>
<p>Java에서는</p>
<pre><code class="language-java">public void move(Point delta) {
    ...
}</code></pre>
<p>처럼 구현할 수 있다.</p>
<p>따라서 개념적으로</p>
<pre><code class="language-text">UML의 Operation
        ↓
구현
        ↓
Java/C++의 Method</code></pre>
<p>라고 이해하면 편하다.</p>
<hr>
<h1 id="7-c에서는-연산이-멤버-함수가-된다">7. C++에서는 연산이 멤버 함수가 된다</h1>
<p>UML에서 다음과 같은 클래스가 있다고 하자.</p>
<pre><code class="language-text">Rectangle
---------------------------
- leftTop : Point
- rightBottom : Point
---------------------------
+ zoomIn(ratio : Real) : void
+ move(delta : Point) : void
+ getArea() : Real</code></pre>
<p>C++에서는 대략 다음과 같이 표현할 수 있다.</p>
<pre><code class="language-cpp">class Rectangle {

private:
    Point leftTop;
    Point rightBottom;

public:
    void zoomIn(float ratio);
    void move(Point delta);
    float getArea();
};</code></pre>
<p>그리고 함수의 실제 구현은 클래스 외부에서 작성할 수도 있다.</p>
<pre><code class="language-cpp">void Rectangle::zoomIn(float ratio) {
    ...
}</code></pre>
<p>여기서</p>
<pre><code class="language-text">Rectangle::</code></pre>
<p>은</p>
<blockquote>
<p><code>Rectangle</code> 클래스의 함수라는 뜻</p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="8-java에서는-연산이-메서드가-된다">8. Java에서는 연산이 메서드가 된다</h1>
<p>Java에서는 클래스 내부에서 바로 구현한다.</p>
<pre><code class="language-java">class Rectangle {

    private Point leftTop;
    private Point rightBottom;

    public void zoomIn(float ratio) {
        ...
    }

    public void move(Point delta) {
        ...
    }

    public float getArea() {
        ...
    }
}</code></pre>
<p>따라서</p>
<pre><code class="language-text">UML

+ zoomIn(ratio : Real) : void</code></pre>
<p>가 Java에서는</p>
<pre><code class="language-java">public void zoomIn(float ratio)</code></pre>
<p>로 대응된다.</p>
<p>UML을 코드로 연결해서 보면 연산 표현이 훨씬 이해하기 쉽다.</p>
<hr>
<h1 id="9-그런데-연산은-어떻게-찾아낼까">9. 그런데 연산은 어떻게 찾아낼까?</h1>
<p>속성에서도 가장 어려웠던 것은</p>
<blockquote>
<p>&quot;그래서 어떤 것을 속성으로 넣어야 하지?&quot;</p>
</blockquote>
<p>였다.</p>
<p>연산도 마찬가지다.</p>
<p>요구사항을 읽고</p>
<pre><code class="language-text">어떤 기능을
어느 클래스의 연산으로
넣어야 하는가?</code></pre>
<p>를 판단해야 한다.</p>
<p>자료에서는 연산을 찾는 대표적인 방법으로 다음 관점을 제시한다.</p>
<pre><code class="language-text">상태에 바탕을 둔 방법

속성에 바탕을 둔 방법

책임에 바탕을 둔 방법</code></pre>
<p>이 세 가지는 시험에서도 클래스 다이어그램을 직접 만들어보라고 하면 유용하게 사용할 수 있다.</p>
<hr>
<h1 id="10-첫-번째-방법-상태state를-보고-연산-찾기">10. 첫 번째 방법: 상태(State)를 보고 연산 찾기</h1>
<p>어떤 객체의 상태가 변한다면</p>
<blockquote>
<p><strong>&quot;무엇 때문에 이 상태가 변하는가?&quot;</strong></p>
</blockquote>
<p>를 생각해볼 수 있다.</p>
<p>엘리베이터를 예로 들어보자.</p>
<p>엘리베이터는</p>
<pre><code class="language-text">정지
올라가는 중
내려가는 중</code></pre>
<p>같은 상태를 가질 수 있다.</p>
<p>상태가 바뀌는 데에는 어떤 사건이 존재한다.</p>
<pre><code class="language-text">정지
 │
 │ goUp
 ▼
올라가는 중</code></pre>
<p>이때</p>
<pre><code class="language-text">goUp()</code></pre>
<p>이라는 연산을 찾아낼 수 있다.</p>
<p>또</p>
<pre><code class="language-text">정지
 │
 │ goDown
 ▼
내려가는 중</code></pre>
<p>이라면</p>
<pre><code class="language-text">goDown()</code></pre>
<p>을 생각할 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">상태 변화
   ↓
그 변화를 발생시키는 Event
   ↓
필요한 Operation</code></pre>
<p>이라는 흐름으로 연산을 찾을 수 있다.</p>
<hr>
<h1 id="11-상태-다이어그램과-클래스-다이어그램을-연결할-수-있다">11. 상태 다이어그램과 클래스 다이어그램을 연결할 수 있다</h1>
<p>예를 들어 엘리베이터의 상태 다이어그램에서</p>
<pre><code class="language-text">activate
shutdown
goUp
goDown
timeout
arrived</code></pre>
<p>같은 이벤트가 발견되었다고 하자.</p>
<p>그러면 클래스의 연산 후보로</p>
<pre><code class="language-text">Elevator
-------------------------
+ activate()
+ shutdown()
+ goUp(destination)
+ goDown(destination)
+ timeout()
+ arrived()</code></pre>
<p>를 생각할 수 있다.</p>
<p>즉 상태 다이어그램은 단순히 상태만 그리는 그림이 아니다.</p>
<blockquote>
<p><strong>객체에 어떤 연산이 필요한지 찾는 단서</strong></p>
</blockquote>
<p>로 사용할 수도 있다.</p>
<h3 id="⭐-시험-대비">⭐ 시험 대비</h3>
<p>상태 다이어그램이 주어지고</p>
<pre><code class="language-text">&quot;이 객체에 필요한 연산을 찾아라.&quot;</code></pre>
<p>라는 문제가 나온다면,</p>
<p><strong>상태 전이를 발생시키는 이벤트 이름을 먼저 확인</strong>해보는 것이 좋다.</p>
<hr>
<h1 id="12-두-번째-방법-속성을-보고-연산-찾기">12. 두 번째 방법: 속성을 보고 연산 찾기</h1>
<p>이번에는 속성을 보자.</p>
<pre><code class="language-text">Person
-----------------
name
age
address</code></pre>
<p>이런 속성이 있다면 처음에는 다음 연산이 떠오르기 쉽다.</p>
<pre><code class="language-text">getName()
setName()

getAge()
setAge()

getAddress()
setAddress()</code></pre>
<p>하지만 이것만 넣는 것은 좋은 객체 설계라고 보기 어렵다.</p>
<p>왜일까?</p>
<p>객체지향에서는 객체에게</p>
<pre><code class="language-text">&quot;너의 age를 줘.&quot;</code></pre>
<p>라고 한 뒤 외부에서 계산하기보다는,</p>
<p>가능하면</p>
<pre><code class="language-text">&quot;너의 나이를 증가시켜.&quot;</code></pre>
<p>처럼 <strong>객체에게 행동 자체를 요청하는 것</strong>이 더 자연스럽기 때문이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">setAge(age + 1)</code></pre>
<p>보다</p>
<pre><code class="language-text">increaseAge()</code></pre>
<p>가 객체의 의미를 더 잘 표현할 수 있다.</p>
<hr>
<h1 id="13-gettersetter를-무조건-만드는-것이-좋은-것은-아니다">13. Getter/Setter를 무조건 만드는 것이 좋은 것은 아니다</h1>
<p>Java를 처음 배우면 클래스를 만들자마자</p>
<pre><code class="language-java">getName()
setName()

getAge()
setAge()

getAddress()
setAddress()</code></pre>
<p>를 만드는 습관이 생기기 쉽다.</p>
<p>하지만 도메인 모델링에서는</p>
<blockquote>
<p><strong>&quot;이 객체가 실제로 해야 하는 행동은 무엇인가?&quot;</strong></p>
</blockquote>
<p>를 먼저 생각해야 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Person
-------------------
- name
- age
- address
-------------------
+ rename()
+ increaseAge()
+ moveTo()</code></pre>
<p>처럼 표현할 수 있다.</p>
<pre><code class="language-text">rename()
→ 이름을 변경하는 행동

increaseAge()
→ 나이를 증가시키는 행동

moveTo()
→ 주소를 변경하는 행동</code></pre>
<p>단순히 데이터를 읽고 쓰는 객체보다</p>
<p><strong>자신의 상태를 스스로 관리하는 객체</strong>가 된다.</p>
<hr>
<h1 id="14-계산할-수-있는-값은-객체에게-계산시키자">14. 계산할 수 있는 값은 객체에게 계산시키자</h1>
<p>예를 들어 일용직 근로자가 있다고 해보자.</p>
<pre><code class="language-text">DailyWorker
-------------------
workDays
dailyWage</code></pre>
<p>급여는</p>
<pre><code class="language-text">workDays × dailyWage</code></pre>
<p>로 계산할 수 있다.</p>
<p>그렇다면 외부에서</p>
<pre><code class="language-java">worker.getWorkDays() * worker.getDailyWage()</code></pre>
<p>를 하는 것보다</p>
<pre><code class="language-java">worker.getSalary()</code></pre>
<p>처럼 객체에게 요청하는 것이 자연스럽다.</p>
<pre><code class="language-text">DailyWorker
-------------------
- workDays
- dailyWage
-------------------
+ getSalary()</code></pre>
<p>객체가 자신의 데이터를 가장 잘 알고 있으므로,</p>
<blockquote>
<p><strong>그 데이터를 이용한 행동도 해당 객체가 책임지게 한다.</strong></p>
</blockquote>
<p>이것이 객체지향적인 설계와 연결된다.</p>
<hr>
<h1 id="15-세-번째-방법-책임responsibility을-보고-연산-찾기">15. 세 번째 방법: 책임(Responsibility)을 보고 연산 찾기</h1>
<p>이 방법이 도메인 모델링에서 특히 중요하다.</p>
<p>클래스가 어떤 책임을 가지고 있다면,</p>
<p>그 책임을 수행하기 위한 연산이 필요하다.</p>
<p>예를 들어 사각형의 책임이</p>
<pre><code class="language-text">생성
삭제
이동
확대
축소
색상 변경
넓이 계산</code></pre>
<p>이라면 연산 후보는 자연스럽게</p>
<pre><code class="language-text">create()
remove()
move()
zoomIn()
zoomOut()
setColor()
getArea()</code></pre>
<p>가 된다.</p>
<p>즉</p>
<pre><code class="language-text">객체의 책임
   ↓
객체가 해야 하는 행동
   ↓
Operation</code></pre>
<p>으로 연결할 수 있다.</p>
<hr>
<h1 id="16-연산-이름도-아무렇게나-지으면-안-된다">16. 연산 이름도 아무렇게나 지으면 안 된다</h1>
<p>연산 이름은 기능을 명확하게 표현해야 한다.</p>
<p>예를 들어 이자 계산 기능이 있다고 하자.</p>
<pre><code class="language-text">calculateInterest()</code></pre>
<p>와</p>
<pre><code class="language-text">getInterest()</code></pre>
<p>는 미묘하게 느낌이 다르다.</p>
<pre><code class="language-text">calculateInterest()
→ 이자를 계산하는 방법에 초점

getInterest()
→ 이자를 얻는 기능에 초점</code></pre>
<p>도메인 모델에서는 내부 구현 방법보다</p>
<blockquote>
<p><strong>사용자가 이 객체에게 무엇을 요청할 수 있는가?</strong></p>
</blockquote>
<p>를 표현하는 것이 중요하다.</p>
<p>그래서 구현 방식이 그대로 드러나는 이름보다는 <strong>객체가 제공하는 기능을 나타내는 이름</strong>이 좋다.</p>
<hr>
<h1 id="17-책임의-방향이-연산-이름에-드러나야-한다">17. 책임의 방향이 연산 이름에 드러나야 한다</h1>
<p>논문 심사 시스템을 생각해보자.</p>
<p>처음에 다음처럼 만들었다고 하자.</p>
<pre><code class="language-text">Reviewer
-------------------
+ submitPaper()</code></pre>
<p>그런데 Reviewer는 논문을 제출하는 사람인가?</p>
<p>보통 Reviewer의 책임은</p>
<pre><code class="language-text">논문을 심사한다.</code></pre>
<p>이다.</p>
<p>그렇다면</p>
<pre><code class="language-text">+ reviewPaper()</code></pre>
<p>가 더 자연스럽다.</p>
<p>즉 연산 이름을 보면</p>
<blockquote>
<p><strong>이 클래스가 무엇을 책임지고 있는지</strong></p>
</blockquote>
<p>알 수 있어야 한다.</p>
<p>좋은 연산 이름은 클래스의 책임과 일치한다.</p>
<hr>
<h1 id="18-화이트보드-시스템으로-연산-찾기">18. 화이트보드 시스템으로 연산 찾기</h1>
<p>자료에서는 화이트보드 시스템을 예제로 사용한다.</p>
<p>도형 관련 클래스에는</p>
<pre><code class="language-text">Text
Line
Oval
Rectangle
Image
FreeDrawing</code></pre>
<p>등이 있다.</p>
<p>Text 객체를 생각해보면</p>
<pre><code class="language-text">생성한다.
삭제한다.
선택한다.
선택을 해제한다.
화면에 표시한다.
숨긴다.
앞으로 이동한다.
뒤로 이동한다.
색상을 변경한다.
폰트를 변경한다.</code></pre>
<p>등의 행동이 필요하다.</p>
<p>따라서</p>
<pre><code class="language-text">Text
----------------------------
create()
remove()
select()
unselect()
show()
hide()

bringToFront()
sendToBack()

setTextColor()
setFontName()
setFontSize()
...</code></pre>
<p>같은 연산이 만들어진다.</p>
<p>여기에서도 연산은 단순히 Getter/Setter를 기계적으로 만드는 것이 아니라</p>
<pre><code class="language-text">상태
속성
책임</code></pre>
<p>을 보고 찾아낸다.</p>
<hr>
<h1 id="19-상태-기반으로-text-연산-찾기">19. 상태 기반으로 Text 연산 찾기</h1>
<p>Text 객체가 다음과 같은 상태를 가진다고 해보자.</p>
<pre><code class="language-text">생성되지 않음
     │
   create()
     ▼
선택되지 않은 상태
     │
   select()
     ▼
선택된 상태</code></pre>
<p>다시</p>
<pre><code class="language-text">선택된 상태
    │
 unselect()
    ▼
선택되지 않은 상태</code></pre>
<p>로 이동할 수 있다.</p>
<p>또</p>
<pre><code class="language-text">표시 상태
   │
 hide()
   ▼
숨김 상태</code></pre>
<p>처럼 상태가 변한다.</p>
<p>그러면 상태 변화에서 자연스럽게</p>
<pre><code class="language-text">create()
remove()
select()
unselect()
show()
hide()</code></pre>
<p>연산을 찾을 수 있다.</p>
<p>이게 바로</p>
<blockquote>
<p><strong>상태에 바탕을 둔 연산 찾기</strong></p>
</blockquote>
<p>다.</p>
<hr>
<h1 id="20-책임-기반으로-text-연산-찾기">20. 책임 기반으로 Text 연산 찾기</h1>
<p>이번에는 Text 객체가</p>
<pre><code class="language-text">앞으로 이동
뒤로 이동
맨 앞으로 이동
맨 뒤로 이동</code></pre>
<p>할 책임이 있다고 생각해보자.</p>
<p>그러면</p>
<pre><code class="language-text">bringForward()
sendBackward()
bringToFront()
sendToBack()</code></pre>
<p>같은 연산을 찾을 수 있다.</p>
<p>즉 상태가 직접 바뀌는 모습을 찾는 것뿐만 아니라,</p>
<p><strong>이 객체가 시스템에서 담당해야 하는 책임</strong>을 생각하면서 연산을 찾아낼 수 있다.</p>
<hr>
<h1 id="21-sequence-diagram에서도-연산을-찾을-수-있다">21. Sequence Diagram에서도 연산을 찾을 수 있다</h1>
<p>이 부분도 중요하다.</p>
<p>시퀀스 다이어그램에는 객체 사이의 메시지가 나타난다.</p>
<p>예를 들어</p>
<pre><code class="language-text">사용자
  │
  │ runCanvasEditingCommand()
  ▼
CanvasManager
  │
  │ selectShapeInRange()
  ▼
Canvas</code></pre>
<p>처럼 메시지가 전달된다.</p>
<p>이때</p>
<pre><code class="language-text">CanvasManager
+ runCanvasEditingCommand()

Canvas
+ selectShapeInRange()</code></pre>
<p>라는 연산이 필요하다는 것을 알 수 있다.</p>
<p>즉</p>
<blockquote>
<p><strong>객체가 받은 메시지는 그 객체가 제공해야 하는 연산의 후보가 된다.</strong></p>
</blockquote>
<h3 id="⭐-시험-대비-1">⭐ 시험 대비</h3>
<p>시퀀스 다이어그램을 보고 클래스의 연산을 채우는 문제가 나온다면</p>
<pre><code class="language-text">A → B : doSomething()</code></pre>
<p>에서</p>
<pre><code class="language-text">B 클래스
+ doSomething()</code></pre>
<p>을 먼저 생각하면 된다.</p>
<p>메시지를 <strong>받는 객체</strong>에 해당 연산이 필요하기 때문이다.</p>
<hr>
<h1 id="22-여기까지-연산-찾기를-한-번에-정리하면">22. 여기까지 연산 찾기를 한 번에 정리하면</h1>
<pre><code class="language-text">[상태를 본다]

상태 A
  │ event
  ▼
상태 B

→ event가 연산 후보</code></pre>
<pre><code class="language-text">[속성을 본다]

workDays
dailyWage

→ getSalary()</code></pre>
<pre><code class="language-text">[책임을 본다]

&quot;논문을 심사한다&quot;

→ reviewPaper()</code></pre>
<pre><code class="language-text">[시퀀스 다이어그램을 본다]

A → B : execute()

→ B.execute()</code></pre>
<p>결국 여러 모델을 서로 독립적으로 보는 것이 아니라</p>
<pre><code class="language-text">상태 다이어그램
시퀀스 다이어그램
클래스 다이어그램</code></pre>
<p>이 서로 연결되어 있다는 것을 알 수 있다.</p>
<hr>
<h1 id="23-고급-연산-클래스-연산">23. 고급 연산: 클래스 연산</h1>
<p>여기서부터 조금 더 중요한 개념이 등장한다.</p>
<p>일반적인 연산은 <strong>특정 객체 하나</strong>를 대상으로 실행된다.</p>
<p>예를 들어</p>
<pre><code class="language-java">student1.getName();
student2.getName();</code></pre>
<p>처럼 각각의 객체에 대해 호출한다.</p>
<p>그런데 어떤 기능은 특정 객체 하나에 속하지 않을 수도 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">현재 생성된 Course 객체의 총 개수</code></pre>
<p>를 알고 싶다고 해보자.</p>
<p>이 값은 특정 Course 하나의 정보가 아니다.</p>
<pre><code class="language-text">course1
course2
course3</code></pre>
<p>전체와 관련된 정보다.</p>
<p>이때 <strong>클래스 연산(Class Operation)</strong>을 사용할 수 있다.</p>
<hr>
<h1 id="24-인스턴스-연산과-클래스-연산">24. 인스턴스 연산과 클래스 연산</h1>
<p>일반적인 인스턴스 연산은</p>
<pre><code class="language-text">특정 객체가 있어야 호출 가능</code></pre>
<p>하다.</p>
<pre><code class="language-java">Course course = new Course();

course.getName();</code></pre>
<p>반면 클래스 연산은 특정 객체가 없어도 호출할 수 있다.</p>
<p>Java에서는 <code>static</code>으로 표현한다.</p>
<pre><code class="language-java">Course.getTotalCourse();</code></pre>
<p>즉</p>
<pre><code class="language-text">Instance Operation
→ 특정 객체에 속함

Class Operation
→ 클래스 자체에 속함</code></pre>
<p>이다.</p>
<hr>
<h1 id="25-⭐-static에서-중요한-점">25. ⭐ static에서 중요한 점</h1>
<p>Java에서 정적 메서드는</p>
<pre><code class="language-java">public static int getTotalCourse() {
    return total;
}</code></pre>
<p>처럼 작성한다.</p>
<p>그리고 객체 없이</p>
<pre><code class="language-java">Course.getTotalCourse();</code></pre>
<p>로 호출할 수 있다.</p>
<p>그런데 중요한 제한이 있다.</p>
<blockquote>
<p><strong>static 메서드 안에서는 일반 인스턴스 필드를 직접 사용할 수 없다.</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-java">class Course {

    String name;
    static int total;

    static void test() {

        System.out.println(total); // 가능

        System.out.println(name);  // 불가능
    }
}</code></pre>
<p>왜 그럴까?</p>
<p><code>name</code>은 객체마다 다르다.</p>
<pre><code class="language-text">course1.name
course2.name
course3.name</code></pre>
<p>그런데 static 메서드는 특정 객체 없이 호출된다.</p>
<pre><code class="language-java">Course.test();</code></pre>
<p>그러면 Java 입장에서는</p>
<pre><code class="language-text">&quot;어느 Course 객체의 name을 말하는 거지?&quot;</code></pre>
<p>를 결정할 수 없다.</p>
<p>따라서</p>
<pre><code class="language-text">static → static 직접 접근 가능
static → instance 직접 접근 불가능</code></pre>
<p>이라는 규칙이 생긴다.</p>
<h3 id="⭐-시험-대비-2">⭐ 시험 대비</h3>
<p>이 부분은 코드 문제로 만들기 굉장히 쉽다.</p>
<pre><code class="language-java">class A {

    int x = 10;
    static int y = 20;

    static void test() {
        System.out.println(y); // O
        System.out.println(x); // X
    }
}</code></pre>
<p>왜 <code>x</code>가 안 되는지를 단순 암기하지 말고</p>
<blockquote>
<p><strong>static 메서드는 특정 객체에 속하지 않기 때문</strong></p>
</blockquote>
<p>이라고 이해해두는 것이 좋다.</p>
<hr>
<h1 id="26-그런데-객체를-가지고-있다면-접근할-수-있다">26. 그런데 객체를 가지고 있다면 접근할 수 있다</h1>
<p>static 메서드에서 인스턴스 멤버를 <strong>절대로 사용할 수 없는 것</strong>은 아니다.</p>
<p>객체가 있다면 가능하다.</p>
<pre><code class="language-java">static void test() {

    Course course = new Course();

    System.out.println(course.name);
}</code></pre>
<p>여기서는</p>
<pre><code class="language-text">course라는 특정 객체</code></pre>
<p>가 존재한다.</p>
<p>따라서 어떤 객체의 <code>name</code>인지 명확하다.</p>
<p>문제는</p>
<pre><code class="language-java">System.out.println(name);</code></pre>
<p>처럼 <strong>객체를 지정하지 않고 인스턴스 필드에 직접 접근하는 것</strong>이다.</p>
<hr>
<h1 id="27-생성자는-왜-필요할까">27. 생성자는 왜 필요할까?</h1>
<p>객체를 생성했다고 생각해보자.</p>
<pre><code class="language-java">Student kim = new Student();</code></pre>
<p>객체가 만들어지긴 했다.</p>
<p>그런데 내부 값은 어떤 상태일까?</p>
<pre><code class="language-text">name = ?
age = ?
address = ?</code></pre>
<p>객체가 생성되었는데 제대로 사용할 수 없는 상태라면 문제가 된다.</p>
<p>그래서 객체가 생성되는 순간 필요한 초기화 작업을 수행한다.</p>
<p>이 역할을 하는 것이 <strong>생성자(Constructor)</strong>다.</p>
<hr>
<h1 id="28-생성자의-핵심-역할">28. 생성자의 핵심 역할</h1>
<p>생성자는</p>
<blockquote>
<p><strong>객체가 생성될 때 자동으로 호출되어 객체를 초기화하는 특별한 연산</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-java">class Student {

    String name;
    int age;

    Student() {
        name = &quot;&quot;;
        age = 0;
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">Student kim = new Student();</code></pre>
<p>를 실행하면 생성자가 자동으로 호출된다.</p>
<pre><code class="language-text">new Student()
     ↓
객체 생성
     ↓
Student() 자동 호출
     ↓
필드 초기화</code></pre>
<p>따라서 생성자의 핵심 목적은</p>
<blockquote>
<p><strong>객체를 사용 가능한 초기 상태로 만들어주는 것</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="29-생성자는-여러-개-만들-수-있다">29. 생성자는 여러 개 만들 수 있다</h1>
<p>Java에서는 생성자를 오버로딩할 수 있다.</p>
<pre><code class="language-java">Student() {
}

Student(String name) {
    this.name = name;
}

Student(String name, int age) {
    this.name = name;
    this.age = age;
}</code></pre>
<p>그러면 객체를 만드는 방법도 달라진다.</p>
<pre><code class="language-java">new Student();

new Student(&quot;Kim&quot;);

new Student(&quot;Kim&quot;, 27);</code></pre>
<p>즉</p>
<pre><code class="language-text">하나의 클래스
→ 여러 초기화 방법</code></pre>
<p>을 제공할 수 있다.</p>
<hr>
<h1 id="30-⭐-필드-초기값과-생성자-중-누가-더-강할까">30. ⭐ 필드 초기값과 생성자 중 누가 더 강할까?</h1>
<p>자료에서 강조된 부분이다.</p>
<p>예를 들어</p>
<pre><code class="language-java">class Student {

    int age = 10;

    Student(int age) {
        this.age = age;
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">Student kim = new Student(20);</code></pre>
<p>을 실행하면 최종 <code>age</code>는 얼마일까?</p>
<pre><code class="language-text">20</code></pre>
<p>이다.</p>
<p>흐름을 보면</p>
<pre><code class="language-text">필드 기본값/명시적 초기값 설정
        ↓
생성자 실행
        ↓
생성자가 다시 값 변경</code></pre>
<p>이기 때문이다.</p>
<p>따라서</p>
<blockquote>
<p><strong>생성자에서 설정한 값이 필드의 초기값보다 우선한다.</strong></p>
</blockquote>
<p>이 부분은 코드 실행 결과 문제로 나오기 좋다.</p>
<hr>
<h1 id="31-c에는-소멸자도-있다">31. C++에는 소멸자도 있다</h1>
<p>객체는 생성되기만 하는 것이 아니다.</p>
<p>언젠가는 사라진다.</p>
<p>C++에서는 객체가 소멸될 때 자동으로 호출되는 특별한 함수가 있다.</p>
<pre><code class="language-cpp">~FileHandler() {
    ...
}</code></pre>
<p>이를 <strong>소멸자(Destructor)</strong>라고 한다.</p>
<p>생성자가</p>
<pre><code class="language-text">객체가 태어날 때</code></pre>
<p>실행된다면,</p>
<p>소멸자는</p>
<pre><code class="language-text">객체가 사라질 때</code></pre>
<p>실행된다.</p>
<hr>
<h1 id="32-왜-소멸자가-필요할까">32. 왜 소멸자가 필요할까?</h1>
<p>예를 들어 객체가 파일을 열었다고 해보자.</p>
<pre><code class="language-text">객체 생성
   ↓
파일 Open
   ↓
작업
   ↓
객체 소멸</code></pre>
<p>객체가 사라졌는데 파일이 계속 열려 있으면 문제가 될 수 있다.</p>
<p>그래서</p>
<pre><code class="language-cpp">~FileHandler() {
    close();
}</code></pre>
<p>처럼 객체가 사라질 때</p>
<pre><code class="language-text">파일 닫기
메모리 해제
리소스 반환</code></pre>
<p>등의 정리 작업을 수행할 수 있다.</p>
<p>이를 이해할 때</p>
<pre><code class="language-text">Constructor
→ Resource 획득 / 초기화

Destructor
→ Resource 해제 / 정리</code></pre>
<p>로 연결하면 쉽다.</p>
<hr>
<h1 id="33-java는-c과-조금-다르다">33. Java는 C++과 조금 다르다</h1>
<p>Java는 C++처럼 개발자가 직접 사용하는 동일한 형태의 소멸자가 없다.</p>
<p>메모리는 Garbage Collector가 관리한다.</p>
<p>즉 개발자가</p>
<pre><code class="language-java">delete object;</code></pre>
<p>같은 방식으로 직접 객체 메모리를 해제하지 않는다.</p>
<p>자료에서는 <code>finalizer</code>도 설명하지만,</p>
<p>핵심적으로 기억할 것은</p>
<pre><code class="language-text">C++
→ 명시적인 Destructor 존재

Java
→ Garbage Collector가 객체 메모리 관리</code></pre>
<p>라는 차이다.</p>
<hr>
<h1 id="34-연산을-검토할-때-무엇을-봐야-할까">34. 연산을 검토할 때 무엇을 봐야 할까?</h1>
<p>연산을 전부 찾았다고 끝나는 것이 아니다.</p>
<p>마지막에는 모델을 검토해야 한다.</p>
<p>첫 번째로 확인할 것은</p>
<blockquote>
<p><strong>각 연산은 실제로 하나의 기능을 나타내는가?</strong></p>
</blockquote>
<p>이다.</p>
<p>하나의 연산이</p>
<pre><code class="language-text">학생 등록
+
결제
+
이메일 발송
+
통계 업데이트</code></pre>
<p>를 전부 처리한다면 너무 많은 책임을 가지고 있을 수 있다.</p>
<hr>
<h1 id="35-연산-이름은-구체적이고-명확한가">35. 연산 이름은 구체적이고 명확한가?</h1>
<p>다음과 같은 이름은 좋지 않다.</p>
<pre><code class="language-text">process()
doIt()
handle()
work()</code></pre>
<p>이름만 봐서는 무엇을 하는지 알기 어렵다.</p>
<p>반면</p>
<pre><code class="language-text">calculateSalary()
reviewPaper()
moveTo()
selectShape()</code></pre>
<p>같은 이름은 기능을 비교적 명확하게 알 수 있다.</p>
<p>따라서</p>
<blockquote>
<p><strong>연산 이름만 보고도 기능을 어느 정도 추측할 수 있어야 한다.</strong></p>
</blockquote>
<hr>
<h1 id="36-연산이-적절한-클래스에-들어갔는가">36. 연산이 적절한 클래스에 들어갔는가?</h1>
<p>이 부분이 도메인 모델링에서 정말 중요하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Reviewer.submitPaper()</code></pre>
<p>가 있다면</p>
<pre><code class="language-text">&quot;논문 심사자가 논문 제출을 책임지는 게 맞나?&quot;</code></pre>
<p>를 생각해야 한다.</p>
<p>책임이 잘못 배치되었다면</p>
<pre><code class="language-text">Author.submitPaper()
Reviewer.reviewPaper()</code></pre>
<p>처럼 재배치해야 한다.</p>
<p>즉 연산 검토는 단순히 메서드 이름을 확인하는 작업이 아니라</p>
<blockquote>
<p><strong>책임이 올바른 객체에게 배정되었는지 확인하는 과정</strong></p>
</blockquote>
<p>이다.</p>
<hr>
<h1 id="37-속성과-연산을-함께-보면-클래스가-보인다">37. 속성과 연산을 함께 보면 클래스가 보인다</h1>
<p>앞 장에서 속성을 배웠고 이번 장에서 연산을 배웠다.</p>
<p>둘을 합치면 이제 클래스가 훨씬 명확하게 보인다.</p>
<pre><code class="language-text">┌──────────────────────────┐
│          Student         │
├──────────────────────────┤
│ id                       │
│ name                     │
│ department               │
│ year                     │
├──────────────────────────┤
│ enrollCourse()           │
│ cancelCourse()           │
│ changeDepartment()       │
└──────────────────────────┘</code></pre>
<p>위쪽은</p>
<pre><code class="language-text">이 객체는 무엇을 알고 있는가?</code></pre>
<p>이고,</p>
<p>아래쪽은</p>
<pre><code class="language-text">이 객체는 무엇을 할 수 있는가?</code></pre>
<p>이다.</p>
<p>즉 객체는</p>
<pre><code class="language-text">State + Behavior

상태 + 행동

Attribute + Operation</code></pre>
<p>으로 생각할 수 있다.</p>
<hr>
<h1 id="38-⭐-시험에서-클래스-다이어그램을-직접-만들라고-한다면">38. ⭐ 시험에서 클래스 다이어그램을 직접 만들라고 한다면</h1>
<p>요구사항이 다음과 같다고 해보자.</p>
<pre><code class="language-text">학생은 이름과 학년을 가진다.
학생은 강좌를 신청할 수 있다.
학생은 강좌를 취소할 수 있다.</code></pre>
<p>먼저 <strong>명사</strong>를 찾는다.</p>
<pre><code class="language-text">학생
이름
학년
강좌</code></pre>
<p>여기서 클래스와 속성 후보를 찾는다.</p>
<pre><code class="language-text">Student
- name
- year

Course</code></pre>
<p>이번에는 <strong>동사</strong>를 본다.</p>
<pre><code class="language-text">신청한다.
취소한다.</code></pre>
<p>연산 후보가 된다.</p>
<pre><code class="language-text">Student
------------------
name
year
------------------
enrollCourse()
cancelCourse()</code></pre>
<p>그리고 Course가 별도의 객체라면</p>
<pre><code class="language-text">Student ───── Course</code></pre>
<p>라는 연관관계도 생각해야 한다.</p>
<p>즉 문제를 볼 때</p>
<pre><code class="language-text">명사
→ 클래스/속성 후보

동사
→ 연산 후보

상태 변화
→ 연산 후보

객체 간 메시지
→ 수신 객체의 연산 후보

다른 객체를 참조
→ 연관관계 후보</code></pre>
<p>라는 흐름으로 접근하면 된다.</p>
<hr>
<h1 id="39-이번-장에서-특히-구분해야-할-것">39. 이번 장에서 특히 구분해야 할 것</h1>
<p>이번 장을 공부하면서 비슷해 보이는 개념들을 다음처럼 구분하면 된다.</p>
<pre><code class="language-text">Attribute
= 객체가 기억하는 정보

Operation
= 객체가 제공하는 기능

Method
= Operation을 실제 코드로 구현한 것</code></pre>
<p>그리고</p>
<pre><code class="language-text">Instance Operation
= 특정 객체가 수행

Class Operation
= 클래스 자체가 수행
= Java/C++의 static</code></pre>
<p>또</p>
<pre><code class="language-text">Constructor
= 객체 생성 시 초기화

Destructor
= 객체 소멸 시 정리</code></pre>
<p>마지막으로 연산을 찾을 때는</p>
<pre><code class="language-text">State
Attribute
Responsibility
Sequence Diagram의 Message</code></pre>
<p>를 단서로 사용할 수 있다.</p>
<hr>
<h1 id="시험-대비-핵심-정리">시험 대비 핵심 정리</h1>
<p>이번 장에서 시험을 준비한다면 단순히</p>
<pre><code class="language-text">&quot;연산은 객체의 기능이다.&quot;</code></pre>
<p>만 외우는 것보다 다음 흐름을 이해하는 것이 중요하다.</p>
<pre><code class="language-text">1. UML 연산 표기 읽기

+ move(delta : Point) : void
│   │       │            │
│   │       │            └ 반환 타입
│   │       └ 매개변수
│   └ 연산 이름
└ 가시성</code></pre>
<pre><code class="language-text">2. 상태 다이어그램에서 연산 찾기

State A
  │ event
  ▼
State B

→ event가 Operation 후보</code></pre>
<pre><code class="language-text">3. 시퀀스 다이어그램에서 연산 찾기

A → B : operation()

→ B가 operation()을 제공</code></pre>
<pre><code class="language-text">4. 책임으로 연산 찾기

Reviewer
&quot;논문을 심사한다&quot;

→ reviewPaper()</code></pre>
<pre><code class="language-text">5. static 이해하기

instance method
→ 객체 필요

static method
→ 객체 없이 클래스 이름으로 호출 가능

static method
→ instance field 직접 접근 불가능</code></pre>
<pre><code class="language-text">6. 생성자

new Object()
     ↓
Constructor 자동 호출
     ↓
객체 초기화</code></pre>
<pre><code class="language-text">7. 소멸자

객체 소멸
   ↓
Destructor
   ↓
Resource 정리</code></pre>
<p>특히 코드를 보여주고</p>
<pre><code class="language-text">어떤 연산이 잘못되었는가?

static에서 접근 가능한 것은 무엇인가?

생성 후 필드 값은 무엇인가?

상태 다이어그램에서 어떤 연산을 찾아낼 수 있는가?

시퀀스 다이어그램의 메시지는 어느 클래스의 연산인가?</code></pre>
<p>같이 묻는 형태는 꼭 대비해두는 것이 좋다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>속성을 공부했을 때는 클래스를</p>
<pre><code class="language-text">&quot;데이터를 담는 상자&quot;</code></pre>
<p>처럼 생각하기 쉬웠다.</p>
<p>하지만 연산까지 배우고 나면 객체지향에서 클래스가 단순한 데이터 묶음이 아니라는 것이 보인다.</p>
<p>객체는 자신의 상태를 가지고 있고,</p>
<pre><code class="language-text">Attribute</code></pre>
<p>그 상태를 바탕으로 자신이 맡은 책임을 수행한다.</p>
<pre><code class="language-text">Operation</code></pre>
<p>그래서 좋은 클래스를 설계한다는 것은 단순히</p>
<pre><code class="language-text">필드를 잘 고르고
메서드를 많이 넣는 것</code></pre>
<p>이 아니다.</p>
<p>오히려</p>
<blockquote>
<p><strong>&quot;이 객체가 무엇을 알아야 하고, 무엇을 책임져야 하는가?&quot;</strong></p>
</blockquote>
<p>를 결정하는 과정에 가깝다.</p>
<p>앞 장의 속성이</p>
<blockquote>
<p><strong>객체가 무엇을 기억해야 하는가?</strong></p>
</blockquote>
<p>에 대한 답이었다면,</p>
<p>이번 장의 연산은</p>
<blockquote>
<p><strong>그 정보를 가지고 객체가 무엇을 해야 하는가?</strong></p>
</blockquote>
<p>에 대한 답이라고 볼 수 있다.</p>
<p>결국 좋은 도메인 모델은</p>
<pre><code class="language-text">올바른 정보(Attribute)
        +
올바른 책임(Operation)
        +
올바른 객체에 책임 배치</code></pre>
<p>가 함께 이루어져야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[누가 시스템의 흐름을 결정하는가? | 데이터 중심 스타일과 이벤트 드리븐 아키텍처]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-3</link>
            <guid>https://velog.io/@daehyun_lee/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-3</guid>
            <pubDate>Wed, 07 Oct 2026 11:05:36 GMT</pubDate>
            <description><![CDATA[<h1 id="누가-시스템의-흐름을-결정하는가--데이터-중심-스타일과-이벤트-드리븐-아키텍처">누가 시스템의 흐름을 결정하는가? | 데이터 중심 스타일과 이벤트 드리븐 아키텍처</h1>
<p>소프트웨어 아키텍처 스타일을 처음 보면 이름부터 많다.</p>
<pre><code class="language-text">Shared Repository
Blackboard
Event Broker
Event Mediator
Publish/Subscribe
...</code></pre>
<p>처음에는 전부 다른 구조처럼 보였는데, 수업을 들으면서 한 가지 질문을 던지니 조금씩 구분되기 시작했다.</p>
<blockquote>
<p><strong>&quot;이 시스템에서는 누가 다음 작업을 결정하는가?&quot;</strong></p>
</blockquote>
<p>데이터 중심 스타일에서는 여러 컴포넌트가 <strong>하나의 데이터를 어떻게 공유하는가</strong>가 중요하다.</p>
<p>이벤트 드리븐 스타일에서는 컴포넌트들이 서로 직접 호출하지 않고 <strong>이벤트가 발생했을 때 누가 어떻게 반응하는가</strong>가 중요하다.</p>
<p>그리고 같은 데이터 중심 스타일 안에서도 Shared Repository와 Blackboard는 흐름을 주도하는 주체가 다르다.</p>
<p>이번 글에서는 이 차이를 중심으로 정리해본다.</p>
<hr>
<h1 id="1-데이터-중심-스타일이란">1. 데이터 중심 스타일이란?</h1>
<p>데이터 중심 스타일(Data-Centered Style)의 가장 큰 특징은 이름 그대로 <strong>데이터가 시스템의 중심에 있다는 것</strong>이다.</p>
<p>기본적인 구조를 단순하게 그리면 다음과 같다.</p>
<pre><code class="language-text">         Component A
              │
              ▼
Component B → Data Store ← Component C
              ▲
              │
         Component D</code></pre>
<p>여러 소프트웨어 컴포넌트가 중앙의 데이터 저장소를 공유한다.</p>
<p>중요한 것은 컴포넌트 A와 B가 직접 데이터를 주고받는 것이 아니라는 것이다.</p>
<pre><code class="language-text">A ──직접 통신──&gt; B</code></pre>
<p>보다는</p>
<pre><code class="language-text">A ──&gt; Data Store &lt;── B</code></pre>
<p>의 형태가 된다.</p>
<p>즉 데이터 저장소가 컴포넌트 사이의 <strong>공유 공간</strong> 역할을 한다.</p>
<p>구조적으로는 크게 두 부분으로 나눌 수 있다.</p>
<pre><code class="language-text">Data Store
    +
Data Accessor</code></pre>
<p><code>Data Store</code>는 데이터를 보관하는 중앙 저장소이고,</p>
<p><code>Data Accessor</code>는 그 저장소에 접근하는 독립적인 소프트웨어 컴포넌트 또는 에이전트다.</p>
<p>따라서 데이터 중심 스타일에서는</p>
<blockquote>
<p><strong>데이터가 Data Accessor 사이의 의사소통 수단이 된다.</strong></p>
</blockquote>
<hr>
<h1 id="2-데이터-중심-스타일에는-두-종류가-있다">2. 데이터 중심 스타일에는 두 종류가 있다</h1>
<p>대표적인 데이터 중심 스타일은 다음 두 가지다.</p>
<pre><code class="language-text">Data-Centered Style
        │
        ├── Shared Repository
        │
        └── Blackboard</code></pre>
<p>둘 다 중앙에 데이터 저장소가 있다는 점은 같다.</p>
<p>그런데 결정적인 차이가 있다.</p>
<pre><code class="language-text">Shared Repository
→ 저장소 Passive
→ Client Active

Blackboard
→ 저장소 Active
→ Client(KS) Passive</code></pre>
<p>여기서 처음 든 의문은 이것이었다.</p>
<blockquote>
<p><strong>&quot;데이터 저장소가 능동적이라는 게 무슨 말이지?&quot;</strong></p>
</blockquote>
<hr>
<h1 id="3-active와-passive는-무엇을-의미할까">3. Active와 Passive는 무엇을 의미할까?</h1>
<p>여기서 Active와 Passive는 단순히 데이터베이스가 CPU 연산을 하느냐 마느냐의 이야기가 아니다.</p>
<p>핵심은 <strong>누가 시스템의 처리 흐름을 주도하느냐</strong>에 가깝다.</p>
<p>Shared Repository를 생각해보자.</p>
<pre><code class="language-text">Client
  │
  │ &quot;이 데이터 저장해줘&quot;
  ▼
Repository</code></pre>
<p>클라이언트가 먼저 요청한다.</p>
<p>예를 들어</p>
<pre><code class="language-sql">INSERT ...
UPDATE ...
DELETE ...
SELECT ...</code></pre>
<p>등을 요청하는 것이다.</p>
<p>Repository가 혼자 판단해서</p>
<pre><code class="language-text">&quot;새 데이터가 들어왔네?
그럼 다음 프로그램을 실행해야겠다.&quot;</code></pre>
<p>라고 결정하는 것이 아니다.</p>
<p>따라서</p>
<pre><code class="language-text">Client       → Active
Repository   → Passive</code></pre>
<p>라고 한다.</p>
<p>즉 <strong>클라이언트가 로직의 흐름을 제어한다.</strong></p>
<hr>
<h1 id="4-shared-repository-style">4. Shared Repository Style</h1>
<p>Shared Repository의 구조를 조금 더 자세히 보면 다음과 같다.</p>
<pre><code class="language-text"> Client A ──────┐
 Client B ──────┤
 Client C ──────┼──&gt; Repository
 Client D ──────┤
 Client E ──────┘</code></pre>
<h2 id="컴포넌트">컴포넌트</h2>
<pre><code class="language-text">하나의 Data Store
+
여러 Client</code></pre>
<p>로 구성된다.</p>
<h2 id="커넥터">커넥터</h2>
<p>클라이언트와 저장소가 상호작용하는 수단이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">직접적인 데이터 접근
Database Query
Procedure Call
API</code></pre>
<p>등이 될 수 있다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>&quot;클라이언트가 데이터베이스에 접근하기 위해 사용하는 수단&quot;</strong></p>
</blockquote>
<p>이라고 생각하면 된다.</p>
<h2 id="제약사항">제약사항</h2>
<p>가장 중요한 제약은</p>
<pre><code class="language-text">Data Store = Passive
Client     = Active</code></pre>
<p>라는 것이다.</p>
<p>클라이언트가 요청을 보내고 로직의 흐름을 제어한다.</p>
<hr>
<h1 id="5-shared-repository는-언제-사용할까">5. Shared Repository는 언제 사용할까?</h1>
<p>대표적으로 <strong>대량의 정보를 오랫동안 저장해야 하는 시스템</strong>에 적합하다.</p>
<p>가장 이해하기 쉬운 예가 관계형 데이터베이스 관리 시스템이다.</p>
<pre><code class="language-text">        GUI Client
            │
            ▼
        Repository
        ▲    ▲    ▲
        │    │    │
     insert update delete</code></pre>
<p>여러 클라이언트가 GUI나 Command Line 등을 통해</p>
<pre><code class="language-text">Insert
Update
Delete
Retrieve</code></pre>
<p>작업을 수행한다.</p>
<p>데이터는 중앙 저장소에 모여 있고 클라이언트들이 필요할 때 접근한다.</p>
<hr>
<h1 id="6-compiler와-ide도-repository를-사용할-수-있다">6. Compiler와 IDE도 Repository를 사용할 수 있다</h1>
<p>조금 의외였던 예제가 Compiler와 IDE다.</p>
<p>컴파일러를 생각하면 보통 다음 과정부터 떠올린다.</p>
<pre><code class="language-text">Source Code
    ↓
Lexical Analysis
    ↓
Syntax Analysis
    ↓
Semantic Analysis
    ↓
Code Generation</code></pre>
<p>Syntax Analysis 과정에서는 대표적으로 AST(Abstract Syntax Tree) 같은 구조가 만들어질 수 있다.</p>
<p>Repository 기반으로 구성하면 이러한 중간 정보를 중앙 저장소에 두고 여러 처리기가 공유할 수 있다.</p>
<pre><code class="language-text">Lexical Analyzer ──────┐
Syntax Analyzer  ──────┤
Semantic Analyzer ─────┤
                       ▼
                   Repository
                   ┌───────────┐
                   │ AST       │
                   │ Symbol    │
                   │ Grammar   │
                   │ Output    │
                   └───────────┘
                       ▲
Optimizer ─────────────┤
Code Generator ────────┘</code></pre>
<p>즉 각 처리기가 서로 직접 데이터를 넘기는 대신 Repository의 공통 데이터를 읽고 수정할 수 있다.</p>
<p>여기서 이전에 배운 Batch Sequential이나 Pipe-and-Filter와의 차이도 생각해볼 수 있다.</p>
<pre><code class="language-text">Pipe-and-Filter
A → B → C → D

Repository
      ┌→ A
      ├→ B
DB ───┼→ C
      └→ D</code></pre>
<p>Pipe-and-Filter에서는 <strong>처리 결과가 다음 처리 단계로 흐르는 구조</strong>가 중요했다.</p>
<p>Repository에서는 <strong>여러 처리기가 동일한 중앙 데이터를 공유한다는 것</strong>이 핵심이다.</p>
<hr>
<h1 id="7-shared-repository의-장점">7. Shared Repository의 장점</h1>
<p>첫 번째는 <strong>확장성(Scalability)</strong>이다.</p>
<p>특히 읽기 중심 구조에서는 수평 확장이 비교적 용이하다.</p>
<p>두 번째는 <strong>변경 용이성(Modifiability)</strong>이다.</p>
<p>새로운 클라이언트를 추가하거나 기존 클라이언트의 기능을 수정하기 쉽다.</p>
<pre><code class="language-text">기존

A ─┐
B ─┼── Repository
C ─┘


새 기능 추가

A ─┐
B ─┤
C ─┼── Repository
D ─┘</code></pre>
<p>기존 A, B, C를 모두 수정하지 않고 D를 추가할 수 있다.</p>
<p>또한 모든 컴포넌트가 같은 공간의 데이터를 공유한다.</p>
<pre><code class="language-text">Component A가 데이터 변경
          ↓
      Repository
          ↓
다른 Component도 변경된 데이터 사용</code></pre>
<p>컴포넌트끼리 계속 데이터를 복제하거나 전달할 필요가 줄어든다는 장점이 있다.</p>
<hr>
<h1 id="8-하지만-중앙-저장소가-하나라는-것은-위험하기도-하다">8. 하지만 중앙 저장소가 하나라는 것은 위험하기도 하다</h1>
<p>Shared Repository의 가장 직관적인 문제는 <strong>Single Point of Failure(SPOF)</strong>다.</p>
<pre><code class="language-text">A ─┐
B ─┤
C ─┼── Repository 💥
D ─┤
E ─┘</code></pre>
<p>모든 컴포넌트가 Repository에 의존하고 있는데 Repository가 죽으면 어떻게 될까?</p>
<p>전체 시스템이 영향을 받을 수 있다.</p>
<p>따라서 신뢰성(Reliability), 그리고 결과적으로 가용성 측면에서 문제가 될 수 있다.</p>
<p>또 다른 문제는 데이터 구조와 클라이언트 사이의 높은 의존성이다.</p>
<pre><code class="language-text">Repository Schema 변경
        ↓
Client A 수정
Client B 수정
Client C 수정
Client D 수정</code></pre>
<p>중앙 데이터 구조를 변경했는데 모든 클라이언트가 그 구조를 알고 있다면 변경의 영향 범위가 매우 커질 수 있다.</p>
<hr>
<h1 id="9-그런데-blackboard는-뭐가-다른가">9. 그런데 Blackboard는 뭐가 다른가?</h1>
<p>Blackboard 역시 중앙에 데이터를 공유한다.</p>
<p>그래서 겉으로 보면 Repository와 매우 비슷하다.</p>
<pre><code class="language-text"> KS1 ─┐
 KS2 ─┤
 KS3 ─┼── Blackboard
 KS4 ─┤
 KS5 ─┘</code></pre>
<p>하지만 가장 큰 차이는</p>
<pre><code class="language-text">Shared Repository
Client가 먼저 요청

Blackboard
Blackboard의 상태 변화가 다음 처리를 유발</code></pre>
<p>한다는 점이다.</p>
<p>Blackboard에서는 클라이언트를 특별히</p>
<pre><code class="language-text">Knowledge Source
KS
지식 소스</code></pre>
<p>라고 부른다.</p>
<p>KS를 처음 이해할 때는 <strong>특정 문제 하나를 해결할 줄 아는 독립적인 Agent</strong>처럼 생각하면 편하다.</p>
<hr>
<h1 id="10-knowledge-source는-무엇인가">10. Knowledge Source는 무엇인가?</h1>
<p>예를 들어 하나의 복잡한 문제가 있다고 하자.</p>
<pre><code class="language-text">Raw Data
   ↓
정보 추출
   ↓
Tree 생성
   ↓
계층 구조 생성
   ↓
최종 결과</code></pre>
<p>이 모든 것을 하나의 거대한 프로그램이 처리하도록 만들 수도 있다.</p>
<p>하지만 Blackboard에서는 문제를 독립적인 해결 단위로 분해한다.</p>
<pre><code class="language-text">KS1 = Raw Data에서 정보 추출

KS2 = 추출된 정보로 Tree 생성

KS3 = 여러 Tree를 계층 구조로 구성</code></pre>
<p>각 KS는 자신이 해결할 수 있는 문제만 알고 있다.</p>
<p>그래서 중요한 것은</p>
<blockquote>
<p><strong>KS들을 얼마나 잘 정의하느냐</strong></p>
</blockquote>
<p>이다.</p>
<p>즉 복잡한 문제를 <strong>독립적인 해결 단위로 얼마나 잘 분해할 수 있는가</strong>가 Blackboard 설계의 핵심이 된다.</p>
<hr>
<h1 id="11-blackboard는-어떻게-동작할까">11. Blackboard는 어떻게 동작할까?</h1>
<p>처음 Blackboard에 Raw Data가 들어왔다고 생각해보자.</p>
<pre><code class="language-text">Blackboard

[ Raw Data ]</code></pre>
<p>Control이 Blackboard의 상태 변화를 관찰한다.</p>
<pre><code class="language-text">&quot;Raw Data가 들어왔네?&quot;</code></pre>
<p>현재 상태를 처리할 수 있는 KS를 찾는다.</p>
<pre><code class="language-text">KS1 : Raw → 정보 추출</code></pre>
<p>KS1이 실행된다.</p>
<pre><code class="language-text">Blackboard

[ Raw Data ]
     ↓
[ 추출된 정보 ]</code></pre>
<p>Blackboard의 상태가 다시 바뀌었다.</p>
<p>그러면 또 조건을 확인한다.</p>
<pre><code class="language-text">&quot;추출된 정보가 존재하네?&quot;

→ KS2 실행</code></pre>
<p>KS2가 Tree를 만든다.</p>
<pre><code class="language-text">Blackboard

Raw Data
   ↓
추출된 정보
   ↓
Tree</code></pre>
<p>상태가 또 변경된다.</p>
<pre><code class="language-text">&quot;Tree가 생겼네?&quot;

→ KS3 실행</code></pre>
<p>결국</p>
<pre><code class="language-text">Raw Data
   ↓
[KS1]
   ↓
Extracted Data
   ↓
[KS2]
   ↓
Tree
   ↓
[KS3]
   ↓
Hierarchy</code></pre>
<p>처럼 문제가 <strong>단계적으로 해결</strong>된다.</p>
<hr>
<h1 id="12-누가-ks를-호출하는가">12. 누가 KS를 호출하는가?</h1>
<p>여기서 중요한 질문이 생긴다.</p>
<blockquote>
<p><strong>&quot;Blackboard가 직접 KS를 호출하는 건가?&quot;</strong></p>
</blockquote>
<p>조금 더 정확하게는 <code>Control</code>이 존재한다.</p>
<pre><code class="language-text">            ┌───────────┐
            │  Control  │
            └─────┬─────┘
                  │ 상태 관찰
                  ▼
            ┌───────────┐
            │ Blackboard│
            └───────────┘
              ▲   ▲   ▲
              │   │   │
             KS1 KS2 KS3</code></pre>
<p>Control은 Blackboard의 변화를 관찰하고</p>
<blockquote>
<p><strong>다음에 어떤 Action을 실행할 것인가?</strong></p>
</blockquote>
<p>를 결정한다.</p>
<p>그리고 조건에 맞는 KS를 활성화한다.</p>
<p>따라서 Blackboard에서 중요한 것은 단순히 데이터를 저장하는 것이 아니다.</p>
<pre><code class="language-text">현재 Blackboard 상태
        ↓
실행 가능한 KS 판단
        ↓
KS 실행
        ↓
Blackboard 상태 변경
        ↓
다시 실행 가능한 KS 판단
        ↓
...</code></pre>
<p>이 순환 구조가 핵심이다.</p>
<hr>
<h1 id="13-repository와-blackboard를-비교하면-확실해진다">13. Repository와 Blackboard를 비교하면 확실해진다</h1>
<pre><code class="language-text">[Shared Repository]

Client
  │
  │ 요청
  ▼
Repository

&quot;Client가 뭘 할지 결정한다.&quot;


[Blackboard]

Blackboard 상태 변경
        ↓
      Control
        ↓
실행할 KS 결정
        ↓
       KS
        ↓
Blackboard 변경

&quot;현재 문제 상태에 따라 다음 처리가 결정된다.&quot;</code></pre>
<p>그래서 수업에서 말한</p>
<pre><code class="language-text">Repository
저장소 Passive
Client Active

Blackboard
저장소 Active
Client Passive</code></pre>
<p>라는 차이를 이해할 수 있다.</p>
<hr>
<h1 id="14-blackboard의-대표적인-활용-음성-인식">14. Blackboard의 대표적인 활용: 음성 인식</h1>
<p>Blackboard가 잘 어울리는 대표적인 문제가 <strong>음성 인식</strong>이다.</p>
<p>음성 인식은 한 번의 처리로 답이 나오는 단순한 문제가 아니다.</p>
<p>예를 들어 개념적으로</p>
<pre><code class="language-text">음성
 ↓
Segmentation
 ↓
Syllable Creation
 ↓
Word Creation
 ↓
결과</code></pre>
<p>처럼 여러 전문적인 처리 과정이 필요할 수 있다.</p>
<p>각 처리기를 KS로 구성한다.</p>
<pre><code class="language-text">KS1 → Segmentation
KS2 → Syllable Creation
KS3 → Word Creation</code></pre>
<p>그리고 Blackboard의 현재 상태를 보고 어떤 KS가 현재 문제 해결에 기여할 수 있는지 판단한다.</p>
<pre><code class="language-text">새로운 입력 발생
      ↓
Blackboard 상태 확인
      ↓
실행 가능한 KS 선택
      ↓
KS 동작
      ↓
Blackboard Update
      ↓
다시 상태 확인</code></pre>
<p>이 과정이 반복되면서 답에 가까워진다.</p>
<hr>
<h1 id="15-blackboard의-장점">15. Blackboard의 장점</h1>
<p>가장 큰 장점은 <strong>기능 확장성(Extensibility)</strong>이다.</p>
<p>KS가 독립적으로 구성되어 있다면</p>
<pre><code class="language-text">KS1
KS2
KS3</code></pre>
<p>에 새로운 기능이 필요할 때</p>
<pre><code class="language-text">KS1
KS2
KS3
KS4 ← 추가</code></pre>
<p>처럼 새로운 Knowledge Source를 추가할 수 있다.</p>
<p>따라서 변경 용이성과 재사용성도 좋아질 수 있다.</p>
<p>또 KS들이 충분히 독립적이라면 여러 KS를 동시에 실행할 수 있다.</p>
<pre><code class="language-text">       ┌→ KS1
BB ────┼→ KS2
       └→ KS3</code></pre>
<p>즉 <strong>Concurrency</strong>를 활용할 수 있고 이는 성능과 확장성으로 이어질 수 있다.</p>
<hr>
<h1 id="16-blackboard의-단점">16. Blackboard의 단점</h1>
<p>하지만 모든 KS가 Blackboard 구조를 알고 있다.</p>
<p>따라서 Blackboard의 데이터 구조 자체가 변경되면</p>
<pre><code class="language-text">Blackboard 구조 변경
       ↓
KS1 영향
KS2 영향
KS3 영향
KS4 영향</code></pre>
<p>처럼 전체 KS가 영향을 받을 수 있다.</p>
<p>또 하나의 어려움은</p>
<blockquote>
<p><strong>&quot;언제 문제 해결이 끝났다고 판단할 것인가?&quot;</strong></p>
</blockquote>
<p>이다.</p>
<p>KS가 Blackboard를 변경하고,</p>
<p>그 변경이 다른 KS를 실행시키고,</p>
<p>다시 Blackboard가 변경되는 구조이기 때문이다.</p>
<p>여러 KS가 동시에 실행될 수도 있기 때문에 시스템 동작이 항상 똑같은 순서로 나타난다고 보장하기도 어렵다.</p>
<p>따라서 시스템 설계와 테스트가 어려워지고 <strong>시험 용이성(Testability)</strong>이 떨어질 수 있다.</p>
<hr>
<h1 id="17-이제-완전히-다른-관점-event-driven-architecture">17. 이제 완전히 다른 관점: Event-Driven Architecture</h1>
<p>앞에서는 중앙 데이터가 중심이었다.</p>
<p>이번에는 <strong>Event가 중심</strong>이다.</p>
<p>기존 프로그램에서는 보통 특정 대상을 직접 호출한다.</p>
<pre><code class="language-text">A
│
├── call B()
│
└── call C()</code></pre>
<p>이것을 명시적 호출(Explicit Invocation)이라고 볼 수 있다.</p>
<p>A가 B를 정확히 알고 있다.</p>
<p>반면 이벤트 기반에서는</p>
<pre><code class="language-text">A
│
└── &quot;OrderCreated 발생!&quot;
           ↓
       Event System
       ↙        ↘
      B          C</code></pre>
<p>A가 B나 C를 직접 호출하지 않는다.</p>
<p>A는 단지</p>
<pre><code class="language-text">OrderCreated</code></pre>
<p>라는 이벤트를 발행한다.</p>
<p>그리고 그 이벤트에 관심 있는 컴포넌트가 반응한다.</p>
<p>이것이 <strong>묵시적 호출(Implicit Invocation)</strong>이다.</p>
<hr>
<h1 id="18-message와-event는-같은-것일까">18. Message와 Event는 같은 것일까?</h1>
<p>둘은 완전히 같은 말은 아니다.</p>
<p><code>Message</code>는 컴포넌트 사이에서 전달되는 <strong>데이터 단위</strong>다.</p>
<p>Message 안에는 여러 의미가 들어갈 수 있다.</p>
<pre><code class="language-text">Message
├── Event
├── Command
└── Information</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">CreateOrder</code></pre>
<p>는 특정 작업을 수행하라는 Command가 될 수 있다.</p>
<p>반면</p>
<pre><code class="language-text">OrderCreated</code></pre>
<p>는</p>
<blockquote>
<p>&quot;주문이 이미 생성되었다.&quot;</p>
</blockquote>
<p>라는 상태 변화를 알리는 Event다.</p>
<p>따라서 Event는 보통 직접적인 응답을 요구하기보다</p>
<pre><code class="language-text">&quot;이런 일이 발생했다.&quot;</code></pre>
<p>라고 알리는 데 초점이 있다.</p>
<hr>
<h1 id="19-event-broker-style">19. Event Broker Style</h1>
<p>첫 번째 이벤트 기반 스타일은 <strong>Event Broker</strong>다.</p>
<p>구조는 다음처럼 생각할 수 있다.</p>
<pre><code class="language-text">Publisher
    │
    │ Event
    ▼
┌──────────┐
│  Broker  │
└──────────┘
  │   │   │
  ▼   ▼   ▼
 S1   S2   S3</code></pre>
<p>Broker는 발행자가 보낸 이벤트를 받아 적절한 구독자에게 전달한다.</p>
<p>주요 컴포넌트는</p>
<pre><code class="language-text">Event Publisher
Event Subscriber
Event Broker
[Event Stream Processor]</code></pre>
<p>다.</p>
<p>Publisher는 이벤트를 생성한다.</p>
<p>Subscriber는 자신이 관심 있는 이벤트를 비동기적으로 받아 처리한다.</p>
<p>Broker는 이벤트를 수집하고 라우팅한다.</p>
<hr>
<h1 id="20-broker가-workflow까지-결정하는-것은-아니다">20. Broker가 Workflow까지 결정하는 것은 아니다</h1>
<p>여기서 굉장히 중요한 차이가 있다.</p>
<p>Broker의 핵심 역할은</p>
<pre><code class="language-text">&quot;이 이벤트를 누구에게 전달할까?&quot;</code></pre>
<p>이다.</p>
<p>즉 <strong>메시지를 전달하고 라우팅하는 역할</strong>에 가깝다.</p>
<p>전체 업무 흐름을 중앙에서 완전히 제어하는 것은 아니다.</p>
<p>예를 들어 주문 시스템을 생각해보자.</p>
<pre><code class="language-text">OrderCreated
     ↓
Payment
     ↓
Inventory
     ↓
Shipping</code></pre>
<p>Broker 스타일에서는 각각의 컴포넌트가 이벤트를 받아 처리하고 다시 다음 이벤트를 발생시킬 수 있다.</p>
<pre><code class="language-text">OrderCreated
     ↓
Payment Service
     ↓
PaymentCompleted
     ↓
Inventory Service
     ↓
InventoryUpdated
     ↓
Shipping Service</code></pre>
<p>각 컴포넌트는 다른 컴포넌트를 직접 알 필요가 없다.</p>
<p>그래서 결합도가 낮아진다.</p>
<p>하지만 반대로 생각하면</p>
<blockquote>
<p>&quot;지금 주문이 전체 과정 중 어디까지 왔지?&quot;</p>
</blockquote>
<p>를 중앙에서 파악하기 어려울 수 있다.</p>
<hr>
<h1 id="21-그래서-event-mediator가-등장한다">21. 그래서 Event Mediator가 등장한다</h1>
<p>복잡한 Workflow를 관리해야 한다면 중간에 <strong>Event Mediator</strong>를 둘 수 있다.</p>
<pre><code class="language-text">             Event
               │
               ▼
        ┌─────────────┐
        │  Mediator   │
        └─────────────┘
          │    │    │
          ▼    ▼    ▼
       Payment Stock Shipping</code></pre>
<p>Mediator는 단순히 이벤트를 전달하는 것을 넘어</p>
<pre><code class="language-text">현재 주문이 어디까지 진행되었는가?

다음에는 어떤 작업을 해야 하는가?

실패했다면 재시도해야 하는가?

보상 처리가 필요한가?</code></pre>
<p>등을 판단하면서 <strong>Workflow를 관리한다.</strong></p>
<hr>
<h1 id="22-배송-시스템으로-broker와-mediator-비교하기">22. 배송 시스템으로 Broker와 Mediator 비교하기</h1>
<p>Broker 방식이라면</p>
<pre><code class="language-text">OrderCreated
     ↓
 Payment
     ↓
PaymentCompleted
     ↓
 Inventory
     ↓
InventoryUpdated
     ↓
 Shipping</code></pre>
<p>처럼 이벤트가 이어진다.</p>
<p>각 서비스는 자신에게 필요한 이벤트에만 반응한다.</p>
<p>반면 Mediator 방식은</p>
<pre><code class="language-text">             ┌───────────────┐
             │ Order Mediator│
             └───────────────┘
               │    │    │
         ┌─────┘    │    └─────┐
         ▼          ▼          ▼
      Payment    Inventory   Shipping</code></pre>
<p>Mediator가 전체 진행 상황을 알고 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">1. 주문 생성
2. 결제 요청
3. 결제 완료 확인
4. 재고 확인
5. 배송 요청</code></pre>
<p>순서를 직접 관리할 수 있다.</p>
<p>따라서 네 필기의</p>
<blockquote>
<p>&quot;배송 중 상태에서 다시 이전 단계로 못 넘어가게 한다?&quot;</p>
</blockquote>
<p>라는 생각은 <strong>워크플로우 제어라는 방향에서 이해하면 된다.</strong></p>
<p>정확히는 단순히 이전 단계로 못 가게 하는 것만이 목적은 아니다.</p>
<p>Mediator가</p>
<pre><code class="language-text">현재 상태
다음 상태
실패 처리
재시도
보상 처리</code></pre>
<p>등 전체 Workflow를 관리할 수 있다는 것이 핵심이다.</p>
<hr>
<h1 id="23-broker-vs-mediator">23. Broker vs Mediator</h1>
<p>두 구조의 차이는 다음 질문으로 기억하면 편하다.</p>
<pre><code class="language-text">Broker

&quot;누구에게 전달할까?&quot;</code></pre>
<pre><code class="language-text">Mediator

&quot;다음에는 무엇을 해야 하지?&quot;</code></pre>
<p>Broker는 서비스들을 강하게 분리할 수 있다.</p>
<p>반면 Mediator는 전체 흐름을 더 쉽게 통제할 수 있다.</p>
<p>그 대신 중앙의 Mediator에 대한 의존성이 생긴다.</p>
<hr>
<h1 id="24-event-broker의-장점과-단점">24. Event Broker의 장점과 단점</h1>
<p>Broker 방식의 가장 큰 장점은 <strong>Decoupling</strong>이다.</p>
<p>이벤트 구독자들이 서로를 직접 알 필요가 없다.</p>
<pre><code class="language-text">A → Broker → B
           → C
           → D</code></pre>
<p>따라서 각각 독립적으로 확장하기 쉽고 높은 확장성과 응답성, 성능을 기대할 수 있다.</p>
<p>또 하나 중요한 장점은 내고장성이다.</p>
<p>각 컴포넌트가 분리되어 있기 때문에 하나의 서비스 장애가 반드시 전체 시스템 장애로 이어지는 구조는 아니다.</p>
<p>하지만 전체 Workflow를 제어하기 어렵다.</p>
<pre><code class="language-text">이 이벤트가 마지막인가?

모든 처리가 끝났나?

중간에서 실패했나?</code></pre>
<p>를 파악하기 어려울 수 있다.</p>
<p>또 비동기 처리 때문에 복구와 재시작이 복잡해질 수 있으며 <strong>데이터 비일관성</strong> 문제도 발생할 수 있다.</p>
<hr>
<h1 id="25-event-mediator의-장점과-단점">25. Event Mediator의 장점과 단점</h1>
<p>Mediator의 장점은 Broker의 약점을 반대로 생각하면 된다.</p>
<pre><code class="language-text">Workflow Control
Error Handling
Recoverability
Restartability
Data Consistency</code></pre>
<p>같은 부분을 관리하기 쉬워진다.</p>
<p>Mediator가 전체 Workflow를 알고 있기 때문이다.</p>
<p>하지만 그 대가가 있다.</p>
<p>Mediator에 대한 의존성이 커지고 Broker보다 확장성과 성능이 다소 떨어질 수 있다.</p>
<p>또 Mediator 자체가 중요한 역할을 담당하기 때문에 내고장성 측면에서도 불리해질 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">Broker
→ 자유롭게 분산
→ 흐름 통제 어려움

Mediator
→ 흐름 통제 쉬움
→ 중앙 의존 증가</code></pre>
<p>라는 Trade-off가 생긴다.</p>
<hr>
<h1 id="26-queue와-topic은-어떻게-다를까">26. Queue와 Topic은 어떻게 다를까?</h1>
<p>이벤트 시스템을 공부하면 Queue와 Topic도 등장한다.</p>
<p>가장 단순하게 구분하면 다음과 같다.</p>
<h2 id="queue">Queue</h2>
<pre><code class="language-text">Producer
   │
   ▼
 Queue
   │
   ▼
Consumer</code></pre>
<p>특정 메시지를 소비자가 가져가 처리하는 P2P 구조로 생각할 수 있다.</p>
<h2 id="topic">Topic</h2>
<pre><code class="language-text">             ┌→ Subscriber A
Publisher → Topic
             └→ Subscriber B</code></pre>
<p>Publisher가 Topic에 메시지를 발행하면 해당 Topic을 구독한 Subscriber들이 메시지를 받을 수 있다.</p>
<p>그래서</p>
<pre><code class="language-text">Queue
→ 특정 소비자를 향한 메시지 전달

Topic
→ 하나의 이벤트를 여러 구독자가 관심에 따라 수신</code></pre>
<p>이라는 관점으로 시작하면 이해하기 쉽다.</p>
<hr>
<h1 id="27-이벤트-기반-시스템의-공통적인-장점">27. 이벤트 기반 시스템의 공통적인 장점</h1>
<p>첫 번째는 <strong>익명성(Anonymity)</strong>이다.</p>
<p>메시지 소비자는 반드시 다음을 알 필요가 없다.</p>
<pre><code class="language-text">누가 메시지를 만들었는가?
어디에 있는가?
언제 만들었는가?</code></pre>
<p>이 때문에 컴포넌트 사이의 결합을 줄일 수 있다.</p>
<p>두 번째는 <strong>Concurrency</strong>다.</p>
<p>생산자와 소비자, 여러 소비자가 독립적으로 동작할 수 있다.</p>
<p>세 번째는 메시지 전달의 신뢰성을 조절할 수 있다는 점이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">메시지 승인
메시지 우선순위
메시지 만료</code></pre>
<p>등을 설정할 수 있다.</p>
<hr>
<h1 id="28-이벤트-기반-시스템의-단점">28. 이벤트 기반 시스템의 단점</h1>
<p>하지만 비동기 시스템에서는 순서를 예측하기 어려워진다.</p>
<pre><code class="language-text">Event 발생
   ↓
Listener A
Listener B
Listener C</code></pre>
<p>이때</p>
<pre><code class="language-text">A → B → C</code></pre>
<p>순서로 반드시 끝난다고 보장하기 어려울 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">응답 순서 예측
종료 시점 판단
디버깅
검증</code></pre>
<p>이 어려워진다.</p>
<p>메시지 큐 자체의 용량 문제나 메시지 처리에 따른 오버헤드도 고려해야 한다.</p>
<p>즉</p>
<blockquote>
<p><strong>&quot;자동으로 반응해서 편하다&quot;</strong></p>
</blockquote>
<p>는 장점의 반대편에는</p>
<blockquote>
<p><strong>&quot;누가 언제 어떤 순서로 반응할지 추적하기 어렵다&quot;</strong></p>
</blockquote>
<p>라는 문제가 존재한다.</p>
<hr>
<h1 id="29-blackboard와-event-driven은-비슷해-보인다">29. Blackboard와 Event-Driven은 비슷해 보인다</h1>
<p>여기까지 보면 한 가지 의문이 생긴다.</p>
<p>Blackboard도</p>
<pre><code class="language-text">상태 변경
→ KS 실행</code></pre>
<p>이고 Event-Driven도</p>
<pre><code class="language-text">Event 발생
→ Subscriber 실행</code></pre>
<p>이다.</p>
<p>둘이 굉장히 비슷해 보인다.</p>
<p>실제로 상태 변화가 다른 컴포넌트의 동작을 유발한다는 점에서는 비슷한 모습을 가질 수 있다.</p>
<p>하지만 <strong>아키텍처의 중심이 다르다.</strong></p>
<pre><code class="language-text">Blackboard
→ 공유된 문제 상태(Data)를 중심으로 협력
→ 여러 KS가 부분적인 해법을 적용
→ 문제를 점진적으로 해결

Event-Driven
→ Event를 중심으로 통신
→ Publisher와 Subscriber의 결합을 줄임
→ 사건 발생에 따라 독립적인 Component가 반응</code></pre>
<p>즉 Blackboard에서는</p>
<blockquote>
<p><strong>&quot;현재 문제의 상태가 무엇인가?&quot;</strong></p>
</blockquote>
<p>가 중요하고,</p>
<p>Event-Driven에서는</p>
<blockquote>
<p><strong>&quot;무슨 사건이 발생했는가?&quot;</strong></p>
</blockquote>
<p>가 중요하다.</p>
<hr>
<h1 id="30-결국-아키텍처에는-무조건-좋은-구조가-없다">30. 결국 아키텍처에는 무조건 좋은 구조가 없다</h1>
<p>마지막으로 가장 중요한 내용이다.</p>
<p>데이터베이스 구조를 다음 세 가지로 생각해보자.</p>
<pre><code class="language-text">1. Monolithic DB

Service A ─┐
Service B ─┼── One DB
Service C ─┘</code></pre>
<pre><code class="language-text">2. Domain별 DB

Domain A ── DB A
Domain B ── DB B</code></pre>
<pre><code class="language-text">3. Service별 DB

Service A ── DB A
Service B ── DB B
Service C ── DB C</code></pre>
<p>처음 보면</p>
<pre><code class="language-text">&quot;서비스별 DB가 가장 분리되어 있으니까
3번이 제일 좋은 것 아닌가?&quot;</code></pre>
<p>라는 생각이 들 수 있다.</p>
<p>하지만 아키텍처에서는 그렇게 단순하게 판단할 수 없다.</p>
<hr>
<h1 id="31-무엇을-중요하게-생각하느냐에-따라-답이-달라진다">31. 무엇을 중요하게 생각하느냐에 따라 답이 달라진다</h1>
<p>평가해야 하는 품질 속성이 있기 때문이다.</p>
<pre><code class="language-text">성능
구축 용이성
변경 용이성
내고장성
확장성</code></pre>
<p>예를 들어 하나의 DB를 공유하면</p>
<pre><code class="language-text">구조 단순
관리 편리
데이터 일관성 관리 상대적으로 쉬움</code></pre>
<p>이라는 장점이 있을 수 있다.</p>
<p>하지만 DB가 하나이므로 장애가 전체 시스템에 영향을 줄 가능성이 커지고 서비스별 독립 확장에도 제약이 생길 수 있다.</p>
<p>반대로 서비스마다 DB를 분리하면</p>
<pre><code class="language-text">서비스 독립성 ↑
변경 독립성 ↑
확장성 ↑
장애 격리 ↑</code></pre>
<p>같은 장점을 얻을 수 있다.</p>
<p>하지만</p>
<pre><code class="language-text">운영 복잡성 ↑
데이터 일관성 관리 난이도 ↑
분산 트랜잭션 문제 ↑</code></pre>
<p>같은 비용을 지불해야 한다.</p>
<p>Domain별 DB는 그 사이에서 하나의 절충안이 될 수 있다.</p>
<hr>
<h1 id="32-그래서-몇-번이-제일-좋아요라는-질문에는-답이-없다">32. 그래서 &quot;몇 번이 제일 좋아요?&quot;라는 질문에는 답이 없다</h1>
<p>이게 이번 내용에서 가장 중요한 결론이라고 생각한다.</p>
<pre><code class="language-text">1번이 나쁘고
3번이 좋다</code></pre>
<p>가 아니다.</p>
<p>만약</p>
<pre><code class="language-text">&quot;나는 확장성과 서비스 독립성이 굉장히 중요해.&quot;</code></pre>
<p>라면 서비스별 분리를 적극적으로 고려할 수 있다.</p>
<p>반대로</p>
<pre><code class="language-text">&quot;시스템이 작고 구축과 운영의 단순성이 훨씬 중요해.&quot;</code></pre>
<p>라면 굳이 모든 것을 서비스별로 쪼갤 필요가 없다.</p>
<p>또</p>
<pre><code class="language-text">&quot;어느 정도 독립성은 필요하지만
서비스마다 DB를 운영하는 복잡성까지 감당하고 싶지는 않아.&quot;</code></pre>
<p>라면 Domain 단위의 분리를 선택할 수도 있다.</p>
<p>결국 아키텍처 설계는</p>
<blockquote>
<p><strong>&quot;어떤 구조가 가장 좋은가?&quot;</strong></p>
</blockquote>
<p>를 찾는 문제가 아니라</p>
<blockquote>
<p><strong>&quot;내 시스템에서 가장 중요한 품질 속성은 무엇이고, 그 속성을 얻기 위해 어떤 비용을 감수할 것인가?&quot;</strong></p>
</blockquote>
<p>를 결정하는 문제에 가깝다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>처음에는 Shared Repository, Blackboard, Event Broker, Event Mediator가 전부 비슷하게 느껴졌다.</p>
<p>하지만 <strong>누가 흐름을 주도하는가?</strong>를 기준으로 보면 차이가 보인다.</p>
<pre><code class="language-text">Shared Repository
────────────────────
Client가 주도
Repository는 수동적

Client → Repository</code></pre>
<pre><code class="language-text">Blackboard
────────────────────
문제 상태가 중심
Control이 상태를 보고 KS 선택

Blackboard 상태
      ↓
   Control
      ↓
     KS
      ↓
Blackboard 갱신</code></pre>
<pre><code class="language-text">Event Broker
────────────────────
Event가 발생하면
관심 있는 Component가 반응

Publisher
    ↓
 Broker
 ↙  ↓  ↘
A   B   C</code></pre>
<pre><code class="language-text">Event Mediator
────────────────────
Mediator가 전체 Workflow를 관리

       Mediator
      ↙   ↓   ↘
     A    B    C</code></pre>
<p>그리고 이 모든 구조에는 장점과 단점이 존재한다.</p>
<p>따라서 소프트웨어 아키텍처를 공부할 때 단순히 구조를 암기하는 것보다</p>
<pre><code class="language-text">왜 이런 구조가 등장했는가?

누가 제어 흐름을 가지고 있는가?

컴포넌트는 어떻게 통신하는가?

무엇을 얻는 대신 무엇을 포기하는가?

어떤 시스템에 적합한가?</code></pre>
<p>를 질문하는 것이 훨씬 중요하다.</p>
<p>결국 아키텍처 스타일은 정답을 고르는 문제가 아니다.</p>
<p><strong>시스템이 중요하게 생각하는 품질 속성에 따라 적절한 Trade-off를 선택하는 문제다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[입력이 커지면 알고리즘은 얼마나 느려질까? | 1-SUM부터 Tilde, Big-O, Big-Ω까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%93%A8%ED%8C%85-%EB%AC%B8%EC%A0%9C%EC%99%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%95%EC%9D%98-4</link>
            <guid>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%93%A8%ED%8C%85-%EB%AC%B8%EC%A0%9C%EC%99%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%95%EC%9D%98-4</guid>
            <pubDate>Sun, 04 Oct 2026 11:28:41 GMT</pubDate>
            <description><![CDATA[<h1 id="입력이-커지면-알고리즘은-얼마나-느려질까--1-sum부터-tilde-big-o-big-ω까지">입력이 커지면 알고리즘은 얼마나 느려질까? | 1-SUM부터 Tilde, Big-O, Big-Ω까지</h1>
<p>알고리즘을 작성하는 것만큼 중요한 것이 있다.</p>
<blockquote>
<p><strong>이 알고리즘은 입력 데이터가 많아졌을 때도 사용할 수 있을까?</strong></p>
</blockquote>
<p>데이터가 10개일 때 잘 동작하는 것과 데이터가 100만 개일 때 잘 동작하는 것은 전혀 다른 문제다.</p>
<p>그래서 알고리즘을 분석할 때는 단순히 &quot;내 컴퓨터에서 몇 초 걸렸다&quot;가 아니라 <strong>입력 크기 <code>N</code>이 증가함에 따라 필요한 연산의 수가 어떻게 증가하는가</strong>를 살펴본다.</p>
<p>이번 장에서는 이를 1-SUM과 2-SUM부터 시작해서 생각해본다.</p>
<hr>
<h1 id="1-왜-알고리즘을-분석할까">1. 왜 알고리즘을 분석할까?</h1>
<p>두 알고리즘이 있다고 생각해보자.</p>
<pre><code class="language-text">알고리즘 A → N²번 정도 연산
알고리즘 B → N log N번 정도 연산</code></pre>
<p>N이 작을 때는 둘의 차이가 크게 느껴지지 않을 수 있다.</p>
<p>하지만 N이 커지면 상황이 달라진다.</p>
<p>예를 들어</p>
<pre><code class="language-text">N = 1,000,000</code></pre>
<p>이라면</p>
<pre><code class="language-text">N²
= 1,000,000,000,000

N log₂N
≈ 1,000,000 × 20
≈ 20,000,000</code></pre>
<p>정도가 된다.</p>
<p>같은 문제를 해결하더라도 알고리즘의 성장 속도에 따라 필요한 연산량이 엄청나게 달라질 수 있다.</p>
<p>그래서 알고리즘 분석에서는 <strong>입력 크기 N에 따른 실행 시간과 메모리의 증가량</strong>을 중요하게 본다.</p>
<hr>
<h1 id="2-가장-간단한-예제-1-sum">2. 가장 간단한 예제: 1-SUM</h1>
<p>다음 코드가 있다고 하자.</p>
<pre><code class="language-java">int count = 0;

for (int i = 0; i &lt; N; i++) {

    if (a[i] == 0) {
        count++;
    }
}</code></pre>
<p>이 알고리즘은 배열을 처음부터 끝까지 한 번 확인하면서 값이 0인 원소의 개수를 센다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[-3, 0, 5, 0, 7]</code></pre>
<p>이라면</p>
<pre><code class="language-text">-3 → 확인
 0 → 확인 → count++
 5 → 확인
 0 → 확인 → count++
 7 → 확인</code></pre>
<p>총 N개의 원소를 확인한다.</p>
<p>따라서 직관적으로 생각하면 실행되는 연산의 수가 N에 비례한다.</p>
<p>하지만 강의에서는 여기서 바로 <code>O(N)</code>이라고 끝내지 않고 <strong>실제로 어떤 명령이 몇 번 실행되는지</strong> 먼저 계산한다.</p>
<hr>
<h1 id="3-1-sum의-명령-횟수를-직접-세어보자">3. 1-SUM의 명령 횟수를 직접 세어보자</h1>
<p>코드를 다시 보자.</p>
<pre><code class="language-java">int count = 0;

for (int i = 0; i &lt; N; i++) {

    if (a[i] == 0) {
        count++;
    }
}</code></pre>
<p>여기에는 여러 연산이 존재한다.</p>
<pre><code class="language-text">변수 선언
대입
i &lt; N 비교
a[i] 접근
a[i] == 0 비교
i++
count++</code></pre>
<p>예를 들어 <code>i &lt; N</code>은 반복문이 종료되는 마지막 검사까지 포함하기 때문에 대략</p>
<pre><code class="language-text">N + 1번</code></pre>
<p>실행된다.</p>
<p><code>a[i]</code> 접근과 <code>a[i] == 0</code> 비교는 각 원소마다 이루어지므로</p>
<pre><code class="language-text">N번</code></pre>
<p>이다.</p>
<p>반면</p>
<pre><code class="language-java">count++;</code></pre>
<p>는 배열에 실제로 0이 몇 개 있는지에 따라 달라진다.</p>
<pre><code class="language-text">최소 0번
최대 N번</code></pre>
<p>실행될 수 있다.</p>
<p>중요한 것은 세부적인 횟수가 조금씩 달라도 전체적으로 보면 <strong>N에 비례해서 증가하는 연산들이 지배적</strong>이라는 것이다.</p>
<p>그래서 1-SUM은 linear한 성장 형태를 가진다.</p>
<hr>
<h1 id="4-2-sum은-무엇일까">4. 2-SUM은 무엇일까?</h1>
<p>이번에는 배열에서 <strong>서로 다른 두 원소를 선택해서 합이 0이 되는 조합</strong>을 찾는다고 하자.</p>
<p>예를 들어</p>
<pre><code class="language-text">[-10, -5, 0, 5, 10]</code></pre>
<p>이라면</p>
<pre><code class="language-text">(-10, 10)
(-5, 5)</code></pre>
<p>가 존재한다.</p>
<p>Brute Force 방식으로 구현하면 다음과 같은 구조가 된다.</p>
<pre><code class="language-java">int count = 0;

for (int i = 0; i &lt; N; i++) {

    for (int j = i + 1; j &lt; N; j++) {

        if (a[i] + a[j] == 0) {
            count++;
        }
    }
}</code></pre>
<p>여기서 중요한 것은</p>
<pre><code class="language-java">j = i + 1</code></pre>
<p>이다.</p>
<p>왜 <code>j = 0</code>부터 시작하지 않을까?</p>
<p>같은 원소를 자기 자신과 비교할 필요가 없고,</p>
<pre><code class="language-text">a[0] + a[1]</code></pre>
<p>을 확인했다면 나중에</p>
<pre><code class="language-text">a[1] + a[0]</code></pre>
<p>을 다시 확인할 필요도 없기 때문이다.</p>
<p>즉 <strong>중복되지 않는 두 원소의 조합</strong>만 검사한다.</p>
<hr>
<h1 id="5-두-원소를-고르는-경우의-수">5. 두 원소를 고르는 경우의 수</h1>
<p>N개의 데이터 중에서 서로 다른 2개를 선택하는 경우의 수는</p>
<pre><code class="language-text">N choose 2</code></pre>
<p>이다.</p>
<p>수식으로 표현하면</p>
<pre><code class="language-text">N(N - 1)
────────
   2</code></pre>
<p>이다.</p>
<p>왜 그런지 반복문으로 생각하면 더 쉽다.</p>
<p>첫 번째 <code>i</code>에서는</p>
<pre><code class="language-text">N - 1</code></pre>
<p>개를 비교한다.</p>
<p>그다음에는</p>
<pre><code class="language-text">N - 2</code></pre>
<p>개.</p>
<p>계속하면</p>
<pre><code class="language-text">(N - 1) + (N - 2) + ... + 2 + 1</code></pre>
<p>이 된다.</p>
<p>결과는</p>
<pre><code class="language-text">N(N - 1) / 2</code></pre>
<p>이다.</p>
<p>전개하면</p>
<pre><code class="language-text">1/2 N² - 1/2 N</code></pre>
<p>이 된다.</p>
<p>여기서부터 알고리즘 분석에서 중요한 질문이 생긴다.</p>
<blockquote>
<p>매번 <code>1/2 N² - 1/2 N</code>처럼 정확하게 써야 할까?</p>
</blockquote>
<hr>
<h1 id="6-정확한-명령-횟수를-계속-계산하는-것은-번거롭다">6. 정확한 명령 횟수를 계속 계산하는 것은 번거롭다</h1>
<p>실제 프로그램에서는 비교뿐 아니라 대입, 배열 접근, 증가 연산 등 다양한 연산이 발생한다.</p>
<p>그래서 정확하게 계산하면 실행 횟수가 다음처럼 나올 수 있다.</p>
<pre><code class="language-text">1/2 N² + 3N + 7</code></pre>
<p>혹은</p>
<pre><code class="language-text">2N² + 5N + 10</code></pre>
<p>처럼 복잡해질 수 있다.</p>
<p>하지만 N이 매우 커졌을 때를 생각해보자.</p>
<pre><code class="language-text">N = 1,000,000</code></pre>
<p>이라면</p>
<pre><code class="language-text">N² = 1,000,000,000,000
N  =         1,000,000</code></pre>
<p>이다.</p>
<p><code>N²</code>에 비하면 <code>N</code>이나 상수는 상대적으로 영향력이 매우 작아진다.</p>
<p>그래서 알고리즘의 성장률을 볼 때는 작은 차수를 제거하고 <strong>가장 빠르게 증가하는 항에 집중</strong>할 수 있다.</p>
<p>여기서 Tilde notation이 등장한다.</p>
<hr>
<h1 id="7-tilde-notation-">7. Tilde Notation <code>~</code></h1>
<p>강의자료에서는 Tilde notation을 다음과 같이 설명한다.</p>
<blockquote>
<p><strong>lower-order terms를 무시하여 수학적 모델을 단순화한다.</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-text">f(N) = 1/2 N² + 3N + 7</code></pre>
<p>이라면 N이 커질수록 가장 큰 영향을 주는 항은</p>
<pre><code class="language-text">1/2 N²</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-text">f(N) ~ 1/2 N²</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>여기서 중요한 점이 하나 있다.</p>
<p><strong>Tilde notation에서는 최고차항의 계수까지 없애는 것이 아니다.</strong></p>
<p>예를 들어</p>
<pre><code class="language-text">3N² + 10N + 5</code></pre>
<p>라면</p>
<pre><code class="language-text">~ 3N²</code></pre>
<p>이다.</p>
<p><code>~ N²</code>이라고 하는 것과는 의미가 다르다.</p>
<p>왜냐하면 Tilde notation의 엄밀한 의미는</p>
<pre><code class="language-text">f(N) ~ g(N)</code></pre>
<p>일 때</p>
<pre><code class="language-text">        f(N)
lim    ───── = 1
N→∞     g(N)</code></pre>
<p>이기 때문이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">f(N) = 3N² + 10N + 5
g(N) = 3N²</code></pre>
<p>이면</p>
<pre><code class="language-text">3N² + 10N + 5
──────────────
     3N²

= 1 + 10/(3N) + 5/(3N²)</code></pre>
<p>이고 N이 무한히 커지면</p>
<pre><code class="language-text">→ 1</code></pre>
<p>이 된다.</p>
<p>따라서</p>
<pre><code class="language-text">3N² + 10N + 5 ~ 3N²</code></pre>
<p>이다.</p>
<hr>
<h1 id="8-그런데-big-o에서는-계수도-무시한다">8. 그런데 Big-O에서는 계수도 무시한다</h1>
<p>여기서 Tilde notation과 Big-O를 구분해야 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">f(N) = 3N² + 10N + 5</code></pre>
<p>라고 하자.</p>
<p>Tilde notation에서는</p>
<pre><code class="language-text">f(N) ~ 3N²</code></pre>
<p>이라고 한다.</p>
<p>반면 Big-O와 같은 점근적 표기에서는 보통</p>
<pre><code class="language-text">O(N²)</code></pre>
<p>이라고 한다.</p>
<p>즉</p>
<pre><code class="language-text">Tilde
→ leading coefficient를 유지

Big-O 계열
→ 상수배까지 중요하지 않게 본다.</code></pre>
<p>이 차이를 알아두면 둘을 혼동하지 않는다.</p>
<hr>
<h1 id="9-big-o-big-ω-big-θ">9. Big-O, Big-Ω, Big-Θ</h1>
<p>알고리즘의 성장률을 표현할 때 자주 등장하는 세 가지 표기가 있다.</p>
<pre><code class="language-text">Big-O     O
Big-Omega Ω
Big-Theta Θ</code></pre>
<p>가장 먼저 큰 그림을 잡으면</p>
<pre><code class="language-text">O(g(N))
→ 점근적 상한

Ω(g(N))
→ 점근적 하한

Θ(g(N))
→ 점근적으로 같은 차수</code></pre>
<p>라고 이해할 수 있다.</p>
<p>그런데 여기서 <code>상한</code>과 <code>하한</code>이라는 표현을 정확히 이해해야 한다.</p>
<hr>
<h1 id="10-big-o--점근적-상한">10. Big-O : 점근적 상한</h1>
<p>어떤 알고리즘의 실행 횟수가</p>
<pre><code class="language-text">f(N) = 10N² + 3N + 20</code></pre>
<p>이라고 하자.</p>
<p>Big-O는 충분히 큰 N에서</p>
<pre><code class="language-text">f(N) ≤ c · g(N)</code></pre>
<p>을 만족하는 어떤 양의 상수 <code>c</code>와 기준점 <code>N₀</code>가 존재하는지를 본다.</p>
<p>예를 들어</p>
<pre><code class="language-text">g(N) = N²</code></pre>
<p>라고 하자.</p>
<p>충분히 큰 N에서는</p>
<pre><code class="language-text">10N² + 3N + 20 ≤ 11N²</code></pre>
<p>같은 관계를 만들 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">f(N)
────────── 11N²</code></pre>
<p>라는 위쪽 경계를 잡을 수 있다.</p>
<p>그래서</p>
<pre><code class="language-text">f(N) = O(N²)</code></pre>
<p>라고 말할 수 있다.</p>
<p>핵심은 정확히 <code>11</code>이어야 한다는 것이 아니다.</p>
<pre><code class="language-text">20N²
100N²
1000N²</code></pre>
<p>등 충분히 큰 상수배를 잡아도 된다.</p>
<p>Big-O는 <strong>어떤 상수배를 이용해서 위에서 덮을 수 있는가</strong>를 보는 것이다.</p>
<hr>
<h1 id="11-big-ω--점근적-하한">11. Big-Ω : 점근적 하한</h1>
<p>같은 함수</p>
<pre><code class="language-text">f(N) = 10N² + 3N + 20</code></pre>
<p>을 생각해보자.</p>
<p>이번에는 아래쪽 경계를 찾는다.</p>
<p>예를 들어</p>
<pre><code class="language-text">9N² ≤ 10N² + 3N + 20</code></pre>
<p>은 충분히 큰 N에서 당연히 성립한다.</p>
<p>즉</p>
<pre><code class="language-text">9N²
────────── f(N)</code></pre>
<p>처럼 아래에서 받칠 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">f(N) = Ω(N²)</code></pre>
<p>라고 말할 수 있다.</p>
<p>Big-Ω의 핵심은</p>
<pre><code class="language-text">f(N) ≥ c · g(N)</code></pre>
<p>이 되는 상수 <code>c &gt; 0</code>과 충분히 큰 <code>N</code>이 존재한다는 것이다.</p>
<hr>
<h1 id="12-내가-헷갈렸던-10n²-11n²-9n²-예제">12. 내가 헷갈렸던 10N², 11N², 9N² 예제</h1>
<p>처음에는 다음처럼 생각하기 쉽다.</p>
<pre><code class="language-text">10N² &lt; 11N²
→ Big-O

10N² &gt; 9N²
→ Big-Ω</code></pre>
<p>직관 자체는 <strong>상한과 하한을 이해하는 데는 괜찮다.</strong></p>
<pre><code class="language-text">       11N²       ← 위쪽 경계
        ↑
       10N²       ← 실제 함수
        ↑
        9N²       ← 아래쪽 경계</code></pre>
<p>그래서</p>
<pre><code class="language-text">10N² = O(N²)

10N² = Ω(N²)</code></pre>
<p>둘 다 맞다.</p>
<p>그리고 위와 아래에서 모두 같은 <code>N²</code> 차수로 잡을 수 있으므로</p>
<pre><code class="language-text">10N² = Θ(N²)</code></pre>
<p>도 맞다.</p>
<p>다만 여기서 중요한 것은</p>
<blockquote>
<p><strong>11과 9라는 숫자 자체가 Big-O와 Big-Ω를 결정하는 것이 아니다.</strong></p>
</blockquote>
<p>Big-O와 Big-Ω에서는 상수배를 허용한다.</p>
<p>따라서 더 좋은 예제는 다음과 같다.</p>
<pre><code class="language-text">f(N) = 10N² + 3N + 20</code></pre>
<p>충분히 큰 N에서</p>
<pre><code class="language-text">9N² ≤ f(N) ≤ 11N²</code></pre>
<p>같은 형태의 경계를 잡을 수 있다면</p>
<pre><code class="language-text">f(N) = Ω(N²)
f(N) = O(N²)</code></pre>
<p>이고 결과적으로</p>
<pre><code class="language-text">f(N) = Θ(N²)</code></pre>
<p>이라고 볼 수 있다.</p>
<hr>
<h1 id="13-big-o는-정확한-실행시간이-아니다">13. Big-O는 &quot;정확한 실행시간&quot;이 아니다</h1>
<p>여기서 자주 하는 착각이 있다.</p>
<pre><code class="language-text">O(N²)</code></pre>
<p>이라고 했다고 해서 실행 횟수가 정확히 <code>N²</code>번이라는 뜻은 아니다.</p>
<p>다음 함수들은 모두 <code>O(N²)</code>라고 할 수 있다.</p>
<pre><code class="language-text">N²
3N² + 10N
0.5N² + 100N + 300</code></pre>
<p>상수배와 낮은 차수의 항을 무시하고 <strong>성장 속도의 상한</strong>을 보는 것이기 때문이다.</p>
<p>그리고 조금 더 엄밀하게 말하면</p>
<pre><code class="language-text">N</code></pre>
<p>인 알고리즘도</p>
<pre><code class="language-text">O(N²)</code></pre>
<p>라고 말할 수 있다.</p>
<p>왜냐하면 충분히 큰 N에서</p>
<pre><code class="language-text">N ≤ N²</code></pre>
<p>이기 때문이다.</p>
<p>하지만 알고리즘의 성장률을 설명하면서 일부러 느슨하게</p>
<pre><code class="language-text">O(N²)</code></pre>
<p>라고 표현하기보다는 더 정확한 경계인</p>
<pre><code class="language-text">O(N)</code></pre>
<p>을 사용하는 것이 일반적으로 더 유용하다.</p>
<hr>
<h1 id="14-big-ω도-마찬가지다">14. Big-Ω도 마찬가지다</h1>
<p>Big-Ω는 하한이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">f(N) = N²</code></pre>
<p>이라면</p>
<pre><code class="language-text">f(N) = Ω(N²)</code></pre>
<p>이다.</p>
<p>그런데</p>
<pre><code class="language-text">f(N) = Ω(N)</code></pre>
<p>도 맞다.</p>
<p>왜냐하면 충분히 큰 N에서</p>
<pre><code class="language-text">N² ≥ N</code></pre>
<p>이기 때문이다.</p>
<p>즉 Big-O와 Big-Ω는 각각 하나의 값만 가리키는 것이 아니라 <strong>가능한 상한과 하한의 집합을 표현한다고 생각하면 이해하기 쉽다.</strong></p>
<hr>
<h1 id="15-big-θ는-위와-아래가-같은-경우">15. Big-Θ는 위와 아래가 같은 경우</h1>
<p>Big-Theta는 상한과 하한을 동시에 잡을 수 있을 때 사용한다.</p>
<pre><code class="language-text">c₁g(N) ≤ f(N) ≤ c₂g(N)</code></pre>
<p>이 관계가 충분히 큰 N에서 성립한다면</p>
<pre><code class="language-text">f(N) = Θ(g(N))</code></pre>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">f(N) = 10N² + 3N + 20</code></pre>
<p>이라면 충분히 큰 N에서</p>
<pre><code class="language-text">c₁N² ≤ f(N) ≤ c₂N²</code></pre>
<p>인 상수 <code>c₁</code>, <code>c₂</code>를 찾을 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">f(N) = Θ(N²)</code></pre>
<p>이다.</p>
<p>그림으로 생각하면</p>
<pre><code class="language-text">c₂N²  ─────────────────  상한
              ↑
       f(N) = 10N² + 3N + 20
              ↓
c₁N²  ─────────────────  하한</code></pre>
<p>처럼 <code>f(N)</code>을 같은 성장률을 가진 함수의 상수배 사이에 끼워 넣는 것이다.</p>
<hr>
<h1 id="16-o-ω-θ를-한-번에-이해하기">16. O, Ω, Θ를 한 번에 이해하기</h1>
<p>다음 함수가 있다고 하자.</p>
<pre><code class="language-text">f(N) = 5N² + 2N + 10</code></pre>
<p>N이 충분히 커지면 <code>N²</code> 항이 지배한다.</p>
<p>그래서</p>
<pre><code class="language-text">f(N) = O(N²)</code></pre>
<p>이라고 할 수 있다.</p>
<p><code>N²</code>의 상수배가 위에서 <code>f(N)</code>을 덮을 수 있기 때문이다.</p>
<p>동시에</p>
<pre><code class="language-text">f(N) = Ω(N²)</code></pre>
<p>이다.</p>
<p><code>N²</code>의 상수배를 아래쪽 경계로 잡을 수 있기 때문이다.</p>
<p>두 조건이 모두 성립하므로</p>
<pre><code class="language-text">f(N) = Θ(N²)</code></pre>
<p>이다.</p>
<p>정리하면</p>
<pre><code class="language-text">             위쪽 경계
                 ↓
Big-O      → 상한

Big-Theta  → 위와 아래가 같은 성장률

Big-Omega  → 하한
                 ↑
             아래쪽 경계</code></pre>
<p>라고 기억할 수 있다.</p>
<hr>
<h1 id="17-best-case와-worst-case하고-같은-말일까">17. Best Case와 Worst Case하고 같은 말일까?</h1>
<p>여기서 또 하나 조심해야 한다.</p>
<p>흔히</p>
<pre><code class="language-text">Big-O = Worst Case
Big-Ω = Best Case</code></pre>
<p>라고 외우기도 한다.</p>
<p>하지만 <strong>엄밀하게는 같은 개념이 아니다.</strong></p>
<p>Big-O와 Big-Ω는 함수의 <strong>점근적 상한과 하한을 표현하는 수학적 표기법</strong>이다.</p>
<p>반면</p>
<pre><code class="language-text">Best Case
Worst Case
Average Case</code></pre>
<p>는 어떤 입력 상황에서 비용을 분석하는지를 나타낸다.</p>
<p>예를 들어 Worst Case 실행시간 함수 자체에 대해서도</p>
<pre><code class="language-text">O(...)
Ω(...)
Θ(...)</code></pre>
<p>를 이야기할 수 있다.</p>
<p>따라서 시험에서는 교수님 강의의 표현을 따라가되 개념적으로는</p>
<pre><code class="language-text">O  → asymptotic upper bound
Ω  → asymptotic lower bound
Θ  → asymptotically tight bound</code></pre>
<p>로 이해해두는 것이 좋다.</p>
<hr>
<h1 id="18-tilde와-big-theta는-비슷해-보이지만-다르다">18. Tilde와 Big-Theta는 비슷해 보이지만 다르다</h1>
<p>다음 함수가 있다고 하자.</p>
<pre><code class="language-text">f(N) = 3N² + 10N + 5</code></pre>
<p>Tilde notation에서는</p>
<pre><code class="language-text">f(N) ~ 3N²</code></pre>
<p>이다.</p>
<p>왜냐하면</p>
<pre><code class="language-text">f(N) / 3N² → 1</code></pre>
<p>이기 때문이다.</p>
<p>반면</p>
<pre><code class="language-text">f(N) = Θ(N²)</code></pre>
<p>이다.</p>
<p>Theta에서는 상수배를 무시하기 때문이다.</p>
<p>그래서</p>
<pre><code class="language-text">Tilde
3N² + 10N + 5
        ↓
      ~ 3N²


Theta
3N² + 10N + 5
        ↓
      Θ(N²)</code></pre>
<p>라고 구분하면 된다.</p>
<hr>
<h1 id="19-1-sum과-2-sum으로-다시-돌아가보자">19. 1-SUM과 2-SUM으로 다시 돌아가보자</h1>
<p>이제 처음의 알고리즘을 다시 보면 성장률이 보인다.</p>
<h2 id="1-sum">1-SUM</h2>
<pre><code class="language-java">for (int i = 0; i &lt; N; i++) {

    if (a[i] == 0) {
        count++;
    }
}</code></pre>
<p>배열을 한 번 순회한다.</p>
<pre><code class="language-text">N에 비례</code></pre>
<p>따라서 성장 차수는</p>
<pre><code class="language-text">Θ(N)</code></pre>
<p>으로 이해할 수 있다.</p>
<h2 id="2-sum">2-SUM</h2>
<pre><code class="language-java">for (int i = 0; i &lt; N; i++) {

    for (int j = i + 1; j &lt; N; j++) {

        if (a[i] + a[j] == 0) {
            count++;
        }
    }
}</code></pre>
<p>두 원소의 모든 조합을 확인한다.</p>
<p>조합의 수는</p>
<pre><code class="language-text">N(N - 1) / 2

= 1/2 N² - 1/2 N</code></pre>
<p>이다.</p>
<p>Tilde notation으로 보면</p>
<pre><code class="language-text">~ 1/2 N²</code></pre>
<p>이고 성장 차수로 보면</p>
<pre><code class="language-text">Θ(N²)</code></pre>
<p>이다.</p>
<p>즉 입력이 커질수록 1-SUM과 2-SUM의 차이는 급격히 커진다.</p>
<pre><code class="language-text">1-SUM → linear

2-SUM → quadratic</code></pre>
<hr>
<h1 id="20-자주-등장하는-성장률">20. 자주 등장하는 성장률</h1>
<p>알고리즘에서는 다음 성장률을 자주 보게 된다.</p>
<pre><code class="language-text">1
log N
N
N log N
N²
N³
2^N</code></pre>
<p>입력 N이 커질수록 일반적으로</p>
<pre><code class="language-text">1
&lt;
log N
&lt;
N
&lt;
N log N
&lt;
N²
&lt;
N³
&lt;
2^N</code></pre>
<p>순서로 빠르게 증가한다.</p>
<p>따라서 같은 문제를 해결한다면 보통 성장률이 낮은 알고리즘이 큰 입력에서 훨씬 유리하다.</p>
<hr>
<h1 id="핵심-정리">핵심 정리</h1>
<p>이번 내용을 공부하면서 가장 헷갈렸던 부분은 <code>~</code>, <code>O</code>, <code>Ω</code>, <code>Θ</code>가 모두 비슷하게 보인다는 것이었다.</p>
<p>하지만 역할을 나누면 생각보다 간단하다.</p>
<pre><code class="language-text">Tilde ~

정확한 leading term에 관심
lower-order term 제거

3N² + 10N + 5
~ 3N²</code></pre>
<pre><code class="language-text">Big-O

점근적 상한

f(N) ≤ c·g(N)</code></pre>
<pre><code class="language-text">Big-Ω

점근적 하한

f(N) ≥ c·g(N)</code></pre>
<pre><code class="language-text">Big-Θ

점근적으로 같은 성장 차수

c₁g(N) ≤ f(N) ≤ c₂g(N)</code></pre>
<p>그리고</p>
<pre><code class="language-text">f(N) = 10N² + 3N + 20</code></pre>
<p>같은 함수가 있다면 직관적으로</p>
<pre><code class="language-text">       c₂N²
         ↑
         │
       f(N)
         │
         ↓
       c₁N²</code></pre>
<p>처럼 같은 <code>N²</code> 성장률의 함수 사이에 들어간다고 생각할 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">f(N) = O(N²)
f(N) = Ω(N²)
f(N) = Θ(N²)</code></pre>
<p>이다.</p>
<p>결국 알고리즘 분석에서 궁금한 것은 작은 연산 몇 번의 차이가 아니다.</p>
<blockquote>
<p><strong>N이 매우 커졌을 때 무엇이 이 알고리즘의 실행 비용을 지배하는가?</strong></p>
</blockquote>
<p>이 질문을 답하기 위해 정확한 명령 횟수에서 출발해 Tilde notation으로 단순화하고, O·Ω·Θ 같은 점근적 표기법으로 성장률을 표현하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Queue는 왜 포인터가 두 개 필요할까? | Generics부터 기본 정렬과 Shellsort까지]]></title>
            <link>https://velog.io/@daehyun_lee/Queue%EB%8A%94-%EC%99%9C-%ED%8F%AC%EC%9D%B8%ED%84%B0%EA%B0%80-%EB%91%90-%EA%B0%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-Generics%EB%B6%80%ED%84%B0-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A0%AC%EA%B3%BC-Shellsort%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/Queue%EB%8A%94-%EC%99%9C-%ED%8F%AC%EC%9D%B8%ED%84%B0%EA%B0%80-%EB%91%90-%EA%B0%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-Generics%EB%B6%80%ED%84%B0-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A0%AC%EA%B3%BC-Shellsort%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sun, 04 Oct 2026 11:23:42 GMT</pubDate>
            <description><![CDATA[<h1 id="queue는-왜-포인터가-두-개-필요할까--generics부터-기본-정렬과-shellsort까지">Queue는 왜 포인터가 두 개 필요할까? | Generics부터 기본 정렬과 Shellsort까지</h1>
<p>컴퓨팅 문제와 알고리즘 3장에서는 Queue에서 시작해서 Generics, Iterator, Bag을 거쳐 정렬 알고리즘까지 다룬다.</p>
<p>처음 보면 서로 크게 관련 없어 보이는 내용들이 한 장에 모여 있다.</p>
<p>그런데 공부하면서 생각해보면 공통된 질문이 있다.</p>
<blockquote>
<p><strong>데이터를 어떤 구조로 저장하고, 어떤 방식으로 접근하고, 얼마나 효율적으로 처리할 것인가?</strong></p>
</blockquote>
<p>Queue에서는 데이터를 넣고 빼는 위치를 고민하고, Generics에서는 하나의 자료구조를 여러 타입에 안전하게 사용하는 방법을 고민한다.</p>
<p>Iterator는 자료구조 내부 구현을 몰라도 데이터를 순회할 수 있게 하고, 정렬에서는 데이터의 순서를 효율적으로 바꾸는 방법을 고민한다.</p>
<p>이번 글에서는 이 흐름을 따라 3장의 내용을 정리해보려고 한다.</p>
<hr>
<h1 id="1-queue란">1. Queue란?</h1>
<p>Queue는 <strong>먼저 들어온 데이터가 먼저 나오는 자료구조</strong>다.</p>
<p>이를 FIFO라고 한다.</p>
<pre><code class="language-text">FIFO
First In First Out

먼저 들어온 데이터
→ 먼저 나간다.</code></pre>
<p>예를 들어 사람들이 줄을 서 있다고 생각하면 된다.</p>
<pre><code class="language-text">입구 → A → B → C → 출구</code></pre>
<p>A가 가장 먼저 들어왔다면 A가 가장 먼저 나간다.</p>
<p>Queue에서는 데이터를 넣는 연산을</p>
<pre><code class="language-text">enqueue</code></pre>
<p>라고 하고,</p>
<p>데이터를 제거하는 연산을</p>
<pre><code class="language-text">dequeue</code></pre>
<p>라고 한다.</p>
<p>중요한 점은 삽입 위치와 삭제 위치가 서로 다르다는 것이다.</p>
<pre><code class="language-text">dequeue                         enqueue
   ↓                               ↓

[ A ] → [ B ] → [ C ] → [ D ]
  ↑                         ↑
front                      rear</code></pre>
<p>새로운 데이터는 뒤쪽에 추가하고 가장 오래된 데이터는 앞쪽에서 제거한다.</p>
<p>따라서 Queue를 구현하려면 보통</p>
<pre><code class="language-text">front
rear</code></pre>
<p>두 위치를 관리해야 한다.</p>
<hr>
<h1 id="2-왜-front와-rear가-모두-필요할까">2. 왜 front와 rear가 모두 필요할까?</h1>
<p>Queue의 핵심 연산을 다시 생각해보자.</p>
<pre><code class="language-text">enqueue
→ 가장 뒤에 데이터 추가

dequeue
→ 가장 앞의 데이터 제거</code></pre>
<p>따라서 빠르게 처리하려면</p>
<pre><code class="language-text">가장 앞이 어디인지
가장 뒤가 어디인지</code></pre>
<p>를 모두 알고 있는 것이 유리하다.</p>
<p>Linked List로 Queue를 구현한다면 개념적으로 다음과 같다.</p>
<pre><code class="language-text">front                         rear
  ↓                             ↓

[A] → [B] → [C] → [D] → null</code></pre>
<p><code>dequeue()</code>를 하면</p>
<pre><code class="language-text">front
  ↓

[A] → [B] → [C]</code></pre>
<p>에서 A를 제거하고</p>
<pre><code class="language-text">      front
        ↓

       [B] → [C]</code></pre>
<p>처럼 front만 다음 노드로 이동시키면 된다.</p>
<p>반대로 <code>enqueue(D)</code>를 한다면 rear가 마지막 노드를 알고 있으므로 바로 뒤에 연결할 수 있다.</p>
<hr>
<h1 id="3-rear를-저장하지-않으면-어떻게-될까">3. rear를 저장하지 않으면 어떻게 될까?</h1>
<p>강의자료에는 다음과 같은 문제가 등장한다.</p>
<blockquote>
<p>Null-terminated singly-linked list로 Queue를 구현하는데 front는 유지하고, 가장 최근에 추가된 item의 reference인 end는 유지하지 않는다면 enqueue와 dequeue의 최악 실행시간은 어떻게 될까?</p>
</blockquote>
<p>여기서 핵심은 <strong>rear가 없다는 것</strong>이다.</p>
<p>현재 구조가 다음과 같다고 하자.</p>
<pre><code class="language-text">front
  ↓

[A] → [B] → [C] → [D] → null</code></pre>
<p><code>dequeue()</code>는 간단하다.</p>
<p>front가 이미 A를 가리키고 있으므로</p>
<pre><code class="language-text">front = front.next;</code></pre>
<p>처럼 처리할 수 있다.</p>
<p>따라서 데이터 개수 N에 관계없이 일정한 작업만 수행한다.</p>
<pre><code class="language-text">dequeue → O(1)</code></pre>
<p>그런데 enqueue는 다르다.</p>
<p>rear가 없으므로 마지막 노드를 모른다.</p>
<p>새로운 E를 추가하려면</p>
<pre><code class="language-text">A
↓
B
↓
C
↓
D
↓
null 발견
↓
E 연결</code></pre>
<p>처럼 처음부터 끝까지 이동해야 한다.</p>
<p>N개의 데이터가 있다면 최악의 경우 N개 정도를 따라가야 한다.</p>
<p>따라서</p>
<pre><code class="language-text">enqueue → O(N)
dequeue → O(1)</code></pre>
<p>이 된다.</p>
<p>이 문제를 통해 왜 Queue 구현에서 front와 rear를 함께 유지하는지가 이해된다.</p>
<blockquote>
<p><strong>포인터 하나를 더 저장하는 대신 연산 시간을 줄이는 것이다.</strong></p>
</blockquote>
<hr>
<h1 id="4-linked-list와-array로-queue를-구현하면">4. Linked List와 Array로 Queue를 구현하면?</h1>
<p>Queue는 Linked List뿐 아니라 Array로도 구현할 수 있다.</p>
<p>Linked List 방식은 각 데이터가 다음 노드를 가리키는 링크를 가지고 있어야 한다.</p>
<pre><code class="language-text">[item | next] → [item | next] → [item | next]</code></pre>
<p>따라서 링크를 저장하기 위한 추가 공간이 필요하다.</p>
<p>반면 Array는 연속된 공간에 데이터를 저장할 수 있다.</p>
<pre><code class="language-text">[ A ][ B ][ C ][ D ][   ][   ]</code></pre>
<p>하지만 배열은 크기 문제를 고민해야 한다.</p>
<p>Queue에 앞으로 데이터가 몇 개 들어올지 클라이언트가 정확히 알 수 없다면 처음 배열의 크기를 어떻게 정할 것인지가 문제가 된다.</p>
<p>결국 자료구조 구현에서는 항상 이런 trade-off가 등장한다.</p>
<pre><code class="language-text">Linked List
→ 링크를 위한 추가 공간 필요

Array
→ 크기 관리 문제 발생</code></pre>
<hr>
<h1 id="5-그런데-queue에-string만-넣어야-할까">5. 그런데 Queue에 String만 넣어야 할까?</h1>
<p>자료구조를 구현하다 보면 또 다른 문제가 생긴다.</p>
<p>예를 들어 다음과 같은 Stack이 있다고 하자.</p>
<pre><code class="language-java">class StringStack {
    String[] data;
}</code></pre>
<p>String을 저장할 때는 문제가 없다.</p>
<p>그런데 Integer도 저장하고 싶다면?</p>
<pre><code class="language-java">class IntegerStack {
    Integer[] data;
}</code></pre>
<p>Double도 저장하려면?</p>
<pre><code class="language-java">class DoubleStack {
    Double[] data;
}</code></pre>
<p>자료형이 달라질 때마다 거의 똑같은 코드를 다시 작성해야 한다.</p>
<p>이것은 상당히 비효율적이다.</p>
<p>코드를 복사해서 계속 수정해야 하고, 한 구현에서 버그를 수정했다면 다른 구현도 모두 수정해야 할 수 있다.</p>
<p>그래서 <strong>Generics</strong>가 필요해진다.</p>
<hr>
<h1 id="6-object로-받으면-되지-않을까">6. Object로 받으면 되지 않을까?</h1>
<p>Java의 모든 클래스는 Object를 기반으로 하므로 다음처럼 만들 수도 있다.</p>
<pre><code class="language-java">class Stack {
    Object[] data;
}</code></pre>
<p>그러면 String도 넣을 수 있고 Integer도 넣을 수 있다.</p>
<pre><code class="language-java">stack.push(&quot;Hello&quot;);
stack.push(Integer.valueOf(10));</code></pre>
<p>하지만 꺼낼 때 문제가 생긴다.</p>
<pre><code class="language-java">String value = (String) stack.pop();</code></pre>
<p>Object로 저장했기 때문에 다시 원래 타입으로 casting해야 한다.</p>
<p>그리고 잘못된 타입으로 casting하면 문제가 실행 중에 발견된다.</p>
<p>강의자료에서 강조하는 원칙이 여기서 나온다.</p>
<pre><code class="language-text">Welcome compile-time errors.
Avoid run-time errors.</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>가능하다면 오류를 실행 중에 발견하지 말고 컴파일할 때 발견하자.</strong></p>
</blockquote>
<p>이것이 Generic을 사용하는 중요한 이유다.</p>
<hr>
<h1 id="7-generic을-사용하면">7. Generic을 사용하면?</h1>
<p>Generic을 사용하면 자료구조를 다음처럼 만들 수 있다.</p>
<pre><code class="language-java">public class Stack&lt;Item&gt; {

    private Item[] s;

    public void push(Item item) {
        ...
    }

    public Item pop() {
        ...
    }
}</code></pre>
<p>여기서 <code>Item</code>은 특정 자료형 하나를 의미하는 것이 아니다.</p>
<p>사용할 때 실제 타입을 지정한다.</p>
<pre><code class="language-java">Stack&lt;String&gt; s1;
Stack&lt;Integer&gt; s2;</code></pre>
<p>같은 Stack 구현을 사용하면서 저장할 데이터 타입만 바꿀 수 있다.</p>
<pre><code class="language-text">Stack&lt;Item&gt;
     ↓

Stack&lt;String&gt;
Stack&lt;Integer&gt;
Stack&lt;Double&gt;</code></pre>
<p>이렇게 하면 클라이언트에서 불필요한 casting을 줄일 수 있고 타입이 맞지 않는 문제를 컴파일 단계에서 발견할 수 있다.</p>
<hr>
<h1 id="8-generic-배열은-바로-만들-수-없다">8. Generic 배열은 바로 만들 수 없다</h1>
<p>강의자료의 구현에서 주의할 부분이 하나 있다.</p>
<p>다음 코드는 Java에서 허용되지 않는다.</p>
<pre><code class="language-java">s = new Item[capacity];</code></pre>
<p>Generic type의 배열을 직접 생성할 수 없기 때문이다.</p>
<p>그래서 강의에서는 다음과 같은 형태를 사용한다.</p>
<pre><code class="language-java">s = (Item[]) new Object[capacity];</code></pre>
<p>먼저 Object 배열을 만들고</p>
<pre><code class="language-java">new Object[capacity]</code></pre>
<p>이를</p>
<pre><code class="language-java">(Item[])</code></pre>
<p>로 casting하는 방식이다.</p>
<p>Generic 자료구조 구현 문제를 볼 때 자주 등장하는 형태이므로 기억해둘 필요가 있다.</p>
<hr>
<h1 id="9-primitive-type은-generic에-넣을-수-있을까">9. primitive type은 Generic에 넣을 수 있을까?</h1>
<p>다음과 같이 작성할 수 있을까?</p>
<pre><code class="language-java">Stack&lt;int&gt; stack;</code></pre>
<p>Java Generic에는 primitive type을 직접 사용할 수 없다.</p>
<p>대신 Wrapper Class를 사용한다.</p>
<pre><code class="language-text">int     → Integer
long    → Long
float   → Float
double  → Double
byte    → Byte
short   → Short
char    → Character
boolean → Boolean</code></pre>
<p>따라서</p>
<pre><code class="language-java">Stack&lt;Integer&gt; stack = new Stack&lt;&gt;();</code></pre>
<p>처럼 사용한다.</p>
<p>그런데 실제 사용할 때는 다음처럼 작성할 수 있다.</p>
<pre><code class="language-java">stack.push(17);</code></pre>
<p><code>17</code>은 int인데 Integer를 요구하는 곳에 넣을 수 있다.</p>
<p>이것이 <strong>Autoboxing</strong>이다.</p>
<p>개념적으로 Java가</p>
<pre><code class="language-java">stack.push(Integer.valueOf(17));</code></pre>
<p>처럼 처리해주는 것이다.</p>
<p>반대로 Integer를 int로 꺼내는 것도 자동으로 처리할 수 있다.</p>
<pre><code class="language-java">int a = stack.pop();</code></pre>
<p>이를 <strong>Unboxing</strong>이라고 생각할 수 있다.</p>
<hr>
<h1 id="10-iterator는-왜-필요할까">10. Iterator는 왜 필요할까?</h1>
<p>자료구조 안에 데이터가 여러 개 있다고 하자.</p>
<p>클라이언트 입장에서는 자료구조가 내부적으로</p>
<pre><code class="language-text">Array인지
Linked List인지</code></pre>
<p>알고 싶지 않을 수도 있다.</p>
<p>그냥</p>
<blockquote>
<p>저장된 데이터를 처음부터 하나씩 보고 싶다.</p>
</blockquote>
<p>는 요구가 있을 수 있다.</p>
<p>이때 Iterator를 사용할 수 있다.</p>
<p>Iterator는 데이터를 하나씩 순회하기 위한 인터페이스다.</p>
<p>핵심 메서드는</p>
<pre><code class="language-java">hasNext()
next()</code></pre>
<p>이다.</p>
<pre><code class="language-text">hasNext()
→ 다음 데이터가 존재하는가?

next()
→ 다음 데이터를 가져온다.</code></pre>
<hr>
<h1 id="11-iterable과-iterator는-다르다">11. Iterable과 Iterator는 다르다</h1>
<p>이 둘은 이름이 비슷해서 구분할 필요가 있다.</p>
<p><code>Iterable</code>은 Iterator를 제공하는 객체다.</p>
<p>개념적으로</p>
<pre><code class="language-java">interface Iterable&lt;Item&gt; {
    Iterator&lt;Item&gt; iterator();
}</code></pre>
<p>형태다.</p>
<p>반면 Iterator는 실제 순회를 담당한다.</p>
<pre><code class="language-java">interface Iterator&lt;Item&gt; {

    boolean hasNext();

    Item next();
}</code></pre>
<p>즉 관계를 생각하면</p>
<pre><code class="language-text">Iterable
   │
   │ iterator()
   ↓
Iterator
   │
   ├── hasNext()
   └── next()</code></pre>
<p>이다.</p>
<p>자료구조가 Iterable을 구현하면 다음과 같은 enhanced for문을 사용할 수 있다.</p>
<pre><code class="language-java">for (String item : stack) {
    System.out.println(item);
}</code></pre>
<hr>
<h1 id="12-enhanced-for문-뒤에서는-무엇이-일어날까">12. enhanced for문 뒤에서는 무엇이 일어날까?</h1>
<p>다음 코드는 매우 간단해 보인다.</p>
<pre><code class="language-java">for (String item : stack) {
    System.out.println(item);
}</code></pre>
<p>하지만 Iterator를 명시적으로 사용하면 다음과 같은 흐름이다.</p>
<pre><code class="language-java">Iterator&lt;String&gt; it = stack.iterator();

while (it.hasNext()) {

    String item = it.next();

    System.out.println(item);
}</code></pre>
<p>즉</p>
<pre><code class="language-text">for-each</code></pre>
<p>를 이해하려면</p>
<pre><code class="language-text">Iterable
→ iterator()
→ Iterator
→ hasNext()
→ next()</code></pre>
<p>의 연결을 알고 있으면 된다.</p>
<hr>
<h1 id="13-bag은-무엇일까">13. Bag은 무엇일까?</h1>
<p>이번 장에서는 Bag이라는 자료구조도 등장한다.</p>
<p>Bag은 데이터를 추가하고 순회할 수 있는 자료구조다.</p>
<p>특징은 <strong>순서가 중요하지 않다</strong>는 것이다.</p>
<pre><code class="language-text">Bag

add(A)
add(B)
add(C)

→ 저장

순회 가능
→ 하지만 순서 자체가 핵심이 아님</code></pre>
<p>강의자료에서는 Bag을</p>
<pre><code class="language-text">Stack without pop

또는

Queue without dequeue</code></pre>
<p>처럼 생각할 수 있다고 설명한다.</p>
<p>즉 데이터를 넣은 뒤 제거 순서를 관리하는 것보다 <strong>데이터를 모아두고 순회하는 것</strong>에 초점이 있다.</p>
<hr>
<h1 id="14-stack은-실제-어디에서-사용될까">14. Stack은 실제 어디에서 사용될까?</h1>
<p>Stack은 LIFO 구조다.</p>
<pre><code class="language-text">Last In First Out</code></pre>
<p>마지막에 들어온 데이터가 먼저 나온다.</p>
<p>Stack은 실제 여러 곳에서 사용된다.</p>
<pre><code class="language-text">Compiler Parsing
Java Virtual Machine
문서 편집기의 Undo
웹 브라우저의 Back 버튼
함수 호출 관리</code></pre>
<p>예를 들어 브라우저에서</p>
<pre><code class="language-text">Google
↓
Naver
↓
GitHub</code></pre>
<p>순서로 이동했다고 하자.</p>
<p>뒤로 가기를 누르면 가장 최근 페이지인 GitHub에서 이전 페이지로 돌아간다.</p>
<p>이런 구조가 Stack의 LIFO와 잘 맞는다.</p>
<hr>
<h1 id="15-수식-계산에도-stack을-사용할-수-있다">15. 수식 계산에도 Stack을 사용할 수 있다</h1>
<p>강의자료에서는 Stack의 응용으로 <strong>Two-Stack Algorithm</strong>을 소개한다.</p>
<p>하나는 연산자를 저장한다.</p>
<pre><code class="language-text">Operator Stack</code></pre>
<p>다른 하나는 값을 저장한다.</p>
<pre><code class="language-text">Value Stack</code></pre>
<p>규칙은 다음과 같다.</p>
<pre><code class="language-text">숫자
→ Value Stack에 push

연산자
→ Operator Stack에 push

왼쪽 괄호 (
→ 무시

오른쪽 괄호 )
→ 연산자 하나와 값 두 개를 pop
→ 계산
→ 결과를 Value Stack에 다시 push</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">(1 + ((2 + 3) * (4 * 5)))</code></pre>
<p>같은 수식을 두 개의 Stack을 이용해 계산할 수 있다.</p>
<p>이 예제에서 중요한 것은 Stack이 단순히 데이터를 뒤집는 자료구조가 아니라 <strong>아직 처리하지 않은 연산을 임시로 보관하는 데 사용할 수 있다는 것</strong>이다.</p>
<hr>
<h1 id="16-stack-두-개로-queue를-만들-수-있을까">16. Stack 두 개로 Queue를 만들 수 있을까?</h1>
<p>강의자료에는 흥미로운 문제가 나온다.</p>
<blockquote>
<p><strong>Stack 두 개를 이용해서 Queue를 구현하라.</strong></p>
</blockquote>
<p>처음에는 이상하다.</p>
<p>Stack은</p>
<pre><code class="language-text">LIFO</code></pre>
<p>이고 Queue는</p>
<pre><code class="language-text">FIFO</code></pre>
<p>다.</p>
<p>서로 반대인데 어떻게 Stack으로 Queue를 만들 수 있을까?</p>
<p>핵심은 <strong>두 번 뒤집는 것</strong>이다.</p>
<p>예를 들어 Stack1에</p>
<pre><code class="language-text">A
B
C
D</code></pre>
<p>순서로 넣었다고 하자.</p>
<p>Stack의 top에는 가장 최근 데이터가 있다.</p>
<pre><code class="language-text">Stack1

TOP
 ↓
[D]
[C]
[B]
[A]</code></pre>
<p>이 데이터를 하나씩 pop해서 Stack2에 넣는다.</p>
<pre><code class="language-text">Stack2

TOP
 ↓
[A]
[B]
[C]
[D]</code></pre>
<p>순서가 다시 뒤집혔다.</p>
<p>이제 Stack2에서 pop하면</p>
<pre><code class="language-text">A</code></pre>
<p>가 먼저 나온다.</p>
<p>즉 가장 먼저 들어온 A가 가장 먼저 나온다.</p>
<pre><code class="language-text">FIFO</code></pre>
<p>가 만들어졌다.</p>
<hr>
<h1 id="17-그런데-매번-옮기면-느리지-않을까">17. 그런데 매번 옮기면 느리지 않을까?</h1>
<p>여기서 중요한 것이 <strong>Amortized Analysis</strong>다.</p>
<p>매 연산마다 모든 데이터를</p>
<pre><code class="language-text">Stack1 → Stack2</code></pre>
<p>로 옮긴다면 비용이 상당히 커질 수 있다.</p>
<p>하지만 데이터를 한 번 이동시킨 뒤 Stack2에 남겨두고 필요한 동안 계속 사용하면 이야기가 달라진다.</p>
<p>각 원소의 이동을 보면 대략</p>
<pre><code class="language-text">Stack1에 push
↓
필요할 때 Stack2로 이동
↓
Stack2에서 pop</code></pre>
<p>정도의 제한된 횟수만 처리된다.</p>
<p>어떤 한 번의 연산은 O(N)이 걸릴 수 있지만 여러 연산 전체의 비용을 나눠보면 연산 하나당 평균적인 비용을 상수 수준으로 볼 수 있다.</p>
<p>이것이 <strong>amortized constant time</strong>을 이해하는 핵심이다.</p>
<blockquote>
<p>최악의 한 번만 보는 것이 아니라 여러 연산에 걸친 전체 비용을 나누어 생각한다.</p>
</blockquote>
<hr>
<h1 id="18-메모리-사용량도-알고리즘의-비용이다">18. 메모리 사용량도 알고리즘의 비용이다</h1>
<p>알고리즘을 평가할 때 실행 시간만 중요한 것은 아니다.</p>
<p>메모리를 얼마나 사용하는지도 중요하다.</p>
<p>Java 객체에는 우리가 선언한 필드만 들어 있는 것이 아니다.</p>
<p>개념적으로</p>
<pre><code class="language-text">Object Overhead
Fields
Padding</code></pre>
<p>등이 존재할 수 있다.</p>
<p>예를 들어 <code>Integer</code> 객체라면 단순히</p>
<pre><code class="language-java">int x;</code></pre>
<p>4바이트만 사용하는 것으로 끝나지 않는다.</p>
<p>객체 자체를 관리하기 위한 추가 메모리가 필요하다.</p>
<p>Linked List의 Node도 마찬가지다.</p>
<pre><code class="language-java">class Node {

    Item item;
    Node next;
}</code></pre>
<p>여기에는</p>
<pre><code class="language-text">item reference
next reference
object overhead</code></pre>
<p>등이 필요하다.</p>
<p>그래서 Linked List가 Array보다 추가적인 메모리를 사용할 수 있다는 설명과 연결된다.</p>
<hr>
<h1 id="19-배열도-데이터만큼의-메모리만-사용하는-것은-아니다">19. 배열도 데이터만큼의 메모리만 사용하는 것은 아니다</h1>
<p>배열 역시 객체다.</p>
<p>예를 들어</p>
<pre><code class="language-java">int[] a = new int[N];</code></pre>
<p>이라면 N개의 int 값뿐 아니라 배열 객체 자체를 관리하기 위한 메모리도 존재한다.</p>
<p>강의자료에서는 배열의 종류에 따라 필요한 메모리를 비교한다.</p>
<pre><code class="language-text">int[]
double[]
Date[]
double[][]</code></pre>
<p>특히 객체 배열은 객체 자체를 배열 안에 직접 저장하는 것이 아니라 <strong>객체를 가리키는 reference들을 저장한다</strong>는 점을 구분해야 한다.</p>
<pre><code class="language-text">Date[]

[ref] → Date 객체
[ref] → Date 객체
[ref] → Date 객체</code></pre>
<p>따라서 객체 배열의 메모리를 계산할 때는</p>
<pre><code class="language-text">배열 자체
+
reference
+
실제 객체</code></pre>
<p>를 구분해서 생각해야 한다.</p>
<hr>
<h1 id="20-이제-정렬로-넘어가자">20. 이제 정렬로 넘어가자</h1>
<p>3장의 후반부에서는 Elementary Sorts를 다룬다.</p>
<p>대표적으로</p>
<pre><code class="language-text">Selection Sort
Insertion Sort
Shellsort</code></pre>
<p>가 등장한다.</p>
<p>먼저 Selection Sort와 Insertion Sort의 차이를 이해해야 한다.</p>
<hr>
<h1 id="21-selection-sort는-어떻게-동작할까">21. Selection Sort는 어떻게 동작할까?</h1>
<p>Selection Sort는 이름 그대로 <strong>가장 작은 값을 선택</strong>한다.</p>
<p>배열이 다음과 같다고 하자.</p>
<pre><code class="language-text">[5, 3, 4, 1, 2]</code></pre>
<p>첫 번째 위치에 들어갈 가장 작은 값을 찾는다.</p>
<pre><code class="language-text">[5, 3, 4, 1, 2]
          ↑
         최소</code></pre>
<p>1을 찾았다.</p>
<p>첫 번째 값 5와 교환한다.</p>
<pre><code class="language-text">[1, 3, 4, 5, 2]</code></pre>
<p>이제 첫 번째 위치는 정렬이 끝났다.</p>
<p>다음 범위에서 다시 가장 작은 값을 찾는다.</p>
<pre><code class="language-text">[1 | 3, 4, 5, 2]
                ↑
               최소</code></pre>
<p>2와 3을 교환한다.</p>
<pre><code class="language-text">[1, 2, 4, 5, 3]</code></pre>
<p>이 과정을 반복한다.</p>
<p>즉 Selection Sort의 핵심은</p>
<pre><code class="language-text">현재 위치
↓
오른쪽에서 최소값 탐색
↓
현재 위치와 최소값 교환</code></pre>
<p>이다.</p>
<hr>
<h1 id="22-selection-sort-코드를-읽어보자">22. Selection Sort 코드를 읽어보자</h1>
<p>강의자료의 핵심 코드는 다음 구조다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; n; i++) {

    int min = i;

    for (int j = i + 1; j &lt; n; j++) {

        if (less(a[j], a[min])) {
            min = j;
        }
    }

    exch(a, i, min);
}</code></pre>
<p>한 줄씩 보면 어렵지 않다.</p>
<pre><code class="language-java">int min = i;</code></pre>
<p>현재 위치를 일단 최소값이라고 가정한다.</p>
<p>그다음</p>
<pre><code class="language-java">for (int j = i + 1; j &lt; n; j++)</code></pre>
<p>로 오른쪽 데이터를 전부 확인한다.</p>
<p>더 작은 값이 발견되면</p>
<pre><code class="language-java">min = j;</code></pre>
<p>로 최소값의 위치를 바꾼다.</p>
<p>탐색이 끝나면</p>
<pre><code class="language-java">exch(a, i, min);</code></pre>
<p>으로 현재 위치와 최소값을 교환한다.</p>
<blockquote>
<p><strong>강의자료에서 Selection Sort 코드 부분에 &#39;코드 시험&#39; 표시가 있으므로 직접 손으로 추적할 수 있어야 한다.</strong></p>
</blockquote>
<hr>
<h1 id="23-selection-sort는-이미-정렬되어-있으면-빨라질까">23. Selection Sort는 이미 정렬되어 있으면 빨라질까?</h1>
<p>다음 배열을 생각해보자.</p>
<pre><code class="language-text">1 2 3 4 5</code></pre>
<p>이미 정렬되어 있다.</p>
<p>그렇다면 Selection Sort는 바로 끝날까?</p>
<p>아니다.</p>
<p>Selection Sort는 현재 값이 정말 최소인지 확인하기 위해 오른쪽 데이터를 계속 검사한다.</p>
<p>첫 번째 단계에서는</p>
<pre><code class="language-text">N - 1</code></pre>
<p>번 비교한다.</p>
<p>그다음에는</p>
<pre><code class="language-text">N - 2</code></pre>
<p>번 비교한다.</p>
<p>계속하면</p>
<pre><code class="language-text">(N-1) + (N-2) + ... + 1</code></pre>
<p>이 된다.</p>
<p>이는 대략</p>
<pre><code class="language-text">N² / 2</code></pre>
<p>에 비례한다.</p>
<p>따라서 Selection Sort는 입력 배열이 이미 정렬되어 있더라도 비교 횟수가 크게 줄지 않는다.</p>
<hr>
<h1 id="24-insertion-sort는-어떻게-동작할까">24. Insertion Sort는 어떻게 동작할까?</h1>
<p>Insertion Sort는 앞부분이 이미 정렬되어 있다고 생각하고 새로운 값을 적절한 위치에 끼워 넣는다.</p>
<p>카드를 손으로 정렬하는 모습을 생각하면 이해하기 쉽다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[2, 5, 7 | 4]</code></pre>
<p>앞의</p>
<pre><code class="language-text">2, 5, 7</code></pre>
<p>은 이미 정렬되어 있다.</p>
<p>새로운 값 4를 넣으려면 왼쪽으로 이동하면서 비교한다.</p>
<pre><code class="language-text">4 &lt; 7
→ 이동

4 &lt; 5
→ 이동

4 &gt; 2
→ 멈춤</code></pre>
<p>결과는</p>
<pre><code class="language-text">[2, 4, 5, 7]</code></pre>
<p>이 된다.</p>
<p>즉 Insertion Sort는</p>
<blockquote>
<p><strong>현재 원소를 왼쪽의 정렬된 영역에서 알맞은 위치까지 이동시킨다.</strong></p>
</blockquote>
<hr>
<h1 id="25-selection-sort와-insertion-sort의-결정적인-차이">25. Selection Sort와 Insertion Sort의 결정적인 차이</h1>
<p>Selection Sort는 매번 남은 데이터 전체를 확인해서 최소값을 찾는다.</p>
<pre><code class="language-text">Selection Sort

최솟값 찾기
↓
교환
↓
최솟값 찾기
↓
교환</code></pre>
<p>반면 Insertion Sort는 현재 데이터가 이미 올바른 위치에 가까우면 많은 이동을 할 필요가 없다.</p>
<p>그래서 <strong>입력 데이터가 이미 어느 정도 정렬되어 있는가</strong>가 성능에 영향을 준다.</p>
<p>강의자료에서 Insertion Sort의 Best Case는</p>
<pre><code class="language-text">이미 오름차순으로 정렬된 배열</code></pre>
<p>이다.</p>
<p>이 경우 각 원소마다 바로 왼쪽 정도만 확인하면 된다.</p>
<p>따라서</p>
<pre><code class="language-text">N - 1 compares
0 exchanges</code></pre>
<p>정도가 된다.</p>
<p>반면 Worst Case는 역순이다.</p>
<pre><code class="language-text">5 4 3 2 1</code></pre>
<p>새로운 원소가 들어올 때마다 왼쪽 끝까지 이동해야 한다.</p>
<p>강의자료에서는 대략</p>
<pre><code class="language-text">1/2 N² compares
1/2 N² exchanges</code></pre>
<p>로 설명한다.</p>
<blockquote>
<p><strong>강의자료에 &#39;삽입정렬 시험&#39;이라고 직접 표시되어 있으므로 Best Case와 Worst Case의 차이를 특히 확인할 필요가 있다.</strong></p>
</blockquote>
<hr>
<h1 id="26-왜-insertion-sort를-개선하려고-했을까">26. 왜 Insertion Sort를 개선하려고 했을까?</h1>
<p>Insertion Sort에는 약점이 있다.</p>
<p>원소가 한 번에 크게 이동하지 못한다.</p>
<p>예를 들어 작은 값이 배열의 아주 오른쪽에 있다고 하자.</p>
<pre><code class="language-text">[9, 8, 7, 6, 5, 4, 3, 2, 1]
                                ↑</code></pre>
<p>1이 맨 앞으로 가려면</p>
<pre><code class="language-text">1칸
1칸
1칸
1칸
...</code></pre>
<p>씩 계속 이동해야 한다.</p>
<p>즉 멀리 떨어진 위치로 이동해야 하는 원소가 많으면 비효율적이다.</p>
<p>여기서 등장하는 것이 <strong>Shellsort</strong>다.</p>
<hr>
<h1 id="27-shellsort는-insertion-sort를-어떻게-개선할까">27. Shellsort는 Insertion Sort를 어떻게 개선할까?</h1>
<p>Shellsort는 Insertion Sort의 확장이라고 볼 수 있다.</p>
<p>핵심 아이디어는 간단하다.</p>
<blockquote>
<p><strong>처음부터 옆에 있는 데이터만 비교하지 말고 멀리 떨어진 데이터끼리 먼저 정렬하자.</strong></p>
</blockquote>
<p>이를 <code>h-sort</code>라고 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">h = 4</code></pre>
<p>라면 바로 옆의 데이터가 아니라 4칸 떨어진 데이터들을 비교한다.</p>
<pre><code class="language-text">0 ↔ 4 ↔ 8
1 ↔ 5 ↔ 9
2 ↔ 6 ↔ 10
3 ↔ 7 ↔ 11</code></pre>
<p>이렇게 하면 멀리 떨어져 있던 값이 한 번에 큰 거리를 이동할 수 있다.</p>
<p>강의자료에서 표현한 핵심은</p>
<pre><code class="language-text">Big increments
→ small subarray

Small increments
→ nearly in order</code></pre>
<p>이다.</p>
<p>처음에는 큰 간격으로 거칠게 정렬한다.</p>
<p>그다음 간격을 줄인다.</p>
<pre><code class="language-text">큰 h
↓
부분적으로 정렬
↓
더 작은 h
↓
더 정렬됨
↓
h = 1
↓
Insertion Sort</code></pre>
<p>마지막에 h가 1이 되면 일반적인 Insertion Sort와 같은 형태가 된다.</p>
<p>하지만 그 시점에는 배열이 이미 상당히 정렬되어 있으므로 Insertion Sort가 훨씬 빠르게 동작할 수 있다.</p>
<hr>
<h1 id="28-shellsort의-h는-어떻게-정할까">28. Shellsort의 h는 어떻게 정할까?</h1>
<p>강의에서는 <strong>Knuth의 3x+1 증가 수열</strong>을 사용한다.</p>
<pre><code class="language-text">1, 4, 13, 40, 121, ...</code></pre>
<p>다음 h를 만드는 식은</p>
<pre><code class="language-text">h = 3h + 1</code></pre>
<p>이다.</p>
<p>코드에서는 다음과 같은 구조가 등장한다.</p>
<pre><code class="language-java">int h = 1;

while (h &lt; N / 3) {
    h = 3 * h + 1;
}</code></pre>
<p>예를 들어 데이터가 16개라면 사용할 수 있는 h는</p>
<pre><code class="language-text">1
4
13</code></pre>
<p>이다.</p>
<p>실제 정렬은 큰 값부터 진행한다.</p>
<pre><code class="language-text">13-sort
↓
4-sort
↓
1-sort</code></pre>
<p>즉</p>
<pre><code class="language-text">멀리 떨어진 데이터 먼저 정리
↓
간격을 줄이면서 다시 정리
↓
마지막에 일반 Insertion Sort</code></pre>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="29-shellsort-코드에서-insertion-sort가-보이는-이유">29. Shellsort 코드에서 Insertion Sort가 보이는 이유</h1>
<p>Shellsort 코드를 보면 다음과 비슷한 부분이 등장한다.</p>
<pre><code class="language-java">for (int i = h; i &lt; N; i++) {

    for (int j = i;
         j &gt;= h &amp;&amp; less(a[j], a[j-h]);
         j -= h) {

        exch(a, j, j-h);
    }
}</code></pre>
<p>일반 Insertion Sort와 상당히 비슷하다.</p>
<p>차이는</p>
<pre><code class="language-text">1칸씩 이동</code></pre>
<p>하는 것이 아니라</p>
<pre><code class="language-text">h칸씩 이동</code></pre>
<p>한다는 것이다.</p>
<p>일반 Insertion Sort가</p>
<pre><code class="language-text">j--</code></pre>
<p>처럼 이동한다면 Shellsort는</p>
<pre><code class="language-text">j -= h</code></pre>
<p>처럼 이동한다.</p>
<p>그리고 h를 계속 줄이다가 마지막에는</p>
<pre><code class="language-text">h = 1</code></pre>
<p>이 된다.</p>
<p>그러면</p>
<pre><code class="language-java">j -= 1;</code></pre>
<p>이 되므로 사실상 Insertion Sort와 같은 형태가 된다.</p>
<p>그래서 Shellsort를 <strong>Insertion Sort의 확장 버전</strong>이라고 이해할 수 있다.</p>
<hr>
<h1 id="30-shellsort는-왜-더-빠를-수-있을까">30. Shellsort는 왜 더 빠를 수 있을까?</h1>
<p>Insertion Sort의 문제는 큰 값이나 작은 값이 멀리 이동해야 할 때 한 칸씩 이동한다는 것이다.</p>
<p>Shellsort는 큰 h부터 시작하므로 한 번의 교환으로 훨씬 멀리 이동할 수 있다.</p>
<pre><code class="language-text">Insertion Sort

A B C D E F G H
              ↑
한 칸씩 이동


Shellsort

A B C D E F G H
↑       ↑
h칸 단위 이동 가능</code></pre>
<p>그 결과 마지막 <code>h = 1</code> 단계에서는 배열이 이미 거의 정렬된 상태가 된다.</p>
<p>Insertion Sort는 거의 정렬된 배열에서 효율적이므로 마지막 단계가 빠르게 끝날 수 있다.</p>
<p>강의자료에서는 Knuth의 <code>3x+1</code> 증가 수열을 사용하는 Shellsort가 최악의 경우에도 대략 <code>N^(3/2)</code>에 비례한다고 설명하고 있으며, 추가 메모리를 사용하지 않고 코드도 비교적 작다는 점을 언급한다.</p>
<hr>
<h1 id="31-마지막은-shuffling">31. 마지막은 Shuffling</h1>
<p>정렬과 반대로 데이터를 무작위로 섞어야 하는 경우도 있다.</p>
<p>Shuffling의 목표는 단순히 아무렇게나 섞는 것이 아니다.</p>
<p>강의자료에서는 두 가지 목표를 제시한다.</p>
<pre><code class="language-text">1. Linear time에 배열을 무작위 permutation으로 만든다.

2. 가능한 모든 순열이 동일한 확률로 등장하도록 한다.</code></pre>
<p>즉 공정하게 섞어야 한다.</p>
<p>여기서 <strong>Knuth Shuffle</strong>을 사용한다.</p>
<hr>
<h1 id="32-knuth-shuffle">32. Knuth Shuffle</h1>
<p>강의자료의 코드는 다음 구조다.</p>
<pre><code class="language-java">public static void shuffle(Object[] a) {

    int N = a.length;

    for (int i = 0; i &lt; N; i++) {

        int r = StdRandom.uniform(i + 1);

        Object tmp = a[i];
        a[i] = a[r];
        a[r] = tmp;
    }
}</code></pre>
<p>핵심은 현재 위치 <code>i</code>에 도달했을 때</p>
<pre><code class="language-text">0 ~ i</code></pre>
<p>범위에서 임의의 위치 <code>r</code>을 선택한다는 것이다.</p>
<p>그리고</p>
<pre><code class="language-text">a[i]
a[r]</code></pre>
<p>을 교환한다.</p>
<p>각 위치를 한 번씩 처리하므로 전체 반복은 N번이다.</p>
<p>따라서 실행 시간은</p>
<pre><code class="language-text">O(N)</code></pre>
<p>으로 볼 수 있다.</p>
<hr>
<h1 id="33-3장을-하나의-흐름으로-보면">33. 3장을 하나의 흐름으로 보면</h1>
<p>처음에는 Queue, Generic, Iterator, 정렬이 왜 한 장에 같이 있는지 조금 뜬금없어 보였다.</p>
<p>하지만 전체를 다시 보면 계속 같은 질문을 하고 있었다.</p>
<pre><code class="language-text">Queue
→ 데이터를 어떤 순서로 넣고 뺄 것인가?

Generics
→ 같은 자료구조를 여러 타입에 어떻게 안전하게 사용할 것인가?

Iterator
→ 내부 구현을 몰라도 데이터를 어떻게 순회할 것인가?

Bag
→ 순서가 중요하지 않은 데이터는 어떻게 모아둘 것인가?

Stack
→ LIFO 구조를 어디에 활용할 수 있는가?

Two-Stack Queue
→ 기존 자료구조를 조합해서 다른 자료구조를 만들 수 있는가?

Memory
→ 자료구조가 실제로 얼마나 많은 공간을 사용하는가?

Selection Sort
→ 최솟값을 반복해서 선택하면 정렬할 수 있는가?

Insertion Sort
→ 정렬된 영역에 새로운 값을 삽입하면 어떻게 되는가?

Shellsort
→ Insertion Sort의 한 칸 이동 문제를 어떻게 개선할 수 있는가?

Shuffling
→ 데이터를 공정하게 무작위 순서로 만들려면 어떻게 해야 하는가?</code></pre>
<p>결국 자료구조와 알고리즘은 단순히 코드를 외우는 문제가 아니었다.</p>
<p><strong>어떤 연산을 빠르게 만들기 위해 무엇을 저장하고, 어떤 비용을 감수할 것인가</strong>를 계속 판단하는 과정이었다.</p>
<hr>
<h1 id="시험-전에-다시-볼-부분">시험 전에 다시 볼 부분</h1>
<p>이번 강의자료에서 특히 다시 볼 부분은 Queue의 <code>front/rear</code>, Generic, Iterator 그리고 정렬 코드다.</p>
<p>Queue에서는 다음 문제를 바로 판단할 수 있어야 한다.</p>
<pre><code class="language-text">Singly Linked List
front만 유지
rear는 유지하지 않음

enqueue → O(N)
dequeue → O(1)</code></pre>
<p>Generic에서는</p>
<pre><code class="language-text">Object 사용
→ casting 필요
→ type mismatch가 runtime에 발견될 가능성

Generics 사용
→ casting 감소
→ type mismatch를 compile time에 발견</code></pre>
<p>의 차이를 이해해야 한다.</p>
<p>Iterator에서는</p>
<pre><code class="language-text">Iterable
→ iterator() 제공

Iterator
→ hasNext()
→ next()</code></pre>
<p>관계를 기억한다.</p>
<p>그리고 정렬에서는 특히 다음 차이가 중요하다.</p>
<pre><code class="language-text">Selection Sort
→ 남은 범위에서 최소값을 찾음
→ 이미 정렬되어 있어도 비교를 계속함

Insertion Sort
→ 왼쪽의 정렬된 영역에 현재 값을 삽입
→ 이미 정렬된 배열에서 매우 적은 작업
→ 역순에서는 많은 비교와 교환

Shellsort
→ h칸 떨어진 데이터부터 정렬
→ h를 줄여가며 배열을 거의 정렬된 상태로 만듦
→ 마지막 h=1에서는 Insertion Sort</code></pre>
<p>코드 역시 단순히 외우기보다 각 반복문의 역할을 이해해야 한다.</p>
<pre><code class="language-text">Selection Sort

i
→ 이번에 확정할 위치

j
→ 오른쪽에서 최소값 탐색

min
→ 현재까지 발견한 최소값의 위치</code></pre>
<p>Shellsort에서는</p>
<pre><code class="language-text">h
→ 비교할 데이터 사이의 간격

j -= h
→ 한 칸이 아니라 h칸씩 왼쪽으로 이동</code></pre>
<p>라는 의미를 잡고 있으면 코드를 훨씬 쉽게 읽을 수 있다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>3장을 공부하면서 가장 중요하다고 느낀 것은 <strong>자료구조의 모양 자체보다 왜 그런 구현을 선택하는지 이해하는 것</strong>이었다.</p>
<p>Queue에서 front와 rear를 모두 저장하는 이유도 결국 시간 때문이다.</p>
<pre><code class="language-text">rear 없음
→ enqueue 때 마지막 노드를 찾아야 함
→ O(N)

rear 있음
→ 마지막 위치를 이미 알고 있음
→ 빠르게 삽입 가능</code></pre>
<p>Generic도 마찬가지다.</p>
<pre><code class="language-text">Object
→ 무엇이든 받을 수 있음
→ 대신 casting과 runtime type error 문제

Generic
→ 사용할 타입을 지정
→ compile time에 type 검사</code></pre>
<p>Insertion Sort와 Shellsort의 관계도 같은 관점에서 이해할 수 있다.</p>
<pre><code class="language-text">Insertion Sort의 문제
→ 멀리 있는 데이터도 한 칸씩 이동

Shellsort
→ 처음에는 큰 간격으로 이동
→ 배열을 부분적으로 정렬
→ 간격을 줄임
→ 마지막에는 거의 정렬된 배열에 Insertion Sort 적용</code></pre>
<p>결국 이번 장을 관통하는 질문은 이것이라고 생각한다.</p>
<blockquote>
<p><strong>“이 연산을 더 효율적으로 만들기 위해 어떤 정보를 추가로 저장하거나, 어떤 처리 순서를 선택해야 할까?”</strong></p>
</blockquote>
<p>Queue의 <code>rear</code> 하나부터 Shellsort의 <code>h</code>까지, 서로 달라 보였던 개념들이 결국 이 질문으로 연결됐다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[`(int n) -> n + 9`는 도대체 무엇일까? | Runnable부터 Java 람다와 예외 처리까지]]></title>
            <link>https://velog.io/@daehyun_lee/int-n-n-9%EB%8A%94-%EB%8F%84%EB%8C%80%EC%B2%B4-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-Runnable%EB%B6%80%ED%84%B0-Java-%EB%9E%8C%EB%8B%A4%EC%99%80-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/int-n-n-9%EB%8A%94-%EB%8F%84%EB%8C%80%EC%B2%B4-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-Runnable%EB%B6%80%ED%84%B0-Java-%EB%9E%8C%EB%8B%A4%EC%99%80-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 03 Oct 2026 15:44:56 GMT</pubDate>
            <description><![CDATA[<h1 id="int-n---n--9는-도대체-무엇일까--runnable부터-java-람다와-예외-처리까지"><code>(int n) -&gt; n + 9</code>는 도대체 무엇일까? | Runnable부터 Java 람다와 예외 처리까지</h1>
<p>Java 문제를 풀다가 갑자기 이런 코드가 등장했다.</p>
<pre><code class="language-java">run((int n) -&gt; n + 9)</code></pre>
<p>처음 봤을 때 가장 먼저 든 생각은</p>
<blockquote>
<p><strong><code>-&gt;</code>는 도대체 뭐지?</strong></p>
</blockquote>
<p>였다.</p>
<p>Java에서 메서드라면 보통 이렇게 생겼다.</p>
<pre><code class="language-java">int add(int n) {
    return n + 9;
}</code></pre>
<p>그런데 문제에서는 메서드 이름도 없이</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>라고 적혀 있었다.</p>
<p>더 복잡한 문제에서는 다음과 같은 코드까지 등장했다.</p>
<pre><code class="language-java">F f = (x) -&gt; {
    if (x &gt; 2) {
        throw new Exception();
    }

    return x * 2;
};</code></pre>
<p>처음에는 이것이 <code>F</code>와 무슨 관계가 있는지, <code>apply()</code>는 어디로 사라졌는지, <code>run(f)</code>를 호출하면 무엇이 실행되는지 이해하기 어려웠다.</p>
<p>그런데 이 문제를 이해하려면 먼저 Java에서 <strong>동작 자체를 객체로 전달하는 방식</strong>부터 생각해볼 필요가 있었다.</p>
<hr>
<h2 id="1-먼저-runnable을-보자">1. 먼저 Runnable을 보자</h2>
<p>Java에서 다음과 같은 코드를 볼 수 있다.</p>
<pre><code class="language-java">class Car implements Runnable {

    @Override
    public void run() {
        System.out.println(&quot;run&quot;);
    }
}</code></pre>
<p><code>Runnable</code>은 인터페이스다.</p>
<p>핵심적으로 실행할 작업을 <code>run()</code>이라는 메서드로 표현한다.</p>
<pre><code class="language-java">public interface Runnable {
    void run();
}</code></pre>
<p>따라서</p>
<pre><code class="language-java">class Car implements Runnable</code></pre>
<p>이라고 하면 Car는 Runnable이 요구하는</p>
<pre><code class="language-java">run()</code></pre>
<p>을 구현해야 한다.</p>
<pre><code class="language-java">class Car implements Runnable {

    public void run() {
        System.out.println(&quot;run&quot;);
    }
}</code></pre>
<p>즉 <code>Car</code> 객체는 단순히 자동차라는 데이터만 표현하는 것이 아니라 <strong>실행할 작업을 가진 객체</strong>로 사용할 수 있다.</p>
<hr>
<h2 id="2-왜-runnable-객체를-thread에-전달할-수-있을까">2. 왜 Runnable 객체를 Thread에 전달할 수 있을까?</h2>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">Thread t = new Thread(new Car());</code></pre>
<p>처음에는 <code>Thread</code> 생성자 안에 왜 <code>Car</code> 객체를 넣을 수 있는지 이상했다.</p>
<p>하지만 <code>Car</code>는</p>
<pre><code class="language-java">implements Runnable</code></pre>
<p>을 했다.</p>
<p>따라서 <code>Car</code> 객체는 Runnable 타입으로 사용할 수 있다.</p>
<p>개념적으로</p>
<pre><code class="language-java">Runnable task = new Car();</code></pre>
<p>가 가능한 것이다.</p>
<p>그리고 Thread는 실행할 작업을 Runnable 형태로 전달받을 수 있다.</p>
<pre><code class="language-text">new Car()
    ↓
Runnable 구현 객체
    ↓
Thread에 전달
    ↓
Thread가 실행할 작업을 가짐</code></pre>
<p>그래서</p>
<pre><code class="language-java">Thread t = new Thread(new Car());</code></pre>
<p>가 가능하다.</p>
<hr>
<h2 id="3-start와-run은-다르다">3. <code>start()</code>와 <code>run()</code>은 다르다</h2>
<p>여기서 시험에서 자주 헷갈리는 부분이 하나 있다.</p>
<pre><code class="language-java">t.start();</code></pre>
<p>와</p>
<pre><code class="language-java">t.run();</code></pre>
<p>은 같지 않다.</p>
<p><code>start()</code>는 새로운 스레드의 실행을 시작한다.</p>
<pre><code class="language-text">t.start()
   ↓
새로운 스레드 시작
   ↓
run() 실행</code></pre>
<p>반면</p>
<pre><code class="language-java">t.run();</code></pre>
<p>을 직접 호출하면 일반적인 메서드 호출처럼 동작한다.</p>
<p>즉</p>
<pre><code class="language-text">start()
→ 새로운 스레드의 실행을 시작

run()
→ run() 메서드를 직접 호출</code></pre>
<p>이라고 구분해야 한다.</p>
<p>이 부분을 이해하고 나니 Runnable이 단순히 <code>run()</code>이라는 이름을 외우는 인터페이스가 아니라 <strong>실행할 동작을 하나의 객체로 표현하는 방식</strong>이라는 것이 조금씩 보이기 시작했다.</p>
<p>그리고 이 생각이 람다식으로 연결된다.</p>
<hr>
<h2 id="4-함수형-인터페이스란">4. 함수형 인터페이스란?</h2>
<p>다음 인터페이스가 있다고 하자.</p>
<pre><code class="language-java">interface F {

    int apply(int x) throws Exception;
}</code></pre>
<p><code>F</code>에는 구현해야 할 추상 메서드가 하나 있다.</p>
<pre><code class="language-java">int apply(int x) throws Exception;</code></pre>
<p>이처럼 람다의 대상으로 사용할 수 있는 <strong>추상 메서드가 하나인 인터페이스</strong>를 함수형 인터페이스라고 한다.</p>
<p>여기서 중요한 것은 <code>F</code>를 구현하는 객체가 사실상 하나의 동작을 가지고 있다는 것이다.</p>
<pre><code class="language-text">F
└── apply(int x)</code></pre>
<p>그렇다면 이 인터페이스를 구현하려면 원래 어떻게 해야 할까?</p>
<hr>
<h2 id="5-클래스를-직접-만들어-구현할-수-있다">5. 클래스를 직접 만들어 구현할 수 있다</h2>
<p>가장 익숙한 방법은 클래스를 하나 만드는 것이다.</p>
<pre><code class="language-java">class MyFunction implements F {

    @Override
    public int apply(int x) throws Exception {

        if (x &gt; 2) {
            throw new Exception();
        }

        return x * 2;
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">F f = new MyFunction();</code></pre>
<p>이라고 하면 된다.</p>
<p>이제</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>을 호출하면 <code>MyFunction</code>에서 구현한 <code>apply()</code>가 실행된다.</p>
<p>하지만 이 동작 하나를 사용하기 위해 클래스를 따로 만드는 것이 번거로울 수도 있다.</p>
<hr>
<h2 id="6-익명-클래스로도-만들-수-있다">6. 익명 클래스로도 만들 수 있다</h2>
<p>별도의 클래스 이름이 필요하지 않다면 익명 클래스를 사용할 수도 있다.</p>
<pre><code class="language-java">F f = new F() {

    @Override
    public int apply(int x) throws Exception {

        if (x &gt; 2) {
            throw new Exception();
        }

        return x * 2;
    }
};</code></pre>
<p>별도의 <code>MyFunction</code> 클래스를 만들지는 않았지만 <code>F</code>의 <code>apply()</code>를 구현한 객체를 바로 만들었다.</p>
<p>그런데 이것도 상당히 길다.</p>
<p>우리가 정말 표현하고 싶은 핵심은 사실 다음 부분이다.</p>
<pre><code class="language-java">int apply(int x) throws Exception {

    if (x &gt; 2) {
        throw new Exception();
    }

    return x * 2;
}</code></pre>
<p>그리고 F에는 구현할 추상 메서드가 하나뿐이다.</p>
<p>그렇다면 Java 입장에서는 어떤 메서드를 구현하려는 것인지 이미 알 수 있다.</p>
<p>그래서 이 부분을 더 간결하게 표현할 수 있다.</p>
<p>그것이 <strong>람다식(Lambda Expression)</strong>이다.</p>
<hr>
<h2 id="7-람다식으로-바꾸면">7. 람다식으로 바꾸면</h2>
<p>익명 클래스 형태의 다음 코드를</p>
<pre><code class="language-java">F f = new F() {

    @Override
    public int apply(int x) throws Exception {

        if (x &gt; 2) {
            throw new Exception();
        }

        return x * 2;
    }
};</code></pre>
<p>람다로 표현하면 다음과 같이 간단해진다.</p>
<pre><code class="language-java">F f = (x) -&gt; {

    if (x &gt; 2) {
        throw new Exception();
    }

    return x * 2;
};</code></pre>
<p>처음에는 <code>apply</code>라는 이름이 사라져서 이상했다.</p>
<p>하지만 <code>F</code>에는 람다의 대상이 되는 추상 메서드가 하나뿐이다.</p>
<pre><code class="language-java">int apply(int x) throws Exception;</code></pre>
<p>따라서 Java는 이 람다가 <code>apply()</code>에 해당하는 동작이라는 것을 알 수 있다.</p>
<p>개념적으로는</p>
<pre><code class="language-text">F f = (x) -&gt; x * 2;</code></pre>
<p>를 볼 때</p>
<pre><code class="language-text">F의 추상 메서드
        ↓
apply(int x)
        ↓
구현 내용
        ↓
return x * 2;</code></pre>
<p>라고 연결해서 생각하면 된다.</p>
<hr>
<h2 id="8-int-n---n--9는-어떻게-읽을까">8. <code>(int n) -&gt; n + 9</code>는 어떻게 읽을까?</h2>
<p>이제 처음에 이해되지 않았던 코드를 다시 보자.</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>처음에는 문법을 세세하게 분석하기보다 다음처럼 읽는 것이 가장 쉬웠다.</p>
<blockquote>
<p><strong>int n을 받아서 n + 9를 반환한다.</strong></p>
</blockquote>
<p>즉</p>
<pre><code class="language-text">입력
 ↓
 n
 ↓
n + 9
 ↓
결과</code></pre>
<p>다.</p>
<p>예를 들어</p>
<pre><code class="language-java">(int n) -&gt; n * 2</code></pre>
<p>라면</p>
<blockquote>
<p>int n을 받아서 n의 2배를 반환한다.</p>
</blockquote>
<p>이고,</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>라면</p>
<blockquote>
<p>int n을 받아서 9를 더한 값을 반환한다.</p>
</blockquote>
<p>라고 읽으면 된다.</p>
<hr>
<h2 id="9-한-줄짜리-람다는-return도-생략할-수-있다">9. 한 줄짜리 람다는 return도 생략할 수 있다</h2>
<p>다음 람다를 보자.</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>여기에는 <code>return</code>이 없다.</p>
<p>하지만 값을 반환하는 표현식 하나로 구성된 람다는 결과를 그대로 반환하는 형태로 사용할 수 있다.</p>
<p>따라서 개념적으로</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>는</p>
<pre><code class="language-java">(int n) -&gt; {
    return n + 9;
}</code></pre>
<p>와 연결해서 이해할 수 있다.</p>
<p>그리고 F의 <code>apply()</code>와 연결하면</p>
<pre><code class="language-java">int apply(int n) {
    return n + 9;
}</code></pre>
<p>와 같은 동작이라고 생각할 수 있다.</p>
<hr>
<h2 id="10-매개변수-타입도-생략되는-이유">10. 매개변수 타입도 생략되는 이유</h2>
<p>다음 두 표현을 볼 수 있다.</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<pre><code class="language-java">(n) -&gt; n + 9</code></pre>
<p>상황에 따라 두 번째처럼 타입이 생략될 수도 있다.</p>
<p>왜냐하면 람다가 어떤 함수형 인터페이스를 대상으로 하는지 알면 매개변수 타입도 알 수 있기 때문이다.</p>
<p>예를 들어</p>
<pre><code class="language-java">interface F {
    int apply(int x) throws Exception;
}</code></pre>
<p>이라면 <code>apply()</code>의 매개변수는 이미</p>
<pre><code class="language-text">int</code></pre>
<p>라고 정의되어 있다.</p>
<p>그래서</p>
<pre><code class="language-java">F f = (x) -&gt; x * 2;</code></pre>
<p>에서 <code>x</code>가 <code>int</code>라는 것을 추론할 수 있다.</p>
<hr>
<h2 id="11-이제-실제-문제를-풀어보자">11. 이제 실제 문제를 풀어보자</h2>
<p>문제는 다음과 같다.</p>
<pre><code class="language-java">public class Main {

    static interface F {
        int apply(int x) throws Exception;
    }

    public static int run(F f) {

        try {
            return f.apply(3);

        } catch (Exception e) {
            return 7;
        }
    }

    public static void main(String[] args) {

        F f = (x) -&gt; {

            if (x &gt; 2) {
                throw new Exception();
            }

            return x * 2;
        };

        System.out.print(
            run(f) + run((int n) -&gt; n + 9)
        );
    }
}</code></pre>
<p>처음에는 코드가 복잡해 보이지만 람다를 <code>apply()</code>의 구현이라고 생각하면 하나씩 따라갈 수 있다.</p>
<hr>
<h2 id="12-먼저-runf를-계산해보자">12. 먼저 <code>run(f)</code>를 계산해보자</h2>
<p><code>run()</code>은 다음과 같다.</p>
<pre><code class="language-java">public static int run(F f) {

    try {
        return f.apply(3);

    } catch (Exception e) {
        return 7;
    }
}</code></pre>
<p>즉 <code>F</code> 객체를 하나 전달받아서</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>을 호출한다.</p>
<p>현재 전달한 <code>f</code>는 다음 람다다.</p>
<pre><code class="language-java">F f = (x) -&gt; {

    if (x &gt; 2) {
        throw new Exception();
    }

    return x * 2;
};</code></pre>
<p>이를 <code>apply()</code>처럼 펼쳐 생각해보면</p>
<pre><code class="language-java">int apply(int x) throws Exception {

    if (x &gt; 2) {
        throw new Exception();
    }

    return x * 2;
}</code></pre>
<p>와 같은 동작이다.</p>
<hr>
<h2 id="13-apply3을-실행하면">13. <code>apply(3)</code>을 실행하면</h2>
<p><code>run(f)</code> 내부에서는</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>을 호출한다.</p>
<p>따라서</p>
<pre><code class="language-text">x = 3</code></pre>
<p>이다.</p>
<p>조건을 확인한다.</p>
<pre><code class="language-java">if (x &gt; 2)</code></pre>
<p>즉</p>
<pre><code class="language-text">3 &gt; 2</code></pre>
<p>이므로 참이다.</p>
<p>따라서</p>
<pre><code class="language-java">throw new Exception();</code></pre>
<p>이 실행된다.</p>
<p>흐름은 다음과 같다.</p>
<pre><code class="language-text">run(f)
   ↓
f.apply(3)
   ↓
x = 3
   ↓
3 &gt; 2
   ↓
true
   ↓
Exception 발생</code></pre>
<hr>
<h2 id="14-예외가-발생하면-어디로-갈까">14. 예외가 발생하면 어디로 갈까?</h2>
<p><code>f.apply(3)</code>은 <code>try</code> 안에서 실행되고 있다.</p>
<pre><code class="language-java">try {
    return f.apply(3);
} catch (Exception e) {
    return 7;
}</code></pre>
<p>따라서 Exception이 발생하면 <code>catch</code>로 이동한다.</p>
<pre><code class="language-text">Exception 발생
      ↓
catch (Exception e)
      ↓
return 7</code></pre>
<p>결국</p>
<pre><code class="language-java">run(f)</code></pre>
<p>의 결과는</p>
<pre><code class="language-text">7</code></pre>
<p>이다.</p>
<hr>
<h2 id="15-두-번째-람다를-보자">15. 두 번째 람다를 보자</h2>
<p>두 번째 호출은 다음과 같다.</p>
<pre><code class="language-java">run((int n) -&gt; n + 9)</code></pre>
<p>처음에는 <code>run()</code>의 매개변수가 <code>F</code>인데 어떻게 갑자기 람다를 바로 넣을 수 있는지 헷갈렸다.</p>
<p><code>run()</code>을 다시 보면</p>
<pre><code class="language-java">public static int run(F f)</code></pre>
<p>다.</p>
<p>즉 <code>F</code> 타입의 동작을 전달해야 한다.</p>
<p>그리고 F는</p>
<pre><code class="language-java">interface F {
    int apply(int x) throws Exception;
}</code></pre>
<p>라는 하나의 추상 메서드를 가지고 있다.</p>
<p>따라서</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>는 그 <code>apply()</code>를 구현하는 동작으로 사용할 수 있다.</p>
<p>개념적으로</p>
<pre><code class="language-java">run((int n) -&gt; n + 9)</code></pre>
<p>를 다음처럼 생각할 수 있다.</p>
<pre><code class="language-java">F temp = (int n) -&gt; n + 9;
run(temp);</code></pre>
<hr>
<h2 id="16-두-번째-run의-결과">16. 두 번째 <code>run()</code>의 결과</h2>
<p><code>run()</code>에서는 항상</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>을 호출한다.</p>
<p>이번 <code>f</code>의 동작은</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>다.</p>
<p>따라서</p>
<pre><code class="language-text">n = 3</code></pre>
<p>이 들어간다.</p>
<pre><code class="language-text">3 + 9 = 12</code></pre>
<p>예외도 발생하지 않는다.</p>
<p>따라서</p>
<pre><code class="language-java">run((int n) -&gt; n + 9)</code></pre>
<p>의 결과는</p>
<pre><code class="language-text">12</code></pre>
<p>다.</p>
<hr>
<h2 id="17-최종-결과">17. 최종 결과</h2>
<p>원래 코드는</p>
<pre><code class="language-java">System.out.print(
    run(f) + run((int n) -&gt; n + 9)
);</code></pre>
<p>였다.</p>
<p>앞에서 각각 계산했다.</p>
<pre><code class="language-text">run(f)
→ 7</code></pre>
<p>그리고</p>
<pre><code class="language-text">run((int n) -&gt; n + 9)
→ 12</code></pre>
<p>따라서</p>
<pre><code class="language-text">7 + 12
= 19</code></pre>
<p>최종 출력은</p>
<pre><code class="language-text">19</code></pre>
<p>다.</p>
<hr>
<h2 id="18-람다-문제는-이렇게-풀면-편하다">18. 람다 문제는 이렇게 풀면 편하다</h2>
<p>처음에는 람다 문법을 그대로 따라가려고 해서 더 어려웠다.</p>
<p>오히려 람다를 만나면 <strong>원래 메서드 형태로 다시 펼쳐서 생각하는 것</strong>이 편했다.</p>
<p>예를 들어</p>
<pre><code class="language-java">F f = (x) -&gt; x * 2;</code></pre>
<p>가 있고</p>
<pre><code class="language-java">interface F {
    int apply(int x);
}</code></pre>
<p>라면 머릿속에서</p>
<pre><code class="language-java">int apply(int x) {
    return x * 2;
}</code></pre>
<p>로 바꿔본다.</p>
<p>그러면</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>도 바로 이해된다.</p>
<pre><code class="language-text">x = 3
↓
3 * 2
↓
6</code></pre>
<p>즉 시험에서는</p>
<pre><code class="language-text">람다 발견
   ↓
대상 인터페이스 확인
   ↓
추상 메서드 확인
   ↓
람다를 그 메서드의 구현으로 생각
   ↓
실제 전달되는 값 대입
   ↓
결과 계산</code></pre>
<p>순서로 따라가면 된다.</p>
<hr>
<h2 id="19-문자열--연산도-같이-헷갈렸다">19. 문자열 <code>+</code> 연산도 같이 헷갈렸다</h2>
<p>Java 문제를 풀면서 람다 외에도 출력 결과에서 자주 헷갈렸던 것이 문자열의 <code>+</code> 연산이었다.</p>
<p>예를 들어</p>
<pre><code class="language-java">int r1 = 9;
int r2 = 2;
char r3 = &#39;3&#39;;

System.out.println(r1 + r2 + &quot;2&quot; + r3);</code></pre>
<p>가 있다고 하자.</p>
<p>처음부터 전부 문자열로 합치는 것이 아니다.</p>
<p>Java의 <code>+</code> 연산은 왼쪽부터 진행된다.</p>
<p>먼저</p>
<pre><code class="language-java">r1 + r2</code></pre>
<p>를 계산한다.</p>
<pre><code class="language-text">9 + 2 = 11</code></pre>
<p>아직 둘 다 숫자이므로 숫자 덧셈이다.</p>
<p>그다음</p>
<pre><code class="language-text">11 + &quot;2&quot;</code></pre>
<p>를 계산한다.</p>
<p>여기서 String을 만났다.</p>
<p>따라서 문자열 연결이 된다.</p>
<pre><code class="language-text">&quot;112&quot;</code></pre>
<p>그리고</p>
<pre><code class="language-text">&quot;112&quot; + &#39;3&#39;</code></pre>
<p>도 문자열 연결이 된다.</p>
<p>결과는</p>
<pre><code class="language-text">&quot;1123&quot;</code></pre>
<p>이다.</p>
<hr>
<h2 id="20-1--2--3과-1--2--3">20. <code>1 + 2 + &quot;3&quot;</code>과 <code>&quot;1&quot; + 2 + 3</code></h2>
<p>이 차이는 시험에서 특히 주의해야 한다.</p>
<p>먼저</p>
<pre><code class="language-java">System.out.println(1 + 2 + &quot;3&quot;);</code></pre>
<p>를 보자.</p>
<p>왼쪽부터 계산한다.</p>
<pre><code class="language-text">1 + 2
↓
3

3 + &quot;3&quot;
↓
&quot;33&quot;</code></pre>
<p>따라서</p>
<pre><code class="language-text">33</code></pre>
<p>이 출력된다.</p>
<p>반면</p>
<pre><code class="language-java">System.out.println(&quot;1&quot; + 2 + 3);</code></pre>
<p>에서는 처음부터 String이 등장한다.</p>
<pre><code class="language-text">&quot;1&quot; + 2
↓
&quot;12&quot;

&quot;12&quot; + 3
↓
&quot;123&quot;</code></pre>
<p>따라서</p>
<pre><code class="language-text">123</code></pre>
<p>이 출력된다.</p>
<p>둘의 차이는 결국 <strong>String을 어느 시점에 만나는가</strong>다.</p>
<hr>
<h2 id="21-이번에-이해한-runnable과-람다의-연결">21. 이번에 이해한 Runnable과 람다의 연결</h2>
<p>처음에는 Runnable과 람다가 서로 다른 문법처럼 느껴졌다.</p>
<p>하지만 둘 다 <strong>어떤 동작을 전달한다</strong>는 관점으로 보면 연결된다.</p>
<p>Runnable에는 실행할 동작을 나타내는</p>
<pre><code class="language-java">void run()</code></pre>
<p>이 있다.</p>
<p>그래서 다음처럼 익명 클래스를 만들 수 있다.</p>
<pre><code class="language-java">Runnable task = new Runnable() {

    @Override
    public void run() {
        System.out.println(&quot;Hello&quot;);
    }
};</code></pre>
<p>그리고 Runnable은 람다의 대상으로 사용할 수 있는 형태이므로 이 동작을 더 간단하게 표현할 수도 있다.</p>
<pre><code class="language-java">Runnable task = () -&gt; System.out.println(&quot;Hello&quot;);</code></pre>
<p>Thread에 전달하면</p>
<pre><code class="language-java">Thread t = new Thread(task);
t.start();</code></pre>
<p>처럼 사용할 수 있다.</p>
<p>더 줄이면</p>
<pre><code class="language-java">Thread t = new Thread(
    () -&gt; System.out.println(&quot;Hello&quot;)
);

t.start();</code></pre>
<p>와 같은 형태도 이해할 수 있게 된다.</p>
<p>처음에는</p>
<pre><code class="language-java">() -&gt; ...</code></pre>
<p>가 갑자기 등장한 이상한 문법처럼 보였지만 결국</p>
<blockquote>
<p><strong>Runnable의 <code>run()</code>에서 실행할 내용을 전달한 것</strong></p>
</blockquote>
<p>이라고 생각할 수 있었다.</p>
<hr>
<h1 id="익명-클래스와-람다를-연결해서-보기">익명 클래스와 람다를 연결해서 보기</h1>
<p>람다가 아직 익숙하지 않다면 다음 세 단계를 비교해보는 것이 가장 이해하기 쉬웠다.</p>
<h3 id="직접-클래스-구현">직접 클래스 구현</h3>
<pre><code class="language-java">class MyFunction implements F {

    @Override
    public int apply(int x) {
        return x * 2;
    }
}

F f = new MyFunction();</code></pre>
<h3 id="익명-클래스">익명 클래스</h3>
<pre><code class="language-java">F f = new F() {

    @Override
    public int apply(int x) {
        return x * 2;
    }
};</code></pre>
<h3 id="람다식">람다식</h3>
<pre><code class="language-java">F f = (x) -&gt; x * 2;</code></pre>
<p>표현은 점점 짧아지지만 핵심적으로 표현하려는 동작은</p>
<pre><code class="language-text">x를 받아서 x * 2를 반환한다.</code></pre>
<p>로 같다.</p>
<hr>
<h1 id="문제를-풀-때-내가-확인할-것">문제를 풀 때 내가 확인할 것</h1>
<p>람다 문제가 나오면 다음 순서로 보는 것이 가장 편했다.</p>
<pre><code class="language-text">1. 람다가 어떤 인터페이스 타입으로 사용되고 있는가?

2. 그 인터페이스의 추상 메서드는 무엇인가?

3. 매개변수 타입과 반환 타입은 무엇인가?

4. 람다를 그 메서드의 구현이라고 생각한다.

5. 실제로 어떤 값이 전달되는지 확인한다.

6. 예외가 발생하는지 확인한다.

7. try-catch가 있다면 어디에서 처리되는지 확인한다.</code></pre>
<p>예를 들어</p>
<pre><code class="language-java">run((int n) -&gt; n + 9)</code></pre>
<p>를 보면</p>
<pre><code class="language-text">run()의 매개변수
↓
F

F의 추상 메서드
↓
int apply(int x)

람다
↓
(int n) -&gt; n + 9

run() 내부
↓
f.apply(3)

따라서
↓
n = 3

결과
↓
3 + 9 = 12</code></pre>
<p>로 따라가면 된다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>처음에는</p>
<pre><code class="language-java">(int n) -&gt; n + 9</code></pre>
<p>라는 표현 자체가 이해되지 않았다.</p>
<p>메서드 이름도 없고 <code>return</code>도 없는데 어떻게 실행되는지 감이 오지 않았다.</p>
<p>하지만 함수형 인터페이스와 연결해서 보니 훨씬 단순했다.</p>
<pre><code class="language-java">interface F {
    int apply(int x) throws Exception;
}</code></pre>
<p>처럼 구현해야 할 추상 메서드가 하나라면</p>
<pre><code class="language-java">(x) -&gt; x * 2</code></pre>
<p>라는 람다를</p>
<pre><code class="language-java">int apply(int x) {
    return x * 2;
}</code></pre>
<p>라는 <strong>동작의 구현</strong>으로 연결해서 생각할 수 있었다.</p>
<p>그리고</p>
<pre><code class="language-java">run(f)</code></pre>
<p>처럼 람다를 전달받는 메서드에서는 결국 그 동작을</p>
<pre><code class="language-java">f.apply(3);</code></pre>
<p>처럼 실행하고 있는 것이다.</p>
<p>이번 내용을 한 흐름으로 정리하면 다음과 같다.</p>
<pre><code class="language-text">함수형 인터페이스
       ↓
하나의 추상 메서드
       ↓
그 메서드의 동작을 구현
       ↓
익명 클래스로 표현 가능
       ↓
람다식으로 더 간결하게 표현 가능
       ↓
다른 메서드의 인자로 전달 가능
       ↓
필요한 시점에 실행</code></pre>
<p>결국 람다를 처음 볼 때부터 어려운 문법으로 생각할 필요는 없었다.</p>
<blockquote>
<p><strong><code>(int n) -&gt; n + 9</code>는 int 값을 하나 받아서 9를 더한 값을 반환하는 동작이다.</strong></p>
</blockquote>
<p>그리고 그 동작이 어떤 메서드에 해당하는지는 <strong>람다가 사용되는 함수형 인터페이스를 확인하면 알 수 있다.</strong></p>
<p>이렇게 생각하니 <code>Runnable</code>, 익명 클래스, 람다식이 따로 떨어진 문법이 아니라 <strong>“실행할 동작을 객체처럼 전달한다”는 하나의 흐름으로 연결되기 시작했다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[`Parent obj = new Child()`인데 어떤 메서드가 실행될까? | 다형성, 오버라이딩과 오버로딩]]></title>
            <link>https://velog.io/@daehyun_lee/Parent-obj-new-Child%EC%9D%B8%EB%8D%B0-%EC%96%B4%EB%96%A4-%EB%A9%94%EC%84%9C%EB%93%9C%EA%B0%80-%EC%8B%A4%ED%96%89%EB%90%A0%EA%B9%8C-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9%EA%B3%BC-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</link>
            <guid>https://velog.io/@daehyun_lee/Parent-obj-new-Child%EC%9D%B8%EB%8D%B0-%EC%96%B4%EB%96%A4-%EB%A9%94%EC%84%9C%EB%93%9C%EA%B0%80-%EC%8B%A4%ED%96%89%EB%90%A0%EA%B9%8C-%EB%8B%A4%ED%98%95%EC%84%B1-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9%EA%B3%BC-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</guid>
            <pubDate>Sat, 03 Oct 2026 15:43:07 GMT</pubDate>
            <description><![CDATA[<h1 id="parent-obj--new-child인데-어떤-메서드가-실행될까--다형성-오버라이딩과-오버로딩"><code>Parent obj = new Child()</code>인데 어떤 메서드가 실행될까? | 다형성, 오버라이딩과 오버로딩</h1>
<p>Java 문제를 풀다가 다음과 같은 코드를 만났다.</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>처음에는 이 코드부터 이상하게 느껴졌다.</p>
<p><code>Child</code> 객체를 만들 거면 그냥 이렇게 하면 되는 것 아닌가?</p>
<pre><code class="language-java">Child obj = new Child();</code></pre>
<p>그런데 굳이</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>라고 작성한다.</p>
<p>그리고 더 헷갈리는 것은 다음 코드였다.</p>
<pre><code class="language-java">class Parent {
    void show() {
        System.out.println(&quot;Parent&quot;);
    }
}

class Child extends Parent {
    void show() {
        System.out.println(&quot;Child&quot;);
    }
}

Parent obj = new Child();
obj.show();</code></pre>
<p><code>obj</code>의 타입은 분명 <code>Parent</code>인데 출력은</p>
<pre><code class="language-text">Child</code></pre>
<p>가 나온다.</p>
<p>처음에는</p>
<blockquote>
<p>왼쪽이 <code>Parent</code>인데 왜 <code>Parent.show()</code>가 실행되지?</p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>이 문제를 이해하려면 Java에서 <strong>참조변수의 타입</strong>과 <strong>실제 생성된 객체의 타입</strong>을 구분해야 했다.</p>
<p>그리고 여기서부터 오버라이딩, 오버로딩, 다형성, 동적 바인딩까지 전부 연결되기 시작했다.</p>
<hr>
<h2 id="1-parent-obj--new-child에서-왼쪽과-오른쪽은-다르다">1. <code>Parent obj = new Child()</code>에서 왼쪽과 오른쪽은 다르다</h2>
<p>다음 코드를 다시 보자.</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>이 코드는 두 부분으로 나눠서 봐야 한다.</p>
<pre><code class="language-text">Parent obj = new Child();
^^^^^^         ^^^^^
참조변수 타입    실제 객체 타입</code></pre>
<p>왼쪽의 <code>Parent</code>는 <strong>참조변수의 타입</strong>이다.</p>
<p>오른쪽의 <code>new Child()</code>는 실제로 생성되는 <strong>객체의 타입</strong>이다.</p>
<p>즉 <code>obj</code>라는 변수는 <code>Parent</code> 타입으로 객체를 바라보고 있지만 실제로 만들어진 객체는 <code>Child</code>다.</p>
<pre><code class="language-text">Parent obj = new Child();

obj
 │
 │ Parent 타입으로 참조
 ↓
┌─────────────┐
│ Child 객체   │
└─────────────┘</code></pre>
<p>상속 관계이기 때문에 이런 대입이 가능하다.</p>
<pre><code class="language-text">Parent
   ↑
 Child</code></pre>
<p><code>Child</code>는 <code>Parent</code>의 자식이므로 Child 객체를 Parent 타입으로 참조할 수 있다.</p>
<hr>
<h2 id="2-그러면-왼쪽과-오른쪽은-각각-무엇을-결정할까">2. 그러면 왼쪽과 오른쪽은 각각 무엇을 결정할까?</h2>
<p>여기서 가장 중요했던 부분이다.</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>를 볼 때 두 가지 질문을 따로 해야 한다.</p>
<pre><code class="language-text">1. 이 메서드를 호출할 수 있는가?
2. 호출할 수 있다면 실제 어떤 메서드가 실행되는가?</code></pre>
<p>첫 번째 질문은 <strong>참조변수의 타입</strong>을 본다.</p>
<p>두 번째 질문은 오버라이딩된 인스턴스 메서드라면 <strong>실제 객체의 타입</strong>을 본다.</p>
<p>즉 시험 문제에서는 다음처럼 생각하면 편했다.</p>
<pre><code class="language-text">Parent obj = new Child();
       │             │
       │             └─ 실제 어떤 오버라이딩 구현이 실행되는가?
       │
       └─ 어떤 메서드를 호출할 수 있는가?</code></pre>
<hr>
<h2 id="3-왜-childshow가-실행될까">3. 왜 <code>Child.show()</code>가 실행될까?</h2>
<p>다시 처음 문제를 보자.</p>
<pre><code class="language-java">class Parent {

    void show() {
        System.out.println(&quot;Parent&quot;);
    }
}

class Child extends Parent {

    @Override
    void show() {
        System.out.println(&quot;Child&quot;);
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">Parent obj = new Child();
obj.show();</code></pre>
<p>를 실행한다.</p>
<p>먼저 왼쪽 타입을 확인한다.</p>
<pre><code class="language-text">obj의 타입 = Parent</code></pre>
<p><code>Parent</code>에는</p>
<pre><code class="language-java">void show()</code></pre>
<p>가 있다.</p>
<p>따라서</p>
<pre><code class="language-java">obj.show();</code></pre>
<p>를 호출하는 것 자체는 가능하다.</p>
<p>그다음 실제 객체를 본다.</p>
<pre><code class="language-text">실제 객체 = Child</code></pre>
<p>그리고 <code>Child</code>가 <code>show()</code>를 오버라이딩했다.</p>
<p>따라서 실제 실행되는 것은</p>
<pre><code class="language-java">Child.show()</code></pre>
<p>다.</p>
<p>결과는</p>
<pre><code class="language-text">Child</code></pre>
<p>가 된다.</p>
<p>흐름을 정리하면 다음과 같다.</p>
<pre><code class="language-text">obj.show()
   ↓
Parent 타입에 show()가 있는가?
   ↓
있음
   ↓
호출 가능
   ↓
실제 객체는?
   ↓
Child
   ↓
Child가 show()를 오버라이딩했는가?
   ↓
YES
   ↓
Child.show() 실행</code></pre>
<hr>
<h2 id="4-그렇다면-child에만-있는-메서드도-호출할-수-있을까">4. 그렇다면 Child에만 있는 메서드도 호출할 수 있을까?</h2>
<p>여기서 바로 다음 의문이 생겼다.</p>
<p>실제 객체가 <code>Child</code>라면 <code>Child</code>에만 있는 메서드도 호출할 수 있지 않을까?</p>
<p>예를 들어 다음과 같다.</p>
<pre><code class="language-java">class Parent {

    void show() {
        System.out.println(&quot;Parent&quot;);
    }
}

class Child extends Parent {

    @Override
    void show() {
        System.out.println(&quot;Child&quot;);
    }

    void childOnly() {
        System.out.println(&quot;Child Only&quot;);
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>라고 했다.</p>
<p>다음 코드는 가능하다.</p>
<pre><code class="language-java">obj.show();</code></pre>
<p>하지만 다음은 불가능하다.</p>
<pre><code class="language-java">obj.childOnly(); // 컴파일 오류</code></pre>
<p>처음에는 실제 객체가 <code>Child</code>인데 왜 안 되는지 이상했다.</p>
<p>하지만 앞에서 세운 규칙을 적용하면 된다.</p>
<blockquote>
<p><strong>호출할 수 있는지를 먼저 참조변수 타입으로 확인한다.</strong></p>
</blockquote>
<p>현재 <code>obj</code>의 타입은</p>
<pre><code class="language-text">Parent</code></pre>
<p>다.</p>
<p>그런데 Parent에는</p>
<pre><code class="language-java">childOnly()</code></pre>
<p>가 없다.</p>
<p>따라서 컴파일 단계에서 호출할 수 없는 메서드라고 판단한다.</p>
<p>실제 객체가 Child라는 사실을 확인하기도 전에 호출 자체가 불가능한 것이다.</p>
<hr>
<h2 id="5-호출-가능-여부와-실제-실행-메서드를-분리해서-생각하자">5. 호출 가능 여부와 실제 실행 메서드를 분리해서 생각하자</h2>
<p>이 부분을 다음처럼 정리하니 훨씬 덜 헷갈렸다.</p>
<pre><code class="language-text">호출할 수 있는가?
        ↓
참조변수 타입 확인

어떤 오버라이딩 구현이 실행되는가?
        ↓
실제 객체 타입 확인</code></pre>
<p>예를 들어</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>에서</p>
<pre><code class="language-java">obj.show();</code></pre>
<p>는 Parent에 <code>show()</code>가 있으므로 호출 가능하다.</p>
<p>그 후 실제 객체가 Child이므로 오버라이딩된 <code>Child.show()</code>가 실행된다.</p>
<p>반면</p>
<pre><code class="language-java">obj.childOnly();</code></pre>
<p>는 Parent에 <code>childOnly()</code>가 없으므로 호출 자체가 불가능하다.</p>
<hr>
<h2 id="6-그래도-child의-메서드를-사용하고-싶다면">6. 그래도 Child의 메서드를 사용하고 싶다면?</h2>
<p>실제 객체가 정말 Child라면 다운캐스팅을 통해 Child 타입으로 바라볼 수 있다.</p>
<pre><code class="language-java">Parent obj = new Child();

((Child) obj).childOnly();</code></pre>
<p>또는</p>
<pre><code class="language-java">Child child = (Child) obj;
child.childOnly();</code></pre>
<p>처럼 사용할 수 있다.</p>
<p>다만 아무 Parent 객체나 Child로 바꿀 수 있는 것은 아니다.</p>
<p>예를 들어</p>
<pre><code class="language-java">Parent obj = new Parent();</code></pre>
<p>인데</p>
<pre><code class="language-java">Child child = (Child) obj;</code></pre>
<p>처럼 강제로 캐스팅하면 실제 객체가 Child가 아니므로 런타임 오류가 발생할 수 있다.</p>
<p>결국 캐스팅에서도 <strong>실제 객체가 무엇인지</strong>가 중요하다.</p>
<hr>
<h2 id="7-override가-있어야-오버라이딩일까">7. <code>@Override</code>가 있어야 오버라이딩일까?</h2>
<p>다음으로 헷갈렸던 부분이다.</p>
<p>처음에는</p>
<pre><code class="language-java">@Override</code></pre>
<p>가 붙어 있어야 오버라이딩이 발생한다고 생각했다.</p>
<p>하지만 아니다.</p>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">class Parent {

    public void show() {
        System.out.println(&quot;Parent&quot;);
    }
}

class Child extends Parent {

    public void show() {
        System.out.println(&quot;Child&quot;);
    }
}</code></pre>
<p><code>@Override</code>가 없다.</p>
<p>그래도 <code>Child.show()</code>는 <code>Parent.show()</code>를 오버라이딩한 것이다.</p>
<p>즉 <code>@Override</code>가 오버라이딩을 <strong>만드는 것</strong>은 아니다.</p>
<hr>
<h2 id="8-그렇다면-override는-왜-사용할까">8. 그렇다면 <code>@Override</code>는 왜 사용할까?</h2>
<p><code>@Override</code>는 Annotation이다.</p>
<p>쉽게 생각하면 컴파일러에게 다음과 같이 알려주는 것이다.</p>
<blockquote>
<p>이 메서드는 부모의 메서드를 오버라이딩하려고 만든 거야. 제대로 작성했는지 검사해 줘.</p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-java">class Parent {

    void show() {
    }
}</code></pre>
<p>가 있는데 자식에서 실수로</p>
<pre><code class="language-java">class Child extends Parent {

    @Override
    void show(int n) {
    }
}</code></pre>
<p>라고 작성했다고 하자.</p>
<p>나는 <code>show()</code>를 오버라이딩하려고 했지만 실제로는</p>
<pre><code class="language-text">show()
show(int)</code></pre>
<p>로 매개변수가 다르다.</p>
<p>따라서 오버라이딩이 아니다.</p>
<p><code>@Override</code>를 붙였다면 컴파일러가 이를 확인하고 오류를 알려준다.</p>
<p>그래서 <code>@Override</code>는 오버라이딩을 발생시키는 문법이라기보다 <strong>실수를 방지해 주는 검사 장치</strong>에 가깝다.</p>
<hr>
<h2 id="9-오버라이딩과-오버로딩은-무엇이-다를까">9. 오버라이딩과 오버로딩은 무엇이 다를까?</h2>
<p>이제 가장 자주 헷갈리는 두 개념이 나온다.</p>
<pre><code class="language-text">Overriding
Overloading</code></pre>
<p>이름도 비슷해서 처음에는 계속 섞였다.</p>
<p>하지만 기준은 명확하다.</p>
<h3 id="오버라이딩overriding">오버라이딩(Overriding)</h3>
<p>부모에게 있던 메서드를 자식이 재정의하는 것이다.</p>
<pre><code class="language-java">class Parent {

    void show() {
        System.out.println(&quot;Parent&quot;);
    }
}

class Child extends Parent {

    void show() {
        System.out.println(&quot;Child&quot;);
    }
}</code></pre>
<p>상속 관계에서 같은 형태의 메서드를 자식이 다시 구현했다.</p>
<pre><code class="language-text">Parent.show()
      ↓
Child.show()</code></pre>
<p>실제 객체가 Child라면 Child의 구현이 실행될 수 있다.</p>
<hr>
<h3 id="오버로딩overloading">오버로딩(Overloading)</h3>
<p>같은 이름의 메서드를 <strong>매개변수를 다르게 해서 여러 개 정의</strong>하는 것이다.</p>
<pre><code class="language-java">void show() {
}

void show(int n) {
}

void show(String s) {
}</code></pre>
<p>이름은 모두 <code>show</code>지만 서로 다른 메서드다.</p>
<pre><code class="language-text">show()
show(int)
show(String)</code></pre>
<p>호출할 때 전달되는 인자에 따라 어떤 메서드를 사용할지 결정된다.</p>
<hr>
<h2 id="10-geta와-getaint는-오버라이딩이-아니다">10. <code>getA()</code>와 <code>getA(int)</code>는 오버라이딩이 아니다</h2>
<p>다음 문제도 처음에는 헷갈렸다.</p>
<pre><code class="language-java">class Parent {

    int getA() {
        return 10;
    }
}

class Child extends Parent {

    int getA(int n) {
        return 20;
    }
}</code></pre>
<p>Child에도 <code>getA</code>가 있으니 오버라이딩처럼 보인다.</p>
<p>하지만</p>
<pre><code class="language-text">Parent → getA()
Child  → getA(int)</code></pre>
<p>로 매개변수가 다르다.</p>
<p>따라서 <code>Child.getA(int)</code>는 <code>Parent.getA()</code>를 오버라이딩한 것이 아니다.</p>
<p><code>Child</code>는 부모로부터 <code>getA()</code>를 물려받고, 별도로 <code>getA(int)</code>라는 오버로드된 메서드를 가지는 것으로 이해할 수 있다.</p>
<p>그래서</p>
<pre><code class="language-java">Parent obj = new Child();

System.out.println(obj.getA());</code></pre>
<p>를 실행하면 <code>Parent.getA()</code>가 실행되어</p>
<pre><code class="language-text">10</code></pre>
<p>이 나온다.</p>
<p>Child가 <code>getA()</code> 자체를 오버라이딩하지 않았기 때문이다.</p>
<hr>
<h2 id="11-오버로딩은-컴파일-시점-오버라이딩은-실행-시점">11. 오버로딩은 컴파일 시점, 오버라이딩은 실행 시점</h2>
<p>여기까지는 이해했는데 다음 문제를 만나면서 다시 헷갈렸다.</p>
<pre><code class="language-java">class A {

    String f(Object x) {
        return &quot;1&quot;;
    }

    String g() {
        return f(&quot;a&quot;);
    }
}

class B extends A {

    String f(Object x) {
        return &quot;2&quot;;
    }

    String f(String x) {
        return &quot;3&quot;;
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">A a = new B();

System.out.println(a.g());</code></pre>
<p>를 실행한다.</p>
<p>처음에는 <code>&quot;3&quot;</code>이라고 생각하기 쉽다.</p>
<p>왜냐하면</p>
<pre><code class="language-java">f(&quot;a&quot;)</code></pre>
<p>에서 <code>&quot;a&quot;</code>는 String이고 B에는 정확히</p>
<pre><code class="language-java">f(String x)</code></pre>
<p>가 있기 때문이다.</p>
<p>하지만 결과는</p>
<pre><code class="language-text">2</code></pre>
<p>다.</p>
<p>왜일까?</p>
<hr>
<h2 id="12-먼저-오버로딩을-결정한다">12. 먼저 오버로딩을 결정한다</h2>
<p><code>g()</code>는 클래스 <code>A</code>에 작성되어 있다.</p>
<pre><code class="language-java">String g() {
    return f(&quot;a&quot;);
}</code></pre>
<p>이 코드를 컴파일할 때 <code>A</code>에서 사용할 수 있는 <code>f()</code>는</p>
<pre><code class="language-java">f(Object x)</code></pre>
<p>다.</p>
<p>따라서</p>
<pre><code class="language-java">f(&quot;a&quot;)</code></pre>
<p>의 호출 형태는 컴파일 단계에서</p>
<pre><code class="language-text">f(Object)</code></pre>
<p>로 결정된다.</p>
<p>String은 Object의 자식이므로 <code>&quot;a&quot;</code>를 Object 매개변수로 전달하는 것도 가능하다.</p>
<p>즉 먼저</p>
<pre><code class="language-text">f(Object)</code></pre>
<p>를 호출한다는 사실이 정해진다.</p>
<hr>
<h2 id="13-그다음-오버라이딩을-확인한다">13. 그다음 오버라이딩을 확인한다</h2>
<p>이제 프로그램이 실행된다.</p>
<pre><code class="language-java">A a = new B();</code></pre>
<p>실제 객체는 <code>B</code>다.</p>
<p>앞에서 호출하기로 결정한 메서드는</p>
<pre><code class="language-text">f(Object)</code></pre>
<p>였다.</p>
<p>그런데 B에는</p>
<pre><code class="language-java">String f(Object x) {
    return &quot;2&quot;;
}</code></pre>
<p>가 있다.</p>
<p>이는 <code>A.f(Object)</code>를 오버라이딩한 메서드다.</p>
<p>따라서 실제 실행은</p>
<pre><code class="language-java">B.f(Object)</code></pre>
<p>가 된다.</p>
<p>결과는</p>
<pre><code class="language-text">2</code></pre>
<p>다.</p>
<p>B에</p>
<pre><code class="language-java">f(String)</code></pre>
<p>이 있다고 해서 실행 도중 갑자기 이것으로 바뀌지는 않는다.</p>
<hr>
<h2 id="14-이-문제는-두-단계로-풀면-된다">14. 이 문제는 두 단계로 풀면 된다</h2>
<p>이 문제를 한 번에 생각하면 상당히 헷갈린다.</p>
<p>그래서 다음처럼 두 단계로 나누는 것이 중요했다.</p>
<pre><code class="language-text">1단계: 오버로딩 결정
       ↓
컴파일 시점
       ↓
어떤 시그니처의 메서드를 호출할 것인가?

2단계: 오버라이딩 결정
       ↓
실행 시점
       ↓
그 시그니처의 실제 구현은 어느 객체의 것인가?</code></pre>
<p>앞의 문제에서는</p>
<pre><code class="language-text">f(&quot;a&quot;)
↓
A에서 f(Object) 선택
↓
실제 객체 B 확인
↓
B가 f(Object)를 오버라이딩함
↓
B.f(Object)
↓
&quot;2&quot;</code></pre>
<p>가 된다.</p>
<p>이 문제를 통해 오버로딩과 오버라이딩의 차이가 훨씬 명확해졌다.</p>
<blockquote>
<p><strong>오버로딩은 어떤 메서드 시그니처를 호출할지 컴파일 시점에 결정하고, 오버라이딩은 그 메서드의 어떤 구현을 실행할지 런타임에 결정한다.</strong></p>
</blockquote>
<hr>
<h2 id="15-그런데-object는-무엇일까">15. 그런데 <code>Object</code>는 무엇일까?</h2>
<p>위 코드에서 또 처음 보는 것이 있었다.</p>
<pre><code class="language-java">String f(Object x)</code></pre>
<p>처음에는 <code>Object x</code>가 특별한 문법인 줄 알았다.</p>
<p>하지만 <code>Object</code>는 그냥 클래스다.</p>
<p>Java에서 <code>Object</code>는 모든 클래스의 최상위 부모 클래스다.</p>
<p>개념적으로 생각하면 다음과 같다.</p>
<pre><code class="language-text">             Object
          /     |      \
      String  Integer   Child</code></pre>
<p>따라서 다음과 같은 코드가 가능하다.</p>
<pre><code class="language-java">Object obj = &quot;Hello&quot;;</code></pre>
<p>String이 Object를 기반으로 하는 클래스이기 때문이다.</p>
<p>그래서</p>
<pre><code class="language-java">void test(Object obj) {
}</code></pre>
<p>라고 하면 여러 종류의 객체를 전달할 수 있다.</p>
<pre><code class="language-java">test(&quot;hello&quot;);
test(Integer.valueOf(10));
test(new Child());</code></pre>
<p>결국</p>
<pre><code class="language-java">f(Object x)</code></pre>
<p>도 특별한 문법이 아니다.</p>
<blockquote>
<p><strong>Object 타입의 객체를 매개변수로 받는 메서드</strong></p>
</blockquote>
<p>라는 뜻이다.</p>
<hr>
<h2 id="16-부모-메서드-안에서-draw를-호출하면-누구의-메서드일까">16. 부모 메서드 안에서 <code>draw()</code>를 호출하면 누구의 메서드일까?</h2>
<p>다형성을 이해하면서 특히 헷갈렸던 부분이 하나 더 있다.</p>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">class Parent {

    void draw() {
        System.out.print(&quot;B&quot;);
        print();
    }

    void print() {
        System.out.print(&quot;P&quot;);
    }
}

class Child extends Parent {

    @Override
    void print() {
        System.out.print(&quot;D&quot;);
    }

    void test() {
        super.draw();
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">Child obj = new Child();
obj.test();</code></pre>
<p>를 실행한다.</p>
<p><code>test()</code>에서는</p>
<pre><code class="language-java">super.draw();</code></pre>
<p>를 호출했으므로 <code>Parent.draw()</code>가 실행된다.</p>
<pre><code class="language-text">Parent.draw()
    ↓
&quot;B&quot; 출력</code></pre>
<p>그런데 Parent의 <code>draw()</code> 안에는</p>
<pre><code class="language-java">print();</code></pre>
<p>가 있다.</p>
<p>이 코드가 Parent 클래스 안에 작성되어 있으니 <code>Parent.print()</code>가 실행될 것 같았다.</p>
<p>하지만 <code>print()</code>는 일반적인 인스턴스 메서드 호출이다.</p>
<p>현재 실제 객체는 <code>Child</code>이고 Child가 <code>print()</code>를 오버라이딩했다.</p>
<p>따라서</p>
<pre><code class="language-text">Parent.draw()
    ↓
&quot;B&quot; 출력
    ↓
print()
    ↓
실제 객체 확인
    ↓
Child
    ↓
Child.print()
    ↓
&quot;D&quot; 출력</code></pre>
<p>이 된다.</p>
<p>최종 출력은</p>
<pre><code class="language-text">BD</code></pre>
<p>다.</p>
<p>즉 <strong>부모 클래스에 작성된 코드라고 해서 무조건 부모의 메서드가 실행되는 것은 아니다.</strong></p>
<p>일반적인 오버라이딩 가능한 인스턴스 메서드 호출이라면 실제 객체를 기준으로 동적 바인딩이 적용될 수 있다.</p>
<hr>
<h2 id="17-그렇다면-superdraw는-왜-부모-메서드를-실행할까">17. 그렇다면 <code>super.draw()</code>는 왜 부모 메서드를 실행할까?</h2>
<p>여기서 <code>draw()</code>와 <code>super.draw()</code>를 구분해야 한다.</p>
<pre><code class="language-java">draw();</code></pre>
<p>또는</p>
<pre><code class="language-java">this.draw();</code></pre>
<p>는 일반적인 인스턴스 메서드 호출이다.</p>
<p>오버라이딩되어 있다면 실제 객체에 따라 실행되는 구현이 결정될 수 있다.</p>
<p>반면</p>
<pre><code class="language-java">super.draw();</code></pre>
<p>는 부모 클래스의 구현을 직접 지정한다.</p>
<p>즉</p>
<pre><code class="language-text">draw()
→ 일반적인 동적 메서드 호출

super.draw()
→ 부모의 draw() 구현을 직접 호출</code></pre>
<p>이다.</p>
<p>하지만 재미있는 점은 <code>super.draw()</code>를 통해 Parent의 메서드 안으로 들어갔다고 해서 이후의 모든 호출까지 Parent에 고정되는 것은 아니라는 것이다.</p>
<pre><code class="language-java">super.draw();</code></pre>
<p>로 <code>Parent.draw()</code>에 들어간 뒤 그 안에서 다시</p>
<pre><code class="language-java">print();</code></pre>
<p>를 호출하면 이것은 새로운 일반 인스턴스 메서드 호출이다.</p>
<p>따라서 다시 동적 바인딩이 적용될 수 있다.</p>
<hr>
<h2 id="18-부모의-private-필드는-자식이-사용할-수-없는데-부모-메서드는-어떻게-사용할까">18. 부모의 private 필드는 자식이 사용할 수 없는데 부모 메서드는 어떻게 사용할까?</h2>
<p>다음 코드도 처음에는 조금 이상했다.</p>
<pre><code class="language-java">class A {

    private int a;

    A(int a) {
        this.a = a;
    }

    void display() {
        System.out.println(a);
    }
}

class B extends A {

    B(int a) {
        super(a);
        super.display();
    }
}</code></pre>
<p><code>a</code>는</p>
<pre><code class="language-java">private int a;</code></pre>
<p>이므로 B에서 직접 접근할 수 없다.</p>
<p>즉 다음은 안 된다.</p>
<pre><code class="language-java">class B extends A {

    void test() {
        System.out.println(a); // 오류
    }
}</code></pre>
<p>그런데</p>
<pre><code class="language-java">super.display();</code></pre>
<p>는 가능하다.</p>
<p>왜일까?</p>
<p><code>display()</code>는 A 클래스 내부에 작성된 메서드이기 때문이다.</p>
<pre><code class="language-java">class A {

    private int a;

    void display() {
        System.out.println(a);
    }
}</code></pre>
<p>A 자신의 메서드는 A 자신의 private 필드에 접근할 수 있다.</p>
<p>따라서 B는 <code>a</code>에 직접 접근하는 것이 아니라 <strong>A가 제공하는 메서드를 호출하고 있는 것</strong>이다.</p>
<pre><code class="language-text">B
│
├─ A.private 필드 직접 접근 → X
│
└─ A의 접근 가능한 메서드 호출 → O
                    │
                    ↓
             A.display()
                    │
                    ↓
          A 자신의 private a 접근 → O</code></pre>
<p>private이 의미하는 접근 제한을 정확히 구분해야 한다.</p>
<hr>
<h2 id="19-super와-supermethod도-구분해야-한다">19. <code>super()</code>와 <code>super.method()</code>도 구분해야 한다</h2>
<p><code>super</code>가 등장한다고 전부 같은 의미도 아니다.</p>
<pre><code class="language-java">super(10);</code></pre>
<p>은 부모 생성자를 호출한다.</p>
<pre><code class="language-java">super.display();</code></pre>
<p>는 부모 클래스의 일반 메서드 구현을 호출한다.</p>
<p>즉</p>
<pre><code class="language-text">super(...)
→ 부모 생성자 호출

super.method()
→ 부모 메서드 호출</code></pre>
<p>이다.</p>
<p>특히</p>
<pre><code class="language-java">super(...)</code></pre>
<p>는 자식 생성자의 첫 번째 문장이어야 한다.</p>
<p>자식 객체를 초기화하기 전에 부모로부터 물려받은 부분을 먼저 초기화해야 하기 때문이다.</p>
<pre><code class="language-text">Child 객체 생성
      ↓
Parent 부분 초기화
      ↓
Child 부분 초기화</code></pre>
<p>그리고 명시적인 다른 생성자 호출이 없다면 Java는 가능한 경우 부모의 매개변수 없는 생성자를 호출한다.</p>
<p>그래서</p>
<pre><code class="language-java">B() {
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>는 동작을 이해할 때</p>
<pre><code class="language-java">B() {
    super();
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>처럼 생각할 수 있다.</p>
<p>부모에게 매개변수 없는 생성자가 없다면</p>
<pre><code class="language-java">super(10);</code></pre>
<p>처럼 알맞은 부모 생성자를 직접 호출해야 한다.</p>
<hr>
<h1 id="문제를-풀-때-내가-사용하는-순서">문제를 풀 때 내가 사용하는 순서</h1>
<p>상속 문제가 나오면 처음에는 <code>Parent</code>, <code>Child</code>, <code>Override</code>, <code>super</code>가 한꺼번에 보여서 상당히 복잡했다.</p>
<p>지금은 다음 순서로 보는 것이 가장 편하다.</p>
<pre><code class="language-text">Parent obj = new Child();
obj.method();</code></pre>
<p>먼저</p>
<pre><code class="language-text">1. obj의 참조변수 타입은 무엇인가?</code></pre>
<p>를 확인한다.</p>
<p>그 타입을 기준으로 해당 메서드를 호출할 수 있는지 확인한다.</p>
<p>그다음 오버로딩된 메서드가 여러 개라면</p>
<pre><code class="language-text">2. 어떤 시그니처가 선택되는가?</code></pre>
<p>를 컴파일 시점 기준으로 판단한다.</p>
<p>그리고 선택된 메서드가 오버라이딩 가능한 인스턴스 메서드라면</p>
<pre><code class="language-text">3. 실제 객체는 무엇인가?</code></pre>
<p>를 확인한다.</p>
<p>마지막으로</p>
<pre><code class="language-text">4. 실제 객체가 해당 메서드를 오버라이딩했는가?</code></pre>
<p>를 확인한다.</p>
<p>정리하면</p>
<pre><code class="language-text">참조변수 타입
     ↓
호출 가능한 메서드 확인
     ↓
오버로딩 해결
     ↓
호출할 메서드 시그니처 결정
     ↓
실제 객체 확인
     ↓
오버라이딩 확인
     ↓
실제 구현 실행</code></pre>
<p>순서다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>처음에는</p>
<pre><code class="language-java">Parent obj = new Child();</code></pre>
<p>라는 한 줄에서부터 혼란이 시작됐다.</p>
<p>왼쪽은 Parent인데 Child 메서드가 실행되고, 그렇다고 Child에만 있는 메서드를 호출할 수 있는 것은 아니었다.</p>
<p>여기에 오버로딩까지 같이 나오면 더 헷갈렸다.</p>
<p>하지만 각각이 담당하는 역할을 나누니 생각보다 규칙이 명확했다.</p>
<pre><code class="language-text">참조변수 타입
→ 어떤 멤버를 사용할 수 있는지 판단하는 데 중요

실제 객체 타입
→ 오버라이딩된 인스턴스 메서드의 실제 구현을 결정

오버로딩
→ 같은 이름 + 다른 매개변수
→ 호출할 시그니처를 컴파일 시점에 결정

오버라이딩
→ 부모의 메서드를 자식이 재정의
→ 실제 객체에 따라 런타임에 구현 결정

@Override
→ 오버라이딩을 만드는 문법이 아님
→ 오버라이딩을 제대로 했는지 검사하는 Annotation

super.method()
→ 부모의 메서드 구현을 직접 호출

super(...)
→ 부모 생성자를 호출</code></pre>
<p>특히 가장 중요했던 것은 <strong>오버로딩과 오버라이딩을 한 번에 판단하지 않는 것</strong>이었다.</p>
<blockquote>
<p><strong>먼저 컴파일 시점에 어떤 메서드 시그니처를 호출할지 결정하고, 그다음 런타임에 실제 객체를 보고 오버라이딩된 구현을 선택한다.</strong></p>
</blockquote>
<p>이 순서가 잡히고 나니 <code>Parent obj = new Child()</code>에서 시작했던 여러 문제가 하나의 원리로 연결되기 시작했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자식 객체를 만드는데 왜 부모 생성자가 먼저 실행될까? | Java의 super()와 생성자 호출 순서]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%9E%90%EC%8B%9D-%EA%B0%9D%EC%B2%B4%EB%A5%BC-%EB%A7%8C%EB%93%9C%EB%8A%94%EB%8D%B0-%EC%99%9C-%EB%B6%80%EB%AA%A8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EB%90%A0%EA%B9%8C-Java%EC%9D%98-super%EC%99%80-%EC%83%9D%EC%84%B1%EC%9E%90-%ED%98%B8%EC%B6%9C-%EC%88%9C%EC%84%9C</link>
            <guid>https://velog.io/@daehyun_lee/%EC%9E%90%EC%8B%9D-%EA%B0%9D%EC%B2%B4%EB%A5%BC-%EB%A7%8C%EB%93%9C%EB%8A%94%EB%8D%B0-%EC%99%9C-%EB%B6%80%EB%AA%A8-%EC%83%9D%EC%84%B1%EC%9E%90%EA%B0%80-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EB%90%A0%EA%B9%8C-Java%EC%9D%98-super%EC%99%80-%EC%83%9D%EC%84%B1%EC%9E%90-%ED%98%B8%EC%B6%9C-%EC%88%9C%EC%84%9C</guid>
            <pubDate>Sat, 03 Oct 2026 15:39:39 GMT</pubDate>
            <description><![CDATA[<h1 id="자식-객체를-만드는데-왜-부모-생성자가-먼저-실행될까--java의-super와-생성자-호출-순서">자식 객체를 만드는데 왜 부모 생성자가 먼저 실행될까? | Java의 super()와 생성자 호출 순서</h1>
<p>Java의 상속을 공부하다가 다음 코드를 보게 됐다.</p>
<pre><code class="language-java">class A {
    private int a;

    public A(int a) {
        this.a = a;
    }

    public void display() {
        System.out.println(&quot;a=&quot; + a);
    }
}

class B extends A {
    public B(int a) {
        super(a);
        super.display();
    }
}</code></pre>
<p>여기서 <code>super(a)</code>는 부모 클래스 <code>A</code>의 생성자를 호출한다.</p>
<p>그런데 Java에서는 한 가지 규칙이 있다.</p>
<blockquote>
<p><strong>생성자에서 <code>super(...)</code>를 직접 호출한다면 반드시 첫 번째 문장이어야 한다.</strong></p>
</blockquote>
<p>처음에는 단순히 Java 문법이 그런가 보다 생각할 수 있다.</p>
<p>하지만 왜 반드시 <strong>첫 번째</strong>여야 하는 걸까?</p>
<p>이 규칙은 상속 관계에서 객체가 만들어지는 과정을 이해하면 자연스럽게 이해할 수 있다.</p>
<hr>
<h2 id="1-super는-무엇일까">1. super는 무엇일까?</h2>
<p>먼저 <code>super</code>는 <strong>부모 클래스를 가리키기 위해 사용하는 키워드</strong>다.</p>
<p>예를 들어 다음과 같이 <code>A</code>를 상속받는 <code>B</code>가 있다고 하자.</p>
<pre><code class="language-java">class A {
}

class B extends A {
}</code></pre>
<p><code>B</code> 입장에서</p>
<pre><code class="language-java">super</code></pre>
<p>는 부모 클래스인 <code>A</code>와 관련된 멤버에 접근할 때 사용할 수 있다.</p>
<p>대표적으로 두 가지 형태를 자주 볼 수 있다.</p>
<pre><code class="language-java">super(...)</code></pre>
<p>와</p>
<pre><code class="language-java">super.method()</code></pre>
<p>이다.</p>
<p>겉으로 보기에는 비슷하지만 역할은 다르다.</p>
<pre><code class="language-java">super(...)</code></pre>
<p>는 <strong>부모 클래스의 생성자를 호출</strong>한다.</p>
<p>반면</p>
<pre><code class="language-java">super.display();</code></pre>
<p>와 같은 표현은 <strong>부모 클래스의 메서드를 호출</strong>한다.</p>
<p>이 차이가 중요하다.</p>
<hr>
<h2 id="2-supera는-무엇을-하는-걸까">2. super(a)는 무엇을 하는 걸까?</h2>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">class A {

    private int a;

    public A(int a) {
        this.a = a;
    }
}</code></pre>
<p><code>A</code>에는 다음 생성자가 존재한다.</p>
<pre><code class="language-java">A(int a)</code></pre>
<p>그리고 <code>B</code>에서</p>
<pre><code class="language-java">class B extends A {

    public B(int a) {
        super(a);
    }
}</code></pre>
<p>라고 작성했다.</p>
<p>여기서</p>
<pre><code class="language-java">super(a);</code></pre>
<p>는 부모 클래스의</p>
<pre><code class="language-java">A(int a)</code></pre>
<p>생성자를 호출한다.</p>
<p>따라서</p>
<pre><code class="language-java">new B(10);</code></pre>
<p>을 실행하면 <code>B</code>의 생성자만 실행되는 것이 아니다.</p>
<p>먼저 부모인 <code>A</code>의 생성자가 호출된다.</p>
<pre><code class="language-text">new B(10)
    ↓
B(int a)
    ↓
super(a)
    ↓
A(int a)</code></pre>
<p>그리고 <code>A</code>의 생성이 끝나면 다시 <code>B</code> 생성자의 나머지 부분을 실행한다.</p>
<hr>
<h2 id="3-그런데-왜-부모-생성자가-먼저-실행될까">3. 그런데 왜 부모 생성자가 먼저 실행될까?</h2>
<p>여기서 가장 궁금했던 부분이다.</p>
<p>왜 Java는 부모 생성자를 먼저 실행하도록 만들어 놓았을까?</p>
<p><code>B extends A</code>라는 것은 <code>B</code> 객체가 <code>A</code>로부터 물려받은 상태와 동작을 포함한다는 뜻이다.</p>
<p>개념적으로 생각하면 <code>B</code> 객체 안에는 다음과 같은 부분이 있다고 볼 수 있다.</p>
<pre><code class="language-text">B 객체

┌──────────────────┐
│ A로부터 물려받은 부분 │
├──────────────────┤
│ B에서 추가한 부분     │
└──────────────────┘</code></pre>
<p>따라서 <code>B</code> 객체를 제대로 만들기 위해서는 먼저 부모 클래스에서 물려받은 부분이 올바르게 초기화되어야 한다.</p>
<p>그 다음에야 <code>B</code>가 자신의 초기화를 진행할 수 있다.</p>
<p>즉 객체 생성 과정은</p>
<pre><code class="language-text">부모 부분 초기화
       ↓
자식 부분 초기화</code></pre>
<p>순서로 진행된다.</p>
<p>이것이 부모 생성자가 자식 생성자보다 먼저 실행되는 이유다.</p>
<hr>
<h2 id="4-실제로-실행-순서를-확인해보자">4. 실제로 실행 순서를 확인해보자</h2>
<p>간단한 코드를 작성해보자.</p>
<pre><code class="language-java">class A {

    A() {
        System.out.println(&quot;A 생성&quot;);
    }
}

class B extends A {

    B() {
        super();
        System.out.println(&quot;B 생성&quot;);
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">public class Main {

    public static void main(String[] args) {
        B obj = new B();
    }
}</code></pre>
<p>를 실행한다.</p>
<p>나는</p>
<pre><code class="language-java">new B();</code></pre>
<p>를 호출했으니 <code>B</code>부터 만들어질 것 같다고 생각할 수도 있다.</p>
<p>하지만 실제 출력은 다음과 같다.</p>
<pre><code class="language-text">A 생성
B 생성</code></pre>
<p>호출 과정을 따라가면 다음과 같다.</p>
<pre><code class="language-text">new B()
   ↓
B()
   ↓
super()
   ↓
A()
   ↓
&quot;A 생성&quot;
   ↓
A() 종료
   ↓
B()로 돌아옴
   ↓
&quot;B 생성&quot;</code></pre>
<p>즉 <strong>자식 객체를 만들더라도 부모 생성자가 먼저 실행된다.</strong></p>
<hr>
<h2 id="5-그래서-super는-반드시-첫-번째-문장이어야-한다">5. 그래서 super()는 반드시 첫 번째 문장이어야 한다</h2>
<p>이제 다음 코드를 보면 왜 문제가 되는지 이해할 수 있다.</p>
<pre><code class="language-java">class B extends A {

    B(int a) {
        System.out.println(&quot;B 시작&quot;);
        super(a);
    }
}</code></pre>
<p>이 코드는 컴파일되지 않는다.</p>
<pre><code class="language-java">super(a);</code></pre>
<p>보다</p>
<pre><code class="language-java">System.out.println(&quot;B 시작&quot;);</code></pre>
<p>이 먼저 실행되도록 작성했기 때문이다.</p>
<p>Java에서는 부모 생성자 호출이 자식 생성자의 다른 코드보다 먼저 이루어져야 한다.</p>
<p>따라서 다음과 같이 작성해야 한다.</p>
<pre><code class="language-java">class B extends A {

    B(int a) {
        super(a);
        System.out.println(&quot;B 시작&quot;);
    }
}</code></pre>
<p>즉,</p>
<pre><code class="language-text">부모 생성자
    ↓
자식 생성자의 나머지 코드</code></pre>
<p>순서를 보장하기 위해 <code>super(...)</code>를 생성자의 첫 번째 문장으로 제한한다.</p>
<hr>
<h2 id="6-그런데-super를-안-쓰는-코드도-많다">6. 그런데 super()를 안 쓰는 코드도 많다</h2>
<p>여기서 또 하나 헷갈리는 부분이 생긴다.</p>
<p>실제로 Java 코드를 보면 생성자에 <code>super()</code>가 없는 경우가 많다.</p>
<p>예를 들어 다음 코드를 보자.</p>
<pre><code class="language-java">class A {

    A() {
        System.out.println(&quot;A&quot;);
    }
}

class B extends A {

    B() {
        System.out.println(&quot;B&quot;);
    }
}</code></pre>
<p><code>B()</code>에는 <code>super()</code>가 없다.</p>
<p>그렇다면 부모 생성자를 호출하지 않는 걸까?</p>
<p>아니다.</p>
<p>이 경우 Java 컴파일러가 생성자의 첫 번째 문장에 부모의 기본 생성자를 호출하는 코드를 넣는 것으로 이해할 수 있다.</p>
<p>즉 다음 코드는</p>
<pre><code class="language-java">B() {
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>사실상 다음과 같이 동작한다.</p>
<pre><code class="language-java">B() {
    super();
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>따라서</p>
<pre><code class="language-java">new B();</code></pre>
<p>를 실행하면 여전히</p>
<pre><code class="language-text">A
B</code></pre>
<p>순서로 출력된다.</p>
<hr>
<h2 id="7-super를-생략했다고-부모-생성자를-호출하지-않는-게-아니다">7. super()를 생략했다고 부모 생성자를 호출하지 않는 게 아니다</h2>
<p>이 부분은 처음 공부할 때 헷갈리기 쉽다.</p>
<pre><code class="language-java">class B extends A {

    B() {
        System.out.println(&quot;B&quot;);
    }
}</code></pre>
<p>코드에 <code>super()</code>가 보이지 않으니까 부모 생성자를 호출하지 않는 것처럼 보인다.</p>
<p>하지만 실제로는 그렇지 않다.</p>
<pre><code class="language-text">작성한 코드

B() {
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>를 다음과 같이 이해하면 된다.</p>
<pre><code class="language-text">실제 동작을 이해하기 위한 형태

B() {
    super();
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>따라서 상속 관계에서 자식 생성자를 호출하면 부모 생성자부터 실행되는 원칙은 그대로 유지된다.</p>
<hr>
<h2 id="8-그런데-부모에게-기본-생성자가-없다면">8. 그런데 부모에게 기본 생성자가 없다면?</h2>
<p>여기서 중요한 문제가 하나 생긴다.</p>
<p>다음 코드를 보자.</p>
<pre><code class="language-java">class A {

    A(int x) {
        System.out.println(x);
    }
}

class B extends A {

    B() {
    }
}</code></pre>
<p><code>A</code>에는</p>
<pre><code class="language-java">A(int x)</code></pre>
<p>만 존재한다.</p>
<p>즉 매개변수가 없는</p>
<pre><code class="language-java">A()</code></pre>
<p>는 존재하지 않는다.</p>
<p>그런데 <code>B()</code>에서는 부모 생성자를 직접 호출하지 않았다.</p>
<p>그러면 컴파일러는 사실상 다음과 같이 처리하려고 한다.</p>
<pre><code class="language-java">B() {
    super();
}</code></pre>
<p>문제는 부모 <code>A</code>에 <code>A()</code>가 없다는 것이다.</p>
<pre><code class="language-text">A()       → 없음 ❌
A(int x)  → 있음 ✅</code></pre>
<p>따라서 호출할 부모 생성자를 찾을 수 없어 컴파일 오류가 발생한다.</p>
<hr>
<h2 id="9-이럴-때는-super값를-직접-작성해야-한다">9. 이럴 때는 super(값)를 직접 작성해야 한다</h2>
<p>부모에게 매개변수가 있는 생성자만 존재한다면 자식이 적절한 값을 전달해야 한다.</p>
<pre><code class="language-java">class A {

    A(int x) {
        System.out.println(x);
    }
}

class B extends A {

    B() {
        super(10);
    }
}</code></pre>
<p>이제</p>
<pre><code class="language-java">super(10);</code></pre>
<p>이 부모의</p>
<pre><code class="language-java">A(int x)</code></pre>
<p>를 호출한다.</p>
<p>따라서 정상적으로 컴파일할 수 있다.</p>
<p>실행 흐름은 다음과 같다.</p>
<pre><code class="language-text">new B()
   ↓
B()
   ↓
super(10)
   ↓
A(10)
   ↓
부모 부분 초기화
   ↓
B() 나머지 실행</code></pre>
<hr>
<h2 id="10-여기서-기본-생성자도-구분해야-한다">10. 여기서 기본 생성자도 구분해야 한다</h2>
<p>이 내용을 이해하면서 한 가지 더 주의해야 했다.</p>
<p>Java에서는 <strong>생성자를 하나도 작성하지 않았을 때</strong> 컴파일러가 기본 생성자를 제공한다.</p>
<p>예를 들어</p>
<pre><code class="language-java">class A {
}</code></pre>
<p>라고 하면 사용할 수 있는</p>
<pre><code class="language-java">A()</code></pre>
<p>가 제공된다.</p>
<p>하지만 내가 직접 생성자를 하나라도 작성하면 이야기가 달라진다.</p>
<pre><code class="language-java">class A {

    A(int x) {
    }
}</code></pre>
<p>이 경우 컴파일러가 자동으로</p>
<pre><code class="language-java">A() {
}</code></pre>
<p>를 추가해주는 것이 아니다.</p>
<p>따라서</p>
<pre><code class="language-java">new A();</code></pre>
<p>는 사용할 수 없다.</p>
<p>마찬가지로 자식에서 암묵적으로</p>
<pre><code class="language-java">super();</code></pre>
<p>를 호출하려고 해도 부모에게 <code>A()</code>가 없으므로 문제가 된다.</p>
<p>그래서 다음처럼 직접 맞는 생성자를 호출해야 한다.</p>
<pre><code class="language-java">super(10);</code></pre>
<hr>
<h2 id="11-super와-superdisplay는-다르다">11. super()와 super.display()는 다르다</h2>
<p>처음 코드로 다시 돌아가보자.</p>
<pre><code class="language-java">class B extends A {

    public B(int a) {
        super(a);
        super.display();
    }
}</code></pre>
<p>여기에는 <code>super</code>가 두 번 등장한다.</p>
<pre><code class="language-java">super(a);</code></pre>
<p>와</p>
<pre><code class="language-java">super.display();</code></pre>
<p>다.</p>
<p>둘의 역할은 완전히 다르다.</p>
<h3 id="supera">super(a)</h3>
<pre><code class="language-java">super(a);</code></pre>
<p>는 <strong>부모 생성자를 호출</strong>한다.</p>
<p>따라서 생성자 안에서 사용한다면 첫 번째 문장이어야 한다.</p>
<h3 id="superdisplay">super.display()</h3>
<pre><code class="language-java">super.display();</code></pre>
<p>는 <strong>부모 클래스에 정의된 일반 메서드를 호출</strong>한다.</p>
<p>생성자 호출이 아니다.</p>
<p>따라서 반드시 첫 번째 문장일 필요가 없다.</p>
<p>예를 들어 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-java">B(int a) {
    super(a);

    System.out.println(&quot;B 생성 중&quot;);
    super.display();
}</code></pre>
<p>이 코드는 가능하다.</p>
<p>즉 <strong>“super는 무조건 첫 번째 문장이어야 한다”</strong>라고 외우면 안 된다.</p>
<p>정확하게는</p>
<blockquote>
<p><strong>부모 생성자를 호출하는 <code>super(...)</code>가 생성자의 첫 번째 문장이어야 한다.</strong></p>
</blockquote>
<p>라고 기억해야 한다.</p>
<hr>
<h2 id="12-this도-생성자-호출이다">12. this()도 생성자 호출이다</h2>
<p>여기서 <code>super()</code>와 함께 알아두면 좋은 것이 <code>this()</code>다.</p>
<pre><code class="language-java">this(...)</code></pre>
<p>는 같은 클래스의 다른 생성자를 호출한다.</p>
<p>예를 들어</p>
<pre><code class="language-java">class Student {

    Student() {
        this(&quot;이름 없음&quot;);
    }

    Student(String name) {
        System.out.println(name);
    }
}</code></pre>
<p>여기서</p>
<pre><code class="language-java">this(&quot;이름 없음&quot;);</code></pre>
<p>은 같은 <code>Student</code> 클래스의</p>
<pre><code class="language-java">Student(String name)</code></pre>
<p>생성자를 호출한다.</p>
<p><code>this(...)</code> 역시 생성자 호출이기 때문에 첫 번째 문장이어야 한다.</p>
<p>따라서 다음 코드는 안 된다.</p>
<pre><code class="language-java">Student() {
    System.out.println(&quot;생성&quot;);
    this(&quot;홍길동&quot;);   // 컴파일 오류
}</code></pre>
<p>반드시 다음처럼 작성해야 한다.</p>
<pre><code class="language-java">Student() {
    this(&quot;홍길동&quot;);
    System.out.println(&quot;생성&quot;);
}</code></pre>
<hr>
<h2 id="13-super와-this를-동시에-사용할-수-있을까">13. super()와 this()를 동시에 사용할 수 있을까?</h2>
<p>생성자에서</p>
<pre><code class="language-java">super(...)</code></pre>
<p>와</p>
<pre><code class="language-java">this(...)</code></pre>
<p>는 모두 <strong>첫 번째 문장</strong>이어야 한다.</p>
<p>그렇다면 다음처럼 작성할 수 있을까?</p>
<pre><code class="language-java">Student() {
    super();
    this(&quot;홍길동&quot;);
}</code></pre>
<p>불가능하다.</p>
<p>둘 다 첫 번째 문장이어야 하는데 첫 번째 문장은 하나밖에 없기 때문이다.</p>
<p>따라서 한 생성자에서는 생성자 호출문으로 <code>this(...)</code> 또는 <code>super(...)</code> 중 하나만 직접 사용할 수 있다.</p>
<p>다만 <code>this(...)</code>를 통해 호출된 다른 생성자가 최종적으로 <code>super(...)</code>를 호출할 수 있다.</p>
<p>개념적으로는</p>
<pre><code class="language-text">자식 생성자 A
     ↓
this(...)
     ↓
자식 생성자 B
     ↓
super(...)
     ↓
부모 생성자</code></pre>
<p>와 같은 연결이 가능하다.</p>
<p>결국 생성자 호출을 따라가다 보면 부모 생성자의 초기화가 이루어진다.</p>
<hr>
<h2 id="14-상속이-여러-단계라면-어떻게-될까">14. 상속이 여러 단계라면 어떻게 될까?</h2>
<p>부모가 하나가 아니라 상속 관계가 여러 단계라면 어떻게 될까?</p>
<pre><code class="language-java">class A {

    A() {
        System.out.println(&quot;A&quot;);
    }
}

class B extends A {

    B() {
        System.out.println(&quot;B&quot;);
    }
}

class C extends B {

    C() {
        System.out.println(&quot;C&quot;);
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">new C();</code></pre>
<p>를 실행한다고 해보자.</p>
<p><code>C()</code>에는 <code>super()</code>가 생략되어 있다.</p>
<p>따라서 <code>B()</code>를 호출한다.</p>
<pre><code class="language-text">C()
↓
B()</code></pre>
<p>그런데 <code>B()</code>에도 <code>super()</code>가 생략되어 있다.</p>
<p>따라서 다시 <code>A()</code>를 호출한다.</p>
<pre><code class="language-text">C()
↓
B()
↓
A()</code></pre>
<p>실제 생성은 가장 위의 부모부터 완료된다.</p>
<pre><code class="language-text">A
B
C</code></pre>
<p>가 출력된다.</p>
<p>즉 상속 계층이</p>
<pre><code class="language-text">A
↑
B
↑
C</code></pre>
<p>라면 <code>new C()</code>를 했을 때 생성자 실행은</p>
<pre><code class="language-text">A → B → C</code></pre>
<p>순서가 된다.</p>
<hr>
<h2 id="15-처음-코드를-다시-보면">15. 처음 코드를 다시 보면</h2>
<p>이제 처음 코드를 다시 살펴보자.</p>
<pre><code class="language-java">class A {

    private int a;

    public A(int a) {
        this.a = a;
    }

    public void display() {
        System.out.println(&quot;a=&quot; + a);
    }
}

class B extends A {

    public B(int a) {
        super(a);
        super.display();
    }
}</code></pre>
<p>그리고</p>
<pre><code class="language-java">B obj = new B(10);</code></pre>
<p>을 실행한다.</p>
<p>호출 흐름을 따라가보면 다음과 같다.</p>
<pre><code class="language-text">new B(10)
     ↓
B(int a)
     ↓
super(10)
     ↓
A(int a)
     ↓
this.a = 10
     ↓
A 생성자 종료
     ↓
B 생성자로 돌아옴
     ↓
super.display()
     ↓
a=10 출력</code></pre>
<p>따라서 결과는</p>
<pre><code class="language-text">a=10</code></pre>
<p>이다.</p>
<p><code>display()</code>를 호출하기 전에 이미</p>
<pre><code class="language-java">super(a);</code></pre>
<p>를 통해 부모의 <code>a</code>가 초기화되었기 때문에 <code>10</code>이 출력되는 것이다.</p>
<hr>
<h1 id="내가-헷갈렸던-부분">내가 헷갈렸던 부분</h1>
<p>처음에는</p>
<pre><code class="language-java">super(a);</code></pre>
<p>가 단순히 부모 클래스에 접근하는 문법이라고 생각했다.</p>
<p>그래서</p>
<blockquote>
<p><strong>왜 굳이 첫 번째 줄이어야 하지?</strong></p>
</blockquote>
<p>라는 의문이 생겼다.</p>
<p>하지만 <code>super(a)</code>는 단순한 부모 멤버 접근이 아니라 <strong>부모 객체 부분을 초기화하기 위한 부모 생성자 호출</strong>이었다.</p>
<p>자식 객체는 부모에게서 물려받은 부분을 포함하고 있기 때문에 자식 자신의 생성 과정을 진행하기 전에 부모 부분부터 초기화해야 한다.</p>
<p>그래서</p>
<pre><code class="language-text">부모 생성자
↓
자식 생성자</code></pre>
<p>라는 순서가 만들어진다.</p>
<p>또 하나 헷갈리기 쉬운 부분은 <code>super()</code>가 코드에 보이지 않는 경우였다.</p>
<pre><code class="language-java">B() {
    System.out.println(&quot;B&quot;);
}</code></pre>
<p>라고 작성했다고 해서 부모 생성자가 실행되지 않는 것이 아니다.</p>
<p>명시적인 다른 생성자 호출이 없다면 부모의 매개변수 없는 생성자를 호출하는 <code>super()</code>가 암묵적으로 들어간다고 이해하면 된다.</p>
<p>그리고 부모에게 매개변수 없는 생성자가 없다면</p>
<pre><code class="language-java">super(10);</code></pre>
<p>처럼 적절한 부모 생성자를 직접 호출해야 한다.</p>
<hr>
<h1 id="시험에서-기억할-내용">시험에서 기억할 내용</h1>
<pre><code class="language-text">super(...)
→ 부모 생성자를 호출한다.</code></pre>
<pre><code class="language-text">super(...)는
→ 자식 생성자의 첫 번째 문장이어야 한다.</code></pre>
<pre><code class="language-text">명시적인 생성자 호출을 생략하면
→ 부모의 매개변수 없는 생성자를 호출하는 super()가 암묵적으로 사용된다.</code></pre>
<p>하지만</p>
<pre><code class="language-text">부모에게 매개변수 없는 생성자가 없다면
→ 알맞은 super(값)를 직접 작성해야 한다.</code></pre>
<p>그리고 반드시 구분해야 한다.</p>
<pre><code class="language-text">super(...)
→ 부모 생성자 호출
→ 첫 번째 문장이어야 함

super.method()
→ 부모 메서드 호출
→ 첫 번째 문장일 필요 없음</code></pre>
<p><code>this(...)</code>도 같은 클래스의 다른 생성자를 호출하는 문장이므로 첫 번째 문장이어야 한다.</p>
<p>따라서 하나의 생성자에서</p>
<pre><code class="language-text">super(...)
this(...)</code></pre>
<p>를 둘 다 직접 호출할 수는 없다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>처음에는 <code>super()</code>가 첫 번째 문장이어야 한다는 것을 단순한 Java 문법 규칙으로 외우려고 했다.</p>
<p>하지만 객체가 생성되는 과정을 생각해보면 이유가 있었다.</p>
<p>자식 클래스 <code>B</code>가 부모 클래스 <code>A</code>를 상속받고 있다면 <code>B</code> 객체를 만드는 과정에는 <code>A</code>에서 물려받은 부분을 초기화하는 과정도 필요하다.</p>
<p>그래서 Java에서는</p>
<pre><code class="language-text">new B()
   ↓
부모 생성자 실행
   ↓
부모 부분 초기화
   ↓
자식 생성자의 나머지 부분 실행
   ↓
B 객체 생성 완료</code></pre>
<p>순서로 객체를 초기화한다.</p>
<p>결국 <code>super(...)</code>가 첫 번째 문장이어야 한다는 규칙은 따로 떨어진 문법이 아니었다.</p>
<blockquote>
<p><strong>자식 객체를 완성하기 전에 부모로부터 물려받은 부분부터 올바르게 초기화하기 위한 규칙이다.</strong></p>
</blockquote>
<p>이렇게 이해하고 나니 <code>super()</code>, 부모 생성자, 기본 생성자가 왜 서로 연결되어 나오는지도 이해할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체가 가진 정보는 어떻게 클래스로 표현할까? | 속성부터 클래스 다이어그램과 연관관계까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EA%B0%9D%EC%B2%B4%EA%B0%80-%EA%B0%80%EC%A7%84-%EC%A0%95%EB%B3%B4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%A0%EA%B9%8C-%EC%86%8D%EC%84%B1%EB%B6%80%ED%84%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8%EA%B3%BC-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%EA%B0%9D%EC%B2%B4%EA%B0%80-%EA%B0%80%EC%A7%84-%EC%A0%95%EB%B3%B4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%A0%EA%B9%8C-%EC%86%8D%EC%84%B1%EB%B6%80%ED%84%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8%EA%B3%BC-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 03 Oct 2026 12:36:13 GMT</pubDate>
            <description><![CDATA[<h1 id="객체가-가진-정보는-어떻게-클래스로-표현할까--속성부터-클래스-다이어그램과-연관관계까지">객체가 가진 정보는 어떻게 클래스로 표현할까? | 속성부터 클래스 다이어그램과 연관관계까지</h1>
<p>객체지향 프로그래밍을 배우면서 <code>class</code>를 만들 때 자연스럽게 필드를 작성해왔다.</p>
<pre><code class="language-java">class Student {
    String id;
    String name;
    String address;
    int year;
}</code></pre>
<p>코드를 작성할 때는 별생각 없이 <code>학생에게 필요한 데이터니까 넣는다</code> 정도로 생각했다.</p>
<p>그런데 도메인 모델링에서는 한 단계 더 생각해야 한다.</p>
<blockquote>
<p><strong>어떤 정보를 클래스의 속성으로 만들어야 할까?</strong></p>
</blockquote>
<p>학생의 이름은 속성이라는 게 자연스럽다.</p>
<p>그런데 학생의 학과도 그냥 <code>department</code>라는 속성으로 넣으면 될까?</p>
<pre><code class="language-text">Student
----------------
id
name
department</code></pre>
<p><code>Department</code>라는 클래스가 따로 존재한다면 이야기가 달라진다.</p>
<pre><code class="language-text">Student ───── Department</code></pre>
<p>이 경우 <code>department</code>는 단순한 값이라기보다 <strong>Student와 Department라는 두 객체 사이의 관계</strong>를 나타내는 정보일 수도 있다.</p>
<p>이번 장에서는 이런 질문을 중심으로 클래스의 <strong>속성(Attribute)</strong>을 정리한다.</p>
<hr>
<h1 id="1-속성이란">1. 속성이란?</h1>
<p>속성(Attribute)은 <strong>객체가 가지고 있는 정보를 나타내는 데이터</strong>다.</p>
<p>예를 들어 학생이라는 객체가 있다고 생각해보자.</p>
<p>학생마다 다음과 같은 정보를 가지고 있을 수 있다.</p>
<pre><code class="language-text">학번
이름
주소
학과
학년</code></pre>
<p>이를 <code>Student</code> 클래스의 속성으로 표현할 수 있다.</p>
<pre><code class="language-text">Student
----------------
id
name
address
department
year</code></pre>
<p>여기서 중요한 것은 <strong>클래스와 객체를 구분하는 것</strong>이다.</p>
<p><code>Student</code>는 학생이라는 개념을 정의한 클래스이고,</p>
<pre><code class="language-text">학생1
학생2
학생3</code></pre>
<p>은 실제 객체다.</p>
<p>예를 들어 세 학생이 있다고 하자.</p>
<pre><code class="language-text">학생1
학번 = 20260001
이름 = 홍길동
학년 = 2

학생2
학번 = 20260002
이름 = 김철수
학년 = 3

학생3
학번 = 20260003
이름 = 이영희
학년 = 1</code></pre>
<p>세 객체는 모두 같은 <code>Student</code> 클래스에서 만들어졌지만 각 객체가 가지고 있는 속성의 <strong>값(Value)</strong>은 다르다.</p>
<p>즉,</p>
<pre><code class="language-text">클래스
→ 어떤 속성을 가지는지 정의

객체
→ 그 속성의 실제 값을 가짐</code></pre>
<p>이라고 생각할 수 있다.</p>
<hr>
<h1 id="2-uml-클래스-다이어그램에서-속성-표현하기">2. UML 클래스 다이어그램에서 속성 표현하기</h1>
<p>UML 클래스 다이어그램에서는 클래스를 일반적으로 사각형으로 표현한다.</p>
<pre><code class="language-text">┌─────────────────────┐
│       Student       │
├─────────────────────┤
│ - id : String       │
│ - name : String     │
│ - address : String  │
│ - year : Integer    │
└─────────────────────┘</code></pre>
<p>위쪽에는 클래스 이름이 들어가고 그 아래에는 속성이 들어간다.</p>
<p>속성을 조금 더 자세하게 표현하면 다음과 같은 요소들을 사용할 수 있다.</p>
<pre><code class="language-text">가시성 이름 : 타입 [다중성] = 초기값</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">- name : String</code></pre>
<p>이라면</p>
<pre><code class="language-text">-        → 가시성
name     → 속성 이름
String   → 타입</code></pre>
<p>을 의미한다.</p>
<p>따라서 UML의 속성을 읽을 때는 단순히 이름만 보는 것이 아니라 다음 정보를 함께 볼 수 있다.</p>
<pre><code class="language-text">1. 가시성
2. 이름
3. 다중성
4. 타입
5. 초기값</code></pre>
<hr>
<h1 id="3-가시성visibility">3. 가시성(Visibility)</h1>
<p>가시성은</p>
<blockquote>
<p><strong>해당 속성에 누가 접근할 수 있는가?</strong></p>
</blockquote>
<p>를 나타낸다.</p>
<p>Java에서 우리가 사용하는</p>
<pre><code class="language-java">public
private
protected</code></pre>
<p>와 연결해서 생각하면 쉽다.</p>
<p>UML에서는 기호로 표현한다.</p>
<table>
<thead>
<tr>
<th>UML</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>+</code></td>
<td>public</td>
</tr>
<tr>
<td><code>-</code></td>
<td>private</td>
</tr>
<tr>
<td><code>#</code></td>
<td>protected</td>
</tr>
<tr>
<td><code>~</code></td>
<td>package</td>
</tr>
</tbody></table>
<p>예를 들어</p>
<pre><code class="language-text">- name : String</code></pre>
<p>이라면 <code>name</code>은 private 속성이다.</p>
<p>Java로 생각하면</p>
<pre><code class="language-java">private String name;</code></pre>
<p>과 비슷하다.</p>
<p>반대로</p>
<pre><code class="language-text">+ name : String</code></pre>
<p>이라면</p>
<pre><code class="language-java">public String name;</code></pre>
<p>에 대응한다.</p>
<p>따라서 UML을 볼 때</p>
<pre><code class="language-text">+
-
#
~</code></pre>
<p>는 연산자가 아니라 <strong>접근 범위</strong>를 표현하는 기호라고 보면 된다.</p>
<hr>
<h1 id="4-속성의-이름">4. 속성의 이름</h1>
<p>속성에는 당연히 이름이 필요하다.</p>
<p>예를 들어 학생 클래스에 다음 속성이 있다고 하자.</p>
<pre><code class="language-text">id
name
address
department
year</code></pre>
<p>속성 이름은 그 정보가 무엇을 의미하는지 알 수 있도록 작성해야 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">n
a
d</code></pre>
<p>같은 이름보다</p>
<pre><code class="language-text">name
address
department</code></pre>
<p>가 의미를 훨씬 명확하게 전달한다.</p>
<p>도메인 모델링에서 이름은 단순한 변수명이 아니라 <strong>도메인의 의미를 표현하는 역할</strong>도 한다.</p>
<hr>
<h1 id="5-다중성multiplicity">5. 다중성(Multiplicity)</h1>
<p>속성이 항상 값 하나만 가지는 것은 아니다.</p>
<p>예를 들어 학생에게 주소가 하나만 있다고 가정하면</p>
<pre><code class="language-text">address : String</code></pre>
<p>으로 표현할 수 있다.</p>
<p>하지만 여러 개의 주소를 저장할 수 있다면 이야기가 달라진다.</p>
<p>이때 <strong>다중성(Multiplicity)</strong>을 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">address : String[2]</code></pre>
<p>와 같이 표현하면 여러 값을 가질 수 있다는 것을 나타낼 수 있다.</p>
<p>다중성은 앞으로 클래스 사이의 <strong>연관관계</strong>에서도 굉장히 중요하게 등장한다.</p>
<p>대표적으로</p>
<pre><code class="language-text">1
0..1
*
1..*</code></pre>
<p>같은 표현을 볼 수 있다.</p>
<p>의미는 대략 다음과 같다.</p>
<pre><code class="language-text">1     → 정확히 하나
0..1  → 없거나 하나
*     → 여러 개
1..*  → 최소 하나 이상</code></pre>
<p>특히 <code>1..*</code>는 이후 클래스 연관관계 문제에서 중요하다.</p>
<hr>
<h1 id="6-타입type">6. 타입(Type)</h1>
<p>속성에는 어떤 종류의 값을 저장할 것인지 타입을 지정할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Student
-------------------------
id : String
name : String
address : String
year : Integer</code></pre>
<p>라고 한다면</p>
<pre><code class="language-text">id      → 문자열
name    → 문자열
address → 문자열
year    → 정수</code></pre>
<p>라는 뜻이다.</p>
<p>Java 코드로 생각하면</p>
<pre><code class="language-java">class Student {

    private String id;
    private String name;
    private String address;
    private int year;
}</code></pre>
<p>와 비슷하다.</p>
<hr>
<h1 id="7-기본-타입과-사용자-정의-타입">7. 기본 타입과 사용자 정의 타입</h1>
<p>속성의 타입이 반드시 <code>String</code>, <code>Integer</code> 같은 기본적인 타입일 필요는 없다.</p>
<p>예를 들어 다음과 같은 속성이 있다고 하자.</p>
<pre><code class="language-text">department : Department</code></pre>
<p>여기서 <code>Department</code>는 우리가 직접 만든 클래스일 수 있다.</p>
<p>Java에서도 똑같다.</p>
<pre><code class="language-java">class Student {

    private String id;
    private String name;
    private Department department;
}</code></pre>
<p>즉 클래스도 다른 클래스의 타입으로 사용할 수 있다.</p>
<p>그런데 여기서 중요한 문제가 생긴다.</p>
<pre><code class="language-text">department : Department</code></pre>
<p>를 정말 Student의 <strong>속성</strong>으로 표현해야 할까?</p>
<p>아니면</p>
<pre><code class="language-text">Student ───── Department</code></pre>
<p>처럼 <strong>두 클래스의 관계</strong>로 표현해야 할까?</p>
<p>이 문제가 이번 장의 중요한 내용 중 하나다.</p>
<hr>
<h1 id="8-초기값initial-value">8. 초기값(Initial Value)</h1>
<p>속성에는 초기값을 지정할 수도 있다.</p>
<p>예를 들어 학생의 기본 학년을 1학년으로 설정한다고 하자.</p>
<p>UML에서는 개념적으로 다음처럼 표현할 수 있다.</p>
<pre><code class="language-text">year : Integer = 1</code></pre>
<p>Java에서는</p>
<pre><code class="language-java">private int year = 1;</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>즉 객체가 처음 만들어졌을 때 별도의 값이 주어지지 않는다면 기본적으로 <code>1</code>을 가지도록 하는 것이다.</p>
<hr>
<h1 id="9-uml과-java를-연결해서-보기">9. UML과 Java를 연결해서 보기</h1>
<p>UML에서 다음과 같은 클래스가 있다고 하자.</p>
<pre><code class="language-text">┌──────────────────────────┐
│         Student          │
├──────────────────────────┤
│ - id : String            │
│ - name : String          │
│ - address : String       │
│ - department : Department│
│ - year : Integer = 1     │
└──────────────────────────┘</code></pre>
<p>Java에서는 대략 다음처럼 표현할 수 있다.</p>
<pre><code class="language-java">class Student {

    private String id;
    private String name;
    private String address;
    private Department department;
    private int year = 1;
}</code></pre>
<p>결국 UML은 코드와 완전히 동떨어진 그림이 아니다.</p>
<p>코드를 작성하기 전에</p>
<blockquote>
<p><strong>시스템에 어떤 클래스가 있고 각 클래스가 어떤 정보를 가지고 있는가</strong></p>
</blockquote>
<p>를 추상적으로 표현하는 방법이라고 생각하면 이해하기 쉽다.</p>
<hr>
<h1 id="10-속성은-어떻게-찾을까">10. 속성은 어떻게 찾을까?</h1>
<p>여기부터가 도메인 모델링에서 더 중요한 부분이다.</p>
<p>문제 기술서가 있다고 해서 등장하는 모든 명사를 무작정 속성으로 넣으면 안 된다.</p>
<p>먼저 객체가 어떤 정보를 알아야 하는지 생각해야 한다.</p>
<p>예를 들어 자동차 시스템을 만든다고 하자.</p>
<p>자동차에 대해 생각할 수 있는 정보는 굉장히 많다.</p>
<pre><code class="language-text">제조사
모델명
색상
가격
차량 번호
구매자
판매자
정비 기록
유통 정보
보험 정보
...</code></pre>
<p>현실 세계의 자동차에는 사실상 수많은 정보가 연결되어 있다.</p>
<p>그런데 우리가 만드는 시스템에 이 모든 정보가 필요한 것은 아니다.</p>
<hr>
<h1 id="11-객체의-유용한-정보">11. 객체의 유용한 정보</h1>
<p>어떤 정보가 속성이 되어야 하는지는 <strong>시스템의 목적</strong>에 따라 달라진다.</p>
<p>예를 들어 자동차 판매 시스템이라면</p>
<pre><code class="language-text">모델
가격
색상
제조사</code></pre>
<p>같은 정보가 중요할 수 있다.</p>
<p>하지만 자동차 정비 시스템이라면</p>
<pre><code class="language-text">차량 번호
주행 거리
정비 기록
마지막 정비 날짜</code></pre>
<p>가 더 중요할 수 있다.</p>
<p>즉 같은 자동차 객체라도</p>
<pre><code class="language-text">판매 시스템의 Car</code></pre>
<p>와</p>
<pre><code class="language-text">정비 시스템의 Car</code></pre>
<p>가 가져야 하는 속성이 다를 수 있다.</p>
<p>이 부분에서 중요한 생각은 다음과 같다.</p>
<blockquote>
<p><strong>현실 세계 객체의 모든 정보를 모델에 넣는 것이 아니라, 현재 시스템에 필요한 정보만 선택한다.</strong></p>
</blockquote>
<p>도메인 모델은 현실 세계를 그대로 복사하는 것이 아니라 <strong>문제를 해결하는 데 필요한 부분을 추상화한 것</strong>이라고 볼 수 있다.</p>
<hr>
<h1 id="12-속성과-객체를-구분하기">12. 속성과 객체를 구분하기</h1>
<p>여기서 한 가지 헷갈리는 문제가 생긴다.</p>
<p>어떤 정보는 단순한 <strong>값(Value)</strong>이고, 어떤 정보는 독립적인 <strong>객체(Object)</strong>다.</p>
<p>예를 들어 Course 클래스가 있다고 하자.</p>
<pre><code class="language-text">Course
----------------
id
name
credit
department</code></pre>
<p>처음에는 <code>department</code>도 그냥 Course의 속성처럼 보인다.</p>
<pre><code class="language-text">department : String</code></pre>
<p>으로 만들 수도 있다.</p>
<p>하지만 시스템에 <code>Department</code> 클래스가 별도로 존재한다면?</p>
<pre><code class="language-text">Department
----------------
name
studentNumber
professorNumber
office</code></pre>
<p>이 경우 <code>department</code>는 단순한 문자열보다 <strong>Department라는 독립적인 객체</strong>로 보는 것이 더 자연스러울 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">Course
----------------
id
name
credit
department</code></pre>
<p>에서 <code>department</code>를 단순 속성으로 유지하는 대신</p>
<pre><code class="language-text">Course ───────── Department</code></pre>
<p>라는 관계로 표현할 수 있다.</p>
<hr>
<h1 id="13-왜-속성을-연관관계로-바꿀까">13. 왜 속성을 연관관계로 바꿀까?</h1>
<p>예를 들어 Course에</p>
<pre><code class="language-text">department : String</code></pre>
<p>이 있다고 하자.</p>
<p>값은</p>
<pre><code class="language-text">&quot;소프트웨어공학과&quot;</code></pre>
<p>정도일 것이다.</p>
<p>그런데 Department 자체가 다음 정보를 가지고 있다면?</p>
<pre><code class="language-text">Department
----------------
name
studentNumber
professorNumber
office</code></pre>
<p>단순한 문자열 하나로 표현하기에는 Department가 가진 의미가 너무 많다.</p>
<p>따라서</p>
<pre><code class="language-text">Course.department</code></pre>
<p>라는 속성 대신</p>
<pre><code class="language-text">Course ───── Department</code></pre>
<p>라는 객체 간 관계로 표현하는 것이다.</p>
<hr>
<h1 id="14-클래스-사이의-연관관계-⭐-시험-중요">14. 클래스 사이의 연관관계 ⭐ 시험 중요</h1>
<p>이번 장에서 특히 중요하게 봐야 할 부분이다.</p>
<p>자료에서도 직접</p>
<blockquote>
<p><strong>시험!! 이 클래스들을 주고 연관관계를 맺어봐라</strong></p>
</blockquote>
<p>라고 표시되어 있다.</p>
<p>즉 여러 클래스와 속성을 주고</p>
<blockquote>
<p><strong>어떤 속성을 클래스 사이의 연관관계로 바꿀 것인가?</strong></p>
</blockquote>
<p>를 판단하는 문제가 나올 가능성이 높다.</p>
<hr>
<h1 id="15-시험-예제의-클래스">15. 시험 예제의 클래스</h1>
<p>자료에는 다음 네 클래스가 등장한다.</p>
<pre><code class="language-text">Course
Lecture
Professor
Department</code></pre>
<p>처음에는 각 클래스 안에 다른 객체를 나타내는 정보가 속성으로 포함되어 있다.</p>
<p>예를 들어 개념적으로 보면</p>
<pre><code class="language-text">Course
----------------
id
name
credit
department</code></pre>
<pre><code class="language-text">Lecture
----------------
course_id
professor_id
semester
classroom
min
max</code></pre>
<pre><code class="language-text">Professor
----------------
id
name
department
major</code></pre>
<pre><code class="language-text">Department
----------------
name
studentNumber
professorNumber
office</code></pre>
<p>이다.</p>
<p>여기서 찾아야 하는 것이 있다.</p>
<pre><code class="language-text">Course.department
Lecture.course_id
Lecture.professor_id
Professor.department</code></pre>
<p>이 정보들이다.</p>
<p>왜 이 속성들이 눈에 띌까?</p>
<p>이미 각각을 표현하는 클래스가 존재하기 때문이다.</p>
<pre><code class="language-text">department
→ Department 클래스가 존재

course_id
→ Course 클래스가 존재

professor_id
→ Professor 클래스가 존재</code></pre>
<p>따라서 단순한 속성으로 표현하는 대신 클래스 사이의 <strong>연관관계(Association)</strong>로 나타낼 수 있다.</p>
<p>자료에서도 이 부분을 시험 문제로 직접 표시하고 있다. Chpater_03_속성.pdf</p>
<hr>
<h1 id="16-course와-department의-관계">16. Course와 Department의 관계</h1>
<p>먼저</p>
<pre><code class="language-text">Course.department</code></pre>
<p>를 보자.</p>
<p><code>Department</code>라는 클래스가 이미 존재한다.</p>
<p>따라서</p>
<pre><code class="language-text">Course
  │
  │
Department</code></pre>
<p>라는 관계로 바꿀 수 있다.</p>
<p>Course 안에서는 기존의</p>
<pre><code class="language-text">department</code></pre>
<p>속성을 제거할 수 있다.</p>
<p>왜냐하면 두 클래스 사이의 선 자체가</p>
<blockquote>
<p><strong>이 Course가 Department와 관계를 가지고 있다</strong></p>
</blockquote>
<p>는 정보를 표현하기 때문이다.</p>
<hr>
<h1 id="17-course와-lecture의-관계">17. Course와 Lecture의 관계</h1>
<p>Lecture에는</p>
<pre><code class="language-text">course_id</code></pre>
<p>가 있다.</p>
<p>이것은 결국</p>
<blockquote>
<p><strong>이 강의가 어떤 Course에 해당하는가?</strong></p>
</blockquote>
<p>를 나타내는 정보다.</p>
<p>따라서</p>
<pre><code class="language-text">Course ───── Lecture</code></pre>
<p>라는 관계로 표현할 수 있다.</p>
<p>그리고 한 Course에 여러 Lecture가 존재할 수 있다면 다중성을 이용해 표현한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Course 1 ───────── 1..* Lecture</code></pre>
<p>처럼 생각할 수 있다.</p>
<p><code>1..*</code>는</p>
<pre><code class="language-text">최소 1개 이상</code></pre>
<p>이라는 뜻이다.</p>
<hr>
<h1 id="18-lecture와-professor의-관계">18. Lecture와 Professor의 관계</h1>
<p>Lecture에는</p>
<pre><code class="language-text">professor_id</code></pre>
<p>가 존재한다.</p>
<p>그런데 <code>Professor</code> 클래스가 이미 있다.</p>
<p>따라서 단순히 교수의 ID를 값으로 가지고 있는 대신</p>
<pre><code class="language-text">Lecture ───── Professor</code></pre>
<p>라는 관계로 표현할 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">professor_id</code></pre>
<p>라는 속성이</p>
<pre><code class="language-text">Professor 객체와의 연관관계</code></pre>
<p>로 바뀌는 것이다.</p>
<hr>
<h1 id="19-professor와-department의-관계">19. Professor와 Department의 관계</h1>
<p>Professor에도</p>
<pre><code class="language-text">department</code></pre>
<p>정보가 있다.</p>
<p>그리고 Department 클래스가 존재한다.</p>
<p>따라서</p>
<pre><code class="language-text">Professor ───── Department</code></pre>
<p>라는 연관관계로 표현할 수 있다.</p>
<hr>
<h1 id="20-시험-문제를-풀-때-보는-순서-⭐⭐⭐">20. 시험 문제를 풀 때 보는 순서 ⭐⭐⭐</h1>
<p>클래스 여러 개를 던져주고</p>
<blockquote>
<p><strong>연관관계를 맺어라.</strong></p>
</blockquote>
<p>라고 하면 처음부터 선을 그으려고 하지 않는 게 좋다.</p>
<p>다음 순서로 보면 된다.</p>
<h2 id="step-1-어떤-클래스들이-존재하는지-확인한다">STEP 1. 어떤 클래스들이 존재하는지 확인한다.</h2>
<p>예를 들어</p>
<pre><code class="language-text">Course
Lecture
Professor
Department</code></pre>
<p>가 있다.</p>
<hr>
<h2 id="step-2-각-클래스의-속성을-확인한다">STEP 2. 각 클래스의 속성을 확인한다.</h2>
<pre><code class="language-text">Course.department

Lecture.course_id
Lecture.professor_id

Professor.department</code></pre>
<p>처럼 다른 객체를 가리키는 것 같은 속성을 찾는다.</p>
<hr>
<h2 id="step-3-그-속성에-대응하는-클래스가-존재하는지-확인한다">STEP 3. 그 속성에 대응하는 클래스가 존재하는지 확인한다.</h2>
<pre><code class="language-text">department
→ Department 있음

course_id
→ Course 있음

professor_id
→ Professor 있음</code></pre>
<p>그렇다면 단순한 값이 아니라 객체 사이의 관계일 가능성이 있다.</p>
<hr>
<h2 id="step-4-해당-속성을-연관관계로-바꾼다">STEP 4. 해당 속성을 연관관계로 바꾼다.</h2>
<pre><code class="language-text">Course ───── Department

Course ───── Lecture

Lecture ───── Professor

Professor ───── Department</code></pre>
<hr>
<h2 id="step-5-다중성을-판단한다">STEP 5. 다중성을 판단한다.</h2>
<p>이제</p>
<pre><code class="language-text">1
0..1
*
1..*</code></pre>
<p>등을 붙인다.</p>
<p>여기서 중요한 것은 무조건 외우는 것이 아니라 <strong>도메인의 의미를 생각하는 것</strong>이다.</p>
<p>예를 들어</p>
<blockquote>
<p>하나의 Course에는 몇 개의 Lecture가 존재할 수 있는가?</p>
</blockquote>
<p>를 생각해서 다중성을 결정한다.</p>
<hr>
<h1 id="21-속성과-연관관계를-구분하는-기준">21. 속성과 연관관계를 구분하는 기준</h1>
<p>처음에는 이 부분이 가장 헷갈렸다.</p>
<p>간단하게 생각하면 다음 질문을 던져볼 수 있다.</p>
<blockquote>
<p><strong>이 정보가 그냥 값인가, 아니면 시스템에서 별도의 객체로 다뤄야 하는 대상인가?</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-text">Student.name</code></pre>
<p>에서 <code>name</code>은 일반적으로 그냥 값이다.</p>
<pre><code class="language-text">name : String</code></pre>
<p>으로 충분하다.</p>
<p>하지만</p>
<pre><code class="language-text">Student.department</code></pre>
<p>에서 Department를 시스템이 별도의 객체로 관리하고 있다면</p>
<pre><code class="language-text">Student ───── Department</code></pre>
<p>라는 관계로 보는 것이 자연스럽다.</p>
<p>즉,</p>
<pre><code class="language-text">name
age
year
price</code></pre>
<p>처럼 자체적으로 객체의 의미를 가질 필요가 없는 값은 <strong>속성</strong>으로,</p>
<pre><code class="language-text">Department
Professor
Course
Student</code></pre>
<p>처럼 시스템에서 독립적인 개념과 정보를 가지는 대상은 <strong>클래스</strong>로 만들고 다른 클래스와 관계를 맺을 수 있다.</p>
<hr>
<h1 id="22-속성-간의-관련성">22. 속성 간의 관련성</h1>
<p>하나의 클래스 안에서도 속성들이 서로 관련되어 있을 수 있다.</p>
<p>예를 들어 강의 클래스에 다음 속성이 있다고 하자.</p>
<pre><code class="language-text">강좌번호
강좌이름
학점
담당학과
개설학기
강의실
최소수강학생수
최대수강학생수</code></pre>
<p>이 속성들은 모두 강의와 관련된 정보지만 조금 더 자세히 보면 성격이 다르다.</p>
<p>예를 들어</p>
<pre><code class="language-text">강좌번호
강좌이름
학점
담당학과</code></pre>
<p>는 강좌 자체의 기본 정보와 관련되어 있고,</p>
<pre><code class="language-text">개설학기
강의실
최소수강학생수
최대수강학생수</code></pre>
<p>는 실제 강의가 개설되는 상황과 더 관련되어 있을 수 있다.</p>
<p>이처럼 한 클래스가 너무 많은 종류의 정보를 가지고 있다면</p>
<blockquote>
<p><strong>정말 하나의 클래스가 모두 책임지는 것이 맞는가?</strong></p>
</blockquote>
<p>를 생각해볼 필요가 있다.</p>
<p>경우에 따라서는 하나의 클래스를 여러 클래스로 분리하는 것이 더 자연스러울 수 있다.</p>
<hr>
<h1 id="23-클래스-범위-속성class-scope-attribute">23. 클래스 범위 속성(Class Scope Attribute)</h1>
<p>지금까지 본 속성은 대부분 <strong>객체마다 각각 존재하는 속성</strong>이었다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Student
----------------
id
name
year</code></pre>
<p>가 있다고 하자.</p>
<p>학생 객체를 세 개 만들면</p>
<pre><code class="language-text">학생1.year = 1
학생2.year = 3
학생3.year = 2</code></pre>
<p>처럼 각각 자기 값을 가진다.</p>
<p>이를 <strong>인스턴스 범위 속성(Instance Scope Attribute)</strong>이라고 한다.</p>
<hr>
<h1 id="24-인스턴스-범위-속성">24. 인스턴스 범위 속성</h1>
<p>예를 들어</p>
<pre><code class="language-text">Course
----------------
id
name
credit</code></pre>
<p>이 있다고 하자.</p>
<p>Course 객체가 세 개 존재하면</p>
<pre><code class="language-text">Course1.credit
Course2.credit
Course3.credit</code></pre>
<p>각 객체마다 별도의 <code>credit</code> 값이 존재한다.</p>
<p>즉 메모리에서도 각 객체가 자신의 속성을 따로 가진다.</p>
<pre><code class="language-text">Course 객체 1
[ id ][ name ][ credit ]

Course 객체 2
[ id ][ name ][ credit ]

Course 객체 3
[ id ][ name ][ credit ]</code></pre>
<p>이것이 일반적인 객체 속성이다.</p>
<hr>
<h1 id="25-클래스-범위-속성">25. 클래스 범위 속성</h1>
<p>그런데 모든 객체가 <strong>하나의 값을 공유해야 하는 정보</strong>도 있다.</p>
<p>예를 들어 전체 Course 객체의 개수를 저장한다고 하자.</p>
<pre><code class="language-text">total</code></pre>
<p>Course 객체마다 <code>total</code>을 하나씩 가지고 있을 필요가 없다.</p>
<pre><code class="language-text">Course1.total
Course2.total
Course3.total</code></pre>
<p>처럼 따로 가지고 있으면 오히려 관리하기 어렵다.</p>
<p>대신 클래스 자체가 하나만 가지고 모든 객체가 공유하면 된다.</p>
<pre><code class="language-text">           Course
              │
         total = 3
        ↙     ↓     ↘
 Course1   Course2   Course3</code></pre>
<p>이것이 <strong>클래스 범위 속성(Class Scope Attribute)</strong>이다.</p>
<p>Java에서는 우리가 익숙하게 사용하는</p>
<pre><code class="language-java">static</code></pre>
<p>과 연결된다.</p>
<hr>
<h1 id="26-java의-static과-연결하기">26. Java의 static과 연결하기</h1>
<p>예를 들어</p>
<pre><code class="language-java">class Course {

    private String id;
    private String name;
    private int credit;

    public static int total;
}</code></pre>
<p>이라고 하자.</p>
<p>여기서</p>
<pre><code class="language-java">id
name
credit</code></pre>
<p>은 객체마다 존재한다.</p>
<p>반면</p>
<pre><code class="language-java">static int total;</code></pre>
<p>은 Course 클래스에 하나만 존재하고 모든 Course 객체가 공유한다.</p>
<p>즉</p>
<pre><code class="language-text">일반 필드
→ 객체마다 하나씩

static 필드
→ 클래스 전체에서 하나</code></pre>
<p>라고 생각하면 된다.</p>
<hr>
<h1 id="27-uml에서-클래스-범위-속성">27. UML에서 클래스 범위 속성</h1>
<p>UML에서는 클래스 범위 속성을 일반 속성과 다르게 표시한다.</p>
<p>교재의 예처럼 클래스 전체에서 공유되는 속성은 <strong>밑줄을 그어서 표현</strong>한다.</p>
<p>즉 밑줄이 있는 속성을 발견하면</p>
<blockquote>
<p><strong>이 값은 객체마다 따로 존재하는 것이 아니라 클래스 전체에서 공유하는 값이다.</strong></p>
</blockquote>
<p>라고 읽으면 된다.</p>
<p>Java의</p>
<pre><code class="language-java">static</code></pre>
<p>을 떠올리면 이해하기 쉽다.</p>
<hr>
<h1 id="28-유도-속성derived-attribute">28. 유도 속성(Derived Attribute)</h1>
<p>유도 속성은 조금 독특하다.</p>
<p>다른 속성의 값을 이용해서 <strong>계산할 수 있는 속성</strong>이다.</p>
<p>예를 들어 사람이 다음 정보를 가지고 있다고 하자.</p>
<pre><code class="language-text">Person
----------------
name
birthYear
age</code></pre>
<p>현재 연도와 <code>birthYear</code>를 알고 있다면 <code>age</code>는 계산할 수 있다.</p>
<p>개념적으로</p>
<pre><code class="language-text">age = 현재 연도 - birthYear</code></pre>
<p>와 같은 관계가 있기 때문이다.</p>
<p>그렇다면 굳이 <code>age</code>를 별도로 저장해야 할까?</p>
<p>저장하지 않고 필요할 때 계산할 수도 있다.</p>
<p>이처럼 다른 속성으로부터 계산할 수 있는 속성을 <strong>유도 속성</strong>이라고 한다.</p>
<hr>
<h1 id="29-uml의-유도-속성-표현">29. UML의 유도 속성 표현</h1>
<p>UML에서는 유도 속성 앞에 <code>/</code>를 붙인다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Person
----------------
name
birthYear
/age</code></pre>
<p>라고 되어 있다면</p>
<pre><code class="language-text">/age</code></pre>
<p>는 다른 정보를 통해 계산할 수 있는 속성이라는 뜻이다.</p>
<p>따라서 UML에서</p>
<pre><code class="language-text">/</code></pre>
<p>가 속성 앞에 있다면 나눗셈 기호가 아니라</p>
<pre><code class="language-text">Derived Attribute</code></pre>
<p>를 의미한다.</p>
<hr>
<h1 id="30-왜-유도-속성을-사용할까">30. 왜 유도 속성을 사용할까?</h1>
<p>예를 들어 다음 두 값을 모두 저장한다고 하자.</p>
<pre><code class="language-text">birthYear = 2000
age = 26</code></pre>
<p>시간이 지나 2027년이 되었다.</p>
<p>그러면</p>
<pre><code class="language-text">birthYear = 2000
age = 26</code></pre>
<p>에서 age가 잘못된 정보가 된다.</p>
<p>따라서 age를 직접 저장하는 대신</p>
<pre><code class="language-text">age = currentYear - birthYear</code></pre>
<p>처럼 필요할 때 계산하면 데이터 불일치 문제를 줄일 수 있다.</p>
<hr>
<h1 id="31-하지만-유도-속성을-항상-계산해야-하는-것은-아니다">31. 하지만 유도 속성을 항상 계산해야 하는 것은 아니다</h1>
<p>유도할 수 있는 값이라고 해서 반드시 저장하면 안 된다는 뜻은 아니다.</p>
<p>계산 비용이 매우 크거나 값이 자주 필요하다면 미리 계산해서 저장하는 것이 더 효율적인 경우도 있다.</p>
<p>즉 유도 속성의 핵심은</p>
<blockquote>
<p><strong>다른 속성으로부터 계산할 수 있는 정보인가?</strong></p>
</blockquote>
<p>를 나타내는 데 있다.</p>
<hr>
<h1 id="32-속성을-검토할-때-생각해야-할-것">32. 속성을 검토할 때 생각해야 할 것</h1>
<p>클래스의 속성을 처음 찾았다고 해서 바로 확정하면 안 된다.</p>
<p>마지막으로 속성이 적절한지 검토해야 한다.</p>
<p>자료의 마지막에서는 특히 다음과 같은 관점으로 속성을 확인한다.</p>
<hr>
<h2 id="1-필요한-정보가-빠지지-않았는가">1. 필요한 정보가 빠지지 않았는가?</h2>
<p>클래스가 역할을 수행하기 위해 필요한 정보가 모두 들어있는지 확인한다.</p>
<p>예를 들어 Student를 관리해야 하는데 학생을 구별할 방법이 없다면 문제가 된다.</p>
<pre><code class="language-text">Student
----------------
name
year</code></pre>
<p>동명이인이 존재하면 객체를 정확히 구별하기 어렵다.</p>
<p>따라서 시스템 요구사항에 따라</p>
<pre><code class="language-text">studentId</code></pre>
<p>같은 식별 정보가 필요할 수 있다.</p>
<hr>
<h2 id="2-속성-이름이-명확한가">2. 속성 이름이 명확한가?</h2>
<pre><code class="language-text">data
value
info</code></pre>
<p>같은 이름은 무엇을 의미하는지 불명확하다.</p>
<p>가능하면</p>
<pre><code class="language-text">studentId
courseName
credit</code></pre>
<p>처럼 의미를 알 수 있는 이름을 사용하는 것이 좋다.</p>
<hr>
<h2 id="3-한-클래스의-속성들이-서로-관련되어-있는가">3. 한 클래스의 속성들이 서로 관련되어 있는가?</h2>
<p>한 클래스 안에 서로 전혀 관계없는 정보가 지나치게 많이 들어있다면 클래스의 역할이 잘못 나누어진 것일 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Course
----------------
courseName
credit
professorName
professorPhone
departmentOffice
studentAddress</code></pre>
<p>처럼 여러 객체의 정보가 뒤섞여 있다면</p>
<pre><code class="language-text">Course
Professor
Department
Student</code></pre>
<p>등으로 객체를 분리하고 관계를 맺는 것이 더 자연스러울 수 있다.</p>
<hr>
<h1 id="33-이번-장을-공부하면서-이해한-핵심">33. 이번 장을 공부하면서 이해한 핵심</h1>
<p>처음에는 속성을 단순히 Java의 필드 정도로 생각했다.</p>
<pre><code class="language-java">class Student {
    String name;
    int year;
}</code></pre>
<p>그래서 클래스에 필요한 정보를 그냥 변수로 넣으면 된다고 생각했다.</p>
<p>하지만 도메인 모델링에서 중요한 것은 <strong>변수를 어떻게 선언하는가보다 어떤 정보를 그 클래스의 책임으로 볼 것인가를 결정하는 것</strong>이었다.</p>
<p>특히</p>
<pre><code class="language-text">Course.department
Lecture.professor_id</code></pre>
<p>처럼 다른 객체를 가리키는 정보는 무조건 일반적인 속성으로 둘 필요가 없다.</p>
<p>시스템에서 <code>Department</code>, <code>Professor</code>가 독립적인 객체라면</p>
<pre><code class="language-text">Course ───── Department
Lecture ──── Professor</code></pre>
<p>처럼 객체 사이의 <strong>연관관계</strong>로 표현할 수 있다.</p>
<p>결국 속성을 찾는다는 것은 단순히 문제 설명에서 명사를 골라 클래스 안에 넣는 작업이 아니었다.</p>
<pre><code class="language-text">이 정보가 현재 시스템에 필요한가?

이것은 단순한 값인가?

독립적인 객체로 봐야 하는가?

다른 클래스와의 관계로 표현해야 하는가?

객체마다 따로 존재해야 하는가?

클래스 전체가 공유해야 하는가?

다른 속성으로 계산할 수 있는가?</code></pre>
<p>를 계속 판단하는 과정에 가깝다.</p>
<hr>
<h1 id="시험-대비-정리">시험 대비 정리</h1>
<p>이번 장에서 특히 확인해야 할 부분을 정리하면 다음과 같다.</p>
<h2 id="uml-속성-표기">UML 속성 표기</h2>
<pre><code class="language-text">- name : String</code></pre>
<pre><code class="language-text">-       → private
name    → 속성 이름
String  → 타입</code></pre>
<p>가시성은</p>
<pre><code class="language-text">+ → public
- → private
# → protected
~ → package</code></pre>
<p>로 표현한다.</p>
<hr>
<h2 id="다중성">다중성</h2>
<pre><code class="language-text">1     → 하나
0..1  → 없거나 하나
*     → 여러 개
1..*  → 하나 이상</code></pre>
<p>클래스 사이의 연관관계를 그릴 때 중요하다.</p>
<hr>
<h2 id="인스턴스-속성과-클래스-속성">인스턴스 속성과 클래스 속성</h2>
<pre><code class="language-text">Instance Attribute
→ 객체마다 각각 존재

Class Scope Attribute
→ 모든 객체가 공유
→ Java의 static과 연결
→ UML에서는 밑줄</code></pre>
<hr>
<h2 id="유도-속성">유도 속성</h2>
<pre><code class="language-text">/age</code></pre>
<p>처럼 <code>/</code>로 표시한다.</p>
<pre><code class="language-text">다른 속성으로부터 계산 가능한 속성</code></pre>
<p>이라는 의미다.</p>
<hr>
<h1 id="⭐-시험에서-연관관계-문제를-풀-때">⭐ 시험에서 연관관계 문제를 풀 때</h1>
<p>자료에서 직접 시험 문제로 표시한 부분이다.</p>
<p>여러 클래스를 주고</p>
<pre><code class="language-text">연관관계를 맺어라.</code></pre>
<p>라고 한다면 다음 순서로 풀어본다.</p>
<pre><code class="language-text">1. 주어진 클래스를 전부 확인한다.

2. 각 클래스의 속성을 확인한다.

3. 다른 클래스를 가리키는 속성을 찾는다.

   department
   course_id
   professor_id
   ...

4. 그 대상이 별도의 클래스로 존재하는지 확인한다.

5. 단순 속성이 아니라 연관관계로 표현할 수 있는지 판단한다.

6. 기존의 중복된 속성을 정리한다.

7. 클래스 사이에 Association을 연결한다.

8. 관계의 의미를 보고 다중성을 결정한다.</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">Course.department</code></pre>
<p>가 있고 <code>Department</code> 클래스가 존재한다면</p>
<pre><code class="language-text">Course ───────── Department</code></pre>
<p>를 생각한다.</p>
<p><code>Lecture.course_id</code>가 있고 <code>Course</code> 클래스가 존재한다면</p>
<pre><code class="language-text">Course ───────── Lecture</code></pre>
<p>를 생각한다.</p>
<p><code>Lecture.professor_id</code>가 있고 <code>Professor</code> 클래스가 존재한다면</p>
<pre><code class="language-text">Lecture ──────── Professor</code></pre>
<p>를 생각한다.</p>
<p><code>Professor.department</code>가 있고 <code>Department</code> 클래스가 존재한다면</p>
<pre><code class="language-text">Professor ────── Department</code></pre>
<p>를 생각한다.</p>
<p>그리고 마지막으로</p>
<pre><code class="language-text">1
0..1
*
1..*</code></pre>
<p>등을 이용해 <strong>각 객체가 상대 객체와 몇 개까지 관계를 가질 수 있는지</strong> 표현한다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>이번 장에서 가장 중요하게 느껴진 부분은 <strong>속성과 객체의 경계를 구분하는 것</strong>이었다.</p>
<p>코드만 생각하면</p>
<pre><code class="language-java">String department;
String professorId;
String courseId;</code></pre>
<p>처럼 전부 필드로 만들어도 프로그램은 작성할 수 있다.</p>
<p>하지만 도메인 모델에서는 한 단계 더 생각한다.</p>
<pre><code class="language-text">department는 정말 단순 문자열인가?

Professor는 ID 하나로만 표현할 대상인가?

Course와 Lecture는 어떤 관계인가?</code></pre>
<p>이런 질문을 통해 현실 세계의 개념을 클래스로 나누고 관계를 표현한다.</p>
<p>그래서 이번 장의 내용을 한 문장으로 정리하면 다음과 같다.</p>
<blockquote>
<p><strong>속성을 찾는다는 것은 객체가 가진 정보를 나열하는 것이 아니라, 어떤 정보가 객체의 값이고 어떤 정보가 다른 객체와의 관계인지를 결정하는 과정이다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터는 0과 1을 어떻게 기억하고 계산할까? | 진법부터 논리회로, 플립플롭과 레지스터까지]]></title>
            <link>https://velog.io/@daehyun_lee/Chapter-2.-%EB%85%BC%EB%A6%AC%ED%9A%8C%EB%A1%9C-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@daehyun_lee/Chapter-2.-%EB%85%BC%EB%A6%AC%ED%9A%8C%EB%A1%9C-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sat, 03 Oct 2026 12:26:34 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-2-논리회로-기초">Chapter 2. 논리회로 기초</h1>
<blockquote>
<p>이 장의 가장 큰 흐름은 다음과 같다.</p>
<p><strong>수와 코드 → 논리 게이트 → Flip-Flop → Register → Bus → Register 간 데이터 전송</strong></p>
<p>처음에는 각각 별개의 내용처럼 보이지만, 결국 <strong>컴퓨터가 0과 1을 어떻게 표현하고, 계산하고, 기억하고, 이동시키는가</strong>를 배우는 과정이다.</p>
</blockquote>
<hr>
<h1 id="21-수와-코드">2.1 수와 코드</h1>
<h2 id="1-2진수와-논리회로">1. 2진수와 논리회로</h2>
<p>컴퓨터 내부의 전기회로에서는 두 가지 상태를 구분하기 쉽다.</p>
<p>예를 들어 전압의 높고 낮음을 추상화해서 다음과 같이 표현할 수 있다.</p>
<pre><code class="language-text">낮은 전압 → 0
높은 전압 → 1</code></pre>
<p>즉, 실제 컴퓨터 내부에서는 전기 신호가 흐르고 있지만 이를 논리적으로는 <code>0</code>과 <code>1</code>이라는 두 값으로 생각한다.</p>
<p>이것이 컴퓨터에서 <strong>2진수(Binary)</strong>가 자연스럽게 사용되는 이유다.</p>
<hr>
<h2 id="2-수의-체계와-weight">2. 수의 체계와 Weight</h2>
<p>우리가 평소 사용하는 숫자는 <strong>10진수</strong>다.</p>
<p>예를 들어 다음 숫자를 보자.</p>
<pre><code class="language-text">3025.37</code></pre>
<p>우리는 그냥 삼천이십오 점 삼칠이라고 읽지만, 실제로 각 자리에는 서로 다른 <strong>가중치(Weight)</strong>가 존재한다.</p>
<pre><code class="language-text">숫자     3     0     2     5   .   3      7
Weight  10³   10²   10¹   10⁰     10⁻¹  10⁻²</code></pre>
<p>따라서 <code>3025.37</code>은 실제로 다음과 같다.</p>
<pre><code class="language-text">3025.37

= 3 × 10³
+ 0 × 10²
+ 2 × 10¹
+ 5 × 10⁰
+ 3 × 10⁻¹
+ 7 × 10⁻²</code></pre>
<p>계산하면</p>
<pre><code class="language-text">= 3000 + 0 + 20 + 5 + 0.3 + 0.07
= 3025.37</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>숫자의 실제 값 = 각 자리의 숫자 × 그 자리의 Weight를 모두 더한 것</strong></p>
</blockquote>
<p>이라고 생각하면 된다.</p>
<hr>
<h2 id="3-r진법의-일반적인-표현">3. R진법의 일반적인 표현</h2>
<p>10진수에서는 Weight가 다음과 같다.</p>
<pre><code class="language-text">..., 10³, 10², 10¹, 10⁰, 10⁻¹, 10⁻², ...</code></pre>
<p>2진수에서는 다음과 같다.</p>
<pre><code class="language-text">..., 2³, 2², 2¹, 2⁰, 2⁻¹, 2⁻², ...</code></pre>
<p>5진수에서는 다음과 같다.</p>
<pre><code class="language-text">..., 5³, 5², 5¹, 5⁰, 5⁻¹, 5⁻², ...</code></pre>
<p>따라서 이를 일반적인 <code>R진법</code>으로 표현하면</p>
<pre><code class="language-text">..., R³, R², R¹, R⁰, R⁻¹, R⁻², ...</code></pre>
<p>이 된다.</p>
<p>수학적으로는 다음과 같이 표현한다.</p>
<p>$begin:math:display$
V(N) = \sum A_kR^k
$end:math:display$</p>
<p>여기서</p>
<ul>
<li><code>R</code> : 몇 진법인지 나타내는 밑(Base, Radix)</li>
<li><code>A_k</code> : 각 자리에 들어있는 숫자</li>
<li><code>R^k</code> : 해당 자리의 Weight</li>
</ul>
<p>이다.</p>
<p>그리고 R진수에서 사용할 수 있는 숫자는</p>
<pre><code class="language-text">0 ~ R-1</code></pre>
<p>이다.</p>
<h3 id="예시">예시</h3>
<p>2진수에서는</p>
<pre><code class="language-text">0, 1</code></pre>
<p>만 사용할 수 있다.</p>
<p>5진수에서는</p>
<pre><code class="language-text">0, 1, 2, 3, 4</code></pre>
<p>만 사용할 수 있다.</p>
<p>16진수에서는 총 16개의 기호가 필요하므로</p>
<pre><code class="language-text">0 1 2 3 4 5 6 7 8 9 A B C D E F</code></pre>
<p>를 사용한다.</p>
<hr>
<h1 id="예제-2-2-12342₅">예제 2-2. <code>(1234.2)₅</code></h1>
<p><code>(1234.2)₅</code>는 <strong>5진수</strong>다.</p>
<p>5진수이므로 각 자리의 Weight는 5의 거듭제곱이다.</p>
<pre><code class="language-text">숫자      1     2     3     4   .   2
Weight   5³    5²    5¹    5⁰      5⁻¹</code></pre>
<p>따라서</p>
<pre><code class="language-text">(1234.2)₅

= 1 × 5³
+ 2 × 5²
+ 3 × 5¹
+ 4 × 5⁰
+ 2 × 5⁻¹</code></pre>
<p>계산하면</p>
<pre><code class="language-text">= 125 + 50 + 15 + 4 + 0.4
= 194.4</code></pre>
<p>따라서</p>
<pre><code class="language-text">(1234.2)₅ = (194.4)₁₀</code></pre>
<p>이다.</p>
<h3 id="중요한-점">중요한 점</h3>
<p>5진수에서는 한 자리에 <code>5</code> 이상이 올 수 없다.</p>
<pre><code class="language-text">(1234)₅ → 가능

(1254)₅ → 불가능</code></pre>
<p>왜냐하면 사용할 수 있는 숫자가</p>
<pre><code class="language-text">0, 1, 2, 3, 4</code></pre>
<p>뿐이기 때문이다.</p>
<hr>
<h1 id="예제-2-3-10111₂">예제 2-3. <code>(1011.1)₂</code></h1>
<p>이번에는 2진수다.</p>
<p>각 자리의 Weight를 적어보면</p>
<pre><code class="language-text">숫자      1     0     1     1   .    1
Weight   2³    2²    2¹    2⁰       2⁻¹</code></pre>
<p>따라서</p>
<pre><code class="language-text">(1011.1)₂

= 1 × 2³
+ 0 × 2²
+ 1 × 2¹
+ 1 × 2⁰
+ 1 × 2⁻¹</code></pre>
<p>계산하면</p>
<pre><code class="language-text">= 8 + 0 + 2 + 1 + 0.5
= 11.5</code></pre>
<p>따라서</p>
<pre><code class="language-text">(1011.1)₂ = (11.5)₁₀</code></pre>
<p>이다.</p>
<p>2진수이기 때문에 각 자리에는 반드시</p>
<pre><code class="language-text">0 또는 1</code></pre>
<p>만 올 수 있다.</p>
<hr>
<h1 id="4-진법-변환-⭐-중요">4. 진법 변환 ⭐ 중요</h1>
<blockquote>
<p>시험 중요</p>
<p>특히 <strong>10진수 ↔ 2진수 ↔ 8진수 ↔ 16진수</strong> 변환은 직접 할 수 있어야 한다.</p>
</blockquote>
<p>10진수를 R진수로 변환할 때는 정수와 소수의 방법이 다르다.</p>
<pre><code class="language-text">정수 부분 → R로 계속 나눈다.
소수 부분 → R을 계속 곱한다.</code></pre>
<hr>
<h1 id="예제-2-6-27625₁₀-→-2진수">예제 2-6. <code>27.625₁₀ → 2진수</code></h1>
<p>먼저 정수와 소수 부분을 분리한다.</p>
<pre><code class="language-text">27.625

정수 부분 : 27
소수 부분 : 0.625</code></pre>
<hr>
<h2 id="4-1-정수-부분-변환">4-1. 정수 부분 변환</h2>
<p>27을 2진수로 바꾸려면 계속 <code>2</code>로 나눈다.</p>
<pre><code class="language-text">27 ÷ 2 = 13 ... 1
13 ÷ 2 =  6 ... 1
 6 ÷ 2 =  3 ... 0
 3 ÷ 2 =  1 ... 1
 1 ÷ 2 =  0 ... 1</code></pre>
<p>나머지를 <strong>아래에서 위로</strong> 읽는다.</p>
<pre><code class="language-text">11011</code></pre>
<p>따라서</p>
<pre><code class="language-text">27₁₀ = 11011₂</code></pre>
<p>이다.</p>
<hr>
<h2 id="왜-2로-나누는가">왜 2로 나누는가?</h2>
<p>우리가 실제로 찾고 싶은 것은 다음 네모 안의 숫자다.</p>
<pre><code class="language-text">27 =
□ × 2⁴
+ □ × 2³
+ □ × 2²
+ □ × 2¹
+ □ × 2⁰</code></pre>
<p>이를 변수로 표현하면</p>
<pre><code class="language-text">27 =
a₄ × 2⁴
+ a₃ × 2³
+ a₂ × 2²
+ a₁ × 2¹
+ a₀ × 2⁰</code></pre>
<p>이다.</p>
<p>2진수이므로 각 <code>a</code>에는</p>
<pre><code class="language-text">0 또는 1</code></pre>
<p>만 들어간다.</p>
<p>27을 직접 2의 거듭제곱으로 표현하면</p>
<pre><code class="language-text">27 = 16 + 8 + 2 + 1

= 1 × 2⁴
+ 1 × 2³
+ 0 × 2²
+ 1 × 2¹
+ 1 × 2⁰</code></pre>
<p>따라서</p>
<pre><code class="language-text">11011₂</code></pre>
<p>이다.</p>
<p>즉, Weight를 직접 보고 구해도 된다.</p>
<p>하지만 숫자가 커지면 불편하기 때문에 <strong>2로 계속 나누는 방법</strong>을 사용한다.</p>
<hr>
<h2 id="왜-나머지를-보는가">왜 나머지를 보는가?</h2>
<p>27을 2로 나누면</p>
<pre><code class="language-text">27 = 2 × 13 + 1</code></pre>
<p>이다.</p>
<p>마지막 <code>1</code>은 바로 <code>2⁰</code> 자리에 들어갈 값이다.</p>
<p>다시 13을 2로 나누면</p>
<pre><code class="language-text">13 = 2 × 6 + 1</code></pre>
<p>이때 나온 나머지는 <code>2¹</code> 자리의 값이 된다.</p>
<p>즉 나머지가</p>
<pre><code class="language-text">2⁰ → 2¹ → 2² → ...</code></pre>
<p>순서로 나오기 때문에 마지막에는 <strong>거꾸로 읽는 것</strong>이다.</p>
<hr>
<h1 id="4-2-소수-부분-변환">4-2. 소수 부분 변환</h1>
<p>이번에는</p>
<pre><code class="language-text">0.625</code></pre>
<p>를 2진수로 바꿔보자.</p>
<p>소수는 반대로 <code>2</code>를 계속 곱한다.</p>
<pre><code class="language-text">0.625 × 2 = 1.25
              ↑
              1</code></pre>
<p>정수 부분 <code>1</code>을 가져온다.</p>
<p>소수 부분 <code>0.25</code>만 남겨 다시 2를 곱한다.</p>
<pre><code class="language-text">0.25 × 2 = 0.5
            ↑
            0</code></pre>
<p>다시</p>
<pre><code class="language-text">0.5 × 2 = 1.0
           ↑
           1</code></pre>
<p>이번에는 정수 부분을 <strong>위에서 아래로</strong> 읽는다.</p>
<pre><code class="language-text">101</code></pre>
<p>따라서</p>
<pre><code class="language-text">0.625₁₀ = 0.101₂</code></pre>
<p>이다.</p>
<p>정수와 합치면</p>
<pre><code class="language-text">27.625₁₀ = 11011.101₂</code></pre>
<p>가 된다.</p>
<hr>
<h2 id="왜-소수는-2를-곱하는가">왜 소수는 2를 곱하는가?</h2>
<p>우리가 찾고 싶은 것은</p>
<pre><code class="language-text">0.625 =
□ × 2⁻¹
+ □ × 2⁻²
+ □ × 2⁻³
+ ...</code></pre>
<p>이다.</p>
<p>이를</p>
<pre><code class="language-text">0.625 =
a₋₁ × 2⁻¹
+ a₋₂ × 2⁻²
+ a₋₃ × 2⁻³
+ ...</code></pre>
<p>라고 하자.</p>
<p>양쪽에 2를 곱하면</p>
<pre><code class="language-text">1.25 =
a₋₁ × 2⁰
+ a₋₂ × 2⁻¹
+ a₋₃ × 2⁻²
+ ...</code></pre>
<p>가 된다.</p>
<p>여기서 <code>a₋₁ × 2⁰</code>가 정수 부분으로 튀어나온다.</p>
<p>실제로</p>
<pre><code class="language-text">0.625 × 2 = 1.25</code></pre>
<p>에서 정수 부분이 <code>1</code>이다.</p>
<p>따라서 첫 번째 소수 자리인 <code>2⁻¹</code> 자리에는 <code>1</code>이 들어간다는 것을 알 수 있다.</p>
<p>다시 소수 부분 <code>0.25</code>에 2를 곱하면 다음 자리인 <code>2⁻²</code>를 알아낼 수 있다.</p>
<p>그래서</p>
<pre><code class="language-text">정수 → 나누기
소수 → 곱하기</code></pre>
<p>를 사용하는 것이다.</p>
<hr>
<h1 id="예시-문제-1-1225₁₀-→-2진수">예시 문제 1. <code>12.25₁₀ → 2진수</code></h1>
<h2 id="정수-부분">정수 부분</h2>
<pre><code class="language-text">12 ÷ 2 = 6 ... 0
 6 ÷ 2 = 3 ... 0
 3 ÷ 2 = 1 ... 1
 1 ÷ 2 = 0 ... 1</code></pre>
<p>아래에서 위로 읽으면</p>
<pre><code class="language-text">1100</code></pre>
<p>이다.</p>
<h2 id="소수-부분">소수 부분</h2>
<pre><code class="language-text">0.25 × 2 = 0.5 → 0
0.5  × 2 = 1.0 → 1</code></pre>
<p>따라서</p>
<pre><code class="language-text">0.01</code></pre>
<p>이다.</p>
<p>최종적으로</p>
<pre><code class="language-text">12.25₁₀ = 1100.01₂</code></pre>
<p>이다.</p>
<p>검산하면</p>
<pre><code class="language-text">1100.01₂

= 1×2³ + 1×2² + 1×2⁻²
= 8 + 4 + 0.25
= 12.25</code></pre>
<p>이다.</p>
<hr>
<h1 id="예시-문제-2-316">예시 문제 2. <code>3/16</code></h1>
<pre><code class="language-text">3/16 = 0.1875</code></pre>
<p>그런데 <code>16 = 2⁴</code>이므로 더 쉽게 생각할 수 있다.</p>
<pre><code class="language-text">3/16
= 2/16 + 1/16
= 1/8 + 1/16
= 2⁻³ + 2⁻⁴</code></pre>
<p>따라서</p>
<pre><code class="language-text">0.0011₂</code></pre>
<p>이다.</p>
<hr>
<h1 id="예시-문제-3-13">예시 문제 3. <code>1/3</code></h1>
<p>소수 부분 변환법을 사용한다.</p>
<pre><code class="language-text">1/3 × 2 = 2/3
정수 부분 → 0</code></pre>
<p>다시</p>
<pre><code class="language-text">2/3 × 2 = 4/3 = 1 + 1/3
정수 부분 → 1</code></pre>
<p>다시 <code>1/3</code>이 등장한다.</p>
<p>따라서 같은 과정이 무한 반복된다.</p>
<pre><code class="language-text">0.010101010101...</code></pre>
<p>즉</p>
<pre><code class="language-text">1/3 = 0.010101...₂</code></pre>
<p>이다.</p>
<p><code>01</code>이 계속 반복된다.</p>
<p>이런 경우 반복되는 부분 위에 선이나 점을 표시하여 <strong>순환한다는 것</strong>을 나타낼 수 있다.</p>
<hr>
<h1 id="5-자릿수와-로그">5. 자릿수와 로그</h1>
<p>숫자의 자릿수를 알아낼 때 로그를 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">3205</code></pre>
<p>는 몇 자리인가?</p>
<p>다음 관계가 성립한다.</p>
<pre><code class="language-text">1000 ≤ 3205 &lt; 10000</code></pre>
<p>이를 10의 거듭제곱으로 쓰면</p>
<pre><code class="language-text">10³ ≤ 3205 &lt; 10⁴</code></pre>
<p>이다.</p>
<p>양쪽에 <code>log₁₀</code>을 취하면</p>
<pre><code class="language-text">3 ≤ log₁₀(3205) &lt; 4</code></pre>
<p>이다.</p>
<p>따라서 <code>log₁₀(3205)</code>의 정수 부분은 3이다.</p>
<p>여기에 1을 더하면</p>
<pre><code class="language-text">3 + 1 = 4자리</code></pre>
<p>가 된다.</p>
<p>따라서 일반적으로</p>
<pre><code class="language-text">10진수 N의 자릿수
= floor(log₁₀N) + 1</code></pre>
<p>이다.</p>
<hr>
<h2 id="2진수의-자릿수">2진수의 자릿수</h2>
<p>2진수에서는 밑을 2로 바꾸면 된다.</p>
<pre><code class="language-text">2진수 N의 자릿수
= floor(log₂N) + 1</code></pre>
<hr>
<h2 id="시험-예시">시험 예시</h2>
<h3 id="문제">문제</h3>
<pre><code class="language-text">1000₁₀은 2진수에서 몇 자리인가?</code></pre>
<p>2의 거듭제곱을 비교한다.</p>
<pre><code class="language-text">2⁹  = 512
2¹⁰ = 1024</code></pre>
<p>따라서</p>
<pre><code class="language-text">512 ≤ 1000 &lt; 1024</code></pre>
<p>즉</p>
<pre><code class="language-text">2⁹ ≤ 1000 &lt; 2¹⁰</code></pre>
<p>이므로 필요한 자릿수는</p>
<pre><code class="language-text">10자리</code></pre>
<p>이다.</p>
<hr>
<h1 id="6-2진수-8진수-16진수-⭐-중요">6. 2진수, 8진수, 16진수 ⭐ 중요</h1>
<p>다음 관계를 반드시 기억한다.</p>
<pre><code class="language-text">8  = 2³
16 = 2⁴</code></pre>
<p>따라서</p>
<pre><code class="language-text">2진수 3자리 ↔ 8진수 1자리
2진수 4자리 ↔ 16진수 1자리</code></pre>
<p>로 정확하게 대응한다.</p>
<hr>
<h1 id="7-8진수-↔-2진수">7. 8진수 ↔ 2진수</h1>
<p>각 8진수 숫자를 2진수 <strong>3자리</strong>로 바꾸면 된다.</p>
<pre><code class="language-text">0 → 000
1 → 001
2 → 010
3 → 011
4 → 100
5 → 101
6 → 110
7 → 111</code></pre>
<hr>
<h2 id="예제-52325₈">예제 <code>(523.25)₈</code></h2>
<p>각 숫자를 3bit로 바꾼다.</p>
<pre><code class="language-text">5 → 101
2 → 010
3 → 011

.

2 → 010
5 → 101</code></pre>
<p>따라서</p>
<pre><code class="language-text">(523.25)₈
=
(101_010_011.010_101)₂</code></pre>
<p>이다.</p>
<p>여기서 <code>_</code>는 숫자를 읽기 쉽게 구분하기 위한 것이다.</p>
<pre><code class="language-text">101010011</code></pre>
<p>과</p>
<pre><code class="language-text">101_010_011</code></pre>
<p>은 같은 값이다.</p>
<hr>
<h1 id="8-16진수">8. 16진수</h1>
<p>16진수는 총 16개의 숫자가 필요하다.</p>
<p>하지만 숫자 기호는 <code>0~9</code>까지밖에 없기 때문에 이후에는 알파벳을 사용한다.</p>
<pre><code class="language-text">10진수   16진수

0        0
1        1
...
9        9
10       A
11       B
12       C
13       D
14       E
15       F</code></pre>
<p>그 다음 숫자는</p>
<pre><code class="language-text">10₁₆</code></pre>
<p>이다.</p>
<p>주의해야 한다.</p>
<pre><code class="language-text">10₁₆ = 16₁₀</code></pre>
<p>이다.</p>
<hr>
<h1 id="9-16진수-↔-2진수">9. 16진수 ↔ 2진수</h1>
<p>16은</p>
<pre><code class="language-text">16 = 2⁴</code></pre>
<p>이므로 16진수 한 자리는 정확히 4bit와 대응한다.</p>
<pre><code class="language-text">0 → 0000
1 → 0001
...
A → 1010
B → 1011
C → 1100
D → 1101
E → 1110
F → 1111</code></pre>
<hr>
<h1 id="예제-d1af₁₆-→-8진수">예제. <code>(D1AF)₁₆ → 8진수</code></h1>
<p>바로 16진수에서 8진수로 바꾸려고 하지 말고</p>
<pre><code class="language-text">16진수 → 2진수 → 8진수</code></pre>
<p>순서로 바꾸면 쉽다.</p>
<p>먼저</p>
<pre><code class="language-text">D → 1101
1 → 0001
A → 1010
F → 1111</code></pre>
<p>따라서</p>
<pre><code class="language-text">D1AF₁₆
=
1101 0001 1010 1111₂</code></pre>
<p>이제 8진수는 3bit씩 묶으면 된다.</p>
<p>오른쪽부터 3개씩 묶는다.</p>
<pre><code class="language-text">1 | 101 | 000 | 110 | 101 | 111</code></pre>
<p>맨 앞은 3bit가 되도록 0을 붙여도 된다.</p>
<pre><code class="language-text">001 | 101 | 000 | 110 | 101 | 111</code></pre>
<p>각각 8진수로 변환하면</p>
<pre><code class="language-text">001 → 1
101 → 5
000 → 0
110 → 6
101 → 5
111 → 7</code></pre>
<p>따라서</p>
<pre><code class="language-text">(D1AF)₁₆ = (150657)₈</code></pre>
<p>이다.</p>
<hr>
<h1 id="10-컴퓨터는-1111을-어떻게-보는가">10. 컴퓨터는 <code>1111</code>을 어떻게 보는가?</h1>
<p>여기서 매우 중요한 개념이 있다.</p>
<pre><code class="language-text">1111</code></pre>
<p>이라는 bit가 있다고 하자.</p>
<p>우리는 이것을 보고</p>
<pre><code class="language-text">2진수 1111 = 15</code></pre>
<p>라고 생각할 수 있다.</p>
<p>하지만 <strong>컴퓨터 내부의 bit 자체에는 &#39;15&#39;라는 의미가 들어있는 것이 아니다.</strong></p>
<p><code>1111</code>이라는 bit pattern을 어떤 규칙으로 해석하느냐에 따라 의미가 달라질 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">1111</code></pre>
<p>은 상황에 따라</p>
<ul>
<li>숫자의 일부</li>
<li>주소의 일부</li>
<li>명령어의 일부</li>
<li>문자 코드의 일부</li>
<li>여러 개의 ON/OFF 상태</li>
</ul>
<p>등으로 사용될 수 있다.</p>
<p>즉,</p>
<blockquote>
<p><strong>컴퓨터 내부에는 결국 0과 1만 존재하고, 그 0과 1을 어떤 의미로 해석할지는 규칙에 의해 결정된다.</strong></p>
</blockquote>
<p>이 개념은 앞으로 컴퓨터구조를 이해할 때 매우 중요하다.</p>
<hr>
<h1 id="11-왜-주소와-데이터를-16진수로-많이-표현하는가">11. 왜 주소와 데이터를 16진수로 많이 표현하는가?</h1>
<p>컴퓨터 내부에서는 실제로 2진수로 데이터를 처리한다.</p>
<p>그런데 2진수는 사람이 읽기에 너무 길다.</p>
<p>예를 들어</p>
<pre><code class="language-text">11111110110111001011101010011000</code></pre>
<p>보다</p>
<pre><code class="language-text">FEDCBA98</code></pre>
<p>처럼 16진수로 표현하는 것이 훨씬 읽기 쉽다.</p>
<p>왜 16진수를 사용하냐면</p>
<pre><code class="language-text">16진수 1자리 = 2진수 4자리</code></pre>
<p>로 정확히 대응하기 때문이다.</p>
<p>그래서 메모리 주소나 기계어 등을 사람이 볼 때 16진수를 많이 사용한다.</p>
<hr>
<h1 id="12-bcd">12. BCD</h1>
<p>BCD는</p>
<pre><code class="language-text">Binary Coded Decimal</code></pre>
<p>의 약자다.</p>
<p>말 그대로 <strong>10진수의 각 자릿수를 이진 코드로 표현하는 방법</strong>이다.</p>
<hr>
<h2 id="일반적인-2진수와-bcd의-차이">일반적인 2진수와 BCD의 차이</h2>
<p>예를 들어</p>
<pre><code class="language-text">59</code></pre>
<p>가 있다고 하자.</p>
<h3 id="일반적인-2진수">일반적인 2진수</h3>
<p><code>59</code>라는 값 전체를 2진수로 바꾼다.</p>
<pre><code class="language-text">59₁₀ = 111011₂</code></pre>
<h3 id="bcd">BCD</h3>
<p>59를</p>
<pre><code class="language-text">5
9</code></pre>
<p>라는 <strong>두 개의 10진수 숫자</strong>로 따로 본다.</p>
<p>그리고 각각을 4bit로 바꾼다.</p>
<pre><code class="language-text">5 → 0101
9 → 1001</code></pre>
<p>따라서</p>
<pre><code class="language-text">59 → 0101 1001</code></pre>
<p>이 된다.</p>
<p>즉 BCD에서는</p>
<blockquote>
<p>10진수 숫자 하나하나를 <strong>숫자의 값 전체가 아니라 하나의 기호처럼 따로 코드화한다.</strong></p>
</blockquote>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="13-bit와-byte">13. bit와 Byte</h1>
<p>기본 단위는 반드시 기억한다.</p>
<pre><code class="language-text">1 bit = 0 또는 1 하나</code></pre>
<p>그리고</p>
<pre><code class="language-text">1 Byte = 8 bit</code></pre>
<p>이다.</p>
<p>일반적으로</p>
<pre><code class="language-text">b = bit
B = Byte</code></pre>
<p>라고 쓴다.</p>
<p>따라서</p>
<pre><code class="language-text">1B = 8b</code></pre>
<p>이다.</p>
<hr>
<h1 id="14-문자-코드와-ascii">14. 문자 코드와 ASCII</h1>
<p>컴퓨터는 문자도 결국 0과 1로 저장해야 한다.</p>
<p>예를 들어 사람이</p>
<pre><code class="language-text">A</code></pre>
<p>라고 입력했다고 하자.</p>
<p>컴퓨터가 문자 <code>A</code> 자체를 저장하는 것이 아니라, <code>A</code>에 해당하는 숫자 코드를 저장한다.</p>
<p>ASCII에서는</p>
<pre><code class="language-text">A = 65</code></pre>
<p>로 약속되어 있다.</p>
<p>65를 2진수로 표현하면</p>
<pre><code class="language-text">1000001</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-text">A → 1000001</code></pre>
<p>로 표현할 수 있다.</p>
<hr>
<h2 id="b6-b5-b4-b3-b2-b1-b0는-무엇인가"><code>b6 b5 b4 b3 b2 b1 b0</code>는 무엇인가?</h2>
<p>각 bit의 <strong>자리 이름</strong>이다.</p>
<pre><code class="language-text">b6 b5 b4 b3 b2 b1 b0</code></pre>
<p>각 자리의 Weight는</p>
<pre><code class="language-text">2⁶ 2⁵ 2⁴ 2³ 2² 2¹ 2⁰</code></pre>
<p>이다.</p>
<p><code>A</code>의 코드인 <code>1000001</code>을 넣으면</p>
<pre><code class="language-text">b6 b5 b4 b3 b2 b1 b0
 1  0  0  0  0  0  1</code></pre>
<p>이다.</p>
<p>따라서</p>
<pre><code class="language-text">1 × 2⁶ + 1 × 2⁰
= 64 + 1
= 65</code></pre>
<p>가 된다.</p>
<p>그래서 ASCII 코드 65가 <code>A</code>다.</p>
<hr>
<h1 id="문자열도-결국-숫자다">문자열도 결국 숫자다</h1>
<p>예를 들어</p>
<pre><code class="language-text">good morning</code></pre>
<p>이라는 문자열도 컴퓨터 내부에서는</p>
<pre><code class="language-text">g → 문자 코드
o → 문자 코드
o → 문자 코드
d → 문자 코드
공백 → 문자 코드
m → 문자 코드
...</code></pre>
<p>처럼 문자 하나하나가 숫자로 저장된다.</p>
<p>즉 컴퓨터 입장에서는 문자열도 결국 <strong>bit들의 집합</strong>이다.</p>
<hr>
<h1 id="unicode">Unicode</h1>
<p>ASCII만으로는 영어 중심의 제한된 문자만 표현할 수 있다.</p>
<p>한글, 일본어, 중국어 등 세계의 다양한 문자를 표현하기 위해 훨씬 큰 문자 체계가 필요하다.</p>
<p>그래서 Unicode가 사용된다.</p>
<p>현재 단계에서는</p>
<blockquote>
<p><strong>Unicode = 세계 여러 문자를 하나의 문자 코드 체계로 표현하기 위한 표준</strong></p>
</blockquote>
<p>정도로 이해하면 충분하다.</p>
<hr>
<h1 id="22-조합-논리회로">2.2 조합 논리회로</h1>
<h2 id="1-조합-논리회로와-순차-논리회로">1. 조합 논리회로와 순차 논리회로</h2>
<p>디지털 회로는 크게 두 종류로 생각할 수 있다.</p>
<h3 id="조합-논리회로">조합 논리회로</h3>
<p>현재 입력만 보고 현재 출력이 결정된다.</p>
<pre><code class="language-text">현재 입력
   ↓
조합 논리회로
   ↓
현재 출력</code></pre>
<p>과거에 어떤 값이 들어왔는지는 중요하지 않다.</p>
<p>프로그래밍으로 비유하면 <strong>상태가 없는 함수</strong>와 비슷하다.</p>
<hr>
<h3 id="순차-논리회로">순차 논리회로</h3>
<p>현재 입력뿐만 아니라 <strong>이전에 저장되어 있던 상태</strong>도 출력에 영향을 준다.</p>
<pre><code class="language-text">현재 입력 ─┐
           ↓
       순차 논리회로 → 출력
           ↑
        이전 상태</code></pre>
<p>즉 <strong>기억 능력</strong>이 있다.</p>
<hr>
<h1 id="2-논리-게이트">2. 논리 게이트</h1>
<p>논리 게이트는 0과 1을 입력받아 특정 규칙에 따라 0 또는 1을 출력하는 가장 기본적인 회로다.</p>
<hr>
<h2 id="and">AND</h2>
<p>두 입력이 <strong>모두 1일 때만 1</strong>이다.</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 0
0 1 | 0
1 0 | 0
1 1 | 1</code></pre>
<p>프로그래밍으로 생각하면</p>
<pre><code class="language-java">A &amp;&amp; B</code></pre>
<p>와 비슷하다.</p>
<hr>
<h2 id="or">OR</h2>
<p>하나라도 1이면 1이다.</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 0
0 1 | 1
1 0 | 1
1 1 | 1</code></pre>
<hr>
<h2 id="xor">XOR</h2>
<p>두 값이 <strong>서로 다르면 1</strong>이다.</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 0
0 1 | 1
1 0 | 1
1 1 | 0</code></pre>
<p>즉</p>
<pre><code class="language-text">같으면 0
다르면 1</code></pre>
<p>이다.</p>
<hr>
<h2 id="not">NOT</h2>
<p>입력을 반대로 만든다.</p>
<pre><code class="language-text">A | Y
------
0 | 1
1 | 0</code></pre>
<hr>
<h2 id="nand">NAND</h2>
<p>AND의 결과를 NOT한다.</p>
<pre><code class="language-text">NAND = NOT(AND)</code></pre>
<p>따라서</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 1
0 1 | 1
1 0 | 1
1 1 | 0</code></pre>
<p>이다.</p>
<hr>
<h2 id="nor">NOR</h2>
<p>OR의 결과를 NOT한다.</p>
<pre><code class="language-text">NOR = NOT(OR)</code></pre>
<hr>
<h2 id="xnor">XNOR</h2>
<p>XOR의 결과를 NOT한다.</p>
<p>따라서 두 값이 같으면 1이다.</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 1
0 1 | 0
1 0 | 0
1 1 | 1</code></pre>
<hr>
<h2 id="buffer">Buffer</h2>
<p>입력을 그대로 출력한다.</p>
<pre><code class="language-text">0 → 0
1 → 1</code></pre>
<p>논리적으로 아무것도 하지 않는 것처럼 보이지만, 실제 회로에서는 신호 전달이나 회로 분리 등에 사용한다.</p>
<p>뒤에서 배우는 <strong>3-State Buffer</strong>가 Bus를 구성할 때 매우 중요하다.</p>
<hr>
<h1 id="3-정논리와-부논리">3. 정논리와 부논리</h1>
<h2 id="정논리">정논리</h2>
<p>일반적으로</p>
<pre><code class="language-text">높은 전압 → 논리 1
낮은 전압 → 논리 0</code></pre>
<p>으로 해석한다.</p>
<h2 id="부논리">부논리</h2>
<p>반대로</p>
<pre><code class="language-text">높은 전압 → 논리 0
낮은 전압 → 논리 1</code></pre>
<p>로 해석한다.</p>
<p>중요한 것은 실제 전기 신호가 바뀌는 것이 아니라 <strong>그 신호에 어떤 논리적 의미를 부여하느냐가 달라지는 것</strong>이다.</p>
<hr>
<h1 id="23-순차-논리회로">2.3 순차 논리회로</h1>
<h1 id="1-동기식-순차-논리회로">1. 동기식 순차 논리회로</h1>
<p>순차 논리회로는 값을 기억할 수 있다.</p>
<p>그중 동기식 순차 논리회로는 <strong>Clock이라는 공통 신호에 맞춰 상태가 변한다.</strong></p>
<p>Clock은 여러 회로에게</p>
<pre><code class="language-text">지금 값을 받아!
지금 상태를 바꿔!</code></pre>
<p>라고 알려주는 공통 박자라고 생각하면 된다.</p>
<hr>
<h1 id="2-flip-flop-⭐-시험-중요">2. Flip-Flop ⭐ 시험 중요</h1>
<blockquote>
<p><strong>Flip-Flop은 1bit의 데이터를 저장하는 기억 소자다.</strong></p>
</blockquote>
<p>이 문장은 반드시 기억한다.</p>
<pre><code class="language-text">Flip-Flop 1개 → 1bit 저장</code></pre>
<p>예를 들어 Flip-Flop의 출력 Q가</p>
<pre><code class="language-text">Q = 1</code></pre>
<p>이라면 현재 <code>1</code>이라는 1bit 정보를 기억하고 있는 것이다.</p>
<hr>
<h1 id="q와-q̅">Q와 Q̅</h1>
<p>Flip-Flop 그림에는 일반적으로 두 개의 출력이 있다.</p>
<pre><code class="language-text">Q
Q̅</code></pre>
<p><code>Q</code>는 정상 출력이다.</p>
<p><code>Q̅</code>는 Q의 반대 값이다.</p>
<pre><code class="language-text">Q = 0 → Q̅ = 1
Q = 1 → Q̅ = 0</code></pre>
<p>이다.</p>
<hr>
<h1 id="3-clock-pulsecp">3. Clock Pulse(CP)</h1>
<p>Clock은 다음과 같이 0과 1을 반복하는 신호다.</p>
<pre><code class="language-text">       ┌─────┐       ┌─────┐
───────┘     └───────┘     └──────
       ↑     ↓       ↑     ↓</code></pre>
<p>Clock이</p>
<pre><code class="language-text">0 → 1</code></pre>
<p>로 변하는 순간을 <strong>상승 에지(Rising Edge, Positive Edge)</strong>라고 한다.</p>
<pre><code class="language-text">      ↑
______|‾‾‾‾‾</code></pre>
<p>반대로</p>
<pre><code class="language-text">1 → 0</code></pre>
<p>로 변하는 순간은 <strong>하강 에지(Falling Edge, Negative Edge)</strong>다.</p>
<pre><code class="language-text">‾‾‾‾‾|______
      ↓</code></pre>
<hr>
<h1 id="edge-sensitive">Edge Sensitive</h1>
<p>Flip-Flop은 Clock의 특정 <strong>순간(edge)</strong>에 반응한다.</p>
<p>예를 들어 Positive Edge Triggered Flip-Flop이라면</p>
<pre><code class="language-text">Clock 0 → 1</code></pre>
<p>이 되는 바로 그 순간에 입력을 확인한다.</p>
<p>따라서 앞으로 타이밍 다이어그램을 볼 때 가장 먼저 해야 하는 것은</p>
<pre><code class="language-text">1. Clock을 찾는다.
2. 상승 에지 ↑를 모두 표시한다.
3. 각 ↑ 순간의 입력을 확인한다.
4. Flip-Flop의 규칙에 따라 Q를 결정한다.</code></pre>
<p>이다.</p>
<p>이것이 순차 논리회로의 그림을 읽는 가장 중요한 방법이다.</p>
<hr>
<h1 id="latch와-flip-flop의-차이-⭐-시험-가능">Latch와 Flip-Flop의 차이 ⭐ 시험 가능</h1>
<p>둘 다 데이터를 기억할 수 있지만 <strong>언제 입력에 반응하는가</strong>가 다르다.</p>
<h2 id="latch">Latch</h2>
<p>특정 <strong>레벨(Level)</strong>이 유지되는 동안 입력에 반응한다.</p>
<p>예를 들어 Enable이 1인 동안 입력이 바뀌면 출력도 영향을 받을 수 있다.</p>
<p>따라서</p>
<pre><code class="language-text">Latch → Level Sensitive</code></pre>
<p>이다.</p>
<h2 id="flip-flop">Flip-Flop</h2>
<p>Clock의 특정 <strong>Edge 순간</strong>에만 입력을 받아들인다.</p>
<p>따라서</p>
<pre><code class="language-text">Flip-Flop → Edge Sensitive</code></pre>
<p>이다.</p>
<h3 id="시험용으로-기억">시험용으로 기억</h3>
<pre><code class="language-text">Latch      = Level Sensitive
Flip-Flop  = Edge Sensitive</code></pre>
<hr>
<h1 id="4-setup-time-hold-time-delay-time">4. Setup Time, Hold Time, Delay Time</h1>
<p>Flip-Flop이 Clock의 상승 에지에서 D 값을 읽는다고 하자.</p>
<p>그런데 Clock이 올라가는 정확한 순간에 D가 계속 변하고 있다면 안정적으로 값을 읽기 어렵다.</p>
<p>그래서 입력이 일정 시간 동안 안정되어 있어야 한다.</p>
<hr>
<h2 id="setup-time">Setup Time</h2>
<p>Clock edge가 발생하기 <strong>이전</strong>에 입력값이 미리 안정되어 있어야 하는 최소 시간이다.</p>
<pre><code class="language-text">             Clock ↑
                 │
D ───────────────1────────────
        &lt;--------&gt;
        Setup Time</code></pre>
<p>즉</p>
<blockquote>
<p>&quot;Clock이 올라오기 직전에는 D를 바꾸지 마라.&quot;</p>
</blockquote>
<p>라고 생각하면 된다.</p>
<hr>
<h2 id="hold-time">Hold Time</h2>
<p>Clock edge가 발생한 <strong>이후에도</strong> 입력을 일정 시간 유지해야 한다.</p>
<pre><code class="language-text">             Clock ↑
                 │
D ───────────────1────────────
                 &lt;-------&gt;
                 Hold Time</code></pre>
<p>즉</p>
<blockquote>
<p>&quot;Clock이 올라간 직후에도 바로 D를 바꾸지 마라.&quot;</p>
</blockquote>
<p>라는 의미다.</p>
<hr>
<h2 id="delay-time">Delay Time</h2>
<p>Clock edge가 발생했다고 해서 출력 Q가 물리적으로 0초 만에 변하는 것은 아니다.</p>
<p>실제 회로이기 때문에 약간의 시간이 필요하다.</p>
<pre><code class="language-text">Clock ↑
   │
   │   Delay
   └────────→ Q 변화</code></pre>
<p>이를 <strong>Propagation Delay</strong>라고 한다.</p>
<p>일반적으로 Delay가 작으면 더 빠르게 동작할 수 있다.</p>
<p>실제 부품을 선택할 때 Datasheet를 보고</p>
<ul>
<li>Setup Time</li>
<li>Hold Time</li>
<li>Propagation Delay</li>
</ul>
<p>등을 확인할 수 있다.</p>
<hr>
<h1 id="5-flip-flop의-종류">5. Flip-Flop의 종류</h1>
<h2 id="d-flip-flop">D Flip-Flop</h2>
<p>가장 이해하기 쉬운 Flip-Flop이다.</p>
<p>규칙은 하나다.</p>
<blockquote>
<p><strong>Clock의 유효 Edge 순간의 D 값을 Q에 저장한다.</strong></p>
</blockquote>
<p>즉</p>
<pre><code class="language-text">Clock ↑일 때

Q ← D</code></pre>
<p>이다.</p>
<h3 id="예시-1">예시</h3>
<p>현재</p>
<pre><code class="language-text">Q = 0</code></pre>
<p>이라고 하자.</p>
<p>Clock 상승 에지 순간에</p>
<pre><code class="language-text">D = 1</code></pre>
<p>이라면</p>
<pre><code class="language-text">Q ← 1</code></pre>
<p>이 된다.</p>
<p>그 이후 D가 다시 0으로 바뀌더라도 다음 Clock 상승 에지가 오기 전까지 Q는 계속 1을 유지한다.</p>
<pre><code class="language-text">D      0 ---- 1 ---- 0 --------
                ↑
Clock ----------|---------------
                ↑
             이 순간 D=1

Q      0 -------1---------------</code></pre>
<p>즉 D의 값이 Q로 전달되지만 <strong>Clock에 맞춰 저장된다.</strong></p>
<hr>
<h1 id="sr-flip-flop">SR Flip-Flop</h1>
<p><code>S</code>는 Set, <code>R</code>은 Reset이다.</p>
<pre><code class="language-text">S = Set   → Q를 1로
R = Reset → Q를 0으로</code></pre>
<p>따라서 기본적으로</p>
<pre><code class="language-text">S=1 → Q=1
R=1 → Q=0</code></pre>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="jk-flip-flop-⭐-중요">JK Flip-Flop ⭐ 중요</h1>
<p>JK Flip-Flop은 다음 표를 기억해야 한다.</p>
<pre><code class="language-text">J K | 다음 Q
----------------
0 0 | 현재 값 유지
0 1 | 0
1 0 | 1
1 1 | 현재 Q 반전</code></pre>
<p>즉</p>
<pre><code class="language-text">J=0, K=0
→ 아무것도 하지 않는다.

J=0, K=1
→ Q를 0으로 만든다.

J=1, K=0
→ Q를 1로 만든다.

J=1, K=1
→ Q를 반대로 바꾼다.</code></pre>
<p>마지막 <code>11</code>이 특히 중요하다.</p>
<p>현재</p>
<pre><code class="language-text">Q=0</code></pre>
<p>이면</p>
<pre><code class="language-text">Q=1</code></pre>
<p>로 바뀌고,</p>
<p>현재</p>
<pre><code class="language-text">Q=1</code></pre>
<p>이면</p>
<pre><code class="language-text">Q=0</code></pre>
<p>으로 바뀐다.</p>
<p>이를 <strong>Toggle</strong>이라고 한다.</p>
<hr>
<h1 id="t-flip-flop">T Flip-Flop</h1>
<p>T Flip-Flop은 더 간단하다.</p>
<pre><code class="language-text">T | 다음 Q
-------------
0 | 현재 Q 유지
1 | 현재 Q 반전</code></pre>
<p>즉</p>
<pre><code class="language-text">T=0 → Q
T=1 → Q̅</code></pre>
<p>이다.</p>
<p>그래서 Counter 같은 회로를 만들 때 유용하다.</p>
<hr>
<h1 id="6-상승-에지-jk-flip-flop의-동작-⭐⭐⭐">6. 상승 에지 JK Flip-Flop의 동작 ⭐⭐⭐</h1>
<p>이 부분은 <strong>Q 출력 파형을 직접 그리는 문제</strong>가 나올 수 있다.</p>
<p>그림을 볼 때 절대 모든 선을 한 번에 보려고 하지 않는다.</p>
<p>다음 순서대로 본다.</p>
<hr>
<h2 id="step-1-clock-상승-에지를-찾는다">STEP 1. Clock 상승 에지를 찾는다.</h2>
<pre><code class="language-text">        ↑       ↑       ↑       ↑
Clock __|‾|_____|‾|_____|‾|_____|‾</code></pre>
<p>Positive Edge Triggered JK FF라면 <strong>이 순간만 보면 된다.</strong></p>
<hr>
<h2 id="step-2-각-상승-에지-순간의-j와-k를-확인한다">STEP 2. 각 상승 에지 순간의 J와 K를 확인한다.</h2>
<p>예를 들어 첫 번째 상승 에지에서</p>
<pre><code class="language-text">J=1
K=0</code></pre>
<p>이라면 JK 표에서</p>
<pre><code class="language-text">10 → Q=1</code></pre>
<p>이다.</p>
<hr>
<h2 id="step-3-다음-상승-에지까지-q를-유지한다">STEP 3. 다음 상승 에지까지 Q를 유지한다.</h2>
<p>Clock 중간에 J와 K가 변해도 Q가 바로 변하는 것이 아니다.</p>
<p>다음 상승 에지가 올 때까지 현재 값을 유지한다.</p>
<hr>
<h2 id="예시-2">예시</h2>
<p>초기 상태가</p>
<pre><code class="language-text">Q=0</code></pre>
<p>이라고 하자.</p>
<p>각 상승 에지에서 J,K가 다음과 같다고 하자.</p>
<pre><code class="language-text">첫 번째 ↑ : J=1, K=0
두 번째 ↑ : J=0, K=0
세 번째 ↑ : J=1, K=1
네 번째 ↑ : J=0, K=1</code></pre>
<p>하나씩 계산한다.</p>
<h3 id="첫-번째-↑">첫 번째 ↑</h3>
<pre><code class="language-text">J=1, K=0
→ Set
→ Q=1</code></pre>
<h3 id="두-번째-↑">두 번째 ↑</h3>
<pre><code class="language-text">J=0, K=0
→ 유지
→ Q=1</code></pre>
<h3 id="세-번째-↑">세 번째 ↑</h3>
<pre><code class="language-text">J=1, K=1
→ 반전
→ Q=0</code></pre>
<h3 id="네-번째-↑">네 번째 ↑</h3>
<pre><code class="language-text">J=0, K=1
→ Reset
→ Q=0</code></pre>
<p>따라서 Q는</p>
<pre><code class="language-text">초기 0
→ 1
→ 1
→ 0
→ 0</code></pre>
<p>으로 변한다.</p>
<hr>
<h1 id="⭐-타이밍-다이어그램-풀이-공식">⭐ 타이밍 다이어그램 풀이 공식</h1>
<p>앞으로 Flip-Flop 파형 문제를 보면 무조건 다음 순서로 푼다.</p>
<pre><code class="language-text">① Clock을 찾는다.

② 유효 Edge를 표시한다.
   Positive → ↑
   Negative → ↓

③ 해당 순간의 입력을 읽는다.

④ Flip-Flop의 동작표를 적용한다.

⑤ 다음 Edge까지 Q를 유지한다.</code></pre>
<p>이것만 기억하면 된다.</p>
<hr>
<h1 id="7-register-⭐-중요">7. Register ⭐ 중요</h1>
<p>Flip-Flop 하나는</p>
<pre><code class="language-text">1bit</code></pre>
<p>를 저장한다.</p>
<p>그렇다면 4bit 데이터를 저장하려면 Flip-Flop이 몇 개 필요할까?</p>
<pre><code class="language-text">4개</code></pre>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">[D FF] → 1bit
[D FF] → 1bit
[D FF] → 1bit
[D FF] → 1bit</code></pre>
<p>이렇게 4개를 묶으면 <strong>4bit Register</strong>가 된다.</p>
<p>따라서</p>
<blockquote>
<p><strong>Register = 여러 개의 Flip-Flop을 묶어 여러 bit의 데이터를 저장하는 장치</strong></p>
</blockquote>
<p>라고 이해하면 된다.</p>
<hr>
<h1 id="register의-input과-output">Register의 Input과 Output</h1>
<p>4bit Register를 예로 들면</p>
<pre><code class="language-text">Input
D3 D2 D1 D0
│  │  │  │
▼  ▼  ▼  ▼
[FF][FF][FF][FF]
│  │  │  │
▼  ▼  ▼  ▼
Q3 Q2 Q1 Q0
Output</code></pre>
<p>이다.</p>
<p><code>D</code>는 입력,</p>
<p><code>Q</code>는 현재 Register에 저장되어 있는 출력이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">D = 1011</code></pre>
<p>이고 저장 조건이 만족되면</p>
<pre><code class="language-text">Q ← 1011</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="control-signal">Control Signal</h1>
<p>Register 그림에는 데이터 이외에도</p>
<pre><code class="language-text">Clear
Enable
Clock</code></pre>
<p>같은 신호가 존재한다.</p>
<p>이들은 저장하려는 <strong>데이터 자체</strong>가 아니다.</p>
<p>Register에게</p>
<pre><code class="language-text">초기화해라.
저장을 허용해라.
지금 저장해라.</code></pre>
<p>와 같은 명령을 내린다.</p>
<p>그래서 <strong>Control Signal(제어 신호)</strong>이라고 한다.</p>
<hr>
<h1 id="clear--low-active"><code>/Clear</code> : Low Active</h1>
<p>그림에서</p>
<pre><code class="language-text">/Clear</code></pre>
<p>처럼 <code>/</code>가 붙어 있다면 일반적으로 <strong>Low Active</strong>라는 뜻이다.</p>
<p>즉 <code>0</code>일 때 기능이 작동한다.</p>
<pre><code class="language-text">/Clear = 0 → Clear 작동
/Clear = 1 → Clear 작동하지 않음</code></pre>
<p>Clear가 작동하면</p>
<pre><code class="language-text">Q = 0000</code></pre>
<p>으로 초기화한다.</p>
<hr>
<h1 id="enable--high-active">Enable : High Active</h1>
<p>Enable은 일반적으로</p>
<pre><code class="language-text">Enable = 1 → 동작 허용
Enable = 0 → 현재 상태 유지</code></pre>
<p>이다.</p>
<p>즉 <code>1</code>일 때 기능이 활성화되므로 <strong>High Active</strong>다.</p>
<hr>
<h1 id="register의-제어-신호-우선순위-⭐-시험-중요">Register의 제어 신호 우선순위 ⭐ 시험 중요</h1>
<p>교수님 설명에서 Clear가 가장 강한 신호라고 이해하면 된다.</p>
<p>예를 들어 다음 표를 생각해보자.</p>
<pre><code class="language-text">/Clear | Enable | Clock | 결과
--------------------------------
   0   |   X    |   X   | Q=0
   1   |   0    |   X   | Q 유지
   1   |   1    |   ↑   | Q ← D</code></pre>
<p>여기서 <code>X</code>는</p>
<pre><code class="language-text">Don&#39;t Care</code></pre>
<p>라는 뜻이다.</p>
<p>즉 0이든 1이든 상관없다.</p>
<hr>
<h2 id="첫-번째-경우">첫 번째 경우</h2>
<pre><code class="language-text">/Clear = 0</code></pre>
<p>이면 다른 신호는 볼 필요가 없다.</p>
<pre><code class="language-text">Q = 0000</code></pre>
<p>이 된다.</p>
<p>따라서 Clear가 가장 우선순위가 높다.</p>
<hr>
<h2 id="두-번째-경우">두 번째 경우</h2>
<pre><code class="language-text">/Clear = 1
Enable = 0</code></pre>
<p>이면 Clear는 작동하지 않지만 Enable이 꺼져 있다.</p>
<p>따라서</p>
<pre><code class="language-text">Q 유지</code></pre>
<p>이다.</p>
<hr>
<h2 id="세-번째-경우">세 번째 경우</h2>
<pre><code class="language-text">/Clear = 1
Enable = 1
Clock ↑</code></pre>
<p>이면 입력 D를 저장한다.</p>
<pre><code class="language-text">Q ← D</code></pre>
<p>이다.</p>
<hr>
<h1 id="비동기-clear와-동기-적재">비동기 Clear와 동기 적재</h1>
<p>Clear가 <strong>비동기(Asynchronous)</strong>라면 Clock을 기다리지 않는다.</p>
<pre><code class="language-text">/Clear : 1 → 0</code></pre>
<p>이 되는 순간</p>
<pre><code class="language-text">Q=0000</code></pre>
<p>이 된다.</p>
<p>반면 일반적인 데이터 적재는</p>
<pre><code class="language-text">Clock ↑</code></pre>
<p>를 기다린다.</p>
<p>따라서 <strong>동기식(Synchronous)</strong>이다.</p>
<hr>
<h1 id="8-4bit-register-동작-파형-읽기-⭐⭐⭐">8. 4bit Register 동작 파형 읽기 ⭐⭐⭐</h1>
<p>이 그래프는 시험에서 매우 중요할 가능성이 있다.</p>
<p>그래프에</p>
<pre><code class="language-text">Clear
Enable
Clock
D
Q</code></pre>
<p>가 있다고 하자.</p>
<p>한 번에 다 보지 않는다.</p>
<p>다음 순서대로 본다.</p>
<hr>
<h2 id="1단계-clear부터-본다">1단계. Clear부터 본다.</h2>
<pre><code class="language-text">/Clear = 0</code></pre>
<p>이면 즉시</p>
<pre><code class="language-text">Q=0000</code></pre>
<p>이다.</p>
<p>다른 신호는 무시한다.</p>
<hr>
<h2 id="2단계-clear1이면-enable을-본다">2단계. Clear=1이면 Enable을 본다.</h2>
<pre><code class="language-text">Enable=0</code></pre>
<p>이면</p>
<pre><code class="language-text">Q 유지</code></pre>
<p>이다.</p>
<hr>
<h2 id="3단계-enable1이면-clock-상승-에지를-찾는다">3단계. Enable=1이면 Clock 상승 에지를 찾는다.</h2>
<pre><code class="language-text">Clock ↑</code></pre>
<p>인 순간의 D를 읽는다.</p>
<p>예를 들어</p>
<pre><code class="language-text">D=0011</code></pre>
<p>이라면</p>
<pre><code class="language-text">Q ← 0011</code></pre>
<p>이다.</p>
<hr>
<h2 id="4단계-다음-유효-clock까지-q를-유지한다">4단계. 다음 유효 Clock까지 Q를 유지한다.</h2>
<p>D가 중간에</p>
<pre><code class="language-text">0011
→ 1010
→ 1111
→ 0101</code></pre>
<p>처럼 계속 변해도 Clock 상승 에지가 없다면 Q는 바뀌지 않는다.</p>
<p>이것이 <strong>동기 적재(Synchronous Load)</strong>다.</p>
<hr>
<h1 id="9-shift-register">9. Shift Register</h1>
<p>Shift Register는 Register에 저장된 bit들을 Clock에 맞춰 <strong>한 칸씩 이동시키는 Register</strong>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">SI → [FF0] → [FF1] → [FF2] → [FF3]
        │       │       │       │
       Q0      Q1      Q2      Q3</code></pre>
<p>와 같은 구조가 있다고 하자.</p>
<p><code>SI</code>는</p>
<pre><code class="language-text">Serial Input</code></pre>
<p>즉 <strong>직렬 입력</strong>이다.</p>
<hr>
<h1 id="직렬-데이터란">직렬 데이터란?</h1>
<p>예를 들어 <code>1100</code>이라는 4bit 데이터를 보내고 싶다고 하자.</p>
<p>병렬 전송이라면</p>
<pre><code class="language-text">1 1 0 0
↓ ↓ ↓ ↓</code></pre>
<p>4bit를 동시에 보낸다.</p>
<p>직렬 전송은</p>
<pre><code class="language-text">1 → 1 → 0 → 0</code></pre>
<p>처럼 한 번에 1bit씩 보낸다.</p>
<hr>
<h1 id="shift-register의-동작">Shift Register의 동작</h1>
<p>초기값을</p>
<pre><code class="language-text">0000</code></pre>
<p>이라고 하자.</p>
<p>SI로 데이터를 하나씩 입력한다.</p>
<p>예를 들어 회로의 이동 방향이 다음과 같다고 하자.</p>
<pre><code class="language-text">SI → Q0 → Q1 → Q2 → Q3</code></pre>
<p>Clock이 한 번 상승할 때마다 한 칸씩 이동한다.</p>
<p>예를 들어 SI에 차례대로</p>
<pre><code class="language-text">1, 1, 0, 0</code></pre>
<p>을 넣으면 각 Clock마다 저장된 값이 이동한다.</p>
<p>중요한 것은 정확한 숫자의 좌우 방향을 무작정 외우는 것이 아니다.</p>
<blockquote>
<p><strong>자료의 화살표를 보고 SI가 어느 Flip-Flop에 들어가고, 각 Q가 다음 어느 D로 연결되는지를 확인해야 한다.</strong></p>
</blockquote>
<p>핵심 원리는</p>
<pre><code class="language-text">Clock ↑
→ 모든 bit가 동시에 한 칸 이동</code></pre>
<p>이다.</p>
<hr>
<h1 id="직렬-데이터를-병렬-데이터로-바꾼다는-의미">직렬 데이터를 병렬 데이터로 바꾼다는 의미</h1>
<p>SI에서는 한 번에 1bit만 들어온다.</p>
<pre><code class="language-text">SI

1
↓
다음 Clock
1
↓
다음 Clock
0
↓
다음 Clock
0</code></pre>
<p>그런데 충분한 Clock이 지난 후 Register의</p>
<pre><code class="language-text">Q3 Q2 Q1 Q0</code></pre>
<p>를 동시에 읽으면 4bit를 한 번에 얻을 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">Serial Input
     ↓
Shift Register
     ↓
Parallel Output</code></pre>
<p>이 가능하다.</p>
<p>그래서 Shift Register를 <strong>직렬 데이터를 병렬 데이터로 변환하는 데 사용할 수 있다.</strong></p>
<hr>
<h1 id="10-4bit-양방향-shift-register">10. 4bit 양방향 Shift Register</h1>
<p>일반 Shift Register가 한 방향으로만 이동한다면 양방향 Shift Register는</p>
<pre><code class="language-text">왼쪽 Shift ←
오른쪽 Shift →</code></pre>
<p>둘 다 가능하다.</p>
<p>어느 방향으로 이동할지는 Control Signal이 결정한다.</p>
<p>자료에서는 <code>S1</code>, <code>S0</code> 같은 선택 신호의 조합에 따라 동작을 선택한다.</p>
<p>예를 들어 구조적으로는</p>
<pre><code class="language-text">S1 S0 | 동작
---------------------
0  0  | 현재 값 유지
0  1  | 오른쪽 Shift
1  0  | 왼쪽 Shift
1  1  | 병렬 Load</code></pre>
<p>와 같이 여러 기능 중 하나를 선택하는 형태로 이해하면 된다.</p>
<blockquote>
<p>정확한 <code>S1</code>, <code>S0</code> 조합은 수업 자료의 표를 기준으로 외우고, 핵심은 <strong>제어 신호 조합으로 Register가 수행할 동작을 선택한다는 것</strong>이다.</p>
</blockquote>
<hr>
<h1 id="11-counter">11. Counter</h1>
<p>Counter는</p>
<blockquote>
<p><strong>정해진 순서대로 상태를 반복적으로 변화시키는 Register의 일종</strong></p>
</blockquote>
<p>이다.</p>
<p>예를 들어 4bit 이진 Counter는 다음처럼 상태가 변할 수 있다.</p>
<pre><code class="language-text">0000 → 0
0001 → 1
0010 → 2
0011 → 3
0100 → 4
...
1110 → 14
1111 → 15
0000 → 0
...</code></pre>
<p>즉</p>
<pre><code class="language-text">0 → 1 → 2 → ... → 15 → 0 → ...</code></pre>
<p>을 계속 반복한다.</p>
<hr>
<h1 id="counter의-상태도">Counter의 상태도</h1>
<p>자료의 원형 그림에서</p>
<pre><code class="language-text">0000 → 0001 → 0010 → ... → 1111
 ↑                            ↓
 └────────────────────────────┘</code></pre>
<p>처럼 연결되어 있는 이유가 이것이다.</p>
<p>마지막 상태가 끝나면 다시 처음 상태로 돌아간다.</p>
<hr>
<h1 id="counter의-출력-파형">Counter의 출력 파형</h1>
<p>4bit Counter를</p>
<pre><code class="language-text">Q3 Q2 Q1 Q0</code></pre>
<p>라고 하자.</p>
<p>숫자를 하나씩 증가시키면</p>
<pre><code class="language-text">0  = 0000
1  = 0001
2  = 0010
3  = 0011
4  = 0100
5  = 0101
6  = 0110
7  = 0111
8  = 1000
...</code></pre>
<p>이 된다.</p>
<p>따라서 가장 낮은 자리 Q0는 매번 바뀐다.</p>
<pre><code class="language-text">Q0 : 0 1 0 1 0 1 0 1 ...</code></pre>
<p>Q1은 두 번마다 바뀐다.</p>
<pre><code class="language-text">Q1 : 0 0 1 1 0 0 1 1 ...</code></pre>
<p>Q2는 네 번마다 바뀐다.</p>
<pre><code class="language-text">Q2 : 0 0 0 0 1 1 1 1 ...</code></pre>
<p>Q3는 여덟 번마다 바뀐다.</p>
<pre><code class="language-text">Q3 : 0 0 0 0 0 0 0 0 1 1 1 1 ...</code></pre>
<p>따라서 각 출력의 주파수도 계속 절반으로 줄어든다.</p>
<hr>
<h1 id="12-병렬-적재-가능한-4bit-이진-counter">12. 병렬 적재 가능한 4bit 이진 Counter</h1>
<p>일반 Counter는</p>
<pre><code class="language-text">Q ← Q + 1</code></pre>
<p>을 반복한다.</p>
<p>하지만 병렬 적재 기능이 있다면 원하는 초기값을 한 번에 넣을 수도 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">D = 1010</code></pre>
<p>이고 Load가 활성화되어 있다면</p>
<pre><code class="language-text">Q ← 1010</code></pre>
<p>으로 값을 넣을 수 있다.</p>
<p>그 후 Counter를 동작시키면</p>
<pre><code class="language-text">1010
1011
1100
1101
...</code></pre>
<p>처럼 카운트할 수 있다.</p>
<p>즉 각 제어 신호의 역할을 다음처럼 생각하면 된다.</p>
<pre><code class="language-text">Clear  → 0으로 초기화
Load   → D값을 Q에 적재
Enable → Counting 허용
Clock  → 실제 상태가 변하는 시점</code></pre>
<hr>
<h1 id="24-레지스터-전송">2.4 레지스터 전송</h1>
<p>이제 지금까지 배운 Register들을 서로 연결한다.</p>
<p>CPU 내부에는 수많은 Register가 존재한다.</p>
<p>그리고 Register 사이에서 데이터를 이동시키고 연산해야 한다.</p>
<hr>
<h1 id="1-마이크로오퍼레이션">1. 마이크로오퍼레이션</h1>
<p>마이크로오퍼레이션(Microoperation)은</p>
<blockquote>
<p><strong>컴퓨터 내부에서 Register에 저장된 데이터에 대해 수행되는 기본적인 작은 동작</strong></p>
</blockquote>
<p>이라고 이해하면 된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>은 R1의 값을 R2로 전달하는 동작이다.</p>
<p>또</p>
<pre><code class="language-text">R3 ← R1 + R2</code></pre>
<p>는 R1과 R2를 더해서 R3에 저장하는 동작이다.</p>
<hr>
<h1 id="2-register-간-데이터-전달">2. Register 간 데이터 전달</h1>
<p>다음 표현을 보자.</p>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>프로그래밍으로 비유하면</p>
<pre><code class="language-java">R2 = R1;</code></pre>
<p>과 비슷하다.</p>
<p>화살표는 <strong>오른쪽에서 왼쪽으로 데이터가 이동한다</strong>고 읽는다.</p>
<pre><code class="language-text">R1 ───────→ R2</code></pre>
<p>주의할 점은 R1의 데이터가 사라지는 것이 아니다.</p>
<p>복사된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">R1 = 1010
R2 = 0000</code></pre>
<p>에서</p>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>을 수행하면</p>
<pre><code class="language-text">R1 = 1010
R2 = 1010</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="4-표시는-무엇인가"><code>4/</code> 표시는 무엇인가?</h1>
<p>회로 그림의 선 옆에</p>
<pre><code class="language-text">4/</code></pre>
<p>처럼 적혀 있다면 그 선이 하나의 bit가 아니라 <strong>4bit를 동시에 전달하는 선 묶음</strong>이라는 의미다.</p>
<p>즉</p>
<pre><code class="language-text">────── 4/ ──────</code></pre>
<p>는 실제로는</p>
<pre><code class="language-text">bit 0 ─────────
bit 1 ─────────
bit 2 ─────────
bit 3 ─────────</code></pre>
<p>네 개의 선을 간단하게 표현한 것이다.</p>
<hr>
<h1 id="r1과-r2가-clock을-공유하는-이유">R1과 R2가 Clock을 공유하는 이유</h1>
<p>두 Register가 같은 Clock을 사용하면 같은 시간 기준으로 동작할 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">Clock ↑</code></pre>
<p>라는 공통 시점을 기준으로 데이터를 전달하고 저장한다.</p>
<p>이를 통해 여러 Register의 동작을 <strong>동기화</strong>할 수 있다.</p>
<hr>
<h1 id="t1--r2-←-r1"><code>T1 : R2 ← R1</code></h1>
<p>이 표현을 해석해보자.</p>
<pre><code class="language-text">T1 : R2 ← R1</code></pre>
<p>은</p>
<blockquote>
<p><strong>T1이라는 제어 시점에 R1의 데이터를 R2에 저장한다.</strong></p>
</blockquote>
<p>라는 뜻이다.</p>
<p>예를 들어 현재</p>
<pre><code class="language-text">R1의 Q = 1100</code></pre>
<p>이라고 하자.</p>
<p>R1의 출력이 R2의 입력 D에 연결되어 있다.</p>
<pre><code class="language-text">R1 Q ─────────→ R2 D</code></pre>
<p>T1이 활성화되면 R2의 Enable이 활성화된다.</p>
<p>하지만 바로 Q2가 바뀌는 것은 아니다.</p>
<p>R2가 상승 에지 Flip-Flop으로 구성되어 있다면</p>
<pre><code class="language-text">Clock ↑</code></pre>
<p>를 기다린다.</p>
<p>Clock이 올라가는 순간</p>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>이 이루어진다.</p>
<p>따라서</p>
<pre><code class="language-text">R2 = 1100</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="register-데이터-전송-타이밍-다이어그램-읽는-법-⭐⭐⭐">Register 데이터 전송 타이밍 다이어그램 읽는 법 ⭐⭐⭐</h1>
<p>이 부분에서 가장 중요한 개념은</p>
<blockquote>
<p><strong>Enable은 저장할 준비를 시키고, 실제 저장은 Clock Edge에서 일어난다.</strong></p>
</blockquote>
<p>는 것이다.</p>
<p>순서를 보면</p>
<pre><code class="language-text">① R1에 데이터가 존재한다.

② T1이 활성화된다.

③ T1에 의해 R2 Enable이 활성화된다.

④ R1의 Q가 R2의 D에 들어와 있다.

⑤ Clock ↑

⑥ R2가 D를 저장한다.

⑦ 약간의 Delay 후 Q2에서 새로운 값이 나타난다.</code></pre>
<p>이다.</p>
<p>그래서 타이밍 그래프에서 계속 <strong>Clock이 올라가는 순간을 보는 것</strong>이다.</p>
<hr>
<h1 id="3-register를-이용한-병렬-덧셈">3. Register를 이용한 병렬 덧셈</h1>
<p>이번에는 단순히 데이터를 복사하는 것이 아니라 계산한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">R2 ← R1 + R0</code></pre>
<p>을 수행한다고 하자.</p>
<p>이때 중간에 <strong>ALU</strong>가 사용된다.</p>
<hr>
<h1 id="alu란">ALU란?</h1>
<p>ALU는</p>
<pre><code class="language-text">Arithmetic Logic Unit</code></pre>
<p>의 약자다.</p>
<p>우리말로 <strong>산술 논리 연산 장치</strong>다.</p>
<p>다음과 같은 계산을 수행한다.</p>
<pre><code class="language-text">덧셈
뺄셈
AND
OR
XOR
...</code></pre>
<p>중요한 점은</p>
<blockquote>
<p><strong>ALU는 기본적으로 조합 논리회로다.</strong></p>
</blockquote>
<p>즉 ALU 자체가 결과를 기억하는 것이 아니다.</p>
<p>입력이 들어오면 입력에 따라 결과가 만들어진다.</p>
<hr>
<h1 id="register--alu">Register + ALU</h1>
<p>구조를 단순하게 보면</p>
<pre><code class="language-text">R1 ─────┐
        ↓
       ALU ─────→ R2
        ↑
R0 ─────┘</code></pre>
<p>이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">R1 = 0101
R0 = 0011</code></pre>
<p>이면 ALU에서</p>
<pre><code class="language-text">0101
+0011
-----
1000</code></pre>
<p>을 계산한다.</p>
<p>ALU는 조합 논리회로이기 때문에 입력이 들어오면 <strong>약간의 전파 지연 후</strong> 결과가 출력된다.</p>
<p>그 결과가 R2의 D 입력에 도착한다.</p>
<p>그 후</p>
<pre><code class="language-text">R2 Enable = 1
Clock ↑</code></pre>
<p>이면</p>
<pre><code class="language-text">R2 ← 1000</code></pre>
<p>이 된다.</p>
<p>즉 CPU 내부에서 매우 자주 등장하는 구조가</p>
<pre><code class="language-text">Register
   ↓
  ALU
   ↓
Register</code></pre>
<p>이다.</p>
<hr>
<h1 id="4-bus-전송-⭐-중요">4. Bus 전송 ⭐ 중요</h1>
<p>Register가 두 개뿐이라면 서로 직접 연결할 수 있다.</p>
<p>하지만 CPU 안에 Register가</p>
<pre><code class="language-text">R0
R1
R2
R3
R4
...</code></pre>
<p>처럼 많이 존재한다고 하자.</p>
<p>모든 Register를 서로 직접 연결하면 선이 너무 많아진다.</p>
<p>그래서 여러 Register가 하나의 공통 데이터 통로를 공유한다.</p>
<p>이 공통 데이터 선을 <strong>Bus</strong>라고 한다.</p>
<blockquote>
<p><strong>Bus = 여러 Register 또는 장치가 공유하는 데이터 전송 통로</strong></p>
</blockquote>
<hr>
<h1 id="왜-bus가-필요한가">왜 Bus가 필요한가?</h1>
<p>예를 들어</p>
<pre><code class="language-text">R0 → R1
R0 → R2
R0 → R3
R1 → R0
R1 → R2
R1 → R3
...</code></pre>
<p>를 전부 별도 선으로 연결하면 회로가 매우 복잡해진다.</p>
<p>대신</p>
<pre><code class="language-text">R0 ─┐
R1 ─┤
R2 ─┼──── BUS ────→ Register들
R3 ─┘</code></pre>
<p>처럼 공용 도로 하나를 만든다.</p>
<hr>
<h1 id="bus의-가장-중요한-규칙-⭐">Bus의 가장 중요한 규칙 ⭐</h1>
<blockquote>
<p><strong>한 순간에 하나의 Register만 Bus에 데이터를 출력해야 한다.</strong></p>
</blockquote>
<p>왜냐하면 R0가 Bus에</p>
<pre><code class="language-text">1010</code></pre>
<p>을 출력하고 있는데 동시에 R1이</p>
<pre><code class="language-text">0101</code></pre>
<p>을 출력하려고 하면 같은 선에서 충돌하기 때문이다.</p>
<p>따라서</p>
<pre><code class="language-text">한 순간 → 하나의 Source만 Bus 사용</code></pre>
<p>이 원칙이 중요하다.</p>
<hr>
<h1 id="high-impedance-상태">High Impedance 상태</h1>
<p>여기서 <strong>3-State Buffer</strong>가 사용된다.</p>
<p>일반적인 논리 신호는</p>
<pre><code class="language-text">0
1</code></pre>
<p>두 가지라고 배웠다.</p>
<p>그런데 3-State Buffer에는 세 번째 상태가 있다.</p>
<pre><code class="language-text">0
1
Z</code></pre>
<p>여기서 <code>Z</code>가</p>
<pre><code class="language-text">High Impedance</code></pre>
<p>상태다.</p>
<p>쉽게 말하면</p>
<blockquote>
<p><strong>&quot;나는 지금 이 선에서 빠져 있을게.&quot;</strong></p>
</blockquote>
<p>라는 상태다.</p>
<hr>
<h1 id="왜-high-z가-필요한가">왜 High-Z가 필요한가?</h1>
<p>예를 들어 R0, R1, R2가 같은 Bus에 연결되어 있다고 하자.</p>
<p>현재 R1의 값을 Bus에 출력하고 싶다.</p>
<p>그러면</p>
<pre><code class="language-text">R0 → Z
R1 → 1010
R2 → Z</code></pre>
<p>로 만든다.</p>
<p>그러면 Bus에는</p>
<pre><code class="language-text">1010</code></pre>
<p>만 나타난다.</p>
<p>R0와 R2도 물리적으로 Bus에 연결되어 있지만 High-Z 상태이므로 Bus를 구동하지 않는다.</p>
<p>따라서 여러 장치가 하나의 Bus를 공유할 수 있다.</p>
<hr>
<h1 id="bus를-통한-register-전송">Bus를 통한 Register 전송</h1>
<p>예를 들어</p>
<pre><code class="language-text">R2 ← R0</code></pre>
<p>을 Bus를 통해 수행한다고 하자.</p>
<p>순서는 다음과 같다.</p>
<pre><code class="language-text">① R0의 3-State Buffer 활성화

② R0의 Q가 Bus에 나타남

③ 다른 Register의 출력은 High-Z

④ Bus의 값이 R2의 D에 도착

⑤ R2 Enable 활성화

⑥ Clock ↑

⑦ R2가 Bus의 값을 저장

⑧ R2의 Q에서 새로운 값 출력</code></pre>
<p>그림으로 보면</p>
<pre><code class="language-text">R0
 │
 ▼
3-State Buffer
 │
 ▼
 BUS ─────────→ D of R2
                   │
              Clock ↑
                   │
                   ▼
                  Q2</code></pre>
<p>이다.</p>
<hr>
<h1 id="5-bus-연산">5. Bus 연산</h1>
<p>이제 Bus와 ALU를 결합한다.</p>
<p>자료에는</p>
<pre><code class="language-text">ABus
BBus</code></pre>
<p>가 등장한다.</p>
<p>처음 보면 Bus가 왜 두 개나 있는지 헷갈린다.</p>
<p>이유는 간단하다.</p>
<blockquote>
<p><strong>ALU는 보통 두 개의 입력을 받아 연산해야 하기 때문이다.</strong></p>
</blockquote>
<p>예를 들어</p>
<pre><code class="language-text">R3 ← R1 + R2</code></pre>
<p>를 계산하고 싶다.</p>
<p>R1과 R2 두 값이 동시에 ALU에 들어가야 한다.</p>
<p>따라서</p>
<pre><code class="language-text">R1 → ABus
R2 → BBus</code></pre>
<p>로 보낸다.</p>
<p>그림으로 표현하면</p>
<pre><code class="language-text">R1 ─────→ ABus ─────┐
                     │
                     ▼
                   ┌─────┐
                   │ ALU │ ─────→ Result Bus
                   └─────┘
                     ▲
                     │
R2 ─────→ BBus ─────┘</code></pre>
<p>이다.</p>
<hr>
<h1 id="예시-3">예시</h1>
<p>현재</p>
<pre><code class="language-text">R1 = 0011
R2 = 0101</code></pre>
<p>이라고 하자.</p>
<p>다음 연산을 수행한다.</p>
<pre><code class="language-text">R3 ← R1 + R2</code></pre>
<p>먼저</p>
<pre><code class="language-text">ABus = 0011
BBus = 0101</code></pre>
<p>이 된다.</p>
<p>ALU가 덧셈을 수행한다.</p>
<pre><code class="language-text">0011
0101
----
1000</code></pre>
<p>따라서 Result Bus에는</p>
<pre><code class="language-text">1000</code></pre>
<p>이 나타난다.</p>
<p>R3 Enable이 활성화되어 있고 Clock이 상승하면</p>
<pre><code class="language-text">R3 ← 1000</code></pre>
<p>이 된다.</p>
<hr>
<h1 id="bus-연산-타이밍-다이어그램-읽는-방법-⭐⭐⭐">Bus 연산 타이밍 다이어그램 읽는 방법 ⭐⭐⭐</h1>
<p>복잡해 보여도 다음 순서만 보면 된다.</p>
<h2 id="1-abus의-source를-찾는다">1. ABus의 Source를 찾는다.</h2>
<pre><code class="language-text">어떤 Register가 ABus를 사용하고 있는가?</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">R1 → ABus</code></pre>
<p>라면</p>
<pre><code class="language-text">ABus = Q1</code></pre>
<p>이다.</p>
<hr>
<h2 id="2-bbus의-source를-찾는다">2. BBus의 Source를 찾는다.</h2>
<p>예를 들어</p>
<pre><code class="language-text">R2 → BBus</code></pre>
<p>라면</p>
<pre><code class="language-text">BBus = Q2</code></pre>
<p>이다.</p>
<hr>
<h2 id="3-alu-연산을-확인한다">3. ALU 연산을 확인한다.</h2>
<p>예를 들어 Add라면</p>
<pre><code class="language-text">Result = ABus + BBus</code></pre>
<p>이다.</p>
<hr>
<h2 id="4-destination-register의-enable을-확인한다">4. Destination Register의 Enable을 확인한다.</h2>
<p>예를 들어</p>
<pre><code class="language-text">R3 Enable = 1</code></pre>
<p>인지 확인한다.</p>
<hr>
<h2 id="5-clock-상승-에지를-확인한다">5. Clock 상승 에지를 확인한다.</h2>
<pre><code class="language-text">Clock ↑</code></pre>
<p>순간 Result Bus의 값을 R3가 저장한다.</p>
<p>따라서</p>
<pre><code class="language-text">R3 ← Result</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="결국-bus-연산은-이것이다">결국 Bus 연산은 이것이다</h1>
<pre><code class="language-text">Source Register A
       ↓
      ABus ─────┐
                │
                ▼
               ALU
                ▲
                │
      BBus ─────┘
       ↑
Source Register B

                ↓
           Result Bus
                ↓
      Destination Register
                ↓
             Clock ↑
                ↓
              저장</code></pre>
<p>이 구조를 머릿속에 넣으면 된다.</p>
<hr>
<h1 id="6-레지스터-전송-언어register-transfer-language">6. 레지스터 전송 언어(Register Transfer Language)</h1>
<p>복잡한 회로의 동작을 매번 그림으로 그리기 어렵다.</p>
<p>그래서 Register 사이의 데이터 이동과 연산을 간단한 기호로 표현한다.</p>
<hr>
<h2 id="register-이름">Register 이름</h2>
<p>예를 들어</p>
<pre><code class="language-text">R1
R2
PC
MAR
AC</code></pre>
<p>등은 Register를 의미한다.</p>
<hr>
<h2 id="화살표-←">화살표 <code>←</code></h2>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>은</p>
<blockquote>
<p>R1의 내용을 R2로 전송한다.</p>
</blockquote>
<p>라는 뜻이다.</p>
<p>프로그래밍의 대입과 비슷하다.</p>
<hr>
<h2 id="연산">연산</h2>
<pre><code class="language-text">R3 ← R1 + R2</code></pre>
<p>은</p>
<blockquote>
<p>R1과 R2를 더한 결과를 R3에 저장한다.</p>
</blockquote>
<p>라는 뜻이다.</p>
<hr>
<h2 id="콜론-">콜론 <code>:</code></h2>
<p>다음 표현을 보자.</p>
<pre><code class="language-text">T1 : R2 ← R1</code></pre>
<p>이는</p>
<blockquote>
<p><strong>T1이라는 제어 조건/시점에 R1의 값을 R2에 전달한다.</strong></p>
</blockquote>
<p>라는 뜻이다.</p>
<p>즉</p>
<pre><code class="language-text">조건 : 수행할 Microoperation</code></pre>
<p>형태다.</p>
<hr>
<h2 id="예시-4">예시</h2>
<pre><code class="language-text">T3 : AC ← AC + MAR</code></pre>
<p>은</p>
<blockquote>
<p>T3 시점에 AC와 MAR의 값을 더해서 그 결과를 AC에 저장한다.</p>
</blockquote>
<p>라는 뜻이다.</p>
<hr>
<h1 id="chapter-2-전체-흐름">Chapter 2 전체 흐름</h1>
<p>이 장의 내용은 사실 전부 하나로 연결되어 있다.</p>
<pre><code class="language-text">컴퓨터
  │
  ▼
모든 정보는 0과 1
  │
  ├──────────────────┐
  ▼                  ▼
계산해야 함          기억해야 함
  │                  │
  ▼                  ▼
논리 게이트         Flip-Flop
  │                  │
  ▼                  ▼
조합 논리회로        Register
  │                  │
  │                  ├── Shift Register
  │                  └── Counter
  │
  └─────────┬────────┘
            ▼
           Bus
            │
       ┌────┴────┐
       ▼         ▼
      ABus      BBus
       │         │
       └────┬────┘
            ▼
           ALU
            │
            ▼
       Result Bus
            │
            ▼
         Register</code></pre>
<hr>
<h1 id="cpu-내부-동작과-연결해서-이해하기">CPU 내부 동작과 연결해서 이해하기</h1>
<p>예를 들어 CPU가 다음 동작을 수행한다고 하자.</p>
<pre><code class="language-text">R3 ← R1 + R2</code></pre>
<p>이 짧은 표현 안에 지금까지 배운 내용이 거의 전부 들어있다.</p>
<h2 id="1단계">1단계</h2>
<p>R1은 여러 개의 Flip-Flop으로 이루어진 Register다.</p>
<pre><code class="language-text">R1 = 0011</code></pre>
<p>이라는 값을 기억하고 있다.</p>
<h2 id="2단계">2단계</h2>
<p>R2 역시 Register다.</p>
<pre><code class="language-text">R2 = 0101</code></pre>
<p>을 기억하고 있다.</p>
<h2 id="3단계">3단계</h2>
<p>제어 회로가 R1을 ABus에 출력하도록 한다.</p>
<pre><code class="language-text">ABus = 0011</code></pre>
<h2 id="4단계">4단계</h2>
<p>R2를 BBus에 출력한다.</p>
<pre><code class="language-text">BBus = 0101</code></pre>
<h2 id="5단계">5단계</h2>
<p>ALU가 두 값을 더한다.</p>
<pre><code class="language-text">0011 + 0101 = 1000</code></pre>
<h2 id="6단계">6단계</h2>
<p>ALU 결과가 Result Bus를 통해 R3의 D 입력으로 전달된다.</p>
<pre><code class="language-text">D3 = 1000</code></pre>
<h2 id="7단계">7단계</h2>
<p>R3의 Enable을 활성화한다.</p>
<h2 id="8단계">8단계</h2>
<p>Clock 상승 에지가 발생한다.</p>
<pre><code class="language-text">Clock ↑</code></pre>
<h2 id="9단계">9단계</h2>
<p>R3가 D 값을 저장한다.</p>
<pre><code class="language-text">R3 ← 1000</code></pre>
<p>이제 R3의 Q 출력은</p>
<pre><code class="language-text">1000</code></pre>
<p>이다.</p>
<hr>
<h1 id="왜-교수님이-계속-clock의-상승-에지를-강조하는가">왜 교수님이 계속 Clock의 상승 에지를 강조하는가?</h1>
<p>조합 논리회로와 순차 논리회로의 차이를 생각하면 된다.</p>
<p>ALU 같은 <strong>조합 논리회로</strong>는 입력이 바뀌면 출력도 바뀐다.</p>
<pre><code class="language-text">입력 변화
   ↓
ALU 계산
   ↓
출력 변화</code></pre>
<p>반면 Register는 아무 때나 값을 저장하면 안 된다.</p>
<pre><code class="language-text">D가 바뀜
→ 아직 저장 안 함

D가 또 바뀜
→ 아직 저장 안 함

Clock ↑
→ 바로 이 순간의 D를 저장</code></pre>
<p>즉 Clock을 이용해서</p>
<blockquote>
<p><strong>&quot;지금 계산 결과를 확정해서 저장해.&quot;</strong></p>
</blockquote>
<p>라는 시점을 정해주는 것이다.</p>
<p>그래서 타이밍 다이어그램 문제에서 Clock의 상승 에지가 가장 중요한 기준점이 된다.</p>
<hr>
<h1 id="⭐-시험-대비-핵심-정리">⭐ 시험 대비 핵심 정리</h1>
<h2 id="1-진법">1. 진법</h2>
<p>반드시 할 수 있어야 한다.</p>
<pre><code class="language-text">10진수 → 2진수

정수 : 2로 나누고 나머지를 아래에서 위로
소수 : 2를 곱하고 정수 부분을 위에서 아래로</code></pre>
<p>그리고</p>
<pre><code class="language-text">2진수 3bit ↔ 8진수 1자리
2진수 4bit ↔ 16진수 1자리</code></pre>
<p>를 기억한다.</p>
<hr>
<h2 id="2-flip-flop">2. Flip-Flop</h2>
<pre><code class="language-text">Flip-Flop = 1bit 저장</code></pre>
<h3 id="d-ff">D FF</h3>
<pre><code class="language-text">Clock ↑ → Q ← D</code></pre>
<h3 id="jk-ff">JK FF</h3>
<pre><code class="language-text">J K | 다음 Q
---------------
0 0 | 유지
0 1 | 0
1 0 | 1
1 1 | 반전</code></pre>
<h3 id="t-ff">T FF</h3>
<pre><code class="language-text">T=0 → 유지
T=1 → 반전</code></pre>
<hr>
<h2 id="3-latch와-flip-flop">3. Latch와 Flip-Flop</h2>
<pre><code class="language-text">Latch      → Level Sensitive
Flip-Flop  → Edge Sensitive</code></pre>
<hr>
<h2 id="4-타이밍-다이어그램">4. 타이밍 다이어그램</h2>
<p>무조건</p>
<pre><code class="language-text">① Clock 찾기
② 유효 Edge 찾기
③ 그 순간 입력 확인
④ 동작표 적용
⑤ Q 결정
⑥ 다음 Edge까지 Q 유지</code></pre>
<p>순서로 푼다.</p>
<hr>
<h2 id="5-register">5. Register</h2>
<pre><code class="language-text">Flip-Flop 여러 개
        ↓
     Register</code></pre>
<p>4bit Register라면 기본적으로 4개의 1bit 저장 요소가 필요하다.</p>
<hr>
<h2 id="6-register-제어-신호">6. Register 제어 신호</h2>
<pre><code class="language-text">/Clear=0
→ 즉시 초기화

/Clear=1, Enable=0
→ 유지

/Clear=1, Enable=1, Clock ↑
→ Q ← D</code></pre>
<hr>
<h2 id="7-shift-register">7. Shift Register</h2>
<pre><code class="language-text">Clock ↑
→ bit가 한 칸 이동</code></pre>
<p>직렬 입력을 받아 여러 Q를 통해 병렬 데이터로 사용할 수 있다.</p>
<hr>
<h2 id="8-counter">8. Counter</h2>
<pre><code class="language-text">0000
0001
0010
0011
...
1111
0000</code></pre>
<p>처럼 정해진 상태를 반복한다.</p>
<hr>
<h2 id="9-register-transfer">9. Register Transfer</h2>
<pre><code class="language-text">R2 ← R1</code></pre>
<p>은</p>
<pre><code class="language-text">R1의 데이터를 R2에 복사</code></pre>
<p>한다는 뜻이다.</p>
<hr>
<h2 id="10-bus">10. Bus</h2>
<pre><code class="language-text">Bus = 여러 Register가 공유하는 데이터 통로</code></pre>
<p>가장 중요한 규칙:</p>
<blockquote>
<p><strong>한 순간에는 하나의 Source만 Bus를 구동해야 한다.</strong></p>
</blockquote>
<hr>
<h2 id="11-high-impedance">11. High Impedance</h2>
<pre><code class="language-text">Z = High Impedance</code></pre>
<p>는</p>
<blockquote>
<p>Bus에 연결되어 있지만 현재는 Bus를 구동하지 않는 상태</p>
</blockquote>
<p>라고 이해한다.</p>
<hr>
<h2 id="12-abus와-bbus">12. ABus와 BBus</h2>
<p>ALU가 두 개의 입력을 필요로 하기 때문에 존재한다.</p>
<pre><code class="language-text">Register A → ABus ─┐
                   ▼
                  ALU → Result
                   ▲
Register B → BBus ─┘</code></pre>
<hr>
<h1 id="⭐-이-장에서-가장-중요한-사고방식">⭐ 이 장에서 가장 중요한 사고방식</h1>
<p>자료의 파형 그림이 나오면 전체를 한 번에 해석하려고 하지 않는다.</p>
<p>예를 들어 다음 신호들이 있다고 하자.</p>
<pre><code class="language-text">Clear
Enable
Clock
D
Q</code></pre>
<p>반드시 다음 순서로 본다.</p>
<pre><code class="language-text">1. Clear가 작동하고 있는가?

   YES → Q 초기화
   NO  → 다음으로

2. Enable이 활성화되어 있는가?

   NO  → Q 유지
   YES → 다음으로

3. Clock의 유효 Edge인가?

   NO  → Q 유지
   YES → D 확인

4. 그 순간 D가 무엇인가?

   Q ← D

5. 다음 유효 Edge까지 Q 유지</code></pre>
<p>JK Flip-Flop도 똑같다.</p>
<pre><code class="language-text">1. Clock ↑를 찾는다.
2. 그 순간 J와 K를 읽는다.
3. JK 표를 적용한다.
4. Q를 결정한다.
5. 다음 ↑까지 유지한다.</code></pre>
<p>Register 전송도 똑같다.</p>
<pre><code class="language-text">1. Source Register는 누구인가?
2. Bus에 어떤 값이 올라왔는가?
3. Destination Register는 누구인가?
4. Destination Enable이 켜져 있는가?
5. Clock ↑인가?
6. 그렇다면 Destination에 저장</code></pre>
<p>Bus 연산도 똑같다.</p>
<pre><code class="language-text">1. ABus에는 누가 올라왔는가?
2. BBus에는 누가 올라왔는가?
3. ALU가 무슨 연산을 하는가?
4. 결과는 무엇인가?
5. 어느 Register가 결과를 받을 것인가?
6. Clock ↑에서 저장</code></pre>
<p>결국 Chapter 2 후반부의 그림은 전부</p>
<p><strong>&quot;어떤 데이터가 어디에 있고 → 어떤 제어 신호가 활성화되고 → Clock의 어느 순간에 → 어디로 저장되는가?&quot;</strong></p>
<p>를 읽는 문제라고 생각하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터는 어떻게 프로그램을 실행할까? | 컴퓨터 구조부터 명령어와 폰 노이만 구조까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%93%A8%ED%84%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%EC%9D%84-%EC%8B%A4%ED%96%89%ED%95%A0%EA%B9%8C-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EB%AA%85%EB%A0%B9%EC%96%B4%EC%99%80-%ED%8F%B0-%EB%85%B8%EC%9D%B4%EB%A7%8C-%EA%B5%AC%EC%A1%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%93%A8%ED%84%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%EC%9D%84-%EC%8B%A4%ED%96%89%ED%95%A0%EA%B9%8C-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EB%AA%85%EB%A0%B9%EC%96%B4%EC%99%80-%ED%8F%B0-%EB%85%B8%EC%9D%B4%EB%A7%8C-%EA%B5%AC%EC%A1%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 25 Sep 2026 16:27:35 GMT</pubDate>
            <description><![CDATA[<h1 id="컴퓨터는-어떻게-프로그램을-실행할까--컴퓨터-구조부터-명령어와-폰-노이만-구조까지">컴퓨터는 어떻게 프로그램을 실행할까? | 컴퓨터 구조부터 명령어와 폰 노이만 구조까지</h1>
<p>컴퓨터 구조론 첫 수업을 들으면서 가장 먼저 든 생각은 단순했다.</p>
<blockquote>
<p><strong>그래서 컴퓨터 구조에서는 대체 뭘 배우는 걸까?</strong></p>
</blockquote>
<p>나는 지금까지 Java나 C++로 프로그램을 만들면서도 컴퓨터 내부에서 실제로 무슨 일이 일어나는지는 크게 신경 쓰지 않았다.</p>
<p>예를 들어 C++에서</p>
<pre><code class="language-cpp">int a = 10;
int b = 20;

cout &lt;&lt; a + b;</code></pre>
<p>라고 작성하면 당연히 <code>30</code>이 출력된다.</p>
<p>그런데 생각해보면 컴퓨터가 <code>int</code>라는 글자를 이해하는 것도 아니고, <code>+</code>를 보고 우리가 생각하는 덧셈의 의미를 이해하는 것도 아니다.</p>
<p>결국 우리가 작성한 프로그램은 컴퓨터가 실제로 실행할 수 있는 형태로 바뀌어야 한다.</p>
<p>그래서 이번에는 조금 더 아래로 내려가 보기로 했다.</p>
<pre><code class="language-text">내가 작성한 프로그램
        ↓
고급 프로그래밍 언어
        ↓
어셈블리어
        ↓
기계어
        ↓
CPU가 명령어 실행
        ↓
논리회로
        ↓
트랜지스터
        ↓
전기 신호</code></pre>
<p>컴퓨터 구조론은 이 중 <strong>소프트웨어와 실제 하드웨어가 만나는 지점</strong>을 이해하는 과목이라고 볼 수 있었다.</p>
<hr>
<h1 id="컴퓨터는-무엇일까">컴퓨터는 무엇일까?</h1>
<p>너무 당연한 질문 같지만 교재에서는 컴퓨터를 두 가지 관점으로 설명한다.</p>
<pre><code class="language-text">컴퓨터

= 계산을 수행하는 기계
+
프로그램을 실행하는 기계</code></pre>
<p>계산기와 컴퓨터의 차이를 생각해보면 조금 이해하기 쉽다.</p>
<p>계산기도</p>
<pre><code class="language-text">10 + 20 = 30</code></pre>
<p>과 같은 계산을 수행한다.</p>
<p>하지만 컴퓨터는 단순히 하나의 계산만 수행하는 것이 아니라 <strong>프로그램에 적혀 있는 명령들을 순서대로 실행</strong>한다.</p>
<p>그렇다면 프로그램은 무엇일까?</p>
<hr>
<h1 id="프로그램은-명령어의-모음이다">프로그램은 명령어의 모음이다</h1>
<p>프로그램은</p>
<blockquote>
<p><strong>명령어들이 의미 있는 순서대로 나열된 것</strong></p>
</blockquote>
<p>이다.</p>
<p>영어로 표현하면</p>
<pre><code class="language-text">sequence of instructions</code></pre>
<p>이다.</p>
<p>그리고 <strong>명령어(Instruction)</strong>는 프로그래머가 컴퓨터에게 실행하도록 지시할 수 있는 기본적인 작업 단위다.</p>
<p>예를 들어 굉장히 단순화하면</p>
<pre><code class="language-text">메모리에서 값을 가져와라.
두 값을 더해라.
결과를 저장해라.</code></pre>
<p>같은 작업들이 명령어가 될 수 있다.</p>
<p>우리가 보는 프로그램은</p>
<pre><code class="language-java">int result = a + b;</code></pre>
<p>한 줄이지만 컴퓨터 입장에서는 이를 더 작은 명령어들로 나누어 처리해야 한다.</p>
<pre><code class="language-text">a를 가져온다
      ↓
b를 가져온다
      ↓
두 값을 더한다
      ↓
result에 저장한다</code></pre>
<p>즉 프로그램은 결국 <strong>CPU가 실행할 수 있는 명령어의 흐름</strong>이라고 볼 수 있다.</p>
<hr>
<h1 id="컴퓨터를-한-층씩-내려가-보면">컴퓨터를 한 층씩 내려가 보면?</h1>
<p>교재에서는 컴퓨터를 계층적으로 바라본다.</p>
<p>처음에는</p>
<pre><code class="language-text">컴퓨터 구조인데 그냥 CPU랑 RAM 배우는 거 아닌가?</code></pre>
<p>라고 생각했는데 실제로는 여러 계층이 연결되어 있다.</p>
<p>대략 다음처럼 생각할 수 있다.</p>
<pre><code class="language-text">고급언어 프로그램
        ↓
어셈블리 프로그램
        ↓
프로그래머 모델
        ↓
컴퓨터 조직
        ↓
논리회로
        ↓
반도체 기술</code></pre>
<p>위쪽으로 갈수록 프로그래머가 보는 세계에 가깝고, 아래쪽으로 내려갈수록 실제 하드웨어에 가까워진다.</p>
<p>예를 들어 나는 Java에서</p>
<pre><code class="language-java">int a = 10;</code></pre>
<p>이라고 작성한다.</p>
<p>하지만 더 아래로 내려가면 CPU는 Java의 <code>int</code>를 직접 실행하지 않는다.</p>
<p>결국 CPU가 이해할 수 있는 명령어가 필요하고, 그 명령어를 실제로 수행하는 회로가 필요하며, 그 회로는 다시 트랜지스터로 만들어진다.</p>
<hr>
<h1 id="결국-컴퓨터의-가장-아래에는-스위치가-있다">결국 컴퓨터의 가장 아래에는 스위치가 있다</h1>
<p>반도체 기술에서는 <strong>트랜지스터(Transistor)</strong>라는 전기적으로 동작하는 스위칭 소자를 제공한다.</p>
<p>쉽게 생각하면 아주 작은 스위치다.</p>
<pre><code class="language-text">OFF
→ 전기 신호 없음

ON
→ 전기 신호 있음</code></pre>
<p>이렇게 두 가지 상태를 표현할 수 있다.</p>
<p>그런데 컴퓨터에서는 이를</p>
<pre><code class="language-text">OFF → 0
ON  → 1</code></pre>
<p>처럼 논리적인 값으로 바꿔 생각한다.</p>
<p>여기서 중요한 것은 실제 전압값 자체보다</p>
<pre><code class="language-text">이 전압을 0으로 볼 것인가?
1로 볼 것인가?</code></pre>
<p>라는 <strong>논리적인 해석</strong>이다.</p>
<p>트랜지스터를 조합하면 논리 게이트를 만들 수 있고,</p>
<pre><code class="language-text">Transistor
    ↓
Logic Gate
    ↓
Logic Circuit
    ↓
Register / ALU ...
    ↓
CPU</code></pre>
<p>처럼 점점 복잡한 컴퓨터를 만들 수 있다.</p>
<p>그래서 다음 Chapter에서 갑자기 <code>0</code>, <code>1</code>, AND, OR 같은 논리회로를 배우는 것이 아니었다.</p>
<p>컴퓨터가 실제로 동작하는 가장 아래쪽 원리를 이해하기 위한 과정이었다.</p>
<hr>
<h1 id="전자회로와-논리회로는-무엇이-다를까">전자회로와 논리회로는 무엇이 다를까?</h1>
<p>처음에는 둘 다 전기 회로인데 굳이 왜 나누는지 헷갈렸다.</p>
<p>전자회로에서는 실제 전기적 특성을 생각해야 한다.</p>
<pre><code class="language-text">전압
전류
저항
트랜지스터의 특성</code></pre>
<p>같은 것들이다.</p>
<p>하지만 컴퓨터 구조에서 매번</p>
<pre><code class="language-text">현재 전압이 몇 V이고,
전류가 몇 A이고...</code></pre>
<p>를 계산한다면 너무 복잡해진다.</p>
<p>그래서 이를 논리적인 세계로 추상화한다.</p>
<pre><code class="language-text">실제 전압
   ↓
0 또는 1
   ↓
논리 연산</code></pre>
<p>예를 들어 두 입력이 모두 <code>1</code>일 때만 출력이 <code>1</code>이 되는 회로가 있다면 전자회로의 세부 구조 대신</p>
<pre><code class="language-text">A B | Y
---------
0 0 | 0
0 1 | 0
1 0 | 0
1 1 | 1</code></pre>
<p>이라고 표현할 수 있다.</p>
<p>이게 바로 AND 게이트다.</p>
<p>즉 컴퓨터 구조에서는 실제 전자회로의 복잡함을 어느 정도 숨기고 <strong>0과 1의 논리 세계로 추상화해서 바라보는 것</strong>이다.</p>
<hr>
<h1 id="컴퓨터-구조는-어디까지-다룰까">컴퓨터 구조는 어디까지 다룰까?</h1>
<p>교재에서는 컴퓨터 구조의 영역을 크게 다음과 같이 나눈다.</p>
<pre><code class="language-text">1. 이진수 체계 및 논리회로
2. 컴퓨터 조직
3. 프로그래머 모델</code></pre>
<p>먼저 <strong>이진수 체계와 논리회로</strong>에서는</p>
<pre><code class="language-text">0과 1
부울 대수
논리 게이트
조합 논리회로
순차 논리회로</code></pre>
<p>등을 배운다.</p>
<p>그다음 <strong>컴퓨터 조직(Computer Organization)</strong>에서는</p>
<pre><code class="language-text">CPU
Memory
I/O Device
System Bus</code></pre>
<p>같은 실제 컴퓨터 구성 요소를 본다.</p>
<p>마지막으로 <strong>Programmer Model</strong>은 이 컴퓨터 구조 중에서도 프로그래머가 프로그램을 작성하기 위해 알아야 하는 부분이다.</p>
<p>예를 들면</p>
<pre><code class="language-text">Instruction Set
Register
Memory Organization</code></pre>
<p>등이 있다.</p>
<hr>
<h1 id="instruction-set은-무엇일까">Instruction Set은 무엇일까?</h1>
<p>CPU가 프로그램을 실행하려면 명령어를 이해할 수 있어야 한다.</p>
<p>그런데 CPU가 아무 명령어나 이해하는 것은 아니다.</p>
<p>CPU마다 실행할 수 있도록 정의된 명령어들의 집합이 있다.</p>
<p>이것이 <strong>Instruction Set</strong>, 즉 명령어 집합이다.</p>
<p>예를 들어 개념적으로</p>
<pre><code class="language-text">LOAD
ADD
SUB
STORE</code></pre>
<p>같은 명령어들이 있을 수 있다.</p>
<pre><code class="language-text">LOAD  → 값을 가져온다
ADD   → 더한다
SUB   → 뺀다
STORE → 저장한다</code></pre>
<p>그리고 이러한 명령어는 결국 CPU가 실제로 해석할 수 있는 이진수 코드로 표현된다.</p>
<p>이것이 <strong>기계어(Machine Instruction / Machine Code)</strong>다.</p>
<p>여기서 중요한 점은 Instruction Set이</p>
<blockquote>
<p><strong>하드웨어와 소프트웨어 사이의 인터페이스</strong></p>
</blockquote>
<p>역할을 한다는 것이다.</p>
<pre><code class="language-text">Software
   ↓
Instruction Set
   ↓
Hardware</code></pre>
<p>프로그래머나 컴파일러는 CPU가 어떤 명령을 지원하는지를 알아야 하고, CPU는 약속된 명령어를 실제 회로를 통해 수행한다.</p>
<hr>
<h1 id="기계어를-사람이-직접-작성하면-안-될까">기계어를 사람이 직접 작성하면 안 될까?</h1>
<p>가능은 하다.</p>
<p>문제는 사람이 읽기 너무 어렵다는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">0010000110000000</code></pre>
<p>같은 코드만 보고</p>
<pre><code class="language-text">아, 이건 어떤 값을 레지스터로 가져오는 명령이구나.</code></pre>
<p>라고 바로 알아보기는 어렵다.</p>
<p>그래서 사람이 조금 더 이해하기 쉬운 기호를 붙였다.</p>
<pre><code class="language-text">LOAD
ADD
MOV
SUB</code></pre>
<p>같은 것이다.</p>
<p>이것이 <strong>Assembly Language</strong>다.</p>
<p>예를 들어</p>
<pre><code class="language-text">ADD R1, R2</code></pre>
<p>처럼 작성하면 이진수 기계어보다 훨씬 이해하기 쉽다.</p>
<p>하지만 어셈블리어도 여전히 CPU 구조에 크게 의존한다.</p>
<p>그래서 더 높은 수준의 언어가 등장한다.</p>
<pre><code class="language-text">C
C++
Java</code></pre>
<p>같은 고급 프로그래밍 언어다.</p>
<hr>
<h1 id="우리가-작성한-코드는-어떻게-cpu까지-내려갈까">우리가 작성한 코드는 어떻게 CPU까지 내려갈까?</h1>
<p>대략적인 흐름을 생각하면 다음과 같다.</p>
<pre><code class="language-text">C / Java 등의 고급 언어
          ↓
       Compiler
          ↓
Assembly / Machine Code
          ↓
         CPU</code></pre>
<p>교재에서는 고급 언어를 기계 독립적인 관점으로, 어셈블리 언어를 기계 의존적인 언어로 설명한다.</p>
<p>물론 실제 언어 구현과 실행 환경은 더 복잡하지만, 컴퓨터 구조의 관점에서는</p>
<pre><code class="language-text">사람이 이해하기 쉬운 표현
        ↓
컴퓨터가 이해할 수 있는 표현</code></pre>
<p>으로 내려간다고 이해하면 된다.</p>
<p>컴파일러 수업에서 배웠던 내용이 여기서 다시 연결됐다.</p>
<p>컴파일러에서는</p>
<pre><code class="language-text">소스 코드가 어떻게 기계어로 번역되는가?</code></pre>
<p>를 중심으로 봤다면,</p>
<p>컴퓨터 구조에서는 한 단계 더 내려가</p>
<pre><code class="language-text">그 기계어를 하드웨어는 어떻게 실행하는가?</code></pre>
<p>를 보게 되는 셈이다.</p>
<hr>
<h1 id="프로그램은-어디에-저장될까">프로그램은 어디에 저장될까?</h1>
<p>여기서 <strong>폰 노이만 구조(Von Neumann Architecture)</strong>가 등장한다.</p>
<p>핵심 아이디어는 생각보다 간단하다.</p>
<blockquote>
<p><strong>프로그램과 데이터를 기억장치에 함께 저장한다.</strong></p>
</blockquote>
<p>그리고 CPU가 기억장치에서 명령어를 하나씩 가져와 실행한다.</p>
<pre><code class="language-text">Memory

[Instruction]
[Instruction]
[Data]
[Data]

      ↓

     CPU</code></pre>
<p>이를 <strong>Stored Program Concept</strong>, 즉 프로그램 내장 구조라고 한다.</p>
<p>실행 과정도 단순하게 생각하면</p>
<pre><code class="language-text">Memory에서 명령어 가져오기
          ↓
명령어 해석하기
          ↓
명령어 실행하기
          ↓
다음 명령어 가져오기
          ↓
...</code></pre>
<p>가 된다.</p>
<p>나중에 배우게 될</p>
<pre><code class="language-text">Fetch
Decode
Execute</code></pre>
<p>가 여기에서 이어진다.</p>
<hr>
<h1 id="컴퓨터는-크게-세-부분으로-볼-수-있다">컴퓨터는 크게 세 부분으로 볼 수 있다</h1>
<p>교재에서는 컴퓨터의 기본 구성 요소를 다음과 같이 나눈다.</p>
<pre><code class="language-text">          ┌───────────┐
          │    CPU    │
          └───────────┘
                ↕
          System Bus
        ↙             ↘
   ┌────────┐      ┌──────────┐
   │ Memory │      │ I/O      │
   └────────┘      │ Device   │
                   └──────────┘</code></pre>
<p>즉</p>
<pre><code class="language-text">CPU
Memory
I/O Device</code></pre>
<p>이다.</p>
<h3 id="cpu">CPU</h3>
<p>프로그램의 명령어를 실제로 실행한다.</p>
<h3 id="memory">Memory</h3>
<p>프로그램과 데이터를 저장한다.</p>
<h3 id="io-device">I/O Device</h3>
<p>컴퓨터와 외부 세계를 연결한다.</p>
<pre><code class="language-text">Keyboard
Mouse
Monitor
Disk
Network</code></pre>
<p>등이 여기에 해당한다.</p>
<p>그리고 이 구성 요소들이 데이터를 주고받는 통로가 <strong>System Bus</strong>다.</p>
<hr>
<h1 id="system-bus는-컴퓨터-안의-도로와-비슷하다">System Bus는 컴퓨터 안의 도로와 비슷하다</h1>
<p>CPU, Memory, I/O Device가 각각 존재하기만 해서는 아무 일도 할 수 없다.</p>
<p>서로 정보를 주고받아야 한다.</p>
<p>그래서 <strong>Bus</strong>가 필요하다.</p>
<p>나는 버스를 컴퓨터 내부의 도로처럼 생각하니 이해하기 편했다.</p>
<pre><code class="language-text">CPU ───────── Memory
 │
 │ System Bus
 │
 └─────────── I/O</code></pre>
<p>CPU가</p>
<pre><code class="language-text">메모리의 어떤 위치에 있는 데이터를 가져와라.</code></pre>
<p>라고 요청하면 주소와 데이터, 제어 정보가 버스를 통해 이동한다.</p>
<p>뒤에서 구체적으로</p>
<pre><code class="language-text">Address Bus
Data Bus
Control Bus</code></pre>
<p>로 나누어 배우게 된다.</p>
<hr>
<h1 id="컴퓨터는-어떻게-발전했을까">컴퓨터는 어떻게 발전했을까?</h1>
<p>컴퓨터의 발전 과정도 결국 <strong>어떤 소자를 사용했는가</strong>와 밀접하게 연결되어 있다.</p>
<p>초기에는 기계적인 장치를 사용했고 이후에는</p>
<pre><code class="language-text">진공관
  ↓
트랜지스터
  ↓
IC
  ↓
LSI
  ↓
더 높은 집적도의 반도체</code></pre>
<p>로 발전했다.</p>
<p>하나의 칩에 더 많은 트랜지스터를 넣을 수 있게 되면서 컴퓨터의 성능도 빠르게 증가했다.</p>
<p>여기에서 <strong>무어의 법칙(Moore&#39;s Law)</strong>도 등장한다.</p>
<p>핵심은 반도체 칩에 집적되는 트랜지스터의 수가 시간이 흐르면서 빠르게 증가해왔다는 관찰이다.</p>
<p>결국 현대의 작은 CPU 하나에 엄청나게 많은 트랜지스터를 넣을 수 있게 되었고, 우리가 사용하는 복잡한 프로그램도 빠르게 처리할 수 있게 된 것이다.</p>
<hr>
<h1 id="그런데-왜-컴퓨터는-0과-1을-사용할까">그런데 왜 컴퓨터는 0과 1을 사용할까?</h1>
<p>Chapter 1을 보고 나면 자연스럽게 다음 질문이 생긴다.</p>
<blockquote>
<p><strong>왜 하필 컴퓨터는 0과 1을 사용할까?</strong></p>
</blockquote>
<p>컴퓨터의 가장 아래에는 전기적으로 동작하는 스위칭 소자가 있다.</p>
<p>스위치는</p>
<pre><code class="language-text">ON
OFF</code></pre>
<p>처럼 두 상태를 구분하기 쉽다.</p>
<p>이를 논리적인 값으로 바꾸면</p>
<pre><code class="language-text">ON  → 1
OFF → 0</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>그래서 컴퓨터에서는 이진수가 자연스럽게 등장한다.</p>
<p>하지만 여기서 또 문제가 생긴다.</p>
<p>우리는 평소</p>
<pre><code class="language-text">10
27
1000</code></pre>
<p>처럼 10진수를 사용한다.</p>
<p>그런데 컴퓨터는 내부에서</p>
<pre><code class="language-text">1010
11011
1111101000</code></pre>
<p>같은 2진수를 사용한다.</p>
<p>그렇다면</p>
<pre><code class="language-text">27은 왜 11011이 되는가?

2진수와 16진수는 어떤 관계가 있는가?

컴퓨터는 1111을 보면 정말 숫자 15라고 생각하는가?

문자 A는 컴퓨터 안에서 어떻게 저장되는가?</code></pre>
<p>라는 질문이 생긴다.</p>
<p>그리고 이 질문들이 그대로 다음 Chapter의 내용으로 이어진다.</p>
<blockquote>
<p><strong>Chapter 2에서는 컴퓨터가 정보를 표현하는 가장 기본적인 방법인 수와 코드, 그리고 그 0과 1을 실제로 처리하고 기억하는 논리회로를 알아보려고 한다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래밍 언어도 수학적으로 정의할 수 있을까? | 형식 언어부터 문법과 Chomsky 계층까지]]></title>
            <link>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%96%B8%EC%96%B4%EB%8F%84-%EC%88%98%ED%95%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%A0%95%EC%9D%98%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-%ED%98%95%EC%8B%9D-%EC%96%B8%EC%96%B4%EB%B6%80%ED%84%B0-%EB%AC%B8%EB%B2%95%EA%B3%BC-Chomsky-%EA%B3%84%EC%B8%B5%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%96%B8%EC%96%B4%EB%8F%84-%EC%88%98%ED%95%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EC%A0%95%EC%9D%98%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-%ED%98%95%EC%8B%9D-%EC%96%B8%EC%96%B4%EB%B6%80%ED%84%B0-%EB%AC%B8%EB%B2%95%EA%B3%BC-Chomsky-%EA%B3%84%EC%B8%B5%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 25 Sep 2026 14:22:38 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래밍-언어도-수학적으로-정의할-수-있을까--형식-언어부터-문법과-chomsky-계층까지">프로그래밍 언어도 수학적으로 정의할 수 있을까? | 형식 언어부터 문법과 Chomsky 계층까지</h1>
<p>지난 Chapter에서는 컴파일러가 소스 프로그램을 한 번에 기계어로 바꾸는 것이 아니라 여러 단계를 거쳐 번역한다는 것을 알아봤다.</p>
<p>특히 앞부분에서는 다음과 같은 과정이 이루어진다.</p>
<pre><code class="language-text">Source Program
      ↓
Scanner
      ↓
Token
      ↓
Parser
      ↓
문법 구조 확인</code></pre>
<p>Scanner는 소스 프로그램을 읽으면서 Token을 찾아내고, Parser는 그렇게 만들어진 Token들이 프로그래밍 언어의 문법에 맞게 작성되었는지를 확인한다.</p>
<p>그런데 여기서 생각해보니 조금 이상했다.</p>
<blockquote>
<p><strong>컴퓨터는 도대체 무엇을 보고 &quot;이 프로그램은 문법에 맞다&quot;고 판단할까?</strong></p>
</blockquote>
<p>나는 Java 코드를 보면 대충 알 수 있다.</p>
<pre><code class="language-java">int number = 10;</code></pre>
<p>이건 맞는 것 같고,</p>
<pre><code class="language-java">int = number 10;</code></pre>
<p>이건 뭔가 이상하다.</p>
<p>사람은 Java 문법을 배웠기 때문에 자연스럽게 판단할 수 있지만 컴퓨터에게</p>
<pre><code class="language-text">이건 딱 봐도 이상하잖아.</code></pre>
<p>라고 할 수는 없다.</p>
<p>결국 컴파일러가 프로그래밍 언어를 처리하려면 <strong>언어 자체를 명확하게 정의할 방법</strong>이 필요하다.</p>
<p>이번 Chapter에서 배우는 <strong>형식 언어(Formal Language)</strong>가 바로 그 출발점이었다.</p>
<hr>
<h1 id="언어를-정의하려면-먼저-기호부터-정의해야-한다">언어를 정의하려면 먼저 기호부터 정의해야 한다</h1>
<p>형식 언어에서 가장 먼저 등장하는 것은 <strong>알파벳(Alphabet)</strong>이다.</p>
<p>처음에는 영어의 <code>a, b, c ...</code>를 말하는 줄 알았는데 여기서 알파벳의 의미는 조금 다르다.</p>
<blockquote>
<p><strong>알파벳은 기호(Symbol)들의 유한 집합이다.</strong></p>
</blockquote>
<p>보통 <code>T</code>와 같은 기호로 표현한다.</p>
<p>예를 들어 다음과 같은 것들이 알파벳이 될 수 있다.</p>
<pre><code class="language-text">T₁ = {a, b, c, ..., z}

T₂ = {0, 1}

T₃ = {auto, break, case, ..., while}</code></pre>
<p>즉 꼭 문자 하나만 알파벳의 원소가 되는 것은 아니다.</p>
<p>프로그래밍 언어의 예약어처럼</p>
<pre><code class="language-text">auto
break
case
while</code></pre>
<p>같은 것도 하나의 기호로 생각할 수 있다.</p>
<p>이 기호들을 이용해서 <strong>문자열(String)</strong>을 만들 수 있다.</p>
<hr>
<h1 id="문자열은-알파벳에-있는-기호를-나열한-것이다">문자열은 알파벳에 있는 기호를 나열한 것이다</h1>
<p>문자열은 어떤 알파벳에 속하는 기호들을 유한하게 나열한 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">T = {a, b}</code></pre>
<p>라면</p>
<pre><code class="language-text">a
b
ab
ba
aab
abba</code></pre>
<p>같은 문자열을 만들 수 있다.</p>
<p>문자열 <code>w</code>의 길이는 보통</p>
<pre><code class="language-text">|w|</code></pre>
<p>로 나타낸다.</p>
<p>예를 들어</p>
<pre><code class="language-text">w = abba

|w| = 4</code></pre>
<p>가 된다.</p>
<p>여기서 특별한 문자열이 하나 등장한다.</p>
<p>바로 <strong>공문자열(Empty String)</strong>이다.</p>
<pre><code class="language-text">ε</code></pre>
<p>또는 교재에서는 <code>λ(lambda)</code>로도 표현한다.</p>
<p>공문자열은 아무 기호도 가지지 않기 때문에 길이가 0이다.</p>
<pre><code class="language-text">|ε| = 0</code></pre>
<p>처음에는</p>
<pre><code class="language-text">아무것도 없는데 이것도 문자열이라고?</code></pre>
<p>싶었는데 뒤에서 문자열과 언어의 연산을 정의할 때 계속 필요해진다.</p>
<p>숫자에서 <code>0</code>이 필요한 것처럼 문자열에서도 <strong>길이가 0인 문자열</strong>을 하나의 대상으로 정의해두는 것이다.</p>
<hr>
<h1 id="문자열을-서로-붙일-수도-있다">문자열을 서로 붙일 수도 있다</h1>
<p>문자열에는 <strong>접합(Concatenation)</strong>이라는 연산이 있다.</p>
<p>두 문자열을 순서대로 이어 붙이는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">u = ab
v = ba</code></pre>
<p>라면</p>
<pre><code class="language-text">uv = abba</code></pre>
<p>가 된다.</p>
<p>반대로 순서를 바꾸면</p>
<pre><code class="language-text">vu = baab</code></pre>
<p>가 된다.</p>
<p>따라서 일반적으로</p>
<pre><code class="language-text">uv ≠ vu</code></pre>
<p>이다.</p>
<p>숫자의 덧셈처럼 순서를 바꿔도 같은 연산이 아니었다.</p>
<p>문자열에서는 <strong>어떤 순서로 연결했는지 자체가 의미를 가지기 때문</strong>이다.</p>
<hr>
<h1 id="prefix와-suffix는-무엇일까">Prefix와 Suffix는 무엇일까?</h1>
<p>문자열의 일부를 이야기할 때 <strong>Prefix</strong>와 <strong>Suffix</strong>라는 개념도 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">w = abc</code></pre>
<p>라고 해보자.</p>
<p><code>w = uv</code>를 만족한다면 <code>u</code>는 <code>w</code>의 <strong>Prefix</strong>, <code>v</code>는 <code>w</code>의 <strong>Suffix</strong>가 된다.</p>
<p>예를 들어</p>
<pre><code class="language-text">w = abc

u = ab
v = c</code></pre>
<p>라면</p>
<pre><code class="language-text">uv = abc</code></pre>
<p>이므로</p>
<pre><code class="language-text">ab = prefix
c  = suffix</code></pre>
<p>라고 할 수 있다.</p>
<p>문자열을 단순히 하나의 덩어리로 보는 것이 아니라 앞부분과 뒷부분을 나눠서 생각할 수 있는 것이다.</p>
<hr>
<h1 id="같은-문자열을-여러-번-반복하면-어떻게-표현할까">같은 문자열을 여러 번 반복하면 어떻게 표현할까?</h1>
<p>문자열을 반복해서 연결하는 것도 표현할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">w = ab</code></pre>
<p>라면</p>
<pre><code class="language-text">w⁰ = ε
w¹ = ab
w² = abab
w³ = ababab</code></pre>
<p>가 된다.</p>
<p>일반적으로</p>
<pre><code class="language-text">wⁿ</code></pre>
<p>은 문자열 <code>w</code>를 <code>n</code>번 접합한 것을 의미한다.</p>
<p>이런 표기법을 이용하면 이후에 언어를 훨씬 간단하게 표현할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">a
aa
aaa
aaaa
...</code></pre>
<p>를 하나씩 모두 적지 않고</p>
<pre><code class="language-text">aⁿ</code></pre>
<p>처럼 표현할 수 있다.</p>
<hr>
<h1 id="알파벳으로-만들-수-있는-모든-문자열을-모으면">알파벳으로 만들 수 있는 모든 문자열을 모으면?</h1>
<p>이제 알파벳 <code>T</code>를 가지고 만들 수 있는 문자열 전체를 생각해볼 수 있다.</p>
<p>먼저</p>
<pre><code class="language-text">T⁰ = {ε}</code></pre>
<p>이다.</p>
<p><code>T¹</code>은 알파벳의 기호 하나로 만들어지는 문자열이다.</p>
<pre><code class="language-text">T = {0, 1}

T¹ = {0, 1}</code></pre>
<p><code>T²</code>는 두 개의 기호로 만들 수 있는 모든 문자열이다.</p>
<pre><code class="language-text">T² = {
    00,
    01,
    10,
    11
}</code></pre>
<p><code>T³</code>라면</p>
<pre><code class="language-text">000
001
010
011
100
101
110
111</code></pre>
<p>처럼 길이가 3인 모든 문자열이 된다.</p>
<p>그리고 이들을 전부 합친 것을</p>
<pre><code class="language-text">T*</code></pre>
<p>로 나타낸다.</p>
<pre><code class="language-text">T* = T⁰ ∪ T¹ ∪ T² ∪ T³ ∪ ...</code></pre>
<p>이것을 <strong>Kleene Closure</strong>라고 한다.</p>
<p>따라서</p>
<pre><code class="language-text">T = {0, 1}</code></pre>
<p>이라면</p>
<pre><code class="language-text">T* = {
    ε,
    0, 1,
    00, 01, 10, 11,
    000, 001, 010, ...
}</code></pre>
<p>가 된다.</p>
<p>즉 알파벳 <code>T</code>를 이용해 만들 수 있는 <strong>모든 유한 길이의 문자열 집합</strong>이다.</p>
<hr>
<h1 id="ε을-제외하고-싶다면-t를-사용한다">ε을 제외하고 싶다면 T+를 사용한다</h1>
<p><code>T*</code>에는 <code>ε</code>도 포함된다.</p>
<p>그런데 길이가 1 이상인 문자열만 필요할 수도 있다.</p>
<p>이때는</p>
<pre><code class="language-text">T+</code></pre>
<p>를 사용한다.</p>
<pre><code class="language-text">T+ = T¹ ∪ T² ∪ T³ ∪ ...</code></pre>
<p>따라서 관계를 보면</p>
<pre><code class="language-text">T+ = T* - {ε}</code></pre>
<p>라고 할 수 있다.</p>
<p>정리하면</p>
<pre><code class="language-text">T*
→ 길이 0 이상
→ ε 포함

T+
→ 길이 1 이상
→ ε 제외</code></pre>
<p>이다.</p>
<p><code>*</code>와 <code>+</code>는 이후 정규 표현에서도 다시 만나게 되기 때문에 여기서 의미를 확실하게 알아두는 것이 중요해 보였다.</p>
<hr>
<h1 id="이제-언어를-정의할-수-있다">이제 언어를 정의할 수 있다</h1>
<p>알파벳과 문자열을 정의했으니 이제 <strong>언어(Language)</strong>를 정의할 수 있다.</p>
<p>형식 언어에서 언어는</p>
<blockquote>
<p><strong>알파벳 <code>T</code>에 대한 <code>T*</code>의 부분집합이다.</strong></p>
</blockquote>
<p>즉</p>
<pre><code class="language-text">L ⊆ T*</code></pre>
<p>이다.</p>
<p>처음에는 언어라고 하면 Java, C++, Python 같은 것부터 떠올랐다.</p>
<p>그런데 형식 언어에서는 훨씬 수학적으로 바라본다.</p>
<p>예를 들어</p>
<pre><code class="language-text">T = {a, b}</code></pre>
<p>라고 해보자.</p>
<p><code>T*</code>에는</p>
<pre><code class="language-text">ε
a
b
aa
ab
ba
bb
aaa
aab
...</code></pre>
<p>처럼 만들 수 있는 모든 문자열이 들어 있다.</p>
<p>그중에서 특정한 조건을 만족하는 문자열만 골라</p>
<pre><code class="language-text">L = {a, aa, aaa, aaaa, ...}</code></pre>
<p>라고 정의했다면 이것도 하나의 언어다.</p>
<p>즉</p>
<pre><code class="language-text">모든 가능한 문자열 T*
          ↓
특정 규칙을 만족하는 문자열 선택
          ↓
Language L</code></pre>
<p>이라고 생각할 수 있다.</p>
<hr>
<h1 id="유한-언어와-무한-언어">유한 언어와 무한 언어</h1>
<p>언어에 포함되는 문자열의 개수에 따라 <strong>유한 언어(Finite Language)</strong>와 <strong>무한 언어(Infinite Language)</strong>로도 나눌 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">L = {a, ab, ba}</code></pre>
<p>처럼 원소의 개수가 정해져 있다면 유한 언어다.</p>
<p>반면</p>
<pre><code class="language-text">L = {aⁿ | n ≥ 1}</code></pre>
<p>이라면</p>
<pre><code class="language-text">a
aa
aaa
aaaa
...</code></pre>
<p>가 끝없이 만들어지므로 무한 언어다.</p>
<p>프로그래밍 언어를 생각해보면 작성할 수 있는 프로그램의 길이를 하나로 제한하지 않기 때문에 사실상 무한한 문자열 집합을 다루게 된다.</p>
<hr>
<h1 id="언어끼리도-연산할-수-있다">언어끼리도 연산할 수 있다</h1>
<p>문자열을 접합할 수 있었던 것처럼 <strong>언어끼리도 곱(Product)</strong>을 정의할 수 있다.</p>
<p>두 언어 <code>L₁</code>, <code>L₂</code>가 있다면</p>
<pre><code class="language-text">L₁L₂ = {uv | u ∈ L₁, v ∈ L₂}</code></pre>
<p>로 정의한다.</p>
<p>즉 <code>L₁</code>의 문자열 하나와 <code>L₂</code>의 문자열 하나를 가져와 접합해서 새로운 언어를 만드는 것이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">L₁ = {a, b}
L₂ = {c, d}</code></pre>
<p>라면</p>
<pre><code class="language-text">L₁L₂ = {
    ac,
    ad,
    bc,
    bd
}</code></pre>
<p>가 된다.</p>
<p>처음에는 문자열에만 접합이 있는 줄 알았는데 결국 언어도 <strong>문자열의 집합</strong>이기 때문에 집합 안의 문자열들을 이용해 새로운 언어를 만들 수 있었다.</p>
<hr>
<h1 id="언어도-거듭제곱할-수-있다">언어도 거듭제곱할 수 있다</h1>
<p>문자열에서 <code>wⁿ</code>을 정의했던 것처럼 언어에서도 거듭제곱을 정의할 수 있다.</p>
<pre><code class="language-text">L⁰ = {ε}</code></pre>
<p>이고,</p>
<pre><code class="language-text">Lⁿ = LLⁿ⁻¹</code></pre>
<p>로 정의할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">L = {a, b}</code></pre>
<p>라면</p>
<pre><code class="language-text">L² = {
    aa,
    ab,
    ba,
    bb
}</code></pre>
<p>가 된다.</p>
<p>그리고 이를 이용하면 언어에 대해서도 <code>*</code>와 <code>+</code>를 정의할 수 있다.</p>
<hr>
<h1 id="언어의-kleene-closure와-positive-closure">언어의 Kleene Closure와 Positive Closure</h1>
<p>언어 <code>L</code>에 대해</p>
<pre><code class="language-text">L* = L⁰ ∪ L¹ ∪ L² ∪ ...</code></pre>
<p>를 <strong>Kleene Closure</strong>라고 한다.</p>
<p>반면</p>
<pre><code class="language-text">L+ = L¹ ∪ L² ∪ L³ ∪ ...</code></pre>
<p>를 생각할 수 있다.</p>
<p>따라서 역시</p>
<pre><code class="language-text">L* = L+ ∪ {ε}</code></pre>
<p>관계가 성립한다.</p>
<p>문자열에서 봤던 <code>*</code>, <code>+</code>가 언어에서도 거의 같은 의미로 확장된 것이다.</p>
<p>여기까지 오니 처음에는 따로따로 보였던</p>
<pre><code class="language-text">Alphabet
String
Language</code></pre>
<p>가 사실 계속 같은 방식으로 확장되고 있다는 느낌이 들었다.</p>
<pre><code class="language-text">Alphabet
   ↓
Symbol로 String 생성
   ↓
String들의 집합 = Language
   ↓
Language에도 접합과 Closure 적용</code></pre>
<hr>
<h1 id="그런데-언어의-문자열을-전부-적어둘-수는-없다">그런데 언어의 문자열을 전부 적어둘 수는 없다</h1>
<p>여기까지 언어를</p>
<pre><code class="language-text">L = { ... }</code></pre>
<p>형태로 정의했다.</p>
<p>그런데 무한 언어라면 문제가 생긴다.</p>
<p>예를 들어</p>
<pre><code class="language-text">L = {
    a,
    aa,
    aaa,
    aaaa,
    ...
}</code></pre>
<p>처럼 생략해서 표현할 수는 있지만 컴파일러가 처리할 프로그래밍 언어를 이런 식으로 전부 나열할 수는 없다.</p>
<p>Java 프로그램으로 만들 수 있는 모든 문자열을 하나씩 적는다는 것은 불가능하다.</p>
<p>그래서 필요한 것이 <strong>문법(Grammar)</strong>이다.</p>
<hr>
<h1 id="문법은-언어를-생성하는-규칙이다">문법은 언어를 생성하는 규칙이다</h1>
<p>교재에서는 언어를 크게 두 가지 방법으로 정의할 수 있다고 설명한다.</p>
<p>하나는 지금까지처럼</p>
<pre><code class="language-text">어떤 문자열이 언어에 포함되는지</code></pre>
<p>를 직접 정의하는 방법이다.</p>
<p>다른 하나는</p>
<pre><code class="language-text">어떤 규칙을 적용하면 그 문자열을 만들 수 있는지</code></pre>
<p>를 정의하는 것이다.</p>
<p>후자가 바로 <strong>문법(Grammar)</strong>이다.</p>
<p>즉</p>
<pre><code class="language-text">Grammar
   ↓
규칙 적용
   ↓
String 생성
   ↓
생성 가능한 String들의 집합
   ↓
Language</code></pre>
<p>가 된다.</p>
<hr>
<h1 id="문법은-4개의-요소로-구성된다">문법은 4개의 요소로 구성된다</h1>
<p>문법 <code>G</code>는 다음과 같이 표현한다.</p>
<pre><code class="language-text">G = (Vₙ, Vₜ, P, S)</code></pre>
<p>처음 보면 갑자기 기호가 많이 등장해서 어려워 보이는데 하나씩 보면 의미가 명확하다.</p>
<pre><code class="language-text">Vₙ : Nonterminal Symbol의 유한 집합

Vₜ : Terminal Symbol의 유한 집합

P  : Production Rule의 유한 집합

S  : Start Symbol</code></pre>
<p>그리고</p>
<pre><code class="language-text">Vₙ ∩ Vₜ = ∅</code></pre>
<p>이다.</p>
<p>즉 Nonterminal과 Terminal은 서로 겹치지 않는다.</p>
<p>또</p>
<pre><code class="language-text">V = Vₙ ∪ Vₜ</code></pre>
<p>를 문법의 <strong>Vocabulary</strong>라고 한다.</p>
<hr>
<h1 id="terminal과-nonterminal은-무엇이-다를까">Terminal과 Nonterminal은 무엇이 다를까?</h1>
<p><strong>Terminal Symbol</strong>은 최종적으로 만들어진 문자열에 남는 기호다.</p>
<p>반면 <strong>Nonterminal Symbol</strong>은 문자열을 만드는 과정에서 다른 기호로 바뀌는 기호다.</p>
<p>예를 들어 다음과 같은 생성 규칙이 있다고 해보자.</p>
<pre><code class="language-text">S → a
S → aS</code></pre>
<p>여기서</p>
<pre><code class="language-text">S</code></pre>
<p>는 Nonterminal이고</p>
<pre><code class="language-text">a</code></pre>
<p>는 Terminal이다.</p>
<p>실제로 생성해보면</p>
<pre><code class="language-text">S
⇒ aS
⇒ aaS
⇒ aaaS
⇒ aaaa</code></pre>
<p>처럼 마지막에는 <code>S</code>가 사라지고 Terminal인 <code>a</code>만 남는다.</p>
<p>그래서 나는 다음처럼 이해하는 것이 편했다.</p>
<pre><code class="language-text">Nonterminal
→ 아직 생성 과정에 있는 문법 기호

Terminal
→ 더 이상 변하지 않고 최종 문자열에 남는 기호</code></pre>
<hr>
<h1 id="생성-규칙-production-rule">생성 규칙 Production Rule</h1>
<p><code>P</code>는 <strong>생성 규칙(Production Rule)</strong>들의 유한 집합이다.</p>
<p>보통</p>
<pre><code class="language-text">α → β</code></pre>
<p>처럼 표현한다.</p>
<p>왼쪽에 있는 문자열을 오른쪽 문자열로 바꿀 수 있다는 의미다.</p>
<p>예를 들어</p>
<pre><code class="language-text">S → a
S → aS</code></pre>
<p>가 있다면 <code>S</code>를 <code>a</code>로 바꿀 수도 있고 <code>aS</code>로 바꿀 수도 있다.</p>
<p>여러 규칙의 왼쪽이 같다면</p>
<pre><code class="language-text">S → a
S → aS</code></pre>
<p>를</p>
<pre><code class="language-text">S → a | aS</code></pre>
<p>처럼 줄여서 표현할 수도 있다.</p>
<p>여기서 <code>|</code>는</p>
<pre><code class="language-text">또는</code></pre>
<p>의 의미다.</p>
<hr>
<h1 id="시작-기호가-필요한-이유">시작 기호가 필요한 이유</h1>
<p>문법에는 여러 Nonterminal이 있을 수 있다.</p>
<p>그러면 문자열을 어디에서부터 만들어야 할지 정해야 한다.</p>
<p>그래서 <strong>시작 기호(Start Symbol)</strong>가 존재한다.</p>
<p>보통</p>
<pre><code class="language-text">S</code></pre>
<p>를 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">G = ({S}, {a, b}, P, S)</code></pre>
<p>라면 마지막 <code>S</code>가 시작 기호다.</p>
<p>문자열 생성은 항상 이 시작 기호에서 출발한다.</p>
<hr>
<h1 id="생성-규칙을-적용하는-과정을-유도라고-한다">생성 규칙을 적용하는 과정을 유도라고 한다</h1>
<p>생성 규칙을 이용해서 하나의 문자열에서 다른 문자열을 만들어내는 과정을 <strong>유도(Derivation)</strong>라고 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">S → aS
S → b</code></pre>
<p>라는 문법이 있다고 하자.</p>
<p>그러면</p>
<pre><code class="language-text">S
⇒ aS
⇒ aaS
⇒ aab</code></pre>
<p>와 같이 문자열을 생성할 수 있다.</p>
<p>한 번의 유도를</p>
<pre><code class="language-text">⇒</code></pre>
<p>로 나타내고, 여러 번의 유도를 포함해서 표현할 때는</p>
<pre><code class="language-text">⇒*</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h1 id="문법이-생성하는-언어는-무엇일까">문법이 생성하는 언어는 무엇일까?</h1>
<p>이제 문법과 언어를 연결할 수 있다.</p>
<p>문법 <code>G</code>의 시작 기호 <code>S</code>에서 출발해서 생성 규칙을 여러 번 적용한 뒤 Terminal만으로 이루어진 문자열 <code>x</code>를 만들 수 있다면 <code>x</code>는 문법 <code>G</code>가 생성하는 언어에 포함된다.</p>
<p>이를</p>
<pre><code class="language-text">L(G)</code></pre>
<p>로 나타낸다.</p>
<p>즉</p>
<pre><code class="language-text">L(G) = {
    x |
    S ⇒* x,
    x ∈ Vₜ*
}</code></pre>
<p>라고 생각할 수 있다.</p>
<p>이 부분이 이번 Chapter에서 가장 중요한 연결 중 하나라고 느꼈다.</p>
<pre><code class="language-text">Grammar G
     ↓
Start Symbol S
     ↓
Production Rule 적용
     ↓
Terminal만 남은 String
     ↓
L(G)</code></pre>
<p>즉 <strong>문법은 언어를 생성한다.</strong></p>
<hr>
<h1 id="실제로-문법에서-언어를-만들어보자">실제로 문법에서 언어를 만들어보자</h1>
<p>예를 들어 다음 문법이 있다고 해보자.</p>
<pre><code class="language-text">G = ({S}, {a}, P, S)

P:
S → a
S → aS</code></pre>
<p><code>S</code>에서 시작해보면</p>
<pre><code class="language-text">S ⇒ a</code></pre>
<p>도 가능하다.</p>
<p>또</p>
<pre><code class="language-text">S
⇒ aS
⇒ aa</code></pre>
<p>도 가능하다.</p>
<p>한 번 더 반복하면</p>
<pre><code class="language-text">S
⇒ aS
⇒ aaS
⇒ aaa</code></pre>
<p>가 된다.</p>
<p>따라서 생성되는 언어는</p>
<pre><code class="language-text">L(G) = {
    a,
    aa,
    aaa,
    aaaa,
    ...
}</code></pre>
<p>이고 간단하게</p>
<pre><code class="language-text">L(G) = {aⁿ | n ≥ 1}</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>처음에는 생성 규칙만 보고 어떤 언어인지 바로 알아보기 어려웠는데, 직접 몇 번 유도해보니 패턴이 보였다.</p>
<pre><code class="language-text">문법 보기
   ↓
S에서 직접 몇 개 생성
   ↓
문자열의 공통 패턴 찾기
   ↓
L(G) 표현</code></pre>
<p>이런 식으로 접근하는 것이 편했다.</p>
<hr>
<h1 id="반대로-언어를-보고-문법을-만들-수도-있다">반대로 언어를 보고 문법을 만들 수도 있다</h1>
<p>앞에서는</p>
<pre><code class="language-text">Grammar → Language</code></pre>
<p>를 봤지만 반대로</p>
<pre><code class="language-text">Language → Grammar</code></pre>
<p>도 가능하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">L = {aⁿbⁿ | n ≥ 1}</code></pre>
<p>같은 언어를 생성하는 문법을 생각할 수 있다.</p>
<p>핵심은 <code>a</code>가 하나 늘어날 때 <code>b</code>도 하나 늘어나야 한다는 것이다.</p>
<p>그래서 재귀적인 생성 규칙을 이용하면 이런 패턴을 표현할 수 있다.</p>
<p>즉 문법 문제에서는 두 방향을 모두 생각해야 한다.</p>
<pre><code class="language-text">Grammar
   ↓
어떤 Language가 만들어지는가?

Language
   ↓
어떤 Grammar로 만들 수 있는가?</code></pre>
<hr>
<h1 id="같은-언어를-만드는-문법은-하나뿐일까">같은 언어를 만드는 문법은 하나뿐일까?</h1>
<p>여기서 또 재미있는 점이 나온다.</p>
<p><strong>하나의 언어를 생성하는 문법은 하나만 존재하는 것이 아니다.</strong></p>
<p>서로 다른 두 문법 <code>G₁</code>, <code>G₂</code>가 있다고 해보자.</p>
<p>만약</p>
<pre><code class="language-text">L(G₁) = L(G₂)</code></pre>
<p>라면 두 문법은 같은 언어를 생성한다.</p>
<p>이런 문법을 <strong>동등한 문법(Equivalent Grammar)</strong>이라고 할 수 있다.</p>
<p>즉 문법의 생성 규칙 자체가 똑같을 필요는 없다.</p>
<p>중요한 것은 최종적으로</p>
<pre><code class="language-text">어떤 문자열들의 집합을 생성하는가?</code></pre>
<p>이다.</p>
<p>이 개념은 뒤에서 문법을 변환할 때도 중요해질 것 같다.</p>
<p>문법의 형태는 바뀌었더라도 생성하는 언어가 같다면 본질적으로 같은 언어를 표현하고 있기 때문이다.</p>
<hr>
<h1 id="재귀적인-생성-규칙">재귀적인 생성 규칙</h1>
<p>프로그래밍을 하면서 재귀 함수는 익숙했는데 문법에서도 <strong>재귀(Recursive)</strong>가 등장한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">A → aA</code></pre>
<p>처럼 생성 규칙의 오른쪽에 다시 자기 자신인 <code>A</code>가 등장할 수 있다.</p>
<p>이런 형태를 이용하면 같은 패턴을 반복해서 생성할 수 있다.</p>
<pre><code class="language-text">A
⇒ aA
⇒ aaA
⇒ aaaA
⇒ ...</code></pre>
<p>이것이 무한한 개수의 문자열을 유한한 생성 규칙으로 표현할 수 있는 이유이기도 하다.</p>
<hr>
<h1 id="left-recursive와-right-recursive">Left-recursive와 Right-recursive</h1>
<p>재귀가 나타나는 위치에 따라 구분할 수도 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">A → Aa</code></pre>
<p>처럼 Nonterminal <code>A</code>가 오른쪽의 가장 왼쪽에 다시 등장하면 <strong>Left-recursive</strong>라고 한다.</p>
<p>반대로</p>
<pre><code class="language-text">A → aA</code></pre>
<p>처럼 오른쪽 끝에 등장하면 <strong>Right-recursive</strong>라고 한다.</p>
<pre><code class="language-text">A → Aa
     ↑
Left-recursive

A → aA
       ↑
Right-recursive</code></pre>
<p>지금 Chapter에서는 재귀 문법의 형태를 이해하는 정도지만, 목차를 보니 이후 Chapter 6의 Top-down 구문 분석에서 <strong>Left-recursion</strong>이 다시 등장한다.</p>
<p>즉 지금 배우는 문법의 표현 방식이 뒤의 Parser 구현과 직접 연결되는 것이다.</p>
<hr>
<h1 id="문장형태와-문장은-무엇이-다를까">문장형태와 문장은 무엇이 다를까?</h1>
<p>생성 규칙을 적용하는 중간 과정에는 아직 Nonterminal이 남아 있을 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">S ⇒ aA ⇒ abB ⇒ abc</code></pre>
<p>가 있다고 해보자.</p>
<p>중간의</p>
<pre><code class="language-text">aA
abB</code></pre>
<p>에는 Nonterminal이 포함되어 있다.</p>
<p>이런 생성 과정 중의 형태를 <strong>문장형태(Sentential Form)</strong>라고 한다.</p>
<p>반면 최종적으로 Terminal만 남은</p>
<pre><code class="language-text">abc</code></pre>
<p>와 같은 문자열은 <strong>문장(Sentence)</strong>이 된다.</p>
<p>따라서</p>
<pre><code class="language-text">Sentential Form
→ Terminal + Nonterminal이 존재할 수 있음

Sentence
→ Terminal만 존재</code></pre>
<p>라고 구분할 수 있다.</p>
<hr>
<h1 id="왜-이렇게-복잡하게-언어를-정의해야-할까">왜 이렇게 복잡하게 언어를 정의해야 할까?</h1>
<p>여기까지 공부하면 이런 생각이 들었다.</p>
<pre><code class="language-text">Java 문법을 배우는데
왜 갑자기 집합, 문자열, 문법, 유도 같은 걸 배우는 거지?</code></pre>
<p>그런데 교재 후반의 설명을 보니 이 내용이 컴파일러와 연결되는 이유가 명확했다.</p>
<p>프로그래밍 언어를 설계한다고 생각해보자.</p>
<p>먼저 그 언어에서 사용할 수 있는</p>
<pre><code class="language-text">identifier
keyword
operator
statement
expression</code></pre>
<p>같은 것들의 형태를 정의해야 한다.</p>
<p>그리고 컴파일러는 입력된 프로그램이 그 문법을 만족하는지 판단해야 한다.</p>
<p>즉 언어 설계자는</p>
<pre><code class="language-text">이 언어에서는 어떤 문자열을 올바른 프로그램으로 인정할 것인가?</code></pre>
<p>를 정확하게 정의해야 한다.</p>
<p>그 역할을 하는 것이 <strong>형식 문법</strong>이다.</p>
<p>결국 형식 언어는 단순한 수학 문제가 아니라 <strong>프로그래밍 언어의 문법을 정확하게 기술하기 위한 방법</strong>이었다.</p>
<hr>
<h1 id="그런데-모든-문법의-표현력은-같을까">그런데 모든 문법의 표현력은 같을까?</h1>
<p>여기까지는 문법을 하나의 종류처럼 생각했다.</p>
<p>그런데 생성 규칙을 얼마나 자유롭게 허용하느냐에 따라 문법의 표현 능력이 달라진다.</p>
<p>이 문법들을 체계적으로 분류한 사람이 <strong>Noam Chomsky</strong>다.</p>
<p>문법은 크게 네 종류로 나뉜다.</p>
<pre><code class="language-text">Type 0 : Unrestricted Grammar
Type 1 : Context-sensitive Grammar
Type 2 : Context-free Grammar
Type 3 : Regular Grammar</code></pre>
<p>처음 이름만 보면 Type 0부터 Type 3까지 각각 별개의 문법처럼 보인다.</p>
<p>하지만 실제로는 <strong>생성 규칙에 점점 더 많은 제약을 추가하면서 분류한 것</strong>이다.</p>
<hr>
<h1 id="type-0---unrestricted-grammar">Type 0 - Unrestricted Grammar</h1>
<p>먼저 <strong>Type 0</strong>, 무제한 문법이다.</p>
<p>이름 그대로 생성 규칙에 대한 제한이 가장 적다.</p>
<p>따라서 네 종류 중 가장 넓은 범위의 언어를 표현할 수 있다.</p>
<p>생성 규칙의 형태가 가장 자유롭기 때문에 복잡한 언어까지 표현할 수 있다.</p>
<p>하지만 자유로운 만큼 이를 인식하는 장치 역시 복잡해진다.</p>
<p>Type 0 언어를 인식하는 대표적인 모델은</p>
<pre><code class="language-text">Turing Machine</code></pre>
<p>이다.</p>
<hr>
<h1 id="type-1---context-sensitive-grammar">Type 1 - Context-sensitive Grammar</h1>
<p>다음은 <strong>Type 1</strong>, 문맥 의존 문법(Context-sensitive Grammar)이다.</p>
<p>이름 그대로 어떤 Nonterminal을 변환할 때 주변의 <strong>Context</strong>가 영향을 줄 수 있다.</p>
<p>즉 단순히</p>
<pre><code class="language-text">A → ...</code></pre>
<p>만 보는 것이 아니라 <code>A</code>의 주변에 어떤 기호가 존재하는지도 생성 규칙에 영향을 줄 수 있다.</p>
<p>Type 0보다 생성 규칙에 더 많은 제약이 있다.</p>
<p>이 문법이 생성하는 언어를 <strong>Context-sensitive Language</strong>라고 한다.</p>
<p>이를 인식하는 장치는</p>
<pre><code class="language-text">Linear Bounded Automata</code></pre>
<p>이다.</p>
<hr>
<h1 id="type-2---context-free-grammar">Type 2 - Context-free Grammar</h1>
<p>다음은 컴파일러에서 굉장히 중요하게 등장하는 <strong>Type 2</strong>, 문맥 자유 문법(Context-free Grammar)이다.</p>
<p>생성 규칙의 왼쪽에는 하나의 Nonterminal만 존재한다.</p>
<p>형태를 단순하게 나타내면</p>
<pre><code class="language-text">A → α</code></pre>
<p>이다.</p>
<p>왜 <strong>Context-free</strong>일까?</p>
<p><code>A</code> 주변에 어떤 기호가 존재하는지와 상관없이 <code>A</code>에 대한 생성 규칙을 적용할 수 있기 때문이다.</p>
<p>이 문법으로 생성되는 언어를</p>
<pre><code class="language-text">Context-free Language</code></pre>
<p>라고 한다.</p>
<p>그리고 이를 인식하는 장치가</p>
<pre><code class="language-text">Pushdown Automata</code></pre>
<p>이다.</p>
<p>여기서 중요한 연결이 하나 있다.</p>
<p>프로그래밍 언어의 <strong>구문 구조(Syntax Structure)</strong>를 표현할 때 Context-free Grammar가 많이 사용된다.</p>
<p>목차를 보니 Chapter 5가 아예</p>
<pre><code class="language-text">Context-free 문법</code></pre>
<p>이고 Chapter 6부터 본격적인 구문 분석으로 들어간다.</p>
<p>즉 지금 배우는 Type 2가 나중에 <strong>Parser</strong>와 연결되는 것이다.</p>
<hr>
<h1 id="type-3---regular-grammar">Type 3 - Regular Grammar</h1>
<p>마지막은 <strong>Type 3</strong>, 정규 문법(Regular Grammar)이다.</p>
<p>네 종류 중 생성 규칙에 가장 강한 제약이 존재한다.</p>
<p>대표적으로 오른쪽 선형 문법이라면</p>
<pre><code class="language-text">A → aB
A → a</code></pre>
<p>와 같은 형태를 가진다.</p>
<p>반대로 왼쪽 선형 형태도 생각할 수 있다.</p>
<p>정규 문법이 생성하는 언어를</p>
<pre><code class="language-text">Regular Language</code></pre>
<p>라고 한다.</p>
<p>그리고 정규 언어를 인식하는 장치가</p>
<pre><code class="language-text">Finite Automata</code></pre>
<p>이다.</p>
<p>이제 다음 Chapter의 제목이 왜 <strong>정규 언어</strong>인지도 연결됐다.</p>
<pre><code class="language-text">Chapter 2
형식 언어
     ↓
Type 3 Regular Grammar
     ↓
Regular Language
     ↓
Chapter 3
정규 언어
     ↓
Regular Expression
Finite Automata</code></pre>
<hr>
<h1 id="문법의-type은-어떻게-구분할까">문법의 Type은 어떻게 구분할까?</h1>
<p>교재에서는 여러 생성 규칙을 보고 해당 문법이 어느 Type인지 판단하는 예제도 다룬다.</p>
<p>여기서 중요한 것은 문법 이름을 외우는 것보다 <strong>생성 규칙의 형태를 보는 것</strong>이다.</p>
<pre><code class="language-text">Type 0
→ 거의 제한 없음

Type 1
→ Context-sensitive 조건

Type 2
→ 왼쪽이 하나의 Nonterminal

Type 3
→ 정규 문법의 제한된 형태</code></pre>
<p>그리고 더 제한적인 문법의 조건을 만족하면 당연히 그보다 넓은 문법의 조건도 만족할 수 있다.</p>
<p>따라서 문법 사이에는 포함 관계가 생긴다.</p>
<hr>
<h1 id="네-가지-문법은-포함-관계를-가진다">네 가지 문법은 포함 관계를 가진다</h1>
<p>Chomsky가 분류한 네 문법은 완전히 별개의 집합이 아니다.</p>
<p>다음과 같은 포함 관계를 가진다.</p>
<pre><code class="language-text">Regular Language
        ⊂
Context-free Language
        ⊂
Context-sensitive Language
        ⊂
Recursively Enumerable Language</code></pre>
<p>즉 Type 3 정규 문법이 표현할 수 있는 언어는 Type 2 문맥 자유 문법으로도 표현할 수 있다.</p>
<p>하지만 반대는 항상 가능한 것이 아니다.</p>
<p>그래서 아래로 갈수록 생성 규칙에는 더 많은 제한이 생기지만 그만큼 언어의 구조가 단순해지고, 이를 인식하는 장치도 달라진다.</p>
<hr>
<h1 id="문법이-달라지면-언어를-인식하는-장치도-달라진다">문법이 달라지면 언어를 인식하는 장치도 달라진다</h1>
<p>이 부분이 이번 Chapter에서 가장 흥미로웠다.</p>
<p>문법의 종류만 나누는 것으로 끝나는 것이 아니라 <strong>각 문법이 생성하는 언어를 인식할 수 있는 장치도 다르다.</strong></p>
<p>정리하면 다음과 같다.</p>
<pre><code class="language-text">Grammar                    Language                     Recognizer

Type 0
Unrestricted Grammar
        ↓
Recursively Enumerable Language
        ↓
Turing Machine


Type 1
Context-sensitive Grammar
        ↓
Context-sensitive Language
        ↓
Linear Bounded Automata


Type 2
Context-free Grammar
        ↓
Context-free Language
        ↓
Pushdown Automata


Type 3
Regular Grammar
        ↓
Regular Language
        ↓
Finite Automata</code></pre>
<p>처음에는</p>
<pre><code class="language-text">왜 문법을 굳이 4종류로 나누지?</code></pre>
<p>싶었는데 이 표를 보니 이유가 조금 보였다.</p>
<p>문법의 표현 능력이 달라지면 그 언어를 인식하기 위해 필요한 계산 모델 역시 달라지는 것이다.</p>
<hr>
<h1 id="결국-컴파일러에서-중요한-건-type-2와-type-3이다">결국 컴파일러에서 중요한 건 Type 2와 Type 3이다</h1>
<p>네 종류의 문법을 모두 배웠지만 컴파일러에서는 특히 두 가지가 중요하다.</p>
<pre><code class="language-text">Type 3
Regular Grammar

Type 2
Context-free Grammar</code></pre>
<p>왜냐하면 컴파일러의 Front-End에서 했던 일을 다시 보면</p>
<pre><code class="language-text">Source Program
      ↓
Scanner
      ↓
Token
      ↓
Parser
      ↓
Syntax Structure</code></pre>
<p>이기 때문이다.</p>
<p>Scanner는</p>
<pre><code class="language-text">identifier
number
keyword
operator</code></pre>
<p>같은 Token의 형태를 찾아야 한다.</p>
<p>이런 <strong>어휘 구조(Lexical Structure)</strong>를 표현하는 데 정규 문법과 정규 언어가 사용된다.</p>
<p>그래서</p>
<pre><code class="language-text">Regular Grammar
      ↓
Regular Language
      ↓
Regular Expression
      ↓
Finite Automata
      ↓
Scanner</code></pre>
<p>로 이어진다.</p>
<p>반면 Parser는</p>
<pre><code class="language-text">if 문
while 문
expression
statement</code></pre>
<p>같은 프로그램의 <strong>구문 구조(Syntax Structure)</strong>를 확인해야 한다.</p>
<p>여기에는 Context-free Grammar가 사용된다.</p>
<pre><code class="language-text">Context-free Grammar
        ↓
Context-free Language
        ↓
Parsing
        ↓
Parser</code></pre>
<p>결국 1장에서 배웠던 Scanner와 Parser가 갑자기 만들어진 것이 아니었다.</p>
<p>그 아래에는 각각의 언어를 표현하기 위한 <strong>형식 언어 이론</strong>이 존재하고 있었다.</p>
<hr>
<h1 id="2장을-공부하고-나니-컴파일러의-흐름이-조금-연결되기-시작했다">2장을 공부하고 나니 컴파일러의 흐름이 조금 연결되기 시작했다</h1>
<p>처음 2장을 펼쳤을 때는 조금 당황했다.</p>
<p>1장에서는 분명</p>
<pre><code class="language-text">Compiler
Scanner
Parser
LEX
Parser Generator</code></pre>
<p>같은 이야기를 하고 있었는데 갑자기</p>
<pre><code class="language-text">집합
문자열
언어
문법</code></pre>
<p>이 나오기 시작했기 때문이다.</p>
<p>처음에는</p>
<blockquote>
<p><strong>&quot;컴파일러 수업인데 왜 갑자기 이걸 배우지?&quot;</strong></p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>그런데 Chapter를 끝까지 보고 나니 오히려 컴파일러가 어떻게 동작하는지를 이해하기 위해 먼저 필요한 내용이었다.</p>
<p>컴파일러가</p>
<pre><code class="language-text">이 문자열은 올바른 Token이다.

이 Token의 나열은 올바른 문장이다.</code></pre>
<p>라고 판단하려면 애초에 <strong>&quot;올바르다&quot;의 기준을 수학적으로 정의할 수 있어야 한다.</strong></p>
<p>그 과정을 가장 밑에서부터 올라가면 다음과 같다.</p>
<pre><code class="language-text">Alphabet
    ↓
Symbol
    ↓
String
    ↓
Language
    ↓
Grammar
    ↓
Derivation
    ↓
Sentence</code></pre>
<p>그리고 문법은 표현할 수 있는 언어에 따라 다시 나뉜다.</p>
<pre><code class="language-text">Type 0
Unrestricted Grammar
        ↓
Turing Machine

Type 1
Context-sensitive Grammar
        ↓
Linear Bounded Automata

Type 2
Context-free Grammar
        ↓
Pushdown Automata

Type 3
Regular Grammar
        ↓
Finite Automata</code></pre>
<p>여기까지 보고 나니 앞으로 배울 내용도 조금 보이기 시작했다.</p>
<p>특히 다음 Chapter에서는 이 중</p>
<pre><code class="language-text">Type 3
Regular Grammar</code></pre>
<p>를 본격적으로 다룬다.</p>
<p>그리고 교재 목차를 따라가면</p>
<pre><code class="language-text">정규 문법
   ↓
정규 언어
   ↓
정규 표현
   ↓
유한 오토마타
   ↓
어휘 분석</code></pre>
<p>으로 이어진다.</p>
<p>그 뒤에는 다시</p>
<pre><code class="language-text">Context-free Grammar
   ↓
구문 분석
   ↓
LL / LR Parser</code></pre>
<p>로 넘어간다.</p>
<p>처음에는 Scanner가 문자를 Token으로 나누고 Parser가 문법을 검사한다는 결과만 알고 있었다.</p>
<p>그런데 이번 Chapter를 공부하면서 그 아래에</p>
<blockquote>
<p><strong>&quot;그럼 언어와 문법이라는 것은 정확히 무엇인가?&quot;</strong></p>
</blockquote>
<p>라는 이론이 먼저 깔려 있다는 것을 알게 됐다.</p>
<p>결국 프로그래밍 언어도 컴퓨터의 입장에서 보면 특별한 것이 아니었다.</p>
<p><strong>정해진 알파벳으로 만들 수 있는 수많은 문자열 중, 특정 문법이 허용하는 문자열들의 집합.</strong></p>
<p>그게 형식 언어의 관점에서 바라본 프로그래밍 언어였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클래스는 어떻게 찾아야 할까? | 후보 클래스 도출부터 부적절한 클래스 제거까지]]></title>
            <link>https://velog.io/@daehyun_lee/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B0%BE%EC%95%84%EC%95%BC-%ED%95%A0%EA%B9%8C-%ED%9B%84%EB%B3%B4-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%8F%84%EC%B6%9C%EB%B6%80%ED%84%B0-%EB%B6%80%EC%A0%81%EC%A0%88%ED%95%9C-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%9C%EA%B1%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%ED%81%B4%EB%9E%98%EC%8A%A4%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B0%BE%EC%95%84%EC%95%BC-%ED%95%A0%EA%B9%8C-%ED%9B%84%EB%B3%B4-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%8F%84%EC%B6%9C%EB%B6%80%ED%84%B0-%EB%B6%80%EC%A0%81%EC%A0%88%ED%95%9C-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%9C%EA%B1%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 25 Sep 2026 12:52:26 GMT</pubDate>
            <description><![CDATA[<h1 id="클래스는-어떻게-찾아야-할까--후보-클래스-도출부터-부적절한-클래스-제거까지">클래스는 어떻게 찾아야 할까? | 후보 클래스 도출부터 부적절한 클래스 제거까지</h1>
<p>객체지향 프로그래밍을 처음 배울 때 클래스는 그렇게 어렵지 않았다.</p>
<pre><code class="language-java">class User {
    String name;
    int age;
}</code></pre>
<p>객체가 가져야 할 데이터를 필드로 만들고, 필요한 기능을 메서드로 넣으면 됐다.</p>
<p>그런데 실제 시스템을 객체지향적으로 설계하려고 하니 조금 다른 문제가 생겼다.</p>
<blockquote>
<p><strong>&quot;그래서 무슨 클래스를 만들어야 하지?&quot;</strong></p>
</blockquote>
<p>코드를 작성할 때는 이미 <code>User</code>, <code>Order</code>, <code>Product</code> 같은 클래스가 필요하다고 어느 정도 정해져 있는 경우가 많았다.</p>
<p>하지만 아무것도 없는 요구사항에서 직접 클래스를 찾아야 한다면 이야기가 달라진다.</p>
<p>예를 들어 화이트보드 시스템을 만든다고 해보자.</p>
<pre><code class="language-text">사용자는 도형을 생성할 수 있다.
사용자는 도형을 이동할 수 있다.
사용자는 그림을 저장할 수 있다.
사용자는 다른 사용자에게 화면을 방송할 수 있다.</code></pre>
<p>여기서 클래스는 무엇일까?</p>
<p><code>User</code>?</p>
<p><code>Shape</code>?</p>
<p><code>Canvas</code>?</p>
<p><code>Move</code>?</p>
<p><code>Save</code>?</p>
<p>요구사항에 등장한다고 해서 전부 클래스로 만들 수도 없고, 그렇다고 감으로 몇 개만 골라낼 수도 없다.</p>
<p>이번 Chapter에서는 <strong>요구사항으로부터 클래스를 어떻게 찾아내고, 그중에서 실제 시스템에 필요한 클래스를 어떻게 골라내는지</strong>를 공부했다.</p>
<hr>
<h1 id="클래스는-시스템을-이루는-기본-단위다">클래스는 시스템을 이루는 기본 단위다</h1>
<p>객체지향 시스템에서는 클래스를 중심으로 시스템을 구성한다.</p>
<p>클래스에는 해당 객체가 가져야 할 <strong>속성(Attribute)</strong>과 수행할 수 있는 <strong>연산(Operation)</strong>이 정의된다.</p>
<pre><code class="language-text">Class
├── Attribute
└── Operation</code></pre>
<p>예를 들어 <code>User</code>라는 클래스가 있다면</p>
<pre><code class="language-text">User
├── userId
├── password
└── login()</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>그런데 중요한 것은 클래스 문법 자체가 아니었다.</p>
<p>실제 개발에서는 그 전에</p>
<blockquote>
<p><strong>&quot;우리 시스템에는 어떤 클래스가 필요한가?&quot;</strong></p>
</blockquote>
<p>를 먼저 결정해야 한다.</p>
<p>그리고 이 작업이 생각보다 쉽지 않다.</p>
<hr>
<h1 id="예제로-화이트보드-시스템을-생각해보자">예제로 화이트보드 시스템을 생각해보자</h1>
<p>이번 Chapter에서는 <strong>화이트보드(Whiteboard) 시스템</strong>을 예제로 사용한다.</p>
<p>화이트보드 시스템은 사용자가 캔버스에서 그림을 그리고 편집하고, 다른 사용자에게 이를 방송할 수 있는 시스템이다.</p>
<p>사용자는 캔버스 위에서 여러 도형을 만들 수 있다.</p>
<pre><code class="language-text">Line
Oval
Rectangle
Text
Image
FreeDrawing</code></pre>
<p>도형을 생성하는 것뿐만 아니라 편집도 가능하다.</p>
<pre><code class="language-text">도형 선택
도형 이동
잘라내기
복사하기
붙여넣기
배치하기</code></pre>
<p>페이지를 추가하거나 삭제하는 <strong>캔버스 관리 기능</strong>도 있고, 사용자가 수행한 모든 편집 동작을 녹화하고 다시 재생할 수도 있다.</p>
<p>또 방송 기능을 이용하면 화이트보드 시스템에 연결된 클라이언트에게 현재 캔버스 상태를 전달할 수 있다.</p>
<p>즉 단순한 그림판보다는</p>
<pre><code class="language-text">그리기
+
편집
+
페이지 관리
+
녹화 / 재생
+
다른 사용자에게 방송</code></pre>
<p>까지 포함된 시스템이다.</p>
<p>이 정도만 봐도 시스템 안에는 상당히 많은 개념이 등장한다.</p>
<p>문제는 여기서부터다.</p>
<blockquote>
<p><strong>이 중에서 무엇을 클래스로 만들어야 할까?</strong></p>
</blockquote>
<hr>
<h1 id="요구사항에-등장하는-모든-단어가-클래스일까">요구사항에 등장하는 모든 단어가 클래스일까?</h1>
<p>처음 생각하면 가장 쉬운 방법은 요구사항에 등장하는 명사를 전부 클래스로 만드는 것이다.</p>
<p>예를 들어 다음과 같은 문장이 있다고 해보자.</p>
<pre><code class="language-text">사용자는 캔버스에서 도형을 선택하고 이동할 수 있다.</code></pre>
<p>명사를 찾아보면</p>
<pre><code class="language-text">사용자
캔버스
도형</code></pre>
<p>이 나온다.</p>
<p>그렇다면</p>
<pre><code class="language-text">User
Canvas
Shape</code></pre>
<p>를 후보 클래스로 생각해볼 수 있다.</p>
<p>이 방법을 <strong>명사 분석 방법</strong>이라고 한다.</p>
<p>요구사항을 읽으면서 명사 또는 명사구를 찾아 <strong>후보 클래스(Candidate Class)</strong>로 뽑아내는 것이다.</p>
<p>중요한 것은 이 시점에서는 아직 <strong>후보</strong>라는 것이다.</p>
<pre><code class="language-text">요구사항
   ↓
명사 찾기
   ↓
후보 클래스
   ↓
검토
   ↓
실제 클래스</code></pre>
<p>처음부터 완벽한 클래스를 찾으려는 것이 아니라 일단 가능성이 있는 것들을 넓게 찾아놓고 이후에 필요 없는 것을 제거한다.</p>
<p>이 접근이 오히려 현실적으로 느껴졌다.</p>
<hr>
<h1 id="명사-분석-방법">명사 분석 방법</h1>
<p>명사 분석 방법은 요구사항에서 명사를 찾아 후보 클래스를 도출하는 방법이다.</p>
<p>화이트보드 시스템의 요구사항을 분석하면 다양한 명사가 등장한다.</p>
<pre><code class="language-text">UserID
Password
Picture
FontName
FontSize

WhiteBoardSystem
Canvas
Shape
FreeDrawing

Image
Text
Line
Oval
Rectangle

Page
Client
Clipboard

Connection
User
Transmission
...</code></pre>
<p>처음 결과를 보면 상당히 많다.</p>
<p>그리고 이상한 것도 보인다.</p>
<pre><code class="language-text">Password도 클래스인가?

FontSize도 클래스인가?

TCP/IP도 클래스인가?

Movement도 클래스인가?</code></pre>
<p>이걸 보고 명사 분석 방법의 의미를 조금 다르게 이해하게 됐다.</p>
<blockquote>
<p><strong>명사를 찾는 것은 클래스를 확정하는 과정이 아니라 후보를 놓치지 않기 위한 과정이다.</strong></p>
</blockquote>
<p>명사라고 해서 무조건 클래스가 되는 것이 아니다.</p>
<p>일단 후보를 넓게 뽑고, 그다음 단계에서 실제 클래스로 적절한지를 판단한다.</p>
<hr>
<h1 id="그런데-요구사항에-모든-클래스가-적혀-있을까">그런데 요구사항에 모든 클래스가 적혀 있을까?</h1>
<p>여기서 또 하나의 문제가 생긴다.</p>
<p>명사 분석 방법은 결국 <strong>문서에 적혀 있는 단어</strong>를 기반으로 한다.</p>
<p>그렇다면 요구사항에 명확하게 적혀 있지 않은데 시스템에는 필요한 클래스가 있다면 어떻게 해야 할까?</p>
<p>이때 필요한 것이 <strong>문제 영역에 대한 배경 지식</strong>이다.</p>
<p>개발자가 시스템의 문제 영역에 대해 가지고 있는 상식이나 유추를 활용해서 추가적인 클래스를 찾아내는 것이다.</p>
<p>화이트보드 시스템의 방송 기능을 생각해보면 단순히</p>
<pre><code class="language-text">방송
클라이언트
전송</code></pre>
<p>이라는 단어만 보고 끝낼 수 없다.</p>
<p>실제로 여러 클라이언트에게 방송을 전달하려면 이를 관리하는 역할이 필요할 수 있다.</p>
<p>녹화와 재생 역시 마찬가지다.</p>
<p>요구사항에 적힌 단어만 기계적으로 가져오는 것이 아니라</p>
<pre><code class="language-text">누가 녹화를 관리하지?

누가 재생하지?

누가 캔버스를 관리하지?

여러 클라이언트에 대한 방송은 누가 관리하지?</code></pre>
<p>처럼 시스템이 실제로 동작하는 모습을 생각해볼 수 있다.</p>
<p>그래서 교재의 후보 클래스 도출 과정에서는 배경 지식을 활용해</p>
<pre><code class="language-text">CanvasManager
BroadcastingManager
Recorder
Player</code></pre>
<p>와 같은 후보를 추가한다.</p>
<p>여기서 흥미로웠던 것은 클래스가 단순히 <strong>요구사항에 적힌 명사</strong>에서만 나오는 것이 아니라는 점이었다.</p>
<pre><code class="language-text">요구사항에 적힌 개념
        +
문제 영역에 대한 배경 지식
        +
시스템이 실제로 해야 할 일
        ↓
후보 클래스</code></pre>
<p>결국 요구사항을 읽는 것과 시스템을 이해하는 것은 조금 다른 문제였다.</p>
<hr>
<h1 id="전형적인-유형으로도-클래스를-찾을-수-있다">전형적인 유형으로도 클래스를 찾을 수 있다</h1>
<p>후보 클래스를 찾는 또 다른 방법은 <strong>전형적인 유형의 클래스</strong>를 생각해보는 것이다.</p>
<p>교재에서는 다음과 같은 관점으로 클래스를 찾아본다.</p>
<pre><code class="language-text">물리적인 객체

업무와 작업의 참여자 및 결과물

시스템 외부의 인터페이스

개념적인 객체</code></pre>
<p>예를 들어 실제 세계에서 존재하는 사람이나 장치처럼 시스템에서 다루어야 하는 객체가 있을 수 있다.</p>
<p>어떤 업무를 수행하는 참여자나 그 결과물이 클래스가 될 수도 있다.</p>
<p>시스템 외부와 연결되는 인터페이스 역할의 클래스가 필요할 수도 있고, 실제 물리적인 형태는 없더라도 시스템 안에서 중요한 개념이라면 클래스가 될 수 있다.</p>
<p>즉 클래스를 찾을 때</p>
<blockquote>
<p><strong>&quot;요구사항에 어떤 명사가 있지?&quot;</strong></p>
</blockquote>
<p>만 보는 것이 아니라</p>
<blockquote>
<p><strong>&quot;이 시스템에서 누가 일하고, 무엇을 다루고, 외부와 어떻게 연결되고, 어떤 개념을 관리해야 하지?&quot;</strong></p>
</blockquote>
<p>라는 관점에서도 생각할 수 있다.</p>
<p>명사 분석 하나에만 의존하는 것보다 여러 관점에서 후보를 찾는 이유가 여기에 있었다.</p>
<hr>
<h1 id="후보-클래스를-많이-찾았다고-끝이-아니다">후보 클래스를 많이 찾았다고 끝이 아니다</h1>
<p>여기까지 하면 후보 클래스가 굉장히 많아진다.</p>
<p>화이트보드 시스템에서도 명사 분석과 배경 지식을 통해 여러 후보가 나온다.</p>
<p>그런데 당연히 이걸 전부 클래스로 만들 수는 없다.</p>
<p>이제 반대 작업이 필요하다.</p>
<blockquote>
<p><strong>부적절한 클래스를 제거해야 한다.</strong></p>
</blockquote>
<p>교재에서는 후보 클래스 중 다음과 같은 것들을 검토하면서 제거한다.</p>
<pre><code class="language-text">시스템 외부의 존재

중복 클래스

시스템의 기능과 무관한 클래스

단순한 속성

연산

구현과 관련된 클래스</code></pre>
<p>처음 후보를 찾는 과정이</p>
<pre><code class="language-text">&quot;혹시 클래스가 될 수 있지 않을까?&quot;</code></pre>
<p>였다면,</p>
<p>이번에는 하나씩</p>
<pre><code class="language-text">&quot;얘가 정말 독립적인 클래스여야 할까?&quot;</code></pre>
<p>를 묻는 과정이다.</p>
<hr>
<h1 id="시스템-외부의-존재는-제거한다">시스템 외부의 존재는 제거한다</h1>
<p>후보 클래스라고 해서 시스템과 관련된 모든 대상을 클래스로 만들 필요는 없다.</p>
<p>예를 들어 어떤 사람이 화이트보드 시스템을 사용한다고 해보자.</p>
<p>그 사람은 분명 시스템과 관련되어 있다.</p>
<p>하지만 우리가 만드는 프로그램의 내부에서 관리해야 하는 객체인지, 단순히 시스템과 상호작용하는 외부 존재인지는 별개의 문제다.</p>
<pre><code class="language-text">외부 사용자
     │
     ▼
┌─────────────────┐
│   Whiteboard    │
│     System      │
└─────────────────┘</code></pre>
<p>교재에서는 시스템 외부의 존재라면 후보 클래스에서 제외할 수 있다고 설명한다.</p>
<p>다만 외부에 존재한다고 해서 항상 제거하는 것은 아니다.</p>
<p>외부 시스템과의 통신을 담당하는 역할처럼 <strong>시스템 내부에서 그 외부 존재와의 상호작용을 표현해야 한다면 관련 클래스가 필요할 수도 있다.</strong></p>
<p>결국 중요한 것은</p>
<blockquote>
<p><strong>&quot;현실 세계에 존재하는가?&quot;</strong></p>
</blockquote>
<p>가 아니라</p>
<blockquote>
<p><strong>&quot;우리 시스템 내부에서 표현하고 책임을 부여할 필요가 있는가?&quot;</strong></p>
</blockquote>
<p>였다.</p>
<p>이 부분은 이전에 시스템을 설계하면서 계속 등장했던 <strong>시스템 경계</strong>라는 개념과도 연결됐다.</p>
<p>클래스를 찾기 전에 결국</p>
<blockquote>
<p><strong>&quot;어디까지가 우리가 만드는 시스템인가?&quot;</strong></p>
</blockquote>
<p>가 먼저 정해져 있어야 한다.</p>
<hr>
<h1 id="중복된-클래스는-하나로-합친다">중복된 클래스는 하나로 합친다</h1>
<p>다음은 <strong>중복 클래스</strong>다.</p>
<p>요구사항을 여러 사람이 작성하거나 서로 다른 문서에서 요구사항을 가져오다 보면 같은 대상을 다른 이름으로 표현할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">User
Client
사용자</code></pre>
<p>처럼 서로 다른 단어가 실제로 동일한 대상을 의미할 수도 있다.</p>
<p>반대로 같은 단어라고 해서 무조건 같은 의미인 것도 아니다.</p>
<p>문맥에 따라 역할이 다르다면 별도의 클래스로 남겨야 할 수도 있다.</p>
<p>그래서 단순히 이름만 비교할 것이 아니라</p>
<blockquote>
<p><strong>&quot;이 두 후보가 시스템에서 실제로 같은 대상과 개념을 의미하는가?&quot;</strong></p>
</blockquote>
<p>를 확인해야 한다.</p>
<p>동일한 대상을 의미한다면 둘 중 더 명확하고 구체적인 이름을 선택한다.</p>
<pre><code class="language-text">같은 대상
+
다른 이름

↓

의미 확인

↓

하나의 클래스</code></pre>
<p>결국 클래스 이름을 정하는 것도 단순한 작명 문제가 아니었다.</p>
<p>이름이 다르면 개발자는 서로 다른 개념이라고 생각할 수 있기 때문이다.</p>
<hr>
<h1 id="시스템-기능과-무관한-클래스는-제거한다">시스템 기능과 무관한 클래스는 제거한다</h1>
<p>후보로 뽑혔다고 해서 시스템에 반드시 필요한 것은 아니다.</p>
<p>클래스가 실제 시스템 기능과 아무런 관련이 없다면 제거해야 한다.</p>
<p>판단 기준은 비교적 명확하다.</p>
<blockquote>
<p><strong>&quot;이 클래스가 시스템이 제공해야 하는 기능에 기여하는가?&quot;</strong></p>
</blockquote>
<p>예를 들어 어떤 후보가 요구사항 설명 과정에는 등장했지만 시스템이 수행해야 하는 기능에 아무런 영향을 주지 않는다면 굳이 내부에서 관리할 필요가 없다.</p>
<p>처음 명사 분석 단계에서는 가능한 후보를 넓게 뽑았기 때문에 이런 후보가 생기는 것이 자연스럽다.</p>
<p>그래서</p>
<pre><code class="language-text">후보를 많이 뽑음
↓
시스템 기능과의 관계 확인
↓
관련 없음
↓
제거</code></pre>
<p>라는 정제 과정이 필요하다.</p>
<hr>
<h1 id="클래스가-아니라-속성일-수도-있다">클래스가 아니라 속성일 수도 있다</h1>
<p>이 부분은 특히 중요하게 느껴졌다.</p>
<p>명사라고 해서 전부 클래스는 아니다.</p>
<p>예를 들어 화이트보드 시스템에서</p>
<pre><code class="language-text">UserID
Password
Picture
FontName
FontSize
TextColor
LineColor
LineThickness
SequenceNumber</code></pre>
<p>같은 명사가 나온다.</p>
<p>처음 명사 분석만 하면 전부 후보 클래스가 될 수 있다.</p>
<p>그런데 생각해보면 <code>Password</code>가 독립적인 객체로 존재해야 할까?</p>
<p>대부분의 경우 그렇지 않다.</p>
<pre><code class="language-text">UserInfo
├── UserID
├── Password
└── Picture</code></pre>
<p>처럼 다른 클래스의 <strong>속성(Attribute)</strong>으로 표현하는 것이 자연스럽다.</p>
<p>마찬가지로</p>
<pre><code class="language-text">Text
├── FontName
├── FontSize
└── TextColor</code></pre>
<p>처럼 표현할 수 있다.</p>
<p><code>LineThickness</code>나 <code>LineColor</code> 역시 독립된 클래스보다는 <code>Line</code>, <code>Oval</code>, <code>Rectangle</code> 같은 도형이 가지는 속성으로 보는 것이 자연스럽다.</p>
<p>즉 후보를 보면서</p>
<blockquote>
<p><strong>&quot;얘가 자기만의 책임과 행동을 가지는 객체인가?&quot;</strong></p>
</blockquote>
<p>아니면</p>
<blockquote>
<p><strong>&quot;다른 객체를 설명하기 위한 값인가?&quot;</strong></p>
</blockquote>
<p>를 구분해야 한다.</p>
<p>교재에서는 클래스가 시스템 기능을 수행하는 데 필요한 <strong>정보와 행위</strong>를 제공하는지를 중요한 판단 기준으로 사용한다.</p>
<p>단순히 다른 객체의 상태를 설명하는 값이라면 독립적인 클래스보다는 속성으로 표현할 수 있다.</p>
<hr>
<h1 id="연산을-클래스로-만들-필요도-없다">연산을 클래스로 만들 필요도 없다</h1>
<p>반대로 어떤 후보는 객체가 아니라 <strong>행동</strong>일 수도 있다.</p>
<p>화이트보드 시스템에는</p>
<pre><code class="language-text">Selection
Movement
Cut
Copy
Paste
Arrangement</code></pre>
<p>같은 후보가 등장한다.</p>
<p>그런데 이것들은 대부분 사용자가 수행하는 <strong>기능 또는 연산</strong>이다.</p>
<p>단순히</p>
<pre><code class="language-text">Copy라는 명사가 있다.
↓
Copy 클래스를 만든다.</code></pre>
<p>라고 생각하면 이상해질 수 있다.</p>
<p>오히려</p>
<pre><code class="language-text">Copy라는 기능이 있다.
↓
누가 이 기능을 책임져야 하지?
↓
적절한 클래스의 연산으로 표현</code></pre>
<p>이라고 생각해야 한다.</p>
<p>화이트보드 시스템에서는 이러한 편집 기능들을 묶어 <code>CanvasEditingCommand</code>와 같은 클래스로 정리한다.</p>
<pre><code class="language-text">CanvasEditingCommand

├── Selection
├── Movement
├── Cut
├── Copy
├── Paste
└── Arrangement</code></pre>
<p>즉 처음에는 각각의 후보처럼 보였던 것들이 실제로는 <strong>하나의 클래스가 제공해야 하는 연산</strong>으로 정리될 수 있다.</p>
<p>이 부분에서 클래스 도출은 결국 명사를 찾는 작업보다 <strong>책임을 배치하는 작업에 가깝다</strong>는 생각이 들었다.</p>
<hr>
<h1 id="방송도-같은-방식으로-정리할-수-있다">방송도 같은 방식으로 정리할 수 있다</h1>
<p>화이트보드 시스템에는 방송과 관련된 후보도 등장한다.</p>
<p>처음에는</p>
<pre><code class="language-text">Transmission
Broadcasting</code></pre>
<p>처럼 각각의 개념이 후보로 나타날 수 있다.</p>
<p>그런데 시스템에서 중요한 것은 이 단어들을 모두 클래스로 만드는 것이 아니다.</p>
<p>실제로 누가 방송을 관리하고, 누가 클라이언트에게 데이터를 전달할 것인지를 생각해야 한다.</p>
<p>그래서 최종적으로는 방송과 관련된 기능을 담당하는</p>
<pre><code class="language-text">BroadcastingManager</code></pre>
<p>와 같은 클래스로 책임을 모을 수 있다.</p>
<pre><code class="language-text">방송 관련 여러 개념
       ↓
실제 책임 분석
       ↓
BroadcastingManager</code></pre>
<p>결국 여기에서도 같은 기준이 반복된다.</p>
<blockquote>
<p><strong>단어가 몇 개 등장했는지가 아니라 어떤 객체가 그 책임을 가져야 하는지가 중요하다.</strong></p>
</blockquote>
<hr>
<h1 id="구현-방법-자체를-클래스로-만들-필요는-없다">구현 방법 자체를 클래스로 만들 필요는 없다</h1>
<p>후보 중에는 구현 기술도 등장할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">TCP/IP</code></pre>
<p>가 있다.</p>
<p>화이트보드 시스템에서 클라이언트끼리 데이터를 전달하기 위해 TCP/IP를 사용할 수 있다.</p>
<p>그런데</p>
<pre><code class="language-text">TCPIP class</code></pre>
<p>를 반드시 만들어야 할까?</p>
<p>TCP/IP는 시스템이 제공해야 하는 <strong>문제 영역의 개념</strong>이라기보다 그 기능을 구현하기 위해 선택한 방법이다.</p>
<pre><code class="language-text">사용자에게 화면을 전송한다.</code></pre>
<p>가 시스템이 해야 하는 일이라면 중요한 것은 <strong>전송이라는 책임</strong>이지,</p>
<pre><code class="language-text">TCP/IP를 사용한다.</code></pre>
<p>라는 구현 방법 자체가 아니다.</p>
<p>교재에서도 구현과 관련된 후보는 실제 문제 영역의 클래스와 구분해야 한다고 설명한다.</p>
<p>이 부분은 요구사항과 구현을 분리해서 생각해야 한다는 점과 연결된다.</p>
<hr>
<h1 id="결국-어떤-후보를-제거해야-할까">결국 어떤 후보를 제거해야 할까?</h1>
<p>지금까지의 과정을 한 번 정리하면 다음과 같다.</p>
<pre><code class="language-text">후보 클래스
    │
    ├── 시스템 외부의 존재인가?
    │       └── 시스템 내부 표현이 필요 없다면 제거
    │
    ├── 다른 클래스와 같은 의미인가?
    │       └── 중복 제거 / 통합
    │
    ├── 시스템 기능과 무관한가?
    │       └── 제거
    │
    ├── 다른 클래스의 단순한 속성인가?
    │       └── Attribute로 이동
    │
    ├── 단순한 연산인가?
    │       └── 적절한 클래스의 Operation으로 이동
    │
    └── 구현 방법 자체인가?
            └── 후보 클래스에서 제거</code></pre>
<p>이 과정을 거치면서 처음에는 복잡했던 후보 목록이 점점 실제 시스템을 구성하는 클래스로 정리된다.</p>
<hr>
<h1 id="화이트보드-시스템에는-어떤-클래스가-남았을까">화이트보드 시스템에는 어떤 클래스가 남았을까?</h1>
<p>후보를 정제한 결과 화이트보드 시스템에는 다음과 같은 클래스들이 남는다.</p>
<pre><code class="language-text">UserInfo

Canvas

Shape

Image
Text
Line
Oval
Rectangle
FreeDrawing

Recorder
Player

CanvasManager

BroadcastingManager

Clipboard

CanvasEditingCommand

ClientManager</code></pre>
<p>그리고 교재 후반에서는 처음 후보로 등장했던 개념들이 왜 제거되거나 다른 클래스에 포함되는지도 정리한다.</p>
<p>예를 들어 다음과 같다.</p>
<pre><code class="language-text">UserID
Password
Picture
→ UserInfo와 관련된 속성

FontName
FontSize
TextColor
→ Text와 관련된 속성

LineThickness
LineColor
→ 도형과 관련된 속성

Selection
Movement
Cut
Copy
Paste
Arrangement
→ 편집과 관련된 연산

TCP/IP
→ 구현과 관련된 개념</code></pre>
<p>처음 후보를 뽑았을 때와 비교하면 꽤 큰 변화다.</p>
<pre><code class="language-text">요구사항에서 발견한 단어
        ↓
후보 클래스
        ↓
역할과 의미 확인
        ↓
속성 / 연산 / 외부 존재 / 구현 기술 구분
        ↓
실제 클래스</code></pre>
<p>처음에는 수많은 단어가 나왔지만 결국 시스템 안에서 <strong>독립적인 역할과 책임을 가져야 하는 대상</strong>을 중심으로 클래스가 남는다.</p>
<hr>
<h1 id="후보-클래스가-적절한지는-어떻게-판단할까">후보 클래스가 적절한지는 어떻게 판단할까?</h1>
<p>여기까지 오면 또 이런 의문이 생긴다.</p>
<blockquote>
<p><strong>&quot;그래서 남은 클래스가 정말 좋은 클래스인지는 어떻게 판단하지?&quot;</strong></p>
</blockquote>
<p>교재 마지막에서는 클래스를 검토할 때 몇 가지 기준을 제시한다.</p>
<p>먼저 하나의 클래스는 가능하면 <strong>하나의 대상이나 개념을 나타내는 것이 좋다.</strong></p>
<p>예를 들어 하나의 클래스가</p>
<pre><code class="language-text">사용자 정보 관리
+
도형 편집
+
방송
+
파일 저장</code></pre>
<p>을 전부 담당한다면 클래스 하나가 너무 많은 개념을 표현하게 된다.</p>
<p>클래스를 봤을 때</p>
<blockquote>
<p><strong>&quot;얘가 무엇을 나타내는 클래스지?&quot;</strong></p>
</blockquote>
<p>라는 질문에 비교적 명확하게 답할 수 있어야 한다.</p>
<hr>
<h1 id="이름도-구체적이어야-한다">이름도 구체적이어야 한다</h1>
<p>클래스 이름 역시 중요하다.</p>
<p>클래스는 다른 개발자가 이름만 보고도 어느 정도 의미를 추측할 수 있어야 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Manager
Data
Object
Thing</code></pre>
<p>처럼 지나치게 추상적인 이름만 사용하면 실제로 무엇을 담당하는지 파악하기 어렵다.</p>
<p>반면</p>
<pre><code class="language-text">CanvasManager

BroadcastingManager

CanvasEditingCommand</code></pre>
<p>처럼 어떤 대상을 관리하고 어떤 역할을 담당하는지가 드러나는 이름은 클래스의 의미를 이해하는 데 도움을 준다.</p>
<p>물론 이름만 구체적이라고 좋은 클래스가 되는 것은 아니지만, 적어도 클래스가 나타내는 개념과 이름이 일치해야 한다.</p>
<hr>
<h1 id="높은-응집도와-낮은-결합도">높은 응집도와 낮은 결합도</h1>
<p>그리고 객체지향 설계에서 계속 등장하는 개념이 나온다.</p>
<p><strong>응집도(Cohesion)</strong>와 <strong>결합도(Coupling)</strong>다.</p>
<p>응집도는 하나의 클래스 내부에 있는 속성과 연산이 서로 얼마나 관련되어 있는지를 나타낸다.</p>
<pre><code class="language-text">하나의 책임을 중심으로

서로 관련된 속성
+
서로 관련된 연산

↓

높은 응집도</code></pre>
<p>반대로 서로 관련 없는 기능을 하나의 클래스에 몰아넣으면 응집도가 낮아진다.</p>
<pre><code class="language-text">Class A

├── 사용자 로그인
├── 도형 그리기
├── 방송
└── 파일 저장</code></pre>
<p>이런 클래스가 있다면</p>
<blockquote>
<p><strong>&quot;이 클래스는 도대체 무슨 클래스지?&quot;</strong></p>
</blockquote>
<p>라는 문제가 생길 수 있다.</p>
<p>따라서 가능하면 하나의 클래스 안에는 <strong>서로 관련성이 높은 속성과 연산을 모으는 것</strong>이 좋다.</p>
<p>반대로 클래스 사이의 의존 관계는 필요 이상으로 강하지 않은 것이 좋다.</p>
<p>즉 <strong>높은 응집도와 낮은 결합도</strong>를 가지도록 클래스를 구성하는 것이 중요하다.</p>
<p>결국 좋은 클래스는 단순히 기능을 많이 가지고 있는 클래스가 아니라</p>
<pre><code class="language-text">자신의 책임은 명확하고

내부 요소들은 서로 관련되어 있으며

다른 클래스와는 필요한 만큼만 관계를 맺는 클래스</code></pre>
<p>에 가깝다.</p>
<hr>
<h1 id="클래스-도출에는-하나의-정답만-있는-것도-아니다">클래스 도출에는 하나의 정답만 있는 것도 아니다</h1>
<p>여기서 또 중요하게 느껴진 부분이 있다.</p>
<p>같은 요구사항을 보고도 개발자마다 조금씩 다른 클래스를 도출할 수 있다.</p>
<p>어떤 개발자는 하나의 클래스로 묶을 수도 있고, 다른 개발자는 책임을 분리해서 여러 클래스로 나눌 수도 있다.</p>
<p>그래서 클래스 도출을 단순히</p>
<pre><code class="language-text">요구사항
↓
정답 클래스 목록</code></pre>
<p>처럼 생각하면 안 된다.</p>
<p>더 중요한 것은</p>
<blockquote>
<p><strong>왜 이 대상을 클래스로 만들었는가?</strong></p>
</blockquote>
<blockquote>
<p><strong>왜 이 후보는 속성으로 옮겼는가?</strong></p>
</blockquote>
<blockquote>
<p><strong>왜 이 기능은 별도 클래스가 아니라 연산으로 표현했는가?</strong></p>
</blockquote>
<blockquote>
<p><strong>왜 두 후보를 하나의 클래스로 합쳤는가?</strong></p>
</blockquote>
<p>를 설명할 수 있는 것이다.</p>
<p>결국 클래스 모델링 역시 시스템을 바라보는 하나의 설계 판단이다.</p>
<hr>
<h1 id="결국-클래스는-명사를-코드로-옮기는-것이-아니었다">결국 클래스는 명사를 코드로 옮기는 것이 아니었다</h1>
<p>이번 Chapter를 보기 전에는 요구사항에서 클래스를 찾는다고 하면 가장 먼저 <strong>명사 찾기</strong>가 떠올랐다.</p>
<pre><code class="language-text">사용자 → User

도형 → Shape

캔버스 → Canvas</code></pre>
<p>실제로 명사 분석은 좋은 출발점이었다.</p>
<p>하지만 그게 끝은 아니었다.</p>
<pre><code class="language-text">요구사항
    ↓
명사 분석
    ↓
후보 클래스 도출
    ↓
문제 영역의 배경 지식 활용
    ↓
전형적인 유형의 클래스 탐색
    ↓
후보 클래스 정제
    │
    ├── 외부 존재 검토
    ├── 중복 제거
    ├── 기능과 무관한 후보 제거
    ├── 속성 분리
    ├── 연산 분리
    └── 구현 관련 후보 제거
    ↓
최종 클래스
    ↓
클래스 검토
    │
    ├── 하나의 대상이나 개념을 나타내는가?
    ├── 이름이 구체적인가?
    └── 응집도와 결합도는 적절한가?</code></pre>
<p>결국 중요한 것은 단어를 클래스 이름으로 바꾸는 것이 아니라 <strong>시스템 안에서 어떤 대상이 어떤 책임을 가져야 하는지를 찾아가는 과정</strong>이었다.</p>
<p>특히 처음에는 후보를 넓게 찾고 나중에 하나씩 제거해나가는 방식이 인상적이었다.</p>
<p>처음부터</p>
<blockquote>
<p><strong>&quot;정답 클래스를 찾아야지.&quot;</strong></p>
</blockquote>
<p>라고 생각하는 것보다</p>
<blockquote>
<p><strong>&quot;일단 후보를 찾아보자. 그리고 얘가 정말 클래스여야 하는지 하나씩 따져보자.&quot;</strong></p>
</blockquote>
<p>라고 접근하는 것이 훨씬 현실적인 방법처럼 느껴졌다.</p>
<p>그리고 이제 요구사항에서 명사를 발견하면 바로 클래스를 만드는 대신 한 번 더 물어볼 것 같다.</p>
<blockquote>
<p><strong>&quot;얘는 왜 클래스여야 하지?&quot;</strong></p>
</blockquote>
<p>아마 이 질문에 제대로 답할 수 있을 때 비로소 단순한 명사가 아니라 시스템을 구성하는 하나의 클래스가 되는 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 상태 코드는 왜 이렇게 많을까? | 2xx부터 리다이렉션, PRG와 4xx·5xx까지]]></title>
            <link>https://velog.io/@daehyun_lee/HTTP-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@daehyun_lee/HTTP-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Fri, 25 Sep 2026 12:09:30 GMT</pubDate>
            <description><![CDATA[<h1 id="http-상태-코드는-왜-이렇게-많을까--2xx부터-리다이렉션-prg와-4xx·5xx까지">HTTP 상태 코드는 왜 이렇게 많을까? | 2xx부터 리다이렉션, PRG와 4xx·5xx까지</h1>
<p>HTTP 요청을 보내면 서버는 응답을 돌려준다.</p>
<p>지금까지는 <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, <code>DELETE</code>처럼 <strong>클라이언트가 서버에 어떤 요청을 보내는가</strong>를 중심으로 봤다면, 이번에는 반대로 서버가 클라이언트에게</p>
<blockquote>
<p><strong>&quot;네 요청을 이렇게 처리했어.&quot;</strong></p>
</blockquote>
<p>라고 알려주는 방법을 공부했다.</p>
<p>그게 바로 <strong>HTTP 상태 코드(Status Code)</strong>다.</p>
<p>예를 들어 서버로 요청을 보냈을 때 이런 숫자를 자주 본다.</p>
<pre><code class="language-text">200

404

500</code></pre>
<p>개발하면서 정말 많이 봤던 숫자들이지만, 막상</p>
<blockquote>
<p><strong>&quot;302와 307은 뭐가 다르지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;401과 403은 정확히 뭐가 다르지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;클라이언트 오류와 서버 오류는 재시도 관점에서 어떤 차이가 있지?&quot;</strong></p>
</blockquote>
<p>라고 물어보면 애매한 부분이 있었다.</p>
<p>이번에는 이 상태 코드들이 각각 무엇을 의미하는지부터, 특히 헷갈렸던 <strong>리다이렉션과 PRG(Post/Redirect/Get)</strong>까지 정리해보려고 한다.</p>
<hr>
<h1 id="http-상태-코드란">HTTP 상태 코드란?</h1>
<p>HTTP 상태 코드는 <strong>클라이언트가 보낸 요청을 서버가 어떻게 처리했는지 응답에서 알려주는 기능</strong>이다. 6.http-status.pdf</p>
<p>크게 다섯 종류로 나뉜다.</p>
<pre><code class="language-text">1xx
→ Informational
→ 요청이 수신되어 처리 중

2xx
→ Successful
→ 요청 정상 처리

3xx
→ Redirection
→ 요청을 완료하려면 추가 행동 필요

4xx
→ Client Error
→ 클라이언트 요청 오류

5xx
→ Server Error
→ 서버 내부 오류</code></pre>
<p>처음에는 상태 코드 하나하나를 전부 외워야 하나 싶었다.</p>
<p>그런데 재미있는 규칙이 하나 있었다.</p>
<hr>
<h1 id="모르는-상태-코드가-나타나면-어떻게-할까">모르는 상태 코드가 나타나면 어떻게 할까?</h1>
<p>HTTP에는 정말 많은 상태 코드가 있다.</p>
<p>그렇다면 서버가 내가 모르는 상태 코드를 반환하면 어떻게 해야 할까?</p>
<p>예를 들어 이런 상태 코드가 있다고 해보자.</p>
<pre><code class="language-text">299 ???

451 ???

599 ???</code></pre>
<p>클라이언트는 세부 상태 코드를 알지 못하더라도 <strong>첫 번째 숫자를 보고 상위 상태 코드로 해석</strong>할 수 있다.</p>
<pre><code class="language-text">299 ???
→ 2xx
→ Successful

451 ???
→ 4xx
→ Client Error

599 ???
→ 5xx
→ Server Error</code></pre>
<p>덕분에 미래에 새로운 상태 코드가 추가되더라도 기존 클라이언트가 모든 상태 코드를 하나하나 알고 있을 필요는 없다. 6.http-status.pdf</p>
<hr>
<h1 id="1xx---informational">1xx - Informational</h1>
<p><code>1xx</code>는 요청이 수신되어 처리 중이라는 의미다.</p>
<p>그런데 강의에서는 거의 사용하지 않기 때문에 자세한 내용은 생략했다. 6.http-status.pdf</p>
<p>그래서 이번 글에서도 바로 <code>2xx</code>부터 살펴보자.</p>
<hr>
<h1 id="2xx---successful">2xx - Successful</h1>
<p><code>2xx</code>는 클라이언트의 요청을 서버가 <strong>성공적으로 처리했다</strong>는 의미다.</p>
<p>이번에 살펴본 대표적인 상태 코드는 다음 네 가지다. 6.http-status.pdf</p>
<pre><code class="language-text">200 OK

201 Created

202 Accepted

204 No Content</code></pre>
<p>그런데 전부 성공인데 왜 이렇게 나눠놨을까?</p>
<p>각각 <strong>어떻게 성공했는지</strong>가 다르기 때문이다.</p>
<hr>
<h1 id="200-ok">200 OK</h1>
<p>가장 익숙한 상태 코드다.</p>
<pre><code class="language-text">200 OK</code></pre>
<p>말 그대로 <strong>요청을 정상적으로 처리했다</strong>는 의미다.</p>
<p>예를 들어 회원 정보를 요청한다고 해보자.</p>
<pre><code class="language-http">GET /members/100 HTTP/1.1
Host: localhost:8080</code></pre>
<p>서버가 정상적으로 회원을 찾았다면</p>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json

{
  &quot;username&quot;: &quot;young&quot;,
  &quot;age&quot;: 20
}</code></pre>
<p>처럼 응답할 수 있다.</p>
<p>가장 기본적인 성공 응답이다. 6.http-status.pdf</p>
<hr>
<h1 id="201-created">201 Created</h1>
<p><code>201 Created</code>도 요청 성공을 의미하지만 한 가지 의미가 추가된다.</p>
<blockquote>
<p><strong>요청이 성공해서 새로운 리소스가 생성됐다.</strong></p>
</blockquote>
<p>예를 들어 새로운 회원을 등록해보자.</p>
<pre><code class="language-http">POST /members HTTP/1.1
Content-Type: application/json

{
  &quot;username&quot;: &quot;young&quot;,
  &quot;age&quot;: 20
}</code></pre>
<p>서버가 새로운 회원을 생성하고 ID <code>100</code>을 부여했다면 다음과 같이 응답할 수 있다.</p>
<pre><code class="language-http">HTTP/1.1 201 Created
Location: /members/100</code></pre>
<p>여기서 눈에 들어온 것이 <code>Location</code> 헤더였다.</p>
<pre><code class="language-text">Location: /members/100</code></pre>
<p>지난번 HTTP API 설계에서 <code>POST</code> 기반 Collection 방식을 공부하면서 <strong>새로운 리소스의 URI를 서버가 만들어준다</strong>고 했었다.</p>
<p>그때의 이야기가 여기서 연결됐다.</p>
<pre><code class="language-text">POST /members
      ↓
서버가 회원 생성
      ↓
/members/100
      ↓
201 Created
Location: /members/100</code></pre>
<p>즉 <code>Location</code> 헤더를 통해 <strong>생성된 리소스를 식별할 수 있다.</strong> 6.http-status.pdf</p>
<hr>
<h1 id="202-accepted">202 Accepted</h1>
<p><code>202 Accepted</code>는 조금 특이하다.</p>
<pre><code class="language-text">요청은 받았다.

그런데 아직 처리가 끝난 것은 아니다.</code></pre>
<p>즉 <strong>요청이 접수되었지만 처리가 완료되지 않은 상태</strong>다.</p>
<p>예를 들어 바로 처리하지 않고 나중에 수행하는 배치 작업이 있을 수 있다.</p>
<pre><code class="language-text">사용자 요청
↓
202 Accepted
↓
요청 접수
↓
1시간 뒤 Batch Process 실행
↓
실제 처리</code></pre>
<p>이런 상황에서 사용할 수 있다. 6.http-status.pdf</p>
<p>처음에는 <code>2xx = 작업 완료</code>라고 생각했는데 정확히는 <strong>요청을 성공적으로 처리했다는 범주</strong>이고, 실제 작업 완료 시점은 상태 코드마다 조금 다를 수 있다는 것을 알 수 있었다.</p>
<hr>
<h1 id="204-no-content">204 No Content</h1>
<p><code>204 No Content</code>는 이름 그대로다.</p>
<blockquote>
<p><strong>서버가 요청을 성공적으로 처리했지만 응답 본문에 보낼 데이터가 없다.</strong></p>
</blockquote>
<p>예를 들어 웹 문서 편집기에서 <code>Save</code> 버튼을 눌렀다고 해보자.</p>
<pre><code class="language-text">문서 작성

↓ Save

서버 저장 성공

↓ ?

현재 화면 그대로 유지</code></pre>
<p>저장이 성공했다는 사실만 알면 되지 굳이 새로운 화면이나 응답 데이터를 받을 필요는 없을 수 있다.</p>
<p>이때</p>
<pre><code class="language-http">HTTP/1.1 204 No Content</code></pre>
<p>만으로도 클라이언트는 성공했다는 사실을 알 수 있다. 6.http-status.pdf</p>
<hr>
<h1 id="3xx---redirection">3xx - Redirection</h1>
<p>이번 내용에서 가장 헷갈렸고, 동시에 가장 재미있었던 부분은 <code>3xx</code>였다.</p>
<p><code>3xx</code>는 <strong>요청을 완료하기 위해 클라이언트의 추가 조치가 필요하다</strong>는 의미다. 6.http-status.pdf</p>
<p>대표적인 상태 코드는 다음과 같다.</p>
<pre><code class="language-text">300 Multiple Choices

301 Moved Permanently

302 Found

303 See Other

304 Not Modified

307 Temporary Redirect

308 Permanent Redirect</code></pre>
<p>그런데 리다이렉션은 정확히 어떻게 동작할까?</p>
<hr>
<h1 id="리다이렉션은-어떻게-동작할까">리다이렉션은 어떻게 동작할까?</h1>
<p>예를 들어 기존 이벤트 페이지가</p>
<pre><code class="language-text">/event</code></pre>
<p>에서</p>
<pre><code class="language-text">/new-event</code></pre>
<p>로 이동했다고 해보자.</p>
<p>사용자가 기존 주소로 요청한다.</p>
<pre><code class="language-http">GET /event HTTP/1.1</code></pre>
<p>그러면 서버가 다음과 같이 응답할 수 있다.</p>
<pre><code class="language-http">HTTP/1.1 301 Moved Permanently
Location: /new-event</code></pre>
<p>웹 브라우저는 <code>3xx</code> 응답에 <code>Location</code> 헤더가 있으면 해당 위치로 <strong>자동으로 이동</strong>한다. 6.http-status.pdf</p>
<p>전체 흐름을 그려보면 다음과 같다.</p>
<pre><code class="language-text">브라우저
   │
   │ 1. GET /event
   ▼
서버
   │
   │ 2. 301 Moved Permanently
   │    Location: /new-event
   ▼
브라우저
   │
   │ 3. 자동 리다이렉트
   │
   │ 4. GET /new-event
   ▼
서버
   │
   │ 5. 200 OK
   ▼
브라우저</code></pre>
<p>즉 서버가 새로운 주소를 알려주면 브라우저가 그 주소로 다시 요청하는 구조다. 6.http-status.pdf</p>
<hr>
<h1 id="리다이렉션에도-종류가-있다">리다이렉션에도 종류가 있다</h1>
<p>리다이렉션은 크게 세 종류로 나눌 수 있다.</p>
<pre><code class="language-text">영구 리다이렉션
→ 리소스의 URI가 영구적으로 변경

일시 리다이렉션
→ 리소스의 URI가 일시적으로 변경

특수 리다이렉션
→ 결과 대신 캐시 사용</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">/members → /users</code></pre>
<p>처럼 앞으로 기존 URI를 사용하지 않을 것이라면 <strong>영구 리다이렉션</strong>이다.</p>
<p>반대로 주문이 끝난 뒤 잠시 주문 결과 화면으로 이동시키는 것처럼 원래 리소스 자체의 URI가 영구적으로 바뀐 것이 아니라면 <strong>일시 리다이렉션</strong>을 사용한다. 6.http-status.pdf</p>
<hr>
<h1 id="영구-리다이렉션---301과-308">영구 리다이렉션 - 301과 308</h1>
<p>영구 리다이렉션에는 대표적으로 두 가지가 있다.</p>
<pre><code class="language-text">301 Moved Permanently

308 Permanent Redirect</code></pre>
<p>둘 다</p>
<blockquote>
<p><strong>리소스의 URI가 영구적으로 이동했다.</strong></p>
</blockquote>
<p>는 의미다.</p>
<p>검색 엔진도 새로운 URL을 인지해야 한다. 6.http-status.pdf</p>
<p>그런데 중요한 차이가 하나 있다.</p>
<hr>
<h1 id="301-moved-permanently">301 Moved Permanently</h1>
<p><code>301</code>에서는 리다이렉트 과정에서 <strong>요청 메서드가 GET으로 변경되고 본문이 제거될 수 있다.</strong></p>
<p>예를 들어 처음 요청이</p>
<pre><code class="language-http">POST /event HTTP/1.1

name=hello&amp;age=20</code></pre>
<p>이었다고 해보자.</p>
<p>서버가</p>
<pre><code class="language-http">HTTP/1.1 301 Moved Permanently
Location: /new-event</code></pre>
<p>를 반환하면 다음 요청이</p>
<pre><code class="language-http">GET /new-event HTTP/1.1</code></pre>
<p>처럼 변경될 수 있다.</p>
<pre><code class="language-text">POST /event
+ Message Body

↓ 301

GET /new-event
Body 제거 가능</code></pre>
<p>강의 예제에서도 POST 요청이 리다이렉션되면서 GET으로 바뀌고 메시지가 제거되는 흐름을 보여준다. 6.http-status.pdf</p>
<hr>
<h1 id="308-permanent-redirect">308 Permanent Redirect</h1>
<p><code>308</code>은 영구 이동이라는 점에서는 <code>301</code>과 같다.</p>
<p>하지만 차이가 있다.</p>
<blockquote>
<p><strong>요청 메서드와 메시지 본문을 유지한다.</strong></p>
</blockquote>
<pre><code class="language-text">POST /event
Body 존재

↓ 308

POST /new-event
Body 유지</code></pre>
<p>즉 처음 POST였다면 리다이렉트된 요청도 POST다. 6.http-status.pdf</p>
<p>정리하면</p>
<pre><code class="language-text">301
→ 영구 이동
→ GET으로 변경될 수 있음
→ Body 제거될 수 있음

308
→ 영구 이동
→ Method 유지
→ Body 유지</code></pre>
<hr>
<h1 id="일시적인-리다이렉션---302-307-303">일시적인 리다이렉션 - 302, 307, 303</h1>
<p>이번에는 URI가 <strong>일시적으로 변경되는 경우</strong>다.</p>
<p>대표적으로 세 가지가 있다.</p>
<pre><code class="language-text">302 Found

307 Temporary Redirect

303 See Other</code></pre>
<p>검색 엔진 등에서는 원래 URL 자체가 영구적으로 변경됐다고 판단하면 안 된다. 6.http-status.pdf</p>
<p>그리고 세 상태 코드의 가장 중요한 차이는 <strong>리다이렉트할 때 HTTP Method를 어떻게 처리하느냐</strong>다.</p>
<hr>
<h1 id="302-found">302 Found</h1>
<p><code>302 Found</code>는 리다이렉트 과정에서</p>
<pre><code class="language-text">요청 Method가 GET으로 변경될 수 있고

Message Body가 제거될 수 있다.</code></pre>
<p>예를 들어</p>
<pre><code class="language-text">POST

↓ 302

GET으로 변경될 수 있음</code></pre>
<p>이다.</p>
<p>여기서 <strong>&#39;될 수 있다&#39;</strong>가 중요하다.</p>
<hr>
<h1 id="307-temporary-redirect">307 Temporary Redirect</h1>
<p><code>307</code>은 <code>302</code>와 같은 일시적인 리다이렉션이지만 동작이 더 명확하다.</p>
<pre><code class="language-text">POST

↓ 307

POST</code></pre>
<p><strong>요청 메서드와 본문을 유지해야 한다.</strong></p>
<p>즉 요청 메서드를 변경하면 안 된다.</p>
<hr>
<h1 id="303-see-other">303 See Other</h1>
<p><code>303</code> 역시 일시적인 리다이렉션이지만</p>
<blockquote>
<p><strong>리다이렉트된 요청을 GET으로 변경한다.</strong></p>
</blockquote>
<pre><code class="language-text">POST

↓ 303

GET</code></pre>
<p>이렇게 정리할 수 있다.</p>
<pre><code class="language-text">302 Found
→ GET으로 변경될 수 있음

307 Temporary Redirect
→ Method와 Body 유지

303 See Other
→ GET으로 변경</code></pre>
<p>그런데 여기까지 공부하니 이런 생각이 들었다.</p>
<blockquote>
<p><strong>&quot;POST를 굳이 GET으로 바꾸는 상황이 왜 필요한 거지?&quot;</strong></p>
</blockquote>
<p>그 이유를 이해하는 데 가장 좋은 예제가 바로 <strong>주문</strong>이었다.</p>
<hr>
<h1 id="post로-주문하고-새로고침하면-무슨-일이-생길까">POST로 주문하고 새로고침하면 무슨 일이 생길까?</h1>
<p>쇼핑몰에서 마우스를 하나 주문한다고 해보자.</p>
<pre><code class="language-http">POST /order HTTP/1.1

itemId=mouse&amp;count=1</code></pre>
<p>서버는 주문을 저장하고 결과 페이지를 반환한다.</p>
<pre><code class="language-text">POST /order

↓ 주문 저장

DB
mouse 1개

↓

200 OK
주문 완료</code></pre>
<p>여기까지는 아무 문제가 없다.</p>
<p>그런데 사용자가 결과 화면에서 <strong>새로고침</strong>을 누르면 어떻게 될까?</p>
<p>새로고침은 마지막 요청을 다시 수행한다.</p>
<p>마지막 요청은</p>
<pre><code class="language-text">POST /order</code></pre>
<p>였다.</p>
<p>그러면 같은 POST가 한 번 더 서버로 전달될 수 있다.</p>
<pre><code class="language-text">POST /order
↓
mouse 1개 주문

새로고침

↓

POST /order
↓
mouse 1개 또 주문</code></pre>
<p>사용자는 한 번 주문했다고 생각했는데 서버에는 주문 데이터가 두 건 들어갈 수 있다. 강의자료에서도 PRG를 적용하기 전 POST 주문 결과 화면에서 새로고침하면 동일한 주문 데이터가 다시 저장되는 흐름을 보여준다. 6.http-status.pdf</p>
<p>이걸 어떻게 막을까?</p>
<hr>
<h1 id="prg-post--redirect--get">PRG: Post / Redirect / Get</h1>
<p>여기서 <strong>PRG(Post/Redirect/Get)</strong> 패턴을 사용한다.</p>
<p>이름 그대로다.</p>
<pre><code class="language-text">POST
↓
Redirect
↓
GET</code></pre>
<p>주문 과정을 다시 만들어보자.</p>
<p>먼저 사용자가 주문한다.</p>
<pre><code class="language-http">POST /order HTTP/1.1

itemId=mouse&amp;count=1</code></pre>
<p>서버는 주문 데이터를 저장한다.</p>
<pre><code class="language-text">DB

19번 주문
mouse 1개</code></pre>
<p>그런데 이번에는 바로 <code>200 OK</code>로 주문 완료 화면을 반환하지 않는다.</p>
<p>대신 리다이렉션 응답을 준다.</p>
<pre><code class="language-http">HTTP/1.1 302 Found
Location: /order-result/19</code></pre>
<p>그러면 브라우저가 자동으로 다음 요청을 보낸다.</p>
<pre><code class="language-http">GET /order-result/19 HTTP/1.1</code></pre>
<p>그리고 서버가 결과 화면을 반환한다.</p>
<pre><code class="language-http">HTTP/1.1 200 OK

&lt;html&gt;주문완료&lt;/html&gt;</code></pre>
<p>전체 흐름은 다음과 같다.</p>
<pre><code class="language-text">POST /order
     │
     ▼
주문 데이터 저장
     │
     ▼
302 Found
Location: /order-result/19
     │
     ▼
자동 리다이렉트
     │
     ▼
GET /order-result/19
     │
     ▼
200 OK
주문 완료</code></pre>
<p>이제 브라우저가 기억하는 마지막 요청은</p>
<pre><code class="language-text">POST /order</code></pre>
<p>가 아니라</p>
<pre><code class="language-text">GET /order-result/19</code></pre>
<p>이다.</p>
<p>따라서 사용자가 새로고침을 눌러도</p>
<pre><code class="language-text">GET /order-result/19

GET /order-result/19

GET /order-result/19</code></pre>
<p>처럼 결과 화면만 다시 조회한다.</p>
<p><strong>POST 주문 요청이 다시 실행되지 않는다.</strong> 6.http-status.pdf</p>
<hr>
<h1 id="prg는-단순히-화면을-이동시키는-기술이-아니었다">PRG는 단순히 화면을 이동시키는 기술이 아니었다</h1>
<p>처음에는 Redirect라고 하면 그냥</p>
<pre><code class="language-text">A 페이지에서 B 페이지로 이동</code></pre>
<p>정도로만 생각했다.</p>
<p>그런데 PRG를 보니 <strong>HTTP Method의 의미와 브라우저의 동작을 이용해서 중복 요청을 줄이는 패턴</strong>이었다.</p>
<p>물론 서버에서도 같은 주문 번호를 중복 처리하지 않는 등의 방어 로직이 필요할 수 있다.</p>
<p>하지만 PRG를 적용하면 사용자가 단순히 새로고침했다는 이유만으로 POST가 다시 전송되는 문제를 크게 줄일 수 있다.</p>
<pre><code class="language-text">POST
→ 상태 변경

Redirect

GET
→ 결과 조회</code></pre>
<p>이 흐름 자체가 꽤 깔끔했다.</p>
<hr>
<h1 id="그런데-prg에서는-302-307-303-중-무엇을-써야-할까">그런데 PRG에서는 302, 307, 303 중 무엇을 써야 할까?</h1>
<p>여기서 또 문제가 생긴다.</p>
<pre><code class="language-text">302?

307?

303?</code></pre>
<p>강의에서는 이 차이를 역사적인 이유와 함께 설명했다.</p>
<p>원래 <code>302</code>의 의도는 HTTP Method를 유지하는 것이었는데, 실제 웹 브라우저들이 대부분 GET으로 변경해서 처리했다.</p>
<p>그래서 동작을 명확하게 구분하기 위해</p>
<pre><code class="language-text">307
→ Method 유지

303
→ GET으로 변경</code></pre>
<p>이 등장했다. <code>301</code>에 대응해 <code>308</code>도 등장했다. 6.http-status.pdf</p>
<p>정리하면</p>
<pre><code class="language-text">302 Found
→ GET으로 변경될 수 있음

307 Temporary Redirect
→ Method를 변경하면 안 됨

303 See Other
→ GET으로 변경</code></pre>
<p>명확한 의미만 보면 <code>307</code>, <code>303</code>을 사용하는 것이 좋지만, 현실에서는 많은 애플리케이션과 라이브러리가 <code>302</code>를 기본으로 사용하고 있다.</p>
<p>따라서 자동 리다이렉션 과정에서 GET으로 변경되어도 문제가 없다면 <code>302</code>도 많이 사용된다. 6.http-status.pdf</p>
<p>그리고 강의의 PRG 예제 역시 <code>302 Found</code>를 사용했다.</p>
<p>그래서 아까 주문 예제에서</p>
<pre><code class="language-http">200 OK</code></pre>
<p>가 아니라</p>
<pre><code class="language-http">302 Found
Location: /order-result/19</code></pre>
<p>를 반환했던 것이다.</p>
<hr>
<h1 id="특수-리다이렉션---304-not-modified">특수 리다이렉션 - 304 Not Modified</h1>
<p>리다이렉션이라고 해서 반드시 다른 URL로 이동하는 것만 있는 것은 아니었다.</p>
<p><code>304 Not Modified</code>는 <strong>캐시</strong>와 관련된다.</p>
<p>클라이언트가 이미 어떤 리소스를 가지고 있다고 해보자.</p>
<pre><code class="language-text">브라우저 캐시

image.jpg</code></pre>
<p>서버에 확인해봤는데 이 리소스가 변경되지 않았다면 서버가 데이터를 다시 전부 보내는 것은 낭비다.</p>
<p>이때 서버는</p>
<pre><code class="language-http">304 Not Modified</code></pre>
<p>를 반환할 수 있다.</p>
<p>그러면 클라이언트는</p>
<pre><code class="language-text">&quot;아, 기존 데이터가 그대로구나.&quot;</code></pre>
<p>라고 판단하고 로컬에 저장된 캐시를 재사용한다.</p>
<p>따라서 <code>304</code> 응답에는 <strong>메시지 바디를 포함하면 안 된다.</strong></p>
<p>어차피 클라이언트가 가지고 있는 데이터를 사용하기 때문이다.</p>
<p><code>304</code>는 조건부 <code>GET</code>, <code>HEAD</code> 요청에서 사용된다. 6.http-status.pdf</p>
<hr>
<h1 id="4xx---client-error">4xx - Client Error</h1>
<p>이제 에러 코드다.</p>
<p><code>4xx</code>는 <strong>클라이언트의 요청에 문제가 있는 경우</strong>다.</p>
<pre><code class="language-text">잘못된 요청

잘못된 문법

잘못된 데이터

권한 부족

존재하지 않는 리소스</code></pre>
<p>등이 해당한다.</p>
<p>여기서 굉장히 중요한 특징이 하나 있다.</p>
<blockquote>
<p><strong>같은 요청을 그대로 다시 보내도 실패한다.</strong></p>
</blockquote>
<p>이미 클라이언트가 잘못된 요청이나 데이터를 보내고 있기 때문이다. 6.http-status.pdf</p>
<pre><code class="language-text">잘못된 요청

↓ 재시도

똑같이 잘못된 요청

↓ 재시도

또 실패</code></pre>
<p>그래서 단순 재시도보다 <strong>요청 자체를 수정해야 한다.</strong></p>
<hr>
<h1 id="400-bad-request">400 Bad Request</h1>
<p><code>400 Bad Request</code>는 클라이언트가 잘못된 요청을 보내 서버가 처리할 수 없는 경우다.</p>
<p>예를 들어</p>
<pre><code class="language-text">요청 파라미터 오류

잘못된 메시지

API 스펙 불일치</code></pre>
<p>등이 있다. 6.http-status.pdf</p>
<p>이 부분에서 백엔드 개발자 입장에서는 <strong>Validation</strong>이 중요하다는 생각이 들었다.</p>
<p>클라이언트가 이상한 데이터를 보내더라도 애매하게 내부 로직까지 흘려보내는 것이 아니라 요청을 적절하게 검증하고 잘못된 요청임을 알려줘야 한다.</p>
<pre><code class="language-text">Request
↓
Validation
↓
잘못된 입력?
├─ YES → 400
└─ NO  → 비즈니스 로직</code></pre>
<hr>
<h1 id="401-unauthorized">401 Unauthorized</h1>
<p>이름 때문에 처음 보면 조금 헷갈린다.</p>
<pre><code class="language-text">401 Unauthorized</code></pre>
<p>인데 실제 의미는 <strong>인증(Authentication)이 필요하다</strong>는 것이다.</p>
<pre><code class="language-text">인증되지 않은 사용자
↓
보호된 리소스 요청
↓
401 Unauthorized</code></pre>
<p>그리고 <code>401</code> 응답에서는 <code>WWW-Authenticate</code> 헤더를 통해 인증 방법을 설명할 수 있다. 6.http-status.pdf</p>
<p>여기서 <strong>인증과 인가</strong>를 구분해야 한다.</p>
<pre><code class="language-text">인증 Authentication
→ 너 누구야?
→ 본인이 누구인지 확인
→ 로그인

인가 Authorization
→ 너 이거 해도 돼?
→ 특정 리소스에 접근할 권한 확인</code></pre>
<p>즉</p>
<pre><code class="language-text">로그인 자체가 안 됨
→ 인증 문제

로그인은 했지만 관리자 페이지 접근 불가
→ 인가 문제</code></pre>
<p>라고 생각하면 이해하기 쉽다.</p>
<p><code>Unauthorized</code>라는 이름 때문에 권한 문제처럼 보이지만 <code>401</code>은 <strong>인증되지 않은 상태</strong>를 의미한다는 점을 기억해야 한다.</p>
<hr>
<h1 id="403-forbidden">403 Forbidden</h1>
<p>그렇다면 로그인은 했는데 권한이 없다면?</p>
<pre><code class="language-text">403 Forbidden</code></pre>
<p>이다.</p>
<p>서버가 요청을 이해했고 사용자가 누구인지도 알지만 <strong>해당 요청을 승인하지 않는 것</strong>이다.</p>
<p>예를 들어</p>
<pre><code class="language-text">일반 사용자 로그인 성공

↓

관리자 페이지 접근

↓

403 Forbidden</code></pre>
<p>처럼 사용할 수 있다. 6.http-status.pdf</p>
<p>그래서 <code>401</code>과 <code>403</code>을 비교하면 다음과 같다.</p>
<pre><code class="language-text">401
→ 너 누구야?
→ 인증 필요

403
→ 네가 누군지는 아는데 이건 안 돼.
→ 권한 부족</code></pre>
<p>이렇게 생각하니 훨씬 기억하기 쉬웠다.</p>
<hr>
<h1 id="404-not-found">404 Not Found</h1>
<p><code>404 Not Found</code>는 요청한 리소스를 서버에서 찾을 수 없다는 의미다.</p>
<p>개발하다 보면 정말 자주 만나게 되는 상태 코드다.</p>
<pre><code class="language-text">GET /members/999999

↓

해당 리소스 없음

↓

404 Not Found</code></pre>
<p>보통은 클라이언트가 요청한 리소스가 서버에 존재하지 않을 때 사용한다.</p>
<p>그런데 강의에서 하나 더 알게 된 부분이 있었다.</p>
<blockquote>
<p><strong>리소스가 실제로 존재하더라도 404를 반환할 수 있다.</strong></p>
</blockquote>
<p>예를 들어 사용자가 접근 권한이 없는 리소스를 요청했다고 해보자.</p>
<p>이때 <code>403 Forbidden</code>을 반환하면 클라이언트는 적어도</p>
<pre><code class="language-text">&quot;접근은 못 하지만 이 리소스가 존재하기는 하는구나.&quot;</code></pre>
<p>라는 사실을 알 수 있다.</p>
<p>만약 <strong>리소스의 존재 자체를 클라이언트에게 숨기고 싶다면</strong> <code>404 Not Found</code>를 반환할 수도 있다.</p>
<p>따라서 <code>404</code>는 크게 다음 두 상황에서 사용할 수 있다.</p>
<pre><code class="language-text">요청한 리소스가 서버에 존재하지 않음

또는

권한이 부족한 사용자에게
해당 리소스의 존재 자체를 숨기고 싶음</code></pre>
<p>처음에는 단순히</p>
<pre><code class="language-text">404 = 없는 페이지</code></pre>
<p>정도로 생각했는데, 반드시 리소스가 물리적으로 존재하지 않는 경우에만 사용하는 상태 코드는 아니었다.</p>
<h1 id="5xx---server-error">5xx - Server Error</h1>
<p>마지막은 <code>5xx</code>다.</p>
<p><code>5xx</code>는 <strong>클라이언트 요청이 아니라 서버 문제로 오류가 발생한 경우</strong>다. 6.http-status.pdf</p>
<p><code>4xx</code>와 중요한 차이가 있다.</p>
<pre><code class="language-text">4xx

클라이언트 요청 자체에 문제
↓
똑같이 재시도
↓
실패</code></pre>
<p>반면</p>
<pre><code class="language-text">5xx

서버에 일시적인 문제
↓
시간이 흐르거나 서버가 복구
↓
재시도
↓
성공할 수도 있음</code></pre>
<p>이다.</p>
<p>그래서</p>
<blockquote>
<p><strong>400 계열은 요청을 수정해야 하고, 500 계열은 서버가 복구되면 동일한 요청이 성공할 가능성이 있다.</strong></p>
</blockquote>
<p>라는 차이가 생긴다.</p>
<p>물론 모든 <code>5xx</code>가 재시도하면 반드시 성공한다는 뜻은 아니다.</p>
<hr>
<h1 id="500-internal-server-error">500 Internal Server Error</h1>
<p>가장 대표적인 서버 오류다.</p>
<pre><code class="language-text">500 Internal Server Error</code></pre>
<p>서버 내부에서 문제가 발생한 경우 사용한다.</p>
<p>강의에서는 서버 내부 문제로 오류가 발생했고 구체적으로 분류하기 애매한 경우 <code>500</code>을 사용할 수 있다고 설명한다. 6.http-status.pdf</p>
<p>그런데 여기서 정말 중요하게 느껴진 부분이 있었다.</p>
<blockquote>
<p><strong>비즈니스 로직에서 거절된 상황을 무조건 500으로 처리하면 안 된다.</strong></p>
</blockquote>
<p>예를 들어 어떤 콘텐츠가</p>
<pre><code class="language-text">19세 이상만 참여 가능</code></pre>
<p>하다고 해보자.</p>
<p>그런데 15세 사용자가 참여를 요청했다.</p>
<pre><code class="language-text">15세 사용자
↓
19세 이상 콘텐츠 참여 요청
↓
500 Internal Server Error</code></pre>
<p>이건 이상하다.</p>
<p>서버가 고장 난 것이 아니다.</p>
<p>서버는 정상적으로 동작했고, 단지 <strong>사용자가 비즈니스 규칙을 만족하지 못했을 뿐이다.</strong></p>
<p>따라서 이런 상황을 서버 내부 오류처럼 <code>500</code>으로 표현하면 클라이언트는</p>
<pre><code class="language-text">&quot;서버가 고장 났나?&quot;</code></pre>
<p>라고 잘못 이해할 수 있다.</p>
<p><code>5xx</code>는 정말 <strong>서버 측 문제</strong>가 있을 때 사용해야 한다.</p>
<hr>
<h1 id="503-service-unavailable">503 Service Unavailable</h1>
<p><code>503 Service Unavailable</code>은 서버가 현재 요청을 처리할 수 없는 상태다.</p>
<p>예를 들어</p>
<pre><code class="language-text">서버 일시적 과부하

예정된 서버 작업

일시적인 서비스 중단</code></pre>
<p>등이 있다.</p>
<p>그리고 서버가 언제쯤 다시 정상화될지 알고 있다면</p>
<pre><code class="language-http">Retry-After</code></pre>
<p>헤더를 이용해 클라이언트에게 복구 예상 시점을 알려줄 수도 있다. 6.http-status.pdf</p>
<hr>
<h1 id="결국-4xx와-5xx의-차이가-중요했다">결국 4xx와 5xx의 차이가 중요했다</h1>
<p>처음에는 상태 코드를 이렇게 외웠다.</p>
<pre><code class="language-text">4xx
→ 클라이언트 오류

5xx
→ 서버 오류</code></pre>
<p>그런데 이것만 외우면 실제 개발에서는 조금 부족하다.</p>
<p>이번에 이해한 핵심은 <strong>누가 다음 행동을 바꿔야 하는가</strong>였다.</p>
<pre><code class="language-text">4xx

클라이언트 요청에 문제
↓
클라이언트가 요청을 수정해야 함
↓
동일 요청 재시도는 보통 의미 없음</code></pre>
<pre><code class="language-text">5xx

서버 측 문제
↓
서버 상태가 복구되어야 함
↓
동일 요청도 이후 성공할 가능성이 있음</code></pre>
<p>이렇게 보니 상태 코드가 단순한 오류 번호가 아니라 <strong>클라이언트에게 다음에 무엇을 해야 하는지 알려주는 정보</strong>라는 생각이 들었다.</p>
<hr>
<h1 id="상태-코드를-한-번에-정리해보면">상태 코드를 한 번에 정리해보면</h1>
<p>이번에 배운 내용을 마지막으로 정리해보자.</p>
<pre><code class="language-text">HTTP Status Code
│
├── 1xx Informational
│   └── 요청 처리 중
│
├── 2xx Successful
│   ├── 200 OK
│   │   └── 요청 성공
│   ├── 201 Created
│   │   └── 새로운 리소스 생성
│   ├── 202 Accepted
│   │   └── 요청 접수, 아직 처리 중
│   └── 204 No Content
│       └── 성공했지만 응답 Body 없음
│
├── 3xx Redirection
│   ├── 301 Moved Permanently
│   │   └── 영구 이동 / GET 변경 가능
│   ├── 308 Permanent Redirect
│   │   └── 영구 이동 / Method 유지
│   ├── 302 Found
│   │   └── 일시 이동 / GET 변경 가능
│   ├── 307 Temporary Redirect
│   │   └── 일시 이동 / Method 유지
│   ├── 303 See Other
│   │   └── 일시 이동 / GET으로 변경
│   └── 304 Not Modified
│       └── 캐시 재사용
│
├── 4xx Client Error
│   ├── 400 Bad Request
│   │   └── 잘못된 요청
│   ├── 401 Unauthorized
│   │   └── 인증 필요
│   ├── 403 Forbidden
│   │   └── 권한 부족
│   └── 404 Not Found
│       └── 리소스를 찾을 수 없음
│
└── 5xx Server Error
    ├── 500 Internal Server Error
    │   └── 서버 내부 오류
    └── 503 Service Unavailable
        └── 서버가 일시적으로 요청 처리 불가</code></pre>
<hr>
<h1 id="상태-코드는-서버의-결과를-설명하는-언어였다">상태 코드는 서버의 결과를 설명하는 언어였다</h1>
<p>처음에는 상태 코드를 그냥</p>
<pre><code class="language-text">200 = 성공

404 = 없음

500 = 서버 터짐</code></pre>
<p>정도로만 알고 있었다.</p>
<p>그런데 이번 내용을 공부하면서 상태 코드도 HTTP Method와 비슷하다는 생각이 들었다.</p>
<p>HTTP Method가</p>
<blockquote>
<p><strong>&quot;클라이언트가 서버에게 무엇을 하고 싶은가?&quot;</strong></p>
</blockquote>
<p>를 표현한다면,</p>
<p>HTTP Status Code는</p>
<blockquote>
<p><strong>&quot;서버가 그 요청을 어떻게 처리했는가?&quot;</strong></p>
</blockquote>
<p>를 표현한다.</p>
<p>특히 <code>3xx</code>를 공부하면서 상태 코드가 단순한 성공/실패 표시가 아니라 <strong>클라이언트의 다음 행동까지 유도할 수 있다는 것</strong>이 재미있었다.</p>
<pre><code class="language-text">POST
↓
302 Redirect
↓
GET</code></pre>
<p>이라는 PRG 패턴만으로 브라우저 새로고침에 의한 중복 주문 문제를 줄일 수 있다는 것도 인상적이었다.</p>
<p>그리고 백엔드 개발자 입장에서는 상태 코드를 그냥 아무거나 반환해서는 안 된다는 것도 중요했다.</p>
<pre><code class="language-text">잘못된 요청인데 500

권한이 없는데 400

리소스를 생성했는데 무조건 200</code></pre>
<p>처럼 상태 코드를 잘못 사용하면 클라이언트는 서버에서 무슨 일이 일어났는지 제대로 판단하기 어렵다.</p>
<p>결국 HTTP 상태 코드도 단순히 외워야 하는 숫자가 아니라,</p>
<blockquote>
<p><strong>서버가 처리 결과를 클라이언트와 약속된 의미로 의사소통하는 방법</strong></p>
</blockquote>
<p>이라고 이해하는 것이 가장 맞는 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋은 시스템이라는 말은 어떻게 검증할 수 있을까? | 품질속성부터 품질속성 시나리오까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%A2%8B%EC%9D%80-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%B4%EB%9D%BC%EB%8A%94-%EB%A7%90%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B2%80%EC%A6%9D%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-%ED%92%88%EC%A7%88%EC%86%8D%EC%84%B1%EB%B6%80%ED%84%B0-%ED%92%88%EC%A7%88%EC%86%8D%EC%84%B1-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4%EA%B9%8C%EC%A7%80-fmzy8w9z</link>
            <guid>https://velog.io/@daehyun_lee/%EC%A2%8B%EC%9D%80-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%B4%EB%9D%BC%EB%8A%94-%EB%A7%90%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B2%80%EC%A6%9D%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C-%ED%92%88%EC%A7%88%EC%86%8D%EC%84%B1%EB%B6%80%ED%84%B0-%ED%92%88%EC%A7%88%EC%86%8D%EC%84%B1-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4%EA%B9%8C%EC%A7%80-fmzy8w9z</guid>
            <pubDate>Tue, 22 Sep 2026 04:19:28 GMT</pubDate>
            <description><![CDATA[<h1 id="좋은-시스템이라는-말은-어떻게-검증할-수-있을까--품질속성부터-품질속성-시나리오까지">좋은 시스템이라는 말은 어떻게 검증할 수 있을까? | 품질속성부터 품질속성 시나리오까지</h1>
<p>지난 Chapter에서는 소프트웨어 아키텍처가 무엇인지부터 시작해서 아키텍처 드라이버, 아키텍처 의사결정, 아키텍처 스타일과 트레이드오프까지 살펴봤다.</p>
<blockquote>
<p>🔗 <strong>이전 글</strong>
<a href="https://velog.io/@daehyun_lee/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-%EB%B0%94%EB%B2%A8%ED%83%91%EB%B6%80%ED%84%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%99%80-%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%93%9C%EC%98%A4%ED%94%84%EA%B9%8C%EC%A7%80">소프트웨어 아키텍처는 왜 필요할까? | 바벨탑부터 아키텍처 드라이버와 트레이드오프까지</a></p>
</blockquote>
<p>그 과정에서 계속 등장했던 단어가 하나 있었다.</p>
<p><strong>품질속성(Quality Attribute)</strong>이다.</p>
<pre><code class="language-text">성능이 좋아야 한다.

사용하기 쉬워야 한다.

장애가 발생해도 복구할 수 있어야 한다.

변경하기 쉬워야 한다.

보안이 좋아야 한다.

처음에는 이 정도만 정하면 충분한 것 아닌가 싶었다.

그런데 조금 생각해보면 문제가 있다.

&gt; **&quot;성능이 좋다는 게 어느 정도인데?&quot;**

응답이 10초 걸리는 시스템과 0.1초 걸리는 시스템 중 어느 시점부터 성능이 좋다고 할 수 있을까?

`사용하기 쉽다`, `변경하기 쉽다`, `가용성이 높다` 같은 표현도 마찬가지다.

이번 Chapter에서는 바로 이 문제를 다뤘다.

강의에서는 단순히 시스템이 `modifiable`하다고 표현하는 것만으로는 충분하지 않으며, 품질속성을 실제로 관찰하고 검증할 수 있는 **품질요구사항과 품질속성 시나리오**로 구체화해야 한다고 설명한다. 1-2. SA개요-QA(1).pdf

그리고 이번에는 수업 내용을 그냥 예제로만 정리하지 않고, 내가 실제로 개발하고 있는 **달려보자 러닝**을 가지고 직접 품질속성과 제약사항을 정리해봤다.

---

# 품질속성과 품질요구사항은 다르다

먼저 두 개념을 구분해야 한다.

**품질속성(Quality Attribute)**은 시스템의 특성이다.

```text
성능

가용성

사용성

보안성

수정용이성

시험용이성</code></pre>
<p>같은 것들이다.</p>
<p>강의에서는 품질속성을 양이나 질로 관찰하여 측정할 수 있는 시스템의 특성으로 설명한다. 1-2. SA개요-QA(1).pdf</p>
<p>그런데</p>
<pre><code class="language-text">우리 시스템은 수정하기 쉬워야 한다.</code></pre>
<p>라고 적는 것만으로는 요구사항으로 부족하다.</p>
<p>그래서 <strong>품질요구사항(Quality Requirement)</strong>이 필요하다.</p>
<p>품질요구사항은</p>
<blockquote>
<p><strong>시스템이 해당 품질속성을 어느 정도 수준으로 제공해야 하는가</strong></p>
</blockquote>
<p>를 나타낸다.</p>
<p>가능하면 정확한 수치까지 포함해야 한다. 1-2. SA개요-QA(1).pdf</p>
<p>예를 들어</p>
<pre><code class="language-text">품질속성

수정용이성(Modifiability)</code></pre>
<p>에서 끝나는 것이 아니라</p>
<pre><code class="language-text">품질요구사항

개발자는 새로운 경매 알고리즘을
하루 이내에 구현하고 통합할 수 있어야 한다.</code></pre>
<p>까지 내려가야 한다.</p>
<p>가용성도 마찬가지다.</p>
<pre><code class="language-text">품질속성

가용성(Availability)</code></pre>
<p>만 적는 것이 아니라</p>
<pre><code class="language-text">시스템에 오류가 발생하면
5초 이내에 사람의 개입 없이 자동 복구한다.</code></pre>
<p>처럼 만들어야 실제로 검증할 수 있다. 강의에서도 이 두 사례를 품질속성을 검증 가능한 요구사항으로 구체화한 예로 사용한다. 1-2. SA개요-QA(1).pdf</p>
<p>여기서 차이가 확실하게 보였다.</p>
<pre><code class="language-text">&quot;빠른 시스템&quot;
→ 애매함

&quot;평균 2초 이내에 응답하는 시스템&quot;
→ 검증 가능</code></pre>
<p>결국 좋은 아키텍처를 설계하려면</p>
<blockquote>
<p><strong>&quot;좋아야 한다.&quot;</strong></p>
</blockquote>
<p>에서 멈추면 안 된다.</p>
<blockquote>
<p><strong>&quot;어떤 상황에서 어느 정도로 좋아야 하는가?&quot;</strong></p>
</blockquote>
<p>까지 내려가야 한다.</p>
<hr>
<h1 id="그렇다면-어떻게-검증-가능한-요구사항으로-만들까">그렇다면 어떻게 검증 가능한 요구사항으로 만들까?</h1>
<p>여기서 <strong>품질속성 시나리오(Quality Attribute Scenario)</strong>가 등장한다.</p>
<p>품질속성 시나리오는 품질 요구사항을 검증 가능하게 표현하는 방법이다.</p>
<p>강의에서는 하나의 품질속성 시나리오를 6개의 요소로 나눈다. 1-2. SA개요-QA(1).pdf</p>
<pre><code class="language-text">1. Source
   누가 이벤트를 발생시키는가?

2. Stimulus
   어떤 이벤트가 발생하는가?

3. Environment
   어떤 상황에서 발생하는가?

4. Artifact
   무엇이 영향을 받는가?

5. Response
   시스템은 어떻게 반응하는가?

6. Response Measure
   그 반응을 어떻게 측정할 것인가?</code></pre>
<p>처음 봤을 때는 조금 복잡해 보였다.</p>
<p>그런데 결국 질문을 하나씩 던지는 것이었다.</p>
<pre><code class="language-text">누가?
↓
무슨 일을 발생시켰고?
↓
어떤 상황에서?
↓
시스템의 무엇에 영향을 줬으며?
↓
시스템은 어떻게 반응했고?
↓
그게 제대로 됐다는 걸 어떻게 확인할 것인가?</code></pre>
<p>강의자료의 성능 예시를 보면 더 명확하다.</p>
<pre><code class="language-text">Source
→ 사용자

Stimulus
→ 트랜잭션 시작

Environment
→ 정상 운영

Artifact
→ 시스템

Response
→ 트랜잭션 처리

Response Measure
→ 평균 지연시간 2초 이내</code></pre>
<p>단순히</p>
<pre><code class="language-text">&quot;성능이 좋아야 한다.&quot;</code></pre>
<p>보다 훨씬 구체적이다.</p>
<hr>
<h1 id="성능은-단순히-빠르면-되는-걸까">성능은 단순히 빠르면 되는 걸까?</h1>
<p>첫 번째로 다루는 품질속성은 <strong>성능(Performance)</strong>이다.</p>
<p>성능은 이벤트가 발생했을 때 시스템이 반응하기까지의 <strong>시간</strong>과 관련된 품질속성이다.</p>
<p>여기에는 요청이 얼마나 자주 들어오는지, 어떤 패턴으로 들어오는지, 요청부터 시스템 반응까지 얼마나 걸리는지 등이 포함된다. 1-2. SA개요-QA(1).pdf</p>
<p>강의의 예시는 다음과 같다.</p>
<pre><code class="language-text">정상 운영 중

사용자가 분당 1,000개의 트랜잭션을 발생시키고

시스템은 이를

평균 지연시간 2초 이내에 처리한다.</code></pre>
<p>이렇게 적으면 실제 테스트를 통해 만족 여부를 판단할 수 있다.</p>
<hr>
<h1 id="내-앱의-품질속성도-한번-찾아봤다">내 앱의 품질속성도 한번 찾아봤다</h1>
<p>수업에서는 직접 만들고 있는 시스템을 대상으로 품질속성과 제약사항을 정리해봤다.</p>
<p>나는 지금 개발하고 있는 <strong>달려보자 러닝</strong>을 가져왔다.</p>
<p>달려보자 러닝은 사용자의 러닝 기록을 가져와 지도 위에 경로를 표시하고, 페이스와 구간 기록 등을 분석하는 러닝 앱이다.</p>
<p>처음에는 다음과 같이 품질속성을 적었다.</p>
<pre><code class="language-text">신뢰성
→ 원본 기록과 애플리케이션이 계산한 값을 따로 구분

개인정보 보호
→ 건강 기록은 읽기만 요청

사용성
→ 지도, 구간, 비교가 명확하게 나누어져 있고 접근이 용이해야 함

응답성
→ 지도를 확대·축소하거나 이동하면
   지도와 관련 수치가 함께 갱신

복구 가능성
→ 조회가 실패하면 다시 시도하는 로직 제공

성능
→ 분석용 AI 모델 실행과 GPS 연산을 수행하여
   러닝 경로를 지도에 정확하게 표현</code></pre>
<p>그리고 시스템 자체의 제약사항도 정리했다.</p>
<pre><code class="language-text">구현 환경
→ iOS 27
→ watchOS 27 이상

개발 언어
→ Swift

데이터
→ Apple HealthKit 데이터
→ 지도, 경로, 걸음 수 등</code></pre>
<p>이렇게 적고 보니 지난 Chapter에서 배운 <strong>품질속성과 제약사항의 차이</strong>도 조금 더 현실적으로 느껴졌다.</p>
<p>예를 들어 Swift를 사용한다는 것은</p>
<pre><code class="language-text">Swift를 사용하면 시스템의 품질이 높아진다.</code></pre>
<p>라는 의미가 아니다.</p>
<p>내가 설계하면서 마음대로 바꿀 수 없는 <strong>구현상의 제약</strong>으로 둔 것이다.</p>
<p>반면</p>
<pre><code class="language-text">조회 실패 후 복구

사용하기 쉬운 UI

지도 변경에 대한 빠른 갱신</code></pre>
<p>은 시스템이 어떤 특성을 가져야 하는지에 관한 이야기다.</p>
<hr>
<h1 id="그런데-내가-적은-품질속성에도-문제가-있었다">그런데 내가 적은 품질속성에도 문제가 있었다</h1>
<p>여기서 Chapter 2의 내용이 바로 연결됐다.</p>
<p>처음 적은 요구사항을 다시 보면</p>
<pre><code class="language-text">접근이 용이해야 한다.

지도가 수치와 함께 갱신되어야 한다.

조회가 실패하면 다시 시도해야 한다.

GPS 연산을 정확히 수행해야 한다.</code></pre>
<p>라고 되어 있다.</p>
<p>그런데 이걸 보고 다시 질문해봤다.</p>
<blockquote>
<p><strong>&quot;얼마나 용이해야 하지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;지도는 몇 초 안에 갱신되어야 하지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;몇 번 다시 시도해야 하지?&quot;</strong></p>
</blockquote>
<blockquote>
<p><strong>&quot;&#39;정확한 GPS 연산&#39;은 어느 정도를 정확하다고 할까?&quot;</strong></p>
</blockquote>
<p>답하기 어려웠다.</p>
<p>즉 <strong>품질속성은 찾았지만 아직 완전한 품질요구사항으로 만든 것은 아니었다.</strong></p>
<p>이게 이번 Chapter에서 배우는 핵심이었다.</p>
<hr>
<h1 id="달려보자-러닝의-응답성을-시나리오로-바꿔보자">달려보자 러닝의 응답성을 시나리오로 바꿔보자</h1>
<p>예를 들어 내가 처음 적었던 요구사항은 이랬다.</p>
<pre><code class="language-text">응답성

범위 변경
(지도를 확대·축소 및 이동)

→ 지도와 수치가 함께 갱신</code></pre>
<p>이것을 6-part 시나리오로 바꿔보면 다음과 같이 생각할 수 있다.</p>
<pre><code class="language-text">Source
→ 사용자

Stimulus
→ 지도를 확대·축소하거나 이동

Environment
→ 러닝 기록 조회 중

Artifact
→ 지도 및 러닝 분석 화면

Response
→ 새로운 지도 범위에 맞춰
   경로와 관련 수치를 다시 계산하여 표시

Response Measure
→ 정해진 시간 이내에 화면 갱신</code></pre>
<p>여기서 마지막이 아직 부족하다.</p>
<pre><code class="language-text">정해진 시간 이내</code></pre>
<p>가 몇 초인지 정하지 않았기 때문이다.</p>
<p>예를 들어 실제 성능 측정과 사용자 테스트를 통해 목표를 정한 뒤</p>
<pre><code class="language-text">Response Measure
→ 지도 조작 후 0.5초 이내에
   지도와 관련 수치 갱신</code></pre>
<p>처럼 만들 수 있다.</p>
<p>다만 <strong>0.5초라는 수치는 현재 강의자료나 내 기존 요구사항에서 나온 값이 아니다.</strong></p>
<p>실제 앱의 목표 수치는 성능 측정을 한 뒤 정해야 한다.</p>
<p>이 구분도 중요했다.</p>
<p>시나리오를 구체적으로 만든다고 해서 근거 없는 숫자를 아무렇게나 붙이면 검증 가능한 요구사항처럼 보일 뿐, 좋은 요구사항이 되는 것은 아니기 때문이다.</p>
<hr>
<h1 id="성능도-다시-생각해봤다">성능도 다시 생각해봤다</h1>
<p>처음에는 달려보자 러닝의 성능을 이렇게 적었다.</p>
<pre><code class="language-text">분석용 AI 모델 실행과
GPS 연산을 정확히 수행하여
지도에 그려내야 한다.</code></pre>
<p>그런데 다시 보니 여기에는 사실 여러 품질 문제가 섞여 있다.</p>
<pre><code class="language-text">AI 모델 추론 시간
→ 성능

GPS 데이터 처리 시간
→ 성능

지도 렌더링 시간
→ 성능

경로와 실제 기록의 일치 여부
→ 정확성/신뢰성과 관련된 문제</code></pre>
<p>즉 <code>빠르게 처리한다</code>와 <code>정확하게 처리한다</code>는 같은 말이 아니다.</p>
<p>그래서 성능 요구사항이라면 오히려</p>
<pre><code class="language-text">러닝 기록 조회 요청
↓
GPS 경로 처리
↓
AI 분석
↓
지도 렌더링
↓
사용자에게 결과 표시</code></pre>
<p>과정에서 <strong>얼마의 시간이 허용되는지</strong>를 정하는 것이 더 적절하다.</p>
<p>강의에서도 성능은 이벤트가 발생했을 때 시스템이 얼마나 빨리 반응하는가와 관련된 품질속성으로 정의한다. 1-2. SA개요-QA(1).pdf</p>
<hr>
<h1 id="수정용이성modifiability">수정용이성(Modifiability)</h1>
<p>두 번째 품질속성은 <strong>수정용이성</strong>이다.</p>
<p>수정용이성은 소프트웨어가 변경을 얼마나 쉽게 수용할 수 있는지, 즉 <strong>변경 비용</strong>과 관련된다. 1-2. SA개요-QA(1).pdf</p>
<p>변경 대상은 단순히 기능만 있는 것이 아니다.</p>
<pre><code class="language-text">기능

플랫폼

하드웨어

운영체제

미들웨어

외부 시스템

프로토콜

심지어 다른 품질속성</code></pre>
<p>까지 변경될 수 있다.</p>
<p>강의에서는 UI 변경을 예로 들었다.</p>
<pre><code class="language-text">Source
→ 개발자

Stimulus
→ UI 변경 요청

Environment
→ 설계 시점

Artifact
→ 코드

Response
→ 변경 및 단위시험 완료

Response Measure
→ 3시간 이내</code></pre>
<p>이렇게 표현하면</p>
<pre><code class="language-text">&quot;수정하기 쉬운 시스템&quot;</code></pre>
<p>이라는 추상적인 목표가 실제로 검증 가능한 요구사항으로 바뀐다. 1-2. SA개요-QA(1).pdf</p>
<hr>
<h1 id="달려보자-러닝도-변경을-피할-수-없다">달려보자 러닝도 변경을 피할 수 없다</h1>
<p>이 부분은 내 앱을 생각하면서 꽤 와닿았다.</p>
<p>지금도 앞으로 추가하고 싶은 기능이 많다.</p>
<pre><code class="language-text">AI 러닝 코치

케이던스 분석

메트로놈

러닝 기록 재생

챌린지

Apple Watch 단독 러닝</code></pre>
<p>지금 하나의 화면이나 클래스에 모든 로직을 몰아넣는다면 기능 하나를 추가할 때 기존 코드 전체를 건드려야 할 수도 있다.</p>
<p>반대로</p>
<pre><code class="language-text">HealthKit 데이터 접근

GPS/경로 처리

AI 분석

지도 표현

UI</code></pre>
<p>의 책임을 적절히 분리해두면 변경의 영향 범위를 줄일 수 있다.</p>
<p>Chapter 1에서 계속 이야기했던</p>
<blockquote>
<p><strong>&quot;변경 가능성을 생각해야 한다.&quot;</strong></p>
</blockquote>
<p>라는 말이 여기서 다시 연결됐다.</p>
<hr>
<h1 id="가용성availability">가용성(Availability)</h1>
<p>다음은 <strong>가용성</strong>이다.</p>
<p>가용성은 사용자가 필요할 때 시스템이 작업을 수행할 준비가 되어 있는 정도와 관련된다.</p>
<p>그리고 단순히</p>
<pre><code class="language-text">&quot;고장이 잘 안 나는 시스템&quot;</code></pre>
<p>만 의미하지 않는다.</p>
<p>문제가 발생했을 때 <strong>얼마나 잘 복구하는가</strong>까지 포함한다.</p>
<p>강의에서는 이를 <code>신뢰성 + 회복</code>이라는 관점으로 설명하고, MTBF와 MTTR을 이용해 안정 상태 가용성을 설명한다. 1-2. SA개요-QA(1).pdf</p>
<pre><code class="language-text">Availability

           MTBF
= ----------------------
     MTBF + MTTR</code></pre>
<p>여기서</p>
<pre><code class="language-text">MTBF
→ Mean Time Between Failure
→ 평균 무고장 시간

MTTR
→ Mean Time To Repair
→ 평균 복구 시간</code></pre>
<p>이다.</p>
<p>즉 장애가 적게 발생하는 것도 중요하지만, 장애가 발생했을 때 빠르게 복구하는 것도 중요하다.</p>
<hr>
<h1 id="내가-적은-복구-가능성도-여기에-연결됐다">내가 적은 &#39;복구 가능성&#39;도 여기에 연결됐다</h1>
<p>내 달려보자 러닝 요구사항에는 이런 내용이 있었다.</p>
<pre><code class="language-text">복구 가능성

조회 실패 시 다시 시도하는 로직</code></pre>
<p>처음에는 이것을 별개의 품질속성처럼 적었다.</p>
<p>그런데 강의에서 가용성을 배우고 나니 <strong>장애나 실패 상황에서 서비스를 다시 정상적으로 사용할 수 있게 만드는 가용성의 관점으로 구체화할 수 있겠다</strong>는 생각이 들었다.</p>
<p>예를 들어 HealthKit에서 운동 기록을 가져오는 중 일시적인 실패가 발생했다고 해보자.</p>
<pre><code class="language-text">Source
→ 외부 데이터 소스 / 시스템

Stimulus
→ 운동 기록 조회 실패

Environment
→ 정상적인 기록 조회 과정

Artifact
→ 기록 조회 기능

Response
→ 실패를 감지하고 재시도 또는 사용자에게 복구 가능한 상태 제공

Response Measure
→ 재시도 횟수와 복구 시간</code></pre>
<p>역시 여기에서도</p>
<pre><code class="language-text">몇 번 재시도할 것인지

얼마 안에 복구할 것인지</code></pre>
<p>를 결정해야 진짜 품질요구사항이 된다.</p>
<p>강의의 가용성 시나리오도 시스템 crash 발생 후 운영자에게 알리고 <strong>5초 이내에 사람의 개입 없이 자동 복구</strong>하는 식으로 측정 가능한 조건을 둔다. 1-2. SA개요-QA(1).pdf</p>
<hr>
<h1 id="사용성usability">사용성(Usability)</h1>
<p>사용성은 사용자가 원하는 작업을 얼마나 쉽게 수행할 수 있는지와 관련된다.</p>
<p>강의에서는 사용성을 단순히</p>
<pre><code class="language-text">UI가 예쁘다.</code></pre>
<p>라는 문제로 다루지 않는다.</p>
<pre><code class="language-text">새로운 기능을 쉽게 배울 수 있는가?

작업을 효율적으로 수행할 수 있는가?

사용자 실수의 영향을 줄일 수 있는가?

사용자의 필요에 맞게 시스템이 지원하는가?

사용자가 시스템을 신뢰할 수 있는가?</code></pre>
<p>등이 모두 사용성에 포함된다. 1-2. SA개요-QA(1).pdf</p>
<p>이 부분도 내 앱에서 중요하다.</p>
<hr>
<h1 id="달려보자-러닝에서-사용성이-중요한-이유">달려보자 러닝에서 사용성이 중요한 이유</h1>
<p>내가 처음 작성한 사용성 요구사항은 다음과 같았다.</p>
<pre><code class="language-text">지도, 구간, 비교가 명확하게 나누어져 있고
접근이 용이해야 한다.</code></pre>
<p>러닝이 끝난 뒤 사용자가 기록을 확인한다고 생각해보자.</p>
<p>사용자는</p>
<pre><code class="language-text">전체 기록

지도

구간별 페이스

심박

이전 기록과 비교</code></pre>
<p>같은 정보를 보고 싶을 수 있다.</p>
<p>그런데 이 기능들을 아무리 많이 제공해도 원하는 정보를 찾기 위해 화면을 계속 내려가거나 여러 단계를 거쳐야 한다면 사용하기 불편하다.</p>
<p>그래서 단순히 기능이 존재하는가보다</p>
<pre><code class="language-text">사용자가 원하는 정보에
얼마나 쉽게 도달할 수 있는가?</code></pre>
<p>도 중요한 품질이 된다.</p>
<p>그리고 여기에서도</p>
<pre><code class="language-text">&quot;접근이 용이하다.&quot;</code></pre>
<p>만으로는 부족하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Source
→ 러닝을 완료한 사용자

Stimulus
→ 구간 기록 확인 요청

Environment
→ 러닝 결과 확인 화면

Artifact
→ 기록 분석 UI

Response
→ 구간별 기록 화면 제공

Response Measure
→ 사용자가 정해진 조작 횟수 이내에
   구간 기록에 접근</code></pre>
<p>처럼 구체화할 수 있다.</p>
<p>실제 조작 횟수 역시 사용자 테스트 등을 통해 정해야 한다.</p>
<hr>
<h1 id="사용성과-수정용이성은-서로-연결될-수도-있다">사용성과 수정용이성은 서로 연결될 수도 있다</h1>
<p>여기서 재미있는 부분이 하나 있었다.</p>
<p>좋은 UI를 만들려면 계속 실험해야 한다.</p>
<pre><code class="language-text">이 탭이 더 편한가?

버튼 위치를 바꾸면 어떨까?

정보 구조를 나누는 것이 좋을까?

한 화면에 보여주는 것이 좋을까?</code></pre>
<p>그러려면 UI를 빠르게 변경할 수 있어야 한다.</p>
<p>강의에서도 사용자 인터페이스를 실험하기 위해 UI 책임을 특정 위치에 지역화하고 다른 부분으로 변경이 퍼지는 것을 줄이는 등 <strong>수정용이성이 사용성을 보완할 수 있다</strong>고 설명한다. 1-2. SA개요-QA(1).pdf</p>
<p>이 부분은 실제로 앱 UI를 계속 수정하고 있는 입장에서 꽤 현실적으로 느껴졌다.</p>
<p>좋은 사용성을 만들기 위해서는 역설적으로 <strong>UI를 계속 바꿀 수 있는 구조</strong>도 필요하다.</p>
<hr>
<h1 id="시험용이성testability">시험용이성(Testability)</h1>
<p>시험용이성은 테스트를 통해 시스템의 결함을 얼마나 쉽게 찾아낼 수 있는가와 관련된 품질속성이다.</p>
<p>이를 위해서는 시스템의 입력을 통제할 수 있고, 그 결과로 나온 출력이나 내부 상태를 관찰할 수 있어야 한다. 1-2. SA개요-QA(1).pdf</p>
<p>강의에서는 다음과 같은 시나리오를 사용했다.</p>
<pre><code class="language-text">Source
→ 단위시험자

Stimulus
→ 단위시험 수행

Environment
→ 컴포넌트 개발 시점

Artifact
→ 코드

Response
→ 컴포넌트의 인터페이스와 출력을 관찰 가능

Response Measure
→ 3시간 이내 85% 경로 커버리지 달성</code></pre>
<p>이렇게 하면</p>
<pre><code class="language-text">&quot;테스트하기 쉬워야 한다.&quot;</code></pre>
<p>라는 말도 측정할 수 있는 요구사항으로 바뀐다. 1-2. SA개요-QA(1).pdf</p>
<hr>
<h1 id="보안성security">보안성(Security)</h1>
<p>보안성은 권한이 없는 접근으로부터 데이터와 정보를 보호하면서, 권한을 가진 사용자에게는 정상적인 접근을 허용하는 능력이다.</p>
<p>강의에서는 보안의 세 가지 중요한 특징인 <strong>CIA</strong>를 다뤘다. 1-2. SA개요-QA(1).pdf</p>
<pre><code class="language-text">Confidentiality
→ 기밀성
→ 권한 없는 접근으로부터 보호

Integrity
→ 무결성
→ 권한 없이 데이터를 변경하지 못하도록 보호

Availability
→ 가용성
→ 정상적인 사용자가 시스템을 사용할 수 있도록 보장</code></pre>
<p>그리고 이를 지원하는 방법으로 인증, 권한 부여, 부인방지 등이 등장한다.</p>
<hr>
<h1 id="내-앱에서는-건강-데이터가-가장-먼저-떠올랐다">내 앱에서는 건강 데이터가 가장 먼저 떠올랐다</h1>
<p>달려보자 러닝에서는 Apple HealthKit의 운동 데이터를 사용한다.</p>
<p>그래서 처음 제약과 품질속성을 정리할 때 다음 내용을 넣었다.</p>
<pre><code class="language-text">개인정보 보호

→ 건강 기록은 읽기만 요청</code></pre>
<p>사용자의 건강 데이터는 앱 기능을 위해 필요하지만, 앱에서 불필요하게 변경 권한까지 가져갈 필요는 없다고 판단했다.</p>
<p>즉</p>
<pre><code class="language-text">필요한 데이터는 읽는다.

필요하지 않은 권한은 요구하지 않는다.</code></pre>
<p>라는 방향이다.</p>
<p>다만 이것도 강의의 6-part 시나리오 관점에서 보면 더 구체적인 보안 요구사항으로 발전시킬 필요가 있다.</p>
<p>지금 단계에서는 <strong>내 프로젝트에서 보안과 개인정보 보호를 고려하며 둔 설계 방향</strong>이라고 보는 편이 정확하다.</p>
<hr>
<h1 id="상호운영성interoperability">상호운영성(Interoperability)</h1>
<p>마지막으로 <strong>상호운영성</strong>도 등장한다.</p>
<p>상호운영성은 둘 이상의 시스템이 특정한 컨텍스트에서 인터페이스를 통해 <strong>의미 있는 정보를 교환하고 올바르게 해석할 수 있는 능력</strong>이다.</p>
<p>단순히 데이터를 주고받을 수 있는 것만으로는 충분하지 않다.</p>
<pre><code class="language-text">데이터를 주고받을 수 있다.
→ 구문론적 상호운영성

받은 데이터의 의미까지 올바르게 이해한다.
→ 의미론적 상호운영성</code></pre>
<p>까지 포함한다. 1-2. SA개요-QA(1).pdf</p>
<p>이 부분도 달려보자 러닝과 연결할 수 있었다.</p>
<hr>
<h1 id="내-앱은-혼자-존재하는-시스템이-아니었다">내 앱은 혼자 존재하는 시스템이 아니었다</h1>
<p>과제로 달려보자 러닝의 <strong>Context Diagram</strong>도 처음 그려봤다.</p>
<p>첫 초안은 대략 이런 구조였다.</p>
<pre><code class="language-text">                   Apple Map 서버
                         ↕
                    지도 데이터
                         ↕
┌─────────────────────────────────┐
│                                 │
│          달려보자 러닝           │
│             (앱)                │
│                                 │
└─────────────────────────────────┘
            ↕              ↕
       운동 기록         운동 경로
            ↕              ↕
     iOS 파일 시스템    iOS 건강 데이터</code></pre>
<p>내가 그린 첫 다이어그램에서는 <strong>달려보자 러닝 앱을 중심으로 Apple Map 서버, iOS 건강 데이터, iOS 파일 시스템과 어떤 데이터를 주고받는지</strong> 표현했다.</p>
<p>Apple Map 서버에는 지도 데이터를 요청하고 받아온다.</p>
<pre><code class="language-text">달려보자 러닝
→ 지도 데이터 요청
→ Apple Map 서버

Apple Map 서버
→ 지도 데이터 전송
→ 달려보자 러닝</code></pre>
<p>건강 데이터와는 운동 경로 데이터를 주고받는 관계로 표현했고,</p>
<pre><code class="language-text">달려보자 러닝
↔ iOS 건강 데이터</code></pre>
<p>파일 시스템에는 운동 기록을 저장하고 다시 불러오는 관계로 표현했다.</p>
<pre><code class="language-text">달려보자 러닝
↔ iOS 파일 시스템</code></pre>
<p>이 다이어그램을 그리면서 한 가지가 보였다.</p>
<blockquote>
<p><strong>내가 만드는 앱만 잘 동작한다고 끝나는 것이 아니구나.</strong></p>
</blockquote>
<p>지도 데이터, 건강 데이터, 로컬 저장소처럼 외부 요소와 데이터를 주고받아야 앱 전체 기능이 완성된다.</p>
<p>따라서 이들 사이에서 데이터가 어떤 형태로 오고 가며, 앱이 그 데이터를 어떻게 해석할지도 아키텍처에서 생각해야 한다.</p>
<p>이게 상호운영성을 배우면서 내 프로젝트와 연결된 부분이었다.</p>
<hr>
<h1 id="context-diagram을-그려보니-제약사항도-더-명확해졌다">Context Diagram을 그려보니 제약사항도 더 명확해졌다</h1>
<p>처음에는 제약사항을 그냥 목록으로 적었다.</p>
<pre><code class="language-text">iOS 27 이상

watchOS 27 이상

Swift

Apple HealthKit</code></pre>
<p>그런데 Context Diagram을 그리고 나니 이것들이 단순히 기술 목록이 아니라 시스템의 경계를 결정한다는 것이 보였다.</p>
<p>예를 들어 HealthKit을 사용한다는 것은 달려보자 러닝이 운동 기록을 완전히 독립적으로 관리하는 시스템이 아니라는 의미이기도 하다.</p>
<pre><code class="language-text">Apple 생태계
      ↓
HealthKit
      ↓
달려보자 러닝</code></pre>
<p>즉 외부 시스템과의 관계 자체가 내 앱의 아키텍처에 영향을 준다.</p>
<p>Chapter 1에서</p>
<blockquote>
<p><strong>아키텍처는 시스템 내부의 구성요소뿐 아니라 외부 환경과의 관계도 보여준다.</strong></p>
</blockquote>
<p>라고 배웠던 내용이 여기서 실제 프로젝트와 연결됐다.</p>
<hr>
<h1 id="마지막으로-품질속성의-우선순위를-정해야-한다">마지막으로 품질속성의 우선순위를 정해야 한다</h1>
<p>그런데 여기까지 하면 또 문제가 하나 생긴다.</p>
<pre><code class="language-text">성능

가용성

사용성

보안성

수정용이성

시험용이성

상호운영성</code></pre>
<p>전부 중요해 보인다.</p>
<p>그렇다면 어떤 품질속성부터 설계에 반영해야 할까?</p>
<p>Chapter 1에서 이미 비슷한 문제를 봤다.</p>
<p><strong>모든 것을 동시에 최고 수준으로 만족시킬 수는 없다.</strong></p>
<p>그래서 아키텍처 드라이버를 선정했고, 아키텍처 결정에는 트레이드오프가 존재했다.</p>
<p>Chapter 2에서는 이를 조금 더 체계적으로 정리하기 위해 <strong>품질속성 유틸리티 트리(Quality Attribute Utility Tree)</strong>를 사용한다.</p>
<hr>
<h1 id="품질속성-유틸리티-트리">품질속성 유틸리티 트리</h1>
<p>Utility는 시스템의 전반적인 <code>goodness</code>, 즉 시스템이 얼마나 유용하고 좋은지를 나타낸다.</p>
<p>그리고 품질속성 시나리오에 우선순위를 부여한다.</p>
<p>강의에서는 두 가지 기준을 사용한다. 1-2. SA개요-QA(1).pdf</p>
<pre><code class="language-text">X
→ 시나리오의 중요도

Y
→ 시나리오 달성의 예상 난이도</code></pre>
<p>각각</p>
<pre><code class="language-text">H = High
M = Medium
L = Low</code></pre>
<p>로 평가한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">Utility
│
├── Performance
│   └── 지도 조작 후 빠른 갱신
│       [중요도 H / 난이도 M]
│
├── Availability
│   └── 데이터 조회 실패 후 복구
│       [H / M]
│
├── Usability
│   └── 구간 기록에 쉽게 접근
│       [H / M]
│
└── Modifiability
    └── 새로운 분석 기능 추가
        [M / H]</code></pre>
<p>같은 형태로 생각할 수 있다.</p>
<p>물론 위의 H/M 값은 <strong>달려보자 러닝에 실제로 확정한 평가가 아니라 구조를 이해하기 위한 예시</strong>다.</p>
<p>실제 과제에서는 프로젝트에서 무엇이 중요한지 판단해서 우선순위를 정해야 한다.</p>
<hr>
<h1 id="처음-작성했던-달려보자-러닝의-요구사항을-다시-보면">처음 작성했던 달려보자 러닝의 요구사항을 다시 보면</h1>
<p>수업 전에는 다음 정도로 정리했다.</p>
<pre><code class="language-text">신뢰성
→ 원본 기록과 계산 값을 구분

개인정보 보호
→ HealthKit 읽기만 요청

사용성
→ 지도 / 구간 / 비교를 명확하게 분리

응답성
→ 지도 범위 변경 시 수치와 함께 갱신

복구 가능성
→ 조회 실패 시 재시도

성능
→ AI와 GPS 연산을 수행하고 지도에 표현</code></pre>
<p>이 자체가 잘못된 것은 아니다.</p>
<p><strong>어떤 품질을 중요하게 생각하는지 찾는 첫 단계</strong>라고 볼 수 있다.</p>
<p>하지만 Chapter 2를 배우고 나면 한 단계 더 내려가야 한다.</p>
<pre><code class="language-text">품질속성 찾기
        ↓
품질요구사항으로 구체화
        ↓
6-part 시나리오 작성
        ↓
측정 가능한 Response Measure 설정
        ↓
중요도 / 난이도 평가
        ↓
아키텍처 설계에 반영</code></pre>
<p>이 과정을 거쳐야 한다.</p>
<hr>
<h1 id="그래서-품질속성-시나리오가-중요했다">그래서 품질속성 시나리오가 중요했다</h1>
<p>이번 Chapter에서 가장 크게 바뀐 생각은 <strong>좋은 시스템이라는 표현만으로는 아키텍처를 설계하기 어렵다</strong>는 것이었다.</p>
<p>예를 들어</p>
<pre><code class="language-text">&quot;빠른 앱을 만들겠다.&quot;

&quot;사용하기 쉬운 앱을 만들겠다.&quot;

&quot;안전한 앱을 만들겠다.&quot;</code></pre>
<p>라고 말하기는 쉽다.</p>
<p>문제는 이 상태에서는 성공했는지 실패했는지 판단하기 어렵다는 것이다.</p>
<p>그래서 질문을 바꿔야 한다.</p>
<pre><code class="language-text">누가?

어떤 상황에서?

어떤 이벤트를 발생시키고?

시스템의 어떤 부분이 영향을 받으며?

시스템은 어떻게 반응하고?

그 반응을 어떤 기준으로 측정할 것인가?</code></pre>
<p>이 질문에 답하면 추상적이었던 품질이 조금씩 <strong>검증 가능한 요구사항</strong>으로 바뀐다.</p>
<hr>
<h1 id="chapter-1과-chapter-2가-이제-연결됐다">Chapter 1과 Chapter 2가 이제 연결됐다</h1>
<p>Chapter 1에서는 아키텍처 설계에서 중요한 요구사항을 <strong>아키텍처 드라이버</strong>라고 배웠다.</p>
<p>그리고 아키텍처 드라이버는 주로 품질요구사항과 제약사항에서 나온다고 했다.</p>
<p>그때는</p>
<pre><code class="language-text">품질요구사항이 중요하구나.</code></pre>
<p>정도로 이해했다.</p>
<p>Chapter 2까지 오니 그 품질요구사항을 어떻게 만들어야 하는지가 보이기 시작했다.</p>
<pre><code class="language-text">시스템 요구사항
        ↓
품질속성
        ↓
품질요구사항
        ↓
Quality Attribute Scenario
        ↓
Source
Stimulus
Environment
Artifact
Response
Response Measure
        ↓
Utility Tree
        ↓
우선순위 결정
        ↓
Architectural Driver
        ↓
Architectural Decision</code></pre>
<p>결국 아키텍처는</p>
<blockquote>
<p><strong>&quot;성능이 중요합니다.&quot;</strong></p>
</blockquote>
<p>라고 말하는 데서 끝나는 것이 아니었다.</p>
<p><strong>왜 중요한지, 어떤 상황에서 필요한지, 어느 정도를 만족해야 하는지, 실제로 그것을 어떻게 검증할 것인지</strong>까지 설명할 수 있어야 했다.</p>
<p>내가 처음 작성한 달려보자 러닝의 품질속성도 아직은</p>
<pre><code class="language-text">좋아야 하는 것들의 목록</code></pre>
<p>에 가까웠다.</p>
<p>하지만 이번 수업을 통해 그 목록을 실제 아키텍처 설계에 사용할 수 있는 요구사항으로 바꾸려면 <strong>측정 가능한 시나리오</strong>가 필요하다는 것을 알게 됐다.</p>
<p>앞으로 달려보자 러닝의 아키텍처를 계속 설계하면서도</p>
<pre><code class="language-text">&quot;빠르게&quot;

&quot;정확하게&quot;

&quot;쉽게&quot;

&quot;안전하게&quot;</code></pre>
<p>같은 표현에서 멈추지 않고,</p>
<blockquote>
<p><strong>&quot;그래서 어느 상황에서, 어느 정도면 이 요구사항을 만족했다고 말할 수 있을까?&quot;</strong></p>
</blockquote>
<p>를 계속 물어봐야 할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 아키텍처는 왜 필요할까? | 바벨탑부터 아키텍처 드라이버와 트레이드오프까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-%EB%B0%94%EB%B2%A8%ED%83%91%EB%B6%80%ED%84%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%99%80-%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%93%9C%EC%98%A4%ED%94%84%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C-%EB%B0%94%EB%B2%A8%ED%83%91%EB%B6%80%ED%84%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%93%9C%EB%9D%BC%EC%9D%B4%EB%B2%84%EC%99%80-%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%93%9C%EC%98%A4%ED%94%84%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Tue, 22 Sep 2026 04:18:09 GMT</pubDate>
            <description><![CDATA[<h1 id="소프트웨어-아키텍처는-왜-필요할까--바벨탑부터-아키텍처-드라이버와-트레이드오프까지">소프트웨어 아키텍처는 왜 필요할까? | 바벨탑부터 아키텍처 드라이버와 트레이드오프까지</h1>
<p>이번 학기에 소프트웨어 아키텍처 수업을 듣기 시작했다.</p>
<p>아키텍처라는 단어 자체는 개발하면서 정말 많이 들어봤다.</p>
<pre><code class="language-text">Layered Architecture
Microservices Architecture
Event-Driven Architecture</code></pre>
<p>Spring을 공부하면서도 계층형 구조를 접했고, 프로젝트를 설계하면서 서비스를 어떻게 나눌지 고민할 때도 아키텍처라는 말을 사용했다.</p>
<p>그런데 막상</p>
<blockquote>
<p><strong>&quot;소프트웨어 아키텍처가 정확히 뭐야?&quot;</strong></p>
</blockquote>
<p>라고 물어보면 명확하게 설명하기 어려웠다.</p>
<p>단순히 프로젝트의 폴더 구조를 잘 나누는 것인지, UML로 시스템을 그리는 것인지, 아니면 어떤 프레임워크와 데이터베이스를 사용할지 결정하는 것인지도 애매했다.</p>
<p>이번 수업에서는 조금 의외의 이야기로 소프트웨어 아키텍처를 시작했다.</p>
<p>바로 <strong>바벨탑</strong>이었다.</p>
<hr>
<h1 id="바벨탑은-왜-무너졌을까">바벨탑은 왜 무너졌을까?</h1>
<p>성경의 바벨탑 이야기에서는 사람들이 하나의 언어를 사용해 거대한 탑을 만들기 시작한다.</p>
<p>하지만 서로 의사소통할 수 없게 되면서 사람들은 흩어지고 결국 바벨탑 건설도 실패한다.</p>
<p>수업에서 중요하게 본 것은 종교적인 의미 자체가 아니라 <strong>실패의 원인</strong>이었다.</p>
<p>바벨탑이 실패한 이유는 기술이나 자원이 부족했기 때문이 아니라 <strong>의사소통의 실패와 그로 인한 조직의 혼란</strong>이었다. 1-1. SA개요-SA개념.pdf</p>
<p>처음에는</p>
<blockquote>
<p><strong>&quot;소프트웨어 아키텍처 수업에서 갑자기 왜 바벨탑 이야기를 하지?&quot;</strong></p>
</blockquote>
<p>싶었다.</p>
<p>그런데 소프트웨어 개발도 생각해보면 비슷하다.</p>
<p>여러 명이 하나의 시스템을 개발한다고 해보자.</p>
<pre><code class="language-text">A 개발자
→ 주문 시스템을 이렇게 이해함

B 개발자
→ 결제 시스템을 저렇게 이해함

C 개발자
→ 전체 구조를 또 다르게 이해함</code></pre>
<p>각자 코드를 잘 작성한다고 해도 <strong>우리가 무엇을 만들고 있는지에 대한 공통된 이해가 없다면</strong> 전체 시스템은 제대로 만들어지기 어렵다.</p>
<p>그래서 수업에서 강조한 표현이 있었다.</p>
<blockquote>
<p><strong>소프트웨어 아키텍처 = 소프트웨어 개발에 대한 공통된 이해</strong></p>
</blockquote>
<p>대규모 소프트웨어 시스템에서도 의사소통의 실패는 중요한 문제가 되고, 아키텍처는 시스템을 설계하고 발전시키기 위한 공통된 이해를 제공한다. 1-1. SA개요-SA개념.pdf</p>
<p>이 이야기를 듣고 나니 아키텍처를 단순히 <strong>&quot;시스템 구조도&quot;</strong>라고 생각했던 것보다 범위가 훨씬 크다는 생각이 들었다.</p>
<hr>
<h1 id="그렇다면-소프트웨어-아키텍처란-무엇일까">그렇다면 소프트웨어 아키텍처란 무엇일까?</h1>
<p>강의에서는 소프트웨어 아키텍처가 <strong>시스템의 구성요소들 사이의 전반적인 관계와 중요한 개발 결정을 보여주는 것</strong>이라고 설명했다.</p>
<p>그리고 중요한 특징이 하나 있다.</p>
<blockquote>
<p><strong>시스템이 실제로 구축되기 전에 존재한다.</strong></p>
</blockquote>
<p>시스템을 다 만들고 나서</p>
<pre><code class="language-text">&quot;우리 시스템 구조가 이렇게 생겼네요.&quot;</code></pre>
<p>라고 정리하는 것이 아니라,</p>
<pre><code class="language-text">우리는 어떤 시스템을 만들 것인가?

어떤 구성요소가 필요한가?

이 구성요소들은 어떻게 연결될 것인가?

우리가 원하는 품질을 만족할 수 있는가?</code></pre>
<p>를 개발 전에 생각하는 것이다.</p>
<p>그래서 아키텍처는 요구되는 기능과 품질을 사전에 검토할 수 있게 해주고, 실제 개발 작업을 위한 <strong>청사진(Blueprint)</strong> 역할을 한다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="건축물의-설계도와-비슷하다">건축물의 설계도와 비슷하다</h1>
<p>이 부분은 건축물의 아키텍처와 비교하면 이해하기 쉬웠다.</p>
<p>건물을 짓는다고 해서 일단 벽돌부터 쌓지는 않는다.</p>
<p>먼저 설계가 존재한다.</p>
<pre><code class="language-text">건축물

설계
↓
원하는 기능과 특징 검토
↓
건설</code></pre>
<p>소프트웨어도 비슷하다.</p>
<pre><code class="language-text">소프트웨어

아키텍처
↓
원하는 기능과 품질 검토
↓
개발</code></pre>
<p>건축물의 아키텍처가 건물을 지탱하면서 원하는 기능과 품질을 제공하기 위한 설계 결정을 담고 있다면, 소프트웨어 아키텍처도 소프트웨어가 원하는 기능과 품질을 제공하기 위한 중요한 설계 결정을 담는다. 1-1. SA개요-SA개념.pdf</p>
<p>다만 소프트웨어에서는 특히 하나를 더 생각해야 한다.</p>
<p><strong>변경이다.</strong></p>
<p>소프트웨어는 한 번 만들어놓고 끝나는 것이 아니다.</p>
<p>요구사항이 바뀌고, 사용자가 늘어나고, 새로운 기능이 추가된다.</p>
<p>따라서 지금 시스템을 어떻게 만들 것인가뿐만 아니라 <strong>앞으로 어떻게 변화할 것인가</strong>까지 고려해야 한다.</p>
<hr>
<h1 id="주문-시스템을-만든다면">주문 시스템을 만든다면?</h1>
<p>조금 더 실제적인 예를 생각해봤다.</p>
<p>쇼핑몰에서 주문을 처리한다고 해보자.</p>
<p>대략 다음과 같은 기능이 필요할 수 있다.</p>
<pre><code class="language-text">주문
↓
결제
↓
재고
↓
배송</code></pre>
<p>처음에는 그냥 하나의 애플리케이션과 하나의 데이터베이스를 만들 수도 있다.</p>
<pre><code class="language-text">[ 주문 ]
   ↓
[ 결제 ]
   ↓
[ 배송 ]
   ↓
[ 하나의 DB ]</code></pre>
<p>그런데 반드시 이렇게 만들어야 할까?</p>
<p>주문 데이터와 결제 데이터를 분리할 수도 있다.</p>
<pre><code class="language-text">[ 주문 ] ── [ 주문 DB ]

[ 결제 ] ── [ 결제 DB ]</code></pre>
<p>혹은 각각의 기능을 더 독립적인 서비스로 나눌 수도 있다.</p>
<p>여기서 중요한 것은</p>
<blockquote>
<p><strong>&quot;어느 쪽이 무조건 더 좋은가?&quot;</strong></p>
</blockquote>
<p>가 아니다.</p>
<p>분리하면 각각을 독립적으로 변경하거나 확장하기 쉬워질 수 있지만, 서비스 사이의 통신과 데이터 관리 문제를 추가로 생각해야 한다.</p>
<p>반대로 하나로 합치면 구조는 단순해질 수 있지만 특정 부분만 독립적으로 변경하거나 확장하기 어려워질 수 있다.</p>
<p>결국 <strong>어떤 구조를 선택하느냐에 따라 시스템이 가지게 되는 특성이 달라진다.</strong></p>
<p>이런 고민이 뒤에서 배우게 될 아키텍처 의사결정과 트레이드오프로 연결된다.</p>
<hr>
<h1 id="소프트웨어-아키텍처에는-무엇이-들어갈까">소프트웨어 아키텍처에는 무엇이 들어갈까?</h1>
<p>소프트웨어 아키텍처에 대한 정의는 하나만 존재하지 않는다.</p>
<p>강의 자료에서도 여러 정의를 소개한다.</p>
<p>공통적으로 등장하는 것은 다음과 같은 내용이었다.</p>
<pre><code class="language-text">소프트웨어 요소

요소 사이의 관계

각 요소가 가지는 속성

외부 환경과의 관계

시스템의 설계와 진화를 이끄는 원칙</code></pre>
<p>관계에도 단순히</p>
<pre><code class="language-text">A → B</code></pre>
<p>만 있는 것이 아니다.</p>
<pre><code class="language-text">호출 방향

동기 / 비동기

통신 프로토콜</code></pre>
<p>같은 속성이 포함될 수 있다. 1-1. SA개요-SA개념.pdf</p>
<p>그래서 어떤 컴포넌트를 만들 것인지뿐만 아니라</p>
<blockquote>
<p><strong>&quot;이 컴포넌트들이 어떤 방식으로 관계를 맺을 것인가?&quot;</strong></p>
</blockquote>
<p>역시 중요한 아키텍처 문제가 된다.</p>
<hr>
<h1 id="uml을-그리는-것과-아키텍처를-설계하는-것은-같을까">UML을 그리는 것과 아키텍처를 설계하는 것은 같을까?</h1>
<p>처음에는 아키텍처라고 하면 UML 같은 다이어그램이 먼저 떠올랐다.</p>
<p>그런데 UML은 시스템을 <strong>표현하고 설계하기 위한 수단</strong>에 더 가깝다.</p>
<p>아키텍처에서 중요한 것은 그림 자체가 아니라 그 안에 담긴 결정이다.</p>
<pre><code class="language-text">왜 서비스를 이렇게 나눴는가?

왜 이 데이터베이스를 분리했는가?

왜 이 컴포넌트끼리는 직접 통신하지 않는가?

왜 동기 통신이 아니라 비동기 통신을 선택했는가?</code></pre>
<p>결국 아키텍처에서는 <strong>구조와 그 구조를 만들어낸 중요한 결정</strong>을 함께 봐야 한다.</p>
<hr>
<h1 id="소프트웨어-아키텍처를-보는-4가지-차원">소프트웨어 아키텍처를 보는 4가지 차원</h1>
<p>강의에서는 소프트웨어 아키텍처를 네 가지 차원으로 나눠서 설명했다.</p>
<pre><code class="language-text">1. QA / Architectural Drivers

2. Architectural Decisions

3. Logical Components / Connectors

4. Architectural Style</code></pre>
<p>이 네 가지는 서로 따로 떨어져 있는 것이 아니라 모두 연결되어 있다. 강의 자료에서도 품질 측면, 아키텍처 의사결정, 논리 컴포넌트, 아키텍처 스타일 네 가지가 아키텍처를 설계하고 설명하는 데 모두 필요하다고 설명한다. 1-1. SA개요-SA개념.pdf</p>
<p>하나씩 살펴보자.</p>
<hr>
<h1 id="1-품질속성qa과-아키텍처-드라이버">1. 품질속성(QA)과 아키텍처 드라이버</h1>
<p>아키텍처 설계도 결국 <strong>요구사항</strong>에서 시작한다.</p>
<pre><code class="language-text">사용자 요구사항
↓
시스템 요구사항
↓
아키텍처 설계</code></pre>
<p>그런데 시스템 요구사항이라고 해서 전부 같은 종류는 아니다.</p>
<p>크게 보면</p>
<pre><code class="language-text">기능 요구사항

비기능 요구사항
├── 품질 요구사항
└── 제약사항</code></pre>
<p>으로 나눠볼 수 있다.</p>
<p>강의에서는 다음과 같은 예를 들었다.</p>
<pre><code class="language-text">기능 요구사항(FR)

시스템은 차량 트래픽 정보를 제공해야 한다.</code></pre>
<p>이것은 <strong>무엇을 해야 하는가</strong>에 관한 요구사항이다.</p>
<p>그런데 다음 요구사항은 조금 다르다.</p>
<pre><code class="language-text">품질 요구사항(QR)

시스템은 차량 트래픽 정보를
1분 간격으로 최대 10만 명에게 제공해야 한다.</code></pre>
<p>단순히 정보를 제공하는 것을 넘어 <strong>어느 정도의 품질로 제공해야 하는가</strong>를 이야기한다.</p>
<p>그리고</p>
<pre><code class="language-text">제약사항(C)

시스템은 개발 시간을 단축하기 위해
J2EE 기반으로 개발되어야 한다.</code></pre>
<p>처럼 개발 과정이나 선택을 제한하는 요구사항도 존재한다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="기능만-잘-동작하면-좋은-시스템일까">기능만 잘 동작하면 좋은 시스템일까?</h1>
<p>예전에는 요구사항이라고 하면 가장 먼저 기능 목록을 떠올렸다.</p>
<pre><code class="language-text">로그인

주문

결제

배송 조회</code></pre>
<p>그런데 아키텍처에서는 기능만큼이나 <strong>품질속성(Quality Attribute)</strong>이 중요하다.</p>
<p>예를 들면 다음과 같다.</p>
<pre><code class="language-text">성능 Performance

신뢰성 Reliability

가용성 Availability

보안성 Security

확장성 Scalability

수정용이성 Modifiability

유지보수성 Maintainability

시험용이성 Testability</code></pre>
<p>강의에서는 서비스 실행 중 드러나는 품질과 소프트웨어 자체의 품질을 나누어 다양한 품질속성을 설명했다. 1-1. SA개요-SA개념.pdf</p>
<p>생각해보면 기능이 똑같은 두 시스템도 완전히 다른 시스템일 수 있다.</p>
<pre><code class="language-text">시스템 A

결제 가능
응답 0.1초
장애가 거의 없음
사용자 증가에 대응 가능</code></pre>
<pre><code class="language-text">시스템 B

결제 가능
응답 10초
자주 장애 발생
사용자가 늘어나면 서버 다운</code></pre>
<p>둘 다</p>
<pre><code class="language-text">&quot;결제 기능이 있다.&quot;</code></pre>
<p>라는 기능 요구사항은 만족한다.</p>
<p>하지만 시스템의 품질은 완전히 다르다.</p>
<p>그래서 아키텍처에서는 <strong>기능을 구현할 수 있느냐뿐만 아니라 어떤 품질로 구현할 것인가</strong>가 중요하다.</p>
<hr>
<h1 id="cfr은-또-뭘까">CFR은 또 뭘까?</h1>
<p>품질속성 중에는 특정 기능 하나에만 필요한 것이 아니라 <strong>시스템 전체 기능에 걸쳐 영향을 미치는 요구사항</strong>도 있다.</p>
<p>강의에서는 이런 중요한 비기능 요구사항을 <code>CFR(Cross-Functional Requirements)</code>이라는 표현으로 설명했다.</p>
<p>예를 들어 보안이나 성능은 특정 API 하나만의 문제가 아닐 수 있다.</p>
<pre><code class="language-text">로그인도 보안이 필요하고

결제도 보안이 필요하고

회원정보 조회도 보안이 필요하다.</code></pre>
<p>즉 여러 기능을 가로질러 적용되는 요구사항이다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="그런데-모든-요구사항을-똑같이-중요하게-다룰-수-있을까">그런데 모든 요구사항을 똑같이 중요하게 다룰 수 있을까?</h1>
<p>여기서 <strong>아키텍처 드라이버(Architectural Driver)</strong>가 등장한다.</p>
<p>아키텍처 드라이버란</p>
<blockquote>
<p><strong>시스템 아키텍처 결정에 큰 영향을 미치는 요구사항</strong></p>
</blockquote>
<p>이다.</p>
<p>모든 요구사항을 똑같은 수준으로 아키텍처에 반영하려고 하면 설계가 지나치게 복잡해질 수 있다.</p>
<p>그래서 시스템에서 특히 중요한 요구사항을 골라낸다.</p>
<p>강의에서는 아키텍처 드라이버가 주로 <strong>품질요구사항이나 제약사항으로부터 얻어지며</strong>, 선정하는 개수도 일반적으로 10개 미만이라고 설명한다. 1-1. SA개요-SA개념.pdf</p>
<p>예를 들어 어떤 서비스에서</p>
<pre><code class="language-text">성능

보안

확장성

수정용이성

가용성

시험용이성

이식성

재사용성

...</code></pre>
<p>을 모두 최고 수준으로 만족시키려고 할 수는 없다.</p>
<p>그중 시스템 성공에 특히 중요한 몇 가지를 선택해야 한다.</p>
<pre><code class="language-text">우리 시스템에서는

성능이 중요한가?

확장성이 중요한가?

보안이 중요한가?

변경 용이성이 중요한가?</code></pre>
<p>이 선택이 이후 아키텍처 구조에 큰 영향을 준다.</p>
<hr>
<h1 id="특정-기술을-선택하는-것도-아키텍처-의사결정일까">특정 기술을 선택하는 것도 아키텍처 의사결정일까?</h1>
<p>여기서 한 가지 헷갈렸던 부분이 있었다.</p>
<blockquote>
<p><strong>&quot;Java를 사용할지 Python을 사용할지 결정하는 것도 아키텍처 의사결정인가?&quot;</strong></p>
</blockquote>
<p>모든 기술 선택이 곧바로 아키텍처 의사결정이 되는 것은 아니다.</p>
<p>강의에서는 <strong>시스템 구조에 장기적이거나 중요한 영향을 미치는 선택과 시스템 구축에 지침이 되는 제약에 관한 선택</strong>을 아키텍처 의사결정으로 본다. 1-1. SA개요-SA개념.pdf</p>
<p>예를 들어</p>
<pre><code class="language-text">UI는 Data Access Service를 통해서만
데이터에 접근한다.

UI는 DB에 직접 접근하지 않는다.</code></pre>
<p>라는 규칙은 시스템 전체 구조에 영향을 준다.</p>
<p>반면</p>
<pre><code class="language-text">클래스 파일을 어떻게 나눌까?

XML 파싱 라이브러리를 무엇으로 할까?

웹 페이지 디자인을 어떻게 바꿀까?</code></pre>
<p>같은 결정은 상대적으로 국소적이다.</p>
<p>강의에서도 클래스 파일 분리, 그래프 DB 선택, UI 프레임워크 선택, 마이크로서비스 전환, 서비스 분리, XML 파싱 라이브러리 선택 등을 놓고 어떤 것이 아키텍처 수준의 결정인지 구분해보는 문제를 다뤘다. 1-1. SA개요-SA개념.pdf</p>
<p>결국 단순히</p>
<pre><code class="language-text">&quot;어떤 기술을 골랐는가?&quot;</code></pre>
<p>보다</p>
<blockquote>
<p><strong>&quot;이 결정이 시스템 전체의 구조와 품질에 얼마나 크고 장기적인 영향을 주는가?&quot;</strong></p>
</blockquote>
<p>를 보는 것이 중요했다.</p>
<hr>
<h1 id="설계와-아키텍처-설계는-무엇이-다를까">설계와 아키텍처 설계는 무엇이 다를까?</h1>
<p>여기까지 듣고 나니 또 하나의 의문이 생겼다.</p>
<blockquote>
<p><strong>&quot;결국 이것도 설계인데, 일반적인 소프트웨어 설계와 뭐가 다른 걸까?&quot;</strong></p>
</blockquote>
<p>강의에서는 둘의 차이를 다음처럼 비교했다.</p>
<table>
<thead>
<tr>
<th>기존의 설계</th>
<th>아키텍처 설계</th>
</tr>
</thead>
<tbody><tr>
<td>Bottom-up</td>
<td>Top-down</td>
</tr>
<tr>
<td>기능 실현에 초점</td>
<td>품질 달성에 초점</td>
</tr>
<tr>
<td>기능 중심(Functionality-centric)</td>
<td>품질 중심(Quality-centric)</td>
</tr>
<tr>
<td>객체 등 작은 단위에서 시작</td>
<td>시스템 전체 구조에서 시작</td>
</tr>
<tr>
<td>아키텍처가 사후적으로 결정될 수 있음</td>
<td>품질속성 달성을 조기에 검토</td>
</tr>
</tbody></table>
<p>강의 자료에서도 일반적인 설계는 Bottom-up과 기능 실현에, 아키텍처 설계는 Top-down과 품질 달성에 초점을 둔다고 비교한다. 1-1. SA개요-SA개념.pdf</p>
<p>아키텍처 설계는 특히 <strong>복잡하고 큰 문제</strong>를 다룬다.</p>
<p>그래서</p>
<pre><code class="language-text">추상화(Abstraction)

분할 정복(Divide &amp; Conquer)</code></pre>
<p>이 중요하다.</p>
<p>그리고 앞에서도 이야기했듯 <strong>변경</strong>을 항상 고려해야 한다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="디자인-패턴과도-조금-다르다">디자인 패턴과도 조금 다르다</h1>
<p>디자인 패턴도 결국 좋은 설계를 위한 방법이다.</p>
<p>특히 객체 사이의 관계나 인터페이스를 설계하면서 <strong>변경에 유연하게 대응하는 것</strong>이 중요한 경우가 많다.</p>
<p>그런데 강의에서는 기존 설계나 디자인 패턴이 상대적으로 하나의 품질속성에 집중한다면, 아키텍처 설계는 <strong>여러 개의 중요한 품질속성을 함께 다룬다</strong>고 설명했다. 1-1. SA개요-SA개념.pdf</p>
<p>즉 문제의 크기와 영향 범위가 다르다.</p>
<hr>
<h1 id="주문-시스템을-다시-보면-아키텍처가-보인다">주문 시스템을 다시 보면 아키텍처가 보인다</h1>
<p>강의에서는 주문 시스템을 예로 아키텍처적인 결정을 찾아봤다.</p>
<p>예를 들어 시스템에 다음과 같은 요소가 존재한다.</p>
<pre><code class="language-text">Order Placement

Inventory Adjuster

Payment Mediator

Reporting Database

Order Database

Inventory Database

Payment Database</code></pre>
<p>여기서 단순히</p>
<pre><code class="language-text">&quot;DB를 사용한다.&quot;</code></pre>
<p>가 중요한 것이 아니다.</p>
<p>왜 DB를 나눴는지가 중요하다.</p>
<p>예를 들어 리포트 데이터는 주로 읽기 작업이 많을 수 있고, 주문 데이터는 계속 읽고 쓰는 작업이 발생한다.</p>
<p>트랜잭션 특성도 다를 수 있다.</p>
<p>그렇다면</p>
<pre><code class="language-text">Reporting DB

Order DB</code></pre>
<p>를 분리하는 것이 시스템이 원하는 품질을 달성하는 데 도움이 될 수 있다.</p>
<p>강의의 주문 시스템 예에서도 결제 서비스를 분리하고, 주문과 리포트 DB를 분리하고, 재고 DB를 별도로 두고, 재고와 결제 사이의 통신을 구성하는 것 등을 아키텍처적 결정으로 살펴봤다. 1-1. SA개요-SA개념.pdf</p>
<p>이걸 보고 나니</p>
<blockquote>
<p><strong>아키텍처 다이어그램에서 선 하나, DB 하나가 그냥 그려져 있는 게 아니구나.</strong></p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>각각의 분리와 연결에는 이유가 있어야 한다.</p>
<hr>
<h1 id="3-논리-컴포넌트">3. 논리 컴포넌트</h1>
<p>다음 차원은 <strong>논리 컴포넌트(Logical Component)</strong>다.</p>
<p>논리 컴포넌트는 시스템 기능을 구현하는 큰 Building Block과 이들이 어떻게 상호작용하는지를 나타낸다.</p>
<p>전자상거래 시스템이라면 예를 들어</p>
<pre><code class="language-text">Order Placement

Payment Processing

Inventory Management

Order Shipping

Order Tracking</code></pre>
<p>같은 컴포넌트가 존재할 수 있다. 1-1. SA개요-SA개념.pdf</p>
<p>여기서 중요한 것은 단순히 컴포넌트를 여러 개 만드는 것이 아니다.</p>
<pre><code class="language-text">Order Placement
      │
      ├── Inventory Management
      │
      └── Payment Processing
                    │
                    ▼
              Order Shipping
                    │
                    ▼
              Order Tracking</code></pre>
<p>처럼 <strong>각 컴포넌트가 어떤 책임을 가지고 어떻게 상호작용하는지</strong>를 함께 봐야 한다.</p>
<p>그리고 이 컴포넌트들을 연결하는 관계를 <strong>Connector</strong>라고 볼 수 있다.</p>
<hr>
<h1 id="4-아키텍처-스타일">4. 아키텍처 스타일</h1>
<p>컴포넌트를 어떻게 구성할 것인지에도 반복적으로 등장하는 형태가 있다.</p>
<p>이것이 <strong>아키텍처 스타일(Architectural Style)</strong>이다.</p>
<p>건축물에도</p>
<pre><code class="language-text">Gothic Style

Byzantine Style</code></pre>
<p>처럼 서로 다른 스타일이 있듯이 소프트웨어에도 여러 아키텍처 스타일이 존재한다.</p>
<p>강의에서는 아키텍처 스타일을 소프트웨어의 <strong>전반적인 물리적 형태와 구조를 정의하는 것</strong>으로 설명한다. 1-1. SA개요-SA개념.pdf</p>
<p>대표적으로 수업에서 등장한 것은</p>
<pre><code class="language-text">Layered Architecture

Microservices

Event-Driven Architecture</code></pre>
<p>였다.</p>
<hr>
<h1 id="아키텍처-스타일은-단순히-모양의-차이일까">아키텍처 스타일은 단순히 모양의 차이일까?</h1>
<p>처음에는 아키텍처 스타일이라고 하면</p>
<pre><code class="language-text">&quot;시스템을 어떤 모양으로 나눌 것인가?&quot;</code></pre>
<p>정도로 생각했다.</p>
<p>그런데 스타일을 선택하면 시스템의 품질도 달라진다.</p>
<p>강의에서는 아키텍처 스타일을 다음 세 가지 요소로 설명했다.</p>
<pre><code class="language-text">Component

Connector

Constraint</code></pre>
<p>즉</p>
<pre><code class="language-text">어떤 구성요소가 존재하는가?

구성요소는 어떻게 연결되는가?

어떤 규칙을 지켜야 하는가?</code></pre>
<p>가 하나의 스타일을 만든다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="layered-architecture를-예로-들어보자">Layered Architecture를 예로 들어보자</h1>
<p>Layered Architecture에서는 시스템을 여러 Layer로 나눈다.</p>
<p>강의 자료의 예시는 다음과 같다.</p>
<pre><code class="language-text">User Interface Layer
        ↓
System Service Layer
        ↓
Basic Utility Layer
        ↓
Kernel Layer</code></pre>
<p>그리고 중요한 <strong>제약조건</strong>이 있다.</p>
<pre><code class="language-text">Layer는 바로 아래 Layer만 보고 접근할 수 있다.</code></pre>
<p>즉 Layer라는 컴포넌트만 있다고 Layered Architecture가 되는 것이 아니라, <strong>Layer 사이에 어떤 관계를 허용할 것인가라는 제약까지 포함</strong>되어야 한다. 1-1. SA개요-SA개념.pdf</p>
<p>Microservices나 Event-Driven Architecture도 마찬가지다.</p>
<p>단순히 그림의 모양이 다른 것이 아니라 구성요소, 연결 방식, 제약이 다르고 그 결과 달성할 수 있는 품질속성에도 차이가 생긴다.</p>
<hr>
<h1 id="그렇다면-가장-좋은-아키텍처를-선택하면-되지-않을까">그렇다면 가장 좋은 아키텍처를 선택하면 되지 않을까?</h1>
<p>여기까지 오면 자연스럽게 이런 생각이 든다.</p>
<blockquote>
<p><strong>&quot;그럼 성능도 좋고, 확장성도 좋고, 보안도 좋고, 변경도 쉬운 아키텍처를 선택하면 되는 것 아닌가?&quot;</strong></p>
</blockquote>
<p>문제는 그런 완벽한 선택이 없다는 것이다.</p>
<p>여기서 이번 Chapter의 마지막 개념인 <strong>트레이드오프(Trade-off)</strong>가 등장한다.</p>
<hr>
<h1 id="트레이드오프">트레이드오프</h1>
<p>트레이드오프는 의사결정을 내릴 때 고려해야 하는 <strong>장점과 단점</strong>이다.</p>
<p>아키텍처에서는 중요한 트레이드오프가 있는 결정일수록 더 많은 분석이 필요하다. 1-1. SA개요-SA개념.pdf</p>
<p>예를 들어 시스템을 여러 서비스로 잘게 분리하면 독립적인 변경이나 확장에는 도움이 될 수 있다.</p>
<p>하지만 그 대신</p>
<pre><code class="language-text">서비스 간 통신

네트워크 오류

데이터 일관성

운영 복잡도</code></pre>
<p>같은 새로운 문제가 생길 수 있다.</p>
<p>반대로 하나의 시스템으로 단순하게 만들면 개발과 운영은 쉬워질 수 있지만 시스템이 커졌을 때 독립적인 확장이나 변경이 어려워질 수 있다.</p>
<p>즉</p>
<pre><code class="language-text">A를 얻는다.
↓
대신 B에서 비용이 발생한다.</code></pre>
<p>는 관계가 생긴다.</p>
<hr>
<h1 id="메시지-전달-방식-하나에도-트레이드오프가-있다">메시지 전달 방식 하나에도 트레이드오프가 있다</h1>
<p>강의 마지막에서는 거래 시스템의 Downstream 서비스와 통신하는 방법을 예로 트레이드오프를 살펴봤다.</p>
<p>예를 들어 거래가 발생했을 때</p>
<pre><code class="language-text">Trading Service</code></pre>
<p>의 데이터를</p>
<pre><code class="language-text">Notification Service

Analytics Service</code></pre>
<p>가 필요로 한다고 해보자.</p>
<p>한 가지 방법은 각각 별도의 Queue를 사용하는 것이다.</p>
<pre><code class="language-text">                 ┌→ Queue → Notification
Trading Service ─┤
                 └→ Queue → Analytics</code></pre>
<p>다른 방법은 하나의 Topic에 메시지를 게시하고 여러 서비스가 이를 구독하는 것이다.</p>
<pre><code class="language-text">Trading Service
       │
       ▼
   Topic: orders
     ↙     ↘
Notification Analytics</code></pre>
<p>둘 중 무엇이 무조건 더 좋은 것은 아니다.</p>
<p>별도의 Queue를 사용하면 서로 다른 소비자에게 다른 메시지를 제공하거나 독립적으로 모니터링하고 확장하기 쉽고 보안 측면에서도 장점이 있다. 하지만 거래 서비스가 여러 Queue와 연결되어야 해서 결합도가 높아지고 추가 인프라가 필요하다. 1-1. SA개요-SA개념.pdf</p>
<p>반대로 Topic을 이용하면 거래 서비스는 한 곳에만 메시지를 게시하면 되기 때문에 결합도가 낮아지고 새로운 서비스를 붙이기 쉬워진다. 대신 모든 서비스에 동일한 메시지를 제공하게 되고, 개별 소비자를 독립적으로 모니터링하거나 확장하기 어렵고 보안 측면의 단점도 생길 수 있다. 1-1. SA개요-SA개념.pdf</p>
<p>결국 선택의 문제다.</p>
<pre><code class="language-text">확장성

보안

결합도

기능확장성

운영 복잡도</code></pre>
<p>중 <strong>우리 시스템에서 무엇이 더 중요한가</strong>를 판단해야 한다.</p>
<hr>
<h1 id="그래서-아키텍처-드라이버가-중요했다">그래서 아키텍처 드라이버가 중요했다</h1>
<p>여기까지 배우고 나니 앞에서 왜 굳이 아키텍처 드라이버를 선정하는지 다시 이해됐다.</p>
<p>모든 품질속성을 최고 수준으로 만족시키는 하나의 구조가 있다면 고민할 필요가 없다.</p>
<p>그런데 현실에서는 트레이드오프가 존재한다.</p>
<pre><code class="language-text">요구사항
      ↓
품질 요구사항 / 제약사항
      ↓
중요한 요구사항 선정
      ↓
Architectural Drivers
      ↓
Architectural Decision
      ↓
Component / Connector
      ↓
Architectural Style
      ↓
Trade-off</code></pre>
<p>즉 결국</p>
<blockquote>
<p><strong>&quot;우리 시스템에서 무엇이 가장 중요한가?&quot;</strong></p>
</blockquote>
<p>를 먼저 결정해야 그에 맞는 아키텍처 의사결정을 내릴 수 있다.</p>
<p>강의에서도 시스템이 추구하는 품질은 품질속성으로 표현되고, 이 가운데 설계에 큰 영향을 주는 것이 아키텍처 드라이버이며, 아키텍처 결정에 따라 품질속성 요구사항을 만족하는 정도가 달라진다고 정리한다. 1-1. SA개요-SA개념.pdf</p>
<hr>
<h1 id="소프트웨어-아키텍처는-결국-그림이-아니었다">소프트웨어 아키텍처는 결국 &#39;그림&#39;이 아니었다</h1>
<p>수업을 듣기 전에는 소프트웨어 아키텍처라고 하면 이런 그림을 먼저 떠올렸다.</p>
<pre><code class="language-text">[Client]
    ↓
[Server]
    ↓
[Database]</code></pre>
<p>그래서 아키텍처를 설계한다는 것도 시스템 구조를 잘 그려놓는 것 정도라고 생각했던 것 같다.</p>
<p>그런데 이번 내용을 공부하고 나니 그림보다 중요한 것은 <strong>왜 그렇게 그렸는가</strong>였다.</p>
<pre><code class="language-text">왜 서비스를 나눴는가?

왜 DB를 분리했는가?

왜 직접 DB에 접근하지 못하게 했는가?

왜 Layered Architecture를 선택했는가?

왜 Queue가 아니라 Topic을 선택했는가?</code></pre>
<p>그리고 그 질문의 뒤에는 대부분 시스템이 달성하려는 <strong>품질속성</strong>이 있었다.</p>
<pre><code class="language-text">성능

보안

확장성

신뢰성

수정용이성

시험용이성</code></pre>
<p>아키텍처는 결국 이런 요구사항을 만족시키기 위해 <strong>시스템 전체에 장기적인 영향을 미치는 결정을 내리는 과정</strong>이라고 이해했다.</p>
<hr>
<h1 id="바벨탑-이야기로-다시-돌아가보면">바벨탑 이야기로 다시 돌아가보면</h1>
<p>이번 Chapter가 바벨탑 이야기로 시작한 이유도 이제 조금 이해된다.</p>
<p>여러 사람이 아무리 뛰어난 기술을 가지고 있어도 서로 다른 시스템을 머릿속에 그리고 있다면 하나의 큰 소프트웨어를 만들기 어렵다.</p>
<p>그래서 아키텍처는 개발자들에게</p>
<pre><code class="language-text">우리가 무엇을 만들고 있는지

시스템이 어떤 구조인지

각 요소가 어떤 책임을 가지는지

어떻게 서로 통신하는지

왜 이런 구조를 선택했는지</code></pre>
<p>에 대한 <strong>공통된 이해</strong>를 제공한다.</p>
<p>그리고 그 구조를 결정하는 과정에서는 항상 선택이 발생한다.</p>
<pre><code class="language-text">품질속성
↓
아키텍처 드라이버
↓
아키텍처 의사결정
↓
논리 컴포넌트
↓
아키텍처 스타일
↓
트레이드오프</code></pre>
<p>결국 좋은 아키텍처라는 것이 단순히 최신 기술을 많이 사용하거나 복잡한 구조를 만드는 것을 의미하는 것은 아닌 것 같다.</p>
<blockquote>
<p><strong>우리 시스템에서 중요한 것이 무엇인지 알고, 그 품질을 달성하기 위해 어떤 것을 얻고 어떤 것을 감수할 것인지 설명할 수 있는 구조.</strong></p>
</blockquote>
<p>지금까지 배운 내용으로는 이것이 소프트웨어 아키텍처를 바라보는 가장 중요한 출발점인 것 같다.</p>
<p>다음 Chapter에서는 여기서 계속 등장했던 <strong>품질속성</strong>을 조금 더 구체적으로 다룬다.</p>
<p>단순히</p>
<pre><code class="language-text">&quot;성능이 좋아야 한다.&quot;

&quot;변경하기 쉬워야 한다.&quot;</code></pre>
<p>라고 말하는 것을 넘어, 이런 품질을 실제로 어떻게 요구사항으로 표현하고 검증할 수 있는지를 이어서 정리해보려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클라이언트는 서버에 데이터를 어떻게 전달할까? | HTTP 데이터 전송부터 API 설계까지]]></title>
            <link>https://velog.io/@daehyun_lee/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@daehyun_lee/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Fri, 18 Sep 2026 14:33:59 GMT</pubDate>
            <description><![CDATA[<h1 id="클라이언트는-서버에-데이터를-어떻게-전달할까--http-데이터-전송부터-api-설계까지">클라이언트는 서버에 데이터를 어떻게 전달할까? | HTTP 데이터 전송부터 API 설계까지</h1>
<p>앞에서는 HTTP 메서드가 각각 어떤 역할을 하는지 공부했다.</p>
<pre><code class="language-text">GET
→ 조회

POST
→ 요청 데이터 처리

PUT
→ 리소스 대체

PATCH
→ 리소스 부분 변경

DELETE
→ 리소스 삭제</code></pre>
<p>그런데 HTTP 메서드의 역할을 알고 나니 다음 질문이 생겼다.</p>
<blockquote>
<p><strong>그래서 클라이언트가 서버에 실제 데이터를 보내려면 어디에 넣어야 할까?</strong></p>
</blockquote>
<p>회원 목록을 조회하면서 검색 조건을 전달할 수도 있고, 회원가입을 하면서 이름과 나이를 전달할 수도 있다.</p>
<p>파일을 업로드해야 할 수도 있다.</p>
<p>이번에는 클라이언트에서 서버로 데이터를 전달하는 방법부터 시작해서, 실제 HTTP API를 어떻게 설계하는지까지 이어서 정리해봤다.</p>
<hr>
<h1 id="클라이언트가-서버에-데이터를-전달하는-두-가지-방법">클라이언트가 서버에 데이터를 전달하는 두 가지 방법</h1>
<p>먼저 데이터 전달 방식은 크게 두 가지로 나눌 수 있다.</p>
<pre><code class="language-text">1. Query Parameter

2. Message Body</code></pre>
<p>Query Parameter는 주로 <code>GET</code>에서 사용한다.</p>
<pre><code class="language-text">GET
→ 조회
→ 검색, 필터, 정렬 조건 전달</code></pre>
<p>반면 Message Body는 주로 다음 메서드에서 사용한다.</p>
<pre><code class="language-text">POST
PUT
PATCH</code></pre>
<p>회원가입, 상품 주문, 리소스 등록이나 변경처럼 서버에 데이터를 전달해야 하는 경우다.</p>
<p>처음에는 그냥</p>
<blockquote>
<p><strong>&quot;데이터를 보내려면 Body에 넣으면 되는 것 아닌가?&quot;</strong></p>
</blockquote>
<p>라고 생각했다.</p>
<p>그런데 HTTP에서는 요청의 목적에 따라 데이터를 전달하는 위치도 달라졌다.</p>
<hr>
<h1 id="데이터-전송-상황은-4가지로-나눌-수-있다">데이터 전송 상황은 4가지로 나눌 수 있다</h1>
<p>수업에서는 클라이언트에서 서버로 데이터를 전송하는 상황을 크게 네 가지로 나눴다.</p>
<pre><code class="language-text">클라이언트 → 서버

├── 정적 데이터 조회
├── 동적 데이터 조회
├── HTML Form 데이터 전송
└── HTTP API 데이터 전송</code></pre>
<p>하나씩 살펴보자.</p>
<hr>
<h1 id="1-정적-데이터-조회">1. 정적 데이터 조회</h1>
<p>가장 단순한 경우다.</p>
<p>이미지나 정적 텍스트 문서를 조회한다고 해보자.</p>
<pre><code class="language-http">GET /static/star.jpg HTTP/1.1
Host: localhost:8080</code></pre>
<p>여기서는 별도의 Query Parameter가 없다.</p>
<p><code>/static/star.jpg</code>라는 URI 자체만으로 어떤 리소스를 원하는지 알 수 있기 때문이다.</p>
<pre><code class="language-text">Client
   │
   │ GET /static/star.jpg
   ▼
Server
   │
   │ star.jpg
   ▼
Client</code></pre>
<p>따라서 이미지나 정적 텍스트 문서는 일반적으로 리소스 경로만으로 단순하게 조회할 수 있다.</p>
<p>그런데 조회를 하면서 서버에게 <strong>추가적인 조건</strong>까지 전달하고 싶다면 어떻게 해야 할까?</p>
<hr>
<h1 id="2-동적-데이터-조회">2. 동적 데이터 조회</h1>
<p>예를 들어 Google에서 <code>hello</code>라는 단어를 검색한다고 해보자.</p>
<p>수업 자료에서는 다음 URL을 예시로 들었다.</p>
<pre><code class="language-text">https://www.google.com/search?q=hello&amp;hl=ko</code></pre>
<p>HTTP 요청으로 보면 다음과 같다.</p>
<pre><code class="language-http">GET /search?q=hello&amp;hl=ko HTTP/1.1
Host: www.google.com</code></pre>
<p>여기서</p>
<pre><code class="language-text">?q=hello&amp;hl=ko</code></pre>
<p>부분이 <strong>Query Parameter</strong>다.</p>
<h2 id="query-parameter가-뭘까">Query Parameter가 뭘까?</h2>
<p>URL을 조금 나눠보면 이해하기 쉽다.</p>
<pre><code class="language-text">/search?q=hello&amp;hl=ko
│      │
│      └─ Query Parameter
│
└─ Resource Path</code></pre>
<p><code>?</code> 뒤부터 Query Parameter가 시작된다.</p>
<p>그리고 여러 값을 전달할 때는 <code>&amp;</code>를 이용해서 연결한다.</p>
<pre><code class="language-text">q=hello
&amp;
hl=ko</code></pre>
<p>즉</p>
<pre><code class="language-text">key=value</code></pre>
<p>형태로 서버에게 추가적인 정보를 전달한다.</p>
<p>예를 들어 회원을 검색한다면</p>
<pre><code class="language-http">GET /members?username=kim&amp;age=20</code></pre>
<p>처럼 만들 수 있다.</p>
<pre><code class="language-text">username=kim
→ 이름이 kim

age=20
→ 나이가 20</code></pre>
<p>서버는 이 조건을 이용해서 조회 결과를 동적으로 만들어낸다.</p>
<p>따라서 Query Parameter는 주로</p>
<pre><code class="language-text">검색

필터링

정렬</code></pre>
<p>같은 조회 조건을 전달할 때 사용한다.</p>
<p>강의 자료에서도 동적 데이터 조회는 GET과 Query Parameter를 사용하고, 이를 기반으로 결과를 필터링하거나 정렬한다고 설명한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="3-html-form으로-데이터를-전송하면">3. HTML Form으로 데이터를 전송하면?</h1>
<p>이번에는 회원가입 화면을 생각해보자.</p>
<p>사용자가 다음 정보를 입력한다.</p>
<pre><code class="language-text">username = kim
age = 20</code></pre>
<p>HTML에서는 <code>&lt;form&gt;</code>을 이용해서 데이터를 서버로 전송할 수 있다.</p>
<pre><code class="language-html">&lt;form action=&quot;/save&quot; method=&quot;post&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;username&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;age&quot;&gt;
    &lt;button type=&quot;submit&quot;&gt;전송&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p><code>action</code>은 데이터를 어디로 보낼지를 의미하고,</p>
<pre><code class="language-html">action=&quot;/save&quot;</code></pre>
<p><code>method</code>는 어떤 HTTP 메서드로 보낼지를 의미한다.</p>
<pre><code class="language-html">method=&quot;post&quot;</code></pre>
<p>Form을 제출하면 브라우저가 HTTP 메시지를 만들어준다.</p>
<pre><code class="language-http">POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded

username=kim&amp;age=20</code></pre>
<p>여기서 처음 눈에 들어온 것은 이 부분이었다.</p>
<pre><code class="language-text">username=kim&amp;age=20</code></pre>
<blockquote>
<p><strong>&quot;이거 아까 Query Parameter랑 모양이 거의 똑같은데?&quot;</strong></p>
</blockquote>
<p>맞다.</p>
<p><code>application/x-www-form-urlencoded</code>에서는 Form 데이터를</p>
<pre><code class="language-text">key=value&amp;key=value</code></pre>
<p>형태로 인코딩해서 Message Body에 넣는다.</p>
<p>수업 자료에서도 HTML Form의 POST 요청은 <code>application/x-www-form-urlencoded</code>를 사용하고, Form의 내용을 Message Body에 <code>key=value</code> 형식으로 전달한다고 설명한다. 5.http-method-use.pdf</p>
<p>차이는 <strong>어디에 들어가느냐</strong>다.</p>
<pre><code class="language-text">GET + Query Parameter

/members?username=kim&amp;age=20
         ↑
       URL</code></pre>
<p>반면</p>
<pre><code class="language-text">POST + HTML Form

POST /save

username=kim&amp;age=20
        ↑
   Message Body</code></pre>
<p>이다.</p>
<hr>
<h1 id="그런데-html-form에서-get을-쓰면-어떻게-될까">그런데 HTML Form에서 GET을 쓰면 어떻게 될까?</h1>
<p>HTML Form에서는 <code>method</code>를 GET으로 지정할 수도 있다.</p>
<pre><code class="language-html">&lt;form action=&quot;/members&quot; method=&quot;get&quot;&gt;</code></pre>
<p>그러면 브라우저는 Form 데이터를 Message Body가 아니라 URL의 Query Parameter로 넣는다.</p>
<pre><code class="language-http">GET /members?username=kim&amp;age=20 HTTP/1.1</code></pre>
<p>즉</p>
<pre><code class="language-text">HTML Form + POST
→ Form 데이터를 Message Body에 전달

HTML Form + GET
→ Form 데이터를 Query Parameter로 전달</code></pre>
<p>이라고 이해할 수 있다.</p>
<hr>
<h1 id="그럼-저장하는-form도-get으로-보내면-되지-않을까">그럼 저장하는 Form도 GET으로 보내면 되지 않을까?</h1>
<p>기술적으로 다음과 같이 작성하는 것 자체는 가능하다.</p>
<pre><code class="language-html">&lt;form action=&quot;/save&quot; method=&quot;get&quot;&gt;</code></pre>
<p>그러면</p>
<pre><code class="language-http">GET /save?username=kim&amp;age=20</code></pre>
<p>이라는 요청이 만들어진다.</p>
<p>그런데 여기서 중요한 문제가 생긴다.</p>
<blockquote>
<p><strong>GET은 조회에 사용해야 한다.</strong></p>
</blockquote>
<p>회원 저장처럼 서버의 리소스를 변경하는 작업을 GET으로 처리해서는 안 된다.</p>
<pre><code class="language-text">GET
→ 조회

POST
→ 데이터 처리 / 저장 등에 사용</code></pre>
<p>따라서</p>
<pre><code class="language-text">GET /save?username=kim&amp;age=20</code></pre>
<p>처럼 GET으로 리소스를 변경하는 API를 설계하면 안 된다.</p>
<p>강의 자료에서도 이 부분을 명확하게 강조하고 있다.</p>
<blockquote>
<p>GET은 조회에만 사용하고, 리소스 변경이 발생하는 곳에 사용하면 안 된다. 5.http-method-use.pdf</p>
</blockquote>
<p>처음에는 단순히</p>
<blockquote>
<p><strong>&quot;GET으로도 데이터가 서버까지 가는데 왜 쓰면 안 되지?&quot;</strong></p>
</blockquote>
<p>라는 생각이 들 수 있다.</p>
<p>그런데 중요한 것은 <strong>데이터를 전달할 수 있느냐</strong>가 아니라 <strong>HTTP 메서드가 가진 의미를 지키느냐</strong>였다.</p>
<hr>
<h1 id="파일은-어떻게-전송할까">파일은 어떻게 전송할까?</h1>
<p>그런데 Form으로 문자열만 보내는 것은 아니다.</p>
<p>회원 프로필 사진처럼 파일을 함께 보내야 할 수도 있다.</p>
<pre><code class="language-text">username = kim
age = 20
file = intro.png</code></pre>
<p>이때 사용할 수 있는 것이</p>
<pre><code class="language-text">multipart/form-data</code></pre>
<p>다.</p>
<p>HTML에서는 다음처럼 작성할 수 있다.</p>
<pre><code class="language-html">&lt;form action=&quot;/save&quot;
      method=&quot;post&quot;
      enctype=&quot;multipart/form-data&quot;&gt;

    &lt;input type=&quot;text&quot; name=&quot;username&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;age&quot;&gt;
    &lt;input type=&quot;file&quot; name=&quot;file1&quot;&gt;

    &lt;button type=&quot;submit&quot;&gt;전송&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>HTTP 메시지는 대략 다음과 같은 형태가 된다.</p>
<pre><code class="language-http">POST /save HTTP/1.1
Content-Type: multipart/form-data; boundary=-----XXX</code></pre>
<p>여기서 <code>boundary</code>가 등장한다.</p>
<hr>
<h1 id="multipart는-왜-이름이-multipart일까">multipart는 왜 이름이 multipart일까?</h1>
<p>처음에는</p>
<pre><code class="language-text">multipart/form-data</code></pre>
<p>라는 이름 자체가 조금 이해가 안 됐다.</p>
<p>HTTP 메시지 안에</p>
<pre><code class="language-text">username

age

image/png</code></pre>
<p>처럼 서로 다른 데이터를 함께 넣어야 한다고 생각해보자.</p>
<p>이들을 하나의 덩어리로 붙여버리면 어디서 데이터가 끝나고 다음 데이터가 시작되는지 구분하기 어렵다.</p>
<p>그래서 <strong>boundary</strong>를 이용해서 각각의 Part를 나눈다.</p>
<pre><code class="language-text">------XXX

username
kim

------XXX

age
20

------XXX

file1
intro.png
Content-Type: image/png
(binary data)

------XXX--</code></pre>
<p>즉 하나의 Message Body 안에 여러 Part가 들어간다.</p>
<p>그래서 이름도</p>
<pre><code class="language-text">Multi + Part</code></pre>
<p>이다.</p>
<p>특히 파일 업로드처럼 바이너리 데이터를 Form 데이터와 함께 전송할 때 사용할 수 있다. 강의 자료의 11페이지 예시도 <code>username</code>, <code>age</code>, <code>intro.png</code>를 각각 boundary로 구분해 하나의 요청으로 보내는 구조를 보여준다. 5.http-method-use.pdf</p>
<hr>
<h1 id="html-form을-정리하면">HTML Form을 정리하면</h1>
<p>HTML Form에서는 기본적으로 GET과 POST를 지원한다.</p>
<pre><code class="language-text">POST

Content-Type:
application/x-www-form-urlencoded

→ 일반적인 Form 데이터</code></pre>
<p>파일을 함께 전송해야 한다면</p>
<pre><code class="language-text">POST

Content-Type:
multipart/form-data

→ 파일 + 여러 Form 데이터</code></pre>
<p>그리고 GET Form이라면</p>
<pre><code class="language-text">GET

→ Query Parameter</code></pre>
<p>형태로 데이터가 전달된다.</p>
<p>여기서 중요한 제약도 있다.</p>
<blockquote>
<p><strong>순수 HTML Form은 GET과 POST만 지원한다.</strong></p>
</blockquote>
<p>이 제약은 뒤에서 API를 설계할 때 다시 등장한다.</p>
<hr>
<h1 id="4-http-api를-통한-데이터-전송">4. HTTP API를 통한 데이터 전송</h1>
<p>마지막은 내가 백엔드 개발을 하면서 가장 많이 보게 될 HTTP API 방식이다.</p>
<p>HTTP API는 다양한 환경에서 사용된다.</p>
<pre><code class="language-text">Server ↔ Server
→ 백엔드 시스템 간 통신

Mobile App ↔ Server
→ iPhone, Android

Web Client ↔ Server
→ JavaScript, AJAX
→ React, Vue 등</code></pre>
<p>HTML Form처럼 브라우저가 알아서 Form 데이터를 만들어 보내는 것이 아니라 클라이언트가 직접 HTTP 요청을 구성한다.</p>
<p>예를 들어 회원을 등록한다고 해보자.</p>
<pre><code class="language-http">POST /members HTTP/1.1
Content-Type: application/json

{
    &quot;username&quot;: &quot;young&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>여기서는</p>
<pre><code class="language-text">Content-Type: application/json</code></pre>
<p>을 사용했다.</p>
<p>즉 Message Body에 들어 있는 데이터가 JSON이라는 것을 서버에게 알려주는 것이다.</p>
<p>HTTP API에서는 JSON을 주로 사용하며, 강의 자료에서는 POST·PUT·PATCH는 Message Body로 데이터를 보내고 GET은 조회 시 Query Parameter를 사용한다고 정리한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="지금까지의-데이터-전송-방식을-연결해보자">지금까지의 데이터 전송 방식을 연결해보자</h1>
<p>처음에는 Query Parameter, Form, JSON이 각각 따로 떨어진 개념처럼 느껴졌다.</p>
<p>그런데 결국 질문은 하나였다.</p>
<blockquote>
<p><strong>클라이언트가 서버에 어떤 데이터를 어떤 방식으로 전달할 것인가?</strong></p>
</blockquote>
<pre><code class="language-text">정적 데이터 조회

GET /static/star.jpg
→ URI로 리소스 식별</code></pre>
<pre><code class="language-text">동적 데이터 조회

GET /members?age=20
→ Query Parameter로 조회 조건 전달</code></pre>
<pre><code class="language-text">HTML Form

POST /save
Content-Type: application/x-www-form-urlencoded

username=kim&amp;age=20</code></pre>
<pre><code class="language-text">파일 Form

POST /save
Content-Type: multipart/form-data

→ Form + Binary File</code></pre>
<pre><code class="language-text">HTTP API

POST /members
Content-Type: application/json

{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>이렇게 놓고 보니 차이가 훨씬 명확해졌다.</p>
<hr>
<h1 id="이제-실제-http-api를-설계해보자">이제 실제 HTTP API를 설계해보자</h1>
<p>데이터를 전달하는 방법을 알아봤으니 이제 조금 더 실제적인 문제로 넘어가보자.</p>
<blockquote>
<p><strong>회원 관리 시스템의 API를 어떻게 설계할까?</strong></p>
</blockquote>
<p>수업에서는 크게 세 가지 상황을 비교했다.</p>
<pre><code class="language-text">HTTP API - Collection
→ POST 기반 등록

HTTP API - Store
→ PUT 기반 등록

HTML Form
→ GET / POST 기반</code></pre>
<p>강의 자료에서도 회원 관리 API는 POST 기반 Collection, 파일 관리는 PUT 기반 Store의 예로 구분한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="post-기반-회원-관리-api">POST 기반 회원 관리 API</h1>
<p>회원 관리 시스템을 만든다고 해보자.</p>
<p>필요한 기능은 다음과 같다.</p>
<pre><code class="language-text">회원 목록 조회
회원 등록
회원 조회
회원 수정
회원 삭제</code></pre>
<p>앞에서 배운 리소스 중심 URI 설계를 적용하면 다음처럼 만들 수 있다.</p>
<pre><code class="language-text">GET /members
→ 회원 목록 조회

POST /members
→ 회원 등록

GET /members/{id}
→ 회원 조회

PATCH /members/{id}
PUT /members/{id}
POST /members/{id}
→ 회원 수정

DELETE /members/{id}
→ 회원 삭제</code></pre>
<p>여기서 회원 등록을 조금 더 자세히 보자.</p>
<pre><code class="language-http">POST /members</code></pre>
<p>클라이언트가 회원을 새로 등록한다.</p>
<p>그런데 클라이언트는 아직 새 회원의 ID를 모른다.</p>
<pre><code class="language-text">100?

101?

102?</code></pre>
<p>이 ID를 누가 결정할까?</p>
<p><strong>서버가 결정한다.</strong></p>
<pre><code class="language-text">Client

POST /members
      ↓
Server

새 회원 생성
ID = 100
      ↓
/members/100</code></pre>
<p>서버는 다음과 같이 응답할 수 있다.</p>
<pre><code class="language-http">HTTP/1.1 201 Created
Location: /members/100</code></pre>
<p>즉 서버가 새롭게 생성된 리소스의 URI를 만들어준다.</p>
<p>강의 자료에서도 POST 기반 회원 등록은 클라이언트가 등록될 리소스의 URI를 모르고, 서버가 <code>/members/100</code> 같은 URI를 생성하는 방식으로 설명한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="이것이-collection이다">이것이 Collection이다</h1>
<p>이런 형태를 <strong>Collection</strong>이라고 한다.</p>
<pre><code class="language-text">Collection

→ 서버가 관리하는 리소스 디렉터리
→ 서버가 새로운 리소스 URI를 생성하고 관리</code></pre>
<p>회원 관리에서는</p>
<pre><code class="language-text">/members</code></pre>
<p>가 Collection이다.</p>
<pre><code class="language-text">/members
   │
   ├── /members/100
   ├── /members/101
   └── /members/102</code></pre>
<p>여기서 중요한 것은 <strong>서버가 URI를 결정한다는 것</strong>이다.</p>
<hr>
<h1 id="그러면-put-기반-등록은-뭐가-다를까">그러면 PUT 기반 등록은 뭐가 다를까?</h1>
<p>이번에는 회원이 아니라 파일 관리 시스템을 생각해보자.</p>
<p>필요한 기능은 다음과 같다.</p>
<pre><code class="language-text">파일 목록 조회
파일 조회
파일 등록
파일 삭제
파일 대량 등록</code></pre>
<p>API는 다음처럼 설계할 수 있다.</p>
<pre><code class="language-text">GET /files
→ 파일 목록

GET /files/{filename}
→ 파일 조회

PUT /files/{filename}
→ 파일 등록

DELETE /files/{filename}
→ 파일 삭제

POST /files
→ 파일 대량 등록</code></pre>
<p>여기서 중요한 것은 PUT이다.</p>
<pre><code class="language-http">PUT /files/star.jpg</code></pre>
<p>POST와 뭔가 다르다.</p>
<hr>
<h1 id="post와-put의-차이가-여기서-보인다">POST와 PUT의 차이가 여기서 보인다</h1>
<p>POST에서는</p>
<pre><code class="language-http">POST /members</code></pre>
<p>라고 요청했다.</p>
<p>클라이언트는 새로운 회원이</p>
<pre><code class="language-text">/members/100</code></pre>
<p>이 될지 알지 못했다.</p>
<p>서버가 결정했다.</p>
<p>그런데 PUT은</p>
<pre><code class="language-http">PUT /files/star.jpg</code></pre>
<p>라고 요청한다.</p>
<p>이미 클라이언트가</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>라는 정확한 URI를 지정했다.</p>
<p>즉</p>
<pre><code class="language-text">POST

Client
→ /members

Server
→ /members/100 결정</code></pre>
<p>반면</p>
<pre><code class="language-text">PUT

Client
→ /files/star.jpg 직접 지정</code></pre>
<p>이다.</p>
<p>이 차이가 생각보다 중요했다.</p>
<hr>
<h1 id="이것이-store다">이것이 Store다</h1>
<p>PUT 기반 등록에서 등장하는 개념이 <strong>Store</strong>다.</p>
<pre><code class="language-text">Store

→ 클라이언트가 관리하는 리소스 저장소
→ 클라이언트가 리소스 URI를 알고 직접 관리</code></pre>
<p>파일 관리 시스템에서는</p>
<pre><code class="language-text">/files</code></pre>
<p>가 Store가 된다.</p>
<p>강의 자료에서도 PUT 기반 파일 등록은 클라이언트가 <code>/files/star.jpg</code>처럼 리소스 URI를 직접 지정하며, 이를 Store라고 설명한다. 5.http-method-use.pdf</p>
<p>그래서 둘을 비교하면 다음과 같다.</p>
<pre><code class="language-text">Collection
POST 기반

Client
   │
   │ POST /members
   ▼
Server
   │
   │ URI 생성
   ▼
/members/100</code></pre>
<pre><code class="language-text">Store
PUT 기반

Client
   │
   │ PUT /files/star.jpg
   ▼
Server

Client가 URI를 이미 결정</code></pre>
<p>수업에서는 실무에서 대부분 POST 기반 등록을 많이 사용한다고 설명했다.</p>
<hr>
<h1 id="그런데-순수-html-form으로-회원-관리를-만들면">그런데 순수 HTML Form으로 회원 관리를 만들면?</h1>
<p>여기서 다시 HTML Form으로 돌아온다.</p>
<p>앞에서 중요한 제약을 하나 봤다.</p>
<pre><code class="language-text">HTML Form

GET
POST

두 Method만 지원</code></pre>
<p>그러면 문제가 생긴다.</p>
<p>HTTP API에서는</p>
<pre><code class="language-text">PATCH /members/100

DELETE /members/100</code></pre>
<p>처럼 표현할 수 있었다.</p>
<p>그런데 순수 HTML Form에서는 PATCH나 DELETE를 직접 사용할 수 없다.</p>
<blockquote>
<p><strong>그럼 회원 수정과 삭제를 어떻게 표현하지?</strong></p>
</blockquote>
<hr>
<h1 id="html-form으로-회원-관리하기">HTML Form으로 회원 관리하기</h1>
<p>강의 자료에서는 다음과 같이 설계한다.</p>
<pre><code class="language-text">회원 목록
GET /members

회원 등록 Form
GET /members/new

회원 등록
POST /members/new
또는
POST /members

회원 조회
GET /members/{id}

회원 수정 Form
GET /members/{id}/edit

회원 수정
POST /members/{id}/edit
또는
POST /members/{id}

회원 삭제
POST /members/{id}/delete</code></pre>
<p>여기서 재미있는 부분은 <strong>Form 자체도 GET으로 조회한다는 것</strong>이다.</p>
<p>예를 들어 회원을 새로 등록하려면 먼저 회원 등록 화면이 필요하다.</p>
<pre><code class="language-text">GET /members/new
        ↓
회원 등록 HTML</code></pre>
<p>사용자가 정보를 입력하고 저장하면</p>
<pre><code class="language-text">POST /members
        ↓
회원 생성</code></pre>
<p>이 된다.</p>
<p>회원 수정도 비슷하다.</p>
<pre><code class="language-text">GET /members/100
        ↓
회원 상세 화면</code></pre>
<p>여기서 수정 버튼을 누른다.</p>
<pre><code class="language-text">GET /members/100/edit
        ↓
회원 수정 Form</code></pre>
<p>정보를 수정한 뒤 저장한다.</p>
<pre><code class="language-text">POST /members/100/edit
        ↓
회원 수정</code></pre>
<p>강의 자료의 회원 관리 Form 예시도 이 흐름으로 URI를 구성하고 있다. 5.http-method-use.pdf</p>
<hr>
<h1 id="그런데-uri에-동사를-넣지-말라고-하지-않았나">그런데 URI에 동사를 넣지 말라고 하지 않았나?</h1>
<p>여기서 앞에서 배운 내용과 충돌하는 것처럼 보였다.</p>
<p>우리는 분명 URI에는</p>
<pre><code class="language-text">/members/100</code></pre>
<p>처럼 리소스를 표현하고,</p>
<pre><code class="language-text">GET
POST
PATCH
DELETE</code></pre>
<p>같은 HTTP Method로 행위를 표현한다고 배웠다.</p>
<p>그런데 갑자기</p>
<pre><code class="language-text">/new

/edit

/delete</code></pre>
<p>같은 동사가 URI에 들어왔다.</p>
<blockquote>
<p><strong>&quot;URI에는 리소스만 넣으라고 했는데 이건 뭐지?&quot;</strong></p>
</blockquote>
<p>이때 등장하는 것이 <strong>Control URI</strong>다.</p>
<hr>
<h1 id="control-uri">Control URI</h1>
<p>순수 HTML Form에서는 GET과 POST만 지원한다.</p>
<p>따라서 HTTP Method만으로 모든 행위를 표현하기 어려운 경우가 있다.</p>
<p>이런 제약을 해결하기 위해</p>
<pre><code class="language-text">/members/new

/members/100/edit

/members/100/delete</code></pre>
<p>처럼 동사가 포함된 URI를 사용할 수 있다.</p>
<p>이것을 <strong>Control URI</strong>라고 한다.</p>
<p>강의 자료에서도 GET과 POST만 지원하는 HTML Form의 제약 때문에 <code>/new</code>, <code>/edit</code>, <code>/delete</code> 같은 Control URI를 사용할 수 있다고 설명한다. 5.http-method-use.pdf</p>
<p>다만 여기서 중요한 것은</p>
<blockquote>
<p><strong>처음부터 모든 API를 Control URI로 만들자는 이야기가 아니다.</strong></p>
</blockquote>
<p>가능하면</p>
<pre><code class="language-text">Resource
+
HTTP Method</code></pre>
<p>로 먼저 표현한다.</p>
<p>그렇게 설계하기 어려운 경우에 Control URI를 사용하는 것이다.</p>
<p>내 식으로 정리하면</p>
<blockquote>
<p><strong>일단 리소스와 HTTP Method로 최대한 설계해보고, 정말 표현하기 애매할 때 사용하는 최후의 수단에 가깝다.</strong></p>
</blockquote>
<p>정도로 기억하면 될 것 같다.</p>
<hr>
<h1 id="http-api-설계를-다시-정리하면">HTTP API 설계를 다시 정리하면</h1>
<p>여기까지 배우고 나니 POST와 PUT을 단순히</p>
<pre><code class="language-text">POST = 등록

PUT = 수정</code></pre>
<p>이라고만 외우면 부족하다는 생각이 들었다.</p>
<p>등록 방식 자체에서도 차이가 있다.</p>
<pre><code class="language-text">HTTP API - Collection

POST 기반 등록
→ 서버가 Resource URI 결정</code></pre>
<pre><code class="language-text">HTTP API - Store

PUT 기반 등록
→ 클라이언트가 Resource URI 결정</code></pre>
<p>그리고 순수 HTML Form은</p>
<pre><code class="language-text">GET
POST</code></pre>
<p>만 지원하기 때문에 필요하다면 Control URI를 사용한다.</p>
<p>강의 마지막에서도 이 세 가지를 <code>POST 기반 Collection</code>, <code>PUT 기반 Store</code>, <code>GET/POST만 지원하는 HTML Form</code>으로 정리한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="마지막으로-uri-설계-개념을-정리해보자">마지막으로 URI 설계 개념을 정리해보자</h1>
<p>이번 수업 마지막에는 URI를 설계할 때 참고할 만한 네 가지 개념이 등장했다.</p>
<pre><code class="language-text">Document

Collection

Store

Controller</code></pre>
<p>처음 보면 이름이 비슷해서 헷갈리는데 결국 <strong>누가 무엇을 식별하고 관리하느냐</strong>를 보면 조금 쉽게 구분할 수 있었다.</p>
<hr>
<h2 id="document">Document</h2>
<p>Document는 하나의 개별 리소스다.</p>
<p>예를 들어</p>
<pre><code class="language-text">/members/100</code></pre>
<p>은 100번 회원 하나를 나타낸다.</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>는 <code>star.jpg</code> 파일 하나를 나타낸다.</p>
<p>즉</p>
<pre><code class="language-text">Document
→ 단일 개념
→ 객체 하나
→ DB Row 하나
→ 파일 하나</code></pre>
<p>정도로 생각할 수 있다.</p>
<hr>
<h2 id="collection">Collection</h2>
<p>Collection은 서버가 관리하는 리소스 디렉터리다.</p>
<pre><code class="language-text">/members</code></pre>
<p>를 생각하면 된다.</p>
<pre><code class="language-text">POST /members
        ↓
Server
        ↓
/members/100 생성</code></pre>
<p>핵심은</p>
<blockquote>
<p><strong>서버가 새로운 Resource URI를 결정한다.</strong></p>
</blockquote>
<hr>
<h2 id="store">Store</h2>
<p>Store는 클라이언트가 관리하는 리소스 저장소다.</p>
<pre><code class="language-text">/files</code></pre>
<p>를 생각하면 된다.</p>
<pre><code class="language-text">PUT /files/star.jpg</code></pre>
<p>처럼 클라이언트가 직접 URI를 알고 지정한다.</p>
<p>핵심은</p>
<blockquote>
<p><strong>클라이언트가 Resource URI를 결정한다.</strong></p>
</blockquote>
<hr>
<h2 id="controller">Controller</h2>
<p>마지막은 Controller다.</p>
<p>Document, Collection, Store만으로 표현하기 어려운 추가적인 프로세스를 실행할 때 사용할 수 있다.</p>
<p>이 경우에는 URI에 동사를 직접 사용할 수 있다.</p>
<pre><code class="language-text">/members/100/delete</code></pre>
<p>즉 앞에서 봤던 Control URI와 연결된다.</p>
<p>강의의 마지막 페이지도 Document를 단일 리소스, Collection을 서버 관리 디렉터리, Store를 클라이언트 관리 저장소, Controller를 추가 프로세스 실행을 위한 동사 URI로 구분한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="정리">정리</h1>
<p>처음에는 이번 내용이 조금 따로 노는 것처럼 느껴졌다.</p>
<pre><code class="language-text">Query Parameter

HTML Form

multipart/form-data

JSON

Collection

Store

Control URI</code></pre>
<p>용어가 계속 새로 나왔기 때문이다.</p>
<p>그런데 하나의 흐름으로 다시 정리해보니 결국 두 가지 질문으로 연결됐다.</p>
<blockquote>
<p><strong>클라이언트가 서버에 데이터를 어떻게 전달할 것인가?</strong></p>
</blockquote>
<p>그리고</p>
<blockquote>
<p><strong>서버의 리소스를 어떤 URI와 HTTP Method로 표현할 것인가?</strong></p>
</blockquote>
<p>첫 번째 질문에서는 데이터 전달 방법이 나온다.</p>
<pre><code class="language-text">조회 조건
→ Query Parameter

HTML Form
→ application/x-www-form-urlencoded

파일 업로드
→ multipart/form-data

HTTP API
→ application/json</code></pre>
<p>두 번째 질문에서는 API 설계 방식이 나온다.</p>
<pre><code class="language-text">Collection
→ POST 기반
→ 서버가 URI 결정

Store
→ PUT 기반
→ 클라이언트가 URI 결정

HTML Form
→ GET / POST 제약

Controller
→ HTTP Method만으로 표현하기 어려운 추가 동작</code></pre>
<p>특히 이번에 가장 기억에 남은 부분은 <strong>POST와 PUT의 차이</strong>였다.</p>
<p>전에는</p>
<pre><code class="language-text">POST = 생성
PUT = 수정</code></pre>
<p>정도로만 생각했다.</p>
<p>그런데 API 설계 관점에서 보면</p>
<pre><code class="language-text">POST /members
→ &quot;회원 하나 만들어줘.&quot;
→ 정확한 URI는 서버가 결정</code></pre>
<p>와</p>
<pre><code class="language-text">PUT /files/star.jpg
→ &quot;바로 이 URI에 저장할 거야.&quot;
→ URI를 클라이언트가 결정</code></pre>
<p>라는 차이도 있었다.</p>
<p>그리고 HTML Form을 보면서 앞에서 배운 HTTP Method의 의미가 왜 중요한지도 다시 연결됐다.</p>
<p>GET으로도 Form 데이터를 서버까지 전달할 수 있다.</p>
<p>하지만</p>
<pre><code class="language-text">전송할 수 있다
≠
그렇게 설계해도 된다</code></pre>
<p>였다.</p>
<p>GET은 조회라는 의미를 가지고 있기 때문에 리소스를 변경하는 용도로 사용해서는 안 된다.</p>
<p>결국 HTTP를 공부하면서 계속 느끼는 것은 단순히</p>
<pre><code class="language-text">&quot;요청이 서버까지 가는가?&quot;</code></pre>
<p>만 생각해서는 안 된다는 것이다.</p>
<pre><code class="language-text">이 요청의 의미가 무엇인지

리소스는 무엇인지

어떤 Method가 그 의미에 맞는지

데이터는 어디에 담아야 하는지</code></pre>
<p>까지 함께 생각해야 한다.</p>
<p>처음에는 HTTP 요청이 그냥</p>
<pre><code class="language-text">URL 하나 정하고
JSON 보내면 되는 것</code></pre>
<p>정도라고 생각했는데, 하나씩 뜯어보니 그 안에도 꽤 많은 설계 기준이 들어 있었다.</p>
<h1 id="그런데-여기서-한-가지-더-궁금해졌다">그런데 여기서 한 가지 더 궁금해졌다</h1>
<p>여기까지 정리하고 나니 앞에서 배운 <strong>URI 설계와 HTTP Method가 이제야 하나로 연결되는 느낌</strong>이 들었다.</p>
<p>앞에서는 URI를 설계할 때 가장 중요한 것은 <strong>리소스를 식별하는 것</strong>이라고 배웠다.</p>
<p>예를 들어 회원 관리 시스템에서 리소스는</p>
<pre><code class="language-text">회원 조회
회원 등록
회원 수정
회원 삭제</code></pre>
<p>가 아니다.</p>
<p>이것들은 모두 <strong>행위</strong>다.</p>
<p>실제 리소스는</p>
<pre><code class="language-text">회원(Member)</code></pre>
<p>이다.</p>
<p>그래서 URI에는 회원이라는 리소스를 표현한다.</p>
<pre><code class="language-text">/members

/members/100</code></pre>
<p>그리고 그 리소스를 가지고 <strong>무엇을 할 것인지</strong>는 HTTP Method가 담당한다.</p>
<pre><code class="language-text">GET    /members/100
POST   /members
PATCH  /members/100
DELETE /members/100</code></pre>
<p>이렇게 보니까 처음 HTTP Method를 배울 때 했던</p>
<blockquote>
<p><strong>&quot;URI에 <code>/getMember</code>, <code>/createMember</code>, <code>/deleteMember</code>라고 쓰면 더 알아보기 쉽지 않나?&quot;</strong></p>
</blockquote>
<p>라는 생각에 대한 답도 조금 명확해졌다.</p>
<pre><code class="language-text">URI
→ 무엇을 대상으로 하는가?

HTTP Method
→ 그것을 가지고 무엇을 하는가?</code></pre>
<p>역할을 나눠놓은 것이다.</p>
<hr>
<h1 id="uri와-query-parameter도-역할이-다르다">URI와 Query Parameter도 역할이 다르다</h1>
<p>여기서 또 하나 헷갈릴 수 있는 것이 있다.</p>
<pre><code class="language-text">/members/100</code></pre>
<p>과</p>
<pre><code class="language-text">/members?age=20</code></pre>
<p>은 무엇이 다른 걸까?</p>
<p>둘 다 회원을 조회하는 것처럼 보인다.</p>
<p>그런데 앞에서 배운 내용을 적용해보면 차이를 이해할 수 있다.</p>
<pre><code class="language-text">GET /members/100</code></pre>
<p>은 <strong>100번이라는 특정 회원 리소스</strong>를 식별한다.</p>
<p>반면</p>
<pre><code class="language-text">GET /members?age=20</code></pre>
<p>은 회원 Collection을 조회하면서</p>
<pre><code class="language-text">age = 20</code></pre>
<p>이라는 <strong>조회 조건</strong>을 추가한 것이다.</p>
<p>즉 내 식으로 구분하면 다음과 같다.</p>
<pre><code class="language-text">Path
→ 어떤 리소스인가?

Query Parameter
→ 어떤 조건으로 조회할 것인가?</code></pre>
<p>예를 들어</p>
<pre><code class="language-http">GET /members/100</code></pre>
<p>이라면</p>
<pre><code class="language-text">100번 회원 주세요.</code></pre>
<p>이고,</p>
<pre><code class="language-http">GET /members?age=20</code></pre>
<p>이라면</p>
<pre><code class="language-text">회원들 중에서
age가 20인 회원을 찾아주세요.</code></pre>
<p>라고 볼 수 있다.</p>
<p>동적 데이터 조회에서 Query Parameter가 주로 검색, 필터, 정렬 조건으로 사용된다는 것도 이 관점에서 이해할 수 있었다. 5.http-method-use.pdf</p>
<hr>
<h1 id="content-type은-왜-필요할까">Content-Type은 왜 필요할까?</h1>
<p>데이터 전송 방식을 정리하면서 계속 등장하는 HTTP Header가 하나 있었다.</p>
<pre><code class="language-text">Content-Type</code></pre>
<p>HTML Form에서는</p>
<pre><code class="language-http">Content-Type: application/x-www-form-urlencoded</code></pre>
<p>파일을 전송할 때는</p>
<pre><code class="language-http">Content-Type: multipart/form-data</code></pre>
<p>HTTP API에서는</p>
<pre><code class="language-http">Content-Type: application/json</code></pre>
<p>을 사용했다.</p>
<p>처음에는 그냥</p>
<blockquote>
<p><strong>&quot;보내는 데이터 형식에 따라서 정해놓은 문자열인가?&quot;</strong></p>
</blockquote>
<p>정도로 생각했다.</p>
<p>그런데 서버 입장에서 생각해보니 왜 필요한지 이해하기 쉬웠다.</p>
<p>클라이언트가 다음 데이터를 보냈다고 해보자.</p>
<pre><code class="language-text">username=kim&amp;age=20</code></pre>
<p>서버는 이 데이터를 어떤 형식으로 해석해야 할지 알아야 한다.</p>
<p>그래서</p>
<pre><code class="language-http">Content-Type: application/x-www-form-urlencoded</code></pre>
<p>이라고 알려준다.</p>
<p>이번에는 다음과 같은 데이터가 들어온다.</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>그러면</p>
<pre><code class="language-http">Content-Type: application/json</code></pre>
<p>을 통해</p>
<blockquote>
<p><strong>&quot;내가 지금 보내는 Message Body는 JSON이야.&quot;</strong></p>
</blockquote>
<p>라고 서버에게 알려주는 것이다.</p>
<p>즉 Content-Type은 <strong>Message Body에 담긴 데이터의 표현 형식(Media Type)</strong>을 알려준다.</p>
<pre><code class="language-text">Client

Content-Type: application/json
             ↓
        Message Body

{
    &quot;username&quot;: &quot;kim&quot;
}

             ↓

Server

&quot;JSON으로 해석하면 되겠구나.&quot;</code></pre>
<p>HTML Form에서는 <code>application/x-www-form-urlencoded</code>를 사용하고 파일과 Form 데이터를 함께 보낼 때는 <code>multipart/form-data</code>를 사용한다. 반면 HTTP API에서는 JSON을 주로 사용한다.  <a href="sediment://file_00000000f6f48209ab1aaf62d03d5015">oai_citation:1‡5.http-method-use.pdf</a></p>
<hr>
<h1 id="post-기반-등록에서-서버가-uri를-만든다는-것은-무슨-뜻일까">POST 기반 등록에서 서버가 URI를 만든다는 것은 무슨 뜻일까?</h1>
<p>Collection 부분을 처음 봤을 때는</p>
<pre><code class="language-text">서버가 URI를 생성한다.</code></pre>
<p>라는 표현이 조금 애매하게 느껴졌다.</p>
<p>회원가입을 예로 들어보자.</p>
<p>현재 서버에 다음 회원들이 있다고 하자.</p>
<pre><code class="language-text">/members/1
/members/2
/members/3</code></pre>
<p>새로운 회원을 등록한다.</p>
<p>클라이언트는 다음 요청을 보낸다.</p>
<pre><code class="language-http">POST /members HTTP/1.1
Content-Type: application/json

{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>여기서 클라이언트는</p>
<pre><code class="language-text">/members/4</code></pre>
<p>를 요청하지 않았다.</p>
<p>그냥</p>
<pre><code class="language-text">/members</code></pre>
<p>라는 Collection에 새로운 회원을 등록해달라고 요청했다.</p>
<pre><code class="language-text">Client

&quot;회원 한 명 등록해주세요.&quot;

POST /members

        ↓

Server

새로운 ID 결정
id = 4

        ↓

/members/4</code></pre>
<p>그리고 서버가 응답한다.</p>
<pre><code class="language-http">HTTP/1.1 201 Created
Location: /members/4</code></pre>
<p>즉 <code>Location</code>을 통해 새로 생성된 리소스가 어디에 있는지를 알려줄 수 있다.</p>
<p>이것이</p>
<blockquote>
<p><strong>Collection에서는 서버가 리소스의 URI를 생성하고 관리한다.</strong></p>
</blockquote>
<p>는 의미였다. 강의에서도 POST로 <code>/members</code>에 등록한 뒤 서버가 <code>/members/100</code>과 같은 새 URI를 만들고 <code>201 Created</code>와 <code>Location</code>으로 응답하는 예시를 사용한다. 5.http-method-use.pdf</p>
<hr>
<h1 id="put-기반-store에서는-왜-클라이언트가-uri를-알아야-할까">PUT 기반 Store에서는 왜 클라이언트가 URI를 알아야 할까?</h1>
<p>이번에는 파일을 저장한다고 해보자.</p>
<p>클라이언트가 <code>star.jpg</code>라는 파일을 정확히 이 위치에 저장하고 싶다.</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>그러면 요청부터 URI가 완성되어 있다.</p>
<pre><code class="language-http">PUT /files/star.jpg</code></pre>
<p>즉 클라이언트가 서버에게</p>
<blockquote>
<p><strong>&quot;<code>/files/star.jpg</code>라는 리소스를 여기에 둘 거야.&quot;</strong></p>
</blockquote>
<p>라고 위치까지 지정한 셈이다.</p>
<pre><code class="language-text">POST /members
→ 어디에 생성될지는 서버가 결정

PUT /files/star.jpg
→ 어디에 저장할지 클라이언트가 이미 결정</code></pre>
<p>그래서 Store에서는 클라이언트가 리소스 URI를 알고 관리한다. 5.http-method-use.pdf</p>
<p>여기서 앞에서 배웠던 PUT의 특징도 다시 연결됐다.</p>
<p>PUT은 클라이언트가 대상 리소스를 정확하게 알고 요청한다.</p>
<pre><code class="language-text">PUT /files/star.jpg</code></pre>
<p>그리고 해당 리소스가 있다면 대체하고, 없다면 생성할 수 있다.</p>
<p>결국 PUT을 이해할 때</p>
<pre><code class="language-text">&quot;수정할 때 쓰는 Method&quot;</code></pre>
<p>라고 외우는 것보다</p>
<pre><code class="language-text">&quot;클라이언트가 대상 리소스 URI를 알고 있고,
그 위치의 리소스를 대체한다.&quot;</code></pre>
<p>라고 이해하는 편이 더 정확했다.</p>
<hr>
<h1 id="collection과-store를-비교하면">Collection과 Store를 비교하면</h1>
<p>처음에는 둘 다</p>
<pre><code class="language-text">&quot;리소스 여러 개가 들어 있는 곳 아닌가?&quot;</code></pre>
<p>라는 생각이 들었다.</p>
<p>실제로 <code>/members</code>, <code>/files</code> 모두 여러 리소스를 관리한다.</p>
<p>하지만 차이는 <strong>새로운 리소스의 URI를 누가 결정하는가</strong>에 있었다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Collection</th>
<th>Store</th>
</tr>
</thead>
<tbody><tr>
<td>대표 Method</td>
<td>POST</td>
<td>PUT</td>
</tr>
<tr>
<td>URI 결정</td>
<td>서버</td>
<td>클라이언트</td>
</tr>
<tr>
<td>예시</td>
<td><code>/members</code></td>
<td><code>/files</code></td>
</tr>
<tr>
<td>등록 요청</td>
<td><code>POST /members</code></td>
<td><code>PUT /files/star.jpg</code></td>
</tr>
<tr>
<td>새 리소스</td>
<td>서버가 <code>/members/100</code> 생성</td>
<td>클라이언트가 <code>/files/star.jpg</code> 지정</td>
</tr>
</tbody></table>
<p>이 기준 하나를 잡으니 두 개념을 외우기가 훨씬 쉬웠다.</p>
<pre><code class="language-text">Collection
→ 서버가 관리

Store
→ 클라이언트가 관리</code></pre>
<hr>
<h1 id="document는-결국-하나의-리소스다">Document는 결국 하나의 리소스다</h1>
<p>Collection과 Store를 이해하고 나면 Document는 비교적 단순하다.</p>
<pre><code class="language-text">/members/100</code></pre>
<p>에서 <code>/members</code>가 Collection이라면</p>
<pre><code class="language-text">/members/100</code></pre>
<p>은 특정 회원 하나를 나타낸다.</p>
<p>이것이 Document다.</p>
<p>파일도 마찬가지다.</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>은 파일 하나를 나타낸다.</p>
<p>강의에서는 Document를 파일 하나, 객체 인스턴스 하나, 데이터베이스의 Row 하나와 같은 <strong>단일 개념</strong>으로 설명한다. 5.http-method-use.pdf</p>
<p>그래서 다음처럼 생각해볼 수 있다.</p>
<pre><code class="language-text">/members
   │
   ├── /members/100
   │        ↑
   │     Document
   │
   ├── /members/101
   │
   └── /members/102

↑
Collection</code></pre>
<hr>
<h1 id="controller는-정말-필요할-때-사용한다">Controller는 정말 필요할 때 사용한다</h1>
<p>마지막으로 Controller가 있다.</p>
<p>앞에서 URI 설계의 가장 중요한 원칙을</p>
<pre><code class="language-text">리소스를 식별한다.</code></pre>
<p>라고 배웠다.</p>
<p>그래서 가능하면 URI에는 명사를 사용하고 행위는 HTTP Method에 맡긴다.</p>
<pre><code class="language-text">GET /members/100

DELETE /members/100</code></pre>
<p>그런데 모든 비즈니스 동작을 HTTP Method만으로 자연스럽게 표현할 수 있는 것은 아니다.</p>
<p>특히 순수 HTML Form처럼 GET과 POST만 사용할 수 있는 환경에서는 제약이 생긴다.</p>
<p>그래서 다음과 같은 URI가 등장할 수 있다.</p>
<pre><code class="language-text">/members/100/delete</code></pre>
<p>여기서는 <code>delete</code>라는 동사가 URI에 직접 들어갔다.</p>
<p>이것이 <strong>Controller 또는 Control URI</strong>다.</p>
<p>강의에서는 Document, Collection, Store로 해결하기 어려운 추가 프로세스를 실행할 때 동사를 직접 사용하는 방식으로 설명한다. 5.http-method-use.pdf</p>
<p>하지만 이 부분은 순서를 기억하는 것이 중요할 것 같다.</p>
<pre><code class="language-text">1. 먼저 리소스를 식별한다.

2. HTTP Method로 행위를 표현한다.

3. 그래도 자연스럽게 표현하기 어렵다면

4. Control URI를 고려한다.</code></pre>
<p>즉 처음부터</p>
<pre><code class="language-text">/createMember
/updateMember
/deleteMember</code></pre>
<p>처럼 모든 동작을 URI에 넣는 것이 아니라는 것이다.</p>
<hr>
<h1 id="결국-앞에서-배운-내용이-전부-연결됐다">결국 앞에서 배운 내용이 전부 연결됐다</h1>
<p>HTTP를 처음 공부할 때는 각각 따로 외웠다.</p>
<pre><code class="language-text">URI

GET

POST

PUT

PATCH

DELETE

Query Parameter

Message Body

Content-Type</code></pre>
<p>그런데 HTTP API 설계까지 오니 이 개념들이 서로 역할을 나눠 가지고 있다는 것이 보이기 시작했다.</p>
<p>예를 들어 회원을 조회한다고 해보자.</p>
<pre><code class="language-http">GET /members/100</code></pre>
<p>여기에는 세 가지 정보가 이미 들어 있다.</p>
<pre><code class="language-text">GET
→ 무엇을 할 것인가?
→ 조회

/members/100
→ 무엇을 대상으로 할 것인가?
→ 100번 회원</code></pre>
<p>검색 조건까지 필요하다면</p>
<pre><code class="language-http">GET /members?age=20</code></pre>
<p>처럼 Query Parameter를 사용한다.</p>
<pre><code class="language-text">?age=20
→ 어떤 조건으로 조회할 것인가?</code></pre>
<p>이번에는 회원을 등록한다.</p>
<pre><code class="language-http">POST /members
Content-Type: application/json

{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>각 부분을 다시 나누면</p>
<pre><code class="language-text">POST
→ 요청 데이터를 처리한다.

/members
→ 대상 리소스는 회원 Collection이다.

Content-Type: application/json
→ Message Body는 JSON 형식이다.

Message Body
→ 실제로 전달할 회원 데이터다.</code></pre>
<p>이렇게 하나의 HTTP 요청 안에서도 각각의 요소가 맡고 있는 역할이 다르다.</p>
<hr>
<h1 id="내가-이해한-http-api-설계-순서">내가 이해한 HTTP API 설계 순서</h1>
<p>여기까지 공부한 내용을 가지고 내가 API를 설계한다고 생각해봤다.</p>
<p>예를 들어 회원 관리 API가 필요하다고 하자.</p>
<p>처음부터</p>
<pre><code class="language-text">GET을 쓸까?
POST를 쓸까?</code></pre>
<p>부터 생각하는 것이 아니라 먼저 <strong>리소스가 무엇인지</strong> 찾는다.</p>
<pre><code class="language-text">회원 등록
회원 조회
회원 수정
회원 삭제

↓

행위를 제거

↓

회원</code></pre>
<p>리소스는 <code>회원</code>이다.</p>
<p>그러면 URI를 만든다.</p>
<pre><code class="language-text">/members
/members/{id}</code></pre>
<p>그다음 행위를 HTTP Method로 분리한다.</p>
<pre><code class="language-text">GET /members
→ 회원 목록

POST /members
→ 회원 등록

GET /members/{id}
→ 회원 조회

PATCH /members/{id}
→ 회원 일부 수정

DELETE /members/{id}
→ 회원 삭제</code></pre>
<p>조회 조건이 필요하면 Query Parameter를 추가한다.</p>
<pre><code class="language-text">GET /members?age=20</code></pre>
<p>서버에 데이터를 전달해야 한다면 Message Body를 사용한다.</p>
<pre><code class="language-http">POST /members
Content-Type: application/json

{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>그리고 HTTP Method만으로 표현하기 애매한 특별한 프로세스가 있다면 마지막으로 Control URI를 생각해본다.</p>
<pre><code class="language-text">리소스 식별
    ↓
URI 설계
    ↓
HTTP Method 선택
    ↓
필요한 데이터 전달 방식 결정
    ↓
그래도 표현하기 어려운 동작
    ↓
Control URI 고려</code></pre>
<p>처음 HTTP API를 볼 때는 URL을 예쁘게 짓는 것이 API 설계라고 생각했던 것 같다.</p>
<p>그런데 지금까지 배운 내용을 연결해보면 API 설계는 단순히 URL 이름을 정하는 작업이 아니었다.</p>
<blockquote>
<p><strong>어떤 것을 리소스로 볼 것인지 결정하고, 그 리소스에 대한 행위를 HTTP의 의미에 맞게 표현하는 과정이었다.</strong></p>
</blockquote>
<hr>
<h1 id="이번-내용을-마무리하며">이번 내용을 마무리하며</h1>
<p>이번 수업에서 가장 크게 바뀐 생각은 <strong>HTTP 요청을 하나의 문자열처럼 보지 않게 됐다는 것</strong>이다.</p>
<p>예전에는</p>
<pre><code class="language-http">POST /members</code></pre>
<p>를 보면 그냥</p>
<blockquote>
<p>&quot;회원 API 호출하는구나.&quot;</p>
</blockquote>
<p>정도로 생각했다.</p>
<p>지금은 조금 다르게 보인다.</p>
<pre><code class="language-text">POST
→ 이 리소스에 데이터를 보내 처리해달라는 의미

/members
→ 회원이라는 리소스 Collection

Content-Type
→ 전달하는 데이터의 표현 형식

Message Body
→ 실제 전달 데이터</code></pre>
<p>그리고 같은 데이터를 서버에 전달하더라도 상황에 따라 방법이 달라진다.</p>
<pre><code class="language-text">단순한 리소스 조회
→ URI

검색 / 필터 / 정렬
→ Query Parameter

HTML Form
→ application/x-www-form-urlencoded

파일 + Form
→ multipart/form-data

HTTP API
→ application/json</code></pre>
<p>API를 설계할 때도 마찬가지였다.</p>
<pre><code class="language-text">Document
→ 하나의 리소스

Collection
→ 서버가 URI를 관리하는 리소스 집합

Store
→ 클라이언트가 URI를 관리하는 저장소

Controller
→ 일반적인 리소스 구조로 표현하기 어려운 추가 동작</code></pre>
<p>결국 이번 내용도 앞에서 배운 <strong>&quot;URI는 리소스를 식별한다&quot;</strong>라는 원칙으로 다시 돌아온다.</p>
<pre><code class="language-text">무엇을 대상으로 하는가?
→ URI

무엇을 할 것인가?
→ HTTP Method

어떤 조건인가?
→ Query Parameter

어떤 데이터를 보낼 것인가?
→ Message Body

그 데이터는 어떤 형식인가?
→ Content-Type</code></pre>
<p>이렇게 역할을 하나씩 분리해서 보니까 HTTP 메시지가 전보다 훨씬 읽기 쉬워졌다.</p>
<p>그리고 백엔드에서 API를 만들 때도 단순히</p>
<pre><code class="language-text">&quot;일단 요청이 들어오게 만들자.&quot;</code></pre>
<p>가 아니라</p>
<blockquote>
<p><strong>&quot;이 요청을 처음 보는 사람도 URI와 HTTP Method만 보고 어떤 의미인지 이해할 수 있을까?&quot;</strong></p>
</blockquote>
<p>를 한 번 더 생각해보는 것이 중요할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조는 어떻게 API가 될까? | ADT부터 Stack과 Resizing Array까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-API%EA%B0%80-%EB%90%A0%EA%B9%8C-ADT%EB%B6%80%ED%84%B0-Stack%EA%B3%BC-Resizing-Array%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-API%EA%B0%80-%EB%90%A0%EA%B9%8C-ADT%EB%B6%80%ED%84%B0-Stack%EA%B3%BC-Resizing-Array%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Thu, 17 Sep 2026 05:49:42 GMT</pubDate>
            <description><![CDATA[<h1 id="자료구조는-어떻게-api가-될까--adt부터-stack과-resizing-array까지">자료구조는 어떻게 API가 될까? | ADT부터 Stack과 Resizing Array까지</h1>
<p>지난 시간에는 알고리즘을 왜 공부하는지부터 시작해서 문제 해결 방법과 재귀 호출까지 살펴봤다.</p>
<p>이번 시간에는 <strong>추상 데이터 타입(Abstract Data Type, ADT)</strong>에 대해 배웠다.</p>
<p>처음 ADT라는 말을 들었을 때는 이름부터 조금 어렵게 느껴졌다.</p>
<pre><code class="language-text">Abstract Data Type
→ 추상 데이터 타입</code></pre>
<p>그런데 수업을 듣다 보니 결국 중요한 질문은 생각보다 단순했다.</p>
<blockquote>
<p><strong>자료구조를 사용하는 사람이 그 자료구조가 내부에서 어떻게 구현되어 있는지까지 알아야 할까?</strong></p>
</blockquote>
<p>예를 들어 Stack을 사용한다고 해보자.</p>
<p>나는 Stack에 데이터를 넣고 싶다.</p>
<pre><code class="language-java">stack.push(&quot;A&quot;);</code></pre>
<p>그리고 가장 마지막에 넣은 데이터를 꺼내고 싶다.</p>
<pre><code class="language-java">stack.pop();</code></pre>
<p>그런데 Stack이 내부에서 배열로 만들어졌는지, 연결 리스트로 만들어졌는지까지 Stack을 사용하는 코드가 알아야 할까?</p>
<p>꼭 그럴 필요는 없다.</p>
<p>이번 시간에는 여기서 출발해서 ADT와 API가 어떤 관계를 가지는지, 그리고 같은 Stack을 Linked List와 Array로 구현했을 때 어떤 차이가 생기는지를 살펴봤다.</p>
<p>특히 Array로 Stack을 구현하면서</p>
<pre><code class="language-text">simple
→ doubling
→ amortized</code></pre>
<p>방식으로 배열 크기를 조절하는 방법이 바뀌는 과정이 재미있었다.</p>
<p>단순히 Stack을 구현하는 문제가 아니라</p>
<blockquote>
<p><strong>같은 기능을 제공하면서 내부 구현을 어떻게 선택해야 할까?</strong></p>
</blockquote>
<p>라는 문제로 이어졌기 때문이다.</p>
<hr>
<h2 id="추상-데이터-타입-adt란-무엇일까">추상 데이터 타입, ADT란 무엇일까?</h2>
<p>먼저 데이터 추상화(Data Abstraction)부터 시작해보자.</p>
<p>데이터 추상화는 새로운 데이터 타입을 정의하는 것이다.</p>
<p>Java에는 이미 여러 데이터 타입이 존재한다.</p>
<pre><code class="language-java">int
double
boolean</code></pre>
<p>뿐만 아니라</p>
<pre><code class="language-java">String
Integer
Double
StringBuilder</code></pre>
<p>같은 타입도 사용할 수 있다.</p>
<p>그런데 프로그램을 만들다 보면 언어에서 기본적으로 제공하는 타입만으로는 부족할 수 있다.</p>
<p>그래서 개발자가 자신이 해결하려는 문제에 맞는 새로운 데이터 타입을 정의할 수 있다.</p>
<p>그중 <strong>ADT(Abstract Data Type)</strong>의 중요한 특징은 데이터가 내부적으로 어떻게 표현되어 있는지를 사용자에게 숨긴다는 것이다.</p>
<p>예를 들어 Counter라는 데이터 타입을 만든다고 해보자.</p>
<pre><code class="language-java">Counter counter = new Counter(&quot;heads&quot;);

counter.increment();

System.out.println(counter.tally());</code></pre>
<p>Counter를 사용하는 입장에서 필요한 것은</p>
<pre><code class="language-text">Counter를 어떻게 생성하는가?

값을 어떻게 증가시키는가?

현재 값을 어떻게 가져오는가?</code></pre>
<p>이다.</p>
<p>내부에서 값을 어떤 변수에 저장하는지는 굳이 알 필요가 없다.</p>
<pre><code class="language-java">private String name;
private int cnt;</code></pre>
<p>내부 구현은 Counter가 책임진다.</p>
<p>사용자는 공개된 동작만 이용하면 된다.</p>
<hr>
<h2 id="adt를-배우면서-api가-왜-나오는-걸까">ADT를 배우면서 API가 왜 나오는 걸까?</h2>
<p>여기서 처음에는 조금 의문이 들었다.</p>
<blockquote>
<p><strong>자료구조를 배우는데 왜 갑자기 API 이야기가 나오는 걸까?</strong></p>
</blockquote>
<p>그런데 ADT를 조금 다르게 보면 연결되는 부분이 있었다.</p>
<p>Stack을 예로 들어보자.</p>
<p>Stack이 제공해야 하는 기능을 먼저 정의할 수 있다.</p>
<pre><code class="language-java">Stack()

void push(Item item)

Item pop()

boolean isEmpty()

int size()</code></pre>
<p>이것이 Stack을 사용하는 사람이 바라보는 <strong>인터페이스</strong>, 즉 API가 된다.</p>
<p>사용자는</p>
<pre><code class="language-java">stack.push(item);
stack.pop();
stack.size();</code></pre>
<p>를 알고 있으면 된다.</p>
<p>그 뒤에서 Stack을 어떻게 구현할지는 별개의 문제다.</p>
<pre><code class="language-text">Client
   │
   │ push(), pop(), size()
   ▼
Interface / API
   │
   ▼
Implementation</code></pre>
<p>즉 알고리즘이나 자료구조를</p>
<pre><code class="language-text">사용 방법

구현 방법</code></pre>
<p>으로 분리할 수 있다.</p>
<p>수업 자료에서는 이를 Modular Programming의 관점에서도 설명한다.</p>
<pre><code class="language-text">Client
→ Interface에 정의된 연산을 사용하는 프로그램

Interface
→ 데이터 타입과 기본 연산에 대한 설명

Implementation
→ 실제 연산을 구현하는 코드</code></pre>
<p>처음에는 API라고 하면 REST API처럼</p>
<pre><code class="language-text">GET /members
POST /members</code></pre>
<p>같은 것만 떠올렸다.</p>
<p>그런데 조금 더 넓게 보면 API는 <strong>어떤 기능을 사용할 수 있고 어떻게 호출해야 하는지를 외부에 제공하는 접점</strong>이라고 볼 수 있었다.</p>
<p>Stack에서도 마찬가지다.</p>
<p>사용자는</p>
<pre><code class="language-java">push()
pop()
isEmpty()
size()</code></pre>
<p>만 알면 된다.</p>
<p>그 안에서 배열을 쓰든 연결 리스트를 쓰든 사용자의 코드와 분리할 수 있다.</p>
<hr>
<h2 id="stack과-queue는-무엇이-다를까">Stack과 Queue는 무엇이 다를까?</h2>
<p>ADT의 대표적인 예로 Stack과 Queue가 있다.</p>
<p>둘의 가장 큰 차이는</p>
<blockquote>
<p><strong>어떤 데이터를 먼저 꺼내는가?</strong></p>
</blockquote>
<p>이다.</p>
<p>Stack은</p>
<pre><code class="language-text">Last In First Out
LIFO</code></pre>
<p>구조다.</p>
<p>마지막으로 들어온 데이터가 가장 먼저 나온다.</p>
<pre><code class="language-text">push A
push B
push C

Stack

│ C │ ← 먼저 pop
│ B │
│ A │
└───┘</code></pre>
<p>따라서</p>
<pre><code class="language-text">pop → C
pop → B
pop → A</code></pre>
<p>순서가 된다.</p>
<p>반면 Queue는</p>
<pre><code class="language-text">First In First Out
FIFO</code></pre>
<p>구조다.</p>
<p>먼저 들어온 데이터가 먼저 나온다.</p>
<pre><code class="language-text">A → B → C

dequeue → A</code></pre>
<p>이번 시간에는 이 중 Stack을 직접 구현하면서 내부 구현 방식의 차이를 살펴봤다.</p>
<hr>
<h1 id="stack은-어떻게-구현할까">Stack은 어떻게 구현할까?</h1>
<p>Stack이라는 ADT의 API는 이미 정했다.</p>
<pre><code class="language-java">push()
pop()
isEmpty()
size()</code></pre>
<p>그런데 여기서 중요한 질문이 생긴다.</p>
<blockquote>
<p><strong>이 기능을 실제로 어떻게 구현할까?</strong></p>
</blockquote>
<p>대표적인 방법은 두 가지다.</p>
<pre><code class="language-text">Stack

├── Linked List
└── Array</code></pre>
<p>흥미로운 점은 내부 구현은 완전히 다른데 외부에서 사용하는 API는 같게 만들 수 있다는 것이다.</p>
<pre><code class="language-java">stack.push(&quot;A&quot;);
stack.pop();</code></pre>
<p>사용자는 Stack 내부가 무엇인지 몰라도 된다.</p>
<p>ADT가 왜 구현을 숨기는지 여기서 조금 더 명확하게 느껴졌다.</p>
<hr>
<h1 id="1-linked-list로-stack-구현하기">1. Linked List로 Stack 구현하기</h1>
<p>먼저 Linked List를 사용할 수 있다.</p>
<p>각 Node가 다음 Node를 가리키는 구조다.</p>
<pre><code class="language-text">first
  ↓
[A | •] → [B | •] → [C | null]</code></pre>
<p>Stack에서는 새로운 데이터를 넣거나 제거할 위치를 <code>first</code>로 잡을 수 있다.</p>
<p>새로운 데이터를 push한다고 해보자.</p>
<pre><code class="language-text">기존

first
  ↓
[A] → [B]</code></pre>
<p><code>C</code>를 push한다면</p>
<pre><code class="language-text">first
  ↓
[C] → [A] → [B]</code></pre>
<p>가 된다.</p>
<p>반대로 pop하면 <code>first</code>가 가리키는 Node를 제거한다.</p>
<pre><code class="language-text">first
  ↓
[C] → [A] → [B]

pop()

first
  ↓
[A] → [B]</code></pre>
<p>Linked List에서는 이런 연산을 위해 모든 배열 데이터를 복사할 필요가 없다.</p>
<p>연결 관계만 바꾸면 된다.</p>
<p>따라서 Stack의 주요 연산을 일정한 시간에 수행할 수 있다.</p>
<hr>
<h2 id="그런데-linked-list도-공짜는-아니다">그런데 Linked List도 공짜는 아니다</h2>
<p>처음에는 여기까지 보고</p>
<blockquote>
<p><strong>&quot;그러면 그냥 Linked List를 쓰면 되는 것 아닌가?&quot;</strong></p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>하지만 Linked List에도 비용이 있다.</p>
<p>Node 하나에는 실제 데이터만 저장되는 것이 아니다.</p>
<pre><code class="language-java">class Node {
    Item item;
    Node next;
}</code></pre>
<p>처럼 다음 Node를 가리키기 위한 참조도 저장해야 한다.</p>
<p>즉</p>
<pre><code class="language-text">[item]</code></pre>
<p>만 필요한 것이 아니라</p>
<pre><code class="language-text">[item | next]</code></pre>
<p>가 필요하다.</p>
<p>데이터가 많아질수록 이 <code>next</code>를 위한 추가 공간도 계속 필요해진다.</p>
<p>그래서 Linked List는 연산 시간이 일정하다는 장점이 있지만 <strong>링크를 유지하기 위한 추가 공간이 필요하다.</strong></p>
<hr>
<h1 id="2-array로-stack-구현하기">2. Array로 Stack 구현하기</h1>
<p>두 번째 방법은 Array다.</p>
<p>배열을 하나 만들고 <code>N</code>을 현재 Stack의 크기로 사용한다.</p>
<pre><code class="language-text">index

 0   1   2   3   4
┌───┬───┬───┬───┬───┐
│ A │ B │ C │   │   │
└───┴───┴───┴───┴───┘
            ↑
            N = 3</code></pre>
<p>push는</p>
<pre><code class="language-java">a[N++] = item;</code></pre>
<p>처럼 구현할 수 있다.</p>
<p>pop은</p>
<pre><code class="language-java">return a[--N];</code></pre>
<p>처럼 마지막 데이터를 꺼낼 수 있다.</p>
<p>처음 보면 Linked List보다 훨씬 단순해 보인다.</p>
<p>그런데 Array에는 다른 문제가 있다.</p>
<hr>
<h1 id="stack이-얼마나-커질지-어떻게-알지">Stack이 얼마나 커질지 어떻게 알지?</h1>
<p>배열은 생성할 때 크기를 정해야 한다.</p>
<pre><code class="language-java">String[] a = new String[10];</code></pre>
<p>그러면 최대 10개의 데이터를 넣을 수 있다.</p>
<p>하지만 Stack을 사용하는 Client가 데이터를 몇 개 넣을지는 미리 알기 어렵다.</p>
<pre><code class="language-text">10개?

100개?

10,000개?</code></pre>
<p>처음부터 너무 큰 배열을 만들면 메모리가 낭비된다.</p>
<p>반대로 너무 작게 만들면 Stack이 가득 찬다.</p>
<p>그래서 필요한 것이 <strong>Resizing Array</strong>다.</p>
<blockquote>
<p><strong>Stack의 크기에 따라 배열 자체의 크기를 동적으로 조절하자.</strong></p>
</blockquote>
<hr>
<h1 id="resize는-어떻게-동작할까">resize()는 어떻게 동작할까?</h1>
<p>배열은 생성한 뒤 그 자체의 크기를 변경할 수 없다.</p>
<p>따라서 크기를 변경한다는 것은 실제로 기존 배열의 크기를 바꾸는 것이 아니다.</p>
<p>새로운 배열을 만든 뒤 기존 데이터를 복사해야 한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">기존 배열

[A][B][C][D]</code></pre>
<p>가 가득 찼다고 해보자.</p>
<p>더 큰 배열을 만든다.</p>
<pre><code class="language-text">새로운 배열

[ ][ ][ ][ ][ ][ ][ ][ ]</code></pre>
<p>그리고 기존 데이터를 전부 복사한다.</p>
<pre><code class="language-text">[A][B][C][D]
 ↓  ↓  ↓  ↓
[A][B][C][D][ ][ ][ ][ ]</code></pre>
<p>코드로는 다음과 같은 형태가 된다.</p>
<pre><code class="language-java">private void resize(int max) {

    Item[] temp = (Item[]) new Object[max];

    for (int i = 0; i &lt; N; i++) {
        temp[i] = a[i];
    }

    a = temp;
}</code></pre>
<p>여기서 중요한 부분은</p>
<pre><code class="language-java">for (int i = 0; i &lt; N; i++)</code></pre>
<p>이다.</p>
<p>현재 데이터가 N개라면 N개의 데이터를 새로운 배열로 복사해야 한다.</p>
<p>즉 <code>resize()</code> 자체에는 <strong>N에 비례하는 복사 비용</strong>이 발생한다.</p>
<p>그래서 Array 기반 Stack을 분석할 때는 단순히</p>
<pre><code class="language-text">push → 배열 한 칸에 저장 → O(1)</code></pre>
<p>이라고만 볼 수 없다.</p>
<blockquote>
<p><strong>resize가 언제, 얼마나 자주 발생하는지도 같이 봐야 한다.</strong></p>
</blockquote>
<hr>
<h1 id="가장-단순하게-resize하면-안-될까">가장 단순하게 resize하면 안 될까?</h1>
<p>가장 먼저 생각할 수 있는 방법은 <code>simple</code> 방식이다.</p>
<p>데이터가 하나 늘어날 때마다 배열도 정확히 하나씩 늘리는 것이다.</p>
<p>예를 들어 현재 배열의 크기가 4라고 해보자.</p>
<pre><code class="language-text">[A][B][C][D]</code></pre>
<p>새로운 <code>E</code>를 넣어야 한다.</p>
<p>그러면 크기가 5인 배열을 만든다.</p>
<pre><code class="language-text">[A][B][C][D][E]</code></pre>
<p>다음 <code>F</code>가 들어온다.</p>
<p>다시 크기가 6인 배열을 만든다.</p>
<pre><code class="language-text">[A][B][C][D][E][F]</code></pre>
<p>계속 반복된다.</p>
<pre><code class="language-text">push E
4 → 5

push F
5 → 6

push G
6 → 7

push H
7 → 8</code></pre>
<p>문제는 배열 크기만 바뀌는 것이 아니라 <strong>매번 기존 데이터를 전부 복사해야 한다는 것</strong>이다.</p>
<p>예를 들어 5개의 데이터를 순서대로 추가한다고 생각해보자.</p>
<pre><code class="language-text">1번째 resize → 1개 복사
2번째 resize → 2개 복사
3번째 resize → 3개 복사
4번째 resize → 4개 복사
5번째 resize → 5개 복사</code></pre>
<p>결국</p>
<pre><code class="language-text">1 + 2 + 3 + 4 + ... + N</code></pre>
<p>만큼의 복사가 필요하다.</p>
<p>즉 전체 비용이 빠르게 커진다.</p>
<p>메모리를 딱 필요한 만큼만 사용한다는 점은 좋아 보이지만, <strong>시간을 너무 많이 사용한다.</strong></p>
<hr>
<h1 id="그러면-한-번에-2배로-늘려보자">그러면 한 번에 2배로 늘려보자</h1>
<p>여기서 <code>doubling</code> 방식이 나온다.</p>
<p>배열이 가득 찼을 때 하나만 늘리는 것이 아니라 <strong>배열 크기를 2배로 늘린다.</strong></p>
<p>예를 들어</p>
<pre><code class="language-text">capacity = 4

[A][B][C][D]</code></pre>
<p>상태에서 새로운 데이터가 들어오면</p>
<pre><code class="language-text">capacity = 8

[A][B][C][D][ ][ ][ ][ ]</code></pre>
<p>로 확장한다.</p>
<p>그 뒤에는 데이터가 몇 개 더 들어와도 resize가 필요 없다.</p>
<pre><code class="language-text">[A][B][C][D][E][F][ ][ ]</code></pre>
<p>다시 배열이 가득 찼을 때</p>
<pre><code class="language-text">8 → 16</code></pre>
<p>으로 늘린다.</p>
<p>그러면 배열 크기는</p>
<pre><code class="language-text">1
2
4
8
16
32
64
...</code></pre>
<p>처럼 증가한다.</p>
<p>이제 <code>resize()</code>가 매번 실행되지 않는다.</p>
<p>N개의 데이터를 넣어도 resize가 발생하는 횟수는 대략</p>
<pre><code class="language-text">log₂N</code></pre>
<p>번이다.</p>
<p>수업 마지막 퀴즈에서</p>
<blockquote>
<p><strong>빈 Stack에서 N번 push하면 resize()는 몇 번 호출될까?</strong></p>
</blockquote>
<p>라는 문제가 나온 이유도 여기와 연결된다.</p>
<p>배열 크기가</p>
<pre><code class="language-text">1 → 2 → 4 → 8 → 16 → ...</code></pre>
<p>으로 증가하기 때문에 resize 횟수는 <strong>logarithmic</strong>하게 증가한다.</p>
<hr>
<h1 id="그런데-doubling에도-문제가-있다">그런데 Doubling에도 문제가 있다</h1>
<p>여기까지 들었을 때는</p>
<blockquote>
<p><strong>&quot;그러면 2배씩 늘리면 끝 아닌가?&quot;</strong></p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>하지만 이번에는 배열을 줄일 때 문제가 생긴다.</p>
<p>단순하게</p>
<pre><code class="language-text">배열이 절반 이하로 사용되면
→ 배열 크기도 절반으로 줄인다.</code></pre>
<p>라고 해보자.</p>
<p>현재 배열 크기가 8이고 데이터가 4개 있다고 하자.</p>
<pre><code class="language-text">capacity = 8
N = 4

[A][B][C][D][ ][ ][ ][ ]</code></pre>
<p>조건에 따라 배열을 4로 줄인다.</p>
<pre><code class="language-text">capacity = 4

[A][B][C][D]</code></pre>
<p>그런데 바로 하나를 push하면?</p>
<pre><code class="language-text">E push</code></pre>
<p>배열이 가득 차 있으므로 다시 8로 늘려야 한다.</p>
<pre><code class="language-text">4 → 8</code></pre>
<p>그리고 다시 pop하면?</p>
<pre><code class="language-text">N = 4</code></pre>
<p>다시 절반 조건에 걸려</p>
<pre><code class="language-text">8 → 4</code></pre>
<p>로 줄어든다.</p>
<p>이 상황이 반복되면</p>
<pre><code class="language-text">push
4 → 8

pop
8 → 4

push
4 → 8

pop
8 → 4</code></pre>
<p>처럼 배열을 계속 늘렸다 줄였다 하게 된다.</p>
<p>매번 배열 전체를 복사해야 하므로 굉장히 비효율적이다.</p>
<p>이런 현상을 <strong>Thrashing</strong>이라고 볼 수 있다.</p>
<hr>
<h1 id="그래서-amortized-방식이-등장한다">그래서 Amortized 방식이 등장한다</h1>
<p>해결 방법은 배열을 늘리는 기준과 줄이는 기준 사이에 여유를 두는 것이다.</p>
<p>수업에서는 다음과 같은 방식을 사용한다.</p>
<pre><code class="language-text">push

배열이 가득 차면
→ capacity × 2</code></pre>
<p>반면 pop에서는</p>
<pre><code class="language-text">배열 사용량이 1/4 이하가 되면
→ capacity / 2</code></pre>
<p>로 줄인다.</p>
<p>예를 들어 capacity가 8이라고 해보자.</p>
<pre><code class="language-text">capacity = 8</code></pre>
<p>데이터가 4개가 되었다고 바로 줄이지 않는다.</p>
<pre><code class="language-text">[A][B][C][D][ ][ ][ ][ ]

N = 4
capacity = 8</code></pre>
<p>아직 그대로 둔다.</p>
<p>데이터가 더 줄어</p>
<pre><code class="language-text">N = 2</code></pre>
<p>가 되었을 때</p>
<pre><code class="language-text">N &lt;= capacity / 4</code></pre>
<p>조건을 만족한다.</p>
<p>그때 배열을 절반으로 줄인다.</p>
<pre><code class="language-text">capacity

8 → 4</code></pre>
<p>이렇게 하면 경계 근처에서 push와 pop이 반복되어도 배열 크기가 계속 바뀌는 현상을 줄일 수 있다.</p>
<hr>
<h1 id="amortized라는-말은-무슨-뜻일까">Amortized라는 말은 무슨 뜻일까?</h1>
<p>여기서 <code>amortized</code>라는 표현이 처음에는 조금 낯설었다.</p>
<p>resize가 발생하는 순간만 보면 분명 빠르지 않다.</p>
<p>N개의 데이터를 복사해야 하므로 시간이 필요하다.</p>
<pre><code class="language-text">resize 발생

[A][B][C][D]

↓

[A][B][C][D][ ][ ][ ][ ]</code></pre>
<p>이 순간의 비용은 크다.</p>
<p>하지만 resize는 <strong>매번 발생하지 않는다.</strong></p>
<p>대부분의 push는 단순하다.</p>
<pre><code class="language-java">a[N++] = item;</code></pre>
<p>즉 거의 모든 연산은 빠르고, 가끔 비싼 resize가 발생한다.</p>
<p>그래서 하나의 연산만 보는 것이 아니라 <strong>여러 연산의 전체 비용을 나눠서 생각한다.</strong></p>
<p>예를 들어 8번 push한다고 해보자.</p>
<pre><code class="language-text">push 1 → resize
push 2 → resize
push 3
push 4 → resize
push 5
push 6
push 7
push 8 → resize</code></pre>
<p>resize가 발생할 때는 비싸지만 매 push마다 발생하는 것은 아니다.</p>
<p>그래서 여러 연산 전체를 놓고 평균적으로 비용을 나눠보면 push와 pop을 <strong>amortized constant time</strong>으로 처리할 수 있다.</p>
<p>즉</p>
<blockquote>
<p><strong>어떤 한 번의 연산은 느릴 수 있지만, 연속된 많은 연산의 전체 비용을 보면 연산 하나당 평균 비용은 일정하게 유지된다.</strong></p>
</blockquote>
<p>라고 이해했다.</p>
<hr>
<h1 id="linked-list와-resizing-array-중-무엇이-더-좋을까">Linked List와 Resizing Array 중 무엇이 더 좋을까?</h1>
<p>이제 두 구현을 다시 비교해볼 수 있다.</p>
<h3 id="linked-list">Linked List</h3>
<pre><code class="language-text">장점

push / pop이 항상 일정한 시간
resize가 필요 없음

단점

각 Node마다 next 참조 필요
추가적인 메모리 사용</code></pre>
<h3 id="resizing-array">Resizing Array</h3>
<pre><code class="language-text">장점

링크를 저장할 필요가 없음
상대적으로 낭비되는 공간이 적음
대부분의 연산이 빠름

단점

가끔 resize가 발생
resize 시 기존 데이터를 복사해야 함</code></pre>
<p>수업 자료에서는 이를 다음과 같은 관점으로 비교한다.</p>
<pre><code class="language-text">Linked List
→ 모든 연산이 worst case에서도 constant time

Resizing Array
→ 모든 연산이 amortized constant time</code></pre>
<p>즉 Linked List는 <strong>각 연산의 처리 시간을 일정하게 보장하고 싶을 때</strong> 장점이 있다.</p>
<p>반면 Resizing Array는 가끔 복사 비용이 발생하지만 <strong>전체 수행 시간과 공간 효율을 중요하게 생각할 때</strong> 유리할 수 있다.</p>
<p>처음에는 단순히</p>
<blockquote>
<p><strong>&quot;둘 다 Stack을 만들 수 있는데 왜 두 가지나 배우지?&quot;</strong></p>
</blockquote>
<p>라는 생각이 들었다.</p>
<p>그런데 구현 방법에 따라</p>
<pre><code class="language-text">시간

공간

최악의 경우

평균적인 전체 수행 비용</code></pre>
<p>이 달라진다는 것을 비교하기 위한 것이었다.</p>
<hr>
<h1 id="array에서-pop할-때-null을-넣는-이유는">Array에서 pop할 때 null을 넣는 이유는?</h1>
<p>Array 기반 Stack 구현에는 처음 보면 이상해 보이는 코드가 하나 있다.</p>
<pre><code class="language-java">Item item = a[--N];
a[N] = null;

return item;</code></pre>
<p>어차피 <code>N</code>을 줄였는데 왜 굳이</p>
<pre><code class="language-java">a[N] = null;</code></pre>
<p>을 다시 넣는 걸까?</p>
<p>이것은 <strong>Loitering</strong> 문제 때문이다.</p>
<p>Stack에서는 해당 객체를 제거했다고 생각하지만 배열에는 여전히 객체를 가리키는 참조가 남아 있을 수 있다.</p>
<pre><code class="language-text">Stack에서는 제거됨

하지만

Array
↓
Object에 대한 Reference는 남아 있음</code></pre>
<p>그러면 Java의 Garbage Collector 입장에서는</p>
<blockquote>
<p><strong>&quot;아직 이 객체를 참조하고 있네?&quot;</strong></p>
</blockquote>
<p>라고 판단할 수 있다.</p>
<p>그래서 더 이상 필요하지 않은 참조를 명시적으로 끊어준다.</p>
<pre><code class="language-java">a[N] = null;</code></pre>
<p>그러면 객체에 대한 다른 참조가 없다면 Garbage Collector가 해당 객체를 정리할 수 있다.</p>
<hr>
<h1 id="여기서-다시-본-java의-얕은-복사와-깊은-복사">여기서 다시 본 Java의 얕은 복사와 깊은 복사</h1>
<p>수업 중 배열을 새로 만들고 기존 데이터를 옮기는 부분을 보면서 Java의 객체 복사도 다시 생각해봤다.</p>
<p>예를 들어 다음 배열이 있다고 해보자.</p>
<pre><code class="language-java">Person[] a = new Person[3];</code></pre>
<p>그리고 새로운 배열을 만든다.</p>
<pre><code class="language-java">Person[] temp = new Person[6];</code></pre>
<p>기존 데이터를 옮긴다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; a.length; i++) {
    temp[i] = a[i];
}</code></pre>
<p>여기서 실제 <code>Person</code> 객체 자체를 새롭게 복제한 것은 아니다.</p>
<p>배열 안에 들어 있는 <strong>객체를 가리키는 참조값을 복사한 것</strong>이다.</p>
<pre><code class="language-text">a[0] ──────┐
            ▼
         Person A
            ▲
temp[0] ────┘</code></pre>
<p>두 배열의 원소가 같은 Person 객체를 바라본다.</p>
<p>이런 형태를 <strong>얕은 복사(Shallow Copy)</strong>라고 볼 수 있다.</p>
<p>반면 깊은 복사(Deep Copy)는 객체 자체도 별도로 만들어준다.</p>
<pre><code class="language-text">a[0]
 ↓
Person A


temp[0]
 ↓
Person A&#39;</code></pre>
<p>따라서 한 객체의 내부 상태를 변경해도 다른 객체에는 영향을 주지 않는다.</p>
<p>Resizing Array의 <code>resize()</code>에서 우리가 원하는 것은 일반적으로 객체 자체를 복제하는 것이 아니다.</p>
<p>새로운 배열을 만들고 <strong>기존 Stack이 가지고 있던 객체의 참조를 새로운 배열로 옮기는 것</strong>이다.</p>
<p>그래서 배열의 크기를 조정할 때 객체를 전부 새로 생성할 필요는 없다.</p>
<hr>
<h1 id="generic-array는-왜-바로-만들-수-없을까">Generic Array는 왜 바로 만들 수 없을까?</h1>
<p>Stack을 특정 타입에만 사용할 필요는 없다.</p>
<pre><code class="language-java">Stack&lt;String&gt;
Stack&lt;Integer&gt;
Stack&lt;Person&gt;</code></pre>
<p>처럼 다양한 타입을 저장하고 싶다.</p>
<p>그래서 Generic을 사용할 수 있다.</p>
<pre><code class="language-java">public class ResizingArrayStack&lt;Item&gt; {</code></pre>
<p>그런데 Java에서는 다음과 같이 Generic Array를 직접 생성할 수 없다.</p>
<pre><code class="language-java">new Item[10];</code></pre>
<p>그래서 수업 코드에서는 Object 배열을 만든 뒤 형변환한다.</p>
<pre><code class="language-java">Item[] a = (Item[]) new Object[1];</code></pre>
<p>Stack의 구현은 Generic을 사용하고, Client는 원하는 타입을 지정할 수 있다.</p>
<pre><code class="language-java">ResizingArrayStack&lt;String&gt; stack
        = new ResizingArrayStack&lt;&gt;();</code></pre>
<p>이렇게 하면 같은 Stack 구현을 여러 타입에서 재사용할 수 있다.</p>
<hr>
<h1 id="iterator는-왜-직접-구현해야-할까">Iterator는 왜 직접 구현해야 할까?</h1>
<p>Stack에 데이터가 다음처럼 들어 있다고 해보자.</p>
<pre><code class="language-text">push A
push B
push C</code></pre>
<p>배열 내부에서는</p>
<pre><code class="language-text">index

0   1   2
A   B   C</code></pre>
<p>처럼 저장되어 있다.</p>
<p>그런데 Stack은 LIFO 구조다.</p>
<p>따라서 순회한다면</p>
<pre><code class="language-text">C → B → A</code></pre>
<p>순서가 자연스럽다.</p>
<p>배열의 기본적인 앞에서 뒤 방향 순회와 반대다.</p>
<p>그래서 Stack의 의미에 맞는 Iterator를 직접 구현한다.</p>
<pre><code class="language-java">public Iterator&lt;Item&gt; iterator() {
    return new ReverseArrayIterator();
}</code></pre>
<p>그리고 Iterator 내부에서는</p>
<pre><code class="language-java">private int i = N;

public boolean hasNext() {
    return i &gt; 0;
}

public Item next() {
    return a[--i];
}</code></pre>
<p>처럼 뒤에서 앞으로 이동한다.</p>
<pre><code class="language-text">Array

A → B → C

Stack Iterator

C → B → A</code></pre>
<p>처음에는</p>
<blockquote>
<p><strong>&quot;그냥 for문 돌리면 되는 것 아닌가?&quot;</strong></p>
</blockquote>
<p>라고 생각했다.</p>
<p>그런데 Iterator를 구현하면 Client는 Stack 내부가 Array인지 Linked List인지 알 필요 없이 동일한 방식으로 데이터를 순회할 수 있다.</p>
<pre><code class="language-java">for (String item : stack) {
    System.out.println(item);
}</code></pre>
<p>여기서 다시 ADT의 처음 이야기로 돌아온다.</p>
<blockquote>
<p><strong>Client에게는 어떻게 구현했는지를 숨기고, 어떻게 사용할지만 제공한다.</strong></p>
</blockquote>
<p>Iterator 역시 그 추상화를 유지하기 위한 방법 중 하나였다.</p>
<hr>
<h1 id="resize의-시간-복잡도를-다시-생각해보자">resize()의 시간 복잡도를 다시 생각해보자</h1>
<p>마지막으로 이번 시간에서 가장 중요했던 부분 중 하나가 시간 복잡도였다.</p>
<p>처음 Array Stack의 push를 보면</p>
<pre><code class="language-java">a[N++] = item;</code></pre>
<p>뿐이다.</p>
<p>그래서</p>
<pre><code class="language-text">push = O(1)</code></pre>
<p>이라고 생각하기 쉽다.</p>
<p>하지만 배열이 가득 차면</p>
<pre><code class="language-java">resize()</code></pre>
<p>가 실행된다.</p>
<p>resize에서는 N개의 데이터를 복사한다.</p>
<pre><code class="language-java">for (int i = 0; i &lt; N; i++) {
    temp[i] = a[i];
}</code></pre>
<p>따라서 resize 한 번 자체는</p>
<pre><code class="language-text">O(N)</code></pre>
<p>의 시간이 필요하다.</p>
<p>그런데 Doubling 방식에서는 매 push마다 resize하지 않는다.</p>
<p>배열 크기는</p>
<pre><code class="language-text">1 → 2 → 4 → 8 → 16 → 32 → ...</code></pre>
<p>로 증가한다.</p>
<p>N개의 데이터를 push할 때 resize가 발생하는 횟수는 대략</p>
<pre><code class="language-text">log₂N</code></pre>
<p>번이다.</p>
<p>그리고 지금까지 복사한 전체 데이터 수를 생각하면</p>
<pre><code class="language-text">1 + 2 + 4 + 8 + ... + N</code></pre>
<p>형태가 된다.</p>
<p>이 합은 N에 비례하는 수준으로 증가한다.</p>
<p>그래서 N번의 push 전체 비용을 N개의 연산에 나눠 생각하면 하나의 push가 평균적으로 <strong>상수 시간에 가까운 비용</strong>을 가진다고 분석할 수 있다.</p>
<p>이것이 Resizing Array에서 말하는</p>
<pre><code class="language-text">amortized O(1)</code></pre>
<p>이다.</p>
<p>여기서 하나를 구분해야 한다.</p>
<pre><code class="language-text">resize() 자체
→ O(N)

push()의 amortized cost
→ O(1)</code></pre>
<p>둘은 같은 이야기가 아니다.</p>
<hr>
<h1 id="정리">정리</h1>
<p>이번 시간에는 ADT부터 시작해서 Stack을 실제로 어떻게 구현하는지까지 살펴봤다.</p>
<p>처음에는 ADT를 단순히</p>
<pre><code class="language-text">추상 데이터 타입</code></pre>
<p>이라는 정의로 외우려고 했다.</p>
<p>그런데 Stack을 두 가지 방법으로 구현해보니 왜 추상화가 필요한지가 조금 더 명확해졌다.</p>
<p>사용자가 원하는 것은</p>
<pre><code class="language-java">push()
pop()
isEmpty()
size()</code></pre>
<p>이다.</p>
<p>그 내부가</p>
<pre><code class="language-text">Linked List인지

Array인지

Resizing Array인지</code></pre>
<p>는 구현의 문제다.</p>
<p>즉</p>
<pre><code class="language-text">Client
    ↓
API
    ↓
Implementation</code></pre>
<p>으로 분리할 수 있다.</p>
<p>그리고 같은 Stack이라도 구현 방식에 따라 서로 다른 비용이 발생했다.</p>
<pre><code class="language-text">Linked List

연산 시간은 일정
하지만 링크를 위한 추가 공간 필요</code></pre>
<p>반면</p>
<pre><code class="language-text">Resizing Array

공간 활용은 좋음
하지만 resize 시 복사 비용 발생</code></pre>
<p>Array의 크기를 조절하는 방법도 처음부터 완성된 것은 아니었다.</p>
<pre><code class="language-text">Simple

필요할 때마다 +1
→ 메모리는 아끼지만 복사를 너무 자주 함

↓

Doubling

가득 차면 ×2
→ resize 횟수를 크게 줄임

↓

Amortized

가득 차면 ×2
1/4 이하가 되면 ÷2
→ push/pop 경계에서 반복되는 resize까지 방지</code></pre>
<p>이 흐름을 보고 나니 자료구조를 공부한다는 것이 단순히</p>
<blockquote>
<p><strong>&quot;Stack은 LIFO다.&quot;</strong></p>
</blockquote>
<p>를 외우는 것만은 아니라는 생각이 들었다.</p>
<p>같은 기능을 제공하더라도</p>
<pre><code class="language-text">어떤 자료구조를 사용할지

시간을 얼마나 사용할지

공간을 얼마나 사용할지

최악의 한 번을 중요하게 볼지

전체 수행 시간을 중요하게 볼지</code></pre>
<p>에 따라 구현 방법이 달라진다.</p>
<p>특히 이번 시간에는 <code>resize()</code>가 인상적이었다.</p>
<p>코드만 보면 단순히 배열을 새로 만들고 복사하는 함수다.</p>
<p>하지만 이 함수가 <strong>언제 호출되도록 설계하느냐</strong>에 따라 전체 알고리즘의 성능이 크게 달라졌다.</p>
<pre><code class="language-text">resize() 한 번
→ O(N)

하지만 resize를 드물게 발생시키면

push() 전체
→ amortized O(1)</code></pre>
<p>결국 이번 시간에 배운 ADT와 Stack도 하나의 흐름으로 연결되는 것 같다.</p>
<blockquote>
<p><strong>사용자에게는 필요한 동작만 API로 보여주고, 그 내부에서는 시간과 공간의 Trade-off를 고려해 적절한 자료구조와 알고리즘을 선택한다.</strong></p>
</blockquote>
<p>ADT라는 개념이 단순히 자료형 하나를 정의하는 이야기가 아니라, 앞으로 프로그램을 설계할 때 구현과 사용을 어떻게 분리할 것인지에 대한 이야기이기도 한 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP API는 어떻게 설계해야 할까? | 리소스 식별부터 HTTP 메서드까지]]></title>
            <link>https://velog.io/@daehyun_lee/HTTP-API%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@daehyun_lee/HTTP-API%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 14 Sep 2026 06:46:44 GMT</pubDate>
            <description><![CDATA[<h1 id="http-api는-어떻게-설계해야-할까--리소스-식별부터-http-메서드까지">HTTP API는 어떻게 설계해야 할까? | 리소스 식별부터 HTTP 메서드까지</h1>
<p>지난 글에서는 HTTP가 무엇인지와 HTTP 메시지의 기본 구조에 대해 공부했다.</p>
<p>이번에는 조금 더 실제 백엔드 개발과 가까운 내용이다.</p>
<blockquote>
<p><strong>API의 URL은 어떻게 설계해야 할까?</strong></p>
</blockquote>
<p>회사에 입사해서 회원 관리 API를 만들어야 한다고 가정해보자.</p>
<p>다음과 같은 요구사항이 주어졌다.</p>
<pre><code class="language-text">회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제</code></pre>
<p>처음 API를 설계한다면 이런 식으로 만들고 싶을 수도 있다.</p>
<pre><code class="language-text">/read-member-list
/read-member
/create-member
/update-member
/delete-member</code></pre>
<p>딱 봐도 무엇을 하는 API인지 알 수 있다.</p>
<p>그런데 정말 이게 좋은 URI 설계일까?</p>
<p>이번 수업에서 가장 중요하게 들었던 것은 이것이었다.</p>
<blockquote>
<p><strong>API URI 설계에서 가장 중요한 것은 리소스를 식별하는 것이다.</strong></p>
</blockquote>
<p>처음에는 이 말이 조금 추상적으로 느껴졌다.</p>
<p>그래서 이번 글에서는</p>
<pre><code class="language-text">리소스가 무엇인지

URI에는 무엇을 넣어야 하는지

조회 / 등록 / 수정 / 삭제는 어디에 표현하는지

GET / POST / PUT / PATCH / DELETE는 각각 어떤 의미인지

안전 / 멱등 / 캐시 가능은 무엇인지</code></pre>
<p>를 하나씩 연결해서 정리해보려고 한다.</p>
<hr>
<h1 id="uri에는-무엇을-넣어야-할까">URI에는 무엇을 넣어야 할까?</h1>
<p>회원 관리 시스템을 다시 생각해보자.</p>
<pre><code class="language-text">회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제</code></pre>
<p>여기서 리소스는 무엇일까?</p>
<p>처음에는</p>
<pre><code class="language-text">회원 조회

회원 등록

회원 수정</code></pre>
<p>같은 기능 자체가 리소스라고 생각하기 쉽다.</p>
<p>그런데 그렇지 않다.</p>
<p>여기서 리소스는 <strong>회원(Member)</strong> 그 자체다.</p>
<p>수업에서 재미있는 비유가 하나 나왔다.</p>
<p>스타크래프트에서</p>
<blockquote>
<p>&quot;미네랄을 캐라.&quot;</p>
</blockquote>
<p>라는 명령이 있다고 해보자.</p>
<p>이때</p>
<pre><code class="language-text">캐라</code></pre>
<p>가 리소스일까?</p>
<p>아니다.</p>
<pre><code class="language-text">미네랄</code></pre>
<p>이 리소스다.</p>
<p><code>캐라</code>는 미네랄이라는 리소스를 대상으로 수행하는 <strong>행위</strong>다.</p>
<p>회원 관리도 똑같이 생각할 수 있다.</p>
<pre><code class="language-text">회원
→ Resource

조회
등록
수정
삭제
→ Resource에 대한 행위</code></pre>
<p>여기서 API URI 설계의 중요한 원칙이 나온다.</p>
<blockquote>
<p><strong>URI에는 행위보다 리소스를 표현한다.</strong></p>
</blockquote>
<hr>
<h1 id="행위를-전부-빼버려보자">행위를 전부 빼버려보자</h1>
<p>처음의 요구사항을 다시 보자.</p>
<pre><code class="language-text">회원 목록 조회
회원 조회
회원 등록
회원 수정
회원 삭제</code></pre>
<p>여기서 행위를 빼버린다.</p>
<pre><code class="language-text">회원 목록
회원
회원
회원
회원</code></pre>
<p>결국 남는 것은</p>
<blockquote>
<p><strong>회원이라는 리소스</strong></p>
</blockquote>
<p>다.</p>
<p>그러면 회원 리소스를 URI에 매핑할 수 있다.</p>
<p>일반적으로 컬렉션을 표현할 때는 복수형 명사를 사용하는 것을 권장한다.</p>
<p>따라서</p>
<pre><code class="language-text">/member</code></pre>
<p>보다는</p>
<pre><code class="language-text">/members</code></pre>
<p>처럼 표현할 수 있다.</p>
<p>회원 한 명을 식별하고 싶다면</p>
<pre><code class="language-text">/members/100</code></pre>
<p>과 같이 표현한다.</p>
<p>정리하면 다음과 같다.</p>
<pre><code class="language-text">/members
→ 회원 컬렉션

/members/100
→ ID가 100인 회원</code></pre>
<p>여기까지만 보면 꽤 깔끔하다.</p>
<p>그런데 문제가 하나 생긴다.</p>
<hr>
<h1 id="그런데-조회와-등록을-어떻게-구분하지">그런데 조회와 등록을 어떻게 구분하지?</h1>
<p>회원 목록도</p>
<pre><code class="language-text">/members</code></pre>
<p>이고,</p>
<p>회원 등록도</p>
<pre><code class="language-text">/members</code></pre>
<p>라고 하면 둘을 어떻게 구분할까?</p>
<p>처음에는 URI 뒤에 다시 동사를 붙이면 되지 않을까 생각할 수 있다.</p>
<pre><code class="language-text">/members/get
/members/create
/members/delete</code></pre>
<p>그런데 그렇게 하면 다시 URI가 행위를 표현하게 된다.</p>
<p>여기서 HTTP가 가진 중요한 기능이 등장한다.</p>
<blockquote>
<p><strong>행위는 HTTP Method가 표현한다.</strong></p>
</blockquote>
<p>즉,</p>
<pre><code class="language-text">URI
→ Resource 식별

HTTP Method
→ Resource에 수행할 행위</code></pre>
<p>로 역할을 나눈다.</p>
<p>그래서 회원 관리 API는 다음처럼 설계할 수 있다.</p>
<pre><code class="language-text">GET    /members
→ 회원 목록 조회

POST   /members
→ 회원 등록

GET    /members/100
→ 100번 회원 조회

PATCH  /members/100
→ 100번 회원 일부 수정

DELETE /members/100
→ 100번 회원 삭제</code></pre>
<p>이제 같은 URI라도 HTTP Method에 따라 수행할 작업을 구분할 수 있다.</p>
<p>개인적으로 여기서 URI 설계가 조금 더 명확하게 이해됐다.</p>
<p>처음에는</p>
<pre><code class="language-text">URL만 봐도 무슨 동작인지 전부 표현해야 하는 것 아닌가?</code></pre>
<p>라고 생각했다.</p>
<p>그런데 실제로는</p>
<pre><code class="language-text">URI
+
HTTP Method</code></pre>
<p>를 함께 봐야 하나의 요청이 완성되는 것이다.</p>
<hr>
<h1 id="http-method에는-무엇이-있을까">HTTP Method에는 무엇이 있을까?</h1>
<p>HTTP에는 여러 Method가 존재한다.</p>
<p>대표적인 Method는 다음과 같다.</p>
<pre><code class="language-text">GET
POST
PUT
PATCH
DELETE</code></pre>
<p>이외에도</p>
<pre><code class="language-text">HEAD
OPTIONS
CONNECT
TRACE</code></pre>
<p>등이 있다.</p>
<p>이번에는 웹 개발에서 자주 사용하는 주요 Method부터 살펴보자.</p>
<hr>
<h1 id="get--리소스를-조회한다">GET — 리소스를 조회한다</h1>
<p>GET은 리소스를 조회할 때 사용한다.</p>
<p>예를 들어 100번 회원을 조회한다고 해보자.</p>
<pre><code class="language-http">GET /members/100 HTTP/1.1
Host: example.com</code></pre>
<p>이를 말로 풀면</p>
<blockquote>
<p><strong>&quot;서버야, 100번 회원 리소스를 줘.&quot;</strong></p>
</blockquote>
<p>정도가 된다.</p>
<p>서버는 요청을 확인하고 해당 회원 정보를 찾는다.</p>
<p>그리고 JSON 형태의 데이터를 응답할 수 있다.</p>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json

{
    &quot;id&quot;: 100,
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>전체 흐름을 단순하게 나타내면 다음과 같다.</p>
<pre><code class="language-text">Client
   │
   │ GET /members/100
   ▼
Server
   │
   │ 100번 회원 조회
   ▼
JSON 생성
   │
   │ 200 OK
   ▼
Client</code></pre>
<hr>
<h1 id="get으로-데이터를-전달하고-싶다면">GET으로 데이터를 전달하고 싶다면?</h1>
<p>조회할 때도 서버에게 추가적인 조건을 전달해야 하는 경우가 있다.</p>
<p>예를 들어 Google 검색을 생각해보자.</p>
<pre><code class="language-text">https://www.google.com/search?q=hello&amp;hl=ko</code></pre>
<p>여기서</p>
<pre><code class="language-text">q=hello
hl=ko</code></pre>
<p>가 Query Parameter다.</p>
<p>HTTP 요청은 다음과 비슷한 형태가 된다.</p>
<pre><code class="language-http">GET /search?q=hello&amp;hl=ko HTTP/1.1
Host: www.google.com</code></pre>
<p>검색이나 목록 조회에서는 Query Parameter를 많이 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">GET /members?age=20</code></pre>
<p>이라면</p>
<blockquote>
<p>20살인 회원을 조회해줘.</p>
</blockquote>
<p>와 같은 의미로 사용할 수 있다.</p>
<p>또</p>
<pre><code class="language-text">GET /products?category=computer&amp;sort=price</code></pre>
<p>라면</p>
<pre><code class="language-text">category=computer
→ 컴퓨터 카테고리

sort=price
→ 가격순 정렬</code></pre>
<p>처럼 조회 조건을 전달할 수 있다.</p>
<p>따라서 GET은 보통</p>
<pre><code class="language-text">리소스 조회
+
Query Parameter를 통한 조회 조건 전달</code></pre>
<p>에 사용한다.</p>
<hr>
<h1 id="get의-message-body는-사용할-수-없을까">GET의 Message Body는 사용할 수 없을까?</h1>
<p>GET도 기술적으로 Message Body를 포함할 수 있는 경우가 있다.</p>
<p>하지만 실무에서는 거의 사용하지 않는 편이다.</p>
<p>서버나 중간 장비가 GET Body를 지원하지 않는 경우도 있고 상호운용성이 좋지 않기 때문이다.</p>
<p>그래서 일반적으로 GET에서 데이터를 전달할 때는</p>
<pre><code class="language-text">Query Parameter</code></pre>
<p>를 사용한다고 이해하는 것이 좋다.</p>
<hr>
<h1 id="post--요청-데이터를-처리해줘">POST — 요청 데이터를 처리해줘</h1>
<p>다음은 POST다.</p>
<p>POST는 클라이언트가 서버에게 데이터를 전달하면서</p>
<blockquote>
<p><strong>&quot;이 데이터를 받아서 네가 정의한 방식대로 처리해줘.&quot;</strong></p>
</blockquote>
<p>라고 요청하는 Method라고 생각하면 이해하기 쉽다.</p>
<p>대표적인 사용 사례가 <strong>신규 리소스 등록</strong>이다.</p>
<p>회원 등록을 생각해보자.</p>
<pre><code class="language-http">POST /members HTTP/1.1
Content-Type: application/json

{
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>여기서 URI는</p>
<pre><code class="language-text">/members</code></pre>
<p>이다.</p>
<p>즉 회원 컬렉션에 요청을 보낸다.</p>
<p>서버는 요청을 받아 새로운 회원을 생성할 수 있다.</p>
<pre><code class="language-text">POST /members

{
    username: daehyun,
    age: 27
}
        ↓
Server
        ↓
새로운 회원 생성
        ↓
ID = 100</code></pre>
<p>그리고 다음과 같이 응답할 수 있다.</p>
<pre><code class="language-http">HTTP/1.1 201 Created
Location: /members/100</code></pre>
<p>여기서 중요한 부분이 있다.</p>
<p>클라이언트는 새로운 회원이</p>
<pre><code class="language-text">/members/100</code></pre>
<p>이 될지</p>
<pre><code class="language-text">/members/101</code></pre>
<p>이 될지 모른다.</p>
<p>새로운 리소스의 URI를 <strong>서버가 결정한다.</strong></p>
<hr>
<h1 id="post는-등록에만-사용하는-걸까">POST는 등록에만 사용하는 걸까?</h1>
<p>여기서 처음에 조금 헷갈렸다.</p>
<p>POST를</p>
<pre><code class="language-text">POST = Create</code></pre>
<p>라고 외우면 간단해 보인다.</p>
<p>하지만 POST의 역할은 그것보다 넓다.</p>
<p>POST는 요청 데이터를 서버가 정의한 방식으로 처리하게 할 수 있다.</p>
<p>대표적으로</p>
<pre><code class="language-text">HTML Form 회원가입

상품 주문

게시글 작성

댓글 등록

결제 요청

프로세스 실행</code></pre>
<p>등에 사용할 수 있다.</p>
<p>즉 신규 리소스 생성뿐만 아니라 <strong>단순한 CRUD로 표현하기 어려운 프로세스 처리</strong>에도 사용할 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-text">POST /orders/100/cancel</code></pre>
<p>처럼 특정 주문을 취소하는 프로세스를 실행하는 식으로 사용할 수도 있다.</p>
<p>이런 URI를 <strong>Controller URI</strong> 또는 <strong>Control URI</strong> 형태로 볼 수 있다.</p>
<p>REST스럽게 리소스와 Method만으로 표현하기 어려운 동작에서 사용할 수 있다.</p>
<hr>
<h1 id="post에는-반드시-body가-있어야-할까">POST에는 반드시 Body가 있어야 할까?</h1>
<p>처음 메모에는</p>
<pre><code class="language-text">POST는 무조건 데이터를 Body에 담아서 보내야 한다.</code></pre>
<p>라고 적어두었다.</p>
<p>그런데 조금 더 정확하게 말하면 <strong>POST라고 해서 반드시 Message Body가 존재해야 하는 것은 아니다.</strong></p>
<p>POST의 핵심은</p>
<blockquote>
<p><strong>대상 리소스에 요청 데이터를 전달하여 리소스가 정의한 방식으로 처리하도록 요청하는 것</strong></p>
</blockquote>
<p>이다.</p>
<p>실제 웹 API에서는 JSON 등의 Body를 전달하는 경우가 많기 때문에</p>
<pre><code class="language-text">POST
→ Message Body</code></pre>
<p>를 자주 보게 되는 것이다.</p>
<hr>
<h1 id="post를-한-문장으로-정리하면">POST를 한 문장으로 정리하면</h1>
<p>POST는 활용 범위가 꽤 넓다.</p>
<p>그래서 다음 정도로 기억하면 좋을 것 같다.</p>
<blockquote>
<p><strong>POST는 서버에게 요청 데이터를 전달하고, 대상 리소스가 정의한 방식대로 처리하도록 요청하는 Method다.</strong></p>
</blockquote>
<p>대표적으로</p>
<pre><code class="language-text">새로운 리소스 생성

프로세스 실행

다른 HTTP Method로 표현하기 애매한 처리</code></pre>
<p>등에 사용할 수 있다.</p>
<hr>
<h1 id="put--리소스를-통째로-대체한다">PUT — 리소스를 통째로 대체한다</h1>
<p>PUT은 처음 배울 때 꽤 헷갈렸다.</p>
<p>보통</p>
<pre><code class="language-text">PUT = 수정</code></pre>
<p>이라고 외우기 쉽기 때문이다.</p>
<p>그런데 PUT의 핵심은 단순 수정이 아니다.</p>
<blockquote>
<p><strong>대상 리소스를 요청 데이터로 완전히 대체한다.</strong></p>
</blockquote>
<p>파일을 폴더에 복사하는 상황을 생각하면 이해하기 쉽다.</p>
<pre><code class="language-text">기존 파일 존재
+
같은 이름의 새 파일 복사

↓

기존 파일을 새 파일로 덮어씀</code></pre>
<p>PUT도 비슷하다.</p>
<p>예를 들어</p>
<pre><code class="language-text">PUT /members/100</code></pre>
<p>으로 다음 데이터를 보냈다고 해보자.</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;daehyun&quot;
}</code></pre>
<p>기존 100번 회원 데이터가</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>이었다면 PUT의 의미는</p>
<pre><code class="language-text">age만 그대로 두고
username만 수정</code></pre>
<p>이 아니다.</p>
<p>요청으로 전달된 표현으로 <strong>리소스 전체를 대체하는 것</strong>에 가깝다.</p>
<p>따라서 결과가</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;daehyun&quot;
}</code></pre>
<p>처럼 될 수 있다.</p>
<p>그래서 PUT을 사용할 때는 주의해야 한다.</p>
<hr>
<h1 id="put에는-또-하나-중요한-특징이-있다">PUT에는 또 하나 중요한 특징이 있다</h1>
<p>POST와 PUT의 중요한 차이가 하나 있다.</p>
<p><strong>누가 리소스의 URI를 결정하는가?</strong></p>
<p>POST 기반 등록에서는</p>
<pre><code class="language-http">POST /members</code></pre>
<p>처럼 서버가 새로운 리소스의 URI를 결정했다.</p>
<p>반면 PUT에서는 클라이언트가 직접 URI를 알고 지정한다.</p>
<pre><code class="language-http">PUT /files/star.jpg</code></pre>
<p>클라이언트가</p>
<blockquote>
<p><strong>&quot;나는 <code>/files/star.jpg</code>라는 리소스를 만들거나 대체할 거야.&quot;</strong></p>
</blockquote>
<p>라고 명확하게 지정한다.</p>
<p>비교하면 다음과 같다.</p>
<pre><code class="language-text">POST

POST /members
      ↓
서버가 ID 결정
      ↓
/members/100</code></pre>
<p>반면</p>
<pre><code class="language-text">PUT

PUT /files/star.jpg
      ↓
클라이언트가 URI 직접 지정</code></pre>
<p>이다.</p>
<hr>
<h1 id="리소스가-없는데-put하면">리소스가 없는데 PUT하면?</h1>
<p>PUT은 지정한 위치에 리소스가 없으면 새롭게 생성할 수도 있다.</p>
<pre><code class="language-text">PUT /files/star.jpg</code></pre>
<p>요청을 보냈는데</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>가 존재하지 않는다면 새로운 리소스를 생성할 수 있다.</p>
<p>따라서 PUT은</p>
<pre><code class="language-text">리소스가 있으면
→ 완전히 대체

리소스가 없으면
→ 생성</code></pre>
<p>이라고 기억할 수 있다.</p>
<hr>
<h1 id="put은-수정-method가-아니었네">PUT은 수정 Method가 아니었네?</h1>
<p>여기까지 보고 나니 처음 생각했던</p>
<pre><code class="language-text">PUT = 수정</code></pre>
<p>이라는 설명이 조금 애매하다는 것을 알게 됐다.</p>
<p>PUT은 더 정확하게</p>
<blockquote>
<p><strong>리소스 전체 대체</strong></p>
</blockquote>
<p>에 가깝다.</p>
<p>그러면 이런 생각이 든다.</p>
<blockquote>
<p><strong>&quot;나는 회원의 이름만 바꾸고 싶은데?&quot;</strong></p>
</blockquote>
<p>이때 사용하는 것이 PATCH다.</p>
<hr>
<h1 id="patch--리소스를-부분-변경한다">PATCH — 리소스를 부분 변경한다</h1>
<p>PATCH는 대상 리소스의 일부를 변경할 때 사용한다.</p>
<p>예를 들어 기존 데이터가 다음과 같다고 해보자.</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>이름만 수정하고 싶다면</p>
<pre><code class="language-http">PATCH /members/100</code></pre>
<p>과 함께</p>
<pre><code class="language-json">{
    &quot;username&quot;: &quot;newName&quot;
}</code></pre>
<p>을 전달할 수 있다.</p>
<p>그러면</p>
<pre><code class="language-text">username
→ 변경

age
→ 유지</code></pre>
<p>처럼 필요한 부분만 수정할 수 있다.</p>
<p>그래서 다음처럼 구분하면 기억하기 쉽다.</p>
<pre><code class="language-text">PUT
→ 전체 대체

PATCH
→ 부분 변경</code></pre>
<p>만약 서버나 클라이언트 환경에서 PATCH를 사용하기 어려운 경우에는 POST를 이용해 변경 기능을 구현하는 경우도 있다.</p>
<hr>
<h1 id="delete--리소스를-삭제한다">DELETE — 리소스를 삭제한다</h1>
<p>DELETE는 이름 그대로 리소스를 삭제한다.</p>
<p>예를 들어</p>
<pre><code class="language-http">DELETE /members/100</code></pre>
<p>은</p>
<blockquote>
<p><strong>100번 회원 리소스를 삭제해줘.</strong></p>
</blockquote>
<p>라는 요청이다.</p>
<pre><code class="language-text">Client
   │
   │ DELETE /members/100
   ▼
Server
   │
   │ 100번 회원 삭제
   ▼
삭제 완료</code></pre>
<p>다른 Method에 비해 의미가 상당히 직관적이다.</p>
<hr>
<h1 id="head">HEAD</h1>
<p>HEAD는 GET과 비슷하다.</p>
<p>하지만 서버는 응답에서 <strong>Message Body를 제외하고 Status Line과 Header만 반환한다.</strong></p>
<p>예를 들어 리소스의 실제 내용을 전부 받아오지 않고</p>
<pre><code class="language-text">Content-Type

Content-Length

Last-Modified</code></pre>
<p>같은 Header 정보만 확인하고 싶을 때 사용할 수 있다.</p>
<pre><code class="language-text">GET
→ Header + Body

HEAD
→ Header만</code></pre>
<p>이라고 기억하면 된다.</p>
<hr>
<h1 id="options">OPTIONS</h1>
<p>OPTIONS는 대상 리소스와 통신할 때 사용할 수 있는 옵션을 확인하는 데 사용할 수 있다.</p>
<p>예를 들어 서버가 어떤 HTTP Method를 허용하는지 확인하는 용도로 사용할 수 있다.</p>
<pre><code class="language-text">GET
POST
PUT
PATCH
DELETE
...</code></pre>
<p>실제 웹 개발에서는 CORS와 관련해서 OPTIONS 요청을 보게 되는 경우도 있다.</p>
<hr>
<h1 id="connect와-trace">CONNECT와 TRACE</h1>
<p>HTTP에는</p>
<pre><code class="language-text">CONNECT
TRACE</code></pre>
<p>같은 Method도 존재한다.</p>
<p>하지만 일반적인 웹 API 개발에서는 직접 사용할 일이 많지 않다.</p>
<p>현재 단계에서는</p>
<blockquote>
<p><strong>&quot;이런 Method도 존재한다.&quot;</strong></p>
</blockquote>
<p>정도로 알아두고 넘어가도 될 것 같다.</p>
<hr>
<h1 id="http-method-전체-정리">HTTP Method 전체 정리</h1>
<p>지금까지 본 내용을 한 번 정리해보자.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>리소스 조회</td>
</tr>
<tr>
<td>POST</td>
<td>요청 데이터 처리, 주로 신규 등록 및 프로세스 처리</td>
</tr>
<tr>
<td>PUT</td>
<td>리소스 전체 대체, 없으면 생성 가능</td>
</tr>
<tr>
<td>PATCH</td>
<td>리소스 부분 변경</td>
</tr>
<tr>
<td>DELETE</td>
<td>리소스 삭제</td>
</tr>
<tr>
<td>HEAD</td>
<td>GET과 비슷하지만 Body 제외</td>
</tr>
<tr>
<td>OPTIONS</td>
<td>대상 리소스의 통신 옵션 확인</td>
</tr>
</tbody></table>
<p>회원 API로 보면 다음과 같다.</p>
<pre><code class="language-text">GET    /members
→ 회원 목록 조회

POST   /members
→ 회원 등록

GET    /members/{id}
→ 회원 조회

PUT    /members/{id}
→ 회원 전체 대체

PATCH  /members/{id}
→ 회원 일부 수정

DELETE /members/{id}
→ 회원 삭제</code></pre>
<p>처음에 봤던</p>
<pre><code class="language-text">/getMembers
/createMember
/updateMember
/deleteMember</code></pre>
<p>와 비교하면 역할이 훨씬 명확하게 분리된다.</p>
<pre><code class="language-text">URI
→ 회원이라는 Resource

Method
→ 회원에게 수행할 Action</code></pre>
<hr>
<h1 id="http-api에서-데이터를-어떻게-전달할까">HTTP API에서 데이터를 어떻게 전달할까?</h1>
<p>HTTP Method를 공부하다 보면 자연스럽게 다음 질문이 생긴다.</p>
<blockquote>
<p><strong>&quot;클라이언트가 서버에게 데이터를 전달하려면 어디에 넣어야 하지?&quot;</strong></p>
</blockquote>
<p>강의에서는 크게 두 가지를 먼저 구분한다.</p>
<pre><code class="language-text">Query Parameter

Message Body</code></pre>
<p>일반적으로</p>
<pre><code class="language-text">GET
→ Query Parameter

POST / PUT / PATCH
→ Message Body</code></pre>
<p>형태를 많이 사용한다.</p>
<p>예를 들어 검색 요청은</p>
<pre><code class="language-http">GET /search?q=hello&amp;sort=date</code></pre>
<p>처럼 Query Parameter를 사용한다.</p>
<p>반면 회원 생성은</p>
<pre><code class="language-http">POST /members
Content-Type: application/json

{
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>처럼 Message Body를 사용한다.</p>
<hr>
<h1 id="데이터-전송-상황은-조금-더-나눠볼-수-있다">데이터 전송 상황은 조금 더 나눠볼 수 있다</h1>
<p>웹에서 클라이언트가 서버로 데이터를 전달하는 상황은 크게 다음처럼 생각할 수 있다.</p>
<pre><code class="language-text">정적 데이터 조회

동적 데이터 조회

HTML Form 데이터 전송

HTTP API 데이터 전송</code></pre>
<hr>
<h1 id="정적-데이터-조회">정적 데이터 조회</h1>
<p>이미지나 정적 HTML 파일처럼 별도의 검색 조건이 필요하지 않은 리소스를 조회한다고 해보자.</p>
<pre><code class="language-http">GET /static/star.jpg HTTP/1.1</code></pre>
<p>이 경우에는 굳이 Query Parameter가 필요하지 않다.</p>
<p>리소스 경로 자체만으로 어떤 데이터를 원하는지 알 수 있기 때문이다.</p>
<pre><code class="language-text">/static/star.jpg</code></pre>
<hr>
<h1 id="동적-데이터-조회">동적 데이터 조회</h1>
<p>검색이나 게시판 목록처럼 조건에 따라 결과가 달라진다면 Query Parameter를 사용할 수 있다.</p>
<pre><code class="language-http">GET /search?q=hello&amp;hl=ko HTTP/1.1</code></pre>
<p>서버는</p>
<pre><code class="language-text">q=hello
hl=ko</code></pre>
<p>를 이용해 결과를 필터링하거나 정렬한 뒤 동적으로 응답을 생성한다.</p>
<hr>
<h1 id="html-form으로-데이터를-전송하면">HTML Form으로 데이터를 전송하면?</h1>
<p>HTML에는 <code>&lt;form&gt;</code>을 이용해서 데이터를 서버로 전달하는 방식이 있다.</p>
<p>예를 들어</p>
<pre><code class="language-html">&lt;form action=&quot;/save&quot; method=&quot;post&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;username&quot;&gt;
    &lt;input type=&quot;text&quot; name=&quot;age&quot;&gt;
    &lt;button type=&quot;submit&quot;&gt;전송&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>을 전송하면 브라우저가 다음과 비슷한 HTTP 요청을 만든다.</p>
<pre><code class="language-http">POST /save HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=kim&amp;age=20</code></pre>
<p>일반적인 HTML Form에서는</p>
<pre><code class="language-text">application/x-www-form-urlencoded</code></pre>
<p>형태를 사용할 수 있다.</p>
<p>그리고 파일까지 함께 전송한다면</p>
<pre><code class="language-text">multipart/form-data</code></pre>
<p>를 사용할 수 있다.</p>
<hr>
<h1 id="html-form에서-get도-사용할-수-있다">HTML Form에서 GET도 사용할 수 있다</h1>
<p>Form에서도 GET 전송이 가능하다.</p>
<pre><code class="language-html">&lt;form action=&quot;/members&quot; method=&quot;get&quot;&gt;</code></pre>
<p>이라면</p>
<pre><code class="language-http">GET /members?username=kim&amp;age=20</code></pre>
<p>처럼 Query Parameter 형태로 전달된다.</p>
<p>다만 여기서 중요한 점이 있다.</p>
<blockquote>
<p><strong>GET은 조회에 사용해야 한다.</strong></p>
</blockquote>
<p>GET 요청으로</p>
<pre><code class="language-text">회원 등록

회원 삭제

데이터 수정</code></pre>
<p>처럼 서버 리소스를 변경하도록 설계하면 안 된다.</p>
<hr>
<h1 id="http-api에서는-json을-많이-사용한다">HTTP API에서는 JSON을 많이 사용한다</h1>
<p>웹 프론트엔드나 모바일 앱, 서버 간 통신에서는 HTML Form보다 HTTP API 형태를 많이 사용한다.</p>
<p>예를 들어</p>
<pre><code class="language-http">POST /members HTTP/1.1
Content-Type: application/json

{
    &quot;username&quot;: &quot;daehyun&quot;,
    &quot;age&quot;: 27
}</code></pre>
<p>처럼 JSON을 Message Body에 넣어 전달한다.</p>
<p>대표적인 상황은</p>
<pre><code class="language-text">서버 ↔ 서버

iOS / Android ↔ 서버

React / Vue ↔ Backend API</code></pre>
<p>등이다.</p>
<hr>
<h1 id="uri-설계에는-collection과-store라는-개념도-있다">URI 설계에는 Collection과 Store라는 개념도 있다</h1>
<p>회원 API와 파일 관리 API를 비교하면 재미있는 차이가 하나 있다.</p>
<p>먼저 회원 등록을 보자.</p>
<pre><code class="language-http">POST /members</code></pre>
<p>클라이언트는 새 회원의 ID를 모른다.</p>
<p>서버가</p>
<pre><code class="language-text">100</code></pre>
<p>이라는 ID를 만들고</p>
<pre><code class="language-text">/members/100</code></pre>
<p>이라는 새로운 URI를 만들어준다.</p>
<p>이런 형태를 <strong>Collection</strong>이라고 한다.</p>
<pre><code class="language-text">Collection

서버가 관리하는 리소스 디렉터리

서버가 새로운 리소스 URI를 생성하고 관리</code></pre>
<p>예:</p>
<pre><code class="language-text">/members</code></pre>
<hr>
<h1 id="store는-무엇일까">Store는 무엇일까?</h1>
<p>이번에는 파일 관리 시스템을 생각해보자.</p>
<pre><code class="language-http">PUT /files/star.jpg</code></pre>
<p>여기서는 클라이언트가 이미</p>
<pre><code class="language-text">/files/star.jpg</code></pre>
<p>라는 URI를 알고 있다.</p>
<p>즉 클라이언트가 저장 위치와 이름을 결정한다.</p>
<p>이런 형태를 <strong>Store</strong>라고 한다.</p>
<pre><code class="language-text">Store

클라이언트가 관리하는 리소스 저장소

클라이언트가 URI를 알고 직접 지정</code></pre>
<p>예:</p>
<pre><code class="language-text">/files</code></pre>
<p>Collection과 Store의 차이를 한 번에 보면 다음과 같다.</p>
<pre><code class="language-text">Collection

POST /members
        ↓
서버가 URI 결정
        ↓
/members/100</code></pre>
<pre><code class="language-text">Store

PUT /files/star.jpg
        ↓
클라이언트가 URI 직접 결정</code></pre>
<hr>
<h1 id="document는-무엇일까">Document는 무엇일까?</h1>
<p>Document는 단일 리소스 하나를 의미한다.</p>
<p>예를 들어</p>
<pre><code class="language-text">/members/100

/files/star.jpg</code></pre>
<p>처럼 특정 회원 하나나 파일 하나를 식별한다.</p>
<p>정리하면</p>
<pre><code class="language-text">Document
→ 단일 리소스

Collection
→ 서버가 URI를 관리하는 리소스 집합

Store
→ 클라이언트가 URI를 관리하는 리소스 저장소</code></pre>
<p>이다.</p>
<hr>
<h1 id="controller-uri는-언제-사용할까">Controller URI는 언제 사용할까?</h1>
<p>가능하면 URI에는 리소스를 표현하고 Method로 행위를 구분하는 것이 좋다.</p>
<p>그런데 현실에서는 모든 동작을 깔끔한 CRUD 형태로 표현하기 어려울 때도 있다.</p>
<p>특히 순수 HTML Form에서는</p>
<pre><code class="language-text">GET
POST</code></pre>
<p>만 지원하기 때문에</p>
<pre><code class="language-text">PATCH
DELETE</code></pre>
<p>같은 Method를 직접 사용하기 어렵다.</p>
<p>그럴 때</p>
<pre><code class="language-text">/members/100/edit

/members/100/delete</code></pre>
<p>처럼 동사가 포함된 URI를 사용하는 경우가 있다.</p>
<p>이런 형태를 <strong>Controller URI</strong>라고 한다.</p>
<p>또 HTTP API에서도 단순 CRUD로 표현하기 어려운 특정 프로세스를 실행해야 한다면 Controller URI를 사용할 수 있다.</p>
<p>즉</p>
<blockquote>
<p><strong>리소스 중심 URI 설계를 우선하되, 표현하기 어려운 프로세스에서는 Controller URI를 사용할 수 있다.</strong></p>
</blockquote>
<p>정도로 이해하면 될 것 같다.</p>
<hr>
<h1 id="http-method에는-속성도-있다">HTTP Method에는 속성도 있다</h1>
<p>HTTP Method 자체의 의미를 이해했다면 다음으로 중요한 것이 있다.</p>
<pre><code class="language-text">안전(Safe)

멱등(Idempotent)

캐시 가능(Cacheable)</code></pre>
<p>이다.</p>
<p>처음 들었을 때 특히</p>
<pre><code class="language-text">안전
멱등</code></pre>
<p>이 둘이 상당히 비슷하게 느껴졌다.</p>
<p>하지만 완전히 다른 개념이다.</p>
<hr>
<h1 id="안전safe이란">안전(Safe)이란?</h1>
<p>안전한 Method는 요청을 수행해도 <strong>대상 리소스의 상태를 변경하지 않는 Method</strong>다.</p>
<p>대표적으로 GET이 있다.</p>
<pre><code class="language-http">GET /members/100</code></pre>
<p>을 한 번 호출했다고 해서</p>
<pre><code class="language-text">100번 회원의 이름이 변경되거나

회원이 삭제되거나

새로운 회원이 생기면</code></pre>
<p>안 된다.</p>
<p>조회만 하는 것이다.</p>
<p>따라서 GET은 Safe Method다.</p>
<hr>
<h1 id="그런데-get을-100만-번-요청하면-서버-로그가-쌓이지-않을까">그런데 GET을 100만 번 요청하면 서버 로그가 쌓이지 않을까?</h1>
<p>수업에서 재미있는 질문이 나왔다.</p>
<p>GET을 계속 호출하면 서버에 로그가 쌓일 수 있다.</p>
<p>극단적으로는 너무 많은 요청 때문에 서버 장애가 발생할 수도 있다.</p>
<p>그렇다면 GET은 정말 안전한 것일까?</p>
<p>여기서 안전이라는 개념의 범위를 이해해야 한다.</p>
<blockquote>
<p><strong>Safe는 요청 대상 리소스 자체의 상태 변화 여부를 기준으로 본다.</strong></p>
</blockquote>
<p>로그가 쌓이거나 통계가 기록되는 등의 부수 효과까지 포함해서 세상의 모든 상태가 전혀 바뀌지 않는다는 의미는 아니다.</p>
<p>즉</p>
<pre><code class="language-text">GET /members/100</code></pre>
<p>으로 회원 데이터 자체가 변경되지 않는다면 HTTP 의미상 Safe라고 볼 수 있다.</p>
<hr>
<h1 id="멱등idempotent이란">멱등(Idempotent)이란?</h1>
<p>멱등은 처음 들으면 단어부터 어렵다.</p>
<p>그런데 뜻은 생각보다 단순하다.</p>
<blockquote>
<p><strong>같은 요청을 한 번 보내든 여러 번 보내든 의도한 최종 상태가 같다면 멱등이다.</strong></p>
</blockquote>
<p>예를 들어 조회 요청을 생각해보자.</p>
<pre><code class="language-text">GET /members/100</code></pre>
<p>한 번 조회하든</p>
<pre><code class="language-text">GET
GET
GET
GET</code></pre>
<p>여러 번 조회하든 요청 자체가 리소스를 변경하지 않는다.</p>
<p>그래서 GET은 멱등이다.</p>
<hr>
<h1 id="put은-왜-멱등일까">PUT은 왜 멱등일까?</h1>
<p>PUT은 리소스를 특정 상태로 대체한다.</p>
<p>예를 들어</p>
<pre><code class="language-http">PUT /members/100

{
    &quot;username&quot;: &quot;kim&quot;,
    &quot;age&quot;: 20
}</code></pre>
<p>을 여러 번 호출한다고 해보자.</p>
<pre><code class="language-text">1회
→ username=kim, age=20

2회
→ username=kim, age=20

100회
→ username=kim, age=20</code></pre>
<p>최종 결과는 같다.</p>
<p>따라서 PUT은 멱등이다.</p>
<hr>
<h1 id="delete도-멱등이다">DELETE도 멱등이다</h1>
<p>100번 회원을 삭제한다고 해보자.</p>
<pre><code class="language-http">DELETE /members/100</code></pre>
<p>한 번 요청하면 회원이 삭제된다.</p>
<p>다시 요청한다고 해도</p>
<pre><code class="language-text">100번 회원이 없는 상태</code></pre>
<p>라는 최종 결과 자체는 같다.</p>
<p>응답 Status Code는 달라질 수도 있지만 리소스의 최종 상태 관점에서는 동일하다.</p>
<p>그래서 DELETE 역시 멱등이다.</p>
<hr>
<h1 id="post는-왜-멱등이-아닐까">POST는 왜 멱등이 아닐까?</h1>
<p>예를 들어 결제 요청을 생각해보자.</p>
<pre><code class="language-text">POST /payments</code></pre>
<p>한 번 호출하면 한 번 결제될 수 있다.</p>
<p>두 번 호출하면</p>
<pre><code class="language-text">결제 2번</code></pre>
<p>이 될 수도 있다.</p>
<p>따라서 POST는 일반적으로 멱등하지 않다.</p>
<pre><code class="language-text">GET
→ 멱등

PUT
→ 멱등

DELETE
→ 멱등

POST
→ 일반적으로 멱등하지 않음</code></pre>
<p>정도로 정리할 수 있다.</p>
<hr>
<h1 id="안전과-멱등은-다르다">안전과 멱등은 다르다</h1>
<p>여기서 둘을 확실히 구분해야 한다.</p>
<pre><code class="language-text">Safe

→ 호출이 리소스 상태를 변경하는가?</code></pre>
<pre><code class="language-text">Idempotent

→ 같은 요청을 여러 번 반복했을 때
   최종 상태가 같은가?</code></pre>
<p>예를 들어 DELETE는</p>
<pre><code class="language-text">리소스를 삭제한다.</code></pre>
<p>따라서 Safe하지 않다.</p>
<p>하지만</p>
<pre><code class="language-text">한 번 삭제
100번 삭제

→ 결국 리소스가 없는 상태</code></pre>
<p>이므로 멱등하다.</p>
<p>이 예시가 둘의 차이를 이해하기 가장 좋은 것 같다.</p>
<hr>
<h1 id="멱등은-왜-중요할까">멱등은 왜 중요할까?</h1>
<p>멱등성은 자동 복구와 재요청에서 중요하다.</p>
<p>예를 들어 클라이언트가 서버에 요청했다.</p>
<pre><code class="language-text">Client
   │
   │ PUT /members/100
   ▼
Server</code></pre>
<p>서버에서는 요청을 정상 처리했다.</p>
<p>그런데 네트워크 문제로 응답이 클라이언트에 도착하지 않았다.</p>
<p>클라이언트 입장에서는 고민된다.</p>
<blockquote>
<p>&quot;서버가 처리한 거야? 안 한 거야?&quot;</p>
</blockquote>
<p>이때 같은 요청을 다시 보내도 최종 결과가 같다면 재요청하기가 상대적으로 안전하다.</p>
<pre><code class="language-text">요청
↓
Timeout
↓
같은 요청 재시도</code></pre>
<p>이런 자동 복구 메커니즘에서 멱등성이 중요하다.</p>
<hr>
<h1 id="중간에-다른-사람이-데이터를-바꾸면">중간에 다른 사람이 데이터를 바꾸면?</h1>
<p>예를 들어 내가 GET을 했다.</p>
<pre><code class="language-text">GET /members/100
→ age = 20</code></pre>
<p>그 사이 다른 사용자가 데이터를 바꿨다.</p>
<pre><code class="language-text">age = 30</code></pre>
<p>내가 다시 GET하면 결과는 달라진다.</p>
<pre><code class="language-text">GET /members/100
→ age = 30</code></pre>
<p>그러면 GET이 멱등하지 않은 것일까?</p>
<p>아니다.</p>
<p>멱등성은 <strong>외부 요인으로 리소스 상태가 변경되는 것까지 고려하는 개념은 아니다.</strong></p>
<p>같은 요청을 반복했을 때 그 요청 자체가 만들어내는 효과를 기준으로 판단한다.</p>
<hr>
<h1 id="캐시-가능cacheable">캐시 가능(Cacheable)</h1>
<p>마지막은 캐시 가능성이다.</p>
<p>캐시는 이전에 받은 응답을 저장해두었다가 같은 요청에 다시 활용하는 것이다.</p>
<p>예를 들어 웹페이지에서 큰 이미지를 받았다고 해보자.</p>
<pre><code class="language-text">Client
        ↓
이미지 요청
        ↓
Server
        ↓
10MB 이미지 응답</code></pre>
<p>같은 이미지를 다시 볼 때마다 서버에서 10MB를 다시 받을 필요가 있을까?</p>
<p>브라우저가 이전에 받은 데이터를 저장하고 있다면 다시 활용할 수 있다.</p>
<pre><code class="language-text">첫 요청
Server → Image → Browser Cache

두 번째 요청
Browser Cache 사용</code></pre>
<p>이것이 캐시의 기본적인 개념이다.</p>
<hr>
<h1 id="어떤-method를-캐시할까">어떤 Method를 캐시할까?</h1>
<p>강의에서는 실무적으로 주로</p>
<pre><code class="language-text">GET

HEAD</code></pre>
<p>정도를 캐시에 활용한다고 설명한다.</p>
<p>특히 GET은 URL을 Cache Key로 활용하기도 쉬워서 웹에서 캐시와 잘 맞는다.</p>
<p>POST 같은 Method도 HTTP 규칙상 특정 조건에서는 캐시가 가능할 수 있지만, 요청 Body와 응답의 캐시 정책 등을 함께 고려해야 하므로 일반적인 웹 개발에서는 GET만큼 널리 사용되지는 않는다.</p>
<p>현재 단계에서는</p>
<blockquote>
<p><strong>실무에서는 GET 기반 캐시를 가장 자주 접한다.</strong></p>
</blockquote>
<p>정도로 기억해두려고 한다.</p>
<hr>
<h1 id="http-method-속성을-한-번에-정리해보자">HTTP Method 속성을 한 번에 정리해보자</h1>
<p>개념적으로 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Safe</th>
<th>Idempotent</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>O</td>
<td>O</td>
<td>조회</td>
</tr>
<tr>
<td>HEAD</td>
<td>O</td>
<td>O</td>
<td>Header 조회</td>
</tr>
<tr>
<td>POST</td>
<td>X</td>
<td>X</td>
<td>신규 등록 / 프로세스 처리</td>
</tr>
<tr>
<td>PUT</td>
<td>X</td>
<td>O</td>
<td>전체 대체</td>
</tr>
<tr>
<td>PATCH</td>
<td>X</td>
<td>경우에 따라 다름</td>
<td>부분 변경</td>
</tr>
<tr>
<td>DELETE</td>
<td>X</td>
<td>O</td>
<td>삭제</td>
</tr>
</tbody></table>
<p>여기서 특히 기억해야 할 것은</p>
<pre><code class="language-text">Safe
≠
Idempotent</code></pre>
<p>라는 점이다.</p>
<p>DELETE가 가장 좋은 예다.</p>
<pre><code class="language-text">DELETE

리소스를 변경함
→ Safe X

반복해도 최종 상태는 동일
→ Idempotent O</code></pre>
<hr>
<h1 id="처음-api를-설계했던-방식으로-다시-돌아가보자">처음 API를 설계했던 방식으로 다시 돌아가보자</h1>
<p>처음에는 이렇게 만들 수도 있었다.</p>
<pre><code class="language-text">/getMembers

/getMember

/createMember

/updateMember

/deleteMember</code></pre>
<p>그런데 HTTP의 리소스와 Method를 이해하고 나면 다음처럼 설계할 수 있다.</p>
<pre><code class="language-text">GET /members

POST /members

GET /members/{id}

PUT /members/{id}

PATCH /members/{id}

DELETE /members/{id}</code></pre>
<p>여기서 핵심은 URI가</p>
<pre><code class="language-text">조회

등록

수정

삭제</code></pre>
<p>를 표현하지 않는다는 것이다.</p>
<p>URI가 표현하는 것은</p>
<pre><code class="language-text">members</code></pre>
<p>라는 <strong>리소스</strong>다.</p>
<p>그리고</p>
<pre><code class="language-text">GET

POST

PUT

PATCH

DELETE</code></pre>
<p>가 해당 리소스에 수행할 행동을 표현한다.</p>
<p>즉</p>
<pre><code class="language-text">URI
→ Resource

HTTP Method
→ Action</code></pre>
<p>이라는 역할 분리가 이루어진다.</p>
<p>처음에는</p>
<blockquote>
<p><strong>&quot;URL에 동사를 넣으면 직관적이고 좋은 거 아닌가?&quot;</strong></p>
</blockquote>
<p>라고 생각할 수도 있었다.</p>
<p>그런데 HTTP Method 자체가 이미 행위를 표현하고 있다는 걸 알고 나니 굳이 URI가 같은 역할까지 할 필요가 없다는 것이 이해됐다.</p>
<hr>
<h1 id="정리">정리</h1>
<p>이번에는 API URI를 어떻게 설계하는지부터 주요 HTTP Method와 Method의 속성까지 정리해봤다.</p>
<p>가장 먼저 기억해야 할 것은</p>
<blockquote>
<p><strong>URI 설계에서 가장 중요한 것은 리소스를 식별하는 것이다.</strong></p>
</blockquote>
<p>라는 점이다.</p>
<p>회원 관리 시스템이라면 리소스는</p>
<pre><code class="language-text">조회

등록

수정

삭제</code></pre>
<p>가 아니라</p>
<pre><code class="language-text">회원</code></pre>
<p>이다.</p>
<p>그래서 URI에는</p>
<pre><code class="language-text">/members

/members/100</code></pre>
<p>처럼 회원 리소스를 표현한다.</p>
<p>행위는 HTTP Method가 담당한다.</p>
<pre><code class="language-text">GET
→ 조회

POST
→ 요청 데이터 처리 / 신규 등록

PUT
→ 전체 대체

PATCH
→ 부분 수정

DELETE
→ 삭제</code></pre>
<p>이렇게 리소스와 행위를 분리해서 생각하면 API URI도 훨씬 단순해진다.</p>
<p>또 POST와 PUT의 차이도 단순히</p>
<pre><code class="language-text">POST = 등록

PUT = 수정</code></pre>
<p>으로 외우면 부족하다.</p>
<pre><code class="language-text">POST 기반 Collection

POST /members
→ 서버가 새로운 URI 결정</code></pre>
<pre><code class="language-text">PUT 기반 Store

PUT /files/star.jpg
→ 클라이언트가 URI 결정</code></pre>
<p>이라는 차이까지 이해하면 둘의 역할이 조금 더 명확해진다.</p>
<p>그리고 Method 자체에도</p>
<pre><code class="language-text">Safe

Idempotent

Cacheable</code></pre>
<p>이라는 속성이 존재한다.</p>
<p>특히 이번에 가장 헷갈렸던 것은 Safe와 Idempotent였다.</p>
<p>처음에는 둘 다</p>
<blockquote>
<p>&quot;여러 번 호출해도 괜찮다는 뜻인가?&quot;</p>
</blockquote>
<p>정도로 비슷하게 느껴졌다.</p>
<p>하지만 실제로는 완전히 다른 질문이었다.</p>
<pre><code class="language-text">Safe
→ 이 요청이 리소스를 변경하는가?

Idempotent
→ 이 요청을 여러 번 반복해도 최종 결과가 같은가?</code></pre>
<p>DELETE처럼</p>
<pre><code class="language-text">Safe X

Idempotent O</code></pre>
<p>인 Method도 있다는 것을 보면 차이가 확실히 보인다.</p>
<p>결국 HTTP API를 설계할 때 중요한 것은 URI 하나만 잘 만드는 것이 아니었다.</p>
<pre><code class="language-text">어떤 Resource인가?

↓

어떤 URI로 식별할 것인가?

↓

어떤 HTTP Method를 사용할 것인가?

↓

데이터는 Query Parameter로 전달할 것인가?
Message Body로 전달할 것인가?

↓

이 Method는 Safe한가?
Idempotent한가?
Cacheable한가?</code></pre>
<p>를 함께 생각해야 했다.</p>
<p>예전에는</p>
<pre><code class="language-text">/api/getMember

/api/createMember</code></pre>
<p>처럼 URL 하나에 모든 의미를 넣는 것이 오히려 직관적이라고 생각했다.</p>
<p>그런데 HTTP가 이미 Method라는 행위 표현 수단을 가지고 있다는 것을 알고 나니</p>
<pre><code class="language-text">Resource는 URI로 식별하고

행위는 HTTP Method로 표현한다.</code></pre>
<p>라는 설계가 왜 나오는지 조금씩 이해가 되는 것 같다.</p>
<p>다음에는 이 HTTP Method들을 실제 API에서 어떻게 활용하는지,</p>
<p>특히</p>
<pre><code class="language-text">HTML Form

HTTP API

POST 기반 Collection

PUT 기반 Store

Controller URI</code></pre>
<p>같은 실제 설계 방식도 조금 더 정리해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴파일러는 소스 코드를 어떻게 번역할까? | 컴파일 단계부터 LEX와 Parser까지]]></title>
            <link>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EB%8A%94-%EC%86%8C%EC%8A%A4-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B2%88%EC%97%AD%ED%95%A0%EA%B9%8C-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EB%8B%A8%EA%B3%84%EB%B6%80%ED%84%B0-LEX%EC%99%80-Parser%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@daehyun_lee/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EB%8A%94-%EC%86%8C%EC%8A%A4-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B2%88%EC%97%AD%ED%95%A0%EA%B9%8C-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EB%8B%A8%EA%B3%84%EB%B6%80%ED%84%B0-LEX%EC%99%80-Parser%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Mon, 14 Sep 2026 04:40:43 GMT</pubDate>
            <description><![CDATA[<h1 id="컴파일러는-소스-코드를-어떻게-번역할까--컴파일-단계부터-lex와-parser까지">컴파일러는 소스 코드를 어떻게 번역할까? | 컴파일 단계부터 LEX와 Parser까지</h1>
<p>이번 학기에 컴파일러 수업을 듣고 있다.</p>
<p>Java나 C를 사용하면서 <code>컴파일</code>이라는 단어는 정말 많이 봤다.</p>
<p>그런데 막상 컴파일러 내부에서 소스 코드가 어떤 순서로 처리되는지 설명해보려고 하니 잘 모르겠었다.</p>
<p>그냥 다음 정도로만 생각하고 있었다.</p>
<pre><code class="language-text">소스 코드
↓
컴파일러
↓
기계어</code></pre>
<p>틀린 설명은 아니다.</p>
<p>하지만 수업에서는 갑자기 Scanner, Parser, Intermediate Code 같은 단어가 한꺼번에 나오기 시작했다.</p>
<blockquote>
<p>“컴파일러는 그냥 코드를 기계어로 바꾸면 되는 것 아닌가?<br>왜 굳이 이렇게 여러 단계로 나누는 거지?”</p>
</blockquote>
<p>그래서 이번 글에서는 강의에서 배운 흐름을 따라가면서 컴파일러가 프로그램을 어떻게 번역하는지 다시 정리해보려고 한다.</p>
<p>중간고사에서 OX 문제로 많이 출제된다고 하니, 마지막에는 헷갈리기 쉬운 문장도 따로 모아봤다.</p>
<hr>
<h2 id="프로그램을-실행하는-방법은-하나가-아니었다">프로그램을 실행하는 방법은 하나가 아니었다</h2>
<p>고급 언어로 작성된 프로그램을 실행하는 방법은 크게 세 가지로 나눌 수 있다.</p>
<pre><code class="language-text">프로그램 처리 기법
├─ 컴파일 기법
├─ 해석 기법
└─ 하이브리드 기법</code></pre>
<p>처음에는 언어마다 셋 중 하나로 딱 나뉜다고 생각했다.</p>
<p>그런데 공부해보니 중요한 것은 언어의 이름보다 <strong>해당 언어를 어떤 방식으로 구현하고 실행하는가</strong>였다.</p>
<p>우선 가장 익숙한 컴파일 기법부터 살펴보자.</p>
<h2 id="컴파일러는-해석이-아니라-번역한다">컴파일러는 ‘해석’이 아니라 ‘번역’한다</h2>
<p>컴파일러(Compiler)는 고급 언어로 작성된 원시 프로그램(Source Program)을 특정 컴퓨터에서 실행할 수 있는 목적 프로그램(Object Program)으로 번역하는 프로그램이다.</p>
<pre><code class="language-text">원시 프로그램
      ↓
   컴파일러
      ↓
목적 프로그램</code></pre>
<p>강의 자료에서는 C 프로그램을 다음과 같이 컴파일하고 실행한다.</p>
<pre><code class="language-bash">$ gcc helloworld.c
$ ./a.out
Hello World</code></pre>
<p>여기서 개인적으로 가장 먼저 정리해야 했던 것은 <code>번역</code>과 <code>해석</code>의 차이였다.</p>
<p>메모에는 다음처럼 적어두었다.</p>
<blockquote>
<p>“컴파일러는 하이레벨 프로그래밍 언어를 해석하는 것?”</p>
</blockquote>
<p>다시 확인해보니 정확한 표현은 아니었다.</p>
<p>컴파일러는 원시 프로그램을 목적 프로그램으로 <strong>번역</strong>한다.</p>
<p>반면 인터프리터는 프로그램을 읽고 그에 해당하는 동작을 수행하여 실행 결과를 만든다.</p>
<pre><code class="language-text">Compiler    → 프로그램을 다른 형태의 프로그램으로 번역
Interpreter → 프로그램을 읽고 동작을 수행하여 결과 생성</code></pre>
<p>따라서 시험에서 다음 문장이 나온다면 주의해야 한다.</p>
<blockquote>
<p><strong>“컴파일러는 고급 언어를 해석하여 바로 실행 결과를 만든다.” → X</strong></p>
</blockquote>
<p>번역이 끝난 목적 프로그램은 이후 빠르게 실행할 수 있다.</p>
<p>그런데 이 번역은 한 번에 일어나지 않는다.</p>
<hr>
<h2 id="컴파일러가-굳이-여러-단계를-거치는-이유">컴파일러가 굳이 여러 단계를 거치는 이유</h2>
<p>강의에서 배운 전체 흐름은 다음과 같다.</p>
<pre><code class="language-text">원시 프로그램
      ↓
어휘 분석 단계
      ↓
구문 분석 단계
      ↓
중간 코드 생성 단계
      ↓
최적화 단계
      ↓
코드 생성 단계
      ↓
목적 프로그램</code></pre>
<p>처음에는 단계 이름이 전부 비슷하게 느껴졌다.</p>
<p>그런데 입력과 출력만 놓고 보니 역할이 조금씩 구분되기 시작했다.</p>
<pre><code class="language-text">문자
↓ 어휘 분석
Token
↓ 구문 분석
Tree
↓ 중간 코드 생성
Intermediate Code
↓ 최적화
Optimized Code
↓ 코드 생성
Object Code</code></pre>
<p>즉, 각 단계는 같은 작업을 잘게 나눈 것이 아니다.</p>
<p>문자를 구분하는 일, 문법 구조를 확인하는 일, 의미를 검사하는 일, 기계 명령을 만드는 일을 서로 나누어 처리한다.</p>
<p>이제 각 단계가 무엇을 확인하는지 하나씩 살펴보자.</p>
<h2 id="1-어휘-분석-단계-문자를-token으로-나눈다">1. 어휘 분석 단계: 문자를 Token으로 나눈다</h2>
<p>어휘 분석기(Lexical Analyzer)는 <strong>Scanner</strong>라고도 부른다.</p>
<p>이건 시험에 나온다고 하니 일단 기억해두자.</p>
<pre><code class="language-text">Lexical Analyzer = Scanner</code></pre>
<p>Scanner는 원시 프로그램을 읽고 Token 단위로 나눈다.</p>
<p>예를 들어 다음 식이 있다.</p>
<pre><code class="language-text">A = 3 + (5 - 2)</code></pre>
<p>어휘 분석을 거치면 다음과 같이 나뉜다.</p>
<pre><code class="language-text">A | = | 3 | + | ( | 5 | - | 2 | )</code></pre>
<p>여기서 Token은 식별자, 숫자, 연산자, 괄호처럼 프로그램을 구성하는 문법적 단위다.</p>
<p>단순히 공백을 기준으로 문자열을 자르는 것은 아니다.</p>
<p>예를 들어 다음 코드를 보자.</p>
<pre><code class="language-c">if (a &gt; 10)</code></pre>
<p>Scanner는 이를 다음과 같은 Token의 연속으로 인식할 수 있다.</p>
<pre><code class="language-text">IF | LPAREN | ID(a) | GT | NUMBER(10) | RPAREN</code></pre>
<p>강의 자료에서는 컴파일러 내부에서 Token을 효율적으로 다루기 위해 정수 번호로 바꾸는 예시가 나온다.</p>
<pre><code class="language-text">Token        : if  (  a  &gt;  10  )
Token Number : 32  7  4  25  5  8</code></pre>
<p>처음에는 여기서 <code>if</code>가 정말 숫자 32로 바뀌는 것인가 싶었다.</p>
<p>정확히는 <code>if</code>라는 문자열 자체가 숫자가 된다는 의미보다, 컴파일러 내부에서 <strong>IF라는 Token 종류를 정수 번호로 표현한다</strong>는 의미에 가깝다.</p>
<p>그리고 식별자나 상수처럼 추가 정보가 필요하면 Token 값도 함께 전달한다.</p>
<pre><code class="language-text">(Token Number, Token Value)</code></pre>
<p>예를 들어 <code>10</code>을 나타내는 Token이라면 <code>NUMBER</code>에 해당하는 번호와 실제 값 <code>10</code>을 함께 전달할 수 있다.</p>
<p>정리하면 Scanner의 입출력은 다음과 같다.</p>
<pre><code class="language-text">입력  : 원시 프로그램의 문자 스트림
출력  : Token의 연속
역할  : 문자를 Token 단위로 구분</code></pre>
<p>그런데 여기서 또 하나 궁금한 게 있다.</p>
<blockquote>
<p>“Token으로 잘 나누기만 하면 올바른 프로그램인 걸까?”</p>
</blockquote>
<p>예를 들어 단어를 전부 알아볼 수 있다고 해서 그 문장이 문법적으로 올바른 것은 아니다.</p>
<p>그래서 다음 단계가 필요하다.</p>
<h2 id="2-구문-분석-단계-token의-문법을-검사한다">2. 구문 분석 단계: Token의 문법을 검사한다</h2>
<p>구문 분석기(Syntax Analyzer)는 <strong>Parser</strong>라고도 부른다.</p>
<pre><code class="language-text">Syntax Analyzer = Parser</code></pre>
<p>Parser는 Scanner가 전달한 Token들이 프로그래밍 언어의 문법에 맞게 배치되었는지 검사한다.</p>
<pre><code class="language-text">Token의 연속
      ↓
    Parser
      ↓
Error Message 또는 Tree</code></pre>
<p>문법이 잘못되었다면 Error Message를 출력한다.</p>
<p>문법이 올바르다면 프로그램 구조를 Tree 형태로 만든다.</p>
<p>강의 자료의 예시는 다음과 같다.</p>
<pre><code class="language-c">if (a &gt; 10) a = 1;</code></pre>
<p>이 문장의 구조를 간단히 표현하면 다음과 같다.</p>
<pre><code class="language-text">       if
      /  \
     &gt;    =
    / \  / \
   a  10 a  1</code></pre>
<p>여기서 메모에 이런 말을 적어두었다.</p>
<blockquote>
<p>“문법 구조 형태로 되어 있는 게 Parse Tree인가?”</p>
</blockquote>
<p>맞다.</p>
<p>Parser가 문법 규칙에 따라 프로그램의 구조를 Tree 형태로 만든 것이 Parse Tree다.</p>
<p>문자열로 볼 때는 한 줄이었던 코드가 Tree가 되면서 조건식과 대입문의 관계가 드러난다.</p>
<h3 id="parse-tree와-ast는-무엇이-다를까">Parse Tree와 AST는 무엇이 다를까?</h3>
<p>강의 자료에는 Parse Tree뿐만 아니라 AST(Abstract Syntax Tree)도 등장한다.</p>
<p>둘 다 Tree 형태로 프로그램의 구조를 나타내지만 표현하는 정보의 양이 다르다.</p>
<ul>
<li>Parse Tree는 문법 규칙에 따라 만들어지는 구조를 자세하게 표현한다.</li>
<li>AST는 문법을 확인하는 데만 필요한 요소를 줄이고 프로그램의 핵심 구조를 표현한다.</li>
</ul>
<p>예를 들어 <code>a = b + 1</code>의 AST는 다음과 같이 단순하게 나타낼 수 있다.</p>
<pre><code class="language-text">      =
     / \
    a   +
       / \
      b   1</code></pre>
<p>괄호나 세미콜론 자체보다 대입과 덧셈의 관계가 중요하기 때문이다.</p>
<h3 id="그럼-parse-tree가-3종류-있다는-뜻일까">그럼 Parse Tree가 3종류 있다는 뜻일까?</h3>
<p>수업 메모를 다시 보다가 가장 이해되지 않았던 부분이다.</p>
<blockquote>
<p>“Parse Tree가 있는데 3종류가 있다? 뭔 소리지?”</p>
</blockquote>
<p>PDF를 다시 확인해보니 Parse Tree가 세 종류라는 뜻이 아니었다.</p>
<p>뒤에서 설명하는 <strong>Parser Generator의 예시가 세 가지</strong>였다.</p>
<pre><code class="language-text">1. Stanford PGS
2. Wisconsin PGS
3. YACC</code></pre>
<p>Parse Tree의 종류와 Parser를 만들어주는 도구의 종류를 섞어서 적어둔 것이었다.</p>
<p>아, 그래서 메모만 다시 봤을 때 아무리 생각해도 연결이 되지 않았던 것 같다.</p>
<h3 id="프로그래밍-언어에는-모호성이-존재할까">프로그래밍 언어에는 모호성이 존재할까?</h3>
<p>메모에는 <code>프로그래밍 언어는 모호성이 존재!</code>라고 적어두었다.</p>
<p>그런데 이 문장도 그대로 외우면 조금 위험하다.</p>
<p>하나의 문장을 두 가지 이상의 구조로 해석할 수 있다면 그 문법을 모호한 문법(Ambiguous Grammar)이라고 한다.</p>
<p>예를 들어 연산자 우선순위가 없다면 다음 식은 두 가지로 해석될 수 있다.</p>
<pre><code class="language-text">a + b * c

(a + b) * c
a + (b * c)</code></pre>
<p>같은 문장으로 둘 이상의 Parse Tree가 만들어질 수 있는 것이다.</p>
<p>하지만 모든 프로그래밍 언어가 반드시 모호한 것은 아니다.</p>
<p>프로그래밍 언어는 연산자 우선순위, 결합 법칙, 문법 규칙 등을 정의해 이런 모호성을 해결한다.</p>
<pre><code class="language-text">정확한 표현
→ 모호한 문법에서는 하나의 문장에 둘 이상의 Parse Tree가 만들어질 수 있다.

주의할 표현
→ 모든 프로그래밍 언어의 문법은 모호하다.</code></pre>
<p>개인적으로 이 부분은 <code>프로그래밍 언어</code>와 <code>프로그래밍 언어의 모호한 문법</code>을 구분하는 것이 중요해 보였다.</p>
<h2 id="3-중간-코드-생성-단계-문법이-아닌-의미를-확인한다">3. 중간 코드 생성 단계: 문법이 아닌 의미를 확인한다</h2>
<p>Parser를 통과했다면 문법적으로는 올바른 프로그램이다.</p>
<p>그렇다면 이제 오류가 없다고 볼 수 있을까?</p>
<p>아니다.</p>
<p>문법은 맞지만 의미가 잘못될 수도 있다.</p>
<p>강의 자료에는 다음 예시가 나온다.</p>
<pre><code class="language-text">if (a &gt; 10) a = 1.0;</code></pre>
<p><code>a</code>가 정수형인데 실수 값인 <code>1.0</code>을 대입할 수 없는 언어라면 의미 오류(Semantic Error)가 된다.</p>
<p>문장의 모양은 문법에 맞지만 타입 규칙에는 맞지 않는 것이다.</p>
<p>다만 실제로 오류가 발생하는지는 각 프로그래밍 언어의 타입 변환 규칙에 따라 달라질 수 있다. 이 예시는 문법 오류와 의미 오류를 구분하기 위한 강의 자료의 예시로 이해했다.</p>
<pre><code class="language-text">Syntax Error
→ Token의 배치가 문법 규칙에 맞지 않음

Semantic Error
→ 문법은 맞지만 타입이나 선언 등의 의미 규칙에 맞지 않음</code></pre>
<p>강의 자료에서는 중간 코드 생성 단계에서 Semantic Checking과 Intermediate Code Generation을 수행한다고 설명한다.</p>
<p>조금 더 세분화된 컴파일러 구조에서는 의미 분석 단계를 별도로 구분하기도 한다.</p>
<p>의미 검사가 끝나면 프로그램을 중간 코드(Intermediate Code)로 바꾼다.</p>
<p>중간 코드는 기계어는 아니지만 특정 기계에 직접 의존하지 않으면서 기계어에 가까운 형태다.</p>
<p>예를 들어 다음 식을 보자.</p>
<pre><code class="language-text">A = 3 + (5 - 2)</code></pre>
<p>강의 자료에서는 이를 다음과 같은 중간 코드로 나타낸다.</p>
<pre><code class="language-text">(sub 5 2 T0)   // T0 = 5 - 2
(add 3 T0 T1)  // T1 = 3 + T0
(mov T1 A)     // A = T1</code></pre>
<p>하나의 복잡한 식을 여러 개의 단순한 연산으로 나눈 것이다.</p>
<p>그런데 왜 곧바로 기계어를 만들지 않고 중간 코드를 거칠까?</p>
<p>중간 코드를 기준으로 앞부분과 뒷부분을 분리하면 여러 언어와 여러 기계를 연결하기 쉬워진다.</p>
<p>이걸 이해하려면 Front-End와 Back-End를 함께 봐야 한다.</p>
<h2 id="front-end와-back-end는-무엇이-다를까">Front-End와 Back-End는 무엇이 다를까?</h2>
<p>컴파일러의 구조는 크게 Front-End와 Back-End로 나눌 수 있다.</p>
<pre><code class="language-text">원시 프로그램
      ↓
  Front-End
      ↓
Intermediate Code
      ↓
   Back-End
      ↓
목적 프로그램</code></pre>
<p>Front-End는 원시 언어에 의존하는 부분(Language Dependent Part)이다.</p>
<p>소스 코드를 읽고 Token과 Tree를 만들며 의미를 분석해 중간 코드로 바꾸는 과정과 관련된다.</p>
<p>Back-End는 목표 기계에 의존하는 부분(Machine Dependent Part)이다.</p>
<p>중간 코드를 최적화하고 특정 기계에서 실행할 수 있는 목적 코드로 바꾸는 과정과 관련된다.</p>
<pre><code class="language-text">Front-End → 어떤 언어로 작성했는가?
Back-End  → 어떤 기계에서 실행할 것인가?</code></pre>
<p>이제 중간 코드가 왜 필요한지 조금 이해됐다.</p>
<p>중간 코드는 언어를 이해하는 Front-End와 기계를 이해하는 Back-End 사이에서 공통 표현 역할을 한다.</p>
<h2 id="4-최적화-단계-같은-의미를-더-효율적으로-만든다">4. 최적화 단계: 같은 의미를 더 효율적으로 만든다</h2>
<p>중간 코드를 만들었다면 불필요한 코드를 제거하거나 더 효율적인 코드로 바꿀 수 있다.</p>
<p>예를 들어 다음 중간 코드가 있다고 해보자.</p>
<pre><code class="language-text">(mov 10 A)
(mov A B)
(mov B C)
(add C 2 D)</code></pre>
<p>만약 <code>B</code>와 <code>C</code>가 다른 곳에서 사용되지 않는다면 다음과 같이 바꿀 수 있다.</p>
<pre><code class="language-text">(mov 10 A)
(add A 2 D)</code></pre>
<p>프로그램이 계산하는 결과는 같지만 불필요한 이동 명령이 사라졌다.</p>
<p>강의 자료에서는 최적화의 주요 목적을 실행 시간 개선, 부수적인 목적을 코드 크기 감소로 설명한다.</p>
<p>여기서 시험에 나올 만한 표현이 하나 있다.</p>
<blockquote>
<p><strong>최적화 단계는 Optional Phase다.</strong></p>
</blockquote>
<p>그리고 최적화가 항상 실행 속도와 코드 크기를 동시에 개선하는 것은 아니다.</p>
<p>어떤 최적화는 실행 속도를 높이는 대신 코드 크기를 늘릴 수 있다. 중요한 것은 프로그램의 의미를 유지하면서 정해진 목표에 맞게 더 효율적인 코드로 바꾸는 것이다.</p>
<h2 id="5-코드-생성-단계-목적-프로그램을-만든다">5. 코드 생성 단계: 목적 프로그램을 만든다</h2>
<p>마지막은 Target Code Generator다.</p>
<p>최적화된 중간 코드로부터 목표 컴퓨터가 인식할 수 있는 Machine Instruction을 생성한다.</p>
<pre><code class="language-text">Optimized Intermediate Code
            ↓
   Target Code Generator
            ↓
       Object Program</code></pre>
<p>같은 중간 코드라도 실행할 기계의 명령어 구조가 다르면 생성되는 목적 코드도 달라진다.</p>
<p>앞에서 Back-End가 기계 의존적인 부분이라고 했던 이유가 여기서 드러난다.</p>
<p>결국 컴파일 과정은 다음 한 줄로 압축할 수 있다.</p>
<pre><code class="language-text">문자 → Token → Tree → Intermediate Code → Optimized Code → Object Code</code></pre>
<p>처음에는 컴파일러가 소스 코드 전체를 한 번에 기계어로 바꾸는 줄 알았다.</p>
<p>그런데 실제로는 각 단계가 서로 다른 질문에 답하고 있었다.</p>
<pre><code class="language-text">Scanner  → 이 문자는 어떤 Token인가?
Parser   → Token의 순서가 문법에 맞는가?
Semantic Checking → 문법뿐 아니라 의미도 올바른가?
Code Generator    → 목표 기계의 명령으로 어떻게 바꿀까?</code></pre>
<hr>
<h2 id="c의--뒤에-붙는-것은-무엇일까">C의 <code>#</code> 뒤에 붙는 것은 무엇일까?</h2>
<p>C 코드를 보면 다음처럼 <code>#</code>으로 시작하는 문장을 자주 볼 수 있다.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#define MAX_SIZE 100</code></pre>
<p>메모에는 <code># 뒤에 있는 건 뭐라고 했는데...</code>라고 적어두었다.</p>
<p>이것은 <strong>전처리 지시문(Preprocessor Directive)</strong>이다.</p>
<p>컴파일러가 원시 프로그램을 분석하기 전에 전처리기(Preprocessor)가 먼저 처리한다.</p>
<pre><code class="language-text">원시 프로그램
      ↓
전처리기
      ↓
확장된 원시 프로그램
      ↓
컴파일러</code></pre>
<p>대표적인 역할은 다음과 같다.</p>
<pre><code class="language-text">#include → 다른 파일의 내용 포함
#define  → 매크로 정의와 치환
#if      → 조건에 따라 컴파일할 코드 선택</code></pre>
<p>강의 자료에서는 Preprocessor를 언어 확장(Language Extension)을 위한 도구로 설명한다.</p>
<p>따라서 <code>#include</code> 자체를 일반적인 C 실행문으로 보는 것이 아니라 컴파일 전에 처리되는 지시문으로 이해해야 한다.</p>
<hr>
<h2 id="인터프리터는-무엇이-다를까">인터프리터는 무엇이 다를까?</h2>
<p>인터프리터(Interpreter)는 고급 언어로 작성된 프로그램을 읽고 기계 동작을 수행하여 결과를 만든다.</p>
<p>강의 자료에서는 <strong>고급 언어를 자신의 기계어로 취급하는 컴퓨터를 시뮬레이션한 것</strong>이라고 설명한다.</p>
<pre><code class="language-text">원시 프로그램 + 입력 데이터
           ↓
       Interpreter
           ↓
          결과</code></pre>
<p>Scheme 인터프리터의 예시는 다음과 같다.</p>
<pre><code class="language-scheme">=&gt; (* 2 3 4)
Value: 24</code></pre>
<p>입력한 표현식을 읽고 바로 결과를 보여준다.</p>
<p>처음에는 Python도 바로 인터프리터 예시로 떠올랐다.</p>
<p>보통 Python을 인터프리터 언어라고 부르지만, 조금 더 정확히 말하면 언어 자체보다 구현 방식을 봐야 한다.</p>
<p>대표적인 Python 구현인 CPython은 소스 코드를 Bytecode로 바꾼 뒤 Python Virtual Machine에서 실행한다.</p>
<p>따라서 <code>인터프리터는 언제나 원시 코드를 한 줄씩 곧바로 실행한다</code>고만 외우면 실제 구현을 설명하기에는 부족하다.</p>
<p>이번 강의 자료에서 명확하게 제시한 인터프리터의 예시는 Scheme이다.</p>
<h2 id="하이브리드-기법-컴파일과-해석을-섞는다">하이브리드 기법: 컴파일과 해석을 섞는다</h2>
<p>하이브리드 기법은 컴파일 기법과 해석 기법을 혼합한 방식이다.</p>
<p>원시 프로그램을 바로 기계어로 만드는 대신, 먼저 해석하기 쉬운 중간 코드로 번역한다.</p>
<p>그다음 중간 코드를 가상 머신이 실행한다.</p>
<pre><code class="language-text">원시 프로그램
      ↓ 컴파일
  중간 코드
      ↓ 해석·실행
     결과</code></pre>
<p>대표적인 예가 Java다.</p>
<pre><code class="language-text">HelloWorld.java
      ↓ javac
HelloWorld.class
   (Bytecode)
      ↓ JVM
    실행 결과</code></pre>
<p>Java 컴파일러는 원시 프로그램을 Bytecode라는 중간 코드로 번역한다.</p>
<p>그리고 운영체제마다 별도로 구현된 JVM(Java Virtual Machine)이 Bytecode를 실행한다.</p>
<pre><code class="language-bash">$ javac HelloWorld.java
$ java HelloWorld
Hello World</code></pre>
<p>현대 JVM은 Bytecode를 해석하기도 하고 자주 실행되는 코드를 JIT(Just-In-Time) 컴파일하기도 한다.</p>
<p>하지만 이번 강의에서 기억해야 할 중심 흐름은 다음과 같다.</p>
<pre><code class="language-text">Java Source Code
↓
Bytecode
↓
운영체제별 JVM
↓
실행</code></pre>
<p>컴파일러, 인터프리터, 하이브리드 기법을 한 번에 비교하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>컴파일 기법</th>
<th>해석 기법</th>
<th>하이브리드 기법</th>
</tr>
</thead>
<tbody><tr>
<td>처리 방식</td>
<td>원시 프로그램을 목적 프로그램으로 번역</td>
<td>프로그램을 읽고 동작을 수행</td>
<td>중간 코드로 번역한 뒤 실행</td>
</tr>
<tr>
<td>실행하는 대상</td>
<td>만들어진 목적 프로그램</td>
<td>원시 프로그램 또는 내부 표현</td>
<td>Bytecode 같은 중간 코드</td>
</tr>
<tr>
<td>강의 자료의 예</td>
<td>C</td>
<td>Scheme</td>
<td>Java</td>
</tr>
</tbody></table>
<hr>
<h2 id="scanner를-매번-직접-만들어야-할까">Scanner를 매번 직접 만들어야 할까?</h2>
<p>어휘 분석과 구문 분석의 역할은 이해했다.</p>
<p>그런데 새로운 언어를 만들 때마다 Scanner와 Parser를 전부 직접 구현해야 한다면 상당히 복잡할 것 같다.</p>
<p>그래서 등장한 것이 컴파일러 자동화 도구다.</p>
<p>강의 자료에서는 이를 Compiler Generating Tool 또는 Compiler-Compiler라고 설명한다.</p>
<p>프로그래밍 언어가 N개이고 실행할 기계가 M개라면 최대 <code>N × M</code>개의 컴파일러 조합이 필요할 수 있다.</p>
<pre><code class="language-text">2개 언어: C, Java
3개 기계: IBM, SPARC, Pentium

필요한 조합
→ C-to-IBM
→ C-to-SPARC
→ C-to-Pentium
→ Java-to-IBM
→ Java-to-SPARC
→ Java-to-Pentium</code></pre>
<p>언어와 기계가 늘어날 때마다 모든 컴파일러를 처음부터 만드는 것은 부담이 크다.</p>
<p>그래서 언어의 문법이나 Token 규칙 같은 설명을 바탕으로 컴파일러의 일부를 자동으로 만들어주는 도구가 필요해진다.</p>
<h2 id="lex-정규표현식으로-lexical-analyzer를-만든다">LEX: 정규표현식으로 Lexical Analyzer를 만든다</h2>
<p>LEX는 1975년 M. E. Lesk가 고안한 어휘 분석기 생성 도구다.</p>
<p>입력 스트림에서 정규표현식(Regular Expression)으로 표현된 Token을 찾아내는 프로그램을 만들 때 사용한다.</p>
<p>강의에서 <code>LEX</code>, <code>정규표현식</code>, <code>어휘 분석기</code>를 함께 강조한 이유가 여기에 있었다.</p>
<pre><code class="language-text">Regular Expression
+
Action Code
      ↓
     LEX
      ↓
Lexical Analyzer Source
    (lex.yy.c)
      ↓
 Token Stream</code></pre>
<p>정규표현식으로 숫자, 식별자, 연산자 같은 Token의 패턴을 정의한다.</p>
<p>그리고 해당 패턴을 찾았을 때 어떤 동작을 수행할지 Action Code로 작성한다.</p>
<p>LEX는 이 내용을 바탕으로 <code>lex.yy.c</code>라는 Lexical Analyzer 소스 코드를 생성한다.</p>
<p>처음에는 LEX가 Parser까지 만들어주는 도구인가 싶었다.</p>
<p>그런데 다시 정리해보니 LEX의 역할은 명확했다.</p>
<pre><code class="language-text">LEX
→ Lexical Analyzer 생성
→ 문자 스트림을 Token Stream으로 변환</code></pre>
<p>만약 LEX 실습이나 과제가 나온다면 최소한 다음 흐름은 기억해야 할 것 같다.</p>
<pre><code class="language-text">입력  : Regular Expression + Action Code
출력  : lex.yy.c
역할  : Scanner 생성</code></pre>
<h2 id="parser-generator-문법으로-parser를-만든다">Parser Generator: 문법으로 Parser를 만든다</h2>
<p>그렇다면 Parser는 어떻게 만들까?</p>
<p>Parser Generator(PGS: Parser Generating System)는 Grammar Description을 입력받아 Parsing Table 또는 Parser를 생성한다.</p>
<pre><code class="language-text">Grammar Description
        ↓
Parser Generator
        ↓
Parsing Table / Parser
        ↓
Program Structure</code></pre>
<p>강의 자료에서 소개한 Parser Generator는 세 가지다.</p>
<h3 id="stanford-pgs">Stanford PGS</h3>
<ul>
<li>John Hennessy가 개발</li>
<li>Pascal로 작성</li>
<li>약 5,000줄</li>
<li>구문 구조를 AST 형태로 얻음</li>
<li>AST 정보를 포함한 Parsing Table 출력</li>
</ul>
<h3 id="wisconsin-pgs">Wisconsin PGS</h3>
<ul>
<li>C. N. Fisher가 개발</li>
<li>Pascal로 작성</li>
<li>약 10,000줄</li>
<li>Error Recovery가 특징</li>
</ul>
<h3 id="yacc">YACC</h3>
<p>YACC는 Yet Another Compiler Compiler의 약자다.</p>
<ul>
<li>UNIX에서 수행</li>
<li>C 언어로 작성</li>
<li>Grammar Rule과 Action Code를 사용</li>
<li><code>y.tab.c</code>라는 구문 분석기 소스 생성</li>
</ul>
<p>LEX와 YACC를 함께 놓고 보면 차이가 더 명확하다.</p>
<pre><code class="language-text">Regular Expression + Action Code
                ↓
               LEX
                ↓
             lex.yy.c
                ↓ Token Stream
             y.tab.c
                ↑
               YACC
                ↑
      Grammar Rule + Action Code</code></pre>
<pre><code class="language-text">LEX  → 문자를 Token으로 바꾸는 Scanner 생성
YACC → Token을 문법 구조로 만드는 Parser 생성</code></pre>
<p>아, 그래서 강의에서 LEX는 정규표현식과 연결하고 Parser Generator는 Grammar와 연결했던 것이었다.</p>
<p>둘 다 컴파일러 자동화 도구이지만 만들어주는 대상이 다르다.</p>
<hr>
<h2 id="중간고사-ox-문제로-다시-확인하기">중간고사 OX 문제로 다시 확인하기</h2>
<p>시험에서 헷갈릴 만한 내용을 OX 형태로 정리해봤다.</p>
<h3 id="1-컴파일러는-고급-언어로-작성된-프로그램을-읽고-바로-실행-결과를-만든다-x">1. 컴파일러는 고급 언어로 작성된 프로그램을 읽고 바로 실행 결과를 만든다. <code>X</code></h3>
<p>컴파일러는 원시 프로그램을 목적 프로그램으로 번역한다. 프로그램을 읽고 동작을 수행하여 결과를 만드는 것은 인터프리터의 설명에 가깝다.</p>
<h3 id="2-lexical-analyzer는-scanner라고도-한다-o">2. Lexical Analyzer는 Scanner라고도 한다. <code>O</code></h3>
<p>원시 프로그램의 문자 스트림을 Token의 연속으로 바꾼다.</p>
<h3 id="3-scanner는-token의-문법-구조가-올바른지-검사한다-x">3. Scanner는 Token의 문법 구조가 올바른지 검사한다. <code>X</code></h3>
<p>Token을 구분하는 것은 Scanner이고, 문법 구조를 검사하는 것은 Parser다.</p>
<h3 id="4-syntax-analyzer는-parser라고도-한다-o">4. Syntax Analyzer는 Parser라고도 한다. <code>O</code></h3>
<p>Token의 연속을 입력받아 문법을 검사하고 Tree 구조를 만든다.</p>
<h3 id="5-parser는-문법이-올바른-프로그램의-구조를-tree-형태로-출력할-수-있다-o">5. Parser는 문법이 올바른 프로그램의 구조를 Tree 형태로 출력할 수 있다. <code>O</code></h3>
<p>문법이 잘못되었다면 Error Message를 출력한다.</p>
<h3 id="6-문법적으로-올바른-프로그램에는-semantic-error가-존재할-수-없다-x">6. 문법적으로 올바른 프로그램에는 Semantic Error가 존재할 수 없다. <code>X</code></h3>
<p>문법은 맞지만 타입, 선언, 유효 범위 같은 의미 규칙이 잘못될 수 있다.</p>
<h3 id="7-중간-코드는-특정-기계에-반드시-의존한다-x">7. 중간 코드는 특정 기계에 반드시 의존한다. <code>X</code></h3>
<p>강의 자료에서는 특정 기계에 직접 의존하지 않으면서 기계어로 변환하기 쉬운 코드로 설명한다.</p>
<h3 id="8-code-optimization은-반드시-수행해야-하는-필수-단계다-x">8. Code Optimization은 반드시 수행해야 하는 필수 단계다. <code>X</code></h3>
<p>강의 자료에서는 Optional Phase라고 설명한다.</p>
<h3 id="9-최적화는-프로그램의-의미를-바꾸는-과정이다-x">9. 최적화는 프로그램의 의미를 바꾸는 과정이다. <code>X</code></h3>
<p>프로그램의 의미는 유지하면서 더 효율적인 코드로 바꾸는 과정이다.</p>
<h3 id="10-front-end는-언어-의존적이고-back-end는-기계-의존적이다-o">10. Front-End는 언어 의존적이고 Back-End는 기계 의존적이다. <code>O</code></h3>
<p>중간 코드가 두 부분을 연결한다.</p>
<h3 id="11-c의-include와-define은-전처리-지시문이다-o">11. C의 <code>#include</code>와 <code>#define</code>은 전처리 지시문이다. <code>O</code></h3>
<p>컴파일러가 원시 프로그램을 분석하기 전에 Preprocessor가 처리한다.</p>
<h3 id="12-java-compiler는-원시-프로그램을-운영체제별-기계어로-바로-번역한다-x">12. Java Compiler는 원시 프로그램을 운영체제별 기계어로 바로 번역한다. <code>X</code></h3>
<p>Java Compiler는 Bytecode를 생성하고 운영체제별 JVM이 이를 실행한다.</p>
<h3 id="13-lex는-grammar-rule을-입력받아-parser를-생성한다-x">13. LEX는 Grammar Rule을 입력받아 Parser를 생성한다. <code>X</code></h3>
<p>LEX는 Regular Expression과 Action Code를 사용하여 Lexical Analyzer를 만든다.</p>
<h3 id="14-yacc는-구문-분석기-생성-도구다-o">14. YACC는 구문 분석기 생성 도구다. <code>O</code></h3>
<p>Grammar Rule과 Action Code를 사용하여 Parser 소스를 생성한다.</p>
<h3 id="15-stanford-pgs-wisconsin-pgs-yacc는-parse-tree의-세-종류다-x">15. Stanford PGS, Wisconsin PGS, YACC는 Parse Tree의 세 종류다. <code>X</code></h3>
<p>세 가지는 강의 자료에서 소개한 Parser Generator의 예시다.</p>
<h3 id="16-모호한-문법에서는-하나의-문장에-둘-이상의-parse-tree가-만들어질-수-있다-o">16. 모호한 문법에서는 하나의 문장에 둘 이상의 Parse Tree가 만들어질 수 있다. <code>O</code></h3>
<p>다만 모든 프로그래밍 언어의 문법이 반드시 모호하다는 뜻은 아니다.</p>
<hr>
<h2 id="정리">정리</h2>
<p>처음에는 컴파일러를 다음 정도로만 이해하고 있었다.</p>
<pre><code class="language-text">컴파일러
=
소스 코드를 기계어로 바꾸는 프로그램</code></pre>
<p>그런데 수업 내용을 다시 따라가 보니 <code>바꾼다</code>는 한 단어 안에 여러 작업이 들어 있었다.</p>
<pre><code class="language-text">문자를 Token으로 나누고
↓
Token의 문법 구조를 만들고
↓
의미가 올바른지 확인하고
↓
기계와 독립적인 중간 코드로 바꾸고
↓
불필요한 코드를 최적화하고
↓
목표 기계가 이해할 수 있는 코드를 생성한다</code></pre>
<p>개인적으로 이번에 가장 크게 정리된 부분은 Scanner와 Parser의 차이였다.</p>
<pre><code class="language-text">Scanner → 문자에서 Token을 찾는다
Parser  → Token에서 문법 구조를 찾는다</code></pre>
<p>그리고 <code>Parse Tree가 세 종류인가?</code>라는 메모도 다시 확인하면서, 실제로는 Parser Generator 세 가지를 잘못 연결해 적어둔 것이라는 걸 알게 되었다.</p>
<p>마지막으로 시험 전에 이것만은 기억해두자.</p>
<pre><code class="language-text">문자 → Token → Tree → Intermediate Code → Optimized Code → Object Code

LEX  → Regular Expression → Scanner → lex.yy.c
YACC → Grammar Rule       → Parser  → y.tab.c</code></pre>
<p>처음에는 단계 이름을 각각 외우려고 했다.</p>
<p>그런데 입력과 출력의 흐름으로 연결하고 나니 왜 단계가 나뉘는지도 조금 보이기 시작했다.</p>
<p>이번 중간고사에서는 제발 OX 문장에 낚이지 말자...!</p>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><code>chapter1_intro_PL and Complier</code> 강의 자료</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[달려보자 러닝 프로젝트 만들기 (1) - 러닝을 조금 더 재미있게 만들 수 없을까?]]></title>
            <link>https://velog.io/@daehyun_lee/%EB%8B%AC%EB%A0%A4%EB%B3%B4%EC%9E%90-%EB%9F%AC%EB%8B%9D-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EB%9F%AC%EB%8B%9D%EC%9D%84-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%9E%AC%EB%AF%B8%EC%9E%88%EA%B2%8C-%EB%A7%8C%EB%93%A4-%EC%88%98-%EC%97%86%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@daehyun_lee/%EB%8B%AC%EB%A0%A4%EB%B3%B4%EC%9E%90-%EB%9F%AC%EB%8B%9D-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EB%9F%AC%EB%8B%9D%EC%9D%84-%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%9E%AC%EB%AF%B8%EC%9E%88%EA%B2%8C-%EB%A7%8C%EB%93%A4-%EC%88%98-%EC%97%86%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Sun, 13 Sep 2026 11:57:32 GMT</pubDate>
            <description><![CDATA[<h1 id="달려보자-러닝-프로젝트-만들기-1---러닝을-조금-더-재미있게-만들-수-없을까">달려보자 러닝 프로젝트 만들기 (1) - 러닝을 조금 더 재미있게 만들 수 없을까?</h1>
<p>러닝을 하다 보면 실제로 가장 어려운 순간은 달리는 중이 아닐 때가 있다.</p>
<p>운동화를 신고 밖으로 나가기 전이다.</p>
<p>한번 달리기 시작하면 정해둔 거리를 어떻게든 채우게 된다.</p>
<p>그런데 시작하기 전에는 핑계가 정말 많아진다.</p>
<pre><code class="language-text">오늘은 조금 피곤한데…

내일부터 다시 달릴까?

하루 정도 쉬어도 괜찮지 않을까?</code></pre>
<p>나도 러닝을 계속하면서 결국 꾸준함을 만드는 것은 대단한 의지보다 <strong>오늘 한 번 더 밖으로 나갈 이유</strong>라는 생각을 하게 되었다.</p>
<p>어떤 날은 이전 기록을 넘고 싶어서 달렸고, 어떤 날은 정해둔 목표를 지키기 위해 달렸다.</p>
<p>그러다 문득 이런 생각이 들었다.</p>
<blockquote>
<p><strong>“혼자 만드는 동기보다 친구가 먼저 달렸다는 알림 하나가 더 강력할 때도 있지 않을까?”</strong></p>
</blockquote>
<p>친구와 가볍게 기록을 겨루고, 새로운 러닝 코스를 발견하고, 다음 마라톤 목표까지 찾을 수 있다면 달리기를 시작할 이유가 하나 더 생길 것 같았다.</p>
<p>단순히 달린 결과를 저장하는 것이 아니라 다음 러닝을 시작하게 만드는 서비스.</p>
<p>그래서 새로운 러닝 프로젝트를 생각하게 되었다.</p>
<h1 id="달려보자"><strong>달려보자</strong></h1>
<p>이름 그대로 일단 한 번 달려보자는 의미다.</p>
<p>이번 글에서는 아직 완성된 기능을 설명하려는 것이 아니다.</p>
<p>어떤 러닝 경험을 만들고 싶은지, 지금 떠올린 아이디어부터 정리해보려고 한다.</p>
<hr>
<h2 id="처음에는-러닝-코스-추천부터-생각했다">처음에는 러닝 코스 추천부터 생각했다</h2>
<p>익숙한 동네에서 달릴 때는 자주 이용하는 코스가 있다.</p>
<p>하지만 다른 지역에 가면 어디서 뛰어야 할지 알기 어렵다.</p>
<p>여행지에서도 아침이나 저녁에 러닝을 하는 사람들이 있다.</p>
<p>그런데 지도만 보고 달릴 장소를 정하려면 생각보다 확인해야 할 것이 많다.</p>
<pre><code class="language-text">이 길은 실제로 달릴 수 있는 길인가?

차량이 많이 다니지는 않는가?

경사가 너무 심하지는 않은가?

한 바퀴를 돌면 거리가 얼마나 되는가?

밤에도 달릴 수 있을 만큼 밝은가?</code></pre>
<p>단순히 지도 위에 경로 하나만 표시한다고 끝나는 문제가 아니었다.</p>
<blockquote>
<p><strong>“러닝 코스를 일정한 기준으로 정리해서 보여주면 어떨까?”</strong></p>
</blockquote>
<p>예를 들어 러닝 코스를 다음과 같은 형태로 정리할 수 있다.</p>
<pre><code class="language-text">지역

코스 이름

출발 지점과 도착 지점

전체 거리

평균 경사와 난이도

도로 형태

화장실·음수대·주차장 등의 편의시설

야간 러닝 가능 여부

실제로 달린 사람들의 후기</code></pre>
<p>이렇게 코스를 정형화하면 사용자는 자신에게 맞는 코스를 조건으로 검색할 수 있다.</p>
<pre><code class="language-text">전북 지역
        ↓
5km 이하
        ↓
초보자 코스
        ↓
야간 러닝 가능</code></pre>
<p>평소에는 집 근처 코스를 찾고, 여행 중에는 현재 위치 주변의 코스를 찾을 수 있다.</p>
<p>단순한 지도 검색이 아니라 <strong>달리는 사람의 관점에서 정리된 코스 정보</strong>를 제공하는 것이다.</p>
<hr>
<h2 id="기록이-쌓이면-순위도-만들-수-있지-않을까">기록이 쌓이면 순위도 만들 수 있지 않을까?</h2>
<p>러닝 코스를 찾았다면 실제로 달린 기록도 남길 수 있다.</p>
<p>거리, 시간, 평균 Pace 같은 기록이 쌓이면 자연스럽게 순위표도 생각할 수 있다.</p>
<pre><code class="language-text">이번 주 5km 기록

특정 코스의 최고 기록

친구끼리 비교한 기록

지역별 누적 거리</code></pre>
<p>순위표는 사람의 경쟁심을 자극한다.</p>
<p>어제의 내 기록을 넘는 것도 재미있지만, 친구가 내 기록보다 조금 빠르면 괜히 한 번 더 뛰고 싶어질 것 같다.</p>
<p>ㅋㅋㅋㅋ</p>
<p>다만 모든 기록을 하나의 순위표에 넣으면 공정하게 비교하기 어렵다.</p>
<p>같은 5km라도 평지와 오르막 코스는 난이도가 다르다.</p>
<p>신호등이 많은 도심과 멈출 필요가 없는 트랙도 기록 조건이 다르다.</p>
<p>그래서 순위표는 비교 기준을 나누어야 한다.</p>
<pre><code class="language-text">같은 코스 안에서 비교

같은 거리 안에서 비교

친구끼리만 비교

주간·월간 기록으로 구분</code></pre>
<p>중요한 것은 무조건 빠른 사람만 보여주는 것이 아니다.</p>
<p>처음 달리기를 시작한 사람도 참여할 이유가 있어야 한다.</p>
<p>최고 Pace뿐 아니라 누적 거리, 꾸준히 달린 횟수, 이전 기록 대비 향상 정도처럼 여러 기준을 사용할 수도 있다.</p>
<p>그런데 여기까지 생각해보니 순위표만으로는 조금 아쉬웠다.</p>
<p>순위표는 이미 기록을 등록한 사람들을 비교한다.</p>
<p>그렇다면 달리기 전에 먼저 경쟁을 시작할 수는 없을까?</p>
<hr>
<h2 id="나-대-너-오늘-하루-러닝으로-대결하기">나 대 너: 오늘 하루 러닝으로 대결하기</h2>
<p>이 프로젝트에서 가장 해보고 싶은 기능은 <code>나 대 너</code>다.</p>
<p>친구 한 명을 선택하고, 달릴 거리를 정해서 오늘 하루 동안 대결하는 기능이다.</p>
<p>예를 들어 내가 친구에게 5km 대결을 신청한다고 생각해보자.</p>
<pre><code class="language-text">상대방 선택
        ↓
대결 거리 5km 선택
        ↓
오늘의 대결 시작
        ↓
상대방에게 알림 전송</code></pre>
<p>상대방에게는 다음과 같은 알림이 간다.</p>
<blockquote>
<p><strong>대현님이 5km 대결을 시작했습니다! 오늘도 화이팅🏃</strong></p>
</blockquote>
<p>꼭 같은 시간과 장소에서 달릴 필요는 없다.</p>
<p>하루 안에 각자 편한 시간에 정해진 거리를 달리면 된다.</p>
<pre><code class="language-text">[대현]                                  [친구]
  │                                       │
  │────── 5km 대결 시작 / 알림 ──────────&gt;│
  │                                       │
  │       각자 원하는 시간에 러닝          │
  │                                       │
  │────── 기록 등록 / 기록 알림 ─────────&gt;│
  │                                       │
  │&lt;────── 친구 기록 등록 / 알림 ─────────│
  │                                       │
  │&lt;────────── 최종 기록 비교 ───────────&gt;│
  │                                       │</code></pre>
<p>한 사람이 먼저 기록을 등록하면 상대방에게 다시 알림을 보낸다.</p>
<blockquote>
<p><strong>대현님이 5km 러닝을 완료했습니다. 이제 당신의 차례입니다!</strong></p>
</blockquote>
<p>이 알림을 받으면 조금 쉬고 싶다가도 괜히 운동화를 신게 될 것 같다.</p>
<p>바로 이런 느낌을 만들고 싶었다.</p>
<h2 id="무엇을-기준으로-승자를-정할까">무엇을 기준으로 승자를 정할까?</h2>
<p>대결 거리가 같다면 가장 단순한 기준은 완주 시간이다.</p>
<pre><code class="language-text">대결 거리: 5km

A 기록: 27분 10초
B 기록: 29분 03초

승자: A</code></pre>
<p>평균 Pace로 표시할 수도 있지만, 정해진 거리가 같다면 완주 시간이 더 직관적이다.</p>
<p>그런데 여기서 또 문제가 있다.</p>
<blockquote>
<p><strong>“서로 다른 코스를 달렸는데 시간만 비교해도 공정할까?”</strong></p>
</blockquote>
<p>완전히 공정하다고 하기는 어렵다.</p>
<p>한 사람은 평지에서 달리고 다른 사람은 오르막이 많은 길을 달릴 수도 있다.</p>
<p>그래서 대결 방식은 두 가지로 나누어볼 수 있다.</p>
<pre><code class="language-text">자유 코스 대결
→ 같은 거리만 맞추고 각자 원하는 장소에서 달린다.
→ 가볍게 동기를 얻는 것이 목적이다.

지정 코스 대결
→ 같은 러닝 코스를 달린 기록끼리 비교한다.
→ 조금 더 공정하게 기록을 비교할 수 있다.</code></pre>
<p>처음부터 완벽한 스포츠 경기처럼 만들 필요는 없다고 생각했다.</p>
<p><code>나 대 너</code>의 첫 번째 목적은 누가 더 뛰어난지 증명하는 것이 아니라 <strong>오늘 달릴 이유를 서로 만들어주는 것</strong>이기 때문이다.</p>
<hr>
<h2 id="대결은-어떤-상태를-가질까">대결은 어떤 상태를 가질까?</h2>
<p>기능을 조금 더 생각해보니 대결도 시작과 종료만 있는 것은 아니었다.</p>
<pre><code class="language-text">대결 생성
    ↓
상대방에게 알림
    ↓
진행 중
    ├─ 한 명만 기록 등록
    ├─ 두 명 모두 기록 등록
    └─ 제한 시간 종료</code></pre>
<p>두 사람 모두 기록을 등록했다면 바로 결과를 계산할 수 있다.</p>
<p>한 사람만 달렸다면 제한 시간이 끝날 때까지 상대방의 기록을 기다려야 한다.</p>
<p>아무도 달리지 않았다면 승패 없이 종료할 수도 있다.</p>
<p>여기서 아직 정하지 못한 부분도 있다.</p>
<pre><code class="language-text">대결 신청을 상대방이 수락해야 시작할 것인가?

하루의 기준은 신청 시점부터 24시간인가, 그날 자정까지인가?

정해진 거리보다 조금 더 달렸다면 어떤 구간을 기록으로 사용할 것인가?

GPS 오차는 어느 정도까지 인정할 것인가?

중간에 멈춘 시간은 기록에 포함할 것인가?</code></pre>
<p>처음에는 단순하게 <code>5km를 달리고 시간이 빠른 사람이 승리</code>라고 생각했다.</p>
<p>그런데 실제 기능으로 만들려고 하니 정해야 할 규칙이 많았다.</p>
<p>이 부분은 앞으로 요구사항과 도메인을 정리하면서 하나씩 결정해보려고 한다.</p>
<hr>
<h2 id="마라톤-대회도-쉽게-찾을-수-있게">마라톤 대회도 쉽게 찾을 수 있게</h2>
<p>러닝을 계속하다 보면 자연스럽게 마라톤 대회에도 관심이 생긴다.</p>
<p>하지만 대회 정보는 여러 사이트와 커뮤니티에 흩어져 있다.</p>
<p>지역과 날짜, 종목을 하나씩 확인해야 해서 원하는 대회를 찾기 번거로울 수 있다.</p>
<p>달려보자에서는 마라톤 대회도 다음과 같은 기준으로 모아보고 싶다.</p>
<pre><code class="language-text">개최 지역

대회 날짜

접수 기간

5km / 10km / Half / Full Course

참가 비용

공식 접수 페이지</code></pre>
<p>예를 들어 사용자가 전북 지역과 10km를 선택하면 참가할 수 있는 대회를 한 번에 확인하는 방식이다.</p>
<pre><code class="language-text">지역 선택
        ↓
거리 선택
        ↓
접수 중인 대회 확인
        ↓
공식 신청 페이지로 이동</code></pre>
<p>처음부터 서비스 안에서 결제와 참가 신청까지 모두 처리하기보다는, 필요한 정보를 정리하고 공식 접수 페이지로 연결하는 것부터 시작하는 편이 현실적일 것 같다.</p>
<hr>
<h2 id="지금까지-생각한-달려보자의-모습">지금까지 생각한 달려보자의 모습</h2>
<p>현재 생각한 기능을 모아보면 다음과 같다.</p>
<pre><code class="language-text">달려보자
│
├─ 러닝 코스
│   ├─ 지역별 코스 탐색
│   ├─ 거리·난이도별 검색
│   ├─ 코스 정보 정형화
│   └─ 후기와 기록
│
├─ 러닝 기록
│   ├─ 거리
│   ├─ 시간
│   ├─ 평균 Pace
│   └─ GPS 경로
│
├─ 순위표
│   ├─ 코스별 순위
│   ├─ 거리별 순위
│   ├─ 친구 순위
│   └─ 주간·월간 순위
│
├─ 나 대 너
│   ├─ 상대방 선택
│   ├─ 대결 거리 설정
│   ├─ 대결 시작 알림
│   ├─ 기록 등록 알림
│   └─ 결과 비교
│
└─ 마라톤 대회
    ├─ 지역·날짜·거리 검색
    ├─ 접수 기간 확인
    └─ 공식 접수 페이지 연결</code></pre>
<p>기능은 여러 개지만 결국 하나의 흐름으로 연결된다.</p>
<pre><code class="language-text">달릴 코스를 찾는다.
        ↓
러닝 기록을 남긴다.
        ↓
내 기록과 다른 사람의 기록을 비교한다.
        ↓
친구와 하루 대결을 시작한다.
        ↓
마라톤이라는 다음 목표를 찾는다.</code></pre>
<p>단순한 러닝 기록 저장 앱보다는 <strong>달리고 싶은 이유를 계속 만들어주는 서비스</strong>에 가깝다.</p>
<hr>
<h2 id="그렇다면-전부-한-번에-만들-수-있을까">그렇다면 전부 한 번에 만들 수 있을까?</h2>
<p>당연히 어렵다.</p>
<p>코스 추천, GPS 기록, 순위표, 알림, 친구 관계, 마라톤 정보까지 한 번에 구현하려고 하면 프로젝트의 범위가 너무 커진다.</p>
<p>그래서 첫 번째 버전에서는 달려보자만의 특징이 가장 잘 드러나는 기능에 집중하려고 한다.</p>
<p>현재 생각한 Minimum Viable Product(MVP), 즉 첫 번째 버전의 중심은 <code>나 대 너</code>다.</p>
<pre><code class="language-text">사용자 등록
        ↓
친구 선택
        ↓
대결 거리 설정
        ↓
대결 시작과 알림
        ↓
러닝 기록 등록
        ↓
두 기록 비교
        ↓
대결 결과 확인</code></pre>
<p>다만 이것을 구현하려면 먼저 러닝 기록을 어떤 방식으로 받을지 결정해야 한다.</p>
<pre><code class="language-text">서비스가 GPS를 직접 측정할 것인가?

사용자가 기록을 직접 입력할 것인가?

기존 러닝 서비스의 기록을 가져올 것인가?</code></pre>
<p>직접 입력만 허용하면 구현은 단순하지만 기록을 믿기 어렵다.</p>
<p>GPS를 직접 측정하면 더 정확한 경험을 제공할 수 있지만 모바일 위치 권한, 백그라운드 위치 추적, 배터리 사용량까지 고려해야 한다.</p>
<p>기존 서비스와 연동하려면 외부 API 제공 여부와 데이터 사용 범위를 확인해야 한다.</p>
<p>즉 MVP를 정하려면 단순히 기능을 줄이는 것뿐 아니라, 핵심 경험을 만들기 위한 최소한의 기술 범위도 함께 확인해야 한다.</p>
<hr>
<h2 id="앞으로-확인해야-할-것">앞으로 확인해야 할 것</h2>
<p>아이디어를 글로 정리하고 나니 만들고 싶은 모습은 조금 더 명확해졌다.</p>
<p>동시에 모르는 것도 많아졌다.</p>
<pre><code class="language-text">러닝 코스는 누가 등록하고 검증할 것인가?

사용자의 GPS 경로와 위치 정보는 어디까지 공개할 것인가?

실제 거리와 측정 거리의 오차를 어떻게 처리할 것인가?

서로 다른 코스의 기록을 어떤 기준으로 비교할 것인가?

대결 중 들어오는 기록을 어떻게 검증할 것인가?

알림을 너무 많이 보내지 않으면서 경쟁심을 어떻게 유지할 것인가?

마라톤 대회 정보는 어디서 가져올 것인가?</code></pre>
<p>처음에는 코스 추천과 순위표, 대결 기능을 넣으면 재미있는 러닝 서비스가 될 것 같다는 생각에서 시작했다.</p>
<p>그런데 실제로 만들려고 하니 기록의 신뢰성, 위치 정보, 대결 규칙처럼 먼저 결정해야 할 문제가 보였다.</p>
<p>아직은 아이디어 단계다.</p>
<p>따라서 지금 떠올린 모든 기능을 바로 구현 대상으로 확정하지는 않으려고 한다.</p>
<p>다음에는 이 아이디어를 사용자 관점에서 다시 살펴보면서 달려보자에 정말 필요한 요구사항을 정리해볼 예정이다.</p>
<hr>
<h2 id="정리">정리</h2>
<p>달려보자를 생각한 출발점은 단순했다.</p>
<blockquote>
<p><strong>“혼자 남기던 러닝 기록을 다른 사람과 함께 달리는 계기로 만들 수 없을까?”</strong></p>
</blockquote>
<p>코스 추천은 새로운 장소에서 달릴 수 있게 도와준다.</p>
<p>순위표는 기록을 다시 확인할 이유를 만든다.</p>
<p>마라톤 정보는 다음 목표를 찾게 해준다.</p>
<p>그리고 <code>나 대 너</code>는 오늘 당장 밖으로 나가 달릴 이유를 만든다.</p>
<pre><code class="language-text">기록을 저장하는 서비스
        ↓
달릴 이유를 만들어주는 서비스</code></pre>
<p>이것이 현재 생각하고 있는 달려보자의 방향이다.</p>
<p>물론 앞으로 요구사항을 정리하고 실제 구현 가능성을 확인하면 지금의 기능은 달라질 수 있다.</p>
<p>나에게도 기록 그 자체보다 다시 밖으로 나가게 만드는 계기가 더 중요했다.</p>
<p>달려보자가 누군가를 대신해 달려줄 수는 없다.</p>
<p>하지만 친구의 기록과 짧은 알림 하나로 밖에 나갈 이유를 만들어줄 수는 있다.</p>
<p>그래도 프로젝트를 시작하는 지금 가장 놓치고 싶지 않은 것은 하나다.</p>
<p>누군가에게 대결 알림이 도착했을 때,</p>
<pre><code class="language-text">“아 귀찮은데…”</code></pre>
<p>라고 생각하면서도 결국 운동화를 신게 만드는 것.</p>
<p>그런 서비스라면 꽤 재미있지 않을까?</p>
]]></description>
        </item>
    </channel>
</rss>