함수(메소드)
작게 만들어라.
- 함수를 만드는 첫 번재 규칙은 '작게'다. 함수를 만드는 두 번째 규칙은 '더 작게'다.
한 가지만 해라.
- 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만 해야 한다.
함수 당 추상화 수준은 하나로
- 함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장이 동일한 추상화 수준에 있어야 한다.
- 코드는 위에서 아래로 이야기처럼 일해야 좋다.
서술적인 이름을 사용하라
함수 인수
- 함수에서 이상적인 인수 개수는 0개(무항)이다. 다음은 1개이고, 다음은 2개이다.
- 4개 이상은 특별한 이유가 있어도 사용하면 안된다
- 인수가 2 ~ 3개 필요한 경우가 생긴다면 인수를 독자적인 클래스를 생성할 수 있는지 검토해 본다.
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
side effect를 만들지 마라.
- side effect는 많은 경우 예상치 못한 버그를 발생시킨다.
명령과 조회를 분리하라.
- 함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야 한다. 둘 다 하면 안된다.
- 개체 상태를 변경하거나 아니면 개체 정보를 반환하거나 둘 중 하나다.
오류 코드보다 예외를 사용하라.
- 오류 처리도 한 가지 작업이다.
- 함수는 '한 가지' 작업만 해야 한다. 오류 처리도 '한 가지' 작업에 속한다.
- 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
- try/catch 블록은 원래가 추하다. 코드 구조에 혼란을 일으키며, 정상적인 동작과 오류 처리 동작을 뒤섞는다. try/catch 블록을 별도 함수로 뽑아내는 편이 낫다.
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
} catch (Exception e) {
logError(e);
}
}
반복하지 마라.
함수를 구현하는 방법은?
소프트웨어를 구현하는 행위는 여느 글짓기와 비슷하다. 논문이나 기사를 쓸 때는 먼저 생각을 기록한 후 읽기 좋게 다듬는다. 초안은 대개 서투르고 어수선하므로 원하는 대로 읽힐 때까지 말을 다듬고 문장을 고치고 문단을 정리한다.
필자는 함수를 구현할 때도 마찬가지다. 처음에는 길고 복잡하다. 들여쓰기 단계도 많고, 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 필자는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
그런 다음 필자는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메소드를 줄이고, 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다.
결국에는 앞에서 다룬 규칙을 따르는 함수를 얻을 수 있다. 처음부터 딱 짜내지 않는다. 그게 가능한 사람은 없으리라.
code convention, format 맞추기
형식을 맞추는 목적
- code convention은 중요하다! 너무 중요해서 무시하기 어렵다.
- code convention은 의사소통의 일환이다. 의사소통은 전문 개발자의 일차적인 의무다.
적절한 행의 길이를 유지하라
가로 format 맞추기
팀 규칙
- 모든 프로그래머는 자신이 선호하는 규칙이 있다. 하지만 팀에 속한다면 자신이 선호해야 할 규칙은 바로 팀 규칙이다.
- 팀은 한 가지 규칙에 합의해야 한다. 그리고 모든 팀원은 그 규칙을 따라야 한다.
의미 있는 이름
의도를 분명히 밝혀라
- 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
- 그러므로 이름을 주의 깊게 살펴 더 나은 이름이 떠오르면 개선하기 바란다. 그러면 (자신을 포함해) 코드를 읽는 사람이 좀더 행복해지리라.
- 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 소리다.
// 잘못된 사용 예
int d; //경과 시간(단위: 날짜 수)
그릇된 정보를 피하라
- 프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다. 그릇된 단서는 코드 의미를 흐린다.
- 서로 흡사한 이름을 사용하지 않도록 주의한다.
의미 있게 구분하라
- 컴파일러나 인터프리터만 통과하려는 생각으로 코드를 구현하는 프로그래머는 스스로 문제를 일으킨다.
- 연속적인 숫자를 덧붙인 이름(a1, a2, ..., aN) 덧붙이거나 불용어(Info, Data, a, an, the)를 추가하는 방식은 적절하지 못하다. 이름이 달라야 한다면 의미도 달라져야 한다.
인터페이스 클래스와 구현 클래스
- 인터페이스 이름은 접두어를 붙이지 않는 편이 낫다고 생각한다.
- IShapeFactory(인터페이스), ShapeFactory(구현 클래스) 구조로 이름을 짓는 것은 좋은 선택은 아니다.
- 오히려 인터페이스를 ShapeFactory로 이름을 짓고 구현 클래스의 의도를 드러낼 수 있는 이름을 짓는 것을 추천한다.
클래스 이름
- 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
- Customer, WikiPage, Account, AddressParser 등이 좋은 예다.
- Manager, Processor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.
메소드 이름
- postPayment, deletePage, save 등이 좋은 예다.
- 접근자, 변경자, 조건자는 자바 빈 표준에 따라 값 앞에 get, set, is를 붙인다.
- 생성자를 중복해 정의할 때는 정적 팩토리 메소드를 사용한다. 메소드를 인수를 설명하는 이름을 사용한다.
개념 하나에 단어 하나를 사용하라
- 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
- 일관성 있는 어휘는 코드를 사용할 프로그래머가 반갑게 여길 선물이다.
경계
외부 코드 사용하기
- API를 사용하는 사용자는 자신의 요구에 집중하는 인터페이스만 존재하기를 기대한다.
- java.util.List, java.util.Map와 같은 collection을 외부에 노출하는 경우 사용자는 너무 많은 인터페이스에 노출하게 된다.
Map<Integer, Sensor> sensors = new HashMap<>();
Sensor s = sensors.get(sensorId);
- 경계 인터페이스인 Map을 Sensors라는 클래스 안으로 숨긴다.
public class Sensors {
Map<Integer, Sensor> sensors = new HashMap<>();
pubilc Sensor getById(String id) {
return sensors.get(id);
}
}
- 위와 같이 Map을 사용자에게 직접 노출하는 경우 사용자에게 Map의 모든 인터페이스를 노출하지 않아도 된다.
- Sensors를 추가함으로써 필요한 인터페이스 하나만 노출하는 것도 가능하다.
- 또 하나의 장점은 Sensors 내부의 자료구조가 Map이 아닌 다른 자료구조로 변경되더라도 외부에 변경이 발생하지 않아도 된다.
경계 살피고 익히기
- 학습 테스트를 통해 외부 코드 사용 방법을 익힌다.
- 외부 코드가 변경될 경우 발생할 버그를 학습 테스트 코드를 통해 검증할 수 있다.
학습 테스트는 공짜 이상이다.
학습 테스트에 드는 비용은 없다. 어쨌든 API를 배워야 하므로 오히려 필요한 지식만 확보하는 손쉬운 방법이다. 학습 테스트는 이해를 높여주는 정확한 실험이다.
학습 테스트는 공짜 이상이다. 투자하는 노력보다 얻어지는 성과가 더 크다. 패키지의 새 버전이 나온다면 학습 테스트를 돌려 차이가 있는지 확인한다.
객체와 자료 구조
자료/객체
- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
- 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.
디미터 법칙
- 디미터 법칙은 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다.
- 객체는 자료를 숨기고 함수를 공개한다. 즉, 객체는 조회 함수로 내부 구조를 공개하면 안 된다는 것이다.
- 다음 코드는 디미터 법칙을 어기는 것으로 보인다.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
자료 전달 객체
- 자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다. 이런 자료 구조체를 Data Transfer Object(DTO)라고 한다.
- 자바에서 DTO의 일반적인 형태는 '자바 빈(java bean)' 구조다.
클래스
클래스는 작아야 한다.
- 클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다. 두 번째 규칙도 크기다. 더 작아야 한다.
- 단일 책임 원칙(Single Responsibility Principle, SRP)은 클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙이다.
- 클래스는 책임, 즉 변경할 이유가 하나여야 한다는 의미다.
- 응집도(cohesion) - 클래스는 인스턴스 변수 수가 작아야 한다.
- 응집도를 유지하면 작은 클래스 여럿이 나온다.
- 큰 함수를 작은 함수 여럿으로 쪼개다 보면 종종 작은 클래스 여럿으로 쪼갤 기회가 생긴다. 그러면서 프로그램에 체계가 더 잡히고 구조가 더 투명해진다.
변경하기 쉬운 클래스
- 요구사항은 변화기 마련이다. 따라서 코드도 변하기 마련이다.
- 구현 클래스에 의존하게 되면 테스트가 어려우며, 변화에 빠르게 대응하기 힘들다. 변화에 따르게 대응하려면 DIP 원칙을 지키는 습관을 가져야 한다.
- DIP(Dependency Inversion Principle) 원칙은 클래스가 상세한 구현이 아니라 추상화(인터페이스)에 의존해야 한다는 원칙이다.
- 테스트가 가능할 정도로 시스템 결합도를 낮추면 유연성과 재사용성도 더 높아진다.