Books

[Clean Code] 3장 함수

728x90

3. 함수

어떤 프로그램이든 가장 기본적인 단위는 함수

이 장에서는 함수를 잘 만드는 법을 소개

 

[ 작게 만들어라! ]

- 함수를 만드는 첫째 규칙도 둘째 규칙도 ‘작게!’

- 저자가 이야기 해주는 한 자바 / 스윙 프로그램

    • 각 함수가 너무도 명백

    • 각 함수가 이야기 하나를 표현

    • 각 함수가 멋지게 다음 무대를 준비

 

▶︎ 블록과 들여쓰기

- if / else문, while문 등에 들어가는 블록은 한 줄이어야 한다

    • 대게 그 한 줄에서 함수를 호출한다

- 바깥을 감싸는 함수가 작아진다

- 호출하는 함수의 이름을 적절히 짓는다면, 코드를 이해하기도 쉬워진다

- 중첩 구조가 생길만큼 함수가 커져서는 안된다

 


[ 한 가지만 해라! ]

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

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

    • 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈

    • 함수 내에 여러 섹션으로 나눠진다면 여러 작업을 한다는 증거

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

 


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

- 함수가 확실히 ‘한 가지’ 작업만 하기 위해서는 함수 내 모든 문장의 추상화 수준이 동일해야 한다

- 함수 내 추상화 수준이 섞인다면?

    → 근본 개념인지 세부사항인지 구분하기 어려워 코드를 읽는 사람이 헷갈린다

- 내려가기 규칙
: 프로그램 코드는 위에서 아래로 이야기처럼 읽히며 함수 추상화 수준이 한 번에 한 단계씩 낮아진다

    • 일련의 TO 문단을 읽듯 프로그램이 읽힌다

- 💡 추상화 (Abstraction)

    • 추상화 : 핵심적 개념 / 기능을 간추려내어 이름을 붙이는 것 (함수로 만드는 것)

    • 코드로 드러나는 정보가 많을 수록 추상화 수준이 낮다고 할 수 있다 

// 추상화 수준 높음 
includeSetupAndTeardownPages(pageData, isSuite); 

// 추상화 수준 낮음 
WikiPage testpage = pageData.getWikiPage(); 
String Buffer newPageContent = new StringBuffer(); 
includeSetupPages(testPage, newPageContent, isSuite); 
... 
includeTeardownPages(testPage, newPageContent, isSuite); 
pageData.setContetn(newPageContent.toString());

 


[ Switch 문 ]

- switch 문은 작게 만들기 어렵다 🥲

- 각 switch 문을 저차원 클래스에 숨기고 절대 반복하지 않는 방법

    • 다형성 이용

    • 💡 다형성 (polymoorphism) : 하나의 객체가 여러 타입을 가질 수 있는 것

- switch 문을 사용하는 예제 코드 3-5

    • switch문을 상속 관계로 추상 팩토리에 숨긴다

    • 다형성 객체를 생성하는 코드에서 switch문 사용

    • 다형성을 이용하여 실제 파생 클래스 함수를 실행한다

 


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

- 한 가지만 하는 작은 함수에 좋은 이름을 붙인다

    • 함수가 작고 단순할 수록 서술적 이름을 고르기도 쉽다

- 이름이 길어도 좋다

    • 길고 서술적인 이름 > 길고 서술적인 주석

- 여러 단어가 쉽게 읽히는 명명법 사용 + 여러 단어를 사용해 기능을 잘 표현하는 이름 선택

- 이름을 정하느라 시간을 들여도 괜찮다

    • 여러 이름을 코드에 넣어보며 읽어보면 더 좋다

- 이름을 붙일 때에는 일관성이 있어야 한다

    • 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용

 


[ 함수 인수 ]

- 함수에서 이상적인 인수 개수는 0개 (무항)

    • 0개 > 1개 > 2개 >>>>>>> 3개 (삼항은 가능한 피하라)

- 인수는 개념을 이해하기 어렵게 만든다

    • 코드를 읽는 사람이 발견할 때마다 의미를 해석해야 한다

- 테스트 관점에서도 어렵다

    • 갖가지 인수 조합으로 테스트 케이스를 작성 🥲

- 출력 인수는 입력 인수보다 이해하기 어렵다

    • 대개 함수에서 인수로 결과를 받을 것이라 예상하지 않는다

 

▶︎ 많이 쓰는 단항 형식

- 가장 흔한 경우

    1. 인수에게 질문을 던지는 경우
        ex. boolean fileExists(”MyFile”)

    2. 인수를 뭔가로 변환해 결과를 반환하는 경우
        ex. InputStream fileOpen(”MyFile”)

- 드물지만 유용한 경우 : 이벤트 함수

    • 입력 인수만 있고 출력 인수는 없다

    • 입력 인수가 시스템 상태를 바꾼다 → 조심할 필요 있다

    • 이벤트라는 사실이 드러나도록 이름과 문맥을 주의

- 위 경우가 아니라면 단항 함수는 피한다

    • 변환 함수에서 출력 인수 사용하면 혼란 일으킨다

        ▪︎ 입력 인수를 변환한다면 변환 결과는 반환값으로 돌려준다

        ▪︎ 입력 인수를 그대로 돌려주더라도, 변환 형태는 유지하기 때문에

 

▶︎ 플래그 인수

- 플래그 인수는 추하다

    • 함수가 한꺼번에 여러 가지를 처리한다고 공표하는 셈!

 

▶︎ 이항 함수

- 이해 난이도 : 인수 2개 > 인수 1개

- 이항 함수가 적절한 경우

    • ex. 직교 좌표계 점 : Point p = new Point(0, 0)

        ▪︎ 이런 경우 인수 2개는 한 값을 표현하는 두 요소

    • ex. assertEquals(expected, actual)

        ▪︎ 인수 순서를 헷갈리는 경우 多

- 이항 함수는 위험이 따른다는 사실을 이해하고 사용

    • 가능하면 단항 함수로 바꾸도록 노력

 

▶︎ 삼항 함수

- 이해 난이도 : 인수 3개 >>> 인수 2개

    • 신중히 고려하여 사용하라

 

▶︎ 인수 객체

- 인수가 2-3개 필요하다면, 일부를 클래스 변수로 선언할 가능성 고려
    Circle makeCircle(Point center, double radius);

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

- 변수를 묶으려면 이름을 붙여야 하므로 개념을 표현하게 됨! 😊

 

▶︎ 인수 목록

- 인수 개수가 가변적인 함수가 필요한 경우가 있다

- String.format(”%s worked %.2f hours.”, name, hours);

    • 사실상 이항 함수

    • 선언부 : public String format(String format, Object… args)

- 가변 인수를 취하는 함수는 단항, 이항, 삼항 함수로 취급 가능

 

▶︎ 동사와 키워드

- 함수의 의도 / 인수의 순서와 의도를 표현하기 위해서는 좋은 함수 이름 필요

- 단항 함수는 함수-인수동사-명사 쌍을 이뤄야 한다

    • ex. writeField(name)

- 함수 이름에 키워드를 추가

    • ex. assertEqualsassertExpectedEqualsActual(expected, actual)

         : 인수 순서를 기억할 필요 ❌

 


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

- 함수에서 한 가지를 수행하겠다고 하고, 남몰래 다른 짓을…!

    • ex. 예제 코드 3-6 : checkPassword 내에서 세션 초기화

        ▪︎ 함수 이름만 보고 함수를 호출하면 세션 정보를 지워버릴 위험

        ▪︎ 시간적인 결합 - 세션을 초기화해도 괜찮은 경우에만 checkPassword 호출 가능

- 많은 경우 시간적인 결합이나 순서 종속성 초래

 


[ 출력 인수 ]

- 일반적으로 인수를 함수 입력으로 해석

- 출력 인수라는 사실을 알기 위해 함수 선언부를 찾아봐야 확실해짐

    • 인지적으로 거슬린다 → 피해야 한다

- 객체 지향 언어에서는 출력 인수를 사용할 필요가 드물다

    • 출력 인수로 사용하라고 설계한 변수 : this

    • 함수에서 상태를 변경해야 한다면, 함수가 속한 객체의 상태를 변경해라

 

 


[ 명령과 조회를 분리하라! ]

- 객체 상태를 변경하거나 / 객체 정보를 반환하거나

    • 둘 다 하면 안돼 !

- if (set(”username”, “unclebob”)) …

    • 설정됨을 확인하는 코드인지 설정하는 코드인지 의미가 모호

 


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

- if문에서 명령을 표현식으로 사용하는 경우

    • 명령 / 조회 분리 규칙을 미묘하게 위반

    • 여러 단계로 중첩되는 코드를 야기

    • 오류 코드 반환 → 호출자는 오류 코드 곧바로 처리해야 함

if (delete(page) == E_OK) { 
	if (registry.deleteReference(page.name) == E_OK) { 
    	... 
    } else { 
    	logger.log("deleteReference from registry failed"); 
    } 
} else { 
    logger.log("delete failed"); return E_ERROR; 
}

- 오류 코드 대신 예외 사용

    • 오류 처리 코드가 원래 코드에서 분리 → 깔끔

 

▶︎Try / Catch 블록 뽑아내기

- try / catch 블록은 원래 추하다

    • 코드 구조 혼란 & 정상 동작 - 오류 처리 동작을 섞는다

    • 별도 함수로 뽑아내는 편이 좋다

public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) {
        logError(e);
    }
}

정상과 오류 처리 동작 분리 → 코드의 이해와 수정에 용이

 

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

- 오류를 처리하는 함수는 오류만 처리해야 마땅하다

 

▶︎ Error.java 의존성 자석

- 클래스든 열거형 변수든 오류 코드를 정의 → 의존성 자석

    • Error enum 수정 → Error enum 사용하는 클래스 전부 다시 컴파일 & 다시 배치

- 예외를 사용

    • 새 예외는 Exception 클래스에서 파생

    • 재컴파일 / 재배치 없이도 새 예외 클래스 추가 가능

 


[ 반복하지 마라! ]

- 알고리즘 중복은 코드 길이를 늘릴 뿐 아니라 알고리즘이 변했을 때 여러 곳을 손보게 한다

    • 오류 발생 확률도 ↑

- 중복은 소프트웨어에서 모든 악의 근원일 수도

- 중복을 없애거나 제어할 목적의 많은 원칙과 기법

    • 관계형 DB의 정규형식 : 자료에서 중복 제거

    • 객체 지향 프로그래밍 : 코드를 부모 클래스로 몰아 중복 제거

    • 구조적 프로그래밍, AOP, COP …

 


[ 구조적 프로그래밍 ]

- 구조적 프로그래밍 원칙

    • 함수의 return문은 하나

    • 루프 안에서 break, continue 안돼 / goto 절대로 안돼

    • 함수가 아주 클 때만 상당한 이익 제공

- 함수를 작게 만든다면

    • return, break, continue 여러 차례 사용 OK

        ▪︎ 오히려 의도를 표현하기 쉬워진다

    • goto ❌ : 큰 함수에서만 의미 있다

 


[ 함수를 어떻게 짜죠? ]

- 처음에는 길고 복잡하다

    • 서툰 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 작성

- 원하는 대로 다듬고 정리

    • 코드를 다듬고, 함수를 만들고, 이름도 바꾸고 중복 제거

    • 메서드를 줄이고 순서도 변경

    • 전체 클래스를 쪼개기고

    • 와중에도 코드는 항상 단위 테스트를 통과

- 최종적으로 이 장에서 설명한 규칙을 따르는 함수가 얻어진다!

 


[ 결론 ]

- 시스템은 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어로 만들어진다

    • 그 언어에서 함수는 동사 / 클래스는 명사

- 프로그래밍의 기술은 언제나 언어 설계의 기술

    • 좀 더 풍부하고 좀 더 표현력 강한 언어를 만들어 이야기 풀어가도록

    • 시스템의 동작을 설명하는 함수가 바로 그 언어에 속한다

- 분명하고 정확하여 깔끔하게 맞아떨어지는 ‘함수’라는 언어로 이야기를 풀어나가자

 

 

 

 

728x90

'Books' 카테고리의 다른 글

[Clean Code] 2장 의미 있는 이름  (0) 2023.09.18
[Clean Code] 1장 깨끗한 코드  (0) 2023.09.18