프로그래밍 규칙
규칙 1 최대한 단순하게, 그러나 너무 단순하지 않게
프로그래밍을 쉽게 만드는 전략. 규칙 전체에 해당.
코드는 단순하게 해야한다. 단순성?
- 읽기 쉬움
- 동료가 쉽게 이해하는가
- 코드의 양이 얼마나 많은가
- 몇 가지 개념을 도입했는가
- 코드를 설명하는데 시간이 얼마나 걸리는가
하지만 너무 단순하게는 하지않는다.
문제를 풀지못하면 의미가없음
단순화하는법
- 문제를 단순하게, 문제의 한계점을 명시하고 제한을 하여 단순화한다.
- 알고리즘을 단순하게 한다
코드의 흐름을 놓쳐선 안된다 단순한 코드는 위에서 아래로 쭐 읽힌다. <- 개인적인 의견으로는 코드를 읽으면서 머리에 ? 가 떠오르면 안된다라는게 개인적인 생각.
규칙 2 버그는 전염된다
버그는 나중에 찾을 수록 고치기 힘들어진다 버그는 조기에 찾아내야한다.
대개 지속적인 자동화 테스트가 좋은 해답 하지만 자동화 테스트 구축 비용이 매우 큼.
상태를 유지하지 않는 코드는 테스트하기 쉽다.
테스트하기 좋은 코드는 작성하기도 쉽고 결국 단순한 코드가 된다. 테스트하기 좋게 코드를 짜려고 노력할것.
제거할 수 없는 상태는 감시할것
외부 테스트를 작성하기 어려우면 내부 테스트를 작성할것. assert 추가.
내 코드를 사용하는 동료를 믿지말고 코드가 잘못사용되기 어렵게 만들것.
잘못된 사용에 대해서 유효성을 검사하고 오류를 표기할것
- assert
- 오류 코드 반환
- throw
규칙 3 좋은 이름은 최고의 문서다
변수명, 명명 규칙을 확실하게 정하고 일관되게 해야한다.
읽는 사람을 생각하게 하지말것
규칙 4 일반화에는 세 가지 사례가 필요하다.
문제를 조건에 특화된 솔루션보다는 일반화(추상화) 하여 처리하고 싶을 수 있다.
추가적인 기능요청이 들어오는 경우, 모든 기능에 대응하는 작업을 만들어 버리고 싶을 수 있다.
섣부른 일반화는 나쁘고 이를 위해 세가지 사례가 나왔을때 비로소 그것들을 일반화하여 처리할것. 언급한 경우가 아닌경우는 일반화 하지말것. 알고 있는 사항에 대해서만 일반화 할것
일반화된 솔루션은 수정하기가 어렵다. ㅊ추상화를 완성하고나면 대안을 찾기조차 어려워진다.
규칙 5 첫 번째 최적화 교훈: 최적화하지 말라
최적화는 생각하지마라 일단 코드를 최대한 단순하게 만들면, 개선하는게 쉽다.
규칙 6 코드 리뷰의 세 가지 장점
서커펀치에서는 리뷰를 할때는 리뷰어가 코드를 보며 설명을 받는 식으로 진행을 한다.
코드 리뷰는 주니어 리뷰이 X 주니어의 리뷰어 구조는 리뷰를 하지않는것이 맞다.
- 버그를 발견할 수 있다
- 모든 사람이 코드를 더 잘 이해할 수 있다.
- 다른 사람들과 기꺼이 공유할 수 있는 코드가 작성된다.
규칙 7 실패 케이스를 제거하라
기능이나 인터페이스를 사용하는 사람들이 제 무덤을 파는 것을 아주 어렵게 만들어야한다.
예 : printf 의 설계는 매우 나빠서 컴파일러단에서 사용 인자에 대해서 검사
컴파일 단에서 감지 -> 실행단에서 감지
실수를 발견하는것도 좋지만 방지하는것이 더 좋다.
예시
16가지 인자가 들어가는 함수가 있다고 하자. 광범위하게 사용되어 인자 추가, 인자 삭제에 대해서 수정 리스크가 너무 크다. 또 사용에서 대부분 인자의 많은 케이스는 default만 넣으면된 다. 이런 실수를 어떻게 방지할까?
ver 1.
- param 클래스에 대해서필요한 인자만 Set -> Commit을 호출하게하여 이후 Set 함수 호출을 하지못하게하고 param.drawSphere() 호출
- 그러나 잘못사용시에 drawSphere 단에서 assert등으로 잡을 수는 있으나, 실수할 가능성을 설계에서 배제하지는 못함 ```c++ Params parmas; parmas.Set~(); parmas.Commit();
parmas.drawSphere();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ver 2.
drawSphere 클래스 분리
```c++
Params parmas;
parmas.SetX();
parmas.SetY();
parmas.SetZ();
Draw draw(parmas);
draw.drawSphere();
이를 좀 더 다듬어서 메서드 체이닝 방식으로 만들 수도 있다.
1
2
3
4
5
6
const Params parmas = Params.SetX()
.SetY()
.SetZ();
Draw draw(parmas);
draw.drawSphere();
장점 const로 고정화가능.
객체를 두개로 나누는것이 번거롭다.
Draw draw(parmas) = Params.SetX()
.SetY()
.SetZ();
draw.drawSphere();
서커펀치에서는 선택적 인수 지원을 위한 가변 인자 생성자를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct Draw
{
template <class... TT>
Draw(TT... args)
{ addArgs(args...); }
void addArgs()
{ ; }
template <class T, class... TT>
void addArgs(const T & arg, TT... remainingArgs)
{
addArg(arg);
addArgs(remainingArgs...);
}
void addArg(const XRay & xRay)
{ ; }
void addArg(const Pulse & pulse)
{ ; }
void addArg(const DrawStyle & drawStyle)
{ ; }
void drawSphere(
const Point & point,
double radius,
Color color)
{ ; }
};
// 사용
Draw draw(XRay(0.5), DrawStyle::Solid, Pulse());
draw.drawSphere(
character->getPosition() + Vector(0.0, 0.0, 2.0),
0.015,
Color(Red));
규칙 8 실행되지 않는 코드는 작동하지 않는다
성숙한 코드베이스 일수록 고아 코드가 존재한다 (로직적으로 실제 작동하지않는 코드)
이런 고아 코드는 발견하기 어렵다.
- 사용 케이스가 없는 함수는 제대로 작동하지않는다고 가정해야한다.
- 코드 변경시에 함수가 고아가 되는케이스는 그 함수를 제거하여야한다.
규칙 9 요약 가능한 코드를 작성하라
사람이 담을 수 있는 단기기억은 7 +- 2개정도된다.
이를 넘어서는 context는 매우 이해하기 힘들어진다. (물흐르듯 읽을 수 있는것이 좋은 코드에서의 세부 내용)
- 좋은 네이밍은 도움이 된다.
- 각 코드블럭의 세부내용은 요약한 적절한 주석은 도움이된다.
- 추상화는 이 단기기억 슬롯을 차지하게된다.
- 일반적으로 모두가 알고있는 패턴, 방식, 네이밍 사용 (이미 모두에게 박혀있는 장기기억은 단기기억 슬롯을 차지하지 않음)
규칙 10 복잡성을 격리하라
점진적으로 단순하게 할 수 있으나, 복잡성을 완전히 제거할 수는 없게된다.
이 맥락에서 제거불가능한 복잡성을 격리하는것은 유용하다.
내부 코드가 복잡하더라도 외부 인터페이스가 단순하면 크게 문제되지않는다.
복잡한 인터페이스와 복잡한 상호작용은 망할 수 있다.
새로운 기능 추가가 여러 곳의 코드를 손대게 만든다면 나쁜 신호라고 할 수 있다.
규칙 11 두 배 좋은가
모든 프로젝트는 각자의 아키텍처에서 필연적인 한계에 부딪힌다.
이 때 문제를 해결하기위한 방법
- 무시
- 미세조정
- 리팩터링(재작업)
기준점 : 대규모 리팩터링을 함으로 두배로 좋은가?를 따질것
규칙 12 큰 팀에는 강력한 컨벤션이 필요하다
ㅇㅇ
규칙 13 산사태를 일으킨 조약돌을 찾으라
디버깅에서의 코드개선
- 증상을 원인 가까이에 둔다.
- 여러 복합적 요소가 합쳐져서 발생하는 버그가 아닌 하나의 요소로 발생하는 버그가 쉽게 파악가능
- 시간을 거슬러가기 쉽게 만들면 쉬워진다. (재현가능성)
순수 함수는 파악하기가 용이하다.
상태를 가지고 있는 함수는 파악이 어렵다.
규칙 14 네 가지 맛의 코드
- 쉬운문제 : 단순함 / 복잡함
- 어려운 문제 : 단순함 / 복잡함
훌륭한 프로그래머는 쉬운문제도 단순하게 짜고, 어려운 문제 또한 단순하게 짠다.
규칙 15 잡초를 뽑으라
고치기 쉬우면서도 무시하기 쉬운 작은 문제.
- 주석과 함수가 일치하지않음
- 주석이 모호함
- 오탈자
- 변수 명명규칙
- 형식화
문제를 안전하게 해결할 수 있음 -> 잡초
기능변경은 잡초가 아님
규칙 16 코드가 아닌 결과에서부터 작업하라
- 주어진 조건 -> 만들어야하는것 순으로 갔을때 기술적으로 생각하는 경향이 있다. 결과물에서부터 시작해라.
규칙 17 더 쉽게 해결되는 큰 문제도 더러 있다
대부분의 경우 특정한 솔루션은 일반적인 솔루션보다 구현하기가 쉽다.
극히 낮은 빈도로 특정한 문제보다 일반적인 문제가 더 쉽고 간단하게 해결되는 경우가 있다.
이런 케이스의 공통점은 극단적인 관점의 변화가 필요했고, 이 새로운 사고방식으로 단순한 솔루션이 가능하게되었음
규칙 18 코드가 스스로 이야기하게 하라
주석은 관리되지않은 코드와 같기에 시간에 따라 사실과 달라질수도있다 -> 이를 피하는 방법은 assert사용
좋은 주석 : 코드에서 바로 알기 어려운 내용을 독자에게 설명해주는 주석
좋은 주석은 코드를 훨씬 이해하기 쉽게한다.
규칙 19 평행 재작업
인터페이스가 변경되는 수준의 시스템을 갈아엎을때 작업 방법
- 변경되는 시스템과 기존 시스템을 병렬로 놓는다.이를 왔다갔다 할 수 있는 Adapter를 하나 만든다.
- 해당 시스템을 관리하는 플래그를 하나놓고 빌드로 구분할 수 있도록 브랜치 반영
- 신규시스템 적용 개발자를 점차 확장한다.
- 기존 인터페이스 변경 -> 파이썬 작업
규칙 20 계산하라
- ex) 자동화를 할지 말지 : 수작업의 시간소모 <> 자동화 작업에 들어가는 시간
그 외의 문제 해결을 위한 타당성 확보를 위해서 계산을 하여야함
- 네트워크 사용량 등등
- 이런 계산이 어려울때는 한계 조건을 설정하고 시작한다.
규칙 21 때로는 못질을 해야 한다
모든 문제에 우아한 솔루션이 있는것은 아니다.
모든 함수 호출 로직에 함수 인자를 변경해야할 수도 있다.
이럴때 아래 기술이 도움될 수 있다.
- 파이썬 코드 작성
- 정규식 사용
결론 자신의 규칙을 만들라
지금껏 이런 코드를 어떻게 짜는게 맞는지 방법론에 대한 책들은 맘에 드는게 크게 없었는데 (code complete는 아직 다 못봄) 이번 책은 꽤 감명깊게 봤습니다
결론은 코드를 깔끔하게 짜라. 로 되는데
여기에 여러가지 현실적인 제약에서 구체적으로 어떤 액션을 취하도록 해야하는지 가이드가 좀 있는게 좋았습니다. 예시도 실제 업무에서 충분히 발생할만한 일들만 있어서 굉장히 공감하면서 봤습니다.
저같은 경우는 책 내용 전체가 모두 회사 생활하면서 실제로 겪고, 고민했고, 제 나름대로 이게 맞는 방향이다라고 생각했던 내용들이 들어있다보니 경험으로 쌓고, 느낌적으로 체화된 것들이 글로 쓰여졌다라는 인상을 많이 받았습니다.