Skip to main content

이정우

1. 📌 핵심 개념 정리

✅ 요약하기

각자 해당 챕터에서 중요하다고 느낀 개념이나 아이디어를 간략하게 정리하고 개선 전, 후에 대한 예시 코드를 비교하며 개념을 설명합니다.

  1. JUnit 프레임워크
import junit.framework.ComparisonCompactor;
import junit.framework.TestCase;

public class ComparisonCompactorTest extends TestCase {
    public void testMessage() {
        String failure = new ComparisonCompactor(0, "b", "c").compact("a");
        assertTrue("a expected:<[b]> but was:<[c]>".equals(failure));
    }

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

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

    public void testSame() {
        String failure = new ComparisonCompactor(1, "ab", "ab").compact(null);
        assertEquals("expected:<ab> but was:<ab>", failure);
    }

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

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

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

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

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

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

    public void testComparisonErrorOverlapingMatches() {
        String failure = new ComparisonCompactor(0, "abc", "abbc").compact(null);
        assertEquals("expected:<...[]...> but was:<...[b]...>", failure);
    }

    public void testComparisonErrorOverlapingMatchesContext() {
        String failure = new ComparisonCompactor(2, "abc", "abbc").compact(null);
        assertEquals("expected:<ab[]c> but was:<ab[b]c>", failure);
    }

    public void testComparisonErrorOverlapingMatches2() {
        String failure = new ComparisonCompactor(0, "abcdde",
                "abcde").compact(null);
        assertEquals("expected:<...[d]...> but was:<...[]...>", failure);
    }

    public void testComparisonErrorOverlapingMatches2Context() {
        String failure =
                new ComparisonCompactor(2, "abcdde", "abcde").compact(null);
        assertEquals("expected:<...cd[d]e> but was:<...cd[]e>", failure);
    }

    public void testComparisonErrorWithActualNull() {
        String failure = new ComparisonCompactor(0, "a", null).compact(null);
        assertEquals("expected:<a> but was:<null>", failure);
    }

    public void testComparisonErrorWithActualNullContext() {
        String failure = new ComparisonCompactor(2, "a", null).compact(null);
        assertEquals("expected:<a> but was:<null>", failure);
    }

    public void testComparisonErrorWithExpectedNull() {
        String failure = new ComparisonCompactor(0, null, "a").compact(null);
        assertEquals("expected:<null> but was:<a>", failure);
    }

    public void testComparisonErrorWithExpectedNullContext() {
        String failure = new ComparisonCompactor(2, null, "a").compact(null);
        assertEquals("expected:<null> but was:<a>", failure);
    }

    public void testBug609972() {
        String failure = new ComparisonCompactor(10, "S&P500", "0").compact(null);
        assertEquals("expected:<[S&P50]0> but was:<[]0>", failure);
    }
}
  • 위 테스트 케이스는 ComparisonCompactor 모듈에 대한 코드 커버리지가 100%가 나왔다.
  • 테스트 케이스가 모든 행, 모든 if 문, 모든 for 문을 실행한다는 의미이다.
  1. ComparisonCompactor 모듈은 저자들이 아주 좋은 상태로 남겨두었지만 보이스카우트 규칙에 따르면 우리는 처음 왔을 때보다 더 깨끗하게 해 놓고 떠나야 한다.
  • 멤버 변수 접두어 f 제거
    • 오늘날 사용하는 개발 환경에서는 변수 이름에 범위를 명시할 필요가 없다.
  • compact() 조건문 캡슐화
    • compact 함수 시작부에 캡슐화되지 않은 조건문이 보인다.
    • 명확한 의도 표현을 위해 조건문을 캡슐화 한다.
    • 조건문을 메서드로 뽑아 적절한 이름을 붙인다.
  • canBeCompacted() 함수명 변경
    • 문자열을 압축하는 함수라지만 실제로 canBeCompacted가 false이면 압축하지 않는다.
    • 함수명이 compact라면 오류 점검이라는 부가 단계가 숨겨진다.
    • 단순 압축된 문자열이 아닌 형식이 갖춰진 문자열을 반환한다.
  • formatCompactedComparison 함수 분리
    • if 문 안에서 예상 문자열과 실제 문자열을 진짜로 압축한다.
  • 숨겨진 시간적인 결합
    • findCommonSuffix에 숨겨진 시간적인 결합(hidden temporal coupling)이 존재한다.
    • findCommonSuffixfinCommonPrefixprefixIndex를 계산한다는 사실에 의존한다.
  1. ComparisonCompactor.java (최종 버전)
package com.example.japring.junit;

public class ComparisonCompactor {

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

    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 = compact(expected);
            compactActual = compact(actual);
        }
        return Assert.format(message, compactExpected, compactActual);
    }

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

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

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

    private char charFromEnd(String s, int i) {
        return s.charAt(s.length() - i - 1);
    }

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

    private void findCommonPrefix() {
        int prefixIndex = 0;
        int end = Math.min(expected.length(), actual.length());
        for (; prefixIndex < end; prefixIndex++) {
            if (expected.charAt(prefixIndex) != actual.charAt(prefixIndex)) {
                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() {
        return prefixLength > contextLength ? ELLIPSIS : "";
    }

    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 > contextLength ? ELLIPSIS : "";
    }
}
  • 코드가 상당히 깔끔하다.
  • 모듈은 일련의 분석 함수와 일련의 조합 함수로 나뉜다.
  • 보이스카우트 규칙을 지켰다.
  • 모듈은 처음보다 조금 더 깨끗해졌다.
  • 전체 함수는 위상적으로 정렬했으므로 각 함수가 사용된 직후에 정의된다.
  • 분석 함수가 먼저 나오고 조합 함수가 그 뒤를 이어서 나온다.
  • 코드를 주의 깊게 살펴보면 리팩토링 했던 부분을 원래대로 되돌렸다는 사실을 눈치챌 수 있다.
  • 코드를 리팩토링 하다 보면 원래 코드로 되돌리는 경우는 흔하다.
  • 리팩토링은 코드가 어느 수준에 이를 때까지 수많은 시행착오를 반복하는 작업이다.
  1. 결론
  • 세상에 개선이 불필요한 모듈은 없다.
  • 코드를 처음보다 조금 더 깨끗하게 만드는 책임은 우리 모두에게 있다.

2. 🤔 이해가 어려운 부분

🔍 질문하기

책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.

  1. 개념 또는 원칙의 이름
    • 어려웠던 부분
      해당 개념이 헷갈리거나 명확하지 않았던 점을 구체적으로 설명합니다.
    • 궁금한 점
      해당 개념이 어떤 원리로 동작하는지, 실무에서 어떻게 활용되는지 등을 질문 형태로 정리합니다.

  1. 개념 또는 원칙의 이름
    • 어려웠던 부분
      .
    • 궁금한 점
      .

  1. 개념 또는 원칙의 이름
    • 어려웠던 부분
      .
    • 궁금한 점
      .

3. 📚 참고 사항

📢 논의하기

관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.

  1. 관련 자료 공유
    • 추가 자료
      관련 블로그 글이나 공식 문서 링크를 제공합니다.

  1. 논의하고 싶은 주제
    • 주제
      논의하고 싶은 내용을 간략히 정리합니다.
    • 설명
      논의하고 싶은 이유를 작성합니다.