Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10730] ファイルの概要

このコミットは、Go言語の標準ライブラリ内の様々な箇所で、時間間隔を表すtime.Duration型の使用方法を整理し、改善することを目的としています。具体的には、マジックナンバーとして直接記述されていたナノ秒単位の整数値や浮動小数点数値を、time.Secondtime.Millisecondといったtime.Duration型の定数と乗算する形式に置き換えることで、コードの可読性、保守性、および型安全性を向上させています。

コミット

commit 3dbecd592b8bf084770c8d6f38bd8094f74b8258
Author: David Symonds <dsymonds@golang.org>
Date:   Tue Dec 13 10:42:56 2011 +1100

    various: a grab-bag of time.Duration cleanups.
    
    R=adg, r, rsc
    CC=golang-dev
    https://golang.org/cl/5475069
---
 src/cmd/godoc/index.go                      |  3 ++-
 src/pkg/exp/inotify/inotify_linux_test.go   |  6 +++---
 src/pkg/exp/norm/normregtest.go             |  2 +-\
 src/pkg/exp/winfsnotify/winfsnotify_test.go |  4 ++--
 src/pkg/go/printer/printer_test.go          |  2 +-\
 src/pkg/io/pipe_test.go                     |  2 +-\
 src/pkg/net/http/doc.go                     |  4 ++--
 src/pkg/net/http/serve_test.go              |  2 +-\
 src/pkg/net/http/server.go                  | 14 +++++++-------
 src/pkg/net/http/transport_test.go          |  2 +-\
 src/pkg/net/rpc/server_test.go              |  7 +++----
 src/pkg/old/netchan/common.go               |  4 ++--
 src/pkg/old/netchan/import.go               |  2 +-\
 src/pkg/old/netchan/netchan_test.go         |  6 +++---
 14 files changed, 30 insertions(+), 30 deletions(-)

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/3dbecd592b8bf084770c8d6f38bd8094f74b8258

元コミット内容

various: a grab-bag of time.Duration cleanups.

R=adg, r, rsc
CC=golang-dev
https://golang.org/cl/5475069

変更の背景

このコミットが行われた背景には、Go言語のコードベース全体における時間間隔の表現の一貫性と明確性の向上という目的があります。以前のコードでは、時間間隔をナノ秒単位の整数値(例: 1e9は1秒を表す)や浮動小数点数で直接表現している箇所が散見されました。このような記述方法は、以下の問題を引き起こす可能性がありました。

  1. 可読性の低下: 1e9が1秒を意味することを即座に理解できない読者にとっては、コードの意図が不明瞭になります。特に、100e6のような値は、それが100ミリ秒なのか、100マイクロ秒なのか、あるいは別の単位なのかを判断するために計算が必要になります。
  2. エラーの誘発: 単位の誤解や計算ミスにより、意図しない時間間隔が設定されるリスクがありました。例えば、ミリ秒と秒を混同するなどのヒューマンエラーが発生しやすくなります。
  3. 型安全性の欠如: 整数値や浮動小数点数は、時間間隔以外の目的でも使用される汎用的な型であるため、コンパイラが時間間隔としての誤用を検出することが困難でした。
  4. 保守性の問題: 時間間隔の単位を変更する場合(例えば、ミリ秒からマイクロ秒へ)、すべてのマジックナンバーを手動で計算し直す必要があり、変更漏れや新たなバグの原因となる可能性がありました。

これらの問題を解決し、より堅牢で理解しやすいコードベースを構築するために、time.Duration型とその関連定数を用いた明示的な時間間隔の表現への移行が推進されました。

前提知識の解説

time.Duration

Go言語の標準ライブラリであるtimeパッケージには、時間間隔を表すためのDuration型が定義されています。

  • 定義: type Duration int64
    • Duration型は、内部的にはint64型として実装されており、ナノ秒単位で時間間隔を保持します。
  • 定数: timeパッケージは、一般的な時間単位に対応する定数を提供しています。
    • time.Nanosecond
    • time.Microsecond
    • time.Millisecond
    • time.Second
    • time.Minute
    • time.Hour
  • 使用方法: これらの定数と整数値を乗算することで、人間が理解しやすい形式で時間間隔を表現できます。
    • 例: 1 * time.Second (1秒), 500 * time.Millisecond (500ミリ秒), 2 * time.Hour (2時間)
  • 利点:
    • 可読性: コードを見ただけで、それが何秒、何ミリ秒を意味するのかが明確になります。
    • 型安全性: Duration型は時間間隔専用の型であるため、誤った単位での計算や、時間間隔ではない値との混同を防ぐことができます。コンパイラが型ミスマッチを検出してくれるため、開発段階でバグを発見しやすくなります。
    • 保守性: 時間の単位を変更する際も、定数名を変更するだけで済み、計算し直す必要がありません。

time.Sleep()time.After()

  • time.Sleep(d Duration): 指定された期間dだけ現在のゴルーチンを一時停止させます。
  • time.After(d Duration) <-chan Time: 指定された期間dが経過した後に、現在の時刻を送信するチャネルを返します。これは、タイムアウト処理などでよく使用されます。

これらの関数は、引数としてtime.Duration型を期待するため、以前のコードではナノ秒単位の整数値を直接渡していましたが、このコミットによりtime.Duration定数を用いたより明確な記述に統一されました。

技術的詳細

このコミットの技術的な詳細は、Go言語の型システムと、時間管理におけるベストプラクティスへの準拠に集約されます。

  1. マジックナンバーの排除:

    • 1e91000e6のような数値リテラルは、その意味が文脈に依存するため「マジックナンバー」と呼ばれます。これらの数値がナノ秒を表すことを知らなければ、コードの意図を正確に把握することは困難です。
    • 1 * time.Second100 * time.Millisecondと記述することで、数値が表す時間単位が明示され、コードの自己文書化能力が向上します。
  2. 型安全性の強化:

    • time.Duration型はint64のエイリアスですが、Goの型システムはエイリアス型を元の型とは異なる型として扱います。これにより、time.Durationを期待する関数に誤って単なるint64を渡そうとすると、コンパイルエラーが発生します。
    • 例えば、time.Sleep(100)と書くとコンパイルエラーになりますが、time.Sleep(100 * time.Millisecond)は正しくコンパイルされます。これにより、開発者は意図しない単位の誤用を防ぐことができます。
  3. 一貫性の確保:

    • Goの標準ライブラリ全体でtime.Durationの使用を統一することで、コードベース全体の一貫性が向上します。これにより、異なるモジュールやパッケージ間での時間間隔の扱いに混乱が生じることを防ぎ、開発者がGoの慣用的な書き方に慣れるのを助けます。
  4. 将来的な互換性と保守性:

    • もし将来的にtime.Durationの内部表現や、時間単位の計算方法に変更があったとしても、time.Secondのような定数を使用していれば、コードの変更は最小限で済みます。マジックナンバーを使用している場合、広範囲にわたる手動での修正が必要になる可能性があります。
  5. net/httpパッケージにおけるタイムアウト設定の改善:

    • net/httpパッケージのServer構造体におけるReadTimeoutWriteTimeoutフィールドの型がint64からtime.Durationに変更されました。
    • これにより、HTTPサーバーのタイムアウト設定がより直感的になり、10 * time.Secondのように直接time.Duration値で指定できるようになりました。
    • 内部的には、SetReadTimeoutSetWriteTimeoutメソッドに渡す際に、time.Duration型のNanoseconds()メソッドを使用してナノ秒単位のint64値に変換しています。これは、これらのメソッドが元々int64(ナノ秒)を期待していたため、既存のAPIとの互換性を保ちつつ、外部からの設定をtime.Durationで受け入れるように変更されたことを示しています。

コアとなるコードの変更箇所

このコミットでは、主に以下のパターンでコードが変更されています。

  1. time.Sleep()およびtime.After()の引数:

    • 変更前: time.Sleep(1000e6) (1000ミリ秒 = 1秒)
    • 変更後: time.Sleep(1 * time.Second)
    • 変更前: time.After(1e9) (1秒)
    • 変更後: time.After(1 * time.Second)
    • 変更前: time.Sleep(50e6) (50ミリ秒)
    • 変更後: time.Sleep(50 * time.Millisecond)
  2. net/httpパッケージのServer構造体のフィールド型変更:

    • 変更前:
      type Server struct {
          // ...
          ReadTimeout    int64   // the net.Conn.SetReadTimeout value for new connections
          WriteTimeout   int64   // the net.Conn.SetWriteTimeout value for new connections
          // ...
      }
      
    • 変更後:
      type Server struct {
          // ...
          ReadTimeout    time.Duration // the net.Conn.SetReadTimeout value for new connections
          WriteTimeout   time.Duration // the net.Conn.SetWriteTimeout value for new connections
          // ...
      }
      
  3. net/httpパッケージのServer構造体フィールドの利用箇所:

    • 変更前:
      if srv.ReadTimeout != 0 {
          rw.SetReadTimeout(srv.ReadTimeout)
      }
      if srv.WriteTimeout != 0 {
          rw.SetWriteTimeout(srv.WriteTimeout)
      }
      
    • 変更後:
      if srv.ReadTimeout != 0 {
          rw.SetReadTimeout(srv.ReadTimeout.Nanoseconds())
      }
      if srv.WriteTimeout != 0 {
          rw.SetWriteTimeout(srv.WriteTimeout.Nanoseconds())
      }
      
  4. その他の時間間隔の表現:

    • src/cmd/godoc/index.go: NewThrottle関数の引数で0.1e9100*time.Millisecondに変更。
    • src/pkg/net/rpc/server_test.go: second = 1e9という定数定義が削除され、直接time.Secondが使用されるように変更。

コアとなるコードの解説

time.Sleep() / time.After() の引数変更

これは最も直接的な変更であり、時間間隔の意図を明確にするためのものです。

  • 1000e61000 * 10^6ナノ秒、つまり10^9ナノ秒 = 1秒を意味します。これを1 * time.Secondと書くことで、誰が見ても1秒であることが一目瞭然になります。
  • 同様に、50e6は50ミリ秒を意味し、50 * time.Millisecondとすることで、単位が明確になります。 この変更は、コードの可読性を劇的に向上させ、将来的な誤解やバグの発生を防ぐ上で非常に重要です。

net/http/server.go における Server 構造体の変更

この変更は、HTTPサーバーのタイムアウト設定のインターフェースを改善するものです。

  • ReadTimeoutWriteTimeoutの型をint64からtime.Durationに変更することで、http.Serverのインスタンスを作成する際に、タイムアウト値を10 * time.Secondのように、より自然なGoの慣用句で指定できるようになりました。
  • 内部的には、net.Conn.SetReadTimeoutSetWriteTimeoutが依然としてナノ秒単位のint64を期待しているため、srv.ReadTimeout.Nanoseconds()のようにDuration型のNanoseconds()メソッドを呼び出してint64に変換しています。これは、外部からの設定をよりGoらしい方法で受け入れつつ、既存の低レベルAPIとの互換性を維持するための設計パターンです。このアプローチにより、APIの使いやすさと内部実装の効率性の両立が図られています。

net/rpc/server_test.go からの second 定数削除

const second = 1e9という定義は、1e9が1秒を意味するというマジックナンバーを定数としてラップしたものでした。しかし、time.Secondというより明確で型安全な定数が既に存在するため、このカスタム定数は冗長であり、混乱を招く可能性がありました。この定数を削除し、直接time.Secondを使用するように変更することで、コードの重複を避け、Go標準ライブラリの慣用的な表現に統一されました。

これらの変更は、Go言語の設計思想である「明確さ (clarity)」と「シンプルさ (simplicity)」を追求したものであり、コードベース全体の品質向上に貢献しています。

関連リンク

参考にした情報源リンク

  • 特になし (Go言語の標準ライブラリのドキュメントと、一般的なGoのコーディング規約に基づいています。)