BOHYUN STORY

[Clean Code] 3장. 함수 본문

IT/기타

[Clean Code] 3장. 함수

bohyunnn 2021. 6. 30. 01:30
반응형

3장 함수

 

작게 만들어라!

- 함수를 만드는 첫째 규칙은 '작게!'다.
- 함수를 만드는 둘째 규칙은 '더 작게!'이다.
- 블록과 들여쓰기 (if/else/whil 문 등에 들어가는 블록은 한 줄이어야 한다는 의미)

 1) 바깥에서 감싸고 있는 함수가 작아진다. (블록 안에서 호출하는 함수 이름을 적절히 짓는다면 코드를 이해하기가 쉬워진다.)

 2) 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안된다. 그래야 함수는 읽고 이해하기 쉬워진다.

 

한 가지만 해라!

함수는 한가지를 해야한다. 그 한가지를 잘 해야 한다. 그 한가지만을 해야한다.

- 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.

- 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다.

 

함수 당 추상화 수준은 하나로!

함수가 확실히 '한 가지' 작업만 하려면, 함수 내 모든 문장의 추상화 수준이 동일해야 한다.

한 함수 내에 추상화 수준을 섞으면, 코드를 읽는 사람이 헷갈린다.

- 위에서 아래로 코드 읽기 : 내려가기 규칙

 => 코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.

 

switch문

case 분기가 처리 때문에 switch문도 길며, 단일 블록이나 함수를 선호 한다. (switch문의 case 조건이 늘어날 떄마다, SPR, OCP 위반)

switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법은 있다. => 다형성 이용

- 추상 팩토리(Abstract Factory)를 이용하여 인터페이스를 거쳐 호출한다.

public abstract class Employee {
	public abstract boolean isPayday();
	public abstract Money calculatePay();
	public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; 
}
-----------------
public class EmployeeFactoryImpl implements EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
		switch (r.type) {
			case COMMISSIONED:
				return new CommissionedEmployee(r) ;
			case HOURLY:
				return new HourlyEmployee(r);
			case SALARIED:
				return new SalariedEmploye(r);
			default:
				throw new InvalidEmployeeType(r.type);
		} 
	}
}

 

서술적인 이름을 사용하라!

- 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다.

- 이름은 길어도 괜찮다. 길고 서술적이 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 서술적인 주석보다 좋다.

 

함수 인수

- 함수에서 이상적인 인수 개수는 0개(무항)다. 3개 이상 부터는 피하는 편이 좋다.

- 플래그 인수

: 플래그 인수는 추하다. 쓰지마라. bool 값을 넘기는 것 자체가 그 함수는 한꺼번에 여러가지 일을 처리한다고 공표하는 것과 마찬가지다.

- 이항 함수

: 단항 함수보다 이해하기가 어렵다. Point 클래스의 경우에는 이항 함수가 적절하다. 2개의 인수간의 자연적인 순서가 있어야함 Point p = new Point(x,y); 무조건 나쁜 것은 아니지만, 인수가 2개이니 만큼 이해가 어렵고 위험이 따르므로 가능하면 단항으로 바꾸도록 애써야 한다.

- 삼항 함수

: 이항 함수보다 이해하기가 훨씬 어려우므로, 위험도 2배 이상 늘어난다. 삼항 함수를 만들 때는 신중히 고려하라 권고한다.

- 인수 객체

: 인수가 많이 필요할 경우, 일부 인수를 독자적인 클래스 변수로 선언할 가능성을 살펴보자 x,y를 인자로 넘기는 것보다 Point를 넘기는 것이 더 낫다.

- 인수 목록

: 때로는 String.format같은 함수들처럼 인수 개수가 가변적인 함수도 필요하다. String.format의 인수는 List형 인수이기 때문에 이항함수라고 할 수 있다.

 

부수 효과를 일으키지 마라!

: 부수효과는 거짓말이다. 함수에서 한가지를 하겠다고 약속하고는 남몰래 다른 짓을 하는 것이므로, 한 함수에서는 딱 한가지만 수행할 것!

 

명령과 조회를 분리하라!

함수는 뭔가를 수행하거나 뭔가에 답하거니 둘 중 하나만 해야 한다. 

public boolean set(String attribute, String value);

 

같은 경우에는 속성 값 설정 성공 시 true를 반환하므로 괴상한 코드가 작성된다.

 

오류 코드보다 예외를 사용하라!

: try/catch를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해 진다.

// 오류 처리도 한 가지 작업이다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
if (deletePage(page) == E_OK) {
	if (registry.deleteReference(page.name) == E_OK) {
		if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
			logger.log("page deleted");
		} else {
			logger.log("configKey not deleted");
		}
	} else {
		logger.log("deleteReference from registry failed"); 
	} 
} else {
	logger.log("delete failed"); return E_ERROR;
}

// 정상 작동과 오류 처리 동작을 뒤섞는 추한 구조이므로 if/else와 마찬가지로 블록을 별도 함수로 뽑아내는 편이 좋다.
public void delete(Page page) {
	try {
		deletePageAndAllReferences(page);
  	} catch (Exception e) {
  		logError(e);
  	}
}

private void deletePageAndAllReferences(Page page) throws Exception { 
	deletePage(page);
	registry.deleteReference(page.name); 
	configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) { 
	logger.log(e.getMessage());
}

 

반복하지 마라!

중복 제거하기

 

구조적 프로그래밍

- 함수는 return 문이 하나여야 한다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로 안된다.

- 함수가 클 경우에만 상당 이익을 제공하므로, 함수를 작게 만든다면 오히려 여러차례 사용하는 것이 함수의 의도를 표현하기 쉬워진다.
그런데 구조적 프로그래밍의 목표와 규율은 공감하지만 함수가 작다면 위 규칙은 별 이익을 제공하지 못한다. 함수가 아주 클 때만 상당한 이익을 제공한다. 그러므로 함수를 작게 만든다면 간혹 return, break, continue를 사용해도 괜찮다. 오히려 때로는 단일 입/출구 규칙보다 의도를 표현하기 쉬워진다.

반응형
Comments