<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>밝은별 개발 블로그</title>
    <link>https://brightstarit.tistory.com/</link>
    <description>호기심 가득한 밤을 하나씩 밝히는 밝은별입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 07:03:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>밝은별 개발자</managingEditor>
    <image>
      <title>밝은별 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/6289454/attach/f3ab267a046f431fa3f4a53d1e311e2d</url>
      <link>https://brightstarit.tistory.com</link>
    </image>
    <item>
      <title>오브젝트 - 조영호, 6장</title>
      <link>https://brightstarit.tistory.com/70</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 수신하는 메시지들이 객체의 퍼블릭 인터페이스를 구성한다.&lt;br /&gt;훌륭한 퍼블릭 인터페이스를 얻기 위해서는 책임 주도 설계 바업을 따르는 것만으로는 부족하다.&lt;br /&gt;유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 익히고 적용해야 한다.&lt;br /&gt;이런 원칙과 기법들을 살펴보는 거이 이번 장의 주제다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라이언트-서버 모델&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 객체 사이의 협력 관계를 설명하기 위해 사용하는 전통적인 메타포는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클라이언트-서버(Client-Server)&lt;/span&gt;모델이다.&lt;br /&gt;협력 안에서 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라고 부른다. 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메시지와 메시지 전송&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지(message)&lt;/span&gt;는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다.&lt;br /&gt;한 객체가 다른 객체에게 도움을 요청하는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지 전송(message sending)&lt;/span&gt;또는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지 패싱(message passing)&lt;/span&gt;이라고 부른다. 이때 메시지를 전송하는 객체를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지 전송자(message sender)&lt;/span&gt;라고 부르고 메시지를 수신하는 객체를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지 수신자(message receiver)&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지는 오퍼레이션명(operation name)과 인자(argument)로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다. 따라서 메시지 전송은 메시지 수신자, 오퍼레이션명, 인자의 조합이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메시지와 메서드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 수신했을 때 실행되는 함수 또는 프로시저를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메서드&lt;/span&gt;라고 부른다.&lt;br /&gt;중요한 것은 코드 상에서 동일한 이름의 변수(condition)에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다는 것이다. 기술적인 관점에서 객체 사이의 메시지 전송은 전통적인 방식의 함수 호출이나 프로시저 호출과는 다르다.&lt;br /&gt;전통적인 방식의 개발자는 어떤 코드가 실행될지를 정확하게 알고 있는 상황에서 함수 호출이나 프로시저 호출 구문을 작성한다.&lt;br /&gt;다시 말해 코드의 의미가 컴파일 시점과 실행 시점에 동일하다는 것이다. 반면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체는 메시지와 메서드라는 두 까지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 전송자와 메시지 수신자는 서로에 대한 상세한 정보를 알지 못한 채 단지 메시지라는 얆고 가는 끈을 통해 연결된다. 실행 시점에 메시지와 메서드를 바인딩하는 메커니즘은 두 객체 사이의 결함도를 낮춤으로써 유연하고 확장 가능한 코드를 작성할 수 있게 만든다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;퍼블릭 인터페이스와 오퍼레이션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체는 안과 밖을 구분하는 뚜렷한 경계를 가진다. 외부에서 볼 때 객체의 안쪽은 검은 장막으로 가려진 미지의 영역이다.&lt;br /&gt;외부의 객체는 오직 객체가 공개하는 메시지를 통해서만 객체와 상호작용할 수 있다. 이처럼 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;퍼블릭 인터페이스&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 언어의 관점에서 퍼블릭 인터페이스에 포함된 메시지를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;오퍼레이션(operation)&lt;/span&gt;이라고 부른다.&lt;br /&gt;오퍼레이션은 수행 가능한 어떤 행동에 대한 추상화다. 흔히 오퍼레이션이라고 부를 때는 내부의 구현 코드는 제외하고 단순히 메시지와 관련된 시그니처를 가리키는 경우가 대부분이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시그니처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합쳐 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;시그니처(signature)&lt;/span&gt;라고 부른다.&lt;br /&gt;오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이다. 메서드는 이 시그니처에 구현을 더한 것이다.&lt;br /&gt;일반적으로 메시지를 수신하면 오퍼레이션의 시그니처와 동일한 메서드가 실행된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디미터 법칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협력하는 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이 바로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;디미터 법칙(Law Of Demeter)&lt;/span&gt;이다.&lt;br /&gt;디미터 법칙을 간단하게 요약하면 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라는 것이다.&lt;br /&gt;디미터 법칙은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;ldquo;낯선 자에게 말하지 말아라&amp;ldquo;&lt;/b&gt; &lt;/span&gt;또는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;ldquo;오직 인접한 이웃하고만 말하라&amp;rdquo;&lt;/span&gt;&lt;/b&gt;로 요약할 수 있다.&lt;br /&gt;자바나 C#과 같이 &amp;lsquo;도트(.)&amp;rsquo;를 이용해 메시지 전송을 표현하는 언어에서는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;ldquo;오직 하나의 도트만 사용하라&amp;rdquo;&lt;/span&gt;&lt;/b&gt;라는 말로 요약되기도 한다.&lt;br /&gt;디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍해야 한다.&lt;br /&gt;모든 클래스 C와 C에 구현된 모든 메서드 M에 대해서, M이 메시지를 전송할 수 있는 모든 객체는 다음에 서술된 클래스의 인스턴스여야 한다. 이때 M에 의해 생성된 객체나 M이 호출하는 메서드에 의해 성성된 객체, 전역 변수로 선언된 객체는 모두 M의 인자로 간주한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;M의 인자로 전달된 클래스(C 자체를 포함)&lt;/li&gt;
&lt;li&gt;C의 인스턴스 변수의 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명이 이해하기 어렵다면 클래스 내부의 메서드가 아래 조건을 만족하는 인스턴스에만 메시지를 전송하도록 프로그래밍해야 한다고 이해해도 무방하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;this 객체&lt;/li&gt;
&lt;li&gt;메서드의 매개변수&lt;/li&gt;
&lt;li&gt;this의 속성&lt;/li&gt;
&lt;li&gt;this의 속성인 컬렉션의 요소&lt;/li&gt;
&lt;li&gt;메서드 내에서 생성된 지역 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디미터 법칙을 따르면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;부끄럼타는 코드(shy code)&lt;/span&gt;를 작성할 수 있다.&lt;br /&gt;부끄럼 타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며, 다른 객체의 구현에 의존하지 않는 코드를 말한다.&lt;br /&gt;디미터 법칙을 따르는 코드는 메시지 수신자의 내부 구조가 전송자에게 노출되지 않으며, 메시지 전송자는 수신자의 내부 구현에 결합되지 않는다. 따라서 서버 사이의 낮은 결합도를 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 전송자가 수신자의 내부 구조에 대해 물어보고 반환반은 요소에 대해 연쇄적으로 메시지를 전송한다.&lt;br /&gt;흔히 이와 같은 코드를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;기차 충돌(train wreck)&lt;/span&gt;이라고 부르는데 여러 대의 기차가 한줄로 늘어서 충돌한 것처럼 보이기 때문이다. 기차 충돌은 클래스의 내부 구현이 외부로 노출됐을때 나타나는 전형적인 형태로 메시지 전송자는 메시지 수신자의 내부 정보를 자세히 알게 된다. 따라서 메시지 수신자의 캡슐화는 무너지고, 메시지 전송자가 메시지 수신자의 내부 구현에 강하게 결합된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;묻지말고 시켜라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디미터 법칙은 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원한느 것을 시켜야 한다는 사실을 강조한다.&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;묻지말고 시켜라(Tell, Don&amp;rsquo;t Ask)&lt;/span&gt;는 이런 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 대체함으로써 인터페이스를 향상시켜라.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;협력을 설계하고 객체가 수신할 메시지를 결정하는 매 순간 묻지 말고 시켜라 원칙과 디미터 법칙을 머릿속에 떠올리는 것은 퍼블릭 인터페이스의 품질을 향상시킬 수 있는 좋은 습관이다.&lt;br /&gt;하지만 단순하게 객체에게 묻지 않고 시킨다고 해서 모든 문제가 해결되는 것은 아니다. 훌륭한 인터페이스를 수확하기 위해서는 객체가 어떻게 작업을 수행하는지를 노출해서는 안 된다. 인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의도를 드러내는 인터페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;켄트 벡(Kent Beck)은 그의 기념비적인 책인 Smalltalk Best Practice Patterns에서 메서드를 명명하는 두 가지 방법을 설명했다.&lt;br /&gt;첫 번째 방법은 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름 짓는 것이다.&lt;br /&gt;메서드의 이름을 짓는 두 번째 방법은 &amp;lsquo;어떻게&amp;rsquo;가 아니라 &amp;lsquo;무엇&amp;rsquo;을 하는지를 드러내는 것이다.&lt;br /&gt;메서드의 구현이 한 가지인 경우에는 무엇을 하는지를 드러내는 이름을 짓는 것이 어려울 수도 있다.&lt;br /&gt;하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무엇을 하는지를 드러내는 이름은 코드를 읽고 이해하기 쉽게 만들뿐만 아니라 유연한 코드를 낳는 지름길이다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;이처럼 어떻게 하느냐가 아니라 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의&lt;span style=&quot;background-color: #f6e199;&quot;&gt;도를 드러내는 선택자(Intention Revealing Selector)&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;명령-쿼리 분리 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔은 필요에 따라 물어야 한다는 사실에 납득했다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;명령-쿼리 분리(Command-Qeury Separation) 원칙&lt;/span&gt;을 알아두면 도움이 될 것이다. 명령-쿼리 분리 원칙은 퍼블릭 인터페이스에 오퍼레이션을 정의할 때 참고할 수 있는 지침을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;모듈을 루틴(routine)&lt;/span&gt;이라고 부른다. 루틴은 다시 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;프로시저(procedure)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;함수(function)&lt;/span&gt;로 구분할 수 있다. 프로시저와 함수를 같은 의미로 혼용하는 경우가 많지만 사실 프로시저와 함수는 부수효과와 반환관 유무라는 측면에서 명확하게 구분된다.&lt;br /&gt;프로시저와 함수를 명확하게 구분하기 위해 루틴을 작성할 때 다음과 같은 제약을 따라야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로시저는 부수효과를 발생시킬 수 있지만 값을 반환할 수 없다.&lt;/li&gt;
&lt;li&gt;함수는 값을 반환할 수 있지만 부수효과를 발생시킬 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;명령(Command)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;쿼리(Query)&lt;/span&gt;는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다.&lt;br /&gt;객체의 상태를 수정하는 오퍼레이션을 명령이라고 부르고 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다. 따라서 개념적으로 명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.&lt;br /&gt;따라서 명령과 쿼리를 분리하기 위해서는 다음의 두 가지 규칙을 준수해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.&lt;/li&gt;
&lt;li&gt;객체의 정보를 반환하는 쿼리는 상태를 변경할 수없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;느낀점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신입 개발자에서 부터 동료들과 지속적으로 객체지향에 대한 고민과 공부를 하면서 디미터 법칙과 TDA법칙은 익숙했다. 하지만 여태껏 메서드의 이름을 &amp;lsquo;무엇을&amp;rsquo; 하는지가 아니라 &amp;lsquo;어떻게&amp;rsquo;했는지 구현에 의존한 메서드를 많이 작성했던 것 같아서 반성이 되는 부분이였다.&lt;br /&gt;또한 MSA 서비스를 개발하면서 아키텍처 레벨의 CQRS 패턴을 공부하면서도 객체의서의 명령-쿼리 분리 원칙을 생각하지 못했다는 것을 반성하게 되는 부분이였다.&lt;br /&gt;DDD를 통해 개발하면서 도메인의 부수적인 효과(side effect)를 방지하기 위해 도메인은 되도록 불변으로 작성하도록 했던 부분이 생각나는 부분이였다.&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/70</guid>
      <comments>https://brightstarit.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 12 Sep 2025 23:10:21 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트 - 조영호, 5장</title>
      <link>https://brightstarit.tistory.com/69</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책임 할당을 위한 GRASP 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향이 태어나고 성숙해가는 동안 많은 사람들이 다양한 책임 할당 기법을 고안했다.&lt;br /&gt;그중에서 대중적으로 가장 널리 알려진 것은 크레이그 라만(Craig Larman)이 패턴 형식으로 제안한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;GRASP 패턴&lt;/span&gt;이다. GRASP는 &amp;ldquo;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;General Responsibility Assignment Software Pattern(일반적인 책임 할당을 위한 소프트웨어 패턴)&lt;/b&gt;&lt;/span&gt;&amp;rdquo;의 약자로 객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리한 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정보 전문가에게 책임을 할당하라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임 주도 설계의 방식의 첫 단계는 애플리케이션이 제공해야 하는 기능을 애플리케이션의 책임으로 생각하는 것이다.&lt;br /&gt;이 책임을 애플리케이션에 대해 전송된 메시지로 간주하고 이 메시지를 책임질 첫 번째 객체를 선택하는 것으로 설계를 시작한다.&lt;br /&gt;메시지는 메시지를 수신할 객체가 아니라 메시지를 전송할 객체의 의도를 반영해서 결정해야한다.&lt;br /&gt;따라서 첫 번째 질문은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메시지를 전송할 객체는 무엇을 원하는가?&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 결정했으므로 메시지에 적합한 객체를 선택해야 한다. 두 번째 질문은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메시지를 수신할 적합한 객체는 누구인가?&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문에 답하기 위해서는 객체가 상태와 행동을 통합한 캡슐화의 단위라는 사실에 집중해야 한다.&lt;br /&gt;객체는 자신의 상태를 스스로 처리하는 자율적인 존재여야 한다.&lt;br /&gt;객체의 책임과 책임을 수행하는 데 필요한 상태는 동일한 객체 안에 존재해야 한다.&lt;br /&gt;따라서 객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다.&lt;br /&gt;GRASP에서는 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;INFORMATION EXPERT(정보 전문가)&lt;/span&gt; 패턴이라고 부른다.&lt;br /&gt;INFORMATION EXPERT 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현한 것이다. 여기서 이야기하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;정보는 데이터와 다르다는 사실에 주의하라.&lt;/b&gt;&lt;/span&gt; 책임을 수행하는 객체가 정보를 &amp;lsquo;알고&amp;rsquo;있다고 해서 그 정보를 &amp;lsquo;저장&amp;rsquo;하고 있을 필요는 없다. 객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나 필요한 정보를 계산해서 제공할 수도 있다.&lt;br /&gt;어떤 방식이건 정보 전문가가 데이터를 반드시 저장하고 있을 필요는 없다는 사실을 이해하는 것이 중요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;높은 응집도와 낮은 결합도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 응집도와 낮은 결합도는 객체에 책임을 할당할 때 항상 고려해야 하는 기본 원리다.&lt;br /&gt;책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도의 측면에서 더 나은 대안을 선택하는 것이 좋다.&lt;br /&gt;다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다.&lt;br /&gt;GRASP에서는 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;LOW COUPLING(낮은 결합도)&lt;/span&gt; 패턴과 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;HIGH COHESION(높은 응집도)&lt;/span&gt; 패턴이라고 부른다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클래스 응집도 판단하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인스턴스 변수가 초기화되는 시점&lt;/span&gt;을 살펴보는 것이다.&lt;br /&gt;응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다. 방면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨진다.&lt;br /&gt;클래스의 속성이 서로 다른 시점에 초기화되거나 일부만 초기화된다는 것은 응집도가 낮다는 증거다. 따라서 &lt;b&gt;함&lt;span style=&quot;color: #ee2323;&quot;&gt;께 초기화되는 속성을 기준으로 코드를 분리해야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 변경의 이유를 파악할 수 있는 두 번째 방법은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메서드들이 인스턴스 변수를 사용하는 방식&lt;/b&gt;&lt;/span&gt;을 살펴보는 것이다. 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다.&lt;br /&gt;반면 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스가 다음과 같은 징후로 몸살을 앓고 있다면 클래스의 응집도는 낮은 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스가 하나 이상의 이유로 변경돼야 한다면 응집도가 낮은 것이다. 변경의 이유를 기준으로 클래스를 분리하라.&lt;/li&gt;
&lt;li&gt;클래스의 인스턴스를 초기화하는 시점에 경우에 따라 서로 다른 속성들을 초기화하고 있다면 응집도가 낮은 것이다. 초기화되는 속성의 그룹을 기준으로 클래스를 분리하라.&lt;/li&gt;
&lt;li&gt;메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면 응집도가 낮은 것이다. 이들 그룹을 기준으로 클래스를 분리하라.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;일반적으로 응집도가 낮은 클래스는 이 세 가지 문제를 동시에 가지는 경우가 대부분이다. 메서드의 크기가 너무 커서 긴 코드 라인 속에 문제가 숨겨져 명확하게 보이지 않을 수도 있다. 이 경우 긴 메서드를 응집도 높은 작은 메서드로 잘게 분해해 나가면 숨겨져 있던 문제점이 명확하게 드러나는 경우가 많다.창조자에게 객체 생성을 책임을 할당하라GRASP의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;CREATOR(창조자)&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;패턴은 객체를 생성할 책임을 어떤 객체에게 할당할지에 대한 지침을 제공한다.&lt;/span&gt;&lt;br /&gt;객체 A를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가? 아래 조건을 최대한 많이 만족하는 B에게 객체 생성 책임을 할당하라.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;B가 A 객체를 포함하거나 참조한다.&lt;/li&gt;
&lt;li&gt;B가 A 객체를 기록한다.&lt;/li&gt;
&lt;li&gt;B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다. (이 경우 B는 A에 대한 정보 전문가다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CREATOR 패턴의 의도는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;어떤 방식으로든 생성되는 객체와 연결되거나 관련될 필요가 있는 객체에 해당 객체를 생성할 책임을 맡기는 것&lt;/span&gt;이다.&lt;/b&gt;&amp;nbsp;생성될 객체에 대해 잘 알고 있어야 하거나 그 객체를 사용해야 하는 객체는 어떤 방식으로든 생성될 객체와 연결될 것이다. 다시 말해서 두 객체는 서로 결합된다.&lt;br /&gt;이미 결합돼 있는 객체에게 생성 책임을 할당하는 것은 설계의 전체적인 결합도에 영향을 미치지 않는다.&lt;br /&gt;결과적으로 CREATOR 패턴은 이미 존재하는 객체 사이의 관계를 이용하기 때문에 설계가 낮은 결합도를 유지할 수 있게 한다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;다형성을 통해 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라.&lt;br /&gt;GRASP에서는 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;POLYMORPHISM(다형성)&lt;/span&gt; 패턴이라고 부른다.&lt;br /&gt;객체의 타입에 따라 변하는 로직이 있을 때 변하는 로직을 담당할 책임을 어떻게 할당해야 하는가?&lt;br /&gt;타입을 명시적으로 정의하고 각 타입에 다형적으로 행동하는 책임을 할당하라.&lt;br /&gt;조건에 따른 변화는 프로그램의 기본 논리다. 프로그램을&amp;nbsp;if ~ else&amp;nbsp;또는&amp;nbsp;switch ~ case&amp;nbsp;등의 조건 논리를 사용해서 설계한다면 새로운 변화가 일어난 경우 조건 논리를 수정해야 한다. 이것은 프로그램을 수정하기 어렵고 변경에 취약하게 만든다.&lt;br /&gt;POLYMORPHISM 패턴은 객체의 타입을 검사해서 타입에 따라 여러 대안들을 수행하는 조건적인 논리를 사용하지 말라고 경고한다. 대신 다형성을 이용해 새로운 변화를 다루기 쉽게 확장하라고 권고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경으로부터 보호하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;PROTECTED VARIATIONS(변경 보호)&lt;/span&gt; 패턴이라고 부른다.&lt;br /&gt;객체, 서브시스템, 그리고 시스템을 어떻게 설계해야 변화와 불안정성이 다른 요소에 나쁜 영향을 미치지 않도록 방지할 수 있을까?&lt;br /&gt;변화가 예상되는 불안정한 지점들을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하라.&lt;br /&gt;PROTECTED VARIATIONS 패턴은 책임 할당의 관점에서 캡슐화를 설명한 것이다. &amp;ldquo;설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라&amp;rdquo;라는 객체지향의 오랜 격언은 PROTECTED VARIATIONS 패턴의 본질을 잘 설명해준다. 우리가 캡슐화해야 하는 것은 변경이다. 변경이 될 가능성이 높은가? 그러면 캡슐화하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 변경에 따라 분리하고 인터페이스를 이용해 변경을 캡슐화하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다. 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 PROTECTED VARIATIONS 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라. 적절한 상황에서 두 패턴을 조합하면 코드 수정의 파급 효과를 조절할 수 있고 변경과 확장에 유연하게 대처할 수 있는 설계를 얻을 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;내용 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GRASP 원칙을 통해 내가 그동안 작성한 코드에 대한 자가 진단을 할 수 있겠다라는 생각이들었다.&lt;br /&gt;1장부터&amp;nbsp;계속해서&amp;nbsp;강조했던&amp;nbsp;부분들이지만&amp;nbsp;실제로&amp;nbsp;이&amp;nbsp;부분들이&amp;nbsp;잘&amp;nbsp;적용된&amp;nbsp;코드를&amp;nbsp;작성했는지&amp;nbsp;판단하기&amp;nbsp;어려운&amp;nbsp;부분이&amp;nbsp;있었는데&amp;nbsp;이번&amp;nbsp;장에서&amp;nbsp;어느정도&amp;nbsp;판단할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;원칙들을&amp;nbsp;알게&amp;nbsp;된&amp;nbsp;것&amp;nbsp;같아&amp;nbsp;유의미했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/69</guid>
      <comments>https://brightstarit.tistory.com/69#entry69comment</comments>
      <pubDate>Mon, 8 Sep 2025 22:59:23 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트 - 조영호, 4장</title>
      <link>https://brightstarit.tistory.com/68</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결합도와 응집도를 합리적인 수준으로 유지할 수 있는 중요한 원칙이 있다.&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;b&gt;객체의 상태가 아니라 행동에 초점을 맞추는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기 때문에 결과적으로 설계가 변경에 취약해진다. 이런 문제를 피할 수 있는 가장 좋은 방법은 객체의 책임에 초점을 맞추는 것이다. 책임은 객체의 상태에서 행동으로, 나아가 객체와 객체 사이의 상호작용으로 설계 중심을 이동시키고, 결합도가 낮고 응집도가 높으며 구현을 효과적으로 캡슐화하는 객체들을 창조할 수 있는 기반을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 중심의 관점에서 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의한다.&lt;br /&gt;책임 중심의 관점에서 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관한다.&lt;br /&gt;데이터 중심의 관점은 객체의 상태에 초점을 맞추고 책임 중심의 관점은 객체의 행동에 초점을 맞춘다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;전자는 객체를 독립된 데이터 덩어리로 바라보고 후자는 객체를 협력하는 공동체의 일원으로 바라본다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템을 분할하기 위해 데이터와 책임 중 어떤 것을 선택해야 할까? 결론부터 말하자면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;훌륭한 객체지향 설계는 데이터가 아니라 책임에 초점을 맞춰야 한다.&lt;/b&gt; &lt;/span&gt;이유는 변경과 관련이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 상태는 구현에 속한다. 구현은 불안정하기 때문에 변하기 쉽다. 상태를 객체 분할의 중심축으로 삼으면 구현에 관한 세부사항이 객체의 인터페이스에 스며들게 되어 캡슐화의 원칙이 무너진다.&lt;br /&gt;결과적으로 상태 변경은 인터페이스의 변경을 초래하며 이 인터페이스에 의존하는 모든 객체에게 변경의 영향이 퍼지게 된다. 따라서 데이터에 초점을 맞추는 설계는 변경에 취약할 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에 비해 객체의 책임은 인터페이스에 속한다. 객체는 책임을 드러내는 안정적인 인터페이스 뒤로 책임을 수행하는 데 필요한 상태를 캡슐화함으로써 구현 변경에 대한 파장이 외부로 퍼져나가는 것을 방지한다. 따라서 책임에 초점을 맞추면 상대적으로 변경에 안정적인 설계를 얻을 수 있게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응집도와 결합도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 응집도와 낮은 결합도를 가진 설계를 추구해야 하는 이유는 단 한가지다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그것이 설계를 변경하기 쉽게 만들기 때문이다.&lt;/b&gt; &lt;/span&gt;변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다. 간단히 말해 하나의 변경을 수용하기 위해 모듈 전체가 함께 변경된다면 응집도가 높은 것이고 모듈의 일부만 변경된다면 응집도가 낮은 것이다. 또한 하나의 변경에 대해 하나의 모듈만 변경된다면 응집도가 높지만 다수의 모듈이 함께 변경돼야 한다면 응집도가 낮은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응&lt;span style=&quot;color: #ee2323;&quot;&gt;집도가 높을수록 변경의 대상관 범위가 명확해지기 때문에 코드를 변경하기 쉬워진다.&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;변경으로 인해 수정되는 부분을 파악하기 위해 코드 구석구석을 헤매고 다닌다거나 여러 모듈을 동시에 수정할 필요가 없으며 변경을 반영하기 위해 오직 하나의 모듈만 수정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결합도 역시 변경의 관점에서 설명할 수 있다. 결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다. 다시 말해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하나의 모듈을 수정할 때 얼마나 많은 모듈을 함께 수정해야 하는지를 나타낸다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;따라서 결합도가 높으면 높을수록 함께 변경해야 하는 모듈의 수가 늘어나기 때문에 변경하기가 어려워진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 중심 설계의 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 중심의 설계가 변경에 취약한 이유는 두 가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.&lt;/li&gt;
&lt;li&gt;데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 중심의 설계를 시작할 때 던졌던 첫 번째 질문은 &amp;ldquo;이 객체가 포함해야 하는 데이터가 무엇인가?&amp;rdquo;다.&lt;br /&gt;데이터는 구현의 일부라는 사실을 명심하라. 데이터 주도 설계는 설계를 시작하는 처음부터 데이터에 관해 결정하도록 강요하기 때문에 너무 이른 시기에 내부 구현에 초점을 맞추게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 중심의 관점에서 객체는 그저 단순한 데이터의 집합체일 뿐이다. 이로 인해 접근자와 수정자를 과도하게 추가하게 되고 이 데이터 객체를 사용하는 절차를 분리된 별도의 객체 안에 구현하게 된다.&lt;br /&gt;접근자와 수정자는 &lt;code&gt;public&lt;/code&gt; 속성과 큰 차이가 없기 때문에 객체의 캡슐화는 완전히 무너질 수밖에 없다. 이것이 첫 번째 설계가 실패한 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 데이터를 처리하는 작업과 데이터를 같은 객체 안에 두더라도 데이터에 초점이 맞춰져 있다면 만족스러운 캡슐화를 얻기 어렵다. 데이터를 먼저 결정하고 데이터를 처리하는 데 필요한 오퍼레이션을 나중에 결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 된다. 결과적으로 객체의 인터페이스는 구현을 캡슐화하는 데 실패하고 코드는 변경에 취약해진다. 이것이 두 번째 설계가 실패한 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;결과적으로 데이터 중심의 설계는 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 된다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;객체의 내부 구현이 객체의 인터페이스를 어지럽히고 객체의 응집도와 결합도에 나쁜 영향을 미치기 때문에 변경에 취약한 코드를 낳게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안타깝게도 데이터 중심 설계에서 초점은 객체의 외부가 아니라 내부로 향한다.&lt;br /&gt;실행 문맥에 대한 깊이 있는 고민 없이 객체가 관리할 데이터의 세부 정보를 먼저 결정한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워맞출 수밖에 없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;느낀점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트를 설계할 때, 데이터를 먼저 결정한 경험이 많기 때문에 뜨끔할 수밖에 없었다.&lt;br /&gt;데이터는 구현에 포함되기 때문에 데이터를 먼저 결정하는 것은 구현을 먼저 결정한 것이기 때문에 유연해질 수 없다는 부분이 인상깊었다.&lt;br /&gt;현재 서비스를 운영하면서도 변경되는 요구사항에 데이터의 저장 테이블을 바꾸게 되는 이유도 초기에 데이터 중심의 설계 때문에 책임을 적절하게 나누지 못해서 라는 생각이 들게 되는 장이였다.&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/68</guid>
      <comments>https://brightstarit.tistory.com/68#entry68comment</comments>
      <pubDate>Sun, 7 Sep 2025 21:33:22 +0900</pubDate>
    </item>
    <item>
      <title>Datadog Trace 적용기</title>
      <link>https://brightstarit.tistory.com/67</link>
      <description>&lt;h1&gt;문제상황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리 서비스의 경우 모든 어플리케이션의 역할이 분리되어 안정성과 확장성을 향상 시켰다.&lt;br /&gt;각 역할은 간단하게 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 서버에서 전처리 요청&lt;/li&gt;
&lt;li&gt;Batch 서버에서 이벤트 발행&lt;/li&gt;
&lt;li&gt;Worker(카프카) 서버에서 분석 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청부터 분석까지 API 서버만 사용하는 경우(동기)에는 모든 Trace Context가 이어져 추적이 가능하겠지만&lt;br /&gt;위 처럼 역할이 분리되고 있는 경우 생성된 요청에 대해서 Batch 서버는 API서버의 어떤 요청에 대한 이벤트 발행인지 알 방법이 없다.&lt;br /&gt;때문에 하나의 trace_id로 End to End에 대한 추적이 불가능한 상황이였다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Trace Context&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datadog에서 Trace를 남길 때, Trace Context라는 것을 기준으로 Trace를 전파하도록 되어있다.&lt;br /&gt;관련 문서 : &lt;a href=&quot;https://docs.datadoghq.com/ko/tracing/trace_collection/trace_context_propagation/?tab=java&quot;&gt;데이터독 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace Context에는 아래와 같은 값들이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;x-datadog-trace-id&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 전체 요청 흐름을 식별하는 고유한 추적 ID&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&lt;/b&gt;: 하나의 사용자 요청이 여러 마이크로서비스를 거쳐가더라도 동일한 trace-id로 연결하여 전체 흐름을 추적&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태&lt;/b&gt;: 64비트 또는 128비트 정수 (보통 16진수로 표현)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-datadog-parent-id&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 현재 span의 부모 span ID&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&lt;/b&gt;: 서비스 간 호출 관계와 계층 구조를 구성하는 데 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태&lt;/b&gt;: 64비트 정수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-datadog-origin&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 추적이 시작된 원점/소스를 나타냄&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&lt;/b&gt;: 추적의 출발점이 어디인지 식별 (예: 'synthetics', 'rum', 'lambda' 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시&lt;/b&gt;: &lt;code&gt;synthetics&lt;/code&gt;, &lt;code&gt;rum&lt;/code&gt;, &lt;code&gt;lambda&lt;/code&gt;, &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-datadog-sampling-priority&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 해당 추적의 샘플링 우선순위&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&lt;/b&gt;: 추적 데이터를 얼마나 중요하게 처리할지 결정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;-1&lt;/code&gt;: 자동 거부 (DROP)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;: 자동 유지 (AUTO_REJECT)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;: 자동 유지 (AUTO_KEEP)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt;: 사용자 유지 (USER_KEEP)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-datadog-tags&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의미&lt;/b&gt;: 추적과 관련된 메타데이터 태그들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&lt;/b&gt;: 추적에 추가적인 컨텍스트 정보를 부여 (환경, 버전, 사용자 ID 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태&lt;/b&gt;: 키-값 쌍들이 URL 인코딩된 형태 (예: &lt;code&gt;_dd.p.key1=value1,key2=value2&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Trace Context의 추출 및 복원&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리 요청을 받을 때 Trace Context가 중단되고 Batch 어플리케이션에서 Trace Context를 불러오지 못하는게 근본적인 원인이다.&lt;br /&gt;때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Trace Context를 추출하고, Trace Context가 끊겨있는 상황에서 복원시켜 이어주는 방식으로 구현한다&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datadog Trace에 접근하기 위해서는 아래 의존성이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;api 'com.datadoghq:dd-trace-ot:1.46.0'  
api 'com.datadoghq:dd-trace-api:1.46.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace Context를 추출 및 복원 하기 위해 TraceUtil 클래스를 정의하고 아래 함수를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Trace Context 추출&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static Map&amp;lt;String, String&amp;gt; extractTraceContext() {  
    Tracer tracer = getTracer();  
    Span span = getSpan();  

    if (Objects.isNull(span)) {  
        return Collections.emptyMap();  
    }  

    Map&amp;lt;String, String&amp;gt; contextMap = new HashMap&amp;lt;&amp;gt;();  
    tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new TextMapAdapter(contextMap));  

    return contextMap;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Trace Context 복원&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static Span createSpanFromContext(String operationName, Map&amp;lt;String, String&amp;gt; contextMap) {  
    Tracer tracer = getTracer();  

    if (CollectionUtils.isEmpty(contextMap)) {  
        return tracer.buildSpan(operationName).start();  
    }  

    try {  
        SpanContext parentContext = tracer.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(contextMap));  

        if (Objects.nonNull(parentContext)) {  
            return tracer.buildSpan(operationName)  
                    .asChildOf(parentContext)  
                    .start();  
        }  
    } catch (Exception e) {  
        // Context 추출 실패 시 새로운 Span 생성  
    }  

    return tracer.buildSpan(operationName).start();  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;복원한 Trace Context의 Span 활성화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static io.opentracing.Scope activateSpan(Span span) {  
    return getTracer().activateSpan(span);  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;추출한 Trace Context 저장&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TraceUtil을 통해 추출 및 복원 코드를 작성했으면 추출한 Trace Context를 저장한다.&lt;br /&gt;Redis를 사용하여 Aggregate root의 ID와 Trace Context를 매핑 시켜 사용하면 좋겠지만 현재 Redis를 사용하지 않는 서비스에 이것만을 위해 연동하기에는 무리가 있다.&lt;br /&gt;따라서 RDB에 저장하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace Context는 아래 테이블들에 저장하도록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Aggregate root인 PreprocessJob생성 시 저장&lt;/li&gt;
&lt;li&gt;이벤트 발행 시 편의상으로 Outbox 테이블에 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;outbox 테이블에 저장하지 않는 경우 이벤트 발행 시 마다 PreprocessJob을 조회해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Slf4j  
@Service  
@RequiredArgsConstructor  
public class PreprocessJobCreateService {  

    //...의존성 코드

    @Transactional    
    public PreprocessJob create(CreatePreprocessJobCommand command) {
        TraceContext traceContext = new TraceContext(TraceUtil.extractTraceContext());  

        PreprocessJob preprocessJob = PreprocessJob.create(  
                //.. 기타 필드
                traceContext  
        );  
        // Aggregate Root
        PreprocessJob savedJob = preprocessJobRepository.save(preprocessJob);   

        //outbox
        outboxRepository.save(PreprocessJobCreatedOutbox.create(savedJob, traceContext));  

        return savedJob;  
    }  &lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;추출한 Trace Context 복원&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Trace가 끊기는 포인트에 Trace Context를 복원한다.&lt;br /&gt;복원 후에는 복원한 Span을 Active하여 비즈니스 로직을 포함할 수 있도록 해야한다.&lt;br /&gt;Span을 active, finish 하지 않은 경우에는 같은 Trace에 비즈니스 로직이 감지되지 않는다.&lt;br /&gt;나는 TraceContext에서 특정 값을 추출하는 로직의 처리를 위해 VO로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;TraceContext.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record TraceContext(Map&amp;lt;String, String&amp;gt; value) {  

    private static final String TRACE_ID_KEY = &quot;x-datadog-trace-id&quot;;  

    public String getTraceId() {  
        if (value.containsKey(TRACE_ID_KEY)) {  
            return value.get(TRACE_ID_KEY);  
        }  

        return &quot;&quot;;  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복원 예시 코드는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Slf4j  
@Service  
@RequiredArgsConstructor  
public class ExtractSentenceService {  

    //... 의존성  

    public PreprocessJobId extract(SentenceExtractCommand command) {  
        PreprocessJob job = jobReader.readById(jobId);
        TraceContext traceContext = job.getTraceContext();
        Span span = TraceUtil.createSpanFromContext(&quot;extract sentence&quot;, traceContext.value()); // Trace Context 복원
        try (Scope scope = TraceUtil.activateSpan(span)) {  // Span Activce
              // 비즈니스 로직
        } finally {  
            span.finish();  // Span Finish
        } 
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;개선&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace Context를 복원하고 Span을 Activce 하는 코드가 모든 비즈니스 로직에 들어가야 하기 때문에 매번 Trace와 관련된 코드가 반복되고 비즈니스 코드에 집중할 수 없는 문제가 발생한다.&lt;br /&gt;이 부분을 AOP를 통해 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trace를 적용할 함수를 지정하기 위해 annotation을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DatadogTrace.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface DatadogTrace {  

    String operationName() default &quot;&quot;;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;operationName은 Trace에 남길 작업명을 기재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Aspect클래스를 정의한다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Around(&quot;@annotation(datadogTrace)&quot;)  
public Object traceSpan(ProceedingJoinPoint joinPoint, DatadogTrace datadogTrace) throws Throwable {  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Aspect 클래스에서 TraceContext객체를 가져와야한다.&lt;br /&gt;내가 생각한 방식은 두 가지 방법이 있었다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Spring Expression을 통해 Arguments에서 가져오는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고문서 : &lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot;&gt;Redisson 분산락 적용 방법&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스를 정의해 Arguments를 추상화 하는 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 2번 방식을 선택했다.&lt;br /&gt;그 이유는 1번 방식은 literal하게 &lt;code&gt;Spring Expression&lt;/code&gt;을 관리해야 했고 파싱하기 위해 Parser를 추가로 개발해야하는 부분보다 2번 방식이 직관적이고 유지보수 측명으로 더 낫다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 TraceContext를 가져온다는 의미로 &lt;code&gt;TraceContextProvider&lt;/code&gt;라고 정의했다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface TraceContextProvider {  

    TraceContext traceContext();  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 TraceContext를 가지고 있는 객체에 &lt;code&gt;TraceContextProvider&lt;/code&gt;를 상속한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Getter  
public class PreprocessJob implements TraceContextProvider {
    //... 추가 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 다음과 같이 TraceContext를 추출 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Around(&quot;@annotation(datadogTrace)&quot;)  
public Object traceSpan(ProceedingJoinPoint joinPoint, DatadogTrace datadogTrace) throws Throwable {  
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
    Method method = signature.getMethod();  
    Object[] args = joinPoint.getArgs();  

    String operationName = determineOperationName(datadogTrace, method);  
    TraceContext traceContext = extractTraceContextFromArgs(args);
    Span span = TraceUtil.createSpanFromContext(operationName, traceContext.value());  

try (Scope scope = TraceUtil.activateSpan(span)) {  
    return joinPoint.proceed();  
} finally {  
    span.finish();  
}
}

private String determineOperationName(DatadogTrace datadogTrace, Method method) {  
    return Optional.ofNullable(datadogTrace.operationName())  
            .filter(name -&amp;gt; !name.isBlank())  
            .orElse(method.getDeclaringClass().getSimpleName() + &quot;.&quot; + method.getName());  
}

private TraceContext extractTraceContextFromArgs(Object[] args) {  
    return Arrays.stream(args)  
            .filter(TraceContextProvider.class::isInstance)  
            .map(TraceContextProvider.class::cast)  
            .findFirst()  
            .map(TraceContextProvider::traceContext)  
            .orElse(new TraceContext(TraceUtil.extractTraceContext()));  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;operationName&lt;/code&gt;은 존재하지 않는 경우 {클래스명.메소드명}의 형식으로 적용했다.&lt;br /&gt;&lt;code&gt;traceContext&lt;/code&gt;는 존재하지 않는 경우 현재 Trace Context를 추출하여 넣도록 했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;예외처리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 발생한 경우 Span에 에러를 표시하기 위해서는 에러 발생 시 Span에 Tag를 추가로 작성해야 한다.&lt;br /&gt;때문에 catch문으로 에러 발생 시 태그를 추가로 에러 정보를 담을 수 있도록 한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Around(&quot;@annotation(datadogTrace)&quot;)  
public Object traceSpan(ProceedingJoinPoint joinPoint, DatadogTrace datadogTrace) throws Throwable {  
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();  
    Method method = signature.getMethod();  
    Object[] args = joinPoint.getArgs();  

    String operationName = determineOperationName(datadogTrace, method);  
    TraceContext traceContext = extractTraceContextFromArgs(args);
    Span span = TraceUtil.createSpanFromContext(operationName, traceContext.value());  

try (Scope scope = TraceUtil.activateSpan(span)) {  
    return joinPoint.proceed();  
} catch (Throwable throwable) {  
    span.setTag(&quot;error&quot;, true);  
    span.setTag(&quot;error.msg&quot;, throwable.getMessage());  
    span.setTag(&quot;error.kind&quot;, throwable.getClass().getSimpleName());  
    span.setTag(&quot;error.stack&quot;, getStackTrace(throwable));  
    span.setTag(&quot;method.name&quot;, method.getName());  
    span.setTag(&quot;class.name&quot;, method.getDeclaringClass().getSimpleName());  

    throw throwable;  
} finally {  
    span.finish();  
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;데이터독 결과 확인&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PreprocessJob 생성 (/preprocessor-api/request) 요청 시&lt;br /&gt;API, Batch, Worker의 모든 흐름이 하나의 trace_id로 확인이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20250825160648.png&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lqpan/btsQjSSDWxV/5eB8QwUwdyKsoeBHuJe62K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lqpan/btsQjSSDWxV/5eB8QwUwdyKsoeBHuJe62K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lqpan/btsQjSSDWxV/5eB8QwUwdyKsoeBHuJe62K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flqpan%2FbtsQjSSDWxV%2F5eB8QwUwdyKsoeBHuJe62K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1038&quot; height=&quot;430&quot; data-filename=&quot;Pasted image 20250825160648.png&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20250825160750.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1077&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Iui0Q/btsQkVH1ZfE/mMWSAbsKDgKSOLv3LSRbKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Iui0Q/btsQkVH1ZfE/mMWSAbsKDgKSOLv3LSRbKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Iui0Q/btsQkVH1ZfE/mMWSAbsKDgKSOLv3LSRbKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIui0Q%2FbtsQkVH1ZfE%2FmMWSAbsKDgKSOLv3LSRbKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1358&quot; height=&quot;1077&quot; data-filename=&quot;Pasted image 20250825160750.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1077&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>백엔드/분산추적</category>
      <category>데이터독</category>
      <category>분산추적</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/67</guid>
      <comments>https://brightstarit.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 5 Sep 2025 00:05:27 +0900</pubDate>
    </item>
    <item>
      <title>Datadog Trace 로컬 환경 설정</title>
      <link>https://brightstarit.tistory.com/66</link>
      <description>&lt;h1&gt;데이터독 계정 생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 환경 설정을 위해 데이터독 계정이 필요하다.&lt;br /&gt;&lt;a href=&quot;https://app.datadoghq.com/account/login?redirect=f&quot;&gt;데이터독 사이트&lt;/a&gt;를 통해 로그인해서 14일 Trial이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;Tip! 14일 사용이 완료된 경우 Region을 변경해서 다시 14일 사용이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;데이터독 APM 수집 원리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터독 APM 수집을 시각화 하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-1.png&quot; data-origin-width=&quot;7059&quot; data-origin-height=&quot;3514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9C0N1/btsQkVnP9uD/rXpTeU78pume9AIKlIuRK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9C0N1/btsQkVnP9uD/rXpTeU78pume9AIKlIuRK0/img.png&quot; data-alt=&quot;데이터독 APM 플로우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9C0N1/btsQkVnP9uD/rXpTeU78pume9AIKlIuRK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9C0N1%2FbtsQkVnP9uD%2FrXpTeU78pume9AIKlIuRK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;7059&quot; height=&quot;3514&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-1.png&quot; data-origin-width=&quot;7059&quot; data-origin-height=&quot;3514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터독 APM 플로우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;출처 : &lt;a href=&quot;https://docs.datadoghq.com/ko/tracing/trace_collection/&quot;&gt;데이터독 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;INSTRUMENTED APPICATION WITH DD-TRACE LIBRARY&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터독 TRACE 라이브러리(이 문서에서는 dd-java-agent.jar)와 함께 어플리케이션을 계측&lt;/li&gt;
&lt;li&gt;계측된 데이터를 데이터독 에이전트로 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DATADOG-AGENT&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전송 받은 애플리케이션 계측 데이터를 DATADOG-BACKEND로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DATADOG-BACKEND&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에이전트가 수집한 데이터를 BACKEND에서 받아 UI로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;dd-java-agent.jar (TRACING LIBRARY) 설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에서 아래 명령어 중 하나를 실행하여 dd-java-agent.jar를 설치한다.&lt;br /&gt;참고문서 : &lt;a href=&quot;https://docs.datadoghq.com/ko/tracing/trace_collection/automatic_instrumentation/dd_libraries/java/?tab=wget&quot;&gt;데이터독 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;wget -O dd-java-agent.jar 'https://dtdg.co/latest-java-tracer'&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;curl -Lo dd-java-agent.jar 'https://dtdg.co/latest-java-tracer'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-7.png&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z5lEh/btsQjWU2euc/5OduqTHYyhKyBheZzcipd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z5lEh/btsQjWU2euc/5OduqTHYyhKyBheZzcipd1/img.png&quot; data-alt=&quot;Tracing Library&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z5lEh/btsQjWU2euc/5OduqTHYyhKyBheZzcipd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ5lEh%2FbtsQjWU2euc%2F5OduqTHYyhKyBheZzcipd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;223&quot; height=&quot;116&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-7.png&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Tracing Library&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;데이터독 에이전트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;을 통해 데이터독 에이전트를 로컬환경에서 띄운다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;datadog-agent:  
  image: gcr.io/datadoghq/agent:7  
  networks:  
    - document_preprocessor_network  
  environment:  
    - DD_API_KEY=${DD_API_KEY}  
    - DD_SITE=us5.datadoghq.com  
    - DD_APM_ENABLED=true  
    - DD_LOGS_ENABLED=true  
    - DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true  
  volumes:  
    - /var/run/docker.sock:/var/run/docker.sock:ro  
    - /proc/:/host/proc/:ro  
    - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro  
    - /var/lib/docker/containers:/var/lib/docker/containers:ro  
  ports:  
    - &quot;8126:8126&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 변수로 아래 값들이 필요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;DD_API_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DD_SITE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;데이터독 API KEY&lt;/span&gt;를 구하기 위해 아래와 같이 접속한다.&lt;br /&gt;&lt;code&gt;Organization Settings&lt;/code&gt; &amp;gt; &lt;code&gt;API Keys&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-2.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0a2Jr/btsQjUJG4i7/J7nDMByFU0z54zfxTdrmVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0a2Jr/btsQjUJG4i7/J7nDMByFU0z54zfxTdrmVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0a2Jr/btsQjUJG4i7/J7nDMByFU0z54zfxTdrmVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0a2Jr%2FbtsQjUJG4i7%2FJ7nDMByFU0z54zfxTdrmVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;370&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-2.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-3.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyF39o/btsQlq1VPa2/RKCpjjphipKK2ZRkfwN5WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyF39o/btsQlq1VPa2/RKCpjjphipKK2ZRkfwN5WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyF39o/btsQlq1VPa2/RKCpjjphipKK2ZRkfwN5WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyF39o%2FbtsQlq1VPa2%2FRKCpjjphipKK2ZRkfwN5WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1252&quot; height=&quot;333&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-3.png&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;API Key가 없는 경우 New Key 버튼을 클릭하여 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-4.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ck65Mc/btsQjz6FknI/Ocei7Y34fEPYzQ2VrnmwSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck65Mc/btsQjz6FknI/Ocei7Y34fEPYzQ2VrnmwSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck65Mc/btsQjz6FknI/Ocei7Y34fEPYzQ2VrnmwSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck65Mc%2FbtsQjz6FknI%2FOcei7Y34fEPYzQ2VrnmwSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;546&quot; height=&quot;464&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-4.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Copy 버튼을 클릭해서 카피한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;warning! &lt;/span&gt;Key값은 .env파일로 관리해서 commit되지 않도록 주의한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;DD_SITE&lt;/span&gt;값은 데이터독 사이트의 Host를 입력하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-5.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDSBw/btsQl4YxjeW/hw1MEC3Ffu66y4iksXjXFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDSBw/btsQl4YxjeW/hw1MEC3Ffu66y4iksXjXFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDSBw/btsQl4YxjeW/hw1MEC3Ffu66y4iksXjXFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDSBw%2FbtsQl4YxjeW%2Fhw1MEC3Ffu66y4iksXjXFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;66&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-5.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;어플리케이션 환경 변수 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM옵션&lt;/b&gt;에는 Tracing Library(dd-java-agent.jar)를 지정해야 한다.&lt;br /&gt;&lt;code&gt;-javaagent:dd-java-agent.jar&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Environment variables&lt;/b&gt;(환경 변수)에는 아래 값들을 입력한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;DD_AGENT_HOST&lt;/code&gt; : 데이터독 에이전트의 호스트&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DD_TRACE_AGENT_PORT&lt;/code&gt; : 8126&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DD_SERVICE&lt;/code&gt; : 서비스명 (검색 쿼리 시에 service에 검색된다.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DD_ENV&lt;/code&gt; : 환경명 (검색 쿼리 시에 env로 검색된다.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DD_VERSION&lt;/code&gt; : 버전 (서비스 버전을 적으면 좋음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텔리제이의 Run/Debug Configuration에 설정하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-6.png&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nngwl/btsQmaxuPLR/arKCnbIzbKgTAn6tifqpWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nngwl/btsQmaxuPLR/arKCnbIzbKgTAn6tifqpWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nngwl/btsQmaxuPLR/arKCnbIzbKgTAn6tifqpWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnngwl%2FbtsQmaxuPLR%2FarKCnbIzbKgTAn6tifqpWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;306&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-6.png&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;결과확인&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 실행 시에 아래와 같은 로그가 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[dd.trace 2025-08-26 10:19:02:018 +0900] [dd-task-scheduler] INFO datadog.trace.agent.core.StatusLogger - DATADOG TRACER CONFIGURATION {&quot;version&quot;:&quot;1.52.1~6b6db17410&quot;,&quot;os_name&quot;:&quot;Mac OS X&quot;,&quot;os_version&quot;:&quot;15.5&quot;,&quot;architecture&quot;:&quot;aarch64&quot;,&quot;lang&quot;:&quot;jvm&quot;,&quot;lang_version&quot;:&quot;21.0.7&quot;,&quot;jvm_vendor&quot;:&quot;Microsoft&quot;,&quot;jvm_version&quot;:&quot;21.0.7+6-LTS&quot;,&quot;java_class_version&quot;:&quot;65.0&quot;,&quot;http_nonProxyHosts&quot;:&quot;local|&lt;i&gt;.local|169.254/16|&lt;/i&gt;.169.254/16&quot;,&quot;http_proxyHost&quot;:&quot;null&quot;,&quot;enabled&quot;:true,&quot;service&quot;:&quot;preprocessor-api&quot;,&quot;agent_url&quot;:&quot;&lt;a href=&quot;http://localhost:8126&amp;quot;,&amp;quot;agent_error&amp;quot;:false,&amp;quot;debug&amp;quot;:false,&amp;quot;trace_propagation_style_extract&amp;quot;:%5B&amp;quot;datadog&amp;quot;,&amp;quot;tracecontext&amp;quot;,&amp;quot;baggage&amp;quot;%5D,&amp;quot;trace_propagation_style_inject&amp;quot;:%5B&amp;quot;datadog&amp;quot;,&amp;quot;tracecontext&amp;quot;,&amp;quot;baggage&amp;quot;%5D,&amp;quot;analytics_enabled&amp;quot;:false,&amp;quot;priority_sampling_enabled&amp;quot;:true,&amp;quot;logs_correlation_enabled&amp;quot;:true,&amp;quot;profiling_enabled&amp;quot;:false,&amp;quot;remote_config_enabled&amp;quot;:true,&amp;quot;debugger_enabled&amp;quot;:false,&amp;quot;debugger_exception_enabled&amp;quot;:false,&amp;quot;debugger_span_origin_enabled&amp;quot;:false,&amp;quot;debugger_distributed_debugger_enabled&amp;quot;:false,&amp;quot;appsec_enabled&amp;quot;:&amp;quot;ENABLED_INACTIVE&amp;quot;,&amp;quot;rasp_enabled&amp;quot;:true,&amp;quot;telemetry_enabled&amp;quot;:true,&amp;quot;telemetry_dependency_collection_enabled&amp;quot;:true,&amp;quot;telemetry_log_collection_enabled&amp;quot;:true,&amp;quot;dd_version&amp;quot;:&amp;quot;1.0&amp;quot;,&amp;quot;health_checks_enabled&amp;quot;:true,&amp;quot;configuration_file&amp;quot;:&amp;quot;no&quot;&gt;http://localhost:8126&quot;,&quot;agent_error&quot;:false,&quot;debug&quot;:false,&quot;trace_propagation_style_extract&quot;:[&quot;datadog&quot;,&quot;tracecontext&quot;,&quot;baggage&quot;],&quot;trace_propagation_style_inject&quot;:[&quot;datadog&quot;,&quot;tracecontext&quot;,&quot;baggage&quot;],&quot;analytics_enabled&quot;:false,&quot;priority_sampling_enabled&quot;:true,&quot;logs_correlation_enabled&quot;:true,&quot;profiling_enabled&quot;:false,&quot;remote_config_enabled&quot;:true,&quot;debugger_enabled&quot;:false,&quot;debugger_exception_enabled&quot;:false,&quot;debugger_span_origin_enabled&quot;:false,&quot;debugger_distributed_debugger_enabled&quot;:false,&quot;appsec_enabled&quot;:&quot;ENABLED_INACTIVE&quot;,&quot;rasp_enabled&quot;:true,&quot;telemetry_enabled&quot;:true,&quot;telemetry_dependency_collection_enabled&quot;:true,&quot;telemetry_log_collection_enabled&quot;:true,&quot;dd_version&quot;:&quot;1.0&quot;,&quot;health_checks_enabled&quot;:true,&quot;configuration_file&quot;:&quot;no&lt;/a&gt; config file present&quot;,&quot;runtime_id&quot;:&quot;f88a9a6e-f9ba-4281-975e-c8b9c6011a02&quot;,&quot;logging_settings&quot;:{&quot;levelInBrackets&quot;:false,&quot;dateTimeFormat&quot;:&quot;'[dd.trace 'yyyy-MM-dd HH:mm:ss:SSS Z']'&quot;,&quot;logFile&quot;:&quot;System.err&quot;,&quot;configurationFile&quot;:&quot;simplelogger.properties&quot;,&quot;showShortLogName&quot;:false,&quot;showDateTime&quot;:true,&quot;showLogName&quot;:true,&quot;jsonEnabled&quot;:false,&quot;showThreadName&quot;:true,&quot;defaultLogLevel&quot;:&quot;INFO&quot;,&quot;warnLevelString&quot;:&quot;WARN&quot;,&quot;embedException&quot;:false},&quot;cws_enabled&quot;:false,&quot;cws_tls_refresh&quot;:5000,&quot;datadog_profiler_enabled&quot;:false,&quot;datadog_profiler_safe&quot;:true,&quot;datadog_profiler_enabled_overridden&quot;:false,&quot;data_streams_enabled&quot;:false}&lt;br /&gt;데이터독 APM Trace에 접속하여 서비스가 정상 연결되었는지 확인한다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-7-1.png&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cduK8B/btsQkOIW4Gl/CSYxPjKmj83d0XioNyYs5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cduK8B/btsQkOIW4Gl/CSYxPjKmj83d0XioNyYs5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cduK8B/btsQkOIW4Gl/CSYxPjKmj83d0XioNyYs5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcduK8B%2FbtsQkOIW4Gl%2FCSYxPjKmj83d0XioNyYs5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;272&quot; data-filename=&quot;Datadog Trace 로컬 환경 설정-7-1.png&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>백엔드/분산추적</category>
      <category>데이터독</category>
      <category>분산추적</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/66</guid>
      <comments>https://brightstarit.tistory.com/66#entry66comment</comments>
      <pubDate>Fri, 5 Sep 2025 00:01:19 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트 - 조영호, 3장</title>
      <link>https://brightstarit.tistory.com/65</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 패러다임의 관점에서 핵심은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;역할(role)&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;책임(responsibility)&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;협력(collaboration)&lt;/span&gt;이다.&lt;br /&gt;객체지향 설계의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;협력을 구성하기 위해 적절한 객체를 찾고 적절한 책임을 할당하는 과정&lt;/b&gt;&lt;/span&gt;에서 드러난다.&lt;br /&gt;클래스와 상속은 객체들의 책임과 협력이 어느 정도 자리를 잡은 후에 사용할 수 있는 구현 메커니즘일 뿐이다.&lt;br /&gt;애플리케이션의 기능을 구현하기 위해 어떤 협력이 필요하고 협력을 위해 어떤 역할과 책임이 필요한지를 고민하지 않은 채 너무 이름 시기에 구현에 초점을 맞추는 것은 변경하기 어렵고 유연하지 못한 코드를 낳는 원인이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;협력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 시스템은 자율적인 객체들의 공동체다.&lt;br /&gt;객체는 고립된 존재가 아니라 시스템의 기능이라는 더 큰 목표를 달성하기 위해 다른 객체와 협력하는 사회적인 존재다. 협력은 객체지향의 세계에서 기능을 구현할 수 있는 유일한 방법이다.&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지 전송(message sending)&lt;/span&gt;은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.&lt;br /&gt;다른 객체의 상세한 내부 구현에 직접 접근할 수 없기 때문에 오직 메시지 전송을 통해서만 자신의 요청을 전달할 수 있다.&lt;br /&gt;메시지를 수신한 객체는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메서드&lt;/span&gt;를 실행해 요청에 응답한다.&lt;br /&gt;여기서 객체가 메시지를 처리할 방법을 스스로 선택한다는 점이 중요하다.&lt;br /&gt;외부의 객체는 오직 메시지만 전송할 수 있을 뿐이며 메시지를 어떻게 처리할지는 메시지를 수신한 객체가 직접 결정한다. 이것은 객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재라는 것을 의미한다.&lt;br /&gt;객체를 자율적으로 만드는 가장 기본적인 방법은 내부 구현을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;캡슐화&lt;/span&gt;하는 것이다.&lt;br /&gt;캡슐화를 통해 변경에 대한 파급효과를 제한 할 수 있기 때문에 자율적인 객체는 변경하기도 쉬워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자율적인 객체는 자신에게 할당된 책임을 수행하던 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다. 메시지를 수신한 객체 역시 메시지를 처리하던 중에 직접 처리할 수 없는 정보나 행동이 필요한 경우 또 다른 객체에게 도움을 요청한다. 이처럼 객체들 사이의 협력을 구성하는 일련의 요청과 응답의 흐름을 통해 애플리케이션의 기능이 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 안에 어떤 객체가 필요하다면 그 이유는 단 하나여야 한다.&lt;br /&gt;그 객체가 어떤 협력에 참여하고 있기 때문이다. 그리고 객체가 협력에 참여할 수 있는 이유는 협력에 필요한 적절한 행동을 보유하고 있기 때문이다.&lt;br /&gt;결론적으로 객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력이다. 협력이 바뀌면 객체가 제공해야 하는 행동 역시 바뀌어야 한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 행동을 결정하는 것이 협력이라면 객체의 상태를 결정하는 것은 행동이다.&lt;br /&gt;객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다.&lt;br /&gt;객체는 자신의 상태를 스스로 결정하고 관리하는 자율적인 존재이기 때문에 객체가 수행하는 행동에 필요한 상태도 함께 가지고 있어야 한다.&lt;br /&gt;상태는 객체가 행동하는 데 필요한 정보에 의해 결정되고 행동은 협력 안에서 객체가 처리할 메시지로 결정된다.&lt;br /&gt;결과적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체가 참여하는 협력이 객체를 구성하는 행동과 상태를 모두를 결정한다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;따라서 협력은 객체를 설계하는 데 필요한 일종의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;문맥(context)&lt;/span&gt;을 제공한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책임&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 설계하기 위해 필요한 문맥인 협력이 갖춰졌다고 하자.&lt;br /&gt;다음으로 할 일은 협력에 필요한 행동을 수행할 수 있는 적절한 객체를 찾는 것이다.&lt;br /&gt;이때 협력에 참여하기 위해 객체가 수행하는 행동을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;책임&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임이란 객체에 의해 정의되는 응집도 있는 행위의 집합으로, 객체가 유지해야 하는 정보와 수행할 수 있는 행동에 대해 개략적으로 서술한 문장이다. 즉, 객체의 책임은 객체가 &amp;lsquo;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무엇을 알고 있는가&lt;/b&gt;&lt;/span&gt;&amp;rsquo;와 &amp;lsquo;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무엇을 할 수 있는가&lt;/b&gt;&lt;/span&gt;&amp;rsquo;로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 책임과 메시지의 크기는 다르다. 책임은 객체가 수행할 수 있는 행동을 종합적이고 간략하게 서술하기 때문에 메시지보다 추상적이고 개념적으로도 더 크다. 처음에는 단순한 책임이라고 생각했던 것이 여러 개의 메시지로 분할되기도 하고 하나의 객체가 수행할 수 있다고 생각했던 책임이 나중에는 여러 객체들이 협력해야만 하는 커다란 책임으로 자라는 것이 일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 사실은 책임의 관점에서 &amp;lsquo;아는 것&amp;rsquo;과 &amp;lsquo;하는 것&amp;rsquo;이 밀접하게 연관돼 있다는 점이다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체는 자신이 맡은 책임을 수행하는 데 필요한 정보를 알고 있을 책임&lt;/b&gt;&lt;/span&gt;이 있다.&lt;br /&gt;또한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체는 자신이 할 수 없는 작업을 도와줄 객체를 알고 있을 책임&lt;/b&gt;&lt;/span&gt;이 있다. 어떤 책임을 수행하기 위해서는 그 책임을 수행하는 데 필요한 정보도 함께 알아야 할 책임이 있는 것이다. 이것은 객체에게 책임을 할당하기 위한 가장 기본적인 원칙에 대한 힌트를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자율적인 객체를 만드는 가장 기본적인 방법은 책임을 수행하는 데 필요한 정보를 가장 잘 알고 있는 전문가에게 그 책임을 할당하는 것이다.&lt;br /&gt;이를 책임 할당을 위한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;INFORMATION EXPERT(정보 전문가)&lt;/span&gt; 패턴이라고 부른다.&lt;br /&gt;정보 전문가에게 책임을 할당하는 것은 일상 생활에서 도움을 요청하는 방식과도 유사하다.&lt;br /&gt;일상 생활에서도 어떤 도움이 필요한 경우 그 일을 처리하는 데 필요한 지식과 방법을 가장 잘 알고 잇는 객체에게 도움을 청한다. 객체의 세계에서도 마찬가지다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체들 역시 협력에 필요한 지식과 방법을 가장 잘 알고있는 개체에게 도움을 요청한다.&lt;/b&gt;&lt;/span&gt; 요청에 응답하기 위해 필요한 이 행동이 객체가 수행할 책임으로 이어지는 것이다.&lt;br /&gt;따라서 객체에게 책임을 할당하기 위해서는 먼저 협력이라는 문맥을 정의해야 한다.&lt;br /&gt;협력을 설계하는 출발점은 시스템이 사용자에게 제공하는 기능을 시스템이 담당할 하나의 책임으로 바라보는 것이다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체지향 설계는 시스템의 책임을 완료하는 데 필요한 더 작은 책임을 찾아내고 이를 개체들에게 할당하는 반복적인 과정을 통해 모양을 갖춰간다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;책임 주도 설계(RDD)&lt;/span&gt;라고 부른다.&lt;br /&gt;다음은 책임 주도 설계 방법의 과정을 정리한 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.&lt;/li&gt;
&lt;li&gt;시스템 책임을 더 작은 책임으로 분할한다.&lt;/li&gt;
&lt;li&gt;분할된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.&lt;/li&gt;
&lt;li&gt;객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.&lt;/li&gt;
&lt;li&gt;해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.&lt;br /&gt;협력은 객체를 설계하기 위한 구체적인 문맥을 제공한다. 협력이 책임을 이끌어내고 책임이 협력에게 참여할 객체를 결정한다. 책임 주도 설계는 자연스럽게 객체의 구현이 아닌 책임에 집중할 수 있게 한다.&lt;br /&gt;구현이 아닌 책임에 집중하는 것이 중요한 이유는 유연하고 견고한 객체지향 시스템을 위해 가장 중요한 재료가 바로 책임이기 때문이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 책임을 할당하는 데 필요한 메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택했다는 것이 중요하다. 다시 말해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 했다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;메시지가 객체를 선택하게 해야 하는 두 가지 중요한 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체가 최소한의 인터페이스(minimal interface)를 가질 수 있게 된다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;필요한 메시지가 실별될 때까지 객체의 퍼블릭 인터페이스에 어떤 것도 추가하지 않기 때문에 객체는 애플리케이션에 크지도, 작지도 않은 꼭 필요한 크기의 퍼블릭 인터페이스를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체는 충분히 추상적인 인터페이스(abstract interface)를 가질 수 있게 된다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;객체의 인터페이스는 무엇(what)을 하는지는 표현해야 하지만 어떻게(how) 수행하는지를 노출해서는 안 된다.&lt;br /&gt;메시지는 외부의 객체가 요청하는 무언가를 의미하기 때문에 메시지를 먼저 식별하면 무엇을 수행할지에 초점을 맞추는 인터페이스를 얻을 수 있다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체는 협력이라는 주어진 문맥 안에서 특정한 목적을 갖게 된다.&lt;br /&gt;객체의 목적은 협력 안에서 객체가 맡게 되는 책임의 집합으로 표시된다. 이처럼 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;역할&lt;/span&gt;이라고 부른다.&lt;br /&gt;실제로 협력을 모델링할 때는 특정한 객체가 아니라 역할에게 책임을 할당한다고 생각하는 게 좋다.&lt;br /&gt;역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할은 객체가 참여할 수 있는 일종의 슬롯이다. 따라서 유용하고 재사용 가능한 설계라는 문맥에서 역할의 중요성은 아무리 강조해도 지나치지 않을 것이다. 그러나 오직 한 종류의 객체만 협력에 참여하는 상황에서 역할이라는 개념을 고려하는 것이 유용할까? 역할이라는 개념을 생략하고 직접 객체를 이용해 협력을 설계하는 것이 더 좋지 않을까? 이런 경우에 역할을 사용하는 것은 상황을 오히려 더 복잡하게 만드는 것은 아닐까?&lt;br /&gt;협력에 적합한 책임을 수행하는 대상이 한 종류라면 간단하게 객체로 간주한다.&lt;br /&gt;만약 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;여러 종류의 객체들이 참여할 수 있다면 역할이라고 부르면된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;느낀점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 기획하고 구상할 때, 객체들간의 협력을 중심으로 설계한적이 몇번이나 있을까 생각하게 되는 부분이였다. 중요한건 객체가 가진 상태가 아니라 행동을 중심으로 도메인을 설계해야한다는 것을 깨달았다.&lt;br /&gt;그 행동을 해야하는 책임을 가질 객체를 선정하고 같은 책임을 가지고있는 객체가 여러개라면 역할을 통해 객체를 설계하는 방식을 다시 한번 리마인드 할 수 있는 계기가 되었다.&lt;br /&gt;현재 프로젝트에서 getter로 행동의 책임이 없는 객체가 다른 객체의 데이터를 직접 가져와 처리하고 있는 부분이 많은 것 같아. 이 부분을 많은 정보를 가지고 있는 정보 전문가에게 책임을 줄 수 있도록 다시 한번 구현해봐야겠다는 생각이 들었다.&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/65</guid>
      <comments>https://brightstarit.tistory.com/65#entry65comment</comments>
      <pubDate>Thu, 4 Sep 2025 23:50:55 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트 - 조영호, 2장</title>
      <link>https://brightstarit.tistory.com/64</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;협력, 객체, 클래스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향은 객체를 지향하는 것이다. 객체지향 프로그램을 작성할 때 가장 먼저 고려하는 것은 무엇인가?&lt;br /&gt;클래스 기반의 객체지향 언어에 익숙한 사람이라면 가장 먼저 어떤 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클래스(class)&lt;/span&gt;가 필요한지 고민할 것이다.&lt;br /&gt;대부분의 사람들은 클래스를 결정한 후에 클래스에 어떤 속성과 메서드가 필요한지 고민한다.&lt;br /&gt;안타깝게도 이것은 객체지향의 본질과는 거리가 멀다. 객체지향은 말 그대로 객체를 지향하는 것이다.&lt;br /&gt;진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 객체에 초점을 맞출 때에만 얻을 수 있다.&lt;br /&gt;이를 위해서는 프로그래밍하는 동안 다음의 두가지에 집중해야한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지를 고민하라.&lt;/b&gt; &lt;/span&gt;클래스는 공통적인 상태와 행동을 공유하는 객체를 추상화한 것이다. 따라서 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 한다. 객체를 중심에 두는 접근 방법은 설계를 단순하고 깔끔하게 만든다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.&lt;/b&gt;&lt;/span&gt; 객체는 홀로 존재하는 것이 아니다. 다른 객체에게 도움을 주거나 의존하면서 살아가는 협력적인 존재다. 객체를 협력하는 공동체의 일원으로 바라보는 것은 설계를 유연하고 확장 가능하게 만든다. 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고 이 타입을 기반으로 클래스를 구현하라. 훌륭한 협력이 훌륭한 객체를 낳고 훌륭한 객체가 훌륭한 클래스를 낳는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인의 구조를 따르는 프로그램 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어는 사용자가 원하는 어떤 문제를 해결하기 위해 만들어진다. 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 &lt;span style=&quot;color: #ee2323;&quot;&gt;도메인&lt;/span&gt;이라고 부른다.&lt;br /&gt;객체지향 패러다임이 강력한 이유는 요구사항을 분석하는 초기 단계부터 프로그램을 구현하는 마지막 단계까지 객체라는 동일한 추상화 기법을 사용할 수 있기 때문이다. 요구사항과 프로그램을 객체라는 동일한 관점에서 바라볼 수 있기 때문에 도메인을 구성하는 개념들이 프로그램의 객체와 클래스로 매끄럽게 연결될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일하거나 적어도 유사하게 지어야 한다.&lt;br /&gt;클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어질 관계와 유사하게 만들어서 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자율적인 객체&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 중요한 사실을 알아야한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;객체가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상태(state)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;행동(behavior)&lt;/span&gt;을 함께 가지는 복합적인 존재라는 것이다.&lt;/li&gt;
&lt;li&gt;객체가 스스로 판단하고 행동하는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;자율적인 존재&lt;/span&gt;라는 것이다.&lt;br /&gt;객체 지향은 객체라는 단위 안에 데이터와 기능을 한 덩어리로 묶음으로써 문제 영역의 아이디어를 적절하게 표현할 수 있게 했다.&lt;br /&gt;이처럼 데이터와 기능을 객체 내부로 함께 묶는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;캡슐화&lt;/span&gt;라고 부른다.&lt;br /&gt;대부분의 객체지향 프로그래밍 언어들은 상태와 행동을 캡슐화하는 것에서 한 걸음 더 나아가 외부에서의 접근을 통제할 수 있는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;접근 제어(access control)&lt;/span&gt; 매커니즘도 할께 제공한다.&lt;br /&gt;많은 프로그래밍 언어들은 접근 제어를 위해 &lt;code&gt;public, protected, private&lt;/code&gt;과 같은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;접근 수정자(access modifier)&lt;/span&gt;를 제공한다.&lt;br /&gt;캡슐화와 접근제어는 객체를 두 부분으로 나눈다. 하나는 외부에서 접근 가능한 부분으로 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;퍼블릭 인터페이스(public interface)&lt;/span&gt;라고 부른다. 다른 하나는 외부에서는 접근 불가능하고 오직 내부에서만 접근 가능한 부분으로 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현(implemetation)&lt;/span&gt;이라고 부른다.&lt;br /&gt;일반적으로 객체의 상태는 숨기고 행동만 외부에 공개해야 한다. 여러분이 사용하는 프로그래밍 언어가 &lt;code&gt;public&lt;/code&gt;이나 &lt;code&gt;private&lt;/code&gt;이라는 키워드를 제공한다면 클래스의 속성은 &lt;code&gt;private&lt;/code&gt;으로 선언해서 감추고 외부에 제공해야 하는 일부 메서드만 &lt;code&gt;public&lt;/code&gt;으로 선언해야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로그래머의 자유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머의 역할을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클래스 작성자(class creator)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;클라이언트 프로그래머(client programmer)&lt;/span&gt;로 구분하는 것이 유용하다. 클래스 작성자는 새로운 데이터 타입을 프로그램에 추가하고, 클라이언트 프로그래머는 클래스 작성자가 추가한 데이터 타입을 사용한다.&lt;br /&gt;클라이언트 프로그래머의 목표는 필요한 클래스들을 엮어서 애플리케이션을 빠르고 안정적으로 구축하는 것이다. 클래스 작성자는 클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 꽁꽁 숨겨야 한다. 클라이언트 프로그래머가 숨겨 놓은 부분에 마음대로 접근할 수 없도록 방지함으로써 클라이언트 프로그래머에 대한 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있다. 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현 은닉(implementation hiding)&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;협력에 관한 짧은 이야기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;요청(request)&lt;/span&gt;할 수 있다. 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;응답(response)&lt;/span&gt;한다.&lt;br /&gt;객체가 다른 객체와 상호작용을 할 수 있는 유일한 방법은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지를 전송(send a message)&lt;/span&gt;하는 것뿐이다. 다른 객체에게 요청이 도착할 때 해당 객체가 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메시지를 수신(receive a message)&lt;/span&gt;했다고 이야기한다. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정한다. 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;메서드(method)&lt;/span&gt;라고 부른다.&lt;br /&gt;메시지와 메서드를 구분하는 것은 매우 중요하다. 객체지향 패러다임이 유연하고, 확장 가능하며, 재용 가능한 설계를 낳는다는 명성을 얻게 된 배경에는 메시지와 메서드를 명확하게 구분한 것도 단단히 한몫한다. 뒤에서 살펴보겠지만 메시지와 메서드의 구분에서부터 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;다형성(polymorphism)&lt;/span&gt;의 개념이 출발한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상속과 다형성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;코드의 의존성과 실행 시점의 의존성은 서로 다를 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;다시 말해 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있다. 그리고 유연하고, 쉽게 재사용할 수 있으며, 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것이다.&lt;br /&gt;한 가지 간과해서는 안되는 사실은 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워진다는 것이다. 코드를 이해하기 위해서는 코드뿐만 아니라 객체를 생성하고 연결하는 부분을 찾아야 하기 때문이다. 반면 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장 가능해진다. 이와 같은 의존성의 양면성은 설계가 트레이드오프의 산물이라는 사실을 잘 보여준다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;차이에 의한 프로그래밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 하나 추가하고 싶은데 그 클래스가 기존의 어떤 클래스와 매우 흡사하다고 가정해보자.&lt;br /&gt;그 클래스의 코드를 가져와 약간만 추가하거나 수정해서 새로운 클래스를 만들 수 있다면 좋을 것이다.&lt;br /&gt;더 좋은 방법은 그 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;클래스의 코드를 전혀 수정하지 않고도 재사용&lt;/b&gt;&lt;/span&gt;하는 것일 것이다. 이를 가능하게 해주는 방법이 바로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상속&lt;/span&gt;이다.&lt;br /&gt;상속은 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법이다. 상속을 이용하면 클래스 사이에 관계를 설정하는 것만으로 기존 클래스가 가지고 있는 모든 속성과 행동을 새로운 클래스에 포함시킬 수 있다.&lt;br /&gt;부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;차이에 의한 프로그래밍(programming by difference)&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상속과 인터페이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.&lt;br /&gt;이것은 상속을 바라보는 일반적인 인식과는 거리가 있는데 대부분의 사람들은 상속의 목적이 메서드나 인스턴스 변수를 재사용하는 것이라고 생각하기 때문이다.&lt;br /&gt;인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다는 것을 기억하라. 상속을 통해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;자식 클래스는 자신의 인터페이스에 부모 클래스의 인터페이스를 포함&lt;/b&gt;&lt;/span&gt;하게 된다. 결과적으로 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주할 수 있다.&lt;br /&gt;이처럼 자식 클래스가 부모 클래스를 대신하는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;업캐스팅(upcasting)&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다형성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 메시지를 전송하지만 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 무엇이냐에 따라 달라진다. 이를 ==다형성==이라고 부른다.&lt;br /&gt;다형성은 객체지향 프로그램의 컴파일 시간 의존성과 실행 시간 의존성이 다를 수 있따는 사실을 기반으로 한다.&lt;br /&gt;다형성은 컴파일 시간 의존성과 실행 시간 의존성을 다르게 만들 수 있는 객체지향의 특성을 이용해 서로 다른 메서드를 실행할 수 있게 한다.&lt;br /&gt;다형성이란 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다. 따라서 다형적인 협력에 참여하는 개체들은 모두 같은 메시지를 이해할 수 있어야 한다. 다시 말해 인터페이스가 동일해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다형성을 구현하는 방법은 매우 다양하지만 메시지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 실행 시점에 결정해야한다는 공통점이 있다. 다시 말해 메시지와 메서드를 실행 시점에 바인딩한다는 것이다. 이를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;지연 바인딩(lazy binding)&lt;/span&gt;또는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;동적 바인딩(dynamic binding)&lt;/span&gt;이라고 부른다. 그에 반해 전통적인 함수 호출처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;초기 바인딩(early binding)&lt;/span&gt; 또는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;정적 바인딩(static binding)&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;합성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;합성&lt;/span&gt;이라고 부른다.&lt;br /&gt;합성은 상속이 가지는 두 가지 문제점을 모두 해결한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화 할 수 있다.&lt;/b&gt; 또한 &lt;b&gt;의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만든다.&lt;/b&gt; &lt;/span&gt;상속은 클래스를 통해 강하게 결합되는 데 비해 합성은 메시지를 통해 느슨하게 결합된다. 따라서 코드 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법이다.&lt;br /&gt;그렇다고 해서 상속을 절대 사용하지 말라는 것은 아니다. 대부분의 설계에서는 상속과 합성을 함께 사용해야 한다.&lt;/p&gt;
&lt;h1&gt;느낀점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 구현할 때, 객체간의 협력을 위해 인터페이스가아닌 구현에 의존하지 않았나라는 반성을 하게 되는 부분이였다.&lt;br /&gt;객체에 대한 정의가 어떤 것인지 되돌아 볼 수 있었고 막연하게 객체지향언어 니까 객체지향적으로 코드를 짜야지라고 생각했던 부분에 대해 이해하기 쉬운 코드와 변경에 유연한 코드에 대한 트레이드 오프에 대해서도 고민할 수 있었다. 앞으로 코드를 객체 간의 메시지를 통한 협력으로 구현해봐야겠다는 생각이 들었다.&lt;br /&gt;현재 회사 코드에서 책을 읽으면서 개선할 수 있었던 부분이 있었나 되짚어 볼 수 있었고, 현재 진행중인 프로젝트에서도 개선점을 많이 찾을 수 있겠다 생각이 들었다.&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/64</guid>
      <comments>https://brightstarit.tistory.com/64#entry64comment</comments>
      <pubDate>Thu, 4 Sep 2025 23:46:01 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트 - 조영호, 1장</title>
      <link>https://brightstarit.tistory.com/63</link>
      <description>&lt;h1&gt;내용 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로버트 마틴은 소프트웨어 모듈이 가져야 하는 세 가지 기능에 관해 설명한다.&lt;br /&gt;여기서 모듈이란 크기와 상관 없이 클래스나 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소를 의미한다.&lt;br /&gt;마틴에 따르면 모든 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모듈은 제대로 실행돼야하고, 변경이 용이해야하며, 이해하기 쉬워야한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;이해 가능한 코드&lt;/span&gt;란 그 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;동작이 우리의 예상에서 크게 벗어나지 않는 코드&lt;/b&gt;&lt;/span&gt;다.&lt;br /&gt;현실에서는 관람객이 직접 자신의 가방에서 초대장을 꺼내 판매원에게 건넨다. 티켓을 구매하는 관람객은 가방 안에서 돈을 직접 꺼내 판매원에게 지불한다. 판매원은 매표소에 있는 티켓을 직접 꺼내 관람객에게서 직접 돈을 받아 매표소에 보관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;변경에 취약한 코드&lt;/span&gt;는 많은 객체사이의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;의존성(dependency)&lt;/span&gt;이 있는 코드다.&lt;br /&gt;의존성은 변경에 대한 영향을 암시한다. 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포되어있다.&lt;br /&gt;그렇다고 해서 객체 사이의 의존성을 완전히 없애는 것이 정답은 아니다.&lt;br /&gt;객체지향 설계는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서로 의존하면서 협력하는 객체들의 공동체&lt;/b&gt;&lt;/span&gt;를 구축하는 것이다.&lt;br /&gt;따라서 우리의 목표는 필요한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;최소한의 의존성만 유지하고 불필요한 의존성을 제거&lt;/b&gt;&lt;/span&gt;하는 것이다.&lt;br /&gt;객체 사의의 의존성이 과한 경우를 가리켜 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;결합도(coupling)&lt;/span&gt;가 높다고 말한다.&lt;br /&gt;반대로 객체들이 합리적인 수준으로 의존하는 경우에는 결합도가 낮다고 말한다.&lt;br /&gt;결합도는 의존성과 관련돼 있기 때문에 결합도 역시 변경과 관련이 있다.&lt;br /&gt;두 객체 사이의 결합도가 높으면 높을수록 함께 변경될 확률도 높아지기 때문에 변경하기 어려워진다.&lt;br /&gt;따라서 설계의 목표는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체 사이의 결합도를 낮춰 변경이 용이한 설계&lt;/b&gt;&lt;/span&gt;를 만드는 것이어야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자율성을 높이자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계를 변경하기 어려운 이유는 하나의 객체가 다른 객체가 가지고있는(has)객체까지 마음대로 접근할 수 있기 때문이다. 해결방법은 객체를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;자율적인 존재&lt;/span&gt;로 만들면 되는 것이다.&lt;br /&gt;자율적인 존재인 객체는 객체가 자신의 문제를 스스로 해결하도록 변경하는 것이다.&lt;br /&gt;이 처럼 자율적인 존재가 된 객체가 내부의 세부적인 사항을 감추는 것을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;캡슐화(encapsulation)&lt;/span&gt;라 부른다.&lt;br /&gt;캡슐화의 목적은 변경하기 쉬운 객체를 만드는 것이다. 캡슐화를 통해 객체 내부로의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있기 때문에 설계를 좀 더 쉽게 변경할 수 있게 된다.&lt;br /&gt;객체를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인터페이스(interface)&lt;/span&gt;와 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;구현(implementation)&lt;/span&gt;으로 나누고 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 가장 기본적인 설계 원칙이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캡슐화와 응집도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 객체 내부의 상태를 캡슐화하고 객체간의 오직 메시지를 통해서만 상호작용하도록 만드는 것이다.&lt;br /&gt;밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 개체를 가리켜 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;응집도(cohesion)&lt;/span&gt;가 높다고 말한다.&lt;br /&gt;자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체의 응집도를 높이기 위해서는 객체 스스로 자신의 데이터를 책임져야 한다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;자신이 소유하고 있지 않은 데이터를 이용해 작업을 처리하는 객체에게 어떻게 연관성 높은 작업들을 할당할 수 있겠는가?&lt;br /&gt;객체는 자신의 데이터를 스스로 처리하는 자율적인 존재여야 한다. 그것이 객체의 응집도를 높이는 첫걸음이다.&lt;br /&gt;외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 것이 훌륭한 객체지향 설계를 얻을 수 있는 지름길인 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책임&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 설계에서는 독재자가 존재하지 않고 각 객체에 책임이 적절하게 분배된다.&lt;br /&gt;따라서 각 객체는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;자신을 스스로 책임&lt;/b&gt;&lt;/span&gt;진다. 객체지향 애플리케이션은 스스로 책임을 수행하는 자율적인 객체들의 공동체를 구성함으로써 완성된다.&lt;br /&gt;사실 객체지향 설계의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;적절한 객체에 적절한 책임을 할당&lt;/b&gt;&lt;/span&gt;하는 것이다.&lt;br /&gt;객체는 다른 객체와의 협력이라는 문맥안에서 특정한 역할을 수행하는 데 필요한 적절한 책임을 수행해야 한다.&lt;br /&gt;따라서 객체가 어떤 데이터를 가지느냐보다는 객체에 어떤 책임을 할당할 것이냐에 초점을 맞춰야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트레이드오프&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 어떤 기능을 설계하는 방법은 한가지 이상일 수 있다.&lt;br /&gt;둘째 동일한 기능을 한 가지 이상의 방법으로 설게할 수 있기 때문에 결국 설계는 트레이드오프의 산물이다.&lt;br /&gt;어떤 경우에도 모든 사람들을 만족시킬 수 있는 설계를 만들 수는 없다.&lt;br /&gt;설계는 균형의 예술이다. 훌륭한 설계는 적절한 트레이드오프의 결과물이라는 사실을 명심하라.&lt;br /&gt;이러한 트레이드오프 과정이 설계를 어려우면서도 흥미진진한 작업으로 만드는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의인화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체지향의 세계에 들어오면 모든 것이 능동적이고 자율적인 존재로 바뀐다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;예를 들어, Bag(가방)과 Theater(소극장)은 실세계에서는 자율적인 존재가 아니다.&lt;br /&gt;소극장에 관람객이 입장하기 위해서는 누군가가 소극장의 문을 열고 입장을 허가해줘야 한다.&lt;br /&gt;가방에서 돈을 꺼내는 것은 관람객이지 가방이 아니다.&lt;br /&gt;그럼에도 우리는 이들을 관람객이나 판매원 같은 생물처럼 다뤘다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;무생물 역시 스스로 행동하고 자기 자신을 책임지는 자율적인 존재로 취급&lt;/b&gt;&lt;/span&gt;한 것이다.&lt;br /&gt;이처럼 능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙을 가리켜 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;의인화(authropomorphism)&lt;/span&gt;라고 부른다.&lt;br /&gt;훌륭한 객체지향 설계란 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계&lt;/b&gt;&lt;/span&gt;를 가리킨다.&lt;br /&gt;그 대상이 비록 실세계에서는 생명이 없는 수동적인 존재라고 하더라도 객체지향의 세계로 넘어오는 순간 그들은 생명과 지능을 가진 싱싱한 존재로 다시 태어난다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 설계란 무엇인가?&lt;br /&gt;우리가 짜는 프로그램은 두 가지 요구사항을 만족시켜야한다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;우리는 오늘 완성해야 하는 기능을 구현하는 코드를 짜야하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야한다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;변경을 수용할 수 있는 설계가 중요한 이유는 요구사항이 항상 변경되기 때문이다.&lt;br /&gt;개발을 시작하는 시점에 구현에 필요한 모든 요구사항을 수집하는 것을 불가능에 가깝다.&lt;br /&gt;모든 요구사항을 수집할 수 있다고 가정하더라도 개발이 진행되는 동안 요구사항을 바뀔 수밖에 없다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체지향 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다.&lt;br /&gt;단순히 데이터와 프로세스를 객체라는 덩어리 안으로 밀어 넣었다고 해서 변경하기 쉬운 설계를 얻을 수 있는 것은 아니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체지향의 세계에서 애플리케이션을 객체들로 구성되며 애플리케이션의 기능은 객체들 간의 상호작용을 통해 구현된다. 그리고 객체들 사이의 상호작용은 객체 사이에 주고 받는 메시지로 표현된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;느낀점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 작가인 조영호님의 &amp;lt;객체지향의 사실과 오해&amp;gt;라는 책을 먼저 읽고나서 한참이 지난 후 다시 객체지향 프로그래밍에 대해 되짚어 볼 수 있었다.&lt;br /&gt;객체는 자기 자신의 세부정보를 캡슐화하여 구현을 숨기고, 인터페이스에 의존하여 구현하도록하여 자신의 데이터를 스스로 책임을 지도록 해야한다는 부분을 다시 되새길 수 있었다.&lt;br /&gt;회사 코드를 보고 내부 구현을 외부로 노출하는 형식의 구현이 있었나 다시 확인 해봐야겠다는 생각이 먼저 들었다.&lt;br /&gt;멀티모듈 프로젝트에 대해 고민하고 레이어드 아키텍처 기준으로 모듈을 분리하면서 ==책임===이라는게 코드수준 뿐만아니라 아키텍처 수준에서도 다시 생각하게 되는 부분이었다.&lt;/p&gt;</description>
      <category>책/오브젝트 - 조영호</category>
      <category>객체지향</category>
      <category>리뷰</category>
      <category>오브젝트</category>
      <category>조영호</category>
      <category>책</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/63</guid>
      <comments>https://brightstarit.tistory.com/63#entry63comment</comments>
      <pubDate>Thu, 4 Sep 2025 23:28:19 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Collection API 개선</title>
      <link>https://brightstarit.tistory.com/61</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;컬렉션 팩토리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 9에서는 작은 컬렉션 객체를 쉽게 만들 수 있는 몇 가지 방법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687494886139&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; friends = new ArrayList&amp;lt;&amp;gt;();
friends.add(&quot;Raphael&quot;);
friends.add(&quot;Olivia&quot;);
friends.add(&quot;Thibaut&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 문자열을 저장하는데도 많은 코드가 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Arrays.asList()&lt;/code&gt; 메서드를 이용하면 코드를 간단하게 줄일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687494959270&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; friends = Arrays.asList(&quot;Rapheal&quot;, &quot;Olivia&quot;, &quot;Thibaut&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정 크기의 리스트를 만들었으므로 요소를 갱신할 순 있지만 새 요소를 추가하거나 요소를 삭제할 순 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1687495057220&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; friends = Arrays.asList(&quot;Raphael&quot;, &quot;Olivia&quot;);
friends.set(0, &quot;Richard&quot;);
friends.add(&quot;Thibaut&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 경우&amp;nbsp;내부적으로 고정된 크기의 변환할 수 있는 배열로 구현되었기 때문에&amp;nbsp;&lt;code&gt;UnsupportedOperationException&lt;/code&gt;이 발생한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리스트(List) 팩토리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;List.of&lt;/code&gt; 팩토리 메서드를 이용해서 간단하게 리스트를 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687495220580&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; friends = List.of(&quot;Raphael&quot;, &quot;Olivia&quot;, &quot;Thibaut&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 코드에 새요소를 추가하면 &lt;code&gt;java.lang.UnsupportedOperationException&lt;/code&gt;이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 변경할 수 없는 리스트가 만들어졌기 때문이다. &lt;code&gt;set()&lt;/code&gt; 메서드로 아이템을 바꾸려해도 비슷한 예외가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 제약이 꼭 나쁜 것은 아니다. 컬렉션이 의도치 않게 변하는 것을 막을 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요소 자체가 변하는 것을 막을 수 있는 방법은 없다. 리스트를 바꿔야 하는 상황이라면 직접 리스트를 만들면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오버로딩 vs 가변 인수&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;List 인터페이스를 조금 더 살펴보면 &lt;code&gt;List.of&lt;/code&gt;의 다양한 오버로드 버전이 있다는 사실을 알 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;static &amp;lt;E&amp;gt; List&amp;lt;E&amp;gt; of(E e1, E e2, E e3, E e4)&lt;/code&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;static &amp;lt;E&amp;gt; List&amp;lt;E&amp;gt; of(E e1, E e2, E e3, E e4, E e5)&lt;br /&gt;&lt;/code&gt;여기서 다중 요소를 받을 수 있도록 다음과 같이 가변 인수를 사용하지 않았을까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;static &amp;lt;E&amp;gt; List&amp;lt;E&amp;gt; of(E... elements)&lt;/code&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부적으로 가변 인수 버전은 추가 배열을 할당해서 리스트로 감싼다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 배열을 할당하고 초기화하며 나중에 가비지 컬렉션을 하는 비용일 지불해야 한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고정된 숫자의 요소를 API로 정의하므로 이런 비용을 제거할 수 있다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;집합(Set) 팩토리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;List.of&lt;/code&gt;와 비슷한 방법으로 바꿀 수 없는 집합을 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687495933247&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set&amp;lt;String&amp;gt; friends = Set.of(&quot;Raphael&quot;, &quot;Olivia&quot;, &quot;Thibaut&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복된 요소를 제공해 집합을 만들려고 하면 요소가 중복되어 있다는 설명과 함께 IllegalArgumentException이 발생한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맵(Map) 팩토리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵을 만드는 것은 리스트나 집합을 만드는 것에 비해 조금 복잡한데 맵을 만들려면 키와 값이 있어야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열 개 이하의 키와 값 쌍을 가진 작은 맵을 만들 때는 &lt;code&gt;Map.of&lt;/code&gt;팩토리 메서드가 유용하다.&lt;/p&gt;
&lt;pre id=&quot;code_1687496161346&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, Integer&amp;gt; ageOfFriends = Map.of(&quot;Raphael&quot;, 30, &quot;Olivia&quot;, 25, &quot;Thibaut&quot;, 26);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이상의 맵에서는 &lt;code&gt;Map.Entry&amp;lt;K, V&amp;gt;&lt;/code&gt;객체를 인수로 받으며 가변 인수로 구현된 &lt;code&gt;Map.ofEntries&lt;/code&gt; 팩토리 메서드를 이용하는 것이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1687496347022&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import static java.util.Map.entry;

Map&amp;lt;String, Integer&amp;gt; ageOfFriends = Map.ofEntries(entry(&quot;Raphael&quot;, 30),
												  entry(&quot;Olivia&quot;, 25),
                                                  entry(&quot;Thibaut&quot;, 26));&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리스트와 집합 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바8에서는 List, Set 인터페이스에 다음과 같은 메서드를 추가했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;removeIf&lt;/b&gt; : 프레디케이트를 만족하는 요소를 제거한다. List나 Set을 구현하거나 그 구현을 상송받은 모든 클래스에서 이용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;replaceAll&lt;/b&gt; : 리스트에서 사용할 수 있는 기능으로 UnaryOperator 함수를 이용해 요소를 바꾼다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sort&lt;/b&gt; : List 인터페이스에서 제공하는 기능으로 리스트를 정렬한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;removeIf 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 숫자로 시작되는 참조 코드를 가진 트랜잭션을 삭제하는 코드다.&lt;/p&gt;
&lt;pre id=&quot;code_1687497583639&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(Transaction transaction : transactions) {
	if(Character.isDigit(transaction.getReferenceCode().charAt(0))){
    	transactions.remove(transaction);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;code&gt;ConcurrentModificationException&lt;/code&gt;을 일으킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 내부적으로 for-each 루프는 Iterator 객체를 사용하므로 반복자의 상태와 컬랙션의 상태가 서로 동기화되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 이 패턴은 Java 8의 &lt;code&gt;removeIf()&lt;/code&gt; 메서드로 바꿀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687497435307&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;transactions.removeIf(transaction -&amp;gt; Character.isDigit(transaction.getReferenceCode().charAt(0)));&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replaceAll 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List 인터페이스의 &lt;code&gt;replaceAll()&lt;/code&gt; 메서드를 이용해 리스트의 각 요소를 새로운 요소로 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 설명한 것처럼 컬렉션 객체를 Iterator 객체와 혼용하면 반복과 컬렉션 변경이 동시에 이루어지면서 쉽게 문제를 일으킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8의 기능을 이용하면 다음처럼 간단하게 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687500356725&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;referenceCodes.replaceAll(code -&amp;gt; Character.toUpperCase(code.charAt(0)) + code.substring(1));&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;맵 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8에서는 Map 인터페이스에 몇 가지 디폴트 메서드를 추가했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;forEach 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map 인터페이스는 BiConsumer(키와 값을 인수로 받음)를 인수로 받는 forEach 메서드를 지원하므로 코드를 조금 더 간단하게 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687511992362&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ageOfFriends.forEach((friend, age) -&amp;gt; System.out.println(friend + &quot; is &quot; + age + &quot; years old&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정렬 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 두 개의 새로운 유틸리티를 이용하면 맵의 항목을 값 또는 키를 기준으로 정렬할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entry.comparingByVaule&lt;/li&gt;
&lt;li&gt;Entry.comparingByKey&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1687512492320&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; favouriteMovies = ofEntries(entry(&quot;Raphael&quot;, &quot;Star Wars&quot;),
    entry(&quot;Cristina&quot;, &quot;Matrix&quot;),
    entry(&quot;Olivia&quot;, &quot;James Bond&quot;));
    
favouriteMovies
    .entrySet()
    .stream()
    .sorted(Entry.comparingByKey())
    .forEachOrdered(System.out::println); // 사람의 이름을 알파벳 순으로 스트림 요소를 처리한다.&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;HashMap 성능&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java 8에서는 HashMap의 내부 구조를 바꿔 성능을 개선했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존에 맵 항목은 키로 생성한 해시코드로 접근할 수 있는 버켓에 저장했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 키가 같은 해쉬코드를 반환하는 상황이 되면 O(n)의 시간이 걸리는 LinkedList로 버킷을 반환해야 하므로 성능이 저하된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최근에는 버킷이 너무 커질 경우 이를 O(log(n))의 시간이 소요되는 정렬된 트리를 이용해 동적으로 치환해 충돌이 일어나는 요소 반환 성능을 개선했다. 하지만 키가 String, Number 클래스 같은 Comparable의 형태여야만 정렬된 트리가 지원된다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;getOrDefault 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 찾으려는 키가 존재하지 않으면 널이 반환되므로 &lt;code&gt;NullPointerException&lt;/code&gt;을 방지하려면 요청 결과가 널인지 확인해야 한다. 기본값을 반환하는 방식으로 이 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getOrDefault()&lt;/code&gt; 메서드를 이용하면 쉽게 이 문제를 해결할 수 있다. 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받으며 맵에 키가 존재하지 않으면 두 번째 인수로 받은 기본값을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1687513096121&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; favouriteMovies = ofEntries(entry(&quot;Raphael&quot;, &quot;Star Wars&quot;),
    entry(&quot;Cristina&quot;, &quot;Matrix&quot;),
    entry(&quot;Olivia&quot;, &quot;James Bond&quot;));

System.out.println(favouriteMovies.getOrDefault(&quot;Olivia&quot;, &quot;Matrix&quot;)); // James Bond 출력
System.out.println(favouriteMovies.getOrDefault(&quot;Thibaut&quot;, &quot;Matrix&quot;)); // Matrix 출력&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계산 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵에 키가 존재하는지 여부에 따라 어떤 동작을 실행하고 결과를 저장해야 하는 상황이 필요한 때가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 세 가지 연산이 이런 상황에서 도움을 준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;computeIfAbsent&lt;/b&gt; : 제공된 키에 해당하는 값이 없으면(값이 없거나 널), 키를 이용해 새 값을 계산하고 맵에 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;computeIfPresent&lt;/b&gt; : 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;compute&lt;/b&gt; : 제공된 키로 새 값을 계산하고 맵에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;computeIfAbsent()&lt;/code&gt;는 키가 존재하지 않으면 값을 계산해 맵에 추가하고 키가 존재하면 기존 값을 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1687513762799&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; friendsToMovies = new HashMap&amp;lt;&amp;gt;();
friendsToMovies.computeIfAbsent(&quot;Raphael&quot;, name -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(&quot;Star Wars&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;computeIfPresent()&lt;/code&gt;는 현재 키와 관련된 값이 맵에 존재하며 널이 아닐 때만 새 값을 계산한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삭제 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 8에서는 키가 특정한 값과 연관되었을 때만 항목을 제거하는 오버로드 버전 메서드를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1687513969160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;favouriteMovies.remove(key, value);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교체 패턴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;replaceAll&lt;/b&gt; : BiFucntion을 적용한 결과로 각 항목의 값을 교체한다. 이 메서드는 이전에 살펴본 List의 replaceAll과 비슷한 동작을 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replace&lt;/b&gt; : 키가 존재하면 맵의 값을 바꾼다. 키가 특정 값으로 매핑되었을 때만 값을 교체하는 오버로드 버전도 있다.\&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1687514501077&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; favouriteMovies = new HashMap&amp;lt;&amp;gt;(); //replaceAll을 적용할 것이므로 바꿀 수 있는 맵을 사용해야 한다.
favouriteMovies.put(&quot;Raphael&quot;, &quot;Star Wars&quot;);
favouriteMovies.put(&quot;Olivia&quot;, &quot;james bond&quot;);
favouriteMovies.replaceAll((friend, movie) -&amp;gt; movie.toUpperCase());
System.out.println(favouriteMovies); // Olivia=JAMES BOND, Raphael=STAR WARS&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;합침&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 그룹의 연락처를 포함하는 두 개의 맵을 합친다고 가정하자. 다음처럼 putAll()을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687515119460&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; family = ofEntries(entry(&quot;Teo&quot;, &quot;Star Wars&quot;),
    entry(&quot;Cristina&quot;, &quot;James Bond&quot;));
Map&amp;lt;String, String&amp;gt; friends = ofEntries(entry(&quot;Raphael&quot;, &quot;Star Wars&quot;));
Map&amp;lt;String, String&amp;gt; everyone = new HashMap&amp;lt;&amp;gt;(family);
everyone.putAll(friends); // friends의 모든 항목을 everyone으로 복사
System.out.println(everyone); // Cristina=James Bond, Raphael=Star Wars, Teo=Star Wars&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복된 키가 없다면 위 코드는 잘 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 좀 더 유연하게 합쳐야 한다면 &lt;code&gt;merge()&lt;/code&gt; 메서드를 이용할 수 있다. 이 메서드는 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;family와 friends 두 맵 모두에 Cristina가 다른 영화 값으로 존재한다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1687515585257&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; family = ofEntries(entry(&quot;Teo&quot;, &quot;Star Wars&quot;),
    entry(&quot;Cristina&quot;, &quot;James Bond&quot;));
Map&amp;lt;String, String&amp;gt; friends = ofEntries(entry(&quot;Raphael&quot;, &quot;Star Wars&quot;),entry(&quot;Cristina&quot;, &quot;Matrix&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;forEach()&lt;/code&gt;와 &lt;code&gt;merge()&lt;/code&gt; 메서드를 이용해 충돌을 해결할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687515659207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt; everyone = new HashMap&amp;lt;&amp;gt;(family);
friends.forEach((k, v) -&amp;gt; everyone.merge(k, v, (movie1, movie2) -&amp;gt; movie1 + &quot; &amp;amp; &quot; + movie2)); // 중복된 키가 있으면 두 값을 연결
System.out.println(everyone); //Raphael=Star Wars, Cristina=James Bond &amp;amp; Matrix, Teo=Star Wars&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ConcurrentHashMap&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentHashMap 클래스는 동시성 친화적이며 최신 기술을 반영한 HashMap 버전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentHashMap은 내부 자료구조의 특정 부분만 잠궈 동시 추가, 갱신 작업을 허용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 동기화된 Hashtable 버전에 비해 읽기 쓰기 연산 성능이 월등하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리듀스와 검색&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;forEach&lt;/b&gt; : 각 (키,값) 쌍에 주어진 액션을 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;reduce&lt;/b&gt; : 모든 (키, 쌍) 쌍을 제공된 리듀스 함수를 이용해 결과로 합침&lt;/li&gt;
&lt;li&gt;&lt;b&gt;search&lt;/b&gt; : 널이 아닌 값을 반환할 때까지 각 (키, 값) 쌍에 함수를 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 키에 함수 받기, 값, Map.Entry, (키, 값) 인수를 이용한 네 가지 연산 형태를 지원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키 값으로 연산(forEach, reduce, search)&lt;/li&gt;
&lt;li&gt;키로 연산(forEachKey, reduceKeys, searchKeys)&lt;/li&gt;
&lt;li&gt;값으로 연산(forEachValue, reduceValues, searchValues)&lt;/li&gt;
&lt;li&gt;Map.Entry 객체로 연산(forEachEntry, reduceEntries, searchEntries)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들 연산은 ConcurrentMap의 상태를 잠그지 않고 연산을 수행한다는 점을 주목하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이들 연산에 제공한 함수는 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존하지 않아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이들 연산에 병렬성 기준값을 지정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵의 크기가 주어진 기준값보다 작으면 순차적으로 연산을 실행한다. 기준값을 1로 지정하면 공통 스레드 풀을 이용해 병렬성을 극대화한다. Long.MAX_VALUE를 기준값으로 설정하면 한 개의 스레드로 연산을 실행한다. 여러분의 소프트웨어 아키텍처가 고급 수준의 자원 활용 최적화를 사용하고 있지 않다면 기준값 규칙을 따르는 것이 좋다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;계수&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentHashMap 클래스는 맵의 매핑 개수를 반환하는 &lt;code&gt;mappingCount&lt;/code&gt; 메서드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 size 메서드 대신 새 코드에선 int를 반환하는 &lt;code&gt;mappingCount&lt;/code&gt; 메서드를 사용하는 것이 좋다. 그래야 매핑의 개수가 int의 범위를 벗어너는 이후의 상황을 대처할 수 있기 때문이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;집합뷰&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConccurentHashMap 클래스는 ConcurrentHashMap을 집합 뷰로 반환하는 keySet이라는 새 메서드를 제공한다. 맵을 바꾸면 집합도 바뀌고 반대로 집합을 바꾸면 맵도 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;newKeySet이라는 새 메서드를 이용해 ConcurrentHashMap으로 유지되는 집합을 만들 수도 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료 : &lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687516862402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;모던 자바 인 액션 | 라울-게이브리얼 우르마 | 한빛미디어- 교보ebook&quot; data-og-description=&quot;람다, 스트림, 함수형, 리액티브 프로그래밍으로 새로워진 자바 마스터하기, ★ 완전히 새로워진 자바 8, 9, 10의 기능을 속 시원하게 배우자! 이 책은 자바 최신 기능을 애플리케이션에 실용적으&quot; data-og-host=&quot;ebook-product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; data-og-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/caB2At/hyS5z9mk9K/rmP0wdDnYuCVNxYQd1HinK/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/k9sCH/hyS5yWUr5r/aVrDN0qiccIbmS8DreA8rK/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/OXqRS/hyS5DDUuzP/QOyQONeen9wFuiHEwNUU20/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=580_232_620_275&quot;&gt;&lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/caB2At/hyS5z9mk9K/rmP0wdDnYuCVNxYQd1HinK/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/k9sCH/hyS5yWUr5r/aVrDN0qiccIbmS8DreA8rK/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/OXqRS/hyS5DDUuzP/QOyQONeen9wFuiHEwNUU20/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=580_232_620_275');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;모던 자바 인 액션 | 라울-게이브리얼 우르마 | 한빛미디어- 교보ebook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;람다, 스트림, 함수형, 리액티브 프로그래밍으로 새로워진 자바 마스터하기, ★ 완전히 새로워진 자바 8, 9, 10의 기능을 속 시원하게 배우자! 이 책은 자바 최신 기능을 애플리케이션에 실용적으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ebook-product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/Java</category>
      <category>Collection</category>
      <category>java8</category>
      <category>모던자바인액션</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/61</guid>
      <comments>https://brightstarit.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 23 Jun 2023 19:42:16 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 병렬 스트림 (parallel stream)</title>
      <link>https://brightstarit.tistory.com/60</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;병렬 스트림이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;병렬 스트림&lt;/span&gt;이란 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 병렬 스트림을 이용하면 모든 멀티코어 프로세서가 각각의 청크를 처리하도록 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 숫자 n을 인수로 받아서 1부터 n까지의 모든 숫자의 합계를 반환하는 메서드를 구현한다했을 때,&lt;/p&gt;
&lt;pre id=&quot;code_1687422468906&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long sequentialSum(long n) {
    return Stream.iterate(1L, i -&amp;gt; i + 1) // 무한 자연수 스티림 생성
        .limit(n) // n개 이하로 제한
        .reduce(0L, Long::sum); // 모든 숫자를 더하는 스트림 리듀싱 연산
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 코드에 n이 커진다면 이 연산을 병렬로 처리하는 것이 좋을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 스트림을 이용하면 병렬 처리와 관련된 문제들을 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;순차 스트림을 병렬 스트림으로 변환&lt;/h2&gt;
&lt;pre id=&quot;code_1687423022815&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public long parallelSum(long n) {
    return Stream.iterate(1L, i -&amp;gt; i + 1)
        .limit(n)
        .parallel() // 스트림을 병렬 스트림으로 변환
        .reduce(0L, Long::sum);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 리듀싱 연산으로 스트림의 모든 숫자를 더한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드와 다른 점은 스트림이 여러 청크로 분할되어 있다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSIcde/btskZzuTSpa/1n4zvhwhpwv9Sul4DoNadK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSIcde/btskZzuTSpa/1n4zvhwhpwv9Sul4DoNadK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSIcde/btskZzuTSpa/1n4zvhwhpwv9Sul4DoNadK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSIcde%2FbtskZzuTSpa%2F1n4zvhwhpwv9Sul4DoNadK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;457&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 순차 스트림에 &lt;code&gt;parallel&lt;/code&gt;을 호출해도 스트림 자체에는 아무 변화도 일어나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로는 &lt;code&gt;parallel&lt;/code&gt;을 호출하면 이후 연산이 병렬로 수행해야 함을 의미하는 불리언 플래그가 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 &lt;code&gt;sequential&lt;/code&gt;로 병렬 스트림을 순차 스트림으로 바꿀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1687423263591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;stream.parallel()
	  .filter(...)
      .sequential()
      .map(...)
      .parallel()
      .reduce();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parallel과 sequential 두 메서드 중 최종적으로 호출된 메서드가 전체 파이프라인에 영향을 미친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우 마지막 호출은 parallel이므로 파이프라인은 전체적으로 병렬로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;병렬 스트림에서 사용하는 스레드 풀 설정&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;병렬 스트림은 내부적으로 ForkJoinPool을 사용한다. 기본적으로 ForkJoinPool은 프로세서 수, 즉 Runtime.getRuntime(), availableProcessors()가 반환하는 값에 상응하는 스레드를 갖는다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;System.setProperty(&quot;java.util.concurrent.ForkJoinPool.common.parallelism&quot;, &quot;12&quot;);&lt;/code&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 예제는 전역 설정 코드이므로 이후의 모든 병렬 스트림 연산에 영향을 준다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재는 하나의 병렬 스트림에 사용할 수 있는 특정한 값을 지정할 수 없다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 기기의 프로세서 수와 같으므로 특별한 이유가 없다면 ForkJoinPool의 기본값을 그대로 사용할 것을 권장한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for 루프를 사용하는 경우와, 순차적 스트림을 사용하는 경우, 병렬 스트림을 사용하는 경우를 비교해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1687423900061&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long iterativeSum(long n) {
      long result = 0;
      for (long i = 1L; i&amp;lt;=n; i++) {
        result += i;
      }
      return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for 루프를 사용해 반복하는 방법이 더 저수준으로 동작할 뿐 아니라 특히 기본값을 박싱 하거나 언박싱할 필요가 없으므로 더 빠를 것이라 예상할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;45&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qn0iU/btskULpKopW/ke5CikpPi6dNzul7PLKvB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qn0iU/btskULpKopW/ke5CikpPi6dNzul7PLKvB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qn0iU/btskULpKopW/ke5CikpPi6dNzul7PLKvB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQn0iU%2FbtskULpKopW%2Fke5CikpPi6dNzul7PLKvB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;45&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;45&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mRpBx/btskUMhSb1I/szd1fl4oxxjfUmoqCs0no1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mRpBx/btskUMhSb1I/szd1fl4oxxjfUmoqCs0no1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mRpBx/btskUMhSb1I/szd1fl4oxxjfUmoqCs0no1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmRpBx%2FbtskUMhSb1I%2Fszd1fl4oxxjfUmoqCs0no1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;48&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결과가 for 루프를 사용한 결과고 아래가 순차적 스트림을 사용한 결과이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 순차적 스트림을 사용하는 버전에 비해 거의 4배가 빠르다는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 스트림을 사용하는 버전은 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dY2N2U/btskZsbDuGc/37t2DwuMNh6La92rvpnuqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dY2N2U/btskZsbDuGc/37t2DwuMNh6La92rvpnuqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dY2N2U/btskZsbDuGc/37t2DwuMNh6La92rvpnuqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdY2N2U%2FbtskZsbDuGc%2F37t2DwuMNh6La92rvpnuqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;47&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 버전이 순차 버전에 비해 다섯 배나 느린 실망스러운 결과가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이러한 결과가 나올까? 다음의 두 가지 문제를 발견할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복 결과로 박싱 된 객체가 만들어지므로 숫자를 더하려면 언박싱을 해야 한다.&lt;/li&gt;
&lt;li&gt;반복 작업은 병렬로 수행할 수 있는 독립 단위로 나누기가 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 문제는 &lt;code&gt;iterate&lt;/code&gt;연산은 이전 연산 결과에 따라 다음 함수의 입력이 달라지기 때문에 청크로 분할하기 어렵다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MeUSc/btskUMPG4LQ/MyE8y9JWSxpVW3DdSphukk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MeUSc/btskUMPG4LQ/MyE8y9JWSxpVW3DdSphukk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MeUSc/btskUMPG4LQ/MyE8y9JWSxpVW3DdSphukk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMeUSc%2FbtskUMPG4LQ%2FMyE8y9JWSxpVW3DdSphukk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;241&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;리듀싱 과정을 시작하는 지점에 전체 수자 리스트가 준비되지 않았으므로 스트림을 병렬로 처리할 수 있도록 청크로 분할할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림이 병렬로 처리되도록 지시했고 각각의 합계가 다른 스레드에서 수행되었지만 결국 순차처리 방식과 크게 다른 점이 없으므로 스레드를 할당하는 오버헤드만 증가하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 병렬 프로그래밍은 까다롭고 때로는 이해하기 어려운 함정이 숨어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 프로그래밍을 오용하면 오히려 전체 프로그램의 성능이 더 나빠질 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제는 &lt;code&gt;LongStream.rangeClosed&lt;/code&gt;라는 메서드를 통해 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 &lt;code&gt;iterate&lt;/code&gt;에 비해 다음과 같은 장점을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본형 long을 직접 사용하므로 박싱과 언박싱 오버헤드가 사라진다.&lt;/li&gt;
&lt;li&gt;쉽게 청크로 분할할 수 있는 숫자 범위를 생산한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1687424911269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long rangedSum(long n) {
    return LongStream.rangeClosed(1, n)
        .reduce(0L, Long::sum);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CAyB1/btskT17foEn/CXhEPK6Ob2izHb8Zqxj7A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CAyB1/btskT17foEn/CXhEPK6Ob2izHb8Zqxj7A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CAyB1/btskT17foEn/CXhEPK6Ob2izHb8Zqxj7A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCAyB1%2FbtskT17foEn%2FCXhEPK6Ob2izHb8Zqxj7A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;47&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 &lt;code&gt;iterate&lt;/code&gt;를 사용한 버전보다 스트림 처리 속도가 더 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;특화되지 않은 스트림을 처리할 때는 오토박싱, 언박싱 등의 오버헤드를 수반하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라서는 어떤 알고리즘을 병렬화하는 것보다 적절한 자료구조를 선택하는 것이 더 중요하다는 사실을 단적으로 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 병렬 스트림을 적용하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1687425190033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long parallelRangedSum(long n) {
    return LongStream.rangeClosed(1, n)
        .parallel()
        .reduce(0L, Long::sum);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj3uEd/btskXO0PuWL/eDCikIkhTBwDCK7duX9yGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj3uEd/btskXO0PuWL/eDCikIkhTBwDCK7duX9yGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj3uEd/btskXO0PuWL/eDCikIkhTBwDCK7duX9yGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj3uEd%2FbtskXO0PuWL%2FeDCikIkhTBwDCK7duX9yGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;48&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 순차 실행보다 빠른 성능을 갖는 병렬 리듀싱을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;병렬화가 완전 공짜는 아니라는 사실을 기억하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬화를 이용하려면 스트림을 재귀적으로 분할해야 하고, 각 서브 스트림을 서로 다른 스레드의 리듀싱 연산으로 할당하고, 이들 결과를 하나의 값으로 합쳐야 한다. 멀티코어 간의 데이터 이동은 우리 생각보다 비싸다. 따라서 코어 간의 데이터 전송 시간보다 훨씬 오래 걸리는 작업만 병렬로 다른 코어에서 수행하는 것이 바람직하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병렬 스트림의 잘못된 사용&lt;/h2&gt;
&lt;pre id=&quot;code_1687426415509&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long sideEffectSum(long n) {
    Accumulator accumulator = new Accumulator();
    LongStream.rangeClosed(1,n).forEach(accumulator::add);
    return accumulator.total;
}
  
public class Accumulator {
    public long total = 0;
    public void add (long value) {total += value;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 n까지의 자연수를 더하면서 공유된 누적자(total)를 바꾸는 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 본질적으로 순차 실행할 수 있도록 구현되어 있으므로 병렬로 실행하면 참사가 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 total을 접근할 때마다 데이터 레이스 문제가 일어난다. 동기화로 문제를 해결하다 보면 결국 병렬화라는 특성이 없어져 버릴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 메서드의 성능은 둘째 치고, 올바른 결괏값이 나오지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 스레드에서 동시에 누적자, 즉 &lt;code&gt;total += value&lt;/code&gt;를 실행하면서 이런 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 여러 스레드에서 공유하는 객체의 상태를 바꾸는 forEach 블록 내부에서 add 메서드를 호출하면서 이 같은 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제처럼 병렬 스트림을 사용했을 때 이상한 결과에 당황하지 않으려면 &lt;span style=&quot;color: #ee2323;&quot;&gt;상태 공유에 따른 부작용을 피해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병렬 스트림 사용 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양을 기준으로 병렬 스트림 사용을 결정하는 것은 적절하지 않다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확신이 서지 않으면 직접 측정하라. 순차 스트림을 병렬 스트림으로 쉽게 바꿀 수 있다.&lt;/li&gt;
&lt;li&gt;박싱을 주의하라. 오토박싱 와 언박싱은 성능을 크게 저하시킬 수 있는 요소다. 기본형 특화 스트림을 통해 박싱 동작을 피할 수 있다. (IntStream, LongStream, DoubleStream)&lt;/li&gt;
&lt;li&gt;순차 스트림보다 병렬 스트림에서 성능이 떨어지는 연산이 있다. 특히 limit이나 findFirst처럼 요소의 순선에 의존하는 연산을 병렬 스트림에서 수행하려면 비싼 비용을 치러야 한다.&lt;/li&gt;
&lt;li&gt;스트림에서 수행하는 전체 파이프라인 연산 비용을 고려하라.&lt;/li&gt;
&lt;li&gt;소량의 데이터에서는 병렬 스트림이 도움 되지 않는다.&lt;/li&gt;
&lt;li&gt;스트림을 구성하는 자료구조가 적절한지 확인하라. 예를 들어 ArrayList를 LinkedList보다 효율적으로 분할할 수 있다.&lt;/li&gt;
&lt;li&gt;스트림의 특성과 파이프라인의 중간 연산이 스트림의 특성을 어떻게 바꾸는지에 따라 분해 과정의 성능이 달라질 수 있다.&lt;/li&gt;
&lt;li&gt;최종 연산의 병합 과정(예를 들면 Collector의 combiner 메서드) 비용을 살펴보라. 별합 과정의 비용이 비싸다면 병렬 스트림으로 얻은 성능의 이익이 서브스트림의 부분 결과를 합치는 과정에서 상쇄될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nkdd1/btsk1TMOjGj/YCuw5rmwkB1yznfOU4ECV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nkdd1/btsk1TMOjGj/YCuw5rmwkB1yznfOU4ECV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nkdd1/btsk1TMOjGj/YCuw5rmwkB1yznfOU4ECV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNkdd1%2Fbtsk1TMOjGj%2FYCuw5rmwkB1yznfOU4ECV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;197&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료 : &lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687429401429&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;모던 자바 인 액션 | 라울-게이브리얼 우르마 | 한빛미디어- 교보ebook&quot; data-og-description=&quot;람다, 스트림, 함수형, 리액티브 프로그래밍으로 새로워진 자바 마스터하기, ★ 완전히 새로워진 자바 8, 9, 10의 기능을 속 시원하게 배우자! 이 책은 자바 최신 기능을 애플리케이션에 실용적으&quot; data-og-host=&quot;ebook-product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; data-og-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDDI4y/hyS4pGkq3z/3w6WV2dEVYq6bREDDIcy51/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/CidX7/hyS4xxDrrd/nJ7Q7H3k4nkNM0Xg5hlk8k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/NazSD/hyS5BE9Tx1/hVJQzXkXzaRyCmnuhzaG0k/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=580_232_620_275&quot;&gt;&lt;a href=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ebook-product.kyobobook.co.kr/dig/epd/ebook/E000002942391&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDDI4y/hyS4pGkq3z/3w6WV2dEVYq6bREDDIcy51/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/CidX7/hyS4xxDrrd/nJ7Q7H3k4nkNM0Xg5hlk8k/img.jpg?width=380&amp;amp;height=488&amp;amp;face=274_109_296_133,https://scrap.kakaocdn.net/dn/NazSD/hyS5BE9Tx1/hVJQzXkXzaRyCmnuhzaG0k/img.jpg?width=800&amp;amp;height=1027&amp;amp;face=580_232_620_275');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;모던 자바 인 액션 | 라울-게이브리얼 우르마 | 한빛미디어- 교보ebook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;람다, 스트림, 함수형, 리액티브 프로그래밍으로 새로워진 자바 마스터하기, ★ 완전히 새로워진 자바 8, 9, 10의 기능을 속 시원하게 배우자! 이 책은 자바 최신 기능을 애플리케이션에 실용적으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ebook-product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/Java</category>
      <category>Java</category>
      <category>java8</category>
      <category>병렬스트림</category>
      <category>스트림</category>
      <author>밝은별 개발자</author>
      <guid isPermaLink="true">https://brightstarit.tistory.com/60</guid>
      <comments>https://brightstarit.tistory.com/60#entry60comment</comments>
      <pubDate>Thu, 22 Jun 2023 19:24:47 +0900</pubDate>
    </item>
  </channel>
</rss>