Clean Code 3판을 읽고 정리한 글입니다
Ⅻ. 창발성(創發性)
- 비로소 창, 쏠 발, 성품 성
- 불시에 솟아 나는 특성, emergent property, emergence
- 단순한 결합이 복잡한 결과를 나타내는 것을 의미
- 예) 인간의 뇌 : 하나의 뉴런은 인식 능력이 없으나 수십억개가 결합하면 자기 인식 발생
- 명령을 내리는 조정자 없이 각 부분의 의사소통으로 자기 조직화를 이루게 되고
이러한 밑으로 부터의 힘은 예기치 못한 기능을 발현하는 힘 Ex) 집단 지성- 창발적 설계 : 어떤 규칙과 원칙에 따라 설계를 하게 되면, 그것들이 모여 아주 좋은 거시적 설계가 된다는 원리?
창발적 설계로 깔끔한 코드를 구현하자
단순한 설계 규칙 4가지(4 Rules of Simple Design)
- 켄트백이 90년에 “Extreme Programming Explained”에서 설명
- 착실하게 따르기만 하면
- 우수한 설계가 나오며
- 코드 구조와 설계 파악이 쉬워지고
- 따라서 SRP, DIP같은 원칙 적용이 쉬워지며
- 우수한 설계의 창발성을 촉진한다
- 규칙 내용 : 이를 따르면 Design is Simple하다!(..라고 켄트백이 말했다;) 중요도 순서로 나열
- 모든 테스트를 실행한다
- 중복을 없앤다
- 프로그래머 의도를 표현한다
- 클래스와 메서드 수를 최소로 줄인다
단순한 설계 규칙 1: 모든 테스트를 실행하라
First, 설계는 의도한 대로 돌아가는 시스템을 내놓아야
- 문서로는 완벽히 설계했지만, 시스템이 의도한 대로 돌아가는지 검증할 간단한 방법이 없다면?
- 문서 작성을 위해 투자한 노력에 대한 가치는 인정받기 힘들다.
테스트가 불가능한 시스템은 검증도 불가능
- 테스트를 철저히 거쳐 모든 테스트 케이스를 항상 통과하는 시스템은 ‘테스트가 가능한 시스템’
- 테스트가 불가능한 시스템은 검증도 불가능하다
- 논란의 여지는 있지만, 검증이 불가능한 시스템은 절대 출시하면 안 된다
철저한 테스트가 가능한 시스템 지향 → 더 좋은 설계가 결과로
- 다행스럽게도, 테스트가 가능한 시스템을 만들려고 애쓰면 설계 품질이 더불어 높아진다
- 크기가 작고 목적 하나만 수행하는 클래스가 나온다
- SRP를 준수하는 클래스는 테스트가 훨씬 더 쉽다
- 테스트를 더 많이 작성하면 할수록 프로그래머가 더 테스트하기 간단하게 코드를 작성할 수 있게 도와준다
- 따라서 철저한 테스트가 가능한 시스템을 만들면 더 나은 설계가 얻어진다
- 자동으로 결합도도 낮아짐
- 결합도가 높으면 테스트 케이스를 작성하기 어렵다. 따라서 지금까지와 마찬가지로
- 테스트 케이스를 많이 작성할수록 개발자는 DIP와 같은 원칙을 적용하고 의존성 주입(Dependency Injection),인터페이스, 추상화 등과 같은 도구를 사용해 결합도를 낮춘다
- 따라서 설계 품질은 더욱 높아진다
- 결론
- 놀랍게도 “테스트 케이스를 만들고 계속 돌려라”라는 간단하고 단순한 규칙을 따르면
- 시스템은 낮은 결합도와 높은 응집력이라는, 객체 지향 방법론이 지향하는 목표를 저절로 달성한다
- 즉, 테스트 케이스를 작성하면 설계 품질이 높아진다.
단순한 설계 규칙 2~4: 리팩터링
걱정없이 리팩터링~
- 테스트 케이스를 모두 작성했다면 이제 코드와 클래스를 정리해도 괜찮다
- 구체적으로는 코드를 점진적으로 리팩터링 해나간다
- 코드 몇 줄을 추가할 때마다 잠시 멈추고 설계를 조감한다
- 새로 추가하는 코드가 설계 품질을 낮춘다면??
- 깔끔히 정리한 후 테스트를 돌려 기존 기능을 깨뜨리지 않았다는 사실을 확인
- 코드를 정리하면서 시스템이 깨질까 걱정할 필요가 없다. 테스트 케이스가 있으니까!
sw 설계 품질 기법 적용
- 리팩터링 단계에서는 소프트웨어 설계 품질을 높이는 기법이라면 무엇이든 적용해도 괜찮다
- 응집도를 높이고, 결합도를 낮추고, 관심사를 분리하고, 시스템 관심사를 모듈로 나누고, 함수와 클래스 크기를 줄이고, 더 나은 이름을 선택하는 등 다양한 기법을 동원한다
- 또한 이 단계는 단순한 설계 규칙 중 나머지 3개를 적용해서
- 중복 제거
- 프로그래머 의도 표현
- 클래스 메서드를 최소한으로 줄인다
중복을 없애라
- 우수한 설계에서 중복은 커다란 적이다
- 중복은 추가 작업, 추가 위험, 불필요한 복잡도를 뜻하기 때문
- 중복은 여러 가지 형태로 표출된다
- 똑같은 코드 → 당연히 중복
- 비슷한 코드 → 더 비슷하게 고쳐주면 리팩터링이 쉬워진다
- 구현 중복도 중복의 한 형태구현
집합 클래스에 다음 메서드가 있을 때 1
2int size() {}
boolean isEmpty{}
- 각 메서드를 따로 구현하는 방법도 있다
- 하지만 size()가 개수를 반환하는 로직
- isEmpty가 이를 이용하면 중복 구현할 필요 없음
1
2
3boolean isEmpty() {
return 0 == size();
}
깔끔한 시스템을 만들려면 단 몇 줄이라도 중복을 제거하겠다는 의지가 필요하다
다음 코드를 살펴보자.
1 | public void scaleToOneDimension(float desiredDimension, float imageDimension) { |
1 | public void scaleToOneDimension(float desiredDimension, float imageDimension) { |
리팩터링
- 아주 적은 양이지만 공통적인 코드를 새 메서드로
- 뽑고 보니 클래스가 SRP를 위반
- 그러므로 새로 만든 replaceImage 메서드를 다른 클래스로 옮겨도 좋겠다고 한다
- 그러면 새 메서드의 가시성이 높아진다
- 따라서 다른 팀원이 새 메서드를 좀 더 추상화해 다른 맥락에서 재사용할 기회를 포착할지도 모른다
- 이런 ‘소규모 재사용’은 시스템 복잡도를 극적으로 줄여준다
- 소규모 재사용을 제대로 익혀야 대규모 재사용이 가능하다
템플릿 메서드 패턴 : 고차원 중복을 제거할 목적으로 자주 사용하는 기법
1 | public class VacationPolicy { |
1 | abstract public class VacationPolicy { |
하위 클래스는 중복되지 않는 정보만 제공해 accrueVacation 알고리즘에서 빠진 ‘구멍’을 메운다.
표현하라
자신만 이해하는 코드의 문제
- 아마 우리 대다수는 엉망인 코드를 접한 경험이 있다
- 아마 우리 대다수는 스스로 엉망인 코드를 내놓은 경험도 있다
- 자신이 이해하는 코드를 짜기는 쉽다
- 코드를 짜는 동안에는 문제에 푹 빠져 코드를 구석구석 이해하니까
- 하지만 나중에 코드를 유지보수할 사람이 그만큼 문제를 깊이 이해할 가능성은 희박
코드는 개발자의 의도를 분명히 표현해야 한다
- 소프트웨어 프로젝트 비용 중 대다수는 장기적인 SM에 들어간다
- 코드를 변경하면서 버그의 싹을 심지 않으려면 SM 개발자가 시스템을 제대로 이해해야 한다
- 하지만 시스템이 점차 복잡
- SM 개발자가 시스템 이해하느라 보내는 시간은 점점 늘어남
- 동시에 코드를 오해할 가능성도 점점 커짐
- 그러므로 코드는 개발자의 의도를 분명히 표현해야 한다
- 개발자가 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워진다
- 그래야 결함이 줄어들고 유지보수 비용이 적게 든다.
How?
- 좋은 이름을 선택
- 이름과 기능이 완전히 딴판인 클래스나 함수로 개발자를 놀라게 해서는 안 된다
- 함수와 클래스 크기를 가능한 한 줄인다
- 작은 클래스와 작은 함수는 이름 짓기도 쉽고, 구현하기도 쉽고, 이해하기도 쉽다
- 표준 명칭을 사용
- 예를 들어, 디자인 패턴은 의사소통과 표현력 강화가 주요 목적
- class가 COMMAND나 VISITOR등의 표준 패턴을 사용하는 경우 클래스 이름에 패턴 이름을 넣어준다
- 그러면 다른 개발자가 클래스 설계 의도를 이해하기 쉬워진다.
- 단위 테스트 케이스를 꼼꼼히 작성
- 테스트 케이스는 소위 ‘예제로 보여주는 문서’다
- 다시 말해, 잘 만든 테스트 케이스를 읽어보면 클래스 기능이 한눈에 들어온다
- 노력과 주의 : 표현력을 높이는 가장 중요한 방법
- 흔히 코드만 돌린 후 다음 문제로 직행하는 사례가 너무도 흔하다
- 나중에 읽을 사람을 고려해 조금이라도 읽기 쉽게 만드려는 충분한 고민은 거의 찾기 어렵다
- 하지만 나중에 코드를 읽을 사람은 바로 자신일 가능성이 높다는 사실을 명심하자
- 그러므로 자신의 작품을 조금 더 자랑하자
- 함수와 클래스에 조금 더 시간을 투자하자
- 더 나은 이름을 선택
- 큰 함수를 작은 함수 여럿으로 나누고
- 자신의 작품에 조금만 더 주의를 기울이자
- 주의는 대단한 재능이다.
클래스와 메서드 수를 최소로 줄여라
(가능한)최소의 의미
- 중복을 제거하고, 의도를 표현하고, SRP를 준수한다는 기본적인 개념도 극단으로 치달으면 득보다 실이 많아진다
- 클래스와 메서드 크기를 줄이자고 조그만 클래스와 메서드를 수없이 만드는 사례도 없지 않다
- 그래서 이 규칙은 함수와 클래스 수를 가능한 한 줄이라고 제안한다
독단을 멀리하고 실용성 선택
- 때로는 무의미하고 독단적인 정책 탓에 클래스 수와 메서드 수가 늘어나기도 한다
- 클래스마다 무조건 인터페이스를 생성하라고 요구하는 구현 표준이 좋은 예
- 자료 클래스와 동작 클래스는 무조건 분리해야 한다고 주장하는 개발자도 좋은 예
- 가능한 독단적인 견해는 멀리하고 실용적인 방식을 택해야 한다
해당 규칙의 목표와 우선순위에 대하여
- 목표는 함수와 클래스 크기를 작게 유지하면서 동시에 시스템 크기도 작게 유지하는 데 있다
- 하지만 이 규칙은 간단한 설계 규칙 네 개 중 우선순위가 가장 낮다
- 다시 말해, 클래스와 함수 수를 줄이는 작업도 중요하지만, 테스트 케이스를 만들고 중복을 제거하고 의도를 표현하는 작업이 더 중요하다는 뜻
결론
- 경험을 대신할 단순한 개발 기법은 없음
- 이 책에서 소개하는 기법은 저자들이 수십 년 동안 쌓은 경험의 정수
- 단순한 설계 규칙을 따른다면 (오랜 경험 후에야 익힐) 우수한 기법과 원칙을 단번에 활용할 수 있다
Related POST
- [Clean Code] Ⅰ. 깨끗한 코드
- [Clean Code] Ⅱ.의미 있는 이름
- [Clean Code] Ⅲ. 함수
- [Clean Code] Ⅳ. 주석
- [Clean Code] Ⅴ. 형식 맞추기
- [Clean Code] Ⅵ. 객체와 자료구조
- [Clean Code] Ⅶ. 오류 처리
- [Clean Code] Ⅷ. 경계
- [Clean Code] Ⅸ. 단위 테스트
- [Clean Code] Ⅹ. 클래스
- [Clean Code] Ⅺ. 시스템
- [Clean Code] Ⅻ. 창발성(創發性)
- [Clean Code] XIII. 동시성
- [Clean Code] XIV. 점진적 개선(SUCCESSIVE REFINEMENT)
- [Clean Code] XV. JUnit 들여다보기
- [Clean Code] XVI. SerialDate 리팩터링
- [Clean Code] XVII. 냄새와 휴리스틱
- [Clean Code] 다 읽었다~