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

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

このコミットは、Go言語の公式ドキュメントの一部である「Go Wiki」チュートリアル (doc/articles/wiki/index.html) に対する多数の修正と改善を目的としています。具体的には、既存のコンテンツの構文更新、表現の改善、そして特にエラーハンドリングに焦点を当てた新しいサンプルプログラム (part3.go および part3-errorhandling.go) の追加が行われました。これにより、チュートリアルの正確性、明瞭性、および堅牢性が向上しています。

コミット

このコミットは、Go Wikiチュートリアル記事のコンテンツを更新し、より堅牢なエラーハンドリングとコード構造を示すための新しいGoプログラム例を追加しました。

  • コミットハッシュ: dad1228cc378f5860a111201ed24ba88cf992a73
  • 作者: Jimmy Zelinskie jimmyzelinskie@gmail.com
  • 日付: 2012年10月11日 木曜日 13:07:34 +1100
  • コミットメッセージ:
    doc/articles/wiki: numerous fixes
    
    Fixes #3733
    Fixes #2149
    Updated Syntax
    Added part3.go example program
    Added part3-errorhandling.go example program
    Improved wording in some places
    
    R=golang-dev, adg, minux.ma
    CC=golang-dev
    https://golang.org/cl/6636048
    

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

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

元コミット内容

doc/articles/wiki: numerous fixes

Fixes #3733
Fixes #2149
Updated Syntax
Added part3.go example program
Added part3-errorhandling.go example program
Improved wording in some places

R=golang-dev, adg, minux.ma
CC=golang-dev
https://golang.org/cl/6636048

変更の背景

このコミットの主な背景には、Go Wikiチュートリアルの既存の課題と改善の必要性がありました。コミットメッセージに記載されている #3733#2149 は、このチュートリアルに関連する既存のバグや改善要求を示唆しています。

具体的な背景としては、以下の点が挙げられます。

  1. チュートリアルの正確性と明瞭性の向上: チュートリアル内のコードスニペットや説明が、Go言語の進化やベストプラクティスに合わせて古くなっていた可能性があります。特に、エラーハンドリングの重要性やその適切な実装方法について、より詳細かつ正確なガイダンスが求められていました。
  2. エラーハンドリングの強化: 従来のチュートリアルでは、エラーが無視されるケースが多く、実際のアプリケーション開発では推奨されない「悪い習慣」として指摘される可能性がありました。このコミットは、エラーを適切に処理し、ユーザーにフィードバックを返す方法を具体的に示すことで、より堅牢なアプリケーション開発の指針を提供しようとしています。
  3. 段階的な学習パスの提供: part3.gopart3-errorhandling.go という2つの新しいサンプルプログラムを追加することで、読者がエラーハンドリングの概念を段階的に理解できるように意図されています。part3.go は基本的な機能拡張を示し、part3-errorhandling.go はそれにエラーハンドリングのロジックを組み込んだ、より実践的な例として機能します。
  4. 構文と表現の統一: チュートリアル全体で使われているGoの構文や一般的な表現を最新の状態に保ち、読者が混乱しないようにするための修正も含まれています。

これらの変更は、Go WikiチュートリアルがGo言語のWebアプリケーション開発の入門として、より高品質で実践的なリソースとなることを目指しています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびWeb開発に関する基本的な知識が必要です。

  1. Go言語の基本:

    • パッケージとインポート: fmt, io/ioutil, net/http, html/template, regexp, errors などの標準ライブラリの役割とインポート方法。
    • 構造体 (Struct): データの集合を定義するための型。Page 構造体のように、関連するデータをまとめるために使用されます。
    • メソッド: 構造体に関連付けられた関数。レシーバ (例: (p *Page)) を持つことで、その構造体のインスタンスに対して操作を実行できます。
    • 関数: Goプログラムの基本的な実行単位。複数の戻り値を返すことができるのが特徴です。
    • スライス ([]byte): 可変長シーケンス。特にファイルの内容やHTTPリクエストのボディなど、バイト列を扱う際によく使用されます。
    • ポインタ: 変数のメモリアドレスを指す型。Goでは、大きな構造体を関数間で渡す際にコピーを避けるためや、メソッドがレシーバの値を変更できるようにするために使用されます。
    • エラーハンドリング: Go言語におけるエラーは、関数の最後の戻り値として error 型で返されるのが一般的です。nil はエラーがないことを意味します。if err != nil のパターンでエラーをチェックし、適切に処理することが推奨されます。
    • ブランク識別子 (_): 戻り値のうち、不要なものを破棄するために使用されます。ただし、エラー値を無視することは、本番環境のコードでは一般的に「悪い習慣」と見なされます。
    • 関数リテラルとクロージャ: 関数を値として扱い、変数に代入したり、他の関数の引数として渡したり、戻り値として返したりできます。クロージャは、それが定義されたスコープの外部変数を参照できる関数リテラルです。これにより、共通のロジック(例: エラーチェック)を複数のハンドラで再利用するパターンを実装できます。
  2. Web開発の基本:

    • HTTPプロトコル: Webブラウザとサーバー間の通信規約。リクエスト (Request) とレスポンス (Response) の概念。
    • HTTPハンドラ: HTTPリクエストを処理し、HTTPレスポンスを生成する関数。Goの net/http パッケージでは、http.Handler インターフェースまたは http.HandlerFunc 型がこれに該当します。
    • ルーティング: 特定のURLパスを対応するハンドラにマッピングするプロセス。http.HandleFunc がこれを行います。
    • HTTPステータスコード: HTTPレスポンスの一部として返される3桁の数字で、リクエストの結果を示します。
      • http.StatusFound (302 Found): リソースが一時的に別のURIに移動したことを示し、リダイレクトに使用されます。
      • http.StatusInternalServerError (500 Internal Server Error): サーバー側で予期せぬエラーが発生したことを示します。
      • http.StatusNotFound (404 Not Found): リクエストされたリソースが見つからないことを示します。
    • HTMLテンプレート: 動的なWebページを生成するための仕組み。Goの html/template パッケージは、Goのデータ構造をHTMLに埋め込む機能を提供し、XSS (Cross-Site Scripting) 攻撃からの保護も行います。
    • フォーム処理: Webフォームからのデータ (r.FormValue) を受け取り、処理する方法。
  3. ファイルシステム操作:

    • ファイル読み書き: io/ioutil.ReadFileio/ioutil.WriteFile を使ったファイルの読み書き。
    • ファイルパーミッション: 0600 のような八進数表記でファイルの読み書き権限を設定する方法(Unix系システムの場合)。
  4. 正規表現:

    • regexp パッケージを使った正規表現のコンパイルとマッチング。特に regexp.MustCompile は、正規表現のコンパイルに失敗した場合にパニックを起こす関数です。

これらの知識があれば、コミットで行われた変更の意図と実装の詳細を深く理解することができます。

技術的詳細

このコミットは、Go Wikiチュートリアルの複数の側面を改善しています。

  1. index.html のコンテンツ更新:

    • 構文と表現の修正: チュートリアル全体で、より正確で自然な表現に修正されています。例えば、「octal integer constant」が「octal integer literal」に変更されるなど、細かな用語の修正が行われています。
    • loadPage 関数の説明の明確化: loadPage 関数が複数の戻り値(*Pageerror)を返すこと、および error 値が nil の場合に成功を示すことが強調されています。
    • ブランク識別子 (_) の使用に関する注意喚起: エラー値を _ で無視することが「悪い習慣」であるという警告が追加され、後で適切に処理されることが示唆されています。
    • fmt パッケージの削除: html/template パッケージの使用により fmt.Fprintf が不要になったため、fmt パッケージのインポートが削除されることが明記されています。
    • エラーハンドリングの導入と説明の拡張:
      • 存在しないページへのアクセスに対する挙動がより正確に記述され、エラーを無視した場合の「HTMLを含むページが表示される」という挙動が説明されています。
      • viewHandler における loadPage のエラーハンドリングが導入され、ページが見つからない場合に /edit/ ページへリダイレクトする http.Redirect の使用が示されています。
      • renderTemplate 関数におけるテンプレートのパースエラーのハンドリングが追加され、http.Error を使用して「Internal Server Error」を返す方法が示されています。
      • saveHandler における p.save() のエラーハンドリングが追加され、エラー発生時に http.Error でエラーメッセージを返す方法が示されています。
    • テンプレートキャッシュの導入: renderTemplate が毎回 ParseFiles を呼び出す非効率性を指摘し、プログラム初期化時に一度だけテンプレートをパースしてキャッシュする template.Must の使用が推奨されています。
    • タイトル検証の導入: 正規表現 (regexp パッケージ) を使用してページタイトルを検証する getTitle 関数が導入され、無効なタイトルに対して「404 Not Found」エラーを返す方法が示されています。
    • ハンドララッパー (クロージャ) の導入: 共通の検証ロジック(タイトル検証など)を複数のハンドラで再利用するために、makeHandler という関数リテラル(クロージャ)を使用してハンドラをラップする高度なパターンが導入されています。これにより、コードの重複が削減され、保守性が向上します。
  2. 新しいGoサンプルプログラムの追加:

    • doc/articles/wiki/part3.go:
      • これは、html/template を使用した基本的なWikiアプリケーションの例です。
      • Page 構造体、save メソッド、loadPage 関数が含まれます。
      • renderTemplate 関数が導入され、テンプレートのレンダリングを抽象化しています。
      • viewHandlereditHandler が実装されていますが、viewHandler では loadPage のエラーが _ で無視されており、saveHandler はコメントアウトされています。これは、エラーハンドリングがまだ完全ではない、または次のステップで導入されることを示す中間的な状態を示しています。
    • doc/articles/wiki/part3-errorhandling.go:
      • part3.go をベースに、より堅牢なエラーハンドリングが組み込まれたバージョンです。
      • viewHandler では、loadPage がエラーを返した場合に http.Redirect を使用して /edit/ ページにリダイレクトします。
      • saveHandler では、p.save() がエラーを返した場合に http.Error を使用して「Internal Server Error」を返します。
      • main 関数で saveHandler も登録されており、完全なWikiアプリケーションの機能が提供されています。
      • このファイルは、Go言語における実践的なエラーハンドリングのベストプラクティスを示すことを目的としています。

これらの変更は、Go Wikiチュートリアルをより包括的で、実践的なWebアプリケーション開発のガイドとして進化させています。特に、エラーハンドリングの重要性と、それをGo言語でどのように実装するかという点に重点が置かれています。

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

このコミットにおけるコアとなるコードの変更は、主に doc/articles/wiki/index.html のコンテンツ更新と、新しく追加された2つのGoプログラムファイルにあります。

  1. doc/articles/wiki/index.html:

    • loadPage の説明変更: loadPage*Pageerror の2つの値を返すこと、およびエラー処理の重要性に関する記述が追加・修正されました。
      --- a/doc/articles/wiki/index.html
      +++ b/doc/articles/wiki/index.html
      @@ -110,35 +110,37 @@ that is the return type of `WriteFile` (a standard library function
       that writes a byte slice to a file).  The `save` method returns the
       error value, to let the application handle it should anything go wrong while
       writing the file.  If all goes well, `Page.save()` will return
      -`nil` (the zero-value for pointers, interfaces, and some other
      +`nil` (the zero-value for pointers, interfaces, and some other
       types).
       </p>
      
       <p>
      -The octal integer constant `0600`, passed as the third parameter to
      +The octal integer literal `0600`, passed as the third parameter to
       `WriteFile`, indicates that the file should be created with
       read-write permissions for the current user only. (See the Unix man page
       `open(2)` for details.)
       </p>
      
       <p>
      -We will want to load pages, too:
      +In addition to saving pages, we will want to load pages, too:
       </p>
      
       {{code "doc/articles/wiki/part1-noerror.go" `/^func loadPage/` `/^}/`}}
      
       <p>
       The function `loadPage` constructs the file name from
      -`Title`, reads the file's contents into a new
      -`Page`, and returns a pointer to that new `page`.
      +the title parameter, reads the file's contents into a new
      +variable `body`, and returns two values: a pointer to a
      +`Page` literal constructed with the proper title and body
      +values and `nil` for the error value.
       </p>
      
       <p>
      -Functions can return multiple values. The standard library function
      -`io.ReadFile` returns `[]byte` and `error`.
      +Functions can return multiple values. The standard library function
      +`io.ReadFile` returns `[]byte` and `error`.
       In `loadPage`, error isn't being handled yet; the "blank identifier"
       represented by the underscore (`_`) symbol is used to throw away the
      -error return value (in essence, assigning the value to nothing).
      +error return value (in essence, assigning the value to nothing).
       </p>
      
    • エラーハンドリングの導入と説明: viewHandlersaveHandlerrenderTemplate におけるエラー処理のコードスニペットと説明が追加されました。特に、存在しないページへのリダイレクトや、サーバーエラーの報告方法が示されています。
      --- a/doc/articles/wiki/index.html
      +++ b/doc/articles/wiki/index.html
      @@ -428,28 +431,31 @@ handlers. Let's remove this duplication by moving the templating code
       to its own function:
       </p>
      
      +{{code "doc/articles/wiki/final-template.go" `/^func renderTemplate/` `/^}/`}}
      {{code "doc/articles/wiki/final-template.go" `/^func viewHandler/` `/^}/`}}
      {{code "doc/articles/wiki/final-template.go" `/^func editHandler/` `/^}/`}}
      -{{code "doc/articles/wiki/final-template.go" `/^func renderTemplate/` `/^}/`}}
      
       <p>
      -The handlers are now shorter and simpler.
      +If we comment out the registration of our unimplemented save handler in
      +`main`, we can once again build and test our program.
      +<a href="part3.go">Click here to view the code we've written so far.</a>
       </p>
      
       <h2>Handling non-existent pages</h2>
      
       <p>
       What if you visit <a href="http://localhost:8080/view/APageThatDoesntExist">
      -`view/APageThatDoesntExist`</a>? The program will crash. This is
      -because it ignores the error return value from `loadPage`. Instead,
      -if the requested Page doesn't exist, it should redirect the client to the edit
      -Page so the content may be created:
      +`view/APageThatDoesntExist`</a>? You'll see a page containing
      +HTML. This is because it ignores the error return value from
      +`loadPage` and continues to try and fill out the template
      +with no data. Instead, if the requested Page doesn't exist, it should
      +redirect the client to the edit Page so the content may be created:
       </p>
      
      -{{code "doc/articles/wiki/final-noclosure.go" `/^func viewHandler/` `/^}/`}}
      +{{code "doc/articles/wiki/part3-errorhandling.go" `/^func viewHandler/` `/^}/`}}
      
       <p>
      -The `http.Redirect` function adds an HTTP status code of
      +The `http.Redirect` function adds an HTTP status code of
       `http.StatusFound` (302) and a `Location`
       header to the HTTP response.
       </p>
      
    • テンプレートキャッシュとタイトル検証、ハンドララッパーの導入: これらの高度なトピックに関する説明と、関連するコードスニペットへの参照が追加されました。
  2. doc/articles/wiki/part3.go (新規追加):

    • html/template を使用した基本的なWikiアプリケーションのコード。
    • viewHandler では loadPage のエラーを無視 (_) している。
    • saveHandlermain 関数でコメントアウトされている。
    // Copyright 2010 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package main
    
    import (
    	"html/template"
    	"io/ioutil"
    	"net/http"
    )
    
    type Page struct {
    	Title string
    	Body  []byte
    }
    
    func (p *Page) save() error {
    	filename := p.Title + ".txt"
    	return ioutil.WriteFile(filename, p.Body, 0600)
    }
    
    func loadPage(title string) (*Page, error) {
    	filename := title + ".txt"
    	body, err := ioutil.ReadFile(filename)
    	if err != nil {
    		return nil, err
    	}
    	return &Page{Title: title, Body: body}, nil
    }
    
    const lenPath = len("/view/")
    
    func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    	t, _ := template.ParseFiles(tmpl + ".html")
    	t.Execute(w, p)
    }
    
    func viewHandler(w http.ResponseWriter, r *http.Request) {
    	title := r.URL.Path[lenPath:]
    	p, _ := loadPage(title) // エラーを無視
    	renderTemplate(w, "view", p)
    }
    
    func editHandler(w http.ResponseWriter, r *http.Request) {
    	title := r.URL.Path[lenPath:]
    	p, err := loadPage(title)
    	if err != nil {
    		p = &Page{Title: title}
    	}
    	renderTemplate(w, "edit", p)
    }
    
    func main() {
    	http.HandleFunc("/view/", viewHandler)
    	http.HandleFunc("/edit/", editHandler)
    	//http.HandleFunc("/save/", saveHandler) // コメントアウト
    	http.ListenAndServe(":8080", nil)
    }
    
  3. doc/articles/wiki/part3-errorhandling.go (新規追加):

    • part3.go をベースに、より堅牢なエラーハンドリングが実装されたコード。
    • viewHandlerloadPage のエラーをチェックし、ページが存在しない場合は /edit/ にリダイレクト。
    • saveHandlerp.save() のエラーをチェックし、エラーが発生した場合は http.Error でサーバーエラーを返す。
    • main 関数で saveHandler も登録されている。
    // Copyright 2010 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package main
    
    import (
    	"html/template"
    	"io/ioutil"
    	"net/http"
    )
    
    type Page struct {
    	Title string
    	Body  []byte
    }
    
    func (p *Page) save() error {
    	filename := p.Title + ".txt"
    	return ioutil.WriteFile(filename, p.Body, 0600)
    }
    
    func loadPage(title string) (*Page, error) {
    	filename := title + ".txt"
    	body, err := ioutil.ReadFile(filename)
    	if err != nil {
    		return nil, err
    	}
    	return &Page{Title: title, Body: body}, nil
    }
    
    const lenPath = len("/view/")
    
    func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    	t, _ := template.ParseFiles(tmpl + ".html")
    	t.Execute(w, p)
    }
    
    func viewHandler(w http.ResponseWriter, r *http.Request) {
    	title := r.URL.Path[lenPath:]
    	p, err := loadPage(title)
    	if err != nil {
    		http.Redirect(w, r, "/edit/"+title, http.StatusFound) // ページが存在しない場合リダイレクト
    		return
    	}
    	renderTemplate(w, "view", p)
    }
    
    func editHandler(w http.ResponseWriter, r *http.Request) {
    	title := r.URL.Path[lenPath:]
    	p, err := loadPage(title)
    	if err != nil {
    		p = &Page{Title: title}
    	}
    	renderTemplate(w, "edit", p)
    }
    
    func saveHandler(w http.ResponseWriter, r *http.Request) {
    	title := r.URL.Path[lenPath:]
    	body := r.FormValue("body")
    	p := &Page{Title: title, Body: []byte(body)}
    	err := p.save()
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError) // 保存エラーを報告
    		return
    	}
    	http.Redirect(w, r, "/view/"+title, http.StatusFound)
    }
    
    func main() {
    	http.HandleFunc("/view/", viewHandler)
    	http.HandleFunc("/edit/", editHandler)
    	http.HandleFunc("/save/", saveHandler) // saveHandlerを有効化
    	http.ListenAndServe(":8080", nil)
    }
    

これらの変更は、Go Wikiチュートリアルの内容を更新し、特にエラーハンドリングのベストプラクティスを段階的に導入することで、読者がより堅牢なWebアプリケーションを構築できるよう指導することを目的としています。

コアとなるコードの解説

このコミットの核となるのは、Go Wikiチュートリアルにおけるエラーハンドリングの導入と、それに関連するコード構造の改善です。特に part3-errorhandling.go は、その集大成として機能します。

1. loadPage のエラー処理とリダイレクト (viewHandler)

viewHandler は、ユーザーが特定のWikiページを閲覧しようとしたときに呼び出されます。このコミットでは、loadPage 関数がページを読み込む際にエラーが発生した場合(例: ファイルが存在しない場合)の処理が強化されました。

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[lenPath:]
	p, err := loadPage(title) // loadPageはエラーも返す
	if err != nil {
		// ページが存在しない場合、編集ページにリダイレクト
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return // リダイレクト後、これ以上処理を行わない
	}
	renderTemplate(w, "view", p)
}
  • loadPage(title) は、*Pageerror の2つの値を返します。
  • if err != nil でエラーの有無をチェックします。
  • エラーがある場合、http.Redirect(w, r, "/edit/"+title, http.StatusFound) を使用して、ユーザーをそのページの編集フォーム (/edit/) にリダイレクトします。http.StatusFound (302) は、リソースが一時的に移動したことを示すHTTPステータスコードです。
  • return ステートメントは、リダイレクトが完了した後にそれ以上の処理が行われないようにするために重要です。これにより、存在しないページに対して不必要にテンプレートをレンダリングしようとするのを防ぎます。

2. save メソッドのエラー処理 (saveHandler)

saveHandler は、ユーザーが編集フォームからページを保存しようとしたときに呼び出されます。このコミットでは、Page.save() メソッドがファイルを書き込む際にエラーが発生した場合の処理が追加されました。

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[lenPath:]
	body := r.FormValue("body") // フォームからボディを取得
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save() // saveメソッドはエラーを返す
	if err != nil {
		// ファイル保存エラーが発生した場合、Internal Server Errorを返す
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return // エラー応答後、これ以上処理を行わない
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound) // 保存成功後、閲覧ページにリダイレクト
}
  • err := p.save() で、ファイルの保存操作の結果として返されるエラーをキャッチします。
  • if err != nil でエラーをチェックします。
  • エラーがある場合、http.Error(w, err.Error(), http.StatusInternalServerError) を使用して、クライアントに「500 Internal Server Error」を返します。err.Error() は、error インターフェースのメソッドで、エラーの詳細な文字列表現を返します。
  • これにより、ファイルシステムの問題など、サーバー側で発生した保存エラーをユーザーに適切に通知し、アプリケーションがクラッシュするのを防ぎます。

3. テンプレートレンダリングのエラー処理 (renderTemplate - index.html の説明のみ)

index.html の説明では、renderTemplate 関数におけるテンプレートのパースエラーの処理も示唆されています(ただし、提供された part3-errorhandling.go には直接含まれていませんが、チュートリアルの最終版では導入される概念です)。

// index.htmlで示唆されるrenderTemplateの改善例
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	t, err := template.ParseFiles(tmpl + ".html") // エラーをチェック
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = t.Execute(w, p) // 実行時のエラーもチェック
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
  • template.ParseFilest.Execute がエラーを返す可能性があるため、それらを適切にチェックし、エラーが発生した場合は http.Error でクライアントに通知します。

4. part3.gopart3-errorhandling.go の役割

  • part3.go: このファイルは、html/template の導入と基本的なハンドラの統合を示しますが、エラー処理はまだ不完全です(例: viewHandlerloadPage のエラーを _ で無視している)。これは、チュートリアルが段階的に複雑な概念を導入するための「中間ステップ」として機能します。
  • part3-errorhandling.go: このファイルは、part3.go を基に、上記で説明したような堅牢なエラーハンドリングのロジックを完全に組み込んだバージョンです。これにより、読者はGo言語でWebアプリケーションを開発する際に、エラーを適切に処理することの重要性と具体的な方法を学ぶことができます。

これらの変更により、Go Wikiチュートリアルは、単なるWebアプリケーションの構築方法だけでなく、Go言語における堅牢なエラーハンドリングのベストプラクティスも教える、より実践的で教育的なリソースへと進化しました。

関連リンク

参考にした情報源リンク