「console.logだけのデバッグから卒業したい…」
「非同期処理のデバッグってどうすればいい?」
「パフォーマンスの問題箇所を特定するには?」
前回はJestを使用したテスト手法について学びました。今回は、開発者ツールを活用した効率的なデバッグ手法を解説します。
基本的なデバッグ手法
console系メソッドの使い分け
JS
// 基本的なログ
console.log('通常のログ');
console.info('情報');
console.warn('警告');
console.error('エラー');
// オブジェクトの詳細表示
const user = {
name: 'John',
age: 30,
address: { city: 'Tokyo' }
};
console.log('単純な出力:', user);
console.dir(user); // プロパティをツリー表示
// テーブル形式での出力
const users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];
console.table(users); // 表形式で表示
// 処理時間の計測
console.time('処理時間');
// 重い処理
console.timeEnd('処理時間');
使い分けのポイント:
console.log
: 一般的なデバッグ情報console.dir
: オブジェクトの階層構造の確認console.table
: 配列やオブジェクトの一覧性の高い表示console.time/timeEnd
: パフォーマンス計測
ブレークポイントの活用
JS
class UserManager {
async fetchUsers() {
try {
// ここにブレークポイントを設定
const response = await fetch('/api/users');
const users = await response.json();
// 条件付きブレークポイント
users.forEach(user => {
if (user.id === 5) {
debugger; // 特定の条件で停止
}
this.processUser(user);
});
return users;
} catch (error) {
console.error('Failed to fetch users:', error);
throw error;
}
}
}
ブレークポイントの種類:
- 通常のブレークポイント:特定の行で停止
- 条件付きブレークポイント:条件が真の時のみ停止
- XHRブレークポイント:特定のAPIリクエストで停止
- イベントリスナーブレークポイント:特定のイベントで停止
非同期処理のデバッグ
JS
async function fetchUserData() {
try {
const startTime = performance.now();
// Promiseの状態を確認
const userPromise = fetch('/api/user');
console.log('Promise状態:', userPromise);
const response = await userPromise;
const userData = await response.json();
const endTime = performance.now();
console.log(`処理時間: ${endTime - startTime}ms`);
return userData;
} catch (error) {
console.error('Error stack:', error.stack);
throw error;
}
}
// async/awaitのデバッグ
async function debugAsyncFlow() {
console.group('非同期処理フロー');
try {
console.log('処理開始');
const result = await fetchUserData();
console.log('処理完了:', result);
} catch (error) {
console.error('エラー発生:', error);
}
console.groupEnd();
}
非同期デバッグのポイント:
- Promise の状態確認
- async/await の実行フロー追跡
- エラースタックの確認
- 処理時間の計測
パフォーマンス計測
JS
// パフォーマンスマークの設定
performance.mark('処理開始');
// 重い処理
for (let i = 0; i < 1000000; i++) {
// 何か処理
}
performance.mark('処理終了');
// 計測結果の取得
performance.measure('処理時間', '処理開始', '処理終了');
const measurements = performance.getEntriesByType('measure');
console.log(measurements);
// メモリ使用状況の確認
const memory = performance.memory; // Chrome のみ
console.log('ヒープサイズ上限:', memory.jsHeapSizeLimit);
console.log('現在のヒープサイズ:', memory.usedJSHeapSize);
メモリリークの調査
JS
class EventManager {
constructor() {
this.handlers = new Set();
// メモリリークの可能性がある実装
window.addEventListener('resize', this.onResize.bind(this));
}
onResize() {
this.handlers.forEach(handler => handler());
}
// クリーンアップが必要
dispose() {
window.removeEventListener('resize', this.onResize.bind(this));
this.handlers.clear();
}
}
// メモリリークを防ぐ実装
class BetterEventManager {
constructor() {
this.handlers = new Set();
this.boundOnResize = this.onResize.bind(this);
window.addEventListener('resize', this.boundOnResize);
}
dispose() {
window.removeEventListener('resize', this.boundOnResize);
this.handlers.clear();
}
}
実践的なデバッグ例
イベントデリゲーションのデバッグ
JS
class ListManager {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.setupEventListeners();
}
setupEventListeners() {
this.container.addEventListener('click', (e) => {
// イベントの伝播を確認
console.log('Event path:', e.composedPath());
const button = e.target.closest('button');
if (!button) return;
// データ属性の確認
console.log('Button data:', button.dataset);
// 特定の条件でブレークポイント
if (button.dataset.action === 'delete') {
debugger;
}
this.handleButtonClick(button);
});
}
handleButtonClick(button) {
const action = button.dataset.action;
const itemId = button.dataset.id;
console.group(`Button Click: ${action}`);
console.log('Item ID:', itemId);
console.log('Button:', button);
console.groupEnd();
// アクションの実行
this[action](itemId);
}
}
ネットワークリクエストのデバッグ
JS
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const startTime = performance.now();
console.group(`API Request: ${endpoint}`);
console.log('URL:', url);
console.log('Options:', options);
try {
const response = await fetch(url, options);
const endTime = performance.now();
console.log('Response status:', response.status);
console.log('Response time:', endTime - startTime);
const data = await response.json();
console.log('Response data:', data);
console.groupEnd();
return data;
} catch (error) {
console.error('Request failed:', error);
console.groupEnd();
throw error;
}
}
}
デバッグのベストプラクティス
1.段階的なデバッグ
JS
function complexOperation(data) {
console.group('複雑な処理の実行');
// 入力データの確認
console.log('Input data:', data);
// 中間処理の確認
const intermediate = processData(data);
console.log('Intermediate result:', intermediate);
// 最終結果の確認
const result = finalizeData(intermediate);
console.log('Final result:', result);
console.groupEnd();
return result;
}
2.エラーハンドリングの改善
JS
class ErrorBoundary {
static handleError(error, context = '') {
console.group(`Error in ${context}`);
console.error('Error message:', error.message);
console.error('Stack trace:', error.stack);
console.log('Error details:', error);
// カスタムエラー情報の記録
if (error.response) {
console.log('Response:', error.response);
}
console.groupEnd();
}
}
まとめ
効果的なデバッグのために:
- 適切なツールの使い分け
- 段階的なアプローチ
- パフォーマンスの意識
- エラー情報の充実