7장: 고급 루틴
7장: 고급 루틴
- 루틴: 한 가지 목적을 위해서 호출할 수 있는 개별 메서드나 프로시저
// 저급 루틴을 C++로 작성한 예제
void HandleStuff( CORP_DATA & inputRec, int crntQtr, EMP_DATA empRec,
double & estimRevenue, double ytdRevenue, int screenX, int screenY,
COLOR_TYPE & newColor, COLOR_TYPE & prevColor, StatusType & status,
int expenseType )
{
int i;
for ( int = 0; i < 100; i++ ) {
intputRec.revenue[i] = 0;
intputRec.expense[i] = corpExpense[ crntQtr ][ i ];
}
UpdateCorpDatabase( empRec );
estimRevenue = ytdRevenue * 4.0 / (double) crntQtr;
newColor = prevColor;
status = SUCCESS;
if ( expenseType == 1) {
for ( i = 0; i, 12; i++ )
profit[i] = revenue[i] - expense.type1[i];
}
else if ( expenseType == 2 ) {
profit[i] = revenue[i] - expense.type2[i];
}
else if ( expenseType == 3 )
profit[i] = revenue[i] - expense.type3[i];
}
- 루틴 이름이 좋지 않다.
HandleStuff()는 루틴이 무엇을 하는지 아무것도 말해주지 않는다. - 루틴에 대한 설명이 없다.
- 루틴의 레이아웃이 엉망이다.
- 루틴의 입력 변수인
inputRect가 변경되었다. 입력변수의 값은 변경되지 않아야한다. - 루틴이 전역변수를 읽고 쓴다.
- 루틴의 목적이 하나가 아니다. 여기서 사용된 루틴은 변수를 초괴화하고 데이터베이스에 기록하고 몇가지 계산을 수행하는데 이러한 기능은 아무런 연관이 없는것처럼 보인다. 루틴은 분명하게 정의된 하나의 목적만을 가져야 한다.
- 루틴이 잘못된 데이터로부터 자신을 방어하지 않는다.
- 루틴이 여러가지 매직 넘버를 사용하고 있다.
- 루틴의 매개변수 중 몇 개를 사용하지 않았다.
- 루틴의 매개변수 중 하나가 잘못 전달되었다.
- 루틴의 매개변수가 너무 많다.
- 루틴의 매개변수가 잘못 정렬되어 있으며 문서화도 되어있지 않다.
루틴은 컴퓨터 과학 역사상 컴퓨터 다음으로 가장 위대한 발명이다. 프로그래밍 언어의 다른 어떤 기능보다도 프로그램을 읽고 이해하기 쉽게 만든다. 루틴은 공간을 절약하고 성능을 향상하기 위해 발명된 가장 훌륭한 기법이다.
7.1 루틴을 작성하는 이유
- 복잡성을 줄인다: 프로그램의 복잡성을 줄이는 것이 루틴을 작성하는 가장 중요한 목적
- 이해하기 쉬운 중간 단계 추상화: 이해하기 쉬운 이름으로 축약
- 중복 코드를 피한다: 오류 방지, 공간 절약, 쉬운 변경, 높은 신뢰도
- 서브클래싱싱: 완성도 높고 짧은 루틴을 오버라이드하여 변경을 줄임
- 코드 실행순서를 감춤: 이벤트 처리 순서를 감추어 고민을 줄임
- 포인터 연산을 감춤: 연산을 한곳에서 처리하고 연산에 대한 고민을 줄임
- 이식성을 높인다: 이식 가능한 기능과 그렇지 않은 기능을 구분 가능. 표준이 아닌 언어의 기능, 하드웨어 의존성, 운영체제 의존성은 이식 불가능.
- 복잡한 불린 테스트 단순화: 조건문 테스트를 자세히 이해하지 않아도 됨.
- 성능 개선: 최적화하여 모든 곳에 적용. 분석 소요 감소.
- 모든 루틴이 짧아야 하는 것은 아니다.
루틴으로 작성하기에는 너무 단순해 보이는 연산
코드가 두세 줄 뿐인 작은 루틴도 유용하다.
points = deviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch() )
Function DeviceunitsToPoints( deviceUnits: Integer ) Integer;
DeviceUnitsToPoints = deviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch() )
End Function
points = DeviceunitsToPoints( deviceUnits );
DeviceUnitsPerInch()가 0을 리턴
Function DeviceunitsToPoints( deviceUnits: Integer ) Integer;
if ( DeviceUnitsPerInch() <> 0 )
DeviceUnitsToPoints = deviceUnits * ( POINTS_PER_INCH / DeviceUnitsPerInch() )
else
DeviceUnitsToPoints = 0
end if
End Function
7.2 루틴 수준의 설계
응집성은 루틴에 있는 연산이 얼마나 밀접하게 연관되어 있는지를 나타낸다. “강한 연결” 이라고도 함. 응집성이 높을수록 코드 오류가 적다.
기능적 응집성은 루틴이 오직 하나의 연산만 처리하는 경우 가장 응집성이 강하다. 순차적 응집성: 루틴이 특정한 순서대로 수행되어야하고 단계마다 정보를 공유하여 동시에 수행될때 완전한 기능을 제공하지 못하는 연산을 포함할 때 통신적 응집성: 루틴에 이쓴 연산들이 같은 데이터를 사용하지만, 서로 아무런 연관성이 없을 때 발생함. 보고서 출력, 데이터 초기화 기능 시간적 응집성: 여러 연산이 동시에 수행되어야 해서 하나의 루틴으로 결합할 때 StartUp()
이 책이 다루는 범위에서는 피해야하는 응집성 절차적 응집성: 루틴에 있는 연산들이 전해진 순서대로 처리될 때 발생함 논리적 응집성: 여러 가지 기능을 한 루틴에서 수행할때 루티에 전달되는 조건에 따라 수행하는 기능이 다른 경우에 발생함.
7.3 좋은 루틴 이름
- 루틴이 하는 모든 것을 표현: 이름에 모든 출력과 부수적인 효과를 설명
- 의미가 없거나 모호하거나 뚜력한 특징이 없는 동사를 사용하지 말라: handle, process, dealwith
- 루틴 이름을 숫자만으로 구분하지 말라
- 루틴 이름의 길이에 신경 쓰지 마라: 9자에서 15자가 적절하다는 연구가 있지만 명료함에 초점을 맞춰 작성
- 함수의 이름을 지을 때는 리턴 값에 관해서 설명하라: 함수는 값을 리턴하기때문에 리턴값에 대한 설명이 필요.
- 프로시저의 이름을 지을 때 확실한 의미가 있는 동사를 객체 이름과 함께 사용하라
- 반의어를 정확하게 사용하라: add/revove, increment/decrement …
- 공통적인 연산을 위한 규약을 만들어라
7.4 루틴의 길이에 대한 문제
루틴의 길이에 대한 많은 연구가 진행되었지만 일부만 적용이 가능하다. 1002ㅜㄹ에서 200줄 크기의 루틴은 더 짧은 루틴과 다를게 없다. 200 줄 이상의 긴 루틴은 주의해야 한다.
7.5 루틴 매개변수 처리
- 매개변수를 입력-수정-출력 순서로 입력한다
- 고유한 in과 out 키워드 사용을 고려한다.: C++은 const
- 유사한 매개변수가 여러 루틴에서 사용된다면 해당 매개변수를 항상 같은 순서로 입력한다 printf, fprintf
- 모든 매개변수를 사용한다
- 상태 변수나 오류 변수는 마지막에 입력한다: 주로 값을 반환하기 위한 변수
- 루틴의 매개변수를 연산을 위한 변수로 사용하지 않는다.
// 입력 매개변수를 부적절하게 사용하는 예 int Sample( int inputVal ) { inputVal = inputVal * CurrentMultiplier( inputVal ); inputVal = inputVal + CurrentAdder( inputVal ); ... return inputVal; }-> 지역변수 사용하기, const 사용하기
- 매개변수에대한 제약사항을 주석으로 작성한다. assertion을 사용하는 것이 더 좋다
- 매개변수가 입력을 위한 것인지, 변경되는지, 값을 반환하기 위한 것인지
- 숫자 매개변수의단위
- enum이 아닌 상태코드 오류 값의 의미
- 값의 범위
- 절대로 가질 수 없는 값
- 루틴의 매개변수를 7개 정도로 제한한다
- 매개변수에 사용할 입력, 수정, 출력 이름 규약을 고려한다.
- 루틴이 인터페이스 추상화를 유지할 수 있도록 변수나 객체를 전달한다. 루틴이 어떤 추상화를 표현하고 있는가를 고려하여 전달하기
- 이름 매개변수를 사용한다.
- 실질적인 매개변수가 형식적인 매개변수와 일치하는지 확인한다. 더미 변수는 정의에 선언된 변수이고 실질적인 변수는 호출에서 사용되는 변수. 컴파일러가 타입변환을 시도한다는 사실을 인지하고 경고를 끄지말자.
7.6 함수를 사용할 때 특별히 고려해야 할 사항
함수는 값을 반환하고 프로시저는 아무 값도 반환하지 않는 루틴이다. void 리턴 형을 갖는 루틴은 의미론적 프로시저
함수를 사용할 때와 프로시저를 사용할 때
함수 리턴 값 설정
7.7 매크로 루틴과 인라인 루틴
매크로 표현식을 괄호로 묶어라
#define Cube( a ) a*a*a
#define Cube( a ) (a)*(a)*(a)
#define Cube( a ) ((a)*(a)*(a))
다중 명령문 매크로를 중괄호로 감싸라
#define LookupEntry( key, index ) \
index = (key - 10) / 5; \
index = min( index, MAX_INDEX ); \
index = max( index MIN_INDEX ); \
...
for ( entryCount = 0; entryCount < numEntryies; entryCount++ )
LookupEntry( entryCount, tableIndex[ entryCount ] );
다음 코드만 동작 index = (key - 10) / 5;
#define LookupEntry( key, index ) { \
index = (key - 10) / 5; \
index = min( index, MAX_INDEX ); \
index = max( index MIN_INDEX ); \
}
필요한 경우 루팅으로 대체될 수 있도록 매크로의 이름을 루틴과 비슷하게 작성하라.
매크로 루틴의 한계
C++이 제공하는 매크로 대안 매크로를 사용하면 디버거, 프로파일러 등의 도구를 사용할 수 없음.
인라인 루틴
이론적으로 루틴 호출 시 발생하는 부하를 줄인다. 그러나 인라인 루틴이 캡슐화를 위반하게 되고 코드의 크기를 증가시킨다.
체크리스트: 고급 루틴
더 근본적인 문제
- 루틴을 반드시 작성해야 하는가?
- 별도의 루틴으로 작성되어야 할 부분이 모두 루틴으로 작성되었는가?
- 루틴 이름이 프로시저나 함수의 리턴 값을 설명하는 ‘동사+객체’형태인가?
- 루틴 이름이 루틴이 하는 모든 것을 설명하는가?
- 일반적인 연산에 대한 명명규약을 구축했는가?
- 루틴이 강하고 기능적 응집성을 갖고 있는가? 즉, 루틴이 오직 한 가지 일만 수행하고 잘 수행하고 있는가?
- 루틴이 느슨하게 연결되어 있는가? 즉, 다른 루틴에 대한 연결이 작고 친말하고 가시작이고 유연한가?
- 루틴의 길이가 코드 작성 표준보다는 기능과 논리에 의해서 자연스럽게 결정되었는가?
매개변수 전달 관련 문제
- 루틴의 매개변수 목록이 일관된 인터페이스 추상화를 제공하는가?
- 루틴의 매개변수가 유사한 루틴에 있는 매개변수의 순서와 일치하고 합리적인 순서로 되어 있는가?
- 인터페이스 가정이 기록되어 있는가?
- 루틴의 매개변수가 7개 이하인가?
- 모든 입력 매개변수가 사용됐는가?
- 모든 출력 매개변수가 사용됐는가?
- 루틴이 입력 매개변수를 작업 변수로 사용하지 않는가?
- 루틴이 함수라면 모든 가능한 조건에서 타당한 값을 리턴하는가?
요점정리
- 루틴을 작성하는 가장 중요한 이유는 사람이 관리하고 이해하기에 더 쉬운 코드를 작성하기 위한 것이며 그 밖의 이유도 다양하다. 단순히 공간을 줄이겠다는 것은 고려할 사항이 아니다. 이해하기 쉽고 믿을 수 있고 변경하기 편한 코드를 작성하는데 그 목표가 있다.
- 때로는 별도의 루틴을 작성함으로써 얻는 이득이 크지 않을 수 있다.
- 루틴을 다양한 형태의 응집성에 따라 분류할 수 있지만, 가장 좋은 기능적 응집성을 갖도록 루틴을 분류하는 것이 가장 좋다.
- 루틴의 품질을 이야기할 때는 이름도 고려한다. 이름이 별로이면서 정확하다면 루틴은 잘못 설계된 것이다. 이름도 별로고 정확하지도 않으면 이름으로 프로그램이 무엇을 하는지 알 수 없다. 어느 경우든지 이름이 좋지 않으면 프로그램을 변경해야 한다.
- 함수는 그것의 주된 목적이 함수의 이름에 묘사된 특정한 값을 리턴할 때만 사용되어야 한다.
- 주의 깊은 개발자는 매크로를 주의 깊게 사요하여 최후의 수단으로만 사용한다.ㄴ