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

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 CodeQwen 3.5
モデルClaude Opus(Claude Code経由)qwen3.5:35b-a3b-coding-nvfp4
実行環境クラウドAPIローカル(Ollama)
パラメータ数非公開35B(アクティブ3B)
指示内容リファクタリング+コードレビュー同一

コードレビュー指摘の比較

共通して指摘された問題

両モデルとも以下の問題を検出しました。

#問題概要
1varの使用const/letに統一すべき
2ファイル読み込みのエラーハンドリングなしtry-catchが必要
3NaNチェック漏れNumber("abc")がNaNになるケースが未検出
4ゼロ除算リスクtotalAmountが0の場合のパーセント計算
5手書きバブルソートArray.prototype.sort()で置き換えるべき
6文字列の+連結配列+join()またはテンプレートリテラルへ
7関数分割なし責務ごとに関数を分離すべき
8CSVパースが素朴カンマを含むフィールドに未対応

Claude Codeのみが指摘した問題

#問題重大度
1==による緩い比較 → ===に統一
2Number()の重複変換(5箇所で毎回変換)
3月別出力が辞書順不定 → localeCompareでソート
4マジックナンバー5 → 定数化
5ファイルパスのハードコード → 定数に切り出し
6Objectを辞書として使用 → Mapに置き換え

Qwen 3.5のみが指摘した問題

#問題重大度
1columns[5]等が存在しない場合のundefined混入リスク
2results配列が実質未使用(メモリの無駄)
3日付形式が不正な場合の処理

リファクタリング結果の比較

構造設計

観点Claude CodeQwen 3.5
関数分割5関数(readCsv, validateOrder, aggregate, buildReport, main3関数(main, processOrders, generateReport
CSVパース汎用的なヘッダーベースのパーサーを実装split(',')のまま(カラム数チェックは追加)
データ構造Mapを使用Objectのまま
バリデーション1行ごとにオブジェクトを返す関数に分離processOrders内にインラインで記述
定数管理ファイルパス・トップN件数を定数化ファイルパスを定数化
非同期対応同期のまま(バッチ用途で据え置き判断)async mainで宣言(ただし中身は同期)

コード品質

観点Claude CodeQwen 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 CodeQwen 3.5
指摘の網羅性
重大度の分類
JS固有のベストプラクティス
防御的プログラミング
コード設計(関数分割)
レビュー解説の丁寧さ
ローカル実行可能×

Claude Codeは指摘の網羅性と構造設計の質で優れており、プロダクションコードのレビューに向いています。一方、Qwen 3.5はローカルで動作し、防御的な視点やレビュー解説の丁寧さに強みがあります。

用途に応じて使い分けるのが現実的で、たとえばローカルでの日常的なコードチェックにはQwen本番前の最終レビューにはClaude Codeという組み合わせが考えられます。