Skip to content

Commit 1be1b4d

Browse files
[커스텀 에러와 에러 확장] 리뷰(WIP)
- wrapping exception부분 미완성
1 parent 8f5bee0 commit 1be1b4d

File tree

1 file changed

+47
-47
lines changed

1 file changed

+47
-47
lines changed

1-js/10-error-handling/2-custom-errors/article.md

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,42 @@
1-
# 커스텀 에러, 에러 확장하기
1+
# 커스텀 에러와 에러 확장
22

3-
무언가 개발할 때, 종종 작업에서 잘못될 수 있는 특정한 것들을 반영하기 위해 자체적인 에러 클래스들이 필요합니다. 네트워크 동작시 에러들에 대해 `HttpError`, 데이터베이스 동작시에 `DbError`, 검색 동작시에 `NotFoundError` 등등이 필요할 수 있습니다.
3+
개발을 하다 보면 자체 에러 클래스가 필요한 경우가 종종 생깁니다. 네트워크 관련 작업 중 에러가 발생했다면 `HttpError`, 데이터베이스 관련 작업 중 에러가 발생했다면 `DbError`, 검색 관련 작업 중 에러가 발생했다면 `NotFoundError`를 사용하는 것이 직관적이기 때문이죠.
44

5-
우리의 에러는 `message`, `name` 같은 기본적인 에러 프로퍼티를 지원해야 하고, `stack`을 지원하는 것도 권장됩니다. 또한 그밖에 다른 프로퍼티도 가질 수 있습니다. 예를 들어 `HttpError` 객체들은 `statusCode` 프로퍼티로 `404` 또는 `403` 또는 `500` 같은 값을 가질 수 있습니다.
5+
직접 에러 클래스를 만든 경우, 이 에러들은 `message`이나 `name`, 가능하다면 `stack` 프로퍼티를 지원해야 합니다. 물론 이런 필수 프로퍼티 이외에도 다른 프로퍼티를 명시할 수 있습니다. `HttpError` 클래스의 객체에 `statusCode` 프로퍼티를 만들고 `404``403`, `500`같은 숫자를 값으로 지정할 수 있을 겁니다.
66

7-
자바스크립트는 `throw` 를 아무 인수와 함께 사용할 수 있게 허용하므로, 기본적으로 커스텀 에러 클래스들은 `Error`를 상속할 필요가 없습니다. 그러나 상속을 하면 `obj instanceof Error`를 사용해서 에러 객체들을 식별하는 것이 가능해집니다. 따라서 상속받는 게 낫습니다.
7+
앞서 배운 바와 같이 `throw`의 인수엔 아무런 제약이 없기 때문에 커스텀 에러 클래스는 반드시 `Error`를 상속할 필요가 없습니다. 그렇지만 `Error`를 상속받아 커스텀 에러 클래스를 만들게 되면 `obj instanceof Error`를 사용해서 에러 객체를 식별할 수 있다는 장점이 생깁니다. 이런 장점 때문에 맨땅에서 커스텀 에러 객체를 만드는 것보다 `Error`를 상속받아 에러 객체를 만드는 것이 낫습니다.
88

9-
애플리케이션의 크기가 점점 증가함에 따라, 자체 에러들은 자연스레 계층 구조를 형성합니다. 예를 들어 `HttpTimeoutError``HttpError`를 상속받는 등입니다.
9+
애플리케이션 크기가 점점 커지면 우리가 만들게 될 커스텀 에러 클래스들은 자연스레 계층 구조를 형성하게 됩니다. `HttpTimeoutError``HttpError`를 상속받는 식으로 말이죠.
1010

1111
## 에러 확장하기
1212

13-
예를 들어, 사용자 데이터를 가진 JSON을 읽어야 하는 `readUser(json)`라는 함수를 생각해 봅시다.
13+
사용자 데이터가 저장된 JSON을 읽는 함수 `readUser(json)`가 있다고 해봅시다.
1414

15-
다음은 유효한 `json`의 모습에 대한 예입니다.
15+
유효한 `json`은 다음과 같은 형태를 띄워야 합니다.
1616
```js
1717
let json = `{ "name": "John", "age": 30 }`;
1818
```
1919

20-
내부적으로 우리는 `JSON.parse`를 이용할 것입니다. 틀린 `json`을 받으면, `SyntaxError`를 던집니다. 그러나 `json`이 문법적으로 맞다고 하더라도, 유효한 사용자라는 의미는 아닙니다, 그렇죠? 필수 데이터를 빠뜨렸을 수도 있습니다. 예를 들어, 사용자들에게 필수인 `name` `age` 프로퍼티가 없을 수도 있습니다.
20+
`readUser` 내부에선 `JSON.parse`를 이용할 겁니다. 잘못된 형식의 `json`이 들어오면 `SyntaxError`가 발생하겠죠. 그러나 인수로 받은 데이터가 JSON 형식이긴 하지만, 유효한 사용자일 것이라는 보장은 없습니다. 사용자 데이터라면 필수적으로 있어야 할 `name`이나 `age`없을 수 있죠.
2121

22-
우리의 함수 `readUser(json)` JSON을 읽을뿐만 아니라, 데이터를 확인("검증")하기도 합니다. 필수 입력란이 없거나, 형식이 틀렸다면, 그것은 에러입니다. 그리고 데이터가 문법적으로 맞기 때문에 `SyntaxError`는 아니고, 다른 종류의 에러입니다. 이를 `ValidationError`라고 부를 것이고 이를 위한 클래스를 생성할 것입니다. 이런 종류의 에러는 또한 문제가 되는 필드에 대한 정보를 가지고 있어야 합니다.
22+
따라서 `readUser(json)` JSON을 읽을 수 있을 뿐만 아니라, 데이터를 검증할 수도 있어야 합니다. 필수 프로퍼티가 없거나, 위 형식에 맞지 않으면 에러를 발생시킬 수 있어야 하죠. 그런데 이때 발생하는 에러는 `SyntaxError`가 아닙니다. JSON 형식은 맞지만, 자체 기준에 맞지 않기 때문에 발생한 에러이므로 전혀 다른 종류의 에러이죠. 여기선 이 에러를 `ValidationError`라고 부르겠습니다. 그리고 `ValidationError`를 위한 클래스를 만들어보겠습니다.
2323

24-
우리의 `ValidationError` 클래스는 내장된 `Error` 클래스로부터 상속받아야 합니다.
24+
`ValidationError` 클래스엔 문제가 되는 필드 정보가 저장될 수 있어야합니다. 내장 클래스인 `Error`를 상속받아 `ValidationError` 클래스를 만들어봅시다.
2525

26-
그 클래스는 내장 클래스지만, 우리 눈 앞에 대략적인 코드가 있어야만 우리가 확장하고 있는 것에 대해 이해할 수 있겠죠. 아래 수도 코드를 살펴봅시다.
26+
먼저 슈도 코드를 통해 `Error` 클래스가 어떻게 생겼는지 알아봅시다.
2727

2828
```js
29-
// 자바스크립트 자체에서 정의한 내장 에러 클래스의 "수도코드"
29+
// 자바스크립트에서 자체적으로 정의한 내장 에러 클래스의 "슈도 코드"
3030
class Error {
3131
constructor(message) {
3232
this.message = message;
33-
this.name = "Error"; // (서로 다른 내장 에러 클래스들의 서로 다른 이름들)
34-
this.stack = <call stack>; // 표준은 아니지만, 대다수의 환경이 지원합니다
33+
this.name = "Error"; // (내장 에러 클래스마다 이름이 다릅니다.)
34+
this.stack = <call stack>; // 표준은 아니지만, 대다수의 환경이 지원합니다.
3535
}
3636
}
3737
```
3838

39-
이제 계속해서 `ValidationError`이 그걸 상속 받도록 해 봅시다.
39+
이제 `ValidationError`에서 `Error`를 상속받아보겠습니다.
4040

4141
```js run untrusted
4242
*!*
@@ -61,11 +61,11 @@ try {
6161
}
6262
```
6363

64-
`(1)` 줄에서 부모 생성자를 호출하고 있다는 것에 주목하시기 바랍니다. 자바스크립트에서는 자식 생성자 안에서 `super`를 호출해야 하므로 이는 필수입니다. 부모 생성자는 `message` 프로퍼티를 설정합니다.
64+
`(1)`에서 부모 생성자를 호출하고 있다는 것에 주목해 주시기 바랍니다. 자바스크립트에서는 자식 생성자 안에서 `super`반드시 호출해야 합니다. 부모 생성자에서 `message` 프로퍼티가 설정됩니다.
6565

66-
부모 생성자는 또한 `name` 프로퍼티를 `"Error"`설정하기 때문에, `(2)` 쥴에서 올바른 값으로 재설정합니다.
66+
부모 생성자에서 `name` 프로퍼티가 `"Error"`설정되기 때문에, `(2)`에서 원하는 값으로 재설정해주었습니다.
6767

68-
`readUser(json)` 안에서 이를 사용해 봅시다.
68+
이제 `readUser(json)` 안에서 `ValidationError` 사용해 봅시다.
6969

7070
```js run
7171
class ValidationError extends Error {
@@ -75,7 +75,7 @@ class ValidationError extends Error {
7575
}
7676
}
7777

78-
// 사용
78+
// 사용법
7979
function readUser(json) {
8080
let user = JSON.parse(json);
8181

@@ -89,7 +89,7 @@ function readUser(json) {
8989
return user;
9090
}
9191

92-
// try..catch와 함께 사용한 동작 예제
92+
// try..catch와 readUser를 함께 사용하면 다음과 같습니다.
9393

9494
try {
9595
let user = readUser('{ "age": 25 }');
@@ -101,31 +101,31 @@ try {
101101
} else if (err instanceof SyntaxError) { // (*)
102102
alert("JSON Syntax Error: " + err.message);
103103
} else {
104-
throw err; // 알려지지 않은 에러, 재던지기를 합니다 (**)
104+
throw err; // 알려지지 않은 에러는 재던지기 합니다. (**)
105105
}
106106
}
107107
```
108108

109-
위의 코드에서 `try..catch` 블록은 `JSON.parse`에서 우리가 만든 `ValidationError`내장된 `SyntaxError`다 처리합니다.
109+
이제 `try..catch` 블록에서 우리가 만든 커스텀 에러인 `ValidationError` `JSON.parse`에서 발생하는 `SyntaxError`다를 처리할 수 있게 되었네요.
110110

111-
`instanceof`를 사용하여 `(*)` 줄에서 에러 유형을 확인하는 방법을 살펴 보세요.
111+
이 과정에서 `instanceof`로 에러 유형을 확인(`(*)`)하였습니다.
112112

113-
다음과 같이 `err.name`를 볼 수도 있습니다.
113+
`instanceof` 말고 `err.name`을 사용해 에러 종류를 확인하는 것도 가능합니다.
114114

115115
```js
116116
// ...
117-
// instead of (err instanceof SyntaxError)
117+
// (err instanceof SyntaxError) 대신 사용 가능
118118
} else if (err.name == "SyntaxError") { // (*)
119119
// ...
120120
```
121121
122-
`instanceof`를 사용하는 게 훨씬 좋습니다. 왜냐하면 나중에 `ValidationError`를 확장하여 `PropertyRequiredError` 같은 서브 타입을 만들 것이기 때문입니다. 그리고 `instanceof` 검사는 상속받은 새로운 클래스들에서도 동작할 것입니다. 따라서 나중에 대비할 수 있게 됩니다.
122+
그런데 `err.name`보다는 `instanceof`를 사용하는 게 훨씬 좋습니다. 나중에 `ValidationError`를 확장하여 `PropertyRequiredError` 같은 새로운 확장 에러를 만들게 될 경우, `instanceof`상속받은 새로운 클래스에서도 동작하기 때문입니다.
123123
124-
또한 `catch`가 알려지지 않은 에러를 만나면 `(**)` 줄에서 재던지기를 한다는 것이 중요합니다. `catch` 블록은 유효성 검사와 문법 오류를 처리하는 방법만 알고 있으며, 다른 종류(코드 오타 등)는 빠져나가야 합니다.
124+
알려지지 않은 에러를 만났을 때 `catch`에서 재 던지기를 한다는 점(`(**)`) 또한 주목해서 봐주시기 바랍니다. `catch` 블록에선 유효성 검사와 문법 오류만 처리하고, 다른 종류의 에러는 밖으로 던져야 합니다.
125125
126126
## 더 깊게 상속하기
127127
128-
`ValidationError` 클래스는 너무 추상적입니다. 많은 것들이 잘못될 있습니다. 프로퍼티가 없거나 잘못된 형식(가령 `age`에 문자열 값이 들어가는 것처럼)으로 될 수 있습니다. 프로퍼티가 없는 바로 그 경우에 대해서 더 구체적인 클래스인 `PropertyRequiredError`를 만들어 봅시다. 누락된 프로퍼티에 대한 추가 정보를 담을 것입니다.
128+
앞서 만든 `ValidationError` 클래스는 너무 포괄적이어서 뭔가 잘못될 확률이 있습니다. 꼭 필요한 프로퍼티가 누락되거나 `age`에 문자열 값이 들어가는 것처럼 형식이 잘못된 경우를 처리할 수 없습니다. 필수 프로퍼티가 없는 경우에 대응할 수 있도록 좀 더 구체적인 클래스 `PropertyRequiredError`를 만들어 봅시다. `PropertyRequiredError`누락된 프로퍼티에 대한 추가 정보가 담겨야 합니다.
129129
130130
```js run
131131
class ValidationError extends Error {
@@ -145,7 +145,7 @@ class PropertyRequiredError extends ValidationError {
145145
}
146146
*/!*
147147

148-
// Usage
148+
// 사용법
149149
function readUser(json) {
150150
let user = JSON.parse(json);
151151

@@ -159,7 +159,7 @@ function readUser(json) {
159159
return user;
160160
}
161161

162-
// try..catch와 함께 사용한 동작 예제
162+
// try..catch와 readUser를 함께 사용하면 다음과 같습니다.
163163

164164
try {
165165
let user = readUser('{ "age": 25 }');
@@ -173,18 +173,18 @@ try {
173173
} else if (err instanceof SyntaxError) {
174174
alert("JSON Syntax Error: " + err.message);
175175
} else {
176-
throw err; // unknown error, rethrow it
176+
throw err; // 알려지지 않은 에러는 재던지기 합니다.
177177
}
178178
}
179179
```
180180
181-
새로운 클래스 `PropertyRequiredError`는 사용하기 쉽습니다. 우리는 단지 프로퍼티 이름을 전달하기만 하면 됩니다. `new PropertyRequiredError(property)`. 사람이 읽기 쉬운 `message`생성자에 의해 생성됩니다.
181+
새롭게 만든 클래스 `PropertyRequiredError`는 사용하기 쉽습니다. `new PropertyRequiredError(property)`처럼 프로퍼티 이름을 전달하기만 하면 됩니다. 사람이 읽을 수 있는 `message`생성자가 알아서 만들어줍니다.
182182
183-
Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = <class name>` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all our custom errors from it.
183+
여기서 주목할 점은 `PropertyRequiredError` 생성자 안에서 `this.name`을 수동으로 할당해 주었다는 것입니다. 이렇게 매번 커스텀 에러 클래스의 생성자 안에서 `this.name`를 할당해 주는 것은 귀찮아 보이긴 합니다. 이런 번거로운 작업은 '기본 에러' 클래스를 만들고 커스텀 에러들이 이 클래스를 상속받게 하면 피할 수 있습니다. 기본 에러의 생성자에 `this.name = this.constructor.name`를 추가하면 되죠.
184184
185-
이 클래스를 `MyError`라고 부릅시다.
185+
이 클래스를 `MyError`라고 부르겠습니다.
186186
187-
여기 `MyError`와 다른 커스텀 에러 클래스들의 간단한 코드가 있습니다.
187+
`MyError`를 사용하면 다음과 같이 커스텀 에러 클래스를 간결하게 할 수 있습니다.
188188
189189
```js run
190190
class MyError extends Error {
@@ -205,23 +205,23 @@ class PropertyRequiredError extends ValidationError {
205205
}
206206
}
207207

208-
// name is correct
208+
// 제대로 된 이름이 출력됩니다.
209209
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError
210210
```
211211
212212
이제 커스텀 에러들, 특히 `ValidationError`는, 생성자에서 `"this.name = ..."` 줄을 제거하여 훨씬 짧아졌습니다.
213213
214214
## 예외 감싸기
215215
216-
위의 코드에서 함수 `readUser`의 목적은 "사용자 데이터를 읽는 것"입니다. 그런데 과정에서 다른 오류들이 발생할 수 있습니다. 지금 당장은 `SyntaxError``ValidationError`만 있지만, 앞으로 `readUser` 함수가 더 커지면 다른 오류들을 만들어 낼 수도 있습니다.
216+
함수 `readUser`는 '사용자 데이터를 읽기' 위해 만들어졌습니다. 그런데 사용자 데이터를 읽는 과정에서 다른 오류들이 발생할 수 있습니다. 지금 당장은 `SyntaxError``ValidationError`만 있지만, 앞으로 `readUser` 함수가 더 커지면 다른 커스텀 에러 클래스를 만들어야 할 수 있습니다.
217217
218-
The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`?
218+
물론 `readUser`는 이런 에러를 모두 처리할 수 있어야 합니다. 그런데 지금은 `catch` 블록 안에 `if`문 여러 개를 넣어 에러를 처리하고 있죠. 미래에 커스텀 에러 클래스가 더 추가될 텐데 앞으로도 이렇게 `readUser`를 호출하는 곳에서 모든 에러를 종류에 따라 하나하나 처리해야만 할까요?
219219
220-
보통 대답은 "아니요"입니다. 바깥쪽 코드는 "모든 것들의 한 수준 위"가 되고 싶어합니다. 바깥쪽 코드는 일종의 "data reading error"를 원합니다. 정확히 왜 그런 일이 발생했는지는 보통 무의미합니다. (에러 메시지가 그것을 설명합니다). 또는, 필요한 경우에만 오류 상세를 얻는 방법이 있으면 훨씬 좋습니다.
220+
Often the answer is "No": the outer code wants to be "one level above all that", it just wants to have some kind of "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, it could have a way to get the error details, but only if we need to.
221221
222-
이런 오류들을 나타내는 새로운 `ReadError` 클래스를 만들어 봅시다. If an error occurs inside `readUser` 안에서 오류가 발생하면, 오류를 거기에서 잡아서 `ReadError`를 생성합니다. 우리는 또한 `cause` 프로퍼티에 실제 오류에 대한 참조도 보관할 것입니다. 그러면 바깥쪽 코드에서는 `ReadError`만 확인하면 됩니다..
222+
So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`.
223223
224-
여기에 `ReadError`를 정의하고 `readUser``try..catch` 안에서 쓰임을 시연하는 코드가 있습니다.
224+
Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`:
225225
226226
```js run
227227
class ReadError extends Error {
@@ -289,14 +289,14 @@ try {
289289
}
290290
```
291291
292-
위의 코드에서 `readUser`는 정확히 설명한대로 동작합니다. 문법 및 유효성 검사 오류들을 잡아서 `ReadError` 오류를 던집니다 (알려지지 않은 오류들은 보통처럼 다시 던져집니다).
292+
In the code above, `readUser` works exactly as described -- catches syntax and validation errors and throws `ReadError` errors instead (unknown errors are rethrown as usual).
293293
294-
따라서 바깥쪽 코드는 `instanceof ReadError`만 체크하면 끝입니다. 발생할 수 있는 모든 오류 유형들을 나열할 필요가 없습니다.
294+
So the outer code checks `instanceof ReadError` and that's it. No need to list all possible error types.
295295
296-
이런 접근법을 "예외 감싸기"라고 합니다. "로우레벨 예외"들을 가져다가 `ReadError` 안으로 "감싸서", 더 추상적이고 호출하는 코드에서 사용하기 편리하기 때문입니다. 객체지향 프로그래밍에서 보편적으로 사용됩니다.
296+
The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming.
297297
298298
## 요약
299299
300-
- `Error`나 다른 내장 오류 클래스로부터 상속받는 게 가능합니다. 이때 `name` 프로퍼티와 `super`를 호출하는 것만 잊지 않으시면 됩니다.
301-
- 특정 오류를 확인하는데 `instanceof`사용할 수 있습니다. 상속된 클래스에도 마찬가지죠. 그러나 서드파티 라이브러리에서부터 온 오류 객체의 경우엔 클래스를 알아내는 것이 쉽지 않습니다. 이때는 `name` 프로퍼티를 사용해 확인할 수 있습니다.
302-
- 예외 감싸기는 널리 사용되는 기법입니다. 함수는 로우-레벨 예외를 처리하고, 이때 로우-레벨 에러를 만드는 대신에 하이-레벨 에러를 만듭니다. 로우-레벨 예외는 위의 예시처럼 가끔 해당 객체의 프로퍼티가 되곤 합니다. `err.cause`처럼 말이죠. 다만 필수사항은 아닙니다.
300+
- 커스텀 클래스는 `Error`나 다른 내장 오류 클래스를 상속받아 만들 수 있습니다. 이때 `super`를 호출해야 한다는 점과 `name` 프로퍼티를 신경써야 한다는 점을 잊지 마세요.
301+
- `instanceof`사용하면 오류 종류를 판별할 수 있습니다. 상속된 클래스에도 마찬가지죠. 그러나 서드파티 라이브러리에서 온 오류 객체는 클래스를 알아내는 것이 쉽지 않습니다. 이때는 `name` 프로퍼티를 사용해 오류 종류를 확인할 수 있습니다.
302+
- Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required.

0 commit comments

Comments
 (0)