방어적 프로그래밍

3 minute read

8. 방어적 프로그래밍

  • 잘못된 입력으로부터 프로그램 보호
  • assertion
  • 오류 처리 기법
  • 예외
  • 오류로 인한 손상을 막기 위한 방책
  • 디버깅 보조 도구
  • 얼마나 방어적으로 프로그래밍할 것인가?
  • 방어적 프로그래밍에 대해 한 번 더 고민하기

8.1 잘못된 입력으로부터 프로그램 보호

  • 외부 입력 데이터 값을 검사하라

  • 루틴의 모든 입력 매개변수 값을 검사하라 (8.5 참조)

  • 잘못된 입력 처리 방법 (8.3 참조)

  • 사전예방이 더 중요! (반복 설계, 사전 의사코드 작성, 사전 테스트 케이스 작성, 저수준 설계에 대한 정밀 검사 등)

8.2 assertion

  • spec 테스트 비슷하다. 절대 절대 발생하면 안되는 오류 검증에 사용
    • 선행조건(메소드 입력값, 루틴 진행 중 변수 상태)
    • 후행 조건(루틴 호출 후 결과)
  • 사전예방이 더 중요! - 사례 선행조건과 후행조건이 최대한 적게 설계하는 것이 더 중요할 듯
  • 책의 예제는 테스트 파일을 따로 분리하기 전의 방식인 것 같다.
    • 테스트 파일을 따로 분리하는 것이 관리가 편한 듯

8.3 오류 처리 기법

  1. 상황에 따라 적절한 선택지를 선택해야 한다.

    • 안전한 값 반환 - 예: 문자열 연산에서 빈 문자열, 수식연산에서 0 등
      • 정확한 데이터가 필요하면 사용해선 안 됨
    • 다음 유효 데이터로 대체
    • 연속으로 데이터를 읽어오는 경우
    • 이전과 같은 값 반환
      • 변화가 점진적인 경우
    • 가장 가까운 유효 값으로 대체.
    • 경고 메시지 로그
      • 암호화여부 고려해야 함
    • 오류 코드 반환: 시스템의 다른 부분에 오류 발생을 알리는 것
      • 상태 변수에 값을 설정
      • 함수 리턴 값으로 상태 값 반환
      • 프로그래밍 언어에서 제공하는 예외 메커니즘 활용
    • 오류 처리 루틴/객체 호출
      • 장점: 오류 처리를 한 곳에 모아서 디버깅이 쉽다
      • 단점: 전체 프로그램과 오류 처리 부분이 밀접하게 결합, 코드 재사용 어려워짐
      • 중요한 보안 문제가 있다!
        • 버퍼오버플로우 같은 방식으로 공격자가 오류 처리 루틴/객체의 주소를 손상시킬 수 있음
    • 오류 발생한 곳에서 오류 메시지 출력
      • 장점: 오류 처리 비용 최소
      • 단점: UI와 나머지 시스템 경계를 흐림, 지역화에 어려움, 공격자에게 정보를 줌
    • 상황에 맞게 각자 최선의 방식으로 처리
      • 장점: 개발자에게 유연성 제공
      • 단점: 각 개발자 역량에 따라 전체 시스템의 견고함이 요구사항을 만족하지 못할 수 있고, 오류 메시지 출력과 비슷한 문제도 발생 가능.
    • 종료한다
      • 안정성이 매우 중요한 곳에서 유용(잘못된 값이 치명적일 수 있는 경우)
  2. 견고함 VS 정확성

    • 견고함: 부정확한 결과를 내더라도 소프트웨어가 계속 작동
    • 정확성: 부정확하느니 아무 결과도 내지 않거나 종료
  3. 오류 처리를 위한 상위 수준 설계

  • 전체 프로그램 내에서 일관된 방식으로 오류를 처리해야 한다.

  • 아키텍트나 상위 수준 설계에서 오류 처리의 일반적 접근법을 결정한다.

  • 접근법이 정해지면, 모두가 일관성 있게 따라야 한다. 코드 컨벤션의 중요성

  • 하위 수준에서 에러를 반환하고, 상위 수준에서 처리하기로 했다면 처리해야 함! 빼먹지 말고.

  • 별 문제 없을 것이라 예상해도 리턴 값을 꼭 검사하자 - 방어적 프로그래밍의 핵심이 예상하지 못한 오류에 대응하는 것이다.

8.4 예외

  • 예외를 사용해 무시해서는 안 되는 오류를 프로그램의 다른 부분에 알림
  • 정말 예외적일 때만 예외를 던져라. 예외는 캡슐화 약화시킴
  • 책임 전가하기 위해 예외를 사용하지 말 것
  • 생성자, 소멸자에서 예외 던지지 마라
  • 올바른 추상 수준에서 오류를 던져라
  • 예외 발생시 모든 정보를 예외 메시지에 포함
  • 비어있는 catch 블록 X
  • 라이브러리 코드가 던지는 예외를 파악하라
  • 중앙 집중화된 예외 보고 시스템 구축을 고려하라
  • 프로젝트 예외 사용을 규격화하라: 프로젝트 전체에서 같은 방식으로 예외 처리하라?
  • 예외의 대안을 고려하라 (종료?)

8.5 오류로 인한 손해 방비책

  • 바리케이트 - 안전하지 않은 데이터 입력 받는 공개된 영역에서 데이터를 검사.살균(?)하여 비공개 영역으로 넘긴다
    • 데이터를 적절한 타입으로 변환
  • 방어 시설과 assertion의 관계
    • 외부 데이터에 대해서는 예상 입력 범위를 정할 수 없기 때문에 assertion을 사용할 수 없고, 오류 처리를 사용해야 한다
    • 방어 시설을 사용하면 아키텍처 수준에서 오류 처리 방법을 결정할 수 있다: 방어 시설 내부에 둘 코드와 외부에 둘 코드를 결정하는 것

8.6 디버깅 보조 도구

  • 제품 제약사항을 개발 버전에 무의식적으로 적용하지 않는다 (디버깅에 사용할 자원이 더 있다는 뜻?)
  • 디버깅 보조 도구를 초기에 도입
  • 공격적인 프로그래밍 기법 사용
    • assert로 프로그램 중단
    • 메모리 할당 오류를 발견할 수 있게 할당된 모든 메모리를 꽉 채움
    • 파일 형식과 관련된 오류를 발견하기 위해 파일이나 스트림을 꽉 채움
    • 객체를 삭제하기 전에 쓰레기 데이터를 채움
    • 가능하면 소프트웨어에 오류가 발행하면 이메일로 오류 로그 파일을 보내도록 설정
  • 디버깅 보조 도구를 제거할 계획을 세운다
    • 버전 관리 도구, ant, make 같은 빌드 도구를 사용
    • 기본 제공되는 전처리기 사용
    • 자신만의 전처리기를 작성
    • 디버깅 루틴 작성

8.7 제품 코드를 얼마나 방어적으로 프로그래밍할 것인가

  • 중요한 오류 검사는 남김
  • 사소한 오류 검사는 제거
  • 심각한 충돌을 발생시키는 코드를 제거 (견고함 UP, 데이터 loss 등 사용자에게 대비할 수 있는 기회를 줘야 함)
  • 프로그램이 우아하게 충돌하도록 돕는 코드를 남겨라. 위랑 비슷한 얘기인 듯
  • 기술 지원을 위해 오류를 기록하라
  • 오류 메시지가 친절한지 확인 (사용자가 상황을 파악하거나 대처할 수 있도록)

8.8 방어적 프로그래밍에 대해서 한 번 더 고민하기

  • 꼭 필요할 때만 사용하기

Tags: , ,

Updated: