[ 책 ] Clean Code 클린 코드 - 애자일 소프트웨어 장인 정신
1장 깨끗한 코드
우리 모두는 자신이 짠 쓰레기 코드를 쳐다보며 나중에 손보겠다고 생각한 경험이 있다. 우리 모두는 대충 짠 프로그램이 돌아간다는 사실에 안도감을 느끼며 그래도 안 돌아가는 프로그램보다 돌아가는 쓰레기가 좋다고 스스로를 위로한 경험이 있다. 다시 돌아와 나중에 정리하겠다고 다짐했었다. 물론 그때 그 시절 우리는 르블랑의 법칙을 몰랐다. 나중은 결코 오지 않는다. - 4p
좋은 코드를 사수하는 일은 바로 우리 프로그래머들의 책임이다. - 7p
깨끗한 코드는 한 가지에 '집중'한다. 각 함수와 클래스와 모듈은 주변 상황에 현혹되거나 오염되지 않은 채 한길만 걷는다. - 10p
깨끗한 코드의 특징은 많지만 그 중에서도 모두를 아우르는 특징이 하나 있다. 깨끗한 코드는 언제나 누군가 주의 깊게 짰다는 느낌을 준다. 고치려고 살펴봐도 딱히 손 댈 곳이 없다. 작성자가 이미 모든 사항을 고려했으므로. (마이클 페더스)
- 8p
중복 줄이기, 표현력 높이기, 초반부터 간단한 추상화 고려하기. 내게는 이 세 가지가 깨끗한 코드를 만드는 비결이다. (론 제프리스) - 14p
우리는 저자다
Javadoc에서 @author 필드는 저자를 소개한다. 우리는 저자다. 저자에게는 독자가 있다. 그리고 저자에게는 독자와 잘 소통할 책임도 있다. 다음에 코드를 짤 때는 자신이 저자라는 사실을, 여러분의 노력을 보고 판단을 내릴 독자가 있다는 사실을 기억하기 바란다. -17p
이 책을 읽는다고 뛰어난 프로그래머가 된다는 보장은 없다. '코드 감각'을 확실히 얻는다는 보장도 없다. 단지 뛰어난 프로그래머가 생각하는 방식과 그들이 사용하는 기술과 기교와 도구를 소개할 뿐이다. -20p
2장 의미 있는 이름
grep - 유닉스를 위해 만들어진 텍스트 검색 기능을 가진 명령어 (출처 : 위키백과)
헝가리식 표기법 - 변수 및 함수의 인자 이름 앞에 데이터 타입을 명시하는 코딩 규칙 (출처 : 나무위키)
개인적으로 인터페이스 이름은 접두어를 붙이지 않는 편이 좋다고 생각한다. 옛날 코드에서 많이 사용하는 접두어 I는 (잘해봤자) 주의를 흐트리고 (나쁘게는) 과도한 정보를 제공한다. 나로서는 내가 다루는 클래스가 인터페이스라는 사실을 남에게 알리고 싶지 않다. - 31p
oodp 수업을 들을 때 교수님께서 인터페이스 클래스에 I를 항상 붙이셔서 나도 그냥 그것을 따라 했다.
생각해보니 좋은 방식이 아니었다.
클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
... 메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등이 좋은 예다. 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다. - 32p
동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽다. DeviceManager와 ProtocolController는 근본적으로 어떻게 다른가?
...
일관성 있는 어휘는 코드를 사용할 프로그래머가 반갑게 여길 선물이다. - 33p
Job queue : 시스템 소프트웨어에서 작업 대기열은 실행할 작업을 포함하는 작업 스케줄러 소프트웨어에 의해 유지 관리되는 데이터 구조 (출처 : 위키백과)
의미 있는 맥락을 추가하라
... 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다. - 35p
firstName, lastName, city, state, zipcode 보다
addrFirstName, addrLastName, addrState이 좋다. 맥락을 분명하게 하자.
불필요한 맥락을 없애라
...일반적으로는 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다.
accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 못하다. - 37p
3장 함수
작게 만들어라!
함수를 만드는 첫째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다. - 42p
한 가지만 해라!
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다. - 44p
함수 당 추상화 수준은 하나로!
함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다. -45p
핵심은 짧으면서도 '한 가지'만 하는 함수다. 위에서 아래로 TO 문단을 읽어내려 가듯이 코드를 구현하면 추상화 수준을 일관되게 유지하기가 쉬워진다. - 46 p
SRP (Single Responsibility Principle) : 객체 지향 프로그래밍에서 단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. (출처 : 위키백과)
OCP (Open Closed Principle) :
개방-폐쇄 원칙(OCP, Open-Closed Principle)은 '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다'는 프로그래밍 원칙 (출처 : 위키백과)
DIP (Dependency Inversion Principle) : 클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙 (190p)
객체지향 설계 5원칙 SOLID (SRP, OCP, LSP, ISP, DIP)
서술적인 이름을 사용하라!
길고 서술적인 이름이 짧고 어려운 이름보다 좋다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다. 함수 이름을 정할 때는 여러 단어가 쉽게 읽히는 명명법을 사용한다. 그런 다음, 여러 단어를 사용해 함수 기능을 잘 표현하는 이름을 선택한다. - p49
이름을 붙일 때는 일관성이 있어야 한다. 모듈 내에서 함수 이름은 같은 문구, 명사, 동사를 사용한다. - p50
함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다. 3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다. 특별한 이유가 있어도 사용하면 안 된다. - 50p
플래그 인수는 추하다. 함수로 부울 값을 넘기는 관례는 정말로 끔찍하다. 왜냐고? 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이니까! 플래그가 참이면 이걸 하고 거짓이면 저걸 한다는 말이니까! - 52p
추하다는 단어를 보고 너무 웃겼는데 순간 boolean 값을 넘기는 내 코드가 생각났다. 휴. 고치러 가야지
오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다. - p58
try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다. - 58p
구조적 프로그래밍 : 절차적 프로그래밍 방식, 하향식 및 폭포수 방식이라고도 부르는데 프로그램이 실행될 때 위에서 아래로의 절차, 순서에 맞게 실행되는 방식. 단계적으로, 순서에 따라 차례대로 하나씩 작성하는 구조. (출처 : 네이버 지식백과)
AOP (Aspect Oriented Programming) : 관점지향프로그래밍. 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍이다. 관점(Aspect)이란, 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것
(출처 : https://velog.io/@kai6666/Spring-Spring-AOP-%EA%B0%9C%EB%85%90 )
COP (Component Oriented Programming) : 컴포넌트지향프로그래밍. 컴포넌트는 프로그래밍에 있어 재사용이 가능한 각각의 독립된 모듈.
처음에는 길고 복잡하다. 들여쓰기 단계도 많고 중복된 루프도 많다. 인수 목록도 아주 길다. 이름은 즉흥적이고 코드는 중복된다. 하지만 나는 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다.
그런 다음 나는 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거한다. 메서드를 줄이고 순서를 바꾼다. 때로는 전체 클래스를 쪼개기도 한다. 이 와중에도 코드는 항상 단위 테스트를 통과한다. -p61
5장 형식 맞추기
어쩌면 '돌아가는 코드'가 전문 개발자의 일차적인 의무라 여길지도 모르겠다. 하지만 이 책을 읽으면서 생각이 바뀌었기 바란다. 오늘 구현한 기능이 다음 버전에서 바뀔 확률은 아주 높다. 그런데 오늘 구현한 코드의 가독성은 앞으로 바뀔 코드의 품질에 지대한 영향을 미친다. - p96
수직거리
변수는 사용하는 위치에 최대한 가까이 선언한다.
인스턴스 변수는 클래스 맨 처음에 선언한다.
함 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까지 배치한다. 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치한다.
개념적인 친화도가 높을수록 코드를 가까이 배치한다. -p101
6장 객체와 자료 구조
자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다. 인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않는다. 개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다. 아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다. -p119
객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다. 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다. -p119
VISITOR 패턴은 주로 상속 없이 클래스에 메서드를 효과적으로 추가하기 위해 사용한다. 하지만 합성 객체의 내부 구조가 visitor에 열리게 되므로 캡슐화를 위반한다는 문제점이 생긴다 - p121
객체 지향 코드에서 어려운 변경은 절차적인 코드에서 쉬우며, 절차적인 코드에서 어려운 변경은 객체 지향 코드에서 쉽다! 복잡한 시스템을 짜다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이때는 클래스와 객체 지향 기법이 가장 적합하다. 반면, 새로운 자료 타입이 아니라 새로운 함수가 필요한 경우도 생긴다. 이때는 절차적인 코드와 자료 구조가 좀 더 적합하다. -p122
절차적인 코드 : 새 함수에 적합. 새 자료 타입은 어려움(모든 함수 고쳐야 함)
객체지향 코드 : 새 자료 타입에 적합. 새 함수는 어려움 (모든 클래스 고쳐야 함)
휴리스틱 (heuristic) : 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있게 보다 용이하게 구성된 간편추론의 방법 (출처 : 위키백과)
디미터 법칙 : 클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다.
- 클래스 C
- f가 생성한 객체
- f 인수로 넘어온 객체
- C 인스턴스 변수에 저장된 객체
자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다. 이런 자료 구조체를 때로는 자료 전달 객체 (Data Transfer Object, DTO)라 한다. DTO는 굉장히 유용한 구조체다. 특히 데이터베이스와 통신하거나 소켓에서 받은 메시지의 구문을 분석할 때 유용하다. 흔히 DTO는 데이터베이스에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체다. - p126
빈(bean)은 비공개(private) 변수를 조회/설정 함수로 조작한다. 일종의 사이비 캡슐화로, 일부 ㅇㅇ 순수주의자나 만족시킬 뿐 별다른 이익을 제공하지 않는다. - p126
활성 레코드는 DTO의 특수한 형태다. 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조지만, 대개 save나 find와 같은 탐색 함수도 제공한다. 활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한 결과다. -p127
객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작은 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 자료 구조는 별다른 동작 없이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다.
어떤 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다. - p127
7장 오류 처리
오류 코드보다 예외를 사용하라 - p130
어떤 면에서 try 블록은 트랜잭션과 비슷하다. try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다. 그러므로 예외가 발생할 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 낫다. - p132
미확인(unchecked) 예외를 사용하라 -p133
확인된 예외는 OCP를 위반한다. -p134
Java에서 예외는 3가지로 볼 수 있다. Checked Exception, Error, Unchecked Exception
우리가 흔히 말하는 Exception(예외)은 Checked Exception과 UnCheckedException이 있다.
두 가지의 차이는 RuntimeException을 상속하느냐(UnChecked) 안하느냐(Checked)의 차이이다.
확인된 예외 : 컴파일 단계에서 확인되며 반드시 처리해야 하는 예외. IOException, SQLException, etc.
try-catch만 이용하거나 throw까지 해서 예외처리 가능
미확인 예외 : 실행 단계(런타임)에서 확인되며 명시적인 처리를 강제하지 않는 예외. RunTimeException, 자식 예외 클래스. NullPointerException, IllegalArgumentException, IndexOutOfBoundException, System.Exception
(참고 : https://velog.io/@vpdls1511/Exception-%EB%8F%84-%EB%8B%A4-%EA%B0%99%EC%9D%80-Exception-%EC%9D%B4-%EC%95%84%EB%8B%88%EB%8B%A4)
실제로 외부 API를 사용할 때는 감싸기 기법이 최선이다. 외부 API를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어든다. 나중에 다른 라이브러리로 갈아타도 비용이 적다. 또한 감싸기 클래스에서 외부 API를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기도 쉬워진다. 마지막 장점으로 감싸기 기법을 사용하면 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다. - p137
메서드에서 null을 반환하는 방식도 나쁘지만 메서드로 null을 전달하는 방식은 더 나쁘다. 정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다. - p140
Collections.emptyList() 사용하면 null 대신 빈 리스트 반환 가능
8장 경계
Map과 같은 경계 인터페이스를 이용할 때는 이를 이용하는 클래스나 클래스 계열 밖으로 노출되지 않도록 주의한다. Map 인스턴스를 공개 API의 인수로 넘기거나 반환값으로 사용하지 않는다. -p146
경계 인터페이스 (경계) : 외부 API나 다른 클래스에서 사용하면서 그 내부 구조가 바뀌어 시스템 전체에 영향을 미칠 수 있는 부분 (참고 : https://mountain96.tistory.com/11)
경계에 위치한 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스도 작성한다. -p152
9장 단위 테스트
TDD 법칙 세 가지
- 첫째 법칙: 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 둘째 법칙: 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 셋째 법칙: 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다. -p155
테스트 코드는 실제 코드 못지 않게 중요하다. 테스트 코드는 이류 시민이 아니다. 테스트 코드는 사고와 설계와 주의가 필요하다. 실제 코드 못지 않게 깨끗하게 짜야 한다. -p157
코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이 바로 단위 테스트다. 이유는 단순하다. 테스트 케이스가 있으면 변경이 두렵지 않으니까! 테스트 케이스가 없다면 모든 변경이 잠정적인 버그다. -p157
이중 표준
테스트 API 코드에 적용하는 표준은 실제 코드에 적용하는 표준과 확실히 다르다.
...실제 환경과 테스트 환경은 요구사항이 판이하게 다르다. -p161
JUnit으로 테스트 코드를 짤 때는 함수마다 assert 문을 단 하나만 사용해야 한다고 주장하는 학파가 있다.
...assert문이 단 하나인 함수는 결론이 하나라서 코드를 이해하기 쉽고 빠르다. -p164
F.I.R.S.T
- 빠르게 (Fast) : 테스트는 빨라야 한다.
- 독립적으로 (Independent) : 각 테스트는 서로 의존하면 안 된다.
- 반복가능하게 (Repeatable) : 테스트는 어떤 환경에서도 반복 가능해야 한다.
- 자가검증하는 (Self-Validating) : 테스트는 부울(bool)값으로 결과를 내야 한다.
- 적시에 (Timely) : 테스트는 적시에 작성해야 한다. 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다.
- p167
10장 클래스
클래스 체계
- 변수 목록
- static public 상수
- static private 변수
- 비공개 인스턴스 변수
- 공개 함수
- 비공개 함수 : 자신을 호출하는 공개 함수 직후에 넣음
함수는 물리적인 행 수로 크기를 측정했다. 클래스는 다른 척도를 사용한다. 클래스가 맡은 책임을 센다. -p173
클래스 이름은 해당 클래스 책임을 기술해야 한다. 실제로 작명은 클래스 크기를 줄이는 첫 번째 관문이다.
... 클래스 이름에 Processor, Manager, Super 등과 같이 모호한 단어가 있다면 클래스에다 여러 책임을 떠안겼다는 증거다. -p175
단일 책임 원칙(SRP)은 클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙이다. SRP는 '책임'이라는 개념을 정의하며 적절한 클래스 크기를 제시한다. 클래스는 책임, 즉 변경할 이유가 하나여야 한다는 의미다. -p175
클래스는 인스턴스 변수 수가 작아야 한다.
... 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높다.
... 우리는 응집도가 높은 클래스를 선호한다. 응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미기 때문이다. -p177
몇몇 함수가 몇몇 변수만 사용한다면 독자적인 클래스로 분리해도 되지 않는가? 당연하다. 클래스가 응집력을 잃는다면 쪼개라! -p179
새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다.
이상적인 시스템이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지는 않는다. -p188
구체적인(concrete) 클래스는 상세한 구현(코드)을 포함하며 추상(abstract) 클래스는 개념만 포함한다고도 배웠다. 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다. 그래서 우리는 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다. -p189
결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미다. -p190
11장 시스템
Main 분리
시스템 생성과 시스템 사용을 분리하는 한 가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.
... 모든 화살표가 main 쪽에서 애플리케이션 쪽을 향한다. 즉, 애플리케이션은 main이나 객체가 생성되는 과정을 전혀 모른다는 뜻이다. -p196
팩토리
물론 때로는 객체가 생성되는 시점을 애플리케이션이 결정할 필요도 생긴다.
... 이 때는 ABSTRACT FACTORY 패턴을 사용한다. 그러면 LineItem을 생성하는 시점은 애플리케이션이 결정하지만 LineItem을 생성하는 코드는 애플리케이션이 모른다. -p197
의존성 주입
사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입이다. 의존성 주입은 제어 역전 (Inversion of Control, IoC) 기법을 의존성 관리에 적용한 메커니즘이다.
제어 역전에서는 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다.
... 의존성 관리 맥락에서 객체는 의존성 자체를 인스턴스로 만드는 책임은 지지 않는다. 대신에 이런 책임을 다른 '전담' 메커니즘에 넘겨야만 한다. 그렇게 함으로써 제어를 역전한다. 초기 설정은 시스템 전체에서 필요하므로 대개 '책임질' 메커니즘으로 'main' 루틴이나 특수 컨테이너를 사용한다.
...클래스가 의존성을 해결하려 시도하지 않는다. 클래스는 완전히 수동적이다. 대신에 의존성을 주입하는 방법으로 설정자(setter) 메서드나 생성자 인수를 제공한다. DI 컨테이너는 필요한 객체의 인스턴스를 만든 후 생성자 인수나 설정자 메서드를 사용해 의존성을 설정한다.
... 스프링 프레임워크는 가장 널리 알려진 자바 DI 컨테이너를 제공한다. 객체 사이 의존성은 XML 파일에 정의한다. 그리고 자바 코드에서는 이름으로 특정한 객체를 요청한다. - p198
AOP는 횡단 관심사에 대처해 모듈성을 확보하는 일반적인 방법론이다. AOP에서 관점이라는 모듈 구성 개념은 "특정 관심사를 지원하려면 시스템에서 특정 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다"라고 명시한다. 명시는 간결한 선언이나 프로그래밍 매커니즘으로 수행한다. -p203
자바의 관점 혹은 관점 유사 메커니즘 (p203)
1. 자바 프록시 : 단순한 상황에 적합. 개별 객체나 클래스에서 메서드 호출을 감싸는 경우. 단점은 코드 '양'과 크기.
2. 순수 자바 AOP 프레임워크 : 순수 자바 관점을 구현하는 스프링 AOP 등의 여러 자바 프레임워크는 내부적으로 프록시 사용.
3. AspectJ 관점 : 관심사를 관점으로 분리하는 가장 강력한 도구. 단점은 새 도구와 문법, 사용법.
스프링은 비즈니스 논리를 POJO로 구현한다. POJO는 순수하게 도메인이 초점을 맞춘다. POJO는 엔터프라이즈 프레임워크에 의존하지 않는다. 따라서 테스트가 개념적으로 더 쉽고 간단하다. -p206
AspectJ는 언어 차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장이다. 스프링 AOP와 JBoss AOP가 제공하는 순수 자바 방식은 관점이 필요한 상황 중 80-90%에 충분하다. -p209
POJO (Plain Old Java Object) : 순수한 오래된 자바 객체. Java로 생성하는 순수한 객체.
최선의 시스템 구조는 각기 POJO (또는 다른) 객체로 구현되는 모듈화도니 관심사 영역(도메인)으로 구성된다. 이렇게 서로 다른 영역은 해당 영역 코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 통합한다. 이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있다. - p211
DSL (Domain-Specific Language)은 간단한 스크립트 언어나 표준 언어로 구현한 API를 가리킨다. -212p
12장 창발성
켄트 벡은 다음 규칙을 따르면 설계는 '단순하다'고 말한다.
- 모든 테스트를 실행한다.
- 중복을 없앤다.
- 프로그래머 의도를 표현한다.
- 클래스와 메서드 수를 최소로 줄인다.
위 목록은 중요도 순이다. - p216
리팩터링 단계에서는 소프트웨어 설계 품질을 높이는 기법이라면 무엇이든 적용해도 괜찮다. 응집도를 높이고, 결합도를 낮추고, 관심사를 분리하고, 시스템 관심사를 모듈로 나누고, 함수와 클래스 크기를 줄이고, 더 나은 이름을 선택하는 등 다양한 기법을 동원한다. -p217
TEMPLATE METHOD 패턴은 고차원 중복을 제거할 목적으로 자주 사용하는 기법이다. -p219
13장 동시성
동시성은 결합(coupling)을 없애는 전략이다. 즉, 무엇과 언제를 분리하는 전략이다. 스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접하다.
... 무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다. 구조적인 관점에서 프로그램은 거대한 루프 하나가 아니라 작은 협력 프로그램 여럿으로 보인다. - p226
(ex. 서블릿)
동시성 코드는 다른 코드와 분리하라. -p230
자바에서는 java.util.concurrent, java.util.concurrent.atomic, javautil.concurrent.locks를 익혀라. -p233
한정된 자원(Bound Resource) : 다중 스레드 환경에서 사용하는 자원으로, 크기나 숫자가 제한적이다. 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버퍼 등이 예따.
상호 배제(Mutual Exclusion) : 한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우
기아(Starvation) : 한 스레드나 여러 스레드가 굉장히 오랫동안 혹은 영원이 자원을 기다린다.
데드락(Deadlock) : 여러 스레드가 서로가 끝나기를 기다린다. 모든 스레드가 각기 필요한 자원을 다른 스레드가 점유하는 바람에 어느 쪽에 더 이상 진행하지 못한다.
라이브락(Livelock) : 락을 거는 단계에서 각 스레드가 서로를 방해한다. -p233
다중 스레드 프로그래밍 실행 모델 예시
- 생산자-소비자 : 한정된 자원. 둘 다 진행 가능한데 서로 기다림 (빈 공간 있을 때까지 - 대기열에 정보 있을 때까지)
- 읽기-쓰기 : 양쪽 균형 잡으면서 동시 갱신 문제 피하는 해법 필요.
- 식사하는 철학자들
코드에 보조 코드 (instrument)를 넣어 돌려라. 강제로 실패를 일으키게 해보라 - 240p
14장 점진적인 개선
[ 사례 ]
돌아가는 코드가 심하게 망가지는 사례는 흔하다. 단순히 돌아가는 코드에 만족하는 프로그래머는 전문가 정신이 부족하다. -p321
15장 JUnit 들여다보기
[ JUnit 리팩토링 예제 ]
16장 SerialDate 리팩터링
[ SerialDate 리팩토링 예제 ]
17장 냄새와 휴리스틱
[ 총정리~ 자주 찾아서 읽어보자! ]