[{"content":"면책사항 (Disclaimer) 최종 수정일: 2026년 8월 24일\nDevTrace(tokkabi.com)는 개발 과정에서 발생하는 오류와 문제를 해결하는 데 도움이 되는 기술 정보와 문제 해결 방법을 제공하는 것을 목적으로 하는 블로그입니다.\n1. 정보의 정확성 본 사이트에서는 개발 경험, 공식 문서, 기술 문서 및 공개된 자료 등을 참고하여 콘텐츠를 작성하고 있습니다.\n하지만 소프트웨어와 개발 환경은 버전, 운영체제, 라이브러리, 서버 환경 및 설정에 따라 동작이 달라질 수 있습니다.\n따라서 본 사이트의 내용이 모든 환경에서 동일하게 적용되거나 항상 정확하다는 것을 보장하지 않습니다.\n2. 기술 정보의 사용 본 사이트에서 제공하는 명령어, 코드, 설정 및 해결 방법을 실제 환경에 적용하기 전에 자신의 개발 환경과 공식 문서를 함께 확인하시기 바랍니다.\n특히 서버 설정, 데이터베이스, 네트워크, 보안 설정 및 운영 환경에 변경을 적용할 경우 먼저 필요한 데이터를 백업하고 테스트 환경에서 검증하는 것을 권장합니다.\n3. 외부 링크 및 참고 자료 본 사이트의 일부 콘텐츠에는 공식 문서, Stack Overflow 및 기타 외부 웹사이트에 대한 링크가 포함될 수 있습니다.\n외부 사이트의 콘텐츠, 정확성, 가용성 및 개인정보 처리 방식에 대해서는 DevTrace가 책임지지 않습니다.\n외부 사이트를 이용할 때에는 해당 사이트의 이용약관과 개인정보처리방침을 확인하시기 바랍니다.\n4. 콘텐츠 변경 소프트웨어와 기술은 지속적으로 변경됩니다.\n이에 따라 과거에 작성된 콘텐츠가 최신 버전의 소프트웨어나 개발 환경에서는 다르게 동작할 수 있으며, DevTrace는 필요에 따라 콘텐츠를 수정하거나 업데이트할 수 있습니다.\n5. 책임의 제한 본 사이트의 정보를 이용하여 발생한 데이터 손실, 서비스 장애, 시스템 오류 또는 기타 직접적·간접적 손해에 대해 DevTrace는 법적으로 허용되는 범위 내에서 책임을 지지 않습니다.\n중요한 시스템이나 운영 환경에 적용하기 전에 반드시 해당 기술의 공식 문서와 자신의 환경을 확인하시기 바랍니다.\n6. 광고 본 사이트는 Google AdSense 등의 광고 서비스를 이용할 수 있습니다.\n광고를 통해 제공되는 제품이나 서비스에 대한 구매 및 이용 여부는 방문자의 판단과 책임에 따라 결정됩니다.\nDevTrace는 광고에 표시되는 제품이나 서비스의 품질 또는 성능을 보증하지 않습니다.\n","permalink":"https://tokkabi.com/disclaimer/","summary":"면책사항 (Disclaimer) 최종 수정일: 2026년 8월 24일 DevTrace(tokkabi.com)는 개발 과정에서 발생하는 오류와 문제를 해결하는 데 도움이 되는 기","title":"면책사항 (Disclaimer)"},{"content":"1. 문제 정의 Windows에서는 Node.js 설치 후 npm install \u0026lt;패키지\u0026gt; 명령을 실행할 때 다음과 같은 메시지가 나오며 설치가 실패하는 경우가 있다.\n1 Error: ENOENT, stat \u0026#39;C:\\Users\\RT\\AppData\\Roaming\\npm\u0026#39; ENOENT는 Error NO ENTry(해당 항목 없음)의 약자로, 운영체제가 요청한 파일이나 디렉터리를 찾지 못했을 때 반환하는 오류 코드다. 즉 npm이 작업해야 할 경로(여기서는 C:\\Users\\RT\\AppData\\Roaming\\npm)가 실제 파일시스템에 존재하지 않는다는 뜻이다.\n출처에서 확인된 실제 사례: Windows 7 32비트 환경에 Node.js 32비트를 설치하고 npm install jquery를 실행하자 위 오류가 발생했다. 채택된 해결책은 오류가 가리키는 경로에 이름이 npm인 폴더를 수동으로 만드는 것이었다.\n흔한 오해 (증상 fingerprint) \u0026ldquo;npm이 설치되지 않았다\u0026quot;고 생각한다. 그러나 이 오류는 npm 자체가 없다는 뜻이 아니라, npm이 전역 패키지를 저장하는 경로 폴더가 없다는 뜻이다. npm 바이너리는 정상 동작한다. **\u0026ldquo;앱데이터 경로를 지워서 벌어진 문제\u0026rdquo;**라는 막연한 인상만 남는다. 실제로는 npm 전역 prefix 경로의 폴더 생성이 누락·삭제·손상된 것이 핵심이다. 빠른 판단 기준 오류 선별 판단 ENOENT, stat 'C:\\Users\\...\\AppData\\Roaming\\npm' 전역 npm 폴더가 없음 → 폴더 생성으로 해결 npm is not recognized / 'npm'은(는) 내부 또는 외부 명령 npm이 PATH에 없음 → 별도의 설치·PATH 설정 문제 ENOENT ... package.json 프로젝트 디렉터리에 package.json이 없음 → npm init 필요 2. 빠른 진단 체크리스트 아래 순서대로 점검하면 원인을 빠르게 좁힐 수 있다. (환경에 따라 명령 프롬프트/파워쉘의 출력은 다를 수 있다.)\n오류에 표시된 경로가 실제로 있는지 확인 — C:\\Users\\RT\\AppData\\Roaming\\ 디렉터리 안에 npm 폴더가 존재하는지 확인한다. 없는 게 원인 대부분이다. npm 전역 prefix 경로 확인 — 아래 명령으로 npm이 전역 설치 폴더로 어디를 쓰는지 확인한다. 1 npm config get prefix 실패한 명령 재실행 — 폴더를 만들기 전후로 npm install \u0026lt;패키지\u0026gt;를 다시 실행해 오류가 사라지는지 확인한다. 관리자 권한 실행 의심 여부 점검 — 일부 시스템 폴더 접근이 필요한 경우 권한 문제로 오류가 재발할 수 있으므로, 필요 시 명령 프롬프트를 관리자로 실행한다. 출처 문서(공식 Troubleshooting 페이지)에서도 이 오류의 해결책으로 해당 경로에 npm 폴더를 직접 만드는 방법을 안내하고 있다.\n3. 원인 후보 분석 원인 후보 매트릭스 원인 후보 확인 명령/방법 맞는 경우의 증상 해결 방향 전역 npm prefix 폴더가 없음 C:\\Users\\사용자\\AppData\\Roaming\\npm 폴더 존재 확인 ENOENT, stat '...\\Roaming\\npm' 오류와 함께 패키지 설치 실패 해당 경로에 npm 폴더를 수동 생성 사용자 프로필(AppData) 경로가 손상·삭제됨 env:USERPROFILE, %APPDATA% 값과 실제 폴더 유무 확인 동일 경로에서 반복적인 ENOENT npm 전역 prefix를 손상되지 않은 경로로 재설정 관리자 권한 부족으로 폴더 접근 거부 일반 사용자로 실행 시 접근 불가 확인 같은 명령이라도 실행 계정에 따라 성공/실패가 다름 관리자 권한으로 실행하거나 사용자 쓰기 가능 경로 사용 흔한 원인 시나리오 Node.js 설치 프로그램이 npm의 전역 폴더(AppData\\Roaming\\npm)를 의도대로 만들지 않은 경우. 원본 질문 사례가 이 경우에 해당한다. 기존에 존재하던 npm 폴더가 임의로 삭제되거나 일부 보안 프로그램·클리너에 의해 제거된 경우. 4. 코드 해결책 방법 A — 오류 경로에 npm 폴더 수동 생성 (채택된 해결책) 오류 메시지가 가리키는 경로(C:\\Users\\사용자\\AppData\\Roaming\\npm)에 이름이 npm인 폴더를 만든다. 해당 경로는 사용자 폴더 안에 있으므로 일반적으로 관리자 권한 없이 생성할 수 있다. 파일 탐색기에서 직접 만들거나, 명령 프롬프트에서 아래처럼 실행한다. (환경에 따라 경로의 사용자명은 다를 수 있다.)\n1 mkdir \u0026#34;C:\\Users\\RT\\AppData\\Roaming\\npm\u0026#34; 이후 실패했던 명령을 다시 실행한다.\n1 npm install jquery 폴더가 생성된 상태에서는 ENOENT, stat '...\\Roaming\\npm' 오류가 더 이상 발생하지 않고 설치가 진행된다.\n방법 B — npm 전역 prefix 경로 재확인 만약 방법 A로도 재발하거나, prefix가 다른 경로를 가리키고 있다면 현재 npm이 쓰는 전역 폴더를 확인해 그 경로가 실제 존재하는지 점검한다.\n1 2 npm config get prefix npm root -g npm root -g로 출력된 경로의 폴더가 없으면 그 경로에 폴더를 만들어 동일 원인을 방지한다.\n잘못된 해결책 (권장하지 않음) %APPDATA% 전체를 임의로 삭제하거나 내용을 정리하는 방법은, 다른 애플리케이션 데이터까지 지울 위험이 커 매우 위험하다. ENOENT 오류 해결을 위해 전체 AppData를 건드리는 것은 피해야 한다. 오류만 잠시 숨기는 방식으로 코드를 무작정 수정하는 것은 근본 원인(폴더 부재)을 해결하지 못하므로, 같은 오류가 다른 패키지 설치에서 반복될 수 있다. 5. 검증 명령 해결이 정상적으로 반영되었는지 다음 순서로 확인한다.\n1 2 3 npm install \u0026lt;패키지명\u0026gt; REM 실패했던 명령이 오류 없이 완료되는지 확인 npm root -g REM 전역 경로가 실제 존재하며 출력되는지 확인 node -v \u0026amp;\u0026amp; npm -v REM node·npm 바이너리가 정상인지 확인 npm install \u0026lt;패키지명\u0026gt;이 ENOENT 없이 성공하면 해결이다. npm root -g가 출력하는 경로에 npm 폴더가 실제 존재하는지도 함께 확인해 재발을 막는다. 위 명령이 정상 완료되면 해결로 간주하되, 기업 보안 정책·제한된 CI 환경 등에서는 별도 프록시·인증 설정이 추가로 필요할 수 있으므로 환경에 따라 결과가 다를 수 있다.\n6. 향후 예방 조치 재발 방지 체크 설치 직후 전역 폴더 존재 여부 확인: Node.js 재설치나 환경 변경 후엔 npm root -g가 가리키는 폴더가 존재하는지 한 번 점검한다. %APPDATA% 디렉터리를 대상으로 하는 디스크 정리·클리너 실행 시 주의: AppData\\Roaming\\npm이 삭제되지 않도록 백업하거나 예외로 설정한다. prefix 경로 고정(선택): 사용자 프로필이 자주 초기화되는 환경이라면 npm 전역 prefix를 사용자 쓰기 가능한 별도 경로로 지정해 두면 재발을 줄일 수 있다. 1 npm config set prefix \u0026#34;D:\\npm-global\u0026#34; (이는 일반적인 점검 기준이며, 경로 지정 시 이전 prefix 폴더 내용을 새 경로로 옮겨야 할 수 있다.) 실전 적용 체크 로컬: 실패 명령(npm install ...) 재실행으로 즉시 검증한다. CI/배포 환경: 스크립트 시작 시 npm root -g 경로 폴더가 있는지 보장하거나, CI 캐시 정책에 따라 전역 폴더가 초기화되지 않도록 조정한다. 프로덕션/서버: 전역 패키지를 쓰는 도구(run나 자동화 스크립트) 실행 전에 동일한 ENOENT가 재현되지 않는지 확인한다. DevTrace verdict: 이 문제의 핵심은 npm 패키지나 node 자체가 없다는 것이 아니라, npm이 전역 패키지 설치 위치로 사용하는 AppData\\Roaming\\npm 폴더가 파일시스템에 존재하지 않아 발생하는 ENOENT 오류이며, 해당 경로에 npm 폴더를 수동 생성하는 것만으로 해결된다.\n출처: StackOverflow — Node.js/Windows error: ENOENT, stat \u0026lsquo;C:\\Users\\RT\\AppData\\Roaming\\npm\u0026rsquo;\n","permalink":"https://tokkabi.com/posts/nodejs-windows-error-enoent-stat-appdata-roaming-npm/","summary":"1. 문제 정의 Windows에서는 Node.js 설치 후 npm install \u0026lt;패키지\u0026gt; 명령을 실행할 때 다음과 같은 메시지가 나오며 설치가 실패하는 경우가 있다. 1 Error: ENOENT, stat \u0026#39;C:\\Users\\RT\\AppData\\Roaming\\npm\u0026#39; E","title":"Node.js/Windows ENOENT stat AppData\\Roaming\\npm 오류 — npm 폴더 생성으로 해결하기"},{"content":"React에서 \u0026lsquo;Minified error occurred\u0026rsquo; — NODE_ENV development로 전체 에러를 확인하는 방법 1. 문제 정의 React 애플리케이션을 브라우저에서 실행하다 런타임 예외가 발생하면, 콘솔에 다음 단축 에러만 출력될 때가 있다.\n1 Uncaught Error: Minified exception occurred; use the non-minified dev environment for the full error message and additional useful warnings. 이 메시지는 무엇이 잘못됐는지를 전혀 알려주지 않는다. 실제 예외 원인은 난독화(minified)된 프로덕션 번들 때문에 숨겨져 있고, 화면에 보이는 것은 \u0026ldquo;낭비적인\u0026rdquo; 안내 문구뿐이다.\n이 글은 이 에러가 왜 나타나는지, 그리고 전체 에러 메시지를 확인하는 방법을 번들러/빌드 도구별로 정리한다.\n출처: StackOverflow 29586928 — React - Minified error occurred (질문 45점 / 답변 12개 / 조회수 약 10만 회) 2. 빠른 진단 체크리스트 당신의 상황이 아래 \u0026lsquo;체크\u0026rsquo;에 해당하면 해당 절로 이동한다.\n# 체크 항목 확인 방법 해당하면 → 1 NODE_ENV가 production이나 배포 설정으로 빌드되고 있는가 echo $NODE_ENV / 번들러 설정 파일 3-1, 4 2 react.min.js(또는 production.min.js)를 스크립트 태그로 직접 링크했는가 HTML \u0026lt;script\u0026gt; src 확인 3-2, 4 3 번들러가 DefinePlugin/define으로 NODE_ENV를 고정 주입하는가 webpack.config / vite.config 3-3, 5 4 리액트가 마운트될 DOM 요소가 존재하는가 document.getElementById('...')의 id 일치 여부 3-4 5 스크립트가 그리려는 #root div보다 먼저 로드되는가 HTML에서 script 위치 3-5 6 render()가 undefined를 반환하거나, 정의되지 않은 변수를 참조하는가 코드 검토 3-6 7 React / 도구 버전이 오래된가 node -v, package.json 3-7, 6 3. 왜 발생하는가 (근본 원인 분석) 3-1. 프로덕션 번들이 난독화되어 메시지가 숨겨졌다 React는 개발용(react.development.js)과 프로덕션용(react.production.min.js) 두 가지 번들을 제공한다. 프로덕션 번들은 용량을 줄이기 위해 에러 메시지까지 난독화하여 길이를 줄여두고, 실제 예외 원인은 미리 정의된 코드/문구로 치환한다. 그래서 런타임 예외가 나면 \u0026ldquo;Minified error occurred\u0026rdquo; 같은 일반적인 문구만 보이고, 정작 원인을 판별하기 어렵다.\n빌드 도구(browserify, webpack, vite 등)는 process.env.NODE_ENV 값에 따라 어느 쪽 React(bundle)를 선택한다. NODE_ENV=development로 빌드하면 개발 번들이 사용되므로 난독화되지 않은 전체 에러 메시지를 볼 수 있다.\n3-2. root cause 후보 매트릭스 원인 후보 확인 명령/지표 이 경우의 증상 해결 방향 A. NODE_ENV=production(browserify) echo $NODE_ENV, 초기화 파일 콘솔에 단가 에러만 표시 set NODE_ENV=development 후 다시 번들 B. react.min.js 직접 링크 HTML \u0026lt;script src=...min.js\u0026gt; 어떤 명령도 효과 없음 개발(development)버전 링크로 교체 C. webpack DefinePlugin 고정값 DefinePlugin 설정 확인 테스트/Karma에서만 발생 NODE_ENV: JSON.stringify('development') 주입 D. jspm 0.16.24가 프로덕션 import package.json jspm 버전 초기 실행부터 오류 downgrade 0.16.23 / 프로덕션 명시 E. DOM id 불일치/미마운트 getElementById 인자 확인 렌더 초기 단계 오류 id 하나로 통일 F. 마운트 전 script 로드 HTML 구조 확인 로드 순서 문제 script를 #root 아래로 이동 G. render()가 undefined/미참조 변수 코드 검토 특정 조건에서만 발생 조건부 render 반환값 가드 4. 해결 방법 4-1. browserify — NODE_ENV=development 설정 원 채택 답변과 본 글 문제의 직접 상황(browserify + require('react'))에 해당한다. 패키지 스크립트나 명령줄에서 NODE_ENV를 development로 설정한 뒤 번들한다.\n1 2 3 4 5 # Windows (cmd) set NODE_ENV=development # macOS / Linux / bash export NODE_ENV=development 1 2 3 4 5 6 // package.json 스크립트 권장 { \u0026#34;scripts\u0026#34;: { \u0026#34;dev\u0026#34;: \u0026#34;NODE_ENV=development browserify -t [ babelify ] app.js -o bundle.js\u0026#34; } } 이 상태로 발생한 예외는 난독화되지 않은 실제 원인(예: undefined 변수, 잘못된 id)을 콘솔에 보여준다.\n3.3 대표 빌드 도구별 설정 webpack + Karma (테스트) — DefinePlugin으로 주입:\n1 2 3 4 5 6 7 8 9 10 11 12 // webpack.config.js const webpack = require(\u0026#39;webpack\u0026#39;); module.exports = { plugins: [ new webpack.DefinePlugin({ \u0026#39;process.env\u0026#39;: { NODE_ENV: JSON.stringify(\u0026#39;development\u0026#39;) } }) ] }; vite — define 옵션으로 주입:\n1 2 3 4 5 6 7 8 // vite.config.js import dotenv from \u0026#39;dotenv/config\u0026#39;; export default { define: { \u0026#34;process.env.NODE_ENV\u0026#34;: JSON.stringify(process.env.NODE_ENV) } }; 3.4. 실제로 마주치는 원인들 (실제 경험 사례) 이 에러는 하나의 원인만 있는 게 아니다. 아래도 같은 메시지를 유발한다.\nreact.min.js(프로덕션 버전)를 직접 링크 — HTML에서 react.min.js 또는 production.min.js를 쓰고 있다면 개발용 전체 파일로 교체한다. 마운트 요소가 없음 / id 불일치 — ReactDOM.render(...)이 참조하는 id가 HTML에 없는 경우. render()가 undefined 반환 — 조건문 경계에서 변수에 값이 할당되지 않아 render(){ return view; }의 view가 undefined인 경우 등. 스크립트가 #root div보다 먼저 로드돼 null에 렌더링 — bundle.js를 \u0026lt;div id=\u0026quot;root\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; 뒤로 옮긴다. 5. 검증 명령 해결 후 예외가 더 이상 \u0026lsquo;Minified\u0026rsquo;로 숨겨지지 않는지, 그리고 실제 원인이 드러나는지 확인한다.\n1 2 3 4 5 6 7 8 9 # 1) NODE_ENV가 development인지 확인 (build 디버그 환경) echo $NODE_ENV # 2) 번들 안에 개발용 React가 들어갔는지 확인 # (development 번들은 \u0026#39;production.min.js\u0026#39; 파일명이 아니라 \u0026#39;react.development.js\u0026#39; 참조) grep -rn \u0026#34;react.development\\|react.production.min\u0026#34; bundle.js | head # 3) 노드에서 React runtime이 예외를 그대로 전달하는지 node -e \u0026#34;process.env.NODE_ENV=\u0026#39;development\u0026#39;; console.log(process.env.NODE_ENV)\u0026#34; 실제 전체 메시지가 화면에 나타나면 검증 완료다.\nReact 15.2 이상부터는 프로덕션 빌드에서도 오류와 함께 https://reactjs.org/errors/... 형태의 URL을 함께 출력한다. 이 URL을 브라우저에서 열면 원문(비화화) 에러 메시지를 열람할 수 있으므로, 프로덕션 크래시 로깅에서도 유용하다. (참고: Dan Abramov 공지)\n6. 향후 예방 조치 (실전 적용 체크) 환경 확인할 점 조치 로컬 개발 NODE_ENV=development로 번들 dev 스크립트에 NODE_ENV 명시 CI 테스트 러너 웹번들의 NODE_ENV DefinePlugin으로 development 주입 프로덕션 개발 번들을 릴리스에 노출하지 않을 것 프로덕션용 min 번들은 별도, 에러 URL 보고 사용 배포 시 환경변수와 번들러 NODE_ENV가 항상 일치하는지 package.json 스크립트로 고정해 둔다. \u0026ldquo;일반적인 점검 기준\u0026rdquo; 부분은 환경(번들러·React 버전)에 따라 상이할 수 있다. DevTrace verdict: 이 문제의 핵심은 React 코드 자체가 틀렸다는 신호가 아니라 번들 환경이 프로덕션(압축) 버전을 골랐기 때문에 원인 메시지가 숨겨지는 것이며, NODE_ENV를 development로 바꾸면 즉시 실제 원인이 노출된다.\n출처: https://stackoverflow.com/questions/29586928/react-minified-exception-occurred\n","permalink":"https://tokkabi.com/posts/react-minified-exception-occurred/","summary":"React에서 \u0026lsquo;Minified error occurred\u0026rsquo; — NODE_ENV development로 전체 에러를 확인하는 방법 1. 문제 정의 React 애플리케이션을 브라우저에서 실행하다 런타임 예외가 발생하면, 콘","title":"React 'Minified exception occurred' 에러 — NODE_ENV=development로 전체 에러 메시지 확인하기"},{"content":"Angular \u0026ldquo;expression has changed after it was checked\u0026rdquo; 예외 — 현재 시간에 의존하는 속성의 근본 원인과 해결책 1. 문제 정의 Angular 2.x 개발 템플릿 모드에서, 컴포넌트 속성이 현재 datetime에 의존할 때 다음과 같은 예외가 발생합니다.\n1 Expression has changed after it was checked 이 예외는 Angular가 템플릿 바인딩의 일관성을 검증하는 동안, 검사 시점과 실제 바인딩 시점 사이에 표현식의 값이 달라졌을 때 던집니다. 프로덕션 빌드에서는 잘 드러나지 않고, 개발 모드(비 프로덕션 빌드)에서 더 쉽게 관찰됩니다.\n원본 질문의 코드를 보면 dateNow 프로퍼티가 컴포넌트의 시각 관련 스타일(글꼴 색상) 계산에 쓰이고, 이 값이 계속 바뀔 수 있습니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 색상 계산 함수 — 현재 시간에 따라 색상이 달라짐 private fontColor( dto : Dto ) : string { // dto의 최근 실행 시각 let dtoDate : Date = new Date( dto.LastExecution ); (...) let color = \u0026#34;hsl( \u0026#34; + hue + \u0026#34;, 80%, \u0026#34; + (maxLigness - lightnessAmp) + \u0026#34;%)\u0026#34;; return color; } // 뷰 체크 훅에서 현재 시각을 갱신 ngAfterViewChecked() { console.log( \u0026#34;! changement de la date du composant !\u0026#34; ); this.dateNow = new Date(); } ngAfterViewChecked() 훅에서 this.dateNow = new Date()로 컴포넌트 속성을 바꾸었기 때문에, 템플릿이 이 표현식을 이미 검사한 뒤 값이 변해 예외가 발생한 것입니다.\n2. 표면 증상 구분 증상 판단 포인트 발생 시점 개발 모드 런타임, 뷰 검사 단계 Angular 런타임 검증기가 동작 중 예외 코드 Expression has changed after it was checked 바인딩 값을 표현식에서 읽는 동안 값이 변경됨 관련 함수 ngAfterViewChecked 뷰 초기화 후 실행되는 훅 — 여기서 부작용을 만듦 진짜 원인 현재 datetime처럼 계속 변하는 표현식이 템플릿에 바인딩됨 값이 매 순간 변해 검사·바인딩 시점이 어긋남 증상 fingerprint\n컴포넌트 속성(또는 뷰 모델 값)이 new Date(), 타임스탬프 계산처럼 외부에서 계속 바뀌는 표현식에 의존한다. 그 값을 컴포넌트의 어떤 훅(라이프사이클 후반 단계) 안에서 덮어쓴다. 개발 모드 빌드에서만 자주 나타나 배포 환경에서는 재현이 잘 안 된다. 이 조건이 하나라도 해당되면, 이 예외가 어느 컴포넌트에서 발생하는지 우선 점검해 둘 가치가 있습니다.\n3. 원인 탐구 Angular의 기본 변경 감지(Change Detection)는 템플릿 표현식의 값을 한 번 판단하고, 그 값이 유효한 동안에는 재평가하지 않도록 동작합니다. 그런데 그 표현식이 순수하지 않고 계속 변하는 값에 의존하고 있다면:\nAngular가 템플릿 표현식 this.dateNow를 먼저 검사한다. 같은 렌더링 사이클 안에서, 개발자 코드가 this.dateNow = new Date() 같은 방식으로 그 값을 바꾼다. Angular가 검사 당시 기억한 값과 현재 실제 값을 비교하다가 값이 달라졌음을 감지하고 예외를 던진다. 질문자가 겪은 시나리오는 전형적인 \u0026ldquo;검사 이후 값이 반영되는 시점의 어긋남\u0026rdquo; 패턴입니다.\n4. 근본 원인 분석 Expression has changed after it was checked의 본질은 \u0026ldquo;값을 바꿨다는 사실 자체\u0026quot;가 아니라, 바꾼 것이 Angular 변경 감지 시스템에 통지되지 않았다는 점입니다.\nAngular 템플릿 엔진은 미리 검사한 바인딩 값을 두고 다시 재평가하지 않는 가정으로 동작합니다. 그런데 개발자 코드가 Angular의 통지 메커니즘을 거치지 않고 직접 속성을 변경하면, 이후 렌더링 시점에 Angular는 \u0026ldquo;아까 검증한 값이 이제 맞지 않음\u0026quot;을 인지하게 됩니다.\n즉 근본 원인은 \u0026ldquo;값의 변경을 Angular가 다시 평가하도록 언제 알려야 하는가\u0026quot;라는 시점 문제입니다. 값을 바꾸는 것 자체가 잘못된 것이 아니라, 정합성이 유지되도록 Angular에게 명시적으로 변경을 통지(detectChanges)하는 것이 해결의 방향입니다.\n5. 코드 해결책 5.1 ChangeDetectorRef 주입 + detectChanges() 호출 @angular/core의 ChangeDetectorRef를 생성자에 주입하고, 값을 바꾸는 훅(여기서는 ngAfterViewChecked)에서 변경 감지를 명시적으로 호출합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { ChangeDetectorRef } from \u0026#39;@angular/core\u0026#39;; export class MyComponent { // 값이 계속 변하는 속성 dateNow: Date; constructor(private cdRef: ChangeDetectorRef) { } ngAfterViewChecked() { console.log( \u0026#34;! valeur modifiée de la date dans la composante !\u0026#34; ); this.dateNow = new Date(); // ★ 핵심: 변경 감지를 명시적으로 재실행 this.cdRef.detectChanges(); } } detectChanges() 호출은 Angular에게 \u0026ldquo;표현식 값이 다시 변동됐으니 재평가하고 바인딩에 반영하라\u0026quot;고 알려줍니다. 이로써 검사된 표현식이 어긋나는 상태가 해소되어 예외로 끝나지 않습니다.\n원리 요약 시점 동작 ngAfterViewChecked() 현재 시각 갱신 + detectChanges() 호출 Angular 변경 감지 detectChanges() 신호로 표현식 재평가 이후 렌더링 새 값이 바인딩되어 예외 없음 일반적인 점검 기준: 이 패턴은 Angular 데이터 개발 템플릿에서 \u0026ldquo;값을 바꾼 직후 반드시 변경을 통지하자\u0026quot;는 원칙의 일부입니다. 환경에 따라 다를 수 있으므로, 직접 속성을 바꾸는 로직에는 반드시 명시적인 변경 감지 통지가 따라와야 합니다.\n5. 향후 예방 조치 — 같은 예외를 피하는 방법 동적 값에 의존하는 표현식은 라이프사이클 훅에서 직접 바꾸지 않는다 — ngAfterViewChecked 같은 후훅에서 값을 덮어쓰면 검사 시점과 충돌하기 쉽다. 값을 변경한다면 반드시 detectChanges()를 같은 흐름 안에서 호출한다 — 여러 곳에서 소비된다면 변경 직후 명시적으로 갱신한다. 부재 재현(Side Effect)이 있는 표현식을 템플릿에 직접 쓰기보다 컴포넌트 필드로 사전 계산 — dateNow처럼 값을 한 번 확정해 참조하면 변경 감지와 충돌을 줄인다. 개발 모드 검증 후 프로덕션 빌드에서도 확인 — 개발 모드에서 발생한 예외는 프로덕션 런타임에서 재현이 어려울 수 있어, 배포 전후로 반복 확인한다. 6. 검증 명령 코드 변경 후 예외가 사라졌는지 아래 순서대로 확인합니다.\n1 2 3 4 5 6 7 8 # 1) 컴포넌트/템플릿 관련 프로젝트 테스트 실행 npm test # 2) 프로젝트 빌드로 타입/문법 오류 확인 npm build # 3) 개발 서버를 실행해 해당 페이지·컴포넌트 동작 확인 npm serve 환경에 따라 다를 수 있습니다. Angular 프로젝트가 npm 기반이 아닌 다른 빌드 방식을 쓴다면 해당 도구의 검증 명령으로 대신하면 됩니다. 핵심 판단은 변경 후 페이지를 새로 고침하거나 컴포넌트를 다시 그릴 때 예외가 재현되지 않는지입니다.\n7. DevTrace 결론 이 문제의 핵심은 \u0026ldquo;값을 바꾸는 행위\u0026quot;가 아니라, \u0026ldquo;Angular 변경 감지 시스템에 값을 바꿨다는 사실을 통지하지 않은 것\u0026rdquo; 입니다. ChangeDetectorRef.detectChanges() 호출로 표현식 재평가를 명시적으로 수행해야 \u0026ldquo;expression has changed after it was checked\u0026rdquo; 예외를 근본적으로 해소할 수 있습니다.\n출처: StackOverflow — How to manage Angular2 \u0026ldquo;expression has changed after it was checked\u0026rdquo; exception when a component property depends on current datetime\n","permalink":"https://tokkabi.com/posts/angular-expression-has-changed-after-it-was-checked/","summary":"Angular \u0026ldquo;expression has changed after it was checked\u0026rdquo; 예외 — 현재 시간에 의존하는 속성의 근본 원인과 해결책 1. 문제 정의 Angular 2.x 개발 템플릿 모드에서, 컴포넌트 속성이 현재 datetime에 의존할 때","title":"Angular 'expression has changed after it was checked' 예외 — 현재 시간에 의존하는 속성과 변경 감지의 정확한 원인"},{"content":"1. 문제 정의 Node.js에서 crypto-js 패키지로 AES 암호화한 데이터를 Go에서 복호화하려고 하면, 값이 깨지거나 panic이 발생한다. 아래는 문제를 일으킨 Node.js 암호화 코드다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const crypto = require(\u0026#39;crypto-js\u0026#39;); const cryptKey = \u0026#39;b676eac8cf70442385dfd4bcfaa61b52\u0026#39;; // 32자리 문자열 const createRandomIv = function () { const keySize = 192 / 32; // 6 const ivSize = 128 / 32; // 4 const evp = crypto.algo.EvpKDF.create({ keySize: keySize + ivSize, hasher: crypto.algo.SHA1 }).compute(cryptKey); const iv = crypto.lib.WordArray.create(evp.words.slice(keySize), ivSize * 4); return iv.toString(); }; const encryptPassword = function (password) { const iv = createRandomIv(); const hash = crypto.AES.encrypt(password, cryptKey, { iv, mode: crypto.mode.CTR }); const base64 = crypto.enc.Base64.parse(hash.toString()); const eHex = base64.toString(crypto.enc.Hex); return `${iv}:${eHex}`; }; 출력 예:\n1 2db5c01aa26fa576e5e24d5e9d3c1e3a:53616c7465645f5f65e90f4b6f2f7444ebc4540ce7424efadc59f34e5f16f2d19a52d0c9ec9b9a1660a6af9a1c 그리고 아래처럼 문자열 key를 그대로 aes.NewCipher에 넘겨 CBC로 복호화한 Go 코드가 panic 및 깨진 값을 만든다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import ( \u0026#34;crypto/aes\u0026#34; \u0026#34;crypto/cipher\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) func main() { secretKey := \u0026#34;b676eac8cf70442385dfd4bcfaa61b52\u0026#34; encryptedPwd := \u0026#34;2db5c01b4825b6d4d7a7b96f04f3bb5:53616c7465645f5ff691671363cda1b9d05ee6bdd637e1e99bc3b29ef2ad7ec53\u0026#34; split := strings.Split(encryptedPwd, \u0026#34;:\u0026#34;) c, _ := aes.NewCipher([]byte(secretKey)) cfbdec := cipher.NewCBCDecrypter(c, []byte(split[0])) plaintext := make([]byte, len(split[1])) cfbdec.CryptBlocks(plaintext, []byte(split[1])) fmt.Println(plaintext) } 2. 원인 탐구 — 표면 증상을 나누어 본다 처음 보기에는 Go 코드가 키를 잘못 쓰는 것 같다. 실제로 맞는 관찰이다. 하지만 원인은 Go 코드 한 줄 문제가 아니라, Node.js 쪽 CryptoJS의 문자열 키 처리 방식에 숨어 있다. 관찰할 수 있는 증상을 정리하자.\n표면 증상 관찰 지점 즉시 드는 가설 실제 요인 Go 복호화 결과가 깨진 바이트 fmt.Println(plaintext) IV가 틀렸다 파생된 키/IV와 다르다는 전제를 Go가 재현하지 못함 Go가 panic 발생 aes.NewCipher 에 16바이트가 아닌 길이의 key key 길이 불일치 문자열 32자를 그대로 raw key로 해석하면 길이 규약이 깨짐, CBC/CTR 블록 규약 위반 Node에서 전달된 iv가 복호화에 무시되는 느낌 복호화 실패 IV 불일치 실제로 CryptoJS는 그 IV를 완전히 무시한다 3. 근본 원인 분석 — CryptoJS의 문자열은 key가 아니라 passphrase CryptoJS의 crypto.AES.encrypt(message, key_or_passphrase, cfg) 에서 두 번째 인자가 문자열이면 그 값은 키(raw key)가 아니라 패스프레이즈(passphrase) 로 해석된다.\n그 순간 다음이 벌어진다.\n암호화할 때 8바이트 임의 salt 를 생성한다. 이 salt와 passphrase로 KDF EVP_BytesToKey() 를 통해 키와 IV를 파생한다. 코드에서 명시적으로 넘긴 createRandomIv()의 IV는 무시된다. (문제 코드에서 iv를 만들고 넘겼지만, 문자열 key를 썼으니 그 IV는 쓰이지 않는다.) hash.toString()의 결과는 OpenSSL 포맷 — 접두어 Salted__ + salt + 실제 ciphertext (Base64). eHex는 이를 Hex로 인코딩한 것에 불과하다. 스트림 모드 CTR이라도 CryptoJS는 자동으로 padding을 끄지 않는다. 따라서 PKCS#7 패딩이 붙어 있다. Go 쪽이 문제인 것은 분명하지만, 정확히는 \u0026ldquo;Go가 이 EVP_BytesToKey 기반 파생 파이프라인을 동일하게 재현하지 못한 것\u0026quot;이 근본 원인이다.\n검증 요지 (답변 원문 중심): \u0026ldquo;In the CryptoJS code, the second parameter in crypto.AES.encrypt() is passed as a string, so it is interpreted as passphrase. … The IV derived with createRandomIv() and explicitly passed in crypto.AES.encrypt() is ignored! … hash.ToString() returns the result in OpenSSL format consisting of the prefix Salted__ followed by the salt and by the actual ciphertext.\u0026rdquo;\n(참고) 문자 key가 아닌 WordArray를 넘겼다고 가정하자 만약 두 번째 인자로 WordArray(실제 key)를 넘겼다면, CryptoJS는 이를 raw key로 해석해 salt·EVP_BytesToKey 파이프라인을 거치지 않는다. 그 경우에는 salt/Salted__ 접두어가 생기지 않아 단순한 key/IV 정합으로 복호화가 된다. 그렇기 때문에 Node 쪽 코드가 \u0026ldquo;문자열을 쓰고 있다\u0026quot;는 사실 자체가 첫 관찰점이다.\n4. 코드 해결책 — Go에서 OpenSSL 파이프라인 복원 아래 Go 코드는 답변에서 제안한 순서대로 동작한다.\n:로 분리해 split[1] (Hex ciphertext)을 바이트로 디코딩한다. saltCiphertext[8:16]에서 salt를, [16:]에서 실제 ciphertext를 얻는다. (Saltd 접두어가 8바이트이고 다음 8바이트가 salt다) github.com/walkert/go-evp의 evp.BytesToKeyAES256CBCMD5(salt, password) 로 키/IV를 재생산한다. (참고로 이 함수는 이름이 \u0026ldquo;AES256CBC\u0026quot;이지만, 반환되는 32바이트 key + 16바이트 IV를 CTR 모드에도 동일하게 사용할 수 있다.) AES-CTR로 복호화하고, PKCS7Unpad로 패딩을 제거한다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import ( \u0026#34;crypto/aes\u0026#34; \u0026#34;crypto/cipher\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;github.com/walkert/go-evp\u0026#34; ) func main() { // 1) NodeJS 코드가 만들어낸 값 (iv:ciphertext) encryptedPwd := \u0026#34;AE2c5a9c1d2f3a4b5c6d7e8f90abcdef12:53616c7465645ffff691671363cda1b9d05ee6bdc719e99bc3b29ef2ad7ec53\u0026#34; split := strings.Split(encryptedPwd, \u0026#34;:\u0026#34;) saltCiphertext, _ := hex.DecodeString(split[1]) // 2) salt(8바이트)와 실제 ciphertext 분리 salt := saltCiphertext[8:16] ciphertext := saltCiphertext[16:] // 3) salt + passphrase로 KDF 재현 key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(\u0026#34;b676aec8cf70442386fd851bcfaa61b52\u0026#34;)) // 4) AES-CTR 복호화 block, _ := aes.NewCipher(key) plaintext := make([]byte, len(ciphertext)) stream := cipher.NewCTR(block, iv) stream.XORKeyStream(plaintext, ciphertext) // 5) PKCS#7 패딩 제거 unpadded := PKCS7Unpad(plaintext) fmt.Println(\u0026#34;Decrypted: \u0026#34;, string(unpadded)) // Decrypted: The quick brown fox jumps over the lazy dog } func PKCS7Unpad(src []byte) []byte { length := len(src) unpadding := int(src[length-1]) return src[:(length - unpadding)] } 일반적인 특징: 위 코드에서 key, iv := evp.BytesToKeyAES256CBCMD5([]byte(salt), []byte(\u0026quot;...\u0026quot;)) 의 동일한 파생 로직이 핵심이다. salt가 먼저 넘어가지 않는다면 키/IV가 하나도 일치하지 않아 cipher.NewCTR에서부터 값이 깨진다.\n5. 검증 명령 및 확인 기준 검증은 단순하다. 복호화된 평문이 다시 원래 문자열과 일치하는지 확인하는 것.\n1 2 3 4 5 6 7 # Go 코드 실행 (패키지에 의존성 추가 후) go mod tidy go run main.go # 기대 출력: Decrypted: The quick brown fox jumps over the lazy dog # Node 쪽도 같은 평문을 암호화했는지 대조 node -e \u0026#34;const C=require(\u0026#39;crypto-js\u0026#39;); console.log(C.AES.encrypt(\u0026#39;Stack Overflow\u0026#39;,\u0026#39;SECRET\u0026#39;).toString());\u0026#34; Go에서 복호화가 되지 않고 plaintext가 깨지거나, XORKeyStream 후 길이가 잘못되면:\nsalt 분리 위치가 잘못되지 않았는지 ([8:16], [16:]) 확인 문자열 key를 그대로 aes.NewCipher에 넘기지 않았는지 확인 padding 제거 함수가 항상 마지막 바이트를 신뢰하는지 확인한다. (가드 로직 부재 시 잘못된 패딩 바이트이면 인덱스 음수로 panic 가능) 6. 더 안전한 설계로 가는 방법 (장기 해결) 답변에서 강조하는 보안 관점도 인식하자.\nEVP_BytesToKey() 는 오늘날 안전하지 않은 것으로 간주된다. password→key 단방향 파생 알고리즘의 안전성 목적에 부합하지 않는다. 권장 대안: 두 번째 문자열 대신 WordArray(정규 key) 를 넘겨서 CryptoJS가 raw key로 처리하도록 하고, 매 암호화마다 임의 IV를 사용한다. 필요하면 PBKDF2 같은 신뢰성 있는 KDF로 salt를 파생한다. CTR보다 GCM을 권장한다. ciphertext의 무결성(authenticity) 검증이 가능해지기 때문이다. GCM이 생성되는 tag를 키·IV·ciphertext와 함께 저장/운반하는 구조로 설계한다. 즉, 장기적으로는 \u0026ldquo;재사용 가능한 동일 복호화 계약\u0026quot;을 설계하고 Node 쪽과 Go 쪽이 같은 KDF(pbkdf2)와 인증 암호화(GCM)를 쓰도록 하는 것이 올바른 가성이다. 이 글의 코드는 기존에 생산된 데이터를 그대로 복호화하는 \u0026ldquo;레거시 호환\u0026rdquo; 해결책이다.\n증상 fingerprint (확인용 정리) 에러 상황: Node.js CryptoJS로 암호화 → Go에서 AES 복호화 → panic / 깨진 문자열 발생 단계: ciphertext를 Go aes.NewCipher에 직접 넘긴 뒤 CryptBlocks/XORKeyStream 시점 관련 도구: Node.js crypto-js, Go crypto/aes, github.com/wonp/go-evp 흔한 오해: 문자열 key를 그대로 raw key로 쓰고 별도 IV를 만들어 주면 된다(사실 아님. 문자열은 passphrase가 되어 직접 IV는 무시, EVP 계약 필요) 빠른 판단 기준: Node.js crypto.AES.encrypt(pass, stringKey, {iv, mode:CTR}) 형태라면 반드시 salt 기반 EVP_BytesToKey 파이프라인이 개입한다. 원인 후보 매트릭스 원인 후보 확인 명령 부합하는 증상 해결 방향 Go에서 raw key 직접 사용 aes.NewCipher([]byte(\u0026quot;32자 문자열\u0026quot;)) NewCipher가 길이·블록 규율 위반으로 오류, panic KDF로 유도한 key 사용 CBC로 복호화 cipher.NewCBCCDecrypter 패딩/블록 크기 오류, 끝부분 깨짐 CTR 모드 + PKCS7 unpad salt 무시 saltCiphertext[16:] 미분리 ciphertext 시작이 Salted__ 8바이트 오염 salt분리 후 BytesToKey 암호화 시 무시된 IV를 당연히 씀 복호화에 split[0] IV 사용 값이 통째로 어긋남 IV를 별도로 신뢰하지 말고 KDF의 IV 사용 DevTrace Verdict 이 문제의 핵심은 \u0026ldquo;Go가 AES key를 잘못 썼다\u0026quot;는 겉보기 증상이지, 실제로는 CryptoJS 문자열 인자 → passphrase → EVP_BytesToKey → Saltd__+salt+ciphertext 가 온전한 암호문 규약인데 Go쪽이 그 KDF 파이프라인을 재현을 못한 데 있다. 문자열이 아닌 WordArray key나 PBKDF2+GCM으로 바꾸면 이런 양쪽 규약 불일치 자체가 사라진다.\n출처\nStackOverflow 원본 질문: Decrypt AES with Secret Key and IV From Node to Golang Panic 채택 답변 (저자 답변 기반 정리, 보안 대안 포함) 일반적인 점검 기준 및 검증 예시는 원본 출처 근거에 더해 환경(Go 버전, github.com/walkert/go-evp 라이브러리, Node/CryptoJS 버전)에 따라 다를 수 있으니 별도로 명시한 부분이다. 암호화 KDF 관련 결정은 항상 보안 플랫폼 권고를 우선하라.\n","permalink":"https://tokkabi.com/posts/decrypt-aes-node-to-golang-evp-bytestokey/","summary":"1. 문제 정의 Node.js에서 crypto-js 패키지로 AES 암호화한 데이터를 Go에서 복호화하려고 하면, 값이 깨지거나 panic이 발생한다. 아래는 문제를 일으킨 Node.js 암호","title":"Node.js CryptoJS AES 암호화를 Go에서 복호화할 때 panic이 나는 이유 — 문자열 키는 passphrase로 처리된다"},{"content":"1단계: 문제 정의 Node.js에서 mongodb npm 패키지로 MongoDB에 연결하려 할 때 다음과 같은 traceback이 발생하는 상황입니다.\n에러 로그 (원문)\n1 2 3 4 5 MongoError: Server at localhost:27017 reports wire version 0, but this version of Node.js Driver requires at least 2 (MongoDB2.6). at /home/.../node_modules/mongodb-core/lib/topologies/server.js:377:39 at /home/.../node_modules/mongodb-core/lib/connection/pool.js:541:18 at _combinedTickCallback (internal/process/next_tick.js:73:7) at process._tickCallback (internal/process/next_tick.js:104:9) 재현 코드 (원문)\n1 2 3 4 5 6 7 8 9 10 11 12 13 var mongo = require(\u0026#39;mongodb\u0026#39;); var MongoClient = mongo.MongoClient; var DB_NAME = \u0026#39;demodb\u0026#39;; var url = \u0026#34;mongodb://localhost:27017/\u0026#34; + DB_NAME; MongoClient.connect(url, function(err, db) { if (err) { console.log(\u0026#39;Error in creating DB \u0026#39; + DB_NAME); throw err; } console.log(\u0026#34;Database \u0026#34; + DB_NAME + \u0026#34; created successfully!\u0026#34;); db.close(); }); 오류 메시지는 두 개의 숫자가 언급됩니다.\nServer ... reports wire version 0 — MongoDB 서버가 응답한 wire protocol 버전(0) Driver requires at least 2 (MongoDB2.6) — mongodb 드라이버가 요구하는 최소 wire protocol 버전(2, 즉 MongoDB 2.6부터 지원) 서버가 보고한 값보다 드라이버가 요구하는 값이 더 크므로, 연결 대상 MongoDB 서버가 이 드라이버 버전에 비해 너무 오래된 것입니다.\n2단계: 원인 탐구 이 오류는 코드 로직 문제가 아닌 버전 호환성 문제입니다. MongoDB는 클라이언트(드라이버)와 서버 사이의 통신 규약을 wire protocol이라는 버전 번호로 협상합니다.\nMongoDB 서버는 응답(handshake)에서 자신이 지원하는 wire version을 알려줍니다. 드라이버는 자신이 요구하는 최소 wire version을 알고 있습니다. 서버가 보고하는 wire version이 드라이버의 최소 요구치보다 낮으면 연결이 거부됩니다. 이 원문 사례에서 연결 대상은 mongodb://localhost:27017/의 로컬 MongoDB 서버입니다. 서버가 wire version 0을 보고한 만큼, 대체로 MongoDB 2.6 이전 또는 매우 오래된 서버 버전이 로컬에 떠 있다는 뜻으로 해석할 수 있습니다.\n3단계: 근본 원인 분석 핵심은 npm으로 최신 mongodb 드라이버를 설치했지만, 실제 MongoDB 서버 버전이 그 드라이버가 요구하는 버전에 미치지 못한다는 점입니다.\nwire protocol 버전과 MongoDB 서버 버전의 대응 관계는 대략 다음과 같습니다. (일반적인 점검 기준이며 환경에 따라 다를 수 있습니다.)\nMongoDB 서버 버전 지원 wire protocol (대략) 비고 MongoDB 2.4 이하 wire version 0 드라이버 요구치(2) 미달 → 이 오류 발생 MongoDB 2.6 wire version 1 최신 드라이버와 협상이 어려울 수 있음 MongoDB 3.0 wire version 2 드라이버 최소 요구치 충족 MongoDB 3.2 이후 wire version 3+ 대부분의 현대 드라이버 호환 원인 후보 확인 명령 맞는 경우의 증상 해결 방향 MongoDB 서버가 드라이버 요구 버전보다 오래됨 mongod --version 서버가 wire version 0을 보고, 드라이버는 2를 요구 서버 업그레이드 또는 드라이버 다운그레이드 mongodb 드라이버 버전이 서버에 비해 너무 높음 npm list mongodb 최신 드라이버인데 오래된 서버에 연결 시도 호환 드라이버 버전으로 변경 서버가 정상적으로 handshake하지 못하는 프록시/터널 mongosh --host localhost 직접 접속 직접 접속은 되지만 드라이버에서만 오류 네트워크 경로 점검 증상 fingerprint 요약: 이 오류가 뜨면 대부분 서버 버전보다 드라이버 버전이 앞서 있습니다. 드라이버 자체를 최신으로 올리기보다 \u0026ldquo;현재 접속하는 MongoDB 서버 버전 + Node 버전\u0026quot;에 맞는 드라이버 버전을 찾는 것이 빠른 판단 기준입니다.\n4단계: 코드 해결책 임시 해결책 1 — 호환 가능한 드라이버 버전 설치 (드라이버 다운그레이드) 원문 답변 작성자는 mongodb-version-list 모듈을 이용해 현재 Node 환경에서 사용 가능한 mongodb 드라이버 버전 목록을 조회한 뒤, 서버와 호환되는 버전을 골라 설치했습니다.\n1 2 3 4 5 6 # 현재 Node 환경에서 사용 가능한 mongodb 드라이버 버전 목록 확인 node -e \u0026#34; require(\u0026#39;mongodb-version-list\u0026#39;)().then(list =\u0026gt; { console.log(list.join(\u0026#39;\\n\u0026#39;)); }).catch(console.error); \u0026#34; 목록에서 서버 버전과 맞는 드라이버를 골라 설치합니다 (예시, --save-exact로 버전 고정 권장):\n1 2 3 npm install mongodb@2 --save-exact # 또는 npm install mongodb@\u0026lt;호환 버전\u0026gt; --save-exact 설치 후 연결을 다시 시도하면 wire version 협상이 성공할 수 있습니다.\n임시/권장 해결책 2 — MongoDB 서버 업그레이드 서버 자체가 오래된 경우(예: MongoDB 2.4 이하) 드라이버를 추측 식으로 낮추기보다, MongoDB 서버를 드라이버가 요구하는 버전 이상으로 업그레이드하는 것이 장기적으로 맞는 방법입니다. 최신 드라이버에서 제공하는 기능과 보안 패치를 함께 쓸 수 있기 때문입니다.\n1 2 # 서버 버전 확인 mongod --version 버전이 MongoDB 2.6 미만이라면 서버 업그레이드를 검토하세요. 프로덕션에서는 업그레이드 전 데이터 백업과 다운타임 계획이 필요합니다.\n5단계: 향후 예방 조치 검증 명령 드라이버를 변경한 뒤 연결이 정상적으로 되는지 아래 명령으로 확인합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 # 1) 설치된 드라이버 버전 확인 npm list mongodb # 2) MongoDB 서버 버전 확인 mongod --version # 3) 실제 연결 테스트 (에러가 없어야 정상) node -e \u0026#34;require(\u0026#39;mongodb\u0026#39;).MongoClient .connect(\u0026#39;mongodb://localhost:27017/demodb\u0026#39;, function(err, db) { if (err) { throw err; } console.log(\u0026#39;DB connection OK\u0026#39;); db.close(); })\u0026#34; 재발 방지 체크 버전 고정: package.json에서 mongodb 버전을 --save-exact로 고정하고, 호환성을 확인한 뒤에만 수동 업그레이드합니다. 서버 버전 사전 확인: 배포/로컬 환경 모두에서 mongod --version으로 서버 버전을 기록해 두면 드라이버 선택 기준이 명확합니다. 의존성 충돌 방지: 프로젝트에 드라이버를 루트에만 설치하고, 서로 다른 버전이 중복으로 설치되지 않게 npm ls mongodb로 트리를 점검합니다. 일반적인 점검 기준: 원본 답변은 드라이버 버전 다운그레이드에 초점을 둡니다. \u0026ldquo;드라이버가 요구하는 버전 vs 서버가 보고하는 버전\u0026quot;을 항상 먼저 비교하세요. 출처 원본 질문/답변: StackOverflow — node.js and mongodb and traceback saying Driver requires at least 2 (MongoDB2.6) 참고 모듈 정보: mongodb-version-list (원문 답변에서 사용) DevTrace verdict 이 문제의 핵심은 MongoDB 서버가 부족해서가 아니라, 접속 대상 MongoDB 서버의 wire protocol 버전(wire version 0)이 mongodb 드라이버가 요구하는 최소 수준(2, MongoDB 2.6)보다 낮은 데 있으며, 드라이버를 다운그레이드하거나 MongoDB 서버를 업그레이드해 양쪽 버전의 스펙을 맞추면 해결됩니다.\n","permalink":"https://tokkabi.com/posts/nodejs-mongodb-driver-requires-at-least-2-wire-version/","summary":"1단계: 문제 정의 Node.js에서 mongodb npm 패키지로 MongoDB에 연결하려 할 때 다음과 같은 traceback이 발생하는 상황입니다. 에러 로그 (원문)","title":"Node.js MongoDB 드라이버 'requires at least 2 (MongoDB2.6)' wire version 오류 — 드라이버·서버 버전 호환성 문제 해결"},{"content":"Express에서 \u0026lsquo;Can\u0026rsquo;t set headers after they are sent to the client\u0026rsquo; 오류 해결하기 1. 문제 정의 Node.js + Express 로 Facebook 인증처럼 콜백이 여러 번 이어지는 라우트를 구현하다 보면 다음과 같은 에러를 만납니다.\n1 2 3 4 5 Error: Can\u0026#39;t render headers after they are sent to the client. at ServerResponse.\u0026lt;anonymous\u0026gt; (http.js:573:11) at ServerResponse._renderHeaders (.../connect/lib/patch.js:64:25) at ServerResponse.writeHead (http.js:813:20) at .../connect-auth/lib/auth.strategies/facebook.js:28:15 즉, 이미 응답이 클라이언트에게 나간 뒤에 헤더를 다시 설정하려고 시도해서 생기는 런타임 오류입니다. 이 StackOverflow 질문은 1240점으로 매우 널리 알려진 Express 오류입니다.\n2. 원인 탐구 Express의 res 객체는 Node.js의 http.ServerResponse를 상속합니다. 서버가 응답을 구성하는 상태 흐름은 다음과 같습니다.\nHead — res.setHeader(), res.statusCode 설정 가능 Body — res.write(data)로 본문 전송 시작 Finished — res.end() 후 종료 res.setHeader()는 res.writeHead()를 호출하기 전까지는 몇 번이든 호출할 수 있지만, writeHead 이후에는 헤더가 확정되므로 본문을 쓰거나 end 한 뒤에는 상태나 헤더를 더 이상 바꿀 수 없습니다.\n3. 근본 원인 분석 에러 메시지가 나타나는 근본 원인은 **\u0026lsquo;Body 또는 Finished 상태에서 어떤 함수가 헤더/statusCode를 다시 설정하려는 것\u0026rsquo;**입니다.\n대표적인 트리거:\n중복 응답: 어떤 경로에서 res.send()/res.end()가 두 번 호출. redirect 후 추가 처리: 원 질문의 경우 res.redirect()로 응답이 Finished가 된 다음, 이후 코드에서 예외(res.req가 null)가 발생. Connect가 그 예외를 잡아 500 페이지를 다시 전송하려 하는데, 헤더가 이미 나갔으므로 setHeader가 실패. 즉, 단순히 \u0026ldquo;한 줄 고치기\u0026rdquo; 보다 응답이 정확히 한 번만 완료되도록 라우트를 분석해야 합니다.\n4. 코드 해결책 반드시 지켜야 할 규칙 1 2 3 4 5 // ✅ 헤더를 먼저 설정 res.status(200); res.setHeader(\u0026#39;Content-Type\u0026#39;, \u0026#39;application/json\u0026#39;); // 그 다음 본문 전송 res.json({ ok: true }); 1 2 3 // ❌ 본문 이후 헤더 재설정 → 에러 발생 res.send(\u0026#39;hello\u0026#39;); res.setHeader(\u0026#39;Content-Type\u0026#39;, \u0026#39;application/json\u0026#39;); // Can\u0026#39;t set headers... 중복 응답 제거 — 가드를 추가 중복 콜백 호출로 두 번 응답이 나가는 것을 방지하려면 \u0026lsquo;한 번만 응답한다\u0026rsquo;는 가드를 두십시오.\n1 2 3 4 5 6 let responded = false; function done(payload) { if (responded) return; // 이미 응답했으면 무시 responded = true; res.json(payload); } redirect 후 코드 경로 정리 1 2 3 4 5 if (!req.user) { return res.redirect(\u0026#39;/noauth\u0026#39;); // return으로 이후 실행 차단 } // 여기까지 왔다면 재귀/추가 응답 없이 단일 흐름 유지 res.json({ ok: true }); return을 쓰면 redirect 후에 아래 코드라 실행되는 것을 막아 \u0026lsquo;redirect + 추가 응답\u0026rsquo; 조합을 피할 수 있습니다.\n5. 향후 예방 조치 모든 라우터에서 응답 함수는 정확히 한 번, 그리고 마지막에 호출하도록 규약화하십시오. 비동기 콜백이 두 번 실행될 수 있는 로직(중복 조건, next() 혼용)은 flag 가드로 방어하십시오. res.setHeader/res.writeHead는 본문 작성 전에만 사용하고, 상태는 route 초반에 결정하십시오. 미들웨어 체인에서 res 종료 후 다시 라우팅되는 흐름이 없는지 점검하십시오. 출처: StackOverflow — Error: Can\u0026rsquo;t set headers after they are sent to the client\n","permalink":"https://tokkabi.com/posts/error-cant-set-headers-after-they-are-sent/","summary":"Express에서 \u0026lsquo;Can\u0026rsquo;t set headers after they are sent to the client\u0026rsquo; 오류 해결하기 1. 문제 정의 Node.js + Express 로 Facebook 인증처럼 콜백이 여러 번 이어지는 라우트를 구현하다 보면 다음과 같은 에러를 만납니","title":"Express에서 'Can't set headers after they are sent' 오류 해결하기"},{"content":"Node.js uncaughtException 처리: 처리되지 않은 예외로 프로세스가 종료되는 문제 1. 문제 정의 Node.js를 처음 사용하다 보면 프로그램에서 **처리되지 않은 예외(unhandled exception)**가 발생하는 순간 Node 프로세스 전체가 종료되는 것을 발견하게 됩니다.\n1 2 3 4 5 // 어떤 라우트/콜백에서 던져진 예외 if (conditionFailed) { throw new Error(\u0026#34;something went wrong\u0026#34;); } // -\u0026gt; 프로세스가 그대로 crash 일반 웹 서버 컨테이너라면 Worker Thread만 죽고 컨테이너는 계속 요청을 받지만, Node.js에서는 프로세스가 통째로 죽습니다. 이때 자연히 다음과 같은 질문이 생깁니다.\nprocess.on('uncaughtException')이 유일한 방어 수단인가? 이 핸들러를 쓰는 것이 안전한가? 2. 원인 탐구 Node.js의 동작 모델을 이해하면 프로세스 종료의 이유가 명확해집니다.\nNode.js는 **단일 이벤트 루프(single event loop)**를 가진 프로세스입니다. 사전에 예외를 잡아둔 핸들러가 없다면, 예외는 이벤트 루프의 맨 위까지 전파되고 결국 실행 환경이 더 이상 안전하게 실행을 계속할 수 없다고 판단합니다. 따라서 Node는 충돌을 방지하기 위해 프로세스를 종료합니다. 원 질문의 스코어가 831점으로 매우 높을 만큼, 이 패턴에 직면한 개발자가 많은 공통 이슈입니다. 3. 근본 원인 분석 핵심 원인은 **\u0026lsquo;예외가 전파되어 이벤트 루프를 핸들러 없는 상태로 통과했기 때문\u0026rsquo;**입니다.\nprocess.on('uncaughtException')는 존재하지만, 공식 문서에서는 이를 만병통치로 쓰지 말 것을 경고합니다. 핸들러는 \u0026ldquo;마지막 순간에 상태를 정리하고 종료하거나, 안전하게 종료 절차를 밟기 위한\u0026rdquo; 용도입니다. 진짜 해결책은 예외를 구조적으로 미리 잡는 것으로, 비동기 콜백의 err 첫 번째 인자 규약과 Promise 기반 에러 전파를 사용하는 방식입니다. Joyent의 공식 가이드(StackOverflow 채택 답변 요약)는 \u0026ldquo;safely throwing errors\u0026quot;라는 개념을 제시합니다.\n4. 코드 해결책 안전하게 \u0026lsquo;던지기\u0026rsquo; — 동기 코드 동기 함수에서 오류가 나면, throw 대신 오류를 반환하는 방식입니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 동기 함수에서 오류를 throw 대신 return으로 반환 var divideSync = function (x, y) { if (y === 0) { // 오류 조건이면 Error 객체를 반환 (throw 아님) return new Error(\u0026#34;Can\u0026#39;t divide by zero\u0026#34;); } return x / y; }; var result = divideSync(4, 2); // 2 if (result instanceof Error) { console.log(\u0026#34;4/2=err\u0026#34;, result); } else { console.log(\u0026#34;4/2=\u0026#34;, result); } result = divideSync(4, 0); // Error if (result instanceof Error) { console.log(\u0026#34;4/0=err\u0026#34;, result); } 콜백 기반 코드 — err-first 규약 1 2 3 4 5 6 7 8 9 10 11 var divide = function (x, y, next) { if (y === 0) { // 콜백의 첫 번째 인자가 err return next(new Error(\u0026#34;Can\u0026#39;t divide by zero\u0026#34;)); } next(null, x / y); }; divide(4, 2, function (err, result) { if (err) return console.error(err); // err 처리 console.log(\u0026#34;4/2=\u0026#34;, result); }); 이벤트 이미터 / Promise로 내보내기 에러를 emit('error')로 내보내거나 Promise reject로 처리해도, 안전하게 전파되고 uncaughtException까지 새지 않습니다.\n5. 향후 예방 조치 process.on('uncaughtException')는 절대 \u0026lsquo;모든 예외 무시\u0026rsquo;로 쓰지 마십시오. 안전한 사용법은 로그를 남기고 프로세스를 깨끗이 종료하는 것입니다. 모든 IO는 try/catch 또는 .catch()를 통해 실패 경로를 명시하십시오. worker처럼 격리된 프로세스/Worker Thread에서 취약한 작업을 수행하면, 실패해도 메인이 종료되지 않게 만들 수 있습니다. 예외를 함수의 실패 반환으로 변환하는 \u0026ldquo;safely throwing\u0026rdquo; 패턴을 코드베이스 규약으로 정하십시오. 출처: StackOverflow — Node.js Handling Uncaught Exceptions\n","permalink":"https://tokkabi.com/posts/nodejs-handling-uncaught-exceptions/","summary":"Node.js uncaughtException 처리: 처리되지 않은 예외로 프로세스가 종료되는 문제 1. 문제 정의 Node.js를 처음 사용하다 보면 프로그램에서 **처리되지 않은 예외(unhandl","title":"Node.js uncaughtException 처리 방법 — 처리되지 않은 예외로 프로세스가 종료되는 문제"},{"content":"Node.js: node-sass/node-gyp 빌드 시 \u0026lsquo;Python not found\u0026rsquo; 오류 해결하기 1. 문제 정의 Jenkins CI 환경에서 Node.js 프로젝트를 빌드할 때 갑자기 빌드가 실패하는 경우가 있습니다. 로컬 머신에서는 정상 동작하지만, 빌드 서버에서만 반복되는 다음과 같은 에러를 만나게 됩니다.\n1 2 3 4 5 6 gyp verb check python checking for Python executable \u0026#34;python2\u0026#34; in the PATH gyp verb `which` failed Error: not found: python2 gyp verb `which` failed at getNotFoundError (...\\node_modules\\which\\which.js:13:12) gyp verb `which` failed at F (...\\node_modules\\which\\which.js:68:19) gyp verb `which` failed at E (...\\node_modules\\which\\which.js:80:29) gyp verb `which` failed at C:\\...\\node_modules\\which\\which.js:89:16 이 문제는 StackOverflow에 475점으로 채택된 아주 흔한 빌드 오류입니다.\n2. 원인 탐구 이 에러의 근본 원인을 이해하려면 의존성 체인을 봐야 합니다.\nnode-sass는 sass 컴파일러인 LibSass를 바이너리로 사용합니다. 특정 버전(여기서는 node-sass v3.8.0)은 내부적으로 node-gyp v3.5.0을 사용해 네이티브 모듈을 빌드합니다. node-gyp는 소스에서 네이티브 모듈을 컴파일할 때 빌드 도구(Python, C++ 컴파일러, MSBuild 등)를 요구합니다. 문헌에서 node-gyp의 전제 조건(prerequisite)은 Python이 설치되어 있어야 한다는 것이 명시적으로 확인됩니다. 문제의 사용자는 Jenkins 서버에 Python이 없어 PATH에서 python2를 찾지 못해 which 명령이 실패한 것입니다.\n3. 근본 원인 분석 핵심 원인은 **\u0026rsquo;node-gyp가 빌드 시 요구하는 Python 의존성의 부재\u0026rsquo;**입니다.\n원 질문의 로그에는 checking for Python executable \u0026quot;python2\u0026quot; in the PATH가 실패하는 것이 명확히 보입니다. node-sass는 인기 있는 순수 자바스크립트 모듈이지만, 설치 시 플랫폼용 사전 빌드(prebuilt) 바이너리를 자동 다운로드하려고 합니다. 그것이 실패하면 소스로 빌드하게 되고, 그 시점에 Python이 필요한 겁니다. 따라서 이 오류가 떴다는 것은 \u0026ldquo;설치 서버가 사전 빌드 바이너리를 받지 못했고, 네이티브 빌드에 필요한 Python/빌드 체인도 없다\u0026quot;는 두 가지가 겹쳐 발생합니다. 로컬에 Python이 있다면 정상이라는 점이 해결 방향을 가리킵니다. 4. 코드 해결책 옵션 A — node-sass를 호환 버전으로 업데이트 StackOverflow 채택 답변에서는 다음을 권장합니다.\n1 2 # 현재 node-sass v3.8.0 → 호환되는 최신 버전으로 npm install node-sass@4.5.3 --save-dev node-sass 3.8.0은 Node 5를 지원합니다. Node 5는 이미 수명이 끝났으므로 버전을 맞추며, 만약 Node 8로 올리고 싶다면 에서 node-sass@4.5.3이 필요합니다. 옵션 B — Node.js를 업그레이드 1 2 3 # 이미 Node 6 이상을 쓴다면 node-sass 최신 버전과 호환 nvm install 6 nvm use 6 Node 6 이상에서 최신 node-sass가 더 이상 레거시 node-gyp를 사용하지 않아 Python 2 의존성을 덜 요구합니다.\n옵션 C — 빌드 환경에 Python 준비 Jenkins(빌드 서버)에 아래와 같은 전제를 갖춥니다.\nPython 2 설치 (node-gyp v3.5 요구) 윈도우라면 Visual Studio(C++ 빌드 도구) 또는 MSBuild 필요 이후 npm rebuild node-sass로 네이티브 모듈 재빌드 1 2 3 # (예시, 빌드 서버에서) npm install npm rebuild node-sass 5. 향후 예방 조치 레거시 node-sass 대신 최신 빌드 체인으로 마이그레이션하면 더욱 안정적입니다. 현대 프로젝트에서는 sass(Dart Sass) 호환 래퍼로 전환하거나 CI 이미지에 Python 빌드 체인을 사전에 포함합니다. 빌드 서버 Docker 이미지에 python2, 빌드 도구를 미리 넣고 CI 파일에 명시적으로 설치하십시오. npm ci(잠금 파일 기반)를 쓰면 설치 중 바이너리 재다운로드 오류를 초기 발견할 수 있습니다. 출처: StackOverflow — Node.js: Python not found exception due to node-sass and node-gyp\n","permalink":"https://tokkabi.com/posts/nodejs-python-not-found-exception-due-to-node-sass-and-node-gyp/","summary":"Node.js: node-sass/node-gyp 빌드 시 \u0026lsquo;Python not found\u0026rsquo; 오류 해결하기 1. 문제 정의 Jenkins CI 환경에서 Node.js 프로젝트를 빌드할 때 갑자기 빌드가 실패하는 경우가 있습니다. 로컬 머신에서는 정상 동작하지만, 빌드","title":"Node.js: node-sass/node-gyp 빌드 시 'Python not found' 오류 해결하기"},{"content":"PhoneGap/Cordova Android 앱을 강제로 크래시하게 만드는 방법 1. 문제 정의 크래시 리포터(Crash Reporter) 플러그인을 개발 중인데, 테스트를 위해 앱을 의도적으로 크래시시키고 \u0026ldquo;Unfortunately, application has stopped\u0026rdquo; 창(앱 중단 다이얼로그)을 띄우고 싶은 상황입니다.\n그런데 자바스크립트에서 의도적으로 unhandled exception을 발생시켜도 앱이 크래시하지 않습니다. 오히려 같은 화면이 그대로 유지됩니다.\n1 2 3 4 5 // JS에서 예외를 던져도 앱이 크래시하지 않는다 function causeCrash() { throw new Error(\u0026#34;crash test\u0026#34;); } causeCrash(); 심지어 자바스크립트에서 1시간 동안 무한 루프를 돌려도 앱은 크래시하지 않고 그대로 멈춰 있었습니다.\n핵심 증상 정리 시도 결과 JS에서 throw new Error() 발생 앱이 크래시하지 않음, 같은 화면 유지 JS에서 무한 루프 실행 (1시간) 앱이 크래시하지 않음, 응답 불능 상태만 네이티브 Java에서 10 / 0 예외 발생 ??? (방법에 따라 크래시됨) 2. 원인 탐구 왜 자바스크립트 예외로는 앱이 크래시하지 않을까요? 이는 Cordova의 아키텍처 때문입니다.\nPhoneGap/Cordova 앱은 자바스크립트와 네이티브 코드(Java)가 WebView를 사이에 두고 통신합니다. 자바스크립트에서 예외가 발생해도, 이 예외는 자바스크립트 런타임(WebView 엔진) 레벨에서 소비되며 네이티브 코드(Java) 계층으로 전파되지 않습니다.\n즉, 자바스크립트의 unhandled exception은 네이티브 안드로이드 런타임의 \u0026ldquo;uncaught exception\u0026quot;으로 변환되지 않으므로, 안드로이드 프로세스가 죽지 않는 것입니다. 앱이 크래시하려면 네이티브(Java) 레벨에서 처리되지 않은 예외가 발생해야 합니다.\n3. 근본 원인 분석 문제의 본질은 다음 두 가지입니다.\n첫째, 자바스크립트 예외는 네이티브 계층에 도달하지 못합니다. 크래시 리포트 플러그인이 잡으려는 \u0026ldquo;애플리케이션 크래시\u0026quot;는 네이티브 uncaught exception으로 정의됩니다. JS 계층의 예외는 이것과 별개의 사건입니다.\n둘째, Cordova 내부에서 예외를 삼키는 catch 블록이 존재합니다. 네이티브 플러그인 내부에서 예외를 던져도, Cordova 프레임워크가 이를 감싸고 있는 catch 계층이 예외를 처리해버리기 때문에 앱이 크래시하지 않습니다. org.apache.cordova 패키지 안의 다음 두 클래스가 핵심입니다.\nPluginManager 클래스의 execHelper 메서드의 catch (Exception e) {} 블록 ExposedJsApi 클래스의 exec 메서드의 catch (Throwable e) {} 블록 이 catch 블록들이 예외를 받아 콘솔 로그만 남기고 삼켜버리므로, 부모 계층까지 exception이 전파되지 않아 크래시로 이어지지 않습니다.\n4. 코드 해결책 크래시를 유도하는 방법은 두 가지가 있습니다. 목적(메뉴 버튼을 누를 때 vs 자바스크립트 호출로)에 따라 선택합니다.\n방법 A: 메뉴 버튼 누를 때 크래시 (CordovaActivity 수정) android/src/\u0026lt;package\u0026gt;/CordovaActivity.java 의 onCreateOptionsMenu 를 다음과 같이 수정합니다.\n1 2 3 4 5 @Override public boolean onCreateOptionsMenu(Menu menu) { this.postMessage(\u0026#34;onCreateOptionsMenu\u0026#34;, menu); throw new RuntimeException(); // 메뉴 버튼을 누르면 강제 크래시 } 그런 다음 앱에서 메뉴 버튼을 누르면 RuntimeException 이 발생하여 앱이 크래시하고 \u0026ldquo;앱이 중단되었습니다\u0026rdquo;(Unfortunately, application has stopped) 창이 표시됩니다. 크래시 리포트 플러그인 테스트에 가장 간단한 방법입니다.\n방법 B: 자바스크립트 호출로 크래시 (네이티브 플러그인에서 예외 던지기) 자바스크립트 호출로 진짜 크래시를 일으키려면 네이티브 플러그인을 만들어 그 execute 메서드 안에서 예외를 던지면 됩니다.\n플러그인 생성 참고 문서: http://docs.phonegap.com/en/3.0.0/guide_platforms_android_plugin.md.html\n1 2 3 4 5 6 public class CrashPlugin extends CordovaPlugin { @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { throw new RuntimeException(\u0026#34;crash from plugin\u0026#34;); // 여기서 예외 발생 } } 1 2 3 4 5 public static void generateCrash() { int a = 0; int b = 10; int c = b / a; // ArithmeticException 발생 } 다만, 위처럼 네이티브 플러그인 내부에서 예외를 던져도, 앞서 설명한 Cordova 내부 catch 계층이 예외를 삼켜버립니다(콘솔에 크래시 로그만 남고 앱은 계속 동작). 따라서 다음의 catch 블록을 제거해야 예외가 부모로 전파되어 크래시가 됩니다.\n1. PluginManager 클래스의 execHelper 메서드에서 catch 블록 제거\n1 2 // class org.apache.cordova.PluginManager // remove catch (Exception e) {} 블록 2. ExposedJsApi 클래스의 exec 메서드에서 catch 블록 제거\n1 2 // class org.apache.cordova.ExposedJsApi // remove catch (Throwable e) {} 블록 이 변경을 적용하면 자바스크립트로부터 플러그인을 호출했을 때 예외가 삼켜지지 않고 네이티브 런타임까지 전파되어 앱이 크래시하게 됩니다.\n방법별 비교 구분 동작 방식 사용 시점 방법 A (onCreateOptionsMenu) 메뉴 버튼 누를 때 RuntimeException 수동 조작으로 크래시 재현이 필요한 경우 방법 B (네이티브 플러그인) JS 호출 → 플러그인 execute → 예외 전파 자바스크립트 트리거로 크래시가 필요한 자동 테스트 5. 향후 예방 조치 크래시 기반 테스트와 크래시 리포트 플러그인 개발 시 다음을 참고합니다.\nJS 예외로는 절대 네이티브 크래시를 만들 수 없다는 점을 명심합니다. 크래시 테스트는 반드시 네이티브(Java) 레벨의 uncaught exception이 필요합니다. Cordova 내부 catch 계층 (PluginManager.execHelper, ExposedJsApi.exec) 의 존재를 숙지하고, 플러그인 테스트 시 예외가 삼켜지는 원인을 이 구조에서 우선 찾습니다. 테스트 목적으로만 Cordova 프레임워크 소스의 catch 블록을 제거하고, 프로덕션 빌드에서는 원본 소스로 복원하는 것을 권장합니다. 프레임워크 내부 예외 처리를 영구적으로 제거하면 다른 플러그인의 오류가 그대로 노출되어 안정성이 저하될 수 있습니다. 실제 운영 환경의 크래시 오류 분석보다는, 크래시 리포터 플러그인의 수집·전송 파이프라인을 검증할 때 이 강제 크래시 방법을 활용합니다. 출처 -원본 질문 및 해결책: How to make my phonegap android app crash? — StackOverflow\n","permalink":"https://tokkabi.com/posts/how-to-make-my-phonegap-android-app-crash/","summary":"PhoneGap/Cordova Android 앱을 강제로 크래시하게 만드는 방법 1. 문제 정의 크래시 리포터(Crash Reporter) 플러그인을 개발 중인데, 테스트를 위해 앱을 의도적으로 크래시시키고 \u0026ldquo;Unfortunately, application has stopped\u0026rdquo; 창","title":"PhoneGap/Cordova Android 앱을 강제로 크래시 시키는 방법 — JS 예외가 잡히지 않는 이유"},{"content":"문제 정의 에러 원문: How to catch exception correctly from http.request()? 출처: StackOverflow 35326689\nAngular 2의 Http 서비스를 이용해 http.request()를 호출하고, 응답을 처리하는 체인에 .catch() 연산자를 추가하려고 했지만 .catch가 함수가 아니라는 TypeError가 발생하는 상황이다. 댓글에서는 추가로 Observable.throw가 함수가 아니다는 오류도 확인되었다.\n즉 map은 정상 동작하는데 catch만 실패한다. 이 때문에 .catch(this.handleError) 한 줄만 주석 처리하면 코드가 정상 동작한다.\n에러 로그 / 문제 코드 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import {Injectable} from \u0026#39;angular2/core\u0026#39;; import {Http, Headers, Request, Response} from \u0026#39;angular2/http\u0026#39;; import {Observable} from \u0026#39;rxjs/Observable\u0026#39;; import \u0026#39;rxjs/add/operator/map\u0026#39;; @Injectable() export class myClass { constructor(protected http: Http) {} public myMethod() { let request = new Request({ method: \u0026#34;GET\u0026#34;, url: \u0026#34;http://my_url\u0026#34; }); return this.http.request(request) .map(res =\u0026gt; res.json()) .catch(this.handleError); // Trouble line. // Without this line code works perfectly. } public handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || \u0026#39;Server error\u0026#39;); } } 발생 오류 문맥 TypeError: ... .catch is not a function .catch() 연산자를 사용한 라인 EXCEPTION: TypeError: Observable_1.Observable.throw is not a function handleError 안에서 Observable.throw() 호출 map이 잘 동작하는데도 catch만 안 되는 점이 이 문제의 특징이다.\n원인 탐구 map은 잘 동작하는데 catch만 실패한다는 사실이 가장 큰 단서다. 코드를 보면:\n1 import \u0026#39;rxjs/add/operator/map\u0026#39;; // map은 임포트했음 위처럼 map 연산자만 명시적으로 임포트되어 있다. 반면 catch는 어디에도 임포트되지 않았다.\n이 현상은 RxJS 5의 \u0026ldquo;패치(patch) 방식 연산자\u0026rdquo; 설계에서 비롯된다.\n원인 1 — RxJS 5는 연산자를 자동으로 불러오지 않는다 RxJS 5(rxjs@5.x)에서는 기본 Observable 임포트만으로 모든 연산자를 사용할 수 없다. 각 연산자는 몽키패치(prototype 확장) 방식으로 Observable에 추가되며, 사용자가 원하는 연산자만 경로로 불러와야 한다.\nObservable.prototype에 map은 추가됐지만 catch는 추가되지 않았기 때문에, 런타임에 .catch()를 호출하면 Observable 프로토타입에 해당 메서드가 존재하지 않아 catch is not a function이 발생한다.\n원인 2 — 팩토리 함수도 마찬가지 Observable.throw()는 연산자가 아니라 Observable의 정적 팩토리(static factory) 메서드다. 이것도 마찬가지로 별도 임포트가 필요하다. 임포트하지 않으면 Observable.throw is not a function이 발생한다.\n(참고: 이후 RxJS 5.5+ 에서는 pipeable operator 방식으로, RxJS 6+ 에서는 catchError, throwError라는 이름으로 바뀌고 패치 임포트가 사라졌다. 이 글은 질문 시점인 RxJS 5 기반 해결책이다.)\n근본 원인 분석 근본 원인은 RxJS 5의 연산자 로딩 방식이다. 요약하면:\nAngular 2(아직 rxjs@5.x였던 시기)에서 import { Observable } from 'rxjs/Observable'는 Observable 클래스 그 자체만 가져온다. map, catch 같은 연산자는 항상 기본 포함되지 않는다. → 표현식이 길고 흔히 쓰는 연산자(map)는 꼭 임포트해야 쓸 수 있고, → 같은 이유로 catch도 임포트하지 않으면 Observable.prototype에 존재하지 않는다. Observable.throw 같은 정적 팩토리도 마찬가지로 별도 임포트가 필요하다. 즉, \u0026ldquo;러브스 map만 되고 catch는 왜 안 되지?\u0026ldquo;라는 혼란이 생기지만, 사실 둘 다 원칙적으로 같은 규칙을 따른다. map을 임포트했을 뿐 catch와 throw를 임포트하지 않은 것이 전부다.\n필요한 심볼 타입 임포트 경로 Observable (기본) 클래스 import { Observable } from 'rxjs/Observable' map 연산자 (prototype) import 'rxjs/add/operator/map' catch 연산자 (prototype) import 'rxjs/add/operator/catch' throw 정적 팩토리 import 'rxjs/add/observable/throw' 코드 해결책 해결책 1 — 누락된 연산자/팩토리 임포트 추가 (채택된 답변) 누락된 임포트 두 줄을 추가하면 해결된다:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import {Injectable} from \u0026#39;angular2/core\u0026#39;; import {Http, Headers, Request, Response} from \u0026#39;angular2/http\u0026#39;; import {Observable} from \u0026#39;rxjs/Observable\u0026#39;; import \u0026#39;rxjs/add/operator/map\u0026#39;; import \u0026#39;rxjs/add/operator/catch\u0026#39;; // 1) .catch() 연산자 import \u0026#39;rxjs/add/observable/throw\u0026#39;; // 2) Observable.throw() 팩토리 @Injectable() export class myClass { constructor(protected http: Http) {} public myMethod() { let request = new Request({ method: \u0026#34;GET\u0026#34;, url: \u0026#34;http://my_url\u0026#34; }); return this.http.request(request) .map(res =\u0026gt; res.json()) .catch(this.handleError); // 이제 정상 동작 } public handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || \u0026#39;Server error\u0026#39;); } } 핵심 변경:\n1 2 import \u0026#39;rxjs/add/operator/catch\u0026#39;; // .catch() 사용 가능 import \u0026#39;rxjs/add/observable/throw\u0026#39;; // Observable.throw() 사용 가능 이 두 줄만 추가하면 catch is not a function과 Observable.throw is not a function 모두 해결된다.\n해결책 2 — catch 대신 subscribe의 에러 콜백 사용 예외를 꼭 연산자로 처리할 필요가 없다면, subscribe의 에러 콜백으로도 처리할 수 있다:\n1 2 3 4 5 6 7 return this.http.request(request) .map(res =\u0026gt; res.json()) .subscribe( data =\u0026gt; console.log(data), err =\u0026gt; console.log(err), // 에러 처리 () =\u0026gt; console.log(\u0026#39;yay\u0026#39;) // 완료 ); 이 방식은 별도 연산자 임포트가 필요 없다는 장점이 있다. 다만 결과가 Subscription이 되어 외부에서 map된 값으로 계속 다루기 어려워진다.\n어느 쪽을 선택할까? 상황 권장 반환값을 옵저버블 체인으로 계속 이어갈 때 해결책 1 (연산자 임포트 + .catch()) 최종 소비만 하고 로그/간단 처리가 목적일 때 해결책 2 (subscribe 에러 콜백) 두 오류(catch is not a function, Observable.throw is not a function)를 확실히 없애려면 해결책 1의 임포트 추가가 가장 깔끔하고, 채택 답변도 이 방법을 제시한다.\n향후 예방 조치 RxJS 5 연산자는 \u0026ldquo;쓸 때마다 임포트\u0026quot;가 원칙이다.\nmap은 물론 catch, finally, retry 등 모든 연산자를 사용하기 전에 import 'rxjs/add/operator/\u0026lt;이름\u0026gt;'을 확인한다. 정적 팩토리(Observable.of, Observable.throw, Observable.from 등)도 import 'rxjs/add/observable/\u0026lt;이름\u0026gt;'이 필요하다. 오류 메시지가 곧 누락된 임포트를 가리킨다.\n... is not a function 형태의 TypeError는 대부분 해당 메서드/팩토리가 Observable에 등록되지 않았음을 뜻한다. 경로를 찾아 임포트하면 대부분 해결된다. 버전을 확인하자.\nRxJS 5: import 'rxjs/add/...' (패치 연산자/팩토리) — 이 글의 방식. RxJS 5.5+: import { catchError } from 'rxjs/operators'와 pipe() (pipeable operator)를 권장. RxJS 6+: 연산자가 catchError, 에러 생성이 throwError로 이름이 바뀌었고, Object.import가 아닌 명명된 임포트만 사용한다. 프로젝트의 package.json의 rxjs 버전을 먼저 확인하고 그 버전에 맞는 문법을 사용하는 것이 혼란을 줄인다. 관련 심볼은 함께 임포트한다.\n.catch()와 그 안에서 쓰는 Observable.throw는 함께 쓸 가능성이 높으므로 두 임포트를 한 번에 같이 추가해 두 번째 오류가 뜨는 일을 막는다. 이렇게 하면 \u0026ldquo;한 라인만 주석 처리하면 잘 돌아가는\u0026rdquo; 상태를 예방하고, 예외를 옵저버블 체인 안에서 안정적으로 처리할 수 있다.\n출처 StackOverflow — How to catch exception correctly from http.request()? (질문 id 35326689, 조회수 332,793 / 점수 161) ","permalink":"https://tokkabi.com/posts/angular-rxjs-catch-exception-from-http-request/","summary":"문제 정의 에러 원문: How to catch exception correctly from http.request()? 출처: StackOverflow 35326689 Angular 2의 Http 서비스를 이용해 http.request()를 호출하고, 응답을 처리하는 체인에 .catch() 연산자를 추가하","title":"Angular RxJS에서 http.request() 예외 올바르게 잡기 — 'catch is not a function' 오류 해결"},{"content":"문제 정의 IntelliJ IDEA 기본 React 프로젝트(CRA, webpack 4 기반)를 실행할 때 다음과 같은 에러가 발생합니다.\n1 2 3 4 5 6 Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:67:19) at Object.createHash (node:crypto:130:10) at module.exports (/Users/user/.../node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/Users/user/.../node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/Users/user/.../node_modules/webpack/lib/NormalModule.js:) 프로젝트는 정상적으로 작성했는데 빌드/시작 단계에서 해시 계산 중 오류가 나며 실행이 중단됩니다.\n원인 탐구 에러 스택을 보면 webpack/lib/util/createHash.js에서 createHash를 호출하는 순간 실패합니다. 즉 문제는 웹팩이 해시 함수를 만들 때 사용하는 Node.js의 OpenSSL 계층에서 발생합니다.\n주요 원인 요약:\n요인 설명 Node.js 버전 17 이상 (OpenSSL 3.0 내장) webpack 버전 4 (구형 해시 알고리즘 사용) 해시 알고리즘 MD4 (OpenSSL 3.0에서 기본 제공이 중단됨) react-scripts(create-react-app) 4.x가 의존하는 webpack 4는 기본적으로 MD4 해시를 사용하는데, Node.js 17부터 내장된 OpenSSL 3.0에서는 MD4가 기본 제공 프로바이더에서 빠졌기 때문에 unsupported 오류가 발생합니다.\n근본 원인 분석 근본 원인은 버전 불일치입니다.\nNode.js 17+ 는 보안 강화를 위해 OpenSSL 3.0으로 전환했습니다. OpenSSL 3.0 은 기본 프로바이더에서 MD4 등 구식 알고리즘을 제거했습니다. webpack 4 는 해시 함수로 MD4를 여전히 사용합니다. 그 결과 createHash('md4') 호출이 digital envelope routines::unsupported 로 실패합니다. 즉, 애플리케이션 코드가 잘못된 것이 아니라, 운영체제(Node.js)가 올린 보안 기준과 프레임워크(webpack 4)가 사용하는 기술이 맞지 않아 발생하는 호환성 문제입니다.\n코드 해결책 채택된 답변은 \u0026ldquo;좋은 방법 2가지\u0026quot;와 \u0026ldquo;임시방편 2가지\u0026quot;를 제시합니다.\n1) node_modules 재설치 (가장 간단한 시도) 의존성이 설치된 Node 버전에 맞춰 컴파일되는 경우, 재설치로 즉시 해결될 수 있습니다.\n1 2 rm -rf node_modules npm install 성공 확률은 가장 낮지만 아무런 부작용 없이 시도해볼 수 있는 방법입니다.\n2) 의존성 업데이트 (권장 · 근본 해결책) 거의 모든 의존성에 호환되는 최신 버전이 있습니다. Node 18이 LTS가 된 이후의 버전으로 의존성을 올리세요. react-scripts의 경우 5 이상으로 업그레이드하면 webpack 5가 적용되어 해결됩니다.\n1 2 3 npm install react-scripts@latest # 또는 npm update 1 2 3 4 5 { \u0026#34;dependencies\u0026#34;: { \u0026#34;react-scripts\u0026#34;: \u0026#34;^5.0.1\u0026#34; } } 채택 답변: \u0026ldquo;이것이 사실상 유일하게 올바른 해결책이다. 의존성도 Node.js처럼 업데이트하지 않으면 보안 취약점에 노출될 수 있다.\u0026rdquo;\n3) Node.js v16으로 다운그레이드 (임시방편) Node를 구형 OpenSSL을 사용하는 v16으로 내려도 실행은 됩니다. 단, 보안이 취약한 구버전을 쓰는 것이므로 근본 해결책이 아닙니다.\n1 2 3 # nvm 사용 nvm install 16 nvm use 16 Windows는 nvm-windows를 사용합니다.\n4) OpenSSL legacy 프로바이더 사용 (임시 우회책) Node에게 legacy OpenSSL 프로바이더를 사용하라고 알려주면 구식 해시 알고리즘을 허용합니다. 실행 환경에 따라 설정 방법이 다릅니다.\nUnix 계열 (Linux, macOS, Git bash):\n1 export NODE_OPTIONS=--openssl-legacy-provider Windows 명령 프롬프트:\n1 set NODE_OPTIONS=--openssl-legacy-provider PowerShell:\n1 $env:NODE_OPTIONS = \u0026#34;--openssl-legacy-provider\u0026#34; react-scripts 사용 시 한 번만 적용:\n1 NODE_OPTIONS=--openssl-legacy-provider npm start 주의: 채택 답변은 \u0026ldquo;Node 18이 LTS가 된 이후에는 방법 3, 4는 심각한 선택지로 간주해선 안 된다\u0026quot;고 경고합니다.\n향후 예방 조치 의존성을 정기적으로 업데이트하세요. Node.js LTS 전환(16→18→20) 시기에 오래된 프레임워크와 충돌하는 호환성 오류가 흔히 발생합니다. webpack 4 기반 프로젝트는 webpack 5(또는 react-scripts 5+)로 업그레이드하세요. webpack 5는 기본 해시 알고리즘을 안전한 것으로 교체했습니다. --openssl-legacy-provider 는 임시 우회책일 뿐입니다. 보안이 약화되므로 장기 운영 환경에서는 사용하지 마세요. Node.js 메이저 버전 업그레이드 전에 package.json 의존성의 호환성을 확인하세요. 이미 Node 18 이후 환경이라면 NODE_OPTIONS 우회보다 의존성 업데이트(방법 2)를 먼저 시도하세요. 출처: StackOverflow 69692842 - Error message \u0026ldquo;error:0308010C:digital envelope routines::unsupported\u0026rdquo;\n","permalink":"https://tokkabi.com/posts/node-17-error0308010c-digital-envelope-routines-unsupported/","summary":"문제 정의 IntelliJ IDEA 기본 React 프로젝트(CRA, webpack 4 기반)를 실행할 때 다음과 같은 에러가 발생합니다. 1 2 3 4 5 6 Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:67:19) at Object.createHash (node:crypto:130:10) at module.exports (/Users/user/.../node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/Users/user/.../node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/Users/user/.../node_modules/webpack/lib/NormalModule.js:) 프로","title":"Node.js 17+ 에서 'error:0308010C:digital envelope routines::unsupported' 오류 해결 (OpenSSL 3.0 호환 문제)"},{"content":"TypeScript \u0026rsquo;not assignable to parameter of type never\u0026rsquo; 오류 완벽 해결 — 빈 배열 타입 추론 1. 문제 정의 TypeScript 코드를 작성하다 보면 다음과 같은 의외의 컴파일 오류를 만날 수 있습니다.\n1 2 3 4 const foo = (foo: string) =\u0026gt; { const result = [] result.push(foo) // ❌ 에러 발생 지점 } 위 코드를 컴파일하면 두 번째 줄에서 아래와 같은 오류가 발생합니다.\n1 [ts] Argument of type \u0026#39;string\u0026#39; is not assignable to parameter of type \u0026#39;never\u0026#39;. result는 분명히 배열이고, push는 배열의 표준 메서드인데 왜 문자열을 넣을 수 없다는 오류가 나올까요? 겉보기에는 타입스크립트 버그처럼 느껴져서 \u0026ldquo;이건 버그 아냐?\u0026rdquo; 싶은 의문이 드는 것이 바로 이 문제의 시작점입니다.\n오류 발생 상황 요약 항목 내용 오류 메시지 Argument of type 'string' is not assignable to parameter of type 'never'. 발생 위치 빈 배열 리터럴 [] 선언 후 push() 호출 시점 발생 코드 const result = [] + result.push(foo) 관련 타입 never 타입, 배열 타입 추론 2. 원인 탐구 — 왜 이 오류가 발생하는가 오류를 이해하려면 TypeScript가 타입 주석 없이 선언된 빈 배열을 어떻게 추론하는지 알아야 합니다.\n아래 두 줄을 비교해 봅시다.\n1 2 // 상태 A: 빈 배열 리터럴만 존재 → 요소 타입을 알 수 없음 const empty = [] 이 경우 TypeScript는 empty 배열에 어떤 타입의 요소가 들어올지 전혀 알 수 없습니다. push를 호출해서 요소를 넣기 전까지는 어떤 값이라도 들어올 수 있다는 신호가 없습니다.\n그런데 TypeScript의 타입 시스템은 \u0026ldquo;무엇이든 될 수 있는\u0026rdquo; 상태를 그대로 두지 않고, bottom type(바닥 타입)인 never 를 사용하여 배열을 never[]로 추론합니다. never는 \u0026ldquo;어떤 값도 가질 수 없는 타입\u0026quot;이기 때문에, never[] 타입 배열에는 어떤 요소도 추가할 수 없습니다.\n결국 result.push(foo)에서 foo(string)를 never[] 배열의 push 파라미터(never)에 넘기려다 보니 타입 불일치 오류가 발생합니다.\n추측이 아니라 확인된 동작 이 원인은 StackOverflow의 채택 답변에서 명시적으로 확인됩니다.\n\u0026ldquo;Without defining the array type, it by default will be \u0026rsquo;never\u0026rsquo;. So when you tried to add a string to it, it was a type mismatch.\u0026rdquo;\n즉, 배열 타입을 정의하지 않으면 기본적으로 never가 되어 문자열을 추가하려는 순간 타입 불일치가 발생한다는 뜻입니다. 이는 버그가 아니라 타입 안전성을 위한 의도된 동작입니다.\n3. 근본 원인 분석 — never[]과 타입 추론 규칙 핵심을 명확히 하기 위해 타입 추론 과정을 단계별로 분해해 봅시다.\n3-1. never 타입이란 개념 설명 never 어떤 값도 할당할 수 없는 bottom type never[] 요소가 절대 존재할 수 없는 배열 표현 의미 \u0026ldquo;이 배열에는 아무것도 들어갈 수 없다\u0026rdquo; never 타입 변수에는 어떤 값도 대입할 수 없고, never 타입 배열에는 어떤 요소도 push할 수 없습니다.\n3-2. 빈 배열의 타입 추론 규칙 TypeScript는 타입 주석 없이 const result = []와 같이 빈 배열 리터럴을 만나면, 요소의 타입을 알 수 없으므로 never[]로 추론합니다. 이는 이후 result.push(foo)처럼 요소를 추가하는 코드가 있어도, 배열 변수 선언 시점의 빈 리터럴 정보를 기준으로 타입이 고정되기 때문입니다.\n3-3. 왜 타입을 명시해야 하는가 never[]로 추론된 배열은 push뿐 아니라 요소가 필요한 모든 연산에서 타입 불일치를 일으킵니다. 따라서 배열에 담길 요소 타입을 개발자가 직접 알려줘야 TypeScript가 올바르게 요소를 허용합니다.\n4. 코드 해결책 — 단계별 해결 방법 해결책 1: 배열 선언 시 명시적 타입 지정 (권장) 가장 간단하고 명확한 해결책은 빈 배열에 요소 타입을 명시적(annotation)으로 지정하는 것입니다.\n1 2 3 4 const foo = (foo: string) =\u0026gt; { const result: string[] = [] // ✅ string[] 타입 명시 result.push(foo) // ✅ 정상 동작 } string[]로 선언하면 never[]가 아니라 string[]로 추론되므로, push 파라미터 타입이 string이 되어 foo를 문제없이 추가할 수 있습니다.\n해결책 2: Array\u0026lt;T\u0026gt; 제네릭 문법 사용 동일한 의미를 제네릭 문법으로도 표현할 수 있습니다.\n1 2 const result: Array\u0026lt;string\u0026gt; = [] result.push(foo) // ✅ 정상 동작 해결책 3: 다른 타입의 배열일 때 result에 담을 요소가 문자열이 아니라면 해당 타입으로 지정하면 됩니다.\n1 2 3 4 5 6 const numbers: number[] = [] numbers.push(42) // ✅ 정상 동작 type User = { id: number; name: string } const users: User[] = [] users.push({ id: 1, name: \u0026#34;홍길동\u0026#34; }) // ✅ 정상 동작 해결 방법 비교 방법 코드 특징 명시적 배열 타입 const result: string[] = [] 가장 직관적, 권장 제네릭 문법 const result: Array\u0026lt;string\u0026gt; = [] 동일 의미, 스타일 취향 타입 별칭 배열 const users: User[] = [] 객체 타입 배열에 유용 최종 수정 코드 1 2 3 4 5 const foo = (foo: string) =\u0026gt; { const result: string[] = [] result.push(foo) return result // ✅ 반환 타입도 string[]로 추론됨 } 이로써 string을 never에 넣으려다 발생하던 타입 불일치 오류가 사라집니다.\n5. 향후 예방 조치 — 같은 오류를 피하는 방법 5-1. 빈 배열 선언 시 타입 주석 습관화 요소가 나중에 추가되더라도 빈 배열을 선언하는 순간 요소 타입을 명시하는 습관을 들이면 이 오류를 원천 차단할 수 있습니다.\n1 2 3 4 5 // ❌ bad — never[]로 추론되어 push 불가 const list = [] // ✅ good — 의도를 명확히 const list: string[] = [] 5-2. 초기값으로 타입을 유추하게 하기 가능하면 빈 배열 대신 타입을 유추할 수 있는 초기값을 넣어 배선 타입 추론이 올바르게 되도록 합니다.\n1 2 3 // ✅ 빈 배열 대신 초기값으로 요소 타입 유추 const list = [\u0026#34;첫 번째 항목\u0026#34;] list.push(\u0026#34;두 번째 항목\u0026#34;) // ✅ string[]으로 추론됨 5-3. 빈 배열을 반환하거나 전달할 때도 타입 지정 함수 반환값이나 파라미터로 빈 배열을 사용할 때도 동일한 원리가 적용되므로 명시적으로 타입을 지정합니다.\n1 2 3 4 5 const getNames = (): string[] =\u0026gt; { const result: string[] = [] // ... 요소 누적 return result } 5-4. ESLint / Lint 규칙 활용 팀 코딩 컨벤션에 따라 \u0026ldquo;빈 배열 선언 시 타입 주석을 요구\u0026quot;하는 Lint 규칙을 적용하면 이 유형의 실수를 구조적으로 방지할 수 있습니다.\n예방 습관 설명 타입 주석 const result: string[] = [] 형태로 선언 초기값 [] 대신 요소를 가진 초기 배열 사용 반환 타입 함수 반환 타입에도 배열 요소 타입 명시 Lint 규칙 빈 배열 타입 주석 요구 규칙 적용 핵심 정리 TypeScript는 타입 주석 없이 선언한 빈 배열을 never[]로 추론하며, 이 때문에 어떤 요소도 push할 수 없다. 빈 배열 변수에 요소를 추가하려면 반드시 string[]처럼 배열 요소 타입을 명시적으로 지정해야 한다. 이는 타입스크립트의 버그가 아니라 타입 안전성을 위한 의도된 동작이다.\n출처 StackOverflow: What is \u0026ldquo;not assignable to parameter of type never\u0026rdquo; error in TypeScript? URL: https://stackoverflow.com/questions/52423842/what-is-not-assignable-to-parameter-of-type-never-error-in-typescript 채택 답변: 빈 배열에 string[] 타입을 명시적으로 지정하는 해결책 ","permalink":"https://tokkabi.com/posts/typescript-not-assignable-to-parameter-of-type-never/","summary":"TypeScript \u0026rsquo;not assignable to parameter of type never\u0026rsquo; 오류 완벽 해결 — 빈 배열 타입 추론 1. 문제 정의 TypeScript 코드를 작성하다 보면 다음과 같은 의외의 컴파일 오류를 만날 수 있습니다. 1 2 3 4 const foo = (foo: string) =\u0026gt; {","title":"TypeScript 'not assignable to parameter of type never' 오류 완벽 해결 — 빈 배열 타입 추론과 never[]"},{"content":"1. 문제 정의 npm i 명령으로 패키지를 설치하려 할 때 다음과 같은 ERESOLVE 오류가 발생하는 경우가 있습니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: gf-kautomata-pipeline-ui@0.0.0 npm ERR! Found: @angular/core@9.1.12 npm ERR! node_modules/@angular/core npm ERR! @angular/core@\u0026#34;^9.1.4\u0026#34; from the root project npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer @angular/core@\u0026#34;7.2.16\u0026#34; from @angular/http@7.2.16 npm ERR! node_modules/@angular/http npm ERR! @angular/http@\u0026#34;^7.2.11\u0026#34; from the root project npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force, or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution. 핵심 에러 메시지는 ERESOLVE unable to resolve dependency tree이며, npm 버전 7 이상에서 peer dependency 충돌을 엄격하게 검사하면서 발생합니다.\n2. 원인 탐구 첫 반응으로 HTTP 프록시 설정을 의심할 수 있지만, 이 오류는 네트워크 문제가 아닙니다. 에러 로그가 직접 말해주듯 의존성 충돌(incorrect and potentially broken dependency)이 원인입니다.\n다음 두 요구가 맞물려 있습니다.\n항목 요구 내용 루트 프로젝트 @angular/core@\u0026quot;^9.1.4\u0026quot;, @angular/http@\u0026quot;^7.2.11\u0026quot; @angular/http@7.2.16 peer 의존성으로 @angular/core@\u0026quot;7.2.16\u0026quot; 요구 즉, 루트 프로젝트가 Angular 9(^9.1.4)를 사용하는데, @angular/http@7.2.16은 동일한 Angular 7(7.2.16)의 핵심을 peer로 요구합니다. 서로 맞지 않는 버전이 공존해야 하므로 npm이 의존성 트리를 해석하지 못하고 오류를 내는 것입니다.\n3. 근본 원인 분석 가장 근본적인 원인은 deprecated(더 이상 관리되지 않는) 패키지의 버전 요구가 멈춘 것입니다.\n@angular/http는 Angular 팀이 폐기한 패키지로, 이를 공식적으로 대체한 것은 @angular/common/http입니다. 따라서 @angular/http의 최신 버전은 7.2.16이 마지막이며 그 이상은 존재하지 않습니다.\n루트 프로젝트가 @angular/http@\u0026quot;^7.2.11\u0026quot;을 요구 하지만 @angular/http의 최신 버전이 7.2.16이므로 ^7.2.11 범위(7.2.11 이상)는 만족 가능 그런데 업데이트 후 No matching version found for @angular/http@^9.1.4 오류가 발생한다면, 요구하는 버전(^9.1.4) 자체가 존재하지 않는 버전을 가리키는 것이라 더 해결이 어렵습니다. 이 경우 프로젝트의 의존성 목록을 확인해 요구 버전을 실제 존재하는 버전으로 바로잡아야 합니다.\n요약하면 두 갈래입니다.\npeer 충돌: @angular/core 9 vs @angular/http가 요구하는 @angular/core 7 → 명령 옵션으로 우회 가능. 존재하지 않는 버전 요구: @angular/http@^9.1.4처럼 없는 버전을 참조 → 의존성 재설정 필요. 4. 코드 해결책 해결책 A: --legacy-peer-deps 옵션 (권장) peer dependency 충돌을 엄격하게 검사하지 않는 예전 npm 방식으로 설치합니다.\n1 npm install --legacy-peer-deps 실제로 npm 에러 로그가 스스로 제안하는 옵션 중 하나이며, peer 충돌만으로 막혀 있을 때 가장 안전합니다.\n해결책 B: --force 옵션 의도적으로 잘못된(잠재적으로 깨진) 의존성 해석을 받아들이고 강제로 설치합니다.\n1 npm install --force 해결책 C: Node.js 버전 다운그레이드 (임시 해결) 위 옵션으로도 해결되지 않으면, 이러한 종류의 오류가 발생할 수 있는 이전 버전의 Node.js로 내려 보는 것이 임시 해결볍이 될 수 있습니다. 단, 이는 근본 원인을 고치지 않으므로 임시 방편으로 사용합니다.\n해결책 D: 존재하지 않는 버전 요구 해결 (근본 해결) 만약 No matching version found for @angular/http@^9.1.4 식의 오류가 함께 나온다면, 해당 패키지가 존재하지 않는 버전을 요구하는 상황입니다.\n@angular/http의 최신 버전은 7.2.16입니다. 프로젝트 package.json에 명시된 버전 범위(^9.1.4 등)가 실제 존재하는 버전을 가리키도록 점검하고 수정합니다. 의존성 버전이 실제 존재하는지 확인합니다.\n1 npm view @angular/http versions deprecated 패키지라면 신규 프로젝트에서는 @angular/common/http처럼 대체 패키지 사용을 검토합니다.\n1 2 3 4 5 // deprecated: @angular/http 사용 import { Http } from \u0026#39;@angular/http\u0026#39;; // 권장: @angular/common/http 사용 import { HttpClient } from \u0026#39;@angular/common/http\u0026#39;; 5. 향후 예방 조치 패키지 버전과 존재 여부를 먼저 확인: npm view \u0026lt;패키지\u0026gt; versions, npm view \u0026lt;패키지\u0026gt; peerDependencies 명령으로 실제 존재하는 버전과 peer 요구를 사전에 확인합니다. deprecated 패키지 제거: @angular/http처럼 폐기된 패키지는 대체 패키지(@angular/common/http)로 마이그레이션해 근본 충돌을 없앱니다. peer dependency 충돌을 무시하지 않기: --force/--legacy-peer-deps는 발생한 충돌을 우회할 뿐 근본 원인을 해결하지 않습니다. 사용 후에도 패키지 간 버전 정합성을 점검합니다. 동일한 메이저 버전 유지: Angular 등 프레임워크는 루트 프로젝트와 하위 패키지가 같은 메이저 버전(예: 둘 다 9)을 쓰도록 맞춥니다. 의존성 명세 재생산: 혼란이 반복되면 package-lock.json을 제거하고 의존성을 다시 잡는 것보다, 먼저 package.json의 버전 범위가 실제 존재하는 버전을 가리키는지 점검합니다. 출처 원본 질문: Unable to resolve dependency tree error when installing npm packages (StackOverflow 64573177) ","permalink":"https://tokkabi.com/posts/unable-to-resolve-dependency-tree-error-when-installing-npm/","summary":"1. 문제 정의 npm i 명령으로 패키지를 설치하려 할 때 다음과 같은 ERESOLVE 오류가 발생하는 경우가 있습니다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree","title":"npm 패키지 설치 시 'Unable to resolve dependency tree' ERESOLVE 오류 해결"},{"content":"Angular 2 *ngFor 오류: Can\u0026rsquo;t bind to \u0026rsquo;ngFor\u0026rsquo; since it isn\u0026rsquo;t a known native property 1. 문제 정의 Angular 2에서 리스트를 반복 렌더링하기 위해 *ngFor 디렉티브를 사용하던 중, 다음과 같은 Template parse 오류가 발생합니다.\n1 2 3 EXCEPTION: Template parse errors: Can\u0026#39;t bind to \u0026#39;ngFor\u0026#39; since it isn\u0026#39;t a known native property (\u0026#34;\u0026lt;div [ERROR -\u0026gt;]*ngFor=\u0026#34;talk of talks\u0026#34;\u0026gt; 브라우저 콘솔에 이 오류가 출력되며 컴포넌트가 정상적으로 렌더링되지 않습니다. 아래는 문제가 발생한 코드입니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 import {bootstrap, Component} from \u0026#39;angular2/angular2\u0026#39; @Component({ selector: \u0026#39;conf-talks\u0026#39;, template: `\u0026lt;div *ngFor=\u0026#34;talk of talks\u0026#34;\u0026gt; {{talk.title}} by {{talk.speaker}} \u0026lt;p\u0026gt;{{talk.description}} \u0026lt;/div\u0026gt;` }) class ConfTalks { talks = [ {title: \u0026#39;t1\u0026#39;, speaker: \u0026#39;Brian\u0026#39;, description: \u0026#39;talk 1\u0026#39;}, {title: \u0026#39;t2\u0026#39;, speaker: \u0026#39;Julie\u0026#39;, description: \u0026#39;talk 2\u0026#39;}]; } 핵심은 템플릿의 \u0026lt;div *ngFor=\u0026quot;talk of talks\u0026quot;\u0026gt; 부분입니다. 오류 메시지가 ngFor를 \u0026ldquo;알려지지 않은 네이티브 속성\u0026quot;으로 지목하지만, 실제로는 디렉티브 문법이 잘못된 상황입니다.\n2. 원인 탐구 브라우저는 ngFor를 알 수 없는 속성으로 보고합니다. 왜 Angular가 *ngFor 디렉티브를 인식하지 못하는 것처럼 보일까요?\n관찰 결과, *ngFor 구조 디렉티브의 문법에서 talk라는 반복 변수를 선언할 때 let 키워드가 누락되어 있습니다.\n올바른 문법: \u0026lt;div *ngFor=\u0026quot;let talk of talks\u0026quot;\u0026gt; 잘못된 문법: \u0026lt;div *ngFor=\u0026quot;talk of talks\u0026quot;\u0026gt; *ngFor의 축약형(desugared) 구문에서 of 앞에는 반드시 \u0026ldquo;변수 선언\u0026quot;이 와야 합니다. 이 변수 선언은 선행 키워드(let 또는 과거의 #)가 필요한 형태입니다. 이 키워드가 없으면 파서가 talk of talks를 유효한 반복 표현식으로 해석하지 못하여 ngFor 바인딩 자체를 실패하게 됩니다.\n3. 근본 원인 분석 근본 원인은 구조 디렉티브 내부의 지역 변수 선언 문법에서 선행 키워드( let )를 생략한 것입니다.\nAngular 2의 * 접두사는 템플릿 문법을 축약하는 기능입니다. 예를 들어 *ngFor=\u0026quot;let talk of talks\u0026quot;는 내부적으로 다음과 같이 변환됩니다.\n1 2 3 \u0026lt;template ngFor [ngForOf]=\u0026#34;talks\u0026#34; let-talk\u0026gt; ... \u0026lt;/template\u0026gt; 이 변환 과정에서 let talk 구문이 실제 바인딩을 생성합니다. 여기서 let이 없으면 ngForOf에 연결할 반복 변수(talk)가 선언되지 못하고, 결과적으로 템플릿 파서가 *ngFor 디렉티브 전체를 \u0026ldquo;알려진 네이티브 속성이 아닌\u0026rdquo; 무언가로 처리해 오류를 냅니다.\n추가로, Angular 2 beta.17 버전부터 구조 디렉티브 안에서 지역 변수를 선언하는 데 사용하던 #... 문법이 deprecated(비권장) 되었습니다. 따라서 이제는 let 키워드를 사용하는 것이 표준입니다.\n문법 상태 예시 let 변수 표준 (권장) \u0026lt;div *ngFor=\u0026quot;let talk of talks\u0026quot;\u0026gt; #변수 deprecated (beta.17 이후) \u0026lt;div *ngFor=\u0026quot;#talk of talks\u0026quot;\u0026gt; — 더 이상 권장되지 않음 변수 (키워드 없음) 오류 \u0026lt;div *ngFor=\u0026quot;talk of talks\u0026quot;\u0026gt; — 문법 오류 4. 코드 해결책 문제는 단순합니다. *ngFor 안에서 반복 변수를 선언할 때 let 키워드를 추가하면 됩니다.\n잘못된 코드:\n1 2 3 4 \u0026lt;div *ngFor=\u0026#34;talk of talks\u0026#34;\u0026gt; {{talk.title}} by {{talk.speaker}} \u0026lt;p\u0026gt;{{talk.description}} \u0026lt;/div\u0026gt; 수정된 코드:\n1 2 3 4 \u0026lt;div *ngFor=\u0026#34;let talk of talks\u0026#34;\u0026gt; {{talk.title}} by {{talk.speaker}} \u0026lt;p\u0026gt;{{talk.description}} \u0026lt;/div\u0026gt; 수정 후 전체 컴포넌트 코드는 다음과 같습니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import {bootstrap, Component} from \u0026#39;angular2/angular2\u0026#39; @Component({ selector: \u0026#39;conf-talks\u0026#39;, template: `\u0026lt;div *ngFor=\u0026#34;let talk of talks\u0026#34;\u0026gt; {{talk.title}} by {{talk.speaker}} \u0026lt;p\u0026gt;{{talk.description}} \u0026lt;/div\u0026gt;` }) class ConfTalks { talks = [ {title: \u0026#39;t1\u0026#39;, speaker: \u0026#39;Brian\u0026#39;, description: \u0026#39;talk 1\u0026#39;}, {title: \u0026#39;t2\u0026#39;, speaker: \u0026#39;Julie\u0026#39;, description: \u0026#39;talk 2\u0026#39;}]; } @Component({ selector: \u0026#39;my-app\u0026#39;, directives: [ConfTalks], template: \u0026#39;\u0026lt;conf-talks\u0026gt;\u0026lt;/conf-talks\u0026gt;\u0026#39; }) class App {} bootstrap(App, []) 만약 기존 코드가 아직 #talk 문법을 사용하고 있다면, #를 let으로 바꿔야 합니다.\n기존 (deprecated):\n1 \u0026lt;div *ngFor=\u0026#34;#talk of talks\u0026#34;\u0026gt; 변경 (권장):\n1 \u0026lt;div *ngFor=\u0026#34;let talk of talks\u0026#34;\u0026gt; 구버전(베타)에서 동작하던 # 문법도 beta.17 이후로는 더 이상 표준이 아니므로, 새 코드에는 항상 let을 사용하세요.\n5. 향후 예방 조치 *ngFor 관련 오류를 다시 만나지 않으려면 다음 규칙을 기억하세요.\nlet 키워드를 습관화하라. *ngFor 안의 변수 선언에는 항상 let을 붙입니다. \u0026lt;div *ngFor=\u0026quot;let item of items\u0026quot;\u0026gt; # 문법을 사용하지 마라. beta.17 이후 deprecated되었습니다. 오래된 예제를 복사할 때 #talk 형태를 발견하면 let talk로 바꿉니다. 오류 메시지를 문법 관점에서 읽어라. \u0026ldquo;Can\u0026rsquo;t bind to \u0026rsquo;ngFor\u0026rsquo; since it isn\u0026rsquo;t a known native property\u0026rdquo; 같은 메시지는 디렉티브 자체가 없는 것이 아니라, 디렉티브의 축약 문법이 잘못됐을 때도 나타납니다. 반복 대상(컬렉션)과 반복 변수(요소) 이름을 구분하라. of 왼쪽은 \u0026ldquo;하나의 요소를 담는 변수 선언\u0026rdquo;, 오른쪽은 \u0026ldquo;컬렉션(배열)\u0026ldquo;입니다. 최신 Angular 버전에서는 *ngFor 대신 새 구조 디렉티브 문법을 확인하라. 최신 Angular에서는 @for (Angular 17+) 같은 공식 반복문이 도입되었으므로, 버전에 맞는 공식 문서를 참고하세요. 이 규칙만 지켜도 \u0026ldquo;Can\u0026rsquo;t bind to \u0026rsquo;ngFor\u0026rsquo;\u0026rdquo; 같은 혼란스러운 템플릿 오류 대부분을 예방할 수 있습니다.\n참고 출처 StackOverflow 34012291 — Exception: Can\u0026rsquo;t bind to \u0026rsquo;ngFor\u0026rsquo; since it isn\u0026rsquo;t a known native property 채택 답변 작성자: Mark Rajcok ","permalink":"https://tokkabi.com/posts/exception-cant-bind-to-ngfor-since-it-isnt-a-known-native-property/","summary":"Angular 2 *ngFor 오류: Can\u0026rsquo;t bind to \u0026rsquo;ngFor\u0026rsquo; since it isn\u0026rsquo;t a known native property 1. 문제 정의 Angular 2에서 리스트를 반복 렌더링하기 위해 *ngFor 디렉티브를 사용하던 중, 다음과 같은 Template parse 오류가 발생합니다. 1 2 3","title":"Angular 2 *ngFor 오류: Can't bind to 'ngFor' since it isn't a known native property 해결"},{"content":"1. 문제 정의 Node.js로 파일시스템 인덱싱(RAID 파일 인덱스 갱신)을 수행하는 스크립트를 실행하던 중, 약 4시간 만에 프로세스가 다음과 같은 메시지와 함께 heap out of memory 로 크래시했다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [md5:] 241613/241627 97.5% [md5:] 241614/241627 97.5% [md5:] 241625/241627 98.1% Creating missing list... (79570 files missing) Creating new files list... (241627 new files) \u0026lt;--- Last few GCs ---\u0026gt; 11629672 ms: Mark-sweep 1174.6 (1426.5) -\u0026gt; 1172.4 (1418.3) MB, 659.9 / 0 ms [allocation failure] [GC in old space requested]. 11630371 ms: Mark-sweep 1172.4 (1418.3) -\u0026gt; 1172.4 (1411.3) MB, 698.9 / 0 ms [allocation failure] [GC in old space requested]. 11631105 ms: Mark-sweep 1172.4 (1411.3) -\u0026gt; 1172.4 (1389.3) MB, 733.5 / 0 ms [last resort gc]. 11631778 ms: Mark-sweep 1172.4 (1389.3) -\u0026gt; 1172.4 (1368.3) MB, 673.6 / 0 ms [last resort gc]. \u0026lt;--- JS stacktrace ---\u0026gt; ==== JS stack trace ========================================= Security context: 0x3d1d329c9e59 \u0026lt;JS Object\u0026gt; 1: SparseJoinWithSe 약 24만 개의 파일에 대한 MD5 해시와 리스트 생성을 수행하는 대용량 작업이었고, 인덱싱이 거의 끝나갈 무렵(97~98%) 메모리 부족으로 종료되었다.\n에러 로그에서 주목할 키워드 로그 키워드 의미 [allocation failure] 새 메모리 할당이 실패함 [GC in old space requested] GC가 구(old space) 영역을 정리하도록 요청됨 [last resort gc] 최후 수단으로 가비지 컬렉션을 수행함 Mark-sweep V8 구 영역의 표시-쓸기(마크 앤 스윕) GC (1426.5) -\u0026gt; (1368.3) MB 힙 크기 변화 2. 원인 탐구 로그를 보면 GC가 반복적으로 수행되고 있다. Mark-sweep이 여러 차례 실행되었는데도 힙 사용량(1174.6 -\u0026gt; 1172.4 MB)이 거의 줄어들지 않았다. 즉 메모리에 쌓인 데이터가 실질적으로 계속 살아남아 해제되지 않는 상황이라는 짐작이 가능하다.\n그런데 핵심은 GC의 동작 자체보다 V8의 기본 메모리 한도에 있다. Node.js의 V8 엔진은 별도 설정 없이 실행하면 힙을 특정 크기까지만 할당할 수 있도록 제한한다. 파일 인덱싱처럼 수십만 개의 객체(각 파일의 경로, 해시, 리스트 등)를 동시에 쥐고 있는 작업은 이 한도에 빠르게 도달한다.\n[GC in old space requested]라는 표현은 구(old space) 영역에 새 메모리가 더 필요하다는 의미다. 구 영역은 오래 살아남은 참조 데이터 구조가 위치하는 곳으로, 이 문제의 주인공이다.\n3. 근본 원인 분석 출처 답변에 따르면 V8에는 메모리 사용량에 대한 기본 상한이 약 1.7GB로 정해져 있다. 이 값을 수동으로 늘려주지 않으면 대용량 작업이 이 한도에 도달하고, GC가 더 이상 메모리를 확보하지 못하는 시점에 프로세스가 크래시한다.\n여기서 핵심은 어느 힙 영역의 한도를 늘려야 하는가다. V8 힙은 크게 두 영역으로 나뉜다:\n영역 역할 new space (신생 영역) 생성 직후의 단기 데이터를 수집 old space (구 영역) 오래 살아남은 모든 참조 데이터 구조를 보관 파일 인덱싱에서 만들어지는 인덱스·리스트·해시 값은 작업 내내 유지되는 장기 참조 데이터이므로 구(old space) 에 쌓인다. 따라서 new space 한도를 올리는 것으로는 부족하고, old space의 한도(--max-old-space-size)를 올려야 문제가 해결된다.\n정리하면 근본 원인은 다음과 같다.\n수십만 개의 장기 데이터가 구(old space)에 누적되어 V8 기본 힙 한도(약 1.7GB)에 도달 GC가 [allocation failure] → [last resort gc]까지 시도해도 새 메모리를 확보하지 못함 V8이 힙 한도 초과로 프로세스를 강제 종료 → heap out of memory 크래시 4. 코드 해결책 방법 A — 실행 시 옵션으로 메모리 한도 늘리기 (권장) 가장 간단하고 안정적인 방법은 스크립트 실행 시 --max-old-space-size 옵션을 넘겨 구(old space) 메모리 한도를 늘리는 것이다.\n1 2 # 구(old space) 메모리 한도를 4096MB(4GB)로 설정하고 실행 node --max-old-space-size=4096 yourFile.js 옵션 값의 단위는 MB이며, 4096은 4GB를 의미한다. 시스템 메모리가 넉넉하다면 더 큰 값을 줄 수 있다.\n시스템 전역 설정 (Linux/macOS)\nNODE_OPTIONS 환경 변수로 프로세스 생성 시 자동 적용되게 할 수도 있다.\n1 2 export NODE_OPTIONS=\u0026#34;--max-old-space-size=4096\u0026#34; node yourFile.js package.json의 npm 스크립트에 반영\n1 2 3 4 5 { \u0026#34;scripts\u0026#34;: { \u0026#34;start\u0026#34;: \u0026#34;node --max-old-space-size=4096 yourFile.js\u0026#34; } } 방법 B — 런타임에 플래그 설정하기 코드 안에서 플래그를 동적으로 설정할 수도 있지만, 프로세스 초기(모듈 로드 초기)에 실행해야 적용된다.\n1 2 3 4 5 // yourFile.js (최상단에서 실행) const v8 = require(\u0026#39;v8\u0026#39;); v8.setFlagsFromString(\u0026#39;--max-old-space-size=4096\u0026#39;); // 이후 대용량 처리 코드... 참고: 방법 A(실행 시 옵션)가 더 명확하고 신뢰할 수 있으므로, 배포 스크립트 등에는 방법 A를 사용하는 것이 일반적이다. 출처 답변 역시 배포 스크립트에서 node --max-old-space-size=4096 yourFile.js로 해결했다고 밝히고 있다.\n해결책 비교 방법 명령/코드 특징 실행 시 옵션 node --max-old-space-size=4096 yourFile.js 가장 간단·명확, 배포 스크립트 권장 환경 변수 NODE_OPTIONS=\u0026quot;--max-old-space-size=4096\u0026quot; 시스템 전역 적용 가능 런타임 플래그 v8.setFlagsFromString('--max-old-space-size=4096') 코드 내 설정, 최상단 실행 필요 5. 향후 예방 조치 같은 크래시를 반복하지 않으려면 메모리 한도를 늘리는 것 외에 다음 사항도 점검하면 좋다.\n메모리 한도 초과는 근본 원인이 아닐 수 있다. --max-old-space-size를 올리면 임시로 해결되지만, 대용량 처리 중 참조가 쌓여 불필요하게 메모리를 점유하는 진짜 메모리 누수(memory leak) 가 있다면 한도를 올려도 결국 다시 크래시한다. 작업이 끝난 객체의 참조를 명시적으로 정리(null 처리 등)하거나, 스트림·페이지네이션으로 처리량을 나눠보자.\n큰 배열 한 번에 메모리에 올리지 않기. 수십만 개 요소를 전부 배열로 모은 뒤 처리하는 대신, 파일을 하나씩 읽고 결과를 즉시 저장하는 방식(스트림, 배치 처리)으로 바꾸면 힙 사용량이 급감한다.\n실제 현재 메모리 사용량을 파악하자. --trace-gc 같은 플래그로 GC 로그를 확인하고, process.memoryUsage()로 힙 사용 추이를 관찰해 어느 시점에 한도에 도달하는지 파악한다.\n메모리 상한을 환경에 맞게 책정하기. 4096은 예시일 뿐이며, 실제 동작 환경의 물리 메모리를 고려해 값을 정한다. 값을 너무 크게 주면 서버가 스왑/메모리 부족(FATAL ERROR: Ineffective mark-compacts near heap limit)으로 죽을 수 있다.\n운영 도구를 활용하기. --inspect와 크롬 개발자 도구 메모리 프로파일러로 힙 스냅샷을 떠서 누수를 잡는 것도 효과적이다.\n출처 StackOverflow: Node.js heap out of memory (질문 38558989) ","permalink":"https://tokkabi.com/posts/nodejs-heap-out-of-memory/","summary":"1. 문제 정의 Node.js로 파일시스템 인덱싱(RAID 파일 인덱스 갱신)을 수행하는 스크립트를 실행하던 중, 약 4시간 만에 프로세스가 다음과 같은 메시지와","title":"Node.js heap out of memory 크래시 원인과 해결 (--max-old-space-size)"},{"content":"Python 크래시 로그 남기기 — 원격 서버에서 stderr 리다이렉션 1. 문제 정의 라즈베리 파이처럼 원격 머신에서 실행되는 Python 스크립트가 있습니다. 이 코드는 원래 영원히 계속 실행되어야 하는데, 몇 시간이 지나면 크래시됩니다.\n문제는 스크립트가 원격 머신에서 돌기 때문에 크래시 순간의 오류 메시지를 직접 볼 수 없다는 점입니다.\n질문 원문 (StackOverflow 15111249 요약)\n\u0026ldquo;I am running a python code in a raspberry Pi. The code is supposed to last forever. However, after a few hours it crashes. Since it is running on a remote machine, I cannot see the message it gives during the crash. How can I store this message on a file so I can see what was the problem?\u0026rdquo;\n즉, 해결해야 할 문제는 **\u0026ldquo;크래시 시점의 오류 메시지를 파일로 저장해 나중에 원인을 확인할 수 있게 하는 것\u0026rdquo;**입니다. 리눅스에서 이런 일이 자동으로 일어나는지, 아니면 오류를 파일로 내보내는 함수를 직접 작성해야 하는지가 핵심 질문입니다.\n2. 원인 탐구 왜 크래시 메시지가 사라지는가? Python 스크립트가 크래시를 일으키면 인터프리터는 traceback(역추적)을 stderr(표준 에러) 로 출력합니다. 이때 기본적으로 오류 메시지는:\n터미널(콘솔)에만 출력되고, 별도 파일로 저장되지 않습니다. 원격 머신에서는 셸 세션을 직접 보고 있지 않거나, 프로세스가 백그라운드로 돌고 있기 때문에 이 출력을 놓치게 됩니다. 따라서 \u0026ldquo;리눅스가 자동으로 오류를 파일에 남겨주는가?\u0026ldquo;라는 질문에 대한 답은 일반적으로 아니오입니다. 원하는 형태로 오류를 파일에 남기려면 명시적으로 조치를 취해야 합니다.\n두 가지 접근 방식 크래시 로그를 남기는 방법은 크게 두 갈래로 나뉩니다.\n접근 방식 장점 단점 셸 리다이렉션 실행 시 \u0026gt;\u0026gt; file 2\u0026gt;\u0026amp;1 로 출력 방향 전환 코드 수정 불필요, 초보자도 쉬움 로그 형식/시간 기록 제어는 별도 코드 내 로깅 logging.exception() / sys.exc_info() 타임스탬프·형식·파일 관리 가능 소스 수정 필요 3. 근본 원인 분석 크래시 메시지를 볼 수 없는 근본 원인은 오류 출력(stderr)이 저장되지 않는 기본 동작 때문입니다. Python이 예외를 던지고 종료되면 traceback은 프로세스의 표준 에러 스트림으로만 흘러갑니다. 그 스트림이 파일로 연결되어 있지 않다면 메시지는 그냥 사라집니다.\n따라서 근본 해결책은 \u0026ldquo;오류 출력이 파일로 흘러가도록 스트림을 연결\u0026rdquo; 하는 것입니다. 이는 두 층위에서 해결할 수 있습니다.\n운영(셸) 층위: 프로세스를 띄울 때 파일로 리다이렉션. 코드 층위: Python 내부에서 예외를 잡아 파일에 직접 기록. 4. 코드 해결책 방법 A — 셸 리다이렉션 (가장 간단, 채택 답변) 소스를 전혀 건드리지 않고, 프로세스를 시작할 때 표준 출력과 표준 에러를 모두 로그 파일로 보냅니다.\n1 python script.py \u0026gt;\u0026gt; /logdir/script.py.log 2\u0026gt;\u0026amp;1 \u0026gt;\u0026gt; : 출력을 파일 끝에 이어붙이기(append). \u0026gt;는 파일을 덮어씁니다. 2\u0026gt;\u0026amp;1 : stderr(2)를 stdout(1)과 같은 곳으로 보냅니다. 이 한 줄 덕분에 크래시 traceback까지 파일에 남습니다. \u0026gt;\u0026gt;를 쓰는 이유는 오래 실행되는 스크립트에서 이전 로그가 유지되도록 하기 위함입니다. 파일을 덮어쓰는 \u0026gt;는 재시작할 때마다 이전 로그를 지워버립니다.\n방법 B — logging 모듈로 메인 함수 크래시 기록 로그 집계(예: Logstash 같은 도구)를 사용하고 있다면, 메인 함수를 try/except로 감싸 크래시 시점을 logging.exception()으로 남기는 방식이 더 유리합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 import logging logger = logging.getLogger(__name__) def main(): # ... 오래 실행되는 작업 ... raise ValueError(\u0026#34;Crashed because I\u0026#39;m a bad exception\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: try: main() except Exception as e: logger.exception(\u0026#34;main crashed. Error: %s\u0026#34;, e) logger.exception()는 예외 메시지뿐 아니라 전체 traceback과 타임스탬프까지 함께 로그로 남기므로, \u0026ldquo;언제 크래시했는지\u0026quot;를 포함해 원인을 추적하기 쉽습니다. 파일/형식/시간 관리는 logging 설정으로 제어합니다.\n방법 C — sys.exc_info()로 크래시 로그 파일 직접 작성 별도 로그 파일(예: CRASH-\u0026lt;timestamp\u0026gt;.txt)에 오류 위치와 메시지를 기록하고 싶다면 sys.exc_info()를 활용할 수 있습니다. 아래는 Python 3 코드입니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import sys import time try: # ... 실행할 프로그램 코드 ... pass except Exception as e: crash = [ \u0026#34;Error on line {}\u0026#34;.format(sys.exc_info()[-1].tb_lineno), \u0026#34;\\n\u0026#34;, str(e), ] print(crash) time_x = str(time.time()) with open(\u0026#34;crashlogs/CRASH-\u0026#34; + time_x + \u0026#34;.txt\u0026#34;, \u0026#34;w\u0026#34;) as crash_log: for line in crash: crash_log.write(line) sys.exc_info()[-1].tb_lineno : 오류가 발생한 라인 번호 time.time() : 크래시 시점의 유닉스 타임스탬프 (파일명에 포함 → 크래시 마다 개별 로그 생성) 방법 비교 요약 구분 방법 A (리다이렉션) 방법 B (logging) 방법 C (sys.exc_info) 코드 수정 불필요 필요 필요 타임스탬프 기록 리다이렉션만으론 없음 자동 포함 수동 추가 traceback 저장 stderr 그대로 저장 exception()이 포함 라인 번호만 직접 기록 적합한 상황 빠른 디버깅, 초보자 로그 집계 도구 사용 시 크래시별 개별 파일 필요 시 5. 향후 예방 조치 항상 리다이렉션을 습관화 — 원격에서 장시간 실행하는 스크립트는 시작 명령부터 \u0026gt;\u0026gt; 로그파일 2\u0026gt;\u0026amp;1을 붙여 오류 스트림이 파일로 가도록 하세요. \u0026gt; 대신 \u0026gt;\u0026gt; — 재시작해도 이전 로그를 지우지 않도록 append를 사용하세요. 구조적인 로깅 도입 — 장기 실행 서비스라면 logging 모듈로 파일 핸들러를 설정하고 메인 함수를 try/except로 감싸 logger.exception()을 남기세요. 타임스탬프와 traceback이 함께 남아 원인 추적이 쉽습니다. 크래시가 잦은 경우 근본 원인 확인 — 로그가 남으면 \u0026ldquo;몇 시간 뒤 크래시\u0026quot;의 원인(메모리 누수, 리소스 고갈, 예외 미처리 등)을 traceback으로 분석해 코드를 고치세요. 출처\nStackOverflow — How to log a Python crash? (Question 15111249) ","permalink":"https://tokkabi.com/posts/how-to-log-a-python-crash/","summary":"Python 크래시 로그 남기기 — 원격 서버에서 stderr 리다이렉션 1. 문제 정의 라즈베리 파이처럼 원격 머신에서 실행되는 Python 스크립트가 있습니다. 이 코드는 원래 영원히 계속 실행되","title":"Python 크래시 로그 남기기 — 원격 서버에서 stderr 리다이렉션"},{"content":"JavaScript에서 undefined 확인하는 방법 — typeof vs in 연산자 1. 문제 정의 JavaScript에서 변수가 undefined인지 확인할 때 개발자들은 여러 가지 방식을 사용합니다. 대표적으로 다음과 같은 코드가 흔히 등장합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 방법 A: window의 속성으로 확인 if (window.myVariable) { // ... } // 방법 B: typeof 연산자 사용 if (typeof(myVariable) != \u0026#34;undefined\u0026#34;) { // ... } // 방법 C: 직접 비교 (에러 발생 위험) if (myVariable) { // ... } 문제는 각 방법이 \u0026lsquo;변수가 무엇을 의미하는지\u0026rsquo;에 따라 동작이 완전히 다르다는 점입니다. 특히 방법 C는 변수가 선언되지 않았을 때 다음과 같은 에러를 발생시킵니다.\n1 ReferenceError: myVariable is not defined 출처: StackOverflow 3390396 - How can I check for \u0026ldquo;undefined\u0026rdquo; in JavaScript?\n2. 원인 탐구 왜 같은 \u0026lsquo;undefined 확인\u0026rsquo; 코드인데 동작이 제각각일까요? 그 이유는 undefined라는 개념이 실제로 두 가지 서로 다른 상황을 포함하기 때문입니다.\n상황 설명 typeof 결과 선언되지 않은 변수 var/let/const로 선언된 적이 없음 \u0026quot;undefined\u0026quot; 선언됐지만 값이 없는 변수 선언만 하고 초기화하지 않음 \u0026quot;undefined\u0026quot; 선언되지 않은 변수에 직접 접근하면 JavaScript 엔진이 ReferenceError를 던집니다.\n1 2 3 4 // abc는 한 번도 선언된 적이 없다. if (abc) { // ReferenceError: abc is not defined } 따라서 if (myVariable) 같은 직접 접근 방식은 변수가 미선언 상태일 때 프로그램을 즉시 중단시킵니다. 이 에러를 피하려고 Try/Catch를 감싸야 하는지가 질문의 핵심입니다.\n또 하나의 함정은 if (window.myVar) 방식입니다. window 속성 접근은 비교가 되지만, falsy 값을 모두 걸러내기 때문입니다.\n1 2 3 4 5 6 7 // 아래 값들은 모두 if 조건에서 거짓으로 처리된다. false 0 \u0026#34;\u0026#34; NaN null undefined 즉, 변수의 값이 실제로 0이나 빈 문자열이라면 if (window.myVar)로는 undefined와 구분할 수 없습니다.\n3. 근본 원인 분석 근본 원인은 확인하고 싶은 것이 \u0026lsquo;존재 여부\u0026rsquo;인지 \u0026lsquo;값의 종류\u0026rsquo;인지에 따라 적절한 연산자가 다르다는 데 있습니다.\n1) 변수가 선언되었는지(존재 여부)를 알고 싶다면 → in 연산자\nin 연산자는 변수가 선언됐는지 여부를 값과 무관하게 확인합니다.\n1 2 3 4 5 6 7 // 전역 스코프 var theFu; // theFu는 선언되었지만 값은 undefined typeof theFu; // \u0026#34;undefined\u0026#34; ← 값 기준이므로 undefined로 나온다 \u0026#34;theFu\u0026#34; in window; // true ← 존재 여부 기준 \u0026#34;theFoo\u0026#34; in window; // false ← 선언된 적 없음 in 연산자는 \u0026lsquo;선언됐지만 초기화되지 않은 변수\u0026rsquo;도 true로 판별합니다. 값이 아닌 존재 자체를 보기 때문입니다.\n2) 변수의 값이 undefined인지 알고 싶다면 → typeof 연산자\ntypeof는 항상 문자열을 반환하므로, 변수가 선언되지 않아도 ReferenceError 없이 안전하게 확인할 수 있습니다.\n1 2 3 if (typeof myVar !== \u0026#39;undefined\u0026#39;) { // myVar가 undefined가 아님 } 3) 왜 undefined와 직접 비교하면 안 되는가?\n과거에는 window.undefined를 덮어쓸 수 있었기 때문에 직접 비교가 오염될 수 있었습니다.\n1 2 window.undefined = \u0026#34;foo\u0026#34;; \u0026#34;foo\u0026#34; == undefined // true ← 오염된 비교 다만 @CMS가 지적했듯이, 이는 ECMAScript 5판에서 패치되어 undefined는 더 이상 쓸 수 없게(non-writable) 되었습니다. 그럼에도 typeof 비교는 명시적이고 안전한 관례로 남아 있습니다.\n4) 직접 접근이 에러를 일으키는 두 번째 경우\nif (myVariable)은 미선언 변수 접근뿐 아니라, getter 함수가 내부에서 예외를 던지는 속성을 접근할 때도 ReferenceError(또는 기타 예외)를 발생시킬 수 있습니다.\n4. 코드 해결책 상황에 맞는 안전한 undefined 판별 코드를 정리합니다.\n해결책 1: 값이 undefined인지 확인 (가장 일반적) 1 2 3 4 5 6 7 8 9 // 선언 여부와 무관하게 값이 undefined인지 안전하게 확인 if (typeof myVar === \u0026#39;undefined\u0026#39;) { console.log(\u0026#39;myVar는 undefined입니다.\u0026#39;); } // !== \u0026#39;undefined\u0026#39;로 부정 확인 if (typeof myVar !== \u0026#39;undefined\u0026#39;) { console.log(\u0026#39;myVar에 값이 있습니다.\u0026#39;); } 해결책 2: 변수/속성이 선언(존재)되었는지 확인 1 2 3 4 5 6 7 8 9 10 // 전역 변수 존재 여부 if (\u0026#34;myVar\u0026#34; in window) { console.log(\u0026#39;myVar가 전역에 선언되었습니다.\u0026#39;); } // 객체 속성 존재 여부 (hasOwnProperty와 병행 가능) const obj = {}; if (\u0026#34;key\u0026#34; in obj) { console.log(\u0026#39;obj에 key 속성이 존재합니다.\u0026#39;); } 해결책 3: NaN 등 다른 falsy 값과 구분 값 자체가 false, 0, \u0026quot;\u0026quot;, NaN, null이어도 그 값은 유효한 데이터일 수 있습니다. 값을 보존하면서 판별하려면 진리값 검사 대신 typeof를 쓰는 것이 안전합니다.\n1 2 3 4 5 6 7 8 9 10 11 const count = 0; // ❌ 값이 0인데도 undefined처럼 취급된다 if (count) { // 실행되지 않음 } // ✅ 0은 undefined가 아니므로 정상적으로 구분된다 if (typeof count !== \u0026#39;undefined\u0026#39;) { console.log(\u0026#39;count는 정의되어 있고 값은\u0026#39;, count); // count는 0 } 방법별 동작 비교 표 확인 코드 미선언 변수 값=undefined 값=0 / \u0026quot;\u0026quot; / false 에러 발생 if (myVar) ReferenceError false false ⚠️ 가능 if (window.myVar) false false false 안전 if (typeof myVar !== 'undefined') false false true 안전 \u0026quot;myVar\u0026quot; in window false true true 안전 typeof myVar !== 'undefined'와 \u0026quot;myVar\u0026quot; in window 모두 미선언 변수에 대해 false를 반환하지만, in 연산자는 값이 undefined인 변수에 대해서도 true 를 반환한다는 차이가 있습니다.\n5. 향후 예방 조치 undefined 확인과 관련된 함정을 피하기 위한 실천 지침입니다.\n미선언 변수 접근을 피하라 — if (myVar)처럼 변수를 직접 조건에 넣지 말고, 반드시 typeof나 in으로 감싸서 접근하세요. 직접 접근은 ReferenceError: x is not defined를 일으킵니다. \u0026lsquo;존재 여부\u0026rsquo;와 \u0026lsquo;값의 종류\u0026rsquo;를 구분하라 — 선언됐는지가 궁금하면 in 연산자, 값이 undefined인지가 궁금하면 typeof 연산자를 쓰세요. 둘을 혼용하면 의도와 다른 결과가 나옵니다. falsy 값 함정을 기억하라 — if (value)는 0, \u0026quot;\u0026quot;, false, NaN, null, undefined를 모두 거짓으로 만듭니다. 값 자체를 유효 데이터로 다뤄야 한다면 진리값 검사 대신 typeof를 사용하세요. ES5 이후의 안전성에 의존하라 — undefined는 SC5에서 non-writable로 고정되었지만, 명시적인 typeof 비교가 코드 의도를 더 잘 드러냅니다. 모듈/스코프 단위로 변수를 초기화하라 — 변수를 사용하기 전에 명시적으로 초기화하면 미선언 상태 자체가 발생할 여지를 줄일 수 있습니다. 출처\nStackOverflow 3390396 - How can I check for \u0026ldquo;undefined\u0026rdquo; in JavaScript? ","permalink":"https://tokkabi.com/posts/how-can-i-check-for-undefined-in-javascript/","summary":"JavaScript에서 undefined 확인하는 방법 — typeof vs in 연산자 1. 문제 정의 JavaScript에서 변수가 undefined인지 확인할 때 개발자들은 여러 가지","title":"JavaScript에서 undefined 확인하는 방법 — typeof vs in 연산자"},{"content":"Angular 컴포넌트에 서비스를 주입할 때 \u0026lsquo;Can\u0026rsquo;t resolve all parameters for component\u0026rsquo; 오류 해결하기 1. 문제 정의 Angular 애플리케이션에서 컴포넌트의 생성자에 서비스를 주입하려는 순간, 다른 컴포넌트에는 정상적으로 주입되지만 특정 컴포넌트에서만 다음과 같은 예외가 발생하는 상황을 만날 수 있습니다.\n1 EXCEPTION: Can\u0026#39;t resolve all parameters for component 구체적인 재현 상황은 다음과 같습니다. 아래처럼 @Injectable() 데코레이터가 붙은 서비스가 있고,\n1 2 3 4 5 6 7 8 9 10 11 12 import { Injectable } from \u0026#39;@angular/core\u0026#39;; @Injectable() export class MobileService { screenWidth: number; screenHeight: number; constructor() { this.screenWidth = window.outerWidth; this.screenHeight = window.outerHeight; } } 이 서비스를 세 개의 컴포넌트에는 잘 주입하면서, 네 번째 컴포넌트에만 아래와 같이 주입하려고 하면 오류가 납니다.\n1 2 3 4 5 6 7 import { Component } from \u0026#39;@angular/core\u0026#39;; import { MobileService } from \u0026#39;./mobile.service\u0026#39;; @Component({ ... }) export class HeaderComponent { constructor(private mobileService: MobileService) { } } 주입 대상인 서비스와 컴포넌트 모두 코드 자체에는 문제가 없어 보이는데, 왜 특정 컴포넌트에서만 주입에 실패하는지 혼란스러운 상황입니다.\n항목 값 오류 메시지 EXCEPTION: Can't resolve all parameters for component 발생 시점 컴포넌트 생성자에 서비스 주입 시 특징 일부 컴포넌트는 정상, 특정 컴포넌트만 실패 관련 기능 Angular 의존성 주입(Dependency Injection), 배럴(barrel) 모듈 2. 원인 탐구 이 오류는 Angular가 컴포넌트의 생성자 매개변수 타입을 리플렉션(metadata)으로 읽어 의존성을 생성해야 하는데, 그 타입 정보를 제대로 해석하지 못할 때 발생합니다.\n컴포넌트의 생성자 시그니처는 다음과 같습니다.\n1 constructor(private mobileService: MobileService) { } Angular는 컴파일 타임에 생성된 디자인 타입 메타데이터를 바탕으로 MobileService라는 타입을 주입합니다. 그런데 이 타입이 특정 방식으로 임포트되면 정상적으로 해석되지 못합니다.\n여기서 핵심 단서는 \u0026ldquo;다른 컴포넌트에는 잘 되고 한 컴포넌트에만 안 된다\u0026quot;는 것입니다. 주입 실패한 컴포넌트만 서비스를 배럴(barrel) 파일을 통해 임포트하고 있었다면, 배럴 파일에서의 재수출 문제를 의심할 수 있습니다.\n배럴 파일이란 여러 모듈을 한곳에서 재수출(export * from ...)하는 index.ts 파일을 말합니다. 예를 들어:\n1 2 3 // components/index.ts (배럴 파일) export * from \u0026#39;./header.component\u0026#39;; export * from \u0026#39;./mobile.service\u0026#39;; 이렇게 배럴로 임포트할 때:\n1 import { MobileService } from \u0026#39;./components\u0026#39;; // 배럴 경유 직접 파일에서 임포트할 때와:\n1 import { MobileService } from \u0026#39;./components/mobile.service\u0026#39;; // 직접 임포트 타입 해석 경로가 달라지며, 이 차이가 오류의 원인으로 이어집니다.\n3. 근본 원인 분석 StackOverflow에서 515점을 받은 채택 답변에 따르면, 이 오류의 근본 원인은 배럴(barrel) 파일의 재수출로 인한 순환 의존성(circular dependency) 입니다.\n1 2 3 컴포넌트 A ──(배럴 index.ts 재수출)──▶ MobileService ▲ │ └────────────(순환 참조)─────────────────┘ 배럴 파일이 서비스와 컴포넌트를 함께 재수출할 때, Angular의 의존성 주입 메타데이터가 로드되는 과정에서 순환 참조가 발생하여 MobileService 타입이 아직 완전히 정의되지 않은 상태로 읽히고, 결국 생성자 매개변수를 해석할 수 없게 됩니다.\n이것이 \u0026ldquo;코드에는 문제가 없는데 특정 컴포넌트에서만 주입이 실패하는\u0026rdquo; 이유입니다. 단순히 순수 TypeScript에서의 임포트 동작과 달리, Angular DI는 디자인 타입 메타데이터를 필요로 하기 때문에 모듈 로딩 순서(배럴 export 순서)에 민감한 것입니다.\n채택 답변은 정확한 메커니즘은 불분명하다고 하면서도, 순환 의존성 문제가 반복적으로 거론된다고 설명합니다.\n\u0026ldquo;I don\u0026rsquo;t know what exactly causes the issue but I saw it mentioned several times (probably some kind of circular dependency).\u0026rdquo;\n즉, 근본 원인은 배럴 파일에서 서비스와 컴포넌트를 함께 재수출하면서 생기는 순환 의존성입니다.\n4. 코드 해결책 이 문제를 해결하는 방법은 대표적으로 두 가지입니다.\n방법 1: 서비스를 선언된 원본 파일에서 직접 임포트 (권장) 배럴 파일을 거치지 않고, 서비스가 실제로 선언된 파일에서 직접 임포트합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 // 수정 전: 배럴 경유로 서비스 임포트 import { Component } from \u0026#39;@angular/core\u0026#39;; import { MobileService } from \u0026#39;./components\u0026#39;; // 배럴 파일 경유 — 오류 발생 // 수정 후: 선언된 원본 파일에서 직접 임포트 import { Component } from \u0026#39;@angular/core\u0026#39;; import { MobileService } from \u0026#39;./components/mobile.service\u0026#39;; // 직접 임포트 — 해결 @Component({ ... }) export class HeaderComponent { constructor(private mobileService: MobileService) { } } 이렇게 하면 배럴 재수출로 인한 순환 의존성을 우회하므로, Angular DI가 MobileService 타입을 정상적으로 해석할 수 있게 됩니다.\n방법 2: 배럴 파일의 export 순서 조정 배럴 파일을 꼭 사용해야 한다면, 순환 의존성이 걸리는 경로를 피하도록 export 순서를 조정합니다. 예를 들어 서비스가 의존하는 타입을 먼저 export하고, 의존하는 컴포넌트를 나중에 export하는 방식입니다.\n1 2 3 4 5 // components/index.ts // 순환 의존성이 생기지 않도록 서비스를 먼저 export export * from \u0026#39;./mobile.service\u0026#39;; // 이후 컴포넌트 export export * from \u0026#39;./header.component\u0026#39;; export 순서를 바꾸어도 문제가 해결되지 않는 경우가 있으므로, 가장 확실한 해결책은 방법 1(직접 임포트) 을 적용하는 것입니다.\n디버깅 표: 어떤 방법을 쓸까 상황 권장 해결책 단일 컴포넌트에서만 오류, 코드 문제 없음 방법 1: 서비스를 원본 파일에서 직접 임포트 배럴 파일을 유지해야 하는 경우 방법 2: 배럴 export 순서 조정 여러 컴포넌트에서 반복적으로 발생 배럴 패턴 자체를 점검하고 직접 임포트로 통일 5. 향후 예방 조치 이 오류를 다시 만나지 않으려면 다음 원칙을 지키면 됩니다.\n배럴 파일이 서비스와 컴포넌트를 함께 재수출하는 구조를 피합니다. 서비스처럼 DI로 주입되는 타입은 배럴을 거치지 않고 원본 파일에서 직접 임포트하는 것이 안전합니다.\n컴포넌트의 생성자 주입 대상은 항상 선언 파일에서 직접 임포트합니다.\n1 2 // 권장: 서비스 선언 파일에서 직접 임포트 import { MobileService } from \u0026#39;./mobile.service\u0026#39;; 배럴을 사용할 때는 export 순서에 순환 의존성이 없는지 확인합니다. 서비스 → 컴포넌트 순서로 모듈이 로드되도록 유지합니다.\n\u0026ldquo;일부 컴포넌트는 되고 일부는 안 된다\u0026quot;는 증상이 보이면 임포트 경로 차이(배럴 vs 직접)를 우선 의심합니다. 코드 로직 문제보다 모듈 로딩 순서 문제일 가능성이 높습니다.\n존재하는 모듈 구조를 유지하면서 해결책을 적용하면 대부분의 경우 즉시 해결됩니다. 핵심은 순환 의존성에 걸린 임포트 경로를 끊어 Angular DI가 서비스 타입을 정상적으로 해석하게 만드는 것입니다.\n출처: StackOverflow 37997824 — Error when trying to inject a service into an angular component \u0026ldquo;EXCEPTION: Can\u0026rsquo;t resolve all parameters for component\u0026rdquo;, why?\n","permalink":"https://tokkabi.com/posts/angular-service-injection-cant-resolve-parameters/","summary":"Angular 컴포넌트에 서비스를 주입할 때 \u0026lsquo;Can\u0026rsquo;t resolve all parameters for component\u0026rsquo; 오류 해결하기 1. 문제 정의 Angular 애플리케이션에서 컴포넌트의 생성자에 서비스를 주입하려는 순간, 다른 컴포넌트에는 정","title":"Angular 컴포넌트에 서비스를 주입할 때 'Can't resolve all parameters for component' 오류 해결하기"},{"content":"Python concurrent.futures Executor.map: 하나의 작업이 예외를 던지면 나머지 결과가 사라지는 문제 1. 문제 정의 concurrent.futures.ProcessPoolExecutor.map 또는 ThreadPoolExecutor.map으로 여러 작업을 병렬 처리할 때, 전달한 함수 중 하나라도 예외를 던지면 그 예외를 결과 반복자에서 꺼낸 순간 반복자가 종료되어, 이후에 정상적으로 완료된 나머지 작업들의 결과를 더 이상 얻을 수 없습니다.\nPython 공식 문서(concurrent.futures)에 따르면 Executor.map은 내장 map(func, *iterables)과 \u0026ldquo;유사\u0026quot;하며, 함수가 비동기로 실행되고 여러 호출이 동시에 일어난다는 점만 다릅니다. 따라서 한 요소에서 예외가 나더라도 내장 map처럼 이후 요소의 결과는 계속 얻을 수 있어야 합니다. 그러나 실제 동작은 그렇지 않습니다.\n관련 코드 / 재현 예시 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import concurrent.futures import contextlib def add_one(value): if value == 2: raise ValueError() return value + 1 def test(executor_cm): with executor_cm as executor: values = executor.map(add_one, (1, 2, 3)) values_it = iter(values) try: while True: try: value = next(values_it) except ValueError: print(\u0026#34;value error\u0026#34;) else: print(f\u0026#34;value: {value}\u0026#34;) except StopIteration: pass print(\u0026#34;ProcessPoolExecutor\u0026#34;) test(concurrent.futures.ProcessPoolExecutor()) print(\u0026#34;\\nThreadPoolExecutor\u0026#34;) test(concurrent.futures.ThreadPoolExecutor()) print(\u0026#34;\\nBuilt-in map function\u0026#34;) class fake_executor: map = staticmethod(map) test(contextlib.nullcontext(fake_executor)) 실제 출력 (버그가 있는 3.11 이하) 1 2 3 4 5 6 7 8 9 10 11 12 ProcessPoolExecutor value: 2 value error ThreadPoolExecutor value: 2 value error Built-in map function value: 2 value error value: 4 기대 출력 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ProcessPoolExecutor value: 2 value error value: 4 ThreadPoolExecutor value: 2 value error value: 4 Built-in map function value: 2 value error value: 4 시퀀스 (1, 2, 3)을 add_one에 넘기면 결과는 2, 3, 4가 되어야 합니다. 여기서 value == 2일 때 ValueError가 발생합니다. 내장 map은 그 뒤에 있는 value: 4(3 → 4)까지 정상 출력하지만, Executor.map은 value error 다음에 바로 종료되어 value: 4가 출력되지 않습니다. 즉 하나의 예외가 나머지 정상 결과를 모두 삼켜버립니다.\n테스트 환경: Python 3.11.5 (Linux), 문제는 사실상 모든 지원 버전에서 발생. 2. 원인 탐구 왜 이런 일이 발생할까요? 가장 먼저 떠오르는 의심은 \u0026ldquo;예외의 전파 방식\u0026quot;입니다.\n내장 map은 지연(lazy) 평가 — 각 요소를 순회하며 즉시 함수를 호출합니다. 한 요소에서 예외가 나도 상위 레벨에서 try/except로 잡으면 다음 next() 호출에서 이어서 진행할 수 있습니다. Executor.map은 비동기 실행 — 내장 map과 달리 모든 입력을 먼저 수집하고, 각 함수 호출을 별도의 쓰레드/프로세스에 제출한 뒤, 결과를 저장해 둡니다. 따라서 원리적으로는 예외가 나더라도 나머지 이미 완료된 결과를 꺼낼 수 있어야 합니다. 이 발상에 착안해, 원인은 \u0026ldquo;예외가 발생한 작업 자체\u0026quot;가 아니라 결과를 순회하는 반복자(result_iterator)의 구현 방식에 있을 것으로 의심할 수 있습니다.\n3. 근본 원인 분석 근본 원인은 concurrent/futures/_base.py의 result_iterator가 제너레이터 함수(generator function)로 구현되어 있다는 점입니다 (참고: 이슈에서 가리킨 지점은 Lib/concurrent/futures/_base.py L612-L625 부근).\nPython에서 제너레이터 함수 내부에서 어떤 예외가 발생하면 그 제너레이터는 그 상태에서 더 이상 재개(resume)할 수 없습니다. next()를 다시 호출하면 StopIteration이 발생하고, 남아 있던 코드는 실행되지 않습니다.\nExecutor.map의 result_iterator도 동일한 패턴입니다. next()가 처리 중인 future의 결과를 꺼낼 때 해당 작업이 예외로 끝났다면, 그 예외를 호출자에게 전달하는 과정에서 제너레이터 내부에서 예외가 발생하고, 결과적으로 반복자 전체가 끝나버립니다. 따라서 이미 완료되어 정상 값을 가지고 있던 이후 future들의 결과에도 접근할 수 없게 됩니다.\n이 동작은 명백히 문서화된 동작(\u0026ldquo;내장 map과 유사\u0026rdquo;)과 모순됩니다. 이슈(FYI)와 참여자 논의에서도 확인된 내용입니다:\n참여자(sterliakov)의 분석: \u0026ldquo;제너레이터 함수는 내부에서 예외가 발생하면 그 지점으로 돌아갈 수 없으며, 현재 동작은 문서와 분명히 모순된다. 문제는 모든 지원 버전에 적용된다.\u0026rdquo; 제안된 해결책: result_iterator를 __next__ 메서드를 가진 클래스로 구현하는 것. (단, 이는 breaking change라서 concurrent.futures 전문가의 검토가 필요.) 종합하면 근본 원인은 다음과 같습니다.\n구분 내용 증상 Executor.map 반복자에서 예외를 꺼낸 뒤 나머지 결과를 얻지 못함 근본 원인 result_iterator가 제너레이터 함수로 구현됨 → 내부 예외 발생 시 재개 불가 내장 map과의 차이 map은 지연 평가라 예외를 건너뛰고 다음 요소를 이어 나감 영향 범위 ProcessPoolExecutor, ThreadPoolExecutor 모두 해당, 모든 지원 버전 공식 수정 Python 3.16에서 내장 map과 일치하도록 동작 변경 (CPython #108518 / PR #109497) 4. 코드 해결책 이 문제는 표준 라이브러리의 동작이므로, 사용자가 직접 result_iterator를 고칠 수는 없습니다. Python 3.16부터는 내장 map()과 일치하는 동작으로 공식 수정되었습니다. 그 이전 버전에서는 아래와 같은 방법으로 우회할 수 있습니다.\n방법 A — 콜러블 내부에서 예외 자체를 처리 (권장) 가장 간단하고 확실한 방법입니다. 반복자가 예외를 던지지 않도록, 작업 함수 안에서 예외를 직접 처리하고 반환 값으로 상태를 돌려줍니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import concurrent.futures def safe_add_one(value): try: if value == 2: raise ValueError() return (\u0026#34;ok\u0026#34;, value + 1) except ValueError as e: return (\u0026#34;error\u0026#34;, str(e)) with concurrent.futures.ProcessPoolExecutor() as executor: results = list(executor.map(safe_add_one, (1, 2, 3))) for status, payload in results: if status == \u0026#34;ok\u0026#34;: print(f\u0026#34;value: {payload}\u0026#34;) # 2, 4 else: print(\u0026#34;value error\u0026#34;) # 2에 대한 오류 방법 B — executor.submit + Future.result()로 개별 제어 submit()으로 각 작업을 개별 Future로 제출하면, 한 Future의 예외가 다른 Future에 영향을 주지 않습니다. 예외는 필요할 때 Future.result()에서만 발생합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import concurrent.futures def add_one(value): if value == 2: raise ValueError() return value + 1 with concurrent.futures.ProcessPoolExecutor() as executor: futures = [executor.submit(add_one, v) for v in (1, 2, 3)] for f in futures: try: print(f\u0026#34;value: {f.result()}\u0026#34;) except ValueError: print(\u0026#34;value error\u0026#34;) # 출력: value: 2 / value error / value: 4 방법 C — (Option) 결과를 한 번에 수집 list(executor.map(...))으로 결과를 한 번에 소비하면, 어느 한 요소에서 예외가 발생해도 그 예외 자체가 상위로 전파되며 다른 결과는 얻지 못합니다. 따라서 \u0026ldquo;예외를 무시하고 나머지 결과만 얻으려는\u0026rdquo; 경우에는 적합하지 않고, \u0026ldquo;아무 작업이라도 실패하면 전체 실패로 처리\u0026quot;하는 시맨틱이 필요할 때 사용합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 import concurrent.futures def add_one(value): if value == 2: raise ValueError() return value + 1 with concurrent.futures.ThreadPoolExecutor() as executor: try: results = list(executor.map(add_one, (1, 2, 3))) except ValueError: print(\u0026#34;하나의 작업이 실패하여 전체 map 실패\u0026#34;) 공식 수정 (Python 3.16+) Python 3.16부터 Executor.map()은 내장 map()과 동일하게 동작하도록 변경되어, 예외가 발생해도 이후 결과를 계속 얻을 수 있습니다. 이 항목은 CPython 이슈 #108518와 병합된 PR #109497(Make concurrent.futures.Executor.map() consistent with built-in map())로 구현되었습니다. 이슈 댓글에서도 \u0026ldquo;behavior changed in 3.16\u0026quot;으로 확인됩니다.\n가능하다면 Python 3.16 이상으로 업그레이드해 이 동작 차이 자체를 없애는 것이 가장 근본적인 해결책입니다.\n5. 향후 예방 조치 Executor.map의 반복자를 예외로부터 보호 — 병렬 작업의 함수가 예외를 던질 수 있다면, 함수 내부에서 예외를 잡고 상태를 반환 값으로 표현하는 패턴(방법 A)을 기본으로 사용합니다. 예외를 \u0026ldquo;값\u0026quot;으로 다루기 — (\u0026quot;ok\u0026quot;, value) / (\u0026quot;error\u0026quot;, msg) 같은 구조로 반환해 반복자 단계에서는 예외가 발생하지 않도록 설계합니다. 개별 제어가 필요하면 submit() 사용 — 각 작업의 성공/실패를 독립적으로 처리해야 한다면 map 대신 submit() + future.result()를 사용합니다. 버전 확인 — 대상 런타임의 Python 버전이 3.16 미만인지 확인하고, 그렇다면 위 우회 방법을 적용합니다. 3.16 이상이면 이 문제는 이미 수정되었습니다. 문서와 일치하는지 점검 — 표준 라이브러리라고 해서 문서가 항상 실제 동작과 같다고 단정하지 말고, 내장 함수와의 차이(지연 평가 vs. 비동기 실행)를 인지하고 테스트로 검증합니다. 요약 항목 내용 문제 Executor.map에서 하나의 작업이 예외를 던지면 결과 반복자가 종료되어 나머지 결과를 얻지 못함 근본 원인 result_iterator가 제너레이터 함수 → 내부 예외 발생 후 재개 불가, 문서와 모순 실제 영향 ProcessPoolExecutor·ThreadPoolExecutor 모두, Python 3.11 이하 모든 버전 우회 방법 함수 내부 예외 처리 / submit + result() 개별 제어 / 리스트 수집 공식 수정 Python 3.16에서 내장 map과 일치하도록 변경 (CPython #108518) 출처\nhttps://github.com/python/cpython/issues/108518 https://github.com/python/cpython/pull/109497 https://docs.python.org/3/library/concurrent.futures.html https://github.com/python/cpython/blob/d08d49dd09917531b113486976b03b5287b574f1/Lib/concurrent/futures/_base.py ","permalink":"https://tokkabi.com/posts/concurrentfutures-executormap-cancels-other-futures-when-one/","summary":"Python concurrent.futures Executor.map: 하나의 작업이 예외를 던지면 나머지 결과가 사라지는 문제 1. 문제 정의 concurrent.futures.ProcessPoolExecutor.map 또는 ThreadPoolExecutor.map으로 여러 작업을 병렬 처리할 때","title":"Python concurrent.futures Executor.map: 하나의 작업이 예외를 던지면 나머지 결과가 사라지는 문제"},{"content":"JSON.parse 예외 안전하게 처리하기 — try-catch로 404/무효 JSON 오류 잡기 1. 문제 정의 JSON.parse는 문자열을 JavaScript 객체로 변환하는 내장 함수이지만, 인자로 들어온 문자열이 유효한 JSON이 아니면 예외(SyntaxError)를 던진다.\n실무에서는 서버 응답을 JSON.parse로 파싱할 때가 많다. 특히 아래 질문처럼 XMLHttpRequest로 요청을 보냈는데 응답이 404인 경우, 응답 본문이 JSON이 아니기 때문에 파싱이 실패한다.\n1 2 3 4 5 6 7 8 9 10 var data = JSON.parse(response, function (key, value) { var type; if (value \u0026amp;\u0026amp; typeof value === \u0026#39;object\u0026#39;) { type = value.type; if (typeof type === \u0026#39;string\u0026#39; \u0026amp;\u0026amp; typeof window[type] === \u0026#39;function\u0026#39;) { return new(window[type])(value); } } return value; }); response에 404 본문(HTML 등)이 들어오면 이 코드는 JSON.parse에서 예외를 던지고, 처리되지 않은 예외(uncaught exception)로 이어져 이후 코드가 실행되지 않는다.\n2. 원인 탐구 왜 이런 예외가 발생하는지 이해하기 위해 JSON.parse의 동작을 살펴본다.\n상황 response의 실제 내용 JSON.parse 결과 정상 응답 {\u0026quot;id\u0026quot;: 1, \u0026quot;type\u0026quot;: \u0026quot;Foo\u0026quot;} 객체 반환 (성공) 404 응답 HTML 또는 빈 문자열 SyntaxError 예외 발생 잘못된 문자열 not json at all SyntaxError 예외 발생 빈 문자열 \u0026quot;\u0026quot; SyntaxError 예외 발생 핵심 요점: JSON.parse는 실패 시 결과를 반환하지 않고 예외를 던진다. 즉 \u0026ldquo;파싱 실패 여부를 리턴값으로 확인하는\u0026rdquo; 방식은 불가능하다. 실패는 반드시 예외로만 신호된다.\n따라서 404 응답처럼 본문이 JSON이 아닌 경우를 대비하지 않으면 처리되지 않은 예외가 발생한다.\n3. 근본 원인 분석 근본 원인은 두 가지가 결합된 것이다.\nJSON.parse의 실패 신호 방식 — 파싱 실패는 반환값이 아니라 예외(SyntaxError)로 전달된다. 호출부가 try-catch로 감싸지 않으면 예외가 그대로 상위로 전파되어 크래시를 일으킨다. 응답 본문이 항상 JSON이라는 보장이 없다 — 404/500 응답은 HTTP 상태 코드가 다를 뿐 아니라 본문 형식도 JSON이 아닐 수 있다(서버 에러 페이지 HTML 등). 요청 성공 여부를 response 내용만으로 가정하면 안 된다. 정리하면, \u0026ldquo;파싱을 시도했는데 그 문자열이 JSON이 아닐 수 있다\u0026quot;는 사실을 전제로 코드를 작성해야 하며, 이를 위해 파싱 실패 경로를 예외 처리로 감싸는 것이 정석이다.\n4. 코드 해결책 가장 간단하고 널리 쓰이는 해결책은 JSON.parse를 try-catch 블록으로 감싸는 것이다.\n1 2 3 4 5 6 7 8 9 10 if (response) { let a; try { a = JSON.parse(response); } catch (e) { return console.error(e); // 파싱 실패 (여기서는 그럴 수 있음!) } // 예외가 없다면 이제 \u0026#34;a\u0026#34;를 안전하게 사용할 수 있다 // a를 사용하는 코드는 try 블록 안/뒤에서 실행 } 이 패턴의 동작 방식:\nresponse가 비어 있지 않은지 먼저 확인한다 (if (response)). JSON.parse는 try 블록 안에서 실행된다. 파싱이 성공하면 a에 객체가 할당되고, try 블록을 정상 통과해 이후 코드가 실행된다. 파싱이 실패하면 throw된 예외가 catch (e)로 잡히고, console.error(e)로 기록한 뒤 return하여 크래시 없이 함수를 끝낸다. 파싱된 결과를 사용하는 코드의 올바른 위치 성공한 파싱 결과 a를 사용하는 코드는 반드시 try-catch 뒤(또는 try 블록 안)에 두어야 한다. catch에서 return하지 않으면 a가 undefined인 채로 아래 코드가 실행될 수 있다.\n1 2 3 4 5 6 7 8 9 if (response) { try { const data = JSON.parse(response); // 파싱 성공 시에만 실행되는 로직 render(data); } catch (e) { console.error(\u0026#39;JSON 파싱 실패:\u0026#39;, e, response); } } 더 엄격한 방식: 파싱 실패를 명시적으로 처리 상황에 따라 404 등을 별도로 분기하고 싶다면, try-catch로 감싸는 동시에 응답 상태 코드를 함께 확인할 수도 있다.\n1 2 3 4 5 6 7 try { const data = JSON.parse(xhr.responseText); handleSuccess(data); } catch (e) { // 무효 JSON (빈 본문, HTML 에러 페이지 등) handleParseError(e); } 5. 향후 예방 조치 같은 예외를 다시 만나지 않으려면 아래 원칙을 지킨다.\n무효할 수 있는 JSON에는 반드시 try-catch를 쓴다. 서버 응답처럼 \u0026ldquo;항상 유효한 JSON\u0026quot;이 보장되지 않는 문자열에는 JSON.parse를 반드시 try-catch로 감싼다. 파싱 전에 응답 상태/형식을 확인한다. XHR/fetch를 쓴다면 응답이 성공(2xx)이고 콘텐츠 타입이 JSON인지 먼저 확인한 뒤 파싱한다. 파싱 결과는 try 블록 안에서만 사용한다. catch에서 return을 빠뜨리면 undefined가 아래로 흘러가 2차 오류가 난다. 오류를 삼키지 말고 기록한다. catch (e) { /* 빈 블록 */ }은 디버깅을 어렵게 만든다. 최소한 console.error(e)처럼 로그를 남긴다. 에러 처리 영역을 헬퍼로 추출한다. 반복되는 파싱에는 안전한 safeParse 같은 헬퍼 함수를 만들어 일관되게 처리한다. 1 2 3 4 5 6 7 8 9 function safeParse(text) { if (!text) return null; try { return JSON.parse(text); } catch (e) { console.error(\u0026#39;JSON 파싱 실패:\u0026#39;, e, text); return null; } } 출처 StackOverflow 4467044 — Proper way to catch exception from JSON.parse ","permalink":"https://tokkabi.com/posts/proper-way-to-catch-exception-from-jsonparse/","summary":"JSON.parse 예외 안전하게 처리하기 — try-catch로 404/무효 JSON 오류 잡기 1. 문제 정의 JSON.parse는 문자열을 JavaScript 객체로 변환하는 내장 함수이지만, 인","title":"JSON.parse 예외 안전하게 처리하기 — try-catch로 404/무효 JSON 오류 잡기"},{"content":"브라우저에서만 발생하는 CORS 오류 \u0026lsquo;No Access-Control-Allow-Origin Header\u0026rsquo; — Postman은 왜 정상일까? 1. 문제 정의 프론트엔드 JavaScript 코드에서 다른 도메인의 서버로 요청을 보낼 때, 다음과 같은 오류가 브라우저에서만 발생하는 경우가 많습니다.\n1 No \u0026#39;Access-Control-Allow-Origin\u0026#39; header is present on the requested resource. 대표적인 발생 상황은 다음과 같습니다.\n페이지가 호스팅된 도메인(예: https://myfrontend.com)과 다른 도메인(예: https://api.example.com)으로 XMLHttpRequest 또는 fetch를 보낼 때 동일한 요청을 Postman, curl 등 클라이언트 도구로 보내면 아무 문제 없이 정상 응답을 받는데, 브라우저에서만 위 오류가 발생 이때 핵심 의문은 \u0026ldquo;요청과 서버는 같은데 왜 브라우저에서만 오류가 나는가?\u0026ldquo;입니다. 이는 오류 자체의 해결 방법보다 왜 이런 차이가 발생하는지를 이해하는 것이 더 중요합니다.\n2. 원인 탐구 첫 번째 단서는 \u0026ldquo;어디서 오류가 발생하는가\u0026quot;입니다.\nPostman은 정상 응답을 받는다. 브라우저에서만 오류가 발생한다. 서버가 요청을 거부해서 오류가 났다면, Postman과 curl에서도 동일하게 실패했을 것입니다. 그런데 클라이언트 도구에서는 성공하고 브라우저에서만 실패한다는 것은, 서버가 요청을 처리했지만 브라우저가 응답을 차단했음을 의미합니다.\n두 번째 단서는 브라우저가 무언가 다른 보안 규칙을 적용한다는 점입니다. 이 규칙이 바로 Same-Origin Policy(동일 출처 정책) 입니다.\n정리하면 오류의 원인 후보는 다음과 같습니다.\n가정 검증 서버가 요청을 거부함 ❌ Postman과 curl은 정상 응답을 받음 네트워크/인증 문제 ❌ 동일 요청이 클라이언트 도구에서는 성공 브라우저의 Same-Origin Policy가 응답을 차단 ✅ 브라우저에서만 발생함 따라서 오류는 서버가 아니라 브라우저의 보안 정책 단계에서 발생합니다.\n3. 근본 원인 분석 근본 원인은 브라우저와 API 클라이언트의 본질적인 차이입니다.\n브라우저는 웹 페이지가 다른 출처의 리소스를 읽는 것을 막는 Same-Origin Policy를 적용합니다. 정규 웹 페이지는 XMLHttpRequest 객체로 원격 서버와 데이터를 주고받을 수 있지만, 동일 출처(Same Origin) 제한을 받습니다. 즉, 현재 페이지와 다른 출처(Cross-Origin) 로의 요청은 기본적으로 차단됩니다. Postman은 브라우저가 아닙니다. 요청을 보내는 주체가 브라우저가 아니므로 Same-Origin Policy의 적용을 받지 않습니다. 따라서 교차 출처 요청이든 무엇이든 서버가 응답하는 대로 그대로 받아서 보여줍니다. 이 때문에 \u0026ldquo;같은 요청인데 브라우저에서만 오류, Postman은 정상\u0026quot;이라는 현상이 발생합니다. 브라우저는 교차 출처 응답에 접근하기 전에 서버가 \u0026ldquo;이 출처를 허용한다\u0026quot;는 신호를 보냈는지 확인하며, 그 신호가 바로 CORS(Cross-Origin Resource Sharing) 헤더입니다.\n응답에 Access-Control-Allow-Origin 헤더가 없으면 브라우저는 해당 응답을 차단하고 위 오류를 던집니다. 반대로 Postman은 이런 출처 검증을 하지 않으므로 오류가 발생하지 않습니다.\n4. 코드 해결책 핵심은 서버가 응답에 CORS 헤더를 포함하도록 만드는 것입니다. 프론트엔드 JavaScript만으로는 이 오류를 우회할 수 없습니다(보안상 브라우저는 이를 허용하지 않습니다).\n교차 출처 요청이 CORS를 통과하려면 서버 응답에 다음 헤더가 필요합니다.\n1 Access-Control-Allow-Origin: https://myfrontend.com 특정 출처 대신 모든 출처를 허용하려면 와일드카드를 사용합니다.\n1 Access-Control-Allow-Origin: * 참고: *는 자격 증명(credentials)이 포함된 요청에는 사용할 수 없으며, 그 경우 명시적 출처를 지정해야 합니다.\n프레임워크별 서버 설정 예시 Express(Node.js) 1 2 3 4 5 6 7 8 9 10 11 const express = require(\u0026#39;express\u0026#39;); const app = express(); app.use((req, res, next) =\u0026gt; { res.setHeader(\u0026#39;Access-Control-Allow-Origin\u0026#39;, \u0026#39;https://myfrontend.com\u0026#39;); next(); }); app.get(\u0026#39;/api/data\u0026#39;, (req, res) =\u0026gt; { res.json({ message: \u0026#39;ok\u0026#39; }); }); 전용 미들웨어인 cors 패키지를 사용할 수도 있습니다.\n1 2 const cors = require(\u0026#39;cors\u0026#39;); app.use(cors()); // 모든 출처 허용 Python Flask 1 2 3 4 5 6 7 8 9 from flask import Flask, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) # 모든 출처 허용 @app.route(\u0026#39;/api/data\u0026#39;) def data(): return jsonify(message=\u0026#39;ok\u0026#39;) Django 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # settings.py INSTALLED_APPS = [ # ... \u0026#39;corsheaders\u0026#39;, ] MIDDLEWARE = [ # ... \u0026#39;corsheaders.middleware.CorsMiddleware\u0026#39;, ] CORS_ALLOWED_ORIGINS = [ \u0026#39;https://myfrontend.com\u0026#39;, ] 사전 요청(Preflight) 주의사항 실제 요청이 application/json Content-Type이나 커스텀 헤더를 사용하는 등 \u0026lsquo;단순 요청(Simple Request)\u0026rsquo; 조건을 벗어나면, 브라우저는 본 요청 전에 **OPTIONS 메서드의 사전 요청(Preflight)**을 먼저 보냅니다. 서버가 이에 응답하지 않으면 동일하게 CORS 오류가 발생합니다.\n사전 요청 응답에 필요한 헤더 예시:\n1 2 3 Access-Control-Allow-Origin: https://myfrontend.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization 디버깅 체크리스트 체크 항목 확인 방법 응답에 Access-Control-Allow-Origin 헤더가 있는가 개발자 도구 \u0026gt; Network 탭 \u0026gt; 응답 헤더 확인 현재 페이지 출처가 허용 목록에 포함되는가 요청 출처와 헤더 값 비교 Preflight(OPTIONS)가 정상 응답하는가 Network 탭에서 OPTIONS 요청 확인 자격 증명 요청인가 credentials: 'include' 사용 시 * 불가, 명시적 출처 필요 5. 향후 예방 조치 같은 문제를 반복하지 않으려면 다음을 기억하세요.\n\u0026ldquo;Postman은 정상인데 브라우저만 오류\u0026quot;는 곧 CORS 문제다. 서버가 요청을 거부한 것이 아니라 브라우저가 출처 검증 후 응답을 차단한 것이다. 먼저 서버가 CORS 헤더를 보내는지 확인하세요. 셋업 시점에 CORS를 고려하라. 프론트엔드와 백엔드 도메인이 다른 구조라면 서버 코드를 작성할 때 처음부터 Access-Control-Allow-Origin 처리를 포함하세요. 보통 프레임워크의 CORS 미들웨어를 활성화하는 것만으로 해결됩니다. 환경별 출처를 명시적으로 관리하라. 개발/스테이징/운영 환경마다 허용할 프론트엔드 출처가 다릅니다. 환경 변수로 출처 목록을 관리하면 잘못된 설정을 줄일 수 있습니다. 모든 출처 허용(*)은 개발용으로만 사용하라. 운영 환경에서 모든 출처를 허용하면 보안 위험이 있습니다. 반드시 신뢰할 수 있는 출처만 명시하세요. 자격 증명을 쓰는 요청은 명시적 출처를 사용하라. 인증 쿠키 등 credentials를 포함한 요청에는 와일드카드가 동작하지 않으며, 서버에서 정확한 출처를 명시해야 합니다. 오류 메시지를 그대로 검색하지 말고 \u0026ldquo;왜 발생하는지\u0026quot;를 먼저 이해하라. 선언한 프레임워크가 다르면 설정 방법이 달라지지만, 근본 원리(브라우저의 Same-Origin Policy + 서버의 CORS 헤더)는 동일합니다. 참고 출처 원문 질문: Why does my JavaScript code receive a \u0026ldquo;No \u0026lsquo;Access-Control-Allow-Origin\u0026rsquo; header is present on the requested resource\u0026rdquo; error, while Postman does not? — StackOverflow ","permalink":"https://tokkabi.com/posts/javascript-cors-access-control-allow-origin-error/","summary":"브라우저에서만 발생하는 CORS 오류 \u0026lsquo;No Access-Control-Allow-Origin Header\u0026rsquo; — Postman은 왜 정상일까? 1. 문제 정의 프론트엔드 JavaScript 코드에서 다른 도메인의 서버로 요청을 보낼 때, 다음과 같은 오류","title":"브라우저에서만 발생하는 CORS 오류 'No Access-Control-Allow-Origin Header' — Postman은 왜 정상일까?"},{"content":"Jest에서 예외 타입 테스트하는 방법 — toThrow로 TypeError/message 검증 JavaScript 함수가 특정 예외를 던지는지, 그리고 그 예외의 타입(TypeError, ReferenceError 등) 까지 맞는지 테스트하는 것은 견고한 유닛 테스트의 필수 요소입니다. 이 글에서는 Jest에서 예외 타입과 메시지를 검증하는 방법을 5단계로 정리합니다. 특히 AVA(Test Anything)의 t.throws 문법에 익숙한 개발자가 Jest로 마이그레이션할 때 자주 헷갈리는 부분을 중심으로 설명합니다.\n1. 문제 정의 기존에 AVA를 사용하던 환경에서는 t.throws의 두 번째 인자로 예외 타입을 넘겨 검증할 수 있었습니다.\n1 2 3 4 5 6 7 8 // AVA 기준 it(\u0026#39;should throw Error with message \\\u0026#39;UNKNOWN ERROR\\\u0026#39; when no params were passed\u0026#39;, (t) =\u0026gt; { const error = t.throws(() =\u0026gt; { throwError(); }, TypeError); t.is(error.message, \u0026#39;UNKNOWN ERROR\u0026#39;); }); 이 코드를 Jest로 다시 작성하려고 할 때, t.throws처럼 예외 타입을 직접 지정하는 문법이 눈에 띄지 않아 어떻게 해야 할지 막막한 상황입니다.\n핵심 질문: \u0026ldquo;Jest에서 던져진 예외가 TypeError, ReferenceError 등 어떤 타입인지 테스트할 수 있는가?\u0026rdquo;\n2. 원인 탐구 Jest에는 AVA의 t.throws와 1:1로 대응하는 \u0026ldquo;예외 타입을 두 번째 인자로 받는\u0026rdquo; 메서드가 없습니다. 대신 Jest는 매처(matcher) 방식, 즉 expect()에 함수를 전달하고 .toThrow() 매처를 호출하는 방식으로 동일한 검증을 수행합니다.\n이 구조적 차이가 \u0026ldquo;Jest에서 예외 타입을 어떻게 테스트하지?\u0026ldquo;라는 혼란의 근본입니다. AVA는 함수를 실행하고 그 결과(던져진 예외)를 반환하는 반면, Jest는 expect에 실행하지 않은 함수 참조를 넘겨야 한다는 점이 핵심입니다.\n3. 근본 원인 분석 혼란의 진짜 원인은 다음 두 가지입니다.\n① expect는 함수 자체를 받아야 한다\n.toThrow()는 expect에 전달된 값이 (a) 실행됐을 때 예외를 던지는 함수이거나, (b) new Error에 넣을 수 있는 메시지 문자열, (c) 예외 생성자(Error subclass)여야 합니다. 만약 expect(x())처럼 함수를 호출한 결과를 넘기면, JavaScript는 expect가 실행되기 전에 그 줄에서 x()를 먼저 호출해 버려 예외가 테스트 코드 밖에서 즉시 발생하고 테스트가 실패합니다.\n1 2 3 4 5 // ❌ 틀린 예 — x()가 먼저 실행되어 예외가 밖에서 발생 expect(throwError()).toThrow(TypeError); // ✅ 올바른 예 — 함수 참조를 넘겨 toThrow가 내부에서 실행 expect(() =\u0026gt; throwError()).toThrow(TypeError); ② AVA와 Jest의 문법적 관용구가 다르다\nAVA는 t.throws(fn, TypeError)로 \u0026ldquo;타입을 두 번째 인자\u0026quot;로 받지만, Jest는 expect(fn).toThrow(TypeError)로 \u0026ldquo;타입을 매처의 인자\u0026quot;로 받습니다. 같은 목표를 향한 표현 방식의 차이일 뿐, 둘 다 예외 타입 검증이 가능합니다.\n구분 AVA Jest 예외 타입 검증 t.throws(fn, TypeError) expect(fn).toThrow(TypeError) 메시지 검증 t.is(error.message, '...') expect(fn).toThrow('...') 전달 대상 함수를 실행해 예외를 반환 함수 참조를 expect에 전달 4. 코드 해결책 4-1. 예외 타입만 검증하기 expect(function).toThrow(예외 생성자) 형태로 함수를 전달합니다.\n1 2 3 4 5 6 test(\u0026#34;Test description\u0026#34;, () =\u0026gt; { const t = () =\u0026gt; { throw new TypeError(); }; expect(t).toThrow(TypeError); }); toThrow의 인자로 TypeError, ReferenceError 등 Error의 하위 클래스 생성자를 넣으면 해당 타입과 일치할 때만 통과합니다.\n4-2. 예외 타입과 메시지 함께 검증하기 타입과 메시지를 각각 toThrow로 검증합니다. 같은 함수를 여러 번 호출해도 내부에서 예외를 던지기만 하면 되므로 안전합니다.\n1 2 3 4 5 6 7 test(\u0026#34;Test description\u0026#34;, () =\u0026gt; { const t = () =\u0026gt; { throw new TypeError(\u0026#34;UNKNOWN ERROR\u0026#34;); }; expect(t).toThrow(TypeError); // 타입 검증 expect(t).toThrow(\u0026#34;UNKNOWN ERROR\u0026#34;); // 메시지 검증 (부분 일치) }); toThrow(\u0026quot;UNKNOWN ERROR\u0026quot;)는 에러 메시지에 해당 문자열이 포함되어 있으면 통과합니다. 정확히 일치하는 메시지를 검증하고 싶다면 toThrow 대신 toThrowError 또는 정규식(/UNKNOWN ERROR/)을 활용할 수 있습니다.\n4-3. 기존 함수(인자 포함) 테스트하기 이미 존재하는 함수가 특정 인자로 호출될 때 예외를 던지는지 확인하려면, 그 함수를 익명 함수로 감싸서 expect에 넘겨야 합니다.\n1 2 3 4 5 test(\u0026#34;Test description\u0026#34;, () =\u0026gt; { expect(() =\u0026gt; { http.get(yourUrl, yourCallbackFn); }).toThrow(TypeError); }); 익명 함수 래퍼가 필요한 이유는 바로 위 \u0026ldquo;근본 원인 분석\u0026quot;에서 설명했듯, expect가 실행될 함수 참조를 요구하기 때문입니다. expect(http.get(yourUrl, yourCallbackFn)).toThrow(...)처럼 쓰면 http.get이 먼저 실행되어버립니다.\n4-4. (참고) 비동기 함수의 예외 테스트 비동기 함수가 거부(reject)하는 경우에는 toThrow 대신 .rejects.toThrow를 사용합니다.\n1 await expect(yourAsyncFn(...)).rejects.toThrow(TypeError); 5. 향후 예방 조치 항상 함수 참조를 넘긴다 — expect(비동기가 아닌 함수).toThrow(...)에서 괄호를 실수로 붙이지 않도록 expect(() =\u0026gt; fn()) 형태의 익명 함수 래퍼를 기본 습관으로 삼습니다. 타입과 메시지를 각각 검증한다 — 타입만 확인하지 말고 toThrow(TypeError)와 toThrow('메시지')를 함께 사용해 더 명확한 실패 원인을 얻습니다. 검증 기준을 정한다 — 메시지가 자주 바뀐다면 전체 문자열보다 정규식(/UNKNOWN ERROR/)이나 부분 문자열로 검증해 테스트의 취약성을 줄입니다. 비동기 예외는 rejects를 사용한다 — async 함수는 await expect(...).rejects.toThrow(...)로 작성합니다. eslint-plugin-jest 사용 시 유의 — try/catch 기반 방식은 no-conditional-expect 규칙에 걸릴 수 있으므로, 기본적으로 toThrow 매처 방식을 권장합니다. 참고 출처: StackOverflow — How to test the type of a thrown exception in Jest (질문 46042613, 채택 답변 득표 845+)\n","permalink":"https://tokkabi.com/posts/how-to-test-the-type-of-a-thrown-exception-in-jest/","summary":"Jest에서 예외 타입 테스트하는 방법 — toThrow로 TypeError/message 검증 JavaScript 함수가 특정 예외를 던지는지, 그리고 그 예외의 타입(TypeError, ReferenceError 등) 까지 맞는","title":"Jest에서 예외 타입 테스트하는 방법 — toThrow로 TypeError/message 검증"},{"content":"자바스크립트에서 직접 throw한 예외의 스택 트레이스를 얻는 방법 1. 문제 정의 자바스크립트에서 예외를 **직접 throw**할 때, 브라우저(Firebug 등) 콘솔에는 단순한 에러 메시지만 출력되고 스택 트레이스(함수가 어디서 어떻게 호출됐는지의 경로)가 보이지 않는 경우가 있습니다.\n에러 원문은 다음과 같습니다.\nIf I throw a JavaScript exception myself (eg, throw \u0026quot;AArrggg\u0026quot;), how can I get the stack trace (in Firebug or otherwise)? Right now I just get the message.\n즉, 아래처럼 재귀적으로 호출하는 함수에서 예외가 발생했을 때, 어떤 경로를 거쳐 이 예외가 던져졌는지를 알고 싶은 상황입니다.\n1 2 3 4 5 6 7 8 9 10 function foo() { bar(2); } function bar(n) { if (n \u0026lt; 2) throw \u0026#34;Oh no! \u0026#39;n\u0026#39; is too small!\u0026#34; bar(n-1); } foo(); 이 코드에서 bar(1)이 호출되는 순간 예외가 던져지지만, 브라우저는 던진 문자열(\u0026quot;Oh no! 'n' is too small!\u0026quot;)만 보여줄 뿐 foo → bar → bar로 이어진 호출 체인을 함께 보여주지 않습니다.\n증상 설명 출력 내용 에러 메시지 텍스트만 표시 누락되는 정보 호출한 함수 이름, 소스 URL, 호출 체인 (스택 트레이스) 발생 조건 throw로 직접 예외를 던질 때 2. 원인 탐구 예외를 throw할 때 스택 트레이스가 안 보이는 이유는, 직접 던진 예외가 자바스크립트 엔진이 생성한 Error 객체가 아니기 때문입니다.\n엔진이 내부적으로 발생시킨 예외(예: TypeError, ReferenceError)는 언어 런타임이 예외를 만들 때 그 시점의 호출 스택을 함께 기록합니다. 반면 throw \u0026quot;문자열\u0026quot;처럼 문자열을 던지면 해당 문자열은 단순한 값일 뿐, 스택 정보를 담고 있는 Error 객체가 아닙니다. 따라서 브라우저는 화면에 표시할 메시지(문자열)는 확인할 수 있지만, 그 문자열에 스택 정보가 없어서 호출 경로를 알려줄 수 없습니다. 여기서 핵심은 \u0026ldquo;던져진 값에 스택 정보가 있느냐 없느냐\u0026quot;입니다.\n3. 근본 원인 분석 근본 원인은 다음과 같이 정리할 수 있습니다.\n직접 던진 예외 값에는 스택 트레이스 정보가 기본적으로 포함되어 있지 않다.\nthrow에 전달된 값(문자열, 숫자 등)은 그 자체로는 호출 스택을 알지 못합니다. 스택 트레이스는 Error 객체가 생성될 때 캡처됩니다. 따라서 Error 객체(new Error())를 만들거나, 이미 만들어진 Error의 stack 속성을 읽어야 호출 체인을 얻을 수 있습니다. 또한 콘솔에 출력하려면 console.trace()처럼 호출 시점의 스택을 명시적으로 출력하는 메서드를 호출해야 합니다. 정리하면, \u0026ldquo;직접 던진 값에는 스택이 없다 → 스택을 얻으려면 스택을 제공하는 객체/메서드를 사용해야 한다\u0026quot;는 것이 곧 근본 원인입니다.\n비교 대상 스택 정보 포함 여부 예시 throw \u0026quot;문자열\u0026quot; ❌ 없음 throw \u0026quot;AArrggg\u0026quot; new Error().stack ✅ 있음 var err = new Error(); return err.stack; console.trace() ✅ 있음 (콘솔 출력) console.trace(); 4. 코드 해결책 가장 간단한 두 가지 방법을 소개합니다.\n방법 1: console.trace() 호출 (권장, 최신 브라우저) 모던 브라우저에서는 예외를 던지기 직전에 console.trace()를 호출하면 현재 호출 스택이 콘솔에 출력됩니다.\n1 2 3 4 5 6 7 8 9 10 11 12 function foo() { bar(2); } function bar(n) { if (n \u0026lt; 2) { console.trace(); // 여기서 호출 체인 출력 throw \u0026#34;Oh no! \u0026#39;n\u0026#39; is too small!\u0026#34; } bar(n-1); } foo(); 실행 결과 콘솔에 foo → bar → bar 순서의 호출 체인(함수 이름과 라인, URL)이 출력됩니다.\n방법 2: new Error().stack 속성 사용 Error 객체는 생성되는 순간 stack 속성에 호출 스택을 기록합니다. 이 속성을 읽으면 함수 이름, URL, 호출 체인을 문자열로 얻을 수 있습니다.\n1 2 3 4 function stackTrace() { var err = new Error(); return err.stack; } 이 함수를 호출하면 다음과 같은 형태의 출력이 생성됩니다.\n1 2 3 4 5 6 7 DBX.Utils.stackTrace@http://localhost:49573/assets/js/scripts.js:44 DBX.Console.Debug@http://localhost:49573/assets/js/scripts.js:9 .success@http://localhost:49573/:462 x.Callbacks/c@http://localhost:49573/assets/js/jquery-1.10.2.min.js:4 x.Callbacks/p.fireWith@http://localhost:49573/assets/js/jquery-1.10.2.min.js:4 k@http://localhost:49573/assets/js/jquery-1.10.2.min.js:6 .send/r@http://localhost:49573/assets/js/jquery-1.10.2.min.js:6 각 줄은 호출한 함수의 이름과 소스 URL 및 라인 번호, 그리고 그 함수를 호출한 상위 함수로 이어지는 체인을 보여줍니다.\n방법 비교 방법 장점 사용 예시 console.trace() 한 줄로 즉시 콘솔 출력, 최신 브라우저에서 안정적 디버깅 중 스택이 궁금할 때 new Error().stack 문자열로 반환해 로그/전송 등에 활용 가능 스택을 변수에 저장하거나 서버로 보낼 때 예외 처리(handling)와 함께 사용할 때 참고 예외를 catch한 뒤에도 Error 객체의 stack을 출력할 수 있습니다.\n1 2 3 4 5 try { foo(); } catch (e) { console.error(e.stack); // Error 객체면 stack 출력 가능 } 단, throw \u0026quot;문자열\u0026quot;처럼 문자열을 던진 경우에는 e.stack이 정의되어 있지 않을 수 있으므로, 스택을 활용하려면 throw new Error(\u0026quot;메시지\u0026quot;) 형태로 예외를 던지는 것이 좋습니다.\n5. 향후 예방 조치 직접 예외를 던질 때 스택 트레이스를 잃지 않기 위한 예방 조치입니다.\n문자열 대신 Error 객체를 throw하자.\n1 2 3 4 // 나쁜 예 throw \u0026#34;Oh no! \u0026#39;n\u0026#39; is too small!\u0026#34; // 좋은 예 throw new Error(\u0026#34;Oh no! \u0026#39;n\u0026#39; is too small!\u0026#34;) Error 객체는 stack 속성을 자동으로 갖기 때문에, 나중에 디버깅할 때 호출 경로를 복구할 수 있습니다.\nconsole.trace()를 디버깅 지점에 배치하자. 예외 직전 또는 의심스러운 분기점에서 한 줄만 추가하면 호출 체인을 즉시 확인할 수 있습니다.\ncatch 블록에서 스택을 로그로 남기자.\n1 2 3 4 5 try { // ... } catch (e) { console.error(e \u0026amp;\u0026amp; e.stack ? e.stack : e); } e.stack이 있는지 먼저 확인하고, 없으면 원본 값을 출력하도록 방어적으로 작성합니다.\narguments.callee.caller에 의존하지 말자. 과거에는 caller 속성으로 스택을 재구성하는 방식이 쓰였지만, 엄격 모드(strict mode)를 비롯한 현대 환경에서는 제한적입니다. console.trace() 또는 Error.stack이 더 안전하고 표준적인 방법입니다.\n이렇게 하면 직접 던진 예외에서도 스택 트레이스를 놓치지 않고 효과적으로 디버깅할 수 있습니다.\n출처 StackOverflow 질문: How can I get a JavaScript stack trace when I throw an exception? ","permalink":"https://tokkabi.com/posts/how-can-i-get-a-javascript-stack-trace-when-i-throw-an-exception/","summary":"자바스크립트에서 직접 throw한 예외의 스택 트레이스를 얻는 방법 1. 문제 정의 자바스크립트에서 예외를 **직접 throw**할 때, 브라우저(Fireb","title":"자바스크립트에서 예외를 직접 throw할 때 스택 트레이스를 얻는 방법 (console.trace / Error.stack)"},{"content":"프로그램 종료 없이 전체 Python 예외 traceback 출력하기 — print_exc()의 함정과 print_exception() 활용 1. 문제 정의 Python으로 프로그램을 작성하다 보면 특정 코드가 실패해도 프로그램 전체는 종료시키지 않고, 에러의 전체 traceback을 로그로 남기고 싶은 상황이 자주 생깁니다.\n아래처럼 try/except를 사용하면 예외를 잡을 수는 있지만, print(Exception, err)는 예외 클래스 이름과 메시지만 출력할 뿐 어디서 어떤 호출 경로를 통해 예외가 발생했는지 즉 전체 traceback을 보여주지 않습니다.\n1 2 3 4 5 6 try: do_stuff() except Exception as err: print(Exception, err) # 전체 traceback을 출력하고 싶지만, # 예외 이름과 상세 내용만 출력됨 목표: try/except가 예외를 가로채지 않았을 때 터미널에 출력되는 것과 동일한 전체 traceback을 출력하면서도, 프로그램은 종료하지 않는 것입니다.\n2. 원인 탐구 왜 print(Exception, err)만으로는 부족할까요?\nPython의 예외 객체(err)에는 일반적으로 에러 메시지 문자열만 담겨 있습니다. traceback(역추적) 정보는 예외 객체 자체에 문자열로 저장되는 것이 아니라, 예외가 발생한 시점의 **호출 스택 프레임(실행 컨텍스트)**과 연결되어 있습니다.\n즉 \u0026ldquo;전체 traceback\u0026quot;을 출력하려면 다음 정보가 필요합니다.\n정보 의미 검색 예시 type 예외 타입 (예: TypeError) TypeError: Oups! value 예외 메시지 Oups! traceback 예외가 전파된 호출 경로 File \u0026quot;t.py\u0026quot;, line 6, in \u0026lt;module\u0026gt; 이 세 가지를 한 번에 얻을 수 있는 함수가 sys.exc_info()입니다. 이 함수는 (type, value, traceback) 튜플을 반환합니다.\n3. 근본 원인 분석 가장 흔히 쓰는 해결책은 traceback.print_exc()입니다. 하지만 이 함수에는 치명적인 함정이 있습니다.\ntraceback.print_exc()는 현재 처리 중인(마지막으로 발생한) 예외의 traceback만 출력합니다. 만약 예외를 처리하는 도중에 또 다른 예외가 발생한다면, 원래 예외의 traceback은 잃어버리게 됩니다.\n1 2 3 4 5 6 7 8 9 10 import traceback try: raise TypeError(\u0026#34;Oups!\u0026#34;) except Exception as err: try: raise TypeError(\u0026#34;Again !?!\u0026#34;) except: pass traceback.print_exc() 이 코드의 실제 출력을 보면 원래 예외(Oups!)가 아니라 두 번째 예외(Again !?!)의 traceback이 출력됩니다.\n1 2 3 4 Traceback (most recent call last): File \u0026#34;e.py\u0026#34;, line 7, in \u0026lt;module\u0026gt; raise TypeError(\u0026#34;Again !?!\u0026#34;) TypeError: Again !?! 이것이 근본 원인입니다. print_exc()는 sys.exc_info()의 현재 상태를 그대로 출력하기 때문에, 예외 처리 도중 예외가 바뀌면 원본 정보를 놓칩니다. 실제 프로덕션 코드에서는 예외 처리 로직 안에서 로깅, 정리, 추가 연산을 수행하다가 중첩 예외가 발생하는 경우가 흔하므로 이 문제를 반드시 인지해야 합니다.\n4. 코드 해결책 해결 방법은 명확합니다. 예외 정보를 처음 잡은 시점에 지역 변수로 캐시해 두고, 이후에는 캐시된 값을 traceback.print_exception()으로 출력하면 됩니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import traceback import sys try: raise TypeError(\u0026#34;Oups!\u0026#34;) except Exception as err: # 1) 예외 처리 첫 줄에서 exc_info를 캐시한다 exc_info = sys.exc_info() # 이 사이에서 유용한 작업 수행 (예외가 발생해도 무방) try: raise TypeError(\u0026#34;Again !?!\u0026#34;) except: pass # 2) 원본 예외의 traceback을 출력한다 traceback.print_exception(*exc_info) # 3) 순환 참조 방지를 위해 캐시를 해제한다 del exc_info 이 코드는 중간에 두 번째 예외(Again !?!)가 발생했음에도 원래 예외의 traceback을 올바르게 출력합니다.\n1 2 3 4 Traceback (most recent call last): File \u0026#34;t.py\u0026#34;, line 6, in \u0026lt;module\u0026gt; raise TypeError(\u0026#34;Oups!\u0026#34;) TypeError: Oups! 주의할 점 — 순환 참조(circular reference) sys.exc_info()의 traceback 객체는 예외 처리 중인 함수의 지역 변수와 순환 참조를 만들 수 있습니다. 공식 문서에 따르면, 예외를 처리하는 함수에서 traceback 반환 값을 지역 변수에 할당하면 순환 참조가 발생할 수 있으며, 이로 인해 해당 traceback이 참조하는 객체가 가비지 컬렉션되지 않고 메모리에 남을 수 있습니다.\n따라서 traceback을 더 이상 사용하지 않는 시점에 반드시 del exc_info(또는 del traceback)로 참조를 명시적으로 해제해야 합니다. finally 블록에서 정리하는 것이 안전한 패턴입니다.\n상황 사용 함수 결과 단순히 마지막 예외의 traceback만 필요할 때 traceback.print_exc() 간단하지만 중첩 예외 시 원본 손실 원본 예외의 traceback을 반드시 보존해야 할 때 sys.exc_info() 캐시 + traceback.print_exception() 안전, 순환 참조 해제 필수 traceback 문자열을 변수로 저장하고 싶을 때 traceback.format_exception(*exc_info) 로그 파일/문자열 저장에 유용 예외 traceback을 로그 파일 등에 문자열로 저장해야 한다면 traceback.format_exception(*exc_info)로 문자열을 얻을 수 있습니다. 이 역시 동일하게 exc_info 캐시 방식과 함께 사용합니다.\n5. 향후 예방 조치 같은 문제를 반복하지 않기 위한 실용적인 예방 조치입니다.\nprint_exc()의 한계를 기억하세요. 예외 처리 블록 안에서 추가 작업(중첩 호출, 로깅, 정리)을 수행한다면 print_exc()는 원본 traceback을 보장하지 않습니다.\n예외 처리 첫 줄에서 sys.exc_info()를 캐시하고, 이후에는 반드시 캐시된 값을 사용하세요. 이렇게 하면 예외 처리 로직이 아무리 복잡해져도 원본 정보가 보존됩니다.\ndel exc_info로 순환 참조를 해제하세요. traceback을 더 이상 쓰지 않는 즉시 해제하는 것이 메모리 누수를 막는 안전한 습관입니다. 필요하다면 finally 블록에서 정리하세요.\n문자열로 저장해야 한다면 format_exception()을 사용하세요. 로그 파일에 남기는 용도라면 format_exception(*exc_info)로 문자열을 얻는 것이 sys.stderr에 출력하는 것보다 유연합니다.\n함수로 캡슐화해서 재사용하면 잊지 않고 일관되게 적용할 수 있습니다. 예외 정보를 받아 로깅하는 공용 유틸리티를 만들어 두면 모든 except 블록에서 동일한 패턴을 강제할 수 있습니다.\n출처: StackOverflow — Catch and print full Python exception traceback without halting/exiting the program\n","permalink":"https://tokkabi.com/posts/catch-and-print-full-python-exception-traceback-without-halt/","summary":"프로그램 종료 없이 전체 Python 예외 traceback 출력하기 — print_exc()의 함정과 print_exception() 활용 1. 문제 정의 Python으로 프로그램을 작성하다 보면 특정 코드가 실패해도","title":"프로그램 종료 없이 전체 Python 예외 traceback 출력하기 — print_exc()의 함정과 print_exception() 활용"},{"content":"Python에서 프로그램 종료 없이 전체 예외 트레이스백 출력하는 방법 1. 문제 정의 Python에서 예외를 발생시키지 않고 로그만 남기고 싶을 때가 많습니다. 많은 초보 개발자는 다음과 같이 try/except 블록을 작성합니다.\n1 2 3 4 5 6 try: do_stuff() except Exception as err: print(Exception, err) # 이 시점에는 예외 이름과 상세 메시지만 출력되고, # 전체 트레이스백(어느 파일의 몇 번째 줄에서 발생했는지)이 출력되지 않습니다. 이 코드는 프로그램을 종료하지 않는 점은 지켜지지만, 예외가 발생한 정확한 위치(파일명, 라인 번호, 호출 스택)를 알 수 없습니다. 디버깅에는 예외 이름과 메시지만으로는 부족합니다.\n원하는 동작: try/except가 예외를 가로채지 않았을 때 Python이 출력하는 것과 똑같은 전체 트레이스백을 출력하면서도, 프로그램은 계속 실행되게 하는 것입니다.\n2. 원인 탐구 왜 print(Exception, err)만으로는 전체 트레이스백이 나오지 않을까요?\nexcept Exception as err: 로 잡은 err 객체는 예외의 값(value) 만 담고 있습니다. 예외가 어느 모듈의 몇 번째 줄에서 발생했는지, 어떤 함수들을 거쳐 전파되었는지에 대한 정보는 예외 객체가 아니라 별도의 트레이스백(traceback) 객체에 들어 있습니다.\n그래서 예외 이름과 값만 출력해서는 호출 스택 정보가 빠지게 됩니다. 전체 정보를 얻으려면 Python의 traceback 모듈을 사용해야 합니다.\n가장 널리 알려진 방법이 traceback.print_exc()입니다.\n1 2 3 4 5 6 import traceback try: do_stuff() except Exception: traceback.print_exc() 이 코드는 동작하고 전체 트레이스백을 출력합니다. 그런데 이 방법에도 숨은 함정이 있습니다.\n3. 근본 원인 분석 traceback.print_exc()는 가장 최근에 발생한(마지막) 예외를 출력합니다. 문제는 except 블록 안에서 다른 작업을 하다가 또 다른 예외가 발생하거나, 예외를 다시 던지는(raise) 경우입니다.\n실제로 그런 상황을 재현해 봅시다.\n1 2 3 4 5 6 7 8 9 10 import traceback try: raise TypeError(\u0026#34;Oups!\u0026#34;) except Exception, err: # Python 2.x 문법 (Python 3은 except Exception as err:) try: raise TypeError(\u0026#34;Again !?!\u0026#34;) except: pass traceback.print_exc() 이 코드가 출력하는 결과는 기대와 다릅니다.\n1 2 3 4 Traceback (most recent call last): File \u0026#34;e.py\u0026#34;, line 7, in \u0026lt;module\u0026gt; raise TypeError(\u0026#34;Again !?!\u0026#34;) TypeError: Again !?! print_exc()가 except 블록 안에서 새로 던져진 TypeError(\u0026quot;Again !?!\u0026quot;), 즉 마지막 예외를 출력한 것입니다. 원래 잡으려고 했던 첫 번째 TypeError(\u0026quot;Oups!\u0026quot;)의 트레이스백은 출력되지 않았습니다.\n이처럼 traceback.print_exc()는 예외 체인에서 가장 늦게 발생한 예외만 보여주므로, \u0026ldquo;원본 예외\u0026quot;의 전체 트레이스백을 보존해야 하는 상황에서는 부적합합니다.\n4. 코드 해결책 해결책: sys.exc_info()로 원본 예외 정보를 캐시하고 print_exception()으로 출력 원본 예외의 트레이스백을 확실히 보존하려면, 예외를 잡는 순간 sys.exc_info()로 예외 정보를 로컬 변수에 캐시한 다음, traceback.print_exception(*exc_info)로 출력하면 됩니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import sys import traceback try: raise TypeError(\u0026#34;Oups!\u0026#34;) except Exception, err: # Python 3: except Exception as err: try: exc_info = sys.exc_info() # 원본 예외 정보를 로컬 변수에 캐시 # 여기서 원하는 유용한 작업 수행 (예: 로깅, 정리) # 주의: 이 사이에 또 다른 예외가 발생해도 괜찮음 try: raise TypeError(\u0026#34;Again !?!\u0026#34;) except: pass finally: # *원본* 예외의 트레이스백을 출력 traceback.print_exception(*exc_info) del exc_info # 순환 참조 해제 이제 출력은 원래 잡으려던 예외를 올바르게 보여줍니다.\n1 2 3 4 Traceback (most recent call last): File \u0026#34;t.py\u0026#34;, line 6, in \u0026lt;module\u0026gt; raise TypeError(\u0026#34;Oups!\u0026#34;) TypeError: Oups! 핵심 동작 원리 sys.exc_info()는 현재 처리 중인 예외에 대한 튜플 (exception_type, exception_value, traceback) 을 반환합니다. 이 3가지를 traceback.print_exception(type, value, traceback)에 그대로 넘기면(*exc_info 언패킹) 원본 예외의 전체 트레이스백이 출력됩니다.\n반드시 기억해야 할 함정: 순환 참조 (circular reference) sys.exc_info() 문서에 따르면, 예외를 처리하는 함수에서 트레이스백 반환 값을 로컬 변수에 할당하면 순환 참조가 발생합니다. 트레이스백 객체는 프레임의 로컬 변수를 참조하고, 그 로컬 변수(exc_info)가 다시 트레이스백을 참조하기 때문입니다.\n이 순환 참조는 같은 함수나 트레이스백이 참조하는 로컬 변수들이 가비지 컬렉션(GC)되지 못하게 막습니다. 따라서 트레이스백이 더 이상 필요 없어졌다면, 로컬 변수에서 참조를 명시적으로 제거해야 합니다. 위 코드의 finally 블록에서 del exc_info로 정리하는 이유가 바로 이것입니다.\n해결책 비교 디버깅 표 방법 출력 내용 함정 적합한 상황 print(Exception, err) 예외 이름 + 값만 스택 정보 없음 간단한 값 확인 traceback.print_exc() 마지막 예외의 전체 트레이스백 예외 체인에서 원본 예외를 놓칠 수 있음 일반적인 단일 예외 처리 sys.exc_info() 캐시 + traceback.print_exception(*exc_info) 원본 예외의 전체 트레이스백 순환 참조 → del로 해제 필요 원본 예외 보존이 중요한 다중 예외 처리 5. 향후 예방 조치 예외를 잡은 직후에 트레이스백을 출력하세요. except 블록 안에서 많은 작업을 하기 전에 sys.exc_info()를 캐시해 두면 예외 체인이 뒤섞이는 문제를 피할 수 있습니다.\n원본 예외가 중요한 경우 print_exc() 대신 print_exception(*exc_info)를 사용하세요. 특히 로깅 과정에서 부수적인 예외가 발생할 수 있는 환경이라면 원본 예외를 캐시해 두는 것이 안전합니다.\n캐시한 exc_info는 반드시 finally에서 del로 해제하세요. 순환 참조를 남기면 해당 함수의 로컬 변수들이 메모리에서 해제되지 않아, 장시간 실행되는 서버/스크립트에서 메모리 누수로 이어질 수 있습니다.\n실제 운영 환경에서는 로깅 모듈을 활용하세요. logging 모듈의 logger.exception() 또는 logger.error(..., exc_info=True)는 트레이스백을 포함해 로그로 남겨주므로, 표준출력보다 유지보수에 유리합니다. (단, 여기서도 예외가 다중으로 발생하는 경우 원본 예외 보존에 주의해야 합니다.)\nsys.exc_info()가 아닌 except Exception as err:로 받은 예외 객체만으로 전체 트레이스백을 얻으려면, 예외 객체의 __traceback__ 속성을 활용하는 방법도 있지만, 이 역시 순환 참조 주의가 필요합니다. 가장 안전한 기본 패턴은 위에서 설명한 캐시 + print_exception 방식입니다.\n출처 StackOverflow: Catch and print full Python exception traceback without halting/exiting the program ","permalink":"https://tokkabi.com/posts/python-exception-traceback-print-without-exit/","summary":"Python에서 프로그램 종료 없이 전체 예외 트레이스백 출력하는 방법 1. 문제 정의 Python에서 예외를 발생시키지 않고 로그만 남기고 싶을 때가 많습니다. 많","title":"Python에서 프로그램 종료 없이 전체 예외 트레이스백(traceback) 출력하는 방법"},{"content":"pytest에서 예외 발생을 올바르게 검증하는 방법 (pytest.raises) 1. 문제 정의 테스트 코드에서 \u0026ldquo;이 함수가 특정 예외를 던져야 한다\u0026quot;는 조건을 검증하려고 할 때, 많은 개발자가 다음과 같이 try/except와 pytest.fail을 조합해서 작성하곤 합니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 # coding=utf-8 import pytest def whatever(): return 9 / 0 def test_whatever(): try: whatever() except ZeroDivisionError as exc: pytest.fail(exc, pytrace=True) 이 테스트를 실행하면 결과는 다음과 같습니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ================================ test session starts ================================= platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2 plugins: django, cov collected 1 items pytest_test.py F ====================================== FAILURES ====================================== ___________________________________ test_whatever ____________________________________ def test_whatever(): try: whatever() except ZeroDivisionError as exc: \u0026gt; pytest.fail(exc, pytrace=True) E Failed: integer division or modulo by zero pytest_test.py:12: Failed ============================== 1 failed in 1.16 seconds ============================== 문제는 명확합니다. 테스트가 실패(Fail)로 표시되긴 하지만, 정작 어떤 예외가 어디서 발생했는지에 대한 원본 traceback이 전혀 출력되지 않습니다. Failed: integer division or modulo by zero 라는 메시지만 나올 뿐, ZeroDivisionError가 발생한 실제 위치나 호출 스택은 확인할 수 없습니다.\n2. 원인 탐구 왜 이런 문제가 발생할까요? 그 이유는 try/except 블록이 예외를 이미 잡아버렸기(caught) 때문입니다.\n1 2 3 4 5 def test_whatever(): try: whatever() # 여기서 ZeroDivisionError 발생 except ZeroDivisionError as exc: pytest.fail(exc, pytrace=True) # 예외가 이미 소비됨 -\u0026gt; 원본 traceback 손실 whatever()에서 ZeroDivisionError가 발생하면, 이 예외는 except ZeroDivisionError 절에서 잡혀 변수 exc에 담깁니다. 이 시점에서 원본 예외의 traceback 정보는 손실된 상태입니다.\n그런 다음 pytest.fail(exc, pytrace=True)를 호출하면, pytest.fail은 전달된 메시지(exc의 문자열 표현)만 사용해 테스트를 실패 처리합니다. 결과적으로:\n원본 예외 타입 정보는 사라지고 발생 위치(traceback) 정보는 사라지고 단순한 실패 메시지만 남습니다 게다가 이 방식은 골치아픈 부작용도 있습니다. 만약 whatever()가 예외를 던지지 않는다면, except 블록은 실행되지 않고 pytest.fail도 호출되지 않아 테스트가 그냥 통과해버립니다. 즉, 예외가 발생하지 않는 잘못된 상황을 놓칠 수 있는 것입니다.\n3. 근본 원인 분석 근본 원인은 잘못된 검증 도구의 선택에 있습니다.\ntry/except는 예외를 \u0026ldquo;처리(handle)\u0026ldquo;하는 용도이지 \u0026ldquo;검증(assert)\u0026ldquo;하는 용도가 아닙니다. 예외를 잡아서 처리하는 순간, 원본 예외 정보를 직접 관리해야 하며 traceback이 오염됩니다.\npytest.fail은 단순히 테스트를 실패시키는 함수로, 예외 검증용으로 설계되지 않았습니다. 전달한 메시지를 그대로 출력할 뿐, 발생한 예외의 타입·메시지·traceback을 구조적으로 보여주지 못합니다.\n따라서 try/except + pytest.fail 조합은 예외 검증이라는 목적에 맞지 않습니다.\npytest에는 이 목적을 위해 설계된 전용 도구가 있습니다. 바로 pytest.raises 컨텍스트 매니저입니다. pytest.raises는 블록 안에서 지정한 예외가 발생하는지 확인하고, 발생했을 때 예외 객체를 반환해 추가 검증이 가능하게 해줍니다. 예외가 발생하지 않으면 테스트를 실패시킵니다.\n4. 코드 해결책 pytest.raises(Exception) 컨텍스트 매니저를 사용하면 됩니다. 다음은 공식 문서에서 권장하는 올바른 패턴입니다.\n4-1. 기본 사용법 — 예외가 발생하는지 확인 1 2 3 4 5 6 7 8 9 10 11 import pytest def test_passes(): with pytest.raises(Exception) as e_info: x = 1 / 0 def test_passes_without_info(): with pytest.raises(Exception): x = 1 / 0 with pytest.raises(Exception): 블록 안의 코드가 Exception을 던지면 테스트는 통과합니다. 예외를 발생시키지 않으면 테스트는 실패합니다.\n4-2. 예외 객체 정보 확인 as e_info를 사용하면 발생한 예외 객체를 받아와서 메시지나 타입을 추가 검증할 수 있습니다.\n1 2 3 4 5 6 7 import pytest def test_exception_message(): with pytest.raises(ZeroDivisionError) as e_info: x = 1 / 0 assert \u0026#34;division by zero\u0026#34; in str(e_info.value) e_info.value에 실제 예외 객체가 들어 있어, 예외 메시지나 속성을 검증할 수 있습니다.\n4-3. 발생하지 않으면 실패하는 경우 1 2 3 4 5 6 import pytest def test_fails(): with pytest.raises(Exception) as e_info: x = 1 / 1 # 예외가 발생하지 않음 -\u0026gt; 테스트 실패 x = 1 / 1은 예외를 발생시키지 않으므로 pytest.raises는 이 테스트를 실패시킵니다. 이처럼 \u0026ldquo;예외가 반드시 발생해야 하는데 발생하지 않은\u0026rdquo; 경우를 정확히 잡아냅니다.\n4-4. 하지 말아야 할 패턴 (Bad style) solution에서 지적하는 잘못된 예시입니다. try/except와 assert를 조합한 다음 패턴은 작동하더라도 \u0026ldquo;좋지 않은 스타일\u0026quot;이며 테스트 결과 정보가 부실해집니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # Don\u0026#39;t do this. Assertions are caught as exceptions. def test_passes_but_should_not(): try: x = 1 / 1 assert False except Exception: assert True # Even if the appropriate exception is caught, it is bad style, # because the test result is less informative # than it would be with pytest.raises(e) # (it just says pass or fail.) def test_passes_but_bad_style(): try: x = 1 / 0 assert False except ZeroDivisionError: assert True def test_fails_but_bad_style(): try: x = 1 / 1 assert False except ZeroDivisionError: assert True 이런 패턴은 테스트가 그냥 \u0026ldquo;pass\u0026rdquo; 또는 \u0026ldquo;fail\u0026quot;로만 표시되고, pytest.raises를 사용했을 때보다 훨씬 적은 정보를 제공합니다.\n4-5. 방식 비교 표 방식 예외 검증 가능 원본 traceback 출력 예외 객체 접근 예외 미발생 시 동작 try/except + pytest.fail 부분적 ❌ 손실 부분적 ❌ 통과해버림 (위험) try/except + assert 부분적 ❌ ❌ 상황에 따라 오동작 pytest.raises(Exception) ✅ ✅ ✅ (e_info.value) ✅ 실패 처리 5. 향후 예방 조치 앞으로 같은 실수를 피하려면 다음 규칙을 지키세요.\n예외 검증은 반드시 pytest.raises를 사용하세요. try/except + pytest.fail이나 try/except + assert 조합은 피하세요. 예외 메시지까지 검증해야 한다면 as e_info 를 사용해 e_info.value로 접근하세요. 예외 타입만 확인할 때는 생략해도 됩니다. 정확한 예외 타입을 지정하세요. pytest.raises(Exception)보다 pytest.raises(ZeroDivisionError)처럼 구체적인 타입을 지정하는 것이 더 엄격하고 명확한 테스트가 됩니다. 예외가 \u0026ldquo;발생하지 않는 경우\u0026quot;도 함께 검증하세요. pytest.raises는 예외가 발생하지 않으면 테스트를 실패시키므로, 이 특성을 활용해 예외 경로를 놓치지 않도록 하세요. 코드 리뷰 시 try/except로 예외 검증을 흉내 낸 테스트 코드가 보이면 pytest.raises로 교체하도록 안내하세요. 이 규칙을 따르면 테스트가 실패했을 때 원본 traceback과 예외 정보가 그대로 출력되어 디버깅 시간을 크게 줄일 수 있습니다.\n출처: StackOverflow 23337471 - How do I properly assert that an exception gets raised in pytest?\n","permalink":"https://tokkabi.com/posts/pytest-exception-assert-raises/","summary":"pytest에서 예외 발생을 올바르게 검증하는 방법 (pytest.raises) 1. 문제 정의 테스트 코드에서 \u0026ldquo;이 함수가 특정 예외를 던져야 한다\u0026quot;는 조건을 검증하","title":"pytest에서 예외 발생을 올바르게 검증하는 방법 (pytest.raises)"},{"content":"Python에서 traceback 없이 프로그램 깔끔하게 종료하기 — sys.exit()와 예외 처리 1. 문제 정의 CLI 도구나 스크립트를 실행하다 보면, 정상적으로 종료하려는데 traceback 덤프가 화면에 출력되는 상황이 발생합니다.\n다음과 같은 질문이 대표적입니다.\n에러 원문: \u0026ldquo;How to exit from Python without having an traceback dump on the output. I still want to be able to return an error code but I do not want to display the traceback log. I want to be able to exit using exit(number) without trace but in case of an Exception (not an exit) I want the trace.\u0026rdquo;\n즉, 두 가지 요구가 동시에 존재합니다.\n요구사항 설명 오류 코드 반환 exit(number) 로 종료 상태 코드를 반환하고 싶다 traceback 숨기기 정상 종료 시 traceback 덤프가 출력되지 않게 하고 싶다 예외는 trace 유지 실제 Exception이 발생한 경우에는 trace를 출력하고 싶다 핵심 증상: exit(number)를 호출해도 traceback이 출력된다.\n2. 원인 탐구 exit(number)를 호출했는데 traceback이 보인다면, 문제의 원인은 exit() 자체가 아닙니다.\nPython의 종료 흐름을 이해해야 합니다.\nsys.exit() 또는 exit()은 SystemExit 예외를 발생시켜 인터프리터를 종료합니다. 하지만 SystemExit이 아닌 다른 예외(예: ValueError, KeyError, RuntimeError 등)가 발생하고 그것이 처리되지 않으면, 인터프리터는 해당 예외의 traceback을 출력한 뒤 프로세스를 종료합니다. 즉, exit(number)를 만났을 때 traceback이 나온 것이 아니라, 그 전에 이미 잡히지 않은 예외가 발생했기 때문에 traceback이 출력된 것입니다. 1 2 3 4 5 6 7 8 9 10 import sys def main(): # 예외가 발생하는 로직 (예: 존재하지 않는 키 접근) data = {\u0026#34;a\u0026#34;: 1} print(data[\u0026#34;b\u0026#34;]) # KeyError 발생! sys.exit(0) # 이 코드는 실행되지 않음 if __name__ == \u0026#34;__main__\u0026#34;: main() 위 코드를 실행하면 sys.exit(0)은 결코 도달하지 못하고, KeyError의 traceback이 출력된 뒤 비정상 종료됩니다.\n3. 근본 원인 분석 근본 원인은 exit()가 아니라, catch되지 않은 예외입니다.\nexit(number)는 SystemExit을 던질 뿐입니다. 문제는 프로그램을 종료하기 전에 처리되지 않은 예외가 발생해 traceback이 출력되는 것입니다.\n따라서 올바른 관점은 다음과 같습니다.\n\u0026ldquo;traceback을 어떻게 숨길까\u0026quot;가 아니라 \u0026ldquo;예외를 어디서 어떻게 잡느냐\u0026rdquo; 가 핵심이다.\n체크 포인트를 표로 정리하면 다음과 같습니다.\n현상 원인 접근 exit(n) 호출에도 traceback 출력 exit() 도달 전 예외 발생 예외를 try/except로 catch 정상 종료인데 trace 출력됨 잡히지 않은 Exception이 종료 유발 Exception과 KeyboardInterrupt 분리 catch 오류 코드는 반환하고 싶음 traceback 없이도 코드 반환 필요 catch 후 sys.exit(0) 호출 4. 코드 해결책 가장 간단하고 견고한 해법은 main() 함수에서 모든 예외를 catch하는 구조로 바꾸는 것입니다.\nStackOverflow에서 채택된 최고 득표 답변은 다음과 같습니다.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import sys import traceback def main(): try: # 메인 프로그램 로직 작성 pass except KeyboardInterrupt: print(\u0026#34;Shutdown requested...exiting\u0026#34;) except Exception: traceback.print_exc(file=sys.stdout) sys.exit(0) if __name__ == \u0026#34;__main__\u0026#34;: main() 동작 설명:\ntry: 블록에서 메인 프로그램의 로직을 실행합니다. except KeyboardInterrupt: — 사용자가 Ctrl+C를 눌렀을 때 traceback 없이 안내 문구만 출력하고 정상 종료합니다. except Exception: — 그 외의 예외가 발생하면 traceback.print_exc()로 trace를 원하는 곳(여기서는 sys.stdout)에 출력하고, sys.exit(0)으로 오류 코드를 반환하며 종료합니다. 예외가 발생하지 않는다면 traceback은 전혀 출력되지 않습니다. file=sys.stdout을 지정하는 이유는, 기본적으로 traceback은 sys.stderr로 출력되는데, 출력 스트림을 명시적으로 제어하고 싶을 때 사용합니다.\n5. 향후 예방 조치 같은 문제를 반복하지 않기 위한 예방 팁입니다.\n예방 조치 설명 main() 진입점 패턴 사용 if __name__ == \u0026quot;__main__\u0026quot;: main() 구조로 로직을 함수에 격리 try/except로 진입점 감싸기 모든 예외를 잡는 최상위 예외 처리기 구성 KeyboardInterrupt 분리 처리 Ctrl+C 상황과 일반 예외를 구분 trace 스트림 명시 traceback.print_exc(file=sys.stderr) 등 출력 위치를 명확히 지정 SystemExit만 별도 처리 sys.exit()와 구분해 SystemExit은 그대로 전파되도록 설계 정리하면, exit(number)로 종료할 때 traceback이 출력되는 것은 exit()의 문제가 아니라 잡히지 않은 예외 때문입니다. main()에서 try/except로 예외를 catch하고, 필요한 경우 trace를 출력한 뒤 sys.exit(0)을 호출하면, 오류 코드를 반환하면서도 traceback 없이 프로그램을 깔끔하게 종료할 수 있습니다.\n출처 StackOverflow: How to exit from Python without traceback? ","permalink":"https://tokkabi.com/posts/how-to-exit-from-python-without-traceback/","summary":"Python에서 traceback 없이 프로그램 깔끔하게 종료하기 — sys.exit()와 예외 처리 1. 문제 정의 CLI 도구나 스크립트를 실행하다 보면, 정상적으로 종료하려는","title":"Python에서 traceback 없이 프로그램 깔끔하게 종료하기 — sys.exit()와 예외 처리"},{"content":"fatal error: Python.h: No such file or directory 해결 1. 문제 정의 Python의 C 확장 모듈(shared library)을 빌드하려고 C 파일을 컴파일하는 과정에서 아래와 같은 컴파일 에러가 발생합니다.\n1 gcc -Wall utilsmodule.c -o Utilc 실행 결과:\n1 2 utilsmodule.c:1:20: fatal error: Python.h: No such file or directory compilation terminated. utilsmodule.c 의 첫 줄에 있는 #include \u0026lt;Python.h\u0026gt; 를 컴파일러가 찾지 못해 빌드가 중단된 상황입니다. 컴파일러(전처리기)는 헤더 파일을 찾지 못하면 fatal error 를 내고 즉시 종료합니다.\n에러 발생 환경 요약 항목 내용 증상 fatal error: Python.h: No such file or directory 발생 지점 컴파일 단계 (전처리기에서 #include \u0026lt;Python.h\u0026gt; 탐색 실패) 주요 원인 후보 Python 개발 헤더 미설치, 컴파일러가 헤더 경로를 모름 대상 Python C/C++ 확장 모듈 빌더 2. 원인 탐구 #include \u0026lt;Python.h\u0026gt; 는 C 전처리기가 헤더 탐색 경로(include path)에서 Python.h 라는 파일을 찾도록 지시합니다. 이 에러가 나는 이유는 크게 두 가지로 나뉩니다.\n1) Python 개발 헤더 패키지가 설치되지 않은 경우\n대부분의 리눅스 배포판은 Python을 실행용 런타임과 개발용 헤더로 나누어 제공합니다. python 이 설치되어 있어도 Python.h 는 별도의 -dev 패키지에 포함되는 경우가 많습니다. 흔한 착각: Python이 잘 동작하는데도 이 에러가 나는 것은 런타임과 헤더가 별개이기 때문입니다. 2) 헤더가 설치되어 있어도 컴파일러가 경로를 모르는 경우\n기본 include 경로(/usr/include 등)에 Python.h 가 없는데, 사용자가 -I 옵션으로 경로를 알려주지 않으면 동일한 에러가 발생합니다. StackOverflow의 해당 질문 작성자처럼 \u0026ldquo;Python.h 파일이 내 컴퓨터 어디 있는지는 아는데\u0026rdquo; 컴파일러가 그 위치를 모르는 경우가 전형적입니다. 3. 근본 원인 분석 근본 원인은 단순합니다. Python.h 파일이 컴파일러의 헤더 탐색 경로 밖에 있거나, 아예 설치되어 있지 않은 것입니다.\n확인해 보는 명령은 아래와 같습니다.\n1 2 3 # Python.h 가 실제로 존재하는지 확인 find /usr/include -name \u0026#34;Python.h\u0026#34; 2\u0026gt;/dev/null python-config --includes # Python이 제공하는 include 경로 출력 python-config --includes 의 출력 예: -I/usr/include/python2.7 -I/usr/include/python2.7 이 경로가 바로 컴파일러에 넘겨줘야 할 include 경로입니다. 즉, \u0026ldquo;내가 실행 중인 Python의 헤더 경로가 어디인지\u0026rdquo; 를 찾아서 -I 옵션으로 명시해 주면 컴파일러는 Python.h 를 찾을 수 있습니다.\n4. 코드 해결책 아래 두 가지 방법 중 상황에 맞게 선택하면 됩니다.\n방법 A. 개발 헤더 패키지 설치 (권장) Python 개발 헤더가 누락된 환경이라면 해당 OS에 맞는 패키지를 설치합니다.\n1 2 3 4 5 6 7 # Debian / Ubuntu sudo apt-get install python-dev # Python 2.x sudo apt-get install python3-dev # Python 3.x # RHEL / CentOS / Fedora sudo yum install python-devel # Python 2.x sudo yum install python3-devel # Python 3.x 설치 후 다시 컴파일하면 헤더가 기본 경로에 들어와 대부분 해결됩니다.\n방법 B. 컴파일러에 헤더 경로 명시 (헤더가 이미 있는 경우) Python.h 가 이미 시스템에 있는데 컴파일러가 못 찾는 경우, -I 옵션으로 디렉토리를 지정합니다. 공유 라이브러리로 빌드할 때는 -fPIC 와 -lpython2.7 도 함께 지정해야 합니다.\nStackOverflow에서 채택된 해결책은 아래 한 줄 명령입니다.\n1 gcc -shared -o UtilcS.so -fPIC -I/usr/include/python2.7 -lpython2.7 utilsmodule.c 옵션 의미 -shared 공유 라이브러리(.so)로 빌드 -o UtilcS.so 출력 파일명 지정 -fPIC 위치 독립 코드(Position Independent Code) 생성 — 공유 라이브러리 필수 -I/usr/include/python2.7 Python.h 가 있는 include 디렉토리 지정 -lpython2.7 Python 라이브러리 링크 Python 버전에 따라 경로가 달라지므로 아래처럼 python-config 를 활용하면 정확합니다.\n1 2 # Python 3 기준 자동 경로 추출 gcc -shared -o UtilcS.so -fPIC $(python3-config --includes) -lpython3.12 utilsmodule.c 단계별 요약 python-config --includes 로 include 경로를 확인한다. 개발 패키지(python-dev/python3-dev) 미설치면 설치하거나, 헤더 경로가 이미 있으면 확인한다. gcc 컴파일 시 -I\u0026lt;헤더경로\u0026gt; 옵션을 추가한다. .so 공유 라이브러리라면 -fPIC 와 -lpython 를 함께 지정한다. 컴파일이 성공하면 .so 파일이 생성되었는지 확인한다. 5. 향후 예방 조치 개발 헤더는 런타임과 별개다. 서버나 CI 환경에 Python만 설치하고 빌드할 때는 -dev 패키지를 함께 설치해야 합니다. 빌드 자동화를 쓴다면 python3-config --includes 나 pkg-config --cflags python3 처럼 시스템이 스스로 경로를 뽑아주는 명령을 사용하세요. 하드코딩된 경로는 Python 버전이 바뀌면 깨집니다. 가상환경에서 빌드할 때는 시스템 Python 대신 해당 가상환경의 헤더 경로를 사용하는지 확인합니다. setuptools/distutils 기반 프로젝트는 setup.py 의 Extension 이 include 경로를 자동 처리하므로, 가능하면 bare gcc 대신 빌드 시스템을 이용하는 것이 안전합니다. 출처: StackOverflow — fatal error: Python.h: No such file or directory (Q 21530577)\n","permalink":"https://tokkabi.com/posts/fatal-error-pythonh-no-such-file-or-directory/","summary":"fatal error: Python.h: No such file or directory 해결 1. 문제 정의 Python의 C 확장 모듈(shared library)을 빌드하려고 C 파일을 컴파일하는 과정에서 아래와 같은 컴파일 에","title":"fatal error: Python.h: No such file or directory 해결"},{"content":"파이썬에서 예외(Exception)를 출력하는 방법 정리 1. 문제 정의 파이썬 코드에서 try ... except 블록을 사용할 때, 발생한 예외의 내용을 화면에 출력하고 싶다면 어떻게 해야 할까요? 많은 초보자가 다음과 같이 작성하지만 실패합니다.\n1 2 3 4 5 try: # 어떤 작업 수행 result = 1 / 0 except: print(exception) # ✅ 아님! NameError 발생 위 코드는 NameError: name 'exception' is not defined 에러를 일으킵니다. exception이라는 이름은 정의되지 않았기 때문입니다. except: 뒤에 오는 변수명은 예외 객체를 자동으로 만들어주지 않습니다. 예외 객체를 사용하려면 반드시 as 구문으로 받아야 합니다.\n이 문제는 StackOverflow에서 230만 회 이상 조회된 대표적인 파이썬 초보자 질문입니다. (출처: StackOverflow 1483429)\n2. 원인 탐구 왜 이런 혼란이 생길까요? 두 가지 요인이 있습니다.\n스택 트레이스(auto traceback)와 문자 메시지의 차이: 예외가 발생하면 파이썬 인터프리터가 기본적으로 전체 스택 트레이스를 출력합니다. 그래서 \u0026ldquo;그냥 출력되잖아\u0026rdquo; 라고 생각하기 쉽지만, except 블록 안에서는 인터프리터가 자동 출력을 하지 않으며 여러 예외를 처리하거나 로그를 남기려면 예외 객체를 직접 다뤄야 합니다.\n파이썬 버전별 문법 차이: 예외 객체를 받는 문법이 파이썬 버전에 따라 달라졌습니다.\n파이썬 2.5 이하: except Exception, e: 파이썬 2.6 이상 및 파이썬 3.x: except Exception as e: 사람들이 구버전 코드를 복사하다 보면 현재 버전에서 동작하지 않는 문법을 쓰게 됩니다. 비어 있는 except: 블록은 모든 예외를 잡지만 예외 객체에 접근할 방법이 없어서, 정작 무엇이 잘못되었는지 파악할 수 없게 됩니다.\n3. 근본 원인 분석 오해 실제 except: 만으로 예외 내용을 알 수 있다 아니다. except: 만으로는 예외 객체에 접근할 수 없다 print(exception) 변수가 자동 생성된다 아니다. 예외 객체를 받지 않으면 존재하지 않는 이름이다 예외 출력 방법은 하나뿐이다 print(e), traceback, logging 등 목적에 따라 선택해야 한다 근본 원인은 예외 객체를 as 구문으로 변수에 받아 들이지 않았기 때문입니다. except 절은 조건을 지정할 뿐, 그 자체로는 내용을 담지 않습니다. 예외의 정보를 활용하려면 반드시 except Exception as e: 형태로 예외 객체(여기서는 e)를 명시적으로 받아야 합니다.\n4. 코드 해결책 4-1. 기초: 예외 메시지만 출력 (파이썬 3.x, 2.6+) 1 2 3 4 try: result = 10 / 0 except Exception as e: print(e) # 출력: division by zero print(e)는 예외의 설명 문자열만 출력합니다. 간단한 확인 용도로 충분합니다.\n4-2. 예외 타입까지 함께 출력 1 2 3 4 5 try: result = 10 / 0 except Exception as e: print(f\u0026#34;{type(e).__name__}: {e}\u0026#34;) # 출력: ZeroDivisionError: division by zero 4-3. 완전한 스택 트레이스 출력 (traceback 모듈) print(e)는 메시지만 보여주므로 파일·줄 번호 등 위치 정보가 필요할 때는 traceback 모듈을 사용합니다.\n1 2 3 4 5 6 import traceback try: result = 10 / 0 except Exception: traceback.print_exc() # stderr로 전체 스택 트레이스 출력 문자열로 얻어 로그·파일에 저장하려면 traceback.format_exc()를 사용합니다.\n1 2 3 4 5 6 7 8 import traceback try: result = 10 / 0 except Exception: traceback_str = traceback.format_exc() with open(\u0026#34;error.log\u0026#34;, \u0026#34;w\u0026#34;) as f: f.write(traceback_str) 4-4. 로깅 시스템 활용 (권장) 운영 환경에서는 print 대신 logging 모듈에 예외를 남기는 것이 좋습니다. logger.exception()는 자동으로 스택 트레이스를 포함해 출력합니다.\n1 2 3 4 5 6 7 8 import logging logging.basicConfig(level=logging.ERROR) try: result = 10 / 0 except Exception: logging.exception(\u0026#34;계산 중 오류 발생\u0026#34;) # 예외 메시지 + 스택 트레이스 자동 포함 4-5. 파이썬 2.5 이하 구버전 대응 매우 오래된 파이썬 2.5 이하 환경에서만 유효합니다.\n1 2 3 4 try: result = 10 / 0 except Exception, e: print str(e) ⚠️ 현대 파이썬(3.x)에서는 이 문법이 동작하지 않습니다. 반드시 except Exception as e:를 사용하세요.\n방법 비교 요약 방법 출력 내용 사용 상황 print(e) 예외 메시지만 간단한 확인, 임시 디버깅 type(e).__name__ + e 예외 타입 + 메시지 원인 유형 파악이 필요할 때 traceback.print_exc() 전체 스택 트레이스 위치·호출 경로 확인이 필요할 때 logging.exception() 스택 트레이스 + 로그 운영 환경, 파일/로그에 기록할 때 5. 향후 예방 조치 빈 except: 대신 except Exception as e: 사용: 예외 객체를 받아야 내용을 알 수 있습니다. (단, 모든 예외를 무조건 삼키는 빈 except: 는 오류를 숨길 수 있으므로 피하세요.) 목적에 맞는 출력 선택: 간단 확인은 print(e), 상세 진단은 traceback, 운영 기록은 logging.exception()을 사용합니다. 스택 트레이스가 필요하면 traceback 모듈 사용: traceback.format_exc()로 문자열을 얻으면 파일 저장이나 API 호출에 재사용할 수 있습니다. 예외를 삼키지 않기: 예외를 출력 후 pass로 끝내면 문제를 숨기게 됩니다. 최소한 로그로 남기세요. 버전 문법 확인: 파이썬 3.x에서는 반드시 as 구문(except Exception as e:)을 사용합니다. 출처 StackOverflow: How do I print an exception in Python? (질문 ID 1483429) ","permalink":"https://tokkabi.com/posts/how-do-i-print-an-exception-in-python/","summary":"파이썬에서 예외(Exception)를 출력하는 방법 정리 1. 문제 정의 파이썬 코드에서 try ... except 블록을 사용할 때, 발생한 예외의 내용을 화면에 출력하고 싶다면 어","title":"파이썬에서 예외(Exception)를 출력하는 방법 정리"},{"content":"Python 2 UnicodeEncodeError: \u0026lsquo;ascii\u0026rsquo; codec can\u0026rsquo;t encode character — str() 대신 encode() 1. 문제 정의 웹 페이지(여러 사이트)에서 가져온 유니코드 텍스트를 처리하다가 다음과 같은 에러가 발생하는 상황입니다.\n1 2 3 4 Traceback (most recent call last): File \u0026#34;foobar.py\u0026#34;, line 792, in \u0026lt;module\u0026gt; p.agent_info = str(agent_contact + \u0026#39; \u0026#39; + agent_telno).strip() UnicodeEncodeError: \u0026#39;ascii\u0026#39; codec can\u0026#39;t encode character u\u0026#39;\\xa0\u0026#39; in position 20: ordinal not in range(128) 문제가 되는 코드는 다음과 같습니다.\n1 2 3 4 5 from bs4 import BeautifulSoup agent_telno = agent.find(\u0026#39;div\u0026#39;, \u0026#39;agent_contact_number\u0026#39;) agent_telno = \u0026#39;\u0026#39; if agent_telno is None else agent_telno.contents[0] p.agent_info = str(agent_contact + \u0026#39; \u0026#39; + agent_telno).strip() 핵심 증상은 다음과 같습니다.\n증상 설명 에러 유형 UnicodeEncodeError 실패 코드 str(...) 호출 과정에서 발생 실패 문자 \\xa0 (줄 바꿈 없는 공백, non-breaking space) 재현성 사이트에 따라 간헐적으로 발생 — 어떤 페이지는 되고 어떤 페이지는 실패 에러가 항상 재현되지 않는다는 것이 이 문제의 까다로운 점입니다. 어떤 페이지는 정상 동작하고, 다른 페이지에서는 에러를 던집니다.\n2. 원인 탐구 에러 메시지를 분해해 보면 원인을 추적할 수 있습니다.\n'ascii' codec can't encode — ASCII 코덱이 이 문자를 인코딩할 수 없다는 뜻입니다. character u'\\xa0' — \\xa0는 유니코드의 줄 바꿈 없는 공백(non-breaking space)으로, 웹 페이지 HTML에 자주 등장하는 문자입니다. ASCII에는 존재하지 않습니다. position 20 — 20번째 위치에 해당 문자가 있다는 뜻입니다. 즉, agent_contact 또는 agent_telno에 포함된 유니코드 문자열(예: \\xa0)을 str()로 변환하는 순간, Python 2의 str()이 기본 인코딩인 ASCII를 사용하려다 실패한 것입니다.\n왜 간헐적으로 발생할까요?\n일부 페이지의 텍스트는 순수 ASCII 문자만 포함 → str()이 성공 다른 페이지의 텍스트는 \\xa0처럼 ASCII 밖의 유니코드 문자를 포함 → str()이 실패 입력 데이터에 따라 성공/실패가 갈리기 때문에, 코드 자체는 똑같은데 에러가 항상 나지 않는 것입니다.\n3. 근본 원인 분석 근본 원인은 Python 2에서 str()로 유니코드를 변환하는 방식에 있습니다.\nPython 2에는 두 가지 문자열 타입이 있습니다.\n타입 설명 기본 인코딩 str 바이트 문자열 ASCII unicode 유니코드 문자열 — str()은 unicode → str 변환 과정에서 시스템 기본 인코딩(ASCII) 을 사용합니다. 따라서 변환 대상에 ASCII 범위 밖의 문자가 하나라도 있으면 UnicodeEncodeError가 발생합니다.\nPython 공식 문서(Python Unicode HOWTO)는 이 상황을 바로 이 예외 케이스로 소개합니다. ASCII 기본 인코딩으로는 \\xa0와 같은 유니코드 문자를 바이트로 표현할 수 없기 때문에 에러가 나는 것이며, 이는 코드가 아니라 str() 사용 방식의 문제입니다.\n즉 근본 원인은 다음 두 가지가 결합된 것입니다.\n웹에서 가져온 텍스트에 \\xa0 등 ASCII 밖 유니코드 문자가 존재함 str()이 기본 코드(ASCII)로 유니코드를 인코딩하려 함 4. 코드 해결책 해결 방법은 두 가지입니다. 핵심 원칙은 유니코드 → 바이트 변환에 str()을 쓰지 말고 .encode()를 쓰는 것입니다.\n방법 1: .encode('utf-8')로 명시적 인코딩 1 p.agent_info = u\u0026#39; \u0026#39;.join((agent_contact, agent_telno)).encode(\u0026#39;utf-8\u0026#39;).strip() u' '.join(...)로 유니코드 문자열을 만듭니다. .encode('utf-8')로 명시적으로 UTF-8 인코딩합니다. .strip()으로 앞뒤 공백을 제거합니다. \\xa0도 UTF-8에서는 정상적으로 인코딩되므로 에러가 발생하지 않습니다.\n방법 2: 전체를 유니코드로 처리 가능하다면 프로그램 전체에서 유니코드 문자열로만 작업하고, 입출력 경계(파일 쓰기, 네트워크 전송 등)에서만 인코딩하는 방식이 더 깔끔합니다.\n1 p.agent_info = (agent_contact + \u0026#39; \u0026#39; + agent_telno).strip() # str() 제거, 유니코드 유지 이렇게 하면 중간 단계에서 ASCII 강제 변환이 일어나지 않아 에러가 원천적으로 사라집니다.\n5. 향후 예방 조치 같은 에러를 반복하지 않으려면 다음을 지키세요.\n조치 설명 str() 대신 .encode() 사용 유니코드 → 바이트 변환은 반드시 .encode('utf-8')로 명시 전체를 유니코드로 유지 입출력 경계에서만 인코딩/디코딩 파이썬 3 사용 Python 3에서는 str이 유니코드가 되어 이 계열 에러가 크게 줄어듦 문자 규범화 \\xa0 같은 특수 문자가 필요하면 \\xa0 → 일반 공백 등으로 정규화 특히 \\xa0(줄 바꿈 없는 공백)는 웹 파싱에서 매우 흔하게 등장하므로, HTML에서 추출한 텍스트를 처리할 때는 항상 유니코드 인코딩을 염두에 두어야 합니다.\n출처 StackOverflow 질문 9942594: UnicodeEncodeError: \u0026lsquo;ascii\u0026rsquo; codec can\u0026rsquo;t encode character u\u0026rsquo;\\xa0\u0026rsquo; in position 20: ordinal not in range(128) Python 공식 문서: Unicode HOWTO ","permalink":"https://tokkabi.com/posts/python2-ascii-unicodeencodeerror-str-vs-encode/","summary":"Python 2 UnicodeEncodeError: \u0026lsquo;ascii\u0026rsquo; codec can\u0026rsquo;t encode character — str() 대신 encode() 1. 문제 정의 웹 페이지(여러 사이트)에서 가져온 유니코드 텍스트를 처리하다가 다음과 같은 에러가 발생하는 상황입니다. 1 2 3 4 Traceback","title":"Python 2 UnicodeEncodeError: 'ascii' codec can't encode character — str() 대신 encode() 사용하기"},{"content":"DevTrace 블로그 시작 안녕하세요. DevTrace입니다.\n이 블로그는 글로벌 개발 에러와 오픈소스 트러블슈팅을 다룹니다. 단순히 \u0026ldquo;이렇게 고치세요\u0026quot;가 아니라, 왜 이런 에러가 발생했는지 근본 원인(Root Cause)을 추적하는 것을 목표로 합니다.\n다루는 주제 GitHub Issues(Closed)에서 실제로 발생한 에러 로그 분석 StackOverflow에서 높은 조회수를 기록한 디버깅 사례 오픈소스 라이브러리 버전 업그레이드 시 발생하는 호환성 문제 코드 블록과 디버깅 표를 활용한 실전 해결책 글의 구조 모든 글은 다음 5단계 논리 구조를 따릅니다:\n문제 정의 — 어떤 에러가 발생했는가 원인 탐구 — 에러 로그와 재현 단계 근본 원인 분석 — 왜 이런 일이 벌어졌는가 코드 해결책 — 실제 동작하는 코드 향후 예방 조치 — 같은 실수를 반복하지 않으려면 운영 및 편집 프로세스 DevTrace는 글로벌 개발 커뮤니티의 실무 데이터를 기반으로 가장 가치 있는 기술 정보를 엄격하게 선별하여 발행합니다.\n데이터 기반 탐색: GitHub Issues 및 StackOverflow에서 전 세계 개발자들이 실제로 겪는 핵심 트러블슈팅 케이스를 수집합니다. 심층 분석 및 검증: 수집된 에러 로그를 코드 레벨에서 철저히 분석하고, 실제 환경에서 재현 및 해결 가능 여부를 검증합니다. 최적화된 콘텐츠 편집: 가독성 높은 코드 블록과 구조화된 디버깅 표를 적용하여, 방문자가 즉시 실무에 적용할 수 있는 완성도 높은 기술 문서를 지향합니다. 첫 포스트가 성공적으로 배포되었습니다. 앞으로 유용한 트러블슈팅 콘텐츠로 찾아뵙겠습니다.\n","permalink":"https://tokkabi.com/posts/hello-devtrace/","summary":"DevTrace 블로그 시작 안녕하세요. DevTrace입니다. 이 블로그는 글로벌 개발 에러와 오픈소스 트러블슈팅을 다룹니다. 단순히 \u0026ldquo;이렇게 고치세요\u0026","title":"DevTrace 블로그 시작 — 글로벌 개발 에러 트러블슈팅"},{"content":"개인정보처리방침 DevTrace(이하 \u0026ldquo;블로그\u0026rdquo;)는 이용자의 개인정보를 중요시하며, 「개인정보 보호법」 및 관련 법령을 준수합니다. 본 방침은 블로그가 어떤 정보를 수집하고, 어떻게 사용하며, 어떻게 보호하는지 설명합니다.\n1. 수집하는 정보 자동 수집 정보 블로그 방문 시 다음과 같은 정보가 자동으로 수집될 수 있습니다:\n접속 로그: IP 주소, 브라우저 종류, 운영체제, 방문 일시 쿠키(Cookie): 사이트 설정 저장, 방문 기록 분석 분석 데이터: 페이지 조회수, 방문 경로, 체류 시간 Google 애드센스 블로그는 Google 애드센스를 통해 광고를 게재합니다. Google 및 광고 파트너사는 쿠키를 사용하여 광고를 게재하고, 사용자의 관심사에 기반한 맞춤 광고를 제공할 수 있습니다.\nGoogle의 쿠키 사용: Google 광고 쿠키 정책 사용자는 Google 광고 설정에서 맞춤 광고를 거부할 수 있습니다. 사용자는 www.aboutads.info에서 타사 광고 쿠키를 거부할 수 있습니다. NONE(필수 아님) 블로그는 회원가입, 댓글 작성, 뉴스레터 구독 등 이용자가 정보를 직접 입력하는 기능을 제공하지 않습니다.\n2. 수집한 정보의 이용 목적 사이트 트래픽 분석 및 서비스 개선 광고 게재 및 성과 측정 부정 이용 방지 3. 쿠키 관리 방법 브라우저 설정에서 쿠키 차단 가능 Chrome: 설정 → 개인정보 및 보안 → 쿠키 및 기타 사이트 데이터 Safari: 설정 → Safari → 쿠키 및 웹 사이트 데이터 Edge: 설정 → 쿠키 및 사이트 권한 → 쿠키 및 사이트 데이터 관리 쿠키를 차단하더라도 블로그의 기본 콘텐츠 이용에는 제한이 없습니다.\n4. 수집한 정보의 보유 및 파기 수집된 자동 로그 정보는 분석 목적에 필요한 기간 동안만 보관하며, 보관 목적이 달성되면 안전하게 파기합니다.\n5. 제3자 제공 블로그는 이용자의 개인정보를 제3자에게 판매하거나 임대하지 않습니다. 다만, 다음의 경우에는 예외로 합니다:\n법령에 의해 요구되는 경우 수사기관의 적법한 요청이 있는 경우 6. 문의 개인정보 처리방침에 관한 문의 사항이 있으시면 아래로 연락해 주시기 바랍니다:\n이메일: mazellan99@gmail.com 7. 방침 변경 본 방침은 법률 또는 서비스 정책 변경 시 사전 고지 후 변경될 수 있습니다.\n최초 시행일: 2026년 7월 1일 최종 수정일: 2026년 7월 1일 ","permalink":"https://tokkabi.com/privacy-policy/","summary":"개인정보처리방침 DevTrace(이하 \u0026ldquo;블로그\u0026rdquo;)는 이용자의 개인정보를 중요시하며, 「개인정보 보호법」 및 관련 법령을 준수","title":"개인정보처리방침"},{"content":"문의하기 DevTrace 블로그에 대한 문의, 제안, 오류 신고, 광고 문의 등은 아래 이메일로 연락해 주시기 바랍니다.\n이메일 문의 이메일: mazellan99@gmail.com 문의 시 참고 사항 에러 관련 문의: 해당 에러가 발생한 환경(OS, 언어 버전, 라이브러리 버전)과 에러 로그를 함께 보내주시면 더 정확한 답변을 드릴 수 있습니다. 광고 문의: 광고 게재 관련 문의는 제목에 [광고]를 붙여 보내주시기 바랍니다. 응답 시간: 영업일 기준 2~3일 이내에 답변드립니다. 자주 묻는 질문 블로그의 글을 인용해도 되나요? 네, 출처를 명시하고 링크를 걸어주시면 자유롭게 인용하실 수 있습니다.\n블로그에 글을 기고할 수 있나요? 현재 외부 기고는 받지 않지만, 좋은 트러블슈팅 사례가 있으시면 이메일로 공유해 주시면 검토 후 반영할 수 있습니다.\n","permalink":"https://tokkabi.com/contact/","summary":"문의하기 DevTrace 블로그에 대한 문의, 제안, 오류 신고, 광고 문의 등은 아래 이메일로 연락해 주시기 바랍니다. 이메일 문의 이메일: mazellan99@gmail.com 문의 시 참고 사항 에러 관련 문의: 해","title":"문의하기"},{"content":"블로그 소개 DevTrace란? DevTrace는 글로벌 오픈소스 에러와 시스템 트러블슈팅을 근본 원인부터 심층 추적하는 한국어 기술 블로그입니다.\n단순히 \u0026ldquo;이렇게 고치세요\u0026quot;라는 해결책만 제시하는 것이 아니라, 왜 이런 에러가 발생했는지를 코드 레벨에서 분석하고, 재발을 방지할 수 있는 실질적인 지식을 전달하는 것을 목표로 합니다.\n다루는 주제 오픈소스 에러 분석: GitHub Issues에서 실제로 발생한 에러 로그를 근본 원인부터 분석 시스템 트러블슈팅: 배포, 빌드, 런타임 환경에서 발생하는 시스템 레벨 문제 해결 언어별 디버깅: Python, JavaScript, Go 등 다양한 언어의 예외 처리와 디버깅 기법 실전 코드 해결책: 코드 블록과 구조화된 디버깅 표로 가독성을 극대화한 해결책 제공 글의 구조 모든 글은 다음 5단계 논리 구조를 따릅니다:\n문제 정의 — 어떤 에러가 발생했는가 원인 탐구 — 에러 로그와 재현 단계 근본 원인 분석 — 왜 이런 일이 벌어졌는가 코드 해결책 — 실제 동작하는 코드 향후 예방 조치 — 같은 실수를 반복하지 않으려면 운영 및 편집 프로세스 DevTrace는 글로벌 개발 커뮤니티의 실무 데이터를 기반으로 가장 가치 있는 기술 정보를 엄격하게 선별하여 발행합니다.\n데이터 기반 탐색: GitHub Issues 및 StackOverflow에서 전 세계 개발자들이 실제로 겪는 핵심 트러블슈팅 케이스를 수집합니다. 심층 분석 및 검증: 수집된 에러 로그를 코드 레벨에서 철저히 분석하고, 실제 환경에서 재현 및 해결 가능 여부를 검증합니다. 최적화된 콘텐츠 편집: 가독성 높은 코드 블록과 구조화된 디버깅 표를 적용하여, 방문자가 즉시 실무에 적용할 수 있는 완성도 높은 기술 문서를 지향합니다. 문의 블로그에 대한 문의나 제안이 있으시면 아래로 연락해 주시기 바랍니다:\n이메일: mazellan99@gmail.com ","permalink":"https://tokkabi.com/about/","summary":"블로그 소개 DevTrace란? DevTrace는 글로벌 오픈소스 에러와 시스템 트러블슈팅을 근본 원인부터 심층 추적하는 한국어 기술 블로그입니다. 단순히","title":"블로그 소개"},{"content":"이용약관 본 약관은 DevTrace 블로그(이하 \u0026ldquo;블로그\u0026rdquo;)의 이용과 관련하여 블로그와 이용자 간의 권리, 의무 및 책임 사항을 규정합니다.\n제1조 (목적) 본 약관은 블로그가 제공하는 콘텐츠와 서비스의 이용 조건 및 절차, 이용자와 블로그의 권리·의무 및 책임 사항을 규정함을 목적으로 합니다.\n제2조 (약관의 효력 및 변경) 본 약관은 블로그에 게시함으로써 효력이 발생합니다. 블로그는 관련 법령을 위배하지 않는 범위에서 본 약관을 변경할 수 있으며, 변경된 약관은 사전 고지 후 효력이 발생합니다. 제3조 (콘텐츠의 저작권) 블로그에 게시된 모든 콘텐츠(글, 코드, 이미지, 표 등)의 저작권은 블로그 운영자에게 있습니다. 블로그의 콘텐츠를 인용할 경우, 출처를 명시하고 원문에 대한 링크를 제공해야 합니다. 블로그의 콘텐츠를 상업적 목적으로 무단 복제, 배포, 수정하는 행위는 금지됩니다. 제4조 (콘텐츠의 정확성) 블로그의 콘텐츠는 정보 제공 목적으로 게시되며, 특정 상황에 대한 전문적인 조언을 대체하지 않습니다. 블로그의 콘텐츠를 활용하여 발생하는 문제에 대해 블로그는 책임을 지지 않습니다. 코드 예제는 특정 환경에서 검증된 것이며, 사용자의 환경에 따라 다르게 동작할 수 있습니다. 제5조 (외부 링크) 블로그에는 외부 사이트로 연결되는 링크가 포함될 수 있습니다. 외부 사이트의 콘텐츠는 해당 사이트의 정책이 적용되며, 블로그는 외부 사이트의 콘텐츠에 대해 책임을 지지 않습니다.\n제6조 (광고 게재) 블로그는 Google 애드센스 등 광고 서비스를 통해 광고를 게재할 수 있습니다. 광고주의 정보 및 광고 콘텐츠에 대한 책임은 해당 광고주에게 있습니다.\n제7조 (면책 조항) 블로그는 천재지변, 서버 장애 등 불가항력적인 사유로 인한 서비스 중단에 대해 책임을 지지 않습니다. 블로그는 이용자의 귀책 사유로 인한 서비스 이용상의 손해에 대해 책임을 지지 않습니다. 제8조 (분쟁 해결) 본 약관과 관련하여 발생한 분쟁에 대해서는 대한민국 법을 적용하며, 분쟁 해결을 위해 필요한 경우 관할 법원에서 해결합니다.\n제9조 (문의) 본 약관에 대한 문의 사항이 있으시면 아래로 연락해 주시기 바랍니다:\n이메일: mazellan99@gmail.com\n시행일: 2026년 7월 1일\n","permalink":"https://tokkabi.com/terms/","summary":"이용약관 본 약관은 DevTrace 블로그(이하 \u0026ldquo;블로그\u0026rdquo;)의 이용과 관련하여 블로그와 이용자 간의 권리, 의무 및 책임 사항을 규정합니다. 제1조","title":"이용약관"}]