Files
hakorune/docs/private/ideas/other/2025-08-25-debugging-nightmare-lessons.md

183 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# デバッグ地獄からの教訓 - TypeError犯人捜しの苦戦
Status: Research
Created: 2025-08-25
Priority: High
Related: リファクタリング戦略、デバッグ手法
## 問題の症状
ChatGPT5さんが現在直面している問題
- `BoxRef(IntegerBox) < BoxRef(IntegerBox)` でTypeError
- ログを仕込んでも該当箇所が出力されない
- 「犯人」が別の場所にいる可能性大
**Nyash開発で何十回も経験した典型的パターン**
## なぜログに出てこないのか
### 1. 別の実行パスを通っている
```
想定: VM → execute_compare → ログ出力
実際: インタープリター → 別の比較処理 → エラー
または
VM → 早期最適化パス → 別の比較処理 → エラー
```
### 2. 型変換が複数箇所で起きている
```
IntegerBox
VMValue::BoxRef場所A
比較処理場所B ← ログはここ
別の型変換場所C ← 実際のエラーはここ!
```
### 3. エラーメッセージが誤解を招く
- 表示: `BoxRef(IntegerBox) < BoxRef(IntegerBox)`
- 実際: 片方が `InstanceBox``UserDefinedBox` の可能性
- `type_name()` が同じでも内部実装が異なる
## リファクタリングによる根本解決
### 1. 関数の超細分化
```rust
// ❌ 現在:巨大な関数
fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result<ControlFlow, VMError> {
// 100行以上のロジック
// どこでエラーが起きてるか分からない
}
// ✅ 改善:細かく分割
fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result<ControlFlow, VMError> {
let left = self.get_and_canonicalize_value(lhs)?;
let right = self.get_and_canonicalize_value(rhs)?;
let result = compare_canonical_values(&op, &left, &right)?;
self.set_value(dst, VMValue::Bool(result));
Ok(ControlFlow::Continue)
}
fn get_and_canonicalize_value(&self, id: ValueId) -> Result<CanonicalValue, VMError> {
eprintln!("[CANON] Getting value {:?}", id);
let raw = self.get_value(id)?;
eprintln!("[CANON] Raw value: {:?}", raw);
canonicalize_vm_value(raw)
}
fn canonicalize_vm_value(val: VMValue) -> Result<CanonicalValue, VMError> {
// 型変換ロジックを完全に独立
}
fn compare_canonical_values(op: &CompareOp, left: &CanonicalValue, right: &CanonicalValue) -> Result<bool, VMError> {
// 比較ロジックを完全に独立
}
```
### 2. トレース可能な設計
```rust
// 各変換ステップを追跡可能に
#[derive(Debug)]
struct ValueTrace {
original: VMValue,
conversions: Vec<String>,
final_value: CanonicalValue,
}
impl ValueTrace {
fn log_conversion(&mut self, step: &str) {
self.conversions.push(format!("[{}] {}", self.conversions.len(), step));
eprintln!("TRACE: {}", self.conversions.last().unwrap());
}
}
```
## 今すぐできる対策80/20
### 1. 一時的ワークアラウンド
```rust
// すべてのBoxRefを強制的にプリミティブ変換
fn force_canonicalize(val: VMValue) -> VMValue {
match val {
VMValue::BoxRef(b) => {
// IntegerBox, BoolBox, StringBoxを強制的に展開
if let Some(ib) = b.as_any().downcast_ref::<IntegerBox>() {
return VMValue::Integer(ib.value);
}
// ... 他の基本型も同様
val // 変換できない場合はそのまま
}
_ => val
}
}
```
### 2. エラー箇所の網羅的ログ
```rust
// マクロで全箇所にログ挿入
macro_rules! trace_compare {
($left:expr, $right:expr, $location:expr) => {
eprintln!("[COMPARE @{}] left={:?} right={:?}", $location, $left, $right);
};
}
```
## 過去の経験からの教訓
### 1. 「ログが出ない = 想定外の場所」の法則
- 9割は別の実行パスを通っている
- 残り1割は早期リターンで到達していない
### 2. 巨大関数は悪
- 100行を超えたら分割を検討
- 50行が理想的
- 各関数は単一の責任のみ
### 3. デバッグ戦略
1. **最小再現コードの作成**
```nyash
local a = new IntegerBox(5)
local b = new IntegerBox(10)
print(a < b) // これだけでエラー再現するか?
```
2. **バイナリサーチデバッグ**
- コードを半分ずつコメントアウト
- エラーが消える境界を探す
3. **printf デバッグの限界**
- ログが多すぎると見落とす
- 構造化されたトレースが必要
## 長期的な改善策
### 1. 実行トレースシステム
```rust
// すべての型変換を記録
struct TypeConversionTrace {
conversions: Vec<(String, String, String)>, // (from, to, location)
}
```
### 2. 型システムの簡素化
- VMValue と NyashBox の境界を明確に
- 変換箇所を最小限に
### 3. テスタブルな設計
- 各変換関数を独立してテスト可能に
- エッジケースのユニットテスト充実
## まとめ
**この種のデバッグ地獄は、関数が大きすぎることが根本原因**
短期的には:
- ワークアラウンドで動作を優先
- 網羅的ログで犯人特定
長期的には:
- 徹底的なリファクタリング
- 関数の細分化
- トレース可能な設計
「何十回も経験した」この問題、今回で最後にしたいですにゃ!