Updated:

버그에 대처하기

버그는 프로그램의 오류나 결함을 뜻한다. 프로그램에 발생한 버그의 원인을 파악하여 제거하고 프로그램이 제대로 동작하도록 수정하는 작업을 디버그(debug)라고 한다.

버그의 원인

① 논리적인 버그
논리적인 버그는 프로그램의 바탕이 되는 알고리즘 자체에 오류가 있거나 알고리즘을 프로그램으로 구현하는 방법이 잘못되었을 때 발생한다.
② 오타
오타는 개발자가 의도하지 않은 동작을 유발한다. 작은 오타도 프로그램에 심각한 오류를 발생시킬 가능성이 있다.
③ 실행 환경의 변화
컴퓨터, OS, 프로그래밍 언어 자체의 사양이 바뀌어 프로그램이 동작하지 않는 경우도 있다. 이 상황은 버그가 아니지만 원인을 찾아 수정해야 한다.

Strict 모드 사용

ECMAScript 5부터 추가된 Strict 모드는 자바스크립트 언어의 사양 중에서 버그를 일으키기 쉬운 부분을 제거한다. 이는 버그를 최대한 발생하지 않게 만들거나 버그가 발생했을 때 즉시 알 수 있도록 언어의 사양을 더욱 엄격하게 제한한다.
Strict 모드 설정
프로그램을 Strict 모드로 실행하려면 스크립트의 첫머리(모든 문장 앞에) 또는 함수의 첫머리(모든 문장 앞에)에 “use strict”;을 입력한다.

function f(x){
    "use strict";
    y = x;
}
f(2);
// Uncaught ReferenceError: y is not defined(...)

스크립트가 여러 개 있을 때 Strict 모드는 모든 스크립트에 반영되자 않고 스크립트 단위로 적용된다.

<script>
    "use strict"; // 이 스크립트는 Strict 모드로 동작함
</script>
<script>
    x = 2;        // 이 스크립트는 비 Strict 모드로 동작함
</script>

Strict 모드를 설정하면 바뀌는 점
① 변수는 모두 선언해야만 한다. 선언되지 않은 변수, 함수, 함수의 인자에 값을 대입하면 ReferenceError가 발생한다.
② 함수를 직접 호출할 때, 함수 안의 this 값이 undefined가 된다.
③ with 문은 사용할 수 없다.
④ 함수 정의문에 같은 이름의 인수가 있으면 문법 오류가 발생한다.
⑤ 객체에 같은 이름의 프로퍼티가 있으면 문법 오류가 발생한다.
⑥ NaN, Infinity, undefined를 표기하면 TypeError가 발생한다.
⑦ arguments[i]는 호출되어을 때의 인수 값을 유지한다.
⑧ arguments.callee를 읽을 수 없다. 읽기를 시도하면 TypeError가 발생한다.
⑨ eval로 실행한 코드는 호출자의 유효 범위 안에 새로운 변수나 함수를 선언할 수 없다.

스타일 가이드 활용하기

스타일 가이드란 프로그램을 작성할 때 버그를 피하고 가독성을 높이기 위해 권장되는 코딩 규칙을 정리한 것이다. 특히 여러 사람이 함께 프로그램을 개발한다면 스타일 가이드를 참고하여 전체적인 코딩 규칙을 정해두면 좋다.

console 디버깅

프로그램의 특정 위치에서 Console 객체의 메서드를 실행하면 그 시점의 프로그램 상태를 확인할 수 있다. 그 확인 결과에 따라 프로그램이 의도한 대로 동작하는지 확인하고 문제의 원인을 추적할 수 있다. 다음 두 메서드를 가장 많이 사용한다.
① console.log로 변수 값을 표기하기
② console.dir로 객체의 프로퍼티 목록을 표시하기
또한 console.trace 메서드를 사용하면 실행 중인 함수의 호출 스택을 볼 수 있다. 이는 함수가 어디에서부터 호출되었는지 콘솔에 표시된다.

웹 브라우저의 개발자 도구를 사용한 디버깅

대부분의 자바스크립트 실행 환경은 효율적인 디버깅을 위한 디버거 기능을 제공한다. 디버거 기능을 사용하면 현재의 변수 값과 처리가 어떻게 진행되고 있는지 console을 사용할 때보다 더욱 자세히 알아볼 수 있다. 처리 흐름을 제어할 수 있을 뿐만 아니라 실행 중인 코드를 수정하는 기능도 갖추고 있어서 때로는 코드 수정 없이도 디버깅할 수 있다.

프로그램 테스트

프로그램 테스트란 프로그램이 의도한 대로 동작하는지 확인하는 작업을 말한다. 프로그램 테스트로 버그를 찾을 수는 있지만 버그가 없다는 사실을 증명할 수는 없다. 프로그램 테스트는 각 함수의 동작을 확인하는 단위 테스트, 단위 테스트를 통과한 프로그램을 결합해서 수행하는 통합 테스트, 모든 프로그램을 결합하여 전체 프로그램이 사양에 따라 작동하는지 확인하는 시스템 테스트, 완성된 프로그램을 실제 사용자가 테스트하는 운용 테스트 순으로 실시한다.

예외 처리

예외란 간단히 말해 오류이다. 일반적으로 프로그램에 오류가 발생하면 그 프로그램은 강제로 종료되지만 자바스크립트 프로그램에서 발생한 오류는 굳이 프로그램을 종료하지 않아도 오류만 적절하게 처리하면 프로그램을 계속 실행시킬 수 있다.

예외

프로그램을 실행하는 도중에 예기치 않은 오류가 발생할 수도 있고, 오류는 아니지만 어떤 대처가 필요한 예외적인 상황이 발생할 수 있다. 예외란 오류 및 예외 조건이 발생한 사실을 알려 주는 신호이다. 예외를 던지는(exeception throw)방법으로 예외 조건이 발생했다는 사실을 통지한다. 통지를 받은 쪽은 예외를 받아서(catch) 적절하게 처리를 한다. 예외를 받아서 처리하는 부분을 가리켜 예외 처리기라고 한다.
자바스크립트에는 throw 문으로 예외를 던지고 try/catch/finally 문으로 예외를 잡아서 처리한다.

throw 문

throw 문은 예외를 던진다.

throw 표현식;

표현식으로는 어떤 타입의 값도 지정할 수 있다. 사용자에게 표시할 오류 메시지가 포함된 문자열이나 오류 코드를 의미하는 숫자도 허용된다.

function permutation(a) {
    if( !(a instanceof Array) ) {
    // instanceof 연산자는 특정 객체의 프로토타입 체인에 특정 생성자의 프로토타입 객체가 포함되어 있는지를 판정한다.
        throw new Error(a + " is not an array");
    }
    return a.reduce(function(list,element) {
        var newlist = [];
        list.forEach(function(seq) {
            for(var i=seq.length; i>=0; i--){
                var newseq = [].concat(seq);
                newseq.splice(i, 0, element);
                newlist.push(newseq);
            }
        });
        return newlist;
        }, [[]] );
}
permutation("ABC"); // Error: ABC is not an array

예외를 던지면 자바스크립트 인터프리터는 프로그램의 실행을 중단하고 바깥 블록에서 예외를 처리하는 예외 처리기를 찾는다. 예외 처리가는 catch이다. 예외 처리기가 없으면 프로그램을 종료한다.

Error 객체

자바스크립트에는 예외를 표현하기 위한 내장 객체가 일곱 개 준비되어 있는데 그중 Error 객체는 범용적인 예외를 표현하기 위한 객체고 나머지 여섯 개는 특정 예외가 발생했을 때 표현하기 위한 객체이다.

생성자 생성하는 인스턴스
Error 범용적인 예외 객체
EvalError eval 함수와 관련해서 발생된 예외 객체
RangeError 숫자 값이 허용 범위를 벗어났을 때 발생하는 예외 객체
ReferenceError 잘못된 참조를 만났을 때 발생하는 예외 객체
SyntaxError 자바스크립트 문법에 어긋나는 구문을 만났을 때 발생하는 예외 객체
TypeReeoe 변수 및 인수 타입이 유효하지 않을 때 발생하는 예외 객체
URIError encodeURI와 decodeURI 메서드에 잘못된 인수가 전달되었을 때 발생하는 예외 객체

예외를 표현하는 모든 내장 객체는 Error.prototype의 프로퍼티와 메서드를 상속받는다. Error.prototype의 프로퍼티는 다음과 같다.
message : 오류 메시지를 뜻하는 문자열
name : 오류 이름을 뜻하는 문자열

Error.prototype의 메서드는 다음과 같다.
toString : 지정된 객체를 표현하는 문자열을 반환

try/catch/finally 문

try/catch/finally 문은 예외가 던져졌을 때 그것을 잡아서 처리한다.

try{
    // 이곳에 실행할 코드를 적는다(예외가 발생할 수 있는 코드)
} catch(exception){
    // 이 블록은 try 블록에서 예외가 발생했을 때 실행된다.
    // exception에는 던져진 예외 값이 들어옴. 이 값을 바탕으로 예외를 처리한다.
} finally{
    // 이 블록 안의 코드는
    // try 블록 코드와 catch 블록 코드가 실행된 이후에 반드시 실행된다.
}

try 블록 안에 예외가 발생할 가능성이 있는 코드를 작성한다. try 블록 다음에 catch 블록과 finally 블록 중 하나를 작성하거나 모두를 작성한다.

try{
    var p = permutation(a); // permutation 함수는 예외를 던질 가능성이 있음
    p.forEach(function(v) { console.log(v); });
} catch(e){
    alert(e);
}

이 코드에서 permutation 함수가 예외를 던지면 p.forEach(…);은 실행되지 않는다. 곧장 catch 블록 안으로 실행의 흐름이 바뀐다. try/finally 문을 작성하면 try 블록에서 예외가 발생해도 예외를 잡지 않는다. 그대로 finally 블록 안으로 실행 흐름이 바뀐다. 예외가 발생하면 그 후에 프록램이 오류로 멈춘다.

예외가 여러 개 발생했을 때 대치하는 방법
복잡한 프로그램에서는 일반적으로 다양한 유형의 예외가 발생한다. 이럴 때는 catch 블록 안에 예외 유형별로 처리를 작성해 줄 필요가 있다. 이런 상황에는 다음과 같이 instanceof 연산자로 예외 타입을 판별한다.

try {
    // 이 지점에서 오류가 발생한다고 가정한다
} catch(e) {
    if( e instanceof TypeError ) {
        // TypeError가 발생했을 때의 처리를 작성한다
    } else if( e instanceof ReferenceError ){
        // ReferenceError가 발생했을 때의 처리를 작성한다
    } else {
        // 그 외의 예외가 발생했을 때의 처리를 작성한다
    }
}

예외의 전파
예외는 호출 스택을 거슬러 올라가며 전파된다.

try {
    f();
} catch(e) {
    console.log("예외를 캐치함 -> " + e);
}
function f() { g(); }
function g() { h(); }
function h() { throw new Error("오류가 발생했습니다"); }

이 코드를 실행하면 콘솔에 다음과 같은 내용이 표시된다.

예외를 캐치함 -> Error: 오류가 발생했습니다

이 예제에서 발생한 예외는 ‘h -> g -> f -> 전역 코드’순서대로 호출 스택을 거슬러 올라가 전파되며, 마지막에는 전역 코드에서 try/catch 문에 걸려서 처리된다. 호출 스택에서 예외 처리기를 찾기 못하면 프로그램이 강제로 종료되며 예외는 사용자 오류로 보고된다.

비동기 처리의 콜백 함수가 던진 예외 처리
비동기 처리의 콜백 함수가 던진 예외는 콜백 함수를 넘긴 함수로 전파되지 않는다.

try {
    setTimeout(function throwError() {
        throw new Error("오류가 발생했습니다");
    },1000);
} catch(e) {
    console.log("예외를 캐치함 -> " + e);
}

이 코드를 실행하면 콘솔에는 다음과 같은 내용이 표시된다. 그리고 예외가 캐치되지 않은 상태로 프로그램이 종료된다.

Uncaught Error: 오류가 발생했습니다

예외를 던지는 콜백 함수 throwError는 함수 정의가 try 블록 안에 있을 뿐 try 블록 안에서 호출된 것이 아니다. 이 콜백 함수를 호출한 주체는 타이머 이벤트이며 try 블록 안에서 발생한 예외가 아니다. 그래서 예외를 잡을 수 없는 것이다.

반복문에서 빠져나오기
‘안쪽에서 예외를 던지면 그 예외는 바깥쪽에서 받아서 처리한다’라는 예외 메커니즘을 중첩 반목문처럼 깊은 반복문 안에서 탈출하는 용도로 이용할 수 있다. 예를 들어 forEach 문은 실행하는 도중에 취소할 수 없지만 예외 메커니즘을 이용하면 실행 중인 반복문에서 빠져나올 수 있다.

var a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
try {
    a.forEach(function(v,i,a) {
        if( i > 5 ){
            throw false;
        }
        return a[i] = v*v;
    });
} catch(e) {
    if(e) throw e;
}
console.log(a); // [0, 1, 4, 9, 16, 25, 6, 7, 8, 9]

또한 의도하지 않은 예외가 try 블록 안에서 발생하는 경우를 대비하여 예외 같이 true로 평가되었을 때는 그 예외를 밖으로 던진다. 예외 값이 false면 그다음 코드인 console.log를 실행한다.

댓글남기기