<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Eternity's Chit-Chat</title>
    <link>https://eternity-object.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 20:02:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>eternity.object</managingEditor>
    <image>
      <title>Eternity's Chit-Chat</title>
      <url>https://tistory1.daumcdn.net/tistory/7007623/attach/cccddcc7b14e4512a28b8f6ba46e4ea2</url>
      <link>https://eternity-object.tistory.com</link>
    </image>
    <item>
      <title>클라우드 네이티브 스프링 인 액션과 읽기 쉬운 코드</title>
      <link>https://eternity-object.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #080809; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;괜찮아 보이는 개발 서적이 있으면 당장 필요하지 않더라도 '나중에 읽겠지' 하는 마음으로 일단 사두는 편입니다. 그러다 보니 쌓인 책들을 주기적으로 몰아서 읽는 시간을 갖곤 하는데, 작년 12월이 바로 그런 시간이었습니다. 한 달 정도 여유를 갖고 방치해두었던 책 몇 권을 읽었고, 그중 인상 깊었던 두 권을 소개합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;책.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ouAG/dJMb99LVjYL/QRO9Cvok3s89fTeXb1ayQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ouAG/dJMb99LVjYL/QRO9Cvok3s89fTeXb1ayQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ouAG/dJMb99LVjYL/QRO9Cvok3s89fTeXb1ayQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ouAG%2FdJMb99LVjYL%2FQRO9Cvok3s89fTeXb1ayQk%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;411&quot; height=&quot;308&quot; data-filename=&quot;책.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;첫 번째는 《클라우드 네이티브 스프링 인 액션(제이펍)》입니다. 이 책은 구성이 체계적이고 완급 조절이 정말 뛰어납니다. Spring Boot를 시작으로 다양한 Spring Cloud 프로젝트, 도커, k8s까지 방대한 기술을 연이어 다루는데, 이해에 부족함이 없도록 상세히 서술하면서도 너무 깊어지지 않게 불필요한 내용은 덜어내는 저자의 능력이 감탄스러울 정도였습니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;책의 구성 또한 훌륭합니다. 다양한 기술을 적절한 타이밍에 도입하여 전체 챕터를 부드럽게 연결하면서도, 난이도가 급격히 상승하지 않도록 밸런스를 유지하는 감각이 탁월합니다. 많은 책이 후반부로 갈수록 난이도 조절에 실패하곤 하는데, 그런 면에서 이 책은 구성에 상당히 공을 들였음을 알 수 있습니다. 책을 써본 경험이 있는 입장에서, 이러한 구성의 완성도는 놀라움을 넘어 부러움이 들 정도였습니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;다만 난이도가 다소 높아 주니어 개발자가 바로 소화하기에는 무리가 있을 수 있으며, 어느 정도 사전 지식을 갖춘 분들에게 적합해 보입니다. 그럼에도 하나의 시스템을 완성하기 위해 고려해야 할 다양한 측면을 실용적으로 풀어냈다는 점에서, 주니어들에게도 충분히 도전해 볼 만한 가치가 있는 매력적인 책입니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;두 번째는 《읽기 쉬운 코드(길벗)》입니다. 앞선 책이 구성 면에서 감탄을 주었다면, 이 책은 개발을 바라보는 독특하고 유연한 시선이 즐거웠습니다. 원제인 *'The Code That Fits in Your Head'*처럼, 인간이 기억하기 쉽고 이해하기 편한 코드를 작성하는 데 필요한 원칙과 휴리스틱을 다룹니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;각 챕터의 주제는 어디선가 본 것처럼 익숙하지만, 이를 풀어가는 방식은 매우 개성적입니다. 특히 개발을 '인지 과학' 측면에서 설명하는 부분은 평소 틀에 박힌 사고방식에 갇혀 있던 저 자신을 되돌아보게 할 만큼 재미있었습니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;주니어분들에게도 충분이 재미있게 느껴질 책이지만 다루는 범위가 넓다 보니 주니어분들보다는 어느 정도 개발 경험이 쌓인 분들이 얻어갈게 더 많을 것 같습니다. 챕터가 넘어갈 때마다 다루는 문제의 초점이 빠르게 이동해 자칫 전체 주제를 놓치기 쉽다는 점은 조금 아쉬웠지만(이번에 시간이 충분치 않아 빠르게 훑어 읽느라 저만 그렇게 느꼈을 수도 있습니다), 오랜만에 새로운 시각에서 개발을 바라볼 수 있게 해주었다는 점에서 꽤나 즐거운 책이었습니다.&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080809; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: start;&quot;&gt;올 해는 생각보다 책을 많이 읽지 못했는데 2026년에는 좀 더 많은 책을 읽을 수있도록 노력해야겠습니다.&lt;/div&gt;
&lt;/div&gt;</description>
      <category>일상</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/46</guid>
      <comments>https://eternity-object.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 22 Jan 2026 19:59:11 +0900</pubDate>
    </item>
    <item>
      <title>설계와 비용</title>
      <link>https://eternity-object.tistory.com/45</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 강의 소식을 궁금해하시는 분들이 종종 계시는데, 현재 작업 중인 강의는 '유지보수성 향상'에 초점을 맞추고 있습니다. 이번 강의 역시 평소처럼 객체지향 설계 관점에서 코드를 이해하고, 수정하기 쉽게 만드는 기법들을 정리하려고 합니다. 개인적으로 병행하는 일이 있어서 작업 속도가 다소 느리지만, 분량이 많지는 않아서 조만간 마무리할 수 있을 것 같습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번에는&amp;nbsp;기존과는&amp;nbsp;조금&amp;nbsp;다르게&amp;nbsp;'패턴&amp;nbsp;언어(Pattern&amp;nbsp;Language)' 방식으로 설계 기법을 정리하고 있는데요. 최근에는 언급되는 빈도가 낮아졌지만 다양한 원칙, 기법, 프랙티스들의 관계와 혼용 방법을 체계화하고 설명할 수 있는 유용한 도구라고 할 수 있습니다. 예전부터 패턴 언어 형식을 빌려 강의 자료를 만들고 싶다는 생각을 품고 있었는데 이번 강의를 만들면서 즐거운 마음으로 시도해 보고 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금 준비하고 있는 강의와는 별개로 유지보수성과 관련된 최근의 고민을 덧붙이자면 요즘 들어 AI와 설계의 관계를 다루는 강의를 만들어보라는 요청이 종종 들어오고 있습니다. AI 시대에서는 설계가 중요하지 않다는 이야기를 빈번하게 듣다보니 한번 만들어보고 싶기는 합니다. 다만 아직까지는 설계 관점에서 AI를 어떻게 활용해야 하는지 실험해 보는 단계다 보니 아직 본격적으로 작업을 시작하지는 못하고 있습니다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;female-hand-putting-coins-stack.jpg&quot; data-origin-width=&quot;7360&quot; data-origin-height=&quot;4912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eHB0lQ/dJMcahiUu5T/jXQxJyRCPuvExty2MhR8H0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eHB0lQ/dJMcahiUu5T/jXQxJyRCPuvExty2MhR8H0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eHB0lQ/dJMcahiUu5T/jXQxJyRCPuvExty2MhR8H0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeHB0lQ%2FdJMcahiUu5T%2FjXQxJyRCPuvExty2MhR8H0%2Fimg.jpg&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;239&quot; height=&quot;160&quot; data-filename=&quot;female-hand-putting-coins-stack.jpg&quot; data-origin-width=&quot;7360&quot; data-origin-height=&quot;4912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;설계는 유지보수성과 관련이 있고, 유지보수성 저하는 비용 증가로 귀결되기 때문에&lt;/b&gt;&amp;nbsp;&lt;b&gt;설계는&amp;nbsp;여전히&amp;nbsp;중요하고,&amp;nbsp;어떻게&amp;nbsp;보면&amp;nbsp;더&amp;nbsp;중요해졌다고&amp;nbsp;볼 수 있습니다&lt;/b&gt;. 단위 시간에 생산되는 코드의 양이 증가했기 때문에 이 코드들을 유지보수하는 비용을 컨트롤하지 못하게 될 경우 생산성이 급격하게 저하될 수 있기 때문입니다. 케이스별로 AI에게 전적으로 위임할지, 주도권을 가지고 직접 드라이브할 지 결정하기 위해서는 프로덕트와 도메인과 코드의 구조에 대한 날카로운 시각을 유지하는 것이 중요합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;AI에게 위임할 수록 AI가 설계를 결정할 수 있도록 적절한 컨텍스트를 제공하고 코드의 품질을 판단할 수 있는 설계 지식이 필요할 수 밖에 없습니다. 결국 우리가 설계를 개선하는 이유는, 변화하는 사용자의 요구사항에 가장 기민하게 대응할 수 있는 상태를 만들고 싶기 때문이니까요.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만들고 싶은 자료들이 많은데 조금씩 만들어가다 보면 언젠가는 전체적인 그림을 완성할 수 있을 거라고 생각합니다. 우선 지금 만들고 있는 강의부터 마무리해야겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>설계 이야기</category>
      <category>AI</category>
      <category>설계</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/45</guid>
      <comments>https://eternity-object.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 22 Jan 2026 19:33:10 +0900</pubDate>
    </item>
    <item>
      <title>소프트웨어 아키텍처</title>
      <link>https://eternity-object.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소프트웨어 공학의 선구자들은 소프트웨어 개발이라는 미숙아를 양육하기 위한 정신적 모델을 구축하기 위해 상대적으로 성숙한 다른 공학 분야로부터 다양한 용어와 개념을 차용해 왔다. 소프트웨어 공학에 영향을 끼친 분야 중에서도 가장 중요한 메타포를 제공한 분야는 건축학이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;arch.jpg&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q0k2X/btsOFLgIwou/JhtuwuhyenavdiGjs6YFek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q0k2X/btsOFLgIwou/JhtuwuhyenavdiGjs6YFek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q0k2X/btsOFLgIwou/JhtuwuhyenavdiGjs6YFek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq0k2X%2FbtsOFLgIwou%2FJhtuwuhyenavdiGjs6YFek%2Fimg.jpg&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;395&quot; height=&quot;312&quot; data-filename=&quot;arch.jpg&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1028&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;건축학으로부터 차용한 용어 중 가장 많은 사람들에 의해 정의된 용어가 바로 &amp;lsquo;소프트웨어 아키텍처(Software Architecture)&amp;rsquo;일 것이다. 다양한 정의가 존재한다는 것은 역설적으로 용어를 사용하는 사람들 사이에 공감대를 거의 형성하지 못하고 있다는 이야기와 같다. 소프트웨어 아키텍처에 대한 수 많은 정의 중에서 비교적 널리 알려진 것은 아래 정의일 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로그램이나 컴퓨팅 시스템의 소프트웨어 아키텍처는 소프트웨어 구성요소와 그들이 지니고 있는 특성 중에 외부에 드러나는 특성, 그리고 구성요소들의 관계를 표현하는 시스템의 구조나 구조체를 말한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;-&amp;nbsp;폴 클레멘츠, Software Architecture in Practice&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: right;&quot; 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: #000000;&quot;&gt;간단한 정의를 통해 소프트웨어 아키텍처의 실체를 파악하는 것은 사실상 불가능하다. 따라서 소프트웨어 아키텍처란 무엇인가에 대한 격한 논쟁의 소용돌이에 휘말리는 것 보다는 소프트웨어 아키텍처라는 말에서 풍겨지는 은은한 향기와 뉘앙스를 음미하는 것이 올바른 길이라고 생각한다.&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;color: #000000;&quot;&gt;소프트웨어 아키텍처를 설명하는 많은 자료들을 찾아 보면 공통적으로 다음과 같은 특징들에 관해 언급하고 있음을 알 수 있다.&lt;/span&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: #000000;&quot;&gt;소프트웨어 아키텍처는 상위 수준의 설계이며 시스템을 부분으로 분할한 것이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;생명주기&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;초기의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;설계&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;결정사항을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;반영하며&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;상세&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;설계&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구현&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;통합&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;테스트&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;작업의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기반을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;마련한다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;변경이&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;어려우며&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;변경&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;시&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;큰&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;비용이&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수반된다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;모든&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이해관계자들이&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동의하고&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;받아들여야&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;공통적인&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;비전&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;으로&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;모든&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이해관계자들이&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이해할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;있도록&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;공유되어야&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하며&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의사소통의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수단으로&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용되어야&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;한다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;주로&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기능적&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;요구사항&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;보다는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;비기능적&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;요구사항&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;예를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;들면&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;품질&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;영향을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;받는다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;한&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가지&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;방식으로만&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;표현할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;없으며&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다양한&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이해관계자의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;요구사항을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수용할&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;수&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;있도록&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;적어도&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;정적인&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구조와&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동적인&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구조를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;포함하는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다수의&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;뷰&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;view&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용해서&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;표현한다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&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; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아키텍처는&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;조직&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구조로부터&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;영향을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;받기도&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하고&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;조직&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구조에&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;영향을&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;미치기도&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;한다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최고(the best)의 소프트웨어 아키텍처가 아니라 우수한(the good) 소프트웨어 아키텍처를 얻기 위해 노력해야 한다. 모든 요구사항을 만족시키려 하지 말고 우선순위 수립 후 이를 트레이드오프 해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위에 열거한 내용으로부터 소프트웨어 아키텍처의 세 가지 중요한 용도를 알 수 있다. 소프트웨어 아키텍처는 시스템 전반에 대한 설계 계획서(Design Plan)이며, 복잡도를 제어하기 위한 추상화(Abstraction)일 뿐만 아니라, 이해당사자들의 공통 비전(Vision)인 동시에 의사소통 수단(Vehicle for Communication)이다.&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;color: #000000;&quot;&gt;소프트웨어 아키텍처는 소프트웨어 개발을 위한 청사진을 제공한다. 소프트웨어 아키텍처는 설계 계획서(Design Plan)와 같다. 소프트웨어를 구성하는 요소의 종류와 요소 간의 관계, 요소 간의 상호작용은 설계 계획서로서 소프트웨어 아키텍처가 갖춰야 하는 기본 항목들이다. 이후의 상세 설계, 구현, 통합, 테스트와 같은 일련의 작업들은 소프트웨어 아키텍처라는 거대한 틀과 어그러짐 없도록 수행되어야 한다. 설계, 구현 등의 세부적인 작업이 소프트웨어 아키텍처를 기반으로 이루어지기 때문에 프로젝트 중, 후반에 소프트웨어 아키텍처를 변경하기는 매우 어렵다. 소프트웨어 아키텍처는 소프트웨어의 구조뿐만 아니라 개발 조직의 구조에도 영향을 미치며 여러 가지 품질 속성을 협의하고 정의할 수 있는 기반을 제공한다. &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;color: #000000;&quot;&gt;소프트웨어 아키텍처의 중요한 측면은 세부적인 사항에 대한 이해 없이도 시스템을 이해할 수 있는 추상화를 제공한다는 점이다. 추상화는 복잡성을 해결하기 위한 핵심 도구다. 따라서 아키텍트는 세부적인 복잡성에 압도되지 않으면서도 시스템의 전반적인 구조와 상호작용을 효과적으로 서술할 수 있을 정도로 적절한 큰 규모의 요소들을 사용하여 소프트웨어 아키텍처를 표현해야 한다. 추상화의 이점은 그것이 재사용성을 촉진시킨다는 점이다. 프로젝트 후반의 코드 레벨 재사용보다 프로젝트 초기에 요구사항이 유사한 시스템 간에 아키텍처 또는 아키텍처 개발 경험을 재사용하는 것이 더 효과적이다. 추상화가 직관적이고 이해하기 용이할수록 복잡성은 감소하고 재사용성은 증가한다.&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;color: #000000;&quot;&gt;이해관계자들은 자신에게 중요한 요구사항을 기준으로 아키텍처를 바라본다. 아키텍트는 다양한 이해관계자들의 요구사항을 모두 취합한 후 요구사항의 우선순위를 협의하고 모든 이해관계자들이 납득할 수 있는 최적의 아키텍처를 설계해야 한다. 아키텍처는 이처럼 다양한 관심사항을 지닌 사람들이 서로 의논하고 토의해 결론을 도출할 수 있는 컨텍스트를 제공하므로 이를 의사소통의 수단으로 사용할 수 있다. 다양한 이해관계자들의 관점은 각각의 아키텍처 뷰로 표현되며 이러한 여러 뷰가 모여 완전한 하나의 아키텍처를 구성한다. 따라서 소프트웨어 아키텍처는 여러 이해당사자들의 시스템을 바라보는 관점의 집합인 동시에 모든 이해관계자들이 동의하고 받아들여야 하는 공통적인 비전이라고 할 수 있다.&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;color: #000000;&quot;&gt;그러나 불행하게도 위와 같은 정형화된 정의만으로는 소프트웨어 아키텍처의 본질을 파악하고 이해하기는 쉽지 않다. 아키텍처가 단순한 설계가 아닌 고차원의 무엇이라는 관점이 우리의 행동 반경을 학구적인 영역만으로 제한하는 것은 아닌지 의심스러울 정도다. 아키텍처는 이론이 아닌 실무의 영역이다. 아키텍처는 단순한 그림이 아닌 실제 소프트웨어의 구조다. 소프트웨어 아키텍처에 대한 만족스러운 정의를 찾지 못하던 도중 마틴 파울로의 또 다른 아티클에서 랄프 존슨이 아키텍처에 관해 메일링 리스트에 올린 글에 대해 알게 되었다. 개인적인 생각으로는 대다수의 실무 개발자 입장에서 가장 합리적이고 실용적이라고 느낄 수 있는 정의가 바로 랄프 존슨의 것이 아닐까 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;랄프 존슨이 익스트림 프로그래밍 메일링 리스트에 올린 글을 읽고 나서야 [아키텍처에 대해] 이해하게 되었다. 그의 글이 워낙 훌륭하기 때문에 여기에 전문을 인용하고자 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음의 포스트는 다음과 같다.&lt;br /&gt;&lt;br /&gt;IEEE의 정의를 해석한 RUP에서는 아키텍처를 &amp;ldquo;자체적인 환경 내에서 시스템의 최상위 레벨 개념. (특정 시점에 있어) 소프트웨어 시스템의 아키텍처란 인터페이스를 통해 상호작용하는 중요한 컴포넌트의 조직 또는 구조이며, 컴포넌트들은 연속적으로 더 작은 컴포넌트와 인터페이스를 조합하여 구성된다.&amp;rdquo;라고 정의한다.&lt;br /&gt;&lt;br /&gt;이에 대한 Ralph Johnson의 답변은 다음과 같다.&lt;br /&gt;&lt;br /&gt;나는 IEEE 표준에 대한 검토자였으며, 명백하게 [아키텍처에 대한] IEEE의 정의가 완전히 잘못된 것이라고 주장했지만 허사였다. 시스템의 최상위 레벨이라는 것은 존재하지 않는다. 고객은 개발자와는 다른 개념을 가진다. 고객은 중요한 컴포넌트의 구조에는 아예 관심이 없다. 따라서, 아마도 아키텍처는 개발자들이 시스템에 대해 가지고 있는 최상위 레벨의 개념일 것이다. 국소적인 부분에 대해서만 이해하는 개발자들은 잠시 접어두기로 하자. 아키텍처는 숙련된 개발자가 바라보는 최상위 레벨 개념이다. 무엇이 컴포넌트를 중요하게 만드는가? 숙련된 개발자가 중요하다고 이야기하기 때문에 중요한 것이다.&lt;br /&gt;&lt;br /&gt;따라서 [아키텍처에 대한] 좀 더 나은 정의는 다음과 같다. &amp;ldquo;대부분의 성공적인 소프트웨어 프로젝트에서 프로젝트에 종사하는 숙련 개발자들은 시스템 설계에 대한 이해를 공유하고 있다. 이 공유된 이해를 &amp;lsquo;아키텍처&amp;rsquo;라고 부른다. 여기에는 시스템이 컴포넌트로 분할되는 방법, 컴포넌트가 인터페이스를 통해 상호작용하는 방법이 포함된다. 컴포넌트들은 일반적으로 더 작은 컴포넌트로 구성되지만, 아키텍처는 오직 모든 개발자들이 이해하는 컴포넌트와 인터페이스만을 포함한다.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;이 정의가 보다 훌륭하다고 할 수 있는데, 아키텍처가 사회적 산물이라는 점을 명확하게 하기 때문이다(물론, 소프트웨어 역시 사회적 산물이기는 하지만, 상대적으로 아키텍처가 좀 더 사회적이라고 할 수 있다). 아키텍처는 그룹이 소프트웨어의 어떤 부분을 중요하다고 생각하는 지에 달려있다.&lt;br /&gt;&lt;br /&gt;&amp;ldquo;아키텍처란 프로젝트 초기에 내려져야 하는 설계 결정사항&amp;rdquo;이라는 다른 스타일의 아키텍처 정의가 존재한다. 나는 이 정의 역시 반대하는데 아키텍처란 프로젝트 초기에 올바르게 결정하기를 바라는 사항이 맞기는 하지만 그렇다고 해서 반드시 다른 어떤 것보다 더 올바르게 결정해야 하는 것은 아니라고 생각하기 때문이다.&lt;br /&gt;&lt;br /&gt;어쨌든, 이 두 번째 정의에 의하면 프로그램 언어는 대부분의 프로젝트에서 아키텍처에 속할 것이다. 첫 번째 정의에 의하면 그렇지 않다.&lt;br /&gt;&lt;br /&gt;어떤 부분이 아키텍처에 속하는가의 여부는 전적으로 개발자들이 그 부분이 중요하다고 생각하는 지 여부에 달려있다. &amp;ldquo;엔터프라이즈 애플리케이션&amp;rdquo;을 개발하는 사람들은 영속성이 중요하다고 생각한다. 이 경우 아키텍처를 그리기 시작할 때, 3개의 레이어를 가지고 시작한다. &amp;ldquo;우리는 데이터베이스로 Oracle을 사용하고 객체를 데이터베이스에 맵핑하기 위해 자체적인 퍼시스턴스 레이어를 가지게 될 것입니다.&amp;rdquo; 그러나 의료용 영산진단장치medical imaging 분야의 애플리케이션이라면 Oracle을 사용하더라도 이를 아키텍처의 일부로 간주하지는 않을 것이다. 이 분야에서 대부분의 복잡성은 이미지를 분석하는 데에서 기인하는 것이지 이를 저장하는 것과 관련된 것은 아니다. 이미지를 조회하고 저장하는 작업은 애플리케이션의 매우 작은 부분이며 대부분의 개발자는 이를 무시한다.&lt;br /&gt;&lt;br /&gt;따라서 이런 특성으로 인해 사람들에게 아키텍처를 서술하는 방법을 설명하기란 어렵다. &amp;ldquo;중요한 것을 이야기하라.&amp;rdquo; 아키텍처는 중요한 것에 대한 것이다. 그것이 무엇이건 말이다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp; 마틴 파울러, Who needs an architct?&lt;/span&gt;&lt;/blockquote&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: #000000;&quot;&gt;지금까지 살펴본 소프트웨어 아키텍처에 대한 다양한 관점을 종합해 보면 소프트웨어 아키텍처의 중요한 특징을 알 수 있다. 소프트웨어 아키텍처란 숙련된 개발자들이 동의하는 중요한 어떤 것이며 모든 이해관계자들이 공유해야 하는 지식으로 프로젝트 초기의 설계 결정을 의미하기 때문에 상대적으로 변경이 어렵다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>설계 이야기</category>
      <category>architecture</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/44</guid>
      <comments>https://eternity-object.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 19 Jun 2025 11:55:05 +0900</pubDate>
    </item>
    <item>
      <title>설계 트레이드오프</title>
      <link>https://eternity-object.tistory.com/43</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제 글이나 강의를 보신 분들은 잘 아시겠지만 설계에 있어 가장 중요하게 생각하는 개념은 &lt;b&gt;트레이드오프(trade-off)&lt;/b&gt;입니다. 일반적으로 트레이드오프는 한 가지를 얻기 위해 다른 것을 포기해야 하는 긴장 상황을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트레이드오프라는 용어를 가장 쉽게 이해할 수 있는 방법은 돈과 시간을 배분하는 경우를 상상하는 것입니다. 돈을 투자할 때는 항상 리스크와 수익 사이에서 트레이드오프를 하게 됩니다. 제 손에 쥐어진 꼬깃꼬깃한 지폐로 아이스크림을 사 먹으려면 과자는 포기해야 합니다. 지금처럼 긴 글을 쓰기 위해서는 게임을 플레이할 수 있는 시간을 포기해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; 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;300&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kU0SL/btsKfemv1Fv/KzGHO6PDiIqk4VJLJbz2lK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kU0SL/btsKfemv1Fv/KzGHO6PDiIqk4VJLJbz2lK/img.gif&quot; data-alt=&quot;인간보다 뛰어난 침팬지의 순간 기억력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kU0SL/btsKfemv1Fv/KzGHO6PDiIqk4VJLJbz2lK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kU0SL/btsKfemv1Fv/KzGHO6PDiIqk4VJLJbz2lK/img.gif&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;300&quot; height=&quot;224&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인간보다 뛰어난 침팬지의 순간 기억력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제가 인간과 침팬지의 이야기를 좋아하는 이유는 설계의 진화가 생물의 진화와 유사한 측면이 많기 때문입니다. 설계도 환경에 맞춰 진화해야 합니다. 시장 상황, 요구사항, 기술 발전, 팀 구성 등 설계에 영향을 주는 다양한 여러 요소들은 항상 변하기 때문에, 이런 변화에 적응하기 위해서는 어떤 부분을 발전시키고 어떤 부분을 포기할지 주의 깊게 결정해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것이 바로 트레이드오프의 핵심입니다. 설계를 선택할 때, 얻는 것이 있으면 잃는 것도 있다는 사실을 받아들이는 것이 트레이드오프를 이해하는 첫걸음입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 사람들이 설계에서 트레이드오프를 해야 한다고 말하면 주로 얻는 것에만 집중하는 경향을 보입니다. 반면에 잃는 것에 대해서는 충분히 고민하지 않는 것으로 보입니다. 예를 들어, &quot;이 설계를 하면 유연성이 좋아진다&quot;라는 말을 들으면, 그 유연성이 시스템의 다른 특성을 약화시킬 수 있다는 사실을 놓치기 쉽습니다. 코드 리뷰를 할 때면 너무 복잡하게 설계된 코드와 마주할 때가 있는데, 유연성을 높여서 얻는 이익에만 집중하고 코드의 단순함이나 가독성은 신경 쓰지 않은 결과물인 경우가 많습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인적으로 설계란 트레이드오프 과정이며 현재의 상황에 가장 적합한 이익과 손해를 저울질하는 의사결정의 과정이라고 생각합니다. 비용과 지출을 저울질해 보고 가장 큰 이익을 남길 수 있는 안을 선택해야 합니다. 설계를 트레이드오프할 때는 이익보다는 손해에 집중하는 것이 현명합니다. 손해가 너무 크다면 이익이 매력적으로 보이더라도 그 설계를 선택해서는 안됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계를 트레이드오프하는 기준은 환경에 따라 바뀌어야 한다는 점도 중요합니다. 시간이 지나면 환경도 그에 맞춰 함께 변하기 때문에, 1년 전의 이익과 손해가 오늘의 이익과 손해와 같을 수는 없습니다. 새로운 환경에 맞춰 설계를 재평가하고 적합하지 않다면 변경해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동일한 시간대라고 하더라도 설계 중인 문맥에 따라서도 기준이 변경됩니다. 전체가 하나의 세계를 구성하는 애플리케이션 코드에서는 중복을 제거하는 것이 좋지만 각 테스트 케이스별로 하나의 세계를 구성하는 단위 테스트에서는 각 테스트별로 중복을 가져가는 것이 효과적인 경우가 있습니다. public 메서드에 전달된 파라미터의 상태를 변경해서는 안되지만 private 메서드에 전달된 파라미터의 상태를 변경하는 것은 선택할 수 있는 합리적인 옵션 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 설계 원칙들은 좋은 설계를 만들 확률을 높일 수 있는 가이드일 뿐이지 반드시 따라야 하는 법칙은 아닙니다. 설계 원칙을 따르지 않는 경우가 유리한 경우도 있고 설계 원칙들이 서로 충돌하는 경우도 있습니다. 이 경우에도 원칙들을 트레이드오프하고 이익과 손해를 비교해 보고 손해가 가장 적은 원칙을 적용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계에는 답이 없다는 이야기를 들으면 불편해하는 분들이 종종 있습니다. 아마 모든 상황에 적용할 수 있는 설계 법칙이 있다고 기대하셨기 때문일 겁니다. &quot;이 기능은 무조건 이렇게 설계해야 한다&quot;는 확실한 답을 원하셨을 수도 있죠. 하지만 설계에 관한 질문을 받으면 저는 먼저 그분의 코드가 어떤 상황에 놓여 있는지, 요구사항은 어떻게 변하는지, 때로는 조직 간의 관계까지도 묻습니다. 그런 자세한 내용을 알지 못한 상태에서는 설계를 트레이드오프하기 어렵기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계에 답이 없다는 말은 상황에 따라 적합한 설계가 다르다는 뜻으로 이해하셔야 합니다. 정말로 답이 없다는 의미가 아니라, 상황에 맞는 설계가 존재한다는 이야기입니다. 그렇기 때문에 설계를 잘하려면 다양한 설계 옵션을 알아야 합니다. 이론을 공부하는 것도 중요하지만, 변화하는 환경에서 코드를 유지보수해 본 경험이 중요한 이유가 바로 여기에 있습니다(제가 관리자가 되면서 가장 아쉬웠던 부분인데 코드 레벨에서의 진화를 직접적으로 느낄 수 없어졌기 때문입니다. 대신 다른 부분이 성장했으니 괜찮다고 생각해야 겠죠).&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;종종 &quot;이 방법이 맞고, 다른 방법은 틀렸다&quot;고 확신하는 분들과 마주하게 됩니다. 코드가 어떤 상황에서, 어떤 트레이드오프를 거쳤는지 생각하지 않고, 최종 결과만 보고 성급하게 비판하는 분들도 많이 봐왔습니다. 하지만 설계를 이해하기 위해서는 설계 당시의 환경과 그 시점에 어떤 이익과 손해를 고려해서 결정했는지를 함께 이해하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;침팬지와 인간의 예처럼, 설계를 할 때도 현재 환경에 맞춰 발전시킬 부분과 포기할 부분을 신중히 선택해야 합니다. 모든 것을 동시에 발전시키는 것은 불가능하다는 사실을 받아들여야 합니다. 잘못된 선택을 하면, 장기 기억력은 약해지고 순간 기억력만 발달한 침팬지처럼 특정 상황에서는 적합하지만 다른 상황에서는 적응하지 못하는 시스템이 만들어지게 됩니다. 그리고 그런 시스템은 밀림에서는 생존할 수 있겠지만, 인간 사회에는 적응하지 못하고 도태되고 말 것입니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>설계 이야기</category>
      <category>트레이드오프</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/43</guid>
      <comments>https://eternity-object.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 23 Oct 2024 11:04:20 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 8부 [끝]</title>
      <link>https://eternity-object.tistory.com/42</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이전 글 :&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 7부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;테스트 케이스 리팩토링&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER를 구현했으므로 이제 이전에 작성한 테스트 케이스를 리팩토링하자. 먼저 대륙의 수가 정확한 지를 검증하는 ContinentSpecification의 테스트 케이스부터 살펴 보자. ContinentSpecification은 Continent가 명세를 만족하는 지만 검증하므로 ContinentSpecification테스트를 위해서는 Continent픽스처를 제외한 다른 정보들은 불필요하다. Atmosphere나 Ocean은 Planet을 생성하기 위해 요구되는 정보일 뿐 현재의 테스트 결과와는 직접적인 관계가 없다. 따라서 &amp;lt;리스트 15&amp;gt;에 나열된 현재의 테스트 케이스는 핵심적인 정보가 불필요한 정보의 홍수 속에 묻혀 있으므로 테스트를 이해하고 수정하기가 어렵다. 또한 도메인 객체의 생성자를 직접 호출하고 있기 때문에 도메인 객체의 변경에 대해서도 취약하다. Planet은 애플리케이션에 있어 핵심 도메인 객체이기 때문에 여러 테스트 케이스에서 유사한 픽스처 생성 코드가 중복될 가능성이 높다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 15&amp;gt; 행성에 포함된 대륙의 개수를 체크하는 테스트 케이스&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120759037&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
  @Test
  public void continentSize() {
    Planet planet = new Planet(
                     new Atmosphere(Money.wons(5000),
                       element(&quot;N&quot;, Ratio.of(0.8)),
                       element(&quot;O&quot;, Ratio.of(0.2))),
                     Arrays.asList(
                       new Continent(&quot;아시아&quot;),
                       new Continent(&quot;유럽&quot;)),
                     Arrays.asList(
                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
   
    ContinentSpecification specification = new ContinentSpecification(2);
       
    assertTrue(specification.isSatisfied(planet));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 15&amp;gt;를 TEST DATA BUILDER 패턴을 이용해 리팩토링하면 Continent 이외의 테스트 결과와 관련이 없는 불필요한 정보를 제거할 수 있다. 테스트와 상관 없는 Atmosphere와 Ocean은 테스트 케이스의 초점을 흐르지 않도록 빌더 안으로 숨겨져 있다. 따라서 픽스처를 생성하는 코드가 간략해지고 테스트 결과와 직접적으로 관련 있는 정보만 코드 상에 보여지기 때문에 테스트의 의도를 파악하기가 쉽다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 16&amp;gt; 관련된 정보만을 표현하는 TEST DATA BUILDER&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #7f0055;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120759039&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
  @Test
  public void continentSize() {
    Planet planet = aPlanet().with(aContinent(), aContinent()).build();
 
    ContinentSpecification specification = new ContinentSpecification(2);
       
    assertTrue(specification.isSatisfied(planet));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;다음은 행성의 가격을 검증하는 PriceSpecification에 대한 테스트 케이스를 나타낸 것이다. ContinentSpecification과 마찬가지로 대륙에 포함된 국가나 대양의 명칭과 같은 불필요한 정보로 인해 테스트와 직접적으로 관련된 중요한 픽스처 정보가 무엇인지를 파악하기가 어렵다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 17&amp;gt; 행성의 전체 가격이 20,000원보다 낮거나 같은 지 여부를 테스트&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #7f0055;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120759040&quot; class=&quot;haxe&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class PriceSpecificationTest  {
  @Test
  public void price() {
    Planet planet = new Planet(
                     new Atmosphere(Money.wons(5000),
                       element(&quot;N&quot;, Ratio.of(0.8)),
                       element(&quot;O&quot;, Ratio.of(0.2))),
                     Arrays.asList(
                       new Continent(&quot;아시아&quot;,
                         new Nation(&quot;대한민국&quot;, Money.wons(1000))),
                       new Continent(&quot;유럽&quot;,
                        new Nation(&quot;영국&quot;, Money.wons(1000)))),
                     Arrays.asList(
                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
   
    Specification specification = new PriceSpecification(Money.wons(9000));
   
    assertTrue(specification.isSatisfied(planet));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER를 이용해서 리팩토링한 테스트 케이스는 제조 가격 이외의 불필요한 정보를 빌더 내부로 숨기고 있다. 따라서 테스트 케이스가 좀 더 간결해지고 이해하기 쉬워졌음을 알 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 18&amp;gt; 관련된 정보만을 표현하는 TEST DATA BUILDER&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #7f0055;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120759041&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class PriceSpecificationTest  {
  @Test
  public void price() {
    Planet planet = aPlanet()
                      .with(anAtmosphere().with(wons(5000)))
                      .with(aContinent().with(aNation().with(wons(1000))),
                            aContinent().with(aNation().with(wons(1000))))
                      .with(anOcean().with(wons(1000)),
                            anOcean().with(wons(1000)))                                
                      .build();

    Specification specification = new PriceSpecification(Money.wons(9000));
   
    assertTrue(specification.isSatisfied(planet));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;두 가지 테스트 케이스의 리팩토링을 통해 알 수 있는 것처럼 TEST DATA BUILDER는 다양한 픽스처의 상태를 조합해야 하는 상황에서도 유연하게 대처가 가능하다. 도메인 객체의 계층을 따라 빌더의 계층을 구축하고 빌더를 조합함으로써 다양한 상태의 복합 객체를 쉽게 생성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;TEST DATA BUILDER 확장&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;최근 영속성 메커니즘으로 JPA(Java Persistence API)를 사용하는 프로젝트에 참여했던 적이 있다. 프로젝트에 합류하던 시점에 개발팀은 픽스처 생성 코드를 관리하기 위해 FACTORY 기반의 OBJECT MOTHER 패턴을 사용하고 있었다. 개발팀은 픽스처 생성과 관련된 코드 중복 문제를 해결하기 위해 메모리 객체뿐만 아니라 REPOSITORY 테스트에서 사용할 영속 객체 또한 OBJECT MOTHER를 통해 생성하기를 원했다. 문제는 시간이 흐를수록 OBJECT MOTHER가 급격하게 복잡해져 갔다는 점이다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OBJECT MOTHER는 메모리 객체를 생성하는 createTransient&amp;hellip;() 류의 메서드들과 영속 객체를 생성하는 createPersistent&amp;hellip;()류의 메서드들이 얽히고 설킨 상태로 방치되어 있었다. createPersistent&amp;hellip;() 메서드들은 내부적으로 createTransient&amp;hellip;() 메서드들을 사용하고 있었기 때문에 메서드 간의 강한 결합과 코드 중복으로 인해 테스트 케이스를 관리하기가 점점 어려워져 갔다. 결국 시간이 흐를수록 생성 메서드(CREATION METHOD)의 수는 급격하게 증가하기 시작했으며 픽스처를 생성하는 코드는 이해하기도, 유지보수하기도 어려운 애물단지로 전락하고 말았다. 결국 OBJECT MOTHER를 TEST DATA BUILDER로 대체하기로 결정했다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;먼저 메모리 객체를 생성하는 TEST DATA BUILDER를 구현한 후 createTransient&amp;hellip;() 계열의 메서드 대신 빌더를 사용하도록 테스트 케리스를 리팩토링했다(실제 프로젝트에 적용한 방법은 반복되는 코드(boilerplate code)를 최소화하기 위해 약간의 리플렉션과 메타 프로그래밍 기법을 조합했다. 실제 프로젝트에서 사용한 TEST DATA BUILDER는 본 글에서 설명한 코드보다 간단한 방법으로 구현이 가능하지만 기본적인 원리는 거의 동일하다). 영속성 관련 로직을 한 곳에 모아 둘 추상 클래스를 추가한 후 모든 빌더들이 추상 클래스를 상속하도록 했다. CGLIB의 바이트 코드 향상 기법과 DECORATOR 패턴을 이용해 픽스처를 생성하는 build() 메서드의 호출을 인터셉트함으로써 생성된 픽스처가 자동으로 데이터베이스에 저장되도록 만들었다. 애플리케이션 로딩 시에 수집된 클래스의 메타 데이터를 이용해 외래키(Foreign key) 관계를 자동으로 판단하도록 함으로써 영속 객체 생성과 관련된 참조 무결성 이슈도 해결할 수 있었다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용 방법 역시 매우 간단했는데 기존의 빌더 호출 코드를 추상 클래스에서 제공하는 persisted() 메서드로 감싸기만 하면 되었다. persisted() 메서드는 DSL 스크립트 작성에 필요한 문맥 변수(CONTEXT VARIABLE)의 범위를 객체 내부로 제한하고 공통 코드를 제공하기 위해 슈퍼 클래스를 사용하는 객체 범위(OBJECT SCOPING)기법을 적용한 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Continent continent = persisted(&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&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;&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;aContinent()&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&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;.with(aNation().with(&lt;span style=&quot;color: #409d00;&quot;&gt;&quot;캐나다&quot;&lt;/span&gt;),&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&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; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;aNation().with(&quot;미국&quot;)))&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&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;.build();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 빌더가 픽스처를 생성하기 위해 동일한 구조와 코딩 규칙을 준수하고 있었기 때문에 간단한 규칙의 추가만으로도 영속성을 관리하는 인프라 영역으로 빌더를 쉽게 확장할 수 있었다. 결론적으로 TEST DATA BUILDER 패턴은 기반 인프라스트럭처에 맞추어(또는 특정 도메인의 요구사항에 맞추어) 빌더의 기능을 쉽게 확장할 수 있도록 한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER의 장점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER 패턴은 테스트 케이스 작성과 관련된 다양한 문제점들을 해결하기 위한 DSL 관점의 접근방법이다. 이 패턴은 생성자를 직접 호출하거나 OBJECT MOTHER 패턴을 이용해 생성 오퍼레이션을 캡슐화하는 방법보다 간결하고 변경에 유연하며 중복 코드의 양을 줄일 수 있다. 또한 모든 빌더들이 동일한 구현 패턴을 따르기 때문에 영속성과 같은 부분으로의 확장이 용이하다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;다음은 TEST DATA BUILDER 패턴의 장점을 설명한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 테스트에서 픽스처와 관련 없는 정보(IRRELEVANT INFORMATION)를 너무 상세하게 많이 보여주거나 미스터리한 손님(Mysterious Guest)과 같이 중요한 정보를 숨기고 보여주지 않을 경우 시스템의 동작에 영향을 미치는 것이 무엇인지 파악하기 어렵다. TEST DATA BUILDER는 테스트와 관련된 정보만 표현하고 불필요한 정보는 빌더 내부로 감춤으로써 각 테스트 케이스에서 중요한 정보가 무엇인지를 쉽게 파악할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;깨지기 쉬운 테스트(FRAGILE TEST)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 테스트 케이스에서 생성자를 직접 호출할 경우 생성자의 시그니처가 변경될 경우 해당 객체들을 참조하는 모든 테스트 케이스를 수정해야 한다. TEST DATA BUILDER는 생성자 호출 부분을 build() 메서드 내부로 캡슐화하고 속성을 설정할 수 있는 유창한 인터페이스만을 외부로 노출하기 때문에 변경에 유연하게 대처할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;테스트 코드 중복(TEST CODE DUPLICATION)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 유사한 주제에 대해서 약간씩 다른 시나리오로 테스트를 해야 할 경우 유사한 픽스처 로직이 여러 테스트 케이스에 중복되어 나타나게 된다. 특히 테스트 케이스와 관련이 없는 정보가 중복되는 경우 애매한 테스트가 되고 만다. TEST DATA BUILDER 패턴은 테스트에 중요한 정보만을 노출하고 불필요한 정보는 숨기기 때문에 테스트와 관련이 없는 중복 코드의 양을 크게 줄일 수 있다. 또한 생성 메서드 간에 제거하기 미묘한 중복 코드를 포함하는 OBJECT MOTHER 패턴과 달리 TEST DATA BUILDER는 하나의 빌더로 다양한 상태 조합이 가능하기 때문에 중복 코드의 발생 가능성을 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;높은 테스트 유지 비용(HIGH TEST MAINTENANCE COST)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;ndash; 애매하고, 깨지기 쉬우며, 중복된 코드는 유지보수하기가 어렵다. TEST DATA BUILDER 패턴은 세 가지 문제를 비교적 깔끔하게 해결함으로써 낮은 비용만으로 테스트를 유지할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;테스트를 작성하지 않는 개발자(DEVELOPERS NOT WRITING TEST)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; TEST DATA BUILDER 의 경우 테스트 작성과 유지보수에 드는 비용이 상대적으로 적기 때문에 개발자는 적극적으로 테스트 케이스를 작성하려고 노력 한다. TEST DATA BUILDER의 비용과 관련된 문제점은 테스트 케이스 자체의 작성 비용이 아니라 빌더 자체를 작성하는데 드는 비용이 높다는 점이다. 경험에 따르면 빌더는 한 번 작성해 두면 여러 테스트 케이스에서 유용하게 반복적으로 재사용되기 때문에 초기 비용을 감수할만한 가치가 있으며 리플렉션이나 메타 프로그래밍을 이용해 최대한 반복되는 코드(boilerplate code)의 양을 줄일 수 있다는 점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER를 사용하라&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;복잡한 도메인을 다루는 애플리케이션에서 테스트 케이스에 사용할 픽스처를 관리하는 데 애를 먹고 있다면 TEST DATA BUILDER 패턴을 적용해 보길 권한다. TEST DATA BUILDER 패턴을 사용하면 테스트에 부차적인 픽스처 생성이 아니라 테스트의 본질인 테스트의 목적에 집중할 수 있게 된다. 더 중요한 점은 테스트 케이스 작성이 쉽고 재미있어 진다는 점이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;단위 테스트에 중독되는 이유는 단위 테스트가 진행중인 작업의 설계와 안전성에 대한 빠른 피드백을 제공하기 때문이다. 빠른 피드백은 단순하고 직관적이며 아름다운 테스트 코드로부터 나온다. TEST DATA BUILDER는 이 모든 것을 가능하게 함으로써 테스트를 즐거운 작업으로 만든다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;테스트를 직관적이고 아름답게 만드는 TEST DATA BUILDER의 접근 방법은 근본적으로 테스트를 위한 언어를 만드는 DSL의 전통으로부터 기인한다. DSL의 가장 큰 특징은 제한된 도메인에 최적화된 단순하고, 간결하며, 유창한 언어를 제공한다는 것이다. TEST DATA BUILDER는 DSL의 이런 특징을 계승한다. 빌더는 테스트 케이스를 단순하고, 간결하게 만들며 유창한 언어를 제공함으로써 적은 코드로도 풍부한 정보를 신속하게 전달할 수 있도록 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;Steve Freeman과 Net Pryce는 TEST DATA BUILDER 패턴의 장점을 아래와 같이 명확하고 직관적인 어조로 설명한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TEST DATA BUILDER를 적용함으로써 테스트를 표현력 있게 작성하고 변경에 대해 탄력적으로 코드를 유지할 수 있다는 점을 발견했다. 첫째, 새로운 객체를 만들 때 대부분의 문법적인 잡음을 감싼다. 둘째, 간단한 경우를 단순하게 만들고, 특수한 경우를 과할 정도로 복잡하게 만들지 않는다. 셋째, 테스트를 객체의 구조 변경으로부터 보호한다. 생성자에 인자를 추가할 경우 적합한 빌더와 새로운 인자를 필요로 하는 테스트만 변경하면 된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;Steve Freeman, Net Pryce, Growing Object-Oriented Software, Guided by Tests&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;결론은 간단하다. 현재 복잡한 픽스처 생성 문제로 골치가 아프거나 이해하기 어려운 테스트 케이스로 인해 품질이 저하되고 리팩토링이 어려운 곤란한 상황에 처해 있다면 TEST DATA BUILDER의 사용을 고민해 보자. TEST DATA BUILDER는 테스트 작성을 고통이 아니라 즐거움으로 바꿔줄 것이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 주도 설계</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/42</guid>
      <comments>https://eternity-object.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 31 May 2024 11:00:42 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 7부</title>
      <link>https://eternity-object.tistory.com/41</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이전 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 6부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;테스트 도메인에 특화된 언어&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;지금까지 살펴 본 것처럼 픽스처로 사용할 객체의 구조가 복잡하고 그로 인해 테스트의 결과를 예측하기 어려울 경우 테스트 케이스를 작성하려는 개발자의 의지는 좌절된다. 테스트를 생성하기 위해 미로처럼 복잡한 픽스처의 내부 구조를 이해해야 할 경우 테스트 케이스의 작성을 미루는 경향이 있다. 실패한 테스트 케이스를 열어 보았을 때 픽스처 설정 부분이 길고 복잡해서 실패의 원인을 파악하기 어려운 경우 테스트 실행 목록에서 해당 테스트 케이스를 제외하거나 테스트가 실패하지 않도록 코드의 구조를 왜곡시킨다. 결국 테스트는 상환하지 못 할 기술 부채(technical debt)를 누적시키는 원흉으로 전락하고 만다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;픽스처로 사용할 객체의 생성자를 직접 호출하는 테스트 케이스는 생성자의 시그니처 변경에 매우 취약하다. 객체의 생성자에 파라미터가 추가되거나 제거될 경우 해당 객체를 픽스처로 사용하는 테스트 케이스 전체가 변경으로 몸살을 앓게 된다. 결국 과도한 양의 테스트 케이스 수정에 지친 개발자들은 객체지행에서 지향하는 풍부한 관계를 도메인 클래스의 작성과 리팩토링을 꺼리게 된다. 또한 테스트와 무관한 정보들로 인해 중요한 정보가 가려지는 불투명한 테스트 케이스는 테스트의 의도를 이해하고 결과를 예측하기가 어렵게 만든다. 결과적으로 복잡한 픽스처 생성 로직을 가진 테스트 케이스는 지친 개발자의 발목을 잡고 시스템의 유지보수와 확장을 방해하는 걸림돌이 되고 만다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;OBJECT MOTHER 패턴은 이와 같은 픽스처 생성과 관련된 문제를 해결하기 위해 사용할 수 있는 한 가지 패턴이다. OBJECT MOTHER는 픽스처 생성을 위한 오퍼레이션을 제공하는 일종의 FACTORY 로 도메인 객체의 생성자 호출을 FACTORY 내부로 캡슐화함으로써 생성자 변경에 의한 파급효과를 OBJECT MOTHER 내부로 제한한다. 그러나 테스트에 필요한 픽스처의 상태 조합에 따라 오퍼레이션 수가 폭발적으로 증가하기 때문에 중복 코드를 양산하고, 구현과 유지보수가 복잡해지며, 변경에 취약하다는 단점을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;FACTORY 패턴을 기반으로 하는 OBJECT MOTHER 패턴과 달리 TEST DATA BUILDER 패턴은 BUILDER 패턴을 기반으로 한다. TEST DATA BUILDER 패턴의 기본적인 접근 방법은 커맨드-쿼리 인터페이스를 제공하는 도메인 계층 위에 픽스처 생성이라는 제한된 범위를 지원하기 위해 유창한 인터페이스(Fluent Interface)의 언어 계층을 얹는 것이다. 즉, TEST DATA BUILDER 패턴의 핵심은 테스트를 위한 도메인-특화 언어(Domain-Specific Language, DSL)를 창조하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER 패턴의 중심에는 연쇄적인 메서드 호출을 통해 유창한 인터페이스를 제공하는 메서드 체이닝(METHOD CHAINING)이 위치한다. 커맨드-쿼리 인터페이스를 기반으로 하는 GOF의 BUILDER 패턴과 달리 TEST DATA BUILDER 패턴은 오퍼레이션의 결과로 반환되는 객체(호스트 객체라고 부른다)에 대한 연쇄적인 메서드 호출을 제공함으로써 픽스처 생성 코드를 유창하고 간결하게 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL의 관점에서 TEST DATA BUILDER 패턴의 의미 모델(SEMANTIC MODEL)은 애플리케이션의 도메인 객체다. 도메인 객체는 커맨드와 쿼리를 명확하게 구분하는 커맨드-쿼리 인터페이스(Command-Query Interface)를 제공한다. TEST DATA BUILDER는 도메인 객체를 의미 모델로 사용하고 오퍼레이션 호출을 결합해 픽스처로 생성할 도메인 객체를 생성하는 표현식 빌더(EXPRESSION BUILDER)다. 따라서 TEST DATA BUILDER는 테스트 케이스 작성자에게 쉽고 간결하게 픽스처를 생성할 수 있는 유창한 인터페이스(Fluent Interface)를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;일반적으로 TEST DATA BUILDER는 아래와 같은 절차에 따라 구현한다. 빌더 클래스나 메서드의 이름은 강제사항은 아니지만 개인적인 경험에 따르면 아래에 명시한 명명 규칙을 따르는 것이 코드의 가독성과 유지보수성을 향상시킨다는 것을 알 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;빌더 클래스를 추가한다. 빌더 클래스의 이름은 &amp;ldquo;애플리케이션 클래스 이름 + Builder&amp;rdquo;의 형식으로 명명한다. 예를 들어 애플리케이션 클래스가 Nation이라면 빌더 클래스의 이름은 NationBuilder가 된다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;빌더 자체의 인스턴스를 생성해서 반환하는 정적 메서드를 추가한다. 정적 메서드의 이름은 &amp;ldquo;a + 애플리케이션 클래스 이름&amp;rdquo; 또는 &amp;ldquo;an + 애플리케이션 클래스 이름&amp;rdquo;의 형식을 따른다. NationBuilder의 경우 정적 메서드의 이름은 aNation()으로 한다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;애플리케이션 클래스의 모든 속성을 빌더 클래스에 추가한다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;속성 선언 시에 반드시 기본값을 할당한다. 기본값은 테스트 케이스에서 픽스처를 생성할 때 테스트와 관련 없는 정보를 표시하지 않아도 무방하도록 만들기 위해 사용한다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;속성을 설정할 수 있는 커맨드(Command) 메서드를 추가한다. 커맨드 메서드의 이름은 set으로 시작하는 일반적인 JavaBeans 관례를 따르지 않고 with로 시작하는 관례를 따른다. 또한 반환값이 없는 전통적인 커맨드 메서드와 달리 빌더의 커맨드는 메서드 체이닝을 지원하기 위해 빌더 자신을 반환한다. 따라서 각 커맨드 메서드의 마지막 문장은 return this로 끝나야 한다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;build() 메서드를 추가한다. build() 메서드의 용도는 픽스처로 사용할 애플리케이션 객체를 생성한 후 반환하는 것으로 메서드 체이닝이 완결되었다는 것을 명시적으로 표현한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌더를 구현하는 방법을 자세히 살펴 보기 위해 지구 검증 소프트웨어의 Nation 객체를 위한 빌더를 구현해 보자. 국가의 개념을 추상화하는 Nation 클래스는 이름(name)과 제조 가격(price)을 속성으로 포함하는 간단한 도메인 객체다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 1&amp;gt; 이름과 제조 가격을 속성으로 포함하는 Nation 클래스&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #7f0055;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120248309&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Nation {
  private String name;
  private Money price;
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;Nation 의 인스턴스를 생성하는 빌더의 이름은 NationBuilder로 정한다. NationBuilder는 Nation의 name과 price의 값을 변경하기 위한 SETTER 메서드인 withName()과 withPrice()를 포함한다. build() 메서드는 설정된 속성 값을 이용해 Nation 객체를 생성한 후 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 2&amp;gt; Nation을 생성하는 NationBuilder&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120260697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NationBuilder {
  private String name = &quot;대한민국&quot;;
  private Money price = Money.wons(1000);

  public NationBuilder withName(String name) {
    this.name = name;
    return this;
  }
    
  public NationBuilder withPrice(Money price) {
    this.price = price ;
    return this;
  }
    
  public Nation build() {
    return new Nation(name, price);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 2&amp;gt;는 DSL과 관련된 TEST DATA BUILDER 패턴의 특징 몇 가지를 잘 보여준다. 첫 째, 인터페이스는 일반적인 커맨드-쿼리 인터페이스 스타일이 아닌 메서드 체이닝 기반의 유창한 인터페이스 스타일을 따른다. NationBuilder의 상태를 변경하는 커맨드 메서드인 withName()과 withPrice()는 빌더 자신을 반환한다(return this). 이것은 커맨드-쿼리 분리(Command-Query Separation) 원칙을 위반하지만 &amp;lt;리스트3&amp;gt;과 같이 메서드의 연쇄적인 호출을 통해 자연스러운 흐름으로 오퍼레이션들이 조합될 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 3&amp;gt; 빌더의 생성자를 직접 호출하는 코드&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120279686&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Nation nation = new NationBuilder()
                  .withName(&quot;대한민국&quot;)
                  .withPrice(Money.wons(1000))
                  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;둘 째, 커맨드 메서드의 명칭은 set으로 시작하는 전통적인 JavaBeans 컨벤션과 다르게 with로 시작한다. with로 시작하는 이유는 메서드의 이름이 독립적으로 사용되는 것이 아니라 다른 메서드와 연결도어 함께 사용되는 문맥을 강조하기 위해서이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 &amp;lt;리스트 2&amp;gt;는 DSL의 간결함과 유창함이라는 측면에서 좀 더 개선할 여지가 있다. 메서드 체이닝을 시작하기 위해 NationBuilder를 생성할 때 현재는 new NationBuilder()와 같이 직접 빌더의 생성자를 호출해야 한다. 큰 문제는 아니지만 클래스의 인스턴스를 생성하기 위해 new라는 키워드를 사용하는 것은 자연스러운 문장의 흐름을 방해한다. 빌더의 생성자를 호출하는 부분을 aNation()이라는 이름을 가진 정적 메서드로 대체함으로써 DSL의 흐름을 깔끔하게 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 4&amp;gt; 빌더를 생성하는 정적 메서드 추가&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120295765&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NationBuilder {
  public static NationBuilder aNation() {
    return new NationBuilder();
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바의 정적 임포트(static import) 기능을 이용해 불필요한 클래스 정보를 생략하면 다음과 같이 전반적인 생성 코드의 흐름을 좀 더 자연스럽게 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 5&amp;gt; 정적 임포트를 이용한 메서드 흐름의 개선&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120312751&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Nation nation = aNation()
                  .withName(&quot;대한민국&quot;)
                  .withPrice(Money.wons(1000))
                  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 1&amp;gt;에서 NationBuilder의 이름(name)과 가격(price) 속성을 선언할 때 기본 값으로 &amp;ldquo;대한민국&amp;rdquo;과 Money.wons(1000)을 할당하는 것에 주목하라. 빌더의 속성에 기본 값을 할당해 놓으면 테스트 케이스 작성 시 테스트 결과와 관련이 없는 불필요한 정보를 생략해도 NullPointerException이 발생하지 않도록 할 수 있다. 따라서 속성의 기본값은 관련 없는 정보가 테스트 케이스에 과도하게 보여지지 않도록 정보를 숨김으로써 애매한 테스트가 되는 것을 방지한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 Nation의 속성 값과 상관없이 인스턴스의 존재만이 중요하다면 다음과 같이 단순한 코드만으로도 목적을 달성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 6&amp;gt; 기본 값을 가지도록 Nation 인스턴스 생성&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120325587&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Nation nation = aNation().build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 코드에서 이름(name) 속성의 값은 중요하지 않고 제조 가격(price) 속성의 값만 중요하다면 다음과 같이 픽스처 생성 시 제조 가격만을 설정하고 이름은 기본 값을 따르도록 할 수 있다. 빌더의 이러한 특성은 별도의 코드 수정이나 새로운 오퍼레이션 추가 없이도 다양한 상태의 픽스처를 조합할 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 7&amp;gt; 제조 가격만을 강조하고 싶은 경우&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120340075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Nation nation = aNation()
                  .withPrice(Money.wons(1000))
                  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TEST DATA BUILDER의 속성은 DSL 관점에서 문맥 변수(Context Variable)에 해당한다. 문맥 변수에 기본 값을 할당함으로써 공유 문맥에 의존하는 여러 오퍼레이션들 호출 간에 전달해야 하는 불필요한 정보의 양을 줄일 수 있다. 또한 문맥 변수의 현재 값을 기반으로 다양하게 오퍼레이션 호출을 조합할 수 있기 때문에 API 사용성의 측면에서 유연성이 높아진다. TEST DATA BUILDER가 제공하는 이러한 유연성은 새로운 상태를 가진 픽스처가 필요한 경우 매번 새로운 오퍼레이션을 추가해야 하는 OBJECT MOTHER 패턴과는 대조적이다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;TEST DATA BUILDER 조합하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Continent를 생성하는 ContinentBuilder 역시 동일한 방식으로 구현할 수 있다. Continent는 이름(name)과 국가 목록(nations)을 속성으로 가진다. nations 속성에 할당할 기본 값을 생성하기 위해 앞에서 구현한 NationBuilder를 재사용하는 것에 주목하라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 8&amp;gt; Continent를 생성하는 ContinentBuilder&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120370441&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentBuilder {
  private String name = &quot;아시아&quot;;
  private List&amp;lt;Nation&amp;gt; nations = Arrays.asList(
        aNation().with(&quot;대한민국&quot;).with(wons(1000)).build(),
        aNation().with(&quot;일본&quot;).with(wons(1000)).build());
    
  public static ContinentBuilder aContinent() {
    return new ContinentBuilder();
  }
    
  public ContinentBuilder withName(String name) {
    this.name = name;
    return this;
  }
    
  public ContinentBuilder withNations(Nation ... nations) {
    this.nations = Arrays.asList(nations);
    return this;
  }
    
  public Continent build() {
    return new Continent(name , nations.toArray(new Nation[0]));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;NationBuilder에서 설명한 것과 동일하게 인스턴스의 존재 여부만이 중요한 경우에는 aContinent.build()와 같이 간략한 방식으로 사용할 수 있다. 만약 테스트 케이스에서 대륙의 이름은 중요하지 않고 포함된 국가의 이름과 개수만 중요하다면 &amp;lt;리스트9&amp;gt;와 같이 두 개의 정보만을 이용해 픽스처를 생성할 수 있다. withNations()에 전달할 Nation 인스턴스를 생성하기 위해 앞에서 구현한 NationBuilder를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 9&amp;gt; 국가 정보만 중요한 경우의 Continent 생성 코드&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120392658&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Continent continent = aContinent()
                        .withNations(aNation().withName(&quot;캐나나&quot;).build(),
                                     aNation().withName(&quot;미국&quot;).build())
                        .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 9&amp;gt;의 코드를 자세히 살펴 보면 withNations() 메소드 안에 aNation() 메서드를 호출하는 코드가 들어 있다는 것을 알 수 있다. 이 경우 두 메서드에 &amp;ldquo;Nation&amp;rdquo;이라는 단어가 중복되어 나타난다(withNations()에서 한 번, aNation()에서 한 번). 두 메서드 이름 모두에 &amp;ldquo;Nation&amp;rdquo;이라는 단어가 포함되는 것은 불필요한 중복이며 결과적으로 DSL의 흐름을 방해한다. 간결함과 유창함을 향상시키는 한 가지 방법은 withNations() 메서드를 간단히 with()로 변경하고 뒤이어 호출되는 aNation() 메서드의 반환 타입을 이용해 컴파일러가 어떤 타입의 파라미터를 받는 with() 메서드 인지를 판단하도록 하는 것이다. 마찬가지로 withName() 메서드 역시 String을 인자로 받는 with()로 변경할 수 있다. 두 메서드의 이름이 같아도 무방한 것은 메서드의 인자 타입이 하나는 String, 하나는 Nation의 배열로 다르기 때문이다. 만약 동일한 타입의 속성이 하나 이상 존재할 경우 앞의 방식처럼 &amp;lsquo;with+속성명&amp;rsquo;의 형식을 따라 메서드 이름을 지어야 한다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 10&amp;gt; withName()과 withNations()를 with()로 수정&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120425296&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentBuilder {
  public ContinentBuilder with(name) {
    this.name = name;
    return this;
  }
   
  public ContinentBuilder with(Nation ... nations) {
    this.nations = Arrays.asList(nations);
    return this;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;NationBuilder 역시 동일한 규칙에 따라 with 메서드들의 이름을 좀 더 간략하게 줄일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 11&amp;gt; with 메서드의 이름을 수정한 NationBuilder&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120438440&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NationBuilder {
  public NationBuilder with(String name) {
    this.name = name;
    return this;
  }
   
  public NationBuilder with(Money price) {
    this.price = price;
    return this;
  }
  ...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이제 &amp;lt;리스트 9&amp;gt;의 코드는 &amp;lt;리스트 12&amp;gt;와 같이 간소화되어 DSL 특유의 간결함과 유창함이 향상되었다는 것을 느낄 수 있다. 이 예에서 알 수 있는 것처럼 문맥 상의 연결성 측면에서 오퍼레이션의 이름을 정할 경우 간결하고 유창한 DSL을 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 12&amp;gt; 국가 정보만 중요한 경우의 Continent 생성 코드&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120452924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Continent continent = aContinent()
                        .with(aNation().with(&quot;캐나나&quot;).build(),
                              aNation().with(&quot;미국&quot;).build())
                        .build();&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 12&amp;gt;에서 한 가지 눈에 거슬리는 부분은 Nation을 생성할 때마다 매번 NationBuilder의 build() 메서드를 호출하는 부분이다. 만약 생략해도 의미 전달에 지장이 없다면 정보를 제거하는 것이 DSL의 간결함과 유창함을 향상시키는 또 다른 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;외부에서 직접 build()를 호출하지 않아도 되도록 Nation이 아닌 NationBuilder를 인자로 전달 받도록 ContinentBuilder의 with 메서드를 수정하자. ContinentBuilder는 Nation을 생성하기 위해 전달받은 NationBuilder의 build() 메서드를&amp;nbsp; 내부적으로 호출한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 13&amp;gt; NationBuilder를 인자로 취하는 ContinentBuilder&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120465907&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentBuilder {
  public ContinentBuilder with(NationBuilder ... nations) {
    this.nations = new ArrayList&amp;lt;Nation&amp;gt;();
       
    for(NationBuilder each : nations) {
        this.nations.add(each.build());
    }

    return this;
  }
  ...
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 Continent를 생성하는 코드에서 불필요한 build() 메서드 호출을 생략할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 14&amp;gt; build() 메서드 호출을 제거한 Continent 생성 코드&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717120482323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Continent continent = aContinent()
                        .with(aNation().with(&quot;캐나나&quot;),
                              aNation().with(&quot;미국&quot;))
                        .build();&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PlanetBuilder와 AtmosphereBuilder, OceanBuilder 역시 동일한 방식으로 구현할 수 있다. 전체 코드는 첨부된 소스 코드를 참고하기 바란다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 14&amp;gt;는 다양한 내부 DSL 설계 기법을 조합해서 사용함으로써 DSL로 작성된 스크립트의 가독성과 사용성을 향상시킬 수 있음을 보여준다. 여기에서 각각의 빌더는 픽스처의 속성을 설정하는 과정을 유창하게 만들기 위해 메서드 체이닝을 이용한다. 빌더가 다른 빌더에 의존할 경우 내포 함수(NESTED FUNTION)를 이용한다. 따라서 TEST DATA BUILDER 패턴은 메서드 체이닝을 통해 전체적인 인터페이스의 명확성과 유연성을 향상시키는 한편 내포 함수를 통해 복잡한 객체의 생성에 필요한 문맥 변수의 양을 감소시킨다. 하나 이상의 대륙이 필요한 경우 다수의 ContinentBuilder()를 호출하는 함수 시퀀스(FUNCTION SEQUENCE) 방식을 조합할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;TEST DATA BUILDER와 리팩토링&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;TEST DATA BUILDER 패턴을 소개할 때 가장 많이 받게 되는 질문은 픽스처로 사용될 애플리케이션 객체의 계층 구조와 빌더 계층 구조 간의 의미적인 결합으로 인해 객체의 리팩토링이 어려워지지 않느냐는 점이다. 빌더의 속성과 빌더 간의 관계가 애플리케이션 객체의 속성과 연관 관계를 따를 경우 도메인 객체의 연관 관계 변경은 곧 빌더의 연관 관계 변경을 의미하기 때문에 결과적으로 두 애플리케이션 객체를 픽스처로 사용하는 모든 테스트 케이스도 함께 리팩토링해야 하기 때문이다. 이로 인해 애플리케이션 객체의 수정으로 인해 영향을 받는 코드 양이 OBJECT MOTHER보다 많아 질 것이며 결과적으로 개발자들이 애플리케이션 객체의 리팩토링을 꺼리게 되지 않겠느냐는 것이 질문의 요지다. 그러나 기우와 달리 오히려 TEST DATA BUILDER 패턴을 사용할 경우 좀 더 쉽고 안정적으로 애플리케이션 객체를 리팩토링할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Java와 같은 정적 언어에서 애플리케이션 객체의 연관 관계를 수정하면 관련된 빌더와 테스트 케이스 모두에서 컴파일 에러가 발생한다. 따라서 컴파일 에러를 기반으로 애플리케이션 객체를 수정할 때 어떤 테스트 케이스가 영향을 받는 지를 쉽게 파악할 수 있다(Michael Feathers는 컴파일러를 이용해 변경 지점을 파악하는 것을 LEANING ONT THE COMPILER라고 부른다). 결과적으로 코드 수정에 의한 변경 범위를 한 눈에 파악할 수 있으며 이것은 시스템의 캡슐화와 의존성 관리에 대한 힌트를 제공해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;이와 달리 OBJECT MOTHER는 픽스처 생성 부분을 생성 메서드 내부로 감추기 때문에 생성 메서드의 수정으로 인해 영향을 받는 테스트 케이스를 파악하기가 힘들다. 영향을 받는 테스트 케이스를 파악하는 유일한 방법은 변경으로 영향을 받는 생성 메서드를 호출하는 부분을 찾아 보는 것뿐이다. 물론 현대적인 IDE의 도움으로 이런 유형의 작업이 수월해 지기는 했지만 정적 언어를 사용하는 경우 컴파일러에 의존하는 것이 더 간편하고 정확하다. 결국 OBJECT MOTHER의 과도한 캡슐화는 오히려 코드 수정으로 인한 영향 정도를 파악하고 코드를 개선하는 작업을 방해한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;경험에 따르면 TEST DATA BUILDER 패턴을 사용할 경우 수정해야 하는 테스트 케이스의 수가 생각보다 많지 않은데 테스트 결과와 직접적으로 관련이 없는 정보는 테스트 케이스에서 감춰지기 때문이다. 오히려 즉각적인 컴파일 에러와 적은 양의 픽스처 생성 코드로 인해 수정해야 하는 부분을 직관적으로 파악할 수 있기 때문에 리팩토링에 소요되는 시간과 노력이 상대적으로 적어진다. OBJECT MOTHER 는 수 많은 생성 메서드의 범람과 메서드 호출에 사용되는 인자의 증가로 인해 사용과 이해가 어렵고 유사 메서드 간에 중복 코드가 발생하기 쉽기 때문에 관리가 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;요약하면 다음과 같다. TEST DATA BUILDER 패턴을 사용할 경우 애플리케이션 객체의 변경으로 인해 영향을 받는 테스트 케이스를 쉽게 파악할 수 있으며 각 테스트 케이스에 꼭 필요한 연관관계와 속성만을 노출하고 무관한 정보는 감추기 때문에 애플리케이션 객체의 리팩토링에 대한 면역력이 강하다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/41</guid>
      <comments>https://eternity-object.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 31 May 2024 10:55:56 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 6부</title>
      <link>https://eternity-object.tistory.com/39</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이전&amp;nbsp; 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 5부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내부 DSL(Internal DSL)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내부 DSL은 호스트 언어가 가진 제약 내에서 DSL을 구축한다. 호스트 언어에 대한 의존성은 양날의 검과 같다. 별도의 파서나 도구를 개발하지 않고도 호스트 언어가 제공하는 컴파일러만 있으면 쉽게 DSL을 구축할 수 있다. 그러나 DSL의 표현력이 호스트 언어의 표현력에 의해 제약을 받기 때문에 외부 DSL에 비해 언어의 유창함과 간결함이 상대적으로 떨어지는 경향이 있다. 또한 호스트 언어에서 사용 가능한 어떤 표현이라도 DSL 내에서 적법한 표현이기 때문에 범용 언어의 자유로움 속에서 DSL의 단순함을 잃어버릴 여지가 있다. 또한 언어에 대한 설계와 언어를 사용하는 사고 방식이 호스트 언어의 틀 안에 구속될 수 밖에 없다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내부 DSL을 구축할 때 사용하는 가장 중요한 추상화 메커니즘은 함수(또는 프로시저)다. 전통적인 커맨드-쿼리 인터페이스의 설계가 함수를 기반으로 하는 것처럼 유창한 인터페이스의 설계 역시 함수를 기반으로 한다. 차이점은 함수가 조합되는 방식에 있다. 커맨드-쿼리 인터페이스의 설계자는 각 함수가 독립적으로 사용될 것이라고 가정한다. 유창한 인터페이스의 설계자는 관련된 함수들이 더 커다란 문맥 안에서 조합되어 사용된다고 가정한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;이런 가정은 오퍼레이션의 이름에도 영향을 미친다. 커맨드-쿼리 인터페이스의 경우 개별 오퍼레이션이 독립적으로 사용되더라도 그 의도를 명확하게 전달할 수 있는 이름을 선택한다. 이에 비해 유창한 인터페이스를 구성하는 각각의 오퍼레이션 이름은 홀로 놓고 보면 그 의도를 명확하게 이해하기가 어렵다. 특정한 문맥 내에서 다른 오퍼레이션들과 함께 사용될 경우에만 의도와 의미가 명확해 진다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;이제 지구 검증 소프트웨어의 도메인 모델을 의미 모델(SEMANTIC MODEL)로 삼아 함수 기반의 내부 DSL을 구현할 수 있는 몇 가지 패턴을 살펴 보기로 하자. 여기에서는 구현 방법보다는 코드 상에 나타나는 인터페이스의 형태에 초점을 맞추어 각 패턴의 차이점을 논의한다. 구현과 관련된 자세한 사항은 Martin Fowler의 저서인 &amp;ldquo;&lt;a href=&quot;http://www.amazon.com/Domain-Specific-Languages-Addison-Wesley-Signature-Series/dp/0321712943&quot;&gt;Domain-Specific Languages&lt;/a&gt;&amp;rdquo;를 참고하기 바란다. 각 패턴의 실제 구현 방법은 첨부된 소스 파일을 참고하기 바란다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝(METHOD CHAINING)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝(METHOD CHAINIG)은 유창한 인터페이스 구현 방식 중에서 가장 대중적이고 널리 사용되는 방식이다. 쉽게 접할 수 있는 유명한 라이브러리나 프레임워크에서 설정 정보를 구성하는 API에 메서드 체이닝 방식을 적용하고 있기 때문에 대부분의 사람들은 유창한 인터페이스라고 하면 머릿속으로 메서드 체이닝 방식을 떠올린다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;메서드 체이닝 방식은 상태를 변경하는 커맨드(COMMAND) 메서드의 반환 값으로 호스트 객체를 반환함으로써 호스트 객체의 메서드를 연쇄적으로 호출할 수 있도록 한다. 따라서 하나의 표현식 안에 다수의 메서드 호출을 일련의 흐름으로 통합할 수 있다. &amp;lt;리스트 1&amp;gt;은 행성을 생성하는 코드를 메서드 체이닝 방식으로 구현한 내부 DSL 코드를 나타낸 것이다. &amp;lt;리스트 1&amp;gt;의 전체 예제가 하나의 표현식이라는 점에 주목하라.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 1&amp;gt; 메서드 체이닝 방식의 내부 DSL&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119292125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;planet()
        .atmosphere()
                .element(&quot;N&quot;, 0.8)
                .element(&quot;O&quot;, 0.2)
                .price(Money.wons(5000))
        .continent()
                .name(&quot;아시아&quot;)
                .nation()
                        .name(&quot;대한민국&quot;)
                        .price(1000)
                .nation()
                        .name(&quot;일본&quot;)
                        .price(1000)
        .continent()
                .name(&quot;유럽&quot;)
                .nation()
                        .name(&quot;영국&quot;)
                        .price(1000)
                .nation()
                        .name(&quot;프랑스&quot;)
                        .price(1000)
        .ocean()
                .name(&quot;태평양&quot;)
                .price(Money.wons(1000))
        .ocean()
                .name(&quot;대서양&quot;)
                .price(Money.wons(1000))
        .end();&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드들이 반환하는 객체가 의미 모델의 객체가 아니라는 점(즉, 도메인 모델의 객체가 아니라는 점)에 주의하라. 커맨드-쿼리 인터페이스로 작성된 의미 모델에 유창한 인터페이스를 혼합할 경우 인터페이스의 일관성이 무너지기 때문에 의미 모델 상의 객체를 메서드 체이닝의 대상으로 사용해서는 안 된다. 따라서 커맨드 메서드들이 반환하는 객체는 유창한 인터페이스 방식으로 구현되는 독립적인 표현식 빌더다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;각 표현식 빌더의 커맨드 메서드는 다음의 호출에 사용될 수 있는 표현식 빌더를 반환한다. 예를 들어 위 예제에서 planet() 메서드는 PlanetBuilder의 인스턴스를 반환하고 연이어 호출되는 PlanetBuilder의 atmosphere() 메서드는 AtmosphereBuilder 인스턴스를 반환한다. element() 메서드는 다시 AtmosphreBuilder 인스턴스를 반환함으로써 뒤 이은 element() 메서드가 연쇄적으로 호출될 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝은 커맨드-쿼리 분리(Command-Query Separation) 원칙을 위반한다. 즉, 상태를 변경하는 커맨드가 값을 반환한다. 또한 반환 값에 대한 메서드 호출을 허용하기 때문에 데메테르 법칙(Law of Demeter)을 위반한다. 따라서 메서드 체이닝 방식으로 작성된 코드는 많은 문헌에서 조롱의 대상이 되고는 하는 &amp;lsquo;열차 충돌(train wreck)&amp;rsquo;의 형태를 나타낸다. 일반적인 커맨드-쿼리 인터페이스에서는 데메테르 법칙의 위반이 (꼭 그런 것은 아니지만) 인터페이스 설계에 결함이 있을 수도 있음을 암시하지 DSL이라는 문맥에서는 인터페이스의 유창함을 만드는 핵심적인 구조를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝의 가장 큰 단점은 컨텍스트 변수를 관리하기가 어렵다는 점이다. &amp;lt;리스트 1&amp;gt;에서는 2개의 대륙을 생성하기 위해 2번의 continent() 메서드를 호출한다. 여기서 문제는 continent() 메서드가 호출될 때마다 기존에 생성 중이던 대륙 정보를 컨텍스트 변수에 보관한 후 새로운 대륙을 생성할 수 있도록 내부적으로 상태를 초기화해야 한다는 점이다. 생성하려는 의미 모델이 복잡하면 복잡할수록 다양한 객체를 생성하기 위해 관리해야 하는 컨텍스트 변수의 복잡도 역시 급격하게 증가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝의 두 번째 문제는 다양한 메서드가 다양한 위치에서 호출될 수 있다는 점이다. &amp;lt;리스트 1&amp;gt;에서 Continent를 생성하기 시작하는 continent() 메서드는 Atmosphere를 생성하는 도중에서도, 다른 Continent를 생성하는 도중에도, Nation을 생성하는 도중에도 호출될 수 있다. 따라서 continent() 메서드는 AtmosphereBuilder에도, ContinentBuilder에도, NationBuilder에도 추가되어야 한다. 이것은 각 표현식 빌더의 인터페이스를 오염시키고 표현식 빌더 간의 코드 중복과 의존성을 관리하기 어렵게 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝의 세 번째 문제는 의미 모델의 생성을 언제 중단할 것인가를 결정하기가 어렵다는 점이다. 가장 명시적인 방법은 &amp;lt;리스트 1&amp;gt;에서와 같이 마지막에 end() 메서드와 같은 종료 메서드를 추가하는 것이다. 그러나 end() 메서드는 대륙, 국가, 대양, 대기, 행성 등의 다양한 위치에서 호출될 수 있기 때문에 모든 표현식 빌더에 end() 메서드를 구현하고 컨텍스트 변수에 빌더 간의 연관 관계를 관리해야 한다는 단점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;함수 시퀀스(FUNCTION SEQUENCE)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;객체를 이용해 메서드를 연결하는 메서드 체이닝과 달리 함수 시퀀스(FUNCTION SEQUENCE) 방식은 상호 독립적인 함수의 나열을 통해 내부 DSL을 구현한다. 함수 시퀀스 방식으로 작성된 &amp;lt;리스트 2&amp;gt;의 코드를 보면 함수 간에 호출 순서 이외의 직접적인 연관성이 없음을 알 수 있다. 결과적으로 함수 호출 사이의 관계는 내부적인 파싱 데이터를 이용해 암묵적으로 관리되므로 함수 시퀀스는 다른 방식보다 상대적으로 많은 양의 컨텍스트 변수를 관리해야 한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 2&amp;gt; 함수 시퀀스 방식의 내부 DSL&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119315568&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;planet();
        atmosphere();
                element(&quot;N&quot;, 0.8);
                element(&quot;O&quot;, 0.2);
                price(5000);
        continent();
                name(&quot;아시아&quot;);
                nation();
                        name(&quot;대한민국&quot;);
                        price(1000);
                nation();
                        name(&quot;일본&quot;);
                        price(1000);
        continent();
                name(&quot;유럽&quot;);
                nation();
                        name(&quot;영국&quot;);
                        price(1000);
                nation();
                        name(&quot;프랑스&quot;);
                        price(1000);
        ocean();
                name(&quot;태평양&quot;);
                price(1000);
        ocean();
                name(&quot;대서양&quot;);
                price(1000);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;함수 시퀀스는 불필요한 표현 상의 잡음을 줄이기 위해 전역 함수의 형태를 취한다. 전통적으로 전역 함수는 두 가지 문제점을 가진다. 첫 째, 전역 함수는 전역 이름 공간(global namespace)에 위치하므로 모든 곳에서 호출될 수 있다. 둘 째, 전역 함수는 정적 파싱 데이터(static parsing data)를 필요로 한다. 두 가지 방식 모두 예측 불가능한 부수 효과(side effect)로 인해 프로그램을 불안정한 상태로 내몰 수 있다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;함수 시퀀스의 문제점을 해결할 수 있는 가장 좋은 방법은 객체를 이용해서 전역 함수와 전역 데이터의 범위를 객체 내부로 제한하는 것이다. 뒤에서 살펴 볼 객체 범위(OBJECT SCOPING) 기법은 전역 함수와 전역 데이터를 상위 클래스에 두고 함수 시퀀스를 포함하는 스크립트는 서브 클래스에 둠으로써 함수 시퀀스의 전역 가시성 문제를 해결한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포 함수(NESTED FUNCTION)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포 함수(NESTED FUNCTION)는 다른 함수 호출 결과를 함수의 인자로 취함으로써 다수의 함수들을 조합하는 방식이다. 메서드 체이닝이나 함수 시퀀스와 달리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 의미 모델의 계층 구조를 코드 상에 자연스럽게 표현할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서 외부 함수가 생성하는 객체는 내부 함수가 생성하는 객체를 계층적으로 포함한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 3&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;함수 방식의 내부 DSL&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119337890&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;planet(
        atmosphere(
                price(5000),
                element(&quot;N&quot;, 0.8),
                element(&quot;O&quot;, 0.2)
        ),
        continents(
                continent(
                        name(&quot;아시아&quot;),
                        nation(
                                name(&quot;대한민국&quot;),
                                price(1000)
                        ),
                       nation(
                                name(&quot;일본&quot;),
                                price(1000)
                        )
                ),
                continent(
                        name(&quot;유럽&quot;),
                        nation(
                                name(&quot;영국&quot;),
                                price(1000)
                        ),
                        nation(
                                name(&quot;프랑스&quot;),
                                price(1000)
                       )
                )
        ),
        oceans(
                ocean(
                        name(&quot;태평양&quot;),
                        price(1000)
                ),
                ocean(
                        name(&quot;대서양&quot;),
                        price(1000)
                )
        )
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝과 함수 시퀀스는 컨텍스트 변수를 이용해 파싱 데이터를 관리한다. 이에 비해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 함수 호출 결과를 함수의 인자로 취하기 때문에 중간 결과를 저장하기 위한 컨텍스트 변수가 필요 없다. 그러나 포함 관계에 따라 왼쪽에서 오른쪽으로 자연스럽게 읽히는 메서드 체이닝과 함수 시퀀스 방식과 달리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 생성 순서에 따라 안에서 밖으로 읽어야 하기 때문에 코드의 가독성이나 이해도가 상대적으로 떨어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 방식의 또 다른 단점은 함수 인자의 기본값을 지정할 수 없는 호스트 언어에서는 모든 함수 인자를 반드시 전달해야 한다는 것이다. 메서드 체이닝과 함수 시퀀스는 값이 불필요한 경우 메서드 호출을 생략함으로써 해당 값을 무시할 수 있지만 함수 시퀀스는 모든 인자값을 전달하지 않을 경우 컴파일 에러가 발생한다. 따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 모든 인자가 필수적인 경우에 적합하며 메서드 체이닝과 함수 시퀀스는 인자가 선택적인 경우에 적합하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;함수 역시 전역 함수의 형태를 취하므로 함수 시퀀스와 유사한 전역 가시성의 문제에 직면하게 된다. 최선의 해결 방법은 함수 시퀀스의 경우처럼 객체 범위 기법을 사용하는 것이다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;객체 범위(OBJECT SCOPING)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;객체 범위(OBJECT SCOPING) 기법은 함수 시퀀스와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수의 전역 가시성 문제를 해결하기 위해 사용된다. 객체 범위는 전역 함수의 이름 공간(namespace)로 사용할 클래스를 추가하고 전역 데이터의 범위를 객체 내부로 제한함으로써 전역 가시성의 문제를 해결한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;객체 범위 기법은 클래스 상속에 기반한다. 새로운 추상 클래스를 추가하고 전역 함수와 전역 데이터를 클래스의 멤버로 선언한다. 앞에서 함수 시퀀스 또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 방식으로 작성된 클래스들을 추상 클래스의 서브 클래스로 만든다. 이제 DSL 스크립트에서 사용하는 함수와 데이터는 전역 범위가 아니라 슈퍼 클래스의 인스턴스 범위로 제한된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;앞에서 살펴 본&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;함수 예제에 객체 범위 기법을 적용해 보자. 먼저 DSL에서 사용할 모든 함수를 포함할 슈퍼 클래스를 정의한다(&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 방식을 사용함으로써 별도의 컨텍스트 변수가 불필요하지만 함수 시퀀스 방식을 사용한다면 컨텍스트 변수 역시 함께 선언해야 할 것이다).&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 4&amp;gt; 객체 범위 기법을 위해 추가한 슈퍼 클래스&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119365291&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PlanetBuilder {
        protected Money price(long price) {
                return Money.wons(price);
        }
   
        protected Element element(String name, double ratio) {
                return Element.element(name, Ratio.of(ratio));
        }

        protected Atmosphere atmosphere(Money price, Element ... elements) {
                return new Atmosphere(price, elements);
        }

        protected String name(String name) {
                return name;
        }

        protected Nation nation(String name, Money price) {
                return new Nation(name, price);
        }

        protected List&amp;lt;Ocean&amp;gt; oceans(Ocean ... oceans) {
                return Arrays.asList(oceans);
        }

        protected Ocean ocean(String name, Money price) {
                return new Ocean(name, price);
        }

        protected List&amp;lt;Continent&amp;gt; continents(Continent ... continents) {
                return Arrays.asList(continents);
        }

        protected Continent continent(String name, Nation ... nations) {
                return new Continent(name, nations);
        }

        protected Planet planet(Atmosphere atmosphere,
                List&amp;lt;Continent&amp;gt; continents, List&amp;lt;Ocean&amp;gt; oceans) {
                return new Planet(atmosphere, continents, oceans);
        }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL 스크립트를 포함하는 클래스를 PlanetBuilder의 서브 클래스로 선언한다. 이제 슈퍼 클래스에 정의된 메소드를 사용할 수 있으므로 전역 이름 공간의 눈치를 볼 필요가 없다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 5&amp;gt; 슈퍼 클래스에 정의된 메서드를 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 방식의 DSL&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119391100&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class EarthBuilder extends PlanetBuilder {
        public Planet build() {
                return planet(
                                    atmosphere(
                                            price(5000),
                                            element(&quot;N&quot;, 0.8),
                                            element(&quot;O&quot;, 0.2)
                                    ),
                                    continents(
                                            continent(
                                                    name(&quot;아시아&quot;),
                                                    nation(
                                                            name(&quot;대한민국&quot;),
                                                            price(1000)
                                                    ),
                                                    nation(
                                                            name(&quot;일본&quot;),
                                                            price(1000)
                                                    )
                                            ),
                                           continent(
                                                    name(&quot;유럽&quot;),
                                                    nation(
                                                            name(&quot;영국&quot;),
                                                            price(1000)
                                                    ),
                                                    nation(
                                                            name(&quot;프랑스&quot;),
                                                            price(1000)
                                                    )
                                           )
                                     ),
                                    oceans(
                                            ocean(
                                                    name(&quot;태평양&quot;),
                                                    price(1000)
                                            ),
                                            ocean(
                                                    name(&quot;대서양&quot;),
                                                    price(1000)
                                            )
                                    )
                           );
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;b&gt;내부 DSL 방식 조합하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;메서드 체이닝, 함수 시퀀스,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수, 객체 범위 방식은 상호 배타적인 것이 아니며 각각의 장점을 취하기 위해 혼합해서 사용할 수 있다. &amp;lt;리스트 6&amp;gt;은 4가지 내부 DSL패턴을 모두 사용해서 두 개의 행성을 생성하는 DSL 코드를 나타낸 것이다. 두 개의 독립적인 planet() 함수 호출은 함수 시퀀스의 형태를 취한다. planet() 함수는 필수 값들을 인자로 전달하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용하고 있으며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에 전달할 인자들을 생성하기 위해서는 메서드 체이닝 기법을 사용한다. 여러 개의 대륙(continent()의 반환 값)과 대양(ocean()의 반환 값)을 목록으로 변환하는 continents()와 oceans()는 슈퍼 클래스인 PlanetBuilder으로부터 상속을 받는다. 따라서 &amp;lt;리스트 6&amp;gt;은 전역 범위의 함수와 데이터를 내부적으로 은폐하기 위해 객체 범위 방식을 따른다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;리스트 6&amp;gt; 다양한 내부 DSL 패턴의 조합&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717119405882&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BuilderCombination extends PlanetBuilder {
        public void build() {
                planet(
                        atmosphere()
                                .elements(element(&quot;N&quot;, 0.8), element(&quot;O&quot;, 0.2))
                                .price(5000),
                        continents(
                                continent().name(&quot;아프리카&quot;)
                                        .nations(
                                                nation().name(&quot;이집트&quot;).price(1000),
                                                nation().name(&quot;콩고&quot;).price(1000)),
                                continent().name(&quot;북아메리카&quot;)
                                        .nations(
                                                nation().name(&quot;캐나다&quot;).price(1000),
                                                nation().name(&quot;미국&quot;).price(1000))),
                        oceans(
                                ocean().name(&quot;남극해&quot;).price(1000),
                                ocean().name(&quot;북극해&quot;).price(1000)));
       
                planet(
                        atmosphere()
                                .elements(element(&quot;N&quot;, 0.8), element(&quot;O&quot;, 0.2))
                                .price(5000),
                        continents(
                                continent().name(&quot;아시아&quot;)
                                       .nations(
                                               nation().name(&quot;대한민국&quot;).price(1000),
                                               nation().name(&quot;일본&quot;).price(1000)),
                                continent().name(&quot;유럽&quot;)
                                       .nations(
                                               nation().name(&quot;영국&quot;).price(1000),
                                               nation().name(&quot;프랑스&quot;).price(1000))),
                                oceans(
                                        ocean().name(&quot;태평양&quot;).price(1000),
                                        ocean().name(&quot;대서양&quot;).price(1000)));
                ......
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 6&amp;gt;과 같이 여러 방식을 조합할 경우의 문제점은 DSL의 사용 방법이 복잡해질 수 있다는 것이다. 어떤 부분에는 메서드 체이닝 기법이 사용되고 어떤 부분에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;내포&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수 기법이 사용되는 지를 예측하기 어려울 경우 전체적인 DSL의 유용성이 저하된다. DSL 방식을 조합할 경우 일정한 규칙에 따라 사용 방식을 쉽게 예측 가능하도록 언어를 설계해야 한다. 빠른 속도로 정보를 처리할 수 있는 DSL의 유창함은 DSL로 작성된 코드를 읽는 사람들뿐만 아니라 DSL을 이용해 코드를 작성하는 사람에게도 중요하다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;br /&gt;&lt;br /&gt;지금까지 내부 DSL에 관한 짧지만 중요한 몇 가지 개념을 살펴 보았다. 이제 앞에서 살펴본 픽스처 생성 문제를 해결하기 위해 DSL을 적용하는 문제로 돌아가 OBJECT MOTHER 패턴을 대체할 수 있는 TEST DATA BUILDER 패턴에 관해 살펴보기로 하자. TEST DATA BUILDER 패턴의 핵심은 테스트 픽스처 생성이라는 제한된 도메인에 특화된 언어를 내부 DSL의 형태로 구현하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;다음 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 7부&lt;/a&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/39</guid>
      <comments>https://eternity-object.tistory.com/39#entry39comment</comments>
      <pubDate>Fri, 31 May 2024 10:37:40 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 5부</title>
      <link>https://eternity-object.tistory.com/38</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 4부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;소프트웨어의 본질적인 복잡성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;프레더릭 브룩스는 그의 기념비적인 논문 &amp;ldquo;은총알은 없다(No Silver Bullet)&amp;rdquo;에서 소프트웨어 개발과 관련된 작업을 본질적인 작업(essential task)과 부차적인 작업(accidental task)으로 구분하고 있다. 브룩스에 따르면 본질적인 작업이란 도메인 내의 추상적인 개념들을 명세하고 설계하고 구현 가능한 상태로 정교하게 다듬는 정신적인 작업을 의미한다. 이에 반해 부차적인 작업이란 고안된 추상화를 공간과 시간 제약 속에서 프로그래밍 언어로 변환하는 작업을 말한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;프레더릭 브룩스는 불행히도 소프트웨어 개발과 관련된 전체 작업 가운데 부차적인 작업이 차지하는 비율이 90%를 넘지 않는 한 부차적인 작업에 쏟는 노력을 완전히 제거한다고 해서 소프트웨어 공학의 현실이 크게 개선될 것 같지 않다는 비관적인 전망을 내놓았다. 소프트웨어와 관련된 본질적인 어려움은 도메인에 내재된 개념과 추상화를 다루는 정신적인 작업과 관련된 것이지 프로그래밍 언어나 기술적인 문제가 아니다. 따라서 10년 내에 생산성을 10배 이상 혁신시킬 수 있는 은총알이란 존재하지 않으며 도전 대상은 부차적인 작업이 아니라 본질적인 작업과 관련된 것이어야 한다고 주장한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;소프트웨어 개발의 본질적인 문제 중 하나는 복잡성(complexity)이다. 소프트웨어는 인간이 창조한 다른 물리적인 기계와 다르게 자연과 유사한 프랙탈적인 복잡성을 가진다. 이미 존재하는 공통 부품들을 조합해서 다양한 요소를 만들 수 있는 물리적인 기계와 달리 소프트웨어는 부품 간의 공통점보다는 차이점이 더 크기 때문에 소프트웨어의 확장은 곧 소프트웨어를 구성하는 부품 수의 급격한 증가를 초래한다. 또한 부품의 수가 증가하면 증가할 수록 부품 간의 상호작용 그래프 역시 급격하게 복잡해지기 시작한다. 따라서 소프트웨어 전체의 복잡성은 요소의 수에 선형적으로 비례하기보다는 훨씬 가파르게 증가한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;소프트웨어의 복잡성은 소프트웨어가 가진 본질적인 어려움이므로 제거가 불가능하다. 그러나 우리가 가진 인지능력의 한계 내에서 복잡성을 좀 더 다루기 쉬운 형태로 재단하는 것은 가능하다. 소프트웨어를 구성하는 요소들을 계층적인 형태(hierarchy)로 배열하고 각 계층에서 다루어야 하는 복잡성을 상대적으로 좁은 범위로 제한함으로써 한 순간에 다루어야 하는 복잡성의 수준을 낮출 수 있다. 그리고 이것이 우리가 고수준 프로그래밍 언어를 사용하거나 다양한 라이브러리와 프레임워크를 사용하는 이유다(복잡도를 해결하기 위한 또 다른 방법은 점증적으로 소프트웨어를 개발함으로써 한 번에 다루어야 하는 기능의 수를 제한하는 것이다).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;프로그래밍 언어의 역사는 프로시저 추상화와 데이터 추상화와 같은 소프트웨어의 비본질적인 복잡도를 낮추기 위한 다양한 시도의 연속이다. 라이브러리와 프레임워크의 역사는 재사용 가능한 부품과 설계를 제공함으로써 개발자들이 본질적인 복잡도에 집중할 수 있도록 하기 위한 시도의 연속이다. 그러나 언어, 라이브러리, 프레임워크와 같은 기술적인 발전에도 불구하고(한 편으로는 기술적인 발전 때문에) 소프트웨어 개발자들이 감내해야 하는 소프트웨어의 본질적인 복잡도는 점점 더 증가하고 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 특화 언어(Domain-Specific Language)&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;최근 들어 소프트웨어의 본질적인 복잡도를 해결할 수 있을 것으로 기대되는 한 가지 설계 방식이 개발자들의 주목을 끌고 있다. 사실 이 방식은 UNIX의 전통과 함께 오랜 시간 동안 소프트웨어 개발자들의 도구 상자 안에 존재해 왔던 것이다. UNIX 커뮤니티에는 특정한 응용 영역의 문제를 해결하기 위해 그 영역에만 적용할 수 있는 특수한 언어를 만들어 문제를 해결하는 오랜 전통이 존재한다. 이런 언어를 UNIX 커뮤니티에서는 &amp;ldquo;작은 언어(little language)&amp;rdquo;, 또는 &amp;ldquo;미니 언어(mini language)&amp;rdquo;라고 부른다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UNIX에는 미니언어(mini language)를 특수한 응용 영역에서 사용하는 전통이 있다. 미니언어의 도움을 받으면 프로그램의 라인 수를 현저하게 줄일 수 있다. UNIX 역사에서는 이와 같이 특수한 영역에 사용하는 언어들을 작은 언어(little language), 또는 미니언어(mini language)라 불렀다. 이렇게 이름 붙인 까닭은 범용 언어에 비해 크기가 작고 복잡도 또한 낮았기 때문이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Eric Raymond,&amp;nbsp;&lt;a style=&quot;color: #000000;&quot; href=&quot;http://kangcom.com/sub/view.asp?sku=200408050002&amp;amp;mcd=571&quot;&gt;Art of UNIX Programming&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;UNIX의 미니 언어 전통 안에서 개발자는 도메인에서 사용되는 어휘, 문법, 의미론을 사용함으로써 도메인에 특화된 새로운 언어를 창조하고 이 언어를 이용해 프로그램을 작성한다. 이런 방식으로 작성된 애플리케이션은 프로그래밍 언어의 계층 위에 특정 도메인을 위한 특수 목적 언어 계층이 위치하는 계층형 아키텍처(Layered Architecture)의 특수한 형태를 띠게 된다. 즉, 하나의 프로그램 안에 다수의 언어 계층이 존재하게 된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;프로그래밍 언어로 작성된 추상화 계층 위에 도메인 언어로 작성된 추상화를 쌓아 올리는 것은 복잡성을 극복하기 위해 소프트웨어를 계층적인 형태(hierarchy)로 분할하는 전통적인 설계 방식을 확장한 것이다. UNIX의 미니 언어를 이용하면 도메인을 구성하는 추상 개념을 통해 사고하고 프로그래밍할 수 있다. 따라서 개발자는 부차적인 복잡성의 무게에 짓눌리지 않고도 본질적인 복잡성의 해결에 집중할 수 있다. 개발자가 도메인의 용어로 프로그램을 개발하면 컴파일러나 도구가 도메인의 용어를 컴퓨터가 이해할 수 있는 명령어로 변환한다. 즉, 명세가 실행 가능한 코드가 되는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;언어의 적용 범위를 특정한 도메인으로 한정시킴으로써 작고 간결한 언어를 얻을 수 있다. 더 단순하고 추상적인 언어를 사용함으로써 구현 세부사항들에 짓눌리지 않고 도메인의 문제를 해결하는 작업에 집중할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기에서는 전통적인 &amp;lsquo;미니언어&amp;rsquo;라는 용어로써 간결함과 작음을 지향하는 설계의 지혜를 강조하도록 하겠다. 특수한 영역을 위한 미니언어는 대체로 매우 강력한 설계 아이디어다. 보다 높은 수준의 언어로 문제를 해결하는 적절한 방법, 규칙, 그리고 알고리즘을 정의할 수 있기 때문에 전체적인 복잡도를 낮게 유지하면서도 낮은 수준에서 손으로 코딩한 것과 대등한 결과를 얻을 수 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Eric Raymond, &lt;a style=&quot;color: #000000;&quot; href=&quot;http://kangcom.com/sub/view.asp?sku=200408050002&amp;amp;mcd=571&quot;&gt;Art of UNIX Programming&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;지금까지 살펴본 UNIX의 미니 언어와 같이 &amp;ldquo;특정한 도메인에 초점을 맞춘 제한적인 표현력을 가진 컴퓨터 프로그래밍 언어&amp;rdquo;를 도메인 특화 언어(Domain-Specific Language), 또는 간단히 DSL이라고 부른다. &amp;ldquo;도메인 특화 언어&amp;rdquo;라는 용어를 구성하고 있는 각 단어를 분석함으로써 DSL의 특성을 이해할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인(Domain)&amp;nbsp;- 도메인이란 &amp;ldquo;지식이나 영향력, 또는 활동의 영역&amp;rdquo;을 의미한다. DSL은 특정한 도메인 내에서 사용될 것을 가정하는 언어다. 특정 도메인에 초점을 맞춤으로써 애플리케이션을 자연스럽게 성장시킬 수 있는 &amp;ldquo;문맥(context)&amp;rdquo;을 제공할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특화(Specific)&amp;nbsp;- DSL에서 &quot;특화&quot;라는 단어는 &amp;ldquo;제한된 문맥(bounded context)&amp;rdquo;과 &amp;ldquo;표현력의 제약(limited expressiveness)&amp;rdquo;을 강조한다. &amp;ldquo;제한된 문맥&amp;rdquo;이란 언어가 적용될 수 있는 영역의 범위가 제한적이라는 것이다. DSL은 제한된 영역에 대해 사고하고 이해하고 표현하기 위한 언어다. 제한된 언어는 오직 언어가 작은 도메인에 대해 명확한 초점을 가질 때만 유용하다. &amp;ldquo;표현력의 제약&amp;rdquo;은 도메인을 서술하는데 사용할 수 있는 구성 요소를 제한한다는 것을 의미한다. DSL은 모든 분야에 적용 가능한 범용 프로그래밍 언어를 목표로 하지 않는다. 범용 프로그래밍 언어는 다양한 데이터 타입, 제어 로직, 추상적인 구조를 제공한다. 다양한 언어 요소는 언어를 다양한 환경에서 사용할 수 있도록 한다는 점에서는 유용하지만 반대로 언어를 배우고 사용하기 어렵게 만든다. DSL은 배우기 쉽고 사용하기 편해야 한다. 따라서 제어나 분기와 같은 요소는 가급적 배제하고 도메인에 적합한 최소한의 기본 요소만을 포함한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;언어(Language)&amp;nbsp;- DSL의 언어적인 특성은 &amp;ldquo;프로그래밍 언어&amp;rdquo;와 &amp;ldquo;유창함(fluency)&amp;rdquo;이라는 두 가지 측면을 포괄한다. 먼저 DSL은 프로그래밍 언어다. 따라서 컴퓨터가 수행할 수 있는 명령을 지시하기 위해 사람들이 사용할 수 있어야 한다. 그러나 DSL은 일반적인 프로그램 언어보다 특정 문맥(context)에서의 유창함(fluency)을 더욱 더 강조한다. 즉, 개별적인 표현이 아닌 표현들이 함께 조합되는 방식으로부터 나타나는 유창함이 DSL의 설계를 이끄는 원동력이 된다. 그러나 유창함이 자연어와의 유사함을 의미하는 것이 아니라는 점에 주의해야 한다. DSL은 단지 프로그래밍 언어일 뿐이다. 프로그래밍 언어가 태생적으로 안고 있는 표현력의 제약 속에 유창한 흐름이 존재할 때 DSL의 강력함이 드러난다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL이 가져야 하는 기본적인 3가지 특징은 단순함(simplicity), 간결함(conciseness), 유창함(fluency)이다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순함(Simplicity)&amp;nbsp;&amp;ndash; DSL을 사용하는 이유가 도메인과 관련된 복잡성을 낮추는 것이므로 DSL은 반드시 단순해야 한다. 도메인에 익숙한 개발자나 클라이언트는 DSL로 작성된 스크립트를 보고 그 의미를 쉽게 파악할 수 있어야 한다. 도메인에서 사용되는 어휘를 사용하고 언어를 익히기 위해 필요한 문법을 제한함으로써 이해하기 쉬운 단순한 언어를 창조할 수 있다. DSL은 튜링 완전(turing completeness)해서는 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간결함(Conciseness)&amp;nbsp;&amp;ndash; DSL은 간단하면서도 표현력이 있는 문법을 선택해야 한다. 문제와 관련되지 않은 군더더기 없는 표현을 최대한 제거함으로써 코드를 간결하게 만들 수 있다. 두 개의 행렬을 곱하는 문제가 있을 때 matrixA * matrixB와 matrixA.multiply(matrixB) 둘 중 어떤 것이 더 간결해 보이는가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유창함(Fluency) - 언어의 유창함이란 &amp;ldquo;얼마나 빠르고 쉽게 사용자들이 정보를 처리할 수 있는가&amp;rdquo;로 정의할 수 있다. DSL의 유창함을 지원하는 두 가지 요소는 &amp;ldquo;전문 용어(jargon)&amp;rdquo;와 &amp;ldquo;문맥(context)&amp;rdquo;이다. 경험이 많은 프로그래머들은 특별한 설명이 없이도 &amp;ldquo;STRATEGY 패턴&amp;rdquo;이라는 전문 용어에 함축되어 있는 수많은 의미를 빠르게 이해한다. 대화 중간에 &amp;ldquo;배&amp;rdquo;라는 단어가 나올 때 사람들은 배가 무엇을 의미하는 지를 어떻게 알 수 있을까? 사람들은 어떤 항구에 정박해 있는 배에 관해 이야기하고 있었을지도 모른다. 아니면 출근 길에 사온 과일에 관해 이야기하고 있었을 것이다. 그것도 아니면 배탈이 났던 경험에 관해 이야기하고 있었을 수도 있다. 대화의 문맥이 존재한다면 별다른 설명이 없이도 &amp;ldquo;배&amp;rdquo;라는 단어가 무엇을 의미하는 지를 빠르고 쉽게 이해할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 특화 언어의 구조&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;프로그램의 구성 요소를 설계할 때 고려해야 하는 기본적인 인터페이스 설계 원칙 중 하나는 커맨드(Command)와 쿼리(Query)를 분리하는 것이다. &amp;ldquo;커맨드-쿼리 분리(Command-Query Separation, CQS)&amp;rdquo; 원칙의 기본 개념은 메서드는 반드시 부수 효과를 발생시키는 커맨드이거나 부수 효과를 발생시키지 않는 쿼리 둘 중 하나여야하고 두 가지 특성 모두를 가져서는 안 된다는 것이다. 즉, 커맨드 메서드는 상태를 변경하는 대신 값을 반환해서는 안되며 쿼리 메서드는 값을 반환화는 대신 상태를 변경해서는 안 된다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부수 효과를 발생시키지 않는 것만을 함수로 제한함으로써 소프트웨어에서 말하는 &amp;ldquo;함수&amp;rdquo;의 개념이 일반 수학에서의 개념과 상충되지 않도록 한다. 우리는 오브젝트를 변경하지만 직접적으로 값을 반환하지 않는 커맨드(Command)와 오브젝트에 대한 정보를 반환하지만 변경하지는 않는 쿼리(Query) 간의 명확한 구분을 유지할 것이다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Betrand Meyer, &lt;a style=&quot;color: #000000;&quot; href=&quot;http://www.amazon.com/Object-Oriented-Software-Construction-CD-ROM-Edition/dp/0136291554&quot;&gt;Object-Oriented Software Construction 2nd Edition&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;커맨드-쿼리 분리 원칙은 객체들을 독립적인 기계로 보는 객체지향의 오랜 전통에 기인한다. 이 관점에서 객체는 블랙박스이며 객체의 인터페이스는 객체의 관찰 가능한 상태를 보기 위한 일련의 디스플레이와 객체의 상태를 변경하기 위해 누를 수 있는 버튼의 집합이다. 이런 스타일의 인터페이스를 사용함으로써 객체의 캡슐화와 다양한 문맥에서의 재사용을 보장할 수 있다. Martin Fowler는 커맨드-쿼리 분리 원칙에 따라 작성된 객체의 인터페이스를 &amp;ldquo;커맨드-쿼리 인터페이스(Command-Query Interface)&amp;rdquo;라고 부른다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;그림 1&amp;gt;은 기계 메타포 관점에서 바라보는 객체의 인터페이스를 개념적으로 표현한 것이다. 디스플레이를 위한 타원을 누르면 기계의 현재 상태를 출력하지만 상태는 변경하지 않는다. 반면에 박스 버튼을 누르면 기계의 상태를 변경하지만 변경된 상태에 관한 어떤 정보도 외부로 노출하지 않는다. 즉, 타원은 쿼리 메서드이고 박스는 커맨드 메서드다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmIdUl/btsHIeCIjoc/V6xBxBwMIDxieWAMN9e7s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmIdUl/btsHIeCIjoc/V6xBxBwMIDxieWAMN9e7s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmIdUl/btsHIeCIjoc/V6xBxBwMIDxieWAMN9e7s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmIdUl%2FbtsHIeCIjoc%2FV6xBxBwMIDxieWAMN9e7s0%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;472&quot; height=&quot;321&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 1&amp;gt; 기계로서의 객체 메타포&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;커맨드-쿼리 인터페이스를 구성하는 각 오퍼레이션들은 독립적인 상황에서 사용될 수 있도록 이름 지어진다. &amp;lt;그림 1&amp;gt;에서 insert() 오퍼레이션은 empty() 오퍼레이션이나 delete() 오퍼레이션에 독립적으로 호출 가능하다. 이와 같이 인터페이스를 구성하는 오퍼레이션 간에 의존성이 적으면 적을수록 다양한 문맥에서 사용 가능하고 자유롭게 조합될 수 있는 객체를 만들 수 있다. 따라서 커맨드-쿼리 인터페이스는 사용 문맥과의 결합도를 최소화하도록 설계된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이와 대조적으로 DSL의 인터페이스는 사용되는 문맥에 기반한 유창함을 강조한다. 인터페이스를 구성하는 각 오퍼레이션은 사용되는 문맥에 강하게 결합되며 함께 사용되는 오퍼레이션들과 밀접하게 연관된다. 즉, 인터페이스를 구성하는 오퍼레이션들은 문법이나 의미론적으로 서로 강한 언어적인 연관성을 가지도록 설계된다. Martin Fowler와 Eric Evans는 DSL에 사용되는 인터페이스 형식을 전통적인 커맨드-쿼리 인터페이스 형식과 구분하기 위해 &amp;ldquo;유창한 인터페이스(Fluent Interface)&amp;rdquo;라고 부른다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;커맨드-쿼리 인터페이스와 유창한 인터페이스를 함께 놓고 보았을 때 가장 두드러지는 차이점은 오퍼레이션의 이름을 짓는 방식에 있다. 커맨드-쿼리 인터페이스는 문맥과 무관하게 독립적으로 사용될 것을 가정하기 때문에 설계자는 오퍼레이션의 이름 안에 최대한 많은 정보를 담으려고 노력한다. 이에 반해 유창한 인터페이스는 특정한 사용 문맥을 가정하기 때문에 독립적으로는 의미가 없고 연결되는 문맥 상에서 충분한 의미를 가질 수 있는 간결한 이름을 사용하려고 노력한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;커맨드-쿼리 인터페이스는 훌륭한 객체 인터페이스를 낳는다. 유창한 인터페이스는 훌륭한 DSL 인터페이스를 낳는다. 문제는 하나의 객체에 두 가지 방식의 인터페이스를 조합할 경우 이해하기 어려워 진다는 것이다. 따라서 DSL 설계와 관련해서 주의해야 할 기본 원칙은 커맨드-쿼리 인터페이스와 유창한 인터페이스를 서로 섞지 않는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL을 구조화하는 일반적인 방식은 커맨드-쿼리 인터페이스를 제공하는 객체 모델 위에 유창한 인터페이스 방식의 독립적인 DSL 레이어를 구축하는 것이다. 이처럼 커맨드-쿼리 인터페이스를 제공하는 하부 모델 위에서 유창한 인터페이스를 제공하는 독립적인 언어 계층을 표현식 빌더(EXPRESSION BUILDER)라고 부른다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;이런 관점에서 DSL은 커맨드-쿼리 인터페이스 기반의 객체 모델을 제어할 수 있는 유창한 인터페이스를 제공하는 일종의 FA&amp;Ccedil;ADE로 볼 수 있다. 하부의 객체 모델은 DSL에 대한 의미를 제공하고 DSL은 하부 모델을 표현하기 위한 또 다른 언어를 제공한다. Martin Fowler는 DSL에 의미론을 제공하는 커맨드-쿼리 인터페이스 기반의 객체 모델을 의미 모델(SEMANTIC MODEL)이라고 부른다. 패러다임이 객체지향일 경우 일반적으로 의미 모델은&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://martinfowler.com/eaaCatalog/domainModel.html&quot;&gt;DOMAIN MODEL&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴의 형태를 따른다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; 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;1.jpeg&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8oNy/btsHIzfwMoA/yCdKLYwiEtrORfLDevV5zk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8oNy/btsHIzfwMoA/yCdKLYwiEtrORfLDevV5zk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8oNy/btsHIzfwMoA/yCdKLYwiEtrORfLDevV5zk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8oNy%2FbtsHIzfwMoA%2FyCdKLYwiEtrORfLDevV5zk%2Fimg.jpg&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;613&quot; height=&quot;432&quot; data-filename=&quot;1.jpeg&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 2&amp;gt; 의미 모델을 생성하는 표현식 빌더로서의 DSL&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL의 일반적인 구조는 커맨드-쿼리 인터페이스를 제공하는 의미 모델(SEMANTIC MODEL) 위에 유창한 인터페이스를 제공하는 표현식 빌더(EXPRESSION BUILDER)를 구축하는 것이다. 표현식 빌더(EXPRESSION BUILDER)는 말 그대로 하부의 의미 모델(SEMANTIC MODEL)을 구축하기 위해 유창한 표현력을 제공하는 빌더다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 특화 언어의 구분&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL을 구축하는 방법은 다음과 같은 3가지 범주로 구분할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 DSL(external DSL)&amp;nbsp;&amp;ndash; 애플리케이션 작성에 사용되는 주 언어(호스트 언어라고 부른다)가 아닌 별도의 독립적인 언어를 이용해서 DSL을 작성한다. 일반적으로 외부 DSL은 자체적인 문법을 가지지만 XML과 같은 기존 언어의 문법을 차용하기도 한다. 외부 DSL로 작성된 스크립트는 텍스트 파싱이나 코드 생성 기법을 사용해서 호스트 언어로 작성된 의미 모델로 파싱된다. 외부 DLS의 예로는 정규 표현식, SQL, Awk, Spring이나 Hibernate에서 사용되는 XML 설정 파일을 들 수 있다. 앞에서 살펴본 UNIX의 미니 언어 전통의 대부분은 외부 DSL의 범주에 속한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부 DSL(internal DSL)&amp;nbsp;&amp;ndash; 외부 DSL과 달리 독립적인 언어가 아닌 범용 프로그래밍 언어를 사용하되 특수한 목적을 위해 제한된 방법으로 사용하는 방식을 의미한다. 내부 DSL로 작성된 스크립트는 범용 언어의 맥락에서 유효한 코드지만 범용 언어의 일부 기능만을 사용하도록 그 범위가 제한된다. 따라서 내부 DSL로 작성된 스크립트는 호스트 언어의 문법적인 특성을 지니면서도 제한된 표현력과 특유의 유창함으로 인해 독자적으로 생성된 언어라는 느낌을 준다. 내부 DSL의 전통은 Lisp 언어에 그 뿌리를 두고 있으며 최근 들어서는 Ruby 커뮤니티에서 전통을 이어가고 있다. Ruby 언어는 내부 DSL로 사용하기에 용이한 유연한 메타 프로그래밍 메커니즘을 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;언어 워크벤치(language workbench)&amp;nbsp;&amp;ndash; 언어 워크벤치는 DSL을 정의하고 개발하기 위한 용도로 개발된 IDE다. 언어 워크벤치는 외부 DSL이나 내부 DSL에 비해 상대적으로 역사가 짧고 최근에 들어서 주목을 받기 시작한 새로운 영역이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;본 글에서는 테스트 케이스 작성이라는 도메인과 관련된 다양한 문제를 해결하기 위해 내부 DSL 의 기법을 적용하는 방법에 초점을 맞춘다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문맥(Context)의 중요성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;인간의 의사소통은 참여자들이 공유하는 &amp;ldquo;문맥(context)&amp;rdquo;을 기반으로 이루어진다. 회의에 늦게 참석한 사람이 아젠다를 알고 있음에도 도착 즉시 대화에 참여할 수 없는 이유는 참석자들 간에 오간 대화의 맥락을 알 지 못하기 때문이다. 10년 만에 만난 친구와 큰 어려움 없이 대화가 가능한 이유는 과거의 추억을 공유하고 있기 때문이다. 문맥을 공유할 경우 적은 수의 단어만으로도 풍부한 의미를 전달할 수 있다. 직장 상사를 가리키며 얼굴을 찌푸리는 동료를 보았을 때 그 동료가 하고 싶은 말이 무엇인지 알 수 있는 이유는 직장 상사에 대해 유사한 기억과 감정을 공유하고 있기 때문이다. 마음에 드는 이성을 만났을 때 사람들은 본능적으로 함께 공유할 수 있는 주제를 찾으려고 노력한다. 일단 문맥을 공유하고 나면 문맥을 바탕으로 대화의 속도를 높일 수 있다. 문맥은 대화를 자연스럽게 연결시켜주는 접착제와 같은 역할을 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;문맥은 사람들이 사고하는 방식에도 영향을 미친다. 사람들은 동일한 단어를 듣더라도 대화의 문맥에 따라 마음 속에 서로 다른 이미지를 떠올릴 수 있다. 문맥은 사고의 폭을 제한함으로써 대화의 의미를 선명하게 채색한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL의 유창함은 신호 대비 잡음 비율을 감소시키는 것으로부터 나온다. 제한된 표현력의 한계 안에서 최대한 풍부한 의미를 전달하기 위해서는 구성 요소들을 문맥에 의존하도록 만드는 것이 중요하다. 이것이 앞에서 설명한 커맨드-쿼리 인터페이스(Command-Query Interface)와 유창한 인터페이스(Fluent Interface) 사이의 중요한 차이점이다. 커맨드-쿼리 인터페이스는 각 오퍼레이션 호출을 최대한 독립적으로 만들기 위해 노력한다. 이에 비해 유창한 인터페이스는 암묵적인 문맥을 기반으로 오퍼레이션들이 조합될 수 있도록 설계한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL의 일반적인 용도는 의미 모델(SEMANTIC MODEL)로 표현된 복잡한 객체들의 집합을 생성하는 것이다. 복잡한 객체들을 하나의 오퍼레이션으로 생성할 수는 없기 때문에 DSL에서는 다양한 오퍼레이션 호출의 흐름을 따라 점진적으로 생성에 필요한 객체의 상태를 수집한 후 최종적으로 수집된 상태 정보를 이용해 객체 집합을 생성한다. 따라서 DSL에서는 생성 중인 의미 모델의 중간 상태를 저장하기 위한 변수를 필요로 한다. 이처럼 내부 DSL에서 의미 모델의 중간 상태를 저장하기 위해 내부적으로 관리하는 변수를 컨텍스트 변수(CONTEXT VARIABLE)라고 부른다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;컨텍스트 변수(CONTEXT VARIABLE)의 역할은 대화에서 문맥이 제공하는 역할과 동일하다. 즉, 내부 DSL의 오퍼레이션 호출 간에 공유될 수 있는 문맥을 제공함으로써 호출들을 연결하는 접착제 역할을 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;DSL은 오퍼레이션의 실행 결과를&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)에 누적시킨다. 이후에 실행되는 다양한 오퍼레이션들은&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)에 누적된 정보를 입력으로 사용한다.&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)를 이용함으로써 오퍼레이션 실행 간에 명시적으로 전달해야 하는 정보를 암시적으로 만듦으로써 신호 대비 잡음 비율을 줄인다. 따라서 언어를 유창하게 만든다. 그러나 여러 오퍼레이션 호출 사이에서&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)를 일관된 상태로 유지해야 하기 때문에&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)가 복잡해 질수록 DSL을 구현하기가 어려워진다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;컨텍스트 변수(CONTEXT VARIABLE)를 다루는 방식은 내부 DSL의 구현 패턴에 커다란 영향을 미친다.&amp;nbsp; 컨텍스트 변수(CONTEXT VARIABLE)의 수를 최소화하면서 현재의 요구사항을 명확하게 표현할 수 있는 구현 패턴을 선택하는 것이 핵심이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/38</guid>
      <comments>https://eternity-object.tistory.com/38#entry38comment</comments>
      <pubDate>Fri, 31 May 2024 10:29:22 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 4부</title>
      <link>https://eternity-object.tistory.com/37</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 3부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FACTORY를 이용한 생성 메서드(CREATION METHOD) 중복 제거&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;픽스처 생성과 관련해서 테스트 케이스 간의 중복을 제거하는 또 다른 방법은 테스트 케이스의 슈퍼 클래스가 아닌 별도의 독립 클래스로&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 옮기는 것이다. 독립 클래스는 테스트에 필요한 픽스처를 생성하기 위한 오퍼레이션을 제공하는 일종의 FACTORY다. 이처럼 테스트 케이스 클래스 간의 중복 코드를 제거하기 위해 재사용 가능한&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Utility%20Method.html&quot;&gt;테스트 유틸리티 메서드(TEST UTILITY METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 제공하는 독립적인 클래스를&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;라고 한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Testcase%20Superclass.html&quot;&gt;테스트 케이스 슈퍼클래스(TESTCASE SUPERCLASS)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 경우 테스트 케이스 클래스가 반드시 다른 클래스를 상속받아야 하기 때문에 단일 상속만을 제공하는 Java나 C#과 같은 언어에서는 사용 상의 제약이 따른다. 또한 다양한 픽스처를 생성하기 위해 많은 수의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 슈퍼 클래스에 포함시킬 경우 관리와 유지보수에 많은 어려움이 따르게 된다. 개인적으로&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Testcase%20Superclass.html&quot;&gt;테스트 케이스 슈퍼클래스(TESTCASE SUPERCLASS)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;보다는&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 선호한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;지구 검증 소프트웨어의 테스트 케이스가&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 이용하도록 리팩토링 하기 위해 새로운 클래스인 PlanetFactory를 추가하고 Planet을 생성하는 생성 메서드를 PlanetFactory로 이동시키자. 이동시킨 생성 메서드의 가시성을&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;으로 수정하고 PlanetFactory의 인스턴스를 생성하지 않고도 사용 가능하도록 하기 위해 정적 메소드(static method)로 변경하자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117776108&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PlanetFactory {
    public static Planet createPlanet(Continent ... continents) {
        return new Planet(
                       new Atmosphere(Money.wons(5000),
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2))),
                       Arrays.asList(continents),
                       Arrays.asList(
                           new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                           new Ocean(&quot;대서양&quot;, Money.wons(1000))));
    }

    public static Planet createPlanet(Money atmospherePrice,
        List&amp;lt;Money&amp;gt; nationsPrice, List&amp;lt;Money&amp;gt; oceansPrice) {
        return new Planet(
                           createAtmosphere(atmospherePrice),
                           createContinents(nationsPrice),
                           createOceans(oceansPrice));
    }

    private Atmosphere createAtmosphere(Money atmospherePrice) {
        return new Atmosphere(atmospherePrice,
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2)));
    }
 
    private List&amp;lt;Continent&amp;gt; createContinents(List&amp;lt;Money&amp;gt; nationsPrice) {
        List&amp;lt;Continent&amp;gt; result = new ArrayList&amp;lt;Continent&amp;gt;();
        for(Money each : nationsPrice) {
            result.add(new Continent(&quot;대륙&quot;, new Nation(&quot;국가&quot;, each)));
        }
        return result;
    }
   
    private List&amp;lt;Ocean&amp;gt; createOceans(List&amp;lt;Money&amp;gt; oceansPrice) {
        List&amp;lt;Ocean&amp;gt; result = new ArrayList&amp;lt;Ocean&amp;gt;();
        for(Money each : oceansPrice) {
            result.add(new Ocean(&quot;대양&quot;, each));
        }
        return result;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;이제 테스트 케이스는 픽스처를 생성하기 위해&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 호출한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117798538&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
    @Test
    public void continentSize() {
        Planet planet = PlanetFactory.createPlanet(
                                   new Continent(&quot;아시아&quot;), new Continent(&quot;유럽&quot;));
       
        ContinentSpecification specification = new ContinentSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;이와 같이 테스트에 필요한 픽스처의 생성, 수정, 삭제와 관련된 책임을 담당하는 독립적인&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;라고 한다. Peter Schuh과 Stephanie Punke는 &quot;&lt;/span&gt;&lt;a href=&quot;http://cf.agilealliance.org/articles/system/article/file/910/file.pdf&quot;&gt;ObjectMother - Easing Test Object Creation in XP&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&quot;라는 논문에서&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 다음과 같은 기능을 제공하는&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;로 정의하고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필수적인 속성들을 포함하는 완벽한 상태를 가진 비즈니스 객체를 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생명 주기 내의 임의 시점에 요청된 객체를 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원하는 상태로 객체를 생성할 수 있는 편리한 방법을 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 프로세스 중에 객체의 상태를 변경할 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요한 경우 테스트 프로세스가 종료될 때 객체 및 관련된 모든 다른 객체들을 제거한다.&lt;/span&gt;&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;1078&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDeooG/btsHJz6KPs1/nDDO6X5g8VhqdjmdIN46V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDeooG/btsHJz6KPs1/nDDO6X5g8VhqdjmdIN46V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDeooG/btsHJz6KPs1/nDDO6X5g8VhqdjmdIN46V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDeooG%2FbtsHJz6KPs1%2FnDDO6X5g8VhqdjmdIN46V1%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;671&quot; height=&quot;92&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 2&amp;gt; OBJECT MOTHER를 통한 중복 코드 제거&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;을 사용하면 클래스 계층 구조와 무관하게 테스트 케이스 클래스로부터 픽스처 생성과 관련된 부담을 제거할 수 있다. 그러나 이같은 장점에도 불구하고 FACTORY 기반의&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;는 구축된 시스템의 규모가 증가하면 증가할 수록 유지보수성의 한계에 봉착하게 된다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OBJECT MOTHER의 한계&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;객체지향 시스템에서 다양한 실행 경로를 테스트하기 위해서는 픽스처의 상태를 자유롭게 설정할 수 있어야 한다. 많은 실행 에러가 분기문을 적절하게 처리하지 못할 경우에 발생하기 때문에 분기 판단에 사용될 픽스처의 속성을 다양한 상태로 설정할 수 있도록 테스트 케이스를 설계하는 것은 단위 테스트의 신뢰성 측면에서 매우 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;앞에서 설명한 것처럼 다양한 상태의 픽스처를 생성하는 동시에 테스트 케이스를 단순하게 유지하는 방법은 테스트에 중요한 상태 정보만을 인자로 받는&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 사용하는 것이다.&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 사용하면 테스트 케이스와 관련된 중요한 정보만을 강조할 수 있다는 장점이 있지만 유지보수 측면에서 한 가지 어려움을 감수해야 한다. 그것은 테스트 케이스에서 필요한 픽스처의 상태에 따라&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 수가 급격하게 증가한다는 점이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;행성에 포함된 대양의 수를 검증하는 새로운 OceanSpecification 클래스를 추가한다고 가정하자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117916377&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OceanSpecification extends AbstractSpecification {
    private int oceanSize;
   
    public OceanSpecification(int oceanSize) {
        this.oceanSize = oceanSize;
    }
   
    @Override
    public boolean isSatisfied(Planet planet) {
        return planet.getOceans().size() == oceanSize;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;Planet의 생성자를 직접 호출해서 픽스처를 생성하는 테스트 케이스는 다음과 같이 작성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117934519&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OceanSpecificationTest {
    @Test
    public void oceanSize() {
        Planet planet = new Planet(
                                   new Atmosphere(Money.wons(5000),
                                       element(&quot;N&quot;, Ratio.of(0.8)),
                                       element(&quot;O&quot;, Ratio.of(0.2))),
                                   Arrays.asList(
                                        new Continent(&quot;아시아&quot;)),
                                   Arrays.asList(
                                        new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                        new Ocean(&quot;대서양&quot;, Money.wons(1000))));
       
        OceanSpecification specification = new OceanSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;이 테스트 케이스의 결과와 관련된 Planet의 상태 정보는 Ocean뿐이다. Atmosphere와 Continent 정보는 테스트와는 상관없이 Planet의 생성자가 요구하기 때문에 컴파일 에러를 방지하기 위해 생성하는 것뿐이다. 따라서 Ocean만을 인자로 받고 그 외의 정보는 기본 값으로 할당하는 매개변수화된 생성 메서드를 PlanetFactory에 추가하고 이를 호출함으로써 테스트 케이스의 가독성과 유지보수성을 향상시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117951325&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OceanSpecificationTest {
    @Test
    public void oceanSize() {
        Planet planet = PlanetFactory.createPlanet(
                                   new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                   new Ocean(&quot;대서양&quot;, Money.wons(1000)));
       
         OceanSpecification specification = new OceanSpecification(2);
       
         assertTrue(specification.isSatisfied(planet));
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;다음으로 대륙과 대양의 개수를 동시에 체크하는 테스트 케이스를 추가한다. 새로운 테스트 케이스는 Specification의 and() 오퍼레이션을 이용해서 생성된 ContinentSpecification과 OceanSpecification의 복합 Specification을 픽스처로 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117964454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AndSpecificationTest {
    @Test
    public void continentAndOceanSize() {
        Planet planet = new Planet(
                                   new Atmosphere(Money.wons(5000),
                                       element(&quot;N&quot;, Ratio.of(0.8)),
                                       element(&quot;O&quot;, Ratio.of(0.2))),
                                   Arrays.asList(
                                       new Continent(&quot;아시아&quot;),
                                       new Continent(&quot;유럽&quot;)),
                                   Arrays.asList(
                                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
       
        Specification specification = new ContinentSpecification(2)
                                                     .and(new OceanSpecification(2));

         assertTrue(specification.isSatisfied(planet));
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;위 테스트 케이스에서 결과에 영향을 미치는 정보는 Continent와 Ocean의 상태 정보이며 Atmosphere의 상태는 테스트 결과와 무관하다. 따라서 PlanetFactory에 Continent와 Ocean 정보를 파라미터로 받는&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 추가하고 이를 이용해 픽스처를 생성하도록 테스트 케이스를 리팩토링 하자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117982938&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AndSpecificationTest {
    @Test
    public void continentAndOceanSize() {
        Planet planet = PlanetFactory.createPlanet(
                                   Arrays.asList(
                                       new Continent(&quot;아시아&quot;),
                                       new Continent(&quot;유럽&quot;)),
                                   Arrays.asList(
                                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
       
         Specification specification = new ContinentSpecification(2)
                                                       .and(new OceanSpecification(2));
       
         assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;위와 같이 여러 테스트 케이스로부터 픽스처 생성 로직을 PlanetFactory로 옮겨감에 따라 PlanetFactory에는 시그니처는 다르지만 구현 상으로는 유사한&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;가 기하급수적으로 늘어나게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117998122&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PlanetFactory {
    public static Planet createPlanet(Continent ... continents) {
        ....
    }
   
    public static Planet createPlanet(Money atmospherePrice, List&amp;lt;Money&amp;gt; nationsPrice,
        List&amp;lt;Money&amp;gt; oceansPrice) {
        ....
    }

    public static Planet createPlanet(Ocean ... oceans) {
        ....
    }
   
    public static Planet createPlanet(List&amp;lt;Continent&amp;gt; continents, List&amp;lt;Ocean&amp;gt; oceans) {
        ....
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;PlanetFactory의 예에서 알 수 있는 것처럼 새로운 테스트 케이스를 추가할 때마다 요구되는 픽스처의 상태가 다양하게 변하기 때문에 시간이 지날수록 PlanetFactory에 추가되는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 수는 폭발적으로 늘어난다. 또한 상태 조합이 복잡해 질수록 PlanetFactory에 포함된&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;간의 중복 코드 역시 함께 증가하게 된다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 증가에 따른 코드 중복을 해결하는 한 가지 방법은&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Derived%20Value.html#One%20Bad%20Attribute&quot;&gt;하나의 잘못된 속성(ONE BAD ATTRIBUTE)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴을 사용하는 것이다. 유효한 상태를 가진 기본 픽스처를 생성한 후 일부 속성만 원하는 상태로 수정함으로써 코드 중복을 제거하는 방법을&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Derived%20Value.html#One%20Bad%20Attribute&quot;&gt;하나의 잘못된 속성(ONE BAD ATTRIBUTE)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴이라고 한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;우선 픽스처의 모든 속성을 기본값으로 할당하는 createDefault()와 같은 이름을 가진 기본&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 추가한다. 다른&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;들은 기본&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 호출한 후 반환되는 기본 픽스처의 일부 속성을 원하는 값으로 수정한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717118037218&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PlanetFactory {
    public static Planet createDefault() {
        return new Planet(
                       new Atmosphere(Money.wons(5000),
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2))),
                       Arrays.asList(
                           new Continent(&quot;아시아&quot;),
                           new Continent(&quot;유럽&quot;)),
                       Arrays.asList(
                           new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                           new Ocean(&quot;대서양&quot;, Money.wons(1000))));
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;테스트 메소드에서 직접 사용되는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;에서는 기본&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;인 createDefault()에서 반환되는 객체의 일부 속성을 각각의 경우에 맞게 재설정한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717118071996&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PlanetFactory {
    public static Planet createPlanet(Continent ... continents) {
        Planet result = createDefault();
        result.setContinents(Arrays.asList(continents));
        return result;
    }
   
    public static Planet createPlanet(Ocean ... oceans) {
        Planet result = createDefault();
        result.setOceans(Arrays.asList(oceans));
        return result;
    }
   
    public static Planet createPlanet(List&amp;lt;Continent&amp;gt; continents, List&amp;lt;Ocean&amp;gt; oceans) {
         Planet result = createDefault();
         result.setContinents(continents);
         result.setOceans(oceans);
         return result;
     }
     ....
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;그러나 이와 같은 FACTORY 기반의&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;에서&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Derived%20Value.html#One%20Bad%20Attribute&quot;&gt;하나의 잘못된 속성(ONE BAD ATTRIBUTE)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴을 적용하는 방법에는 다음과 같은 단점이 존재한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불변 객체(Immutable Object) 생성 불가능&amp;nbsp;-&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Derived%20Value.html#One%20Bad%20Attribute&quot;&gt;하나의 잘못된 속성(ONE BAD ATTRIBUTE)&lt;/a&gt;&amp;nbsp;패턴을 적용하기 위해서는 기본 생성 메서드가 반환하는 객체의 상태를 변경할 수 있어야 한다. 따라서 객체의 상태를 변경할 수 없는 VALUE OBJECT에 대해서는 하나의 잘못된 속성 패턴을 적용할 수 없다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캡슐화 저해&amp;nbsp;- 비록 상태 변경이 가능한 ENTITY라고 하더라도 클래스에 해당 속성을 변경할 수 있는 SETTER가 반드시 존재해야 한다. 비록 테스트를 위해 가시성을 패키지 수준으로 낮춘다고 하더라도 속성 별로 SETTER를 무분별하게 제공하는 것은 객체의 캡슐화를 저해한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Derived%20Value.html#One%20Bad%20Attribute&quot;&gt;하나의 잘못된 속성(ONE BAD ATTRIBUTE)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴을 사용해 코드 중복을 제거하더라도 FACTORY 기반의&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;nbsp;패턴은 근본적으로 다음과 같은 문제를 가지고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 메서드(CREATION METHOD)의 폭발적 증가&amp;nbsp;&amp;ndash; 비록 픽스처 생성 코드에 대한 중복은 제거할 수 있을지 몰라도 테스트에 필요한 픽스처의 속성 조합에 따른&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;의 폭발적 증가를 막을 수 있는 방법은 없다. 테스트의 수가 많아질수록 어떤&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;를 사용해야 하는 지조차 파악하기 힘들게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클래스 인터페이스 변경으로 인한 파급 효과&amp;nbsp;&amp;ndash; 객체를 생성하는 코드가 FACTORY 내부로 캡슐화되기 때문에 생성자의 시그니처 변경으로 인한 테스트 케이스의 수정은 막을 수 있다. 그러나 속성의 조합을 기반으로&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;가 추가되기 때문에 이미 존재하는 속성을 제거하거나 새로운 속성을 추가할 경우&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;뿐만 아니라&amp;nbsp;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;를 호출하는 모든 테스트 케이스의 코드를 수정해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;일정 수준의 복잡도를 갖춘 시스템의 단위 테스트를 작성할 경우 앞서 살펴본 바와 같이 동일한 타입의 객체가 하나 이상의 테스트 케이스에서 픽스처로 사용되는 것이 일반적이다. 따라서&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;와 같은 FACTORY 기반의&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html&quot;&gt;테스트 도우미(TEST HELPER)&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;를 사용할 경우 여러 테스트 케이스에서 픽스처 생성 코드가 중복되는 문제는 해결할 수 있지만 테스트를 통해 검증해야 하는 픽스처의 상태 변이가 다양해질수록 테스트 코드의 유지보수를 어렵게 만드는 장애물로 변해버리고 만다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;Steve Freeman과 Net Pryce는 &amp;ldquo;&lt;/span&gt;&lt;a href=&quot;http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627&quot;&gt;Growing Object-Oriented Software, Guided by Tests&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;&amp;rdquo;에서&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Helper.html#Object%20Mother&quot;&gt;OBJECT MOTHER&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;의 단점을 해결할 수 있는 TEST DATA BUILDER라는 패턴을 제시하고 있다. TEST DATA BUILDER의 기본 아이디어는 FACTORY 패턴이 아닌 BUILDER 패턴을 기반으로 픽스처 생성 코드를 설계하는 것이다. 그러나 TEST DATA BUILDER의 진정한 핵심은 테스트를 위한 전용 언어인 '도메인 특화 언어(Domain-Specific Language, DSL)'를 구축하는 것이다. 따라서 짧게나마 도메인 특화 언어가 무엇인지에 관해 살펴볼 필요가 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;다음 글 :&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 5부&lt;/a&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/37</guid>
      <comments>https://eternity-object.tistory.com/37#entry37comment</comments>
      <pubDate>Fri, 31 May 2024 10:16:46 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 3부</title>
      <link>https://eternity-object.tistory.com/36</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/35&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 2부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;테스트 코드 리팩토링&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Obscure%20Test.html#Irrelevant%20Information&quot;&gt;관련 없는 정보(IRRELEVANT INFORMATION)&lt;/a&gt;를 너무 상세하게 노출시켜&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Obscure%20Test.html&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/a&gt;가 되어버리는 문제를 해결할 수 있는 한 가지 방법은 안정적인 인터페이스를 이용해 픽스처를 생성하는 코드를 캡슐화시키는 것이다. 가장 간단한 방법은 픽스처 생성 코드를 메서드로 추출(EXTRACT METHOD 리팩토링)해서 별도의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Utility%20Method.html&quot;&gt;테스트 유틸리티 메서드(TEST UTILITY METHOD)&lt;/a&gt;로 분리하는 것이다. 이처럼 픽스처를 생성하기 위한 목적을 가진 독립적인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Utility%20Method.html&quot;&gt;테스트 유틸리티 메서드(TEST UTILITY METHOD)&lt;/a&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 픽스처를 생성하기 위해 직접 Planet의 생성자를 호출하던 부분을 createPlanet()이라는 독립적인 생성 메서드를 이용하도록 리팩토링한 ContinentSpecification의 테스트 케이스를 나타낸 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717117301315&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
    @Test
    public void continentSize() {
        Planet planet = createPlanet();
       
        ContinentSpecification specification = new ContinentSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }

    private Planet createPlanet() {
        return new Planet(
                       new Atmosphere(Money.wons(5000),
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2))),
                       Arrays.asList(
                           new Continent(&quot;아시아&quot;),
                           new Continent(&quot;유럽&quot;)),
                      Arrays.asList(
                           new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                           new Ocean(&quot;대서양&quot;, Money.wons(1000))));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 메서드 내부로 픽스처 생성 로직을 캡슐화하는 장점은 인터페이스에 민감함(INTERFACE SENSITIVITY) 문제로 인한 &lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Fragile%20Test.html&quot;&gt;깨지기 쉬운 테스트(FRAGILE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 위험성을 낮출 수 있다는 것이다. 앞에서 언급한 것처럼 여러 테스트 케이스에서 클래스의 생성자를 직접 호출할 경우 생성자 변경으로 인해 다수의 테스트 케이스가 영향을 받게 된다. 따라서 픽스처 생성 로직을 생성 메서드 내부로 캡슐화하고 직접 생성자를 호출하는 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 호출하도록 변경하면 생성자의 시그니처 변경으로 인한 파급 효과의 범위를 제한할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 픽스처 생성을 위해 생성자를 직접 이용할 경우 동일한 타입의 픽스처를 생성하는 테스트 메소드 간에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Code%20Duplication.html&quot;&gt;테스트 코드 중복(TEST CODE DUPLICATION)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제가 발생할 수 밖에 없는데 일반적으로 유사한 픽스처 생성 로직을 복사한 후 일부만 수정해서 사용하기 때문이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 중복되는 코드를 별도의 메서드로 추출할 수 있기 때문에 제한적이나마 테스트 코드 중복 문제를 해결할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 이런 장점에도 불구하고 앞에서&amp;nbsp; 리팩토링한 테스트 코드에서는 여전히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제가 존재한다. 리팩토링 전의 코드가 관련 없는 정보를 과도하게 노출시킴으로써&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제를 초래했다면 리팩토링 후의 코드는 테스트 결과와 관련된 중요한 정보를 은폐하고 있기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제를 초래한다. createPlanet()이라는 메서드의 시그니처 어디에서도 대륙의 개수를 검증하기 위해 Planet을 생성한다는 정보를 알 수가 없다. 즉, 이번에는 너무 많은 정보를 감추고 있기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제가 발생한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이처럼 픽스처와 검증 로직의 일부가 테스트 메서드 밖에 있어 코드를 봤을 때 이들 간의 인과 관계가 보이지 않는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제의 또 다른 형태를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Obscure%20Test.html#Mystery%20Guest&quot;&gt;미스터리한 손님(Mysterious Guest)&lt;/a&gt;이라고 부른다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;미스터리한 손님(Mysterious Guest)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제를 해결하는 한 가지 방법은 테스트와 무관한 정보는 기본값을 설정하도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에 캡슐화하고 테스트와 관련된 정보만 생성 메서드의 파라미터로 전달함으로써 테스트 검증에 중요한 정보가 무엇인지를 한 눈에 파악할 수 있도록 하는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이와 같이 애매한 테스트 문제를 해결하기 위해 테스트와 관련된 중요한 정보를 파라미터로 전달받는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 특별한 형태를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;라고 한다. 파라미터로 전달되지 않는 관련 없는 정보에는 미리 정해진 기본값을 할당함으로써 픽스처를 생성하는 이유를 명확하게 드러낼 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 createPlanet() 메서드의 파라미터로 Continent 인스턴스의 목록을 받도록 리팩토링함으로써 테스트에 중요한 픽스처 정보를 강조하도록 수정한 것이다. 또한 테스트 검증과 무관한 속성은 외부에 노출하지 않고 createPlanet() 메소드 안에 미리 정의된 기본값으로 초기화함으로써 테스트와 관련 없는 정보는 감추고 있다. 리팩토링된 테스트 코드를 읽어 보면 아시아와 유럽 대륙을 가진 Planet 이 2개의 대륙을 가지고 있는 지 검증하는 ContinentSpecification의 검증 로직을 통과한다는 사실을 쉽게 파악할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717117357375&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
    @Test
    public void continentSize() {
        Planet planet = createPlanet(
                                     new Continent(&quot;아시아&quot;),
                                     new Continent(&quot;유럽&quot;));
       
        ContinentSpecification specification = new ContinentSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }

    private Planet createPlanet(Continent ... continents) {
        return new Planet(
                       new Atmosphere(Money.wons(5000),
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2))),
                       Arrays.asList(continents),
                       Arrays.asList(
                          new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                          new Ocean(&quot;대서양&quot;, Money.wons(1000))));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성의 제조 요금을 검증하는 PriceSpecification의 테스트 케이스를 리팩토링하는 것은 조금 더 복잡하다. 제조 요금을 테스트하기 위해 필요한 정보는 대기의 제조 요금, 대륙에 포함된 국가들의 총 제조 요금, 대양들의 총 제조 요금을 모두 더한 값이다.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717117377205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PriceSpecificationTest {
    @Test
    public void price() {
        Planet planet = new Planet(
                                   new Atmosphere(Money.wons(5000),
                                       element(&quot;N&quot;, Ratio.of(0.8)),
                                       element(&quot;O&quot;, Ratio.of(0.2))),
                                   Arrays.asList(
                                       new Continent(&quot;아시아&quot;, new Nation(&quot;대한민국&quot;, Money.wons(1000))),
                                       new Continent(&quot;유럽&quot;, new Nation(&quot;영국&quot;, Money.wons(1000)))),
                                   Arrays.asList(
                                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
       
        Specification specification = new PriceSpecification(Money.wons(9000));
       
        assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 테스트 케이스를 행성 제조에 필요한 요금 정보만을 강조하기 위해 createPlanet()이라는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html#Parameterized%20Creation%20Method&quot;&gt;매개변수화된 생성 메서드(PARAMETERIZED CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 사용하도록 리팩토링한 코드는 다음과 같다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717117400088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PriceSpecificationTest {
    @Test
    public void price() {
        Planet planet = createPlanet(
                                     Money.wons(5000),                       
                                     Arrays.asList(Money.wons(1000), Money.wons(1000)),
                                     Arrays.asList(Money.wons(1000), Money.wons(1000)));

        Specification specification =  PriceSpecification(Money.wons(9000));
       
        assertTrue(specification.isSatisfied(planet));
    }

    private Planet createPlanet(Money atmospherePrice,
        List&amp;lt;Money&amp;gt; nationsPrice, List&amp;lt;Money&amp;gt; oceansPrice) {
        return new Planet(
                        createAtmosphere(atmospherePrice),
                        createContinents(nationsPrice),
                        createOceans(oceansPrice));
    }

    private Atmosphere createAtmosphere(Money atmospherePrice) {
        return new Atmosphere(atmospherePrice,
                               element(&quot;N&quot;, Ratio.of(0.8)),
                               element(&quot;O&quot;, Ratio.of(0.2)));
    }
 
    private List&amp;lt;Continent&amp;gt; createContinents(List&amp;lt;Money&amp;gt; nationsPrice) {
        List&amp;lt;Continent&amp;gt; result = new ArrayList&amp;lt;Continent&amp;gt;();
        for(Money each : nationsPrice) {
            result.add(new Continent(&quot;대륙&quot;, new Nation(&quot;국가&quot;, each)));
        }
        return result;
    }
   
    private List&amp;lt;Ocean&amp;gt; createOceans(List&amp;lt;Money&amp;gt; oceansPrice) {
        List&amp;lt;Ocean&amp;gt; result = new ArrayList&amp;lt;Ocean&amp;gt;();
        for(Money each : oceansPrice) {
            result.add(new Ocean(&quot;대양&quot;, each));
        }
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만족스럽지는 않지만 createPlanet() 메서드의 인자를 살펴 보는 것만으로도 테스트에서 검증하려는 결과 값을 쉽게 유추할 수 있다(결과 값은 간단하게 createPlanet()에 나열된 Money의 금액을 합하면 된다). 또한 테스트에 사용할 요금 정보를 수정하고 싶을 경우에도 간단하게 createPlanet()에 파라미터로 전달된 Money의 값을 변경하거나 개수를 변경하기만 하면 된다.&lt;/span&gt;&lt;br /&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;슈퍼 클래스를 이용한 생성 메서드의 중복 제거&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 ContinentSpecificationTest에는 대륙의 목록을 인자로 받아 행성을 생성하는 createPlanet() 메서드가 정의되어 있고, PriceSpecificationTest에는 대기, 대륙, 대양의 제조 요금 정보를 인자로 받아 Planet을 생성하는 createPlanet() 메서드가 정의되어 있다. 새로운 문제는 위 두 테스트 케이스 이외의 다양한 테스트 케이스에서도 이 2가지 버전의 createPlanet()을 사용할 수 있어야 한다는 것이다. 현재의 구조 상으로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가 특정한 테스트 케이스 클래스와 강하게 결합되어 있기 때문에 다른 테스트 케이스 클래스에서 원하는 메서드를 재사용하기가 쉽지 않다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 가지 방법은 각 테스트 케이스로 적절한 createPlanet() 메서드를 복사하는 것이다. 그러나 메서드를 복사하는 것은 테스트 코드 중복(TEST CODE DUPLICATION)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제로의 회귀를 의미한다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;더 좋은 방법은 EXTRACT SUPER CLASS 리팩토링을 통해 두 테스트 케이스 클래스의 공통 부모 클래스를 추출한 후 PULL UP METHOD 리팩토링을 통해 생성 메서드를 공통의 부모 클래스로 옮기는 것이다. Planet을 생성할 필요가 있는 테스트 케이스에서는 추출한 부모 클래스를 상속 받는 것으로 간단하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 재사용할 수 있다. 이처럼 테스트 케이스 클래스 간의 중복 코드를 제거하기 위해 공통 코드를 포함하도록 생성되는 부모 클래스를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Testcase%20Superclass.html&quot;&gt;테스트 케이스 슈퍼클래스(TESTCASE SUPERCLASS)&lt;/a&gt;라고 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 서브 클래스에서만 접근 가능하도록 제한하는 것이 좋으므로 가시성을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #993399;&quot;&gt;protected&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로 변경한다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;pre id=&quot;code_1717117447126&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class AbstractPlanetTest {
    protected Planet createPlanet(Continent ... continents) {
        return new Planet(
                       new Atmosphere(Money.wons(5000),
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2))),
                       Arrays.asList(continents),
                       Arrays.asList(
                           new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                           new Ocean(&quot;대서양&quot;, Money.wons(1000))));
    }

    protected Planet createPlanet(Money atmospherePrice,
        List&amp;lt;Money&amp;gt; nationsPrice, List&amp;lt;Money&amp;gt; oceansPrice) {
        return new Planet(
                           createAtmosphere(atmospherePrice),
                           createContinents(nationsPrice),
                           createOceans(oceansPrice));
    }

    private Atmosphere createAtmosphere(Money atmospherePrice) {
        return new Atmosphere(atmospherePrice,
                           element(&quot;N&quot;, Ratio.of(0.8)),
                           element(&quot;O&quot;, Ratio.of(0.2)));
    }
 
    private List&amp;lt;Continent&amp;gt; createContinents(List&amp;lt;Money&amp;gt; nationsPrice) {
        List&amp;lt;Continent&amp;gt; result = new ArrayList&amp;lt;Continent&amp;gt;();
        for(Money each : nationsPrice) {
            result.add(new Continent(&quot;대륙&quot;, new Nation(&quot;국가&quot;, each)));
        }
        return result;
    }
   
    private List&amp;lt;Ocean&amp;gt; createOceans(List&amp;lt;Money&amp;gt; oceansPrice) {
        List&amp;lt;Ocean&amp;gt; result = new ArrayList&amp;lt;Ocean&amp;gt;();
        for(Money each : oceansPrice) {
            result.add(new Ocean(&quot;대양&quot;, each));
        }
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Testcase%20Superclass.html&quot;&gt;테스트 케이스 슈퍼클래스(TESTCASE SUPERCLASS)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 각 테스트 케이스 클래스가 상속받도록 테스트 케이스를 리팩토링하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Creation%20Method.html&quot;&gt;생성 메서드(CREATION METHOD)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 재사용할 수 있게 된다. 또한 테스트 케이스 클래스에 존재하던 생성 메서드를 부모 클래스로 이동 시킴으로써 테스트의 목적과는 무관한 생성 코드를 테스트 케이스 클래스로부터 제거할 수 있다. 다음은 리팩토링된 ContinentSpecificationTest 코드를 나타낸 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;pre id=&quot;code_1717117483047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest extends AbstractPlanetTest {
    @Test
    public void continentSize() {
        Planet planet = createPlanet(new Continent(&quot;아시아&quot;), new Continent(&quot;유럽&quot;));
       
        ContinentSpecification specification = new ContinentSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 PriceSpecificationTest 역시 요금 계산과 무관한 생성 로직을 제거할 수 있으므로 테스트 케이스 클래스를 좀 더 간결하고 단순하게 유지할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;pre id=&quot;code_1717117529041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PriceSpecificationTest extends AbstractPlanetTest {
    @Test
    public void price() {
        Planet planet = createPlanet(
                                   Money.wons(5000),                       
                                   Arrays.asList(Money.wons(1000), Money.wons(1000)),
                                   Arrays.asList(Money.wons(1000), Money.wons(1000)));

        Specification specification = new PriceSpecification(Money.wons(9000));
       
        assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Testcase%20Superclass.html&quot;&gt;테스트 케이스 슈퍼클래스(TESTCASE SUPERCLASS)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 이용해 관련 없는 정보를 배제하고 테스트 코드 중복을 제거하면 깔끔하고, 의도가 명확하며, 원인과 결과를 파악하기 쉬운 테스트 케이스를 만들 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccf1VW/btsHJPVNlg3/eJFFAW2TnDZtjh9i85dDf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccf1VW/btsHJPVNlg3/eJFFAW2TnDZtjh9i85dDf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccf1VW/btsHJPVNlg3/eJFFAW2TnDZtjh9i85dDf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccf1VW%2FbtsHJPVNlg3%2FeJFFAW2TnDZtjh9i85dDf0%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;610&quot; height=&quot;252&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 1&amp;gt; TEST CASE SUPERCLASS를 이용한 중복 제거&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글 :&amp;nbsp;&lt;a href=&quot;https://eternity-object.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 4부&lt;/a&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/36</guid>
      <comments>https://eternity-object.tistory.com/36#entry36comment</comments>
      <pubDate>Fri, 31 May 2024 10:07:03 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 2부</title>
      <link>https://eternity-object.tistory.com/35</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이전 글 :&amp;nbsp;&lt;a href=&quot;https://eternity-object.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 1부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;검증을 위한 SPECIFICATION 패턴 적용&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체지향 프로그래밍의 기본 원칙은 데이터와 데이터를 이용하는 행위를 함께 두는 것이다. 그러나 때로는 데이터와 행위를 뭉쳐 놓는 것이 최선의 선택이 아닐 수도 있다. 예를 들어 대부분의 개발자들은 도메인 개념을 표현하는 엔티티 내에 데이터베이스 저장과 같은 영속성 로직을 위치시키는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://martinfowler.com/eaaCatalog/activeRecord.html&quot;&gt;&lt;span&gt;ACTIVE RECORD&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;패턴보다는 별도의 독립적인 클래스로 영속성 로직을 분리하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://martinfowler.com/eaaCatalog/dataMapper.html&quot;&gt;&lt;span&gt;DATA MAPPER&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;패턴을 선호한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기준은 응집도와 결합도다. 영속성 로직과 도메인 로직을 엔티티에 함께 섞는 것은 전체적으로 객체들의 응집도를 낮추고 결합도를 높이는 부정적인 결과를 낳는다. 따라서 영속성 로직이 엔티티의 데이터를 사용하더라도 시스템 전체적인 관점에서 응집도를 높이고 결합도를 낮추기 위해서는 영속성 로직을 별도의 클래스로 분리하는 것이 더 좋은 선택이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이와 유사하게 엔티티의 정합성을 검증하는 조건 체크 로직이 엔티티의 응집도를 낮추고 결합도를 높인다면 독립적인 클래스로 정합성 검증로직을 분리하는 것이 더 좋다. 이처럼 특정 엔티티에 대한 정합성을 체크하는 로직을 엔티티 자체가 아니라 독립적인 객체에 두는 것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Specification_pattern&quot;&gt;&lt;span&gt;SPECIFICATION&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴이라고 한다.&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Specification_pattern&quot;&gt;&lt;span&gt;SPECIFICATION&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴은 검증을 위한 술어(predicate)를 명세(specification) 형태의 객체로 표현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 행성의 정합성을 체크하기 위해 사용될 공통 인터페이스인 Specification을 선언하자. Specification 인터페이스는 Planet 객체의 정합성을 체크하는 isSatisfied() 오퍼레이션과 두 개의 Specification을 조합하는 and() 오퍼레이션으로 구성된다. isSatisfied() 오퍼레이션은 검증 조건이 맞을 경우 true를, 틀릴 경우 false를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717116657529&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Specification {
    boolean isSatisfied(Planet planet);
    Specification and(Specification other);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추상 클래스인 AbstractSpecification 은 두 개의 Specification 인스턴스를 조합하는 and() 오퍼레이션의 기본 구현을 제공한다. 모든 Specification 구현 클래스들은 and() 오퍼레이션의 구현 코드를 공유해야 하므로 AbstractSpecification 클래스의 서브 클래스로 선언되어야 한다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717116686482&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class AbstractSpecification implements Specification {
    @Override
    public Specification and(Specification other) {
        return new AndSpecification(this, other);   
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AndSpecification 클래스는 두 개의 Specification을 공통의 인터페이스로 캡슐화하는 간단한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;COMPOSITE&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717116705825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AndSpecification extends AbstractSpecification {
    private Specification one;
    private Specification other;
   
    public AndSpecification(Specification one, Specification other) {
        this.one = one;
        this.other = other;
    }
   
    @Override
    public boolean isSatisfied(Planet planet) {
        return one.isSatisfied(planet) &amp;amp;&amp;amp; other.isSatisfied(planet);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성에 포함된 대륙의 개수를 체크하는 ContinentSpecification은 Planet 클래스의 getContinent()로 반환된 리스트의 개수와 자신의 continentSize 속성 값을 비교한다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717116725692&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecification extends AbstractSpecification {
    private int continentSize;

    public ContinentSpecification(int continentSize) {
        this.continentSize = continentSize;
    }

    @Override
    public boolean isSatisfied(Planet planet) {
        return planet.getContinents().size() == continentSize;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성의 제조 가격이 생쥐들이 원하는 가격보다 저렴하거나 적어도 동일한 지 여부를 체크하는 PriceSpecification은 검증을 위해 Planet의 getPrice() 메서드의 반환 값을 이용한다.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717116739121&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PriceSpecification extends AbstractSpecification {
    private Money price;
   
    public PriceSpecification(Money price) {
        this.price = price;
    }
   
    @Override
    public boolean isSatisfied(Planet planet) {
        return price.isGreaterThanOrEqual(planet.getPrice());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성이 가지는 대륙의 개수가 2이고 제조 가격이 20,000원 이하인 지를 체크하는 경우 다음과 같이 두 개의 Specification을 and() 메서드로 조합한 후 Planet 인스턴스를 체크하면 된다.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1717116754759&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Specification specification = new ContinentSpecification(2)
                                 .and(new PriceSpecification(Money.wons(9000)));
specification.isSatisfied(planet);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 3&amp;gt;은 Planet의 정합성을 검증하는 Specification 계층의 전체적인 구조를 클래스 다이어그램으로 표현한 것이다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OW3rO/btsHIUQ4D4M/G3lBRmqM4KTXpbjlPdLIr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OW3rO/btsHIUQ4D4M/G3lBRmqM4KTXpbjlPdLIr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OW3rO/btsHIUQ4D4M/G3lBRmqM4KTXpbjlPdLIr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOW3rO%2FbtsHIUQ4D4M%2FG3lBRmqM4KTXpbjlPdLIr0%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;583&quot; height=&quot;356&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;486&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;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 3&amp;gt; Specification 계층 구조&lt;/span&gt;&lt;/div&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단위 테스트의 늪&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 모델의 개발이 완료되었으므로 이제 Specification이 정상적으로 동작하는지 확인하는 단위 테스트를 살펴보도록 하자.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 테스트 케이스는 &amp;lsquo;픽스처 설치&amp;rsquo;, &amp;lsquo;SUT 실행&amp;rsquo;, &amp;lsquo;결과 검증&amp;rsquo;, &amp;lsquo;픽스처 해체&amp;rsquo;의 4단계로 구성된다. 이처럼 테스트 케이스를 4단계로 구성하는 것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Four%20Phase%20Test.html&quot;&gt;4단계 테스트(FOUR-PHASE TEST)&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;패턴이라고 한다.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;픽스처 설치(setup)&lt;/b&gt; - 테스트 대상 코드가 실행될 환경인 컨텍스트를 준비한다. 이 단계에서는 테스트에서 사용할 실제 테스트 픽스처를 생성하거나 Mock 객체 등의 TEST DOUBLE을 준비한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SUT 실행(execute)&lt;/b&gt; - SUT(System Under Test의 약자로 현재 테스트 중인 시스템을 의미)를 호출하여 테스트 대상 행위를 실행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결과 검증(verify)&lt;/b&gt; - 실행 후 SUT가 기대한 대로 동작했는지 여부를 확인한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;픽스처 해체(teardown)&lt;/b&gt; - 테스트 픽스처를 제거해 테스트 실행 전 상태로 컨텍스트를 복구한다. 이를 통해 테스트 간의 부수효과를 방지할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ldquo;행성(Planet)에 포함된 대륙(Continent)의 개수가 정해진 명세에 일치하는 지 여부&amp;rdquo;를 검증하는 ContinentSpecification의 테스트 케이스부터 살펴 보자. 테스트 케이스의 목적은 &amp;ldquo;아시아&amp;rdquo;와 &amp;ldquo;유럽&amp;rdquo;이라는 두 개의 Continent 인스턴스를 속성으로 포함하는 Planet 인스턴스가 존재할 때 ContinentSpecification이 2개의 대륙 정보를 포함하고 있다는 것을 정상적으로 검증하는지를 테스트하는 것이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ContinentSpecification에 대한 테스트 케이스 역시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Four%20Phase%20Test.html&quot;&gt;4단계 테스트(FOUR-PHASE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴에 따라 픽스처를 생성하는 것으로 시작한다. 먼저 두 개의 대륙을 가진 Planet 인스턴스를 생성한다. 그 후 실제 테스트 대상인 ContinentSpecification을 생성하고 생성된 Planet을 isSatisfied() 메서드에 전달해서 반환 값이 true인지 확인한다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1717116846697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ContinentSpecificationTest {
    @Test
    public void continentSize() {
        Planet planet = new Planet(
                                   new Atmosphere(Money.wons(5000),
                                       element(&quot;N&quot;, Ratio.of(0.8)),
                                       element(&quot;O&quot;, Ratio.of(0.2))),
                                   Arrays.asList(
                                       new Continent(&quot;아시아&quot;),
                                       new Continent(&quot;유럽&quot;)),
                                   Arrays.asList(
                                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
   
        ContinentSpecification specification = new ContinentSpecification(2);
       
        assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 테스트 케이스를 자세히 살펴보면 픽스처 생성과 관련해서 다음과 같은 몇 가지 문제점이 존재한다는 사실을 알 수 있다. 아래의 문제점 분류는Gerard Meszaros의 저서인 &amp;ldquo;&lt;a href=&quot;http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Signature/dp/0131495054&quot;&gt;xUnit Test Patterns&lt;/a&gt;&amp;rdquo;(번역서 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://book.naver.com/bookdb/book_detail.nhn?bid=6241917&quot;&gt;xUnit 테스트 패턴&lt;/a&gt;)에서 사용한 용어를 기반으로 한 것이다(Meszaros의 책에서는 리팩토링 전통에 따라 테스트 '냄새&amp;rsquo;라는 용어를 사용하고 있다).&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://xunitpatterns.com/Obscure%20Test.html&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 테스트에서 픽스처와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;관련 없는 정보(IRRELEVANT INFORMATION)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 너무 상세하게 보여주기 때문에 테스트 중인 시스템의 동작에 실제로 영향을 미치는 것이 어떤 것인지를 파악하기 어렵다. 위 테스트 케이스에서 Planet의 인스턴스를 생성하기 위해 생성자에 전달된 세 가지 인자 중 대륙의 수를 검증하는 테스트에 직접적으로 관련이 있는 것은 Continent뿐이다. Atmosphere와 Ocean은 테스트 결과와 아무런 상관이 없다. 그럼에도 불구하고 Atmosphere와 Ocean은 Planet을 생성하기 위해 컴파일러가 요구하는 필수 파라미터이기 때문에 테스트와의 관련성과 무관하게 생성자에 전달해야 한다. 이처럼 테스트하려는 행위와 관련이 없는 불필요한 정보는 테스트의 목적을 흐리고 테스트 케이스를 이해하는데 불필요한 정신적 과부하를 초래한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://xunitpatterns.com/Fragile%20Test.html&quot;&gt;깨지기 쉬운 테스트(FRAGILE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 위 테스트 케이스는 Planet, Atmosphere, Continent, Ocean의 생성자를 직접 호출하기 때문에 생성자의 시그니처가 변경될 경우 객체들을 사용하는 모든 테스트 케이스를 수정해야 한다. 이처럼 테스트 케이스에서 참조하는 객체의 API가 변경될 경우 테스트 케이스에서 컴파일 에러(동적 타입 언어일 경우에는 런타임 에러)가 발생하는 것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인터페이스에 민감함(INTERFACE SENSITIVITY)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;문제라고 한다. 도메인 객체들을 사용하는 테스트 케이스가 늘어날수록 테스트 케이스를 수정하고 관리하는 것이 점점 더 어려워진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Planet의 생성자를 하나의 테스트 케이스 클래스에서만 호출하고 있다면 큰 문제가 되지는 않는다. 그러나 객체들의 풍부한 협력 관계를 기반으로 하는 대부분의 객체 지향 시스템은 동일한 클래스의 인스턴스를 여러 테스트 클래스의 픽스처로 사용하는 것이 일반적이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 코드는 &amp;ldquo;행성의 실제 제조 가격이 생쥐들이 제시한 계약 금액보다 낮거나 동일한 지 여부&amp;rdquo;를 판단하는PriceSpecification에 대한 테스트 케이스 클래스를 나타낸 것이다. 앞에서 살펴 본 ContinentSpecification의 테스트 케이스에서 사용한 픽스처와 동일한 타입인 Planet을 픽스처로 사용한다는 것을 알 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1717116887683&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PriceSpecificationTest {
    @Test
    public void price() {
        Planet planet = new Planet(
                                   new Atmosphere(Money.wons(5000),
                                       element(&quot;N&quot;, Ratio.of(0.8)),
                                       element(&quot;O&quot;, Ratio.of(0.2))),
                                   Arrays.asList(
                                       new Continent(&quot;아시아&quot;,
                                           new Nation(&quot;대한민국&quot;, Money.wons(1000))),
                                       new Continent(&quot;유럽&quot;,
                                           new Nation(&quot;영국&quot;, Money.wons(1000)))),
                                   Arrays.asList(
                                       new Ocean(&quot;태평양&quot;, Money.wons(1000)),
                                       new Ocean(&quot;대서양&quot;, Money.wons(1000))));
       
        Specification specification = new PriceSpecification(Money.wons(9000));
       
        assertTrue(specification.isSatisfied(planet));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 테스트 케이스 간에 발생하는 문제점은 명확하다. 픽스처를 생성하는 부분에 중복 코드가 존재하는 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Code%20Duplication.html&quot;&gt;테스트 코드 중복(TEST CODE DUPLICATION)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 유사한 주제에 대해서 약간씩 다른 시나리오로 테스트를 해야 하는 경우 유사한 픽스처 로직이 여러 테스트 케이스에 중복되어 나타난다. 이를 테스트 코드 중복이라고 한다. 테스트 케이스 간에 유사한 코드가 나타나야 하는 경우 이를 해결하는 가장 간단한 방법은 코드를 잘라 붙여넣기(CUT-AND-PASTE) 한 후 필요한 부분을 수정하는 것이다. 픽스처 생성과 관련된 테스트 코드 중복 문제는 특히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Fragile%20Test.html&quot;&gt;깨지기 쉬운 테스트(FRAGILE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 원인이 되곤 한다. 여러 테스트 케이스에 생성자를 호출하는 코드가 중복되어 나타나기 때문에 생성자의 시그니처가 변경되면 중복 코드가 나타나는 모든 테스트 케이스를 수정해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;a href=&quot;http://xunitpatterns.com/Obscure%20Test.html&quot;&gt;애매한 테스트(OBSCURE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Fragile%20Test.html&quot;&gt;깨지기 쉬운 테스트(FRAGILE TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;http://xunitpatterns.com/Test%20Code%20Duplication.html&quot;&gt;테스트 코드 중복(TEST CODE DUPLICATION)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;문제로 속을 썩이는 테스트 케이스는 다음과 같은 유지 보수 문제를 일으키는 골치 덩어리로 전락하고 만다.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://xunitpatterns.com/High%20Test%20Maintenance%20Cost.html&quot;&gt;높은 테스트 유지 비용(HIGH TEST MAINTENANCE COST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ndash; 애매하고, 깨지기 쉬우며, 중복 코드에 시달리는 테스트 케이스는 유지보수하기 어렵다. 프로덕션 코드에 이런 문제가 발생한다면 최소한 리팩토링을 해보려는 시도라도 하겠지만 테스트 코드에 이런 문제가 발생하는 경우 시간에 쫓기는 대부분의 사람들은 @Ignore를 붙이거나 테스트 케이스를 삭제해버리고 만다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://xunitpatterns.com/Developers%20Not%20Writing%20Tests.html&quot;&gt;테스트를 작성하지 않는 개발자(DEVELOPERS NOT WRITING TEST)&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ndash; 높은 테스트 유지 비용으로 인해 테스트가 개발 과정의 병목이라는 인식이 퍼질수록 개발자들은 테스트를 작성하지 않으려고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lsquo;깨진 창문 이론&amp;rsquo;을 기억하라.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트가 방치되기 시작하면 빠른 속도로 복잡하고 이해하기 어려운 테스트들이 단순하고 깔끔한 테스트의 비율을 잠식해 나갈 것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;더 많은 창문이 깨지기 전에 테스트 케이스를 보수해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 글 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 3부&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/35</guid>
      <comments>https://eternity-object.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 31 May 2024 09:56:57 +0900</pubDate>
    </item>
    <item>
      <title>도메인 특화 언어와 단위 테스트 - 1부</title>
      <link>https://eternity-object.tistory.com/34</link>
      <description>&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;단위 테스트 딜레마&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;XP를 위시로 한 애자일 진영이 소프트웨어 커뮤니티에 미친 가장 큰 영향은 소프트웨어의 품질을 좌우하는 핵심적인 설계 기법으로서 단위 테스트(Unit Test)의 지위가 격상되었다는 점이다. 단위 테스트는 개발자 관점에서 코드의 안전성과 정확성을 보장할 수 있는 최고의 실행지침이다. 실패하는 단위 테스트 없이 코드를 작성하지 말라는 테스트 주도 개발(Test-Driven Development)의 기본 원칙은 리팩토링(Refactoring), 지속적인 통합(Continuous Integration)과 같은 피드백 기반의 실행지침과 결합됨으로써 안정적이고 신뢰도 높은 소프트웨어 개발을 가능하게 한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;table style=&quot;background-color: #ffffff; color: #666666; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #ebebeb;&quot; width=&quot;677&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단위 테스트(Unit Test)란 무엇인가&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소프트웨어 개발 커뮤니티에서 사용되는 대부분의 용어와 마찬가지로 단위 테스트 역시 다양한 문맥에서 다양한 의미로 사용되고 있다. 개인적으로 가장 선호하는 단위 테스트의 정의는 Michael Feathers의 것이다. Michael Feathers의 정의에 따라 테스트가 아래에 나열한 조건을 하나라도 만족한다면 그 테스트는 단위 테스트가 아니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: justify;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터베이스에 접근한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네트워크를 통해 통신한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일 시스템을 이용한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 실행을 위해 환경에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대해 (설정 파일 수정과 같은)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;특별한 작업을 해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 정의가 절대적인 것은 아니다. 실용적인 관점에서 DAO나 REPOSITORY처럼 데이터베이스에 접속해서 응집도 높은 기능을 수행하는 단일 모듈을 테스트하는 경우에도 단위 테스트의 범주에 포함시키기도 한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트의 지위가 격상됨에 따라 &amp;lsquo;레거시 코드(Legacy Code)&amp;rsquo;의 의미 역시 테스트라는 관점에 알맞도록 조정되었다. 전통적인 관점에서 레거시 코드란 이해하기 어렵고 유지보수가 까다로운 과거의 코드 베이스를 의미한다. 그러나 리팩토링을 보조하기 위한 단위 테스트의 중요성이 강조됨에 따라 레거시 코드라는 용어는 &amp;lsquo;테스트가 존재하지 않는 코드&amp;rsquo;를 의미하는 것으로 확장되었다. 이 정의에 따르면 방금 작성된 따끈따끈한 코드라고 하더라도 단위 테스트가 존재하지 않는다면 그 코드는 레거시 코드라고 볼 수 있다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업계에서 레거시 코드라는 용어는 우리가 이해할 수 없고 변경하기 까다로운 코드를 가리키는 속어로 사용된다. 그러나 몇 해 동안 코드 상의 심각한 문제를 해결하는 지원 업무를 담당하면서 개인적으로 이와는 다른 정의를 사용하게 되었다. 내게 있어, 레거시 코드는 단순히 테스트가 존재하지 않는 코드다. ... 테스트가 존재하지 않는 코드는 형편 없는 코드다. 코드가 얼마나 잘 작성되었는지는 중요하지 않다. 또한 코드가 얼마나 깔끔한가, 객체 지향적인가, 잘 캡슐화 되어 있는 지 역시 중요하지 않다. 테스트가 존재한다면 코드를 빠르게 수정할 수 있고 문제가 발생했는지 여부를 확인할 수 있다. 테스트가 없다면 코드의 품질이 향상되었는지 아니면 저하되었는지를 알 수 있는 방법이 존재하지 않는다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Michael Feathers, Working Effectively with Legacy Code&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;그러나 단위 테스트의 장점에도 불구하고 테스트를 아무렇게나 작성할 경우 프로젝트의 진행을 방해하고 소프트웨어의 품질을 저하시키는 걸림돌로 전락할 수 있다. 이해하기 어렵고 복잡한 테스트 케이스는 설계를 개선하려는 개발자의 의지를 꺾는다. 어디서, 어떤 이유로 실패하는지 원인조차 파악하기 어려운 테스트 케이스를 다루어야 하는 개발자는 마치 살얼음판을 걷는 기분으로 코드를 수정하고 기능을 추가할 수 밖에 없다. 실패하는 테스트 케이스를 고치지 못 해 무시하거나 아예 삭제해버리는 경우 운영 환경에서의 장애로 이어질 수도 있다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;올바르게 작성된 테스트 케이스는 신선하고 상쾌한 공기를 제공하는 공기청정기와 같다. 깨끗한 공기가 사람들의 몸과 마음을 맑고 활기차게 만드는 것처럼 잘 작성된 단위 테스트는 코드를 깔끔하고 유연하게 만든다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 케이스를 방치하지 마라. 테스트 커버리지를 높일 목적으로 너저분한 테스트 케이스를 덕지덕지 바르지 마라. 클린 코드(clean code)를 작성하고 싶다면 먼저 클린 테스트 코드(clean test code)에 집중해야 한다.&lt;/span&gt;&lt;/div&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;b&gt;테스트를 개발하는 히치하이커를 위한 안내서&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;슬라티바트패스트는 마그라테아 행성에 거주하고 있는 초공간 엔지니어다. 이 엔지니어는 이름 따위는 중요하지 않다는 신념을 가지고 있으며 삶, 우주, 그리고 모든 것에 관한 해답을 구하기 위해 개발된 지구라는 이름을 가진 컴퓨터의 개발에 참여했던 독특한 이력을 지니고 있다. 불행하게도 지구는 삶, 우주, 그리고 모든 것에 대한 해답을 내놓기 바로 직전에 항성계를 관통하는 초공간 고속도로를 건설 중인 보고인들에 의해 순식간에 파괴되고 말았다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원래 슬라티바트패스트는 은하계에 불어 닥친 경제 공황으로 행성 개발 의뢰가 줄어들자 경제가 다시 활성화될 때까지 기다릴 요량으로 수만 년 동안 동면 상태에 들어가 있었던 중이었다. 불행하게도 동면에 들어가 있던 동안 지구가 산산조각 나버렸고(모든 것이 보고인때문이다) 이로 인해 화가 난 생쥐들에 의해 강제로 동면에서 깨어나 새로운 지구를 개발해야 하는 골치 아픈 상태에 놓이고 말았다. 우리가 할 일은 불쌍한 이 엔지니어를 도와 새롭게 건설된 두 번째 지구가 생쥐들의 요구사항을 만족하는 지를 검증하는 소프트웨어를 개발하는 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;슬라티바트패스트에 따르면 지구는 대기, 대륙, 대양의 3가지 핵심 요소로 이루어져 있으며 생쥐들은 다음의 4가지 요구사항을 반드시 만족시켜줄 것을 요구하고 있다고 한다.&lt;/span&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대기는 원래의 지구와 유사하게 80%의 질소와 20%의 산소로 구성되어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새로운 지구는 원래의 지구와 동일한 개수의 대륙과 대양을 포함해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대기, 대륙, 대양 각각에는 제조가격이 책정되며 행성의 가격은 이 3가지 요소의 총합과 같다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예산을 초과해서는 안 된다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.jpeg&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;619&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diQ3cU/btsHHqjmfj8/4AmPFCtXTG4A0mIMT9b8QK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diQ3cU/btsHHqjmfj8/4AmPFCtXTG4A0mIMT9b8QK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diQ3cU/btsHHqjmfj8/4AmPFCtXTG4A0mIMT9b8QK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiQ3cU%2FbtsHHqjmfj8%2F4AmPFCtXTG4A0mIMT9b8QK%2Fimg.jpg&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;727&quot; height=&quot;450&quot; data-filename=&quot;1.jpeg&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;619&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;&amp;lt;그림 1&amp;gt; 새로운 지구를 건조 중인 마그라테아 행성 중심부&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;지구 검증 소프트웨어 도메인&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;우선 행성 검증이라는 도메인을 구성하는 대기, 대륙, 대양, 행성이라는 4가지 핵심적인 도메인 개념을 코드로 표현해 보기로 하자.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;b&gt;대기(Atmosphere)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;대기(Atmosphere)는 산소와 질소의 두 가지 원소로 구성되어 있으며 두 원소는 8:2의 비율로 혼합되어야 한다. 따라서 대기를 만들기 위해서는 대기를 구성하는 원소(Element)를 구현해야 한다. 원소는 이름(&lt;/span&gt;name&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;)과 비율(&lt;/span&gt;ratio&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;)의 두 가지 속성을 가져야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717115889947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Element {
    private String name;
    private Ratio ratio;

    public static Element element(String name, Ratio ratio) {
        return new Element(name, ratio);
    }

    private Element(String name, Ratio ratio) {
        this.name = name;
        this.ratio = ratio;
    }

    public Ratio getRatio() {
        return ratio;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;대기는 구성 원소의 목록(&lt;/span&gt;elements&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;)과 가격 정보(&lt;/span&gt;price&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;)를 포함한다. 대기는 자신을 구성하는 원소의 개수가 2개 이상이어야 하고 모든 원소의 비율을 합한 값이 반드시 1이 되어야 한다는 제약 조건을 포함한다(즉, 질소 0.8, 산소 0.2와 같이 모든 비율의 합은 1이어야 한다). 이 제약 조건은 대기의 불변식(invariant)의 일부이므로 생성시에 제약 조건을 만족하는 지 여부를 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717115922863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Atmosphere {
    private Money price;
    private List&amp;lt;Element&amp;gt; elements = new ArrayList&amp;lt;Element&amp;gt;();

    public Money getPrice() {
        return price;
    }    

    public Atmosphere(Money price, Element ... elements) {
        this.price = price;
        this.elements = Arrays.asList(elements);
        assertElements();
    }

    private void assertElements() {
        checkElementSize();       
        checkElementRatio();
    }

    private void checkElementSize() {
        if (elements.size() &amp;lt; 2) {
             throw new IllegalArgumentException(&quot;대기는 적어도 2개 이상의 원소로 구성되어야 합니다.&quot;);
        }
    }

    private void checkElementRatio() {
        if (!accumulateRatio().isOne()) {
            throw new IllegalArgumentException(&quot;전체 원소의 비율 총합은 1이어야 합니다.&quot;);
        }
    }

    private Ratio accumulateRatio() {
        Ratio result = Ratio.of(0);
        for(Element element : elements) {
            result = result.plus(element.getRatio());
        }
        return result;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;대륙(Continent)&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;지구의 육지는 하나 이상의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙(Continent)&lt;/span&gt;으로 구성되어 있으며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙&lt;/span&gt;에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;국가(Nation)&lt;/span&gt;가 존재할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;국가&lt;/span&gt;는 이름(&lt;span style=&quot;color: #000099;&quot;&gt;name&lt;/span&gt;)과 제조 가격(&lt;span style=&quot;color: #000099;&quot;&gt;price&lt;/span&gt;)을 속성으로 가지는 간단한 클래스로 추상화할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717115961732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Nation  {
    private String name;
    private Money price;

    public Money getPirce() {
        return price;
    }

    public Nation(String name, Money price) {
        this.name = name;
        this.price = price;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙&lt;/span&gt;은 이름(&lt;span style=&quot;color: #000099;&quot;&gt;name&lt;/span&gt;)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;국가&lt;/span&gt;의 목록(&lt;span style=&quot;color: #000099;&quot;&gt;nations&lt;/span&gt;)을 속성으로 포함한다. 남극 대륙과 같이 국가가 존재하지 않는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙&lt;/span&gt;도 존재할 수 있으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000099;&quot;&gt;nations&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;는 크기가 0인 빈 컬렉션일 수도 있다.&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대륙&lt;/span&gt;의 제조 가격은&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대륙&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;안에 존재하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;국가(Nation)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;전체의 제조 가격을 더한 금액과 동일하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717115980154&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Continent {
    private String name;
    private List&amp;lt;Nation&amp;gt; nations = new ArrayList&amp;lt;Nation&amp;gt;();

    public Money getPirce() {
        Money result = Money.wons(0);
        for(Nation each : nations) {
            result = result.plus(each.getPirce());
        }
        return result;
    }

    public Continent(String name) {
        this.name = name;
    }
   
    public Continent(String name, Nation ... nations) {
        this.name = name;
        this.nations = Arrays.asList(nations);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;대양(Ocean)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;지구의 2/3를 차지하고 있는 바다는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대양(Ocean)&lt;/span&gt;들의 집합으로 추상화할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대양&lt;/span&gt;은 이름(&lt;span style=&quot;color: #000099;&quot;&gt;name&lt;/span&gt;)과 제조 가격(&lt;span style=&quot;color: #000099;&quot;&gt;price&lt;/span&gt;)을 속성으로 가지는 단순한 객체다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717115999375&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Ocean {
    private String name;
    private Money price;

    public Money getPirce() {
        return price;
    }

    public Ocean(String name, Money price) {
        this.name = name;
        this.price = price;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;행성(Planet)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성(Planet)&lt;/span&gt;은 개발 중인 소프트웨어의 가장 핵심적인 도메인 개념이다. 생쥐들의 요구사항에 부합하는 지 여부를 검증할 지구는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성&lt;/span&gt;의 인스턴스로 표현된다. 행성은 대기(&lt;span style=&quot;color: #000099;&quot;&gt;atmosphere&lt;/span&gt;), 대륙의 집합(&lt;span style=&quot;color: #000099;&quot;&gt;continents&lt;/span&gt;), 대양의 집합(&lt;span style=&quot;color: #000099;&quot;&gt;oceans&lt;/span&gt;)으로 구성된다. 3가지 속성은 반드시 존재해야 하는 필수 속성이며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성&lt;/span&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙&lt;/span&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대양&lt;/span&gt;을 각각 최소 1개 이상 포함해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717116063197&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Planet {
    private Atmosphere atmosphere;
    private List&amp;lt;Continent&amp;gt; continents;
    private List&amp;lt;Ocean&amp;gt; oceans;
   
    public Planet(Atmosphere atmosphere, List&amp;lt;Continent&amp;gt; continents, List&amp;lt;Ocean&amp;gt; oceans) {
        this.atmosphere = atmosphere;
        this.continents = continents;
        this.oceans = oceans;
        assertValid();
    }

    private void assertValid() {
        if (continents.size() &amp;lt; 1) {
            throw new IllegalArgumentException(&quot;대륙은 1개 이상 존재해야 합니다.&quot;);       
        }
       
        if (oceans.size() &amp;lt; 1) {
            throw new IllegalArgumentException(&quot;대양은 1개 이상 존재해야 합니다.&quot;);   
        }
    }

    public Collection&amp;lt;Continent&amp;gt; getContinents() {
        return Collections.unmodifiableCollection(continents);
    }

    public Collection&amp;lt;Ocean&amp;gt; getOceans() {
        return Collections.unmodifiableCollection(oceans);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;행성&lt;/span&gt;의 제조 가격은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대기&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대륙&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대양&lt;/span&gt;의 제조 가격을 모두 더한 값이다. 따라서 Planet의 getPrice() 메서드는 다음과 같이 구현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717116114731&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Planet {
    public Money getPrice() {
        return atmosphere.getPrice().plus(getContinentsPrice()).plus(getOceansPrice());
    }

    private Money getOceansPrice() {
        Money result = Money.wons(0);
        for(Continent each : continents) {
            result = result.plus(each.getPirce());
        }
        return result;
    }

    private Money getContinentsPrice() {
        Money result = Money.wons(0);
        for(Ocean each : oceans) {
            result = result.plus(each.getPirce());
        }
        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;lt;그림 2&amp;gt;는 지금까지 구현한 지구 검증 소프트웨어의 최종적인 도메인 레이어 클래스들을 다이어그램으로 표현한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; 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;2.jpeg&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6WIUc/btsHIIi2mps/qnFqCV5nxsXKYJJdnEMry0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6WIUc/btsHIIi2mps/qnFqCV5nxsXKYJJdnEMry0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6WIUc/btsHIIi2mps/qnFqCV5nxsXKYJJdnEMry0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6WIUc%2FbtsHIIi2mps%2FqnFqCV5nxsXKYJJdnEMry0%2Fimg.jpg&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;581&quot; height=&quot;275&quot; data-filename=&quot;2.jpeg&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 2&amp;gt; 지구 검증 소프트웨어의 도메인 모델&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글 :&amp;nbsp;&lt;a href=&quot;https://eternity-object.tistory.com/35&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도메인 특화 언어와 단위 테스트 - 2부&lt;/a&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>DSL</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>도메인 특화 언어</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/34</guid>
      <comments>https://eternity-object.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 31 May 2024 09:43:46 +0900</pubDate>
    </item>
    <item>
      <title>Information Hiding</title>
      <link>https://eternity-object.tistory.com/33</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소프트웨어 설계 시에 고려해야 할 기본 원리 중 가장 중요한 원리가 무엇이냐고 물어본다면 주저 없이&amp;nbsp;&amp;lsquo;정보 은닉(Information Hiding)&amp;rsquo;이라고 대답할 것이다.&amp;nbsp;정보 은닉(또는 정보 은폐라고도 한다)은&amp;nbsp;1972년&amp;nbsp;Davis Parnas가 발표한&amp;nbsp;&amp;ldquo;On the Criteria To Be Used in Decomposing Systems Into Modules&amp;rdquo;에 소개된 이후로 오랜 세월 동안 소프트웨어 개발 분야에 지대한 영향을 끼친 원리이다.&amp;nbsp;그러나 이런 중요성에도 불구하고 정보 은닉의 개념을 정확하게 이해하고 있는 사람은 많지 않다.&amp;nbsp;소프트웨어 개발 분야에서 가장 큰 영향력을 지닌 동시에 가장 큰 오해를 받고 있는 두 가지 원리가 있다면 바로 정보 은닉과 캡슐화(Encapsulation)일 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 사람들은 외부에서 제공된 공용 메소드를 통해서만 내부 데이터에 접근할 수 있도록 하고 직접적으로 내부 데이터에 접근할 수 없도록 막는 방법을 정보 은닉이라고 알고 있다.&amp;nbsp;이것은 정보 은닉과 데이터 캡슐화라는 두 용어 간의 유사성으로 인해 빚어진 오해이다.&amp;nbsp;데이터를 공용 메서드를 통해서만 접근하도록 허용하는 방법을 데이터 캡슐화라고 하며 정보 은닉과 데이터 캡슐화는 동일한 개념이 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 말하자면 정보 은닉은 모듈을 분할하기 위한 기본 원리다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모듈은 서브 프로그램이라기 보다는 책임의 할당이다.&amp;nbsp;모듈화는 개별적인 모듈에 대한 작업이 시작되기 전에 정해져야 하는 설계 결정들을 포함한다. &amp;hellip;&amp;nbsp;분할된 모듈은 다른 모듈에 대해 감추어야 하는 설계 결정에 따라 특징지어진다.&amp;nbsp;해당 모듈 내부의 작업을 가능한 적게 노출하는 인터페이스 또는 정의를 선택한다. &amp;hellip;&amp;nbsp;어려운 설계 결정이나 변화할 것 같은 설계 결정들의 목록을 사용해서 설계를 시작할 것을 추천한다.&amp;nbsp;이러한 결정이 외부 모듈에 대해 숨겨지도록 각 모듈을 설계해야 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Davis Parnas, &amp;ldquo;On the Criteria To Be Used in Decomposing Systems Into Modules&amp;rdquo;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정보 은닉은 외부에 감추어야 하는 비밀에 따라 시스템을 분할하는 모듈 분할의 원리이다.&amp;nbsp;모듈은 변경될 가능성이 있는 비밀을 내부로 감추고 잘 정의되고 쉽게 변경되지 않을 공용 인터페이스를 외부에 제공하여 내부의 비밀에 함부로 접근하지 못하도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시스템의 모듈을 분할하기 위한 설계 원리인 정보 은닉은 변경 가능한 측면을 추상화하는 인터페이스를 사용하여 변경에 대한 세부 내용을 캡슐화하거나 감추고 사용자(이 경우 다른 시스템 모듈 내의 소프트웨어)에게 일반적이고 통합된 서비스 집합을 제공한다.&amp;nbsp;이것은 각 모듈이 자신만의 작은 도메인(small domain)으로 구성되어 있음을 의미한다.&amp;nbsp;여기에서는 특별한 지식 영역 또는 전문적인 식견을 의미하기 위해 도메인(domain)이라는 용어를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;Len Bass, Paul Clements, Rick Kazman, &amp;ldquo;Software Architecture in Practice 2nd Edition&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모듈은 다음과 같은 두 가지 비밀을 감추어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복잡성&amp;nbsp;:&amp;nbsp;모듈이 너무 복잡한 경우 이해하고 사용하기가 어렵다.&amp;nbsp;외부에 모듈을 추상화시킬 수 있는 간단한 인터페이스를 제공해서 모듈의 복잡도를 낮춘다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변경 가능성&amp;nbsp;:&amp;nbsp;변경 가능한 설계 결정이 외부에 노출될 경우 실제로 변경이 발생할 경우 파급 효과가 커진다.&amp;nbsp;변경 발생 시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 쉽게 변경되지 않을 인터페이스를 제공하도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정보 은닉은 데이터 캡슐화가 아니다.&amp;nbsp;정보 은닉은 복잡하거나 변경 가능한 설계 결정을 안정적인 인터페이스 뒤로 숨기는 기본 설계 원리이다.&amp;nbsp;정보 은닉의 목적은 변경에 대한 유연성을 제공하는 것이다.&amp;nbsp;정보 은닉의 원리에 따라 설계된 시스템은 변경에 대한 파급효과가 지역적이기 때문에 변경에 따르는 비용이 상대적으로 적다.&amp;nbsp;적절히 모듈화 된 코드는 주어진 코드의 일부를 이해하기 위해 필요한 정보의 양이 적기 때문에 코드를 이해하기가 더 쉽다.&amp;nbsp;또한 상호 교환해야 하는 정보의 양을 최소화하면서 독자적으로 각 모듈에 대한 작업을 진행할 수 있으므로 개발 기간을 단축시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대규모의 소프트웨어를 만들 때 겪는 주요 어려움은 프로그래머들이 다수의 업무를 동시에 진행할 수 있게 일을 나누는 것이다.&amp;nbsp;이런 일의 모듈화는 정보 은닉의 개념에 매우 의존적이다.&amp;nbsp;정보 은닉은 객체와 알고리즘이 필요하지 않은 시스템 일부에는 가능한 한 이들을 보이지 않게 한다.&amp;nbsp;적절히 모듈화된 코드는 시스템의 주어진 일부를 이해하기 위해 필요한 정보의 양을 최소화함으로써 프로그래머가&amp;nbsp;&amp;ldquo;인지하는 부하&amp;rdquo;를 줄여준다.&amp;nbsp;잘 설계된 프로그램에서는 모듈 간의 인터페이스가 가능한&amp;nbsp;&amp;ldquo;좁으며(즉,&amp;nbsp;간단하며)&amp;rdquo;&amp;nbsp;변경될 수 있는 설계적 결정 사항은 하나의 모듈에 숨겨진다.&amp;nbsp;여기서 모듈에 숨겨져 있다는 것이 중요한데,&amp;nbsp;대부분의 상용 소프트웨어는 처음의 개발보다 유지보수(오류 수정과 개선)에 들어가는 프로그래머의 시간이 훨씬 더 많기 때문이다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Michael L. Scott, &amp;ldquo;다시 보는 프로그래밍 언어&amp;rdquo;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: justify; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정보 은닉의 가장 큰 힘은 자기 유사성이다.&amp;nbsp;시스템을 구성하는 작은 단위의 함수부터,&amp;nbsp;이벤트 기반 아키텍처의 한 구성 요소인 시스템에 이르기까지 모든 요소를 설계하기 위한 원리로 적용이 가능하다.&amp;nbsp;자기 유사성이라는 정보 은닉의 장점은 다양한 프로그래밍 패러다임의 변화를 주도해 왔으며 현재 주도적인 패러다임의 위치를 차지하고 있는 객체 지향 프로그래밍은 정보 은닉을 효율적으로 적용할 수 있는 메커니즘인 클래스를 언어 차원에서 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Parnas의 모듈에 대한 정보 은닉 정의는 매우 중요한 연구 프로그램에서 공개적으로 내디딘 첫 발걸음이며,&amp;nbsp;객체 지향 프로그래밍의 지적인 조상이다.&amp;nbsp;그는 모듈을 자체 데이터 모델과 자체 작동 집합을 가진 소프트웨어 실체라고 정의했다.&amp;nbsp;모듈의 데이터에는 그 모듈의 적절한 작동들 가운데 하나를 통해서만 접근할 수 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;두 번째 발걸음은 여러 사상가들의 헌신으로 이루어졌다.&amp;nbsp;즉,&amp;nbsp;그들이&amp;nbsp;Parnas의 모듈을 추상 데이터 타입(Abstract Data Type)으로 업그레이드하였기에,&amp;nbsp;그것으로부터 수많은 객체들이 도출될 수 있었다.&amp;nbsp;추상 데이터 타입은 모듈 인터페이스에 대한 일관된 사고와 명시 그리고 시행하기 쉬운 액세스 기율 등을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;세 번째로 내디딘 발걸음은 객체 지향 프로그래밍으로,&amp;nbsp;상속이라는 강력한 개념이 처음으로 도입되었고,&amp;nbsp;그에 따라 클래스들(데이터 타입들)은 클래스 계층 속의 조상들로부터 지정된 속성들을 기본적으로 갖게 된다.&amp;nbsp;우리가 객체 지향 프로그램에서 얻고자 하는 대부분은 사실상&amp;nbsp;Parnas가 내디딘 첫 발걸음에서,&amp;nbsp;즉 모듈의 캡슐화,&amp;nbsp;여기에 덧붙여 재사용을 목표로 설계되고 테스트된 모듈이나 클래스로 이뤄진 사전 구축된 라이브러리라는 아이디어에서 비롯된 것이다.&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;Frederick P. Brooks, Jr. &amp;ldquo;맨먼스 미신, 20주년 기념판&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정보 은닉은 구조적인 설계와 객체 지향적인 설계 모두에 있어서 기본적인 구조이다.&amp;nbsp;구조적인 설계에서는,&amp;nbsp;&amp;nbsp;&amp;ldquo;블랙박스&amp;rdquo;라는 개념이 정보 은닉으로부터 왔다.&amp;nbsp;객체 지향적인 설계에서는 정보 은닉이 캡슐화와 모듈화를 발생케 하였으며,&amp;nbsp;추상화 개념과 연결된다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Steve McConnell, &quot;Code Complete 2nd Edition&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 정보 은닉과 캡슐화의 차이는 무엇일까?&amp;nbsp;대부분의 사람들은 캡슐화를 데이터를 공용 메소드로 감추는 방법,&amp;nbsp;또는 관련된 데이터와 함수를 하나의 단위로 묶는 방법이라고 생각한다.&amp;nbsp;전자는 캡슐화의 한 종류인 데이터 캡슐화를 의미하며,&amp;nbsp;후자는 추상 데이터 타입(ADT)-책임 할당의 개념으로 보면&amp;nbsp;INFORMATION EXPERT&amp;nbsp;패턴-의 개념이다.&amp;nbsp;그러나 이런 관점은 캡슐화의 한 단면만을 이야기할 뿐이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캡슐화는&amp;nbsp;&amp;lsquo;온갖 방법의 숨기기&amp;rsquo;라고 생각하면 쉽다.&amp;nbsp;캡슐화를 통해 데이터를 숨길 수 있다.&amp;nbsp;그리고 구현 코드,&amp;nbsp;파생 클래스 또는 여러 가지의 것들을 숨길 수 있다. &amp;hellip;&amp;nbsp;따라서,&amp;nbsp;실제로 어떠한 종류의 파생 클래스가 나타나는지를 알고 있는 클라이언트를 가지지 않으면서 다형적으로 행위하는 추상 클래스가 존재할 때 이러한 캡슐화가 이루어질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;알란 섈로웨이, 제임스 트로트, &amp;ldquo;알기 쉬운 디자인 패턴&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 다양한 방법의 숨기기를 통해 클래스,&amp;nbsp;클래스 그룹,&amp;nbsp;또는 패키지 전체에 가해지는 변경에 대한 파급 효과를 일정한 영역 내부로 제한시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계에서 무엇이 변화될 수 있는지 고려하자.&amp;nbsp;이 접근법은 재설계의 원인에 초점을 맞추는 것과 반대되는 것이다.&amp;nbsp;설계에 변경을 강요하는 것이 무엇인지에 대해 고려하기보다는,&amp;nbsp;재설계 없이 변경시킬 수 있는 것이 무엇인지 고려하자.&amp;nbsp;여기에서의 초점은 많은 디자인 패턴의 주제인 변화하는 개념을 캡슐화하는 것이다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;GOF, &amp;ldquo;디자인 패턴&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 컨텍스트에서 캡슐화와 정보 은닉은 동일한 의미를 지니다.&amp;nbsp;즉,&amp;nbsp;복잡한 설계 결정,&amp;nbsp;또는 변경이 발생할 수 있는 설계 결정을 안정적인 인터페이스 배후로 감추는 것이다.&amp;nbsp;따라서 캡슐화와 정보 은닉의 차이점에 대해 고민하기보다는 원리를 충족시키는 방법에 초점을 맞추는 것이 올바른 접근방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정보 은닉과 캡슐화가 전해주는 만트라는 변경에 대비하여 설계를 하라는 것이다.&amp;nbsp;이것은&amp;nbsp;Craig Larman이&amp;nbsp;&amp;ldquo;UML과 패턴의 적용&amp;rdquo;에서 제시한&amp;nbsp;PROTECTED VARIATION&amp;nbsp;패턴과 동일하며,&amp;nbsp;객체 지향 분석/설계 영역에서 이를 달성하기 위한 가장 훌륭한 방법은&amp;nbsp;OCP(Open-Closed Principle)&amp;nbsp;원리를 따르는 것이다. OCP에 대한 자세한 논의는&amp;nbsp;Rober C. Martin의 저서인&amp;nbsp;&amp;ldquo;소프트웨어 개발의 지혜&amp;rdquo;에서 자세히 다루고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러나 변경에 대비하는 설계가 항상 좋은 것은 아니다.&amp;nbsp;변경은 실제로 변경이 발생할 때에만 그 의미가 있다.&amp;nbsp;불필요한 복잡성(Needless Complexity)의 문제는 개발자가 요구 사항에 대한 변경 내용을 막연히 예상하고 잠재적인 변경을 처리할 수 있는 불필요한 코드를 넣었을 때 발생한다.&amp;nbsp;이것은&amp;nbsp;XP의 슬로건인 &amp;ldquo;YAGNI(You Aren&amp;rsquo;t Gonna Need It)&amp;rdquo;와도 연관이 있다. YAGNI는 지금 당장 그 기능이 필요하지 않다면 정말 필요해질 때까지 해당 기능을 구현하지 말라는 의미다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #3d4444; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게&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;릴리즈&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;hellip;&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;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Martin Fowler, &amp;ldquo;Is Design Dead?&amp;rdquo;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 정보 은닉과 캡슐화의 원리를 지키면서 변경에 따르는 비용을 최소화하기 위한 방법은 무엇일까? 변경이 발생할 가능성이 높다면 변경에 대비하기 위한 설계를 하자. 만약 변경의 발생 여부가 명확하지 않다면 실제로 변경이 발생할 때까지는 그 사실을 잊도록 하자. 변경이 발생할 경우 리팩토링에 따른 위험성을 낮추기 위해 테스트 케이스라는 안전망으로 코드를 보호하자. 그리고 변경될 것이라면 최대한 빨리 변경이 발생하도록 촉진시키기 위해 다음과 같은 방법을 사용하자.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&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;- 주 단위보다는 일 단위로, 아주 짧은 주기로 개발한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 기반 구조 보다 기능 요소를 먼저 개발하고, 자주 이 기능 요소를 이해당사자(stakeholder)에게 보인다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 가장 중요한 기능 요소를 먼저 개발한다. 소프트웨어를 빨리, 그리고 자주 릴리즈한다. 가능한 빠르게, 자주 고객과 사용자 앞에서 그것을 시연한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Robert C. Martin, &amp;ldquo;소프트웨어 개발의 지혜&amp;rdquo;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>information hiding</category>
      <category>정보 은닉</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/33</guid>
      <comments>https://eternity-object.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 31 May 2024 00:54:22 +0900</pubDate>
    </item>
    <item>
      <title>테스트 커버리지에 현혹되지 말자</title>
      <link>https://eternity-object.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;얼마 전에 열렸던 회사 컨퍼런스에서 테스트 커버리지를 주제로 한 발표가 있었다.&amp;nbsp;발표 내용을 간략하게 요약하면 어떤 서비스에 단위 테스트를 작성하도록 정책적인 장치를 마련한 결과&amp;nbsp;70%의 테스트 커버리지를 얻게 되었다는 것이다.&amp;nbsp;코드에 실행 가능한 단위 테스트가 존재하고 팀이 지속적으로&amp;nbsp;70%의 테스트 커버리지를 유지할 수 있다는 것은 팀과 코드의 상태 모두 양호하고 프로젝트의 가시성이 확보되었다는 것을 나타내주는 긍정적인 척도다.&amp;nbsp;그러나 문제는 발표에서 언급하고 있는 테스트 커버리지의 종류가 라인 커버리지(Line Coverage)라는데 있다.&amp;nbsp;그리고 더 큰 문제는 대부분의 사람들이 라인 커버리지라는 용어는 무시한 채&amp;nbsp;70%라는 수치 자체를 중요시 한다는데 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하자면 테스트 커버리지에 대한 정책을 수립할 때 라인 커버리지(Line Coverage)나 브랜치 커버리지(Branch Coverage)를 목표로 하는 것은 옳지 않다.&amp;nbsp;특히 앞의 예에서처럼 테스트 커버리지에 대한 전반적인 이해 없이 맹목적으로 테스트 커버리지 수치만을 강조할 경우 오히려 코드 전체의 품질이 저하될 위험이 있다.&amp;nbsp;뒤에서 살펴보겠지만&amp;nbsp;70%의 라인 커버리지를 달성했다고 해서&amp;nbsp;70%의 코드 품질을 달성했다고 장담할 수는 없다.&amp;nbsp;따라서 정책적으로 무의미한 목표 커버리지를 강요할 경우 개발자가 취할 수 있는 마지막 방법은 코드의 품질을 보장하지는 않더라도 테스트 커버리지를 높일 수 있는 무가치한 테스트 케이스를 작성하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라인 커버리지(Line Coverage)-또는 스테이트먼트 커버리지(Statement Coverage)라고도 한다-는 전체 코드 중 테스트에 의해 실행된 코드 라인 수의 비율을 의미한다.&amp;nbsp;전체&amp;nbsp;100라인의 코드 중에서 테스트 케이스가&amp;nbsp;80&amp;nbsp;라인을 실행한 경우의 라인 커버리지는&amp;nbsp;80%다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 개념을 이해하는 가장 좋은 방법은 실행되는 예제를 보는 것이므로 판매 도메인과 관련된 간단한 예제를 통해 라인 커버리지의 문제점을 살펴 보도록 하자.&amp;nbsp;어떤 상점에서 고객에게 물건을 판매할 때 고객의 등급과 상품의 할인 여부에 따라 할인율을 다르게 적용한다고 가정하자.&amp;nbsp;고객 등급은&amp;nbsp;VIP, GOLD, SILVER, REGULAR로 나뉘며&amp;nbsp;VIP&amp;nbsp;회원일 경우&amp;nbsp;0.1%, GOLD&amp;nbsp;회원일 경우&amp;nbsp;0.05%, SILVER회원일 경우&amp;nbsp;0.02%, REGULAR&amp;nbsp;회원일 경우에는 할인되지 않는다.&amp;nbsp;고객 등급과 별도로 상품이 할인 가능한 경우 상품에 할당된 할인율에 따라 가격을 할인해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고객 등급과 등급별 할인율은&amp;nbsp;Credit&amp;nbsp;열거형에 정의한다. discountAmount()&amp;nbsp;메소드는 고객 등급에 따라 할인된 가격을 계산하여 반환한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083092205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Credit {
  VIP(0.1), GOLD(0.05), SILVER(0.02), REGULAR(0);

  private double discountRate;    

  private Credit(double discountRate) {
    this.discountRate = discountRate;
  }

  public Money discountAmount(Money price) {
    return price.minus(price.times(discountRate));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Product은 상품의 명칭,&amp;nbsp;가격,&amp;nbsp;할인율을 속성으로 가지고,&amp;nbsp;상품 자체의 할인율에 따라 할인된 가격을 계산한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083156699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Product {
  private String name;
  private Money price;
  private double discountRate;

  Product(String name, Money price) {
    this.name = name;
    this.price = price;
    this.discountRate = 0;       
  }
  
  Product(String name, Money price, double discountRate) {
    this(name, price);
    this.discountRate = discountRate;         
  }

  Money getPrice() {
    return price;
  }

  public boolean isDiscountable() {
    return discountRate != 0;
  }

  public Money discount(Money amount) {
    return amount.minus(amount.times(discountRate));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Customer는 이름과 고객 등급을 속성으로 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083249910&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Customer {
  private Credit credit;
  private String name;
    
  public Customer(String name, Credit credit) {
    this.name = name;
    this.credit = credit;
  }      

  public boolean isSpecialCredit() {
    return credit != Credit.REGULAR;
  }

  public Credit getCredit() {
    return credit;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리가 테스트하려는 대상은&amp;nbsp;Seller&amp;nbsp;클래스의&amp;nbsp;calculatePrice()&amp;nbsp;메소드로&amp;nbsp;Customer, Product,&amp;nbsp;상품의 할인 여부를 선택하는&amp;nbsp;applyProductDiscount&amp;nbsp;속성을 받아 금액을 계산한다.&amp;nbsp;고객 등급에 따른 할인 정책은 항상 적용되지만 상품 자체의 할인율은applyProductDiscount&amp;nbsp;파라미터가&amp;nbsp;&lt;b&gt;true&lt;/b&gt;&amp;nbsp;인 경우에만 적용된다(이 예제는&amp;nbsp;INFORMATION EXPERT도 준수하지 않고 여기 저기 책임도 잘못 할당되어 있다.&amp;nbsp;설명을 위해 인위적으로 만든 예제라고 생각하고 봐주기 바란다).&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083310647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Seller {
  Money calculatePrice(Customer customer, Product product, boolean applyProductDiscount) {
    Money amount = product.getPrice();            

    if (customer.isSpecialCredit()) {
      amount = customer.getCredit().discountAmount(amount);
    }            

    if (applyProductDiscount) {
      amount = product.discount(amount);
    }
    
    return amount;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라인 커버리지는 단순히 테스트 케이스에 의해 실행된&amp;nbsp;calculatePrice()&amp;nbsp;메소드의 라인 수를 측정하기 때문에 두 조건식을&amp;nbsp;&lt;b&gt;true&lt;/b&gt;로 만드는 파라미터를 전달하면 간단하게&amp;nbsp;100%의 라인 커버리지를 얻게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083369396&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SellerTest {
  @Test
  public void calculateVIPCustomerAmount() {
    Customer customer = new Customer(&quot;조영호&quot;, Credit.VIP);
    Product product = new Product(&quot;X-Box 360&quot;, Money.wons(300000), 0.05);

    assertEquals(Money.wons(256500),
        new Seller().calculatePrice(customer, product, true));
  }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 케이스 하나로&amp;nbsp;100%의 라인 커버리지를 얻었지만 두&amp;nbsp;&lt;b&gt;if&lt;/b&gt;&amp;nbsp;문 내의 조건식이&amp;nbsp;&lt;b&gt;false&lt;/b&gt;&amp;nbsp;인 경우는 테스트하지 않았다.&amp;nbsp;결론적으로&amp;nbsp;100%의 라인 커버리지를 얻는 것은 매우 간단하지만 코드의 품질을 보장하기에는 신뢰성이 떨어진다는 사실을 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브랜치 커버리지(Branch Coverage)는 라인 커버리지의 단점을 보완하고 개별 조건식이&amp;nbsp;&lt;b&gt;true&lt;/b&gt;,&amp;nbsp;&lt;b&gt;false&lt;/b&gt;&amp;nbsp;인 경우 모두 테스트되었는지를 판단하는 테스트 커버리지 유형이다.&amp;nbsp;앞의 예에서 고객이 특별 등급인 경우와 아닌 경우(두 조건의 조합이&amp;nbsp;TRUE-TRUE인 경우),상품의 할인 여부를 적용하는 경우와 적용하지 않는 경우(두 조건의 조합이&amp;nbsp;FALSE-FLASE인 경우)의 테스트 케이스가 모두 존재할 경우&amp;nbsp;100%의 브랜치 커버리지를 얻을 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞에서 작성한 테스트 케이스는 고객이 특별 등급인 경우와 상품 할인 여부를 적용하는 경우를 테스트하므로,&amp;nbsp;고객이 특별 등급이 아닌 경우와 상품 할인 여부를 적용하지 않는 경우에 대한 테스트 케이스를 추가하면&amp;nbsp;100%의 브랜치 커버리지를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083407656&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SellerTest {
  @Test
  public void calculateRegularCustomerAmount() {
    Customer customer = new Customer(&quot;조영호&quot;, Credit.REGULAR);
    Product product = new Product(&quot;X-Box 360&quot;, Money.wons(300000));           

    assertEquals(Money.wons(300000),
        new Seller().calculatePrice(customer, product, false));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 커버리지를 측정할 수 있는 다양한 오픈 소스,&amp;nbsp;상용 제품이 존재하며, Ant, Maven과 같은 빌드 스크립트와&amp;nbsp;CI&amp;nbsp;툴에 테스트 커버리지 측정 도구를 플러그인할 수 있다.&amp;nbsp;여기에서는 오픈 소스&amp;nbsp;Cobertura를 사용해서 라인 커버리지와 브랜치 커버리지를 측정한 결과를 표시한 것이다.&amp;nbsp;두 개의 테스트 케이스를 실행한 결과&amp;nbsp;Seller의 라인 커버리지와 브랜치 커버리지 모두&amp;nbsp;100%를 달성한 것에 주목하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.jpeg&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1TWZG/btsHIukVy46/KIJJVXHhhEomGtZKghcXPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1TWZG/btsHIukVy46/KIJJVXHhhEomGtZKghcXPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1TWZG/btsHIukVy46/KIJJVXHhhEomGtZKghcXPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1TWZG%2FbtsHIukVy46%2FKIJJVXHhhEomGtZKghcXPk%2Fimg.jpg&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;674&quot; height=&quot;179&quot; data-filename=&quot;1.jpeg&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브랜치 커버리지의 경우 개별 조건의&amp;nbsp;TRUE, FALSE&amp;nbsp;여부가 테스트 되었는지는 확인 가능하지만 조건의 조합이 테스트되었는지는 확인할 수 없다.&amp;nbsp;따라서 고객이 일반 회원이고 제품의 할인율을 적용하는 경우나,&amp;nbsp;고객이 특별 회원이고 제품의 할인율을 적용하지 않는 경우는 테스트하지 않는다는 단점이 있다.&amp;nbsp;따라서 라인 커버리지와 브랜치 커버리지 모두&amp;nbsp;100%를 달성했다고 하더라도 코드 자체의 모든 경로를 테스트했다고 볼 수는 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 단점을 보완하기 위해서는 패스 커버리지(Path Coverage)를 목표로 삼아야 한다.&amp;nbsp;패스 커버리지는 개별 라인이나 개별 조건식이 아닌 메소드가 실행될 수 있는 경로의 조합을 테스트하는 방식을 사용한다.&amp;nbsp;일반적으로 패스 커버리지를 만족시키기 위해서는 최소 순환 복잡도(Cyclomatic Complexity)만큼의 테스트 케이스가 필요하다.&amp;nbsp;위&amp;nbsp;Cobertura&amp;nbsp;레포트에서&amp;nbsp;Complexity&amp;nbsp;컬럼이 바로 순환 복잡도를 의미한다.&amp;nbsp;따라서&amp;nbsp;100%의 패스 커버리지를 달성하기 위해서는 최소&amp;nbsp;3개의 테스트 케이스가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패스 커버리지를 만족시키는 테스트 케이스의 조합을 만들기 위해서는 간단하게 개별 조건식의 결과를 순서대로 토글시키도록 파라미터를 전달하면 된다.&amp;nbsp;판매 예에서는 고객의 특별 등급 여부와 상품 할인 여부에 대한 두 개의 조건식이 존재하므로,&amp;nbsp;고객이 특별 등급이고 상품 할인을 적용하는 경우(TRUE-TRUE)와 고객이 특별 등급이 아니고 상품 할인을 적용하는 경우(FALSE-TRUE),&amp;nbsp;마지막으로 고객이 특별 등급이고 상품 할인을 적용하지 않는 경우(TRUE-FALSE)의&amp;nbsp;3가지 테스트 케이스를 작성하면 된다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717083478824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SellerTest {
  @Test
  public void calculateVIPCustomerAmount() {
    Customer customer = new Customer(&quot;조영호&quot;, Credit.VIP);
    Product product = new Product(&quot;X-Box 360&quot;, Money.wons(300000), 0.05);
 
    assertEquals(Money.wons(256500), 
        new Seller().calculatePrice(customer, product, true));
  }

  @Test
  public void calculateRegularCustomerAndDiscountAmount() {
    Customer customer = new Customer(&quot;조영호&quot;, Credit.REGULAR);
    Product product = new Product(&quot;X-Box 360&quot;, Money.wons(300000), 0.05);
    
    assertEquals(Money.wons(285000), 
        new Seller().calculatePrice(customer, product, true));
  }     

  @Test
  public void calculateVIPCustomerAndNonDiscountAmount() {
    Customer customer = new Customer(&quot;조영호&quot;, Credit.VIP);
    Product product = new Product(&quot;X-Box 360&quot;, Money.wons(300000), 0.05);
          
    assertEquals(Money.wons(270000), 
        new Seller().calculatePrice(customer, product, false));
  }     
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 살펴본 것처럼 팀에서 테스트 커버리지를 측정하고자 한다면 라인 커버리지와 브랜치 커버리지가 아니라 패스 커버리지를 목표로 해야 한다.&amp;nbsp;물론 라인 커버리지에서 패스 커버리지로 단계적으로 목표를 조정해 가는 것은 좋은 방법이지만 맹목적으로 라인 커버리지를 강제하는 것은 올바른 접근 방법이 아니다.&amp;nbsp;모든 클래스가70%의 라인 커버리지를 갖는 것보다 소수의 핵심 클래스가&amp;nbsp;70%의 패스 커버리지를 갖는&amp;nbsp;것이 더 좋은 품질을 보장할 수 있다는 점에 주목하자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파레토 원리를 잊지 말자.&amp;nbsp;우리는 전체 오류의&amp;nbsp;80%를 차지하는&amp;nbsp;20%&amp;nbsp;부분에 집중해야 한다.&amp;nbsp;테스트 커버리지 측정은&amp;nbsp;적은 노력으로도 매우 큰 이익을 얻을 수 있는 효과적인 프랙티스다.&amp;nbsp;테스트 커버리지를 바보들의 황금으로 만드는 것은 테스트 그 자체가 아니라 테스트 커버리지를 사용하는 우리들 자신이라는 것을 잊지 말아야 한다.&amp;nbsp;배를 난파시키려는 세이렌의 노래에 현혹되지 말자.&amp;nbsp;눈을 감고 귀를 막을 필요까지는 없지만 무엇이 옳은 길인가에 대한 냉철한 판단력을 유지해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4444; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/32</guid>
      <comments>https://eternity-object.tistory.com/32#entry32comment</comments>
      <pubDate>Fri, 31 May 2024 00:38:42 +0900</pubDate>
    </item>
    <item>
      <title>프레임워크 - 2부 [끝]</title>
      <link>https://eternity-object.tistory.com/31</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 : &lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프레임워크 - 1부&lt;/a&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;제어 역전(Inversion of Control)과 프레임워크&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전은 의존성의 방향뿐만 아니라 제어 흐름의 주체 역시 역전 시킨다. 앞서 설명한 것처럼 상위 정책이 구체적인 세부사항에 의존하는 전통적인 구조에서는 상위 정책의 코드가 하부의 구체적인 코드를 호출한다. 즉, 애플리케이션의 코드가 재사용 가능한 라이브러리나 툴킷의 코드를 호출한다. 그러나 의존성을 역전 시킨 객체 지향 구조에서는 반대로 프레임워크가 애플리케이션에 속하는 서브 클래스의 메소드를 호출한다. 따라서 프레임워크를 사용할 경우 개별 애플리케이션에서 프레임워크로 제어 흐름의 주체가 이동된다. 즉, 의존성을 역전시키면 제어 흐름의 주체 역시 역전된다. 이를 제어 역전(Inversion of Control)의 원리, 또는 할리우드(Hollywood) 원리라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계 수준의 재사용은 애플리케이션과 기반이 되는 소프트웨어 간에 제어를 바꾸게 한다. 툴킷을 사용하여 애플리케이션을 작성하면, 애플리케이션은 재사용하는 툴킷의 코드를 호출한다. 그러나 프레임워크를 재사용할 때는 프레임워크가 제공하는 주(main) 프로그램을 재사용하고 이 주 프로그램이 호출하는 코드를 애플리케이션 개발자가 작성하는 것이다. 이미 특정 이름과 호출 방식이 결정된 오퍼레이션을 작성해야 하지만 결정해야 하는 설계 개념은 줄어들고 애플리케이션 별 구체적인 오퍼레이션의 구현만 남게 된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;GOF, 디자인 패턴&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크에서는 일반적인 해결책만 제공하고 애플리케이션에 따라 달라질 수 있는 특정한 동작은 비워둔다. 그리고 이렇게 완성되지 않은 채로 남겨진 동작을 훅(hook)이라고 하며, 훅의 구현은 애플리케이션의 컨텍스트에 따라 달라진다. 훅은 프레임워크 코드에서 호출하는 프레임워크의 특정 부분이다. 재정의된 훅은 제어 역전 원리에 따라 프레임워크가 원하는 시점에 호출된다. 만약 프레임워크를 처음 사용한다면 제어 흐름이 손가락 사이로 스멀스멀 빠져나가는 듯한 느낌에 불안해질 수도 있다. 그러나 이러한 제어의 역전이 프레임워크의 핵심 개념인 동시에 코드의 재사용을 가능하게 하는 힘이라는 사실을 이해해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVZleU/btsHJR6YGOq/9KgHgFZh8F8O7fB0aZlDPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVZleU/btsHJR6YGOq/9KgHgFZh8F8O7fB0aZlDPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVZleU/btsHJR6YGOq/9KgHgFZh8F8O7fB0aZlDPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVZleU%2FbtsHJR6YGOq%2F9KgHgFZh8F8O7fB0aZlDPk%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;2348&quot; height=&quot;962&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: justify;&quot;&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 5&amp;gt; 프레임워크에 정의된 제어 흐름에 따라 호출되는 애플리케이션 객체&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프레임워크의 진화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재사용 가능한 프레임워크는 실제로 재사용되었을 때에만 재사용 가능하다고 말할 수 있다. 의존성 역전과 제어 역전의 원리를 통해 확장 가능하도록 프레임워크를 구축했다고 해도 요구사항의 변화와 도메인 규칙의 확장을 수용하지 못하는 프레임워크는 재사용되기 어렵다. 그러나 프레임워크의 사용 패턴을 예견하고 변화에 대응하기 위해서는 실제적으로 프레임워크를 적용한 애플리케이션이 필요하다. 따라서 프레임워크를 개발을 위해 가장 필요한 것은 구체적인 애플리케이션 개발을 통한 피드백이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재사용 가능한 프레임워크를 개발하기 위해 피드백을 증폭시킬 수 있는 가장 핵심적인 방법은 반복(iteration)이다. 프레임워크의 초기 버전을 사용해서 애플리케이션을 구축하고, 구축된 애플리케이션을 통해 얻어진 피드백을 통해 반복적으로 프레임워크를 정제하고 확장하라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크를 구축하기 위해 반복적인 접근 방법을 적용할 경우 다음과 같은 사항에 주의해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫째, 프레임워크는 실제로 애플리케이션을 구축하기 위해 반복적으로 적용 가능해야 한다. 여기에 언급할 가치가 있는 두 가지 &amp;lsquo;3의 법칙&amp;rsquo;이 있다. 재사용 가능한 컴포넌트를 만드는 것은 단일 목적의 컴포넌트를 만드는 것보다 세배는 어렵다. 컴포넌트는 재사용 라이브러리로 인정할 만큼 일반적이라 생각하기 전에 서로 다른 세 가지 애플리케이션에 적용해 봐야 한다. 이처럼 세 가지 예제 애플리케이션을 통해 프레임워크를 개발하는 방식을 THREE EXAMPLES 패턴이라고 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;둘째, 대부분의 사람들의 예상과 달리 성공적인 프레임워크를 만들기 위한 최선의 접근 방법은 프레임워크를 염두에 두지 않는 것이다. 애플리케이션을 개발하기 전에 프레임워크부터 개발할 경우 재사용과 확장이 어렵고 불필요한 기능과 복잡도로 인해 사용이 어려운 프레임워크를 개발하게 될 확률이 높다. 따라서 개발이 완료된 애플리케이션 간의 공통 부분을 기반으로 프레임워크를 개발하는 것이 효과적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;셋째, 프레임워크가 갖추어야 할 가장 기본적인 필요조건은 적절한 추상화다. 다양한 컨텍스트에 대한 가장 최적의 추상화를 구축하는 방법은 실제 애플리케이션의 공통적인 부분을 식별하고 이를 프레임워크를 구성하는 아키텍처의 근간으로 삼는 것이다. 따라서 프레임워크의 추상 클래스 및 인터페이스의 목록, 이들 간의 관계는 사전 설계가 아닌 경험을 기반으로 해야 한다. 경험이 추정을 향상시킨다는 XP의 슬로건과 유사하게 경험은 프레임워크의 추상화 역시 향상 시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성공적으로 재사용되는 프레임워크는 적용 과정에서 발생하는 다양한 피드백을 통해 지속적으로 진화하게 된다. 프레임워크가 변경되지 않는 시점은 프레임워크가 더 이상 사용되지 않게 되었을 때뿐이다. Ralph Johnson은 성공적으로 재사용되는 프레임워크는 적어도 동일한 도메인에 포함된 3개 이상의 애플리케이션의 공통적인 추상화를 기반으로 하며, 화이트 박스 프레임워크에서 블랙박스 프레임워크로, 그리고 최종적으로 잘 구축된 도메인 특화 언어(DSL, Domain-Specific Language)의 형태로 진화한다고 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Three Examples&lt;/b&gt;&amp;nbsp;- 프레임워크는 적어도 동일한 도메인에 포함된 3개 이상의 애플리케이션에서 공통적인 부분을 사용해서 추상화를 이끌어 내야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;White Box Framework&lt;/b&gt;&amp;nbsp;- 두 번째 애플리케이션을 개발하는 경우 상속을 사용해서 프레임워크의 기능을 확장하는 화이트 박스 프레임워크를 구축한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Component Library&lt;/b&gt;&amp;nbsp;- 세 번째 프레임워크를 개발하는 경우 반복적인 코드 작성을 피하기 위해 공통적으로 사용되는 클래스들을 프레임워크에 포함시킨다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Hot Spots&lt;/b&gt;&amp;nbsp;- 프레임워크를 사용해서 애플리케이션을 사용하는 과정에서 유사한 코드들이 나타날 경우 반복되는 코드와 반복되지 않는 코드를 분리한 후 변경되는 부분을 캡슐화해서 상속이 아닌 조합을 통해 애플리케이션을 작성할 수 있도록 한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Pluggable Objects&lt;/b&gt;&amp;nbsp;- 변경의 범위가 어느 정도 예측할 수 있게 되었다면 변경 가능성을 파라미터화할 수 있도록 객체에 유연성을 추가한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Fine-grained Objects&lt;/b&gt;&amp;nbsp;- 컴포넌트 라이브러리의 재사용성을 향상시키기 위해 문제 도메인 내에서 더 이상 분해하는 것이 의미가 없을 정도로 객체를 분해한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Black-Box Framework&lt;/b&gt;&amp;nbsp;- 컴포넌트 라이브러리를 구축하는 경우에만 상속을 통해 프레임워크를 확장하고, 애플리케이션에서는 단순히 컴포넌트들을 조합해서 사용할 수 있는 블랙박스 프레임워크를 구축한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Visual Builder&amp;nbsp;&lt;/b&gt;&amp;ndash; 조합을 통해 애플리케이션을 개발할 수 있는 블랙 박스 프레임워크를 가지게 되었으므로 이를 시각적으로 조합할 수 있는 그래픽 에디터를 제공한다. 이것은&amp;nbsp; DSL의 Language-Workbench를 개발하는 것을 의미한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Language Tools&lt;/b&gt;&amp;nbsp;&amp;ndash; Visual Builder를 사용해서 조합된 객체의 정합성을 검증하고 디버깅할 수 있는 도구를 제공한다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4qpmF/btsHJjCPMZ9/ofO6ATsEDZtkTxcHXIXn2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4qpmF/btsHJjCPMZ9/ofO6ATsEDZtkTxcHXIXn2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4qpmF/btsHJjCPMZ9/ofO6ATsEDZtkTxcHXIXn2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4qpmF%2FbtsHJjCPMZ9%2FofO6ATsEDZtkTxcHXIXn2K%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;564&quot; height=&quot;374&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 6&amp;gt; 프레임워크의 진화 패턴&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크를 효과적으로 재사용하기 위해서는 프레임워크의 최종 모습뿐만 아니라 현재의 모습을 띠게 되기까지 진화한 과정을 살펴 보는 것이 가장 효과적이다. 프레임워크의 진화 과정 속에는 프레임워크의 구성 원리 및 설계 원칙, 재사용 가능한 컨텍스트와 변경 가능성에 관련된 다양한 정보가 들어 있기 때문이다. 그렇다면 프레임워크 진화 속에 담겨 있는 다양한 정보를 효과적으로 전달할 수 있는 방법이 없을까? 패턴과 패턴 언어에서 그 해답을 찾을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;패턴 언어와 프레임워크&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크의 진화 과정은 유사한 애플리케이션들을 일반화를 통해 공통적인 추상화를 식별하고 변경 지점을 캡슐화 시켜 블랙 박스 프레임워크로 개선시키는 과정으로 요약할 수 있다. 이러한 진화 과정을 거치면서 자연스럽게 변경이 빈번하게 발생하는 &amp;ldquo;핫 스팟(Hot Spot)&amp;rdquo;을 식별하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다양한 요구사항을 수용할 수 있도록 핫 스팟을 유연하게 만들고 코드 중복을 제거하는 가장 보편적인 방법은 디자인 패턴을 사용하는 것이다. 객체의 알고리즘이 빈번하게 변경된다면 STRATEGY 패턴을 적용해 알고리즘을 객체로 캡슐화시키고 실행시간에 알고리즘을 교체할 수 있도록 만든다. 객체의 행위가 특정 상태에 따라 변경되어야 한다면 STATE 패턴을 사용해서 상태 변경에 유연해지도록 만든다. GOF에 소개된 23개의 디자인 패턴 중 대다수는 다양한 변이를 캡슐화시키고 애플리케이션을 유연하기 만들기 위해 적용될 수 있다. 대부분의 프레임워크는 진화 과정 속에서 리팩토링을 통해 다양한 패턴을 적용하는(또는 제거하는) 과정을 통해 유연하고 확장성 있는 구조를 갖추게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 언급한 재사용 가능하고 확장 가능한 유연한 설계는 프레임워크 설계에는 매우 필수적이다. 디자인 패턴을 이용하는 프레임워크는 그렇지 않은 프레임워크보다 설계와 코드 재사용의 수준을 높일 수 있다. 성숙한 프레임워크는 일반적으로 여러 개의 디자인 패턴을 사용하는데, 이런 디자인 패턴은 프레임워크의 아키텍처를 재설계하지 않고도 다른 많은 애플리케이션에 재사용할 수 있도록 도와준다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;GOF,디자인 패턴&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 프레임워크는 다양한 분석 패턴, 디자인 패턴, 아키텍처 패턴의 조합으로 표현할 수 있다. 예를 들어 전통적인 Smalltalk MVC 프레임워크의 경우 MODEL-VIWE-CONTROLLER 아키텍처 패턴을 사용하여 구축되었으며, OBSERVER, COMPOSITE, STRATEGY 디자인 패턴을 포함한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴은 반복적으로 발생하는 문제와 해결책뿐만 아니라 언제(when), 어떤 방식으로(how) 패턴을 적용해야 하는지에 대한 규칙과 패턴의 구조에 이르게 된 이유(why)까지도 포함한다. 따라서 패턴의 형식을 빌어 설계를 설명할 경우 언제(when), 어떻게(how) 설계를 확장할 지에 대한 지침을 전달하고 설계 원리(why) 및 근거를 효과적으로 설명할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변화나 요구사항과 같은 다양한 원인을 설계로 변경하여 아키텍처를 유도하기 위해 적용할 수 있는 패턴을 &amp;ldquo;생성적인 패턴(generative pattern)&amp;rdquo;이라고 부른다. &amp;ldquo;생성적인 패턴&amp;rdquo;의 핵심은 변화의 요인을 설계로 변형시키기 위해 패턴에 포함된 언제(when)와 어떻게(how)라는 측면을 사용한다는 점이다. &amp;ldquo;생성적인 패턴&amp;rdquo;을 적용한 경우 설계의 최종 모습만이 아니라 설계가 그런 구조를 가지게 된 이유(why)까지도 쉽게 전달할 수 있다. 따라서 프레임워크를 가장 효과적으로 문서화할 수 있는 방법은 패턴을 사용하는 것이다. 프레임워크 문서에는 프레임워크의 목적, 프레임워크 사용 방법, 프레임워크의 세부 설계가 포함되어야 한다. 언제(when), 어떻게(how), 왜(why)와 관련된 패턴의 3가지 측면은 프레임워크 문서화의 3가지 요구사항을 만족시킬 수 있는 최고의 도구를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크의 전반적인 구조를 설명하고 이를 문서화하기 위해서는 개별 패턴이 아닌 패턴 언어(pattern language)에 초점을 맞추어야 한다. 패턴 언어는 특정한 애플리케이션 도메인의 본질적인 설계 지식을 표현한다. 패턴 언어와 마찬가지로 프레임워크 역시 특정 도메인을 대상으로 하며 도메인 문제를 해결하기 위해 필요한 연관성 높은 패턴들의 집합을 포함한다. 화이트 박스 프레임워크에서 블랙 박스 프레임워크로의 진화 과정을 거치면서 추가되는 다양한 패턴들은 상호 연관되어 있으며 이들이 모여 패턴 언어를 구성한다. 패턴과 패턴 간의 관계에 초점을 맞춤으로써 프레임워크의 진화 과정과 설계 원리, 사용 방법을 좀 더 높은 개념에서 조망하고 이해할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개별적인 디자인 패턴이 애플리케이션 설계에 있어서의 의사 결정 지점이라면, 패턴 언어는 개별 패턴이 일련의 다른 패턴으로 연결되는 트리 또는 그래프 형태로 구성될 수 있다. 이런 구조는 완전한 애플리케이션을 설계하기 위해 필요한 시간에 따른 일련의 의사 결정을 표현하며, 애플리케이션은 프레임워크를 사용해서 구현된다. 따라서, 패턴 언어는 프레임워크를 애플리케이션으로 변환하기 위해 필요한 구체적인 방법(method)이 된다. &amp;hellip; 특정한 애플리케이션 도메인을 위한 패턴 언어와 프레임워크를 사용할 수 있다면 프레임워크가 패턴과 패턴 언어의 재사용 가능한 구현을 제공하기 때문에 아무 것도 없는 상태에서 새로운 애플리케이션을 개발할 필요가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;Davide Brugali,Frameworks and pattern languages: an intriguing relationship&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 살펴본 바와 같이 패턴 언어는 아키텍처와 설계 측면의 의사소통과 재사용성 향상, 공통 용어의 제공, 개념적 무결성 확립을 위한 메타포의 제공뿐만 아니라, 구체적인 코드 재사용을 위한 프레임워크의 근간으로 활용할 수 있다. 패턴을 적용하기 위한 패턴에는 한계가 없다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>design pattern</category>
      <category>framework</category>
      <category>디자인 패턴</category>
      <category>프레임워크</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/31</guid>
      <comments>https://eternity-object.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 30 May 2024 23:24:39 +0900</pubDate>
    </item>
    <item>
      <title>프레임워크 - 1부</title>
      <link>https://eternity-object.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;재사용과 프레임워크&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 이상적인 재사용 방법은 추가적인 프로그래밍 작업 없이 이미 존재하는 컴포넌트(component)를 조립하여 시스템을 구축하는 것이다. 그러나 이와 같은 컴포넌트 기반의 레고 블록(LEGO block, 또는 집적 회로 IC) 접근 방법에는 분명한 한계가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다양한 컨텍스트 내에서 컴포넌트를 재사용하기 위해서는 조립 시 다양한 매개변수와 옵션을 설정할 수 있어야 한다. 설정 가능한 파라미터와 옵션의 종류가 적을수록 컴포넌트를 재사용할 수 있는 콘텍스트의 범위가 줄어든다. 반대로 설정 가능한 파라미터와 옵션의 종류가 많을수록 재사용을 위해 기억해야 하는 정보의 양이 늘어나기 때문에 프레임워크를 사용하기가 복잡해진다. 컴포넌트와 관련된 역설은 컴포넌트를 재사용 가능하게 만들려고 하는 모든 노력은 컴포넌트의 재사용 가능성을 감소시킨다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보다 본질적인 문제는 비즈니스 컴포넌트와 같은 대규모의 재사용 가능한 컴포넌트를 개발하는 것이 거의 불가능하다는 사실이다. 설정 가능한 파라미터와 옵션이 다양해야 한다는 것은 소프트웨어가 수용해야 하는 요구사항의 스펙트럼이 다양해야 한다는 것을 의미한다. 여러 애플리케이션에 대해 코드를 수정하지 않고도 재사용 가능할 정도로 유사한 요구사항을 발견하는 것은 예외에 가깝다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 논쟁의 핵심은 소프트웨어 다양성이라 불리는 주제에 있을 것이다. 만약 여러 프로젝트나 애플리케이션 도메인 사이에 비슷한 문제가 충분히 많이 존재한다면 컴포넌트 기반의 접근방법은 결국 효과가 있을 것이다. 그러나 많은 사람들이 의심하는 바와 같이, 애플리케이션과 도메인의 다양성으로 인해 두 가지 문제가 아주 비슷한 경우가 거의 없다면, 가장 기본이 되는 공통적 작업만이 일반화될 수 있을 것이고, 이것이 프로그램 코드에서 차지하는 비율 또한 매우 작을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Robert L. Glass, 우리가 미처 알지 못한 소프트웨어 공학의 사실과 오해&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컴포넌트 재사용이 비현실적인 접근방법이라면 컴포넌트를 개발하는 동안 축적된 경험과 지식을 재사용하는 방법을 생각해 볼 수 있다. 컴포넌트 자체를 재사용하는 것은 코드 재사용(code reuse)을 의미한다. 이에 비해 경험과 지식의 재사용은 설계의 재사용(design reuse)을 의미한다. 언어나 플랫폼과 같은 다양한 제약으로 인해 적용 범위가 제한되는 코드 재사용에 비해 설계를 재사용할 수 있는 범위는 무한대에 가깝다. 또한 설계의 재사용은 프로젝트 초반부터 적용 가능하기 때문에 코드 재사용보다 투자 대비 효과가 더 크다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계를 재사용하는데 있어 가장 큰 어려움은 재사용 가능하도록 설계를 표현하고 커뮤니케이션할 수 있는 효과적인 방법을 찾는 것이다. UML을 중심으로 한 그래픽적인 표기법과 패턴을 통해 일정 수준의 의사소통은 가능할지 몰라도 설계에 담긴 모든 휴리스틱과 근거, 대안을 모호하지 않도록 전달하는 것은 쉽지 않은 일이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴의 목적은 설계 의도나 근거를 설명하는 것이지 구현 결과를 제공하는 것이 아니다. 따라서 패턴이라는 형식을 통해 표현된 설계를 재사용하기 위해서는 패턴을 구성하는 각 부분에 대응되는 코드를 재작성하는 반복적인 작업이 요구된다. 또한 패턴은 어떤 애플리케이션에라도 적용 가능해야 하므로 언어나 구현 방법에 독립적이며 추상적이어야 한다. 디자인 패턴을 효과적으로 적용하기 위해서는 트레이드오프를 통해 상황에 적합한 패턴을 선택하고 가공해야 한다. 우리는 항상 TEMPLATE METHOD와 STRATEGY 패턴의 갈림길에서 고민해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 이상적인 형태의 재사용 방법은 설계 재사용과 코드 재사용을 적절한 수준으로 조합하는 것이다. 코드 재사용만을 강조하는 컴포넌트는 실패했다. 추상적인 수준에서의 설계 재사용을 강조하는 패턴은 설계를 재사용하기 위해 매번 유사한 코드를 작성해야만 한다. 패턴과 유사하게 기존 설계를 재사용할 수 있으면서도 패턴을 반복적으로 구현하는 문제를 피하기 위해 이미 존재하는 코드를 재사용할 수 있는 방법은 없을까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크에서 해답을 찾을 수 있다. 프레임워크란 &amp;ldquo;추상 클래스 집합과 추상 클래스 인스턴스 간의 상호작용을 통해 시스템 전체나 일부를 표현하는 재사용 가능한 설계&amp;rdquo;, 또는 &amp;ldquo;애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이즈 할 수 있는 애플리케이션의 골격(skeleton)&amp;rdquo;을 의미한다. 첫 번째 정의가 프레임워크의 구조적인 측면에 초점을 맞추고 있는 반면 두 번째 정의는 프레임워크를 사용하는 목적인 코드와 설계의 재사용에 초점을 맞추고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크는 애플리케이션의 아키텍처를 제공하며 문제 해결에 필요한 설계 결정과 이에 필요한 기반 코드 모두를 포함하고 있다. 프레임워크는 애플리케이션이 확장할 수 있도록 부분적으로 구현된 추상 클래스와 추가적인 작업 없이도 재사용 가능한 다양한 종류의 컴포넌트 라이브러리를 제공한다. 설계 아이디어를 재사용하기 위한 수단으로 프레임워크를 이용할 경우 패턴에서 살펴본 설계의 전달 방안에 대해 고민할 필요가 없다. 프레임워크에서는 코드가 곧 설계 재사용을 위한 표기법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크에서 설계를 재사용 가능하도록 지원해 주는 핵심 구성요소는 추상 클래스(abstract class)다. 추상 클래스의 경우 인스턴스를 생성할 수 없기 때문에 서브 클래스를 생성하기 위한 템플릿의 용도로 사용된다. 프레임워크를 사용해서 애플리케이션을 구축할 경우 추상 클래스를 상속받은 서브 클래스를 추가하여 애플리케이션에 특화된 기능을 구현하게 된다. 따라서 프레임워크에 속한 추상 클래스는 애플리케이션에 속한 서브 클래스의 인터페이스를 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인터페이스의 정의가 프레임워크에 속하기 때문에 자연스럽게 애플리케이션의 제어 흐름 역시 프레임워크에 의해 결정된다. 프레임워크의 핵심은 추상 클래스가 아니라 추상 클래스의 인터페이스에 의해 정의되는 상호작용 방식이다. 서브 클래스는 추상 클래스가 사용되는 문맥 내에서 추상 클래스와 동일한 방식으로 상호작용할 수 있어야 하며 리스코프 치환 원리(LSP, Liskov Substitution Principle)에 따라 대체 가능해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프레임워크는 애플리케이션에 대한 아키텍처를 제공한다. 즉, 프레임워크는 클래스와 객체들의 분할, 전체 구조, 클래스와 객체들 간의 상호작용, 객체와 클래스 조합 방법, 제어 흐름에 대해 미리 정의한다. 프레임워크는 설계의 가변성을 미리 정의해 두었기 때문에 애플리케이션 설계자나 구현자는 애플리케이션에 종속된 부분에 대해서만 설계하면 된다. 프레임워크는 애플리케이션 영역에 걸쳐 공통의 클래스들을 정의하여 일반적인 설계 결정을 미리 내려 둔다. 비록 프레임워크가 즉시 업무에 투입할 수 있는 구체적인 서브 클래스를 포함하고 있기는 하지만 프레임워크는 코드의 재사용보다는 설계 자체의 재사용을 중요시 여긴다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;b&gt;GOF, 디자인 패턴&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전(Dependency Inversion)과 프레임워크&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞에서 살펴 본 바와 같이 프레임워크의 핵심은 추상 클래스라고 할 수 있다. 그렇다면 추상 클래스(또는 인터페이스)의 어떤 특징이 프레임워크의 재사용성을 향상시키는 것일까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체-지향 이전의 구조적 분석/설계와 같은 전통적인 소프트웨어 개발 방법의 경우 상위 레벨 모듈이 하위 레벨 모듈에, 그리고 상위 정책이 구체적인 세부적인 사항에 의존하도록 소프트웨어를 구성한다. 이해를 돕기 위해 핸드폰 과금 시스템에서 가입자가 문자 서비스를 사용할 경우의 요금 계산 로직을 떠올려 보자. 여기에서의 상위 정책은 DOMAIN EVENT의 타입에 따라 DISPATCHER AGREEMENT에서 POSTING RULE을 선택하는 일련의 흐름이다. 실제 요금을 계산하는 DOMAIN EVENT, ACCOUTING ENTRY, POSTING RULE 타입은 구체적인 세부 사항에 속한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 1&amp;gt;은 통화 요금을 계산하는 상위 정책인 PeriodicServiceContract가 세부적인 SmsStandardPostingRule, SmsSendingEvent, MonetaryEntry와 강하게 결합되어 있는 전통적인 의존 관계를 표현한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crmMog/btsHHoFFVKu/vbca9xIAGMjFRCH7tB0mYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crmMog/btsHHoFFVKu/vbca9xIAGMjFRCH7tB0mYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crmMog/btsHHoFFVKu/vbca9xIAGMjFRCH7tB0mYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrmMog%2FbtsHHoFFVKu%2Fvbca9xIAGMjFRCH7tB0mYk%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;723&quot; height=&quot;196&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 1&amp;gt; 정책이 세부적인 사항에 의존하는 전통적인 의존 관계&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 1&amp;gt;의 설계에 나타난 가장 큰 문제점은 다양한 종류의 POSTING RULE과 DOMAIN EVENT, ACCOUNTING ENTRY를 처리해야 하는 PeriodicServiceContract가 문자 메시지에 특화된 구체적인 클래스들에게 의존한다는 점이다. 따라서 통화 요금 계산을 위해 새로운 POSTING RULE, DOMAIN EVENT, ACCOUNTING ENTRY 타입을 사용해야 할 경우 PeriodicServiceContract를 재사용할 수 없다. 즉, 상위 레벨의 정책이 하위 레벨의 구체적인 세부사항에 의존하도록 구조를 설계할 경우 다양한 콘텍스트에서의 재사용이 불가능해진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이와 같은 의존성 문제를 문제를 해결할 수 있는 방법이 없을까? 방법은 의외로 간단하다. 상위 레벨의 정책을 재사용하기 위해서는 상위 레벨의 정책이 하위 레벨에 대해 독립적이어야 한다. 즉, 정책이 상세에 의존하지 않도록 의존성을 역전시켜야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상위 레벨의 정책이 의존하고 있는 세부적인 클래스들로부터 EXTRACT INTERFACE를 한다. 이제 상위 레벨 정책이 하위 레벨의 구체적인 클래스에 직접적으로 의존하지 않게 되며 상세와 정책 모두 추상적인 인터페이스에 의존하게 된다. 이와 같은 방법은 전통적인 분석/설계 방법에서 다루는 의존성의 방향을 역전시킨다. 이처럼 재사용 가능한 객체 지향 설계를 만들기 위해 인터페이스 또는 추상 클래스를 사용해서 의존성의 방향을 바꾸는 것을 의존성 역전 원리(DIP, Dependency-Inversion Principle)라고 한다. &amp;lt;그림 2&amp;gt;는 의존성을 역전시킨 후의 핸드폰 과금 시스템의 구조를 표현한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK83Kr/btsHJy7soFx/G7YTKeX8rXRfT59kg06jFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK83Kr/btsHJy7soFx/G7YTKeX8rXRfT59kg06jFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK83Kr/btsHJy7soFx/G7YTKeX8rXRfT59kg06jFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK83Kr%2FbtsHJy7soFx%2FG7YTKeX8rXRfT59kg06jFk%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;693&quot; height=&quot;306&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 2&amp;gt; 정책과 상세 모두 인터페이스에 의존하도록 의존성을 역전&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 &amp;lt;그림 3&amp;gt;과 같이 상위 정책인 PeriodicServiceContract와 추출된 인터페이스를 별도의 패키지로 묶어 보자. DOMAIN EVENT와 ACCOUNTING ENTRY의 인터페이스는 상위 정책의 필요에 의해 결정되기 때문에 상위 정책에 속한다. 즉, 인터페이스와 구현 클래스를 별도의 패키지로 분리해야 한다. 이처럼 재사용을 위해 인터페이스와 구현 부를 별도의 패키지로 분리하는 것을 SEPARATED INTERFACE 패턴이라고 한다. 상위 정책과 인터페이스를 포함하는 패키지는 전체 애플리케이션에 있어 프레임워크로서의 기능을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvwTiZ/btsHIte04QV/Vg2s6rETbJVYgbeqjcQac1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvwTiZ/btsHIte04QV/Vg2s6rETbJVYgbeqjcQac1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvwTiZ/btsHIte04QV/Vg2s6rETbJVYgbeqjcQac1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvwTiZ%2FbtsHIte04QV%2FVg2s6rETbJVYgbeqjcQac1%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;772&quot; height=&quot;367&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 3&amp;gt; 상위 정책과 인터페이스를 포함하는 프레임워크&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 4&amp;gt;와 같이 상위 정책을 표현하는 패키지에 속한 인터페이스 또는 추상 클래스를 구현하는 새로운 POSTING RULE, DOMAIN EVENT, ACCOUNTING ENTRY를 추가해서 상위 정책의 설계를 재사용할 수 있다. 이것이 애플리케이션이 프레임워크의 설계와 코드를 재사용하는 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNZCLe/btsHHpxBTqv/UkUX6uIpd5r2TGYgs0GuGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNZCLe/btsHHpxBTqv/UkUX6uIpd5r2TGYgs0GuGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNZCLe/btsHHpxBTqv/UkUX6uIpd5r2TGYgs0GuGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNZCLe%2FbtsHHpxBTqv%2FUkUX6uIpd5r2TGYgs0GuGK%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;774&quot; height=&quot;362&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 4&amp;gt; 프레임워크를 재사용하는 애플리케이션&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실, 좋은 객체 지향 설계의 증명이 바로 이와 같은 의존성의 역전이다. 프로그램이 어떤 언어로 작성되었는가는 상관없다. 프로그램의 의존성이 역전되어 있다면, 이것은 OO 설계를 갖는 것이다. 그 의존성이 역전되어 있지 않다면, 절차적 설계를 갖는 것이다.&amp;nbsp;의존성 역전의 원칙은 객체 지향 기술에서 당연하게 요구되는 많은 이점 뒤에 있는 하위 수준에서의 기본 메커니즘이다. 재사용 가능한 프레임워크를 만들기 위해서는 이것의 적절한 응용이 필수적이다. 이 원칙은 또한 변경에 탄력적인 코드를 작성하는 데 있어 결정적으로 중요하다. 추상화와 구체적인 사항이 서로 고립되어 있기 때문에, 이 코드는 유지보수하기가 훨씬 쉽다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Robert C. Martin, 클린 소프트웨어&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글 :&amp;nbsp;&lt;a href=&quot;https://eternity-object.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프레임워크 - 2부 [끝]&lt;/a&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>design pattern</category>
      <category>framework</category>
      <category>디자인 패턴</category>
      <category>프레임워크</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/30</guid>
      <comments>https://eternity-object.tistory.com/30#entry30comment</comments>
      <pubDate>Thu, 30 May 2024 18:53:27 +0900</pubDate>
    </item>
    <item>
      <title>의존성 끊기와 단위 테스트 &amp;ndash; 2부 [끝]</title>
      <link>https://eternity-object.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 : &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://eternity-object.tistory.com/28&quot;&gt;의존성 끊기와 단위 테스트 - 1부&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;의존성 끊기와 설계&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 끊기 게임의 효과는 단위 테스트만으로 국한되지 않는다. 일반적으로 클래스 간의 의존성을 제어하여 테스트 용이성을 향상시킬 경우 결과적으로 시스템의 설계를 개선시킬 수 있는 가능성이 높아 진다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞에서 예로 든 통계 서비스의 경우 일간 통계뿐만 아니라, 주간, 월간 단위의 통계 데이터 역시 사용자에게 제공하고 있다. 의존성을 끊기 전에는 &amp;lt;그림 6&amp;gt;에서 볼 수 있는 것처럼 일간, 주간, 월간 작업을 실행하는 클래스 모두가 JobConf와 JobClient에 의존하고 있었으며 입력 경로나 출력 경로 값 이외에 &amp;lt;리스트 1&amp;gt;에서 보인 대부분의 코드가 중복되어 있었다. 따라서 일간, 주간, 월간 통계를 테스트하기 어려웠을 뿐만 아니라 절차적인 방식의 설계와 코드 중복으로 인해 코드의 확장및 유지보수하기가 어려운 구조를 가지고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ALmh0/btsHGUj9RLQ/xLktBPkl5EKMpgtaS4VKl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ALmh0/btsHGUj9RLQ/xLktBPkl5EKMpgtaS4VKl1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ALmh0/btsHGUj9RLQ/xLktBPkl5EKMpgtaS4VKl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FALmh0%2FbtsHGUj9RLQ%2FxLktBPkl5EKMpgtaS4VKl1%2Fimg.jpg&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;615&quot; height=&quot;241&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 6&amp;gt; 의존성을 끊기 전의 일간, 주간, 월간 통계 구조&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러나 앞에서 살펴본 바와 같이 의존성 제어를 통해 테스트 가능하도록 구조를 개선한 후에는 &amp;lt;그림 7&amp;gt;과 같이 코드 중복이 없으며 코드 수정 없이도 새로운 기간의 통계 처리를 추가할 수 있는 OCP(Open-Closed Principle)를 만족하는 구조를 얻게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn1MOe/btsHHhTqmkw/CivQvkFDPkntTnYkCAaKb0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn1MOe/btsHHhTqmkw/CivQvkFDPkntTnYkCAaKb0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn1MOe/btsHHhTqmkw/CivQvkFDPkntTnYkCAaKb0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn1MOe%2FbtsHHhTqmkw%2FCivQvkFDPkntTnYkCAaKb0%2Fimg.jpg&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;690&quot; height=&quot;279&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 7&amp;gt; 테스트 용이한 구조로 개선한 후의 OCP를 만족하는 구조&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 살펴본 바와 같이 테스트 용이성을 향상시키기 위해 의존성 끊기 게임을 수행할 경우 얻을 수 있는 장점은 크게 두 가지로 나눌 수 있다. 첫 째, 목적 자체가 테스트를 용이하게 하는 것이기 때문에 의존성 끊기 게임의 결과 단위 테스트 작성이 용이한 구조를 얻을 수 있다. 단위 테스트라는 안전망을 통해 지켜지는 코드는 더 빠른 피드백을 제공하고 문제 없이 전진할 수 있다는 용기를 심어 준다. 둘 째, 의존성 끊기 게임의 결과 전체적으로 응집도가 높고 결합도가 낮은 설계를 얻게 된다. 그 결과 가능한 한 객체-지향 원칙을 지키는 품질 높은 코드를 얻을 수 있다.&lt;br /&gt;&lt;br /&gt;단위 테스트 가능한 코드를 목적으로 할 때 설계가 향상될 가능성이 높다는 사실을 알게 되었다. 다음 호에는 SEAM 의 개념을 소개하고 단위 테스트를 통한 의존성 끊기와 설계와의 관계를 좀 더 구체적으로 살펴 볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로그램과 의존성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로그램을 바라보는 프로그래머의 관점은 프로그램을 작성하고 수정하는 행위에 영향을 미친다. 프로그램을 텍스트의 목록으로 바라보는 관점에서 프로그램 작성과 수정은 단순하게 텍스트를 편집하는 작업으로 요약할 수 있다. 새로운 행위가 필요하면 텍스트를 추가하고, 행위를 변경하기 위해서는 텍스트를 수정한다. 프로그램은 텍스트의 목록이기 때문에 전체적인 문맥 흐름에 문제만 없다면 텍스트를 추가하거나 수정하는데 아무런 제약도 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불행하게도 프로그램에 대한 텍스트 메타포는 높은 응집도와 낮은 결합도, 캡슐화와 같은 훌륭한 설계가 갖추어야 하는 기본 덕목을 설명하기에는 적절하지 않다. 애플리케이션 설계를 적절한 추상화의 발견과 복잡한 의존성의 제어라고 요약할 때, 프로그램을 다른 각도에서 바라볼 경우 의존성을 제어하기 위한 체계적인 접근 방법을 적용할 가능성이 높아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인적으로 단위 테스트 관점에서 프로그램을 바라보게 되면서 훌륭한 설계란 어떠해야 하는지에 대해 심사숙고 하게 되었다. 어떤 클래스는 간단하게 단위 테스트 할 수 있기 때문에 작동하는 가장 단순한 방법으로 구현하면 된다. 어떤 클래스는 의존성의 사슬에 묶여 옴짝달싹하지 못하기 때문에 의존성을 끊을 수 있는 설계를 요구한다. 너무 많은 의존성을 가진 클래스는 SRP(Single Responsibility Principle)를 위반한다는 증거다. EXTRACT CLASS를 적용하라. 내부에서 직접 생성하는 클래스로 인해 단위 테스트를 실행할 수 없다면&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DEPENDENCY INJECTION을 통해 외부에서 객체를 조립하도록 변경하거나 DEPENDENCY LOOKUP을 통해 생성되는 인스턴스를 변경할 수 있는 여지를 제공하라. 인프라스트럭처에 대한 의존성을 가진 코드가 여기저기에 흩어져 있어 단위 테스트를 수행하기가 어렵다면 인프라스트럭처에 의존하는 모든 코드를 단일 클래스나 패키지 내부로 캡슐화하고 이를 사용하는 클래스에서는 STRATEGY 패턴을 적용하라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단위 테스트를 작성하다 보면 수많은 설계 이슈들의 아우성 소리가 들려온다. 단위 테스트를 작성하기 시작하면 프로그램은 더 이상 단순한 텍스트의 집합이 아니다. 프로그램은 이야기를 담고 있지만 그 이야기는 평면적이지 않다. 프로그램이 들려주는 이야기는 완결된 스토리를 가지고 있지만 그렇다고 해서 완전히 고정된 것은 아니다. 단위 테스트는 소프트웨어가 좀 더 소프트해지기를 요구한다. 결국 프로그램은 유연해져야 하며 유연한 설계는 훌륭한 프로그램의 필요 조건이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단위 테스트는 훌륭한 설계를 작성할 수 있도록 우리를 인도한다. 이것이 이번 컬럼의 주제다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SEAM과 의존성 제어&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성을 제어한다는 것은 의존하고 있는 코드를 다른 코드로 대체한다는 것을 의미한다. 그러나 의존성을 대체하기 위해 테스트 플래그를 이용하는 TEST HOOK을 적용할 필요는 없다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전통적으로 PROCEDURAL TEST STUB은 필요한 코드가 준비되기 전까지 디버깅을 진행 하기 위해서 사용되었다. PROCEDURAL TEST STUB은 실행 시간에 교체할 수 없으며, 대부분의 절차적 프로그래밍 언어에서 이를 교체하는 것은 매우 어려운 작업이다. 프로덕션 코드에 테스트 코드를 추가하는 것에 대해 거부감을 느끼지만 않는다면 SUT(System Under Test) 네에 if testing then &amp;hellip; else와 같은 TEST HOOK을 사용하는 PROCEDURAL TEST STUB을 구현할 수 있다.&lt;/span&gt; &lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Gerard Meszaros, xUnit Test Patterns&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;TEST HOOK은 코드를 대체하기 어려운 절차형 언어에서 테스트를 용이하게 하기 위해 사용하는 방법이다. 현재 주류를 이루는 객체지향 언어에서는 TEST HOOK을 사용하지 않고도 의존성을 대체하는 것이 가능하다. 이를 위해서는 SEAM의 개념을 이해하는 것이 필요하다.&lt;/span&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Michael Feathers는 그의 저서 &amp;ldquo;Working Effectively with Legacy Code&amp;rdquo;에서 SEAM을 다음과 같이 정의하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SEAM이란 코드를 수정하지 않고도 프로그램의 행위를 변경할 수 있는 지점을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Michael Feathers, Working Effectively with Legacy Code&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단위 테스트 관점에서 SEAM이란 테스트 대상 코드를 수정하지 않고도 테스트 실행 중에 다른 코드로 대체할 수 있는 부분이라고 정의할 수 있다. SEAM은 코드를 대체하는 시점에 따라 컴파일 단계에서 행위를 대체하는 PREPROCESSING SEAM, 링크 단계에서 행위를 대체하는 LINK SEAM, 실행 시에 행위를 대체하는 OBJECT SEAM의 3가지로 분류할 수 있다. 대부분의 객체 지향 언어의 경우에는 OBJECT SEAM을 사용하는 것이 적절하다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OBJECT SEAM은 객체 지향 프로그램 상에서 메소드 호출부가 존재할 때, 실제로 어떤 메소드가 호출되는 지는 실행 시간에 결정된다는 특성을 사용한다. &amp;lt;리스트 3&amp;gt;은 이전 컬럼에서 예제로 들었던 통계 코드에서 발췌한 것이다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717044427528&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String [] inputPathes = jobConfiguration.resolveInputPath();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 3&amp;gt; 통계 코드에서의 메소드 호출 예제&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&amp;lt;리스트 2&amp;gt;에서 resolveInputPath() 메소드는 &amp;lt;그림 8&amp;gt;에 표시된 3개의 클래스 중 어떤 클래스의 메소드를 호출하는 것일까?&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVJaYi/btsHIcYaAz4/ADCQ8QTVUdGLklNyMSorD0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVJaYi/btsHIcYaAz4/ADCQ8QTVUdGLklNyMSorD0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVJaYi/btsHIcYaAz4/ADCQ8QTVUdGLklNyMSorD0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVJaYi%2FbtsHIcYaAz4%2FADCQ8QTVUdGLklNyMSorD0%2Fimg.jpg&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;838&quot; height=&quot;230&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 8&amp;gt; JobConfiguration 인터페이스의 계층도&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;jobConfiguration이 어떤 객체를 가리키는 지 알지 못한 다면 어떤 메소드가 호출되는 지를 알 수는 없다. jobConfiguration은 DailyJobConfiguration의 인스턴스일 수도, WeeklyJobConfiguration의 인스턴스일 수도, MonthlyJobConfiguration의 인스턴스일 수도 있다.&amp;nbsp; 실제로 어떤 객체를 가리키는 지는 코드의 전후 관계 또는 실행 시의 설정을 살펴보아야만 한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 지향 프로그램의 실행 구조는 소스 코드 구조와 일치하지 않는 경우가 종종 있다. 코드 구조는 컴파일 시점에 확정되는 것이고 이 구조에는 고정된 상속 클래스 관계들을 포함한다. 그러나 프로그램의 런타임 시 구조는 교류하는 객체들에 따라서 달라질 수 있다. 즉, 이 두 구조는 전혀 다른 별개의 독립성을 갖는다. 하나로부터 다른 하나를 이해하려는 것은 생태계의 동적인 성질을 식물과 동물과 같은 정적 분류 구조를 바탕으로 이해하려는 것과 똑같다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GOF, Design Patterns&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 지향의 이러한 특성을 사용해서 jobConfiguration이 가리키는 객체를 변경할 수 있다면 해당 호출 부분은 OBJECT SEAM이 될 수 있다. 그러나, 모든 메소드 호출 부분이 OBJECT SEAM인 것은 아니다. &amp;lt;리스트 4&amp;gt;의 경우 jobConfiguration이 가리키는 객체를 변경하는 것이 불가능하기 때문에 SEAM이라고 할 수 없다. 소스 코드 구조와 실행 구조가 동일할 경우 SEAM의 개념을 이용하는 것이 불가능하다.&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717040971498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void resolvePathes() {
  ...
  JobConfiguration jobConfiguration = new DailyJobConfiguration();
  ...
  String [] inputPathes = jobConfiguration.resolveInputPath();
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 4&amp;gt; SEAM이 존재하지 않는 경우&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;코드를 수정하지 않고도 해당 코드의 행위를 변경하기 위해서는 행위를 선택할 수 있는 수단을 제공해야 한다. 이를 ENABLING POINT라고 한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 SEAM에는 특정 행위를 선택할 수 있는 코드 상의 지점인 ENABLING POINT가 존재한다.&lt;/span&gt; &lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Michael Feathers, Working Effectively with Legacy Code&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;lt;리스트 4&amp;gt;에는 행위를 선택할 수 있는 ENABLING POINT가 존재하지 않는다. 따라서 만약 DailyJobConfiguration이 인프라스트럭처에 대해 의존하고 있을 경우 해당 클래스에 대한 단위 테스트를 실행하기가 쉽지 않다. 의존성을 제어하기 위해서는 &amp;lt;리스트 5&amp;gt;와 같이 ENABLING POINT를 제공해야 한다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717041029686&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setJobConfiguration(JobConfiguration jobConfiguration) {
  this.jobConfiguration = jobConfiguration;
}

public void resolvePathes() {
  ...
  String [] inputPathes = jobConfiguration.resolveInputPath();
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;br /&gt;&amp;lt;리스트 5&amp;gt; ENABLING POINT가 존재하도록 리팩토링&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&amp;lt;리스트 5&amp;gt;를 테스트하기 위해서는 JobConfiguration 인터페이스를 상속받는 새로운 TEST STUB을 만들고 이를 setJobConfiguration에 전달하면 된다. ENABLING POINT를 통해 코드 상에 OBJECT SEAM을 추가함으로써 단위 테스트 가능한 코드를 얻게 되었다. 그리고 OBJECT SEAM이 존재하는 코드는 일반적으로 유연하고 응집도가 높은 설계를 가지게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;b&gt;SEAM과 유연한 설계, 그리고 단위 테스트&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;앞의 예제를 통해 OBJECT SEAM을 사용할 경우 코드가 유연해 진다는 사실을 알게 되었다. OBJECT SEAM은 런 타임 시에 객체를 변경할 수 있도록 하기 때문에 객체 간에 낮은 결합도를 유지할 수 있도록 해 준다. 단위 테스트는 실제로 OBJECT SEAM이 필요한 위치에 대한 정보를 제공한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;단위 테스트와 동시에 코드를 작성할 경우 코드가 유연해지고 설계가 향상된다. 따라서 테스트 주도 개발(Test-Driven Development, TDD) 방식으로 개발된 코드가 그렇지 않은 코드보다 더 훌륭한 방식으로 설계될 확률이 높다. 의존성 끊기 게임의 목적은 단위 테스트를 가능하게 함으로써 설계를 향상시키는 것이다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TDD는 테스트에 관한 것이 아니다. 프로그래밍과 설계에관한 것이다. TDD는 더 단순하고, 더 깔끔하고, 더 견고한 코드를 작성하는 일과 관련된 것이다!(물론, 부수 효과로 작성된 단위 테스트 케이스는 매우 중요하다.)&lt;/span&gt; &lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;br /&gt;Jimmy Nilsson, Applying Domain-Driven Design and Patterns&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: right;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드를 단순히 평면적인 텍스트의 집합으로 보지 않고 그 안에서 적절한 SEAM을 식별하는 방법을 익히는 것이 코드의 테스트 용이성을 향상시키는 최선의 방법이다. 그리고 테스트 용이성을 목표로 할 때 훌륭한 설계를 얻을 수 있다. 코드를 작성하기 전에 테스트를 작성하는 TDD 방식은 다양한 방식으로 OBJECT SEAM을 강요하기 때문에 자연스럽게 훌륭한 설계에 도달할 수 있도록 한다.&lt;/span&gt;&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;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Rod Johnson, Expert One-On-One J2EE without EJB&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;앞서 설명한 것처럼 테스트 가능한 애플리케이션을 작성하는 방법은 테스트와 코드를 함께 작성하는 것이다. 그러나 테스트와 함께 코드를 작성하더라도 훌륭한 설계에 대한 감각을 기르지 않는다면 최적의 설계에 이를 수 없다. 테스트 용이성을 향상시킬 수 있는 다음과 같은 지침이 설계를 향상시키기 위해서도 도움이 될 것이라 생각한다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;구현이 아닌 인터페이스에 따라 프로그래밍하라&lt;br /&gt;&lt;/b&gt;인터페이스에 대해 프로그래밍을 하면 할수록, 실행 시간이나 테스트 시점에 서로 다른 구현체를 좀 더 자유롭게 플러그 인할 수 있다. 이것은 구현 세부 사항을 변경할 수 있는 OBJECT SEAM을 손쉽게 추가할 수 있는 아키텍처적인 유연성을 제공한다. 인터페이스에 따라 프로그래밍함으로써 테스트에 적합한 TEST STUB이나 MOCK OBJECT를 구현할 수 있으며 인프라스트럭처에 대한 의존성을 보다 쉽게 제어할 수 있다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;상속보다는 합성을 사용하라&lt;br /&gt;&lt;/b&gt;객체 지향 프로그래밍 환경에서 기능을 재사용하는 방법은 상속(Inheritance)과 합성(Composition)의 두 가지 방식이 존재한다. 상속은 클래스와 클래스 간의 정적인 관계를 통해 기능을 재사용하며 두 클래스 간의 관계가 컴파일 시점에 고정된다. 합성은 객체와 객체 간의 동적인 관계를 통해 기능을 재사용하며 두 객체 간의 관계가 고정적이지 않고 실행 시간에 변경 가능하다.&lt;br /&gt;일반적으로 상속은 캡슐화를 저해한다. 부모 클래스를 수정할 경우 모든 서브 클래스가 영향을 받게 된다. 따라서 변경에 대한 파급효과를 줄이기 위해서는 상속보다는 합성을 사용하는 것이 유리하다.&lt;br /&gt;단위 테스트 관점에서 상속보다 합성이 선호되는 이유는 무엇인가? 상속 계층을 다룰 경우 테스트 하니스 상에서 객체를 생성하기가 어려워질 수 있기 때문이다. 부모 클래스가 생성자의 파라미터로 거대한 객체 그래프를 취하는 경우를 생각해 보자. 서브 클래스의 일부 기능을 테스트하기 위해 전체 객체 그래프가 필요하지 않음에도 불구하고 테스트를 위해서는 해당 객체 그래프를 부모 클래스의 생성자로 전달해야 한다. 또한 부모 클래스가 인프라스트럭처와 강하게 결합되어 있을 경우에도 하위 클래스를 단위 테스트하는 것이 어렵다.&lt;br /&gt;상속의 경우 컴파일 타임에 관계가 고정된다는 사실을 기억하자. 따라서 상속을 사용하기 위해서는 실행 시점이 아닌 컴파일 시점에 ENABLING POINT를 제공해야 한다는 제약이 따른다. 따라서 OBJECT SEAM을 추가하고자 한다면 가능하면 상속 보다는 합성을 선택하고 객체와 객체를 조합하는 위치에 ENABLING POINT를 제공하도록 하자. 이를 위한 가장 훌륭한 방법은 STRATEGY 패턴을 이용하는 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;static 메소드와 SINGLETON의 사용을 피하라&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;static 메소드와 SINGLETON 패턴은 클라이언트 코드를 구체적인 클래스와 강하게 결합시킨다. static 메소드와 SINGLETON이 단위 테스트 하니스 상에서 인스턴스를 생성하거나 실행시키기 어려운 대상에 의존할 경우 인스턴스를 변경할 수 있는 static setter 를 이용하거나 최악의 경우 바이트 코드를 갱신시키는 기법을 사용하지 않는 한 이를 대체하기가 거의 불가능하다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 static 메소드와 SINGLETON을 사용하는 것이 무조건 잘못 된 것은 아니다. 클래스 계층 구조에 섞여 있는 유틸리티성 메소드를 외부로 빼내거나 Thread Local을 사용하기 위해서는 static 메소드가 유용하다. 하나의 인스턴스가 필요한 경우에는 SINGLETON을 사용하는 것이 유용하다. 그러나 전역 접근이나 DEPENDENCY LOOKUP을 위해 SINGLETON을 사용하는 것은 시스템 전체적인 결합도를 높이는 것이다. 가능하면 구체적인 클래스에 강하게 얽매이는 코드를 작성하지 마라. 잘못 사용된 static 메소드와 SINGLETON은 SEAM에 대한 안티 패턴이다.&lt;br /&gt;결론적으로 static 메소드와 SINGLETON의 경우 SEAM을 식별하는 것이 어렵기 때문에 이를 인스턴스 메소드와 인터페이스 기반의 클래스로 변경하는 것이 좋다.&amp;nbsp; &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;의존성을 고립시켜라&lt;/b&gt;&lt;br /&gt;이전 컬럼에서는 예제로 사용한 통계 애플리케이션을 테스트 가능하도록 만들기 위해 Apache Hadoop에 관련된 코드를 다른 코드로부터 분리시켰다. 이렇게 함으로써 Apache Hadoop에 의존하지 않는 코드에 대해 단위 테스트를 실행할 수 있었다.&lt;br /&gt;일반적으로 테스트하기 어려운 프레임워크, 표준 라이브러리, 인프라스트럭처에 관련된 코드를 개별적인 클래스로 고립시키는 것이 좋다. 이것은 높은 응집성과 낮은 결합도를 달성할 수 있는 가장 간단한 방법인 동시에 기본적인 캡슐화 원칙을 지키는 설계 방법이다. 별도로 고립시킨 클래스에 대한 인터페이스를 제공함으로써 이를 사용하는 클라이언트에 대해 SEAM을 제공할 수 있다. SEAM을 지원하기 위해 ENABLING POINT를 작성하는 가장 간단한 방법은 DEPENDENCY INJECTION을 사용하는 것이다. &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;의존성을 요청하지 말고 주입하라&lt;br /&gt;&lt;/b&gt;static 메소드와 SINGLETON을 통해 알아본 것처럼 의존성이 하드코딩되어 있으면 SEAM을 제공하기 어렵다. 구체적인 클래스가 아닌 인터페이스에 대해 프로그래밍하고 구체적인 클래스는 이를 사용하는 클래스가 아닌 외부에서 설정하도록 하라. DEPENDENCY LOOKUP의 경우에는 사용하려는 객체에 대한 의존성은 제거하지만 객체 탐색 메커니즘에 대한 의존성은 여전히 존재한다. DEPENDENCY INJECTION이 OBJECT SEAM에 대한 ENABLING POINT를 제공하기 위한 가장 간단하면서도 최적의 방법이다.&lt;br /&gt;현재 Spring을 위시로 한 많은 프레임워크들이 DEPENDENCY INJECTION을 제공하고 있으므로 이를 적극 활용하도록 한다. DEPENDENCY INJECTION을 사용할 경우 자연스럽게 STRATEGY 패턴, 인터페이스에 대한 프로그래밍, 상속보다는 합성의 원칙을 준수하게 된다.&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 9&amp;gt;는 이전 연재에서 의존성 끊기 게임의 결과로 얻게 된 코드의 구조를 나타낸 것이다. 구체적인 클래스 대신 JobConfiguration 인터페이스를 사용하고, LogAnalyzeJob과 JobConfiguration을 합성 관계로 구성하며, Apache Hadoop에 대한 의존성은 모두 LogAnalyzeJob 내부로 고립시키고, LogAnalyzeJob에서 JobConfiguration으로의 의존성은 DEPENDENCY INJECTION에 의해 외부에서 주입된다. 리팩토링된 코드는 단위 테스트 가능하며, 확장 가능한 동시에 유연한 설계를 가지게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n16m4/btsHGRnypOl/9nYwJkzTsaqQ6B4wQvSkDk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n16m4/btsHGRnypOl/9nYwJkzTsaqQ6B4wQvSkDk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n16m4/btsHGRnypOl/9nYwJkzTsaqQ6B4wQvSkDk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn16m4%2FbtsHGRnypOl%2F9nYwJkzTsaqQ6B4wQvSkDk%2Fimg.jpg&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;690&quot; height=&quot;279&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;div style=&quot;color: #000000; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;그림 9&amp;gt; 테스트 가능한 코드를 목표로 할 경우 설계가 개선된다.&lt;/span&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;의존성 끊기와 단위 테스트&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시스템은 상호 관계를 맺고 있는 수 많은 객체로 구성되어 있다. 단위 테스트의 목적은 이렇게 얽히고 설킨 객체를 고립시켜 예상한 대로 동작하는 지를 테스트하는 것이다. 단위 테스트를 실행하기 위해서는 적절한 위치에서 의존성을 끊어야 한다.&lt;br /&gt;&lt;br /&gt;평면적인 텍스트의 목록이 아닌 SEAM의 관점에서 프로그램 코드를 살펴 보는 것은 효과적으로 단위 테스트를 수행할 수 있는 코드가 무엇인지에 관한 통찰을 제공한다. SEAM을 고려해서 작성된 코드는 높은 응집도와 낮은 결합도를 가지고 있으며 적절한 책임을 지닌 작은 객체들로 구성된 캡슐화가 잘 된 코드다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;SEAM을 고려한 코드는 단위 테스트가 가능한 코드이며, 단위 테스트가 가능한 코드는 훌륭하게 설계된 코드다. 훌륭한 코드를 작성하는 개발자로 성장하고 싶은가? 그렇다면 테스트 가능한 코드를 작성하기 위해 노력하라. 테스트 가능한 코드가 곧 훌륭한 코드일 가능성이 높다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>depndency</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>의존성</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/29</guid>
      <comments>https://eternity-object.tistory.com/29#entry29comment</comments>
      <pubDate>Thu, 30 May 2024 14:09:07 +0900</pubDate>
    </item>
    <item>
      <title>의존성 끊기와 단위 테스트 - 1부</title>
      <link>https://eternity-object.tistory.com/28</link>
      <description>&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;b&gt;단위 테스트 표류기&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;최근 몇 년 동안 소프트웨어 개발 방식은 혁신적인 전환점을 맞이하게 되었다. 과거의 무겁고 형식적인 프로세스 중심의 개발 방식을 벗어나, 점차 소프트웨어와 사람에 초점을 맞추는 기민하고 적응적인 개발 방식을 채택하는 조직이 늘어나고 있다. 이와 함께 소프트웨어를 개발하는 방식 역시 커다란 변화를 맞이하게 되었는데 그 중 가장 주목할만한 점은 단위 테스트(Unit Test)가 소프트웨어 개발 프로세스의 핵심 요소로 자리를 잡았다는 점이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;단위 테스트의 핵심 아이디어는 소프트웨어를 구성하는 개별적인 클래스를 고립시킨 상태에서 테스트하는 것이다. 문제는 테스트를 수행하기 위해 클래스를 고립시킨다는 것이 말처럼 간단하지 않다는 점이다. 객체-지향 시스템 안에서 숨쉬고 있는 객체들은 다른 객체들과의 긴밀한 협력 관계를 통해 자신의 역할을 수행한다. Order 객체를 테스트하기 위해서는 Order 객체와 연관 관계를 맺고 있는 Customer 객체, OrderLineItem 객체, Product객체가 필요하다. 끝없는 연관 관계의 미로 속을 헤매다 정신을 차려 보면 작은 단위 테스트 안에 커다란 객체 그래프를 몰골 사납게 꾸겨 넣은 채 끙끙거리고 있는 자신을 발견하게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;/b&gt;&lt;/b&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;f0081118_4c948feb7369f.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaRLo/btsHF1X8qv4/7xfx5UojJPx02tiDhLuD9K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaRLo/btsHF1X8qv4/7xfx5UojJPx02tiDhLuD9K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaRLo/btsHF1X8qv4/7xfx5UojJPx02tiDhLuD9K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxaRLo%2FbtsHF1X8qv4%2F7xfx5UojJPx02tiDhLuD9K%2Fimg.jpg&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;500&quot; height=&quot;350&quot; data-filename=&quot;f0081118_4c948feb7369f.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;b&gt;&lt;b&gt;그림 1 객체 간의 의존성은 얽히고 설킨 사슬과 같다&lt;/b&gt;&lt;/b&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;그렇다면 단위 테스트에서 클래스를 고립시키는 것이 중요한 이유가 무엇일까? 일반적으로 커다란 객체 그래프를 대상으로 수행되는 테스트는 다음과 같은 문제점을 지니고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;b&gt;에러 위치 확인(Error Localization)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 테스트가 실패할 경우 거대한 객체 그래프의 어디에서 에러가 발생했는지 확인하기 어렵다. 여러 클래스를 가로지르는 실행 경로를 살펴 보면서 입력 값과 출력 값을 추적하는 기나긴 여정 속에서 테스트에 대한 애정이 조용히 사라지는 것을 느끼게 된다. 클래스를 고립시키면 상대적으로 제한된 부분만 확인하면 되므로 에러의 원인을 파악하기가 쉽다.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;b&gt;실행 시간(Execution Time)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 당연한 이야기지만 거대한 객체 덩어리를 테스트하는 것은 소수의 객체를 테스트하는 것보다 오랜 시간이 소요된다. 만약 내부의 특정 객체가 데이터베이스와 같은 외부 리소스에 의존하고 있다면 수행 시간은 기하급수적으로 늘어난다. 테스트 실행 시간이 길어지면 길어질수록 개발자들이 테스트를 수행하는 횟수가 줄어들며 결과적으로 건강한 피드백 루프의 장점이 손상된다.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;b&gt;테스트 커버리지(Coverage)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 거대한 객체 그래프의 특정 코드를 수행하기 위해 필요한 입력 값을 찾는 것보다는 특정 클래스에 포함된 단일 메소드를 실행하기 위해 필요한 입력 값을 찾는 것이 쉽다. 따라서 테스트 대상 클래스를 고립시킬수록 높은 테스트 커버리지를 얻을 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;단위 테스트의 효과를 극대화시키기 위해서는 클래스를 고립시켜야 한다. 따라서 클래스와 클래스 간의 의존성을 끊기 위한 기법이 필요하다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;b&gt;의존성 끊기(Breaking Dependency) 게임&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;의존성(dependency)이란 &amp;ldquo;두 요소 간의 관련성으로 한 요소에 대한 변경이 다른 요소가 필요로 하는 정보를 제공하거나 다른 요소가 제공하는 정보에 영향을 주는 관계&amp;rdquo;를 의미한다. 즉, 어떤 요소의 변경이 다른 요소에 영향을 미친다면 두 요소 간에 의존성이 존재한다고 말한다. 클래스가 다른 클래스의 인스턴스를 속성으로 포함하는 경우, 메소드의 파라미터로 사용하는 경우, 메소드 내부에서 지역적으로 인스턴스를 생성하는 경우, 다른 클래스를 상속받는 경우 모두 두 클래스 간에 의존 관계의 형성된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;앞에서 살펴본 바와 같이 단위 테스트의 핵심은 개별 클래스를 고립시키는 것이다. 단위 테스트를 수행하기 위해 Order 클래스의 인스턴스를 생성해야 하는데, Order 클래스가 Customer, OrderLineItem, Product에 의존하고 있다면 의존하고 있는 모든 객체들을 포함하는 거대한 객체 그래프를 생성해야 할까?&lt;br /&gt;&lt;/span&gt;&lt;span&gt;&lt;br /&gt;결국 단위 테스트란 의존성 끊기 게임이다. 단위 테스트의 가장 큰 적은 클래스 간의 의존성이다. 개별 클래스를 단위 테스트하기 위해서는 객체 그래프 상의 적절한 위치에서 의존성을 제어할 필요가 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;f0081118_4c948fa1e7267.jpg&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bciMhj/btsHGcef0aZ/o8gWhk21iK9A8tBUk7gNjK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bciMhj/btsHGcef0aZ/o8gWhk21iK9A8tBUk7gNjK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bciMhj/btsHGcef0aZ/o8gWhk21iK9A8tBUk7gNjK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbciMhj%2FbtsHGcef0aZ%2Fo8gWhk21iK9A8tBUk7gNjK%2Fimg.jpg&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;1007&quot; height=&quot;664&quot; data-filename=&quot;f0081118_4c948fa1e7267.jpg&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 2 단위 테스트를 위해 최소한의 의존성만을 남겨둔 채 객체를 고립시켜라&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작년 초에 함께 일하던 분이 다른 팀으로 전배를 가게 되어 그 분이 담당하고 있던 통계 서비스를 맡게 되었다. 하루 동안 수집된 로그 데이터를 매일 밤 배치로 처리하여 분석한 다양한 통계 결과를 데이터베이스에 저장하는 기능이었다. 대용량 데이터를 다루는 작업이기 때문에 통계 로그 데이터를 분산 파일 시스템인 HDFS(Hadoop Distributed File System)에 저장하며 대용량 분산 처리를 효율적으로 처리하기 위해 프레임워크로 Map-Reduce 기술을 기반으로 하는 Apache Hadoop을 사용하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드를 처음 보았을 때 받은 첫 느낌은 코드 전체적으로 Apache Hadoop이라는 인프라스트럭처에 너무 단단하게 결합되어 있었다는 점이다. 따라서 Apache Hadoop이 실행되지 않는 분산 클러스터 외부에서는 특정 클래스의 인스턴스를 생성하거나 메소드를 실행하기가 불가능했다. 결과적으로 단위 테스트 작성이 거의 불가능한 수준이었으며 테스트 케이스가 작성되었다고 하더라도 인프라스트럭처에 의존성을 가지지 않는 유틸리티 성 클래스로 그 영역이 제한될 수 밖에 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;통계 코드를 테스트하는 유일한 방법은 Apache Hadoop이 구동된 의사(pseudo) 분산 환경에 테스트용 로그 데이터를 저장한 후 통합 테스트를 수행하여 데이터베이스의 변경 사항을 체크하는 것뿐이었다. 이와 같은 환경에서는 앞서 살펴본 바와 같이 복잡한 실행 경로의 어떤 위치에서 오류가 발생했는지를 추적하기가 어렵고, 로그 파일을 읽고 데이터베이스에 저장해야 하므로 실행 시간이 오래 걸리며, 다양한 실행 경로를 테스트하기 위한 테스트 데이터를 준비하기가 쉽지 않기 때문에 높은 테스트 커버리지를 기대하기도 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결국 코드 전반적으로 테스트 커버리지를 높이고 안정적인 코드 품질을 유지하기 위해서는 의존성 끊기 게임을 시작할 수 밖에 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 1&amp;gt;은 일간 통계 작업을 수행하는 실제 프로젝트 코드를 발췌한 것이다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716997360546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
...
public class LogDailyAnalyzeJob {

  public void analyze(String[] parameters) throws Exception {
    JobConf conf = new JobConf(LogDailyAnalyzeJob.class);
    ...
    conf.setJobName(&quot;LogDailyAnalyzer : &quot; + parameters[0] + &quot; for &quot;
      + parameters[1]);
    conf.setMapperClass(BasicMapper.class); 
    conf.setCombinerClass(BasicCombiner.class);
    conf.setReducerClass(analyzer.getReducerClass());
    ...
    String year = parameters[1].substring(0,4);
    String month = parameters[1].substring(4,6);
    String day = parameters[1].substring(6);
    String path = &quot;/&quot;+year+&quot;/&quot;+month+&quot;/&quot;+day;

    conf.setInputPath(new Path(&quot;/user/statlogs&quot; + path));
    conf.setOutputPath(new Path(&quot;/user/result&quot; + path + &quot;/&quot; + parameters[0]
      + &quot;/daily&quot;));
    ...
    JobClient.runJob(conf);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 1&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Apache Hadoop에 대해 강한 의존 관계를 가진 클래스&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;Apache Hadoop에 대한 사전 지식이 없다고 하더라도 전반적인 주제를 이해하는데 큰 무리는 없을 것이라고 생각된다. 여기에서 중요한 것은 JobConf, JobClient와 같이 테스트 하니스에서 생성하거나 실행하기 어려운 객체에 의존성을 가지고 있는 경우 의존성을 끊어야 한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lt;리스트 1&amp;gt;의 코드에서 단위 테스트와 관련된 가장 큰 문제점은 위 코드가 Apache Hadoop에서 제공하는 JobConf와 JobClient에 직접적으로 의존한다는 점이다. JobConf와 JobClient는 Apache Hadoop 클러스터 환경이 정상적으로 실행 중이어야만 인스턴스 생성 및 실행이 가능하다. 따라서 단위 테스트를 작성하기 위해서는 먼저 테스트 대상 기능으로부터 JobConf와 JobClient에 대한 의존성을 끊어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;f0081118_4c9c97424eaa4.jpg&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpsqqH/btsHGW2PONZ/lIu9AAfTmIVDZ9OG7WwTt0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpsqqH/btsHGW2PONZ/lIu9AAfTmIVDZ9OG7WwTt0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpsqqH/btsHGW2PONZ/lIu9AAfTmIVDZ9OG7WwTt0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpsqqH%2FbtsHGW2PONZ%2FlIu9AAfTmIVDZ9OG7WwTt0%2Fimg.jpg&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;403&quot; height=&quot;242&quot; data-filename=&quot;f0081118_4c9c97424eaa4.jpg&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 3 단위 테스트 작성을 어렵게 만드는 인프라에 대한 의존성&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성을 끊기 위해서는 현재 무엇을 테스트하고자 하는 지를 생각해야 한다. &amp;lt;리스트 1&amp;gt;에서 수행하고 있는 주된 작업은 Map-Reduce 작업을 수행할 분산 파일 시스템 상의 입력 파일과 출력 파일의 경로를 구하는 것이다. 따라서 입력 파일과 출력 파일의 경로를 구하는 로직을 인프라스트럭처에 대해 독립적으로 구성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 LogDailyAnalyzeJob의 인프라스트럭처에 대한 의존성을 제거하자. Apache Haddop에 대한 의존성을 끊는 가장 간단한 방법은 JobConf와 JobClient에 대한 의존성을 특정한 클래스 내부로 고립시키는 것이다. JobConf와 JobClient를 다루는 모든 코드를 별도의 인터페이스와 클래스로 추출하여 LogDailyAnalyzeJob이 오직 인터페이스에 대해서만 의존하도록 코드를 리팩토링한다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;f0081118_4c9c979ab9fc4.jpg&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CbVvp/btsHHafv2Er/RTFKlbijCV7FMD8AdezzN0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CbVvp/btsHHafv2Er/RTFKlbijCV7FMD8AdezzN0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CbVvp/btsHHafv2Er/RTFKlbijCV7FMD8AdezzN0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCbVvp%2FbtsHHafv2Er%2FRTFKlbijCV7FMD8AdezzN0%2Fimg.jpg&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;611&quot; height=&quot;343&quot; data-filename=&quot;f0081118_4c9c979ab9fc4.jpg&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 4 인프라에 대한 의존성을 클래스 내부로 고립시켜 의존성 끊기&lt;/span&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;JobRunnable에서 JobConf에 설정할 값들을 제공하기 위해 JobConfiguration 인터페이스를 추가한다. JobConfiguration 인터페이스는 &amp;lt;리스트 1&amp;gt;에서 JobConf에 설정된 다양한 값들을 계산하여 반환하는 역할을 수행한다. 일간 작업과 관련된 모든 로직이 DailyJobConfiguration으로 이동되었으며 LogDailyAnalyzeJob의 이름은 좀 더 일반적인 LogAnalyzeJob으로 변경한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;5.jpeg&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mGuZd/btsHHrnZ0Ae/FKjMrdZMsuUS9upX54zsV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mGuZd/btsHHrnZ0Ae/FKjMrdZMsuUS9upX54zsV1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mGuZd/btsHHrnZ0Ae/FKjMrdZMsuUS9upX54zsV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmGuZd%2FbtsHHrnZ0Ae%2FFKjMrdZMsuUS9upX54zsV1%2Fimg.jpg&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;612&quot; height=&quot;434&quot; data-filename=&quot;5.jpeg&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 5 테스트 가능하도록 의존성이 끊긴 클래스 구조도&lt;/span&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 분산 파일 시스템 상의 입출력 경로를 확인하는 단위 테스트를 작성하는 것이 매우 간단해졌다. DailyJobConfiguration은 JobConf, JobClient에 대한 의존성을 가지고 있지 않기 때문에 간단하게 테스트 하니스 상에서 픽스처를 생성한 후 테스트를 실행하기만 하면 된다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717044314872&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
public void checkInputPath() throws Exception {
  DailyJobConfiguration jobConfiguration = new DailyJobConfiguration(
   new String [] {&quot;CafeAllBase&quot;, &quot;20091001&quot;});
 String expectedInputPath = LogAnalyzeJob.LOG_PATH_PREFIX + &quot;/2009/10/01&quot;;

assertEquals(expectedInputPath, jobConfiguration.resolveInputPath()[0]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&amp;lt;리스트 2&amp;gt; DailyJobConfiguration에 대한 단위 테스트&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;다음 글 :&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://eternity-object.tistory.com/29&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;의존성 끊기와 단위 테스트 &amp;ndash; 2부 [끝]&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>Dependency</category>
      <category>Unit Test</category>
      <category>단위 테스트</category>
      <category>의존성</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/28</guid>
      <comments>https://eternity-object.tistory.com/28#entry28comment</comments>
      <pubDate>Thu, 30 May 2024 00:45:06 +0900</pubDate>
    </item>
    <item>
      <title>명령-쿼리 분리(Command-Query Separation, CQS) 원리</title>
      <link>https://eternity-object.tistory.com/27</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컴퓨터 프로그래밍이라는 것을 처음 배우기 시작하던 시절에&amp;nbsp;x = x + 1이라는 문장을 보고 의아하게 생각했던 기억이 있다.&amp;nbsp;어떻게&amp;nbsp;x에&amp;nbsp;1을 더한 값이&amp;nbsp;x와 같을 수 있지?&amp;nbsp;더 당황스러웠던 것은 프로그램 내의 함수&amp;nbsp;f에 대해&amp;nbsp;f(x) = y이고&amp;nbsp;f(x) = z일 경우&amp;nbsp;y와&amp;nbsp;z가 다른 값일 수 있다는 사실이었다.&amp;nbsp;별다른 배경지식이 없었던 당시에는 그저 프로그래밍과 수학에서 말하는 함수가 이름은 같지만 정의에 있어서는 미묘한 차이가 있는 것이라고 막연하게 추측했던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 몇 년 후&amp;nbsp;x = x + 1이라는 문장이 에러라고 생떼를 쓰는 프로그래밍 언어를 보고 다시한 번 당혹감에 빠져 들었다.&amp;nbsp;왜&amp;nbsp;x에&amp;nbsp;1을 더한 값을&amp;nbsp;x에 대입하는데 에러가 나는거지?&amp;nbsp;더 짜증이 났던 것은&amp;nbsp;f(x) = y이고&amp;nbsp;f(x) = z일 때 무조건&amp;nbsp;y와&amp;nbsp;z가 같도록 프로그래밍을 짜야 한다는 사실이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기에서 예로 든 두가지 방식의 가장 큰 차이점은 &lt;b&gt;부수 효과(side effect)&lt;/b&gt;의 존재 유무라고 할 수 있다.&amp;nbsp;부수 효과를 기반으로 하는 언어에서&amp;nbsp;f(x) = y이고&amp;nbsp;f(x) = z라고 해도&amp;nbsp;y와&amp;nbsp;z가 같지 않을 수 있다.&amp;nbsp;함수&amp;nbsp;f에서 발생하는 부수 효과에 의해 결과값이 변경될 수 있기 때문이다.&amp;nbsp;반면에 부수 효과를 금지하는 언어에서는&amp;nbsp;f(x) = y이고&amp;nbsp;f(x) = z일 때&amp;nbsp;항상&amp;nbsp;y와&amp;nbsp;z가 동일하다.&amp;nbsp;전자를 &lt;b&gt;명령형 언어(imperative language)&lt;/b&gt;라고 부르고 후자를 &lt;b&gt;함수형 언어(functional language)&lt;/b&gt;라고 부른다.&amp;nbsp;현재 주류 언어의 위치를 차지하고 있는&amp;nbsp;Algol&amp;nbsp;계열의 언어들은 명령형 언어에 속하며 지금까지 우리가 책이나 학교에서 배웠던 프로그래밍 방식은 거의 대부분 부수 효과를 이용하는 명형형 프로그래밍 방식이라고 봐도 무관하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음&amp;nbsp;Ruby&amp;nbsp;프로그램을 살펴보자.&amp;nbsp;메소드&amp;nbsp;n은 전역변수&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;value&lt;/span&gt;를 변경시키는 부수 효과를 포함하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716968362622&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$value = 0

def n

  $value = $value + 1

end&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수가 부수 효과를 가질 경우 &lt;b&gt;참조 투명성(referential transparency)&lt;/b&gt;을 만족할 수 없다.&amp;nbsp;참조 투명성이란 어떤 표현식&amp;nbsp;e가 있을 때, e의 값을 바꾸지 않고 다른 표현식으로 대체할 수 있음을 의미한다.&amp;nbsp;참조 투명성을 만족시키는 함수&amp;nbsp;f에 대해&amp;nbsp;f(x) = y이고&amp;nbsp;f(x) = z인 경우&amp;nbsp;y와&amp;nbsp;z는 항상 동일한 값을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수학에서 표현식&amp;nbsp;n + n&amp;nbsp;과&amp;nbsp;2*n은 동일하다.&amp;nbsp;즉, n + n = 2*n이다.&amp;nbsp;그러나 부수 효과를 가지는 경우&amp;nbsp;n + n과&amp;nbsp;2*n은 서로 다른 값을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716968394979&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ReferentialTransparencyTest&amp;lt;Test::Unit::TestCase

  def test_n

    assert_equal(2*n,n+n)

  end 

end&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2*n의 결과가&amp;nbsp;2인 반면&amp;nbsp;n + n의 값은&amp;nbsp;5이다.&amp;nbsp;참조 투명성이 유지되지 않는 경우 함수의 결과는 함수의 호출 순서에 의존한다.&amp;nbsp;다음 테스트 케이스에서 n + n과 2*n의 순서를 바꾸어 호출 할 경우&amp;nbsp;n + n이&amp;nbsp;3을, 2*n이&amp;nbsp;6을 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716968409751&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ReferentialTransparencyTest&amp;lt;Test::Unit::TestCase

  def test_n

    assert_equal(n+n,2*n)

  end 

end&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부수 효과는 프로그램의 버그를 발생시키는 온상이다. n + n = 2*n이라는 수식에 문제가 있다는 것을 발견하기는 쉽지 않다.&amp;nbsp;따라서 부수 효과를 없애면 디버깅이 용이해 진다.&amp;nbsp;부수 효과를 제거하고 참조 투명성을 유지함으로써 프로그램의 수행 결과를 예측 가능한 상태로 유지할 수 있다.&amp;nbsp;부수 효과를 가진 프로그램은 함수 호출 순서에 따라 다른 결과를 얻게 되므로 프로그래밍 동안 함수 호출 순서에 주의를 기울여야 한다.&amp;nbsp;부수 효과의 존재 유무를 판단하기 위해서는 추상화 뿐만 아니라 구현 세부 사항까지 인지하고 있어야 한다.&amp;nbsp;이것은 프로그래가 짊어져야 할 개념적 무게를 증가시킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러나 명령형 언어를 사용하는 경우 부수 효과를 피할 수는 없다. 대신 부수 효과의 영향을 최소화하기 위해 부수 효과를 가진 함수(일반적으로 프로시져라고 부른다)와 부수 효과를 가지지 않는 순수한 함수를 분리시키는 방법을 생각해 볼 수 있다. &lt;b&gt;명령-쿼리 분리(Command-Query Separation)&lt;/b&gt; 원리의 기본 개념은 부수 효과의 발생 여부에 따라 객체의 메소드를 &lt;b&gt;명령(Command)&lt;/b&gt;과&amp;nbsp;&lt;b&gt;쿼리(Query)&lt;/b&gt;로 분리하자는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부수 효과를 발생시키지 않는 것만을 함수로 제한함으로써 소프트웨어에서 말하는&amp;nbsp;&amp;ldquo;함수&amp;rdquo;의 개념이 일반 수학에서의 개념과 상충되지 않도록 한다.&amp;nbsp;우리는 오브젝트를 변경하지만 직접적으로 값을 반환하지 않는&amp;nbsp;Command와 오브젝트에 대한 정보를 반환하지만 변경하지는 않는&amp;nbsp;Query&amp;nbsp;간의 명확한 구분을 유지할 것이다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;i&gt;&lt;i&gt;&lt;i&gt;Betrand Meyer, Object-Oriented Software Construction 2nd Edition&lt;/i&gt;&lt;/i&gt;&lt;/i&gt;&lt;/i&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명령-쿼리 분리 원리를 한 문장으로 줄여 표현하면&amp;nbsp;&amp;ldquo;질문이 답변을 수정해서는 안 된다&quot;는 것이다. 명령은&amp;nbsp;상태를 변경하는 대신 객체의 상태를 반환해서는 안된다. 쿼리는 객체의 상태를 반환하는 대신 값을 변경해서는 안 된다. 명령과&amp;nbsp;쿼리를 분리함으로써 명령형 언어의 틀 안에서 함수형 언어의 장점을 제한적이나마 누릴 수 있게 된다.&amp;nbsp;즉,&amp;nbsp;버그를 줄일 수 있고,&amp;nbsp;디버깅이 용이하며, 쿼리의 순서에 따라 실행 결과가 변하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;명령-쿼리 분리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: justify;&quot;&gt;원리&lt;/span&gt;에서 말하는 부수 효과의 범위는&amp;nbsp;외부에서 인식 가능한 부수 효과만으로 제한된다. Meyer는 이를&amp;nbsp;'추상적인 부수 효과(abstract side effect)'라고 표현했으며, Martin Folwer는&amp;nbsp;'관찰가능한 상태(Observable State)'의 변경이라고 표현했다. 명령에서 객체의 내부 상태를 변경하더라도 변경 내용이 클라이언트에게 노출되지 않는다면 추상적인 부수 효과라고 볼 수 없다.&amp;nbsp;객체의 모든 상태 변경을&amp;nbsp;'구체적인 부수 효과(concrete side effect)'라고 할 때,&amp;nbsp;추상적인 부수 효과는 구체적인 부수 효과의 부분 집합이다. Meyer가 두 가지 부수 효과를&amp;nbsp;구분한 이유는 프로그래밍 언어의 부수 효과를 자동적으로 검출 할 수 없다는 사실을 강조하기 위해서이다. 따라서 명령에 의해 발생하는 추상적인 부수 효과를 인식하고 부수 효과에 의한 버그를 방지하는&amp;nbsp;것은 전적으로 프로그래머의 몫이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[옛날 글들] 설계 이야기</category>
      <category>command-query separation</category>
      <category>side effect</category>
      <category>명령-쿼리 분리</category>
      <category>부수효과</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/27</guid>
      <comments>https://eternity-object.tistory.com/27#entry27comment</comments>
      <pubDate>Wed, 29 May 2024 16:44:52 +0900</pubDate>
    </item>
    <item>
      <title>도메인 주도 설계의 본질 - 2부[끝]</title>
      <link>https://eternity-object.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글 : &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://eternity-object.tistory.com/25&quot;&gt;도메인 주도 설계의 본질 - 1부&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;순수의 시대&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제조, 건축 메타포로부터 파생된 분석/설계/구현의 명확한 분리가 소프트웨어 개발에 적용하기에는 부적합하다는 깨달음과, 애자일 방법론의 대두로 인한 프로세스와 문서화로부터 개인과 팀, 소프트웨어로의 무게 중심 이동, 리팩토링과 피드백을 통한 점진적인 설계 기법의 적용을 통한 분석/설계/구현 사이클 통합, 그리고 엔터프라이즈 애플리케이션 환경에서 표현적 차이를 줄일 수 있는 POJO 중심의 경량 프레임워크의 대두로 소프트웨어 업계는 기민함과 가벼움의 시대로 접어 들었다. 애자일 동맹은 소프트웨어 개발이 기계적인 작업이 아닌 사람의 창조성과 협업에 의한 작업이라는 사실을 일깨워 주었다. POJO와 경량 프레임워크는 소프트웨어가 기술이 아닌 도메인에 의해 주도되어야 한다는 사실을 일깨워 주었다. 기술을 향한 골드러시에 동참했던 사람들의 열기가 서서히 식기 시작했다. 사람들은 발전된 기술에 열광하면서도 과거에 소프트웨어를 개발하던 순수한 방식으로 회귀하기를 갈망했다. 그리고 이러한 방법론과 기술에 대한 기대치가 점점 높아져가고 있던 2003년 Eric Evans는 &amp;ldquo;Domain-Driven Design&amp;rdquo;이라는 책을 출판한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ldquo;Domain-Driven Design&amp;rdquo;은 새로운 개념이 아니다. 책의 부제인 &amp;ldquo;Tackling Complexity in the Heart of Software&amp;rdquo;에서 알 수 있듯이 DDD는 소프트웨어의 본질적인 문제인 복잡성을 제어하기 위해 기존에 존재해 왔던 다양한 방법과 기법들을 패턴 언어Pattern Language 형식으로 정리해 놓은 것이다. DDD가 전혀 새로운 기법을 제시하지 않고 있음에도 불구하고 많은 사람들이 그것에 열광하는 이유는 DDD의 접근 방식에 있다. DDD는 소프트웨어 개발의 베스트 프랙티스를 도메인이라는 컨텍스트 안에서 체계적으로 조합하고 연결시킨다. DDD는 &amp;ldquo;모든 소프트웨어 복잡성은 도메인에 기인한다&amp;rdquo;는 매우 일반적이고 직관적인 명제로부터 출발한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애자일 연맹이 소프트웨어를 개발하는 본질이 사람이라는 사실을, POJO가 소프트웨어 구성 단위의 본질이 순수한 객체라는 사실을 묻혀있던 과거의 기억 속에서 끄집어 냈다면, DDD는 우리가 기술의 홍수 속에서 잊고 있었던 소프트웨어가 딛고 있는 땅의 본질이 도메인이라는 사실을 깨닫게 해준다. DDD의 가치는 과거로부터 중요하고 본질적이라고 믿어 왔던 가치들을 현재의 복잡한 엔터프라이즈 애플리케이션 환경에 적용 가능한 수준으로 확장했다는 점에 있다. 즉, 눈부시게 빠른 속도로 발전하는 기술의 변화에 유연하게 대처하면서도 소프트웨어의 본질인 도메인의 표현을 가능하게 해주는 패턴과 기법의 복합체가 바로 DDD인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD는 도메인 모델과 소프트웨어 모델, 즉 코드 간의 표현적 차이를 최소화하기 위한 접근 방법이다. 이를 위해 EJB와 같은 구현 기술이 소프트웨어 개발을 주도함으로써 발생되는 여러 가지 문제점을 해결하기 위해 기술 주도적인 방식이 아닌 도메인 주도적인 방식으로 소프트웨어를 개발할 것을 주장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD를 성공적으로 적용하기 위해서는 기본적으로 두 가지 요소가 갖추어져야 한다. MODEL-DRIVEN DESIGN과 UBIQUITOUS LANGUAGE가 바로 그것이다. 두 가지 원리를 이해하기 위해 모델과 소프트웨어의 관계에 관해 간략히 살펴 보기로 하자.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MODEL&lt;/b&gt;&lt;b&gt;과 DOMAIN-DRIVEN DESIGN&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델(Model)은 대상을 단순화한 것이다. 모델은 추상화다. 즉, 모델은 얽히고 설킨 복잡한 사실에 대한 하나의 해석이며, 당면한 문제를 해결하기 위해 필요한 측면을 강조하고 문제 해결에 무관하거나 불필요한 세부사항에는 의도적으로 주의를 기울이지 않는다. 사실과 완전히 동일한 모델을 만드는 것은 바람직하지 않다. 모델은 현실이라는 기반 위에 해결하고자 하는 문제에 적합한 새로운 추상화 계층을 창조하는 과정이다. &amp;lsquo;천하도&amp;rsquo;는 과거 사람들이 생각하던 세계의 모델이다. 천하도가 세계의 모습과 완전히 동일하지 않다고 해도 과거 사람들의 세계관을 적절히 반영하고 있기 때문에 적합한 모델이라고 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 소프트웨어는 특정한 사용자의 문제를 해결하는 것을 목적으로 한다. 이 때 소프트웨어를 사용할 사용자의 활동이나 관심사의 대상이 되는 영역을 소프트웨어의 도메인(Domain)이라고 한다. 따라서 도메인 모델(Domain Model)이란 소프트웨어가 문제를 해결해야 하는 대상 영역을 단순화시키고 추상화시킨 것이다. 적절한 도메인 모델을 선택함으로써 소프트웨어 개발과 관련된 다양하고 복잡한 측면들을 이해할 수 있고 해결하려는 문제에 집중할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 모델은 어떤 특정한 다이어그램이나 문서가 아니라 이를 통해 전달하고자 하는 개념의 집합이다. 도메인 모델은 단지 도메인 전문가의 머리 속에만 존재하는 지식이 아니라 소프트웨어 개발에 적합하도록 지식을 정밀하게 조직하고 선택적으로 추상화한 것이다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소프트웨어의 본질적인 복잡성은 도메인의 복잡성에 기인한다. Frederick Brooks의 말을 재인용하면 소프트웨어의 본질적인 문제를 외면한 상태에서 비약적인 생산성의 향상을 기대할 수는 없다. 따라서 소프트웨어의 본질적인 문제를 해결하기 위해서는 도메인을 이해하고 끊임없는 탐구를 통해 적절한 도메인 모델을 선택하는 것이 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yeHbk/btsHFoMFBPM/QmLv4FJrv29DgdCwwIaUfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yeHbk/btsHFoMFBPM/QmLv4FJrv29DgdCwwIaUfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yeHbk/btsHFoMFBPM/QmLv4FJrv29DgdCwwIaUfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyeHbk%2FbtsHFoMFBPM%2FQmLv4FJrv29DgdCwwIaUfk%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;364&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 5 도메인 모델을 중심으로 소프트웨어 개발을 진행하라&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;훌륭한 도메인 모델은 다음의 3가지 요구사항을 충족시킨다.&lt;/span&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;모델과 핵심 설계는 상호 영향을 주고 받으며 구체화된다.&lt;br /&gt;&lt;/b&gt;모델과 구현 간의 긴밀한 연결과 피드백을 통해 모델이 의미를 가지도록 하고 최종 산출물인 동작하는 프로그램이 사용자의 요구사항을 만족시킬 수 있도록 보장한다. 모델과 구현을 연결 시키는 것은 분석/설계/구현을 동일한 사이클로 묶음으로써 달성된다. 애자일의 언제나 설계하기 사상, 즉 리팩토링을 통한 점진적인 설계는 모델과 구현의 불일치를 예방하기 위한 프로세스적인 장치다. 모델과 구현이 일치하기 위해서는 모델과 코드의 표현적 차이가 적어야 한다. 코드가 인프라스트럭처에 대해 강하게 의존할 경우 모델과 코드 간의 표현적 차이가 커진다. POJO 기반의 경량 프레임워크 기술이 인프라스트럭처의 늪에서 코드를 구원한다. 모델을 기반으로 분석/설계/구현을 통합하고 피드백을 통해 개선시키는 과정을 MODEL-DRIVEN DESIGN이라고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델은 모든 팀 구성원들이 사용하는 언어의 근간을 이룬다.&lt;br /&gt;&lt;/b&gt;모델과 구현이 긴밀하게 연관되어 있으므로 개발자들은 모델을 사용해서 대화할 수 있다. 모델은 도메인 전문가가 도메인을 바라보는 관점을 담고 있기 때문에 도메인 전문가 간의 대화 시에 사용할 수 있다. 개발자와 도메인 전문가 간에 공통의 모델을 공유하기 때문에 별도의 번역 과정 없이 개발자와 도메인 전문가 사이에에 모델을 기반으로 의사소통 할 수 있다. 애자일 동맹에서 강조하는 문서에 의한 지식 전달이 아닌 직접 대면과 대화를 통한 의사소통 방식은 언어의 중요성을 강조한다. 모델은 소프트웨어 지식의 집합체이며 정보 흐름의 통로다. 이해관계자들 간의 의사소통에 사용되는 언어에 변화가 생기면 모델의 용어가 변경되고, 모델의 용어가 변경되면 코드가 수정된다. 반대로 코드가 변경될 경우 모델과 의사소통에 사용되는 용어가 변경된다. 모델은 이해관계자들의 공통 언어인 UBIQUITOUS LANGUAGE를 구성하는 기반이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델은 불순물을 걸러낸 핵심 지식만을 포함한다.&lt;br /&gt;&lt;/b&gt;효과적인 도메인 모델은 끊임없는 지식 탐구 활동과 개선을 통해 얻어진다. 수많은 모델을 시도하고, 버리고, 변형한 끝에 도메인의 모든 세부 사항에 적절한 일련의 추상적 개념들을 발견함으로써 적절한 도메인 모델을 얻게 된다. 지식 탐구 활동은 개발자와 도메인 전문가 간의 긴밀한 협력이 전제되어야 한다. UBIQUITOUS LANGUAGE가 빛을 발휘하는 때다. 지식 탐구 활동은 끊임없이 모델과 코드를 개선하는 정제 과정이다. 모델과 코드를 긴밀히 연결함으로써 새로운 통찰이 모델과 코드에 반영되도록 할 수 있다. MODEL-DRIVEN DESIGN이 우리를 정제의 길로 인도한다. 끊임없는 정제가 가능하기 위해서는 소스 코드를 항상 깨끗하고 안정적인 상태로 유지해야 한다. 애자일의 언제나 설계하기 사상과 분석/설계/구현 사이클의 통합, 리팩토링을 통한 점진적인 설계 방식은 이를 가능하게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD는 훌륭한 도메인 모델을 만들고 이를 소프트웨어 반영하기 위한 모든 것이다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MODEL-DRIVEN DESIGN&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MODEL-DRIVEN DESIGN 의 개념은 간단하다. &amp;nbsp;도메인 모델을 반영하는 코드를 작성하라. 코드에 나타나는 구조와 용어는 도메인 모델에 기반해야 한다. 도메인에 대한 이해가 바뀐다면 이를 도메인 모델과 코드 양쪽 모두에 반영하라. 만약 모델이 코드를 작성하기에 적합하지 않다면 구현에 적합하도록 모델을 수정하라. 모델과 코드의 개념적 거리가 멀어지면 멀어질수록 소프트웨어는 도메인과 멀어지고, 도메인 전문가와 개발자는 서로 간의 개념을 일치시키기 위해 번역 과정을 거쳐야 하므로 의사소통이 어려워지고 유지보수가 어려운 코드를 얻게 된다. 모델을 기반으로 코드를 작성하되 모델과 코드를 동기화하기 위해 모든 노력을 쏟아라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MODEL-DRIVEN DESIGN을 적용하기 위해서는 분석/설계/구현이 개별적인 활동이라는 환상에서 벗어나야 한다. 이를 하나의 사이클로 묶음으로써 모델과 코드 간의 거리를 좁히도록 노력해야 한다. 둘 사이의 개념적 거리가 멀어지면 지속적인 리팩토링을 통해 간격을 좁히도록 최선을 다해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델링과 구현이 개별적인 사람들에 의해 이루어져야 한다는 착각 역시 MODEL-DRIVEN DESIGN을 방해하는 요소다. 구현에 관여하는 사람들은 모델링 작업에 직접 참여해야 한다. 두 역할을 분리할 경우 모델링 작업에서 얻어지는 지식이 코드에 반영되지 못한 채 소리 없이 사라져 버리게 된다. 또한 구현 과정에서 얻어지는 통찰을 모델에 반영할 수 없게 되므로 결과적으로 코드와 모델 간의 연결 고리가 점점 약해지게 된다. 코드는 모델이며, 모델은 코드다. 극단적인 흑백논리는 소프트웨어 개발에 있어 최대의 적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델과 코드 간의 표현적 차이를 줄이기 위해서는 도메인 전문가와 개발자 간에 동일한 언어를 기반으로 의사소통하는 것이 중요하다. 역동적이고 활동적인 UBIQUITOUS LANGUAGE가 MODEL-DRIVEN DESIGN의 기반을 이룬다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 모델과 소프트웨어 시스템 간의 맵핑이 명확해지도록 도메인 모델을 충실하게 반영하는 소프트웨어 시스템을 설계하라. 도메인에 대한 더 깊은 식견을 반영할 방법을 찾는 순간 소프트웨어 내에서 모델을 더 자연스럽게 구현할 수 있도록 모델을 재검토하고 수정하라. &amp;nbsp;견고한 UBIQUITOUS LANGUAGE를 지원함과 동시에 두 가지 목적 모두에 잘 부합하는 하나의 모델을 추구하라.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&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;&lt;b&gt;&lt;br /&gt;Eric Evans, Domain-Driven Design&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;UBIQUITOUS LANGUAGE&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바벨탑을 쌓아 신의 권위에 도전하려던 인간의 오만은 결국 신의 분노로 인해 실패하고 만다. 인류 역사상 가장 거대한 공학 프로젝트인 동시에 가장 큰 프로젝트 실패 사례로 꼽히는 바벨탑의 가장 큰 실패 요인은 언어의 분할로 인해 이해관계자 간에 의사소통이 불가능해졌다는 것이다. 신의 권위에 도전하지 않는 현대의 소프트웨어 프로젝트에 있어 불가능한 의사소통이 프로젝트를 실패하게 만들지는 않을 지라도 그 길을 더디고 험난하게 만들 수는 있다.&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO35Do/btsHHq2MAJQ/f5O9CKVmek2WO9hpAPjsm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO35Do/btsHHq2MAJQ/f5O9CKVmek2WO9hpAPjsm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO35Do/btsHHq2MAJQ/f5O9CKVmek2WO9hpAPjsm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO35Do%2FbtsHHq2MAJQ%2Ff5O9CKVmek2WO9hpAPjsm0%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;494&quot; height=&quot;318&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 6 의사소통의 단절로 인해 실패한 바벨탑 프로젝트&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 전문가와 개발자가 서로 다른 용어를 사용할 경우 두 역할 간의 의사소통을 위해 번역 작업이 수반되어야 하며, 이는 곧 의사소통의 효율성 저하, 두 역할 간의 불협화음으로 인한 프로젝트 능률 저하, 도메인과 격리된 코드라는 결과를 낳게 된다. 따라서 프로젝트에 참여하는 모든 이해관계자들이 의사소통 시에 사용할 수 있는 공통 언어가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도메인 모델은 도메인에 대해 이해관계자들이 가지고 있는 관점과 지식의 축적이며 추상화의 산물이다. 도메인 모델 내에 표현된 모든 개념과 관계는 도메인을 바라보는 이해관계자들의 지식 탐구 활동을 통해 얻어진 통찰이 반영되어 있다. 따라서 도메인 모델은 이해관계자들의 의사소통을 위한 공통 언어 구축의 핵심 요소로 사용할 수 있다. 도메인 모델을 기반으로 한 공통 언어는 소프트웨어 개발과 관련된 전반적인 활동의 중심에 모델을 위치시키며, 결과적으로 모델과 코드를 연결시키기 위한 MODEL-DRIVEN DESIGN에 있어 매우 중요한 연결고리를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모델을 언어의 기반으로 삼아라. 팀에서 이루어지는 모든 의사소통과 코드에 적극적으로 공통의 언어를 적용하라. 다이어그램과, 문서화, 특히 대화에 동일한 언어를 사용하라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 가지 모델을 반영하는 다른 표현을 실험해봄으로써 공통 언어 선택에 따르는 어려움을 해소하라. 그 후 새로운 모델에 적합하도록 클래스, 메서드, 모듈의 이름을 변경하여 코드를 리팩토링하라. 일상에서 사용하는 용어를 다르게 사용할 경우 의미에 대한 공감대를 형성하는 것과 동일한 방식으로 대화에 사용하는 용어 상의 혼란 역시 해결하라.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UBIQUITOUS LANGUAGE의 변경은 곧 모델의 변경이라는 사실을 인식하자.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Eric Evans, Domain-Driven Design&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애자일 진영은 문서나 다이어그램을 통한 의사소통보다는 직접 대면과 대화를 통한 의사소통 방식을 선호한다. UBIQUITOUS LANGUAGE를 확립하기 위해서는 대화와 같은 직접적인 의사소통 수단을 적극 활용하여 언어가 팀 전체에 골고루 퍼지게 해야 한다. 언어가 널리 사용되면 사용될수록 이해관계자들 간의 원활한 의사소통이 가능해진다. 형식적인 문서화도 중요하지만 가능하면 언어가 지닌 장점을 최대한 활용하라.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UBIQUITOUS LANGUAGE의 용어와 개념이 도메인에서 사용되는 개념이라고 해서 반드시 도메인 전문가들이 사용하는 용어만으로 구성되는 것은 아니다. 도메인 전문가의 용어 중 실제 소프트웨어가 해결하려는 컨텍스트와 관련된 용어들만이 UBIQUITOUS LANGUAGE에 포함된다. 또한 개발에 필요한 용어 중 도메인 전문가와 개발자 간의 의사소통을 위해 필요한 용어들 역시 UBIQUITOUS LANGUAGE에 포함된다. 따라서 UBIQUITOUS LANGUAGE는 도메인 전문가들이 사용하는 용어 일부와 개발자들이 사용하는 용어 일부로 구성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XqsaB/btsHHjpkHGX/sF1dGc8zuZwInk4Q9KYo71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XqsaB/btsHHjpkHGX/sF1dGc8zuZwInk4Q9KYo71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XqsaB/btsHHjpkHGX/sF1dGc8zuZwInk4Q9KYo71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXqsaB%2FbtsHHjpkHGX%2FsF1dGc8zuZwInk4Q9KYo71%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;554&quot; height=&quot;296&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 7 도메인 전문가와 개발자 용어의 교집합으로 구성된 UBIQUITOUS LANGUAGE&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UBIQUITOUS LANGUAGE 상의 용어에 변경이 발생하면 곧 모델, 코드에 대한 변경으로 파급되어야 한다. 역으로 코드에 사용된 어떤 용어가 수정될 경우 변경 사항은 모델과 UBIQUITOUS LANGUAGE로 파급되어야 한다. 가장 중요한 것은 변경 전의 용어를 폐기처분하고 일상적인 의사소통, 특히 대화 시에 새로운 용어를 사용하도록 노력하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buUtI2/btsHGko3N8O/zXT3gEPM642FwOeCxxRO8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buUtI2/btsHGko3N8O/zXT3gEPM642FwOeCxxRO8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buUtI2/btsHGko3N8O/zXT3gEPM642FwOeCxxRO8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuUtI2%2FbtsHGko3N8O%2FzXT3gEPM642FwOeCxxRO8k%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;486&quot; height=&quot;368&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 8 UBIQUITOUS LANGUAGE와 코드 간의 순환 관계&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;패턴에 대해서&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD를 접하는 대부분의 사람들이 가장 먼저 접하게 되는 것이 ENTITY, VALUE OBJECT, AGGREGATE, SERVICE, REPOSITORY와 같은 패턴이다. 지면상의 제약으로 인해 본 글에서 DDD의 패턴에 대해 포괄적으로 설명하기는 어려울 것으로 판단되어 여기에서는 DDD의 패턴을 바라보는 개인적인 견해를 피력하는 정도로 마무리하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴에 대한 자세한 내용이 궁금한 사람들은 Eric Evans가 저술한 &amp;ldquo;Domain-Driven Design&amp;rdquo;을 읽어 보기 바란다. 부족하나마 필자의 블로그(&lt;a style=&quot;color: #000000;&quot; href=&quot;http://aeternum.egloos.com/&quot;&gt;http://aeternum.egloos.com/&lt;/a&gt;)에 올려져 있는 글을 읽어 보는 것도 도움이 되리라고 생각한다. 웹에서 얻을 수 있는 Domain Driven Design Quickly와 Domain-Driven Design Step-By-Step 역시 패턴을 이해할 수 있는 유용한 정보를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인적으로 도메인 모델을 반영한 코드를 작성하고, 도메인 모델과 코드가 상호 연결된 관계를 유지할 수 있도록 해 주는 두 가지 핵심 원리인 UBIQUITOUS LANGUAGE와 MODEL-DRIVEN DESIGN이 DDD의 핵심을 이루는 개념이라고 생각한다. 따라서 위 두 가지 원리를 이해하는 것이 DDD의 개별 패턴을 이해하는 것 보다 더 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다고 해서 패턴이 중요하지 않다는 것은 아니다. 원리를 이해하는 것과 이를 실천하는 것은 별개의 문제다. DDD에서 제시하는 패턴은 DDD의 기본 원리를 실행할 수 있는 기반을 제공한다는 점에서 그 가치가 매우 높다. 그러나 기본적인 원리의 이해 없이 성급하게 개별 패턴을 적용하려는 시도는 위험하다. DDD의 패턴에서 차용된 Annotation을 코드에 사용하고 DDD를 지원한다고 선전하는 프레임워크를 적용한다고 해서 DDD를 적용하는 것은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4683P/btsHGiY8iNN/b37ykwkVMm1xDNEYwkgkHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4683P/btsHGiY8iNN/b37ykwkVMm1xDNEYwkgkHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4683P/btsHGiY8iNN/b37ykwkVMm1xDNEYwkgkHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4683P%2FbtsHGiY8iNN%2Fb37ykwkVMm1xDNEYwkgkHk%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;564&quot; height=&quot;348&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림 9 패턴은 중요하다. 그러나 원리가 그보다 더 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패턴의 적용 유무와 무관하게 다음 질문에 대해 모두 YES라고 대답할 수 있다면 DDD를 실천하고 있다고 봐도 무방하다.&lt;/span&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;모든 이해당사자들이 의사소통에 사용하는 UBIQUITOUS LANGUAGE가 존재하는가?&lt;/li&gt;
&lt;li&gt;소프트웨어가 MODEL-DRIVEN DESIGN 방식으로 개발되고 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그래도 은 총알은 없다&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD는 끊임없는 지식 탐구 활동을 통해 적절한 도메인 모델을 선택하고, 선택된 도메인 모델을 기반으로 분석, 설계, 구현에 걸친 소프트웨어 개발 전 과정을 주도하고, 모델을 의사소통의 핵심 수단으로 사용하기 위해 적용할 수 있는 기법과 패턴의 집합이다. 그렇다면 DDD가 과연 우리가 찾고 있던 은 총알인가? 그렇지는 않다고 생각한다. DDD를 소프트웨어 개발과 관련된 모든 문제를 해결해 줄 수 있는 만병 통치약으로 생각해서는 안 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD는 장점이 많은 만큼 적용에 따르는 리스크와 제약 역시 크다. 그렇다면 DDD를 적용해야 하는 때는 언제인가? 이에 대한 Casey Charlton의 견해를 들어 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; 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;DDD는 다음과 같은 경우 적용하기 적절한 소프트웨어 방법이다 &amp;ndash; 매우 복잡하고 잘 정의된 비즈니스 모델에 초점을 맞추어야 하는 소프트웨어. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;아마 모든 소프트웨어 애플리케이션의 95%는 &amp;ldquo;DDD를 적용하기에 적절하지 않은&amp;rdquo; 범주에 속할 것이다. 대부분의 소프트웨어는 근본적으로 데이터 중심적이다 &amp;ndash; 대부분의 웹 사이트가 그렇고, 대부분의 데스크톱 애플리케이션이 그렇다. 사실 데이터를 수정하고 보고하는 대부분의 애플리케이션은 데이터 중심적이라고 할 수 있다. 나머지 부류의 애플리케이션이라고 하더라도 매우 복잡한 도메인을 가진 경우는 많지 않다. 대부분은 단순하거나, 소프트웨어의 규모가 크더라도 그렇게 복잡하지는 않다. &lt;br /&gt;&lt;br /&gt;DDD를 적용하기에 적절한 나머지 5%의 애플리케이션에 대해서는 DDD를 적용하기에 매우 적합하다고 할 수 있다. 그런 상황에서 DDD를 적용하는 것은 어렵고 복잡한 문제를 해결하는데 있어 매우 큰 도움이 될 것이다. 이런 상황이라면 DDD는 늑대를 물리치기 위한 은 총알이 될 지도 모른다. &lt;br /&gt;&lt;br /&gt;비록 애플리케이션이 DDD에 적합한 5% 범주에 포함되지 않는다고 하더라도 DDD에는 대량의 지혜와 경험이 녹아 있다 &amp;ndash; 자신의 상황에 적용할 수 있다고 생각되는 기법을 적용하면 소프트웨어가 좀 더 유연해지고, 사용자에게 더 잘 반응하며, 이해하기 더 쉬워짐을 알게 될 것이다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Casey Charlton, Domain Driven Design Step by Step Guide&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DDD는 복잡한 도메인을 공략하기 위한 최상의 설계 지침을 제공한다. DDD의 가치는 소프트웨어의 부차적인 문제가 아니라 본질적인 문제를 해결하기 위해 모든 사람들의 역량을 모을 수 있도록 해준다는데 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비록 DDD의 지침을 현재의 프로젝트에 적용할 수 없는 상황에 있다고 하더라도 DDD의 세계에 발을 들여 놓는 것이 큰 도움이 될 것이라 생각된다. Eric Evans의 충고에 귀를 기울여 보자.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&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;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Eric Evans, Domain-Driven Design&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;</description>
      <category>[옛날 글들] 도메인 주도 설계</category>
      <category>DDD</category>
      <category>Domain-Driven Design</category>
      <category>도메인 주도 설계</category>
      <author>eternity.object</author>
      <guid isPermaLink="true">https://eternity-object.tistory.com/26</guid>
      <comments>https://eternity-object.tistory.com/26#entry26comment</comments>
      <pubDate>Wed, 29 May 2024 16:05:24 +0900</pubDate>
    </item>
  </channel>
</rss>