5.2) 로그 스트림
5장의 QuickNASM을 사용하면서도, 6장에서 NASM을 배울 때도 우리는 항상 로그 스트림을 만들었다. 왜냐하면 로그 스트림은 프로그램에서 발생한 예외를 화면을 옮기지 않고 바로 알아볼 수 있도록 하는 아주 편리한 도구이기 때문이었다. 따라서 여기서도 로그 스트림을 사용한다.
로그 스트림을 만드는 방법은 이미 6장을 공부해서 잘 알고 있다. 다만 이전에 만든 main 뼈대 파일은 예외가 발생하면 어디서 예외가 발생했는지를 알아보기 힘들기 때문에 이 문제를 개선하는 것이 좋겠다. 예를 들어 다음의 코드는 정의되지 않은 식별자를 사용했기 때문에 오류가 발생한다.
Error.htm |
<html> <body> <script> test; </script> </body> </html> |
실행 결과 |
|
test가 정의되지 않았다는 예외가 catch되지 않아서 콘솔 창에 로그가 출력된다. 사실 Chrome 브라우저는 F12 버튼을 누르면 개발자 모드로 들어가고 내부적으로 로그 스트림을 지원한다. 다만 이 프로젝트에서는 이것을 통해 로그를 확인하는 것이 불편하다고 생각했기 때문에 별도로 스트림을 만든 것이다. 이렇게 문법 오류 등의 이유로 브라우저 내부에서 발생하는 예외 객체는 일반적으로 Error 형식이 된다.
잠깐 생각해보자. C++에서는 try-catch 구문을 쓸 때 예외의 형식에 따라 루틴을 구분할 수 있었다.
TryCatch1.cpp |
#include <iostream>
// 일반 예외 형식 Exception을 정의합니다. class Exception { std::string _msg; public: Exception(const std::string &msg) : _msg(msg) {} const char *toString() const { return _msg.c_str(); } }; // null 포인터를 참조하는 경우 반환할 예외 클래스입니다. class NullPointerException: public Exception { public: NullPointerException() : Exception("포인터가 null을 참조합니다.") {} }; // 0으로 나누려고 한 경우 반환할 예외 클래스입니다. class DivideWithZeroException: public Exception { public: DivideWithZeroException() : Exception("0으로 나누려고 했습니다.") {} };
int main(void) { try { int n = 5, d = 0; // 분자(numerator), 분모(denominator) 변수 int *ptr = nullptr;
// 포인터가 분모 변수를 가리키게 합니다. ptr = &d; // 이 문장을 주석 처리해보세요! if (ptr == nullptr) // 포인터가 null이라면 예외 처리합니다. throw NullPointerException(); std::cin >> (*ptr); // 포인터를 이용하여 사용자로부터 값을 입력받습니다.
// 입력받은 수를 이용하여 나눗셈 연산을 시도합니다. if (d == 0) // 분모가 0이라면 예외 처리합니다. throw DivideWithZeroException(); // 결과를 출력합니다. std::cout << n << " / " << d << " = " << (n / d) << std::endl; return 0; } catch (DivideWithZeroException &ex) { std::cout << ex.toString() << std::endl; std::cout << "0으로 나누지 마세요." << std::endl; return 4; } catch (NullPointerException &ex) { std::cout << ex.toString() << std::endl; std::cout << "null 포인터를 참조하지 마세요." << std::endl; return 3; } } |
이렇게 형식에 따라 루틴을 분리할 수 있다면 코드의 가독성이 높아진다. 따라서 이는 아주 바람직한 방법이다. 그런데 저번 문서에서 말했듯 JavaScript의 예외 처리 구문에서는 형식을 기록하지 않는다. 그렇다면 JavaScript에서는 어떻게 예외를 구분해야 할까?
방법은 두 가지가 있다. 다른 객체와 절대로 중복되지 않을 이름을 하나 골라서 필드로 만들고, 이 필드의 값을 적당한 값으로 설정하면 일반적인 형식과 이 필드가 있는 형식을 구분하는 것이 가능하다. 예를 들어 다음과 같이 하면 적당한 예외 처리 모델이 된다.
Exception1.htm <html.body.script> |
/** jscc 일반 예외 형식 Exception을 정의합니다. @param {string} msg */ function Exception(msg) { // 예외에 대한 설명을 보관하는 문자열입니다. this.message = msg; // 다른 멤버와 중복되지 않을 필드를 만들고 적당히 설정합니다. this.handy = true; }
// 0으로 나누려고 시도하면 예외를 반환하는 예제입니다. try { var n = 10, d = 0;
// d = 5; // 주석을 해제해보세요! if (d == 0) { // 0으로 나누려고 하면 예외를 발생합니다. throw new Exception("Tried to divide number with 0"); } alert(n / d); // 0으로 나눈 결과를 출력합니다.
// 일반 예외 형식인 Error 객체를 발생합니다. test;
} catch (ex) { // handy 필드가 정의되지 않았다면 Exception 객체가 아닙니다. if (ex['handy'] == undefined) { alert('Catched object is not instance of Exception'); } // 그 외의 경우 Exception 객체로 간주하고 처리합니다. else { alert(ex.message); } } |
다른 방법은 instanceof 연산자를 이용하는 것인데 다음과 같다.
Exception2.htm <html.body.script> |
function Exception(msg) { this.message = msg; }
try { var n = 10, d = 0;
// d = 5; // 주석을 해제해보세요! if (d == 0) { // 0으로 나누려고 하면 예외를 발생합니다. throw new Exception("Tried to divide number with 0"); } alert(n / d); // 0으로 나눈 결과를 출력합니다.
// 일반 예외 형식인 Error 객체를 발생합니다. test;
} catch (ex) { // ex가 Exception의 인스턴스인 경우의 처리입니다. if (ex instanceof Exception) { alert(ex.message); } // 그 외의 경우에 대한 처리입니다. else { alert('Catched object is not instance of Exception'); } } |
여기에서 알 수 있듯이 불필요한 멤버를 추가하는 것보다 instanceof 연산자를 이용하는 것이 코드도 깔끔해지고 목적도 명확하므로 instanceof 연산자를 사용하는 것이 바람직하다고 할 수 있다.
그럼 이제 예외 처리가 고려된 새로운 뼈대 파일을 보이겠다.
main.htm |
<html> <head> <script> function main() { } function init() { var logStream = document.getElementById('HandyLogStream'); logStream.style.width = 800; logStream.style.height = 200; }
/** @param {string} msg @param {object} data */ function Exception(msg, data) { this.description = msg; this.data = (data != undefined) ? data : null; } Exception.prototype.toString = function() { var type = 'Exception: '; var message = this.description; var data = (this.data != undefined) ? this.data.toString() : ''; return type + message + ' [' + data + ']'; }
/** 로그 스트림에 문자열을 출력합니다. @param {string} message */ function log(message) { var logStream = document.getElementById('HandyLogStream'); logStream.value += (message + '\n'); } </script> </head> <body> <textarea id='HandyLogStream'></textarea> <script> try { init(); main(); } catch (ex) { if (ex instanceof Exception) { log(ex); } else { // Exception 예외 객체가 아니라면 여기서 처리하지 않습니다. throw ex; } } </script> </body> </html> |
그런데 이렇게 써놓고 보니 파일 하나에 코드가 지나치게 길다. init과 main은 우리가 수정할 함수이니 htm 파일에 있는 것이 편하다고 쳐도, log 함수와 Exception 생성자 함수는 앞으로 수정할 일이 없는 것들이다. 아무래도 파일 단위로 코드를 분리할 수 있다면 좋겠다.
JavaScript 코드를 파일로 분리할 때는 <script> 태그의 속성인 src를 이용한다. 다음은 Exception 생성자와 log함수의 정의를 바깥으로 뜯어낸 것이다.
main.htm <html.head> |
<!-- 기존 코드를 가져올 때 src 속성을 이용합니다. --> <script src="handy.js"></script> <!-- 필요한 코드를 새롭게 추가합니다. --> <script> function main() { log("Hello, world!"); } function init() { var logStream = document.getElementById('HandyLogStream'); logStream.style.width = 800; logStream.style.height = 200; } </script> |
handy.js |
/** @param {string} msg @param {object} data */ function Exception(msg, data) { this.description = msg; this.data = (data != undefined) ? data : null; } Exception.prototype.toString = function() { var type = 'Exception: '; var message = this.description; var data = (this.data != undefined) ? this.data.toString() : ''; return type + message + ' [' + data + ']'; } /** 로그 스트림에 문자열을 출력합니다. @param {string} message */ function log(message) { var logStream = document.getElementById('HandyLogStream'); logStream.value += (message + '\n'); } |
script 태그 내에 src 속성을 추가하여 handy.js 소스 파일을 포함하였다. 참고로 HTML 문서의 주석은 JavaScript의 주석과 달리 “<!--”, “-->” 기호를 이용한다. 이렇게 파일을 분리함으로써 HTML 문서의 크기가 줄고 필요한 부분만 볼 수 있게 되어 가독성이 높아지게 된다.
이후의 내용에서는 다시 이에 관해 얘기하기 전까지 handy.js를 기본으로 포함하고 위의 HTML 문서를 기본 형식으로 하자. 아직 우리의 로그 스트림은 개선해야 하는 부분이 더 있지만, 이는 문제가 닥칠 때마다 해결하는 것으로 하겠다.