회사에서 책을 읽을 기회가 있었다.
물론 도서 자체는 회사에서 구매해 준 것은 아니고.. 집에 모셔놓았던 것인데, 3장까지만 읽다가 1년을 모셔둔 책을 드디어 다시 꺼내어 본 것이다. 지금까지 전공 서적을 정리한 것은 학부생때 이후로 거의 처음이 아닐까 싶다.
기회가 된다면 학부생때 쓰던 칼라펜과 포스트잇의 봉인을 풀고 다시 책을 더럽혀도 좋을 그런 도서 같다.
책보다 게임이 더 좋은 내게 꽤 맞춤형(?) 지도 교사가 온 것 같아 한편으로는 즐겁다. (다른 한편은 노코멘트 하겠다)
1. 깨끗한 코드
1.1. 나쁜 코드? 좋은 코드?
- 이 책의 전반적인 내용은 깨끗한 코드 VS 나쁜 코드에 대한 내용
- 르블랑의 원칙
- 나중은 결코 오지 않는다 =
나중에 수정해야지~는 없다 - 물론 14장 점진적인 개선에서는 우선 개발하고 리팩토링 개념을 소개함
- 나중은 결코 오지 않는다 =
1.2. 나쁜 코드가 초래하는 대가
- 유지보수/업무 파악(화자는 해독이라 표현)에 들어가는 생산성에 문제 → 회사 망함
1.3. 깨끗한 코드 (여기서는 나쁜 코드의 상반되는 단어)
- 보기에 즐거운, 효율적, 한가지를 잘하는 집중
- 가독성, 명쾌
- 테스트와 다른 사람이 고치기 쉬운
- 주의 깊게 작성한 코드
- 중복을 피하라, 한 기능만 수행하라
- 짐작한대로, 놀랄 일이 없어야 한다
1.4. 보이스카우트 규칙
- 언제나 처음 왔을때보다 더 깨끗하게
- 14장 점진적인 개선을 소개
2. 의미 있는 이름
2.1. 의도를 분명히 밝혀라
- 함축성 → 변수/함수 등 이름을 지을 때는 유의미한 정보를 제공
- 별도의 주석이 없어도 의도가 드러나도록
2.2. 그릇된 정보를 피하라
- 약어를 피하라
- 내가 생각하고 작성한 약어가 다른 개발자도 동일한 약어로 바라볼 것이라는 것은 오산
- 유사한 개념은 유사한 표기법을 사용해라
- 표현에는 일관성을 유지해라
2.3. 의미 있게 구분하라
- 불용어 쓰지 마라
- 불용어 → 없어도 큰 의미가 없는 것들
- ex) moneyAmount VS money, nameString VS name …
2.4. 발음하기 쉬운 이름을 사용하라
- 쉬운 단어와 이해 가능한 이름을 만들어라
2.5. 검색하기 쉬운 이름을 사용하라
2.6. 인코딩을 피하라
- IDE 를 활용하자
- 접두 접미사를 인터페이스나 구현클래스에 활용하는 것은 지양 → 하지만 IShapeFactory 보다는 ShapeFactoryImpl 이 차라리 좋다 (꼭 써야한다면 의미있게~)
2.7. 기억력을 자랑하지 마라
- 과시하려고 어려운 단어 괜히 꺼내 쓰지 말고 그냥 명료하게 해라
2.8. 클래스 이름
- 명사/명사구 추천
- 불용어와 동사 비추천
2.9. 메서드 이름
- 동사/동사구 추천
2.10. 기발한 이름은 피하라
- 의도를 분명히하고 농담(장난)은 피해라
2.11. 한 개념에 한 단어를 사용하라
- 똑같은 기능이면 명칭이 같아야 하지 않냐
2.12. 말장난 하지마라
- 한 단어를 여러가지 개념으로 재활용하지 마라
2.13. 해법 영역에서 가져온 이름을 사용해라
- 코드를 읽는 독자도 결국 같은 개발자다 → 너무 어려운 도메인 용어를 사용하기 보다는 그냥 기술용어를 써라
- ex) Job Queue 에 jobQueue
2.14. 문제 영역에서 가져온 이름을 사용해라
- 적절한 용어가 없으면 도메인 지식을 물어봐서 지어라 → 결국 수정할 개발자도 도메인 지식은 물어볼거다
- DSL 부분 참고
2.15. 의미 있는 맥락을 추가하라
- 접두/접미를 추가해 의미를 좀 더 분명히 하기 -> name 앞에 addr 접두를 붙인 경우
- 메소드에 의미를 주어 쪼개는 방법도 권장
2.16. 불필요한 맥락은 없애라
- 불필요한 접두/접미 빼라
- 짧은 이름이 긴 이름보다는 좋다
3. 함수
3.1. 작게 만들어라
- 들여쓰기가 너무 많으면 읽기 불편하다
- 가능한 짧고 단순하게
3.2. 한 가지만 해라!
- 함수는 한가지만 잘 기능하는 게 좋다
3.3. 함수 당 추상화 수준은 하나로!
- 내려가기 규칙
- 함수내에는 같은 수준의 추상화, 깊이 들어갈 수록 보다 구체화된 수준의 추상 수준이 표현될 수 있다
- 이 부분은 추상 단계에 대한 이해와 고민이 필요해 보임
3.4. Switch 문
- 길이도 길어지고, 기능을 분기하거나 객체를 생성하는데 사용하면 너무 많은 기능을 부담하게 되어 문제
- 다형성을 이용해서 숨기기 권장
3.5. 서술적인 이름을 사용하라!
3.6. 함수 인수
- 입력 인수가 적을 수록 의미는 명확해진다
- 단항형식
- 질문형 - existFile(String fileName)
- 변환형 - fileOpen(String fileName) → InputStream
- 이벤트 함수는 주의해서 사용해라
- 플래그 인수 ← 권장하지 않는다
- 이항함수
- equals 나 point 같은 누가봐도 두 개가 필요한 것이 아니라면 피하라
- 인수객체
- 인수를 개념으로 묶을 수 있다면 묶어보자 → 함수 인수가 줄어든다
3.7. 부수 효과를 일으키지 마라!
- 시간적인 결합과 순서 종속성 ← 한 가지 기능에만 충실하자
3.8. 명령과 조회를 분리하라!
3.9. 오류 코드보다 예외를 사용하라!
- 오류 코드 보다는 try-catch 쓰자
- try-catch 쓸 거면 좀 한 군데서 써라 ← 안 이쁘다 (핸들러 같은거 만들어 쓰던 메소드로 뽑던 해라)
3.10. 반복하지 마라!
- 중복되는 코드는 제거해라
3.11. 구조적 프로그래밍
- 하나의 함수에서 return break continue 는 최대한 적게 써라
- 함수가 충분히 작다면, return break continue 가 여러번 나와도 좋다
3.12. 함수를 어떻게 짜죠?
- 글쓰기다 → 초안을 작성하면 인수도 많고 중복도 많을 테지만 조금씩 다듬는 것 어떠냐
4. 주석
4.1. 주석은 나쁜 코드를 보완하지 못한다
- 코드에 주석을 추가하는 이유는 품질이 나쁘기 때문이다
4.2. 코드로 의도를 표현하라!
4.3. 좋은 주석
- 법적인 주석 <- 계약/ 저작권 같은 명시
- 정보 제공 <- 왠만하면 코드로 표현해라지만 도메인 지식이 필요한 경우, 도메인 지식을 제공하면 어떨까?
- 의미를 명료하게 밝히는 주석 <- 변경하지 못하는 코드를 사용할때, 의미를 설명하면 좋을 수 있다
- TODO 주석
4.4. 나쁜 주석
- 주절거리는 주석
- 불필요한 정보 제공
- 같은 이야기를 반복하는 주석
- 오해할 여지가 있는 주석
- 살짝 잘못된 정보 는 위험하다
- 의무적으로 다는 주석
- 불필요한 javadocs 는 문제
- 이력을 기록하는 주석
- 형상관리 툴을 써라
- 있으나 마나한 주석
- 무서운 잡음
- 복붙 문제
- 위치를 표시하는 주석
- 닫는 괄호에 다는 주석
- 주석으로 처리한 코드
- html 주석
- 특히 안드로이드 자동완성…
- 전역정보
- 정보는 근처의 내용만 전달
- 너무 많은 정보
- TMI
- 모호한 관계
- 주석 자체가 논쟁거리나 설명이 필요한 내용이 되어서는 본말전도
- 비공개 코드에서 javadocs
- 때로는 알고리즘이 난해하면 간단한 설명을 적어주는 것도 좋다
5. 형식 맞추기
5.1. 형식을 맞추는 목적
- 유지보수 용이
- 확장성
5.2. 적절한 행 길이를 유지하라
- 신문 기사처럼 작성하라
- 첫 문단은 내용을 요약한다 → 추상화 수준을 관리
- 이름만 봐도 무슨 내용을 말하려는지 알 수 있도록 분명히 한다 ← 2장
- 세로 밀집도
- 서로 밀접한 코드 행은 붙여 두자
- 수직 거리
- 변수 선언
- 최대한 사용하는 곳 근처에 선언해라
- 인스턴스 변수
- 얘는 시작과 동시에 선언해라 ← C++ 가위 규칙
- 종속 함수
- 콜리는 콜러 근처에 두자
- 개념적 유사성
- 유사한 개념끼리 모아두자
- 변수 선언
5.3. 가로 형식 맞추기
- 가로 길이 맞추기
- 가로 정렬
- 들여쓰기
- 가짜 범위
- ; 만 가볍게 찍은 반복문이나 분기문
5.4. 팀 규칙
6. 객체와 자료 구조
6.1. 자료 추상화
- private 변수에 무작정 get set 을 추가하지 말자
- 구현을 모른채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다
6.2. 자료/객체 비대칭
- 새로운 자료 타입이 필요한 경우 → 클래스와 객체 지향
- 새로운 함수가 필요한 경우 → 절차적 코드와 자료구조
6.3. 디미터 법칙
- 모듈은 자신이 조작하는 객체의 속사정을 몰라야만 한다
- 기차 충돌
- 체이닝은 조잡해 보이니 왠만하면 피하라
- 빈즈는 모든 자료구조에 get set 을 강요하기에 예외
- 잡종 구조
- 객체면서 자료구조인 이런 구조는 이상하다
6.4. 자료 전달 객체
- DTO 공개 변수만 있고 함수가 없는, DB 통신이나 소켓 메시지 등을 분석할때 사용하는 객체
- 활성 레코드
- DTO에 save 와 find 같은 탐색을 추가할 수도 있다
7. 오류 처리
7.1. 오류 코드보다 예외를 사용하라
- 오류 코드로 제어하기 보다 그냥 예외를 터트려라 ← 논리오류와 뒤섞여서 헷갈린다
7.2. Try-catch-finally 문부터 작성하라
- 어떤 의미에서 try 블록은 트랜잭션과 비슷하다
- 강제로 예외를 일으키는 TC를 먼저 작성한 후 코드를 작성해보자
7.3. 미확인 예외를 사용하라
7.4. 예외에 의미를 제공하라
- 정보를 함께 전달하자
7.5. 호출자를 고려해 예외 클래스를 정의하라
- 감싸기 기법
- 라이브러리를 사용하는 경우, API 설계 방식에 얽매이지 않도록
7.6. 정상 흐름을 정의하라
- 특수 사례 패턴 → 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식
7.7. null 을 반환하지 마라
- null을 던져서 콜러에게 일을 떠맡기는 행위는 위험하다
7.8. null 을 전달하지 마라
- 고려된 스펙이 아니라면 함수에 null 을 인자로 받지마라
8. 경계
8.1. 외부 코드 사용하기
- map 계열을 사용할때 삭제(clear) 추가/수정(put)이 가능하므로 여기저기 넘기는 것은 경계
8.2. 경계 살피고 익히기
- 외부 패키지 테스트의 책임은 없으나 사용하는 부분의 테스트는 필요
- 학습 테스트 → 간단한 테스트 케이스를 작성해 외부 코드를 익히는 방법
8.3. log4j 익히기
8.4. 학습 테스트는 공짜 이상이다
- 학습 테스트는 패키지가 예상대로 도는지 확인한다
- 새 버전의 호환 이슈도 학습 테스트가 포착한다
8.5. 아직 존재하지 않는 코드를 사용하기
- 아직 개발되지 않은 코드에 대해 API 명칭과 인수만 공유받고 개발 진행
9. 단위 테스트
9.1. TDD 법칙 세가지
- 실패하는 단위 테스트를 작성할때까지 실제 코드를 작성하지 않는다
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다
- 현재 실패하는 테스트를 통과하는 정도로만 실제 코드를 작성한다
9.2. 깨끗한 테스트 코드 유지하기
- 테스트 코드는 실제 코드 못지 않게 중요하다
- 테스트 코드가 제공하는 것
- 유연성
- 유지보수성
- 재사용성
9.3. 깨끗한 테스트 코드
- 가독성이 중요하다
- 빌드 오퍼레이트 체크 패턴
- given 테스트 자료를 만든다
- when 테스트 자료를 조작한다
- then 조작한 결과가 예상대로인지 확인한다
- 이중 표준
- 테스트 코드도 품질이 좋아야 하나 실제 코드만큼 효율적일 필요는 없다
- 의미만 안다면 규칙 위반이 조금 허용된다 (p 163)
9.4. 테스트당 assert 하나
- 함수마다 assert 문이 하나면 결론이 하나라는 의미라 명확하고 좋다
- 하나의 테스트당 하나의 개념만 테스트 하자
9.5. F.I.R.S.T
- 빠르게(fast)
- 빨라야 한다
- 독립적으로(Independent)
- 테스트는 다른 테스트에 종속되면 안된다
- 반복가능하게(Repeatable)
- 어떤 환경이든 반복 가능해야 한다
- 자가검증하는(Self-validating)
- True/False 로 떨어져야 한다
- 적시에(Timely)
- 실제 코드 작성 직전에 먼저 작성해야 한다
- 작성 후에 테스트 코드를 짜려하면 어렵다
10. 클래스
10.1. 클래스 체계
- 캡슐화
10.2. 클래스는 작아야 한다
- 함수와 마찬가지로 작아야 한다
- SRP
- 응집도가 높아지도록 적절히 함수 분리와 클래스를 쪼개야 한다
10.3. 변경하기 쉬운 클래스
- 하나의 수정에 다른 기능도 위험을 떠 안을 필요가 없다
- 너무 많은 일을 하는 클래스 → 닫힌 클래스 집합으로 분리 해보자
11. 시스템
11.1. 도시를 세운다면?
- 큰 그림을 모르더라도 개개인의 구성요소는 맞물려 돌아간다 ←
저스티스 리그
11.2. 시스템 제작과 시스템 사용을 분리하라
- 준비과정(의존성 연결)과 런타임 로직은 분리되어야 한다
- 초기화 지연, 계산 지연 기법은 본의 아니게 SRP를 깰 수 있다
- get 에서 null 인 경우 init 또는 set 을 처리해버리는 경우
- Main 분리
- main (setup) → application 으로 객체 생성은 main 이 하고 application 은 사용만
- 팩토리
- 객체가 생성되는 시점이 main 이 모르는 경우, main 은 factory 만 만들어 둔다
- application 은 factory를 사용하여 객체를 생성하므로 상세 생성 내용을 알지는 못하게 만드는게 핵심
- 의존성 주입
- 객체가 맡은 책임을 다른 객체에게 일임하면서 제어를 역전함 → 담당자로 특수 컨테이너
- 권장하는 세 가지 *
- 생성자 주입 ← 사내에서 서비스/컨트롤러에 사용하는 방식
- setter 주입
- 인터페이스 주입 ← 사내 서비스와 serviceImpl 구현체
11.3. 확장
- 처음부터 올바르게 ← 이건 미신이다!
- 횡단(cross-cutter) 관심사
- 영속성
- 관점(aspect)
- AOP 에서 어떤 관점에서 개발할 것인가.
- 보안이면 보안, 출납이면 출납, 물류면 물류만 유통이면 유통만 특정 시스템에서 해당 팀이 바라보는 주요 관점
- 문제는 이런 관심사간 세부 구현 모듈끼리는 중복이 발생할 수 있다
11.4. 자바 프록시
- 단순한 상황에 적합
- 단점
- 깨끗한 코드 작성이 어렵다
- 시스템 단위로 실행 지점 을 명시하는 매커니즘을 제공하지 않는다
11.5. 순수 자바 AOP 프레임 워크
- POJO
- 간단한 자바 객체
- 상속을 강제하지 않아야 하고
- 인터페이스 구현을 강제하지 않아야 하고
- 어노테이션 사용을 강제하지 않음
- 순수하게 도메인에 초점을 둔다
- 간단한 자바 객체
11.6. AspectJ 관점
- 관심사를 관점으로 분리하는 언어
11.7. 테스트 주도 시스템 아키텍처 구축
- 건축과 달리 구조 수정이 용이 하므로 관점만 잘 분리된다면 BDUF 가 필요하지 않다
- BDUF (Big Design Up Front)
- 개발전 디자인을 먼저 ←
약간 워터풀
- 개발전 디자인을 먼저 ←
11.8. 의사 결정을 최적화하라
- 때로는 가능한 마지막 순간까지 결정을 미루는 방법이 최선일 수 있다
11.9. 명백한 가치가 있을때 표준을 현명하게 사용하라
- 최적화된 방법과 표준을 활용하라
- 그러나 너무 낡고 어울리지 않는, 과장된 표준에는 집착하지 마라
11.10. 시스템은 도메인 특화 언어가 필요하다
- DSL (Domain Specific Language)
- 도메인 특화 언어는 관련 특정 분야에 최적화된 프로그래밍 언어
- DSL은 해당 분야 또는 도메인의 개념과 규칙을 사용
- 좋은 DSL 은 의사소통 간극을 줄여 준다
12. 창발성
- 창발성 (創發)
- 창발 또는 떠오름 현상은 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상 (
??? 이러면 도리어 이슈 아닌가???)
- 창발 또는 떠오름 현상은 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상 (
12.1. 창발적 설계로 깔끔한 코드를 구현하라
- 단순한 설계 규칙
- 모든 테스트를 실행한다
- 중복을 없앤다
- 프로그래머 의도를 표현한다
- 클래스와 메서드 수를 최소로 줄인다
12.2. 모든 테스트를 실행하라
- 설계 의도대로 돌아가는 시스템을 내놓아야 한다
- 일단 돌아가야 한다 ←
그래야 검증을 하지 - 테스트가 가능한 시스템을 구현하다 보면 자연스레 코드는 점차 클린해질 것이다(?)
12.3. 리팩터링
- TC를 작성하고 점진적으로 리팩토링 해라
- 코드 정리로 생기는 리스크는 TC가 있다
- 그만큼 TC를 잘 만들어 둬야 하지 않나…
12.4. 중복을 없애라
- 적은 줄이라도 중복을 줄이면 리팩터링이 보인다!
12.5. 표현하라
- 자신이 이해하는 코드를 짜는 것은 쉽다
- 개발자의 의도를 분명하게 하라
- 의도를 표현하는 방법
- 좋은 이름을 사용하라
- 함수, 클래스 크기를 가능한 줄여라
- 표준 명칭을 사용해라
- UT 를 꼼꼼히 작성해라
12.6. 클래스와 메서드 수를 최소로 줄여라
- 앞서 12.4, 12.5 를 너무 사용하면 너무 많아지니까 이건 좀 적당히 해라(??)
13. 동시성
13.1. 동시성이 필요한 이유?
- 결합을 없애는 전략이다
- 무엇과 언제를 분리하는 전략
- 타당한 생각
- 다소 부하를 일으킨다
- 복잡하다
- 동시성 버그는 재현하기 어렵다
- 동시성을 구현하려면 흔히 근본적인 설계 전략을 고민해야 한다
13.2. 난관
- Thread → 전위연산자 후위연산자 예제
13.3. 동시성 방어 원칙
- SRP
- 동시성 코드는 다른 코드와 분리하라
- 자료 범위를 제한하라
- 자료를 캡슐화하라. 공유 자료를 최소화해라
- 자료 사본을 사용하라
- 애초에 사본을 이용하여 본 자료를 공유하지 않는 방법도 있다
- 스레드는 가능한 독립적으로 구현하라
- 독자적인 스레드로, 가능하면 다른 프로세서에서 돌려도 괜찮도록 독립적인 단위로 분할하라 (???)
13.4. 라이브러리를 이해하라
- 스레드 환경에 안전한 컬렉션 (자바 over 5 버전)
- 서로 무관한 작업을 수행할때는 executor 프레임워크를 추천
- 가능하다면 스레드 blocking 이 되지 않는 방법을 써라
- 일부 라이브러리는 스레드 환경에서 안전하지 않다!
- concurrent atomic locks ← (???)
13.5. 실행 모델을 이해하라
- 생산자-소비자 (Producer-consumer)
- 한정된 대기열(buffer)에 생산자가 작업을 올리고 대기하던 소비자가 구매하는 방식
- 잠자는 이발사
- 임계구역에 대한 상호배타 처리를 하거나 운영체제에서는 세마포어를 사용해 해결한다
- 읽기-쓰기 (Read-write)
- 공유 자원을 읽기 스레드와 쓰기 쓰레드가 각각 사용할 때 처리율 문제
- 처리율
- 단위 시간당 몇개의 작업(프로세스)을 처리하였는지
- 상호 배제를 너무 강조할 경우, 처리율에 문제가 발생
- 처리율만 고집하면, 상대적으로 작업 비용이 큰 작업은 기아 상태에 빠짐
- 에이징 같은 우선순위를 고려한 해결책이 필요
- 식사하는 철학자들 (Dining philosophers)
- 철학자들은 원형 식탁에 앉아 생각하거나 식사하는데, 자기 앞의 포크를 남과 공유하는 상황
- 교착상태
13.6. 동기화화는 메서드 사이에 존재하는 의존성을 이해하라
- synchronized 공유 객체 하나에는 메서드 하나만 사용하라
13.7. 동기화하는 부분을 작게 만들어라
- lock 은 반드시 임계구역에만 걸자
13.8. 올바른 종료 코드는 구현하기 어렵다.
- 동시성이 예상되는 시스템은 종료 코드도 고민하자
13.9. 스레드 코드 테스트 하기
- 말이 안되는 실패는 잠정 스레드 문제
- 일회성이 아닐 수 있다.
- 다중 스레드 이전에 돌아가는 코드부터 만들어라
- 동시성 테스트와 기본 테스트는 분리해야 한다
- 다중 스레드를 쓰는 코드 부분을 상황에 맞게 조정할 수 있게 해라
- 프로세서 수보다 많은 스레드를 돌려봐라 ← 톰캣의 경우 work thread 보다 많이 동시 요청 해봐라
- JMeter 같은 툴이 있다
- 강제로 실패를 일으켜 봐라
반응형