이정우
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 문을 실행한다는 의미이다.
ComparisonCompactor
모듈은 저자들이 아주 좋은 상태로 남겨두었지만 보이스카우트 규칙에 따르면 우리는 처음 왔을 때보다 더 깨끗하게 해 놓고 떠나야 한다.
- 멤버 변수 접두어
f
제거- 오늘날 사용하는 개발 환경에서는 변수 이름에 범위를 명시할 필요가 없다.
compact()
조건문 캡슐화compact
함수 시작부에 캡슐화되지 않은 조건문이 보인다.- 명확한 의도 표현을 위해 조건문을 캡슐화 한다.
- 조건문을 메서드로 뽑아 적절한 이름을 붙인다.
canBeCompacted()
함수명 변경- 문자열을 압축하는 함수라지만 실제로
canBeCompacted
가 false이면 압축하지 않는다. - 함수명이
compact
라면 오류 점검이라는 부가 단계가 숨겨진다. - 단순 압축된 문자열이 아닌 형식이 갖춰진 문자열을 반환한다.
- 문자열을 압축하는 함수라지만 실제로
formatCompactedComparison
함수 분리- if 문 안에서 예상 문자열과 실제 문자열을 진짜로 압축한다.
- 숨겨진 시간적인 결합
findCommonSuffix
에 숨겨진 시간적인 결합(hidden temporal coupling)이 존재한다.findCommonSuffix
는finCommonPrefix
가prefixIndex
를 계산한다는 사실에 의존한다.
- 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 : "";
}
}
- 코드가 상당히 깔끔하다.
- 모듈은 일련의 분석 함수와 일련의 조합 함수로 나뉜다.
- 보이스카우트 규칙을 지켰다.
- 모듈은 처음보다 조금 더 깨끗해졌다.
- 전체 함수는 위상적으로 정렬했으므로 각 함수가 사용된 직후에 정의된다.
- 분석 함수가 먼저 나오고 조합 함수가 그 뒤를 이어서 나온다.
- 코드를 주의 깊게 살펴보면 리팩토링 했던 부분을 원래대로 되돌렸다는 사실을 눈치챌 수 있다.
- 코드를 리팩토링 하다 보면 원래 코드로 되돌리는 경우는 흔하다.
- 리팩토링은 코드가 어느 수준에 이를 때까지 수많은 시행착오를 반복하는 작업이다.
- 결론
- 세상에 개선이 불필요한 모듈은 없다.
- 코드를 처음보다 조금 더 깨끗하게 만드는 책임은 우리 모두에게 있다.
2. 🤔 이해가 어려운 부분
🔍 질문하기
책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.
- 개념 또는 원칙의 이름
- 어려웠던 부분
해당 개념이 헷갈리거나 명확하지 않았던 점을 구체적으로 설명합니다. - 궁금한 점
해당 개념이 어떤 원리로 동작하는지, 실무에서 어떻게 활용되는지 등을 질문 형태로 정리합니다.
- 어려웠던 부분
- 개념 또는 원칙의 이름
- 어려웠던 부분
. - 궁금한 점
.
- 어려웠던 부분
- 개념 또는 원칙의 이름
- 어려웠던 부분
. - 궁금한 점
.
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.
- 관련 자료 공유
- 추가 자료
관련 블로그 글이나 공식 문서 링크를 제공합니다.
- 추가 자료
- 논의하고 싶은 주제
- 주제
논의하고 싶은 내용을 간략히 정리합니다. - 설명
논의하고 싶은 이유를 작성합니다.
- 주제