Error.stack을 통한 신속한 JavaScript 오류 진단

Windows 8 Consumer Preview의 IE10에는 Error.stack 지원 기능이 포함되어 있어 웹 개발자가 버그를 보다 신속하게 진단하고 수정할 수 있으며, 특히 재현하기 어려운 버그 진단에 유용합니다. 이에 따라, 개발자는 웹 플랫폼 기능으로 최신 브라우저를 구동하는 멋진 앱을 만들 수 있습니다. Windows 8에서는 이 기능을 Internet Explorer 10과 JavaScript의 Metro 스타일 앱을 통해 구현합니다. 이러한 앱의 기능과 복잡성이 계속 증가하고 있기 때문에 개발자가 보다 효과적으로 오류를 처리하고 버그를 진단하기 위해서는 Error.stack과 같은 효율적인 도구가 필요합니다.

응용 프로그램 디버깅

JavaScript의 구조화된 오류 처리 작업은 throwtry/catch를 기반으로 합니다. 이를 통해 개발자는 오류를 선언하고 오류 처리를 담당하는 프로그램 부분에 제어 흐름을 전달합니다. 그리고 오류가 발생하면 Internet Explorer의 Chakra JavaScript 엔진은 오류 발생 지점에 연결된 호출 체인을 캡처합니다. 이는 호출 스택이라고도 합니다. 반환되는 개체가 Error(또는 프로토타입 체인이 다시 Error로 연결되는 함수)이면 Chakra는 사람이 읽을 수 있는 호출 스택 목록인 스택 추적을 생성합니다. 이 목록은 Error 개체에 stack 속성으로 표시됩니다. stack에는 오류 메시지, 함수 이름 및 함수의 소스 파일 정보가 포함됩니다. 이 정보를 통해 호출 중인 함수 및 문제가 있는 코드 줄까지 알 수 있으므로 개발자는 신속하게 오류를 진단할 수 있습니다. 예를 들어, 함수에 전달된 매개 변수가 null 또는 잘못된 형식이었음을 나타낼 수 있습니다.

두 지점 (0, 2)(12, 10) 사이의 거리를 계산하는 간단한 스크립트를 예로 살펴보겠습니다.

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

try {

sample();

}

catch (e) {

console.log(e.stack);

}

})();

이 스크립트에는 버그가 있습니다. 구성 요소의 차이를 제곱하지 않았습니다. 결과적으로 일부 입력에서 pointDistance 함수는 곱이 잘못된 결과를 반환하고 오류가 발생하게 됩니다. 스택 추적을 이해하기 위해 F12 개발자 도구에서 스크립트 탭을 보며 오류를 검토하겠습니다.

console.log(e.stack)을 호출하여(여기서 e는 try/catch 블록의 catch 절에 전달된 Error 개체) 기록된 스택 추적을 보여 주는 F12 개발자 도구.

스택 추적은 catch 절에서 콘솔로 덤프됩니다. 스택 맨 위에 있기 때문에 Error가 squareRoot 함수에서 시작되었음을 쉽게 알 수 있습니다. 문제를 디버깅하기 위해 개발자가 스택 추적에 깊이 들어갈 필요는 없습니다. squareRoot의 전제 조건이 위반되었으므로 스택의 한 수준 위를 보면 이유를 명확히 알 수 있기 때문입니다. squareRoot 호출 내의 하위 식 자체가 square에 대한 매개 변수여야 합니다.

디버깅 시 stack 속성은 중단점 설정에 대한 코드를 식별하는 데 도움이 됩니다. 다른 방법으로 호출 스택을 볼 수도 있습니다. 예를 들어, 스크립트 디버거를 'Break on caught exception(catch된 예외에서 중단)' 모드로 설정하면 디버거로 호출 스택을 검사할 수 있습니다. 배포된 응용 프로그램의 경우 실패한 호출을 캡처하고 이를 서버에 기록하기 위해 문제가 있는 코드를 try/catch로 래핑할 수 있습니다. 그러면 개발자가 호출 스택을 보고 문제 영역을 구분하는 데 도움이 됩니다.

DOM 예외 및 Error.stack

앞서 언급했듯이 반환되는 개체는 Error이거나 프로토타입 체인을 통해 다시 Error로 연결되어야 합니다. 이는 의도적인 것으로 JavaScript는 모든 개체(기본 형식까지도)를 예외로 반환할 수 있습니다. 모든 예외를 catch하고 검사할 수 있지만 모두가 오류 또는 진단 정보를 포함하도록 특별히 설계된 것은 아닙니다. 따라서 반환 개체 중 Error만 stack 속성으로 업데이트됩니다.

DOM 예외가 개체이더라도 여기에는 Error로 연결되는 프로토타입 체인이 없으므로 stack 속성이 없습니다. DOM 조작을 수행 중이고 JavaScript와 호환되는 오류를 노출하려면 DOM 조작 코드를 try/catch 블록 안에 래핑하고 새로운 Error 개체를 catch 절 내에 반환해야 합니다.

function causesDomError() {

try {

var div = document.createElement('div');

div.appendChild(div);

} catch (e) {

throw new Error(e.toString());

}

}

하지만 이 패턴의 사용 여부는 개발자가 선택해야 합니다. 이 패턴은 유틸리티 라이브러리 개발에 가장 적합합니다. 구체적으로 설명하면 코드의 목적이 DOM 조작을 숨기기 위한 것인지 아니면 단순히 작업을 수행하기 위한 것인지를 고려하여 DOM 조작을 숨기려면 조작을 래핑하고 Error를 발생시키는 것이 최선의 방법으로 보여집니다.

성능 고려 사항

스택 추적 구성은 오류 개체 발생 시 시작됩니다. 여기에는 현재 실행 작업에 대한 검색이 필요합니다. 재귀 스택 체인과 같은 대형 스택 검색으로 인한 성능 문제를 방지하기 위해 IE는 상위 10개 스택 프레임만 수집합니다. 이 설정은 정적 속성인 Error.stackTraceLimit를 다른 값으로 설정하여 구성할 수 있습니다. 이 설정은 전역 설정이며 오류가 발생하기 전에 변경해야 합니다. 그렇지 않으면 스택 추적에 적용되지 않습니다.

비동기 예외

스택 추적이 비동기 콜백(예: timeout, interval 또는 XMLHttpRequest)에서 생성되면, 비동기 콜백을 생성한 코드가 아닌 비동기 콜백이 호출 스택의 맨 아래에 배치됩니다. 이는 문제가 있는 코드를 추적하는 데 영향을 줍니다. 동일한 콜백 함수를 여러 비동기 콜백에 사용할 경우 오류를 발생시킨 콜백이 어느 것인지 검사만으로 확인하기는 어려울 것입니다. 앞의 샘플을 약간 수정하여 sample()을 직접 호출하지 않고 timeout 콜백에 넣어 보겠습니다.

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

setTimeout(function () {

try {

sample();

}

catch (e) {

console.log(e.stack);

}

}, 2500);

})();

이 코드 조각을 실행하면 약간의 지연 후에 스택 추적이 표시되는 것을 볼 수 있습니다. 또한 스택 맨 아래가 Global Code가 아니고 Anonymous function임을 볼 수 있습니다. 사실 이것은 동일한 익명 함수가 아닌 setTimeout에 전달된 콜백 함수입니다. 콜백 연결 주변의 컨텍스트가 없어 콜백 호출이 발생한 원인을 알 수는 없을 것입니다. 서로 다른 여러 단추의 click 이벤트를 처리하기 위해 하나의 콜백이 등록된 시나리오를 생각해 본다면 해당 등록이 어느 콜백을 참조한다고 말할 수 없을 것입니다. 이러한 경우는 소수에 불과하며 대부분의 경우 문제 영역은 스택 맨 위에 알기 쉽게 표시됩니다.

테스트 드라이브 데모 탐색

Error.stack 탐색 테스트 드라이브 데모의 스크린샷

Windows 8 Consumer Preview에서 IE10을 사용하여 이 테스트 드라이브 데모를 확인합니다. eval 컨텍스트에서 코드를 실행하여 오류가 발생하면 이를 검사할 수 있습니다. IE10에서 코드를 실행하는 경우 스택 추적의 오류 줄 위에 마우스를 올려 놓으면 코드 줄이 강조 표시됩니다. 코드 영역에 직접 코드를 입력하거나 목록의 여러 샘플 중 선택할 수 있습니다. 또한 코드 샘플을 실행할 때 Error.stackTraceLimit 값을 설정할 수 있습니다.

참조 자료가 필요한 경우 Error.stackstackTraceLimit에 대한 MSDN 문서를 참조하시기 바랍니다.

- Chakra Runtime 프로그램 관리자, Rob Paveza