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. 먼저 순서 종속성이 없는 코드를 쓰려고 노력
  2. 다음으로 종속성이 명백한 코드를 쓰려고 노력 (지침 1~3)
  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. 겹친다면 관련 명령문이 더 잘 묶이도록 재조직할 것
  • 묶고 나니 강하게 관련된 명령문들이 앞뒤 명령문과는 의미 있는 관계가 없다면, 그 덩어리를 별도 루틴으로 리팩터링하는 것을 고려

results matching ""

    No results matching ""