////
Search
Duplicate
🖌️

3장. 함수

: 함수를 잘 만드는 방법
: 의도를 분명히 표현하는 함수를 어떻게 구현할 수 있을까?
: 함수에 어떤 속성을 부여해야 처음 읽는 사람이 프로그램 내부를 직관적으로 파악할 수 있을까?

작게 만들어라

: 함수를 잘 만들 수 있는 첫번째 방법은 작게다. 큰 함수에서 각 구체화된 부분을 추상화시켜 이름을 붙여주면 이해하기 쉬워진다.
: if/else 문, while 문에 들어가는 블록은 한 줄이어야 한다. 이 말은 중첩 구조가 생길만큼 함수가 커져서는 안된다는 것이다.
: 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서는 안 된다. 그래야 함수를 읽고 이해하기 쉬워진다.

한 가지만 해라 & 함수 당 추상화 수준은 하나로

: 지정된 함수 이름 아래에서 추상화 수준이 하나라면 한 가지 작업만 하는 좋은 함수인 것이다.
: 한 가지 작업만 하는 함수는 머리, 가슴, 배를 구분하기 힘들다. 이미 머리니까! 머리, 가슴, 배로 나누어진다면 나누어야 한다.
: 추상화 수준이란 해당 코드를 읽으면서 파악할 수 있는 정보의 수준을 의미한다. 즉 코드에 구체적으로 기술되어 있을수록 낮은 추상화 수준을 가진다.
: 추상화 수준은 상대적인 개념이기 때문에 얼마든지 깊어질수도 얕아질 수도 있다.
: 한 함수 내에서 여러 추상화 수준이 섞인다면 어떤 표현이 더 높은 추상화 수준을 가지는 지 파악하기 힘들기 때문에, 혼동이 발생한다.
내려가기 규칙
: 코드는 위에서 아래로 내려가면서 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 더 낮은 함수가 와야한다.
: 즉 위에서 아래로 코드를 읽으면 함수의 추상화 수준이 한 번에 한 단계씩 낮아진다.

Switch 문

: Switch 문이나 if/else가 여러 개 이어지는 구문은 작게 만들기 어렵다. 본질적으로 N가지를 처리하기 때문이다.
: 다만 이런 Switch 문이나 if/else 문이 반복되지 않도록 다형성을 사용해볼 수 있다.

개선 전

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.
type에 따라 다른 함수들을 호출하기 때문에 여러 작업을 수행한다. ⇒ SRP 위반
3.
새 직원 유형을 추가할 때마다 코드를 변경해야 한다. ⇒ OCP 위반
4.
위 함수와 구조가 동일한 함수가 다양한 곳에서 존재하게 된다.

개선 후

public abstract class Employee { public abstract boolean isPayday(); public abstract boolean calculatePay(); public abstract boolean deliverPay(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 CommissionedEmployee(r); case HOURLY : return new HourlyEmployee(r); case SALARIED : return new SalariedEmployee(r); default : throw new InvalidEmployeeType(r.type); } } }
Java
복사
: 추상 팩토리 패턴을 이용해 해결했다.
: Employee를 상속하는 다양한 직업(HourlyEmployee, SalariedEmployee 등)을 만들고 Employee의 추상화 함수를 구현한 다양한 직원 유형에서 각 함수들을 호출한다.

서술적인 이름을 사용하라

: 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
: 특히, 이름을 붙일 때는 일관성이 있어야한다. includeA, includeB가 있으면 includeC도 있을 수 있음을 짐작하곤 하니까!

함수 인수

: 함수에서 가장 이상적인 인수는 0개다. 인수 개수가 늘어날수록 함수를 어렵게 만들기 때문이다.
: 인수 개수가 가변적인 경우, 가변 인수들은 List형 인수 하나로 취급하자.
: 함수의 이름에서 인수의 순서와 형식 또는 정보를 표현할 수 있도록 해보자.
flag 인수
: 인수로 플래그 변수를 입력받는 것은 함수 선언부터 나는 여러 일을 하는 함수라고 광고하는 것이다..!
인수가 1개인 형식
: 함수에 인수 1개를 넘기는 이유로 가장 흔한 경우는 다음 두 개다.
인수에 질문을 던지는 경우 ⇒ ex) boolean fileExists(”MyFile”)
인수를 변환해 결과를 반환하는 경우 ⇒ ex) InputStream fileOpen(”MyFile”)
: 가급적 위의 경우 아니라면 단항 함수를 피하자
인수가 2개인 형식
: 이 경우, 순서가 헷갈릴 수 있다. 우리가 항상 쓰는 assertThat(expect, actual) 종종 expectactual을 바꿔쓰곤 한다..
: Point p = new Point(0, 0)처럼 인수 2개가 하나의 인수처럼 자연스럽게 표현되는 경우가 아니라면 불가피하지 않은 경우 사용을 자제해야 한다.
인수가 3개인 형식
: 인수가 3개인 경우, 주춤, 순서, 무시 세 개의 당혹스러운 상황에 봉착할 수 있다. 가급적이면 신중히 고려해서 인수가 3개인 함수를 만들자.
⇒ 어? 이거 매개변수 뭐더라..
⇒ 어? 이거 매개변수 순서 뭐더라..
⇒ 어? 매개변수 3개였네?
인수 객체
: 인수가 2-3개 필요하다면 객체로 대신할 수 없을지 생각해보자.
ex) Circle makeCircle(double x, double y, double radius) -> Circle makeCircle(Point center, double radius)

부수 효과를 일으키지 마라

: 함수 이름으로 명시된 한 가지 작업 외에 다른 일이 수행된다면 부수 효과가 발생하는 경우를 조심해야 한다.
: 함수가 한 가지 일을 하게 만드는게 최선이지만 그렇게 할 수 없다면 함수가 하는 모든 일을 함수 명에 분명히 명시하는 것이 차선이다.

명령과 조회를 분리하라

: 함수는 뭔가를 처리하거나 뭔가에 답하거나 둘 중 하나만 수행해야 한다. 즉 Command와 Query가 하는 일은 다르다

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

: 그냥 오류 코드로 뭔가 처리하려하지 말고 예외를 사용해서 처리하자
Try/Catch 블록 뽑아내기
// try catch 블록을 포함하는 delete 함수 public void delete(Page page) { try { deletePageAndAllReferences(page); } catch (Exception e) { logError(e); } } // 실질 적인 로직을 모아 놓은 deletePageAndAllReferences 함수 private void deletePageAndAllReferences(Page page) throws Exception { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey()); } // 로그를 보여주는 logError 함수 private void logError(Exception e) { logger.log(e.getMessage()); }
Java
복사
: 이건.. 잘 모르겠음..

반복하지 마라

: 많은 원칙과 기법들이 중복을 없애고자 만들어졌다. 중복이 발생한다면 코드 길이가 늘어날 뿐 아니라 중복 코드에 변동이 있을 경우, 많은 부분을 수정해야 한다.
: 구조적 프로그래밍, AOP, COP 모두 어떤 면에서는 중복 제거 전략이다.

구조적 프로그래밍

: 가급적 return, break, continue를 적절히만 잘 사용하자

이거 다 지키면서 함수를 어떻게 짜죠?

: 처음부터 모든 것을 지키는 것은 어렵다. 처음에는 길게 먼저 만들더라도 이후에 점차 리팩토링시켜가면서 코드를 다듬고 함수를 분리하고 이름을 바꾸고 중복을 제거하는 과정으로 배워나가면 된다.

3장을 마치며..

: 열심히 하쟈..!