작게 만들어라
첫째 규칙은 ‘작게’ 다. 둘째 규칙은 ‘더 작게’ 다.
규칙의 근거를 대기는 곤란하지만, 함수가 작을 수록 더 좋다.
if/else, while 등에 들어가는 블록은 한 줄이어야 한다. → 바깥 함수는 작아지고, 블록 안에서 호출하는 함수 이름만 적절히 짓는다면 코드를 이해하기 쉬워진다.
즉, 중첩 구조가 생길만큼 함수가 커져서는 안된다.
들여쓰기가 1단이나 2단을 넘어서면 안된다.
한가지만 해라!
함수는 한 가지를 해야한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 하는 것이다.
단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다,
함수 당 추상화 수준은 하나로!
함수가 확실히 ‘한 가지’ 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
위에서 아래로 코드 읽기 (내려가기) - 코드는 위에서 아래로 이야기 처럼 읽혀야 좋다. 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
TO 설정 페이지와 해제 페이지를 포함하려면,
설정 페이지를 포함하고, 테스트 페이지 내용을 포함하고, 헤제 페이지를 포함한다.
TO 설정 페이지를 포함하면,
슈트이면 슈트 설정 페이지를 포함한 후 일반 설정 페이지를 포함한다.
TO 슈트 설정 페이지를 포함하려면,
부모 계층에서 "SuiteSetUp" 페이지를 찾아 include 문과 페이지 경로를 추가한다.
TO 부모 계층을 검색하려면, ....
Switch 문
switch 문을 작게 만들기는 어렵다.
또, 한가지만 하는 switch 문을 만들기 어렵다 → 본질적으로 N가지를 처리하기 때문
불행하게도 switch 문을 완전히 피할 방법은 없다, 하지만 각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법이 있다. 물론 다형성(polymorphism)을 이용한다.
서술적인 이름을 사용하라!
함수가 하는 일을 조금 더 잘 표현하면 훨씬 좋은 이름이다. (testableHtml → SetupTeardownIncluder.render)
“코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 된다.”
이름이 길어도 괜찮다. 겁머긍ㄹ 필요 없다,. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
함수 이름을 정할 때 여러 단어가 쉽게 익히는 명명법을 사용한다.
서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다.
함수 인수
가장 이상적인 인수개수는 0개.
다음은 1개, 다음은 2개, 3개는 가능한 피하는 편이 좋다.
4개 이상은 특별한 이유가 필요하다. (특별한 이유가 있어도 사용하면 안 된다..)
인수는 어렵고, 인수는 개념을 이해하기 어렵게 만든다.
코드를 읽는 사람이 현 시점에서 중요하지 않은 세부사항을 알아야 한다.
테스트 관점에서 보면 인수는 더 여럽다. 갖가지 인수 조합으로 함수를 검증하는 테스트 케이스를 작성한다고 상상해보면 더 어렵다.
출력 인수는 입력 인수 보다 더 이해하기 더 어렵다.
SetupTeardownIncluder.render(pageData) → pageData 객체 내용을 렌더링 하겠다고 읽힌다.
많이 쓰이는 단항 형식
함수에 인수 1개를 넘기는 이유로 가장 흔한 경우는 두 가지이다.
- 인수에 질문을 던지는 경우 → booelan fileExists(”MyFile”)
- 인수를 뭔가로 변환해 결과를 반환하는 경우 -< InputStream fileOpen(”MyFile”)
다소 드물게 사용하지만 그래도 아주 유용한 단항 함수 형식이 이벤트다. 이벤트 함수는 입력 인수만 있다. 출력 인수는 없다. → passwordAttemptFailedNtimes(int attemptes)
이런 경우가 아니면 단항 함수는 가급적 피한다. → void includeSetupPageInto(StringBuffer pageText)
void transform(StringBuffoer out) 보다는 StringBuffer transform(StringBuffer in)
플래그 인수
함수에 bool 값을 전달하는 것은 함수가 여러 역할을 한다고 말하는 것이라 추하다!
이항 함수
인수가 2개인 함수는 인수가 1개인 함수보다 이해가 어려움.
writeField(name) 보다 writeField(outputStream, name) 이 이해하기 어려움.
이항 함수가 무조건 나쁜 것은 아님.
writerFiled(outputStream, name) 에서 outputStream 을 클래스 구성원으로 만들어서 outputStream.writeField(name) 으로 호출을 하거나, outputStream 을 클래스 구성 변수로 만들어서 인수를 넘기지 않거나.. 등등 의 방법으로 줄이는 것이 좋다.
삼항 함수
인수가 3개인 함수는 인수가 2개인 함수보다 이해가 어려움. 그래서 신중해야 함.
Circle makeCircle(double x, double y, double radius) 가 있을 때 Circle makeCircle(Point center, double radius) 로 바꾸면 보기 좋다. 단순히 x, y 를 묶은 것 외에도 묶으면서 이름을 남기기 때문에 개념을 표현하게 되기 때문.
인수 목록
String.format 처럼 인수 개수가 가변적인 함수도 필요.
public String format(String format, Object… args) 형태로 로 구현이 되기 때문에 이항 함수 이다.
동사와 키워드
함수의 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함수 이름은 필수.
단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다.
write(name) 은 누구나 바로 이해 한다.그러다 조금 더 나은 이름은 writeField(name)
함수 이름에 인수 이름을 넣는 것도 좋다 assertEqual 보다 assertExpectEqualsActural(expected, actual) 이 좋다. 이러면 인수의 순서를 기억할 필요는 없다.
부수 효과를 일으키지 마라!
부수 효과는 거짓말이다.
함수에 한가지만 하겠다고 약속하고 남몰래 다른짓을 하는 것이다.
때로는 함수로 넘어온 인수나 시스템 전역 변수를 수정한다.
출력 인수
일반적으로 인수는 함수 입력으로 해석한다.
public void appendFooter(StringBuffer report) 라는 함수가 있을 때 인수를 빼고 StringBuffer 는 클래스 인스턴스 변수로 만들어서 report.appendFooter() 같은 형태로 개선이 필요함.
명령과 조회를 분리하라!
함수는 뭔가를 수행하거나 뭔가에 답허가나 둘 중 하남나 해야 한다. 둘다 하면 안된다.
객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나만.. 둘다 하면 혼란을 초래한다.
오류 코드보다 예외를 사용하라!
오류 코드를 사용하기보다는 exception 을 raise 시켜라.
Try/Catch 블록을 뽑아내기
try/catch 는 추하다. 정상 동작과 오류 처리 동작을 뒤 섞는다.
try/catch 블록을 별도 함수로 뽑아내는 것이 좋다.
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logger.log(e.getMessage());
}
보다는...
publiv void delete(Page page) {
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
오류 처리도 한 가지 작업이다
함수는 한 가지 작업만 해야한다.
반복하지 마라!
그냥 반복하지 마라. 중복을 없애라..
구조적 프로그래밍
데이크르스트라는 모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 함.
즉, 함수는 return 문이 하나여야 한다.
루프 안에서 break, contiune 를 사용하면 안되고 goto는 당연히 절대 안된다.
함수를 어떻게 짜죠?
소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다.
논문이나 기사를 작성할 때는 먼저 생각을 기록한 후 읽기 좋게 다듬는다. 초안은 서투르고 어수선하고, 원하는 대로 읽힐 때까지 말을 다듬고 문장을 고치고 문단을 정리한다.
함수를 짤때도 마찬가지다. 처음에는 길고 복잡하고 들여쓰기 단계도 많고 중복된 루프도 많다.
인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만들고 코드를 다듬고, 함수를 만들고 이름을 바꾸고 중복을 제거한다. 때로는 이름을 바꾸기도 하고 전체 클래스를 쪼개기도 하고 메소드를 바꾸기도 한다.
이 와중에도 코드는 항상 단위 테스트를 통과한다.
결론
모든 시스템은 특정 응용 분야 시스템을 기술할 목적으로 프로그래머가 설계한 도메인 특화 언어로 만들어진다.
함수는 그 언어에서 동사며, 클래스는 명사다.
높은 레벨의 프로그래머는 시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여긴다. 프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 좀 더 표현력이 강한 언어를 만들어 이야기를 풀어간다.
댓글 없음:
댓글 쓰기