PS/자주 하는 답변

런타임 에러의 이유가 힌트입니다.

djm03178 2024. 5. 9. 10:58

BOJ에서는 몇 년 전에 아주 편리한 기능을 업데이트했습니다. 바로 런타임 에러의 이유를 찾아주는 기능입니다. 모든 종류의 에러를 다 잡아주지는 못하지만, 흔하게 발생하는 많은 종류의 에러들을, 인터프리터 언어의 예외는 바로 잡아주고, 컴파일 언어의 에러는 디버깅 기능을 이용하여 최대한 에러의 원인을 찾아주는 방향으로 동작합니다. (물론, undefined behavior는 디버거조차도 속일 수 있습니다!)

 

런타임 에러가 발생했을 때 '런타임 에러'라고 쓰인 곳을 클릭하면 도움말 페이지로 이동하는데, 이곳에서 각 에러 종류에 대한 설명과 발생하는 주 원인들을 볼 수 있습니다. 자료가 풍부하지는 않지만, 적어도 초보들이 흔히 실수하는 부분에 대한 설명은 웬만하면 찾을 수 있는 정도는 됩니다.

 

런타임 에러의 이유는 대개의 경우 그 이름만으로도 코드의 문제에 대한 큰 힌트를 제공해 줍니다. 대표적으로 C++에서 'DivisionByZero'는 이름 그대로 코드 어딘가에 0으로 나누는 연산이 있다는 뜻이므로 나누기를 사용한 부분을 중심으로 문제를 찾으면 되고, 'OutOfBounds'는 이름 그대로 배열의 경계를 넘어간 것이므로 배열 크기가 올바르게 설정되었는지, 그리고 루프가 배열의 경계 내에서만 돌게 설정되었는지 등을 위주로 검사하면 되며, 'WithoutReturning'은 리턴을 안 했다는 뜻이므로 반환형이 void가 아닌 함수가 값을 반환하지 않고 끝내는 경우가 있는지, 'IntegerOverflow'는 정수형이 표현할 수 있는 한계를 넘어가는 연산이 있지 않은지를 보면 됩니다. Python에서는 'IndexError'가 났으면 리스트의 크기를 넘어선 인덱스에 접근하지 않는지, 'RecursionError'는 재귀 깊이가 너무 깊어질 수 있지 않았는지, 'ValueError'는 자료형을 올바르게 지정해줬는지 (특히 입력을 제대로 파싱해줬는지), 'ModuleNotFoundError'는 표준에 있지 않은 모듈을 사용하지는 않았는지 (대표적으로 numpy) 등을 확인하면 됩니다.

 

도움말 페이지에 충분한 정보가 없다고 해도, 해당 런타임 에러의 이유를 구글 등에 검색해 보면 대개 좋은 정보가 많이 나옵니다. 비록 이 정보가 에러의 위치까지 정확히 알려주지는 못하더라도, 에러의 대략적인 카테고리를 아는 것만으로도 디버깅에 큰 도움이 됩니다. 적극적으로 이 정보를 활용하기를 권장합니다.

 

또한 이러한 정보는 스스로 디버깅할 때에도 매우 유용하지만, 질문 시 답변자에게도 매우 큰 도움이 되기 때문에 질문글에도 그냥 런타임 에러라고만 쓰기보다 이 이유를 같이 명시해주는 것이 좋습니다. 질문자에게는 익숙하지 않은 에러 종류라고 해도, 경험자들은 에러 이름만 보고도 실수를 쉽게 예측할 수 있는 경우가 많습니다.

 

C/C++의 경우, 런타임 에러가 발생하면 처음에는 이유가 NZEC으로 나왔다가, 잠시 뒤에 '런타임 에러의 이유를 찾는 중'으로 바뀌고 다시 런타임 에러의 이유가 바뀌어 나오는 것을 볼 수 있습니다. 이는 C/C++의 실행 파일은 일반적으로 효율을 위해 최적화된 상태로 실행하므로 에러의 원인을 찾기 위해서는 디버깅 옵션을 주고 디버거를 함께 실행하여 해당 케이스를 다시 돌려봐야 하기 때문입니다. 따라서 이 경우 런타임 에러의 원인은 NZEC이 아니라, 런타임 에러의 이유를 제대로 찾은 이후의 결과를 보아야 합니다. NZEC은 그냥 '프로그램이 0이 아닌 값을 반환했다' = '비정상적으로 종료되었다'를 나타내는 최소한의 신호일 뿐입니다.