<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>기억용 로그</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 10 Jan 2024 11:34:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>기억용 로그</title>
            <url>https://velog.velcdn.com/images/seunghee-ryu/profile/9c05b648-6313-493f-b626-4422037fd3bc/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 기억용 로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seunghee-ryu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[04 아키텍처 - 4.2]]></title>
            <link>https://velog.io/@seunghee-ryu/04-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-4.2</link>
            <guid>https://velog.io/@seunghee-ryu/04-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-4.2</guid>
            <pubDate>Wed, 10 Jan 2024 11:34:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/516d3482-6fa4-4b42-ba95-8ccaf67c16a3/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[04 아키텍쳐 - 4.1]]></title>
            <link>https://velog.io/@seunghee-ryu/04-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-4.1</link>
            <guid>https://velog.io/@seunghee-ryu/04-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-4.1</guid>
            <pubDate>Mon, 08 Jan 2024 12:30:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/c1cde9a4-92d2-44de-b69f-b6708ac91b6a/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도메인 특화 언어]]></title>
            <link>https://velog.io/@seunghee-ryu/%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%96%B8%EC%96%B4</link>
            <guid>https://velog.io/@seunghee-ryu/%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%96%B8%EC%96%B4</guid>
            <pubDate>Wed, 03 Jan 2024 12:47:12 GMT</pubDate>
            <description><![CDATA[<h2 id="도메인-특화-언어dsl">도메인 특화 언어(DSL)</h2>
<ul>
<li>관련 특정 분야에 최적화된 프로그래밍 언어</li>
<li>해당 분야 또는 도메인의 개념과 규칙을 사용</li>
<li>언어 지향 프로그래밍의 일종</li>
<li>ex : Junit, 쿼리DSL</li>
</ul>
<h3 id="이점">이점</h3>
<ul>
<li>언어와 변환 엔진을 갖추면 지루한 작업을 일일이 수행할 필요가 없어, 해당 DSL과 관련된 소프트웨어 개발의 특정 부분에서 훨씬 더 효율적으로 작업할 수 있다</li>
<li>중요하지 않은 복잡한 요소에서 필수 항목만 분리할 수 있다</li>
<li>DSL은 프로그래머와 해당 분야의 전문가를 매우 원활하게 연결해준다</li>
</ul>
<h3 id="언어-지향-프로그래밍">언어 지향 프로그래밍</h3>
<ul>
<li>언어 지향 프로그래밍은 개발자가 고유한 DSL을 빌드하거나 해당 접근 방식의 일부로 도메인 특화 개념을 사용하여 기존 언어를 확장하도록 뚜렷하게 권장한다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[17 냄새와 휴리스틱]]></title>
            <link>https://velog.io/@seunghee-ryu/17-%EB%83%84%EC%83%88%EC%99%80-%ED%9C%B4%EB%A6%AC%EC%8A%A4%ED%8B%B1</link>
            <guid>https://velog.io/@seunghee-ryu/17-%EB%83%84%EC%83%88%EC%99%80-%ED%9C%B4%EB%A6%AC%EC%8A%A4%ED%8B%B1</guid>
            <pubDate>Sat, 30 Dec 2023 14:49:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/098f7979-77dd-452c-925b-91f2cd018e8e/image.png" alt=""></p>
<ul>
<li>리팩터링이 필요하다고 생각되는 것들에 대해 정리하였다</li>
</ul>
<h2 id="주석">주석</h2>
<h3 id="c1-부적절한-정보">C1: 부적절한 정보</h3>
<ul>
<li>주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다</li>
</ul>
<h3 id="c2-쓸모없는-주석">C2: 쓸모없는 주석</h3>
<ul>
<li>쓸모없어질 주석은 아예 달지 않는 것이 가장 좋고 달려있다면 삭제하는 편이 좋다</li>
</ul>
<h3 id="c3-중복된-주석">C3: 중복된 주석</h3>
<ul>
<li>주석은 코드만으로 다하지 못하는 설명만 적는다</li>
</ul>
<h3 id="c4-성의없는-주석">C4: 성의없는 주석</h3>
<ul>
<li>주석을 달거라면 최대한 간결하고 명료하게 작성한다</li>
</ul>
<h3 id="c5-주석-처리된-코드">C5: 주석 처리된 코드</h3>
<ul>
<li>주석 처리된 코드는 즉각 지운다</li>
<li>기존의 코드는 소스 코드 관리 시스템이 기억하기 때문에 괜찮다</li>
</ul>
<h2 id="환경">환경</h2>
<h3 id="e1-여러-단계로-빌드해야-한다">E1: 여러 단계로 빌드해야 한다</h3>
<ul>
<li>빌드는 간단히 한 단계로 끝나야 한다</li>
<li>한 명령으로 전체를 체크아웃해서 한 명령으로 빌드를 할 수 있어야 한다</li>
</ul>
<pre><code class="language-java">git clone mySystem
cd mySystem
mvn package</code></pre>
<h3 id="e2-여러-단계로-테스트해야-한다">E2: 여러 단계로 테스트해야 한다</h3>
<ul>
<li>모든 단위 테스트는 한 명령으로 돌려야 한다</li>
<li>방법이 빠르고 쉽고 명백해야 한다</li>
</ul>
<h2 id="함수">함수</h2>
<h3 id="f1-너무-많은-인수">F1: 너무 많은 인수</h3>
<ul>
<li>함수에서 인수 개수는 작을수록 좋고 아예 없으면 가장 좋다</li>
</ul>
<h3 id="f2-출력-인수">F2: 출력 인수</h3>
<ul>
<li>일반적으로 독자는 인수를 출력이 아닌 입력으로 간주한다<pre><code class="language-java">appendFooter(report) -&gt; report.appendFooter()</code></pre>
<h3 id="f3-플래그-인수">F3: 플래그 인수</h3>
</li>
<li>boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거</li>
<li>SRP 위반<pre><code class="language-java">render(boolean isSuite) -&gt; renderForSuite(), renderForSingleTest()</code></pre>
</li>
</ul>
<h3 id="f4-죽은-함수">F4: 죽은 함수</h3>
<ul>
<li>아무도 호출하지 않는 함수는 삭제한다</li>
<li>죽은 코드는 낭비다, 과감히 삭제하라</li>
<li>소스 코드 관리 시스템이 모두 기억한다</li>
</ul>
<h2 id="일반">일반</h2>
<h3 id="g1-한-소스-파일에-여러-언어를-사용한다">G1: 한 소스 파일에 여러 언어를 사용한다</h3>
<ul>
<li>JSP -&gt; HTML, 자바, 태그 라이브러리, XML, JavaScript 등</li>
<li>소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다</li>
</ul>
<h3 id="g2-당연한-동작을-구현하지-않는다">G2: 당연한 동작을 구현하지 않는다</h3>
<ul>
<li>당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어렵다</li>
</ul>
<h3 id="g3-경계를-올바로-처리하지-않는다">G3: 경계를 올바로 처리하지 않는다</h3>
<ul>
<li><p>코드는 올바로 동작해야 한다</p>
</li>
<li><p>그런데 우리는 올바른 동작이 아주 복잡하다는 사실을 자주 간과한다</p>
</li>
<li><p>스스로의 직관에 의존하지 말고 모든 경계 조건을 찾아내고 모든 경계 조건을 테스트화하는 테스트 케이스를 작성한다</p>
<pre><code class="language-java">import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

@Transactional
public Comment write(Id&lt;Post, Long&gt; postId, Id&lt;User, Long&gt; postWriterId, Id&lt;User, Long&gt; userId, Comment comment) {
  checkArgument(comment.getPostId().equals(postId), &quot;comment.postId must equals postId&quot;);
  checkArgument(comment.getUserId().equals(userId), &quot;comment.userId must equals userId&quot;);
  checkNotNull(comment, &quot;comment must be provided.&quot;);

  ...</code></pre>
</li>
</ul>
<h3 id="g4-안전-절차-무시">G4: 안전 절차 무시</h3>
<ul>
<li>체르노빌 원전 사고는 책임자가 안전 절차를 차례로 무시하는 바람에 일어났다</li>
<li>안전 절차를 무시하면 위험하다</li>
<li>실패하는 테스트 케이스를 일단 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다</li>
</ul>
<h3 id="g5-중복">G5: 중복</h3>
<ul>
<li>소프트웨어 설계를 거론하는 저자라면 거의 모두가 이 규칙을 언급한다<pre><code class="language-html">데이비드 토머스, 앤디 헌트: DRY(Don&#39;t Repeat Yourself)
켄트 백: &quot;한 번, 단 한 번만(Once, and only once)&quot;
론 제프리스: 이 규칙을 &quot;모든 테스트를 통과한다&quot;는 규칙 다음으로 중요하게 꼽았다.</code></pre>
</li>
<li>코드에서 중복을 발견할 때마다 추상화할 기회로 간주한다</li>
<li>중복된 코드는 하위 루틴이나 다른 클래스로 분리</li>
<li>중복은 다형성으로 대체 : TEMPLATE METHOD 패턴이나 STRATEGY 패턴으로 중복을 제거한다</li>
</ul>
<h3 id="g6-추상화-수준이-올바르지-못하다">G6: 추상화 수준이 올바르지 못하다</h3>
<ul>
<li>추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다</li>
<li>추상화로 개념을 분리할 때는 철저해야 한다</li>
<li>모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다<ul>
<li>예를 들어 세부 구현과 관련한 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안된다</li>
<li>기초 클래스는 구현 정보에 무지해야 마땅하다</li>
</ul>
</li>
</ul>
<pre><code class="language-java">import java.util.EmptyStackException;

public interface Stack {
    Object pop() throws EmptyStackException;
    void push(Object o) throws FullException;
    double percentFull();
    ...
} </code></pre>
<ul>
<li>percentFull() 함수는 추상화 수준이 올바르지 못하다</li>
<li>Stack을 구현하는 방법은 다양한데, 어떤 구현은 &#39;꽉 찬 정도&#39;라는 개념이 타당하다</li>
<li>어떤 구현은 알아낼 방법이 전혀 없다</li>
<li>따라서 함수는 BoundedStack과 같은 파생 인터페이스에 넣어야 마땅하다</li>
</ul>
<h3 id="g8-과도한-정보">G8: 과도한 정보</h3>
<ul>
<li>잘 정의된 모듈은 인터페이스가 아주 작지만 작은 인터페이스로도 많은 동작이 가능하다</li>
<li>잘 정의된 인터페이스는 많은 함수를 제공하지 않기 때문에 결합도(coupling)가 낮다</li>
<li>자료나 유틸리티 함수, 상수와 임시 변수를 숨기고 메서드나 인스턴스 변수가 넘쳐나는 클래스는 피해야 한다</li>
</ul>
<h3 id="g9-죽은-코드">G9: 죽은 코드</h3>
<ul>
<li>죽은 코드: 실행되지 않는 코드</li>
<li>죽은 코드는 시간이 지나면 악취를 풍기기 시작한다</li>
<li>죽은 코드를 발견하면 적절한 장례식을 치뤄주고 시스템에서 제거하라</li>
</ul>
<h3 id="g10-수직-분리">G10: 수직 분리</h3>
<ul>
<li><p>변수와 함수는 사용되는 이치에 가깝게 정의한다</p>
</li>
<li><p>비공개 함수는 처음으로 호출한 직후에 정의한다</p>
<pre><code class="language-java">  @Transactional
  fun createFolder(request: Folder.FolderCreateRequest): Folder {
      return when (isParentFolder(request.parentId)) {
          true -&gt; {
              val rootFolder = Folder.dtoToEntity(request)
              folderRepository.save(rootFolder)
          }
          false -&gt; {
              ...
          }
      }
  }

  private fun isParentFolder(parentId: Long) = parentId == 0L

  ...</code></pre>
<h3 id="g11-일관성-부족">G11: 일관성 부족</h3>
</li>
<li><p>어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다</p>
</li>
<li><p>착실하게 적용한다면 간단한 일관성만으로도 코드를 읽고 수정하기가 대단히 쉬워진다</p>
</li>
</ul>
<h3 id="g12-잡동사니">G12: 잡동사니</h3>
<ul>
<li>소스파일은 언제나 깔끔하게 정리한다</li>
</ul>
<h3 id="g13-인위적-결합">G13: 인위적 결합</h3>
<ul>
<li>서로 무관한 개념을 인위적으로 결합하지 않는다</li>
</ul>
<h3 id="g14-기능욕심">G14: 기능욕심</h3>
<ul>
<li>마틴 파울러가 말하는 코드 냄새 중 하나다</li>
<li>클래스 메소드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다<pre><code class="language-java">public class HourlyPayCalculator {
  public Money calculateWeeklyPay(HourlyEmployee e) {
      final int tenthRate = e.getTenthRate().getPennies();
      final int tenthsWorked = e.getTenthsWorked();
      final int straightTime = Math.min(400, tenthsWorked);
      final int overTime = Math.max(0, tenthsWorked - straightTime);
      final int straightPay = straightTime * tenthRate;
      final int overTimePay = (int) Math.round(overTime * tenthRate * 1.5);
      return new Money(straightPay + overTimePay);
  }
} </code></pre>
</li>
<li>위 calculateWeeklyPay 메소드는 HourlyEmployee 클래스에서 온갖 정보를 가져온다.</li>
<li>즉, calculateWeeklyPay 메소드는 HourlyEmployee 클래스의 범위를 욕심낸다.</li>
</ul>
<h3 id="g15-선택자-인수">G15: 선택자 인수</h3>
<ul>
<li>인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다</li>
</ul>
<h3 id="g16-모호한-의도">G16: 모호한 의도</h3>
<ul>
<li>코드를 짤 때는 의도를 최대한 분명히 밝힌다<ul>
<li>행을 바꾸지 않고 표현한 수식, 매직 번호 등등은 저자의 의도를 흐린다</li>
</ul>
</li>
</ul>
<h3 id="g17-잘못-지운-책임">G17: 잘못 지운 책임</h3>
<ul>
<li>독자가 자연스럽게 기대할 위치에 코드를 배치한다</li>
</ul>
<h3 id="g18-부적절한-static-함수">G18: 부적절한 static 함수</h3>
<ul>
<li>Math.max(double a, double b)는 좋은 static 메소드다</li>
<li>그러나 아래 함수는 static으로 정의하면 안되는 함수다</li>
<li>왜인가?<pre><code class="language-java">HourlyPayCalculator.calculatePay(employee, overTimeRate)</code></pre>
</li>
<li>함수를 재정의할 가능성이 존재하기 때문이다</li>
<li>수당을 계산하는 알고리즘이 여러 개일지도 모른다</li>
<li>일반적으로 static 함수보다 인스턴스 함수가 더 좋다</li>
<li>조금이라도 의심스럽다면 인스턴스 함수로 정의한다.</li>
<li>반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다</li>
</ul>
<h3 id="g19-서술적-변수">G19: 서술적 변수</h3>
<ul>
<li>프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 것이다<pre><code class="language-java">Matcher match = headerPattern.matcher(line);
if (match.find()) {
  String key = match.group(1);
  String value = match.group(2);
}</code></pre>
</li>
</ul>
<h3 id="g20-이름과-기능이-일치하는-함수">G20: 이름과 기능이 일치하는 함수</h3>
<ul>
<li>이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면
더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야한다</li>
</ul>
<h3 id="g21-알고리즘을-이해하라">G21: 알고리즘을 이해하라</h3>
<ul>
<li>대다수 괴상한 코드는 사람들이 알고리즘을 충분히 이해하지 않은 채 코드를 구현한 탓이다</li>
<li>잠시 멈추고 실제 알고리즘을 고민하는 대신 여기저기 if문과 플래그를 넣어보며 코드를 돌리는 탓이다</li>
<li>프로그래밍은 흔히 탐험인데 사실 코드가 &#39;돌아갈&#39; 때까지 이리저리 찔러보고 굴려본다</li>
<li>&#39;돌아간다&#39;는 사실이 중요한게 아니라 작성자가 알고리즘이 올바르다는 사실을 알아야 한다</li>
<li>가장 좋은 방법은 함수를 깔끔하고 명확하게 구성하는 것이다</li>
</ul>
<h3 id="g22-논리적-의존성은-물리적으로-드러내라">G22: 논리적 의존성은 물리적으로 드러내라</h3>
<ul>
<li>한 모듈이 다른 모률에 의존한다면 물리적인 의존성도 있어야 한다</li>
<li>논리적인 의존성만으로는 부족하다</li>
<li>의존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면 안 된다</li>
<li>의존하는 모든 정보를 명시적으로 요청하는 편이 좋다</li>
</ul>
<h3 id="g23-ifelse-혹은-switchcase-보다는-다형성을-사용하라">G23: if/else 혹은 switch/case 보다는 다형성을 사용하라</h3>
<ul>
<li>선택 유형 하나에는 switch 문을 한 번만 사용한다</li>
<li>같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다</li>
</ul>
<h3 id="g24-표준-표기법을-따르라">G24: 표준 표기법을 따르라</h3>
<ul>
<li>팀은 업계 표준에 기반한 구현 표준을 따라야 한다</li>
<li>표준을 설명하는 문서는 코드 자체로 충분해야 하며 별도 문서를 만들 필요는 없어야 한다</li>
<li>팀이 정한 표준은 팀원 모두가 따라야 한다</li>
<li>실제 괄호를 넣는 위치는 중요 하지 않다</li>
<li>모두가 동의한 위치에 넣는다는 사실이 중요하다</li>
<li>이 사실을 이해할 정도로 팀원들이 성숙해야 한다</li>
</ul>
<h3 id="g25-매직-숫자는-명명된-상수로-교체하라">G25: 매직 숫자는 명명된 상수로 교체하라</h3>
<ul>
<li>매직숫자는 상수로 변경하라</li>
<li>어떤 상수는 이해하기 쉬우므로, 코드 자체가 자명하다면, 상수 뒤로 숨길 필요가 없다</li>
<li>하단 예제에 TWO (int 2)라는 상수가 반드시 필요할까? 어떤 공식은 그냥숫자를 쓰는 편이 훨씬 좋다<pre><code class="language-java">double circumference = radius * Math.PI * 2;</code></pre>
</li>
<li>매직 숫자라는 용어는 단지 숫자만 의미하지 않는다</li>
<li>의미가 분명하지 않은 토큰을 모두 가리킨다</li>
</ul>
<h3 id="g26-정확하라">G26: 정확하라</h3>
<ul>
<li>검색 결과 중 첫 번째 결과만 유일한 결과로 간주하는 행동은 순진하다</li>
<li>코드에서 뭔가를 결정할 때는 정확히 결정한다</li>
<li>결정을 내리는 이유, 예외 처리 방법을 분명히 알아야한다</li>
<li>호출하는 함수가 null을 반환할지도 모른다면 null을 반드시 점검한다</li>
<li>코드에서 모호성과 부정확은 제거해야 마땅하다</li>
</ul>
<h3 id="g27-관례보다-구조를-사용하라">G27: 관례보다 구조를 사용하라</h3>
<ul>
<li>설계 결정을 강제할 때는 규칙보다 관례를 사용한다</li>
<li>명명 관례도 좋지만 구조 자체로 강제하면 더 좋다</li>
<li>enum 변수가 멋진 switch/case 문보다 추상 메서드가 있는 기초 클래스가 더 좋다</li>
</ul>
<h3 id="g28-조건을-캡슐화하라">G28: 조건을 캡슐화하라</h3>
<ul>
<li>조건의 의도를 분명히 밝히는 함수로 표현하라<pre><code class="language-java">// Bad
if (timer.hasExpired() &amp;&amp; !timer.isRecurrent())
</code></pre>
</li>
</ul>
<p>// Good
if (shouldBeDeleted(timer))</p>
<pre><code>### G29: 부정 조건은 피하라
* 부정 조건은 긍정 조건보다 이해하기 어렵다
* 가능하면 긍정 조건으로 표현한다

### G30: 함수는 한 가지만 해야 한다
* 함수를 짜다보면 한 함수 안에 여러 단락을 이어, 일련의 작업을 수행하고픈 유혹에 빠진다
* 한 가지만 수행하는 좀 더 작은 함수 여렷으로 나눠야 마땅하다
```java
// bad
public void pay() {
  for (Employee e : employees) {
    if (e.isPayday()) {
      Money pay = e.calculatePay();
      e.deliveryPay(pay);
    }
  }
}

// good
public void pay() {
  for (Employee e : employees) {
    payIfNeessary(e);
  }
}

public void payIfNecessary(Employee e) {
  if (e.isPayday()) {
    cacluateAndDeliverPay(e);
  }
}

private void calculateAndDeliveryPay(Employee e) {
  Money pay = e.calculatePay();
  e.deliverPay(pay);
}</code></pre><h3 id="g31-숨겨진-시간적인-결합">G31: 숨겨진 시간적인 결합</h3>
<ul>
<li>시간적인 결합을 숨겨서는 안 된다</li>
<li>함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다<pre><code class="language-java">// Bad, 세 함수가 실행되는 순서가 중요하다
public class MoogDriver {
public void dive(String reason) {
  staturateGradient();
  reticulateSplines();
  diveForMoog(reason);
}
} 
</code></pre>
</li>
</ul>
<p>// Good, 일종의 연결소자를 생성해 시간적인 결합을 노출한다
public class MoogDriver {
  public void dive(String reason) {
    Gradient gradient = staturateGradient();
    List<Spline> splines = reticulateSplines(gradient);
    diveForMoog(splines, reason);
  }
}</p>
<pre><code>* 위에서 인스턴스 변수를 그대로 두었다는 사실에 주목한다
* 해당 클래스의 private 메서드에 필요한 변수일지도 모른다
* 그렇다 치더라도 제자리를 찾은 변수들이 시간적인 결합을 좀 더 명백히 드러낼 것이다

### G32: 일관성을 유지하라
* 코드 구조를 잡을 때는 이유를 고민해야한다
* 그리고 그 이유를 코드 구조로 명백히 표현해야한다
* 구조에 일관성이 없어 보인다면 남들이 맘대로 바꿔도 괜찮다고 생각한다
* 시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다

### G33: 경계 조건을 캡슐화하라
* 경계 조건은 빼먹거나 놓치기 십상이다
* 경계 조건은 한 곳에서 별도로 처리한다
* 코드 여기저기에서 처리하지 않는다
* 다시 말해, 코드 여기저기에 +1이나 -1 을 흩어놓지 않는다
```java
// Bad
if (level + 1 &lt; tags.length) {
  parts = new Parse(body, tags, level + 1);
  body = null
}

// Good, 경계 조건 변수로 캡슐화
int nextLevel = level + 1;
if (nexLevel &lt; tags.length) {
  parts = new Parse(body, tags, nextLevel);
  body = null
}</code></pre><h3 id="g34-함수는-추상화-수준을-한-단계만-내려가야-한다">G34: 함수는 추상화 수준을 한 단계만 내려가야 한다</h3>
<ul>
<li>함수내 모든 문장은 추상화 수준이 동일해야한다</li>
<li>그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다</li>
<li>개념은 아주 간단하지만 인간은 추상화 수준을 뒤섞는 능력이 너무나도 뛰어나다</li>
<li>추상화 수준 분리는 리팩터링을 수행하는 가장 중요한 이유 중 하나다</li>
</ul>
<h3 id="g35-설정-정보는-최상위-단계에-둬라">G35: 설정 정보는 최상위 단계에 둬라</h3>
<ul>
<li>추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨기면 안된다</li>
<li>대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다</li>
</ul>
<h3 id="g36-추이적-탐색을-피하라">G36: 추이적 탐색을 피하라</h3>
<ul>
<li>일반적으로 한 모듈은 주변 모듈을 모를수록 좋다</li>
<li>A가 B를 사용하고 B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다는 뜻이다</li>
<li>이를 디미터의 법칙(Law of Demeter)이라 부른다</li>
<li>실용주의 프로그래머들은 부끄럼 타는 코드 작성 이라고도 한다</li>
<li>요지는 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다</li>
</ul>
<h2 id="자바">자바</h2>
<h3 id="j1-긴-import-목록을-피하고-와일드카드를-사용하라">J1: 긴 import 목록을 피하고 와일드카드를 사용하라</h3>
<ul>
<li>패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오라</li>
<li>와일드카드 import 문은 때로 이름 충돌이나 모호성을 초래한다</li>
<li>다소 번거롭지만 자주 발생하지 않으므로 여전히 와일드카드 import 문이 명시적인 import 문보다 좋다</li>
</ul>
<h3 id="j2-상수는-상속하지-않는다">J2: 상수는 상속하지 않는다</h3>
<ul>
<li>어떤 프로그래머는 상수를 인터페이스에 넣은 다음 그 인터페이스를 상속해 해당 상수를 사용한다<pre><code class="language-java">public class HourlyEmployee extends Employee {
int str = Math.min(tents, TENTS_PER_WEEK); // 이 상수는 어디에서 왔을까?
}
</code></pre>
</li>
</ul>
<p>public abstract class Employee implements Payroll {
}</p>
<p>public interface Payroll {
  public static final int TENTS_PER_WEEK = 400; // 이렇게 사용하면 안된다
}</p>
<pre><code>* 상수를 상속 계층 맨 위에 숨겨놨다
* 언어의 범위 규칙을 속이는 행위다
* 대신 static import 를사용하라

### J3: 상수 대 Enum
* 자바 5는 enum을 제공한다
* public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다
* int 보다 훨씬 더 유연하고 서술적인 강력한 도구다

## 이름
### N1: 서술적인 이름을 사용하라
* 이름은 성급하게 정하지 않고 서술적인 이름을 신중하게 고른다
* 단순히 ‘듣기 좋은’ 충고가 아니다 
- 소프트웨어 가독성의 90%는 이름이 결정 한다
* 신중하게 선택한 이름은 추가 설명을 포함한 코드보다 강력하다

### N2: 적절한 추상화 수준에서 이름을 선택하라
* 구현을 드러내는 이름은 피하라
* 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라
* 코드를 살펴볼 때마다 추상화 수준이 너무 낮은 변수 이름을 발견 하리라 발견할 때마다 기회를 잡아 바꿔놓아야 한다
* 안정적인 코드를 만들려면 지속적인 개선과 노력이 필요하다

### N3: 가능하다면 표준 명명법을 사용하라
* 기존 명명법을 사용하는 이름은 이해하기 더 쉽다
* DECORATOR 패턴을 활용 한다면 장식하는 클래스 이름에 Decorator 라는 단어를 사용해야 한다
* ToString 과 같이 관례가 존재하는 이름은 관례를 따른다
* 프로젝트에 유효한 의미가 담긴 이름을 많이 사용할수록 독자가 코드를 이해하기 쉬워진다

### N4: 명확한 이름
* 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다
* 길더라도 명확하고 서술적으로 지어야 한다

### N5: 긴 범위는 긴 이름을 사용하라
* 이름 길이는 범위 길이에 비례해야 한다
* 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다
* 하지만 범위가 길어지면 긴 이름을 사용한다
* 이름 범위가 길수록 이름을 정확하고 길게 짓는다

### N6: 인코딩을 피하라
* 이름에 유형 정보나 범위 정보를 넣어서는 안 된다

### N7: 이름으로 부수 효과를 설명하라
* 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다
* 이름에 부수 효과를 숨기지 않는다
* 실제로 여러 작업을 수행하는 함수에다 동사 하나만 달랑 사용하면 곤란하다
```java
// Bad
getOos();

// Good, 없으면 새로운 객체를 만드는 메서드
createOrReturnOos();</code></pre><h2 id="테스트">테스트</h2>
<h3 id="t1-불충분한-테스트">T1: 불충분한 테스트</h3>
<ul>
<li>테스트 케이스는 몇 개나 만들어야 충분할까?</li>
<li>잠재적으로 깨질 만한 부분을 모두 테스트해야 한다</li>
<li>테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다</li>
</ul>
<h3 id="t2-커버리지-도구를-사용하라">T2: 커버리지 도구를 사용하라</h3>
<ul>
<li>커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다</li>
<li>대다수 IDE는 테스트 커버리지를 시각적으로 표현한다</li>
</ul>
<h3 id="t3-사소한-테스트를-건너뛰지-마라">T3: 사소한 테스트를 건너뛰지 마라</h3>
<ul>
<li>사소한 테스트는 짜기 쉽다</li>
<li>사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다</li>
</ul>
<h3 id="t4-무시한-테스트는-모호함을-뜻한다">T4: 무시한 테스트는 모호함을 뜻한다</h3>
<ul>
<li>때로는 요구사항이 불분명하기에 프로그램이 돌아가는 방식을 확신하기 어렵다</li>
<li>선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다</li>
</ul>
<h3 id="t5-경계-조건을-테스트하라">T5: 경계 조건을 테스트하라</h3>
<ul>
<li>경계 조건은 각별히 신경 써서 테스트한다</li>
<li>알고리즘의 중앙 조건은 올바로 놓고 경계 조건에서 실수하는 경우가 흔하다</li>
</ul>
<h3 id="t6-버그-주변은-철저히-테스트하라">T6: 버그 주변은 철저히 테스트하라</h3>
<ul>
<li>버그는 서로 모이는 경향이 있다</li>
<li>한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다</li>
<li>십중팔구 다른 버그도 발견한다</li>
</ul>
<h3 id="t7-실패-패턴을-살펴라">T7: 실패 패턴을 살펴라</h3>
<ul>
<li>때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다</li>
<li>테스트 케이스를 최대한 꼼꼼히 짜라는 이유도 여기에 있다</li>
<li>합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다</li>
</ul>
<h3 id="t8-테스트-커버리지-패턴을-살펴라">T8: 테스트 커버리지 패턴을 살펴라</h3>
<ul>
<li>통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다</li>
</ul>
<h3 id="t9-테스트는-빨라야-한다">T9: 테스트는 빨라야 한다</h3>
<ul>
<li>느린 테스트 케이스는 실행하지 않게 된다</li>
<li>일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛴다</li>
<li>그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다</li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>이 장에서 소개한 휴리스틱과 냄새 목록이 완전하다 말하기는 어렵다</li>
<li>여기서 소개한 목록은 가치 체계를 피력할 뿐이다</li>
<li>사실상 가치 체계는 이 책의 주제이자 목표다</li>
<li>일련의 규칙만 따른다고 깨끗한 코드가 얻어지지 않는다</li>
<li>휴리스틱 목록을 익힌다고 소프트웨어 장인이 되지는 못한다</li>
<li>전문가 정신과 장인 정신은 가치에서 나온다</li>
<li>그 가치에 기반한 규율과 절제가 필요하다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>주석으로 변경 이력을 아주 간단히 달아놓는 경우도 많이 봤는데 이건 약간의 개인차가 있을 것 같다는 생각이 들었다. 변경이 많이 혹은 자주 되는 경우에는 소스코드로 이력을 다 확인하기가 쉽지 않기 때문에 힌트를 남겨놓는 것은 괜찮지 않을까 싶은데 이런 경우에는 차라리 변경 이력에 대한 문서화를 하는게 낫겠다는 결론에 다다랐다. 복잡한 시스템의 경우 실제로는 어떻게 하고 있는지 궁금해졌다.</li>
<li>지금까지 책에서 나온 것들을 정리해놨기 때문에 위에 나오는 휴리스틱에 주의한다면 한결 더 나은 코드를 짤 수 있지 않을까 싶었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[16 serialDate 리팩터링]]></title>
            <link>https://velog.io/@seunghee-ryu/16-SerialDate-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@seunghee-ryu/16-SerialDate-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</guid>
            <pubDate>Sat, 30 Dec 2023 14:49:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/a73c9be9-da6c-4922-bac2-6f2bc9cbbea2/image.png" alt=""></p>
<ul>
<li>날짜를 표현하는 SerialDate 클래스를 살펴보고 리팩터링 한다</li>
</ul>
<h2 id="돌려보자">돌려보자</h2>
<ul>
<li>SerialDateTests라는 클래스가 있지만 모든 경우를 커버하지 않기 때문에 단위 테스트 케이스를 구현한다</li>
<li>새 단위 테스트는 92% 정도의 코드 커버리지를 가진다</li>
<li>테스트 케이스를 통해 일부를 변경한다</li>
</ul>
<h2 id="고쳐보자">고쳐보자</h2>
<h3 id="클래스-이름에-관한-고찰">클래스 이름에 관한 고찰</h3>
<ul>
<li><p>클래스 선언에 Serial이라는 단어가 들어가는 이유는 일련번호(serial number)를 사용해 클래스를 구현했기 때문이다</p>
</li>
<li><p>serialDate라는 이름이 서술적이지 못하다고 생각한다</p>
</li>
<li><p>SerialDate라는 이름은 구현을 암시하지만 실상은 추상 클래스이기 때문에 구현을 암시할 필요가 없다</p>
<pre><code> - 고민 끝에 DayDate라는 용어로 변경한다</code></pre><h3 id="monthconstants를-상속한다">MonthConstants를 상속한다</h3>
</li>
<li><p>MonthConstants 클래스는 달을 정의하는 static final 상수 모음</p>
</li>
<li><p>monthConstants는 enum으로 정의해야 마땅하다</p>
</li>
<li><p>달을 int로 받던 메서드는 Month로 받도록 한다</p>
<pre><code class="language-java">public abstract class DayDate implements Comparable, Serializable {
  public static enum Month {
      JANUARY(1),
      FEBRUARY(2),
      MARCH(3),
      APRIL(4),
      MAY(5),
      JUNE(6),
      JULY(7),
      AUGUST(8),
      SEPTEMBER(9),
      OCTOBER(10),
      NOVEMBER(11),
      DECEMBER(12);

      Month(int index) {
          this.index = index;
      }

      public static Month make(int monthIndex) {
          for (Month m : Month.values()) {
              if (m.index == monthIndex)
                  return m;
          }
          throw new IllegalArgumentException(&quot;Invalid month index &quot; + monthIndex);
      }
      public final int index;
  }
}</code></pre>
</li>
</ul>
<h3 id="serialversionuid-변수는-직렬화를-제어한다">serialVersionUID 변수는 직렬화를 제어한다</h3>
<ul>
<li>자동 제어가 되지 않기 때문에 해당 변수를 당분간 삭제한다<h3 id="불필요한-주석은-삭제한다">불필요한 주석은 삭제한다</h3>
<h3 id="daydate-클래스가-표현할-수-있는-최초와-최후-날짜를-의미하는-변수를-좀-더-깔끔하게-표현하기-위해-아래와-같은-코드로-만들어준다">DayDate 클래스가 표현할 수 있는 최초와 최후 날짜를 의미하는 변수를 좀 더 깔끔하게 표현하기 위해 아래와 같은 코드로 만들어준다</h3>
</li>
</ul>
<pre><code class="language-java">public static final int EARLIEST_DATE_ORDINAL = 2;      // 1/1/1900
public static final int LATEST_DATE_ORDINAL = 2958465;  // 12/31/9999</code></pre>
<ul>
<li>EARLIEST_DATE_ORDINAL이 0이 아닌 2인 이유는 엑셀에서 날짜를 표시하는 방법과 관계있다고 하지만 이 부분이 SpreadsheetDate의 구현과 관련되지 DayDate와는 아무 상관이 없다</li>
<li>따라서 SpreadsheetDate 클래스로 옮겨져야 한다</li>
</ul>
<h3 id="450page-104행과-107행">450page 104행과 107행</h3>
<ul>
<li><p>MINIMUM_YEAR_SUPPORTED와 MAXIMUM_YEAR_SUPPORTED 두 변수를 살펴봤을 때 DayDate는 추상 클래스이므로 구체적인 구현 정보를 포함할 필요가 없어서 두 변수를 SpreadsheetDate 클래스로 옮기려고 했다</p>
</li>
<li><p>하지만 RleativeDayOfWeekRule 클래스에서도 두 변수를 getDate로 넘어온 인수 year가 올바른지 확인할 목적으로 사용함을 알 수 있었다</p>
</li>
<li><p>이에 따라 DayDate 자체를 훼손하지않으면서 구현 정보를 전달할 방법이 필요하다</p>
</li>
<li><p>살펴보면 getDate 메서드로 넘어오는 인수는 DayDate 인스턴스가 아니지만 getDate는 DayDate 인스턴스를 반환하는 것을 알 수 있고 이는 어디선가 인스턴스를 생성하는 부분이 존재함을 의미한다</p>
</li>
<li><p>일반적으로 기반 클래스는 파생 클래스를 모르는 것이 좋기 때문에 추상 팩토리 패턴을 적용하여 DayDate 인스턴스를 생성하는 DayDateFactory를 만들어 해결한다</p>
<pre><code class="language-java">public abstract class DayDateFactory {
  private static DayDateFactory factory = new SpreadsheetDateFactory();
  public static viud setInstance(DayDateFactory factory) {
      DayDateFactory.factory = factory;
  }

  protected abstract DayDate _makeDate(int ordinal);
  protected abstract DayDate _makeDate(int day, DayDate.Month month, int year);
  protected abstract DayDate _makeDate(int day, int month, int year);
  protected abstract DayDate _makeDate(java.util.Date date);
  protected abstract int _getMinimumYear();
  protected abstract int _getMaximumYear();

  public static DayDate makeDate(int ordinal) {
      return factory._makeDate(ordinal);
  }

  public static DayDate makeDate(int day, DayDate.Month month, int year) {
      return factory._makeDate(day, month, year);
  }

  public static DayDate makeDate(int day, int month, int year) {
      return factory._makeDate(day, month, year);
  }

  public static DayDate makeDate(java.util.Date date) {
      return factory._makeDate(date);
  }

  public static int getMinimumYear() {
      return factory._getMinimumYear();
  }

  public static int getMaximumYear() {
      return factory._getMaximumYear();
  }
}</code></pre>
</li>
<li><p>위 클래스에서 createInstance 메서드를 makeDate라고 좀 더 서술적으로 변경했다.</p>
<pre><code class="language-java">public class SpreadsheetDateFactory extends DayDateFactory {
  public DayDate _makeDate(int ordinal) {
      return new SpreadsheetDate(ordinal);
  }

  public DayDate _makeDate(int day, DayDate.Month month, int year) {
      return new SpreadsheetDate(day, month, year);
  }

  public DayDate _makeDate(int day, int month, int year) {
      rturn new SpreadsheetDate(day, month, year);
  }

  public DayDate _makeDate(Date date) {
      final GregorianCalendar calendar = new GregorianCalendar();
      calendar.setTime(date);
      return new SpreadsheetDate(
          calendar.get(Calendar.DATE),
          DayDate.Month.make(calendar.get(Calendar.MONTH) + 1),
          calendar.get(Calendar.YEAR));
  }

  protected int _getMinimumYear() {
      return SpreadsheetDate.MINIMUM_YEAR_SUPPORTED;
  }

  protected int _getMaximumYear() {
      return SpreadsheetDate.MAXIMUM_YEAR_SUPPORTED;
  }
}</code></pre>
</li>
<li><p>MINIMUM_YEAR_SUPPORTED와 MAXIMUM_YEAR_SUPPORTED 변수를 적절한 위치인 SpreadsheetDate로 옮긴다</p>
</li>
</ul>
<h3 id="해당-부분의-요일-상수들은-enum형이여야-하므로-이에-맞게-수정해준다">해당 부분의 요일 상수들은 enum형이여야 하므로 이에 맞게 수정해준다.</h3>
<h3 id="해당-부분의-변수-이름만으로-설명이-충분하기-때문에-주석-삭제">해당 부분의 변수 이름만으로 설명이 충분하기 때문에 주석 삭제</h3>
<ul>
<li>주석은 최소화 하는것이 좋다</li>
<li>적절하게 접근 제어자를 이용, 사용하지 않는 변수 제거</li>
<li>변수가 한 곳에서만 사용될 경우 사용 위치와 최대한 가깝게 옮긴다</li>
</ul>
<h3 id="달에서-주를-의미하는-부분을-아래와-같이-enum으로-변환한다">달에서 주를 의미하는 부분을 아래와 같이 enum으로 변환한다</h3>
<pre><code class="language-java">public enum WeekInMonth {
    FIRST(1), SECOND(2), THIRD(3), FOURTH(4), LAST(0);
    public final int index;

    WeekInMonth(int index) {
        this.index = index;
    }
}</code></pre>
<h3 id="모호한-상수를-수정한다">모호한 상수를 수정한다</h3>
<ul>
<li>INCLUDE_NONE, FIRST, SECOND, BOTH 상수는 수학적으로 개구간, 반개구간, 폐구간을 의미하기 때문에 이를 더 잘 나타낼 수 있도록 enum 이름을 DateInterval로 결정하고 CLOSED, CLOSED_LEFT, CLOSED_RIGHT, OPEN으로 수정한다</li>
</ul>
<h3 id="enum-형태-적용">enum 형태 적용</h3>
<ul>
<li>주어진 날짜를 기준으로 특정 요일을 계산할때 사용되는 상수를 enum으로 변경한다</li>
<li>직전 요일은 LAST, 다음 요일은 NEXT, 가까운 요일은 NEAREST로 정의하고 enum 이름을 WeekdayRange로 한다</li>
</ul>
<h3 id="불필요한-부분을-삭제한다">불필요한 부분을 삭제한다</h3>
<ul>
<li>사용하지 않는 변수와 기본 생성자, 주석, final 키워드 등을 삭제한다</li>
</ul>
<h3 id="for-루프안에-if문-두번을--연산자를-이용해-if문을-하나로-만든다">for 루프안에 if문 두번을 || 연산자를 이용해 if문을 하나로 만든다</h3>
<h3 id="weekdaycodetostring-메서드를-day로-옮기고-tosring이라-명명한다">weekDayCodeToString 메서드를 Day로 옮기고 toSring이라 명명한다</h3>
<ul>
<li><p>weekDayCodeToString : 넘어온 주 중 일자를 표현하는 문자열 반환</p>
<pre><code class="language-java">public enum Day {
  MONDAY(Calendar.MONDAY),
  TUESDAY(Calendar.TUESDAY),
  WEDNESDAY(Calendar.WEDNESDAY),
  THURSDAY(Calendar.THURSDAY),
  FRIDAY(Calendar.FRIDAY),
  SATURDAY(Calendar.SATURDAY),
  SUNDAY(Calendar.SUNDAY);

  public final int index;
  private static DateFormatSymbols dateSymbols = new DateFormatSymbols();

  Day(int day) {
      index = day;
  }

  public static Day make(int index) throws IllegalArgumentException {
      for (Day d : Day.values())
          if (d.index == index)
              return d;
      throw new IllegalArgumentException(
          String.format(&quot;Illegal day index: %d.&quot;, index));
  }

  public static Day parse(String s) throws IllegalArgumentException {
      String[] shortWeekdayNames = 
          dateSymbols.getShortWeekdays();
      String[] weekDayNames =
          dateSymbols.getWeekdays();

      s = s.trim();
      for (Day day : Day.values()) {
          if (s.equalsIgnoreCase(shortWeekdayNames[day.index])) ||
              s.equalsIgnoreCase(weekDayNames[day.index])) {
              return day;
          }
      }
      throw new IllegalArgumentException(
          String.format(&quot;%s is not a valid weekday string&quot;, s));
  }

  public String toString() {
      return dateSymbols.getWeekdays()[index];
  }
}</code></pre>
<h3 id="이름을-서술적으로-변경한다">이름을 서술적으로 변경한다</h3>
</li>
<li><p>getMonths라는 메서드 두 개에서 첫 번째가 두 번째 getMonths를 호출한다</p>
</li>
<li><p>두 번째 getMonths를 호출하는 메서드는 첫 번째 getMonths 메서드 뿐이기 때문에 이 둘을 합쳐 단순화 하고, 이름을 좀 더 서술적으로 변경하였다</p>
<pre><code class="language-java">public static String[] getMonthNames() {
  return dateFormatSymbols.getMonth();
}</code></pre>
<h3 id="isvalidmonthcode-메서드는-month-enum을-만들면서-불필요해졌기-때문에-삭제한다">isValidMonthCode 메서드는 Month enum을 만들면서 불필요해졌기 때문에 삭제한다</h3>
</li>
</ul>
<h3 id="메서드를-추가한다">메서드를 추가한다</h3>
<ul>
<li>monthCodeToQuarter 메서드는 기능 욕심으로 보인다</li>
<li>이는 뚜렷한 목적없이 상수를 나열하고 return 하는 방식으로 좋지 못한 방법이다 - 따라서 아래와 같은 메서드를 Month에 추가해주었다<pre><code class="language-java">public int quarter() {
  return 1 + (index-1)/3;
}</code></pre>
</li>
<li>이렇게 수정하게 되면 Month가 너무 커지게 되므로 Day enum과 일관성을 유지하도록 DayDate에서 분리해 독자적인 파일로 만들어 준다.</li>
</ul>
<h3 id="인수로-플래그를-보내지-않도록-변경한다">인수로 플래그를 보내지 않도록 변경한다</h3>
<ul>
<li>monthCodeToString이라는 메서드 두 개가 나오는데 한 메서드가 다른 메서드를 호출하며 플래그를 넘긴다</li>
<li>메서드 인수로 플래그는 바람직하지 못하기 때문에 이름을 변경하고 단순화하여 Month enum으로 옮긴다<pre><code class="language-java">public String toString() {
  return dateFormatSymbols.getMonths()[index - 1]; // return monthCodeTodString(month, false)
}
</code></pre>
</li>
</ul>
<p>public String toShortString() {
    return dateFormatSymbols.getShortMonths()[index - 1];
}</p>
<pre><code>### 단순화한다
- 문자열을 달 코드로 반환하는 stringToMonthCode 메서드의 이름을 변경하고 앞선 과정들처럼 Month enum으로 옮기고 단순화 하는 과정을 거친다
```java
public static Month parse(String s) {
    s = s.trim();
    for (Month m : Month.values())
        if (m.matches(s))
            return m;

    try {
        return make(Integer.parseInt(s));
    }
    catch (NumberFormatException e) {}
    throw new IllegalArgumentException(&quot;Invalid month &quot; + s);
}

private boolean matches(String s) {
    return s.equalsIgnoreCase(toString()) ||
        s.equalsIgnoreCase(toShortString());
}</code></pre><h3 id="넘어온-년도가-윤년인지-확인하는-isleapyear-메서드는-더-서술적인-표현을-이용하여-수정해준다">넘어온 년도가 윤년인지 확인하는 isLeapYear 메서드는 더 서술적인 표현을 이용하여 수정해준다</h3>
<pre><code class="language-java">public static boolean isLeapYear(int year) {
    boolean fourth = year % 4 == 0;
    boolean hundredth = year % 100 == 0;
    boolean fourHundredth = year % 400 == 0;
    return fourth &amp;&amp; (!hundreth || fourHundredth);
}</code></pre>
<h3 id="윤년-횟수를-반환해주는-leapyearcount는-daydate에-속하지-않기-때문에-분리해준다">윤년 횟수를 반환해주는 leapYearCount는 DayDate에 속하지 않기 때문에 분리해준다</h3>
<h3 id="단순화한다">단순화한다</h3>
<ul>
<li><p>윤년을 고려해 마지막 일자를 반환하는 메서드인 lastDayOfMonth 에서 사용하는 LAST_DAY_OF_MONTH 배열은 Month enum에 속하므로 메서드도 여기로 옮기고 단순하게 고쳐주었다.</p>
<pre><code class="language-java">public static int lastDayOfMonth(Month month, int year) {
  if (month == Month.FEBRUARY &amp;&amp; isLeapYear(year))
      return month.lastDay() + 1;
  else
      return month.lastDay();
}</code></pre>
<h3 id="재정의-가능성이-있는-함수는-인스턴스-메서드로-변경한다">재정의 가능성이 있는 함수는 인스턴스 메서드로 변경한다</h3>
</li>
<li><p>받아온 기준 날짜에 넘어온 일자를 더해 새로운 날짜를 만들어주는 addDays 메서드에서 DayDate 변수를 사용하는데 해당 함수를 재정의할 가능성이 있기 때문에 인스턴스 메서드로 변경한다</p>
</li>
<li><p>해당 메서드가 호출하는 toSerial 메서드의 이름을 toOrdinal로 변경하고 단순화 한다.</p>
<pre><code class="language-java">public DayDate addDays(int days) {
  return DayDateFactory.makeDate(toOrdinal() + days);
}</code></pre>
</li>
<li><p>기준 날짜에 넘어온 달을 더해 새로운 날짜를 만드는 addMonths 메서드 에서도 위와 같이 인스턴스 메서드로 변경해주고, 읽기 쉬운 형태로 수정하였다.</p>
<pre><code class="language-java">public DayDate addMonths(int months) {
  int thisMonthAsOrdinal = 12 * getYear() + getMonth().index - 1;
  int resultMonthAsOrdinal = thisMonthAsOrdinal + months;
  int resultYear = resultMonthAsOrdinal / 12;
  Month resultMonth = Month.make(resultMonthAsOrdinal % 12 + 1);

  int lastDayOfResultMonth = lastDayOfMonth(resultMonth, resultYear);
  int resultDay = Math.min(getDayOfMonth(), lastDayOfResultMonth);
  return DayDateFactory.makeDate(reusltDay, resultMonth, resultYear);
}</code></pre>
</li>
<li><p>이렇게 정적 메서드를 인스턴스 메서드로 바꾸게 되면 date.addDays(5)라는 표현이 date 객체를 변경하지않고 새 DayDate 인스턴스를 반환한다는 사실을 나타낼 수 있는지 여부가 불확실 하다</p>
<pre><code class="language-java">DayDate date = DateFactory.makeDate(5, Month.DECEMBER, 1952);
date.addDays(7); // 날짜를 일주일만큼 뒤로 미룬다.</code></pre>
</li>
<li><p>위와 같이 코드를 작성하게 되면 addDays가 date객체를 변경했다고 생각할 수 있기 때문에 이를 해결하고자 이름을 변경해준다</p>
<pre><code class="language-java">DayDate date = oldDate.plusDays(5);</code></pre>
<h3 id="인스턴스-메서드-변경과-중복-제거를-한다">인스턴스 메서드 변경과 중복 제거를 한다</h3>
</li>
<li><p>넘어온 주 중 일자 범위에 해당하면서 기준 날짜보다 빠른 마지막 날짜를 반환하는 getPreviousDayOfWeek 함수를 단순화하고 임시 변수 설명을 이용해 가독성을 높인다</p>
</li>
<li><p>인스턴스 메서드로 변경하고 중복된 부분을 제거한다</p>
</li>
<li><p>마찬가지로 getFollowingDayOfWeek 도 구조가 같기 때문에 같은 방법으로 수정해준다.</p>
</li>
<li><p>또한 getNearestDayOfWeek 메서드도 위의 두 메서드를 수정한것과 같은 구조를 가지도록 고쳐준다.</p>
<pre><code class="language-java">public DayDate getPreviousDayOfWeek(Day targetDayOfWeek) {
  int offsetToTarget = targetDayOfWeek.index - getDayOfWeek().index;
  if (offsetToTarget &gt;= 0)
      offsetToTarget -= 7;
  return plusDays(offsetToTarget);
}
</code></pre>
</li>
</ul>
<p>public DayDate getFollowingDayOfWeek(Day targetDayOfWeek) {
    int offsetToTarget = targetDayOfweek.index - getDayOfWeek().index;
    if (offsetToTarget &lt;= 0)
        offsetToTarget += 7;
    return plusDays(offsetToTarget);
}</p>
<p>public DayDate getNearestDayOfWeek(final Day targetDay) {
    int offsetToThisWeeksTarget = targetDay.index - getDayOfWeek().index;
    int offsetToFutureTarget = (offsetToThisWeeksTarget + 7) % 7;
    int offsetToPreviousTarget = offsetToFutureTarget - 7;
    if (offsetToFutureTarget &gt; 3)
        return plusDays(offsetToPreviousTarget);
    else
        return plusDays(offsetToFutureTarget);
}</p>
<pre><code>- 현재 달의 마지막 일자를 반환해주는 getEndOfCurrentMonth 메서드를 인스턴스 메서드로 고치고 이름을 수정해준다.
```java
public DayDate getEndOfMonth() {
    Month month = getMonth();
    int year = getYear();
    int lastDay = lastDayOfMonth(month, year);
    return DayDateFactory.makeDate(lastDay, month, year);
}</code></pre><h3 id="불필요한-코드는-삭제한다">불필요한 코드는 삭제한다</h3>
<ul>
<li>월 중 주를 반환해주는 함수인 weekInMonthToString과 relativeToString 메서드는 테스트 케이스 이외에 다른 코드를 호출하지 않기 때문에 해당 메서드와 테스트케이스를 삭제한다</li>
</ul>
<h3 id="추상-클래스-daydate의-추상-메서드를-살펴본다">추상 클래스 DayDate의 추상 메서드를 살펴본다</h3>
<ul>
<li><p>toSerial 메서드 : 날짜에 대한 직렬번호 반환</p>
<pre><code>- 처음에 toOrdinal로 변경했지만 getOrdinalDay가 더 적합한 이름이라 판단하여 변경해준다.</code></pre></li>
<li><p>toDate 메서드 : DayDate를 java.util.date 반환</p>
<pre><code>- SpreadsheetDate에서 구현한 toDate를 살펴보면 메서드는 SpreadsheetDate에 의존하지 않기 때문에 해당 코드를 DayDate 메서드로 옮겨 준다</code></pre></li>
<li><p>getDayOfWeek 도 SpreadsheetDate 구현에 의존성을 갖는지 확인해본다       - getDayOfWeek 함수는 SpreadsheetDate의 서수 날짜 시작 요일에 논리적 의존성을 가지기 때문에 이 부분은 DayDate 메서드로 옮길 수 없다</p>
<pre><code>- 이를 대신하여 물리적 의존성을 가질 수 있도록 DayDate에 getDayOfWeekForOrdinalZerof라는 추상 메서드를 구현하고 SpreadsheetDate에서 Day.SATURDAY를 반환하도록 구현해준다
- 논리적 의존성 : 다른 함수에서 사용하는 변수를 사용하여 의존성을 갖는 것
- 물리적 의존성 : 변수 대신 함수를 이용해서 함수 간 데이터를 공유하는 것</code></pre><pre><code class="language-java">public Day getDayOfWeek() {
  Day startingDay = getDayOfWeekForOrdinalZero(); // 물리적 의존성
  int startingOffset = startingDay.index - Day.SUNDAY.index;
  return Day.make((getOrdinalDay() + startingOffset) % 7 + 1);
}</code></pre>
</li>
<li><p>getDayOfWeek 메서드를 DayDate로 옮긴 후 getOrdinalDay와 getDayOfWeekForOrdinalZero를 호출하도록 변경해 줄 수 있게 된다</p>
</li>
<li><p>위와 같은 방법으로 compare 메서드도 DayDate로 옮겨주고 불분명한 이름을 변경하고 이에 맞는 테스트 케이스를 추가하는 리팩토링을 진행한다</p>
</li>
<li><p>isInRange 함수도 DayDate로 끌어올리고 if문 연쇄 대신에 enum 형식을 취해 if문 연쇄를 삭제해준다</p>
<pre><code class="language-java">public enum DateInterval {
  OPEN {
      public boolean isIn(int d, int left, int right) {
          return d &gt; left &amp;&amp; d &lt; right;
      }
  },
  CLOSED_LEFT {
      public boolean isIn(int d, int left, int right) {
          return d &gt;= left &amp;&amp; d &lt; right;
      }
  },
  CLOSED_RIGHT {
      public boolean isIn(int d, int left, int right) {
          return d &gt; left &amp;&amp; d &lt;= right;
      }
  },
  CLOSED {
      public boolean isIn(int d, int left, int right) {
          return d &gt;= left &amp;&amp; d &lt;= right;
      }
  };

  public abstract boolean isIn(int d, int left, int right);
}
</code></pre>
</li>
</ul>
<p>public boolean isInRange(DayDate di, DayDate d2, DateInterval interval) {
    int left = Math.min(d1.getOrdinalDay(), d2.getOrdinalDay());
    int right = Math.max(d1.getOrdinalDay(), d2.getOrdinalDay());
    return interval.isIn(getOrdinalDay(), left, right);
}</p>
<pre><code>
## 결론
- 너무 오래된 주석을 간단하게 고치고 개선한다
- enum을 모두 독자적인 소스 파일로 옮긴다
- 정적 변수와 정적 메서드를 새 클래스로 이동시킨다
- 일부 추상 메서드를 DayDate 클래스로 끌어올린다
- Month.make를 Month.fromInt로 변경하고 다른 enum도 똑같이 변경한다
      - 모든 enum에 toInt() 접근자를 생성하고 index 필드를 private로 정의한다
- 새 메서드를 생성하고 중복을 삭제한다
- 여기저기서 사용되던 숫자 1을 삭제했다
- 테스트 커버리지 수치가 낮아지게 되었다

### 보이스카우트 규칙을 따라 체크아웃한 코드보다 좀 더 깨끗한 코드를 체크인하게 되었다

### 개인적인 감상
- 처음에 import 문은 java.text.*와 java.util.*로 줄여도 된다는 말이 나와서 모든 것을 임포트하면 느려지지 않을까 하는 의문이 생겼다
      - 컴파일을 할 때엔 느려질 수 있으나 런타임 시에는 속도에 영향을 주지 않는 것으로 판단된다
- 테스트 커버리지를 100%로 만들지 않아도 된다는 것을 알 수 있었다
      - 테스트 커버리지 100%를 맞추려고 하다보면 테스트를 통과하기 위한 코드를 짜게 될 가능성이 있다
      - 테스트 때문에 로직을 변경하는 것은 바람직하지 않다</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[15 JUnit 들여다보기]]></title>
            <link>https://velog.io/@seunghee-ryu/15-JUnit-%EB%93%A4%EC%97%AC%EB%8B%A4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@seunghee-ryu/15-JUnit-%EB%93%A4%EC%97%AC%EB%8B%A4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 27 Dec 2023 13:35:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/34c1d05e-7875-4eff-9f7e-d19f27581fc7/image.png" alt=""></p>
<ul>
<li>자바 프레임워크 중에서 가장 유명한 jUnit 프레임워크에서 가져온 코드를 평가한다</li>
</ul>
<h2 id="junit-프레임워크">JUnit 프레임워크</h2>
<ul>
<li>JUnit은 저자가 많지만 시작은 켄트 벡과 에릭 감마이다</li>
<li>ComparisonCompactorTest.java</li>
<li>두 문자열을 받아 차이를 반환하는 코드</li>
</ul>
<pre><code class="language-java">package junit.tests.framework;

import junit.framework.ComparisonCompactor;
import junit.framework.TestCase;

public class ComparisonCompactorTest extends TestCase {

    public void testMessage() {
        String failure = new ComparisonCompactor(0, &quot;b&quot;, &quot;c&quot;).compact(&quot;a&quot;);
        assertTrue(&quot;a expected:&lt;[b]&gt; but was:&lt;[c]&gt;&quot;.equals(failure));
    }

    public void testStartSame() {
        String failure = new ComparisonCompactor(1, &quot;ba&quot;, &quot;bc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;b[a]&gt; but was:&lt;b[c]&gt;&quot;, failure);
    }

    public void testEndSame() {
        String failure = new ComparisonCompactor(1, &quot;ab&quot;, &quot;cb&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[a]b&gt; but was:&lt;[c]b&gt;&quot;, failure);
    }

    public void testSame() {
        String failure = new ComparisonCompactor(1, &quot;ab&quot;, &quot;ab&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab&gt; but was:&lt;ab&gt;&quot;, failure);
    }

    public void testNoContextStartAndEndSame() {
        String failure = new ComparisonCompactor(0, &quot;abc&quot;, &quot;adc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[b]...&gt; but was:&lt;...[d]...&gt;&quot;, failure);
    }

    public void testStartAndEndContext() {
        String failure = new ComparisonCompactor(1, &quot;abc&quot;, &quot;adc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;a[b]c&gt; but was:&lt;a[d]c&gt;&quot;, failure);
    }

    public void testStartAndEndContextWithEllipses() {
        String failure = new ComparisonCompactor(1, &quot;abcde&quot;, &quot;abfde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...b[c]d...&gt; but was:&lt;...b[f]d...&gt;&quot;, failure);
    }

    public void testComparisonErrorStartSameComplete() {
        String failure = new ComparisonCompactor(2, &quot;ab&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab[]&gt; but was:&lt;ab[c]&gt;&quot;, failure);
    }

    public void testComparisonErrorEndSameComplete() {
        String failure = new ComparisonCompactor(0, &quot;bc&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[]...&gt; but was:&lt;[a]...&gt;&quot;, failure);
    }

    public void testComparisonErrorEndSameCompleteContext() {
        String failure = new ComparisonCompactor(2, &quot;bc&quot;, &quot;abc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[]bc&gt; but was:&lt;[a]bc&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches() {
        String failure = new ComparisonCompactor(0, &quot;abc&quot;, &quot;abbc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[]...&gt; but was:&lt;...[b]...&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatchesContext() {
        String failure = new ComparisonCompactor(2, &quot;abc&quot;, &quot;abbc&quot;).compact(null);
        assertEquals(&quot;expected:&lt;ab[]c&gt; but was:&lt;ab[b]c&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches2() {
        String failure = new ComparisonCompactor(0, &quot;abcdde&quot;, &quot;abcde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...[d]...&gt; but was:&lt;...[]...&gt;&quot;, failure);
    }

    public void testComparisonErrorOverlappingMatches2Context() {
        String failure = new ComparisonCompactor(2, &quot;abcdde&quot;, &quot;abcde&quot;).compact(null);
        assertEquals(&quot;expected:&lt;...cd[d]e&gt; but was:&lt;...cd[]e&gt;&quot;, failure);
    }

    public void testComparisonErrorWithActualNull() {
        String failure = new ComparisonCompactor(0, &quot;a&quot;, null).compact(null);
        assertEquals(&quot;expected:&lt;a&gt; but was:&lt;null&gt;&quot;, failure);
    }

    public void testComparisonErrorWithActualNullContext() {
        String failure = new ComparisonCompactor(2, &quot;a&quot;, null).compact(null);
        assertEquals(&quot;expected:&lt;a&gt; but was:&lt;null&gt;&quot;, failure);
    }

    public void testComparisonErrorWithExpectedNull() {
        String failure = new ComparisonCompactor(0, null, &quot;a&quot;).compact(null);
        assertEquals(&quot;expected:&lt;null&gt; but was:&lt;a&gt;&quot;, failure);
    }

    public void testComparisonErrorWithExpectedNullContext() {
        String failure = new ComparisonCompactor(2, null, &quot;a&quot;).compact(null);
        assertEquals(&quot;expected:&lt;null&gt; but was:&lt;a&gt;&quot;, failure);
    }

    public void testBug609972() {
        String failure = new ComparisonCompactor(10, &quot;S&amp;P500&quot;, &quot;0&quot;).compact(null);
        assertEquals(&quot;expected:&lt;[S&amp;P50]0&gt; but was:&lt;[]0&gt;&quot;, failure);
    }
}</code></pre>
<ul>
<li>ComparisonCompactor 모듈에 대한 코드 커버리지 분석 결과 100%<ul>
<li>테스트 케이스가 모든 if문, 모든 for문을 실행한다는 뜻</li>
</ul>
</li>
<li>ComparisonCompactor 모듈을 살펴보고 보이스카우트 규칙에 따라 코드를 개선한다</li>
</ul>
<h3 id="1-접두어-f를-제거한다">1. 접두어 f를 제거한다</h3>
<pre><code class="language-java">private int contextLength;
private String expected;
private String actual;
private int prefix;
private int suffix;</code></pre>
<h3 id="2-조건문을-캡슐화-한다">2. 조건문을 캡슐화 한다</h3>
<ul>
<li><p>즉 조건문을 메서드로 뽑아내 적절한 이름을 붙인다</p>
<pre><code class="language-java">public String compact(String message) {
  if (shouldNotCompact()) {
      return Assert.format(message, expected, actual);
  }

  findCommonPrefix();
  findCommonSuffix();
  String expected = compactString(this.expected);
  String actual = compactString(this.actual);
  return Assert.format(message, expected, actual);
}
</code></pre>
</li>
</ul>
<p>private boolean shouldNotCompact() {
    return expected == null || actual == null || areStringsEqual();
}</p>
<pre><code>
### 3. 이름을 명확하게 붙인다
- Compact 함수에서 사용하는 this.expected와 this.actual도 이미 지역변수가 있기 때문에 눈에 거슬린다
    - 이는 fExpected에서 f를 빼버리는 바람에 생긴 결과다
- 함수에서 멤버 변수와 이름이 똑같은 변수를 사용하는 이유는 무엇인가? 서로 다른 의미라면 이름은 명확하게 붙인다
```java
String compactExpected = compactString(expected);
String compactActual = compactString(actual);</code></pre><h3 id="4-부정-조건은-피한다">4. 부정 조건은 피한다</h3>
<ul>
<li>부정문은 긍정문보다 이해하기가 어렵다<pre><code class="language-java">public String compact(String message) {
  if (canBeCompacted()) {
      findCommonPrefix();
      findCommonSuffix();
      String compactExpected = compactString(expected);
      String compactActual = compactString(actual);
      return Assert.format(message, compactExpected, compactActual);
  } else {
      return Assert.format(message, expected, actual);
  }       
}
</code></pre>
</li>
</ul>
<p>private boolean canBeCompacted() {
    return expected != null &amp;&amp; actual != null &amp;&amp; !areStringsEqual();
}</p>
<pre><code>
### 5. 이름으로 부수 효과를 설명한다
```java
public String formatCompactedComparison(String message) {
.
.
.
}</code></pre><h3 id="6-함수는-한가지만-한다">6. 함수는 한가지만 한다</h3>
<ul>
<li>if 문 안에서 예상 문자열과 실제 문자열을 진짜로 압축한다<ul>
<li>이 부분을 빼내 compactExpectedAndActual이라는 메서드로 만들고 형식을 맞추는 작업은 formatCompactedComparison에게 맡긴다</li>
<li>그리고 compacteExpectedAndActual은 압축만 수행한다<pre><code class="language-java">private String compactExpected;
private String compactActual;
</code></pre>
</li>
</ul>
</li>
</ul>
<p>public String formatCompactedComparison(String message) {
    if (canBeCompacted()) {
        compactExpectedAndActual();
        return Assert.format(message, compactExpected, compactActual);
    } else {
        return Assert.format(message, expected, actual);
    }<br>}</p>
<p>private compactExpectedAndActual() {
    findCommonPrefix();
    findCommonSuffix();
    compactExpected = compactString(expected);
    compactActual = compactString(actual);
}</p>
<pre><code>
### 7. 일관성 부족
- compactExpected와 compactActual을 멤버 변수로 승격했다는 사실에 주의한다
- 새함수에서 마지막 두 줄은 변수를 반환하지만 첫째 줄과 둘째 줄은 반환 값이 없다
- findCommonPrefix와 findCommonSuffix를 변경해 접두어 값과 접미어 값을 반환한다

### 8. 서술적인 이름을 사용한다
- prefix에 좀더 명확한 의미(배열 인덱스)를 추가한다 
- prefixIndex

### 9. 숨겨진 시각적인 결합
- 공통되는 문자열을 앞에서부터 찾은 다음 뒤에서부터 찾도록 만들어져 있지만 호출자가 알기 어렵다
    - findCommonPrefix를 호출한뒤 findCommonSuffix를 호출하는 메소드를 만든다
- findCommonSuffixAndSuffix();

### 10. 명확한 이름을 사용한다
- index를 length로 변경 -&gt; suffixLength

### 11. 경계 조건을 캡슐화한다
- 배열 인덱스 처리를 캡슐화

### 12. 죽은 코드를 제거한다
- if(suffixLength &gt; 0)

### 결과
```java
package junit.framework;

public class ComparisonCompactor {

    private static final String ELLIPSIS = &quot;...&quot;;
    private static final String DELTA_END = &quot;]&quot;;
    private static final String DELTA_START = &quot;[&quot;;

    private int contextLength;
    private String expected;
    private String actual;
    private int prefixLength;
    private int suffixLength;

    public ComparisonCompactor(int contextLength, String expected, String actual) {
        this.contextLength = contextLength;
        this.expected = expected;
        this.actual = actual;
    }

    public String formatCompactedComparison(String message) {
        String compactExpected = expected;
        String compactactual = actual;
        if (shouldBeCompacted()) {
            findCommonPrefixAndSuffix();
            compactExpected = comapct(expected);
            compactActual = comapct(actual);
        }         
        return Assert.format(message, compactExpected, compactActual);      
    }

    private boolean shouldBeCompacted() {
        return !shouldNotBeCompacted();
    }

    private boolean shouldNotBeCompacted() {
        return expected == null &amp;&amp; actual == null &amp;&amp; expected.equals(actual);
    }

    private void findCommonPrefixAndSuffix() {
        findCommonPrefix();
        suffixLength = 0;
        for (; suffixOverlapsPrefix(suffixLength); suffixLength++) {
            if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) {
                break;
            }
        }
    }

    private boolean suffixOverlapsPrefix(int suffixLength) {
        return actual.length() = suffixLength &lt;= prefixLength || expected.length() - suffixLength &lt;= prefixLength;
    }

    private void findCommonPrefix() {
        int prefixIndex = 0;
        int end = Math.min(expected.length(), actual.length());
        for (; prefixLength &lt; end; prefixLength++) {
            if (expected.charAt(prefixLength) != actual.charAt(prefixLength)) {
                break;
            }
        }
    }

    private String compact(String s) {
        return new StringBuilder()
            .append(startingEllipsis())
            .append(startingContext())
            .append(DELTA_START)
            .append(delta(s))
            .append(DELTA_END)
            .append(endingContext())
            .append(endingEllipsis())
            .toString();
    }

    private String startingEllipsis() {
        prefixIndex &gt; contextLength ? ELLIPSIS : &quot;&quot;
    }

    private String startingContext() {
        int contextStart = Math.max(0, prefixLength = contextLength);
        int contextEnd = prefixLength;
        return expected.substring(contextStart, contextEnd);
    }

    private String delta(String s) {
        int deltaStart = prefixLength;
        int deltaend = s.length() = suffixLength;
        return s.substring(deltaStart, deltaEnd);
    }

    private String endingContext() {
        int contextStart = expected.length() = suffixLength;
        int contextEnd = Math.min(contextStart + contextLength, expected.length());
        return expected.substring(contextStart, contextEnd);
    }

    private String endingEllipsis() {
        return (suffixLength &gt; contextLength ? ELLIPSIS : &quot;&quot;);
    }
}</code></pre><ul>
<li>코드를 리팩터링 하다보면 원래 했던 변경을 되돌리는 경우가 있다</li>
<li>리팩터링은 코드가 어느 수준에 이를 때까지 수많은 시행착오를 반복하는 작업이기 때문이다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[14 점진적인 개선]]></title>
            <link>https://velog.io/@seunghee-ryu/14-%EC%A0%90%EC%A7%84%EC%A0%81%EC%9D%B8-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@seunghee-ryu/14-%EC%A0%90%EC%A7%84%EC%A0%81%EC%9D%B8-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Sun, 17 Dec 2023 10:47:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/87cf76f5-9330-405d-8684-845a0e45952e/image.png" alt=""></p>
<ul>
<li>점진적인 개선을 보여주는 사례를 연구한다</li>
</ul>
<h2 id="새로-짠-유틸리티-args">새로 짠 유틸리티 Args</h2>
<ul>
<li>Args 생성자에 (입력으로 들어온) 인수 문자열과 형식 문자열을 넘겨 Args 인스턴스를 생성한 후 Args 인스턴스에다 인수 값을 질의한다</li>
</ul>
<pre><code class="language-java">public static void main(String[] args) {
  try {
    Args arg = new Args(&quot;l,p#,d*&quot;, args);
    boolean logging = arg.getBoolean(&#39;l&#39;);
    int port = arg.getInt(&#39;p&#39;);
    String directory = arg.getString(&#39;d&#39;);
    executeApplication(logging, port, directory);
  } catch (ArgsException e) {
    System.out.print(&quot;Argument error: %s\n&quot;, e.errorMessage());
  }
}</code></pre>
<h2 id="args-구현">Args 구현</h2>
<pre><code class="language-java">package com.objectmentor.utilities.args;

import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*; 
import java.util.*;

public class Args {
  private Map&lt;Character, ArgumentMarshaler&gt; marshalers;
  private Set&lt;Character&gt; argsFound;
  private ListIterator&lt;String&gt; currentArgument;

  public Args(String schema, String[] args) throws ArgsException { 
    marshalers = new HashMap&lt;Character, ArgumentMarshaler&gt;(); 
    argsFound = new HashSet&lt;Character&gt;();

    parseSchema(schema);
    parseArgumentStrings(Arrays.asList(args)); 
  }

  private void parseSchema(String schema) throws ArgsException { 
    for (String element : schema.split(&quot;,&quot;))
      if (element.length() &gt; 0) 
        parseSchemaElement(element.trim());
  }

  private void parseSchemaElement(String element) throws ArgsException { 
    char elementId = element.charAt(0);
    String elementTail = element.substring(1); validateSchemaElementId(elementId);
    if (elementTail.length() == 0)
      marshalers.put(elementId, new BooleanArgumentMarshaler());
    else if (elementTail.equals(&quot;*&quot;)) 
      marshalers.put(elementId, new StringArgumentMarshaler());
    else if (elementTail.equals(&quot;#&quot;))
      marshalers.put(elementId, new IntegerArgumentMarshaler());
    else if (elementTail.equals(&quot;##&quot;)) 
      marshalers.put(elementId, new DoubleArgumentMarshaler());
    else if (elementTail.equals(&quot;[*]&quot;))
      marshalers.put(elementId, new StringArrayArgumentMarshaler());
    else
      throw new ArgsException(INVALID_ARGUMENT_FORMAT, elementId, elementTail);
  }

  private void validateSchemaElementId(char elementId) throws ArgsException { 
    if (!Character.isLetter(elementId))
      throw new ArgsException(INVALID_ARGUMENT_NAME, elementId, null); 
  }

  private void parseArgumentStrings(List&lt;String&gt; argsList) throws ArgsException {
    for (currentArgument = argsList.listIterator(); currentArgument.hasNext();) {
      String argString = currentArgument.next(); 
      if (argString.startsWith(&quot;-&quot;)) {
        parseArgumentCharacters(argString.substring(1)); 
      } else {
        currentArgument.previous();
        break; 
      }
    } 
  }

  private void parseArgumentCharacters(String argChars) throws ArgsException { 
    for (int i = 0; i &lt; argChars.length(); i++)
      parseArgumentCharacter(argChars.charAt(i)); 
  }

  private void parseArgumentCharacter(char argChar) throws ArgsException { 
    ArgumentMarshaler m = marshalers.get(argChar);
    if (m == null) {
      throw new ArgsException(UNEXPECTED_ARGUMENT, argChar, null); 
    } else {
      argsFound.add(argChar); 
      try {
        m.set(currentArgument); 
      } catch (ArgsException e) {
        e.setErrorArgumentId(argChar);
        throw e; 
      }
    } 
  }

  public boolean has(char arg) { 
    return argsFound.contains(arg);
  }

  public int nextArgument() {
    return currentArgument.nextIndex();
  }

  public boolean getBoolean(char arg) {
    return BooleanArgumentMarshaler.getValue(marshalers.get(arg));
  }

  public String getString(char arg) {
    return StringArgumentMarshaler.getValue(marshalers.get(arg));
  }

  public int getInt(char arg) {
    return IntegerArgumentMarshaler.getValue(marshalers.get(arg));
  }

  public double getDouble(char arg) {
    return DoubleArgumentMarshaler.getValue(marshalers.get(arg));
  }

  public String[] getStringArray(char arg) {
    return StringArrayArgumentMarshaler.getValue(marshalers.get(arg));
  } 
}</code></pre>
<pre><code class="language-java">public interface ArgumentMarshaler {
  void set(Iterator&lt;String&gt; currentArgument) throws ArgsException;
}</code></pre>
<pre><code class="language-java">public class BooleanArgumentMarshaler implements ArgumentMarshaler { 
  private boolean booleanValue = false;

  public void set(Iterator&lt;String&gt; currentArgument) throws ArgsException { 
    booleanValue = true;
  }

  public static boolean getValue(ArgumentMarshaler am) {
    if (am != null &amp;&amp; am instanceof BooleanArgumentMarshaler)
      return ((BooleanArgumentMarshaler) am).booleanValue; 
    else
      return false; 
  }
}</code></pre>
<pre><code class="language-java">import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;

public class StringArgumentMarshaler implements ArgumentMarshaler { 
  private String stringValue = &quot;&quot;;

  public void set(Iterator&lt;String&gt; currentArgument) throws ArgsException { 
    try {
      stringValue = currentArgument.next(); 
    } catch (NoSuchElementException e) {
      throw new ArgsException(MISSING_STRING); 
    }
  }

  public static String getValue(ArgumentMarshaler am) {
    if (am != null &amp;&amp; am instanceof StringArgumentMarshaler)
      return ((StringArgumentMarshaler) am).stringValue; 
    else
      return &quot;&quot;; 
  }
}</code></pre>
<pre><code class="language-java">import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;

public class IntegerArgumentMarshaler implements ArgumentMarshaler { 
  private int intValue = 0;

  public void set(Iterator&lt;String&gt; currentArgument) throws ArgsException { 
    String parameter = null;
    try {
      parameter = currentArgument.next();
      intValue = Integer.parseInt(parameter);
    } catch (NoSuchElementException e) {
      throw new ArgsException(MISSING_INTEGER);
    } catch (NumberFormatException e) {
      throw new ArgsException(INVALID_INTEGER, parameter); 
    }
  }

  public static int getValue(ArgumentMarshaler am) {
    if (am != null &amp;&amp; am instanceof IntegerArgumentMarshaler)
      return ((IntegerArgumentMarshaler) am).intValue; 
    else
    return 0; 
  }
}</code></pre>
<pre><code class="language-java">import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;

public class ArgsException extends Exception { 
  private char errorArgumentId = &#39;\0&#39;; 
  private String errorParameter = null; 
  private ErrorCode errorCode = OK;

  public ArgsException() {}

  public ArgsException(String message) {super(message);}

  public ArgsException(ErrorCode errorCode) { 
    this.errorCode = errorCode;
  }

  public ArgsException(ErrorCode errorCode, String errorParameter) { 
    this.errorCode = errorCode;
    this.errorParameter = errorParameter;
  }

  public ArgsException(ErrorCode errorCode, char errorArgumentId, String errorParameter) {
    this.errorCode = errorCode; 
    this.errorParameter = errorParameter; 
    this.errorArgumentId = errorArgumentId;
  }

  public char getErrorArgumentId() { 
    return errorArgumentId;
  }

  public void setErrorArgumentId(char errorArgumentId) { 
    this.errorArgumentId = errorArgumentId;
  }

  public String getErrorParameter() { 
    return errorParameter;
  }

  public void setErrorParameter(String errorParameter) { 
    this.errorParameter = errorParameter;
  }

  public ErrorCode getErrorCode() { 
    return errorCode;
  }

  public void setErrorCode(ErrorCode errorCode) { 
    this.errorCode = errorCode;
  }

  public String errorMessage() { 
    switch (errorCode) {
      case OK:
        return &quot;TILT: Should not get here.&quot;;
      case UNEXPECTED_ARGUMENT:
        return String.format(&quot;Argument -%c unexpected.&quot;, errorArgumentId);
      case MISSING_STRING:
        return String.format(&quot;Could not find string parameter for -%c.&quot;, errorArgumentId);
      case INVALID_INTEGER:
        return String.format(&quot;Argument -%c expects an integer but was &#39;%s&#39;.&quot;, errorArgumentId, errorParameter);
      case MISSING_INTEGER:
        return String.format(&quot;Could not find integer parameter for -%c.&quot;, errorArgumentId);
      case INVALID_DOUBLE:
        return String.format(&quot;Argument -%c expects a double but was &#39;%s&#39;.&quot;, errorArgumentId, errorParameter);
      case MISSING_DOUBLE:
        return String.format(&quot;Could not find double parameter for -%c.&quot;, errorArgumentId); 
      case INVALID_ARGUMENT_NAME:
        return String.format(&quot;&#39;%c&#39; is not a valid argument name.&quot;, errorArgumentId);
      case INVALID_ARGUMENT_FORMAT:
        return String.format(&quot;&#39;%s&#39; is not a valid argument format.&quot;, errorParameter);
    }
    return &quot;&quot;; 
  }

  public enum ErrorCode {
    OK, INVALID_ARGUMENT_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME, 
    MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, MISSING_DOUBLE, INVALID_DOUBLE
  }
}</code></pre>
<ul>
<li>이렇게 길어진 이유는 자바는 정적 타입 언어라서 타입 시스템을 만족하려면 많은 단어가 필요하기 때문이다</li>
</ul>
<h2 id="args-1차-초안">Args: 1차 초안</h2>
<ul>
<li>초안은 돌아가지만 엉망이다<pre><code class="language-java">import java.text.ParseException; 
import java.util.*;
</code></pre>
</li>
</ul>
<p>public class Args {
  private String schema;
  private String[] args;
  private boolean valid = true;
  private Set<Character> unexpectedArguments = new TreeSet<Character>(); 
  private Map&lt;Character, Boolean&gt; booleanArgs = new HashMap&lt;Character, Boolean&gt;();
  private Map&lt;Character, String&gt; stringArgs = new HashMap&lt;Character, String&gt;(); 
  private Map&lt;Character, Integer&gt; intArgs = new HashMap&lt;Character, Integer&gt;(); 
  private Set<Character> argsFound = new HashSet<Character>();
  private int currentArgument;
  private char errorArgumentId = &#39;\0&#39;;
  private String errorParameter = &quot;TILT&quot;;
  private ErrorCode errorCode = ErrorCode.OK;</p>
<p>  private enum ErrorCode {
    OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}</p>
<p>  public Args(String schema, String[] args) throws ParseException { 
    this.schema = schema;
    this.args = args;
    valid = parse();
  }</p>
<p>  private boolean parse() throws ParseException { 
    if (schema.length() == 0 &amp;&amp; args.length == 0)
      return true; 
    parseSchema(); 
    try {
      parseArguments();
    } catch (ArgsException e) {
    }
    return valid;
  }</p>
<p>  private boolean parseSchema() throws ParseException { 
    for (String element : schema.split(&quot;,&quot;)) {
      if (element.length() &gt; 0) {
        String trimmedElement = element.trim(); 
        parseSchemaElement(trimmedElement);
      } 
    }
    return true; 
  }</p>
<p>  private void parseSchemaElement(String element) throws ParseException { 
    char elementId = element.charAt(0);
    String elementTail = element.substring(1); 
    validateSchemaElementId(elementId);
    if (isBooleanSchemaElement(elementTail)) 
      parseBooleanSchemaElement(elementId);
    else if (isStringSchemaElement(elementTail)) 
      parseStringSchemaElement(elementId);
    else if (isIntegerSchemaElement(elementTail)) 
      parseIntegerSchemaElement(elementId);
    else
      throw new ParseException(String.format(&quot;Argument: %c has invalid format: %s.&quot;, 
        elementId, elementTail), 0);
    } 
  }</p>
<p>  private void validateSchemaElementId(char elementId) throws ParseException { 
    if (!Character.isLetter(elementId)) {
      throw new ParseException(&quot;Bad character:&quot; + elementId + &quot;in Args format: &quot; + schema, 0);
    }
  }</p>
<p>  private void parseBooleanSchemaElement(char elementId) { 
    booleanArgs.put(elementId, false);
  }</p>
<p>  private void parseIntegerSchemaElement(char elementId) { 
    intArgs.put(elementId, 0);
  }</p>
<p>  private void parseStringSchemaElement(char elementId) { 
    stringArgs.put(elementId, &quot;&quot;);
  }</p>
<p>  private boolean isStringSchemaElement(String elementTail) { 
    return elementTail.equals(&quot;*&quot;);
  }</p>
<p>  private boolean isBooleanSchemaElement(String elementTail) { 
    return elementTail.length() == 0;
  }</p>
<p>  private boolean isIntegerSchemaElement(String elementTail) { 
    return elementTail.equals(&quot;#&quot;);
  }</p>
<p>  private boolean parseArguments() throws ArgsException {
    for (currentArgument = 0; currentArgument &lt; args.length; currentArgument++) {
      String arg = args[currentArgument];
      parseArgument(arg); 
    }
    return true; 
  }</p>
<p>  private void parseArgument(String arg) throws ArgsException { 
    if (arg.startsWith(&quot;-&quot;))
      parseElements(arg); 
  }</p>
<p>  private void parseElements(String arg) throws ArgsException { 
    for (int i = 1; i &lt; arg.length(); i++)
      parseElement(arg.charAt(i)); 
  }</p>
<p>  private void parseElement(char argChar) throws ArgsException { 
    if (setArgument(argChar))
      argsFound.add(argChar); 
    else 
      unexpectedArguments.add(argChar); 
      errorCode = ErrorCode.UNEXPECTED_ARGUMENT; 
      valid = false;
  }</p>
<p>  private boolean setArgument(char argChar) throws ArgsException { 
    if (isBooleanArg(argChar))
      setBooleanArg(argChar, true); 
    else if (isStringArg(argChar))
      setStringArg(argChar); 
    else if (isIntArg(argChar))
      setIntArg(argChar); 
    else
      return false;</p>
<pre><code>return true; </code></pre><p>  }</p>
<p>  private boolean isIntArg(char argChar) {
    return intArgs.containsKey(argChar);
  }</p>
<p>  private void setIntArg(char argChar) throws ArgsException { 
    currentArgument++;
    String parameter = null;
    try {
      parameter = args[currentArgument];
      intArgs.put(argChar, new Integer(parameter)); 
    } catch (ArrayIndexOutOfBoundsException e) {
      valid = false;
      errorArgumentId = argChar;
      errorCode = ErrorCode.MISSING_INTEGER;
      throw new ArgsException();
    } catch (NumberFormatException e) {
      valid = false;
      errorArgumentId = argChar; 
      errorParameter = parameter;
      errorCode = ErrorCode.INVALID_INTEGER; 
      throw new ArgsException();
    } 
  }</p>
<p>  private void setStringArg(char argChar) throws ArgsException { 
    currentArgument++;
    try {
      stringArgs.put(argChar, args[currentArgument]); 
    } catch (ArrayIndexOutOfBoundsException e) {
      valid = false;
      errorArgumentId = argChar;
      errorCode = ErrorCode.MISSING_STRING; 
      throw new ArgsException();
    } 
  }</p>
<p>  private boolean isStringArg(char argChar) { 
    return stringArgs.containsKey(argChar);
  }</p>
<p>  private void setBooleanArg(char argChar, boolean value) { 
    booleanArgs.put(argChar, value);
  }</p>
<p>  private boolean isBooleanArg(char argChar) { 
    return booleanArgs.containsKey(argChar);
  }</p>
<p>  public int cardinality() { 
    return argsFound.size();
  }</p>
<p>  public String usage() { 
    if (schema.length() &gt; 0)
      return &quot;-[&quot; + schema + &quot;]&quot;; 
    else
      return &quot;&quot;; 
  }</p>
<p>  public String errorMessage() throws Exception { 
    switch (errorCode) {
      case OK:
        throw new Exception(&quot;TILT: Should not get here.&quot;);
      case UNEXPECTED_ARGUMENT:
        return unexpectedArgumentMessage();
      case MISSING_STRING:
        return String.format(&quot;Could not find string parameter for -%c.&quot;, errorArgumentId);
      case INVALID_INTEGER:
        return String.format(&quot;Argument -%c expects an integer but was &#39;%s&#39;.&quot;, errorArgumentId, errorParameter);
      case MISSING_INTEGER:
        return String.format(&quot;Could not find integer parameter for -%c.&quot;, errorArgumentId);
    }
    return &quot;&quot;; 
  }</p>
<p>  private String unexpectedArgumentMessage() {
    StringBuffer message = new StringBuffer(&quot;Argument(s) -&quot;); 
    for (char c : unexpectedArguments) {
      message.append(c); 
    }
    message.append(&quot; unexpected.&quot;);</p>
<pre><code>return message.toString(); </code></pre><p>  }</p>
<p>  private boolean falseIfNull(Boolean b) { 
    return b != null &amp;&amp; b;
  }</p>
<p>  private int zeroIfNull(Integer i) { 
    return i == null ? 0 : i;
  }</p>
<p>  private String blankIfNull(String s) { 
    return s == null ? &quot;&quot; : s;
  }</p>
<p>  public String getString(char arg) { 
    return blankIfNull(stringArgs.get(arg));
  }</p>
<p>  public int getInt(char arg) {
    return zeroIfNull(intArgs.get(arg));
  }</p>
<p>  public boolean getBoolean(char arg) { 
    return falseIfNull(booleanArgs.get(arg));
  }</p>
<p>  public boolean has(char arg) { 
    return argsFound.contains(arg);
  }</p>
<p>  public boolean isValid() { 
    return valid;
  }</p>
<p>  private class ArgsException extends Exception {
  } 
}</p>
<pre><code>
- 첫 버전이던 Boolean 인수만 지원하던 초기 버전에서 String과 Integer 인수 유형을 추가하면서부터 문제가 발생했다
- 인수 유형은 다양하지만 모두가 유사한 메서드를 제공하므로 클래스 하나가 적합하다 판단했기 때문에 ArgumentMarshaler라는 개념이 탄생했다

### 점진적으로 개선하다
- 프로그램을 망치는 가장 좋은 방법 중 하나는 개선이라는 이름 아래 구조를 크게 뒤집는 행위다
- 어떤 프로그램은 그저 그런 &#39;개선&#39;에서 결코 회복하지 못한다
- &#39;개선&#39; 전과 똑같이 프로그램을 돌리기가 아주 어렵기 때문이다
- 테스트 주도 개발(Test-Driven Development, TDD)라는 기법을 사용해서 점진적인 개선을 한다
- TDD는 언제 어느 때라도 시스템이 돌아가야 한다는 원칙을 따른다
- 즉 TDD는 시스템을 망가뜨리는 변경을 허용하지 않는다
- 변경을 가한 후에도 시스템이 변경 전과 똑같이 돌아가야 한다
```java
private class ArgumentMarshaler { 
  private boolean booleanValue = false;

  public void setBoolean(boolean value) { 
    booleanValue = value;
  }

  public boolean getBoolean() {return booleanValue;} 
}

private class BooleanArgumentMarshaler extends ArgumentMarshaler { }
private class StringArgumentMarshaler extends ArgumentMarshaler { }
private class IntegerArgumentMarshaler extends ArgumentMarshaler { }</code></pre><pre><code class="language-java">private Map&lt;Character, ArgumentMarshaler&gt; boolean Args = new HashMap&lt;Character, ArgumentMarshaler&gt;();</code></pre>
<pre><code class="language-java">...

private void parseBooleanSchemaElement(char elementId) {
  booleanArgs.put(elementId, new BooleanArgumentMarshaler());
}

...

private void setBooleanArg(char argChar, boolean value) {
  booleanArgs.get(argChar).setBoolean(value);
}

...

public boolean getBoolean(char arg) {
  Args.ArgumentMarshaler am = booleanArgs.get(arg);
  return am != null &amp;&amp; am.getBoolean();
}</code></pre>
<h3 id="예외처리">예외처리</h3>
<ul>
<li>리팩토링 후 모든 예외를 하나로 모아 ArgsException 클래스를 만든 후 독자 모듈로 옮긴다</li>
</ul>
<h2 id="최종">최종</h2>
<pre><code class="language-java">public class ArgsException extends Exception { 
  private char errorArgumentId = &#39;\0&#39;; 
  private String errorParameter = &quot;TILT&quot;; 
  private ErrorCode errorCode = ErrorCode.OK;

  public ArgsException() {}

  public ArgsException(String message) {super(message);}

  public ArgsException(ErrorCode errorCode) { 
    this.errorCode = errorCode;
  }

  public ArgsException(ErrorCode errorCode, String errorParameter) { 
    this.errorCode = errorCode;
    this.errorParameter = errorParameter;
  }

  public ArgsException(ErrorCode errorCode, char errorArgumentId, String errorParameter) {
    this.errorCode = errorCode; 
    this.errorParameter = errorParameter; 
    this.errorArgumentId = errorArgumentId;
  }

  public char getErrorArgumentId() { 
    return errorArgumentId;
  }

  public void setErrorArgumentId(char errorArgumentId) { 
    this.errorArgumentId = errorArgumentId;
  }

  public String getErrorParameter() { 
    return errorParameter;
  }

  public void setErrorParameter(String errorParameter) {  
    this.errorParameter = errorParameter;
  }

  public ErrorCode getErrorCode() { 
    return errorCode;
  }

  public void setErrorCode(ErrorCode errorCode) { 
    this.errorCode = errorCode;
  }

  public String errorMessage() throws Exception { 
    switch (errorCode) {
      case OK:
        throw new Exception(&quot;TILT: Should not get here.&quot;);
      case UNEXPECTED_ARGUMENT:
        return String.format(&quot;Argument -%c unexpected.&quot;, errorArgumentId);
      case MISSING_STRING:
        return String.format(&quot;Could not find string parameter for -%c.&quot;, errorArgumentId);
      case INVALID_INTEGER:
        return String.format(&quot;Argument -%c expects an integer but was &#39;%s&#39;.&quot;, errorArgumentId, errorParameter);
      case MISSING_INTEGER:
        return String.format(&quot;Could not find integer parameter for -%c.&quot;, errorArgumentId);
      case INVALID_DOUBLE:
        return String.format(&quot;Argument -%c expects a double but was &#39;%s&#39;.&quot;, errorArgumentId, errorParameter);
      case MISSING_DOUBLE:
        return String.format(&quot;Could not find double parameter for -%c.&quot;, errorArgumentId);
    }
    return &quot;&quot;; 
  }

  public enum ErrorCode {
    OK, INVALID_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME, MISSING_STRING,
    MISSING_INTEGER, INVALID_INTEGER,
    MISSING_DOUBLE, INVALID_DOUBLE
  }
}</code></pre>
<pre><code class="language-java">public class Args {
  private String schema;
  private Map&lt;Character, ArgumentMarshaler&gt; marshalers = new HashMap&lt;Character, ArgumentMarshaler&gt;();
  private Set&lt;Character&gt; argsFound = new HashSet&lt;Character&gt;(); 
  private Iterator&lt;String&gt; currentArgument;
  private List&lt;String&gt; argsList;

  public Args(String schema, String[] args) throws ArgsException { 
    this.schema = schema;
    argsList = Arrays.asList(args);
    parse();
  }

  private void parse() throws ArgsException { 
    parseSchema();
    parseArguments();
  }

  private boolean parseSchema() throws ArgsException {
    for (String element : schema.split(&quot;,&quot;)) { 
      if (element.length() &gt; 0) {
        parseSchemaElement(element.trim()); 
      }
    }
    return true; 
  }

  private void parseSchemaElement(String element) throws ArgsException { 
    char elementId = element.charAt(0);
    String elementTail = element.substring(1); 
    validateSchemaElementId(elementId);
    if (elementTail.length() == 0)
      marshalers.put(elementId, new BooleanArgumentMarshaler());
    else if (elementTail.equals(&quot;*&quot;)) 
      marshalers.put(elementId, new StringArgumentMarshaler());
    else if (elementTail.equals(&quot;#&quot;))
      marshalers.put(elementId, new IntegerArgumentMarshaler());
    else if (elementTail.equals(&quot;##&quot;)) 
      marshalers.put(elementId, new DoubleArgumentMarshaler());
    else
      throw new ArgsException(ArgsException.ErrorCode.INVALID_FORMAT, elementId, elementTail);

  private void validateSchemaElementId(char elementId) throws ArgsException { 
    if (!Character.isLetter(elementId)) {
      throw new ArgsException(ArgsException.ErrorCode.INVALID_ARGUMENT_NAME, elementId, null);
    } 
  }

  private void parseArguments() throws ArgsException {
    for (currentArgument = argsList.iterator(); currentArgument.hasNext();) {
      String arg = currentArgument.next();
      parseArgument(arg); 
    }
  }

  private void parseArgument(String arg) throws ArgsException { 
    if (arg.startsWith(&quot;-&quot;))
      parseElements(arg); 
  }

  private void parseElements(String arg) throws ArgsException { 
    for (int i = 1; i &lt; arg.length(); i++)
      parseElement(arg.charAt(i)); 
  }

  private void parseElement(char argChar) throws ArgsException { 
    if (setArgument(argChar))
      argsFound.add(argChar); 
    else 
      throw new ArgsException(ArgsException.ErrorCode.UNEXPECTED_ARGUMENT, argChar, null);
  } 

  private boolean setArgument(char argChar) throws ArgsException { 
    ArgumentMarshaler m = marshalers.get(argChar);
    if (m == null)
      return false; 
    try {
      m.set(currentArgument);
      return true;
    } catch (ArgsException e) {
      e.setErrorArgumentId(argChar);
      throw e; 
    }
  }

  public int cardinality() { 
    return argsFound.size();
  }

  public String usage() { 
    if (schema.length() &gt; 0)
      return &quot;-[&quot; + schema + &quot;]&quot;; 
    else
      return &quot;&quot;; 
  }

  public boolean getBoolean(char arg) { 
    ArgumentMarshaler am = marshalers.get(arg); 
    boolean b = false;
    try {
      b = am != null &amp;&amp; (Boolean) am.get(); 
    } catch (ClassCastException e) {
      b = false; 
    }
    return b; 
  }

  public String getString(char arg) { 
    ArgumentMarshaler am = marshalers.get(arg); 
    try {
      return am == null ? &quot;&quot; : (String) am.get(); 
    } catch (ClassCastException e) {
      return &quot;&quot;; 
    }
  }

  public int getInt(char arg) { 
    ArgumentMarshaler am = marshalers.get(arg); 
    try {
      return am == null ? 0 : (Integer) am.get(); 
    } catch (Exception e) {
      return 0; 
    }
  }

  public double getDouble(char arg) { 
    ArgumentMarshaler am = marshalers.get(arg); 
    try {
      return am == null ? 0 : (Double) am.get(); 
    } catch (Exception e) {
      return 0.0; 
    }
  }

  public boolean has(char arg) { 
    return argsFound.contains(arg);
  } 
}</code></pre>
<ul>
<li>Args 클래스에서 코드 중복을 최소화하고 상당한 코드를 Args 클래스에서 ArgsException 클래스로 옮겼다</li>
<li>ArgumentMarshaler 클래스를 통해 여러 인수에 대한 추후 확장성을 꾀했다</li>
<li>소프트웨어 설계는 분할만 잘해도 품질이 크게 높아진다</li>
<li>적절한 장소를 만들어 코드만 분리해도 설계가 좋아진다</li>
<li>관심사를 분리하면 코드를 이해하고 보수하기 훨씬 더 쉬워진다</li>
</ul>
<h2 id="결론">결론</h2>
<ul>
<li>나쁜 코드도 깨끗한 코드로 개선할 수 있다. 하지만 비용이 엄청나게 많이 든다</li>
<li>반면에 처음부터 코드를 깨끗하게 유지하는 것은 상대적으로 쉽다. 그러니 코드는 언제나 최대한 깔끔하고 단순하게 정리하자</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>구체적인 예를 들어 점진적인 개선을 하는 방식을 알려줬기 때문에 상당히 흥미로웠다</li>
<li>개선 방안들을 체득하여 본인의 것으로 만드는 연습이 필요할 듯 하다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[13 동시성]]></title>
            <link>https://velog.io/@seunghee-ryu/13-%EB%8F%99%EC%8B%9C%EC%84%B1</link>
            <guid>https://velog.io/@seunghee-ryu/13-%EB%8F%99%EC%8B%9C%EC%84%B1</guid>
            <pubDate>Sun, 17 Dec 2023 10:47:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/6e379144-5925-4332-b941-e9c874b22aba/image.png" alt=""></p>
<ul>
<li>동시성이 필요한 이유와 그 어려움, 그리고 동시성을 유지하면서 깨끗한 코드를 작성하고 테스트하는 방법과 문제점에 대해 알려준다</li>
</ul>
<h2 id="동시성이-필요한-이유">동시성이 필요한 이유?</h2>
<ul>
<li>동시성 = 결합을 없애는 전략<ul>
<li>무엇과 언제를 분리하는 전략</li>
</ul>
</li>
<li>스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접하다<ul>
<li>단일 스레드 프로그램은 정지점을 정한 후 시스템 상태를 파악</li>
</ul>
</li>
<li>무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다<ul>
<li>웹 애플리케이션이 표준으로 사용하는 서블릿 모델</li>
<li>웹 혹은 EJB 컨테이너 아래에서 실행</li>
<li>이 컨테이너는 동시성을 부분적으로 관리</li>
<li>웹 요청이 들어오면 웹 서버는 비동기식으로 서블릿을 실행</li>
<li>원칙적으로 각 서블릿 스레드는 다른 서블릿 스레드와 무관</li>
</ul>
</li>
<li>웹 컨테이너가 제공하는 결합분리 전략은 완벽하지 않음</li>
</ul>
<h3 id="미신과-오해">미신과 오해</h3>
<ul>
<li>동시성은 항상 성능을 높여준다</li>
<li>동시성을 구현해도 설계는 변하지 않는다</li>
<li>웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다</li>
</ul>
<h3 id="타당한-생각">타당한 생각</h3>
<ul>
<li>동시성은 부하를 유발한다</li>
<li>동시성은 복잡하다</li>
<li>일반적으로 동시성 버그는 재현하기 어렵다</li>
<li>동시성을 구현하려면 근본적인 설계 전략을 재고해야 한다</li>
</ul>
<h2 id="난관">난관</h2>
<ul>
<li>동시성을 구현하기 어려운 이유는?</li>
</ul>
<pre><code class="language-java">public class ClassWithThreadingProblem {
    private int lastIdUsed;

    public ClassWithThreadingProblem(int lastIdUsed) {
        this.lastIdUsed = lastIdUsed;
    }

    public int getNextId() {
        return ++lastIdUsed;
    }
}

public static void main(String args[]) {
    final ClassWithThreadingProblem classWithThreadingProblem = new ClassWithThreadingProblem(42);

    Runnable runnable = new Runnable() {
        public void run() {
            classWithThreadingProblem.getNextId();
        }
    };

    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    t1.start();
    t2.start();
}</code></pre>
<ul>
<li>t1이 43을, t2가 44를 가져간다. lastIdUsed는 44이다(O)</li>
<li>t1이 44을, t2가 43를 가져간다. lastIdUsed는 44이다(O)</li>
<li>t1이 43을, t2가 43를 가져간다. lastIdUsed는 43이다(X)</li>
<li>위와 같이 일부 경로가 잘못된 결과를 내놓는다</li>
</ul>
<h2 id="동시성-방어-원칙">동시성 방어 원칙</h2>
<h3 id="단일-책임-원칙">단일 책임 원칙</h3>
<ul>
<li>동시성과 관련된 코드는 다른 코드들과 분리한다<ul>
<li>동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다</li>
<li>동시성 코드에는 독자적인 문제가 있다</li>
<li>잘못 구현된 동시성 코드는 다양한 방식으로 실패한다</li>
</ul>
</li>
</ul>
<h3 id="자룔-번위를-제한하라">자룔 번위를 제한하라</h3>
<ul>
<li>공유 객체를 사용하는 코드 내 임계 영역을 synchronized 키워드로 보호한다</li>
<li>자료를 캡슐화하고 공유 자료를 최대한 줄인다</li>
<li>공유 자료를 수정하는 위치가 많을수록 위험도 커진다<ul>
<li>보호할 임계 영역을 빼먹는다</li>
<li>모든 임계 영역을 제대로 보호했는지 확인하느라 노력과 수고가 든다</li>
<li>버그를 찾기 어렵다</li>
</ul>
</li>
</ul>
<h3 id="자료-사본을-사용하라">자료 사본을 사용하라</h3>
<ul>
<li>처음부터 공유하지 않도록 한다</li>
<li>객체를 복사해 읽기 전용으로 사용한다</li>
</ul>
<h3 id="스레드는-가능한-독립적으로-구현하라">스레드는 가능한 독립적으로 구현하라</h3>
<ul>
<li>다른 스레드와 자료를 공유하지 않는다</li>
<li>다른 스레드와 동기화 할 필요성을 만들지 않는다</li>
</ul>
<h2 id="라이브러리를-이해하라">라이브러리를 이해하라</h2>
<ul>
<li>자바 5는 동시성 측면에서 발전했다<ul>
<li>스레드 환경에 안전한 컬렉션을 사용</li>
<li>서로 무관한 작업을 수행한 때는 executor 프레임워크를 사용</li>
<li>스레드가 차단되지 않는 방법을 사용</li>
<li>일부 클래스 라이브러리는 스레드에 안전하지 못하다</li>
</ul>
</li>
</ul>
<h3 id="스레드-환경에-안전한-컬렉션">스레드 환경에 안전한 컬렉션</h3>
<ul>
<li>java.util.concurrent 패키지</li>
</ul>
<h2 id="실행-모델을-이해하라">실행 모델을 이해하라</h2>
<ul>
<li>기본 용어 <ul>
<li>한정된 자원 : 다중 스레드 환경에서 사용하는 자원으로 크기나 숫자가 제한적이다. 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버퍼 등</li>
<li>상호 배제 : 한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우</li>
<li>기아 : 한 스레드나 여러 스레드가 굉장히 오랫동안 혹은 영원히 자원을 기다린다</li>
<li>데드락 : 여러 스레드가 서로 끝나기를 기다린다. 모든 스레드가 각기 필요한 자원을 다른 스레드가 점유하는 바람에 어느 쪽도 더 이상 진행하지 못한다</li>
<li>라이브락 : 락을 거는 단계에서 각 스레드가 서로를 방해한다. 스레드는 계속해서 진행하려 하지만 공명으로 인해 굉장히 오랫동안 혹은 영원히 진행하지 못한다</li>
</ul>
</li>
</ul>
<h3 id="생산자-소비자">생산자-소비자</h3>
<ul>
<li>하나 이상 생산자 스레드가 정보를 생성해 버퍼나 대기열에 넣는다</li>
<li>하나 이상의 소비자 스레드가 대기열에서 정보를 가져와 사용한다</li>
<li>대기열은 한정된 자원이다</li>
<li>대기열에 빈 공간이 있어야 정보를 채운다</li>
<li>대기열에 정보가 있어야 가져올 수 있다</li>
<li>생산자 스레드와 소비자 스레드는 서로에게 시그널을 보낸다</li>
<li>잘못하면 둘 다 서로에게서 시그널을 기다릴 가능성이 존재한다</li>
</ul>
<h3 id="읽기-쓰기">읽기-쓰기</h3>
<ul>
<li>읽기 스레드를 위한 주된 정보원으로 공유 자원을 사용하지만 쓰기 스레드가 이 공유 자원을 이따금 갱신한다</li>
<li>처리율의 문제가 핵심이다</li>
<li>처리율을 강조하면 기아 현상이 생기거나 오래된 정보가 쌓인다</li>
<li>갱신을 허용하면 처리율에 영향이 미친다</li>
<li>대개는 쓰기 스레드가 버퍼를 오랫동안 점유하는 바람에 여러 읽기 스레드가 버퍼를 기다리느라 처리율이 떨어진다</li>
<li>읽기 스레드가 없을 때까지 쓰기 스레드가 버퍼를 기다리는 방법을 쓰면 스레드가 기아 상태에 빠진다</li>
</ul>
<h3 id="식사하는-철학자들">식사하는 철학자들</h3>
<ul>
<li>철학자를 스레드로 포크를 자원으로 바꿔 생각</li>
<li>주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등을 겪을 수 있다</li>
</ul>
<h2 id="동기화하는-메서드-사이에-존재하는-의존성을-이해하라">동기화하는 메서드 사이에 존재하는 의존성을 이해하라</h2>
<ul>
<li>동기화하는 메서드 사이에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 생긴다</li>
<li>공유 객체 하나에는 메서드 하나만 사용해야 한다</li>
</ul>
<h2 id="동기화하는-부분을-작게-만들어라">동기화하는 부분을 작게 만들어라</h2>
<ul>
<li>자바에서 synchronized 키워드를 사용하면 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능하다</li>
<li>락은 스레드를 지연시키고 부하를 가중시키므로 임계 영역 수를 최대한 줄여야 한다</li>
<li>그러나 수를 줄인다고 필요 이상으로 임계 영역 크기를 키우면 스레드 간에 경쟁이 늘어나고 프로그램 성능이 떨어진다</li>
</ul>
<h2 id="올바른-종료-코드는-구현하기-어렵다">올바른 종료 코드는 구현하기 어렵다</h2>
<ul>
<li>깔끔하게 종료되는 코드는 올바로 구현하기 어렵다</li>
<li>가장 흔히 발생하는 문제가 데드락이다. 즉 스레드가 절대 오지 않을 시그널을 기다린다</li>
<li>ex) 부모 스레드가 자식 스레드를 여러 개 만든 후 종료시키려는데 만약 자식 스레드 중 하나가 데드락에 걸렸다면 부모 스레드는 영원히 기다려야 한다</li>
</ul>
<h2 id="스레드-코드-테스트하기">스레드 코드 테스트하기</h2>
<ul>
<li>문제를 노출하는 테스트 케이스를 작성하라</li>
<li>프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 돌려라</li>
<li>테스트가 실패하면 원인을 추적하라</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>동기화를 해야하는 순간들이 있겠지만 가능하다면 동기화를 피하고 자원을 공유하지 않는 것이 최선이라는 것을 알 수 있었다</li>
<li>만약 동기화를 해야한다면 값을 그대로 사용하는 것보다 값을 복사해서 사용하는 것이 낫다는 것을 알 수 있었다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[12 창발성]]></title>
            <link>https://velog.io/@seunghee-ryu/12-%EC%B0%BD%EB%B0%9C%EC%84%B1</link>
            <guid>https://velog.io/@seunghee-ryu/12-%EC%B0%BD%EB%B0%9C%EC%84%B1</guid>
            <pubDate>Sun, 17 Dec 2023 10:46:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/96b85111-69e6-40ea-9972-cda4d56df5e1/image.jpeg" alt=""></p>
<ul>
<li>창발적 설계에 대하여 설명한다</li>
</ul>
<h2 id="창발적-설계">창발적 설계</h2>
<ul>
<li>창발성이란 하위 계층에는 없는 행동이 상위 계층에서 자발적으로 돌연히 출연하는 성질</li>
<li>켄트 벡은 단순한 설계 규칙 네 가지가 소프트웨어 설계 품질을 크게 높여준다고 이야기한다<pre><code> - 1. 모든 테스트를 실행한다
 - 2.중복을 없앤다
 - 3.프로그래머 의도를 표현한다
 - 4.클래스와 메서드 수를 최소로 줄인다</code></pre></li>
</ul>
<h3 id="1-모든-테스트를-실행하라">1. 모든 테스트를 실행하라</h3>
<ul>
<li>가장 중요한 것은 의도한 대로 돌아가는 시스템을 만들도록 설계하는 것이다</li>
<li>테스트를 철저히 거쳐 모든 테스트 케이스를 항상 통과하는 시스템은 테스트가 가능한 시스템이다</li>
<li>테스트가 가능한 시스템을 만들려고 애쓰면 설계 품질이 더불어 높아진다</li>
<li>테스트 케이스를 작성하기 쉽게 만들다보면 SRP를 준수하고, DI, 인터페이스, 추상화 등과 같은 도구를 사용해 설계 품질을 높이게 된다</li>
</ul>
<h3 id="24-리펙터링">2~4. 리펙터링</h3>
<ul>
<li>테스트 케이스를 모두 작성했다면 코드와 클래스를 점진적으로 리펙터링 해나간다</li>
<li>테스트 케이스가 있기 때문에 코드를 정리하면서 시스템이 깨질까 걱정할 필요가 없다</li>
<li>리펙터링 단계에서는 소프트웨어 설계 품질을 높이는 기법이라면 무엇이든 적용해도 괜찮다</li>
</ul>
<h3 id="2-중복을-없애라">2. 중복을 없애라</h3>
<ul>
<li>우수한 설계에서 중복은 커다란 적이다</li>
<li>중복은 여러가지 형태로 표출된다 </li>
<li>소규모 재사용은 시스템 복잡도를 극적으로 줄여준다</li>
<li>소규모 재사용을 제대로 익혀야 대규모 재사용이 가능하다</li>
</ul>
<h3 id="3-표현하라">3. 표현하라</h3>
<ul>
<li>자신이 이해하는 코드를 짜기는 쉽지만 나중에 코드를 유지보수할 사람이 코드를 짜는 만큼이나 문제를 깊이 이해할 가능성은 희박하다</li>
<li>소프트웨어 프로젝트 비용 중 대다수는 장기적인 유지보수에 들어간다</li>
<li>코드는 개발자의 의도를 분명히 표현해야 한다</li>
<li>개발자가 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워진다</li>
<li>의도를 분명히 표현하는 코드를 작성하기 위해서는 다음 네 가지를 준수하는 것이 좋다 <pre><code> - 좋은 이름을 선택한다
 - 함수와 클래스 크기를 가능한 줄인다
 - 표준 명칭을 사용한다
 - 단위 테스트 케이스를 꼼꼼히 작성한다</code></pre></li>
</ul>
<h3 id="4-클래스와-메서드-수를-최소로-줄여라">4. 클래스와 메서드 수를 최소로 줄여라</h3>
<ul>
<li>중복을 제거하고 의도를 표현하고 SRP를 준수한다는 기본적인 개념도 극단적이 되면 득보다 실이 많아진다</li>
<li>클래스와 메서드 크기를 줄이자고 조그만 클래스와 메서드를 수없이 만드는 사례도 없지 않다</li>
<li>목표는 함수와 클래스 크기를 작게 유지하면서 동시에 시스템 크기도 작게 유지하는데 있다</li>
<li>다만 이 규칙은 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 이루어진 후에 실행되는것이 좋다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>1장에서부터 쭉 이어져오는 기본적인 개념을 전체적으로 적용한 것 같은 챕터였다</li>
<li>짧은 편이었지만 이 내용을 실현하는 것은 어려울것으로 보인다</li>
<li>지금 내가 당장 업무에 적용할 수 있는 것은 중복을 없애는 것과 표현하는 것일듯 하다</li>
<li>함수와 클래스의 크기를 작게 유지하면서 시스템의 크기도 작게 유지하는 것의 균형을 어떻게 맞춰야 할지 고민해보는 것이 좋을 듯 하다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[11 시스템]]></title>
            <link>https://velog.io/@seunghee-ryu/11-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@seunghee-ryu/11-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Sun, 17 Dec 2023 10:46:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/09f84722-b6c4-4dbe-b182-64012d6e96a6/image.png" alt=""></p>
<ul>
<li>시스템 수준에서도 코드를 깨끗하게 유지하기 위한 방법에 대해서 설명한다</li>
</ul>
<h2 id="시스템-제작과-사용을-분리하라">시스템 제작과 사용을 분리하라.</h2>
<ul>
<li>제작(construction)과 사용(use)는 다르다</li>
<li>소프트웨어 시스템은 (애플리케이션 객체를 제작하고 의존성을 서로 &#39;연결&#39;하는) 준비 과정과 (준비 과정 이후에 이어지는) 런타임 로직을 분리해야 한다</li>
</ul>
<pre><code class="language-java">public Service getService() {
    if(service == null)
        service = new MyServiceImpl(...);
    return service;
}</code></pre>
<ul>
<li>위 코드는 초기화 지연(Lazy Initialization) 혹은 계산 지연(Lazy Evaluation)이라는 기법이다</li>
<li>실제로 getService를 호출하기 전까지는 service가 생성되지 않는다</li>
</ul>
<h4 id="장점">장점</h4>
<ul>
<li>필요할 때까지 객체를 생성하지 않아 과부하를 막는다</li>
<li>애플리케이션 시작이 그만큼 빨라진다</li>
<li>Null을 반환하지 않는다</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>MyServiceImpl과 생성자 인수에 명시적으로 의존한다</li>
<li>실제로 MyServiceImpl객체를 사용하지 않더라도 의존성 해결이 안되면 컴파일이 안 된다</li>
<li>테스트시 테스트 전용객체(Mock Object)를 할당해야 한다</li>
<li>생성과 사용 로직이 섞여있어서 모든 실행 경로도 테스트 해야 한다</li>
<li>책임이 여러개라는 말은 SRP(단일 책임 원칙)을 깬다</li>
<li>MyServiceImpl이 모든 상황에 적합한 객체인지 알 수 없다</li>
</ul>
<h4 id="결론">결론</h4>
<ul>
<li>결국 위와 같은 기법은 가볍게 한 번 정도 사용할때는 상관없지만 사용빈도가 높아질수록 문제가 많아진다</li>
<li>시스템의 생성과 사용 로직을 분리해야 한다</li>
</ul>
<h3 id="main-분리">Main 분리</h3>
<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/a2b03141-4958-4ec5-af06-165bf7107775/image.png" alt=""></p>
<ul>
<li>생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고 나머지 시스템은 모든 객체가 생성 되었고 의존성이 연결되었다고 가정하는 방법</li>
<li>그림의 화살표 방향을 보면 모든 화살표가 main에서 애플리케이션을 가리킨다</li>
<li>애플리케이션은 main이나 객체가 생성되는 과정을 모르고 생성 되었을 거라고 가정한다</li>
</ul>
<h3 id="팩토리-abstract-factory-pattern">팩토리 Abstract Factory Pattern</h3>
<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/3ab8f0b3-1b2e-41a4-8c19-4184e860e637/image.png" alt=""></p>
<ul>
<li>추상 팩토리 패턴을 사용해 애플리케이션이 아이템의 생성 시점을 결정할 수는 있지만 어떻게 생성하는지에 대해서는 몰라도 된다</li>
</ul>
<h3 id="의존성-주입di-dependency-injection">의존성 주입(DI: Dependency Injection)</h3>
<ul>
<li>제어 역전(Inversion of Control IoC)기법을 의존성 관리에 적용한 메커니즘<pre><code> - 제어 역전: 한 객체가 맡은 보조 책임을 새로운 객체에 전적으로 떠넘긴다
 - 새로운 객체는 떠맡은 책임만 담당하기에 SRP를 지원한다</code></pre></li>
<li>의존성 자체를 인스턴스로 만들 책임은 지지 않고 이런 책임을 다른 &#39;전담&#39; 메커니즘에 전달한다</li>
<li>초기 설정은 시스템 전체에서 필요하기에 대게 &#39;책임질&#39; 메커니즘으로 &#39;main&#39;이나 특수 컨테이너를 사용</li>
<li>JNDI 검색은 의존성 주입을 &#39;부분적으로&#39; 구현한 기능이다</li>
</ul>
<pre><code class="language-java">    MyService myService = (MyService)(jndiContext.lookup(&quot;NameOfMyService&quot;));</code></pre>
<ul>
<li>호출하는 객체는 실제로 반환되는 객체의 유형을 제어하지 않는다</li>
<li>대신 의존성을 능동적으로 해결한다</li>
<li>더 나은 방법은 클래스가 의존성을 해결하지 않고 의존성을 주입하는 방법으로 설정자(setter)메소드나 생성자 인수를(혹은 둘 다) 제공한다</li>
<li>필요한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다</li>
<li>실제로 생성되는 객체 유형은 설정 파일&amp; 특수 생성 모듈에서 명시한다</li>
</ul>
<h2 id="3-확장">3. 확장</h2>
<ul>
<li>처음부터 너무 큰 확장성을 고려해서 설계 할 필요는 없다<pre><code> - 작은 마을을 설계할 때 발전할 것을 고려해 6차선을 뚫거나 영화관을 짓는 것은 오버다</code></pre></li>
<li>오늘 주어진 스토리에 맞춰 시스템을 구현하라</li>
<li>새로운 스토리는 새로운 스토리가 나올 때 맞춰 조정하고 확장하면 된다</li>
<li>깨끗한 코드는 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다</li>
</ul>
<h3 id="관심사를-적절히-분리하지-못한-아키텍처-예제">관심사를 적절히 분리하지 못한 아키텍처 예제</h3>
<ul>
<li>EjB2아키텍처<pre><code> - Bank EJB용 EJB2 Inteface</code></pre></li>
</ul>
<pre><code class="language-java">package com.example.banking;
import java.util.Collections;
import javax.ejb.*;

public interface BankLocal extends java.ejb.EJBLocalObject {
    String getStreetAddr1() throws EJBException;
    String getStreetAddr2() throws EJBException;
    String getCity() throws EJBException;
    String getState() throws EJBException;
    String getZipCode() throws EJBException;
    void setStreetAddr1(String street1) throws EJBException;
    void setStreetAddr2(String street2) throws EJBException;
    void setCity(String city) throws EJBException;
    void setState(String state) throws EJBException;
    void setZipCode(String zip) throws EJBException;
    Collection getAccounts() throws EJBException;
    void setAccounts(Collection accounts) throws EJBException;
    void addAccount(AccountDTO accountDTO) throws EJBException;
}</code></pre>
<ul>
<li>Bank 주소, 은행이 소유하는 계좌가 열거되어 있다. </li>
</ul>
<pre><code class="language-java">package com.example.banking;
import java.util.Collections;
import javax.ejb.*;

public abstract class Bank implements javax.ejb.EntityBean {
    // 비즈니스 논리
    public abstract String getStreetAddr1();
    public abstract String getStreetAddr2();
    public abstract String getCity();
    public abstract String getState();
    public abstract String getZipCode();
    public abstract void setStreetAddr1(String street1);
    public abstract void setStreetAddr2(String street2);
    public abstract void setCity(String city);
    public abstract void setState(String state);
    public abstract void setZipCode(String zip);
    public abstract Collection getAccounts();
    public abstract void setAccounts(Collection accounts);

    public void addAccount(AccountDTO accountDTO) {
        InitialContext context = new InitialContext();
        AccountHomeLocal accountHome = context.lookup(&quot;AccountHomeLocal&quot;);
        AccountLocal account = accountHome.create(accountDTO);
        Collection accounts = getAccounts();
        accounts.add(account);
    }

    // EJB 컨테이너 논리
    public abstract void setId(Integer id);
    public abstract Integer getId();
    public Integer ejbCreate(Integer id) { ... }
    public void ejbPostCreate(Integer id) { ... }

    // 나머지도 구현해야 하지만 일반적으로 비어있다.
    public void setEntityContext(EntityContext ctx) {}
    public void unsetEntityContext() {}
    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void ejbLoad() {}
    public void ejbStore() {}
    public void ejbRemove() {}
}</code></pre>
<ul>
<li>비즈니스 논리는 컨테이너와 <code>강결합</code>이다.  클래스를 생성할 때 컨테이너에서 파생해야 하며 컨테이너가 요구하는 생명주기 메서드도 제공해야 한다</li>
<li>비즈니스 논리의 덩치가 매우 큰 컨테이너와 강결합 된 상태이기에 독자적인 단위테스트가 힘들다</li>
<li>프레임워크 밖에서 재사용하기가 거의 불가능하다</li>
<li>상속조차 불가능하다</li>
</ul>
<h3 id="횡단-관심사cross-cutting">횡단 관심사(cross-cutting)</h3>
<ul>
<li>EjB2는 이렇게 관심사가 잘 분리되지 않았지만 일부 영역에서는 또 제대로 분리된 부분이 있다</li>
<li>원하는 트랜잭션, 보안, 일부 영속적인 동작은 소스 코드가 아닌 배치 기술자에서 정의한다</li>
<li>관심사가 여러 객체에 흩어져있는 기능, 관심들을 횡단 관심사(cross-cutting-concerns)라 한다</li>
<li>이러한 횡단관심사를 관점 지향 프로그래밍AOP(Aspect-Oriented-Programming) 방법론을 이용해 모듈성을 확보한다<pre><code> - AOP에서 관점(aspect) 라는 모듈 구성 개념은 &quot;특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다&quot; 라고 명시한다
 - Ex: 프로그래머는 영속적으로 저장할 객체와 속성을 선언 후 영속성 책임을 영속성 프레임워크에 위임한다 그러면 AOP 프레임워크는 대상 코드에 영향을 미치지 않는 상태로 동작 방식을 변경한다</code></pre></li>
</ul>
<h2 id="4-자바에서-사용하는-관점혹은-유사한-메커니즘-세-가지">4. 자바에서 사용하는 관점(혹은 유사한) 메커니즘 세 가지</h2>
<h3 id="자바-프록시">자바 프록시</h3>
<ul>
<li>단순한 상황에 적합하다<pre><code> - 개별 객체나 클래스에서 메서드 호출을 감싸는 경우</code></pre></li>
<li>JDK 에서 제공하는 동적 프록시는 인터페이스만 지원한다</li>
<li>Bank에서 계좌 목록을 조회/설정하는 예제</li>
</ul>
<pre><code class="language-java">    import java.utils.*;

    // 은행 추상화
    public interface Bank {
        Collection&lt;Account&gt; getAccounts();
        void setAccounts(Collection&lt;Account&gt; accounts);
    }

    // BankImpl.java
    import java.utils.*;

    // 추상화를 위한 POJO(&quot;Plain Old Jaa Object&quot;) 
    public class BankImpl implements Bank {
        private List&lt;Account&gt; accounts;

        public Collection&lt;Account&gt; getAccounts() {
            return accounts;
        }

        public void setAccounts(Collection&lt;Account&gt; accounts) {
            this.accounts = new ArrayList&lt;Account&gt;();
            for (Account account: accounts) {
                this.accounts.add(account);
            }
        }
    }
    // BankProxyHandler.java
    import java.lang.reflect.*;
    import java.util.*;

    // 프록시 API가 필요한 &quot;Invocationhandler&quot;
    public class BankProxyHandler implements InvocationHandler {
        private Bank bank;

        public BankHandler (Bank bank) {
            this.bank = bank;
        }

        // InvocationHandler에 정의된 메서드
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals(&quot;getAccounts&quot;)) {
                bank.setAccounts(getAccountsFromDatabase());

                return bank.getAccounts();
            } else if (methodName.equals(&quot;setAccounts&quot;)) {
                bank.setAccounts((Collection&lt;Account&gt;) args[0]);
                setAccountsToDatabase(bank.getAccounts());

                return null;
            } else {
                ...
            }
        }

        // 세부사항
        protected Collection&lt;Account&gt; getAccountsFromDatabase() { ... }
        protected void setAccountsToDatabase(Collection&lt;Account&gt; accounts) { ... }
    }

    // 다른 곳에 위치하는 메서드
    Bank bank = (Bank) Proxy.newProxyInstance(
        Bank.class.getClassLoader(),
        new Class[] { Bank.class },
        new BankProxyHandler(new BankImpl())
    );</code></pre>
<ul>
<li>Bank 프록시객체에서는  InvocationHandler를 구현하여 invoke를 오버라이딩한다</li>
<li>프록시에 호출되는 Bank 메소드를 구현하는데 사용하며 Reflection API를 사용해 제네릭스 메소드를 상응하는 BankImpl 메서드로 매핑한다</li>
<li>프록시 객체에서는 인터페이스를 통해 실 구현체로부터 모델과 로직을 구분했다</li>
<li>하지만, 단순한 예제에서도 코드가 많고 복잡하다. 즉, 프록시를 사용하면 깨끗한 코드 작성이 어렵다</li>
<li>시스템 단위로 실행&#39;지점&#39;을 명시하는 메커니즘도 제공하지 않는다</li>
</ul>
<h3 id="순수-자바-aop-프레임워크">순수 자바 AOP 프레임워크</h3>
<ul>
<li>대부분의 프록시 코드는 비슷하기에 자동화가 가능하며 그러한 도구가 스프링 AOP, jBoss AOP등의 프레임워크가 있다<pre><code> - 스프링은 비즈니스 논리를 순수하게 도메인에 초점을 맞춘 PJOJ로 구현하는데 POJO는  엔터프라이즈 프레임워크나 다른 도메인에 의존하지 않기에 테스트가 더 쉽고 간단하다 그렇기에 자기 도메인에 집중할 수 있게 해준다</code></pre></li>
<li>설정파일이나 API를 사용해 필수적인 애플리케이션 기반 구조를 구현한다</li>
<li>스프링의 설정파일</li>
</ul>
<pre><code class="language-java">     &lt;beans&gt;
        ...
        &lt;bean id=&quot;appDataSource&quot;
            class=&quot;org.apache.commons.dbcp.BasicDataSource&quot;
            destroy-method=&quot;close&quot;
            p:driverClassName=&quot;com.mysql.jdbc.Driver&quot;
            p:url=&quot;jdbc:mysql://localhost:3306/mydb&quot;
            p:username=&quot;me&quot;/&gt;

        &lt;bean id=&quot;bankDataAccessObject&quot;
            class=&quot;com.example.banking.persistence.BankDataAccessObject&quot;
            p:dataSource-ref=&quot;appDataSource&quot;/&gt;

        &lt;bean id=&quot;bank&quot;
            class=&quot;com.example.banking.model.Bank&quot;
            p:dataAccessObject-ref=&quot;bankDataAccessObject&quot;/&gt;
        ...
    &lt;/beans&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/8aba83bf-9892-40da-aefb-984d6eaf76a7/image.png" alt=""></p>
<ul>
<li><p>위  설정파일을 도식화 한게 위 그림이다</p>
</li>
<li><p>각각의 빈들은 상자속의 상자 마치 러시아 인형처럼 <code>Bank</code> 도메인 객체는 자료 접근 객체(<code>Data Accessor Object, DAO</code>)로 프록시 되었으며 이 객체도 JDBC 자료 소스로 프록시 되어있다</p>
</li>
<li><p>사용자(client)는 <code>Bank</code>의 메소드를 호출한다고 생각하지만 실제로는 <code>Bank POJO</code>의 기본 동작을 확장한 중첩 <code>DECORATOR</code>  객체 집합의 가장 외곽과 통신한다</p>
</li>
<li><p>여기서 필요하다면 트랜잭션이나 캐싱 등에도 <code>DECORATOR</code>를 추가할 수도 있다</p>
</li>
<li><p>위와 같이 생성된 최상위 Bank 프록시 객체를 생성하는 방법은 아래와 같으며 사실상 애플리케이션은 스프링과 독립적이라고 볼 수 있다 기존의 복잡하던 프록시 객체 생성방법을 사용하지 않아도 된다</p>
<pre><code class="language-java">  XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource(&quot;app.xml&quot;, getClass()));
  Bank bank = (Bank) bf.getBean(&quot;bank&quot;);</code></pre>
</li>
<li><p>XML은 장황하고 읽기 어렵고, 설정 파일에 명시된 <code>정책</code>이  겉으로 노출되지 않지만 자동으로 생성되는 프록시나 관점 논리보다 단순하다. 그래서 스프링 프레임워크에서는 EjB 버전 3을 완전히 뜯어 고쳐서 XML설정 파일과 자바 5 애너테이션 기능을 사용해 횡단 관심사를 선언적으로 지원하는 모델을 따른다</p>
</li>
<li><p>EjB3 Bank EJB 코드</p>
<pre><code class="language-java">  package com.example.banking.model;

  import javax.persistence.*;
  import java.util.ArrayList;
  import java.util.Collection;

  @Entity
  @Table(name = &quot;BANKS&quot;)
  public class Bank implements java.io.Serializable {
      @Id @GeneratedValue(strategy=GenerationType.AUTO)
      private int id;

      @Embeddable // Bank의 데이터베이스 행에 &#39;인라인으로 포함된&#39; 객체
      public class Address {
          protected String streetAddr1;
          protected String streetAddr2;
          protected String city;
          protected String state;
          protected String zipCode;
      }

      @Embedded
      private Address address;

      @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy=&quot;bank&quot;)
      private Collection&lt;Account&gt; accounts = new ArrayList&lt;Account&gt;();
      public int getId() {
          return id;
      }

      public void setId(int id) {
          this.id = id;
      }

      public void addAccount(Account account) {
          account.setBank(this);
          accounts.add(account);
      }

      public Collection&lt;Account&gt; getAccounts() {
          return accounts;
      }

      public void setAccounts(Collection&lt;Account&gt; accounts) {
          this.accounts = accounts;
      }
  }</code></pre>
</li>
<li><p>훨씬 깨끗해졌다.  엔티티의 상세한 정보는 애너테이션 안에 기술되어 있기 때문에 코드는 깔끔하다</p>
</li>
<li><p>여기서 애너테이션에 있는 영속성 정보를  XML 배치 기술자로 옮기면 POJO만 남는다</p>
</li>
</ul>
<h3 id="aspectj-관점">AspectJ 관점</h3>
<ul>
<li>관심사를 관점으로 분리하는 가장 강력한 도구</li>
<li>언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다</li>
<li>새 도구를 사용하고 새 언어 문법과 사용법을 익혀야 한다는 단점이 있다<ul>
<li>AjpectJ 애노테이션 폼이 부담을 어느정도 완화 해 주기는 한다</li>
</ul>
</li>
</ul>
<h2 id="5-테스트-주도-시스템-아키텍처-구축">5. 테스트 주도 시스템 아키텍처 구축</h2>
<ul>
<li>관점 혹은 유사 개념으로 관심사를 분리하는 방식은 매우 강력하여 애플리케이션 논리를 <code>POJO</code>로 작성할 수 있다면 즉 코드 수준에서 아키텍처 관심사 분리가 가능하다면 진정한 <code>테스트 주도 아키텍처 구축</code>이 가능하다</li>
<li>즉 처음부터 크게 디자인하는 BDUF(Big Design Up Front)를 추구하지 않아도 된다</li>
<li>작지만 멋지게 분리된 아키텍처를 진행해 빠른 결과를 낸 후 기반 구조를 추가하여 확장을 할 수 있다는 의미이다</li>
<li>최선의 시스템 구조는 각기 POJO(또는 다른) 객체로 구현되는 모듈화 된 관심사 영역(도메인)으로 구성된다</li>
<li>이렇게 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합한다</li>
<li>이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있다</li>
</ul>
<h2 id="6-의사-결정을-최적화하라">6. 의사 결정을 최적화하라</h2>
<ul>
<li>책임은 가장 적합한 사람에게 맡기면 가장 좋다</li>
<li>최대한 정보를 모아 최선의 결정을 하기 위해 가장한 마지막 순간까지 결정을 미루는게 좋다<pre><code> - 성급한 결정은 충분치 않은 지식과 자료로 내린 결정이다</code></pre></li>
</ul>
<h2 id="7-명백한-가치가-있을-때-현명하게-사용하라">7. 명백한 가치가 있을 때 현명하게 사용하라</h2>
<ul>
<li>표준이라는 의미로 충분히 필요가 없을때도 불필요하게 사용하는 것은 좋지 않다<pre><code> - EjB2는 표준이라는 이유로 더 가볍고 간단한 설계만으로 충분한 프로젝트에서도 사용해서 무거워지는 경우가 있었다</code></pre></li>
<li>표준을 사용하면 재사용성과 지식이 있는 기술자 구인이 쉽지만 표준이 너무 방대할 경우 표준이 나오는 시기가 너무 오래 걸려 업계가 기다리지 못해 무용지물이 될 수 있다</li>
</ul>
<h2 id="8-시스템은-도메인-특화-언어가-필요하다">8. 시스템은 도메인 특화 언어가 필요하다</h2>
<ul>
<li>최근 DSL(Domain Specific Language)이 조명받기 시작했다</li>
<li>DSL은 간단한 스크립트 언어나 표준 언어로 구현한 API를 말한다   </li>
<li>좋은 DSL은 도메인 개념과 그 개념을 구현한 코드 사이에 존재하는 <code>의사소통 간극</code>을 줄여준다.<pre><code> - 애자일 기법이 팀과 프로젝트 이해관계자 사이에 의사소통 간극을 줄여주는 것처럼</code></pre></li>
<li>도메인 특화 언어를 사용하면 고차원 정책에서 저차원 세부사항에 이르기까지 모든 추상화 수준과 모든 도메인을 POJO로 표현할 수 있다</li>
</ul>
<h2 id="9-결론">9. 결론</h2>
<ul>
<li>코드, 클래스와 마찬가지로 시스템도 깨끗해야 한다</li>
<li>아키텍처가 깨끗하지 못하면 도메인 논리를 흐리며 기민성을 떨어트린다</li>
<li>도메인 논리가 흐려지면 제품의 품질이 저하되며 버그가 생길 위험이 높아진다</li>
<li>모든 추상화 단계에서 의도는 명확히 표현해야 하는데 이를 위해 <code>POJO</code>를 작성해 관점 혹은 유사한 메커니즘을 사용해 구현 관심사를 분리해야 한다</li>
<li>결과적으로 시스템이던 클래스던 컴팩트하게 작성하여 사용해야 한다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>시스템은 도메인 특화 언어가 필요하다는 것이 잘 와닿지 않았다</li>
<li>이론적으로는 이해가 되지만 실제로 깨끗한 시스템을 본 적이 없어서 더 와닿지 않는것이지 않을까 싶었다</li>
<li>결국 간결하고 가독성이 좋은 코드로 이루어진 가벼운 시스템을 만드는 것이 중요하다는  결론에  도달할 수는 있었다</li>
</ul>
<h3 id="도메인-특화-언어">도메인 특화 언어</h3>
<ul>
<li>도메인 특화 언어에 대해 잘 와닿지 않아서 간결하게 정리해보았다</li>
<li><a href="https://velog.io/@seunghee-ryu/%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%96%B8%EC%96%B4">https://velog.io/@seunghee-ryu/%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%96%B8%EC%96%B4</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[10 클래스]]></title>
            <link>https://velog.io/@seunghee-ryu/10-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@seunghee-ryu/10-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Sun, 17 Dec 2023 10:46:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/1ae8ea7b-b540-42ac-8207-6651f6157654/image.png" alt=""></p>
<ul>
<li>클래스를 깨끗하게 설계하고 작성하는 법에 대해 설명한다</li>
</ul>
<h2 id="클래스-체계">클래스 체계</h2>
<ul>
<li>JAVA Convention에 따르면 가장 먼저 변수 목록이 나온다.</li>
<li>static public --&gt; static private --&gt; private 인스턴스 --&gt; (public은 필요한 경우가 거의 없다)  </li>
<li>변수목록 다음에는 공개 함수가 나온다. 비공개 함수는 자신을 호출 하는 공개 함수 직후에 나온다  </li>
<li>즉, 추상화 단계가 순차적으로 내려간다</li>
</ul>
<h3 id="캡슐화">캡슐화</h3>
<ul>
<li>변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 하는 것은 아니다  </li>
<li>우리에게 테스트는 중요하므로 테스트를 위해 protected로 선언해서 접근을 허용하기도 한다  </li>
<li>하지만 비공개 상태를 유지할 온갖 방법을 강구하고, 캡슐화를 풀어주는 결정은 언제나 최후의 수단이다</li>
</ul>
<h2 id="클래스는-작아야-한다">클래스는 작아야 한다!</h2>
<ul>
<li>클래스는 작아야한다</li>
<li>함수와는 다르게 클래스는 맡은 책임을 측정한다</li>
</ul>
<h3 id="개념은-빈-행으로-분리하라">개념은 빈 행으로 분리하라</h3>
<ul>
<li>코드의 각 줄은 수식이나 절을 나타내고 여러 줄의 묶음은 완결된 생각 하나를 표현한다  </li>
<li>생각 사이에는 빈 행을 넣어 분리해야한다 그렇지 않다면 단지 줄바꿈만 다를 뿐인데도 코드 가독성이 현저히 떨어진다</li>
</ul>
<pre><code class="language-java">// 어마어마하게 큰 슈퍼 만능 클래스
public class SuperDashboard extends JFrame implements MetaDataUser {
    public String getCustomizerLanguagePath()
    public void setSystemConfigPath(String systemConfigPath) 
    public String getSystemConfigDocument()
    public void setSystemConfigDocument(String systemConfigDocument) 
    public boolean getGuruState()
    public boolean getNoviceState()
    public boolean getOpenSourceState()
    public void showObject(MetaObject object) 
    public void showProgress(String s)
    public boolean isMetadataDirty()
    public void setIsMetadataDirty(boolean isMetadataDirty)
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public void setMouseSelectState(boolean isMouseSelected) 
    public boolean isMouseSelected()
    public LanguageManager getLanguageManager()
    public Project getProject()
    public Project getFirstProject()
    public Project getLastProject()
    public String getNewProjectName()
    public void setComponentSizes(Dimension dim)
    public String getCurrentDir()
    public void setCurrentDir(String newDir)
    public void updateStatus(int dotPos, int markPos)
    public Class[] getDataBaseClasses()
    public MetadataFeeder getMetadataFeeder()
    public void addProject(Project project)
    public boolean setCurrentProject(Project project)
    public boolean removeProject(Project project)
    public MetaProjectHeader getProgramMetadata()
    public void resetDashboard()
    public Project loadProject(String fileName, String projectName)
    public void setCanSaveMetadata(boolean canSave)
    public MetaObject getSelectedObject()
    public void deselectObjects()
    public void setProject(Project project)
    public void editorAction(String actionName, ActionEvent event) 
    public void setMode(int mode)
    public FileManager getFileManager()
    public void setFileManager(FileManager fileManager)
    public ConfigManager getConfigManager()
    public void setConfigManager(ConfigManager configManager) 
    public ClassLoader getClassLoader()
    public void setClassLoader(ClassLoader classLoader)
    public Properties getProps()
    public String getUserHome()
    public String getBaseDir()
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber()
    public MetaObject pasting(MetaObject target, MetaObject pasted, MetaProject project)
    public void processMenuItems(MetaObject metaObject)
    public void processMenuSeparators(MetaObject metaObject) 
    public void processTabPages(MetaObject metaObject)
    public void processPlacement(MetaObject object)
    public void processCreateLayout(MetaObject object)
    public void updateDisplayLayer(MetaObject object, int layerIndex) 
    public void propertyEditedRepaint(MetaObject object)
    public void processDeleteObject(MetaObject object)
    public boolean getAttachedToDesigner()
    public void processProjectChangedState(boolean hasProjectChanged) 
    public void processObjectNameChanged(MetaObject object)
    public void runProject()
    public void setAçowDragging(boolean allowDragging) 
    public boolean allowDragging()
    public boolean isCustomizing()
    public void setTitle(String title)
    public IdeMenuBar getIdeMenuBar()
    public void showHelper(MetaObject metaObject, String propertyName) 

    // ... many non-public methods follow 
}</code></pre>
<pre><code class="language-java">// 메소드를 5개로 줄인다고 하더라도 여전히 책임이 많다

public class SuperDashboard extends JFrame implements MetaDataUser {
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber() 
}</code></pre>
<ul>
<li>클래스 이름은 해당 클래스 책임을 기술해야된다</li>
<li>작명은 클래스 크기를 줄이는 첫번째 관문이다</li>
<li>간결한 이름이 떠오르지 않는다면 클래스 책임이 너무 많아서이다 (e.g. Chapter 2장에 언급한 것 처럼 Manager, Processor, Super 등)</li>
<li>또한 클래스 설명은 &quot;if&quot;, &quot;and&quot;, &quot;or&quot;, &quot;but&quot;을 사용하지 않고 25 단어 내외로 가능해야된다</li>
<li>한글의 경우 만약, 그리고, ~하며, 하지만 이 들어가면 안된다</li>
</ul>
<h3 id="단일-책임의-원칙---single-responsibility-principle">단일 책임의 원칙 - Single Responsibility Principle</h3>
<ul>
<li>단일 책임의 원칙 (이하 SRP)은 클래스나 모듈을 변경할 이유가 단 하나뿐이어야 한다는 원칙이다</li>
<li>책임, 즉 변경할 이유를 파악하려고 애쓰다 보면 코드를 추상화 하기도 쉬워진다.  </li>
</ul>
<pre><code class="language-java">// 이 코드는 작아보이지만, 변경할 이유가 2가지이다.
public class SuperDashboard extends JFrame implements MetaDataUser {
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber() 
}</code></pre>
<pre><code class="language-java">// 위 코드에서 버전 정보를 다루는 메서드 3개를 따로 빼서
// Version이라는 독자적인 클래스를 만들어 다른 곳에서 재사용하기 쉬워졌다.
public class Version {
    public int getMajorVersionNumber() 
    public int getMinorVersionNumber() 
    public int getBuildNumber()
}</code></pre>
<ul>
<li>SRP는 객체지향설계에서 더욱 중요한 개념이고 지키기 수월한 개념인데 개발자가 가장 무시하는 규칙 중 하나이다</li>
<li>대부분의 프로그래머들이 돌아가는 소프트웨어에 초점을 맞춘다. 전적으로 올바른 태도이기는 하지만 돌아가는 소프트웨어가 작성되면 깨끗하고 체계적인 소프트웨어라는 다음 관심사로 전환을 해야한다</li>
<li>작은 클래스가 많은 시스템이든, 큰 클래스가 몇 개뿐인 시스템이든 돌아가는 부품은 그 수가 비슷하다</li>
</ul>
<blockquote>
<p>&quot;도구 상자를 어떻게 관리하고 싶은가?<br>   작은 서랍을 많이 두고 기능과 이름이 명확한 컴포넌트를 나눠 넣고 싶은가?<br>   아니면 큰 서랍 몇개를 두고 모두 던져 넣고 싶은가?&quot;  </p>
</blockquote>
<ul>
<li>큰 클래스 몇개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다 </li>
<li>작은 클래스는 각자 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다</li>
</ul>
<h3 id="응집도">응집도</h3>
<ul>
<li>클래스는 인스턴스 변수 수가 작아야 한다</li>
<li>각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다</li>
<li>일반적으로 메서드가 변수를 더 많이 사용할 수록 메서드와 클래스는 응집도가 더 높다  </li>
<li>모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높지만 이런 클래스는 가능하지도 바람직하지도 않다</li>
<li>하지만 가능한한 응집도가 높은 클래스를 지향해야 한다</li>
<li>응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미기 때문이다</li>
</ul>
<pre><code class="language-java">
// Stack을 구현한 코드, 응집도가 높은 편이다.
public class Stack {
    private int topOfStack = 0;
    List&lt;Integer&gt; elements = new LinkedList&lt;Integer&gt;();

    public int size() { 
        return topOfStack;
    }

    public void push(int element) { 
        topOfStack++; 
        elements.add(element);
    }

    public int pop() throws PoppedWhenEmpty { 
        if (topOfStack == 0)
            throw new PoppedWhenEmpty();
        int element = elements.get(--topOfStack); 
        elements.remove(topOfStack);
        return element;
    }
}</code></pre>
<ul>
<li>함수를 작게, 매개변수 목록을 짧게라는 전략을 따르다 보면 때때로 몇몇 메서드만이 사용하는 인스턴스 변수가 아주 많아진다</li>
<li>이는 새로운 클래스를 쪼개야 한다는 신호다</li>
<li>응집도가 높아지도록 변수와 메서드를 적절히 분리해 새로운 클래스 두세 개로 쪼개준다</li>
</ul>
<h3 id="응집도를-유지하면-작은-클래스-여럿이-나온다">응집도를 유지하면 작은 클래스 여럿이 나온다</h3>
<ul>
<li><p>큰 함수를 작은 함수 여럿으로 나누기만 해도 클래스 수가 많아진다</p>
</li>
<li><p>ex)   </p>
<pre><code> - 변수가 아주 많은 큰 함수가 하나 있다  
 - --&gt; 큰 함수 일부를 작은 함수로 빼내고 싶다   
 - --&gt; 빼내려는 코드가 큰 함수에 정의 된 변수를 많이 사용한다  
 - --&gt; 변수들을 새 함수에 인수로 넘기지 않아도 된다
 - --&gt; 변수들을 클래스 인스턴스 변수로 승격 시키면 인수가 필요없다 하지만 응집력이 낮아진다
 - --&gt; 몇몇 함수가 몇몇 인스턴스 변수만 사용한다면 독자적인 클래스로 분리해도 된다</code></pre></li>
<li><p>큰 함수를 작은 함수 여럿으로 쪼개다 보면 종종 작은 클래스 여럿으로 쪼갤 기회가 생긴다</p>
</li>
</ul>
<pre><code class="language-java">
// 이 하나의 크고 더러운 함수를 여러 함수와 클래스로 잘게 나누면서 적절한 이름을 부여해본다
package literatePrimes;

public class PrintPrimes {
    public static void main(String[] args) {
        final int M = 1000; 
        final int RR = 50;
        final int CC = 4;
        final int WW = 10;
        final int ORDMAX = 30; 
        int P[] = new int[M + 1]; 
        int PAGENUMBER;
        int PAGEOFFSET; 
        int ROWOFFSET; 
        int C;
        int J;
        int K;
        boolean JPRIME;
        int ORD;
        int SQUARE;
        int N;
        int MULT[] = new int[ORDMAX + 1];

        J = 1;
        K = 1; 
        P[1] = 2; 
        ORD = 2; 
        SQUARE = 9;

        while (K &lt; M) { 
            do {
                J = J + 2;
                if (J == SQUARE) {
                    ORD = ORD + 1;
                    SQUARE = P[ORD] * P[ORD]; 
                    MULT[ORD - 1] = J;
                }
                N = 2;
                JPRIME = true;
                while (N &lt; ORD &amp;&amp; JPRIME) {
                    while (MULT[N] &lt; J)
                        MULT[N] = MULT[N] + P[N] + P[N];
                    if (MULT[N] == J) 
                        JPRIME = false;
                    N = N + 1; 
                }
            } while (!JPRIME); 
            K = K + 1;
            P[K] = J;
        } 
        {
            PAGENUMBER = 1; 
            PAGEOFFSET = 1;
            while (PAGEOFFSET &lt;= M) {
                System.out.println(&quot;The First &quot; + M + &quot; Prime Numbers --- Page &quot; + PAGENUMBER);
                System.out.println(&quot;&quot;);
                for (ROWOFFSET = PAGEOFFSET; ROWOFFSET &lt; PAGEOFFSET + RR; ROWOFFSET++) {
                    for (C = 0; C &lt; CC;C++)
                        if (ROWOFFSET + C * RR &lt;= M)
                            System.out.format(&quot;%10d&quot;, P[ROWOFFSET + C * RR]); 
                    System.out.println(&quot;&quot;);
                }
                System.out.println(&quot;\f&quot;); PAGENUMBER = PAGENUMBER + 1; PAGEOFFSET = PAGEOFFSET + RR * CC;
            }
        }
    }
}</code></pre>
<pre><code class="language-java">
// 변경 후
package literatePrimes;

public class PrimePrinter {
    public static void main(String[] args) {
        final int NUMBER_OF_PRIMES = 1000;
        int[] primes = PrimeGenerator.generate(NUMBER_OF_PRIMES);

        final int ROWS_PER_PAGE = 50; 
        final int COLUMNS_PER_PAGE = 4; 
        RowColumnPagePrinter tablePrinter = 
            new RowColumnPagePrinter(ROWS_PER_PAGE, 
                        COLUMNS_PER_PAGE, 
                        &quot;The First &quot; + NUMBER_OF_PRIMES + &quot; Prime Numbers&quot;);
        tablePrinter.print(primes); 
    }
}</code></pre>
<pre><code class="language-java">package literatePrimes;

import java.io.PrintStream;

public class RowColumnPagePrinter { 
    private int rowsPerPage;
    private int columnsPerPage; 
    private int numbersPerPage; 
    private String pageHeader; 
    private PrintStream printStream;

    public RowColumnPagePrinter(int rowsPerPage, int columnsPerPage, String pageHeader) { 
        this.rowsPerPage = rowsPerPage;
        this.columnsPerPage = columnsPerPage; 
        this.pageHeader = pageHeader;
        numbersPerPage = rowsPerPage * columnsPerPage; 
        printStream = System.out;
    }

    public void print(int data[]) { 
        int pageNumber = 1;
        for (int firstIndexOnPage = 0 ; 
            firstIndexOnPage &lt; data.length ; 
            firstIndexOnPage += numbersPerPage) { 
            int lastIndexOnPage =  Math.min(firstIndexOnPage + numbersPerPage - 1, data.length - 1);
            printPageHeader(pageHeader, pageNumber); 
            printPage(firstIndexOnPage, lastIndexOnPage, data); 
            printStream.println(&quot;\f&quot;);
            pageNumber++;
        } 
    }

    private void printPage(int firstIndexOnPage, int lastIndexOnPage, int[] data) { 
        int firstIndexOfLastRowOnPage =
        firstIndexOnPage + rowsPerPage - 1;
        for (int firstIndexInRow = firstIndexOnPage ; 
            firstIndexInRow &lt;= firstIndexOfLastRowOnPage ;
            firstIndexInRow++) { 
            printRow(firstIndexInRow, lastIndexOnPage, data); 
            printStream.println(&quot;&quot;);
        } 
    }

    private void printRow(int firstIndexInRow, int lastIndexOnPage, int[] data) {
        for (int column = 0; column &lt; columnsPerPage; column++) {
            int index = firstIndexInRow + column * rowsPerPage; 
            if (index &lt;= lastIndexOnPage)
                printStream.format(&quot;%10d&quot;, data[index]); 
        }
    }

    private void printPageHeader(String pageHeader, int pageNumber) {
        printStream.println(pageHeader + &quot; --- Page &quot; + pageNumber);
        printStream.println(&quot;&quot;); 
    }

    public void setOutput(PrintStream printStream) { 
        this.printStream = printStream;
    } 
}</code></pre>
<pre><code class="language-java">package literatePrimes;

import java.util.ArrayList;

public class PrimeGenerator {
    private static int[] primes;
    private static ArrayList&lt;Integer&gt; multiplesOfPrimeFactors;

    protected static int[] generate(int n) {
        primes = new int[n];
        multiplesOfPrimeFactors = new ArrayList&lt;Integer&gt;(); 
        set2AsFirstPrime(); 
        checkOddNumbersForSubsequentPrimes();
        return primes; 
    }

    private static void set2AsFirstPrime() { 
        primes[0] = 2; 
        multiplesOfPrimeFactors.add(2);
    }

    private static void checkOddNumbersForSubsequentPrimes() { 
        int primeIndex = 1;
        for (int candidate = 3 ; primeIndex &lt; primes.length ; candidate += 2) { 
            if (isPrime(candidate))
                primes[primeIndex++] = candidate; 
        }
    }

    private static boolean isPrime(int candidate) {
        if (isLeastRelevantMultipleOfNextLargerPrimeFactor(candidate)) {
            multiplesOfPrimeFactors.add(candidate);
            return false; 
        }
        return isNotMultipleOfAnyPreviousPrimeFactor(candidate); 
    }

    private static boolean isLeastRelevantMultipleOfNextLargerPrimeFactor(int candidate) {
        int nextLargerPrimeFactor = primes[multiplesOfPrimeFactors.size()];
        int leastRelevantMultiple = nextLargerPrimeFactor * nextLargerPrimeFactor; 
        return candidate == leastRelevantMultiple;
    }

    private static boolean isNotMultipleOfAnyPreviousPrimeFactor(int candidate) {
        for (int n = 1; n &lt; multiplesOfPrimeFactors.size(); n++) {
            if (isMultipleOfNthPrimeFactor(candidate, n)) 
                return false;
        }
        return true; 
    }

    private static boolean isMultipleOfNthPrimeFactor(int candidate, int n) {
        return candidate == smallestOddNthMultipleNotLessThanCandidate(candidate, n);
    }

    private static int smallestOddNthMultipleNotLessThanCandidate(int candidate, int n) {
        int multiple = multiplesOfPrimeFactors.get(n); 
        while (multiple &lt; candidate)
            multiple += 2 * primes[n]; 
        multiplesOfPrimeFactors.set(n, multiple); 
        return multiple;
    } 
}</code></pre>
<ul>
<li>가장 먼저 원래 프로그램의 정확한 동작을 검증하는 테스트 케이스를 작성한다</li>
<li>그 다음 한번에 하나씩 여러번에 걸쳐 코드를 변경하고 코드를 변경 할 때 마다 테스트를 수행해 원래 프로그램과 동일하게 동작하는지 확인한다</li>
</ul>
<h2 id="변경하기-쉬운-클래스">변경하기 쉬운 클래스</h2>
<ul>
<li>시스템은 변경이 불가피하며 변경이 있을 때 마다 의도대로 동작하지 않을 위험이 따른다</li>
<li>깨끗한 시스템은 클래스를 체계적으로 관리해 변경에 따르는 위험을 최대한 낮춘다  </li>
</ul>
<pre><code class="language-java">// 해당 코드는 새로운 SQL문을 지원할 때 손대야 하고, 기존 SQL문을 수정할 때도 손대야 하므로 SRP위반

public class Sql {
    public Sql(String table, Column[] columns)
    public String create()
    public String insert(Object[] fields)
    public String selectAll()
    public String findByKey(String keyColumn, String keyValue)
    public String select(Column column, String pattern)
    public String select(Criteria criteria)
    public String preparedInsert()
    private String columnList(Column[] columns)
    private String valuesList(Object[] fields, final Column[] columns) private String selectWithCriteria(String criteria)
    private String placeholderList(Column[] columns)
}</code></pre>
<ul>
<li>클래스 일부에서만 사용되는 비공개 메서드는 코드 개선의 잠재적인 여지를 시사한다</li>
</ul>
<pre><code class="language-java">// 공개 인터페이스를 전부 SQL 클래스에서 파생하는 클래스로 만들고, 비공개 메서드는 해당 클래스로 옮기고 공통된 인터페이스는 따로 클래스로 뺐다
// 이렇게 하면 update문 추가 시에 기존의 클래스를 건드릴 이유가 없어진다.

    abstract public class Sql {
        public Sql(String table, Column[] columns) 
        abstract public String generate();
    }
    public class CreateSql extends Sql {
        public CreateSql(String table, Column[] columns) 
        @Override public String generate()
    }

    public class SelectSql extends Sql {
        public SelectSql(String table, Column[] columns) 
        @Override public String generate()
    }

    public class InsertSql extends Sql {
        public InsertSql(String table, Column[] columns, Object[] fields) 
        @Override public String generate()
        private String valuesList(Object[] fields, final Column[] columns)
    }

    public class SelectWithCriteriaSql extends Sql { 
        public SelectWithCriteriaSql(
        String table, Column[] columns, Criteria criteria) 
        @Override public String generate()
    }

    public class SelectWithMatchSql extends Sql { 
        public SelectWithMatchSql(String table, Column[] columns, Column column, String pattern) 
        @Override public String generate()
    }

    public class FindByKeySql extends Sql public FindByKeySql(
        String table, Column[] columns, String keyColumn, String keyValue) 
        @Override public String generate()
    }

    public class PreparedInsertSql extends Sql {
        public PreparedInsertSql(String table, Column[] columns) 
        @Override public String generate() {
        private String placeholderList(Column[] columns)
    }

    public class Where {
        public Where(String criteria) public String generate()
    }

    public class ColumnList {
        public ColumnList(Column[] columns) public String generate()
    }</code></pre>
<ul>
<li>잘 짜여진 시스템은 추가와 수정에 있어서 건드릴 코드가 최소이다</li>
</ul>
<h3 id="변경으로부터-격리">변경으로부터 격리</h3>
<ul>
<li>OOP입문에서 concrete 클래스와 abstract 클래스가 있는데 concrete 클래스에 의존(상세한 구현에 의존)하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다 </li>
<li>그래서 인터페이스와 abstract 클래스를 사용해 구현이 미치는 영향을 격리시켜야 한다  </li>
<li>상세한 구현에 의존하는 코드는 테스트가 어렵다</li>
<li>그래서 추상화를 통해 테스트가 가능할 정도로 시스템의 결합도를 낮춤으로써<br>유연성과 재사용성도 더욱 높아진다</li>
<li>결함도가 낮다는 말은 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어있다는 뜻이다</li>
</ul>
<pre><code class="language-java">
// Portfolio 클래스를 구현하자, 그런데 이 클래스는 외부 TokyoStockExchange API를 사용해 포트폴리오 값을 계산한다
// 따라서 API 특성 상 시세 변화에 영향을 많이 받아 5분마다 값이 달라지는데 이때문에 테스트 코드를 짜기 쉽지 않다
// 그러므로 Portfolio에서 외부 API를 직접 호출하는 대신 StockExchange라는 인터페이스를 생성한 후 메서드를 선언한다

public interface StockExchange { 
    Money currentPrice(String symbol);
}</code></pre>
<pre><code class="language-java">// 이후 StockExchange 인터페이스를 구현하는 TokyoStockExchange 클래스를 구현한다.
// 그리고 Portfolio 생성자를 수정해 StockExchange 참조자를 인수로 받는다.

public Portfolio {
    private StockExchange exchange;
    public Portfolio(StockExchange exchange) {
        this.exchange = exchange; 
    }
    // ... 
}</code></pre>
<pre><code class="language-java">// 이제 TokyoStockExchange 클래스를 흉내내는 테스트용 클래스를 만들 수 있다.(FixedStockExchangeStub)
// 테스트용 클래스는 StockExchange 인터페이스를 구현하며 고정된 주가를 반환한다
// 그럼으로써 무난히 테스트 코드를 작성 할 수 있다

public class PortfolioTest {
    private FixedStockExchangeStub exchange;
    private Portfolio portfolio;

    @Before
    protected void setUp() throws Exception {
        exchange = new FixedStockExchangeStub(); 
        exchange.fix(&quot;MSFT&quot;, 100);
        portfolio = new Portfolio(exchange);
    }

    @Test
    public void GivenFiveMSFTTotalShouldBe500() throws Exception {
        portfolio.add(5, &quot;MSFT&quot;);
        Assert.assertEquals(500, portfolio.value()); 
    }
}
</code></pre>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>메소드를 클린하게 유지하는 것까지는 어떻게 할 수 있을것 같았는데 클래스를 클린하게 유지하는 것부터는 더 많은 연습이 필요할 것 같다</li>
<li>클래스가 단일책임원칙을 지키게 하면서 만들면 내부의 메서드 수가 줄어들게 될 것 같은데 그렇게 되면 관리해야 할 파일의 갯수가 늘어나게 될 것 같다</li>
<li>처음부터 클래스 설계를 제대로 하지 않으면 수정이 굉장히 많이 발생할 것 같았다</li>
<li>단일책임원칙을 지키는 클래스를 일하면서 본 적이 없는 것 같다는 점이 아쉬웠다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Null-safety]]></title>
            <link>https://velog.io/@seunghee-ryu/Null-safety</link>
            <guid>https://velog.io/@seunghee-ryu/Null-safety</guid>
            <pubDate>Sun, 17 Dec 2023 10:45:59 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[SpEL]]></title>
            <link>https://velog.io/@seunghee-ryu/SpEL</link>
            <guid>https://velog.io/@seunghee-ryu/SpEL</guid>
            <pubDate>Sun, 17 Dec 2023 10:45:43 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[데이터 바인딩]]></title>
            <link>https://velog.io/@seunghee-ryu/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</link>
            <guid>https://velog.io/@seunghee-ryu/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</guid>
            <pubDate>Sun, 17 Dec 2023 10:45:26 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Resource / Validation]]></title>
            <link>https://velog.io/@seunghee-ryu/Resource-Validation</link>
            <guid>https://velog.io/@seunghee-ryu/Resource-Validation</guid>
            <pubDate>Sun, 17 Dec 2023 10:45:11 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[IoC 컨테이너]]></title>
            <link>https://velog.io/@seunghee-ryu/%EC%8A%A4%ED%94%84%EB%A7%81-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88</link>
            <guid>https://velog.io/@seunghee-ryu/%EC%8A%A4%ED%94%84%EB%A7%81-IoC-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EB%B9%88</guid>
            <pubDate>Sun, 17 Dec 2023 10:30:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/300365c3-e82c-4465-b839-faade97e4c03/image.png" alt=""></p>
<h1 id="1-스프링-ioc-컨테이너와-빈">1. 스프링 IoC 컨테이너와 빈</h1>
<h2 id="inverse-of-controll">Inverse of Controll</h2>
<ul>
<li>의존 관계 주입(Dependency Injection)이라고 하며 어떤 객체가 사용하는 의존객체를 직접 생성하지 않고 외부로부터 주입 받아 사용하는 방법</li>
</ul>
<h2 id="스프링-ioc-컨테이너">스프링 IoC 컨테이너</h2>
<ul>
<li>IoC 컨테이너는 스프링에서 쓰이는 여러 객체들을 생성, 관리하는 객체다 여기서 IoC 컨테이너가 관리하는 객체들을 빈 이라고 한다</li>
<li>IoC 컨테이너 내부적으로, BeanFactory 객체를 통해, 빈 설정 소스로부터 빈 정의를 읽고, 빈을 구성 및 제공한다</li>
<li>객체를 의존성 주입을 통해 받고 싶으면, 해당 객체가 IoC 컨테이너에 등록되어 있어야 한다</li>
<li>BeanFactory : 스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스, 애플리케이션 컴포넌트의 중앙 저장소</li>
</ul>
<h2 id="bean이란-">Bean이란 ?</h2>
<ul>
<li>스프링 IoC 컨테이너가 관리하는 객체</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>의존성 관리가 쉽다</li>
<li>스코프 설정이 쉽게 가능하다<pre><code> - 싱글톤 : 흔히 알고 있는 싱글톤 패턴과 같이 하나만 생성
 - 프로포토 타입 : 빈이 생성될 때 마다 매번 다른 객체를 생성하는 방법</code></pre></li>
<li>라이프사이클 인터페이스를 지원한다</li>
</ul>
<h3 id="applicationcontext">ApplicationContext</h3>
<ul>
<li>BeanFactory를 상속받고, 스프링에서 제공하는 다양한 기능을 사용할 수 있는 인터페이스</li>
<li>ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver 등</li>
</ul>
<h1 id="2-applicationcontext">2. ApplicationContext</h1>
<h2 id="고전적인-application-설정">고전적인 Application 설정</h2>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd&quot; &gt;

    &lt;bean id=&quot;bookService&quot; class=&quot;me.freelife.BookService&quot;&gt; &lt;!-- bookService 빈 등록 --&gt;
        &lt;!-- bookRepository 프로퍼티에 bookRepository 빈을 레퍼런스로 주입  --&gt;
        &lt;!-- bookRepository name은 setter에서 가지고 온 것 --&gt;
        &lt;!-- ref는 레퍼런스로 다른 빈을 참조한다는 뜻 --&gt;
        &lt;!-- ref에는 setter 에 들어갈 수 있는 다른 bean의 id가 와야됨 --&gt;
        &lt;property name=&quot;bookRepository&quot; ref=&quot;bookRepository&quot;/&gt;
    &lt;/bean&gt;

    &lt;bean id=&quot;bookRepository&quot; class=&quot;me.freelife.BookRepository&quot;/&gt; &lt;!-- bookRepository 빈 등록 --&gt;

&lt;/beans&gt;</code></pre>
<ul>
<li><p>BookRepository</p>
<pre><code class="language-java">public class BookRepository {
}</code></pre>
</li>
<li><p>BookService</p>
<pre><code class="language-java">public class BookService {

  BookRepository bookRepository;

  public void setBookRepository(BookRepository bookRepository) {
      this.bookRepository = bookRepository;
  }
}</code></pre>
</li>
<li><p>application</p>
<pre><code class="language-java">public class Application {

  public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext(&quot;application.xml&quot;);
      String[] beanDefinitionNames = context.getBeanDefinitionNames();
      System.out.println(Arrays.toString(beanDefinitionNames));
      BookService bookService = (BookService) context.getBean(&quot;bookService&quot;);
      System.out.println(bookService.bookRepository != null);
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
### 일일히 빈으로 등록하는 단점을 보완하기 위한 방법
&gt; 단점을 보완하기위해 패키지를 스캔해서 @Component @Service @Repository 처럼  
&gt; @Component를 확장한 애노테이션들을 스캐닝해서 빈으로 자동으로 등록해줌  
&gt; 이렇게 등록된 빈은 @Autowired 나 @Inject를 통해 의존성을 주입하여 사용  
&gt; 애너테이션 기반에 빈을 등록하고 설정하는 기능은 스프링 2.5부터 가능한기능  
```xml
&lt;!-- @Compnent @Service @Repository 애노테이션을 스캐닝 해서 빈으로 등록 해줌 --&gt;
&lt;context:component-scan base-package=&quot;me.freelife&quot;/&gt;</code></pre><ul>
<li><p>BookRepository</p>
<pre><code class="language-java">@Repository
public class BookRepository {
}</code></pre>
</li>
<li><p>BookService</p>
<pre><code class="language-java">@Service
public class BookService {

  @Autowired
  BookRepository bookRepository;

  public void setBookRepository(BookRepository bookRepository) {
      this.bookRepository = bookRepository;
  }
}</code></pre>
</li>
</ul>
<h2 id="java-설정-파일">java 설정 파일</h2>
<ul>
<li>빈 설정 파일을 xml이 아닌 java로 설정하는 파일</li>
</ul>
<pre><code class="language-java">@Configuration
public class ApplicationConfig {

    @Bean
    public BookRepository bookRepository() {
        return new BookRepository();
    }

    @Bean
    public BookService bookService() {
        BookService bookService = new BookService();
        bookService.setBookRepository(bookRepository());
        return bookService;
    }
}</code></pre>
<ul>
<li><p>BookRepository</p>
<pre><code class="language-java">public class BookRepository {
}</code></pre>
</li>
<li><p>BookService</p>
<pre><code class="language-java">public class BookService {

  BookRepository bookRepository;

  public void setBookRepository(BookRepository bookRepository) {
      this.bookRepository = bookRepository;
  }
}</code></pre>
</li>
<li><p>application</p>
<pre><code class="language-java">public class Application {

  public static void main(String[] args) {
      //ApplicationConfig 를 빈 설정으로 사용하겠다는 설정
      ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

      // ApplicationContext context = new ClassPathXmlApplicationContext(&quot;application.xml&quot;);
      String[] beanDefinitionNames = context.getBeanDefinitionNames();
      System.out.println(Arrays.toString(beanDefinitionNames));
      BookService bookService = (BookService) context.getBean(&quot;bookService&quot;);
      System.out.println(bookService.bookRepository != null);
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
### ComponentScan
- Application.class 가 있는 곳에서 ComponentScan하여 빈으로 등록


```java
@Configuration
@ComponentScan(basePackageClasses = Application.class)
public class ApplicationConfig {

}</code></pre><h2 id="현재의-방식">현재의 방식</h2>
<ul>
<li>스프링 부트에서 사용하는 방식 @ComponentScan과 다수의 설정이 포함되어 있음</li>
</ul>
<pre><code class="language-java">@SpringBootApplication
public class Application {

    public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
    }

}</code></pre>
<h2 id="스프링-ioc-컨테이너의-역할">스프링 IoC 컨테이너의 역할</h2>
<ul>
<li>빈 인스턴스 생성</li>
<li>의존 관계 설정</li>
<li>빈 제공</li>
</ul>
<h2 id="appcliationcontext">AppcliationContext</h2>
<ul>
<li>ClassPathXmlApplicationContext (XML)</li>
<li>AnnotationConfigApplicationContext (Java)</li>
</ul>
<h2 id="빈-설정">빈 설정</h2>
<ul>
<li>빈 명세서</li>
<li>빈에 대한 정의를 담고 있다<ul>
<li>이름</li>
<li>클래스</li>
<li>스코프</li>
<li>생성자 아규먼트 (constructor)</li>
<li>프로퍼트 (setter)</li>
<li>..</li>
</ul>
</li>
</ul>
<h2 id="컴포넌트-스캔">컴포넌트 스캔</h2>
<ul>
<li>설정방법<ul>
<li>XML 설정에서는 context:component-scan</li>
<li>자바 설정에서 @ComponentScan</li>
</ul>
</li>
<li>특정 패키지 이하의 모든 클래스 중에 @Component 애노테이션을 사용한 클래스를 빈 으로 자동으로 등록 해 줌</li>
</ul>
<h1 id="3-autowired">3. @Autowired</h1>
<h2 id="required">required</h2>
<ul>
<li>@Autowired 를 처리 하다가 해당하는 빈의 타입을 못찾거나 의존성 주입을 할수 없는 경우 에러가 나고 애플리케이션 구동이 불가능함  </li>
<li>아래와 같이 설정하면 의존성 주입을 할 수없는 경우 패스함  </li>
</ul>
<pre><code class="language-java">@Autowired(required = false)</code></pre>
<ul>
<li>생성자를 사용한 의존성 주입에는 사용할 수 없음  </li>
<li>생성자를 사용한 의존성 주입은 빈을 만들때에도 개입이 됨   </li>
<li>생성자에 전달받아야 되는 타입의 빈이 없으면 인스턴스를 만들지 못하고 서비스도 등록이 되지 않음  </li>
</ul>
<h3 id="사용할-수-있는-위치">사용할 수 있는 위치</h3>
<ul>
<li>생성자 (스프링 4.3 부터는 생략 가능)</li>
<li>세터</li>
<li>필드</li>
</ul>
<h3 id="경우의-수">경우의 수</h3>
<ul>
<li>해당 타입의 빈이 없는 경우</li>
<li>해당 타입의 빈이 한 개인 경우</li>
<li>해당 타입의 빈이 여러 개인 경우<ul>
<li>빈 이름으로 시도<ul>
<li>같은 이름의 빈 찾으면 해당 빈 사용</li>
<li>같은 이름 못 찾으면 실패</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="같은-타입의-빈이-여러개-일-때">같은 타입의 빈이 여러개 일 때</h3>
<ul>
<li>@Primary - </li>
<li>해당 타입의 빈 모두 주입받기 - List<Repository> bookRepositories;</li>
<li>@Qualifier(빈 이름으로 주입)</li>
<li>그외 위의 어노테이션을 사용하지 않고 필드이름과 동일하게 지정해서 의존성을 주입하면 그이름과 동일한 레파지토리를 자동으로 주입받음</li>
</ul>
<pre><code class="language-java">@Autowired
BookRepository myBookRepository;</code></pre>
<h3 id="동작-원리">동작 원리</h3>
<ul>
<li><p>BeanFactory 가 자신에게 등록된 BeanPostProcessor 들을 찾아서 일반적인 Bean들에게 로직을 적용함  </p>
</li>
<li><p>AutowiredAnnotationBeanPostProcessor 가 기본적으로 Bean으로 등록되어있음  </p>
</li>
<li><p>BeanPostProcessor 의 라이프사이클 구현체에 의해 동작함  </p>
</li>
<li><p>@Autowired 는 postProcessBeforeInitialization 단계  </p>
</li>
<li><p>ApplicationRunner 의 경우 애플리케이션 구동이 다 끝나고 동작함  </p>
</li>
<li><p>Runner을 사용하지 않으면 애플리케이션 구동 중에 처리됨  </p>
</li>
<li><p>BeanPostProcessor</p>
<pre><code> - 아래의 세가지 라이프사이클 단계가 있다
 - postProcessBeforeInitialization
 - InitializingBean&#39;s afterPropertiesSet
 - postProcessAfterInitialization</code></pre></li>
<li><p>Bean을 Initializer(만들다)하여 인스턴스를 만든 다음에  </p>
</li>
<li><p>Bean의 초기화 라이프사이클(Initialization LifeCycle) 이전 OR 이후에 부가적인 작업을 할 수 있는 또다른 라이프사이클 콜백  </p>
</li>
<li><p>Initialization</p>
</li>
<li><p><code>@PostConstruct</code> 등의 어노태이션으로 Bean이 만들어진 이후에 해야할일을 정의 해주는것</p>
</li>
<li><p>InitializingBean을 implement 해서 afterPropertiesSet 메서드를 구현</p>
</li>
</ul>
<h1 id="4-component와-컴포넌트-스캔">4. @Component와 컴포넌트 스캔</h1>
<h2 id="애플리케이션-구동방법">애플리케이션 구동방법</h2>
<h3 id="1-기본적인-static-메서드-사용">1. 기본적인 static 메서드 사용</h3>
<ul>
<li>싱글톤 스코프의 Bean들을 초기에 다 생성함 등록할 Bean이 많다면 구동시에 초기 구동시간이 꽤 걸림</li>
</ul>
<pre><code class="language-java">SpringApplication.run(Demospring51Application.class, args);</code></pre>
<h3 id="2-builder를-활용한-방법">2. Builder를 활용한 방법</h3>
<h3 id="3-인스턴스를-만들어-사용하는-방법">3. 인스턴스를 만들어 사용하는 방법</h3>
<pre><code class="language-java">var app = new SpringApplication(Demospring51Application.class);
app.run(args);</code></pre>
<h3 id="4-functional-방법">4. Functional 방법</h3>
<ul>
<li>구동 시 리플렉션이나 Proxy 같은 cg라이브러리 기법을 안쓰므로 성능상의 이점이 있음  </li>
<li>하지만 일일히 Bean들을 등록해주기에는 너무 불편함 @ComponentScan을 사용하는 것에 비해 너무 불편함  </li>
<li>lambda 적용</li>
</ul>
<pre><code class="language-java">var app = new SpringApplication(Demospring51Application.class);
app.addInitializers(new ApplicationContextInitializer&lt;GenericApplicationContext&gt;() {
    @Override
    public void initialize(GenericApplicationContext ctx) {
        ctx.registerBean(MyService.class);
        ctx.registerBean(ApplicationRunner.class, new Supplier&lt;ApplicationRunner&gt;() {
            @Override
            public ApplicationRunner get() {
                return new ApplicationRunner() {
                    @Override
                    public void run(ApplicationArguments args) throws Exception {
                        System.out.println(&quot;Funational Bean Definition!!&quot;);
                    }
                };
            }
        });
    }

});
app.run(args);</code></pre>
<ul>
<li>lambda 적용후<pre><code class="language-java">var app = new SpringApplication(Demospring51Application.class);
app.addInitializers((ApplicationContextInitializer&lt;GenericApplicationContext&gt;) ctx -&gt; {
  ctx.registerBean(MyService.class);
  ctx.registerBean(ApplicationRunner.class, () -&gt; args1 -&gt; System.out.println(&quot;Funational Bean Definition!!&quot;));
});
app.run(args);</code></pre>
</li>
</ul>
<h2 id="컴포넌트-스캔-주요-기능">컴포넌트 스캔 주요 기능</h2>
<ul>
<li><ol>
<li>스캔 위치 설정: 어디부터 어디까지 scan할 것인가에 관한 설정</li>
</ol>
</li>
<li><ol start="2">
<li>필터: 어떤 애노테이션을 스캔 할 지 또는 하지 않을 지 scan하는 중에 어떤 것을 걸러낼 것 인가에 관한 설정</li>
</ol>
</li>
</ul>
<h2 id="component">Component</h2>
<ul>
<li>@Repository</li>
<li>@Service</li>
<li>@Controller</li>
<li>@Configuration</li>
</ul>
<h2 id="componentscan">@ComponentScan</h2>
<ul>
<li>@ComponentScan은 스캔할 패키지와 애노테이션에 대한 정보  </li>
<li>다른 Bean 들을 등록하기 전에 먼저 Bean을 등록 해줌  </li>
<li>실제 스캐닝 ConfigurationClassPostProcess 라는 BeanFactoryPostProcessor에 의해 처리됨  </li>
<li>주의 사항<pre><code> - @ComponentScan 이나 @SpringBootApplication 위치를 확인 위치에 따라 다른 패키지라면 Bean으로 등록되지않음
 - @Component 로 지정이되어있는지 확인
 - @ComponentScan 에 excludeFilters(예외)로 지정된 사항은 Bean으로 등록되지 않음</code></pre></li>
</ul>
<h1 id="5-빈의-스코프">5. 빈의 스코프</h1>
<h2 id="스코프">스코프</h2>
<ul>
<li>스프링에서는 기본적으로 싱글톤 스코프를 사용함  </li>
<li>싱글톤 스코프는 애플리케이션을 초기에 구동할때 ApplicationContext를 만들때 만드므로 시간이 좀 걸림  </li>
<li>싱글톤 : 해당 빈의 인스턴스가 오직 한개 뿐임</li>
<li>프로토타입 : 매번 새로운 객체를 만들어서 사용해야되는 스코프<pre><code> - Request
 - Session
 - WebSocket 
 - etc</code></pre></li>
</ul>
<h3 id="프로토타입-빈이-싱글톤-빈을-참조하면">프로토타입 빈이 싱글톤 빈을 참조하면?</h3>
<ul>
<li>아무 문제 없음  </li>
</ul>
<h3 id="싱글톤-빈이-프로토타입-빈을-참조하면">싱글톤 빈이 프로토타입 빈을 참조하면?</h3>
<ul>
<li>프로토타입 빈이 업데이트가 안된다</li>
<li>업데이트 하려면<pre><code> - scoped-proxy
 - Object-Provider
 - Provider (표준)</code></pre></li>
</ul>
<h3 id="프로토타입으로-적용해야될-경우">프로토타입으로 적용해야될 경우</h3>
<ul>
<li>그냥 호출하면 싱글톤으로 호출하게 되므로 프록시로 감싸서 매번 새로운 객체가 만들어지도록 함  </li>
<li>매번 바꿔줄 수 있는 Proxy로 감싸도록 아래와 같이 설정하면 됨  </li>
<li>CG라이브러리라는 써드파트 라이브러리가 CLASS도 Proxy로 만들 수 있게 해줌  </li>
<li>Java JDK 안에 있는 라이브러리는 인터페이스만 Proxy로 만들 수 있음  </li>
<li>프록시 인스턴스가 만들어지고 프록시 빈을 의존성으로 주입함  </li>
</ul>
<pre><code class="language-java">@Component @Scope(value=&quot;prototype&quot; , proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {

}</code></pre>
<ul>
<li>넓은 생명주기의 싱글톤 스코프에서 짧은 생명 주기를 가진 스코프를 주입받아야 할 때는 위와 같이 설정하여 사용  </li>
</ul>
<h3 id="objectprovider-로-지정하는-방법">ObjectProvider 로 지정하는 방법</h3>
<ul>
<li>ObjectProvider<T> 로 지정하여 도됨 추천하지는 않음  </li>
<li>빈 설정하는 부분에만 설정하여 깔끔하게 코딩하는 것을 추천  </li>
</ul>
<pre><code class="language-java">@Component
public class Single {

    @Autowired
    private ObjectProvider&lt;Proto&gt; proto;

    public Proto getProto() {
        return proto.getIfAvailable();
    }
}</code></pre>
<h2 id="프록시">프록시</h2>
<ul>
<li>​<a href="https://en.wikipedia.org/wiki/Proxy_pattern%E2%80%8B">https://en.wikipedia.org/wiki/Proxy_pattern​</a></li>
<li><a href="https://en.m.wikipedia.org/wiki/Proxy_pattern">https://en.m.wikipedia.org/wiki/Proxy_pattern</a></li>
</ul>
<h2 id="싱글톤-객체-사용시-주의할-점">싱글톤 객체 사용시 주의할 점</h2>
<ul>
<li>프로퍼티가 공유: 여기저기서 사용하면서 값을 바꾸면 그 값이 공유되어 위험하므로 쓰레드 세이프한 방법으로 사용해야 함</li>
<li>ApplicationContext 초기 구동시 인스턴스 생성되므로 구동하면서 시간이 좀 걸림</li>
</ul>
<h1 id="6-1-프로파일">6-1. 프로파일</h1>
<p>프로파일과 프로퍼티를 다루는 인터페이스</p>
<pre><code class="language-java">ApplicationContext extends ​EnvironmentCapable
getEnvironment()</code></pre>
<h2 id="profile">profile</h2>
<ul>
<li>빈들의 묶음</li>
<li>환경 설정(dev, prod, staging, alpha, beta, gamma...)<pre><code> - 각각의 환경에따라 다른 빈들을 사용해야되는 경우
 - 특정 환경에서만 어떠한 빈을 등록해야되는 경우
 - Environment​의 역할은 활성화할 프로파일 확인 및 설정</code></pre></li>
</ul>
<h2 id="프로파일-유즈케이스">프로파일 유즈케이스</h2>
<ul>
<li>테스트 환경에서는 A라는 빈을 사용하고 배포 환경에서는 B라는 빈을 쓰고 싶다</li>
<li>이 빈은 모니터링 용도니까 테스트할 때는 필요가 없고 배포할 때만 등록이 되면 좋겠다</li>
</ul>
<h2 id="프로파일-정의하기">프로파일 정의하기</h2>
<ul>
<li>클래스에 정의<pre><code> - @Configuration @Profile(“test”)
 - @Component @Profile(“test”)</code></pre></li>
<li>메소드에 정의<pre><code> - @Bean @Profile(“test”)</code></pre></li>
</ul>
<h2 id="프로파일-설정하기">프로파일 설정하기</h2>
<ul>
<li>-Dspring.profiles.avtive=”test,A,B,...”</li>
<li>@ActiveProfiles​ (테스트용)</li>
</ul>
<h2 id="프로파일-표현식">프로파일 표현식</h2>
<ul>
<li><p>! (not)</p>
</li>
<li><p>&amp; (and)</p>
</li>
<li><p>|(or)</p>
</li>
<li><p>특정 환경(test)에서의 예시</p>
<pre><code> - Ultimate
 - intelliJ - Edit Configuration - Environment - Active profiles 에 test 입력

 - Community
 - intelliJ - Edit Configuration - Environment - VM option 에 `-Dspring.profiles.active=&quot;test&quot;` 입력</code></pre></li>
<li><p>Configuration 으로 프로파일 설정하는 방법</p>
<pre><code class="language-java">@Configuration
@Profile(&quot;test&quot;)
public class TestConfiguration {
  @Bean
  public BookRepository bookRepository() {
      return new TestBookRepository();
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
- Repository 에 직접 설정
- Configuration으로 프로파일을 설정하는 방법은 번거로우므로 아래와 같이 설정하면 간편함
```java
@Repository
@Profile(&quot;test&quot;)
public class TestBookRepository implements BookRepository {

}</code></pre><ul>
<li>!(not), &amp;(and), |(or)</li>
<li>아래 코드는 prod가 아니면 빈으로 등록 시킴<pre><code class="language-java">@Repository
@Profile(&quot;!prod &amp; test&quot;)
public class TestBookRepository implements BookRepository {
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
# 6-2. Environment 2부. 프로퍼티

## property
- 다양한 방법으로 정의할 수 있는 설정값
- Environment의 역할은 프로퍼티 소스 설정 및 프로퍼티 값 가져오기

### 우선순위
- StandardServletEnvironment의 우선순위
- ServletConfig 매개변수
- ServletContext 매개변수
- JNDI (java:comp/env/)
- JVM 시스템 프로퍼티 (-Dkey=&quot;value&quot;)
- JVM 시스템 환경변수 (운영체제 환경변수)
- VM option에 주는 법</code></pre><p>-Dapp.name=spring5</p>
<pre><code>
### properties 파일로 설정하는법
- 1. resources 에 app.properties 파일 생성</code></pre><p>app.about=spring</p>
<pre><code>- 2. @Configuration or @SpringBootApplication 설정 파일이 있는 곳에 설정
```java
@PorpertySource(&quot;classpath:/app.properties&quot;)</code></pre><ul>
<li><ol start="3">
<li><p>Property 값 가져오기</p>
<pre><code class="language-java">@Component
public class AppRunner implements ApplicationRunner {

@Autowired
ApplicationContext ctx;

@Override
public void run(ApplicationArguments args) throws Exception {
   Environment environment = ctx.getEnvironment();
   System.out.println(environment.getProperty(&quot;app.name&quot;));
   System.out.println(environment.getProperty(&quot;app.about&quot;));
}
}</code></pre>
</li>
</ol>
</li>
</ul>
<h3 id="스프링-부트에서-property-가져오기">스프링 부트에서 Property 가져오기</h3>
<pre><code class="language-java">@Value(&quot;${app.name}&quot;)
String appName;</code></pre>
<h1 id="7-messagesource">7. MessageSource</h1>
<ul>
<li><p>국제화(i18n) 기능을 제공하는 인터페이스
ApplicationContext 에서 상속 받은 인터페이스</p>
</li>
<li><p>MessageSource 직접설정 예시</p>
</li>
<li><p>ReloadableResourdeBundleMessageSource 로 메세지 변경 시 변경된 메세지를 반영</p>
<pre><code class="language-java">@Bean
public MessageSource messageSource() {
  var messageSource = new ReloadableResourceBundleMessageSource();
  messageSource.setBasename(&quot;classpath:/messages&quot;);
  messageSource.setDefaultEncoding(&quot;UTF-8&quot;);
  messageSource.setCacheSeconds(3); //캐싱하는 시간을 최대 3초까지만 캐싱하고 다시 읽음
  return messageSource;
}</code></pre>
</li>
</ul>
<h2 id="스프링부트">스프링부트</h2>
<ul>
<li>스프링 부트를 사용한다면 별다른 설정 필요없이 아래와 같이 messages.properties 사용할 수 있음  </li>
<li>원래 빈으로 각각 등록시켜줘야 하지만 스프링 부트를 쓰면 자동으로 <code>ResourceBundleMessageSource</code> 가 빈으로 등록되어있음  </li>
<li>messages.properties<pre><code>greeting=hello, {0}</code></pre></li>
<li>messages_ko_KR.properties<pre><code>greeting=안녕, {0}</code></pre></li>
</ul>
<h1 id="8-applicationeventpublisher">8. ApplicationEventPublisher</h1>
<ul>
<li>이벤트 프로그래밍에 필요한 인터페이스 제공 ​옵저버 패턴​ 구현체</li>
</ul>
<pre><code class="language-java">ApplicationContext extends ​ApplicationEventPublisher
publishEvent(ApplicationEvent event)</code></pre>
<h2 id="이벤트-만들기">이벤트 만들기</h2>
<ul>
<li>ApplicationEvent 상송</li>
<li>스프링 4.2 부터는 이 클래스를 상속받지 않아도 이벤트로 사용할 수 있다.</li>
</ul>
<h2 id="이벤트-발생-시키는-방법">이벤트 발생 시키는 방법</h2>
<ul>
<li>ApplicationEventPublisher.publishEvent();</li>
</ul>
<h2 id="이벤트-처리하는-방법">이벤트 처리하는 방법</h2>
<ul>
<li>ApplicationListener&lt;이벤트&gt; 구현한 클래스 만들어서 빈으로 등록하기</li>
<li>스프링 4.2 부터는 ​@EventListener​를 사용해서 빈의 메소드에 사용할 수 있다</li>
<li>기본적으로는 synchronized</li>
<li>순서를 정하고 싶다면 @Order와 함께 사용</li>
<li>비동기적으로 실행하고 싶다면 @Async와 함께 사용</li>
</ul>
<h2 id="스프링이-제공하는-기본-이벤트">스프링이 제공하는 기본 이벤트</h2>
<ul>
<li><p>ContextRefreshedEvent: ApplicationContext를 초기화 했더나 리프래시 했을 때 발생</p>
</li>
<li><p>ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈들이 시작 </p>
<ul>
<li>신호를 받은 시점에 발생</li>
</ul>
</li>
<li><p>ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈들이 정지</p>
<ul>
<li>신호를 받은 시점에 발생.</li>
</ul>
</li>
<li><p>ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에 발생</p>
</li>
<li><p>RequestHandledEvent: HTTP 요청을 처리했을 때 발생</p>
</li>
<li><p>스프링 4.2 부터는 ApplicationEvent 클래스를 상속받지 않아도 이벤트로 사용할 수 있다</p>
<h3 id="42이전-상속을-받아-event를-정의하는-로직">4.2이전 상속을 받아 Event를 정의하는 로직</h3>
<pre><code class="language-java">public class MyEvent extends ApplicationEvent {

  private int data;
  /**
   * Create a new ContextStartedEvent.
   *
   * @param source the {@code ApplicationContext} that the event is raised for
   *               (must not be {@code null})
   */
  public MyEvent(Object source) {
      super(source);
  }

  public MyEvent(Object source, int data) {
      super(source);
      this.data = data;
  }

  public int getData() {
      return data;
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>### 4.2 이후 Event 정의 로직
- 스프링이 추구하는 스프링 프레임웍 코드가 전혀 들어가지 않은 POJO 기반의 프로그래밍의 비침투성 로직  
- 더 편하고 더 코드를 유지보수하기 쉬워짐  
- 이벤트는 빈이 아님  
```java
public class MyEvent {

    private int data;
    private Object source;

    public MyEvent(Object source, int data) {
        this.source = source;
        this.data = data;
    }

    public Object getSource() {
        return source;
    }

    public int getData() {
        return data;
    }

}</code></pre><h3 id="applicationeventpublisher">ApplicationEventPublisher</h3>
<ul>
<li><p>Event를 발생시키는 로직</p>
<pre><code class="language-java">@Component
public class AppRunner implements ApplicationRunner {

  @Autowired
  ApplicationEventPublisher publisherEvent;

  @Override
  public void run(ApplicationArguments args) throws Exception {
      publisherEvent.publishEvent(new MyEvent(this, 100));
  }
}</code></pre>
</li>
</ul>
<h3 id="applicationlistener">ApplicationListener</h3>
<ul>
<li><p>Event를 받아서 처리하는 Handler 구현</p>
<h4 id="42-이전-로직">4.2 이전 로직</h4>
<pre><code class="language-java">@Component
public class MyEventHandler implements ApplicationListener&lt;MyEvent&gt; {

  @Override
  public void onApplicationEvent(MyEvent event) {
      System.out.println(&quot;이벤트 받았다. 데이터는 &quot; + event.getData());
  }
}</code></pre>
</li>
</ul>
<h4 id="42-이후-로직">4.2 이후 로직</h4>
<pre><code class="language-java">@Component
public class MyEventHandler {

    @EventListener
    public void handle(MyEvent event) {
        System.out.println(&quot;이벤트 받았다. 데이터는 &quot; + event.getData());
    }
}</code></pre>
<h3 id="order">Order</h3>
<ul>
<li><p>Event 실행 순서 지정</p>
</li>
<li><p><code>@Order(Ordered.HIGHEST_PRECEDENCE)</code> 라고 설정하면 가장 먼저 실행됨 </p>
</li>
<li><p><code>@Order(Ordered.HIGHEST_PRECEDENCE + 2)</code> 라고 설정하면 조금 더 늦게 실행됨 </p>
<pre><code class="language-java">@Component
public class MyEventHandler {

  @EventListener
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public void handle(MyEvent event) {
      System.out.println(Thread.currentThread().toString());
      System.out.println(&quot;이벤트 받았다. 데이터는 &quot; + event.getData());
  }
}</code></pre>
</li>
</ul>
<h3 id="enableasync">@EnableAsync</h3>
<ul>
<li><p>비동기 설정 @EnableAsync 를 설정하면 메인 Thread 가 아닌 별도의 Thread를 생성하여 수행</p>
<pre><code class="language-java">@SpringBootApplication
@EnableAsync
public class Ioccontainer8Application {

  public static void main(String[] args) {
      SpringApplication.run(Ioccontainer8Application.class, args);
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
#### Handler 로직에 @Async 설정
```java
@Component
public class MyEventHandler {

    @EventListener
//    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Async
    public void handle(MyEvent event) {
        System.out.println(Thread.currentThread().toString());
        System.out.println(&quot;이벤트 받았다. 데이터는 &quot; + event.getData());
    }
}</code></pre><h3 id="lifecycle-event">LifeCycle Event</h3>
<ul>
<li>ContextRefreshedEvent: ApplicationContext를 초기화 했거나 리프래시 했을 때 발생</li>
<li>ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈 들이 시작신호를 받은시점에 발생</li>
<li>ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈 들이 정지신호를 받은시점에 발생</li>
<li>ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸 되는시점에 발생</li>
<li>RequestHandledEvent: HTTP 요청을 처리 했을 때 발생</li>
</ul>
<h1 id="9-resourceloader">9. ResourceLoader</h1>
<ul>
<li>리소스를 읽어오는 기능을 제공하는 인터페이스</li>
</ul>
<pre><code class="language-java">ApplicationContext extends ​ResourceLoader</code></pre>
<h2 id="리소스-읽어오기">리소스 읽어오기</h2>
<ul>
<li>파일 시스템에서 읽어오기</li>
<li>클래스패스에서 읽어오기</li>
<li>URL로 읽어오기</li>
<li>상대/절대 경로로 읽어오기</li>
</ul>
<pre><code class="language-java">Resource getResource(java.lang.String location)</code></pre>
<pre><code class="language-java">@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Resource resource = resourceLoader.getResource(&quot;classpath:test.txt&quot;); // 파일 읽어오기
        System.out.println(resource.exists()); // 파일 유무 
        System.out.println(resource.getDescription()); // 설명
        System.out.println(Files.readString(Path.of(resource.getURI()))); // URL로 읽어오기
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[9장 단위 테스트]]></title>
            <link>https://velog.io/@seunghee-ryu/9%EC%9E%A5-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@seunghee-ryu/9%EC%9E%A5-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Wed, 13 Dec 2023 12:38:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/a8a844a9-0bb4-45d5-99eb-d6bde26f6669/image.png" alt=""></p>
<ul>
<li>단위 테스트의 중요성과 규칙에 대해 설명한다</li>
</ul>
<h2 id="tdd-법칙-세-가지">TDD 법칙 세 가지</h2>
<ul>
<li>실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다</li>
<li>컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다</li>
<li>현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다</li>
</ul>
<h2 id="깨끗한-테스트-코드-유지하기">깨끗한 테스트 코드 유지하기</h2>
<ul>
<li>더러운 테스트 코드는 없느니만 못하다</li>
<li>테스트 코드가 복잡할수록 테스트 케이스를 작성하는 시간이 더 오래 걸린다</li>
<li>기능이 추가되거나 변경될 때마다 테스트 케이스를 유지/보수 하는 비용은 늘어난다</li>
<li>테스트 코드는 실제 코드 못지 않게 깨끗하게 짜야 한다</li>
</ul>
<h2 id="테스트는-유연성-유지보수성-재사용성을-제공한다">테스트는 유연성, 유지보수성, 재사용성을 제공한다</h2>
<ul>
<li>테스트 케이스가 있고 커버리지가 높을 수록 유연한 기능 추가/변경이 쉽다</li>
<li>아키텍처나 설계가 부실한 곳에서 더 효과있다</li>
<li>기능 변경이 되더라도 테스트 케이스만 다 통과한다면 문제가 발생하지 않기 때문</li>
</ul>
<h2 id="깨끗한-테스트-코드">깨끗한 테스트 코드</h2>
<ul>
<li>깨끗한 테스트 코드를 만들기 위해서는 가독성이 중요하다</li>
<li>중복된 코드를 합치고, 테스트 케이스에 노출이 필요없는 코드는 숨긴다</li>
<li>BUILD-OPERATE-CHECK 패턴이 이런 테스트 구조에 적합하다<pre><code> - Build: 테스트 전 필요한 데이터를 빌드하고,
 - Operate: 테스트할 API 메소드를 호출하고
 - Check: 실행한 메소드의 결과가 시스템에 준영향과 DB에 준 영향을 check(확인) 하라</code></pre></li>
<li>핵심은 잡다하고 세세한 코드를 다 없애 정말 필요한 자료 유형과 함수만 사용한다는 점</li>
</ul>
<h2 id="도메인에-특화된-테스트-언어">도메인에 특화된 테스트 언어</h2>
<ul>
<li>테스트 코드를 작성하며 세세한 정보와 함수를 은닉하고 테스트에 필요한 함수만 노출시키는데 이렇게 특수 API가되며 이게 테스트 언어다</li>
<li>깨끗한 테스트 코드를 만들기 위해 리팩토링을 하며 더 간결하고 표현력 있는 코드를 만들어라</li>
</ul>
<h2 id="이중-표준">이중 표준</h2>
<ul>
<li>테스트 API코드에 적용하는 표준은 실제 코드에 적용하는 표준과 다르다</li>
<li>실제 코드만큼 퍼포먼스를 따지지 않아도 된다. </li>
<li>테스트 환경에서 돌아가는 코드이기 때문에 요구사항이 다르기 때문이다</li>
</ul>
<h2 id="테스트당-assert-하나">테스트당 assert 하나</h2>
<ul>
<li>최대한 하나의 테스트에서는 하나의 assert만 할 수 있도록 한다</li>
<li>하지만, 테스트를 억지로 분리하며 중복되는 코드가 많아질 경우 합치는 것도 좋다 </li>
<li>assert 문을 최대한 줄이는 것에 초점을 맞춘다</li>
</ul>
<h2 id="테스트당-개념-하나">테스트당 개념 하나</h2>
<ul>
<li>코드의 중복을 고려해 assert 문을 하나로 못줄이는 경우가 많다면 생각을 바꾼다</li>
<li>테스트 함수에 하나의 개념만 테스트하라.</li>
</ul>
<pre><code class="language-java">// addMonths ( ) 메서드를 테스트히는 장황한 코드
public void testAddMonths() {
        SerialDate dl = SerialDate.createlnstance(31, 5, 2004);

        SerialDate d2 = SerialDate.addMonths(1, dl);
        assertEquals(30, d2.getDayDfMonth());
        assertEquals(6, d2.getMonth());
        assertEquals(2004, d2.getYYYY());

        SerialDate d3 = SerialDate.addMonths(2, d1);
        assertEquals(31, d3.getDayOfMonth());
        assertEquals(7, d3.getMonth());
        assertEquals(2004, d3.getYYYY());

        SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1));
        assertEquals(30, d4.getDayOfMonth());
        assertEquals(7,  d4.getMonth()) ;
        assertEquals(2004, d4.getYYYY());
}</code></pre>
<ul>
<li>위 코드를 보면 개행순으로 3가지의 테스트를 하는데, 얼핏보면 다 날짜를 검사하는 것 같아서 같은 개념을 테스트하는거니 괜찮을 것 같지만, 사실 다 다른 개념이다<ul>
<li><ol>
<li>첫 케이스는 30일로 끝나는 한 달을 더하면 날짜는 30일이 되어야지 31이 되면 안된다</li>
</ol>
</li>
<li><ol start="2">
<li>두 번째 케이스는 2달을 더하면 그리고 그 달이 31일로 끝나면 31일이 되어야 한다</li>
</ol>
</li>
<li><ol start="3">
<li>마지막은 31일로 끝나는 한 달을 더하면 30일이 되어야지 31일이 되면 안되며 각각의 다른 개념을 가지고 있기에 함수를 나누는게 맞으며, 2월같은 마지막 날짜가 28일인 경우의 테스트 케이스도 채워야 한다</li>
</ol>
</li>
</ul>
</li>
</ul>
<h2 id="first">F.I.R.S.T</h2>
<ul>
<li>깨끗한 테스트가 되기 위해서는 다음 다섯 가지 규칙을 따른다</li>
</ul>
<h3 id="빠르게first">빠르게(First)</h3>
<ul>
<li>테스트는 빨라야 한다</li>
<li>테스트가 느리면 자주 돌리지 못하고 그 시간만큼 코드를 정리하지 못 할뿐 아니라 테스트를 자주 수행할 수 없다</li>
</ul>
<h3 id="독립적으로independent">독립적으로(Independent)</h3>
<ul>
<li>각각의 테스트가 서로 의존하면 안된다</li>
<li>한 테스트가 다음 테스트가 실행될 환경을 준비해서는 안된다</li>
</ul>
<h3 id="반복가능하게repeatable">반복가능하게(Repeatable)</h3>
<ul>
<li>테스트는 어떤 환경에서든 반복 가능해야 한다</li>
<li>실제 환경, QA환경, 오프라인 환경 모두 가능해야 한다</li>
<li>테스트가 돌아가지 않는 환경이 있다면 핑계거리가 되며 테스트를 수행하지 못해도 넘겨야하는 상황이 생긴다</li>
</ul>
<h3 id="자가검증하는self-validating">자가검증하는(Self-Validating)</h3>
<ul>
<li>테스트는 Boolean 값으로 결과를 내야 한다 (성공 or 실패)</li>
<li>통과했는지를 알기 위해 로그 파일이나 콘솔창을 봐야하는것은 잘못된 것이다</li>
</ul>
<h3 id="적시에timely">적시에(Timely)</h3>
<ul>
<li>테스트는 적시에 작성해야 한다</li>
<li>즉, 테스트하려는 실제 코드를 구현하기 직전에 구현한다 </li>
<li>실제 코드를 작성 한 뒤에 테스트 코드를 작성하려 하면 테스트하기가 어려울 수도 있고, 테스트가 불가능하도록 실제 코드를 설계할 수도 있다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>TDD에 대한 중요성은 들었지만 실제 개발에 적용해본적이 없어서 이론적으로만 이해가 되었다</li>
<li>각종 강의나 유튜브 등에 예제가 있을테니 테스트를 만드는 것에 대해서 조금 더 검색해봐야 할 것 같다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[8장 경계]]></title>
            <link>https://velog.io/@seunghee-ryu/8%EC%9E%A5-%EA%B2%BD%EA%B3%84</link>
            <guid>https://velog.io/@seunghee-ryu/8%EC%9E%A5-%EA%B2%BD%EA%B3%84</guid>
            <pubDate>Mon, 11 Dec 2023 12:01:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/7c275c85-bf9c-4d8c-a60f-ede599b2abd7/image.png" alt=""></p>
<ul>
<li>소프트웨어 경계를 깔끔하게 처리하는 기법을 알 수 있다</li>
</ul>
<h2 id="1-외부-코드-사용하기">1. 외부 코드 사용하기</h2>
<ul>
<li>인터페이스 제공자는 적용성을 최대한 넓히려 한다</li>
<li>인터페이스 사용자는 자신의 요구에 집중하는 인터페이스를 바란다</li>
<li>이러한 양방향의 차이로 인해 시스템 경계에서 문제가 생길 수 있다</li>
</ul>
<h3 id="map">Map</h3>
<ul>
<li>외부 라이브러리 중 컬렉션이 있고 이중 자주 쓰이는 것으로 java.util.Map이 있다</li>
<li>이 맵은 api로 clear, containsKey, entrySet 등의 다양하고 많은 함수를 제공한다</li>
<li>Map 인터페이스가 변할 경우 해당 인터페이스를 사용해서 만든 인스턴스가 많다면 수정할 코드가 많아진다</li>
<li>일급 콜렉션을 사용한다면 Map을 콜렉션으로 숨기면서 불변성도 보장할 수 있고 해당 일급 콜렉션만 수정하면 되기에 변경의 범위가 축소된다</li>
<li>즉 Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다</li>
</ul>
<h2 id="경계-살피고-익히기">경계 살피고 익히기</h2>
<ul>
<li>간단한 테스트 케이스(학습 테스트)를 작성해서 외부 코드를 익힌다</li>
<li>프로그램에서 사용하려는 방식대로 외부 API를 호출한다</li>
<li>통제된 환경에서 API를 제대로 이해하는지 확인하며 API를 사용하려는 목적을 명확하게 인지한다</li>
</ul>
<pre><code class="language-java">
// first test case
// Appender가 필요하다는 에러 발생
@Test
public void testLogCreate(){
    Logger logger = Logger.getLogger(&quot;MyLogger&quot;);
    logger.info(&quot;hello&quot;);
}

// second test case
// ConsoleAppender라는 클래스가 있다는 사실 확인
// Appender에 출력 스트림이 없다는 에러 발생
@Test
public void testLogCreate(){
    Logger logger = Logger.getLogger(&quot;MyLogger&quot;);
    ConsoleAppender appender = new ConsoleAppender();
    logger.addAppender(appender);
    logger.info(&quot;hello&quot;);
}

// third test case
// 정상 작동
@Test
public void testLogCreate(){
    Logger logger = Logger.getLogger(&quot;MyLogger&quot;);
    logger.removeAllAppenders();
    logger.addAppender(new ConsoleAppender(
                new PatternLayout(&quot;%p %t %m%n&quot;),
                ConsoleAppender.SYSTEM_OUT));
    logger.info(&quot;hello&quot;);
}

//최종
public class LogTest {
    private Logger logger;

    @Before
    public void setup() {
        logger = Logger.getLogger(&quot;logger&quot;);
        logger.removeAllAppenders();
        Logger.getRootLogger().removeAllAppenders();
    }

    @Test
    public void basicLogger(){
        BasicConfigurator.configure();
        logger.info(&quot;basicLogger&quot;);
    }

    @Test
    public void addAppenderWithStream() {
        logger.addAppender(new ConsoleAppender(
                new PatternLayout(&quot;%p %t %m%n&quot;),
                ConsoleAppender.SYSTEM_OUT));
        logger.info(&quot;addAppenderWithStream&quot;);
    }

    @Test
    public void addAppenderWithoutStream() {
        logger.addAppender(new ConsoleAppender(
                new PatternLayout(&quot;%p %t %m%n&quot;)));
        logger.info(&quot;addAppenderWithoutStream&quot;);
    }
}
</code></pre>
<ul>
<li>학습 테스트를 함으로써 API에 대한 이해도를 높이고 새로운 버전으로 업데이트 되더라도 기존에 작성한 학습 테스트를 통해 호환성 체크도 할 수 있고 그에 따른 적용도 가능하다</li>
</ul>
<h2 id="3-아직-존재하지-않는-코드를-사용하기">3. 아직 존재하지 않는 코드를 사용하기</h2>
<ul>
<li>경계의 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 것이다</li>
<li>협업을 하다보면 아직 다른 팀에서 완성하지 못한 API를 사용해야 하는 경우가 있다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/7bc2c3d4-edb4-47f8-a8f1-cb0eea216f19/image.png" alt=""></p>
<ul>
<li>다른 팀에서 아직 API를 제작하지 못했다면 해당 API에서 인터페이스를 추출해 Controller에서 분리한 뒤 Fake 구현체를 생성해서 사용하며, API가 제작이 완료되면 Adapter를 이용해 간극을 메워준다</li>
<li>이렇게 Fake 구현체 클래스를 사용하면 controller도 테스트 할 수 있다</li>
</ul>
<h3 id="깨끗한-경계">깨끗한 경계</h3>
<ul>
<li>외부 패키지를 호출하는 코드를 줄여 경계를 관리하며 새로운 클래스나 Adapter patter을 이용한다</li>
<li>외부 패키지의 수정이나 변경에 대응 범위가 작아지고 경계를 분명히 해 코드 가독성이 높아진다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>가장 처음 맡았던 프로젝트에서 외부 api를 사용했었는데 그때 adater 패턴에 대해 알았다면 조금 더 깔끔하게 만들 수 있었지 않았을까 싶다</li>
<li>테스트 케이스를 만들어 본 적이 없기 때문에 외부 api를 호출할 때 학습 테스트를 만든다는 것을 처음 알았다</li>
<li>일급 컬렉션이라는 개념이 제대로 정립되지 않아서 추가적인 검색이 필요하다</li>
</ul>
<h3 id="일급-컬렉션">일급 컬렉션</h3>
<ul>
<li>Collection을 Wrapping하면서, Wrapping한 Collection 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다</li>
</ul>
<pre><code class="language-java">
// warpping 전
public class Person {
    private String name;
    private List&lt;Car&gt; cars;
    // ...
}

public class Car {
    private String name;
    private String oil;
    // ...
}


// wrapping 후
public class Person {
    private String name;
    private Cars cars;
    // ...
}

// List&lt;Car&gt; cars를 Wrapping
// 일급 컬렉션
public class Cars {
    // 멤버변수가 하나 밖에 없다!!
    private List&lt;Car&gt; cars;
    // ...
}

public class Car {
    private String name;
    private String oil;
    // ...
}</code></pre>
<ul>
<li>일급 컬렉션은 List<Car> cars 외 다른 멤버 변수가 없다</li>
<li>일급 컬렉션을 사용하면 상태과 로직을 따로 관리할 수 있기 때문에 로직이 사용되는 클래스의 부담을 줄일 수 있고, 중복코드를 줄일 수 있다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[7장 오류 처리]]></title>
            <link>https://velog.io/@seunghee-ryu/7%EC%9E%A5-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@seunghee-ryu/7%EC%9E%A5-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Mon, 11 Dec 2023 07:31:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/1c13c036-4627-4a75-bfce-854cd8c84bfd/image.png" alt=""></p>
<ul>
<li>예외 처리에 관한 가이드를 제공해준다</li>
</ul>
<h2 id="1-오류-코드보다-예외를-사용하라">1. 오류 코드보다 예외를 사용하라</h2>
<ul>
<li>오류를 코드로 처리하게 되면 코드의 가독성은 낮아지고 계층의 깊이는 심하게 높아진다</li>
<li>오류 코드로 처리하는 경우 개발자가 작성하는 것이기 때문에 실수하는 경우 프로그램이 멈출 수 있다</li>
<li>예외 처리를 통해 코드를 분리하고 각 개념을 읽기 쉽게 처리할 수 있다</li>
</ul>
<h2 id="2-try-catch-finally-문부터-작성하라">2. Try-Catch-Finally 문부터 작성하라</h2>
<ul>
<li>try 블록은 무슨 일이 생기든지 catch 블록으로 넘어가기 때문에 일관된 상태를 유지할 수 있다</li>
<li>try 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬워진다</li>
</ul>
<h3 id="tdd-작성-권장-방법">TDD 작성 권장 방법</h3>
<ul>
<li>강제로 예외를 일으키는 테스트 케이스를 작성 후 테스트를 통과하게 코드를 작성하는 방법</li>
<li>자연스럽게 try 블록의 트랜잭션 번위부터 구현하게 되기에 범위 내에서 트랜잭션 본질을 유지하기 쉬워진다</li>
</ul>
<h2 id="3-미확인unchecked-예외를-사용하라">3. 미확인(Unchecked) 예외를 사용하라</h2>
<h3 id="checked와-unchecked">checked와 unchecked</h3>
<ul>
<li><p>checked</p>
<pre><code> - 꼭 처리를 해줘야하는 예외
 - 컴파일 타임에 확인 가능
 - 예외 발생시 트랜잭션 처리 : 롤백O
 - 예시 : IOException, SQLException</code></pre></li>
<li><p>unchecked</p>
<pre><code> - 꼭 처리를 하지 않아도 되는 예외(개발자 부주의)
 - 런타임 시에 확인 가능
 - 예외 발생 트랜잭션 처리 : 롤백X
 - 예시 : RuntimeException, NullPointerException</code></pre></li>
<li><p>확인된 예외는 OCP를 위반한다</p>
<pre><code> - 메소드에서 예외를 던졌는데 catch 블록이 세 단계 위에 있다면 그 사이 메소드 모드가 예외를 정의해야 한다
 - 모든 함수에서 최하위 함수에서 던지는 예외를 알아서 캡슐화가 깨진다
 - 예외를 처리하는 비용 소모가 크다</code></pre></li>
</ul>
<h2 id="4-예외에-의미를-제공하라">4. 예외에 의미를 제공하라</h2>
<ul>
<li>자바는 모든 예외에 호출 스택을 제공하지만 그것만으로는 부족하다</li>
<li>오류 메세지에 정보를 담아 예외와 함께 던진다</li>
</ul>
<h2 id="5-호출자를-고려해-예외-클래스를-정의하라">5. 호출자를 고려해 예외 클래스를 정의하라</h2>
<ul>
<li>Third party library를 사용하고 여기서 던지는 에러를 처리해야 한다면 wrapping하여 관리하는 것이 좋다<pre><code> - 라이브러리 교체 등의 변경에 대응하기 쉽다
 - 라이브러리 쓰는 곳을 테스트하는 경우, 해당 라이브러리를 가짜로 만들어 테스트하기 쉬워진다
 - 라이브러리의 API 디자인에 관계없이 내 프로그램에 맞도록 정제하여 사용할 수 있다</code></pre></li>
</ul>
<h2 id="6-정상-흐름을-정의하라">6. 정상 흐름을 정의하라</h2>
<ul>
<li>예외가 발생하는 특수상황 자체가 없도록 구현을 한다</li>
<li>클라이언트 코드가 예외적인 상황을 처리할 필요가 없어지는 것을 특수 사례 패턴이라고 부른다</li>
</ul>
<h2 id="7-null을-반환하지-마라">7. null을 반환하지 마라</h2>
<ul>
<li>메소드가 null을 반환한다면 메소드를 호출하는 모든 로직에서 null 검사를 해줘야 하는데 이를 놓칠 수 있다</li>
<li>null 대신 예외를 던지거나 특수 사례 객체를 반환한다</li>
<li>Optional 객체를 통해 구현할 수 있다</li>
</ul>
<h2 id="8-null을-전달하지-마라">8. null을 전달하지 마라</h2>
<ul>
<li>null을 인수값으로 전달하는 방식도 피해야 한다</li>
<li>인수값으로 null을 전달하면 결국 메소드 위치에서 null을 검사하는 로직을 넣어야 한다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>현재 프로젝트에서 null에 관한 처리를 고민 중이었기 때문에 null을 반환, 전달하지 말라는 부분이 인상적이었다</li>
<li>null 체크를 하는 부분을 완전히 리팩토링 하는 것은 어렵기 때문에 이에 관한 고민이 깊어졌다</li>
<li>null 값을 넘기지 않도록 만들어놨는데 if문이나 for문 안쪽에서 null 값을 체크하는 경우에 대해 생각해봐야겠다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[6장 객체와 자료구조]]></title>
            <link>https://velog.io/@seunghee-ryu/6%EC%9E%A5-%EA%B0%9D%EC%B2%B4%EC%99%80-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@seunghee-ryu/6%EC%9E%A5-%EA%B0%9D%EC%B2%B4%EC%99%80-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Mon, 11 Dec 2023 06:45:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/6578071b-faf0-469e-891b-2473c3634b11/image.png" alt=""></p>
<ul>
<li>새로운 자료 타입 추가에 대한 유연성이 필요할 때는 객체, 새로운 동작에 대한 유연성이 필요하면 자료 구조와 절차적인 코드를 사용하는것이 좋다</li>
<li>상황에 맞는 방법을 선택하라</li>
</ul>
<h2 id="1-자료-추상화">1. 자료 추상화</h2>
<ul>
<li>구현을 감추려면 추상화가 필요하다</li>
<li>추상 인터페이스를 제공해 사용자가 구현을 몰느채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다</li>
<li>자료는 추상적인 개념으로 표현하는 것이 좋다<pre><code class="language-java"></code></pre>
</li>
</ul>
<p>// 구체적인 Point 클래스
public class Point { 
  public double x; 
  public double y;
}</p>
<p>// 추상적인 Point 클래스
public interface Point {
  double getX();
  double getY();
  void setCartesian(double x, double y); 
  double getR();
  double getTheta();
  void setPolar(double r, double theta); 
}</p>
<pre><code>
### 자료와 객체의 차이
- 객체 : 추상화 뒤에 자료를 숨긴 채 자료를 다루는 함수만 공개한다
- 자료 구조 : 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다

```java

// 절차지향적인 코드
public class Square { 
  public Point topLeft; 
  public double side;
}

public class Rectangle { 
  public Point topLeft; 
  public double height; 
  public double width;
}

public class Circle { 
  public Point center; 
  public double radius;
}

public class Geometry {
  public final double PI = 3.141592653589793;

  public double area(Object shape) throws NoSuchShapeException {
    if (shape instanceof Square) { 
      Square s = (Square)shape; 
      return s.side * s.side;
    } else if (shape instanceof Rectangle) { 
      Rectangle r = (Rectangle)shape; 
      return r.height * r.width;
    } else if (shape instanceof Circle) {
      Circle c = (Circle)shape;
      return PI * c.radius * c.radius; 
    }
    throw new NoSuchShapeException(); 
  }
}
</code></pre><pre><code class="language-java">
// 객체지향적인 코드
public class Square implements Shape { 
  private Point topLeft;
  private double side;

  public double area() { 
    return side * side;
  } 
}

public class Rectangle implements Shape { 
  private Point topLeft;
  private double height;
  private double width;

  public double area() { 
    return height * width;
  } 
}

public class Circle implements Shape { 
  private Point center;
  private double radius;
  public final double PI = 3.141592653589793;

  public double area() {
    return PI * radius * radius;
  } 
}
</code></pre>
<ul>
<li>절차지향적인 코드에서는 if문을 통해 절차적으로 처리한다</li>
<li>새로운 참수를 추가하고 싶다면 geometry 내부에 하나의 함수를 새로 만들기만 하면 된다</li>
<li>기존의 자료 구조에 영향을 끼치지 않는다</li>
<li>새로운 도형인 오각형을 넣는다고 하면 geometry에 속한 모든 함수를 변경해줘야 한다</li>
<li>객체를 사용하여 만들었을 경우 변수를 감추고 함수로만 외부에서 사용하도록 열어준다</li>
<li>새로운 함수를 추가하고 싶다면 모든 클래스에 해당 함수를 구현해줘야 한다</li>
<li>새로운 도형인 오각형을 추가하고 싶다면 새로운 클래스를 만들어 그에 맞는 메서드만 구현해주면 된다</li>
</ul>
<h2 id="3-디미터-법칙">3. 디미터 법칙</h2>
<ul>
<li>객체는 조회 함수로 내부 구조를 공개하면 안된다</li>
</ul>
<p><img src="https://velog.velcdn.com/images/seunghee-ryu/post/d56eb514-b18a-4c0a-a97d-b918c7f5e67e/image.png" alt=""></p>
<ul>
<li>클래스 C의 메서드 f는 이하의 메서드만 호출해야 한다<ul>
<li><ol>
<li>Class C</li>
</ol>
</li>
<li><ol start="2">
<li>f가 생성한 객체</li>
</ol>
</li>
<li><ol start="3">
<li>f 인수로 넘어온 객체</li>
</ol>
</li>
<li><ol start="4">
<li>C instance 변수에 저장된 객체</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3 id="기차-충돌">기차 충돌</h3>
<pre><code class="language-java">
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
</code></pre>
<ul>
<li>여러 객체가 한 줄로 이어진 기차처럼 보인다고 해서 이런 코드를 기차 충돌이라고 부른다</li>
<li>위 코드는 피하고 아래와 같이 나누는 것이 좋다</li>
</ul>
<pre><code class="language-java">
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
</code></pre>
<ul>
<li>이렇게 작성하는 경우 ctxt, optx, scratchDir이 객체인지 자료구조인지를 먼저 명확하게 해야한다</li>
<li>객체인 경우 내부 구조가 노출되었기 때문에 디미터 법칙을 위반하게 된다</li>
<li>자료 구조인 경우는 내부 구조를 노출하기 때문에 디미터 법칙이 적용되지 않는다</li>
</ul>
<pre><code class="language-java">
final String outputDir = ctxt.options.scratchDir.absolutePath; 
</code></pre>
<ul>
<li>자료 구조인 경우에는 의미없는 getter, setter를 사용하는 것보다 .을 이용해 잇는것이 좋다</li>
</ul>
<h3 id="잡종-구조">잡종 구조</h3>
<ul>
<li>객체와 자료구조가 섞인 잡종 구조는 피하는 것이 좋다</li>
</ul>
<h2 id="4-해결책">4. 해결책</h2>
<h3 id="구조체-감추기">구조체 감추기</h3>
<ul>
<li>ctxt, options, scratchDir이 객체일 때</li>
</ul>
<pre><code class="language-java">
String outFile = outputDir + &quot;/&quot; + className. replace( &#39;.&#39;, &#39;/&#39;) + &quot;.class&quot;;
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);

// 위임
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
</code></pre>
<ul>
<li>절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위함임을 알았다</li>
<li>임시 파일을 생성하는 것을 해당 객체가 아닌 ctxt 객체에 위임을 하는 경우 ctxt는 불필요하게 내부 구조를 노출할 필요가 없어진다</li>
</ul>
<h3 id="자료-전달-객체">자료 전달 객체</h3>
<ul>
<li>공개 변수만 있고 함수가 없는 클래스(DTO : Data Transfer object)</li>
<li>데이터를 전달만하며 가공되지 않은 원천정보를 애플리케이션 코드에서 사용할 객체로 변환하는 과정 중 가장 처음 사용하는 구조체</li>
</ul>
<h3 id="활성-레코드">활성 레코드</h3>
<ul>
<li>DTO의 특수한 형태</li>
<li>DTO에서 save나 find같은 탐색 함수도 제공한다</li>
<li>활성 레코드에 비즈니스 규칙 메소드를 추가하면 잡종 구조가 된다</li>
<li>이를 해결하기 위해 활성 레코드는 자료 구조로 취급한다</li>
</ul>
<h2 id="개인적인-감상">개인적인 감상</h2>
<ul>
<li>DTO, DAO, VO의 구분이 어려웠기에 이번 기회를 통해 정리를 해보았다</li>
<li>객체인지 자료 구조인지 구별하지 않고 사용해왔고 개념에 대한 정의 또한 없었기 때문에 객체와 자료구조에 대해 생각해 볼 수 있었다</li>
</ul>
<h3 id="추가적인-정리">추가적인 정리</h3>
<ul>
<li>private를 사용해서 외부에 노출시키고 싶지 않은 데이터를 보호하는데 private로 접근자를 설정한 변수가 외부로 노출되지 않는가?</li>
<li>프로그래밍적으로는 노출되지 않지만 getter와 setter를 통해서 조작이 가능하다</li>
<li>그렇기에 추상화와 메서드명을 통해 대략의 목적은 유추할 수 있으나 세부적인 내용은 유추하지 못하도록 작성한다</li>
<li>자료구조는 객체가 데이터와 메서드를 분리시켜 객체의 역할을 축소시킨 모습니다</li>
<li>자료구조를 사용하면 모듈에 동작을 정의하게 되면 이런 형태의 코딩 방식을 절차지향적 프로그래밍이라고 한다</li>
<li>자료구조는 객체이지만 동작을 가지지 않기 때문에 외부의 동작에 사용되는 절차지향적 프로그래밍을 따르게 된다</li>
<li>모든 것이 객체라는 생각은 미신이다</li>
</ul>
<h3 id="dao-dto-vo">DAO, DTO, VO</h3>
<ul>
<li>DAO<pre><code> - Data Access Object
 - DB의 데이터에 접근하기 위한 객체
 - 직접 DB에 접근하여 data를 삽입, 삭제 , 조회 등 조작할 수 있는 기능을 수행한다</code></pre></li>
<li>DTO<pre><code> - Data Transfer Object
 - 계층 간 데이터 교환을 위한 Java Bean을 의미
 - 로직을 가지지 않는 데이터 객체
 - getter, setter 메소드만 가진 클래스를 의미한다</code></pre></li>
<li>VO<pre><code> - Value Object
 - Read-only 속성을 가진 값 오브젝트
 - 값 타입을 표현하기 위해 불변 클래스를 만들어 사용한다
 - 따라서 getter 기능만 존재한다</code></pre></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>