루프의 종류선택

  • 계수형 루프(coiunted loop) 는 특정한 횟수만큼 수행된다
  • 계속 판단 형 루프(continuously evaluated loop) 얼마나 실행될 것인지를 미리 알지 못하며, 반복할 때마다 중단할지를 검사한다.
  • 무한 루프(endless loop)는 일단 시작되면 끝없이 실행된다.
  • 반복자(iterator)루프는 컨테이너 클래스에 있는 각 요소들에 대해서 한번씩 수행한다

루프의 종류는 유연성에 의해서 차별화된다. 즉, 루프가 지정된 횟수만큼 실행되는지 또는 반복마다 완료 조건을 테스트할 것인지에  따라서 달라진다

 

Loop-With-Exit 루프를 사용하는 시기

종료조건이 루프의 시작이나 끝이 아니라 중간일 경우에 사용

 

이런 루프를 사용할때 주의점

  • 모든 조건들을 한 곳에 입력한다.
  • 조건을 분명히 하기 위해서 주석을 쓴다

 

for 루프를 사용하는 시기

for루프는 지정한 횟수만큼 실행되는 루프가 필요할때 사용한다. 내부적인 루프제어가 필요하지 않은 간단한 작업에 대해서 for루프를 사용한다. 루프제어가 컨테디어에 있는 요소들을 반복하는 것과 같이,

간단한 증가나 감소인 경우에 for를 사용한다. for루프의 핵심은 루프의 맨위에서 설정한 다음, 잊어버리면 된다는 점이다. 루프를 제어하기 위해서 루프내부에서 아무것도 할 필요가 없다.

만약 실행 중에 루프를 빠져나가야 한다면 while 루프를 선호해라.

 

루프의 제어

루프를 다룰때 생기는 문제들 - 잘못된 루프 초기화 , 루프 초기화 생략 , 연산자 혹은 루프와 관련된 다른 변수들의 초기화 생략, 부적절한 중첩, 잘못된 루프 종료, 루프 변수를 증가시키는 것을 잊거나

변수를 잘못 증가시키는 것, 그리고 루프 인덱스에서 배열 요소를 잘못 인덱스 하는 것 등

이것을 방지하기 위한 습관 - 루프 영향을 미치는 요소의 수를 최소화한다. 단순화! 단순화! 단순화! 루프의 내부를 마치 별도의 루틴처럼 처리한다. 될수 있는 한 제어부분을 푸의 밖에서 입력하고 루프 몸체 내에서

실행되어야 하는 조건들을 분명히 한다

루프 진입하기

  •  한 위치에서만 루프에 진입한다
  • 루프가 시작하기 바로 전에 초기화 코드를 입력한다 - 근접성의 원칙에 따른 것. 수정시 오류를 쉽게 피할수 있다. 루프 초기화명령문을 루프 근처에 둔다
  • 무한 루프는 while(true)를 사용한다. - 어떤 프로그래머는 for( ;; )를 대안으로 쓴다
  • 적절할때 for루프를 택한다
  • while루프가 더 적절할때 for루프를 사용하지 않는다

ex while루프를 억지로 for루프 헤더에 밀어 넣은 C++예제

//파일에서 모든 레코드를 읽는 것

for( inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) inputFile.GetRecord();

for 루프헤더는 루프 제어 명령문, 즉 루프를 초기화하고 종료하거나 종료의 상황으로 접근하는 명령문을 위해서 사용한다. inputFile.GetRecord()명령문이 루프를 종료상황에서 접근시키고 있지만, recordCount명령문은 그렇지 않다

이 명령문들은 루프의 진행을 제어하지 않는 보조 관리 명령문이다. 루프헤더에 recordCount명령문을 이동시키고, inputFilr.GetRecord()명령문을 밖으로 빼낸 것이 잘못된 것이다. recordCount가 루프를 제어하는 듯한 오해를 만들고 있다.만약 이 경웨 while대신 for를 쓰고 싶다면 루프제어 명령문을 루프헤더에 입력하고 나머지를 모두 밖으로 빼내도록 한다

ex 푸르 헤더에서 논리적인 조건을 자유롭게 사용한 C++예제

recordCount = 0;

for( inputFile.MoveToStart(); !inputFile.EndoOfFile(); inputFile.GetRecord()) recordCount++;

ex while 루프를 적절하게 사용한 C++예제

inputFile.MoveToStart();

recordCount = 0;

while(!inputFile.EndOfFile())

{

inputFile.GetRecord(); recordCount++;

}

 

루프의 중간 부분 처리하기

  • 루프에 있응 명령문들을 둘러싸기 위해서 중괄호를 사용한다
  • 빈 루프를 피한다
  • 루프에서 보고관리 작업들(housekeeping)은 루프의 시작이나 끝에 놓는다. - 루프의 보조관리 작업은 루프가 처리해야 하는 일이 아니라 루프를 제어하기 위한 것이 주된 목적인 표현식, 즉 i= i+ 1 , j++ m같은 표현식이다.
  • 루프가 하나의 기능만 수행해야 한다 - 루프가 한번에 두가지 일을 하기 위해서 사용될수 있다는 사실만으로 두가지 기능을 동시에 수행해도 된다는 것은 정당화되지 못한다. 하나의 루프는 반드시 한가지 일을 수행해야 한다

 

루프의 종료

  • 루프가 종료되는 확인한다 - 루프가 종료되는지 반드시 확인해야 한다
  • 루프 종료조건을 명확하게 한다
  • 루프를 종료하기 위해서 for루프의 인덱스를 조작하지 않는다

    ex루프 인덱스를 조작하는 자바예제

    for(iint i = 0; i < 100; i++){

//코드들

if(...){ i = 100} -> 여기서 조작하고 있다!

//코드들2

}

for루프를 설정할때, 루프 카운터는 사용이 금지된다 루프의 종료조건을 더 제어하기 위해서는 while루프를 사용한다

  • 루프인덱스의 마지막 값에 의존하는 코드를 피한다 - 루프가 끝난 후에 루프의 인덱스값을 사용하는 것은 나쁜 방법이다. 이 값은 루프가 정상적인 종료할때와 비정상종료일때도 다를수 있다. 가독성도 떨어진다.  루프 내에서 적절한 순간에 최종값을 변수에 할당해서 사용하는 것이 좋은 코드이다.쓰기도 읽기도.
  • 안전한 카운터를 사용한다 - 안전한 카운터는 루프가 너무 많이 실행되었는지를 결정하기 위해서 반복될때마다 증가시키는 변수이다. 만약 오류가 발생하면 치명적인 영향을 미치는 프로그램이 있다면, 모든 루프의 종료를 보장하기 위해서 안전한 카운터를 사용할수 있다.

ex 안전한 카운터를 사용할 수 있는 루프에 대한 C++예제

do{

node = node -> Next; //...

}while(node->Next != NULL);

ex 위의 예제에 안전한 카운터를 추가한 예제

safetyCounter = 0;

do{

node = node->Next;

//...

safetyCounter++;

if(safetyCounter >= SAFETY_LIMIT){

Assert("안전한 카운터 오류");

}

//...

}while(node->Next != NULL)

안전한 카운터를 만병통치약이 아니다. 한번에 하나씩 코드에 추가한다. 안전한 카운터는 복잡도를 증가시키고 추가적인 오류를 야기할수 있다. 안전한 카운터가 모든 루프에서 사용되지 않기 때문에, 안전한 카운터를 사요용하는 부분에 있는 루프를 수정할때 빼먹을수 있다.

  • 루프 일찍 종료하기

    • while문에서는 boolean플래그보다는 break문을 사용해라
    • 루프내에 수많은 break문이 산재되는 것을 주의한다
    • 루프의 앞부분에서 테스트를 위한 목적으로 continue를 사용한다 - 각 순회에서 필요없는 부분에 대한 연산을 빼고 진행할수 있다.
    • 언어에서 continue를 지원하지 않는 다면 레이블 break구조를 사용한다.

do {

switch()

{

case A:

if(){

//...

break;   //레이블 break대상이 분명하다

}

}

}while(...);

break와 continue를 신중하게 사용한다.

 

종결점 확인

우리가 루프에 가지는 관심사는 세가지 경우이다. 첫번째, 마지막, 중간에 임의번째이다. 이것이 정상적인 절차를 거친 것인지 확인하기 위한 과정이  효율적인 프로그래머와 아닌 사람이 차이가 난다. 효율적인 프로그래머는 머리에서 돌려보고 손으로 계산해 보는 것이 오류를 찾는데 도움을 준다. 비율적인 프로그래머는 우너하는 조합을 찾을때까지 무작정 실험하는 경향이 있다. 만약 루프가 예상했던 대로 작동하지 않는 다면, 비효율적인 프로그래머는 < 기호를 <=로 바꾼다. 만약 여전히 작동하지 않는 다면, 루프 인덱스에 1을 더하거나 뺀다. 결국 이러한 접근 방법은 우연일뿐만 아니라 더 큰 오류를 만들수 있다. 프로그래머는 프로그램이 왜 정확한지 알지 못하는 결과를 초래한다.

머리로 돌려본다는 것은 여러분이 코드가 어떻게 동작하는지 추측하지 않고 이해하고 있다는 것을 의미한다

 

루프 변수사용

  • 배열과 루프에 한계를 두기 위해서 서수나 열거형을 사용하라
  • 읽기 쉬운 중첩 루프를 작성하기 위해서 의미 있는 변수이름을 사용해라
  • 루프 인덱스 혼선(cross-talk)을 피하기 위해서 의미 있는 이름을 사용해라. - 동일한 이름의 변수를 다르게 사용하는 경우에 혼란을 야긴한다
  • 루프 인덱스 변수의 범위를 루프 자체로 제한하라 - 루프안에서 사용하는 변수를 루프 밖에서 사용하지 마라

 

루프가 얼마나 길어야 할까?에 대한 지침

  • 한군에 볼수 있도록 루프의 길이를 짧게 만들어라
  • 중첩을 3수준으로 제한하라
  • 긴 루프의 내부 루프를 루틴으로 이동하라 - 만약 루프가 잘 설계되었다면, 루프 내에 있는 코드를 루프 내에서 호출하는 하나 이상의 루틴으로 이동시킬수 있다
  • 길이가 긴 루프는 특히 명료하게 작성하라 - 길이가 길수록 복잡해 보인다. - 만약 길이가 짧은 루프를 작성한다면 break와 continue 여러개의 탈줄점(exit) 복잡한 종료조건과 같은 위험한 제어 구조를 사용할수 있다

 

 

루프를 쉽게 작성하는 법 - 안에서부터 밖으로

루프를 쉽게 작성하기 위해서는 다음과 같은 순서로 작성한다

한 문장으로 시작한다. 그 경우를 문장으로 작성한 다음에 들여쓰기하고 그 문장 주위에 루프를 입력한다. 그리고 문장을 루프 인덱스나 계산 표현식으로 바꾼다. 기능이 완성될때까지 이 과정을 반복한다.

작업을 마치고 난 후 필요한 모든 초기화 작업을 추가한다. 간단한 경우에서 시작해서 일반화하는 쪽으로 작업하기때문에, 이러한 코드 작성방식을 안에서 밖으로 진행하는 방식을 생각할수 있다.

 

보험회사에서 사용할 프로그램을 가정해보자. 이 프로그램은 고객의 나이와 성에 따라서 달라지는 보험료 표를 갖고 있다. 해야 할 일은 보험료를 계산하는 루틴을 작성하는 것이다. 목록에 있는 각각의 사람에 대한

보험료를 얻어서 총계에 더하는 루프가 필요하다.

  1. 주석으로 루프의 몸체에 수행해야 하는 단계를 작성한다. 문법의 세부 사항과 루프인덱스, 배열 인덱스 등에 대해서 신경쓰지 않으면, 처리해야 하는 일을 쉽게 적을수 있다.

    1단계 - 루프를 안에서부터 밖으로 작성하는 법 - 표에서 보험료를 읽어온다

    • 총계에 보험료를 더한다
  2. 전체 루프를 작성하지 않는 범위내에서 루프 몸체이 있는 주석을 코드로 변환한다 이 경우에는 한 사람의 보험료를 얻어서 총계에 보험료를 더한다. 추상적인 데이터보다는 실질적이고 구체적인 데이터를 사용한다.

    2단계 - rate = table[]

               totalRate = totalRate + rate;

    이 예제는 table이 보험료 데이터를 보관하고 있는 배열이라고 가정하고 있다. 처음에는 배열의 인덱스에 대해서 걱정할 필요가 없다. rate는 보험료 표에서 선택된 보험료 데이터를 보관하고 있는 변수이고 totalRate는 보험료의 총계를 보관하는 변수이다

  3. 다음으로 table배열에 인덱스를 입력한다

    3단계   - rate = table[census.Age ][ census.Gender ]

                totalRAte = totalRate + rate

    배열은 나이와 성으로 접근되기때문에 인자를 주었다. census가 그룹안에 있는 사람들에 대한 정보를 보관하는 구조체라고 가정하고 있다

  4. 다음 단계는 지금까지 작성한 명령문에 루프를 작성하는 것이다. 이 루츠는 그룹에 있는 사람들에 대한 보험료를 계산해야 하기떄문에 사람들 인덱스로 사용한다

    4단계 For person = firstPerson to lastPerson

                  rate = table [census.Age, census.Gender ]

                 totalRate = totlaRate + rate;

            end For

  5. 이제 적절한 들여쓰기를 한 후에 census변수가 사람마다 달라지므로 적절하게 생성되어야 한다

    For person = firstPerson to lastPerson

                  rate = table [census[person].Age, census[person].Gender ]

                 totalRate = totlaRate + rate;

            end For

  6. 마지막으로 초기화코드를 작성한다. 이경우네는 totalRAte변수가 초기화되어야 한다

    totalRate = 0;

    For person = firstPerson to lastPerson

     

                  rate = table [census[person].Age, census[person].Gender ]

     

                 totalRate = totlaRate + rate;

     

            end For

     

 

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 1. 19. 15:53