14장: 순차적 코드 구성하기
14장: 순차적 코드 구성하기
14.1 순서가 중요한 명령문
핵심 개념: 종속성(dependency)
순서를 정하기 가장 쉬운 명령문
- 순서가 중요한(order counts) 명령문
data = ReadData();
results = CalculateResultsFromData( data );
PrintResults( results );
- 데이터를 읽어야 결과를 계산할 수 있고, 계산해야 출력할 수 있음
- 이 예제의 밑바탕 개념이 종속성 — 세 번째 문장은 두 번째에, 두 번째는 첫 번째에 종속됨
- 여기서는 루틴 이름만 읽어도 종속성이 명백함
종속성이 덜 명백한 경우
revenue.ComputeMonthly();
revenue.ComputeQuarterly();
revenue.ComputeAnnual();
- 분기 매출 계산은 월 매출이 이미 계산됐다고 가정함
- 회계 지식이나 상식이 있어야 순서를 짐작할 뿐, 코드만 읽어서는 종속성이 드러나지 않음
종속성이 문자 그대로 숨겨진(hidden) 경우
ComputeMarketingExpense
ComputeSalesExpense
ComputeTravelExpense
ComputePersonnelExpense
DisplayExpenseSummary
ComputeMarketingExpense()가 다른 루틴들이 데이터를 넣을 클래스 멤버 변수를 초기화한다면, 반드시 제일 먼저 호출해야 함- 매개변수가 없으니 클래스 데이터를 쓰겠거니 짐작만 할 뿐, 코드를 읽어서 확실히 알 방법이 없음
When statements have dependencies that require you to put them in a certain order, take steps to make the dependencies clear.
명령문에 순서 종속성이 있다면 그것을 분명히 드러내는 조치를 하라 — 지침 5가지를 소개
지침 1 - 종속성이 명백해지도록 코드를 구성하라
Organize code so that dependencies are obvious
ComputeMarketingExpense()가 멤버 변수 초기화까지 하는 것은 피하자- 이름상 다른 Compute 루틴들과 똑같아 보이는데 왜 하필 마케팅 루틴이 초기화를 담당하는가?
- 마케팅 비용을 계산하는 루틴에 초기화가 포함되는게 문제
- 별도의
InitializeExpenseData()루틴을 만들면, 이름 자체가 “다른 expense 루틴들보다 먼저 불려야 한다”는 명백한 표시가 됨- 코드 본문을 옮기자 - 구성 변경
지침 2 - 종속성이 명백해지도록 루틴 이름을 지어라
Name routines so that dependencies are obvious
ComputeMarketingExpense는 마케팅 비용 계산 이상의 일(멤버 데이터 초기화)을 하므로 잘못된 이름- 루틴을 추가로 만들기 싫다면 최소한 하는 일을 전부 설명하는 이름을 붙여야 함:
ComputeMarketingExpenseAndInitializeMemberData()
You might say it’s a terrible name because it’s so long, but the name describes what the routine does and is not terrible. The routine itself is terrible!
- 이름이 길어서 끔찍한 게 아님 — 이름은 루틴이 하는 일을 정확히 설명할 뿐, 끔찍한 것은 루틴 자체
지침 3 - 루틴 매개변수로 종속성을 드러내라
Use routine parameters to make dependencies obvious
- 루틴 사이에 데이터가 전달되지 않으면 같은 데이터를 쓰는지조차 알 수 없음
- 모든 루틴이
expenseData를 매개변수로 받게 하면 “같은 데이터를 다루고 있고, 실행 순서가 중요할 수 있다”는 단서가 생김
InitializeExpenseData( expenseData )
ComputeMarketingExpense( expenseData )
...
DisplayExpenseSummary( expenseData )
- 더 나은 방법:
expenseData를 입력받아 갱신본을 반환하는 함수로 바꾸면 순서 종속성이 한층 분명해짐
expenseData = InitializeExpenseData( expenseData )
expenseData = ComputeMarketingExpense( expenseData )
...
- 데이터는 반대로 순서가 중요하지 않음을 알려주기도 함
- 네 루틴이 각자 다른 데이터(
marketingData,salesData, …)를 쓰면 호출 순서가 무관하다는 암시 - 마지막
DisplayExpenseSummary가 네 데이터를 모두 받으면, 앞의 넷 이후에 실행돼야 한다는 것을 알 수 있음
- 네 루틴이 각자 다른 데이터(
지침 4 - 불명확한 종속성은 주석으로 문서화하라
Document unclear dependencies with comments
우선순위가 있음
- 먼저 순서 종속성이 없는 코드를 쓰려고 노력
- 다음으로 종속성이 명백한 코드를 쓰려고 노력 (지침 1~3)
- 그래도 종속성이 충분히 드러나지 않는다고 걱정되면 그때 주석으로 문서화
- 불명확한 종속성의 문서화는 “코딩 가정(assumption)의 문서화”의 한 측면
- 주석보다 코드 기법(지침 1~3)이 우선. 다만 엄격하게 통제되는 코드라 고칠 수 없는 사정이 있다면 문서화로 코드의 약점을 보완함
지침 5 - 어설션이나 오류 처리 코드로 종속성을 검사하라
Check for dependencies with assertions or error-handling code
- 충분히 중요한 코드라면 상태 변수와 어설션/오류 처리 코드로 순차 종속성을 검사함
- 생성자에서
isExpenseDataInitialized = false로 초기화 InitializeExpenseData()에서true로 설정expenseData에 의존하는 각 함수가 작업 전에isExpenseDataInitialized를 검사- 종속성이 광범위하면
isMarketingExpenseComputed같은 변수가 더 필요할 수도 있음
- 생성자에서
- 트레이드오프: 새 변수, 새 초기화 코드, 새 검사 코드 자체가 새로운 오류 가능성임
- 이 기법의 이점 vs 추가 복잡성과 2차 오류 증가 가능성을 저울질해야 함
14.2 순서가 중요하지 않은 명령문
핵심 원리: 근접성의 원리(Principle of Proximity)
The guiding principle is the Principle of Proximity: Keep related actions together.
근접성의 원리: 관련된 작업은 가까이 둘 것
위에서 아래로 읽히는 코드 만들기
As a general principle, make the program read from top to bottom rather than jumping around.
- 일반 원칙: 프로그램이 여기저기 널뛰지 않고 위에서 아래로 읽히게 만들 것. 전문가들은 top-to-bottom 순서가 가독성에 가장 크게 기여한다는 데 동의함
- 런타임 제어 흐름이 위에서 아래로 흐르는 것만으로는 부족 — 읽는 사람이 필요한 정보를 찾으러 프로그램 전체를 뒤져야 한다면 재조직해야 함
나쁜 예: 널뛰는 코드
MarketingData marketingData;
SalesData salesData;
TravelData travelData;
travelData.ComputeQuarterly();
salesData.ComputeQuarterly();
marketingData.ComputeQuarterly();
salesData.ComputeAnnual();
marketingData.ComputeAnnual();
travelData.ComputeAnnual();
salesData.Print();
travelData.Print();
marketingData.Print();
marketingData가 어떻게 계산되는지 알려면 마지막 줄에서 첫 줄까지 모든 참조를 역추적해야 함- 그 사이의 모든 줄을 읽고 생각해야 계산 과정을 파악할 수 있음. 실제 시스템 코드는 이 예제보다 훨씬 복잡함
좋은 예: 위에서 아래로 읽히는 순차적 코드
MarketingData marketingData;
marketingData.ComputeQuarterly();
marketingData.ComputeAnnual();
marketingData.Print();
SalesData salesData;
salesData.ComputeQuarterly();
salesData.ComputeAnnual();
salesData.Print();
TravelData travelData;
...
좋아진 점 세 가지
- 각 객체에 대한 참조가 가까이 모임 — “지역화(localized)”됨
- 객체가 “살아 있는(live)” 코드 줄 수가 짧아짐 (live time은 10.4절 참고)
- 가장 중요한 점: 코드가 마케팅/영업/출장 데이터별로 별도 루틴으로 분해될 수 있어 보임
- 널뛰는 버전에서는 그런 분해가 가능하다는 힌트조차 없었음
관련된 명령문 묶기
Put related statements together.
관련되었다는 것의 기준 — 다음 중 하나면 관련된 것
- 같은 데이터를 다룸
- 비슷한 작업을 수행함
- 순서대로 실행되어야 하는 종속 관계임
잘 묶였는지 확인하는 방법: 루틴을 출력해서 관련된 명령문들에 상자를 그려봄
- 잘 구성된 코드 → 상자들이 겹치지 않음 (중첩은 가능) — 그림 14-1
- 잘못 구성된 코드 → 상자들이 겹침 — 그림 14-2. 겹친다면 관련 명령문이 더 잘 묶이도록 재조직할 것
- 묶고 나니 강하게 관련된 명령문들이 앞뒤 명령문과는 의미 있는 관계가 없다면, 그 덩어리를 별도 루틴으로 리팩터링하는 것을 고려