AIコードレビュー比較:Claude Code vs Qwen 3.5(35B)でJavaScriptリファクタリングを検証

目次
注意事項
- 本記事の内容は試験的な実装であり、アイデアベースの検証です
- 実務での利用を保証するものではありません
- 実装についての責任は負いかねます。自己責任でご利用ください
- AIの出力結果は常に検証が必要です
同じJavaScriptコードに対して Claude Code(Opus) と Qwen 3.5:35b-a3b-coding(ローカルLLM) でリファクタリング&コードレビューを実施し、その結果を比較しました。
お題のコード
CSV形式の注文データを読み込み、月別売上レポートを出力するNode.jsバッチスクリプトです。意図的に古いスタイル(var、手書きソート、エラーハンドリングなし等)で書かれています。
const fs = require('fs');
// ユーザーの注文データを読み込んで月別売上レポートを出力するバッチ
// CSV形式: id,name,email,amount,date,category
var data = fs.readFileSync('orders.csv', 'utf-8');
var lines = data.split('\n');
var results = [];
var totalAmount = 0;
var categoryTotals = {};
var monthlyTotals = {};
var userPurchaseCount = {};
var errors = [];
// ヘッダースキップ
for (var i = 1; i < lines.length; i++) {
if (lines[i] == '') {
continue;
}
var columns = lines[i].split(',');
var id = columns[0];
var name = columns[1];
var email = columns[2];
var amount = columns[3];
var date = columns[4];
var category = columns[5];
// バリデーション
if (amount == undefined || amount == '' || amount == null) {
errors.push('行 ' + i + ': 金額が空です');
continue;
}
if (Number(amount) < 0) {
errors.push('行 ' + i + ': 金額がマイナスです');
continue;
}
// 金額を合計
totalAmount = totalAmount + Number(amount);
// カテゴリ別集計
if (categoryTotals[category] == undefined) {
categoryTotals[category] = 0;
}
categoryTotals[category] = categoryTotals[category] + Number(amount);
// 月別集計
var month = date.split('-')[0] + '-' + date.split('-')[1];
if (monthlyTotals[month] == undefined) {
monthlyTotals[month] = 0;
}
monthlyTotals[month] = monthlyTotals[month] + Number(amount);
// ユーザーごとの購入回数
if (userPurchaseCount[email] == undefined) {
userPurchaseCount[email] = 0;
}
userPurchaseCount[email] = userPurchaseCount[email] + 1;
// 結果に追加
results.push({
id: id,
name: name,
email: email,
amount: Number(amount),
date: date,
category: category,
month: month
});
}
// レポート出力
var report = '';
report = report + '=== 売上レポート ===\n';
report = report + '処理件数: ' + results.length + '\n';
report = report + '総売上: ¥' + totalAmount + '\n';
report = report + '\n';
// カテゴリ別
report = report + '--- カテゴリ別売上 ---\n';
var categoryKeys = Object.keys(categoryTotals);
for (var i = 0; i < categoryKeys.length; i++) {
var key = categoryKeys[i];
var percent = categoryTotals[key] / totalAmount * 100;
report = report + key + ': ¥' + categoryTotals[key] + ' (' + Math.round(percent) + '%)\n';
}
report = report + '\n';
// 月別
report = report + '--- 月別売上 ---\n';
var monthKeys = Object.keys(monthlyTotals);
for (var i = 0; i < monthKeys.length; i++) {
var key = monthKeys[i];
report = report + key + ': ¥' + monthlyTotals[key] + '\n';
}
report = report + '\n';
// トップ購入者
report = report + '--- 購入回数トップ5 ---\n';
var userKeys = Object.keys(userPurchaseCount);
var userList = [];
for (var i = 0; i < userKeys.length; i++) {
userList.push({ email: userKeys[i], count: userPurchaseCount[userKeys[i]] });
}
// ソート
for (var i = 0; i < userList.length; i++) {
for (var j = i + 1; j < userList.length; j++) {
if (userList[j].count > userList[i].count) {
var temp = userList[i];
userList[i] = userList[j];
userList[j] = temp;
}
}
}
for (var i = 0; i < 5; i++) {
if (userList[i] != undefined) {
report = report + (i + 1) + '位: ' + userList[i].email + ' (' + userList[i].count + '回)\n';
}
}
report = report + '\n';
// エラー出力
if (errors.length > 0) {
report = report + '--- エラー ---\n';
for (var i = 0; i < errors.length; i++) {
report = report + errors[i] + '\n';
}
}
// ファイルに書き出し
fs.writeFileSync('report.txt', report);
console.log('レポートを出力しました: report.txt');
console.log('エラー件数: ' + errors.length);
使用モデル
| 項目 | Claude Code | Qwen 3.5 |
|---|---|---|
| モデル | Claude Opus(Claude Code経由) | qwen3.5:35b-a3b-coding-nvfp4 |
| 実行環境 | クラウドAPI | ローカル(Ollama) |
| パラメータ数 | 非公開 | 35B(アクティブ3B) |
| 指示内容 | リファクタリング+コードレビュー | 同一 |
コードレビュー指摘の比較
共通して指摘された問題
両モデルとも以下の問題を検出しました。
| # | 問題 | 概要 |
|---|---|---|
| 1 | varの使用 | const/letに統一すべき |
| 2 | ファイル読み込みのエラーハンドリングなし | try-catchが必要 |
| 3 | NaNチェック漏れ | Number("abc")がNaNになるケースが未検出 |
| 4 | ゼロ除算リスク | totalAmountが0の場合のパーセント計算 |
| 5 | 手書きバブルソート | Array.prototype.sort()で置き換えるべき |
| 6 | 文字列の+連結 | 配列+join()またはテンプレートリテラルへ |
| 7 | 関数分割なし | 責務ごとに関数を分離すべき |
| 8 | CSVパースが素朴 | カンマを含むフィールドに未対応 |
Claude Codeのみが指摘した問題
| # | 問題 | 重大度 |
|---|---|---|
| 1 | ==による緩い比較 → ===に統一 | 高 |
| 2 | Number()の重複変換(5箇所で毎回変換) | 高 |
| 3 | 月別出力が辞書順不定 → localeCompareでソート | 中 |
| 4 | マジックナンバー5 → 定数化 | 中 |
| 5 | ファイルパスのハードコード → 定数に切り出し | 中 |
| 6 | Objectを辞書として使用 → Mapに置き換え | 低 |
Qwen 3.5のみが指摘した問題
| # | 問題 | 重大度 |
|---|---|---|
| 1 | columns[5]等が存在しない場合のundefined混入リスク | 中 |
| 2 | results配列が実質未使用(メモリの無駄) | 低 |
| 3 | 日付形式が不正な場合の処理 | 低 |
リファクタリング結果の比較
構造設計
| 観点 | Claude Code | Qwen 3.5 |
|---|---|---|
| 関数分割 | 5関数(readCsv, validateOrder, aggregate, buildReport, main) | 3関数(main, processOrders, generateReport) |
| CSVパース | 汎用的なヘッダーベースのパーサーを実装 | split(',')のまま(カラム数チェックは追加) |
| データ構造 | Mapを使用 | Objectのまま |
| バリデーション | 1行ごとにオブジェクトを返す関数に分離 | processOrders内にインラインで記述 |
| 定数管理 | ファイルパス・トップN件数を定数化 | ファイルパスを定数化 |
| 非同期対応 | 同期のまま(バッチ用途で据え置き判断) | async mainで宣言(ただし中身は同期) |
コード品質
| 観点 | Claude Code | Qwen 3.5 |
|---|---|---|
| 行数 | 約140行 | 約150行 |
===統一 | ○ | △(一部==が残る) |
| テンプレートリテラル | ○ | ○ |
| 金額フォーマット | toLocaleString()使用 | toLocaleString()使用 |
| 月別ソート | localeCompareで時系列順 | ソートなし |
| 未使用変数の除去 | results配列をordersとして活用 | results配列を削除 |
| JSDoc | 各関数にJSDocコメント | 各関数にJSDocコメント |
レビュー品質の総評
Claude Code
- 指摘の網羅性が高い。重大度を「高・中・低」の3段階に分類し、優先度が明確
==と===の違い、Mapへの置き換えなどJavaScript固有のベストプラクティスを的確に指摘- リファクタリング結果の関数分割が細かく、テスタビリティを意識した設計
- CSVパーサーをヘッダーベースで汎用的に実装しており、拡張性が高い
- 「対応しなかった既知の制約」を明記しており、判断の透明性がある
Qwen 3.5(35B)
- ローカルLLM(アクティブ3B)としては十分に実用的な指摘内容
- カラム数チェックや日付形式不正時のフォールバックなど、防御的プログラミングの視点が強い
async mainの宣言は将来性を意識しているが、中身が同期のためやや不整合- レビュー解説が丁寧で、各変更の理由が明確に説明されている
results配列の未使用指摘はClaude Codeが見逃した点で、独自の視点がある
まとめ
| 評価軸 | Claude Code | Qwen 3.5 |
|---|---|---|
| 指摘の網羅性 | ◎ | ○ |
| 重大度の分類 | ◎ | △ |
| JS固有のベストプラクティス | ◎ | ○ |
| 防御的プログラミング | ○ | ◎ |
| コード設計(関数分割) | ◎ | ○ |
| レビュー解説の丁寧さ | ○ | ◎ |
| ローカル実行可能 | × | ◎ |
Claude Codeは指摘の網羅性と構造設計の質で優れており、プロダクションコードのレビューに向いています。一方、Qwen 3.5はローカルで動作し、防御的な視点やレビュー解説の丁寧さに強みがあります。
用途に応じて使い分けるのが現実的で、たとえばローカルでの日常的なコードチェックにはQwen、本番前の最終レビューにはClaude Codeという組み合わせが考えられます。