본문 바로가기

JavaScript

8장 버그와 에러

# Chapter 8 - Bugs and Error Handling

   

   

프로그램은 생각을 구체화 하는 것이다.

때때로 그 프로그램들은 혼란스러워 진다.

다른때에는 그런 혼란스러운 것들이 코드에 들어갈때 실수가 발생한다.

어느쪽이든 그 결과는 결점이 있는 프로그램이라는 것이다.

   

프로그램의 결점들은 대게 **버그** 라고 불려진다.

버그들은 프로그래머의 실책이 될 수도 있고 상호작용하는 다른 프로그램의 문제를 일으킬 수도 있다.

   

어떤 버그들은 즉시 식별할 수 있는 반면에 나머지들은 미묘하거나 시스템에 수년동안 숨어서 남이있을 수 있다.

   

문제는 프로그래머가 최초에 고려하지 못했던 상황에서 드러나게 된다.

때때로 이 상황은 피하기 어려운 것이다.

   

   

## Programmer mistakes

   

프로그래머의 잘못에 대해서 우리의 목적은 단순하다. 우리는 그것을 찾기를 원하고 수정하기를 원한다. 이런 실수 들은 타이핑부터 우리가 동작의 이해에 대한 미묘한 실수 그리고 특정 상황에서만 발생하는 잘못된 결과까지 포함한다.

문자열 버그들은 찾는데 몇주가 걸릴 수도 있다.

   

언어마다 오류를 찾아주는 정도는 다 다르다.

당연히 자바스크립트는 거의 전혀 도움이 되지 않는다고 볼수 있다.

어떤 언어는 프로그램이 실행되기전에 변수와 표현식의 타입에 대해서 알기를 원하고 잘못된 방법으로 사용될 경우 올바른 방법을 알려줄 수 있기를 원한다.

자바스크립트는 오직 실행시간에 타입을 고려한다 심지어 다음과 같이 명백히 터무니없는 것들도 지원한다

   

x = true * "monkey"

 

그럼에도 불구하고 자바스크림트는 몇몇 상황에대해서 오류를 나타낸다.

첫번째로 문법적으로 맞지 않는 프로그램을 작성할때 즉시 에러를 발생시킨다. 그리고 함수가 아닌 것을 호출 한다던가 값이 **undefined** 인 속성에 접근할때이다.

   

그러나 종종 의미없는 연산은 **NaN** 이나 **undefined**를 나타낸다.

그리고 운좋게 프로그램은 계속 진행되고 그것이 의미있는 동작이었다고 수긍하게 된다. 그 오류는 나중에 분명히 나타날 것이고 그후 잘못된 값들이 몇몇의 함수들을 통해 이동하게 될 것이다. 이것은 에러를 전혀 발생기키진 않지만 조용히 프로그램의 출력지 잘못되도록 야기 시킬 수 있다.

이런 문제의 소스코드를 찾는 것은 어려운 일이 될 수 있다.

   

오류, 버그를 프로그램안에서 찾는 것을 **디버깅** 이라 한다.

   

## STRICT MODE

   

자바스크립트는 ** *strict* ** 모드 활성화를 통해서 조금 엄격하게 만들어 질 수 있다. 사용하는 방법은 파일 맨위나 함수본문 안에서 ** "use strict" ** 만 선언하면 된다.

   

function canYouSpotTheProblem(){

        "use strict";

for (counter = 0; counter < 10; counter++)

        console.log("Happy happy");

}

 

canYouSpotTheProblem();

--> ReferenceError : counter is not defined

 

위의 예제의 coutner 처럼 변수앞에 **var**를 입력하는 것을 잊어버렸을때 보통은 자바스크립트가 조용히 글로벌 변수를 생성하고 그것을 사용한다.

하지만 **STRICT** 모드에서는 에러를 표시한다. 이것은 매우 유용하다.

   

주의할점은 이미 글로벌 변수에 변수가 할당 되어있다면 이부분은 그냥 넘어가게 된다.

   

strict모드에서 다른 점은 호출 되지않는 함수안의 **this** binding **undefined** 값을 가지고 있다.

 

이러한 호출을 strict mode 밖에서 만들었을때 **this** 글로벌 스코프 오브젝트로 나타낸다. 그래서 만약 우연히 메소드나 생성자를 stict mode에서 잘못 호출할 경우 자바스크립트는 **this** 읽기를 시도하자마자 에러를 발생 시킬켜서 글로별 변수가 만들어지고 읽혀서 운좋게 동작하는 것을 막는다.

 

        function Person(name) { this.name = name; }

var ferdinand = Person("Ferdinand"); // oops

console.log(name);

// Ferdinand

 

위의 호출은 name을 글로벌 변수로 생성한다.

   

"use strict";

function Person(name) { this.name = name; }

// Oops, forgot 'new'

var ferdinand = Person("Ferdinand");

// TypeError: Cannot set property 'name' of undefined

 

strict 모드에서는 이전과 다르게 에러를 바로 발생시킨다.

   

Strict 모드는 몇가지 동작하는데 함수에서 같은 이름의 여러개의 파라미터를 할당하는 것을 방지하고 문제가 있는 언어젹인요소( ex with ) 완전히 제거 한다.

   

"use strict" 사용해서 문제를 해결 있도록 하자!

   

   

## TESTING

   

언어적으로 오류를 찾는데 더이상 도움을 받을 수 없다면 프로그램을 실행시키고 잘 동작하는지 지켜보는 어려운 방법으로 찾을 수 있다.

   

이것을 손으로 계속해서 하는것은 정신병에 걸리게 하는 방법이다.

다행이도 자동으로 실제 프로그램을 테스트 할수 있는 프로그램을 작성 할 수 있다.

   

function Vector(x, y) {

this.x = x;

this.y = y;

}

Vector.prototype.plus = function(other) {

return new Vector(this.x + other.x, this.y + other.y);

};

   

function testVector() {

var p1 = new Vector(10, 20);

var p2 = new Vector(-10, 5);

var p3 = p1.plus(p2);

   

if (p1.x !== 10) return "fail: x property";

if (p1.y !== 20) return "fail: y property";

if (p2.x !== -10) return "fail: negative x property";

if (p3.x !== 0) return "fail: x from plus";

if (p3.y !== 25) return "fail: y from plus";

return "everything ok";

}

console.log(testVector());

// everything ok

 

위처럼 테스트 를 작성하는 것은 어색한 코드를 반복적으로 나타내는 경향이 있다. 다행이도 테스트를 쉽게 하고 그 결과를 잘 제공받을 수 있도록 지원해주는 도구들이 있고 이것을 **testing frameworks** 라고 부른다.

ex) mocha, should, expect for node.js

   

   

## Debugging

   

일단 잘못된 동작이나 에러가 발생하는 것문에 프로그램이 이상하다는 것을 알게되면 그다음은 할일은 무엇이 문제인지 찾는 것이다.

   

때때로 에러는 명백하게 나타난다. 에러 메세지가 해당 라인을 알려주고 그라인의 코드와 에러 설며을 보는동안 문제를 찾을 수 있다.

   

하지만 항상 그런것은 아니다. 때로는 트리거 된 라인의 문제는 여러 잘못된 사용처 중에서 첫번째 위치만 알려주거나 단지 이상잘못된 결과만 나타날뿐 에러메세지가 전혀 없는경우도 있다.

   

function numberToString(n, base) {

var result = "", sign = "";

if (n < 0) {

sign = "-";

n = -n;

}

do {

result = String(n % base) + result;

n /= base;

} while (n > 0);

return sign + result;

}

console.log(numberToString(13, 10));

// 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…

 

위 예제는 모든 숫자를 문자로 바꿔주는 기능을 한다.

하지만 이상한 결과가 나오고있다.

   

해결법은 n /= base -> n = Math.floor(n/base);

 

   

## Error propagation // 에러 전이

   

불행히도 모든 문제가 프로그래머로부터 예방 될수 있는것은 아니다. 해당 프로그램이 외부와의 상호작용이 있게될 경우 잘못된 입력이 발생하거나 다른 시스템이 작동하지 않을 수도 있다.

   

간단한 프로그램이나 직접적으로 관리하는 프로그램들은 이러한 문제를 계속 들여다 보면서 해결해 나갈 수 있다. 하지만 다른 경우에는 문제가 쉽게 예측되지 않을 수 있다.

   

때떄로 옳은 해결밥업은 이 잘못된 입력을 넘기고 계속해서 진행하는 것이다. 이러한 상황에서는 유저에게 무엇이 문제이고 그리고 이문제는 해결할 수없다고 리포팅을 하는 것이다.

하지만 다른 상황이라면 프로그램은 적극적으로 프로그램의 문제에대해서 해결해야 한다.

   

아래의 예제는 유저에게 숫자를 입력받고 리턴하는 함수이다. 그런데 유저가 *orange*를 입력했다면 무엇을 리턴해야 하는가?

   

function promptNumber(question) {

var result = Number(prompt(question, ""));

if (isNaN(result)) return null;

else return result;

}

   

console.log(promptNumber("How many trees do you see?"));

 

위와 같은 문제를 올바르게 해결하기 위해서는 반환값에대한 명확한 명세가 있어야 한다.

   

   

## Exceptions

   

함수가 정상적인 동작을 할수 없을때 하려고하는 동작을 멈추고 해당 문제를 처리할 수 있는 곳으로 이동해야 한다. 이것이 바로 **예외처리** 이다

   

예외는 코드에게 발생한 예외에게 접근할수 있게 하는 매카니즘이다. 예외가 발생하는 것은

특별한 함수리턴과 약간 비슷한점이 이있는데 현재 함수 뿐만 아니라 호출한 곳으로도 이동이 가능하고 현재의 실행이 최초로 시작된 곳 까지 이동이 가능하다. 이것을 unsinding the stack (스택풀기)라 한다.

   

만약 예외가 항상 스택의 바닥까지 내려간다면 예외가 자주 사용되지 않을 것이다.

예외는 단지 프로그램을 확대시키는 새로운 방법을 제공할 뿐이다.

   

function promptDirection(question) {

var result = prompt(question, "");

if (result.toLowerCase() == "left") return "L";

if (result.toLowerCase() == "right") return "R";

throw new Error("Invalid direction: " + result);

}

   

function look() {

if (promptDirection("Which way?") == "L")

return "a house";

else

return "two angry bears";

}

   

try {

console.log("You see", look());

} catch (error) {

console.log("Something went wrong: " + error);

}

   

**throw** 키워드로 예외를 발생시킬수 있고 **try** 블럭안에서 실행시킨 **catch** 블럭을 용해서 예외를 잡을 있다. **catch** 블럭이 끝나거나 예외없이 **try**블럭이 끝나면 **try/catch** 표현식 다음의 내용이 순서대로 실행된다.

   

위의 예제에서는 Error 생성자를 이용해서 예외의 값을 담아서 사용하였다. **Error** 는 자바스크립트에서 제공하는 표준 생성자 이다. 모던 자바스크립트 환경에서는 생성자의 인스턴드도 예외가 만들어 졌을때 존재하는 콜스택의 정보에 대해서 가질수 있고 이것을 **stack trace** 라 부른다. 이 정보는 **stack** 프로퍼티에 저장되어지고 디버깅할때 도움을 줄 수 있다.

   

유의할 점은 위의 예제에서 look 함수는 prompotDirection 함수가 잘못된 방향으로 진행될 가능성을 완전히 배제한다. 이것은 예외처리에 있어서 에러가 발생한 부분에 대해서만 다룰수 있게하는 큰 이점이 된다. 다른 영역에 대해서는 완전히 잊어버리게 할 수 있기 때문이다.

   

   

## Cleaning up after exceptions

   

var context = null;

   

function withContext(newContext, body) {

var oldContext = context;

context = newContext;

var result = body();

context = oldContext;

return result;

}

 

만약 **body()**가 예외를 발생시킨다면? context에 oldContext 값이 영원히 셋팅되지 않을 것이다.

   

**try** 구문에는 한가지 요소가 남아 있다. **finally** 블록이다. **finally** 블록은 무슨일이 있어도 이블럭내의 내용을 실행시킨다는 의미를 가지고 있다. 그래서 보통 예외발생시 DB 커넥션과 같이 중요한 리소스 해제시에 사용한다.

위의 예지의 해결법은 아래와 같다.

   

function withContext(newContext, body) {

var oldContext = context;

context = newContext;

try {

return body();

} finally {

context = oldContext;

}

}

 

위코드에서 try문에서 예외가 발생하던 안하던 아래의 finally 구문이 무조건 실행된다.

   

   

## Selective catching

   

자바스크립트의 예외처리시 catch 문안에서 try문에서 발생된 예외를 잡을 수 있지만

정확히 무엇인지, 어떤 예외가 발생했는지는 알 수가 없다.

자바스크립트 자체적으로 선택적 예외 케치를 지원하지 않기때문이다.

하지만 이걸 쉽게 만들어 볼 수 있다.

   

먼저 그러한 상황이 필요한 예제를 살펴보자.

   

for (;;) {

try {

var dir = promtDirection("Where?"); // typo!

console.log("You chose ", dir);

break;

} catch (e) {

console.log("Not a valid direction. Try again.");

}

}

   

The for (;;) construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. But we misspelled promptDirection, which will result in an "undefined variable" error. Because the catch block completely ignores its exception value (e), assuming it knows what the problem is, it wrongly treats the variable error as indicating bad input. Not only does this cause an infinite loop, but it also "buries" the useful error message about the misspelled variable.

   

   

일단 새로운 Error 객체를 만들어 보자.

   

function InputError(message) {

this.message = message;

this.stack = (new Error()).stack;

}

InputError.prototype = Object.create(Error.prototype);

InputError.prototype.name = "InputError";

 

instanceof Error 이용할때 InputError 나오도록 하기 위해서 프로토타입을 새로 만들었다.

   

표준 Error를 사용하지 않았기 때분에 stack 프로퍼티가 없어서 이부분을 새로 만들어 준다.

   

그리고 나서 promptDirection 함수에서 에러를 발생 시켜 본다.

   

function promptDirection(question) {

var result = prompt(question, "");

if (result.toLowerCase() == "left") return "L";

if (result.toLowerCase() == "right") return "R";

throw new InputError("Invalid direction: " + result);

}

   

그리고 좀더 안전하게 케치를 할 수 있다.

   

for (;;) {

try {

var dir = promptDirection("Where?");

console.log("You chose ", dir);

break;

} catch (e) {

if (e instanceof InputError)

console.log("Not a valid direction. Try again.");

else

throw e;

}

}

   

여기에서는 InputError 인스턴스만 케치를 하고 나머지 관계없는 예외는 흘려보낸다.

   

   

   

## Assertions

   

Assertions 프로그래머가 에러를 체크 할수 있게 도와주는 도구이다.

아래와 같은 **assert** 헬퍼 함수를 생각해보자

   

function AssertionFailed(message) {

this.message = message;

}

AssertionFailed.prototype = Object.create(Error.prototype);

   

function assert(test, message) {

if (!test)

throw new AssertionFailed(message);

}

   

function lastElement(array) {

assert(array.length > 0, "empty array in lastElement");

return array[array.length - 1];

}

   

'JavaScript' 카테고리의 다른 글

9장 정규 표현식  (0) 2016.11.13
5장 Higher - Order Functions  (0) 2016.11.13
4장. 데이터 구조: 객체와 배열  (0) 2016.11.13
Chapter 1 - VALUES, TYPES, AND OPERATERS  (0) 2016.11.13