개발
home
🎻

[클린코드] 3장 함수 (1/2)

Created
2022/06/13
Tags
클린코드
CleanCode
2022-06-13 @이영훈
프로그래밍 초창기에는 시스템을 루틴과 하위 루틴으로 나눴다. 지금은 함수만 살아남았다. 어떤 프로그램이든 가장 기본적인 단위가 함수다.
이 장은 함수를 잘 만드는 법을 소개한다.
작게 만들어라!
함수를 만드는 첫째 규칙은 작게다. 함수를 만드는 두번째 규칙은 더 작게다. 지금까지 경험을 바탕으로 오랜 시행착오를 바탕으로 나는 작은 함수가 좋다고 확신한다.
회사 Sparkle은 코든 함수가 2줄, 3줄, 4줄 정도였다.
일반적으로 10줄 이하여야 한다 (짧을수록 더 좋다)
블록과 들여쓰기
if/else/while문 등에 들어가는 블록은 한 줄이어야 한다라는 의미다. 대게 거기서 함수를 호출한다. 그러면 바깥을 감싸는 함수가 작아질 뿐만 아니라 블록 안에서 호출하는 함수 이름을 적절히 짓는다면 코드를 이해하기도 쉬워진다.
이 말은 중첩 구조가 생길만큼 함수가 커져서는 안 된다라는 뜻이다. 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
한 가지만 해라!
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
public sttaic String renderPageWithSetupsAdnTeardowns( PageData pageData, boolean isSuite) throws Exception { if (isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml(); }
Java
복사
이 코드는 세 가지를 한다고 주장할 수도 있다.
1.
페이지가 테스트 페이지인지 판단한다.
2.
그렇다면 설정 페이지와 해제 페이지를 넣는다.
3.
페이지를 HTML로 렌더링한다.
지정된 함수 이름 아래에서 추상화 수준이 하나다.
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다. 함수를 만드는 이유는 큰 개념을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서이다.
함수 당 추상화 수준은 하나로!
함수가 확실히 ‘한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야한다.
한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다. 특정 표현이 근본 개념인지 아니면 세부 사항인지 구분하기 어렵기 때문이다. 근본 개념과 세부사항을 뒤섞기 시작하면 깨어진 창문처럼 사람들이 함수에 세부사항을 점점 더 추가한다.
내려가기 규칙: 위에서 아래로 코드 읽기
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮벼은 함수가 온다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다. 나는 이것을 내려가기 규칙이라 부른다.
하지만 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다. 핵심은 짧으면서도 ‘한 가지’만 하는 함수다. 위에서 아래로 TO(파이썬의 def) 문단을 읽어내려 가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다.
// 3-7 코드 적으면 더 좋음
Switch 문
switch(if)문은 작게 만들기 어렵다. 본질적으로 여러가지를 처리한다. switch 문을 완전히 피할 방법은 없다.
다형성을 이용한다.
public Money calculatePay(Employee e) throws InvalidEmployeeType { switch (e.type) { case COMMISIONED: return calculateCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new invalidEmployeeType(e.type); } }
Java
복사
위 함수에는 몇 가지 문제가 있다.
1.
함수가 길다. 새 직원 유형을 추가하면 더 길어진다.
2.
한 가지 작업만 수행하지 않는다
3.
SRP를 위반한다. 코드를 변경할 이유가 여럿이다.
4.
OCP를 위반한다. 새 직원 유형을 추가할 때마다 코드를 변경하기 때문이다.
다음 코드는 이 문제를 해결한다. switch 문을 추상 팩토리(abstract factory)에 꽁꽁 숨긴다. 아무에게도 보여주지 않는다. 팩토리는 switch문을 사용해 적절한 Employee 파생 클래스의 인스턴스를 생성한다. calculatePay, isPayday, deliverPay 등과 같은 함수는 Employee 인터페이스를 거쳐 호출횐다. 그러면 다형성으로 인해 실제 파생 클래스의 함수가 실행된다.
public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePate(); public abstract void deliveryPay(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 COMMISIONED: return new CommisionedEmployee(r); case HOURLY: return new HourlyEmployee(r); case SALARIED: return new SalariedEmployee(r); default: throw new invalidEmployeeType(r.type);
Java
복사
일반적으로 나는 switch 문을 단 한 번만 참아준다. 다형적 객체를 생성하는 코드 안에서다. 이렇게 상속 관계로 숨긴 후에는 절대로 다른 코드에 노출하지 않는다.