포스트

실용주의 프로그래머 4

실용주의 편집증

여러분은 완벽한 소프트웨어를 만들 수 없다.

세상에 완벽한 소프트웨어는 존재하지 않는다. 그래서 우리는 방어적으로 개발을 해야 한다. 문제가 생기기 전에 주의하고, 일어나지 않을 법한 일에 대비해야 한다. 조금이라도 의심이 들면 주어진 모든 정보를 확인한다. 잘못된 데이터를 찾아내기 위해 단정문을 사용하고, 공격자나 불량 사용자가 만들었을지 모르는 데이터를 불신한다. 실용주의 프로그래머는 자기 자신 역시 믿지 않는다.

계약에 의한 설계

정직한 거래를 보장하는 최선의 해법 중 하나는 ‘계약’이다. 계약은 자신과 상대편의 권리 및 책임을 정의한다.

DBC

계약에 의한 설계 (Design By Contract) 라는 개념이 존재한다. 이는 다음과 같은 개념을 가진다.

  • 선행 조건 : 루틴이 호출되기 위해 참이어야 하는 것. 즉 루틴의 요구 사항이다.
  • 후행 조건 : 루틴이 할 것이라고 보장하는 것. 즉 루틴이 완료됐을 때 세상의 상태다.
  • 클래스 불변식 : 호출자 입장에서 조건이 언제나 참인 것이 보장된다. 루틴이 끝나고 호출자로 제어권이 반환되는 시점에는 불변식이 참이어야 한다.

즉, 호출자가 루틴의 모든 선행 조건을 충족한다면, 루틴은 종료 시 모든 후행 조건과 불변식이 참이 되는 것을 보장한다. 이것이 계약이다. 계약에 기반해서 설계를 하고, 계약에 근거해서 제약 조건을 추가함으로서 안전한 프로그래밍을 할 수 있을 것이다.

DBC 구현

DBC 는 설계 깁접이다. 단정문을 사용해서 계약을 검사할 수도 있고, 메서드 호출 후 불변식 검사를 할 수도 있을 것이다.

일찍 멈추기

검사 결과로 계약에 위배된다면 일찍 작동을 멈추는 것이 현명하다. 오류를 처리하려다 예상 밖의 결과를 얻는 시점이 생각보다 한참 나중일 수 있다. 문제를 찾고 원인을 밝히기 위해서는 사고가 난 지점에서 일찍 멈추는 것이 유리하다.

죽은 프로그램은 거짓말을 하지 않는다.

방어적으로 코딩하기 위해서는 데이터가 생각한 대로인지, 서비스에서 작동하는 코드가 우리가 생각하는 그 코드인지 확인해야 한다. 필요한 라이브러리들이 올바른 버전으로 실제로 로드됐는지도 확인해야 한다.
모든 오류는 정보를 준다. 예외를 잡아서 처리하기보다 그대로 전파하는 편이 추적에 유리할 수 있다. 게다가 문제가 발생하면 좀 더 일찍 시스템을 멈출 수 있으니 더 낫다.
얼랭과 엘릭서의 경우에는 프로그램이 실패하고 멈추도록 설계되어 있다. 이런 실패는 슈퍼바이저가 관리하고, 슈퍼바이저는 코드를 실행시키고, 실패된 경우의 처리를 담당한다. 이런 기법은 고가용성, 결함 감내 시스템에서 효과적이다.

단정적 프로그래밍

모든 프로그래머가 암기해야 하는 계명이 있다.

그런 일은 절대 일어날 리 없어

코딩할 때는 단정문으로 불가능한 상황을 예방하는 자세를 가져라. 그런 일이 일어나지 않을 거야 라는 생각이 든다면, 그런 일을 확인하는 코드를 추가해라. 문제를 발견하려고 넣은 코드가 오히려 새로운 문제를 만드는 결과를 낳기도 한다. 디버깅하는 행위가 디버깅하려는 시스템의 행동을 바꿔버리는 ‘하이젠버그’적인 문제다.
단정 기능은 테스트나 배포 단계 이후에는 필요치 않다고 생각할 수 있다. 그러나 테스트로는 모든 버그를 발견하기 어렵고, 프로그렘이 돌아가는 리얼 월드는 많은 예기치 못한 문제가 있을 수 있다는 것이다. 따라서 가능한 오류를 모두 검사하고, 놓친 것을 잡아내기 위해 단정을 사용하라. 프로그램을 출시할 때 단정 기능을 꺼버리는 것은 줄타기 곡예를 연습으로 한 번 건넜다고, 그물 없이 건너는 것이다.

리소스 사용의 균형

리소스 할당과 해제를 안전하게 하는 일관된 방침이 있다.

자신이 시작한 것은 자신이 끝내라

리소스를 할당하는 함수나 객체가 리소스를 해제하는 책임 역시 져야 한다는 뜻이다. 파일을 열고 쓰고 닫는 경우를 생각해보자

1
2
3
4
5
6
7
8
def update_customer(transaction_amount)
  file=File.open(@name +_ ".rec", "r+")
  ..
  ..
  wrtie_customer(file)
  file.close()

파일 객체를 매개 변수로 전달해서 파일을 쓰고, 파일을 쓰는 행위의 오류 여부에 관계 없이 항상 update_customer 메서드에서 파일을 다는다. 지역적으로 생각하면 파일은 해당 메서드 내부에서만 열렸다 닫힌다.
다른 리소스 할당의 경우에도 이런 원칙을 지키며, 항상 할당의 역순으로 해제하라. java 에서는, try carch finally 절을 활용할 수 있을 것이다. finally 절에서는 예외가 발생하든 말든 정리를 진행하기 때문이다.

1
2
3
4
5
6
7
8
begin
    thing = allocate_resource();
    process(thing)
finally
    deallocate(thing)
end

이런 식으로 사용하는 것이 흔한데 위와 같은 경우에는 할당이 실패한 경우에도 thing 을 해제하려고 한다. 따라서 핟당을 begin 절 이전에 해줘야 할 것이다.

헤드라이트를 앞서가지 마라

소프트웨어 개발에서도 ‘헤드라이트’가 제한되어 있다. 먼 미래를 내다볼 수 없고 정면에서 벗어난 곳일수록 더 어둡다. 따라서 언제나 작은 단계들을 밟아라.
언제나 신중하게 작은 단계를 발고, 피드백을 확인하고 조정해라. 피드백은 테스트의 결과일 수도 있고, 사용자 데모나 대화일 수도 있다. 불확실한 미래ㅔ 대비한 설계를 하느라 진을 빼기보다 언제나 교체 가능한 코드를 작성하여 대비해라.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.