deveq 블로그
스터디 6주차-2] 버그와 오류에 대처하는 방법(11) 본문
버그와 오류에 대처하는 방법
11.1 버그에 대처하기
11.1.1 버그의 원인
프로그램에서 버그가 발생하는 원인은 3가지로 나눌 수 있습니다.
- 논리적인 버그
프로그램의 바탕이 되는 알고리즘 자체에 오류가 있거나, 알고리즘을 프로그램으로 구현하는 바업이 잘못되었을 때 발생합니다.
후자에는 프로그래밍 언어의 문법을 충분히 이해하지 못한 경우도 포함됩니다. - 오타
- 실행 환경의 변화
컴퓨터, OS, 프로그래밍 언어 자체가 사양이 바뀌어 프로그램이 동작하지 않는 경우가 있습니다.
이러한 상황은 버그는 아니지만, 프로그램이 동작하지 않는 원인을 찾아 수정해야합니다.
11.1.2 Strict 모드 사용
버그를 만들지 않는 가장 좋은 방법은 프로그램 개발 시 버그가 발생하기 어려운 환경을 조성하는것입니다.
자바스크립트는 다른 언어보다 버그가 발생하기 쉬운 언어입니다.
예를들어 변수를 선언하지 않고 사용하는 경우 전역변수로 취급하기 때문에 오류가 발생하지는 않지만
의도치 않은 동작을 할 가능성이 있습니다. 이런 경우 버그가 있더라도 정상적으로 동작하는것처럼 보여
버그를 발견하기가 더 어렵습니다.
ECMAScript5부터 추가된 Strict 모드는 자바스크립트 언어의 사양중에서 버그를 일으키기 쉬운 부분을 제거합니다.
이는 버그를 최대한 발생하지 않게 만들거나 버그가 발생했을 때 즉시 알 수 있도록 언어의 사양을 더욱 엄격하게 제한합니다.
따라서 일부 프로그램은 오류가 발생하여 실행되지 않을 수 있습니다.
또한 Strict모드로 자바스크립트를 실행하면 자바스크립트 엔진이 내부적으로 최적화 처리에 장애가 되는 부분을 제거합니다.
따라서 몇몇 프로그램은 Strict모드로 실행했을 때 더 빠르게 실행되는 경우가 있습니다.
Strict모드 설정
스크립트의 첫머리(모든 문장 앞에) 또는 함수의 첫머리(모든 문장 앞에)
"use strict"; 를 입력합니다.
function f(x) {
"use strict";
y = x;
}
f(2);
// -> Uncaught ReferenceError : y is not defined( ... )
스크립트의 첫머리에 입력하면 그 스크립트가 Strict모드로 동작합니다.
함수 몸통의 첫 머리에 입력하면 그 함수가 Strict모드로 동작합니다.
그 함수에 중첩된 함수도 Strict모드로 동작합니다.
Strict모드는 모든 스크립트에 반영되는것은 아닙니다.
<script>
"use strict" // 이 스크립트는 Strict모드로 동작함
</script>
<script>
x = 2; // 이 스크립트는 Strict모드로 동작하지 않음
</script>
Strict 모드를 설정하면 바뀌는 점
- 변수는 모두 선언해야만 한다. 선언되지 않은 변수, 함수, 함수의 인자에 값을 대입하면 ReferenceError가 발생한다.
- 함수를 직접 호출할 때, 함수 안의 this 값이 undefined가 된다. Strict모드가 아닐 경우 함수 안의 this값이 전역객체의 참조가 된다.
- with문은 사용할 수 없다.
- 함수 정의문에 같은 이름의 인수가 있으면 문법오류가 발생한다.
- 객체에 같은 이름의 프로퍼티가 있으면 문법 오류가 발생한다.
- NaN, Infinity, undefined를 표기하면 TypeError가 발생한다.
- arguments[i]는 호출되었을 때의 인수 값을 유지한다. 비 Strict 모드에서는 arguments[i]가 인자의 별명이다.
따라서 한쪽을 수정하면 다른쪽도 바뀐다 - arguments.callee를 읽을 수 없다. 읽기를 시도하면 TypeError가 발생한다
- eval로 실행한 코드는 호출자의 유효 범위 안에서 새로운 변수나 함수를 선언할 수 없다.
11.1.3 스타일 가이드 활용하기
- 스타일 가이드
프로그램을 작성할 때 버그를 피하고 가독성을 높이기 위해 권장되는 코딩 규칙
여러 사람이 함께 프로그램을 개발한다면 스타일 가이드를 참고하여 전체적인 코딩규칙을 정해두는게 좋습니다.
- Google JavaScript Style Guide - 영어
- JavaScript style guide - MDN Docs - 영어
- jQuery JavaScript Style Guide - 영어
- Airbnb JavaScript Style Guide - 한국어
11.1.4 console 디버깅
프로그램의 특정 위치에서 Console객체의 메서드를 실행하면 그 시점의 프로그램 상태를 확인할 수 있습니다.
- 가장 많이 사용되는 두 메서드
- console.log - 변수의 값 표시하기
- console.dir - 객체의 프로퍼티 목록 표시하기
그 외에도 console.trace를 사용하면 현재 실행중인 함수의 호출 스택을 볼 수 있습니다.
fuction f() {
function g() {
function h() {
console.trace();
}
h();
}
g();
}
f();
console.trace가 함수 h에 의해 실행되었고, h는 g가 호출했으며, g는 f가 호출했고, f는 익명함수가 호출했다고 보여줍니다.
11.1.5 웹 브라우저의 개발자 도구를 사용한 디버깅
F12 혹은 command + option + I를 누르면 개발자모드가 열립니다
Node의 경우 내장 디버거는
# 내장 디버거
$ node debug <파일이름>
# node-inspector
$ node --debug-brk <파일이름>
# node-inspector의 경우 npm으로 라이브러리를 설치해야함
11.1.6 프로그램 테스트
- 프로그램 테스트
의도대로 동작하는지 확인하는 작업.
프로그램 테스트로 버그를 찾을 수는 있지만 버그가 없다는 사실을 증명할 수는 없습니다.
특히 대규모 프로그램에서 버그를 완전히 제거하기란 매우 어려우므로 규정된 프로그램 테스트를 통과하면 어느정도의 품질을 확보한것으로 간주합니다.
- 단위테스트
각 함수의 동작을 확인하는 테스트입니다. 주로 설계대로 함수가 작동하는지 테스트하여 적절한 알고리즘이 사용되었는지 확인합니다. - 통합 테스트
단위 테스트를 통과한 프로그램을 결합해서 수행하는 테스트입니다.
상태 다이어그램 등을 바탕으로 동작을 확인하거나 결합한 프로그램이 원하는 기능을 구현했는지 검증하는 기능 테스트 등을 실시합니다. - 시스템 테스트
모든 프로그램을 결합하여 전체 프로그램이 사양에 따라 작동하는지 확인하는 테스트입니다.
단순한 동작 확인은 물론 보안성 및 사용성 측면도 평가합니다.
프로그램이 부하(사용자 수나 장시간의 연속 작업 등(를 얼마나 견딜 수 있는지 테스트하는 등 다양한 관점에서 테스트합니다. - 운용 테스트
완성된 프로그램을 실제 사용자가 테스트합니다. 주로 프로그램 결함이나 사용하는 데 따르는 어려움 등을 확인하는 테스트입니다.
11.2 예외처리
- 예외
간단히 말해 오류입니다. 일반적인 프로그램에서 오류가 발생하면 그 프로그램은 강제종료됩니다.
그러나 자바스크립트는 프로그램에서 발생한 오류는 굳이 프로그램을 종료하지 않아도
오류만 적절하게 처리하면 프로그램을 계속 실행시킬 수 있습니다.
11.2.1 예외
프로그램 실행 중 예기치 못한 오류가 발생하거나, 오류는 아니지만 어떤 대처가 필요한 예외적인 상황이 발생할 수 있습니다.
예를들어 파일이 없거나, 함수에 주어진 인수가 기대하는 타입이 아닌 경우 등입니다.
이때 함수는 일반적으로 null을 호출자에게 전달하는 방법을 사용하지만, 또 다른 방법으로
예외(exception)를 던지는(throw) 방법이 있습니다.
예외란 오류 및 예외 조건이 발생한 사실을 알려주는 신호입니다. 예외를 던져서 오류 또는 예외 조건이 발생했다는 사실을 통지합니다.
통지를 받은 쪽은 예외를 적절하게 처리합니다. 예외를 받아서 처리하는 부분을 가리켜 예외 처리기라 합니다.
예외를 받는 작업을 예외를 잡는다(catch)라고 합니다.
자바스크립트에서는 throw문으로 던지고 try/catch/finnaly로 예외를 잡아서 처리합니다.
11.2.2 throw문
throw문은 예외를 던집니다.
throw 표현식;
표현식으로는 어떤 타입의 값도 지정할 수 있습니다. 사용자에게 표시할 오류 메시지가 포함된 문자열이나 오류 코드를 의미하는 숫자도 허용됩니다.
그러나 일반적으로 Error객체나 Error객체를 상속받은 객체를 지정합니다.
function permutation(a) {
if ( !(a instanceof Array) ) {
throw new Error(a + 'is not an array);
}
//....
}
permutation('ABC'); // Error : ABC is not an array
예외를 던지면 자바스크립트 인터프리터는 프로그램의 실행을 중단하고 바깥 블록에서 예외를 처리하는 예외처리기를 찾습니다.
예외 처리기는 바로 try/catch/finally 절 부분의 catch절입니다.
예외 처리기가 없으면 프로그램을 종료합니다.
타입의 판정
함수가 받은 인수의 타입이 적절한지 확인하고, 적절하지 않을 때 오류를 던지려면 먼저 인수의 타입을 판정해야합니다.
인수가 원시값인지 또는 함수 타입인지 판정할 때는 typeof 연산자를 사용할 수 있습니다.
if (typeof callback !== 'function') throw new Error(callback + 'is not a function');
그러나 인수가 함수 타입 외의 객체타입이면 typeof 연산자가 'object'를 반환하므로 어떤 타입의 객체인지를 확인할 수 없습니다.
이때는 instanceof 연산자를 사용합니다. instanceof 연산자는 특정 객체의 프로토타입 체인에 특정 생성자의 프로토타입 객체가 포함되어있는지를 판정합니다.
if( !(map instanceof Map)) throw new Error(map + 'is not a Map object'0;
앞에서 예로들었던 permutation함수는 인수타입이 배열인지를 instanceof 연산자로 판정합니다.
이외에도 배열을 판정하는 방법이 하나 더 있습니다. 바로 Array.isArray메서드를 사용하는 방법입니다.
Array.isArray(a) // a가 배열이면 true 그렇지 않으면 false를 반환
11.2.3 Error 객체
자바스크립트에는 예외를 표현하기 위한 내장객체가 일곱 개 준비되어있습니다. 그 중에 Error 객체는 범용적인 예외를 표현하기 위한 객체고 나머지 여섯 개는 특정 예외가 발생했을 때 표현하기 위한 객체입니다.
생성자 | 생성하는 인스턴트 |
---|---|
Error | 범용적인 예외처리 |
EvalError | eval함수와 관련해서 발생한 예외 객체 |
RangeErrpr | 숫자값이 허용 범위를 벗어났을 때 발생하는 예외 객체 |
ReferenceError | 잘못된 참조를 만났을 때 발생하는 예외 객체 |
SyntaxError | 자바스크립트 문법에 어긋난는 구문을 만났을 때 발생하는 예외 객체 |
TypeError | 변수 및 인수 타입이 유효하지 않을 때 발생하는 예외 객체 |
URIError | encodeURI와 decodeURI 메서드에 잘못된 인수가 전달되었을 때 발생하는 예외 객체 |
자바스크립트 인터프리터는 오류가 발생했을 때 그 오류에 따라 위와 같은 예외들을 던집니다.
x++; // ReferenceError : 없는 변수 x를 만남.
if (a>0) [ a++; // SyntaxError : }가 없음
var obj = {};
obj.join(); // TypeError : join메서드가 정의되어있지 않음
var pi = 3.141592;
pi.toFixed(100); // RangeError : toFixed메서드는 0~20 사이의 값만 인수로 받을 수 있음
decodeURIComponent('%'); // URIError : %이후에 아무것도 없음.
예외를 표현하는 모든 내장객체는 Error.prototype의 프로퍼티와 메서드를 상속받습니다.
Error.prototype의 프로퍼티는 다음과 같습니다.
message : 오류 메세지를 뜻하는 문자열
name : 오류 이름을 뜻하는 문자열
Error.prototype의 메서드는 다음과 같습니다.
toString : 지정된 객체를 표현하는 문자열을 반환.
// 오류 인스턴스를 생성할 때 인수로 문자열을 넘길 수 있습니다. 인수로 넘긴 문자열은 message프로퍼티에 저장됩니다.
var error = new TypeError('배열이 아닙니다.');
console.log(error.message); //배열이 아닙니다.
console.log(error.name); // TypeError
console.log(error.toString()); // TypeError : 배열이 아닙니다.
11.2.4 try/catch/finally 문
try {
// 실행할 코드를 적는다 (예외가 발생할 수 있는 코드)
} catch (exception) {
// try 블록에서 예외가 발생할 때 실행된다.
// exception에는 던져진 예외 값이 들어옴. 이 값을 바탕으로 예외 처리를 한다.
} finally {
// try, catch가 실행 된 이후 반드시 실행된다.
}
- try 블록 안에 예외가 발생할 가능성이 있는 코드를 작성합니다. try 블록 다음에 catch 블록과 finally 블록 중 하나를 작성하거나 모두 작성합니다.
- catch 블록 안에는 try 블록에서 예외가 발생했을 때 실행할 문장을 작성합니다. catch 블록 뒤의 괄호 안에는 변수를 입력할 수 있습니다.
그 변수에는 try 블록에서 던진 예외 값(Error)이 들어옵니다.
catch 블록의 처리가 끝나면 그 변수는 사용할 수 없게 됩니다.
catch 블록은 하나의 함수처럼 동작합니다. 예외를 이벤트로 비유하면 catch블록은 예외 이벤트 처리기에 비유할 수 있습니다. - finally 블록 안의 코드는 예외 처리의 마지막에 반드시 실행됩니다. try 블록 내부 또는 catch 블록의 내부에서
return, continue, break문이 실행되어 try/catch/finally 문장에서 빠져나오는 경우에는 빠져나오기 직전에 finally 블록 안의 코드를 실행합니다.
try {
var p = permutation(a); // permutation은 예외를 던질 가능성이 있음
p.forEach(function(v) { console.log(v); });
} catch (e){
alert(e);
}
이 코드에서 permutation이 예외를 던지면, p.forEach(...)은 실행되지 않습니다. 곧장 catch블록 안으로 실행의 흐름이 바뀝니다.
이 외에도 catch문을 생략한 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);
}
위 코드 실행 시 예외가 catch되지 않고 프로그램이 종료됩니다.
Uncaught Error : 오류가 발생했습니다.
예외를 던지는 콜백함수 throwError는 함수 정의가 try블록 안에 있을 뿐 try 블록 안에서 호출된것이 아니기 때문입니다.
이 콜백 함수를 호출한 주체는 타이머 이벤트이며 ㅅ교 블록 안에서 발생한 예외가 아닙니다. 그래서 예외를 잡을 수 없는것입니다.
이처럼 비동기 처리의 콜백함수에서 발생한 예외는 바깥의 try/catch문으로는 잡을 수 없습니다.
ES6에서 도입된 제너레이터를 활용하면 비동기 처리중에 발생한 예외를 잡을 수 있습니다.
//테스틍요 함수(첫 번째 인수로 제너레이터의 참조가 필요함)
function sleepAndError(g,n) {
setTimeout(function() {
for(var i=0; i<n; i++) {
console.log(i); // do something
}
g.throw(new Error('오류가 발생했습니다.'));
}, 1000);
}
//callback 함수를 실행하는 함수 : callback 함수가 비동기 처리 중에 발생시킨 예외도 잡아서 처리함
function run(callback, ...argsList) {
var g = (function* (cb, args) {
try {
yield cb(g, ...args);
} catch (e) {
console.log("예외를 잡음 -> " e);
}
})(callback, argsList);
g.next();
}
//실행하기
run(sleepAndError, 10);
/*
0
1
2
...
예외를 잡음 -> Error : 오류가 발생했습니다.
*/
run은 인수로 받은 함수 callback을 실행합니다. 이때 인수로 받은 함수가 비동기처리를 하는 중에 발생시킨 예외도 잡아서 처리합니다.
두번재 이후 인수로 callback 함수가 원래 받아야하는 모든 인수를 지정할 수 있습니다.
callback 함수는 첫번째 인수로 제너레이터의 참조인 g를 받아야합니다. 그리고 callback 함수 안에서 예외를 던질 때 g.throw메서드를 실행합니다.
sleepAndError함수의 실행을 run함수에 위임하고, sleepAndError 함수는 setTimeout함수를 사용해 1초 후에 예외를 던지는 비동기 처리를 하는 함수입니다.
sleepAndError 함수가 예외를 던질때 그 예외를 throw문이 아니라 첫번째 인수로 받은 제너레이터 객체 g의 throw에 던지는 부분이 포인트입니다.
제너레이터 객체 g의 throw 메서드는 호출된 그 자리에서 예외를 던지는것이 아니라, 제너레이터를 한 번 동작시킨 다음에 yield문이 위치한 자리에서 예외를 던집니다. 그러면 이 예외는 제너레이터가 던진 예외가 되므로 catch 블록 안에서 잡아낼 수 있습니다.
반복문에서 빠져나오기
안쪽에서 예외를 던지면 그 예외는 바깥쪽에서 받아서 처리한다 라는 예외 메커즘을
중첩문처럼 깊은 반복문 안에서 탈출하는 용도로 이용할 수 있습니다.
예를들어 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; // false를 반환하는게 중요
}
return a[i] = v*v;
})
} catch (e) {
if(e) throw e; //i >5일 경우 false가 반환되는데, if(false)가 되므로 throw를 하지 않음.
// 만약 예상치 못한 에러가 발생하게되면 if문의 조건이 true가 될것이므로 에러처리가 되어야함.
}
console.log(a); // [ 0, 1, 4, ... 25, 6, 7, 8, 9];
'개발 > JavaScr1pt' 카테고리의 다른 글
스터디 8주차-1] 문서제어 (14) (0) | 2021.05.27 |
---|---|
스터디 7주차-2] 웹브라우저의 객체(13) (0) | 2021.05.25 |
스터디 7주차-1] 정규표현식(12) (0) | 2021.05.21 |
스터디 6주차-1] 배열의 다양한 기능(10.3~) (0) | 2021.05.11 |
JavaScript (0) | 2021.05.10 |