본문 바로가기

프로그램언어/code complate

[ Code Complate 2 ] 8 방어적 프로그래밍

요약
  • 8.1 타당하지 않는 입력으로부터 프로그램의 보호
  • 8.2 어설션( assertion )
  • 8.3 오류 처리 기법
  • 8.4 예외
  • 8.5 오류에 의해서 발생하는 손해를 막기 위한 방책
  • 8.6 디버깅 보조 도구
  • 8.7 제품 코드 안에 남는 방어적인 프로그래밍 코드 정의.
  • 8.8 방어적인 프로그래밍에 대해서 방어적이 되는 것.
  • ref :: 실용주의 프로그래머 ( 6장 코딩하는 동안 해야 할 일들 )

프로그래밍에 대해 방어적인 자세를 취하라는 것이 아니라 방어적인 운전을 바탕으로 피해를 입지 않도록 하는 경우를 말한다.

분문발췌: 방어운전을 통해 여러분들은 피해를 당하지 않을 것이라는 확신으로 스스로를 보호할 책임이 있다.


8.1 타당하지 않는 입력으로부터 프로그램의 보호
  쓰레기를 넣으면 쓰레기가 나온다는 말이 있다. 좋은 프로그램은 쓰레기를 넣으면 아무것도 결과를 나타내지 않으며, 정보창을 통해 알려줌으로써 프로그램을 보호해야 한다.
  다음은 쓰레기 입력을 처리하는 일반적인 3가지 방법이 있다.
  1. 외부로부터 들어오는 모든 데이터의 값을 검사하라.
  2. 루틴의 모든 입력 매개 변수 값을 검사하라.
  3. 잘못된 입력을 어떻게 처리할 것인지를 결정하라.

 

8.2 어설션( assertion )
   복잡한 프로그램과 신뢰성이 높은 프로그램에서 특히 유용하게 사용이 된다. 코드가 오류가 있을 경우 보다 빠르게 찾아 낼 수 있다. 코드에서 가정한 것들을 문서화하고 예상치 못한 조건을 찾아내기 위해서 어설션을 사용하라.
  1. 입력(또는 출력) 매개변수의 값이 예상된 범위 안에 들어가는지
  2. 파일이나 스트림이 루틴이 시작할 때(또는 끝날 때) 열려 있는지(또는 닫혀 있는지)
  3. 파일이나 스트림이 루틴이 시작할 때(또는 끝날 때) 시작(또는 끝)에 있는지
  4. 파일이나 스트림이 읽기 전용, 쓰기 전용, 또는 읽기/쓰기 열려 있는지
  5. 입력만 가능한 변수의 값이 루틴에 의해서 변경되지 않는지
  6. 포인터가 널이 아닌지
  7. 루틴에 전달되는 배열이나 다른 컨테이너가 적어도 X개의 데이터 요소를 포함할 수 있는지
  8. 테이블이 실제 값을 포함할 수 있도록 초기화되었는지
  9. 컨테이너가 루틴이 시작할 때(또는 끝날 때) 비어( 또는 채워)있는지
  10. 최적화되어 있는 복잡한 루틴의 결과가 느리지만 분명하게 작성된 루틴과 일치하는지

어설션은 주로 개발과 유지 보수 동안에 사용하기 위한 목적이다. 어설션은 일반적으로 개발 시 컴파일되고 제품에서는 제외된다. 개발중에는 어설션이 모순되는 가정, 예상치 못한 조건, 루틴에 전달된 잘못된 값 등을 검출한다.




< 예제 > 어설션 매크로를 C++로 작성한 예제.

8.2.1 어설션 사용 지침
  • 발생할 것이라고 예상되는 상황들에 대해서 오류 처리 코드를 사용하라. 즉, 절대로 발생해서는 안 되는 조건을 위해서 어설션을 사용하라.
  • 실행 가능한 코드를 어설션 내에 입렵하지 않는다.
  • 선행 조건과 후행 조건을 문서화하고 검증하기 위하여 어설션을 사용하라.
  • 매우 견고한 코드를 작성하기 위해서는 어설튼한 다음 오류를 처리하라.


8.3 오류 처리 기법
  절대로 발생해서는 안되는 오류를 처리하기 위하여 사용된다.

  • 중립적인 값을 리턴하라
  • 다음에 오는 타당한 데이터로 대체하라 [ 데이터가 깨진 경우 ]
  • 이전과 동일한 값을 리턴하라
  • 가강 가까운 타당한 값으로 대체하라
  • 경고 메시지를 파일에 기록하라
  • 오류 코드를 리턴하라.
  • 오류 처리 루틴이나 객체를 호출하라 [ 전역적인 오류처리기 ]
  • 오류가 발생한 곳에서 오류 메시지를 출력하라
  • 지역적으로 가장 잘 작동하는 방법으로 오류를 처리하라
  • 종류하라

오류 처리를 위한 고수준 설계
   프로그램 전체에 걸쳐서 일관된 방법으로 타당하지 않은 매개변수를 처르해야 한다. 오류가 처리되는 방법은 소프트웨어가 정확성, 견고성, 그리고 다른 비-기능적인 특성들과 관련된 요구 사항들을 충족시킬 수 있는 능력에 영향을 미친다. 방어적인 프로그래밍의 가장 큰 핵심은 예상치 못한 오류로부터 보호하는 것이다.


8.4 예외
  코드가 오류나 예외적인 이벤트를 루틴을 호출한 코드에 전달 할 수 있는 특수한 방법이다. 오류가 발생한것을 인지 못하는 코드는 오류를 해석하고 오류를 위해서 유용한 무언가를 할 수 있는 능력이 있는 시스템의 다른 부분에 제어을 넘길 수 있다.

 예외특성 C++  자바  비주얼 베이직
 try-catch 지원  yes  yes  yes
 try-catch-finally 지원  no  yes  yes
 던져질(thrown) 수있는 것  Exception 객체나 Exception 클래스로부터 파생된 객체, 객체 포인터, 객체 참조, string 이나 in 같은 데이터 형  Exception 객체나 Exception 클래스로부터 파생된 객체  Exception 객체나 Exception 클래스로부터 파생된 객체
 잡하지 않은 예외의 효과  abort()에 의해서 기본적으로 호출되고, std::terminate() 에 의해서 기본적으로 호출되는 std::unexception()를 호출한다.  만약 예외가 "cheched 예외"라면 실행 스레드를 중단시키고, "런타임 예외"라면 아무 작업도 하지 않는다.  프로그램을 중단한다.
 던져지는 예외가 반드시 클래스 인터페이스에 정의되어 있어야 하는지의 여부  아니요  예  아니요
 잡히는 예외가 클래스 인터페이스에 정의되어 있어야 하는지의 여부  아니요  예  아니요

정상적인 처리의 일부로 예외를 사용하는 프로그램은 고전적인 스파게티 코드가 갖는 가독성과 유지 보수성 문제로 어려움을 겪는다. - Andy Hunt 와 Dave thomas. -

  • 무시되어서는 안 되는 오류를 프로그램의 다른 부분에 알리기 위하여 예외를 사용하라.
  • 정말로 예외적인 조건인 경우에만 예외를 던져라
  • 책임을 전가하기 위해서 예외를 사용하지 말라.
  • 만약 생성자와 소멸자에서 예외를 잡을 수 없다면 생성자와 소멸자에서 예외를 던지지 말라.
  • 올바른 추상화 수준에서 오류를 던져라.
  • 예외를 야기한 모든 정보를 예외 메시지에 포함시켜라
  • 비어있는 catch 블록을 피하라
  • 라이브러리 코드가 던지는 예외를 파악하라
  • 집중된 예외 보고자의 구축을 고려하라.
  • 예외를 사용을 규격화하라
  • 예외를 대안들을 고려해 보라

 
8.5 오류에 의해서 발생하는 손해를 막기 위한 방책
  타이타닉 구조가 객실 구조로 되어 있었다면은 빙산에 침몰하지 않았을 것이다. [ 구조적 모순에 대한 해결 ]

  • 입력 시에 입력 데이터를 적절한 형으로 변환하라


8.6 디버깅 보조 도구
  방어적인 프로그래밍의 또 다른 핵심적인 측면은 오류를 빠르게 발견하기 위한 협력자인 디버깅 보조 도구의 사용이다.

  • 제품의 제약 사항들을 개발 버전에 무의식적으로 적용하지 않는다.
    개발을 좀 더 원활히 진행하도록 도와주는 도구들을 위해서 개발중에는 속도와 자원의 사용을 기꺼이 교환하도록 한다.
  • 디버깅 보조 도구의 초기 사용
  • 공격적인 프로그래밍을 사용하라.
    - 예외적인 경우는 개발 시 눈에 띄어야 하는 제품 코드가 실행중일 때에는 복구가 가능한 방법으로 처리
  • 디버깅 보조 도구를 제거하기 위한 계획
    - 버전 제거 도구를 사용하고 ant와 make 와 같은 도구를 구축하라
    - 기본으로 제공되는 전처리기를 사용하라
    - 자신만의 전처리기를 작성하라
    - 디버깅 스텁( stub )을 사용하라


8.7 제품 코드 안에 남는 방어적인 프로그래밍 코드 정하기

  • 중요한 오류를 검사하는 코드는 남겨 놓는다.
  • 사소한 오류들을 검사하는 코드를 제거하라
  • 심각한 출동을 야기하는 코드를 제거하라
  • 프로그램이 우아하게 충돌하도록 돕는 코드를 남겨두라
  • 기술적인 지원을 위해서 오류를 기록하라
  • 오류 메시지가 친절한지 확인하라