3장 - 함수

어떤 프로그램이든 가장 기본적인 단위가 함수다. 이 장은 함수에 어떤 속성을 부여해야 처음 읽는 사람이 프로그램 내부를 직관적으로 파악할 수 있는지, 더욱 읽기 쉽고 이해하기 쉬운지, 의도를 분명히 표현하는 함수를 구현할 수 있는 지에 대해 소개한다.

작게 만들어라

function renderPageWithSetupsAndTeardown(pageData, isSuite) {
    if (isTestPage(pageDate)) includeSetupAndTeardownPages(pageData, isSuite);
    return pageData.getHtml();
}

블록과 들여쓰기

한 가지만 해라

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

한가지란? 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행하는 것

함수 내 섹션

한 함수를 여러 섹션으로 나눌 수 있다면 여러 작업을 한다는 것이다.
한 가지 작업만 하는 함수는 자연스럽게 섹션으로 나누기 어렵다.

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

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

Switch문

switch문은 본질적으로 N가지를 처리한다. 불행히도 switch문을 완전히 피할 순 없으나, interface나 abstract factory 등 상속 관계로 숨기고 다른 코드에 노출하지 않는다.

function calculatePay(employee) {
    switch (employee.type) {
        case COMMISSIONED:
            return calculateCommissionedPay(employee);
        case COMMISSIONED:
            return calculateHourlyPay(employee);
        case COMMISSIONED:
            return calculateSalariedPay(employee);
        default:
            throw new InvalidEmployeeType(employee.type);
    }
}

하지만 불가피하게 switch를 사용할 수 밖에 없는 상황도 생기므로 상황에 따라 사용한다.

서술적인 이름을 사용하라

함수 인수

많이 쓰는 단항 형식

플래그(flag) 인수

이항 함수

삼항 함수

인수 객체

인수 목록

동사와 키워드

부수 효과를 일으키지 마라

function userValidator() {
    let cryptographer;

    function checkPassword(userName, password) {
        const user = UserGateWay.findByName(userName);
        if (user !== User.NULL) {
            const codePhrase = user.getPhraseEncodedByPassword();
            const phrase = cryptographer.decrypt(codePhrase, password);
            if ("Valid Password" === phrase) {
                Session.initialize();
                return true;
            }
        }
        return false;
    }
}

위의 코드에서 부수효과는 Session.initialize()이다. 이름만 봐서는 함수 내부에서 세션을 초기화한다는 것을 알 수 없으므로, 이름만 보고 함수를 호출하면 기존의 세션 정보를 지워버릴 위험에 처한다.

이런 부수 효과가 시간적 결합을 초래한다. 즉, checkPassword는 세션을 초기화해도 괜찮은 경우에만 호출이 가능하다. 만약 시간적 결합이 필요하다면 함수 checkPasswordAndInitializeSession으로 이름에 분명이 명시한다.

출력 인수

명령과 조회를 분리하라

if (set('username', 'unclebob'))

개발자는 set을 동사로 의도했지만 if문에 넣고 보면 형용사로 느껴진다. 해결책은 명령과 조회를 분리해 혼란을 애초에 뿌리뽑는 것이다.

if (attributeExists("username")) {
    setAttribute("username", "unclebob");
}

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

오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.

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

위의 코드에서 정상 동작과 오류 처리 동작을 분리하면 코드를 이해하고 수정하기 쉬워진다.

function deletePage(page) {
    try {
        deletePageAndAllReferences(page);
    } catch (e) {
        logError(e);
    }
}
function deletePageAndAllReferences(page) {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKye());
}
function logError(e) {
    logger.log(e.getMessage());
}

오류 처리도 한 가지 작업이다

enum Error {
    OK,
    INVALID,
    NO_SUCH,
    LOCKED,
    OUT_OF_RESOURCES,
    WAITING_FOR_DEVENT
}

반복하지 마라

구조적 프로그래밍

함수를 어떻게 짜죠?

결론

대게 프로그래머는 시스템을 (구현할) 프로그램이 아니라 (풀어갈) 이야기로 여긴다. 프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 좀 더 표현력이 강한 언어를 만들어 이야기를 풀어간다. 시스템에서 발생하는 모든 동작을 설명하는 함수 계층이 바로 그 언어에 속한다.