make it simple
article thumbnail

12장 - 창발성(하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상)

  • 켄트 백은 밑과 같은 규칙을 따르면 설계는 '단순하다'라고 말한다. 
    • 모든 테스트를 실행한다 : 시스템은 의도한대로 작동해야한다. 의도한대로 돌아가는지 확인할 수 있는 시스템은 테스트가 가능한 시스템이다. 또한, 결합도가 높으면 테스트하기가 힘들다. 테스트 코드를 짬으로서 결합도를 낮추고 개발할 수 있다.
    • 중복을 없앤다:  테스트 케이스를 작성하고 리팩토링을 한다. 중복 제거는 리팩토링에서 우선순위이다. 중복은 시스템을 지저분하게 만든다. 단 몇 줄이라도 중복을 제거하겠다는 의지가 필요하다.
    • 프로그래머 의도를 표현한다: 의도를 표현하지 않은 코드는 유지보수비가 많이든다. 개발자가 코드를 명백하게 짤수록 그 코드는 이해하기 쉬워진다. 
      1. 좋은 이름을 선택한다. 
      2. 함수나 클래스 크기를 가능한 줄인다.
      3. 표준 명칭을 사용한다. Command나 Visitor같은 표준 패턴을 쓰면 클래스에 이름을 포함하는게 의도를 나타내기 좋다. 
      4. 단위 테스트 케이스를 꼼꼼히 작성한다. 잘 만든 테스트 케이스는 클래스 기능이 한눈에 들어온다. 
    • 클래스와 메서드 수를 최소로 줄인다:  클래스와 함수의 크기를 작게 유지하면서 동시에 시스템 크기도 작게 유지하는 것도 중요하지만, 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 더 중요하다.

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 목록을 피하고 와일드 카드를 사용해라. 
profile

make it simple

@keep it simple

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!