함수를 만드는 규칙은 다음과 같다.
[ 작게 만들어라 ]
함수는 작을 수록 더 좋다. 이에 대한 정확한 근거나 자료를 제시하기 어렵지만 많은 프로그래머들이 개발을 하며 오래 시행착오 끝에 작은 함수가 좋다고 생각한다고 한다.
if/else문 , while문 등에 들어가는 블록은 한 줄이어야 한다. 대게 여기서 함수를 호출한다.
이 말은 중첩 구조가 생길 만큼 함수가 커져서는 안 된다는 뜻이다. 그래야 함수를 읽고 이해하기 쉬워진다.
( switch문은 기본적으로 작게 만들기가 어렵다. switch문을 완전히 피할 방법은 없다..)
[ 한 가지만 해라 ]
함수는 한 가지를 해야한다.
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 하는 것이다.
함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
[ 내려가기 규칙 : 위에서 아래로 코드 읽기 ]
코드는 위에서 아래로 이야기처럼 읽혀야 좋다. 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계씩 낮아진다.
하지만 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다.
ex)
TO 설정 페이지와 해제 페이지를 포함하려면, 설정페이지를 포함하고, 테스트 페이지 내용을 포함하고, 해제 페이지를 포함한다.
TO 설정 페이지를 포함하려면, 슈트이면 슈트 설정 페이지를 포함한 후 일반 설정 페이지를 포함한다.
TO 슈트 설정 페이지를 포함하려면 부모 계층에서 "SuiteSetUp" 페이지를 찾아 include문과 페이지경로를 추가한다.
TO 부모 계층을 검색하려면 ...
[ 서술적인 이름을 사용해라 ]
좋은 이름을 짓도록 노력해라. 함수 이름을 본 후 코드를 읽으면서 짐작했던 기능이 수행되면 깨끗한 코드라고 부를 수 있다. 위에 한 함수에 한 가지 기능만 담으라고 하였는데 함수가 작고 단순할수록 서술적인 이름을 짓기도 쉬워진다.
이름이 길어도 상관없다. 짧으면서 의도가 안 담긴 이름보다 훨씬 낫기 때문이다. 거기다가 서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다. 추가로 이름을 붙일 때는 일관성이 있어야 한다. 예를 들어 includeSetupPage 처럼 동사 다음에 명사가 오게 한다거나 하는 문체, 어순을 같게 하는 것이 더 깨끗한 코드이다.
[ 함수 인수 ]
함수에서 인수는 작을 수록 좋다. ( 0개 이면 제일 이상적이다. )
인수는 개념을 이해하기 어렵게 만들기 떄문이다. 그렇기에 최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우이다.
함수에 인수 1개를 넘기는 경우는 흔히 두 가지 경우이다.
하나는 인수에 질문을 던지는 경우이다 예를 들어 boolean fileExists("MyFile") 같은 경우다.
다른 하나는 인수를 뭔가로 변환해 결과를 반환하는 경우이다. InputStream fileOpen("MyFile")은 String 형의 파일 이름을 InputStream으로 변환한다. 이들 두 경우는 코드를 읽는 사람들이 쉽게 받아들일 수 있다.
함수 의도나 인수의 순서와 의도를 제대로 표현하려면 좋은 함 수 이름이 필수다. 단항 함수는 함수와 인수가 동사/명사 쌍을 이뤄야 한다. 예를 들어, write(name)은 누구나 곧바로 이해한다. 좀 더 나은 이름은 writeField(name)이다. 그러면 '이름'이 '필드'라는 의미까지 포함되기 때문이다.
[ 부수 효과를 일으키지 마라 ]
부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다 약속하고 남몰래 다른 것도 한 것이니 말이다.
public boolean checkPassword(String userName, String password) {
User user = UserGateWay.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize();
return true;
}
}
return false;
}
여기서, 함수가 일으키는 부수 효과는 Session.initialize() 호출이다. checkPassword 함수는 이름 그대로 암호를 확인한다. 이름만 봐서는 세션을 초기화한다는 사실이 드러나지 않는다. 그래서 함수 이름만 보고 함수를 호출하는 사용자는 사용자를 인증하면서 기존 세션 정보를 지워버릴 위험에 처하게 된다.
이런 부수 효과가 시간적인 결합을 초래한다. 즉, checkPassword 함수는 특정 상황에서만 호출이 가능하다. 다시 말해, 세션을 초기화해도 괜찮은 경우에만 호출이 가능하다. 자칫 잘못 호출하면 의도치 않게 세션 정보가 날아가기 떄문이다. 특히 부수효과로 숨겨진 경우에는 더욱 혼란이 커질 것 이다. 만약 시간적인 결합이 필요하다면 함수 이름에 분명히 명시한다. checkPasswordAndInitializeSession이라는 이름이 훨씬 좋다. 물론 함수가 ‘한 가지’만 한다는 규칙을 위반하지만 말이다.
[ 명령과 조회를 분리하라 ]
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다.
[ 오류 코드보다 예외를 사용해라 ]
명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.
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;
}
위 코드는 동사/형용사 혼란을 일으키지 않는 대신 여러 단계로 중첩되는 코드를 야기한다. 반면 오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
logger.log(e.getMessage());
}
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
위에서 delete함수가 모든 오류를 처리한다. 그래서 코드를 이해하기 쉽다.
[ 결론 ]
함수는 그 언어에서 동사며, 클래스는 명사다.
대가 프로그래머는 시스템을 (구현할) 프로그램이 아니라 (풀어갈) 이야기로 여긴다.
우리에게 진짜 목표는 시스템이라는 이야기를 풀어가는데 있다는 사실을 명심해야 한다.
'Reading Book > Clean Code' 카테고리의 다른 글
[Clean Code]클린코드_6_객체와 자료 구조 (0) | 2021.08.17 |
---|---|
[Clean Code]클린코드_5_형식 맞추기 (0) | 2021.08.17 |
[Clean Code]클린코드_4_주석 (0) | 2021.08.16 |
[Clean Code]클린코드_2_의미있는 이름 (0) | 2021.08.16 |
[Clean Code]클린코드_1_깨끗한 코드 (0) | 2021.08.16 |