함수를 만드는 첫째 규칙은 '작게!'다. 놀랍게도 저자가 말하는 둘째 규칙은 '더 작게!'다. ( 작게 만들어라! ) <p.42>
if 문 / else 문 / while 문 등에 들어가는 블록은 한 줄이어햐 한다. 대개 거기서 함수를 호출한다. ( 중첩 구조가 생길만큼 함수가 커져서는 안 된다는 뜻 )
함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한가지만을 해야 한다. ( 한 가지만 해라! ) <p.44>
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다. ( ex) 90p / 함수는 declarations, initializations, sieve의 섹션으로 나누어진다. 이는 한 함수에서 여러 작업을 한다는 증거이다. 한 가지 작업만 하는 함수는 자연스럽게 섹션으로 나누기 어렵다. )
함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. ( 함수 당 추상화 수준은 하나로! ) <p.45>
내려가기 규칙 : 한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 온다. ( 즉, 위에서 아래로 프로그램을 읽으면 함수 추상화 수준이 한 번에 한 단계식 낮아진다. )
하지만 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다. 핵심은 짧으면서도 '한 가지'만 하는 함수다.
위에서 아래로 TO문단을 읽어내려 가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다.
switch 문은 작게 만들기 어렵다. '한 가지' 작업만 하는 switch 문도 만들기 어렵다. 불행하게도 switch 문을 완전히 피할 방법은 없다. ( 저차원 클래스에 숨겨 반복하지 않는 방법만이 존재한다. ) <p.47>
예제 함수 이름 testableHtml 을 setupTeardownInclude.render로 변경했다. 함수가 하는 일을 좀 더 잘 표현하므로 훨씬 좋은 이름이다. ( 서술적인 이름을 사용하라! ) <p.49>
함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다.
서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
이름을 붙일 때는 일관성이 있어야 한다. ( 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다. )
함수에서 이상적인 인수 개수는 0개(무항)다. 인수는 어렵다. 인수는 개념을 이해하기 어렵게 만든다. 최선은 입력 인수가 없는 경우이며, 차선은 입력 인수가 1개뿐인 경우다. <p.50>
많이 쓰는 단항 형식
boolean fileExists("MyFile")
InputStream fileOpen("MyFile")
passwordAttemptFailedNtimes(int attempts)
플래그 인수는 추하다. 함수로 부울 값을 넘기는 것은 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 것과 다름 없다.
이항 함수
Point p = new Point(0,0)
assertEquals(expected, actual)
// expected와 actual 값의 순서를 헷갈리는 경우
writeField(outputStream, name)
// 이항 함수
outputStream.writeField(name)
// 단항 함수 ( outputStream을 현재 클래스 구성원 변수로 만들어 인수로 넘기지 않는다. )
삼항 함수
assertEquals(message, expected, actual)
assertEquals(1.0, amount, .001)
인수 객체
Circle makeCircle(double x, double y, double radius)
Circle makeCircle(Point center, double radius)
인수 목록
String.format("%s worked %.2f" hours.", name, hours);
public String format(String format, Object... args)
동사와 키워드
write(name)
writeField(name)
assertExpectedEqualsActual(expected, actual)
// assertEquals와 다르게 인수 순서를 기억하기 쉽다.
부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 남몰래 다른 짓도 하니까. ( 부수 효과를 일으키지 마라! ) <p.54>
부수 효과는 시간적인 결합(temporal coupling)이나 순서 종속성(order dependency)와 같은 문제를 초래한다. > 특정 상황에만 코드를 사용할 수 있게된다.
시간적 결합이 필요할 때는 꼭 이름에 명시해준다. ( checkPassword > checkPasswordAndInitializeSession ) 물론 함수가 '한 가지'만 한다는 기능을 위반하고 있다.
일반적으로 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.
함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다. 객체 상태를 변경하거나 아니면 객체 정보를 반환하거나 둘 중 하나다. ( 명령과 조회를 분리하라! )
public boolean set(String attribute, String value);
// attribute 속성을 찾아 값을 value로 설정한 후 성공하면 true, 실패하면 false를 반환한다.
if (set("username", "unclebob"))...
// "username"을 "unclebob"으로 설정되어 있는 확인하는코드인가 "unclebob"으로 설정하는 코드인가.
if (attributeExists("username")) {
setAttribute("username", "unclebob");
...
}
// 명령과 조회를 분리해 근본적인 혼란을 해결한다.
명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다. 자칫하면 if 문에서 명령을 표현식으로 사용하기 쉬운 탓이다. ( 오류 코드보다 예외를 사용하라! ) <p.56>
오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
}
함수는 '한 가지' 작업만 해야 한다. 오류 처리도 '한 가지' 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
Error.java 의존성 자석 > 오류 코드를 반환한다는 이야기는 어디선가 오류 코드를 정의한다는 뜻이다. 이러면 수정시 클래스 전부를 다시 컴파일하고 다시 배치해야한다. ( 이런 번거러움에 새 오류 코드를 추가하는 대신 기존 오류 코드를 재사용한다. )
오류 코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생된다. 따라서 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.
어쩌면 중복은 소프트웨어에서 모든 악의 근원이다. 많은 원칙과 기법이 중복을 없애거나 제어할 목적으로 나왔다. ( 반복하지 마라! ) <p.60>
함수는 return 문이 하나여야 한다. 루프 안에서 break나 continue를 사용해선 안 되며 goto는 절대로, 절대로 안 된다. ( 구조적 프로그래밍 ) > 함수가 아주 클 때만 상당한 이익을 제공한다. <p.61>
초안은 대개 서투르고 어수선하다. 함수도 처음에는 즉흥적이고 코드는 중복된다. 하지만 그 서투른 코드를 빠짐없이 테스트하는 단위 케스트 케이스도 만든다. 그런 다음 나는 코드를 다듬는다. 처음부터 딱 짜내는 것이 가능한 사람은 없으리라. <p.62>
함수는 그 언어에서 동사며, 클래스는 명사다. 프로그래머는 시스템을 (구현할) 프로그램이 아니라 (풀어갈) 이야기로 여긴다. <p.62>
함수에 대해서 많은 것을 배울 수 있었던 것 같다. 최근 프로그래밍을 짜면서 함수의 중요성을 많이 느끼고 있었는데 이에 반해 제대로된 함수 네이밍이나 분리를 하지 못하여 곤란한 적이 많았다. 노마드 코드에서 배운 divide & conquer 원칙을 따라 어느정도 이번에 배운 개념을 따라하고 있었다고 보지만 부족한 부분이 많았다고 생각한다.
추상화 수준을 하나로 하는것 추상화 단계를 한 단계씩 내려 이어지는 이야기의 형태로 만드는것. 명령과 조회를 분리하는 것. 부수 효과를 일으키지 않는것 등등 앞으로 명심해야 할 것들이 많다. 물론 이 원칙들이 절대적이지는 않지만 대부분의 상황에서 따라야할 지침임에 틀림이 없음을 느꼈다. 마지막에 목록 3-7로 나오는 코드는 확실히 잘 정리되어있고 확실히 편하게 읽을 수 있었다.
이와 동시에 영어의 중요성을 그 어느때보다 절실히 느끼고 있는 요즘이다. 간간히 doc을 영문서 그대로 읽고 다양한 정보를 찾아볼 때 영어로 된 글들을 접하고 있긴 하지만 아직 학생때의 영어 수준이 유지되고 있는 거 같다. 졸업하기 전까지 꼭 영어를 더 편한 언어로 만들어야 할 필요가 느껴진다.
포트란(Formula Translator) : 과학 기술 계산용의 프로그래밍 언어의 하나. 정확히는 FORTRAN 언어라 한다. 본래 수치 계산을 행하기 위한 프로그램 언어로 개발되었다.
PL/1(Programming Language 1) : 프로그램 언어의 일종. 컴퓨터의 이용이 고도화됨에 따라 기술적 계산은 FORTRAN, 사무적 계산은 COBOL이라는 영역구별이 점점 사라져가고 있으며, 이 양분야에 공통으로 사용되는 단일 언어의 초판이라는 뜻으로 PL/1 이라는 이름이 개발사인 IBM에 의해 1966년에 붙여졌다.
SRP(Single Responsibility Principle) : 클래스를 변경해야 하는 이유는 오직 하나여야 한다. > 클래스는 한 가지 책임(기능)만 갖도록 설계하자.
OCP(Open-Closed Principle) : 확장(상속)에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
추상 팩토리 : 팩토리 패턴(팩토리 메서도 패턴)의 조건문(if-else, switch등)으로부터 벗어날 수 있는 방법.
AOP(Aspect Oriented Programming) : 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 바라보고, 그 관점을 기준으로 각각 모듈화하여 프로그래밍하는 기법.
DSL(Domain Specific Language) : 도메인 특화 언어(Domain-specific language)는 특정한 도메인을 적용하는데 특화된 컴퓨터 언어이다. 이는 어느 도메인에서나 적용 가능한 범용 언어(General-purpose language)와는 반대되는 개념이다.