< 작게 만들자 > 

함수를 만들 때 가장 중요한 규칙은 크기다. “작게 만들어야 한다”는 것이다. 더 작다면 더 좋다. 요리 초보자가, 특별한 재료를 사용하는 50단계 요리보다, 간단한 재료로 만드는 5단계 요리를 만들어 보고 싶을 것이다. 마찬가지로, 함수도 단계가 적을수록 이해하기 쉽다.

public static String prepareDeliciousDish(Ingredients ingredients, boolean isVegan) {
    if (isGoodCombination(ingredients)) {
        mixIngredients(ingredients, isVegan);
    }
    return serveDish(ingredients);
}

< 간격을 일정하게 유지하자 >

요리할 때도 조리도구와 재료를 가까운 곳에 두는 것이 편리하듯이 코드도 마찬가지다. 만약 함수 안에 if, else 또는 while 같은 구문이 있다면, 그 안에 넣을 내용은 간단하게 한 줄 정도로 만들어보자. 그 한 줄에는 다른 함수를 호출하는 내용이 들어갈 수 있다. 이렇게 하면 더 읽기 쉬운 코드가 되고, 동시에 더 가까운 간격으로 구성된다.

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

함수를 만들 때, 내부의 내용은 동일한 수준의 ‘추상화’를 가져야 한다. 한 그림에는 한 스타일로 그리는 것과 같다. 높은 수준의 개념과 세부적인 요소를 함께 섞으면, 누군가 그 그림을 볼 때 혼란스러울 것이다. 함수도 마찬가지로, 동일한 수준의 추상화를 유지하면 더 이해하기 쉽다.

< 위에서 아래로 읽혀야 한다 >

코드를 읽는 것은 마치 이야기를 읽는 것과 같아야 한다. 이야기를 읽을 때는 위에서 아래로 읽으며, 내용이 점점 풀어진다. 마찬가지로 코드도 그렇게 작성되면 좋다. 함수의 추상화 수준이 한 단계씩 점점 낮아지는 방향으로 코드를 작성하면 더 이해하기 쉽다.

쉽게 말해서, 먼저 개요를 읽고, 그다음에 세부사항을 읽는 방식이다. 이를 '내려가기 규칙'이라고 부른다.

< 스위치문 > 

스위치문은 여러 가지 경우의 수를 한 번에 처리할 수 있어 매우 유용하나, 잘못 사용하면 너무 복잡해질 수 있다. 예를 들어, 회사에서 직원들의 급여를 계산하는데 스위치문을 사용한다고 생각해 보자.

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


이 코드는 직원의 유형에 따라 다른 급여 계산 방식을 적용하는데, 새로운 직원 유형이 추가될 때마다 이 스위치문도 업데이트해야 하는 단점이 있다.  이 Switch문을 사용하면 복잡도가 급격히 늘어나는 문제가 있다.

[다형성]

이 문제를 해결할 수 있는 해결책이 있다. 그것은 바로 '다형성'을 이용하는 것이다. 다형성을 사용하면 하나의 코드로 다양한 경우를 처리할 수 있다.

"다형성"을 이용하면, 각 직원 유형마다 다른 계산 방식을 적용하는 클래스를 만들 수 있다. 그리고 이 클래스들을 'Employee'라는 공통 인터페이스를 통해 관리할 수 있다.

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


이렇게 하면, 새로운 직원 유형이 추가되어도 스위치문을 업데이트할 필요가 없게 된다. 단지 새로운 클래스를 추가하기만 하면 된다. 

 

여기서 다형성이란? ↘

더보기

다형성(polymorphism)은 그리스어에서 유래한 말로, '많은(many)'와 '형태(shape)'의 합성어로서 '많은 형태를 가짐'을 의미한다. 컴퓨터 과학에서의 다형성은 한 가지의 인터페이스를 가지고 다양한 동작을 수행할 수 있는 능력을 뜻한다. 다시 말해, 한 가지 이름 아래에서 여러 가지 동작을 하는 것이다.

예를 들어, 동물원에 간다고 상상해 보자. 거기에는 사자, 원숭이, 펭귄 등 다양한 동물들이 있다. 이 동물들 모두 '동물'이라는 공통의 이름 아래에서 그들 각각의 동작(울음소리, 움직임 등)을 한다. 다형성이라는 마법은 바로 이것과 같다. 

[다형성을 이용한 코드]
앞에서 살펴본 직원 급여 계산 문제로 보자면. 여기서 각 직원 유형(COMMISSIONED, HOURLY, SALARIED)마다 다른 계산 방식을 적용해야 했다.

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


먼저 'Employee'라는 공통 인터페이스를 만들고, 이 인터페이스에는 모든 직원이 가질 수 있는 메서드들을 정의한다. 이렇게 되면 각 직원 유형의 클래스는 이 'Employee' 인터페이스를 통해 서로 다른 급여 계산 방식을 구현할 수 있게 된다.

따라서 새로운 직원 유형이 추가되더라도 기존의 스위치문을 수정할 필요가 없게 된다. 단지 새로운 직원 유형 클래스를 만들어 'Employee' 인터페이스를 구현하기만 하면 되는 것이다.

이렇게 '다형성'이라는 것을 이용하면, 각 직원 유형마다 다른 계산 방식을 적용하는 동시에 코드의 복잡성을 크게 줄일 수 있다. 

< 숨겨야 가장 효과적 >

효과적으로 사용하려면, 스위치문을 숨겨야 한다. 이를 '추상 팩토리'라는 기법을 사용해서 가능하게 할 수 있다.

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


이렇게 스위치문을 팩토리 클래스 안에 숨겨두면, 다른 부분의 코드에서는 이 스위치문을 신경 쓸 필요가 없어진다. 

 

출처 : 출판사 인사이트북 / 클린 코드 / https://product.kyobobook.co.kr/detail/S000001032980

※저의 블로그에 있는 모든 참고 서적, 강의 내용은 출판사, 저자(혹은 편집자)에게 허락을 직접 맡고, 게시하는 글입니다.

 

Clean Code(클린 코드) | 로버트 C. 마틴 - 교보문고

Clean Code(클린 코드) | 프로그래머, 소프트웨어 공학도, 프로젝트 관리자, 팀 리더, 시스템 분석가에게 추천하는더 나은 코드를 만드는 책『Clean Code(클린 코드)』은 오브젝트 멘토(Object Mentor)의 동

product.kyobobook.co.kr

 

+ Recent posts