- 어떤 프로그램이든 지금 가장 기본적인 단위는 함수
- 이 장에서는 함수를 잘 만드는 법을 소개한다
- 읽기 쉽고 이해하기 쉬운 함수는?
작게 만들어라
- 함수 만들기에서 가장 중요한 규칙
- 각 함수는 명백하고 하나의 이야기만을 표현한다
블록과 들여쓰기
- if / else / while 문 등에 들어가는 블록은 한 줄이어야 한다
대게 거기서 함수를 호출한다 바깥을 감싸는 함수(enclosing function)가 작아질 뿐 아니라,
블록 안에서 호출하는 함수 이름이 적절하다면 코드를 이해하기 쉬워진다
한 가지만 해라
함수는 한 가지를 해야한다. 그 한 가지를 잘 해야한다. 그 한 가지만을 해야한다.
- 함수 당 추상화 수준은 하나로!
- 함수 내 모든 문장의 추상화 수준이 동일해야한다
추상화 수준이 다르면 읽는 사람이 헷갈린다. 특정 표현이 근본 개념인지 세부사항인지 구분하기 어렵다.
문제는 뒤섞이기 시작하면, 깨진 창문처럼 사람들이 함수에 세부사항을 점점 더 추가한다는 점이다.
내려가기 규칙
- 위에서 아래로 코드 읽기
- 코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다.
Switch 문
- 스위치문은 작게 만들기 어렵다.
- 추상 팩토리(Abstract factory)에 수기는 방법으로 아무에게도 안보여줄 수 있다
상속관계로 숨긴 후 다른 코드에 노출하지 않는다. 불가피하면 어쩔 수 없고~
서술적인 이름을 사용하라
- 함수가 하는 일을 좀 더 잘 표현하면 훨씬 좋은 이름이다.
- 예 ) testableHtml 에서 SetupTeardownIncluder.render 으로 변경
- 이름이 길어져도 겁먹을 필요 없다. 짧고 어려운 이름보다 길고 서술적인 이름이 좋다.
- 이름을 붙일 때는 일관성이 있어야한다. 모듈 내에 함수 이름은 같은 문구, 명사, 동사를 사용한다.
함수 인수
- 이상적인 인수 개수는 0개 (무항), 그 다음은 1개, 그 다음은 2개
3개는 가능하면 피하기. 4개 이상은 특별한 이유가 있어도 금지
- 함수 이름과 인수 사이의 추상화 수준이 다르고, 코드를 읽는 사람이 현 시점에서 별로 중요하지 않은 세부사항을 알아야한다
- 테스트 관점에서도 인수가 많으면 고려할 것이 많다. 부담스러워진다.
많이 쓰는 단항 형식
-
인수에 질문을 던지는 경우
-
인수를 뭔가로 변환해 결과를 반환하는 경우
이 두 경우는 독자가 당연하게 받아들인다. 이름을 지을 때는 두 경우를 분명히 구분하고 일관적인 방식으로 사용하면 된다.
-
이벤트 : 아주 유용한 단항 함수 형식
- 이벤트는 입력 인수만 있고 출력 인수는 없다. 프로그램은 함수 호출을 이벤트로 해석해
입력 인수로 시스템 상태를 바꾼다.
- 이벤트라는 사실이 코드에 명확하게 나오도록 조심히 사용해야한다.
위 3가지 경우가 아니라면 단항함수는 가급적 피한다.
플래그 인수
- 추하다고 한다...
- 함수로 부울 값을 넘기는 관례는 끔찍하다고 한다...
- 분류하고 실행하는 것까지 2가지 일을 하게 된다. 피하자
이항 함수
- 인수가 2개. 단항보다 당연히 코드를 이해하기 어렵다.
- 불가피하게 이항 함수를 만들어야하는 경우가 있지만 될 수 있으면 단항으로 바꾸도록 애써야한다.
삼항 함수
- 이항 함수보다 2배 읽기 어렵다.
- 인수가 2 ~ 3개가 되면 일부를 독자적인 클래스 변수로 나눠야하는지 고려해야한다
인수 목록
String.format()
처럼 인수 개수가 가변적인 함수도 필요할 수 있다.
- 가변인수를 전부 동등하게 취급하면 List형 인수 하나로 취급할 수 있다.
이 논리로 생각하면 format 함수는 사실상 이항 함수다.
public String format(String format, Object... args)
동사와 키워드
- 함수의 의도나 인수의 순서, 의도를 표현하려면, 단항함수는 함수와 인수가 동사/명사 쌍을 이뤄야한다.
- 예)
write(name)
은 읽으면 바로 이해가 된다. name 변수를 write 하는 함수구나! 같은 느낌
부수효과를 일으키지 마라
- 함수가 한 가지 일만하겠다고 해놓고 남몰래 다른 짓을 하면 안된다.
- 많은 경우 시간적 결합(temporal coupling)이나 순서 종속성(order dependency)를 초래한다.
출력 인수
명령과 조회를 분리하라
- 함수는 뭔가를 수행하거나 뭔가를 답하거나 둘 중 하나만 해야한다.
- 객체의 상태를 변경하거나 정보를 반환하거나 하나만 해야한다.
오류 코드보다 예외를 사용하라
- 명령 함수에서 오류 코드를 반환하는 방식은 명령 / 조회 분리 규칙에 위반된다.
if(오류 코드를 확인하는 함수)
를 사용하는 것 보다 try/catch 블록 뽑아내기
를 활용한다.
try / catch 블록 뽑아내기
- try / catch 블록은 추하게 생겼다. 코드 구조에 혼란을 준다. 정상과 오류 동작을 뒤섞는다.
- 블록을 별도의 함수로 뽑아낸다.
오류 처리도 한 가지 작업이다
반복하지 마라
- 다른 코드와 섞이면서 모양새가 조금씩 달라진 탓에 중복이 드러나보이지 않을 수 있다.
그래도 중복은 중복이다. 그리고 중복은 문제다. 코드의 길이가 늘어나고 알고리즘이 변하면 중복된 부분을 다 변경해야한다.
중복은 소프트웨어에서 모든 악의 근원이다.
- 객체지향, 구조적 프로그래밍, AOP(Aspect Oriented Programming), COP(Component Oriented Programming) 모두 중복 제거 전략이 되기도 한다.
구조적 프로그래밍
- 에츠허르 데이크스트라(Edsger Djikstra) (다익스트라?) 의 구조적 프로그래밍 원칙을 따를 수도 있다.
- 모든 함수와 함수 내 모든 블록에 입구(entry)와 출구(exit) 하나만 존재해야한다. 즉, 함수에 return은 하나여야 한다.
- 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로 안된다.
저자의 구조적 프로그래밍에 대한 생각
- 함수가 작다면 위 규칙은 별 이익을 제공하지 못하고 클 때만 상당한 이익을 제공한다.
- 함수가 작다면 return, break, continue를 여러 차례 사용해도 된다.
- 단일 입/출구 규칙(single entry-exit rule)보다 의도를 표현하기 쉬울 수 있다.
- goto는 작은 함수에서 금지다.
함수를 짜는 방법
- 글짓기와 비슷하다. 초고는 서투르고 어수선하다. 원하는데로 읽힐 때까지 다듬고 고치고 정리한다.
- 서투른 코드에도 바짐없이 테스트하는 단위 테스트 케이스도 만든다.
- 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거하고, 메서드를 줄이고 순서를 바꾼다. 필요하면 전체 클래스를 쪼갠다.
- 그 와중에도 단위 테스트는 통과한다.
3장 결론
- 모든 시스템은 특정 응용 분야 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어(Domain Specific Language, DSL)로 만들어진다.
- 함수는 그 언어의 동사, 클래스는 명사다. 프로그래밍 기술은 언제나 언어 설게의 기술이다.