부스트캠프 웹·모바일 9기 베이직 과정에서 미션을 수행하던 중 디버깅에 대해 공부하게 됐습니다. 디버깅의 중요성을 깨닫고, 이를 개발 블로그에 기록하여 공유하고자 합니다.
디버깅(Debugging)
은 소프트웨어 개발 과정에서 발생하는 오류(버그)를 찾아내고 수정하는 핵심 과정입니다. 소프트웨어가 예상대로 작동하지 않을 때, 프로그래머는 코드의 오류를 식별하고 해결하기 위해 디버깅을 수행합니다. 이 과정에서는 주로 IDE(Integrated Development Environment)와 같은 도구를 사용하여 코드를 단계별로 실행하고, 변수의 값을 확인하며, 프로그램의 흐름을 추적합니다. 디버깅은 소프트웨어의 안정성과 신뢰성을 높이며, 개발 생산성을 향상시키는 데 중요한 역할을 합니다.
IDE, Intergrated Development Environment, 통합 개발 환경?
통합 개발 환경(IDE)은 프로그래머가 소프트웨어 코드를 효율적으로 개발하도록 돕는 소프트웨어 애플리케이션입니다. 이는 소프트웨어 편집, 빌드, 테스트, 패키징 등의 기능을 하나의 사용자 친화적인 환경에서 제공하여 개발자 생산성을 높입니다. 작가가 텍스트 편집기를 사용하고 회계사가 스프레드 시트를 사용하는 것처럼 소프트웨어 개발자는 IDE를 사용해 작업을 쉽게 처리합니다.
예시로는 Visual Studio Code, IntelliJ IDEA, PyCharm 등이 있습니다.
컴퓨터 프로그래밍은 추상적이고 개념적인 활동으로, 예기치 못한 버그와 오류가 발생하기 마련입니다. 모든 종류의 소프트웨어는 여러 가지 추상화 계층을 거치며 다양한 구성 요소들이 상호작용하여 작동합니다. 이런 복잡성으로 인해 소프트웨어는 종종 예상치 못한 문제에 직면하게 되는데, 이러한 문제를 디버깅을 통해 해결할 수 있습니다.
디버깅이 필요한 이유:
오류 발생 시 원인을 찾기 어려울 수 있습니다.
디버깅 도구는 코드의 실행 과정을 추적하고 변수의 값을 확인하여 복잡성을 해결하는 데 도움을 줍니다.문제 해결에 더 적은 시간을 소비
하게 하여 생산성을 높이는 데 기여합니다.디버깅은 소프트웨어 개발 과정에서 필수적인 활동으로, 오류 발견과 해결을 통해 소프트웨어의 완성도와 신뢰성을 높이는 데 중요한 역할을 합니다.
대부분의 최신 웹 브라우저들은 개발자 도구를 통해 JavaScript 코드를 디버깅 할 수 있는 기능을 제공합니다. 주로 Chrome 개발자 도구를 예시로 설명하겠습니다.
Chrome 개발자 도구는 Windows에서는 F12
또는 Ctrl+Alt+I
, Ctrl+Alt+C
, Mac에서는 Command+option+I
단축키로 열 수 있습니다.
소스
탭 > 좌측 상단에 페이지
를 눌러서 현재 페이지의 소스 코드를 확인합니다.
브레이크 포인트 설정: 코드 특정 라인에 중단점을 설정하여 코드 실행을 멈추고, 해당 시점에서 변수의 값을 확인할 수 있습니다.
스텝 인/오버: 코드를 한 줄씩 실행하거나 함수 진입점에서 실행을 중지하고, 코드의 흐름을 단계별로 따라갈 수 있습니다.
왼쪽부터 Resume
, Step Over
, Step into
, Step out
, Step
, 모든 중단점 활성화/비활성화
콘솔 로그 및 변수 확인: cosnole.log()
를 통해 특정 변수의 값을 콘솔 탭
에 출력하여 코드 실행 중 데이터의 상태를 확인할 수 있습니다.
(function () {
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const total = nums.reduce((acc, currNum) => acc + currNum, 0);
console.log(total);
})();
위 코드 실행 결과:
debugger
문 사용하기JavaScript 코드 내에서 debugger;
문을 사용하여 명시적으로 중단점을 설정할 수 있습니다. 이는 특정 조건이나 특정 함수 실행 시점에서 코드를 중지시키는 데 유용합니다. 이 경우에는 브라우저 개발자 도구에서 자동으로 해당 위치에서 코드 실행을 멈춥니다.
function add(n1, n2){
const result = n1 + n2;
debugger; // 해당 코드에서 중지
return result;
}
JavaScript에서 예외 처리를 통해 코드에서 발생한 오류를 추적하고 처리할 수 있습니다. try-catch
문을 사용하여 예외가 발생한 부분에서 오류 메시지를 콘솔에 출력하거나 다른 처리를 할 수 있습니다.
try{
const x;
} catch(err){
console.error(err);
// Uncaught \n SyntaxError: Missing initializer in const declaration
}
개발자 도구 소스 탭에서 확인한 에러:
개발자 도구 콘솔 탭에서 확인한 에러 메시지:
주요 통합 개발 환경(IDE)들은 강력한 디버깅 도구를 포함하고 있어, JavaScript 코드의 오류를 해결할 수 있습니다. 예를 들어, Visual Stodio Code는 사용자가 코드를 단계별로 실행하고 변수의 값을 실시간으로 확인할 수 있는 디버거를 제공합니다. 디버깅 세션을 시작하고 중단점을 설정하여 코드의 실행을 멈추고 문제의 원인을 분석하는 등, 다양한 디버깅 기능을 활용할 수 있습니다.
Visual Studio Code 디버깅 도구:
IntelliJ IDEA 디버깅 도구:
Visual Studio Code 공식 문서에서 가져온 내용입니다.🫡
Visual Studio Code(VS Code)는 Node.js
런타임을 위한 built-in 디버깅 지원 기능을 제공하며, JavaScript, TypeScript 또는 JavaScript로 트랜스파일되는 다른 언어도 디버깅할 수 있습니다.
트랜스파일, transpile?
소스 코드를 한 프로그래밍 언어에서 다른 프로그래밍 언어로 변환하는 과정을 의미합니다.
VS Code의 실행 및 디버그 뷰를 열려면, 측면의 활동 표시줄에서 Run and Debug
아이콘을 선택합니다. 또는 단축키 Ctrl+Shift+D
를 사용할 수 있습니다.
Run and Debug
뷰에는 실행 및 디버깅과 관련된 모든 정보가 표시되며, 디버깅 명령 및 구성 설정이 있는 상단 바가 포함되어 있습니다.
만약 실행 및 디버깅이 아직 구성되어 있지 않은 경우(launch.json 파일이 생성되지 않은 경우), VS Code는 실행 시작 화면을 표시합니다.
VS Code에서 간단한 앱을 실행하거나 디버깅하려면, 디버그 시작 화면에서 Run and Debug
를 선택하거나 F5
키를 누르면 현재 활성 파일을 실행하려고 시도합니다.
그러나 대부분의 디버깅 시나리오에서는 실행 구성 파일을 생성하는 것이 유리합니다. 이는 디버깅 설정 세부 성보를 구성하고 저장할 수 있게 해줍니다. VS Code는 작업 공간의 .vscode
폴더 또는 사용자 설정 또는 작업 공간 설정에 launch.json
파일로 디버깅 구성 정보를 저장합니다.
launch.json
파일을 생성하려면, 실행 시작 화면에서 create a launch.json file
을 선택합니다.
VS Code는 디버그 환경을 자동으로 감지하려고 시도하지만, 실패할 경우 수동으로 선택해야 합니다.
Node.js 디버깅을 위한 launch.json
파일 예시는 다음과 같습니다.
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\app.js"
}
]
}
파일 탐색기 뷰로 돌아가면 (Ctrl+Shift+E
), VS Code가 .vscode
폴더를 생성하고 launch.json
파일을 작업 공간에 추가한 것을 볼 수 있습니다.
디버그 세션이 시작되면, 편집기 상단에 디버그 툴바가 나타납니다.
F5
F10
: 다음 메서드를 단일 명령으로 실행하며, 구성 요소 단계를 따르지 않습니다.F11
: 다음 메서드로 진입하여 한 줄씩 실행을 따라갑니다.Shift+F11
: 메서드나 서브루틴 내부에서 현재 메서드의 남은 라인을 단일 명령처럼 실행하여 이전 실행 컨텍스트로 돌아갑니다.Ctrl+Shift+F5
: 현재 프로그램 실행을 종료하고 현재 실행 구성으로 디버깅을 다시 시작합니다.Shift+F5
: 현재 프로그램 실행을 종료합니다.편집기 여백을 클릭하거나 현재 줄에서 F9
를 사용하여 중단점을 토글할 수 있습니다. 더 세밀한 중단점 제어(활성화/비활성화/재적용)는 실행 및 디버그 뷰의 중단점 섹션에서 할 수 있습니다.
중단점은 일반적으로 빨간색 채워진 원
으로 표시됩니다. 비활성화된 중단점은 회색으로 채워진 원
으로 표시됩니다. 디버깅 세션이 시작될 때 등록할수 없는 중단점은 회색 빈 원으로 변경됩니다. 디버거가 다양한 종류의 오류나 예외에서 중단을 지원하는 경우, 이러한 옵션도 중단점 뷰에서 사용할 수 있습니다.
Reapply All Breakpoints
명령은 모든 중단점을 원래 위치에 다시 설정합니다. 이는 디버그 환경이 중단점을 배치하는 경우에 유용합니다.
설정에서 debug.showBreakpointsInOverviewRuler
를 활성화하여 중단점을 편집기의 개요 눈금자에 표시할 수 있습니다.
변수는 Run and Debug
뷰의 VARIABLES
섹션이나 편집기에서 소스 위에 마우스를 올려 놓아 검사할 수 있습니다. 변수 값과 표현식 평가는 CALL STACK
섹션의 선택된 스택 프레임에 상대적입니다.
변수 값은 변수의 컨텍스트 메뉴에서 Set Value
작업을 통해 수정할 수 있습니다. 또한, Copy Value
작업을 사용하여 변수 값을 복사하거나 Copy as Expression
작업을 사용하여 변수에 접근하는 표현식을 복사할 수 있습니다.
변수와 표현식은 Run and Debug
뷰의 WATCH
섹션에서 평가하고 감시할 수 있습니다.
VARIABLES
섹션에 포커스가 있는 동안 타이핑하여 변수 이름과 값을 필터링할 수 있습니다.
N * M
크기의 두 행렬 A와 B가 주어졌을 때, 두 행렬을 더하는 프로그램을 작성하시오.4 3
1 1 1
2 2 2
0 1 0
1 1 1
3 3 3
4 4 4
5 5 100
2 2 2
4 4 4
6 6 6
5 6 100
3 3 3
3*3
크기의 행렬을 테스트 케이스로 입력할 때는 잘 수행되지만, 4*3
크기의 행렬을 테스트 케이스로 입력할 경우에는 원하는 정답이 나오지 않았습니다.
const path = require("path");
const fs = require("fs");
const input = fs
.readFileSync(path.join(__dirname, "../dev/stdin"))
.toString()
.trim()
.split("\r\n");
const [N, M] = input[0].split(" ").map(Number);
function getMatrix(matrixData, row) {
const matrixA = [];
const matrixB = [];
for (let i = 0; i < matrixData.length; i++) {
if (Math.floor(i / row) > 0) {
matrixB.push(matrixData[i].split(" ").map(Number));
} else {
matrixA.push(matrixData[i].split(" ").map(Number));
}
}
return [matrixA, matrixB];
}
function addMatrix(matrixA, matrixB, row, col) {
const [flatMatrixA, flatMatrixB] = [
matrixA.flat(Infinity),
matrixB.flat(Infinity),
];
const resultMatrix = Array.from({ length: row }, () =>
new Array(col).fill(0)
);
for (let i = 0; i < flatMatrixA.length; i++) {
resultMatrix[Math.floor(i / row)][i % col] =
flatMatrixA[i] + flatMatrixB[i];
}
return resultMatrix;
}
function solution(input, n, m) {
const matrixData = input.slice(1);
const [matrixA, matrixB] = getMatrix(matrixData, n);
const resultMatrix = addMatrix(matrixA, matrixB, n, m);
return resultMatrix.map((row) => row.join(" ")).join("\n");
}
console.log(solution(input, N, M));
Run and Debug
기능을 통해 알아낸 오류디버깅을 통해 resultMatrix[Math.floor(i / row)][i % col]
코드에서 resultMatrix
배열의 1번째 행의 값이 두 번 재할당되는 것을 확인했습니다. 이 문제는 배열의 인덱스 계산에서 행(row) 대신 열(col)을 사용해야 하는 것에서 발생했습니다.
반복문에서 i가 0~2까지 진행된 경우:
반복문에서 i가 3으로 진행된 경우:
const path = require("path");
const fs = require("fs");
const input = fs
.readFileSync(path.join(__dirname, "../dev/stdin"))
.toString()
.trim()
.split("\r\n");
const [N, M] = input[0].split(" ").map(Number);
function getMatrix(matrixData, row) {
const matrixA = [];
const matrixB = [];
for (let i = 0; i < matrixData.length; i++) {
if (Math.floor(i / row) > 0) {
matrixB.push(matrixData[i].split(" ").map(Number));
} else {
matrixA.push(matrixData[i].split(" ").map(Number));
}
}
return [matrixA, matrixB];
}
function addMatrix(matrixA, matrixB, row, col) {
const [flatMatrixA, flatMatrixB] = [
matrixA.flat(Infinity),
matrixB.flat(Infinity),
];
const resultMatrix = Array.from({ length: row }, () =>
new Array(col).fill(0)
);
for (let i = 0; i < flatMatrixA.length; i++) {
resultMatrix[Math.floor(i / col)][i % col] =
flatMatrixA[i] + flatMatrixB[i]; // 해당 코드 변경 후 문제 풀이 해결
}
return resultMatrix;
}
function solution(input, n, m) {
const matrixData = input.slice(1);
const [matrixA, matrixB] = getMatrix(matrixData, n);
const resultMatrix = addMatrix(matrixA, matrixB, n, m);
return resultMatrix.map((row) => row.join(" ")).join("\n");
}
console.log(solution(input, N, M));
VS Code에서 Run and Debug 기능을 사용하지 않고 디버깅할 때는 console.log()
를 사용하여 변수의 값을 확인했습니다. 그러나 이는 코드 한 줄씩 확인할 수 없어 불편했습니다.
이번에 Run and Debug 기능을 사용해보니 한 줄씩 코드를 확인하고, 변수에 어떤 값이 들어있는지 실시간으로 볼 수 있어 훨씬 편리했습니다. 이를 통해 로직의 문제를 더 빠르게 찾아낼 수 있었습니다. 앞으로는 이 기능을 더욱 자주 사용해야겠습니다.
AWS: 디버깅이란 무엇입니까?
F-LAB: 효율적인 디버깅 기술과 전략
AWS: 통합 개발 환경(IDE)란 무엇인가요?
Visual Studio Code - Debugging
IntelliJ IDEA - Debugging