////
Search
Duplicate
🪕

19. 제어와 관련된 일반적인 이슈

코드를 작성할 때, 사용하는 제어 구조에 대해 일반적인 이슈를 인지하지 못하고 있다면 제대로 된 코드를 작성할 수 없다.

1. 불린 표현식

순차 제어 구조에서 명령문을 실행하는 가장 간단한 제어 구조를 제외하면 모든 제어 구조는 불린 표현식의 평가에 의존한다.

불린 테스트에서 true와 false 사용하기

불린 표현식에는 0이나 1과 같은 값 대신 truefalse같은 식별자를 명시적으로 사용해야 한다.
이 식별자들은 사용하기 쉽고 불린 변수에 truefalse 외의 값을 할당하는 것을 허용하지 않는다.
0과 1같은 플래그를 일반적으로 사용하면 무엇이 문제일까? 이런 경우 테스트 결과가 true일 때 함수 호출이 실행되는지, false일 때 실행되는지 코드만 봐서 확인하기가 어렵다.
불린 표현식 조건문에서는 truefalse같은 용어를 명시적으로 사용해야 한다.
이를 통해 의도를 더 분명히 나타낼 수 있고 10이 각각 무엇을 의미하는지 추론할 필요가 없으며 실수로 바꿔 쓸 일도 없다.
다음은 불린 조건문에서 truefalse를 정의하는 방법에 대한 팁이다.
불린 값을 암묵적으로 truefalse에 비교하라.
불린 표현식으로 코드를 작성하면 조건문임이 명확해지고 제어 구조에 대한 흐름 파악이 쉬워진다.

복잡한 표현식을 단순하게 만들기

복잡한 테스트를 새로운 불린 변수로 추출해 부분적인 테스트로 나눠라.
큰 문제를 작은 문제로 쪼개 구체적이게 만든다면 이해하기가 쉽다.
복잡한 표현식을 불린 함수로 추출해라.
어떤 테스트가 자주 반복되거나 메소드, 클래스의 흐름에 부적합하다면 함수로 옮기거나 클래스로 추출해낸 후, 함수의 결과를 이용하는 것이 적합하다.
불린 함수로 추출해내면 테스트를 고립시킬 수 있고 유틸성 성격이 짙다면 비즈니스 로직 코드를 읽는 사람이 크게 의미부여를 하지 않아도 된다.
의사결정 테이블을 사용해 복잡한 조건을 대신하라.
복잡한 조건이란 일반적으로 많은 상황을 가지고 있는 조건을 의미한다.
이런 구조에 ifcase 문을 사용하는 대신 의사결정 테이블을 사용해 테스트를 수행하면 가독성을 향상시키는데 도움이 될 수 있다.

불린 표현식을 긍정문으로 작성하기

일반적으로 길이가 긴 부정문을 쉽게 이해할 수 있는 사람은 많지 않다. 즉 대부분의 사람은 부정문을 이해하는데 어려움을 겪는다는 말이다.
부정문은 일반적으로 긍정 조건의 역으로 생각을 두 번해야한다. 따라서 긍정문보다 어렵다.
프로그램에서 부정문으로 작성된 복잡한 불린 표현식을 사용하지 않는 방법들은 다음과 같은 지침이 존재한다.
if 문에서는 부정문을 긍정문으로 변환한 다음 if 절과 else 절에 있는 코드를 바꿔라.
드모르간의 법칙을 이용해 부정 불린 테스트를 단순화하라.
드모르간의 법칙을 사용하면 기존 표현식과 같은 의미가 있는 다른 표현식 사이의 논리적인 관계를 활용할 수 있다.
다음과 같은 예가 존재한다.
if ( !displayOK || !printerOK ) ...if ( !( displayOK && printerOK ) ) ...

괄호를 사용해 이해하기 쉬운 불린 표현식 만들기

복잡한 불린 표현식을 언어의 평가 순서에 의존하지 말고 괄호를 사용해 의미를 분명히하라.
괄호를 사용하면 프로그래밍 언어가 불린 표현식을 어떻게 처리하는지 상관없이 코드를 분명하게 할 수 있다.
논리 표현식에는 충분할 정도로 괄호를 사용하라.
괄호를 비용이 거의 들지 않으면서도 가독성 좋은 코드를 작성하는데 도움을 준다. 논리 표현식에 괄호를 충분히 사용하는 것은 좋은 습관이다.

불린 표현식 평가 방법

모든 프로그래밍 언어는 나름의 평가 방식이 존재하고 표현식을 평가하는 것도, 구현하는 사람에 따라 달라지는 경향이 존재하므로 언어의 버전에 따라 어떤 평가 방식을 사용하는지 알고 있어야 한다.
더 좋은 방법은 개발된 코드를 읽는 사람이 개발자만큼 똑똑하지 않을 수 있으니 평가 순서와 단축 평가에 의존하지 않고 중첩된 테스트를 이용해 의도를 분명히 나타내는 것이다.

숫자의 크기 순서대로 수치 표현식 작성하기

기본 개념은 각 항목을 왼쪽에서 오른쪽으로 작은 값에서 큰 값으로 정렬하는 것이다.
이러한 접근 방식은 다음 코드보다 그 의미가 더 분명하다.
( i > MIN_ELEMENTS ) and ( i < MAX_ELEMENTS )

0을 비교하는 방법

각 타입에서 초기화되지 않은 상태와 초기화는 되었으나 값이 없는 상태는 매우 다양하다. 각 의미에 적합한 상태들을 규정하고 비교시 사용하자.

불린 표현식과 관련된 일반적인 문제

자바에서는 a==ba.eqauls(b)의 차이점을 이해하라.

2. 복합문(블록)

복잡문 또는 블록은 프로그램의 흐름을 제어하기 위해서 단일 명령문으로 취급되는 명령문의 집합을 의미한다.
블록 내 코드 길이에 상관없이 블록 구조를 사용하여 의도를 분명히하라.

3. 널 명령문

구현이 존재하지 않고 말 그대로 명령문이 null인 형태를 의미한다.

4. 지나치게 깊은 중첩 구조 처리

들여쓰기의 깊이가 지나치게 깊은 코드는 코드를 이해하기 어렵게 만드는 주범 중 하나다.
노엄 촘스키와 제럴드 와인버그의 연구에 따르면 세 단계 이상 중첩된 if 문을 이해할 수 있는 사람은 거의 없다.
깊은 중첩 구조를 피하는 것은 어렵지 않은데, 깊은 중첩 구조를 가질 것 같다고 판단하게 되면 테스트를 재설계하거나 더 간단한 메소드로 추출해내면 된다.
다음은 중첩 구조의 깊이를 줄이기 위한 가이드라인들이다.
조건의 일부분을 재설계하여 중첩된 if 문을 단순화하라.
break 블록을 사용하여 중첩된 if 문을 단순화하라.
중첩된 if 문을 다른 조건문으로 변환하라.
일반적으로 if-then-else 문으로 중첩된 if 문을 재구성할 수 있다.
정수를 비교하는 경우처럼 어떠한 비교문은 case 문을 사용해 작성할 수 있다.
중첩 구조가 깊은 코드를 메소드로 추출하라.
이 과정에서 객체지향적인 접근법을 선택할 수 있다. 조건문에 따라 사용하는 서브 클래스가 다르게 한다던지 말이다.
깊게 중첩된 코드는 재설계하라.

깊은 중첩 구조를 줄이는 기법 요약

깊은 중첩 조건 코드 부분을 재설계 하라.
if-then-elsecase로 변환하라.
깊은 중첩 코드를 메소드로 추출하라.
객체와 다형성을 사용하라.
조건 테스트 대신 상태 변수를 정의하여 이를 사용하도록 코드를 작성하라.
특정 조건을 이용하여 메소드를 종료하고 코드의 정상적인 흐름을 명시하라.
예외를 사용하라.

5. 프로그래밍 기초: 구조적 프로그래밍

구조적 프로그래밍은 오직 하나의 입구와 출구만이 존재하는 제어 구조를 사용해야 한다는 간단한 개념이다.
단일 진입점과 단일 탈출점 제어 구조라고도 한다.
구조적 프로그래밍은 흐름이 예측 불가능한 곳으로 이동하기보다는 순차적이고 체계적인 방법으로 실행된다. 프로그램을 하향식으로 읽을 수 있고 거의 같은 방법으로 실행된다.

구조적 프로그래밍의 세 가지 요소

순차
// a sequence of assignment statements a = "1"; b = "2"; c = "3"; // a sequence of calls to routines System.out.println( a ); System.out.println( b ); System.out.println( c );
Visual Basic
복사
순차적 명령문은 순서대로 실행되는 명령문의 집합이다. 전형적인 순차적 명령문에는 할당문과 함수 호출이 존재한다.
선택
// selection in an if statement if ( totalAmount > 0.0 ) { // do something ... } else { // do something else ... } // selection in a case statement switch ( commandShortcutLetter ) { case 'a': PrintAnnualReport(); break; case 'q': PrintQuarterlyReport(); break; case 's': PrintSummaryReport(); break; default: DisplayInternalError( "Internal Error 905: Call customer support." ); }
Visual Basic
복사
선택은 명령문을 선택적으로 수행하는 제어 구조를 의미하는데 두 절 중 하나만 실행되기 위해서 선택된다.
if-then-else, if-then, else, case 등이 존재한다.
반복
' example of iteration using a For loop For index = first To last DoSomething( index ) Next ' example of iteration using a while loop index = first While ( index <= last ) DoSomething ( index ) index = index + 1 Wend ' example of iteration using a loop-with-exit loop index = first Do If ( index > last ) Then Exit Do DoSomething ( index ) index = index + 1 Loop
Visual Basic
복사
반복은 명령문 집합을 여러 번 실행하는 제어 구조다.
구조적 프로그래밍의 이론의 핵심은 모든 제어 구조를 순차, 선택, 반복이라는 세 구성 요소로부터 만들어낼 수 있다는 것이다.
저자는 개인적으로 이 세 가지 구조적 프로그래밍 요소가 아닌 break, continue, return, throw-catch와 같이 다른 제어 구조를 비판적으로 바라보고 사용 시, 더 주의해야 한다고 생각한다.

6. 제어 구조와 복잡성

이처럼 제어 구조에 주의를 많이 기울여야 하는 이유는 제어 구조가 프로그램의 전체 복잡도에 영향을 끼치기 때문이다.
제어 구조를 잘 사용하면 복잡도가 감소하고 잘못 사용하면 증가한다.
복잡도를 측정하는 방법 중 하나는 프로그램을 이해하기 위해서 기억해야 하는 객체의 수를 세어보는 것이다.
프로그램의 복잡도는 프로그램을 이해하기 위해서 얼마만큼 노력해야 하는지 결정하는 데 중대한 역할을 한다.

복잡도가 얼마나 중요한가?

제어 흐름의 복잡도는 코드의 신뢰성 및 오류 발생 확률과 관련있기 때문에 중요하다.

복잡도를 줄이기 위한 일반적인 방법

다음 방법으로 복잡도를 처리할 수 있다.
프로그램의 복잡도와 프로그램을 이해하기 위해 집중해야 하는 대상의 수를 줄이는 방법이다.

복잡도 측정법

가장 영향력 있는 복잡도 수치 계산 기법은 톰 맥케이브가 제안한 방법으로 이 방법은 한 메소드의 의사결정 지점의 수를 세어 복잡도를 측정한다.
구체적인 방법은 다음과 같다.
1.
1부터 시작하여 메소드를 따라 진행한다.
2.
if, while, foreach, for, and, or와 같은 키워드를 만날때 마다 1을 더한다.
3.
case 문의 경우마다 1을 더한다.

복잡도 측정의 활용

모든 의사결정 지점을 센 후, 메소드의 복잡도를 분석하는 데 의사결정 지점의 수를 적용할 수 있다.
0-5
이 메소드는 괜찮다.
6-10
이 메소드를 단순화하기 위해 생각해봐야한다.
10+
메소드를 다른 메소드들로 쪼갠다음 첫 번째 메소드에서 다음 메소드를 호출한다.
메소드의 일부분을 다른 메소드로 만든다고 하더라도 단지 의사결정 지점을 이동시킬 뿐 프로그램의 전체적인 복잡도는 감소하지 않지만 한 번에 다뤄야 하는 복잡도 줄어든다.
두뇌로 처리해야 하는 항목의 수를 최소화하는 것이 목적이므로 가치있는 행동이다.

다른 종류의 복잡도

맥케이브의 방법 외에도 데이터의 양, 제어 구조에서의 중첩도, 코드의 줄 수, 변수의 수명 등을 사용한다.
이런 간단한 측정법들을 조합해 복합적인 측정법을 개발한 학자들도 있다.

요점 정리

품질이 좋은 코드를 작성하기 위해서 불린 표현식을 단순하고 읽기 쉽게 작성한다.
중첩의 깊이가 깊은 메소드는 이해하기 어렵다. 다행스럽게도 비교적 쉽게 중첩된 코드를 피할 수 있는 방법들이 존재한다.
구조적 프로그래밍은 순차, 선택, 반복의 조합으로 어떠한 프로그램도 작성할 수 있다는 간단한 개념이며 오늘날까지도 적용된다.
복잡도를 최소화하는 것이 우수한 코드로 가는 지름길이다.