TDD, Clean Code with Java λ₯Ό μλ£νλ©΄μ μμ±νλ νκ³ κΈ

μ΄λ²μ£Όμ TDD, Clean Code with Java 14κΈ° κ³Όμ μ μλ£νλ€. κΈ°μ‘΄μ λͺ©νλ 6μ£Ό μμ λ―Έμ μ λͺ¨λ λλ§μΉκ³ μ΄μ λ―Έμ λ€μ λν΄ λΆμ‘±νμ μ λλμλ³΄λ €κ³ νμ§λ§ μμνλ κ²κ³Όλ λ¬λ¦¬ λ³Όλ§ λ―Έμ μ΄λΌλ μ΅μ’ 보μ€λ₯Ό λ§λ μ€λ μκ°μ ν¬μνλ€. (λ€μ μκ°ν΄λ λ³Όλ§ λ―Έμ μ μ§μ§ μ΄λ €μ λ€...) λ€νν 8μ£Ό κΈ°κ° λ΄μ λ―Έμ μ μλ£νλ€. λ§€μΌ μ λ λ¨Ήκ³ μ리μ μμ μλ 보λμ΄ μμλ€. λ§μ§λ§μ 체λ ₯μ μΌλ‘ μ§μ³ κ°λμ©μ κ·Έλ₯ λλΈλ¬μ Έ μκΈ°λ νλλ° κ·Έλλ κΈ°κ° λ΄μ μλ£νκ³ μΆμλ μμ¬ λλΆμ κ°μ ν μμ£Όν μ μμλ€.
- μλμ°¨ κ²½μ£Ό λ―Έμ
- λ‘λ λ―Έμ
- μ¬λ€λ¦¬ λ―Έμ
- λ³Όλ§ λ―Έμ
1. TDD, Clean Code with Java μ₯μ : μ½λ리뷰
λ―Έμ μ μ§ννλ©΄μ ν΄λ¦°μ½λμ κ°μ²΄μ§ν₯μ μΈ μ€κ³μ κ΄ν΄ λ§μ κ³ λ―Όμ ν΄λ³Ό μ μλ μ’μ κΈ°νμλ€. λ¨μν ꡬνμ μ΄μ μ λ§μΆμ§ μκ³ κ°μ²΄ 체μ μ± μκ³Ό νλ ₯μ λν΄ κ³ λ―Όν μ μμλ€.TDD, Clean Code with Java κ³Όμ μ μ₯μ μ μ½λ 리뷰λΌκ³ μκ°νλ€. μ½λ λ¦¬λ·°κ° μ λ§ μ’μλ μ΄μ λ λ΄κ° μ€μ€λ‘ 곡λΆνκ³ μκ°ν΄λ³Ό μ μλλ‘ λ¦¬λ·°μ΄μ κ³ λ―Όνκ³ μλ λ΄μ©κ³Ό κ°μ μ¬νμ λν΄ μλ‘ λ Όμν΄λ³Ό μ μλ μ μ΄λ€. μλ§ μ½λλ₯Ό μμ±νλ©΄μ λ΄κ° μμ±ν λ°©λ²μ΄ μ’μ λ°©λ²μΈκ°? λ μ’μ λ°©λ²μ μλ? μ κ΄ν μκ°μ΄ λ€κ³€ νλ€. μ΄λ° κ³ λ―Όλ€μ 리뷰μ΄μ μ견μ μ£Όκ³ λ°μΌλ©΄μ λ λμ μ€κ³μ λν΄ ν¨κ» κ³ λ―Όν΄λ³΄κ³ νΉμ λμμΈ ν¨ν΄μ μ¬μ©κ³Ό μ΄μ λ₯Ό μ§μ μ½λμ μ μ©ν΄λ³΄λ©΄μ 체κ°ν΄λ³Ό μ μμλ€. κ·Έλ¦¬κ³ λμ μ½λ μ΅κ΄μ κ³ μΉ μ μλ€. μμ’μ μ΅κ΄μ λ΄κ° μ°ΎκΈ°λ μ΄λ ΅λ€. νμ§λ§ κΌΌκΌΌν νΌλλ°± λλΆμ λμ λΆμ‘±ν μ μ λ€μ νλ² μκΈ°μν¬ μ μμκ³ λ¬΄μν μμκ³Ό ν¨κ» μ½λλ₯Ό κ³ μΉλ μμ μ λͺ¨μ΅μ λ³Ό μ μλ€. (μ€μλ₯Ό νμ§ μκΈ° μν΄μ μ€λ΅λ ΈνΈλ κ°μ λ°©μμ λν΄μλ νμλ€.)
2. μΆμ² μ¬λΆμ μμΌλ‘μ κ³ν
λ΄ λλ£, μ§μΈλ€μ μΆμ²ν μμ¬κ° μλ€λ©΄ μΈμ λ YES μ΄λ€. κ·Έλμ λμ μ½λ μ΅κ΄μ μ κ²ν λΏλ§ μλλΌ λ μ’μ μ½λμ λν΄ ν¨κ» κ³ λ―Όνκ³ λ Έλ ₯νλ λ¬Ένλ₯Ό λ§λ€μ΄ λκ° μ μλ κΈμ μ μΈ μ μνμ΄ λ μ μμ κ² κ°λ€. λΉλ‘ κ°κ²©μ΄ μ‘°κΈ μλ€λ μ μ΄ μμ§λ§ νμ κ°λ°μμκ² μ½λ리뷰λ₯Ό λ°μ μ μκ³ λ μλ£ν μ¬λλ€ μ€μ λ°μ¬μ±λμ΄ μ§μ λ―Έμ μ½λλ₯Ό νμΈνκ³ λ¦¬λ·°μ΄ μ μμ νμ λ€κ³ νλ κ·Έλ§ν ν리ν°λ₯Ό μ΄λμ λ 보μ₯νλ κ² κ°λ€.
3. μ€λ΅λ ΈνΈ
μλλ μ€κ³λ₯Ό νλ©΄μ κ³ λ―Όνλ λΆλΆκ³Ό νΌλλ°±κ³Ό μ€μλ₯Ό λ°©μ§νκΈ° μν΄ νλ λ Έλ ₯μ λν΄ μ 리ν λ΄μ©μ΄λ€. λ΄κ° νλ κ³ λ―Όλ€μ 곡μ νλ©΄ μ’μ κ² κ°μ μλμ κΈ°λ‘νλ€.
1. 컀μ€ν μμΈ ν΄λμ€ μΆκ° λ° μμΈ μμΈλ₯Ό λ°ν
λΉμ¦λμ€ λ‘μ§μμ μμΈμ λν΄ μ»€μ€ν ν΄λμ€λ₯Ό μ μνλ©΄ λμ± μλ―Έμλ λΉμ¦λμ€ μ€ν¨ μΌμ΄μ€λ₯Ό κ²μ¦ν μ μλ€. λν μ€ν¨ μμΈμ μμΈ λ©μΈμ§μ μΆκ°νλ©΄ λμ± λͺ ννκ² μμΈ μμΈμ λν΄ νμ ν μ μλ€. (μ΄νν°λΈ μλ° Item 75. μμΈμ μμΈ λ©μμ§μ μ€ν¨ κ΄λ ¨ μ 보λ₯Ό λ΄μλΌ )
2. final class μ μΈ
classμ finalμ μμμ λ°©μ§νκ² λ€λ μλ―Έμ΄λ€. λΉλ‘ default constructorμ private access modifierμ μ μΈνλ©΄ μ묡μ μΌλ‘ μμμ νμ§ μμ ν¨κ³Όκ° μμ§λ§ μλ° λ¬Έλ²μμ μμμ κΈμ§μν€λ λͺ νν μ΄μ λ finalμ΄λ―λ‘ final μμ½μ΄λ₯Ό μ¬μ©νλλ‘ νμ.
3. μλ°μμ static μ¬μ©μ μ§μνλ μ΄μ
staticμ κ°μ²΄ μ§ν₯μ μ΄μ§ μλ€. μΊ‘μν μμΉμ μλ°°λλ€. μΊ‘μνλ κ°μ²΄λ μν κ³Ό μ± μμ κ°μ§κ³ λ°μ΄ν°λ₯Ό μ§μ κ΄λ¦¬νμ§λ§ static μ μ λ³μλ₯Ό μ¬μ©νκ² λ κ²½μ°μ λ³μμ λ²μκ° μ μμΌλ‘ μ¬μ©λμ΄ μΈλΆμμ λ°μ΄ν°λ₯Ό μ°Έμ‘°ν μ μκΈ° λλ¬Έμ΄λ€. λν GCμ μν΄ λ©λͺ¨λ¦¬λ₯Ό νμ λμμ΄ μλλ―λ‘ Memory Leakμ μμΈμ΄ λ μ μλ€. staticμ λΆκ°νΌνκ² μ¬μ©μν΄μΌ νλ€λ©΄ access modifierλ₯Ό privateλ‘ μ€μ νμ¬ μΈλΆμ λ ΈμΆλμ§ μλλ‘ νλ€. (Ref.μ μλ°μμ staticμ μ¬μ©μ μ§μν΄μΌ νλκ°? )
4. μμ λμ μ‘°ν©μ μ¬μ©νλ λ°©λ²μ μκ°ν΄λ³΄μ.
μ¬μ¬μ©μ λͺ©μ μΌλ‘ μμμ μ μΈνλ κ²μ μλΉν μνν λ°©λ²μ΄λ€. λΆλͺ¨, μμ ν΄λμ€ κ°μ κ°ν κ²°ν©μΌλ‘ μ°κ²°νμ¬ μ μ§λ³΄μκ° μ΄λ €μ΄ μ½λκ° λλ€. (λΆλͺ¨ λ‘μ§μ΄ λ°λλ©΄ μμ λ‘μ§μ΄ λ°λ μ μκ³ μ΄λ 리μ€μ½ν μΉν μμΉμ μλ°°ν μ μμ) λν μμ ν΄λμ€κ° λΆλͺ¨μ λ©μλ μ¬μ©νκΈ° λλ¬Έμ μΊ‘μνλ₯Ό μλ°νλ€.
μμμ λ체ν μ μλ λ°©λ² μ€ νλλ μ‘°ν©μ μ¬μ©νλ κ²μ΄λ€. κΈ°μ‘΄μ ν΄λμ€μ μλ‘μ΄ ν΄λμ€ κ΅¬μ±μμλ₯Ό μΆκ°ν΄ μΊ‘μνλ₯Ό μ μ§νλ©΄μ κΈ°λ₯μ νμ₯ν μ μλ μ₯μ μ΄ μλ€. (Ref. [tecoble] μμ보λ€λ μ»΄ν¬μ§μ (μ‘°ν©)μ μ¬μ©νμ)
public class WinningTicket {
private final LottoTicket lottoTicket;
private final LottoNumber bonusNumber;
public WinningTicket(Set<LottoNumber> numbers, LottoNumber bonusNumber) {
this(new LottoTicket(numbers), bonusNumber);
}
public WinningTicket(LottoTicket lottoTicket, LottoNumber bonusNumber) {
validateBonusNumber(lottoTicket, bonusNumber);
this.lottoTicket = lottoTicket;
this.bonusNumber = bonusNumber;
}
public Rank drawLotto(LottoTicket lottoTicket) {
int count = this.lottoTicket.countMatches(lottoTicket);
boolean matchBonus = lottoTicket.includeBonusNumber(bonusNumber);
return Rank.valueOf(count, matchBonus);
}
private void validateBonusNumber(LottoTicket lottoTicket, LottoNumber bonusNumber) {
if(lottoTicket.includeBonusNumber(bonusNumber)) {
throw new InvalidBonusNumberException(bonusNumber);
}
}
}
5. μ λ΅ ν¨ν΄(Strategy Pattern) μ¬μ©
μ λ΅ ν¨ν΄μ νμλ₯Ό ν΄λμ€λ‘ μΊ‘μννμ¬ νμμ λν΄ λ³κ²½μ μ μ°νκ² ν΄μ€ μ μλ ν¨ν΄μ΄λ€.
public interface NumberGenerationStrategy {
Set<LottoNumber> generateLottoNumbers();
}
public class ManualGenerationStrategy implements NumberGenerationStrategy {
@Override
public Set<LottoNumber> generateLottoNumbers() {
return InputUtil.readNumbers();
}
}
public class RandomGenerationStrategy implements NumberGenerationStrategy {
private static final int MAX_LOTTO_NUMBER_BOUND = 45;
private static final int LOTTO_NUMBERS_SIZE = 6;
private static final int LOTTO_MIN_NUMBER = 1;
private static final Random RANDOM = new Random();
@Override
public Set<LottoNumber> generateLottoNumbers() {
Set<Integer> numbers = generateRandomNumber();
return numbers.stream()
.map(LottoNumber::create)
.collect(Collectors.toSet());
}
private Set<Integer> generateRandomNumber() {
Set<Integer> numbers = new HashSet<>();
while(numbers.size() < LOTTO_NUMBERS_SIZE) {
int number = generateNumber();
if (numbers.contains(number) || isInvalidLottoNumber(number)) {
continue;
}
numbers.add(number);
}
return numbers;
}
private boolean isInvalidLottoNumber(int number) {
return ((number > MAX_LOTTO_NUMBER_BOUND) || (number < LOTTO_MIN_NUMBER));
}
private int generateNumber() {
return RANDOM.nextInt(MAX_LOTTO_NUMBER_BOUND);
}
}
6. νλΌμ΄μ¨μ΄νΈ ν¨ν΄ (FlyWeight Pattern)
λ‘λ λ²νΈλ₯Ό μμ±ν λ, λ§€λ² new μ°μ°μλ₯Ό ν΅ν΄ μλ‘μ΄ κ°μ²΄λ₯Ό μμ±νλ©΄ κ°μ μΈμ€ν΄μ€λ₯Ό λ°λ³΅ν΄μ μμ±νκΈ° λλ¬Έμ Memory Leak λ°μ μ§μ μ΄ λ μλ μλ€. (μ¬μ©μ 100λͺ μ΄ λ‘λλ₯Ό μ¬λ©΄ 600κ°μ μλ‘μ΄ λ‘λ λλ²...) λ‘λ λ²νΈλ κ³ μ λμ΄ μμΌλ―λ‘ λ―Έλ¦¬ μΊμ±ν΄μ μ¬μ©ν μ μλ flyweight patternμ μ μ©νλ€. flyweight patternμ κ°μ μλ‘ μμ±νμ§ μκ³ μ¬μ¬μ©νκΈ° μν΄μ μ’ μ’ μ¬μ©λλ ν¨ν΄μ΄λ€.
(Ref. νλΌμ΄μ¨μ΄νΈ(Flyweight) ν¨ν΄μ΄λ?)
public class LottoNumber {
private static final Map<Integer, LottoNumber> lottoNumbers = new HashMap<>();
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private final int value;
private LottoNumber(int value) {
validate(value);
this.value = value;
}
public static LottoNumber create (int value) {
lottoNumbers.putIfAbsent(value, new LottoNumber(value));
return lottoNumbers.get(value);
}
...
}
7. μμ±μ νλλ₯Ό μ£Ό μμ±μλ‘ μ¬μ©νκΈ°
λ©μλκ° λ§μμ§λ©΄ ν΄λμ€μ μ΄μ μ΄ νλ €μ§κ³ SRP(Single Responsiblility Principle)μ μλ°νλ€. μμ±μμ μ£Όλ μμ μ μ 곡λ μΈμλ₯Ό μ¬μ©ν΄ μΊ‘μννκ³ νλ‘νΌν°λ₯Ό μ΄κΈ°ννλ μΌμ΄λ€. μ΄λ° μ΄κΈ°ν λ‘μ§μ λ¨ νλμ 'μ£Ό μμ±μ'μλ§ μμΉμν€κ³ λ€λ₯Έ 'λΆ μμ±μ'λ€μ΄ μ΄ μ£Ό μμ±μλ₯Ό νΈμΆνλλ‘ νλ€. (Ref.[μλ κ°νΈ μ€λΈμ νΈ] 1. μΆμ - (2) μμ±μ νλλ₯Ό μ£Ό μμ±μλ‘ λ§λμΈμ)
8. μΆμ ν΄λμ€λ³΄λ€ μΈν°νμ΄μ€λ₯Ό μ°μ μ νλΌ. + λ§μ»€ μΈν°νμ΄μ€
μμμ ν΄λμ€μ μΊ‘μνλ₯Ό μλ°μν€λ©° μ€λ²λΌμ΄λ λ©μλλ₯Ό μμν ν΄λμ€μ μμΉλ₯Ό νμ νκΈ° μ΄λ €μ κ°λ μ±μ λ¨μ΄λ¨λ¦¬λ λ¨μ μ΄ μλ€. νμ§λ§ μΈν°νμ΄μ€λ κ³μΈ΅ κ΅¬μ‘°κ° μλ ννλ‘ ν΄λμ€λ₯Ό ꡬνν μ μμ΄ μΊ‘μνλ₯Ό μ§ν¬ μ μλ€. (Ref.Effective Java Item 20. μΆμ ν΄λμ€λ³΄λ€λ μΈν°νμ΄μ€λ₯Ό μ°μ νλΌ) λν λ¨μν νμ 체ν¬λ₯Ό μν λ§μ»€μΈν°νμ΄μ€(ex. serializable)μ μ¬μ©μ ν΄λμ€μ μΈμ€ν΄μ€λ₯Ό ꡬλΆνκ³ μ»΄νμΌ νμ μμ νμΈμ΄ κ°λ₯νλ€. (Ref. Effective Java Item 41. μ μνλ €λ κ²μ΄ νμ μ΄λΌλ©΄ λ§μ»€ μΈν°νμ΄μ€λ₯Ό μ¬μ©ν΄λΌ)
4. λ§μ§λ§μΌλ‘
νλ€ λ νλ²μ© λ΄μΌκ² λ€. μΉμ°¬ κ°μ¬ν©λλ€ πβοΈ
