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

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

コミット

commit fe252584f5bae7d1ce3b729b83d968c0e3b77139
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Apr 27 23:16:54 2012 +1000

    misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6128044

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

https://github.com/golang/go/commit/fe252584f5bae7d1ce3b729b83d968c0e3b77139

元コミット内容

misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044

変更の背景

このコミットは、Goプロジェクトのコードレビューダッシュボード(misc/dashboard/codereview)のフロントページにおける並行処理を簡素化し、処理時間の計測機能を追加することを目的としています。

当時のGoプロジェクトでは、コードレビュープロセスを管理するためのウェブベースのダッシュボードが利用されていました。このダッシュボードのフロントページでは、複数の異なる種類のコードレビューリスト(例:自分に割り当てられたレビュー、自分が送ったレビュー、その他のアクティブなレビュー、最近クローズされたレビューなど)を同時に表示する必要がありました。これらのリストの取得は、Google App EngineのDatastoreに対するクエリとして実行されており、それぞれが独立した処理であるため、並行して実行することでページのロード時間を短縮することが期待されます。

しかし、元の実装では、これらの並行処理が個別にgo func() { ... }()ブロックとして記述されており、コードの重複や管理の複雑さがありました。また、各データ取得処理がどれくらいの時間を要しているかを把握するメカニズムがなかったため、パフォーマンスのボトルネックを特定し、最適化を行うための情報が不足していました。

このコミットは、これらの課題に対処し、コードの可読性と保守性を向上させるとともに、パフォーマンス分析のための基盤を導入することを意図しています。

前提知識の解説

このコミットを理解するためには、以下の技術的知識が前提となります。

  • Go言語: GoはGoogleによって開発された静的型付けのコンパイル型言語です。特に、軽量な並行処理を実現するためのGoroutineとChannelの概念が重要です。
    • Goroutine: Goにおける軽量なスレッドのようなものです。goキーワードを使って関数呼び出しの前に記述することで、その関数を新しいGoroutineで実行し、並行処理を実現します。
    • sync.WaitGroup: 複数のGoroutineの完了を待つための同期プリミティブです。Addで待つGoroutineの数を増やし、各Goroutineが完了する際にDoneを呼び出し、Waitで全てのGoroutineが完了するまでブロックします。
    • chan error: エラーをGoroutine間で安全に伝達するためのチャネルです。
  • Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)であり、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。
    • Datastore: Google App Engineが提供するNoSQLデータベースサービスです。このコミットでは、コードレビューに関するデータをDatastoreから取得するために利用されています。
    • appengine.Context: App Engineアプリケーションの各リクエストに関連付けられたコンテキストオブジェクトです。DatastoreクエリなどのApp Engineサービスへの呼び出しには、このコンテキストが必要です。
  • ウェブアプリケーションのフロントエンド開発: HTMLテンプレート(Goのhtml/templateパッケージ)とCSSに関する基本的な知識が必要です。
    • html/template: Goの標準ライブラリに含まれるHTMLテンプレートエンジンです。サーバーサイドで動的にHTMLを生成するために使用されます。
  • パフォーマンス計測: プログラムの実行時間を計測し、ボトルネックを特定するための基本的な概念。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. 並行処理の抽象化と簡素化:

    • handleFront関数内で、複数のデータ取得処理(CLs assigned to you, CLs sent by you, Other active CLs, Recently closed CLs)がそれぞれ独立したGoroutineで実行されていました。
    • このコミットでは、これらの並行処理をtableFetchという新しいヘルパー関数に抽象化しました。tableFetchは、Goroutine内で実行される匿名関数を受け取り、その関数内でデータ取得ロジックを実行します。
    • これにより、各データ取得処理の記述が簡潔になり、コードの重複が削減されました。
  2. 処理時間の計測機能の追加:

    • tableFetch関数内で、time.Now()を使って各データ取得処理の開始時刻を記録し、処理完了後に再度time.Now().Sub(start)で経過時間を計算しています。
    • 計測された時間は、frontPageData構造体の新しいフィールドTiming [4]time.Durationに格納されます。これにより、フロントページに表示される各テーブルのデータ取得にかかった時間を追跡できるようになります。
    • このタイミング情報は、HTMLテンプレート内で表示され、開発者がページのパフォーマンスを分析するのに役立ちます。
  3. sync.WaitGroupの利用:

    • tableFetch関数は、内部でwg.Add(1)を呼び出し、Goroutineの開始をsync.WaitGroupに通知します。
    • Goroutineの実行が完了すると、defer wg.Done()が呼び出され、WaitGroupのカウンターが減らされます。
    • handleFront関数の最後でwg.Wait()を呼び出すことで、全てのデータ取得Goroutineが完了するまで処理がブロックされ、全てのデータが揃ってからテンプレートのレンダリングに進むことが保証されます。
  4. エラーハンドリングの改善:

    • 元のコードでは、各Goroutine内でエラーが発生した場合、errc <- errを通じてエラーチャネルにエラーを送信していました。
    • tableFetch関数に抽象化されたことで、データ取得ロジックをカプセル化し、エラーをより一貫した方法で処理できるようになりました。tableFetchに渡される関数はエラーを返すようになり、そのエラーがerrcに送信されます。
  5. HTMLテンプレートの更新:

    • frontPageDataTimingフィールドが追加されたことに伴い、front.go内のHTMLテンプレートも更新されました。
    • 新しく追加された<address>タグ内に、各データストアのタイミング情報が表示されるようになりました。これにより、ユーザー(または開発者)はページのロードにどの部分が寄与しているかを視覚的に確認できます。
    • CSSも更新され、<address>タグのスタイルが定義されています。

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

misc/dashboard/codereview/dashboard/front.go

このファイルが最も大きく変更されています。

  • import文の追加:

    +	"time"
    

    timeパッケージがインポートされ、処理時間の計測に利用されます。

  • handleFront関数の変更:

    • tableFetchという新しいヘルパー関数が定義されました。
      +	tableFetch := func(index int, f func(tbl *clTable) error) {
      +		wg.Add(1)
      +		go func() {
      +			defer wg.Done()
      +			start := time.Now()
      +			if err := f(&data.Tables[index]); err != nil {
      +				errc <- err
      +			}
      +			data.Timing[index] = time.Now().Sub(start)
      +		}()
      +	}
      
      この関数は、sync.WaitGroupの管理、Goroutineの起動、処理時間の計測、エラーのチャネルへの送信をカプセル化します。
    • 既存の個別のGoroutineブロックがtableFetchの呼び出しに置き換えられました。
      --- a/misc/dashboard/codereview/dashboard/front.go
      +++ b/misc/dashboard/codereview/dashboard/front.go
      @@ -34,42 +35,42 @@ func handleFront(w http.ResponseWriter, r *http.Request) {
       		Filter("Closed =", false).
       		Order("-Modified")
      
      -	if data.UserIsReviewer {
      -+	tableFetch := func(index int, f func(tbl *clTable) error) {
      -	t	wg.Add(1)
      -	t	go func() {
      -	t		defer wg.Done()
      -	t		tbl := &data.Tables[0]
      -	t		q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
      -	t		tbl.Title = "CLs assigned to you for review"
      -	t		tbl.Assignable = true
      -	t		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -	t			errc <- err
      -	t		}
      -	t	}()
      -	}
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[1]
      -+	if data.UserIsReviewer {
      -+	t	tableFetch(0, func(tbl *clTable) error {
      -+	t		q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
      -+	t		tbl.Title = "CLs assigned to you for review"
      -+	t		tbl.Assignable = true
      -+	t		_, err := q.GetAll(c, &tbl.CLs)
      -+	t		return err
      -+	t	})
      -+	}
      -+
      -+	tableFetch(1, func(tbl *clTable) error {
       		q := activeCLs.Filter("Author =", currentPerson).Limit(10)
       		tbl.Title = "CLs sent by you"
       		tbl.Assignable = true
      -		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -		}
      -	}()
      -+		_, err := q.GetAll(c, &tbl.CLs)
      -+		return err
      -+	})
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[2]
      -+	tableFetch(2, func(tbl *clTable) error {
       		q := activeCLs.Limit(50)
       		tbl.Title = "Other active CLs"
       		tbl.Assignable = true
       		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -			return
      -+			return err
       		}
       		// filter
       		if data.UserIsReviewer {
      @@ -81,22 +82,19 @@ func handleFront(w http.ResponseWriter, r *http.Request) {\n         			}\n         		}\n         	}\n        -	}()
      -+		return nil
      -+	})
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[3]
      -+	tableFetch(3, func(tbl *clTable) error {
       		q := datastore.NewQuery("CL").
       			Filter("Closed =", true).
       			Order("-Modified").
       			Limit(10)
       		tbl.Title = "Recently closed CLs"
       		tbl.Assignable = false
      -		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -		}
      -	}()
      -+		_, err := q.GetAll(c, &tbl.CLs)
      -+		return err
      -+	})
      
  • frontPageData構造体の変更:

    type frontPageData struct {
    	Tables [4]clTable
    +	Timing [4]time.Duration
    
    	Reviewers      []string
    	UserIsReviewer bool
    

    Timingフィールドが追加され、各テーブルのデータ取得時間を格納します。

  • HTMLテンプレートの変更:

    --- a/misc/dashboard/codereview/dashboard/front.go
    +++ b/misc/dashboard/codereview/dashboard/front.go
    @@ -175,6 +174,10 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n             color: blue;\n         	text-decoration: none;  /* no link underline */\n           }\n    +      address {\n    +        font-size: 10px;\n    +	text-align: right;\n    +      }\n           .email {\n             font-family: monospace;\n           }\n    @@ -235,6 +238,11 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n     {{end}}\n     {{end}}\n     \n    +<hr />\n    +<address>\n    +datastore timing: {{range .Timing}} {{.}}{{end}}\n    +</address>\n    +\n       </body>\n     </html>\n      `))
    

    Timing情報を表示するための<address>タグと、そのスタイルが追加されました。

misc/dashboard/codereview/dashboard/gc.go

このファイルは、timeパッケージのインポート順序が変更されたのみです。機能的な変更はありません。

--- a/misc/dashboard/codereview/dashboard/gc.go
+++ b/misc/dashboard/codereview/dashboard/gc.go
@@ -4,10 +4,10 @@ package dashboard

 import (
 	"net/http"
+	"time"

 	"appengine"
 	"appengine/datastore"
-	"time"
 )

 func init() {

コアとなるコードの解説

このコミットの核心は、front.goにおけるtableFetchヘルパー関数の導入です。

tableFetch := func(index int, f func(tbl *clTable) error) {
	wg.Add(1) // WaitGroupに、新しいGoroutineが開始されることを通知
	go func() {
		defer wg.Done() // Goroutineが終了する際にWaitGroupのカウンターを減らす
		start := time.Now() // 処理開始時刻を記録
		if err := f(&data.Tables[index]); err != nil { // 渡された関数を実行し、エラーがあればチャネルに送信
			errc <- err
		}
		data.Timing[index] = time.Now().Sub(start) // 処理終了時刻から経過時間を計算し、Timing配列に格納
	}()
}

このtableFetch関数は、以下の役割を担っています。

  1. 並行処理の統一的な管理: wg.Add(1)defer wg.Done()により、複数のデータ取得処理がsync.WaitGroupによって適切に同期されることを保証します。これにより、handleFront関数は全ての並行処理が完了するまでwg.Wait()で待機できます。
  2. 処理時間の自動計測: 各データ取得処理の開始時と終了時にtime.Now()を呼び出し、その差分をtime.Durationとして計算します。この計測結果はfrontPageData.Timing配列に自動的に格納されます。これにより、個々のデータ取得にかかる時間を簡単に把握できるようになり、パフォーマンスのボトルネック特定に役立ちます。
  3. エラーハンドリングの一元化: 渡された関数fがエラーを返した場合、そのエラーはerrcチャネルを通じてメインのGoroutineに通知されます。これにより、エラー処理ロジックが分散することなく、一箇所で管理できるようになります。
  4. コードの簡素化と再利用性: 以前は各データ取得処理ごとに重複していたGoroutineの起動、WaitGroupの管理、エラーチャネルへの送信といった定型的なコードがtableFetchに集約されました。これにより、handleFront関数内のコードが大幅に簡潔になり、可読性と保守性が向上しました。

例えば、以前は以下のように記述されていたコードが、

wg.Add(1)
go func() {
    defer wg.Done()
    tbl := &data.Tables[0]
    q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
    tbl.Title = "CLs assigned to you for review"
    tbl.Assignable = true
    if _, err := q.GetAll(c, &tbl.CLs); err != nil {
        errc <- err
    }
}()

tableFetchの導入により、以下のように簡潔に記述できるようになりました。

tableFetch(0, func(tbl *clTable) error {
    q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
    tbl.Title = "CLs assigned to you for review"
    tbl.Assignable = true
    _, err := q.GetAll(c, &tbl.CLs)
    return err
})

このように、tableFetchはGoの関数型プログラミングの特性(高階関数)を活かし、共通のロジックを抽象化することで、コードの品質と効率を向上させています。

関連リンク

参考にした情報源リンク

  • Go言語の並行処理に関するチュートリアルや記事
  • Google App Engine Datastoreに関するドキュメント
  • Goのhtml/templateに関するドキュメント
  • Goのコードレビューシステムに関する一般的な情報 (GoのGerritベースのコードレビューシステムについて)# [インデックス 12987] ファイルの概要

コミット

commit fe252584f5bae7d1ce3b729b83d968c0e3b77139
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Apr 27 23:16:54 2012 +1000

    misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6128044

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

https://github.com/golang/go/commit/fe252584f5bae7d1ce3b729b83d968c0e3b77139

元コミット内容

misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044

変更の背景

このコミットは、Goプロジェクトのコードレビューダッシュボード(misc/dashboard/codereview)のフロントページにおける並行処理を簡素化し、処理時間の計測機能を追加することを目的としています。

当時のGoプロジェクトでは、コードレビュープロセスを管理するためのウェブベースのダッシュボードが利用されていました。このダッシュボードのフロントページでは、複数の異なる種類のコードレビューリスト(例:自分に割り当てられたレビュー、自分が送ったレビュー、その他のアクティブなレビュー、最近クローズされたレビューなど)を同時に表示する必要がありました。これらのリストの取得は、Google App EngineのDatastoreに対するクエリとして実行されており、それぞれが独立した処理であるため、並行して実行することでページのロード時間を短縮することが期待されます。

しかし、元の実装では、これらの並行処理が個別にgo func() { ... }()ブロックとして記述されており、コードの重複や管理の複雑さがありました。また、各データ取得処理がどれくらいの時間を要しているかを把握するメカニズムがなかったため、パフォーマンスのボトルネックを特定し、最適化を行うための情報が不足していました。

このコミットは、これらの課題に対処し、コードの可読性と保守性を向上させるとともに、パフォーマンス分析のための基盤を導入することを意図しています。

前提知識の解説

このコミットを理解するためには、以下の技術的知識が前提となります。

  • Go言語: GoはGoogleによって開発された静的型付けのコンパイル型言語です。特に、軽量な並行処理を実現するためのGoroutineとChannelの概念が重要です。
    • Goroutine: Goにおける軽量なスレッドのようなものです。goキーワードを使って関数呼び出しの前に記述することで、その関数を新しいGoroutineで実行し、並行処理を実現します。
    • sync.WaitGroup: 複数のGoroutineの完了を待つための同期プリミティブです。Addで待つGoroutineの数を増やし、各Goroutineが完了する際にDoneを呼び出し、Waitで全てのGoroutineが完了するまでブロックします。
    • chan error: エラーをGoroutine間で安全に伝達するためのチャネルです。
  • Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)であり、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。
    • Datastore: Google App Engineが提供するNoSQLデータベースサービスです。このコミットでは、コードレビューに関するデータをDatastoreから取得するために利用されています。
    • appengine.Context: App Engineアプリケーションの各リクエストに関連付けられたコンテキストオブジェクトです。DatastoreクエリなどのApp Engineサービスへの呼び出しには、このコンテキストが必要です。
  • ウェブアプリケーションのフロントエンド開発: HTMLテンプレート(Goのhtml/templateパッケージ)とCSSに関する基本的な知識が必要です。
    • html/template: Goの標準ライブラリに含まれるHTMLテンプレートエンジンです。サーバーサイドで動的にHTMLを生成するために使用されます。
  • パフォーマンス計測: プログラムの実行時間を計測し、ボトルネックを特定するための基本的な概念。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. 並行処理の抽象化と簡素化:

    • handleFront関数内で、複数のデータ取得処理(CLs assigned to you, CLs sent by you, Other active CLs, Recently closed CLs)がそれぞれ独立したGoroutineで実行されていました。
    • このコミットでは、これらの並行処理をtableFetchという新しいヘルパー関数に抽象化しました。tableFetchは、Goroutine内で実行される匿名関数を受け取り、その関数内でデータ取得ロジックを実行します。
    • これにより、各データ取得処理の記述が簡潔になり、コードの重複が削減されました。
  2. 処理時間の計測機能の追加:

    • tableFetch関数内で、time.Now()を使って各データ取得処理の開始時刻を記録し、処理完了後に再度time.Now().Sub(start)で経過時間を計算しています。
    • 計測された時間は、frontPageData構造体の新しいフィールドTiming [4]time.Durationに格納されます。これにより、フロントページに表示される各テーブルのデータ取得にかかった時間を追跡できるようになります。
    • このタイミング情報は、HTMLテンプレート内で表示され、開発者がページのパフォーマンスを分析するのに役立ちます。
  3. sync.WaitGroupの利用:

    • tableFetch関数は、内部でwg.Add(1)を呼び出し、Goroutineの開始をsync.WaitGroupに通知します。
    • Goroutineの実行が完了すると、defer wg.Done()が呼び出され、WaitGroupのカウンターが減らされます。
    • handleFront関数の最後でwg.Wait()を呼び出すことで、全てのデータ取得Goroutineが完了するまで処理がブロックされ、全てのデータが揃ってからテンプレートのレンダリングに進むことが保証されます。
  4. エラーハンドリングの改善:

    • 元のコードでは、各Goroutine内でエラーが発生した場合、errc <- errを通じてエラーチャネルにエラーを送信していました。
    • tableFetch関数に抽象化されたことで、データ取得ロジックをカプセル化し、エラーをより一貫した方法で処理できるようになりました。tableFetchに渡される関数はエラーを返すようになり、そのエラーがerrcに送信されます。
  5. HTMLテンプレートの更新:

    • frontPageDataTimingフィールドが追加されたことに伴い、front.go内のHTMLテンプレートも更新されました。
    • 新しく追加された<address>タグ内に、各データストアのタイミング情報が表示されるようになりました。これにより、ユーザー(または開発者)はページのロードにどの部分が寄与しているかを視覚的に確認できます。
    • CSSも更新され、<address>タグのスタイルが定義されています。

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

misc/dashboard/codereview/dashboard/front.go

このファイルが最も大きく変更されています。

  • import文の追加:

    +	"time"
    

    timeパッケージがインポートされ、処理時間の計測に利用されます。

  • handleFront関数の変更:

    • tableFetchという新しいヘルパー関数が定義されました。
      +	tableFetch := func(index int, f func(tbl *clTable) error) {
      +		wg.Add(1)
      +		go func() {
      +			defer wg.Done()
      +			start := time.Now()
      +			if err := f(&data.Tables[index]); err != nil {
      +				errc <- err
      +			}
      +			data.Timing[index] = time.Now().Sub(start)
      +		}()
      +	}
      
      この関数は、sync.WaitGroupの管理、Goroutineの起動、処理時間の計測、エラーのチャネルへの送信をカプセル化します。
    • 既存の個別のGoroutineブロックがtableFetchの呼び出しに置き換えられました。
      --- a/misc/dashboard/codereview/dashboard/front.go
      +++ b/misc/dashboard/codereview/dashboard/front.go
      @@ -34,42 +35,42 @@ func handleFront(w http.ResponseWriter, r *http.Request) {
       		Filter("Closed =", false).
       		Order("-Modified")
      
      -	if data.UserIsReviewer {
      -+	tableFetch := func(index int, f func(tbl *clTable) error) {
      -	t	wg.Add(1)
      -	t	go func() {
      -	t		defer wg.Done()
      -	t		tbl := &data.Tables[0]
      -	t		q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
      -	t		tbl.Title = "CLs assigned to you for review"
      -	t		tbl.Assignable = true
      -	t		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -	t			errc <- err
      -	t		}
      -	t	}()
      -	}
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[1]
      -+	if data.UserIsReviewer {
      -+	t	tableFetch(0, func(tbl *clTable) error {
      -+	t		q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
      -+	t		tbl.Title = "CLs assigned to you for review"
      -+	t		tbl.Assignable = true
      -+	t		_, err := q.GetAll(c, &tbl.CLs)
      -+	t		return err
      -+	t	})
      -+	}
      -+
      -+	tableFetch(1, func(tbl *clTable) error {
       		q := activeCLs.Filter("Author =", currentPerson).Limit(10)
       		tbl.Title = "CLs sent by you"
       		tbl.Assignable = true
      -		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -		}
      -	}()
      -+		_, err := q.GetAll(c, &tbl.CLs)
      -+		return err
      -+	})
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[2]
      -+	tableFetch(2, func(tbl *clTable) error {
       		q := activeCLs.Limit(50)
       		tbl.Title = "Other active CLs"
       		tbl.Assignable = true
       		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -			return
      -+			return err
       		}
       		// filter
       		if data.UserIsReviewer {
      @@ -81,22 +82,19 @@ func handleFront(w http.ResponseWriter, r *http.Request) {\n         			}\n         		}\n         	}\n        -	}()
      -+		return nil
      -+	})
      -
      -	wg.Add(1)
      -	go func() {
      -		defer wg.Done()
      -		tbl := &data.Tables[3]
      -+	tableFetch(3, func(tbl *clTable) error {
       		q := datastore.NewQuery("CL").
       			Filter("Closed =", true).
       			Order("-Modified").
       			Limit(10)
       		tbl.Title = "Recently closed CLs"
       		tbl.Assignable = false
      -		if _, err := q.GetAll(c, &tbl.CLs); err != nil {
      -			errc <- err
      -		}
      -	}()
      -+		_, err := q.GetAll(c, &tbl.CLs)
      -+		return err
      -+	})
      
  • frontPageData構造体の変更:

    type frontPageData struct {
    	Tables [4]clTable
    +	Timing [4]time.Duration
    
    	Reviewers      []string
    	UserIsReviewer bool
    

    Timingフィールドが追加され、各テーブルのデータ取得時間を格納します。

  • HTMLテンプレートの変更:

    --- a/misc/dashboard/codereview/dashboard/front.go
    +++ b/misc/dashboard/codereview/dashboard/front.go
    @@ -175,6 +174,10 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n             color: blue;\n         	text-decoration: none;  /* no link underline */\n           }\n    +      address {\n    +        font-size: 10px;\n    +	text-align: right;\n    +      }\n           .email {\n             font-family: monospace;\n           }\n    @@ -235,6 +238,11 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n     {{end}}\n     {{end}}\n     \n    +<hr />\n    +<address>\n    +datastore timing: {{range .Timing}} {{.}}{{end}}\n    +</address>\n    +\n       </body>
     </html>
      `))
    

    Timing情報を表示するための<address>タグと、そのスタイルが追加されました。

misc/dashboard/codereview/dashboard/gc.go

このファイルは、timeパッケージのインポート順序が変更されたのみです。機能的な変更はありません。

--- a/misc/dashboard/codereview/dashboard/gc.go
+++ b/misc/dashboard/codereview/dashboard/gc.go
@@ -4,10 +4,10 @@ package dashboard

 import (
 	"net/http"
+	"time"

 	"appengine"
 	"appengine/datastore"
-	"time"
 )

 func init() {

コアとなるコードの解説

このコミットの核心は、front.goにおけるtableFetchヘルパー関数の導入です。

tableFetch := func(index int, f func(tbl *clTable) error) {
	wg.Add(1) // WaitGroupに、新しいGoroutineが開始されることを通知
	go func() {
		defer wg.Done() // Goroutineが終了する際にWaitGroupのカウンターを減らす
		start := time.Now() // 処理開始時刻を記録
		if err := f(&data.Tables[index]); err != nil { // 渡された関数を実行し、エラーがあればチャネルに送信
			errc <- err
		}
		data.Timing[index] = time.Now().Sub(start) // 処理終了時刻から経過時間を計算し、Timing配列に格納
	}()
}

このtableFetch関数は、以下の役割を担っています。

  1. 並行処理の統一的な管理: wg.Add(1)defer wg.Done()により、複数のデータ取得処理がsync.WaitGroupによって適切に同期されることを保証します。これにより、handleFront関数は全ての並行処理が完了するまでwg.Wait()で待機できます。
  2. 処理時間の自動計測: 各データ取得処理の開始時と終了時にtime.Now()を呼び出し、その差分をtime.Durationとして計算します。この計測結果はfrontPageData.Timing配列に自動的に格納されます。これにより、個々のデータ取得にかかる時間を簡単に把握できるようになり、パフォーマンスのボトルネック特定に役立ちます。
  3. エラーハンドリングの一元化: 渡された関数fがエラーを返した場合、そのエラーはerrcチャネルを通じてメインのGoroutineに通知されます。これにより、エラー処理ロジックが分散することなく、一箇所で管理できるようになります。
  4. コードの簡素化と再利用性: 以前は各データ取得処理ごとに重複していたGoroutineの起動、WaitGroupの管理、エラーチャネルへの送信といった定型的なコードがtableFetchに集約されました。これにより、handleFront関数内のコードが大幅に簡潔になり、可読性と保守性が向上しました。

例えば、以前は以下のように記述されていたコードが、

wg.Add(1)
go func() {
    defer wg.Done()
    tbl := &data.Tables[0]
    q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
    tbl.Title = "CLs assigned to you for review"
    tbl.Assignable = true
    if _, err := q.GetAll(c, &tbl.CLs); err != nil {
        errc <- err
    }
}()

tableFetchの導入により、以下のように簡潔に記述できるようになりました。

tableFetch(0, func(tbl *clTable) error {
    q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
    tbl.Title = "CLs assigned to you for review"
    tbl.Assignable = true
    _, err := q.GetAll(c, &tbl.CLs)
    return err
})

このように、tableFetchはGoの関数型プログラミングの特性(高階関数)を活かし、共通のロジックを抽象化することで、コードの品質と効率を向上させています。

関連リンク

参考にした情報源リンク

  • Go言語の並行処理に関するチュートリアルや記事
  • Google App Engine Datastoreに関するドキュメント
  • Goのhtml/templateに関するドキュメント
  • Goのコードレビューシステムに関する一般的な情報 (GoのGerritベースのコードレビューシステムについて)