본문 바로가기

코딩 스탠다드/코드 컴플리트

[코드 컴플리트 2] 7장 고급루틴

루틴 : 한가지 목적을 위해서 호출 할 수 있는 개별 메서드나 프로시저.

 

 

7.1 루틴을 작성하는 이유

  • 복잡성을 줄인다.
    • 루틴 사용시 해당 데이터를 어떻게 처리할지 고민 하지 않아도 된다.
  • 이해하기 쉬운 중간 단계의 추상화를 도입한다.
  • 이름만으로 별도의 문서가 필요 없을 수 있다. 
  • 중복 코드를 피한다.
  • 서브클래싱을 지원한다.
  •  구조화 덜된 루틴보다 길이도 짧고 구조적으로 완성도 높은 루틴을 오버라이드 하면 변경할 내용 많지 않다.
  • 코드의 실행 순서를 감춘다.
  • 포인터 연산을 감춘다
  • 이식성을 높인다.
  • 복잡한 불린 테스트를 단순화 한다.
  • 성능을 개선한다.
  • **간단한 작업도 루틴화 하는게 가독성 향상에도 좋지만 단순히 길이를 짧게 하기 위해서 루틴을 쓰는 건 부적합하다. 크기가 큰 하나의 루틴 내에서 처리하는 것이 좋은 경우도 있다. 

 

7.2 루틴 설계

  • 응집성(cohesion)이 높을 수록 코드의 오류가 적다.
  • 한가지 이상을 처리하게 되면 응집성이 낮아진다.
1. 450개의 루틴을 대상으로 한 연구에서 응집성이 높은 루틴의 50%는 오류가 없었던 반면, 응집성이 낮은 루틴 에서는 18%만이 오류가 없었다.(Card, Church, and Agresti, 1986)

2. 응집도 비율이 가장 높은 루틴이 가장 낮은 비율의 루틴보다 오류가 7배 많고 변경 비용도 20배나 더 들었다.
(Selby and Basili, 1991)
<Details>
<응집성의 종류>
기능적 응집성: 오직 하나의 연산만 처리(가장 바람직)

(알아 둘만한 응집성)
- 순차적 응집성: 순서대로 실행해야 하며 단계마다 정보 공유하며 동시해 수행되어 홀로 기능 제공 불가.
     -> 해결법: 기능별로 나누어 따로 호출가능 하도록 변경. 
- 통신적 응집성: 루틴의 연산들이 같은 데이터를 사용.해결법: 개별적 루틴으로 나누어서 통신적 응집성 루틴을 호출했던 고급 루틴에서 이 루틴들 호출.시간적 응집성: 동시에 수행 되어야 하는 루틴.
     -> 해결법: 하나로 묶인 응집된 루틴보다 각 단계 수행하는 작은 루틴들을 호출. 

(지양해야 하는 응집성)
- 절차적 응집성: 연관은 없지만 절차상 묶인 루틴
     ex> 이름 받은 후 주소.
     -> 해결법: 독립적인 루틴을 가진 별도의 루틴에 구현.
- 논리적 응집성: if, case등으로 연관성이 아닌 조건으로 묶인 루틴.예를 들면 매개변수의 타입에 따라 다른 정보를 입력.
     -> 해결법: 조건에 따라 여러 작업으로 나누거나 다른 조건인데 데이터를 공유한다면 해당 코드를 루틴으로 변경.
    (루틴이 다른 루틴을 호출하는 if, case문으로 이루어 져 있는 경우는 있다면 좋다. = 이벤트 핸들러.)
- 우연적 응집성: 연관없이 묶인 루틴.
     -> 해결법: 다시 설계 해야 한다. 

 

7.3 좋은 루틴이름

  • 루틴이 하는 모든 것을 표현하라.
  • 만약 이름이 너무 길어 진다면 부수적 영향이 적도록 루틴의 역할을 좀 더 분명하게 해야 한다. 
  • 의미가 없거나 모호하거나 뚜력한 특징이 없는 동사를 사용하지 마라. 
    • ex> HandleOutput(), Service()
  • 루틴이름을 숫자만으로 구분하지 마라.
  • 루틴 이름의 길이에 신경쓰지 마라.
  • 연구에 의하면 루틴 이름의 적적한 길이는 문자로 9~15자 사이이다. 
  • 그러나 루틴이 객체와 연관된 작업시 객체의 이름도 들어가야 한다. 
    • = “명료함”에 집중하라.
  • 함수의 이름을 지을 때는 리턴 값에 관해서 설명하라.
    • ex> printer.IsReady(), pen.CurrentColor()
  • 프로시저의 이름을 지을 때 확실한 의미가 있는 동사를 객체이름과 함께 사용하라. <프로시저 = void 함수>
  • 기능적 응집성을 갖는 프로시저는 일반적으로 하나의 객체에 대해서 한가지 연산을 수행
  • 연산에 객체 이름을 붙여 쓴 이름 사용 (PrintDocument())
    • -> 객체 지향 언어에서는 객체 자체가 호출에 포함되어 프로시저에 객체 이름 있을 필요없다. (document.Print())
  • 반의어를 정확하게 사용하라.
    • ex> add/remove, begin/end
  • 일관성 유지를 위해 이름 규약 사용도 도움이 된다.
  • 공통적인 연산을 위한 규약을 만들어라.  
    • employee.id.Get(), dependent.GetId() 같은 경우 지양한다. 한가지 규약으로 통일.

 

7.4 루틴의 길이에 대한 문제

연구 결과들
     1. 최대 200줄 까지는 커질수록 오류 감소(Basili and Perricone, 1984)

     2. 구조적 복잡성과 데이터의 양은 오류와 연관 관계가 있지만 루틴의 크기는 오류와 관련 없음(Shen et al., 1985)

     3. 루틴이 작다고(32줄 이하) 비용 적거나 오류 발생 적지 않다.
     상대적으로 큰 루틴(65줄 이상의 코드)이 한 줄당 개발 비용 저렴(Card, Church, and Agresti, 1986)

     4. 작은루틴(주석포함 명령문수 143개 이하)이 큰 루틴보다 오류수 23% 높지만 수정비용 2.5배 낮음
     (Selby and Basili,1991)

     5. 100-150줄일때 변경 확률 가장 낮음(Lind and Vairavan, 1989)

     6. IBM: 오류를 발생시킬 가능성 가장 높은 루틴은 500줄 보다 큰경우이며 이후 크기에 비례(Jones, 1986a)
  • 루틴이 길다고 작은 루틴보다 오류가 높지는 않으며 필요한 경우 100-200줄 까지 유기적으로 커질 수 있도록 허용 해야 한다.
  • *다만  200줄 이상시 주의. 

 

7.5 루틴 매개변수 처리

전체 오류의 39%가 내부 인터페이스 오류 즉, 루틴 끼리 서로 호출할. 때 발생하는 오류 (Basili and Perricone 1984)

 

  • 매개변수를 입력-수정-출력 순서로 입력.
  • 저자의 개인적 의견이나 어떤 방식이든 매개변수 일관성 있게 정렬한다면 읽는 사람에게 도움.
  • 고유한 in 과 out 키워드의 사용 고려<자바 경우 다른 방법 필요>
--> C++경우 전처리기로 만들 수 있다.

#define IN
#define OUT
void PrintPageNumber(
     IN int pageNumber,
     OUT StatusType &status
)
이렇게 언어가 지원해서가 아니라 만들어야 된다면 일관성 있게 하고 프로젝트 전체에서 사용하길 권장.

 

  • 유사한 매개변수가 여러 루틴에서 사용된다면 해당 매개변수를 같은 순서로 입력
  • 루틴에 전달된 모든 매개변수를 사용.
모든 매개변수 사용시 46% 정도 오류 없음. 반면 사용하지 않는 매개변수 하나 이상시 17~29% 무오류.
(Card, Church, and Aresti, 1986)

 

  • 그러나 내개변수를 사용하지 않는 타당한 이유가 있다면 그대로 남겨두어도 된다. 
  • 상태 변수나 오류 변수를 마지막에 입력. 
  • 이 변수들은 루틴의 주요 목적보다 값 반환위한 매개 변수임. 
  • 루틴의 매개 변수를 연산에 사용하지 않는다.
EX>

int Sample (int inputVal){
     int workingVal = inputVal;
     workingVal = workingVal + CurrentMultiplier(workingVal);
     return workingVal;
}
  • 매개변수에 대한 제약사항을 주석으로 작성
  • 주석으로 작성하면 좋은 것.
    • 매개변수가 입력을 위한것인지, 변경되는지, 값을 반환하기 위한 것인지에 대한 내용
    • 숫자 매개변수의 단위(인치, 피트, 미터 등)
    • 열거형(enumerator)이 아닌 경우 상태 코드와 오류 값의 의미
    • 값의 범위
    • 절대로 가질 수 없는 값
  • 루틴의 매개 변수를 7개 정도로 제한
    • 7의 법칙
  • 매개 변수에 사용할 입력, 수정, 출력 이름 규약을 고려
    • ex> Input_, Modift_ 
  • 루틴이 인터페이스 추상화를 유지할 수 있도록 변수나 객체를 전달
<Details>
<객체 전달시 두가지 상충하는 방법>
1. 구체적인 값만 전달: 루틴끼리 연결 최소화, 이해 쉽고 재사용 가능. 루틴 전달시 다른 데이터 사용 가능해 캡슐화 위배.
2. 객체를 전달: 객체의 다른 데이터 사용가능해 안정감 유지 가능. 오히려 구체적 값 전달시 데이터 요소 노출해 캡슐화 위배. 

"루틴이 어떤 추상화를 표현하는지 고려"

(구체적인 값 전달)
- 루틴에 필요한 3개의 구체적 데이터요소가 우연히 그 객체에서 온 경우. 
     -> 하지만 루틴이 그 객체를 이용해 무언가를 한다면 추상화 깨짐객체 생성시 이미 값들을 입력 했는데 루틴에서 호출 후
     생성한 값 자체 사용하거나 변경시에는 값을 전달해야함.(미리 객체에서 값 설정후 바뀌는 경우 설계의 오류일 가능성이 높음.)

(객체 전달)
- 루틴에 전달하는 매개변수 목록 자주 변경하고 그 매개변수가 동일한 객체에서 온 경우.
  • 이름(named) 매개변수 사용. <자바 x>
  • 형식적 매개변수와 실질적인 매개변수를 명시적으로 연관지어 전달 오류 줄임.
  • 질적 매개변수가 형식적인 매개변수와 일치하는지 확인.(변수 타입 확인) <자바 x>

 

7.6 함수를 사용할 때 특별히 고려해야 하는 사항

프로시저와 함수.
함수는 리턴 값을 고려해 입력을 위한(읽기 전용) 매개변수만 받아야함.
프로시저는 입력, 수정, 출력 매개변수를 원하는 만큼 받을 수 있다. 

but, 프로시저처럼 작동하는 함수를 작성하고 상태값 반환하는건 널리 사용되는 프로그래밍 습관이다.

 

  • 리턴값이 있는 프로시저는 엄밀히 함수이고 리턴값이 함수의 주 목적과 상관이 없지만 프로시저의 성공 여부를 리턴하는 기법을 일관되게 사용되면 혼란을 일으키지 않는다.
  • 또한 루틴 호출과 상태값 검사를 분리하는게 명령문을 덜 복잡하게 한다.
<Ex>

if(report.FormatOutput(formattedReport) = Success) then …

보다

outputStatus = report.FormatOutput(formattedReport)
if(outputtStatus = Success) then
  •  함수 리턴값 설정
    • 가능한 모든 리턴 경로를 검사하라.
    • 루틴을 작성 할 때 모든 가능한 환경에서 함수가 값을 리턴하는지 확인하기 위하여 각각 경로 머릿속으로 실행해 본다.
    • 함수를 시작할때 리턴 값을 기본 값으로 초기화 하는 것은 좋은 습관(정확한 리턴 값이 설정되어 있지 않은 경우에 대한 안전장치를 제공함.)
    • 지역 데이터에 대한 참조나 포인터를 리턴하지 말자. 
    • 루틴이 끝나자마자 지역데이터에 대한 참조와 포인터는 무효한 상태가 됨. 
      • 객체가 내부 데이터 정보 리턴해야 한다면 그러한 정보를 클래스의 맴버 데이터로 저장하여 리턴.

 

7.7 매크로 루틴과 인라인 루틴 <자바x>

on hold.