月次決算スクリプトの仕様バグを発見した話

この記事の要点

  • 月次決算レポートスクリプトの「4月期首対応」に、症状ではなく仕様レベルの誤実装が潜んでいた
  • 「ファイルが存在しない」という症状を6種のツールで確認したが、真の原因は参照先そのものの誤りだった
  • 「症状レベルの切り分け」と「仕様レベルの妥当性検証」は別物であり、両方を習慣的に行う必要がある

問題の発端

ある中堅企業の月次決算レポートを自動生成するPythonスクリプトがあります。毎月対象年月を指定して実行すると、前月の数値と比較した異常値検出レポートが出力される仕組みです。

3月決算・4月始まりの会計年度を持つ企業では、4月が「期首月」にあたります。4月のレポートを作成する際には、前月にあたる同年3月のデータを参照する必要があります。この「4月期首対応」のロジックを追加実装したところ、実行時に「前月フォルダが見つからない」というエラーが出続けました。


6種のツールで確認した「症状」

「前月フォルダが存在しない」というメッセージが出るため、まずフォルダの実在を徹底的に検証しました。使用したツールは以下の6種です。

  • Python pathlib.Path.exists / is_dir
  • Python os.path.exists / isdir
  • Python os.listdir による列挙
  • PowerShell Test-Path(複数ドライブ)
  • コマンドプロンプト dir

すべてのツールが「対象フォルダは存在しない」と返しました。この段階では「コードにバグはなく、フォルダが実際に存在しないのだ」という結論に傾きかけていました。

ところが、担当者から核心を突く指摘が入りました。

「4月の前月は同年3月ではないのですか?」


真因:「期首」と「前年度同月」の取り違え

この一言で状況が一変しました。コードを再確認すると、問題箇所がすぐに見つかりました。

# 誤実装(v1.0〜v1.3)
prev_year_yyyymm = f"{int(yyyymm_str[:4]) - 1}03"

対象月が202604(2026年4月)の場合、このコードは202503(2025年3月=前年度3月)を参照先として計算します。

しかし正しい参照先は202603(2026年3月=同年3月、カレンダー上の前月)です。この会計システムでは年度全体(4月〜3月の12ヶ月)を1ファイルに収めているため、当年度の3月データは202603のフォルダに入っています。

修正は「- 1を削除する」という1文字の変更でした。

# 修正後(v1.5)
prev_month_yyyymm = f"{int(yyyymm_str[:4])}03"

この修正後、--target 202604で実行したところ、[4月期首対応] 前月フォルダ検出: 202603というログが正常に出力されました。


「症状レベル」と「仕様レベル」の切り分けは別物

このインシデントから得られた最も重要な教訓は、エラーの切り分けには2つの独立したレイヤーがあるという点です。

症状レベルの切り分けとは、「エラーが出ているのはなぜか」を診断することです。今回であれば「ファイルが存在しないのはなぜか」を6種のツールで検証した作業がこれにあたります。

仕様レベルの検証とは、「そもそも参照しようとしている先が正しいか」を問い直すことです。今回であれば「4月の前月として202503を参照しようとしていること自体が正しいか」という問いがこれにあたります。

症状レベルで「ファイルが存在しない」という事実を確定した段階で、安易に「コードは正しい、フォルダが用意されていないだけ」と結論するのは早計です。「参照先そのものが間違っている可能性」を忘れずに仕様レベルの検証を行う必要があります。

会計年度の境界月(4月・1月など、期首にあたる月)は特にこの取り違えが起きやすい場所です。「4月の前月は3月」という事実がカレンダー年として自明であっても、「前年度」「期首」「前月」という言葉の意味が文脈によって微妙に異なるため、実装段階で混乱が生じます。


修正後の動作仕様

修正後のスクリプトは以下の仕様で動作します。

  • 通常月(5月〜翌3月):同一CSVファイル内の前月列から前月値を取得
  • 4月(期首):同年3月のフォルダを検索し、3月の最終列の値を前月値として注入
  • 3月フォルダが見つからない場合:警告を出力し、同階層に存在するフォルダ一覧を表示した上で前月値ゼロでフォールバック

後方互換性は構造的に保証されています。4月期首対応のブロックはcol_prev is Noneという単一条件でのみ発火するため、5月以降の通常処理には一切影響しません。


横展開チェックの重要性

今回の修正後、同種の誤実装が他のスクリプトにも潜んでいないかの確認作業を実施しました。対象として挙げられたのは以下のようなスクリプト群です。

  • 複数社連結の月次処理スクリプト(11月決算の場合、12月の前月は同年11月)
  • 複数ファイル横断の予実管理処理スクリプト
  • 海外子会社対応の集計スクリプト

特に決算月が3月・11月・12月など標準的でない場合、「期首月」と「前年度同月」の混同リスクが高まります。会計年度境界の処理を含むスクリプトを新規作成・改修する際は、この観点での検証を標準的なチェック項目として組み込むことを推奨します。


まとめ

今回のインシデントを通じて確認された実務的な教訓を整理します。

1. エラー診断は2段階で行う まず症状の原因を特定し(ファイルが存在しないのはなぜか)、次に仕様の妥当性を検証する(参照先そのものが正しいか)。前者で「バグなし」と結論する前に後者を実施する。

2. 会計年度境界の概念整理を先に行う 「前月」「期首」「前年度」という言葉は文脈によって指す対象が異なる。実装前に「この会計システムにおける4月の前月はいつか」を明示的に確認する。

3. 実データの構造に立ち戻る 概念が混乱した際は、実際のCSVファイル構造(どのファイルのどの列に何の値が入っているか)を確認することで、正しい参照先を特定できる。

4. ログ設計に検索手がかりを組み込む 今回の修正では、フォールバック時に同階層フォルダの一覧を出力するログを追加しました。次回類似の問題が発生した際に、切り分けにかかる時間を短縮できます。

症状の確認は必要ですが、それだけで完結させないこと。仕様の妥当性検証をセットで行う習慣が、会計実務のスクリプト開発では特に重要です。