12장 - 창발성(하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상)
- 켄트 백은 밑과 같은 규칙을 따르면 설계는 '단순하다'라고 말한다.
- 모든 테스트를 실행한다 : 시스템은 의도한대로 작동해야한다. 의도한대로 돌아가는지 확인할 수 있는 시스템은 테스트가 가능한 시스템이다. 또한, 결합도가 높으면 테스트하기가 힘들다. 테스트 코드를 짬으로서 결합도를 낮추고 개발할 수 있다.
- 중복을 없앤다: 테스트 케이스를 작성하고 리팩토링을 한다. 중복 제거는 리팩토링에서 우선순위이다. 중복은 시스템을 지저분하게 만든다. 단 몇 줄이라도 중복을 제거하겠다는 의지가 필요하다.
- 프로그래머 의도를 표현한다: 의도를 표현하지 않은 코드는 유지보수비가 많이든다. 개발자가 코드를 명백하게 짤수록 그 코드는 이해하기 쉬워진다.
- 좋은 이름을 선택한다.
- 함수나 클래스 크기를 가능한 줄인다.
- 표준 명칭을 사용한다. Command나 Visitor같은 표준 패턴을 쓰면 클래스에 이름을 포함하는게 의도를 나타내기 좋다.
- 단위 테스트 케이스를 꼼꼼히 작성한다. 잘 만든 테스트 케이스는 클래스 기능이 한눈에 들어온다.
- 클래스와 메서드 수를 최소로 줄인다: 클래스와 함수의 크기를 작게 유지하면서 동시에 시스템 크기도 작게 유지하는 것도 중요하지만, 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 더 중요하다.
13장 ,14장 , 15장 은 코드 위주로 되어있어서 생략했다.
16장 - 냄새와 휴리스틱
- Refactoring 저자 마틴 파울러는 다양한 코드 냄새를 거론한다. 밑에 코드 냄새를 설명한다.
- 주석
- 작성자, 최종수정일, Software Problem Report같은 메타 정보만 주석에 넣는다. 그 외는 불필요하다.
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다. 주석은 아예 달지 않는 편이 가장 좋다.
- 주석 처리된 코드도 지워버려라. 흉물 그 자체다.
- 환경
- 빌드는 간단히 한 단계로 끝내야 한다. 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
- 모든 단위 테스트는 한 명령으로 돌려야 한다. 모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하다. 따라서, 그방법이 쉽고 명백해야한다.
- 함수
- 함수에서 인수 개수는 작을수록 좋다. 없으면 가장 좋다.
- boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다. 플래그 인수는 혼란을 초래하므로 피해야 마땅하다.
- 아무도 호출하지 않는 함수는 삭제한다. 죽은 코드다.
- 일반
- 이상적으로 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다. 소스 파일에서 언어 수와 범위를 최대한 줄이자.
- 코드는 올바로 동작해야한다. 스스로의 직관의 의존하지 마라. 코드를 믿어라. 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.
- 중복을 피하고 또 피해라. 클린 코드에서 가장 중요하게 강조한 내용 중 하나다.
- 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라. 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라.
public interface MessageService {
void sendMessage(String message);
}
// 인터페이스를 구현한 서비스 클래스 - Email 메시지 서비스
@Service
public class EmailService implements MessageService {
@Override
public void sendMessage(String message) {
// Email을 보내는 코드
System.out.println("이메일을 보냅니다: " + message);
}
}
// 인터페이스를 구현한 서비스 클래스 - SMS 메시지 서비스
@Service
public class SMSService implements MessageService {
@Override
public void sendMessage(String message) {
// SMS를 보내는 코드
System.out.println("SMS를 보냅니다: " + message);
}
}
// 중복 코드를 추상화한 클래스
@Component
public class MessageSender {
private final MessageService messageService;
// 의존성 주입을 통해 MessageService 구현체를 주입 받음
public MessageSender(MessageService messageService) {
this.messageService = messageService;
}
// 중복되는 코드를 포함한 메서드
public void sendNotification(String message) {
// 중복되는 로직
System.out.println("알림을 보냅니다.");
// MessageService 구현체에 따라 메시지를 전송
messageService.sendMessage(message);
}
}
- 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다. 모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다. 밑에 코드에서 Animal이라는 클래스를 기초로 하여 공통된 특성을 가지고, 각 동물들이 개별적으로 각 동물들의 소리를 내는 특성을 가지도록 추상화했다. 저차원의 구체적인 개념들(개,고양이,사자)를 파생 클래스로 분리하고, 고차원 개념(동물)을 기초 클래스로 분리한 추상화다.
// 추상 동물 클래스 (고차원 개념)
public abstract class Animal {
public abstract void makeSound(); // 동물이 소리를 내는 행동을 추상화한 메서드
}
// 각각의 동물을 나타내는 구체 클래스 (저차원 개념)
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("야옹!");
}
}
public class Lion extends Animal {
@Override
public void makeSound() {
System.out.println("어흥!");
}
}
- 일반적으로 static 함수보다 인스턴스 함수가 더 좋다. 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.
- 서술적 이름을 사용하라.
- 매직 숫자는 명명된 상수로 교체해라.
- 부정 조건은 피해라.
if(buffer.shouldCompact()) // 이게 훨씬 직관적이고 더 좋다.
if(!buffer.shouldNotCompact())
// 한 함수가 세개의 임무를 수행한다. -> 나쁜 예제
public void pay(){
for (Employee e : employees){
if (e.isPayday()){
Money pay = e.calculatePay();
e.deliverPay(pay);
}
}
}
==================================================================================
// 각 함수들은 한가지 임무만 수행한다. -> 좋은 예제
public void pay(){
for (Employee e: employees)
payIfNecessary(e)
}
private void payIfNecessary(Employee e){
if(e.isPayday())
calculateAndDeliverPay(e);
}
private void calculateAndDeliveryPay(Employee e){
Money pay = e.calculatePay();
e.deliveryPay(pay);
}
- 긴 import 목록을 피하고 와일드 카드를 사용해라.