Summary
동료 평가에서 예외 처리의 기준에 대한 이야기가 많이 이루어지는 것 같습니다. 하지만 그 이전에 예외의 정의에 대한 이야기가 선행되어야 할 것 같습니다. 제가 알고 있는 수준에서 정의된 동작, 지정되지 않은 동작, 정의되지 않은 동작, 예외에 대한 이야기를 공유해보고자 합니다.
Function
함수는 집합 간의 방향성이 있는 대응 규칙입니다. 이 때 대응 관계에서 정의역의 원소를 결정자(Determinant), 치력의 원소를 종속자(Dependent)고 부릅니다.
하나의 결정자에는 반드시 하나의 종속자가 대응되어야 합니다. (복수의 결정자에 동일한 종속자가 대응할 수는 있습니다.) 우리가 구현하는 함수도 동일한 입력에 대해서는 항상 유일한 출력값을 반환해야합니다.
Defined Behavior
입력값이 정의역의 원소이고 출력값이 유일하다면 출력값은 정의된 행동입니다. 우리가 함수의 ‘기능’이라고 부르는 것이 바로 정의된 행동입니다.
프로그래밍 언어에서 정의된 행동은 어떠한 Statement가 반드시 하나의 Instruction set으로 generate될 수 있음을 의미합니다. 다시 말해 하나의 소스코드를 어떤 컴파일러로 컴파일하더라도 동일한 목적 코드가 생성됩니다.
Unspecified Behavior
하나의 결정자에 두 개 이상의 종속자가 대응되는 성질을 모호성이라고 부릅니다. 그리고 모호성이 있는 행동을 지정되지 않은 행동이라고 부릅니다. 동일한 입력 값에 대해 출력 값이 유일하지 않을 수 있다는 의미입니다.
프로그래밍 언어에서 지정되지 않은 행동은 어떠한 Statement가 컴파일러에 따라 다른 목적 코드를 생성할 수 있음을 의미합니다. 이런 상황은 해당 Statement를 어떤 목적 코드로 generate할 것인지 Document에 명시되어있지 않기 때문에 발생할 수 있습니다.
Undefined Behavior
입력값이 정의역의 원소가 아닐 때 출력값은 정의되지 않은 행동입니다. 만약 정의역을 Integer로 정의한 함수 F에 1.5라는 값이 입력되었다면 함수가 반환하는 출력 값은 정의되지 않은 행동입니다.
위와 같이 정의역의 원소가 아닌 값이 입력되었을 때 반환되는 값은 치역의 원소가 아닐 수도, 우연히 치역의 값일 수도 있습니다. 어떤 값이 나올지 예측할 수 없습니다.
정의역의 원소가 단순히 수치적 값이 아니라 어떠한 상황이나 행동일 수도 있습니다. 가장 대표적인 정의되지 않은 행동이 바로 널포인터를 역참조하는 행동입니다. 후술하겠지만 정의되지 않은 행동은 예외로 이어질 가능성이 높아 고급 언어에서는 정의되지 않은 행동으로 유래될 수 있는 예외 클래스를 정의하는 경우가 많습니다.
지정되지 않은 행동과 정의되지 않은 행동의 차이점이 궁금하신 분도 계실 것 같습니다. 전자의 경우에는 정의역의 결정자가 치역의 종속자에 대응하지만 2개 이상의 종속자에 대응하는 경우이고 후자는 애초에 결정자가 정의역의 원소가 아닌 경우입니다.
Exception
입력값이 정의역의 원소이지만 유일한 출력값을 반환할 수 없는 상황을 예외라고 부릅니다. 예외의 원인에는 하드웨어의 자원 부족, 정의되지 않은 행동 등이 있습니다.
함수가 항상 예측할 수 있는 유일한 값을 반환하면 좋겠지만 현실은 그렇지 못합니다. 앞서 설명한 하드웨어의 자원 부족이나 정의되지 않은 행동과 같은 소스코드 외적인 변수들이 있기 때문입니다. 따라서 예외 상황에서의 행동도 유일하게 결정할 필요가 있습니다. 이를 예외 처리라고 부릅니다.
Example : ft_printf
Undefined format specifier
ft_printf에 format에 %q 라는 값이 전달되었다고 가정하겠습니다. format에서 사용할 수 있는 서식자 중에서 q라는 서식 지정자는 없습니다. 따라서 %q 는 ft_printf의 정의역이 아닙니다. 따라서 %q 라는 값이 foramt으로 전달되었을 때 반환되는 값은 정의되지 않은 행동입니다.
write fail
ft_printf에 유효한 입력 값을 집어넣었음에도 write함수가 실패하여 예측했던 출력값이 나오지 않을 수 있습니다. ft_printf의 입장에서 이 상황은 예외입니다. 따라서 ft_printf가 예외 상황에서 유일한 값을 반환할 수 있도록 해줄 필요가 있습니다. 원본 함수의 경우에는 -1를 반환합니다.
Conclusion
하지만 소프트웨어는 사람이 만듭니다. 설령 우리가 함수의 매개변수를 int로 선언하더라도 프로그래머가 해당 함수의 Document에 float 값이 들어왔을 때의 행동을 명시해두었다면 Integer 뿐만 아니라 Float도 정의역이 될 수 있습니다. 그래서 항상 프로그래머가 작성하는 소스코드 이상으로 소스코드를 설명하는 Document가 중요하다는 생각이 듭니다. 반대로 매개변수를 Integer로 받더라도 Document에 Short값만 취급한다고 명시되어있다면 오로지 Short 값의 범위만 정의역이 될 것입니다. 때문에 man 명령어를 통해 함수의 설명을 살펴보면 DESCRIPTION과 RETURN VALUE를 통해 정의역와 치역, 예외 상황에서의 반환 값을 명시하는 것을 확인할 수 있습니다.
상술했듯이 소프트웨어는 사람이 만들기 때문에 모든 예외에 대처하는 것에는 어려움이 있습니다. 설령 더 많은 예외를 처리할 수 있더라도 예외 처리 과정에서 발생하는 오버헤드를 고려하여 발생 가능성이 희박한 예외 처리를 제외하는 것도 프로그래머의 재량이지 않을까 싶습니다. (물론 그러한 재량은 충분한 근거가 뒷받침될 필요가 있습니다.) 이에 대해 정중한 이야기를 나누어보는 것도 서로에게 좋은 공부가 될 것 같습니다.
사실과 다른 부분이 있다면 댓글로 피드백 부탁드리겠습니다. 감사합니다.