Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

견고한 프로그램 작성하기

7. 견고한 프로그램 작성하기

이번장에서는 견고한 프로그램을 작성하는 방법에 대해서 알아보도록 하겠다. 견고한 프로그램이란 어떠한 문제도 발생시키지 않는 프로그램이 아니다. 물론 가능한 문제를 발생시키지 않도록 만들어야 겠지만, 문제 발생시 이를 깔끔하게 처리해서 심각한 문제로 가지 않도록 하는 프로그램을 말한다. 문제의 처리를 위해서는 에러코드를 제대로 검사하는 프로그램의 작성이 필요하다.

7.1. 왜 시간이 지연되는가 ?

프로그래머의 공통된 의견 중 하나는 언제나 시간 부족에 시달린다는 점이다. 이러한 사실을 잘 알고 있는 (경험있는) 프로그래머들은 프로젝트 기간을 산정하는데 있어서 자신이 예상하는 시간의 2배 길게는 4배까지를 잡는다. 그러나 그럼에도 불구하고 여전히 시간은 부족하다. 이러한 문제가 발생하는 원인은 다음과 같다.

  • 프로젝트 코드와 관련되지 않는 미팅과 모임, 다른 (틈틈이 발생하는) 업무들이 발생하고 여기에 시간을 빼앗긴다.

  • 프로그래머는 종종 프로젝트에 대한 이해가 부족한 상태에서(물론 자신은 완전히 이해했다고 생각을 하지만) 프로젝트를 실행한다.

  • 프로그래머가 만들어야 하는 제품의 모든 제반사항을 이해할 수 있는건 아니다.

  • 프로그래머는 종종 이전에 수행했던 비슷한 종류의 프로젝트를 경험삼아서 지금 수행하는 프로젝트에 대한 시간을 산출한다(보통 더 짧게). 그러나 수행하다 보면 이전 프로젝트와 지금 프로젝트는 전혀 별개라는 것을 뒤늦게 깨닫게 된다.

  • 프로그래머는 시간을 기간을 계산할 때 프로그램 안정화와 관련된 부분을 빼고 시간을 계산한다. 프로젝트 기간을 초과했을 때 "예전에 말한 프로젝트 시간은 안정화(디버깅및 테스트)와 관련된 부분은 제외한 겁니다."라고 프로젝트 지연을 책망하는 관리자에게 지연이유를 설명하는 모습을 흔히 볼 수 있다.

    막상 프로젝트 종반에 다다르면 디버깅과 테스트에 상당히 많은 시간이 소요된다는 것을 알게된다 - 사실 누구나 다 알지만 막상 프로젝트가 닥치면 무시하는 경우가 많다 -.

이러한 지연요소 중 마지막 요소인 "견고한 프로그램 작성"에 관련된 내용을 다룰 것이다. 프로그램 작성시에 견고한 (방식의)코딩을 한다면 나중에 디버깅과 테스트 시간을 상당히 줄일 수 있을 뿐더러, 질 좋은 제품을 만들어 낼 수 있다.

대문자 변환(toupper) 프로그램은 분명히 잘 작동하긴 하지만, 그것은 조건이 완벽하게 맞아들어가는 경우가 된다. 예를 들면 파일이 존재하지 않는다거나 존재하지만 권한문제 등으로 읽어들이지 못하는 경우가 발생할 수도 있다. 우리가 만든 프로그램은 이러한 예외 상황에 대처하지 못하고 있다. 문제가 발생할 경우 어디에서 발생했는지 알아 내기 힘들 수도 있다.

그래도 toupper 프로그램은 꽤나 작은 프로그램이기 때문에 문제가 좀 발생해도 사후 처리를 하는데 그리 문제가 되지 않겠지만, 프로그램의 크기가 커지면 심각한 문제가 될 수 있다. 수천 수만라인의 프로그램이 돌다가 갑자기 "암런 에러메시지도 없이" 작동이 중단되어 버린다면 난감할 것이다. 그러므로 프로그램을 만들 때는 상태체크하고 이를 제어하는데 많은 시간을 할애해야 한다. 만약 여러분이 프로그램의 완성 한데 2주의 시간이 걸린다고 하면 최소한 그중 2일정도를 견고한 프록램을 만드는데 사용해야 한다. 모든 에러메시지들은 항상 화면에 출력 될 수 있어야 함을 기억하라.

7.2. 견고한 프로그램을 만들기 위한 몇가지 팁

7.2.1. 사용자 테스트

테스트는 프로그래머가 해야될 가장 기본적인 업무중 하나다. 만약 충분한 테스트가 이루어지지 않는다면, 제대로된 작동을 보장할 수 없게된다. 예를 들어 여러분의 프로그램이 양수를 입력받아서 처리해서 결과를 알려주는 프로그램이라고 가정해 보자. 그런데 어떤 이유로 음수가 들어올 수도 있을 것이다. 혹은 숫자 대신에 문자가 출력된다 든지 지나치게 큰 수가 입력되거나 0이 입력되는 등의 일이 발생할 수 있을 것이다. 이런 경우에 대해서 충분히 테스트가 이루어 져야 한다. 또한 처음과 마지막에 공백문자가 입력된 경우, 중간에 공백문자가 입력된 경우 아뭏든 발생가능 할거라고 생각되는 모든 경우에 대해서 테스트 해야 한다. 사용자는 어떤 입력을 할지 예측할 수 없기 때문이다.

문제가 발생되는 경우를 확인했다면, 재입력을 요구할 건지, (공백 등을 제거해서) 제대로된 데이터로 만들어 줄건지, 프로그램의 다시 실행시킬 건지를 결정해줘야 한다. 이런 테스트가 충분하지 않다면, 실제 제품이 출시되고나서 고객들로 부터 테스트를 받게 될 것이다. 물론 상당한 스트레스가 될 것이다.

7.2.2. 데이터 테스트

프로그램을 디자인 할때, 당신이 사용하는 각각의 함수는 받아들여야할 데이터의 범위에 대해서 명확히 해야 한다. 이를 위해서 프로그래머는 함수에 입력되는 데이터에 대한 테스트를 해야 한다. 가장 중요한 테스트는 corner caseedge case 테스트다. 간단히 말해서 경계값을 입력했을 때 어떤 문제가 발생하는지를 테스트 하는 것이다.

다음은 이러한 테스트를 위해서 특별히 다음과 같은 값들을 중요하게 테스트 해야 한다.

  • 숫자 0

  • 숫자 1

  • 범위에 들어가는 숫자

  • 범위를 초과하는 숫자

  • 범위에서 가장 큰 숫자

  • 범위에서 가장 작은 숫자

  • 범위에서 가장 작은 숫자보다 한단계 작은 숫자

  • 범위에서 가장 큰 숫자보다 한단계 큰 숫자

예를 들어서 5와 200사이의 값을 받아들이는 프로그램이라고 한다면 개인적으로 0, 1, 4, 5, 153, 200, 201, 255등에 대해서 테스트를 할것이다. 그리고 어떤 수들에 대해서 중점적으로 테스트를 해야 하는지에 대해서도 결정할 필요가 있다. 예를들어서 프로그래머의 나이를 받아들여서 테스트하는 프로그램을 작성한다고 하면 20에서 40사이의 수에 대해서 집중적으로 테스트할 필요가 있기 때문이다.

특히 0에 대해서는 각별히 테스트할 필요가 있는데, 만약 함수가 입력된 값으로 나누는 연산이 있는데, 이 때 0이 입력되면 프로그램의 작동이 아예 종료되어 버리는 경우가 발생할 수도 있기 때문이다.

이런 이유로 내부에서 사용되는 함수들은 올바른 데이터인지, 혹은 좋은 데이터인지를 확인하기 위해서 (값의 범위가 0-255까지라고 했을 때, 사람의 나이를 체크하는 프로그램 이라면, 244가 비록 올바른 데이터라고 하더라도 좋은 데이터라고는 할 수 없다), 대소 관계를 이용하게 된다. 만약 입력된 값이 범위를 벗어난다면 에러메시지를 출력하거나 프로그램이 종료하도록 하면 될것이다. C언어에서는 assert라는 매크로 함수를 이용하면 이러한 체크과정을 좀더 수월하게 할 수 있다. assert함수는 조건을 테스트해서 조건에 맞지 않으면 에러메시지를 출력하고, 내부적으로 abort()함수를 호출해서 프로그램을 종료 시킨다. 만약에 assert함수가 활성화 되지 않도록 할려면 컴파일시 NDEBUG옵션을 주면 된다.

#include <assert.h>

int main()
{
  int a=3, b=4;
  assert(a > b);
}
				
위 프로그램을 컴파일해서 실행 시키면 에러가 발생하고 프로그램이 종료된다. 만약 assert함수를 무시하고 싶다면 아래와 같이 컴파일 하면 된다.
# gcc -DNDEBUG -o assert assert.c
				
이렇게 코드를 만들어 놓으면 디버깅 코드와 실제 배포될 코드를 구분지을 수 있다는 장점을 가진다. 어쨋든 고객에게 배포되는 코드에 디버깅 메시지가 출력되거나 해서는 난감하기 때문이다.

7.2.3. 모듈 테스트

어떤 테스트를 할때 반드시 프로그램의 모든 함수와 흐름에 대해서 테스트를 할 필요는 없을 것이다. 이런식의 테스트는 보통 시간도 많이 걸리고 체크범위가 넓어져서 문제점을 찾아내기도 힘들기 때문이다. 그래서 보통은 각 모듈(함수)단위로 테스트를 하고, 어느정도 테스트가 끝나면 전체 테스트를 하게 된다. 함수단위로 테스트를 하면 아무래도 좀더 빠르고 명확하게 문제점을 확인하고 예측할 수 있기 때문이다.

프로그램은 어떤 문제를 풀기위해서 여러개의 함수를 호출하며, 프로그래머는 이 함수들을 단계별로 체크함으로써 테스트를 수행하게 된다. 함수에 대한 체크는 입력값과 결과(return 값)을 확인함으로써 이루어진다. 프로그램의 테스트를 단위 함수별로 분리시켜서 쉽게 테스트 할 수 있다는 것도 프로그램을 여러개의 조그만 함수들로 쪼개서 개발하는 이유이기도 하다.

또한 함수단위로 테스트는 프로그램이 완전히 개발되지 않은 상태에서도 가능하다. 전자 상거래 관련 애플리케이션을 만든다고 한다면 is_ready_to_checkin이라는 함수를 만들어서 고객의 인증관련 함수를 먼저 만들어서 이부분만 따로 떼어내서 테스트 할 수 있을 것이다. 만들어지고 테스트된 함수는 레고블럭 쌓듯이 전체 프로그램에 가져다 붙이기만 하면 된다.

7.3. 에러 핸들링의 기본

테스트가 중요하다는 것은 말할 필요도 없을 것이다. 여기에서는 테스트를 통해서 에러를 찾아내고 이를 처리하는 방법에 대해서 알아보도록 하겠다.

7.3.1. 모든 것은 에러코드를 가진다.

7.4. 견고한 프로그램으로 만들기

이번장에서는 6.3절에서 다루었던 add-year.s 프로그램을 견고하게 만들어 보도록 하겠다.

이 프로그램은 간단하기 때문에 단지 하나의 recovery 포인터를 가지도록 제한하겠다.