CSV::MalformedCSVErrorハンドリング
結論
CSV::MalformedCSVError はCSVの構文が壊れており、パーサーが次のレコードを確定できなくなったことを示す例外である。
アプリケーションがすべきことは4つ
1. 最初の構文エラーで処理を中断する
2. 利用者には行番号付近と一般的な修正方法を示す
3. 詳細な例外情報はログへ残す
4. 途中まで更新されないようにする
エラーメッセージの完全な日本語化や、構文エラーの全件収集は目指さない。
CSV::MalformedCSVErrorから精密な診断情報を引き出そうとしない。
この例外が保証する本質は一つだけである。
この位置以降を、現在のCSV仕様のまま安全に解釈できない。
したがって、正しいハンドリングは「賢く復旧すること」ではなく、安全に中断し、修正可能な情報だけを返し、データの整合性を守ることである。
1. これは入力値エラーではなく、構文エラーである
CSV::MalformedCSVError は、不正なCSV形式をパースしたときに発生する。line_number を保持するが、提供されるのはパーサーが異常を検出した位置である。
2. 最初のエラーで中断する
RubyのCSVパーサーは逐次的にレコードを読み取る。
code:ruby
CSV.foreach(path, headers: true) do |row|
import(row)
end
構文エラーが発生すると、その時点で例外が送出され、後続レコードは処理されない。
CSVでは、クォートされたフィールド内に改行を含められる。したがって、物理行を独立したCSVとしてパースし、エラー行だけ読み飛ばすことは一般には不可能である。
3. rescue はループの外側に置く
例外は、ブロックへrowが渡される前のパース処理で発生する。
code:ruby
begin
CSV.foreach(path, headers: true) do |row|
process(row)
end
rescue CSV::MalformedCSVError => e
handle_error(e)
end
次の発想は捨てる。
code:ruby
CSV.foreach(path, headers: true) do |row|
begin
process(row)
rescue CSV::MalformedCSVError
# 原則ここでは捕捉できない
end
end
ブロック内部で扱うのは、パース済みレコードに対する検証・保存エラーである。
4. 文字コードエラーだけは分けてもよい
CSV::InvalidEncodingError はCSV::MalformedCSVErrorのサブクラスである。個別に扱う場合は先にrescueする。
code:ruby
begin
CSV.foreach(path, headers: true, encoding: "UTF-8") do |row|
process(row)
end
rescue CSV::InvalidEncodingError => e
raise ImportError.new(
code: :invalid_encoding,
line_number: e.line_number
)
rescue CSV::MalformedCSVError => e
raise ImportError.new(
code: :malformed_csv,
line_number: e.line_number
)
end
分類は原則としてこの2種類で十分
文字コードが不正
CSV形式が不正
e.messageを正規表現で解析し、「クォート未終了」「不正な改行」などへ細分化しない。例外メッセージはアプリケーション向けの安定した分類APIではない。
liberal_parsing安易に有効化しない
Ruby CSVのliberal_parsingは、非準拠な入力を可能な範囲で解釈するオプションであり、デフォルトはfalseである。
code:ruby
CSV.foreach(path, liberal_parsing: true)
これはエラーハンドリングではなく、受け入れるCSV仕様の変更である。
有効化すると、本来拒否すべき入力が別の値として取り込まれる可能性がある。
使用してよいのは、次を満たす場合に限る。
入力元のCSV仕様が既知
許容する崩れ方が明確
解釈結果をテストで固定できる
データの誤登録リスクを受容できる
「エラーが出るから有効にする」は設計ではない。