본문 바로가기

카테고리 없음

클린코드 3장 함수

728x90

함수는 가능한 작게 만드는 것이다.

 

if, else, for 등의 블록은 중첩 구조를 최소화 하여야 한다.

그러므로 함수에서 들여쓰기 수준은 1단이나 2단을 넘기면 안된다. 그래야 코드를 읽고 이해하기 쉬워진다.

이를 위 여러가지 디자인 패턴들 또한 고려할 수 있다.(AOP, ExceptionHandler, Proxy...)

 

물리적으로 작게 만든다는 것 뿐만 아니라 꼭 필요한 기능 한 가지만 한다는 뜻이다. (그 한 가지를 잘해야한다.)

 

여기서 오해하지 말아야 할 중요한 내용이 있다.

우리는 한 메소드 안에 테이블을 조회하고, 경로를 렌더링하고, 문자열을 덧붙이고, HTML을 생성하는 등 여러가지를 처리하는 메소드를 만들어왔다. 이는 여러가지 기능을 하고 있지만 추상화 수준으로 보면 결국 '페이지를 렌더링한다' 라는 하나의 작업을하고 있다. 이를 판별하기 위해 도메인을 구분하는 것과 클래스와 메소드의 작명을 명확하게 함이 중요하다.

 

함수가 '여러가지 일'을 하는지 판단하는 방법은 함수 내부에서 의미 있는 이름으로 다른 함수로 추출할 수 있는지 이다. 추출할 수 있는 함수는 여러 작업을 하고 있는는 셈이다. 여기서 고려해야할 점은 코드 내부의 추상화 수준이다.

getHtml() 은 추상화 수준이 매우 높다.

PathParser.render(pagepath) 는 중간의 추상화 수준이고

.append("\n") 와 같은 코드는 아주 낮은 추상화 수준이다.

함수 내  추상화 수준을 섞으면 코드를 읽을 때 헷갈린다.  특정 표현에 대한 개념인지 세부사항인지 구분하기 어렵기 때문이다. 따라서 함수 내 모든 추상화 수준을 동일하게 작성하여야 한다.

 

switch문은 클린코드에 부적합하다.

너무 길고, 한 가지 작업만 하지 않는다. 본질적으로 switch 문은 N가지를 처리한다. 하지만 switch 문을 완전히 피할 방법은 없다. 대신 switch문을 저차원 클래스에 숨기고 반복하지 않을 수 있다 (다형성을 이용한다)

 

// 월급 계산법
public Employee caculatePay(Employee e) {
  switch(e.type) {
    case COMMISSIONED : // 임원
    case SALARIED : // 직원
    case HOURLY :  // 아르바이트 
    default : return null;
  }
}

 

위 메소드는 월급을 계산해주는 한 가지 작업만 하는 것 같지만 사실 case 별 다른 계산 방법이 존재하여 한 메소드가 여러 상황을 통제해야한다. 한 메소드는 하나의 책임을 가져야하는 SRP원칙에 어긋난다.

 

또 한 월급 외 휴가, 근무시간, 등 다양한 부분에서 직종별 분류가 필요하다.

직종별로 분류해야할 때 마다 switch문을 사용했다면? 만약 직종이 하나 더 추가된다면 월급, 휴가, 근무시간 등 모든 메소드에 case를 하나씩 더 추가해야한다. 이는 OCP 원칙에 어긋난다.

 

그렇다면 어떻게 switch문을 사용하여야 할까?

 

대표적인 디자인 패턴 중 추상 팩토리 패턴이 있다.

A객체가 B객체를 직접 생성하면 강한 결합 이루어진다 (유지보수에 안좋다.)

객체 생성하는 클래스를 분리하자. 이 분리한 클래스를 팩토리라고 한다.

 

 

팩토리(Factory) 클래스는 B인터페이스의 다양한 구현체를 생성하여 제공한다. A클래스가 B인터페이스의 구현체를 직접 생성하는 것이 아니라, Factory가 B인터페이스의 구현체를 생성한 후 A 클래스에게 '공급'한다. 이처럼 객체의 생성권한이 다른 클래스로 넘어간 것을 두고 제어의 역전(IOC)이라 부른다.   (스프링에서는 DI(의존성 주입)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다. )

 

 

public abstract class Employee {
	public abstract boolean isPayDay();
	public abstract Money calculate();
	public abstract void deliverPay(Money pay);
}

Employee 추상 클래스는 CommissionedEmployee 클래스, SalariedEmployee 클래스, HourlyEmployee 클래스를 자식클래스로 갖는다. 그러므로 Employee 추상클래스는 3가지 형태로 구현가능하다. ( 다형성 )

 

카드리더기가 사원증을 긁으면 직원정보를 저장하는 프로그램을 만든다고 가정하자.

 

public enum EmployeeRecord {
	COMMISSIONED, // 임원
	SALARIED, // 직원
    	HOURLY; // 아르바이트
}

카드리더기는 COMMISIONED, SALARIED, HOURLY 까지 3가지 데이터를 갖는다. 사원증이 카드에 읽히면 3가지 중 하나의 데이터가 Factory 클래스로 입력된다.

 

public class EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) {
		switch(r) {
			case COMMISSIONED : return new CommissionedEmployee(r); // 임원 객체 생성
			case HOURLY : return new HourlyEmployee(r); // 직원 객체 생성
			case SALARIED : return new SalariedEmployee(r); // 아르바이트 객체 생성
			default : return null;
		}
	}
}

Factory 객체는 makeEmployee(EmployeeRecord r) 의 매개변수로 COMMISIONED, SALARIED,HOURLY 중 한 가지 데이터를 받는다. 그 후, switch문에 의해 3가지 객체 중 하나가 선택되어 생성된다.

 

이처럼 switch문을 다형성 있는 객체 생성에 사용하면 switch문의 특성이 단점이 아닌 장점으로 바뀔 수 있다.