넘치게 채우기

3장. 함수 본문

개발/Clean Code

3장. 함수

riveroverflow 2023. 7. 30. 21:19
728x90
반응형

작게 만들어라

함수를 만드는 첫 번째 규칙은 ‘작게!’ 입니다.

함수를 만드는 둘째 구칙은 ‘더 작게!’ 입니다.

 

함수에서의 들여쓰기 수준은 1단이나 2단을 넘어서면 읽기 매우 힘들어집니다.

한 함수에 많은 중첩 구조가 들어가지 않도록 해야합니다.

 

 

한 가지만 해라

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

 

우리가 함수를 만드는 이유는 큰 개념을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위함입니다.

 

 

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

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

한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈립니다. 특정 표현이 근본 개념인지 세부사항인지 구분하기가 어려워집니다.

 

 

내려가기 규칙

코드는 위에서 아래로 이야기처럼 읽히는 게 좋습니다.

즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아집니다.

일련의 TO 문단을 읽듯히 프로그램이 읽혀야 합니다.

아래의 예시처럼 말이죠.

 

TO 설정 페이지와 해제 페이지를 포함하려면, 설정페이지를 포함하고, 테스트 페이지 내용을 포함하고, 해제 페이지를 포함한다.

TO 설정 페이지를 포함하려면, 슈트이면 슈트 설정 페이지를 포함한 후 일반 설정 페이지를 포함한다.

TO 슈트 설정 페이지를 포함하려면, 부모 계층에서…..

 

추상화 수준이 하나임 함수를 구현하기란 쉽지 않습니다. 핵심은 짧으면서도 ‘한 가지’만 하는 함수입니다.

문단을 읽어내려 가듯이 코드를 구현하면 일관된 추상화가 가능할 것 입니다.

 

 

Switch문

switch문은 작게 만들기 어렵습니다. case가 N가지를 처리해야 하고, 코드가 길어지기 쉽습니다.

 

public Money calculatePay(Employee e)
throws InvalidEmployeeType{
	switch (e.type) {
		case COMMISSIONED:
			return calculateCommissionedPay(e);
		case HOURLY:
			return calculateHourlyPay(e);
		case SALARIED:
			return calcultaeSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);
	}
}

이 switch문을 추상 책토리에 꽁꽁 숨기면 더욱 깨끗해보일 것 입니다.

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

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 SalariedEmployee(r);
			default:
				throw new InvalidEmployeeType(r.type);
		}
	}
}

 

서술적인 이름을 사용하라

이름이 길어도 괜찮습니다. 길고 서술적인 이름이 짧고 난해한 이름보다 훌륭합니다.

 

코드를 읽으면서 짐작했던 기능을 각 루틴이 수행한다면 깨끗한 코드입니다. 이름을 붙이는 것으로 벌써 절반이 완성된 것이나 다름없죠.

 

개발자 입장에서도 머릿속에 구현이 빠르게 될 것으로, 매우 효율적이라 할 수 있습니다.

 

 

함수 인수

인수가 많을수록 사용하기 불편하고, 까다로워집니다.

인수는 이해하기 어렵고, 테스트하기 상당히 부담스럽습니다.

 

인수가 많아진다면 일부를 독자적인 클래스로 선언하는것도 좋은 방법입니다.

 

Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);

위 함수들을 보면, 중앙점을 클래스로 만들어서 눈속임한 것 뿐인데도 코드가 보기 편해졌음을 알 수 있습니다.

 

 

부수 효과를 일으키지 마라

함수에서 한 가지를 하겠다고 약속하고선 다른 짓을 하면 혼란을 불어일으킵니다.

함수의 이름과 함수가 취하는 기능은 일치해야 합니다. 다른 기능이 1이라도 포함되어 있으면 안됩니다.

 

 

명령과 조회를 분리하라

함수는 뭔가를 수행하거나 뭔가를 답하거나 둘 중 하나만 해야합니다. 둘 다 하면 혼란을 초래합니다.

public boolean set(String attribute, String value);

이 함수는 attribute인 속성을 찾아 value로 값을 바꾸고 성공하면 true, 실패하면 false를 반환하는 함수라고 해봅시다.

 

if(set("username", "unclebob"))...

읽는 사람 입장에서는 username이 unclebob으로 설정되어 있는건지, username을 unclebob으로 쓰라는건지 알 수가 없습니다.

 

 

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

예외 처리를 통해서 if문으로 인해 더러워지는 코드를 막을 수 있습니다.

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;
}

를 예외 처리 코드로 바꾸면

try{
	deletePage(page);
	registry.deleteReference(page.name);
	configKeys.deleteKey(page.name.nameKey());
}
catch (Exception e){
	logger.log(e.getMessage());
}

으로 오류 처리 코드가 분리되어 훨씬 깔끔해집니다.

try-catch문은 원래 더럽기 때문에, 별도 함수로 뽑아내는 것이 좋습니다.

 

 

 

클래스는 코드의 명사, 함수는 동사입니다. 프로그래밍 기술은 언어 설계의 기술입니다.

프로그래밍 언어라는 수단을 사용해서 우리는 이야기를 깔끔하게 풀어나가야 할 줄 알아야합니다.

 

 

728x90
반응형

'개발 > Clean Code' 카테고리의 다른 글

6장. 객체와 자료 구조  (0) 2023.08.04
5장. 형식 맞추기  (0) 2023.08.03
4장. 주석  (0) 2023.08.01
Clean Code 2장. 의미 있는 이름  (0) 2023.07.25
Clean Code 1장. 깨끗한 코드  (0) 2023.07.23