0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テストやデバッグのためにコンソール出力を足すのは、なぜ「原則NO」なのか

0
Posted at

はじめに

テストやデバッグで問題が起きたとき、まず Console.WriteLine や console.log を追加する。これは、とても一般的なアプローチです。

実行結果を見れば値が分かるので、手軽で効果がありそうに見えます。私自身も、かつては最初にコンソール出力を足していました。

しかし、現在の私の答えは明確です。

プロダクションコードに、テストやデバッグのためのコンソール出力を一時的に追加する方法は、原則として使いません。

理由はシンプルです。テスト対象のコードと、テストやデバッグのためだけに追加したコードを混ぜてはいけないと考えているからです。

この記事では、なぜこの方法を避けるのか、そして代わりにどのような手段を使うのかを整理します。

「1行だけ」の変更でも、テスト対象は変わる

たとえば、次のようなコードがあったとします。

foreach (var item in items)
{
    Process(item);
}

ここで処理の途中を確認するために、次の1行を追加します。

foreach (var item in items)
{
    Console.WriteLine($"Processing: {item}");
    Process(item);
}

見た目には、観測のための1行です。しかし、実際にはプログラムの振る舞いを変えています。

  • 標準出力への書き込みが発生する
  • 実行時間や処理のタイミングが変わる
  • 出力先の状態やバッファリングの影響を受ける
  • 並行処理では、スレッドの動きや競合の発生条件が変わる可能性がある

つまり、コンソール出力は「外から眺めるだけの窓」ではありません。実行されるコードです。

本質的には、1行でもプロダクションコードを変更したなら、その状態は再テストの対象です。「デバッグ用だからテストしなくてよい」という特別扱いはできません。

出力を消すと、元のコードに戻ったことになるのか

では、調査が終わったあとに追加した行を削除すればよいのでしょうか。

ここにも問題があります。追加した行を削除した状態と、追加していた状態では、すでに別の実行結果を観測しています。

さらに、次のようにコメントアウトする場合も同じです。

foreach (var item in items)
{
    // Console.WriteLine($"Processing: {item}");
    Process(item);
}

コメントアウトされたコードを含む差分を作り、保存し、ビルドし、実行しています。開発者の操作も含めれば、デバッグのために本番コードを何度も変更していることになります。

そして、出力を削除したあとには「出力がない状態で、元のコードが問題なく動くか」を改めて確認する必要があります。結局、コンソール出力を追加したことによって、テストすべき状態を増やしています。

「その出力方法は正しいのか」という別の問題

コンソール出力を追加すると、次に別の問いが生まれます。

そのコンソール出力によって見えた値を、どのように信頼するのか?

出力された値が期待どおりだったとしても、次のことは分かりません。

  • 出力を追加したことで、問題が再現しなくなっていないか
  • 出力される順番が、実際の処理順を正しく表しているか
  • 非同期処理や並行処理の関係を、出力だけで説明できるか
  • その出力がない状態でも同じ結果になるか

すると、出力を確認するために別の出力を追加したくなります。出力の順番を確認するためにタイムスタンプを付け、処理の関係を確認するために識別子を付け、さらに条件分岐の前後にも出力を追加する。いつの間にか、デバッグ用のコードを検証するためのデバッグ用コードが必要になります。

これは、問題の原因を小さく切り分けるどころか、観測方法そのものを新しい不確実性にしてしまうループです。

デバッグは「コードを変えずに観測する」

私が重視したいのは、デバッグを次のように考えることです。

デバッグとは、問題を見つけるためにテスト対象を変更することではなく、テスト対象をできるだけ変えずに観測することです。

この考え方なら、最初に選ぶ道具も変わります。

ブレークポイントと条件付きブレークポイント

特定の行で処理を止め、ローカル変数やコールスタックを確認します。特定の値のときだけ止める条件付きブレークポイントを使えば、コードに出力処理を追加する必要はありません。

デバッガーのトレースポイント

処理を止めずに、デバッガーへ値を出力するトレースポイントを使える環境もあります。これは、実行中のアプリケーションへ一時的な出力処理を埋め込むのではなく、デバッガー側で観測する方法です。

再現するテストを書く

問題が再現できる入力や状態が分かっているなら、それをテストにします。

[Fact]
public void Invalid_item_is_rejected()
{
    var result = service.Process(invalidItem);

    Assert.False(result.IsAccepted);
}

テストにできれば、実行のたびに手でコンソールを確認する必要はありません。期待する振る舞いを、実行可能な形で残せます。

既存の観測基盤を使う

プロダクションで必要な情報を記録するなら、場当たり的なコンソール出力ではなく、アプリケーションで使っているロギングやテレメトリーの仕組みに乗せます。

ログレベル、相関 ID、出力先、個人情報の扱い、保持期間などを設計したうえで記録するものです。これはデバッグのために一時的な Console.WriteLine を足すこととは、目的も責任も異なります。

Web アプリケーションなら Application Insights も有力

Web アプリケーションでは、Application Insights も有力な観測手段です。

リクエスト、依存関係、例外、パフォーマンスなどをアプリケーションのテレメトリーとして扱い、あとから状況を調査できるようにします。必要な情報を設計して組み込むものなので、調査のたびに Console.WriteLine を追加して削除する方法とは違います。

もちろん、Application Insights を導入したからといって、何を記録してもよいわけではありません。個人情報や機密情報を送らないこと、サンプリングや保持期間を考えること、記録したい失敗ケースをテストで確認することが必要です。観測基盤もプロダクションコードの一部として設計し、レビューし、テストするという点が重要です。

コンソール出力そのものを否定したいわけではない

ここでいう「原則NO」は、コンソールアプリケーションの出力や、設計されたロギングを否定するものではありません。

たとえば、次のような出力はプロダクションコードの仕様として必要になることがあります。

  • CLI ツールが利用者へ結果を表示する
  • Worker が起動・停止の状態を記録する
  • 開発環境で既存のログ設定を使って診断情報を出す
  • テストランナーが失敗内容を報告する

問題にしたいのは、調査のためだけに本体へ一時的な出力を埋め込み、調査後に消すという進め方です。

最初からログやテレメトリーを必要な機能として設計しているなら、それはコードレビューやテストの対象です。逆に「すぐ分かるから」という理由だけで追加する一時的な出力は、変更の影響と取り外しのコストを過小評価しがちです。

テストとデバッグの境界を分ける

テストは、期待する振る舞いを確認する活動です。デバッグは、期待と実際の差分から原因を探す活動です。

どちらも、次のようにプロダクションコードの外側へ寄せていくほうが安全です。

目的 避けたい方法 まず使いたい方法
🧪 期待する結果を確認する 出力を目視する 自動テストのアサーション
🔍 値や状態を調べる Console.WriteLine を追加する ブレークポイント、ウォッチ、トレースポイント
🔁 不具合を再現する 実行のたびに手で操作する 再現テストや最小再現コード
📈 本番の状態を知る 一時的なログを埋め込む 設計済みのログ、メトリクス、トレース、Application Insights

この境界を意識すると、「とりあえず出力してみる」より先に、どの観測手段が適切かを考えられるようになります。

おわりに

テストやデバッグのためにコンソール出力を追加することは、手軽です。しかし、手軽さと安全さは同じではありません。

出力を追加した瞬間に、テスト対象のコードは変わります。出力を削除したあとには、元の状態での再確認が必要です。そして、その出力が本当に正しい観測なのかを検証しようとすると、さらに出力を追加するループに入りやすくなります。

だから私は、デバッグではまずコードを変えずに観測する方法を選びます。ブレークポイント、再現テスト、最小再現コード、そして設計済みの観測基盤です。

「1行だけだから大丈夫」と考えるのではなく、「1行でも変更したなら、それは再テストされるべきコードだ」と考える。この習慣が、デバッグのための変更をプロダクションコードへ持ち込まないための、シンプルな基準になります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?