💊
副作用に苦しめられた件
最近、作っているアプリのCSV取込機能で以下のような不具合が見つかりました。
- レコードが100件を超えるとエラーで終了する
- ちょうど100件目を境に行が結合してしまう
しかし、コードのどこを見ても「100件で分割する」ような処理は見当たらず、バグは迷宮入りしそうでした。
🔍 原因調査:BOM判定メソッドが怪しい
デバッグしてみると、ファイルにBOMがあろうとなかろうと、HasBom
メソッドが常にfalse
を返していることが判明しました。
`using (var reader = new StreamReader(ms, ConfigUserImportEncode))
{
if (HasBom(reader))
{
reader.BaseStream.Seek(3, SeekOrigin.Begin);
}
*// CSVパース処理が続く...*
}`
一見正しそうに見えるBOM判定メソッド:
*/// <summary>/// BOMの有無を確認/// </summary>*
static bool HasBom(StreamReader reader)
{
reader.Peek();
if (reader.CurrentEncoding.Equals(Encoding.UTF8) && reader.BaseStream.Length >= 3)
{
byte[] bom = new byte[3];
reader.BaseStream.Read(bom, 0, 3); *// ⚠️ ここが問題!*
return bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF;
}
return false;
}
💡 問題の本質:StreamReaderの内部バッファ
このメソッドを削除すると、なぜか正常に動作することに気づきました。
実は、StreamReaderはデフォルトで自動的にBOMを処理してくれるのです!
AIに相談したところ、BaseStream.Read()
でストリームの読み込み位置が進んでしまうことが原因ではないか、という回答が。公式ドキュメントを確認すると、まさにその通りでした。
何が起きていたか
-
reader.Peek()
でStreamReaderが内部バッファにデータを読み込む(デフォルトバッファサイズ:1024バイト) -
BaseStream.Read()
で3バイト読み込む → ストリーム位置が進む - StreamReaderの内部バッファとストリーム位置に不整合が発生
- 約100件(バッファサイズに依存)を超えたところで、バッファの境界で読み込みがおかしくなる
📝 教訓
これじゃHasBomじゃなくて、HasBombですね!
・・・・
・・・・
・・・・
このバグから
- 副作用への考慮
- バッファリングの仕組みを理解することの重要性
- 公式ドキュメントを読むことの重要性
を学びました
余談ですが、BOMの歴史を見ると、エンコーディング周りの苦しみが感じられますね。
Discussion