Skip to content

Commit d0721c6

Browse files
[autofix.ci] apply automated fixes
1 parent 79bfa58 commit d0721c6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1739
-1544
lines changed

fundamentals/code-quality/.vitepress/theme/composables/useGithubApi.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ export function useGithubApi(config: GithubApiConfig) {
179179
}
180180

181181
const fetchGithubDiscussion = async (query: string) => {
182-
const isLocalhost = typeof window !== 'undefined' && window.location.hostname === "localhost";
182+
const isLocalhost =
183+
typeof window !== "undefined" && window.location.hostname === "localhost";
183184

184185
if (isLocalhost) {
185186
const accessToken = await (import.meta as any).env

fundamentals/debug/index.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,3 @@ features:
2929
title: 같은 실수를 반복하고 싶지 않다면
3030
details: 디버깅 경험을 문서화하고 팀과 공유해서 재발을 방지해요.
3131
---
32-
33-
34-

fundamentals/debug/pages/diagnose/error-message.md

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
코드에 문법적인 문제가 있을 때는 보통 `"SyntaxError: ~"`로 시작하는 에러 메시지가 출력돼요. 이 메시지는 자바스크립트 엔진이 **코드를 실행하기도 전에 문법을 해석하다가 실패했음을 알려주는 신호**예요.
99

1010
예를 들어, 괄호가 닫히지 않았거나, `export``return` 같은 예약어를 잘못 썼을 때 아래와 같은 메시지를 볼 수 있어요.
11+
1112
```tsx 5
1213
function run() {
1314
const name = 'Hello'
1415
if (true) {
1516
console.log(name)
1617

1718
```
19+
1820
```
1921
SyntaxError: Unexpected token '{'
2022
```
@@ -25,90 +27,97 @@ SyntaxError: Unexpected token '{'
2527
```tsx
2628
JSON.parse('{ foo: "bar" }");
2729
```
30+
2831
```
2932
SyntaxError: The string did notmatch
3033
the expected pattern.
3134
```
3235
3336
### 확인할 것
37+
3438
- 문법에 맞게 작성되었는지 확인해요.
3539
- 사용자가 코드를 작성하는 동안 IDE가 구문 오류를 실시간으로 감지하고 강조 표시하여 판단해주기 때문에, 문법오류는 코드 작성 시 문제를 파악하고 고칠 수 있어요.
3640
3741
## 모듈 import 오류
3842
39-
모듈을 import하는 과정에서도 문법 오류(`SyntaxError`)가 발생할 수 있어요. 특히 ES 모듈(ESM)과 CommonJS(CJS) 방식이 혼합된 환경에서는 설정이 어긋나기 쉽고, 이로 인해 문법 오류처럼 보이는 에러가 나타날 수 있어요. 모듈 관련 에러 메시지를 보면 단순 문법 문제가 아니라 **모듈 시스템 설정에 문제가 있을 가능성**을 유추할 수 있어요.
40-
43+
모듈을 import하는 과정에서도 문법 오류(`SyntaxError`)가 발생할 수 있어요. 특히 ES 모듈(ESM)과 CommonJS(CJS) 방식이 혼합된 환경에서는 설정이 어긋나기 쉽고, 이로 인해 문법 오류처럼 보이는 에러가 나타날 수 있어요. 모듈 관련 에러 메시지를 보면 단순 문법 문제가 아니라 **모듈 시스템 설정에 문제가 있을 가능성**을 유추할 수 있어요.
4144
4245
예를들어, .js 파일에 import 구문을 사용하게 되면 아래와 같은 에러머세지가 나타나요. Node.js는 기본적으로 .js 파일을 CommonJS로 해석하기 때문에, import를 사용할 수 없고 require()를 써야 해요.
4346
4447
```js
4548
// example.js
46-
import fs from 'fs';
49+
import fs from "fs";
4750

48-
fs.readFile('example.txt', 'utf8', (err, data) => {
51+
fs.readFile("example.txt", "utf8", (err, data) => {
4952
if (err) throw err;
5053
console.log(data);
5154
});
5255
```
56+
5357
```
5458
SyntaxError: Cannot use import statement outside a module
5559
```
5660
57-
### 확인할 것
61+
### 확인할 것
62+
5863
- 프로젝트의 모듈 시스템 설정이 올바른지 확인해요
5964
- ESM 사용 시: `package.json``"type": "module"` 설정을 확인해요
6065
- CommonJS 사용 시: `"type"` 필드를 생략하거나, `"type": "commonjs"`로 명시해도 돼요
6166
- `.mjs`, `.cjs`, `.js` 확장자가 적절히 사용됐는지 확인해요
6267
- 잘못된 번들 경로로 `esm` 전용 모듈을 가져오지 않았는지 확인해요
6368
64-
6569
## 타입 오류 (`TypeError`)
6670
6771
값의 타입이 예상과 다를 때는 `"TypeError: ~"`로 시작하는 에러 메시지가 출력돼요. 이 에러는 주로 **정의되지 않았거나 잘못된 값에 접근하거나, 함수가 아닌 값을 함수처럼 호출했을 때** 발생해요.
6872
6973
예를 들어 객체가 `null`이나 `undefined`인 상태에서 속성에 접근하려고 하면 이런 메시지를 볼 수 있어요.
74+
7075
```tsx 2
7176
const user = null;
7277
console.log(user.name);
7378
```
79+
7480
```
75-
TypeError: Cannot read property 'name' of null
81+
TypeError: Cannot read property 'name' of null
7682
```
7783
7884
<br/>
7985
8086
함수가 아닌 객체를 호출하면 다음과 같은 에러 메세지가 나요
87+
8188
```tsx 2
8289
const num = 42;
8390
num();
84-
8591
```
92+
8693
```
8794
TypeError: num is not a function
8895
```
8996

9097
<br/>
9198

92-
`async` 함수 안에서 비동기 작업을 실행할 때 `await`를 빠뜨리면 의도하지 않은 동작이나 `Promise` 타입 관련 오류가 발생할 수 있어요. 예를 들어, 함수가 `Promise`를 반환하는데 이를 `await`하지 않고 그대로 사용하면 타입스크립트는 `Promise<T>`와 `T`를 혼동해서 에러를 발생시켜요.
99+
`async` 함수 안에서 비동기 작업을 실행할 때 `await`를 빠뜨리면 의도하지 않은 동작이나 `Promise` 타입 관련 오류가 발생할 수 있어요. 예를 들어, 함수가 `Promise`를 반환하는데 이를 `await`하지 않고 그대로 사용하면 타입스크립트는 `Promise<T>`와 `T`를 혼동해서 에러를 발생시켜요.
93100

94101
```tsx 2
95102
async function getUserName() {
96103
return "Alice";
97104
}
98105

99106
async function main() {
100-
const name: string = getUserName();
107+
const name: string = getUserName();
101108
}
102109
```
110+
103111
```
104112
Type 'Promise<string>' is not assignable to type 'string'
105113
```
106114
107115
### 확인할 것
116+
108117
- 객체가 실제로 존재하는지 확인해요
109-
- API 응답 데이터 구조가 맞는지 확인해요
118+
- API 응답 데이터 구조가 맞는지 확인해요
110119
- `typeof`, `Array.isArray()` 등으로 미리 검사했는지 확인해요
111-
- `await` 누락 여부를 확인해요.
120+
- `await` 누락 여부를 확인해요.
112121
113122
## 참조 오류 (`ReferenceError`)
114123
@@ -118,29 +127,31 @@ Type 'Promise<string>' is not assignable to type 'string'
118127
119128
```tsx 1
120129
console.log(userName);
121-
let userName = 'Alice';
122-
130+
let userName = "Alice";
123131
```
132+
124133
```
125134
ReferenceError: userName is not defined
126135
```
127136
128-
### 확인할 것
137+
### 확인할 것
138+
129139
- 변수가 선언되었는지 확인해요
130140
- 선언보다 먼저 접근한 건 아닌지 확인해요
131141
- 외부 스코프 참조가 의도한 것인지 확인해요
132142
133143
## 리소스 로딩 오류
134144
135-
외부 자원을 가져오는 요청이 네트워크 단계에서 실패하면 브라우저는 `fetch`에서 `TypeError`를 던져요. 브라우저에 따라 `"TypeError: Load failed"` 또는 `"TypeError: Failed to fetch"`로 나타나요. 이 에러는 **HTTP 4xx, 5xx 같은 응답 에러와 다르게** 네트워크 자체가 실패했거나 보안 정책으로 차단됐을 때 발생해요. 이 경우엔 **reject**되어 바로 catch로 넘어가요.
145+
외부 자원을 가져오는 요청이 네트워크 단계에서 실패하면 브라우저는 `fetch`에서 `TypeError`를 던져요. 브라우저에 따라 `"TypeError: Load failed"` 또는 `"TypeError: Failed to fetch"`로 나타나요. 이 에러는 **HTTP 4xx, 5xx 같은 응답 에러와 다르게** 네트워크 자체가 실패했거나 보안 정책으로 차단됐을 때 발생해요. 이 경우엔 **reject**되어 바로 catch로 넘어가요.
136146
137147
```tsx 3
138-
fetch('https://api.otherdomain.com/data')
148+
fetch("https://api.otherdomain.com/data")
139149
.then((res) => res.json())
140150
.catch((err) => {
141-
console.error('네트워크 또는 CORS 오류:', err.message);
151+
console.error("네트워크 또는 CORS 오류:", err.message);
142152
});
143153
```
154+
144155
```
145156
TypeError: Load failed
146157
TypeError: Failed to fetch
@@ -151,26 +162,29 @@ TypeError: Failed to fetch
151162
참고로, **HTTP 4xx, 5xx 같은 응답 에러**가 발생했을 때는 **reject하여 catch로 넘기지 않고** `res.ok``false`인 응답을 돌려줘요.
152163
153164
```ts
154-
fetch('/api/data')
165+
fetch("/api/data")
155166
.then(async (res) => {
156167
if (!res.ok) {
157168
const text = await res.text();
158-
throw new Error(`서버 오류: HTTP ${res.status} ${res.statusText} ${text}`);
169+
throw new Error(
170+
`서버 오류: HTTP ${res.status} ${res.statusText} ${text}`
171+
);
159172
}
160173
return res.json();
161174
})
162175
.catch((err) => console.error(err));
163176
```
164177
165-
166178
### 확인할 것
179+
167180
- 콘솔에 `TypeError: Load failed``Failed to fetch`가 보이면 네트워크, CORS, 인증서, CSP(Content Security Policy), 확장 프로그램 차단 가능성을 먼저 의심해요.
168181
169182
---
170183
171184
### 📚 더 알아보기
185+
172186
- [MDN: SyntaxError](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError)
173187
- [MDN: TypeError](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/TypeError)
174188
- [MDN: ReferenceError](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError)
175189
- [Node.js ESM 가이드](https://nodejs.org/api/esm.html)
176-
- [CORS 이해하기](https://developer.mozilla.org/ko/docs/Web/HTTP/CORS)
190+
- [CORS 이해하기](https://developer.mozilla.org/ko/docs/Web/HTTP/CORS)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# 진단하기
2-
에러를 진단할 때는 원인을 구체적으로 좁혀가야 해요. 문제를 체계적으로 분석하고 해결하는 데 도움이 되는 방법이에요.
2+
3+
에러를 진단할 때는 원인을 구체적으로 좁혀가야 해요. 문제를 체계적으로 분석하고 해결하는 데 도움이 되는 방법이에요.

fundamentals/debug/pages/diagnose/map.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,34 @@
44

55
특히 데이터가 어떻게 흘러가는지를 단계별로 정리하면, 실제 동작한 결과와 기대한 결과를 비교해가며 에러의 원인을 좁혀갈 수 있어요. 이 방식은 비동기 요청, 상태 변화, 복잡한 사용자 입력 처리 흐름을 분석할 때 특히 유용해요.
66

7-
8-
## 예시
7+
## 예시
98

109
유효성 검사 입력창을 예시로 들어볼게요. 입력값이 어떻게 처리되고 어떤 검증 단계를 거치는지를 살펴볼 수 있는 간단한 예시예요.
1110

1211
![](../../images/diagnose/map-ex-input-error-message.png){width=200}
1312

1413
## 동작 순서
14+
1515
1. 사용자가 검색 입력창에 키워드를 입력해요.
1616
2. 입력이 바뀔 때마다 유효성 검사 함수가 실행돼요.
1717
3. 검사 결과, 입력에 따라 `true / false`를 반환해요.
1818
4. 검사 결과가 `false`이면 오류 메시지나 경고 아이콘 같은 에러 UI를 보여줘요.
1919
5. 검사 결과가 `true`이면 정상 UI를 보여줘요.
2020

21-
2221
## 작업지도
22+
2323
위의 동작 순서에 따라 작업지도를 그려보아요. 코드를 실행했을때 아래의 순서를 모두 통과하면 이 코드는 문제가 없다는 뜻이에요.
2424
![](../../images/diagnose/map.png)
2525

2626
## 코드
27+
2728
아래 코드에는 치명적인 오류가 있는데요, 작업지도를 하나씩 검증하며 어디에 문제가 발생하는지 살펴보아요.
2829

2930
```tsx 8,15,19,26
30-
import React, { useState } from 'react';
31+
import React, { useState } from "react";
3132

3233
const ValidatedInput = () => {
33-
const [input, setInput] = useState('');
34+
const [input, setInput] = useState("");
3435
const [isValid, setIsValid] = useState(true);
3536

3637
const validate = (value) => {
@@ -53,17 +54,17 @@ const ValidatedInput = () => {
5354
<div>
5455
<input type="text" value={input} onChange={handleChange} />
5556
{/* 4. isValid값이 false이면 에러메세지를 출력해요 */}
56-
{isValid && (
57-
<div style={{ color: 'red' }}>Error message</div>
58-
)}
57+
{isValid && <div style={{ color: "red" }}>Error message</div>}
5958
</div>
6059
);
6160
};
6261
export default ValidatedInput;
6362
```
63+
6464
![](../../images/diagnose/map-check.png)
6565

6666
## 에러 도출
67+
6768
검증 결과, `isValid &&` 조건으로 작성된 에러 UI 렌더링 부분에 **논리 오류**가 있었어요.`isValid === false`로 작성해야 하는데, `isValid &&`로 되어 있어서, **정상적인 UI가 아닌, 잘못된 조건에 의해 오류 메시지가 렌더링**되고 있었어요.
6869

6970
```tsx 4
@@ -78,5 +79,4 @@ export default ValidatedInput;
7879
};
7980
```
8081

81-
82-
이처럼 작업 지도를 그리고 단계별로 동작을 검증해 나가면, 코드 흐름이 머릿속에 명확하게 그려지고 문제를 빠르게 찾아낼 수 있어요. 특히 UI 상태 변화나 비동기 처리처럼 복잡한 로직에서는 작업 지도가 강력한 디버깅 도구가 될 수 있어요.
82+
이처럼 작업 지도를 그리고 단계별로 동작을 검증해 나가면, 코드 흐름이 머릿속에 명확하게 그려지고 문제를 빠르게 찾아낼 수 있어요. 특히 UI 상태 변화나 비동기 처리처럼 복잡한 로직에서는 작업 지도가 강력한 디버깅 도구가 될 수 있어요.

fundamentals/debug/pages/fix/correct.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
# 근본 원인 수정하기
2+
23
에러를 수정할 때 종종 겉보기에만 문제를 해결한 코드를 작성하는 경우가 있어요. 이는 일시적으로 동작하더라도, 추후에 같은 문제가 다시 발생하거나 다른 곳에서 파급 효과를 일으킬 수 있어요. 진짜 중요한 건 “근본 원인을 찾아 수정하는 것” 이에요.
34

45
## 예시 1
6+
57
배열에서 값을 찾을 때 undefined 에러가 나는 경우가 있어요. 사용자는 id === 3인 유저를 찾으려 했지만 배열에 해당 유저가 없으므로 undefined를 반환하고, 이후 .name에 접근하면서 에러가 발생해요.
68

79
```tsx
8-
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
10+
const users = [
11+
{ id: 1, name: "Alice" },
12+
{ id: 2, name: "Bob" }
13+
];
914
const selectedUser = users.find((user) => user.id === 3);
1015

11-
console.log(selectedUser.name);
12-
16+
console.log(selectedUser.name);
1317
```
1418

1519
### 겉보기에만 해결된 코드
20+
1621
as로 타입 단언을 해버리거나, assert!를 사용하는 등, 겉으로는 타입에러가 발생하지 않도록 처리할 수 있어요. 하지만, 로직상 undefined.name이 undefined로 처리되어 잘못된 데이터가 조용히 넘어가요. 문제의 원인(해당 유저가 없다는 사실)을 숨기기만 하고, 애초에 find() 결과를 제대로 확인하지 않았다는 잘못된 가정을 그대로 유지한 셈이에요.
22+
1723
```tsx
1824
const selectedUser = users.find((user) => user.id === 3) || {};
1925
const selectedUser = users.find((user) => user.id === 3) as User;
20-
const selectedUser = users.find((user) => user.id === 3)!
26+
const selectedUser = users.find((user) => user.id === 3)!;
2127
```
2228

2329
### 근본 원인을 해결한 코드
30+
2431
데이터의 불완전성을 명시적으로 확인하고, 문제가 생기면 명확하게 처리해요. 또는, 타입 단언은 제거하고, 옵셔널 체이닝을 잘 사용하여 의도하지 않은 에러를 방지해요.
32+
2533
```tsx
2634
const selectedUser = users.find((user) => user.id === 3);
2735

2836
if (!selectedUser) {
29-
throw new Error('User with ID 3 not found');
37+
throw new Error("User with ID 3 not found");
3038
}
3139

3240
console.log(selectedUser.name);
@@ -38,13 +46,14 @@ console.log(selectedUser?.name ?? "사용자 없음");
3846
```
3947

4048
## 예시 2
49+
4150
검색어 자동완성 API 요청 중 이전 요청 결과가 덮어써지는 문제를 예시로 들어볼게요.
4251

4352
검색창에서 빠르게 입력 시, 결과가 엉켜서 표시되는 상황이에요. 사용자가 "app", "apple", "apples"처럼 빠르게 입력하면 fetch 요청이 비동기적으로 여러 번 호출되요. 마지막 요청보다 먼저 끝난 이전 응답이 덮어쓰기 되는 현상 발생하고, 오래된 결과가 화면에 나타나는 현상이 발생해요.
4453

4554
```tsx
4655
function SearchBox() {
47-
const [query, setQuery] = useState('');
56+
const [query, setQuery] = useState("");
4857
const [result, setResult] = useState("");
4958

5059
useEffect(() => {
@@ -73,8 +82,10 @@ function SearchBox() {
7382
);
7483
}
7584
```
76-
### 겉보기에 해결된 코드:
77-
`setTimeout`으로 딜레이를 주고, `clearTimeout`으로 이전 요청을 정리하는 것 같지만 실제로는 문제를 해결하지 못해요. `clearTimeout`은 다음 요청 전 대기 중인 타임아웃만 취소할 뿐, 이미 시작된 `fetch` 요청은 취소하지 않아요. 그래서 불필요한 요청이 계속 서버에 전달될 수 있어요. 근본 원인인 "응답 순서 관리" 를 하지 않았기 때문에 race condition은 그대로 존재해요.
85+
86+
### 겉보기에 해결된 코드:
87+
88+
`setTimeout`으로 딜레이를 주고, `clearTimeout`으로 이전 요청을 정리하는 것 같지만 실제로는 문제를 해결하지 못해요. `clearTimeout`은 다음 요청 전 대기 중인 타임아웃만 취소할 뿐, 이미 시작된 `fetch` 요청은 취소하지 않아요. 그래서 불필요한 요청이 계속 서버에 전달될 수 있어요. 근본 원인인 "응답 순서 관리" 를 하지 않았기 때문에 race condition은 그대로 존재해요.
7889

7990
```tsx
8091
useEffect(() => {
@@ -93,6 +104,7 @@ useEffect(() => {
93104
```
94105

95106
### 근본 원인을 해결한 코드
107+
96108
AbortController 또는 요청 순서 관리하도록 수정했어요. 디바운스로 불필요한 요청 자체를 줄이고, AbortController로 진행 중이던 이전 요청을 취소하여 레이스를 막았어요.
97109

98110
```tsx 6,7,15,16,22,35,37,38,39,40,41,42,43
@@ -129,7 +141,7 @@ function SearchBox() {
129141
//Abort Error는 조용히 무시해요
130142
if (err instanceof DOMException && err.name === "AbortError") return;
131143
console.error(err);
132-
})
144+
});
133145
}, 300);
134146

135147
return () => {
@@ -157,4 +169,4 @@ function SearchBox() {
157169
</>
158170
);
159171
}
160-
```
172+
```

0 commit comments

Comments
 (0)