Go言語とテンプレート

最終更新: 2022-05-03 10:12:40 +0800 +0800
tags: Go | Templates
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

Goはソースをパッケージとモジュールに整理し、回復可能な失敗を値として報告し、標準ライブラリでリフレクションベースのテキストテンプレートとHTMLテンプレートを提供します。これらの領域を結び付ける同じ設計原則は、API境界で依存関係と制御フローを明示することです。

モジュール、パッケージ、ビルド #

パッケージは、1つのディレクトリ内で同じパッケージ宣言を持つ、テスト用ではない.goファイルによって形成されるコンパイルおよび名前空間の境界です。モジュールは、go.modをルートとする、バージョン管理されたパッケージの集合です。モジュールパスはインポートパスのプレフィックスを提供します。

sh
mkdir example
cd example
go mod init example.com/team/example
go get golang.org/x/sync@v0.17.0
go mod tidy
go test ./...
go build ./cmd/server

go getはモジュール要件を変更します。go install package@versionは現在のモジュールを変更せずにコマンドをインストールします。現在のGoリリースでは、モジュールモードが標準の依存関係モデルであり、モジュールはGOPATHの外部に置くことができます。ネストされたモジュールは独立した依存関係の境界であり、親モジュールのパッケージパターンから除外されます。

ビルドキャッシュは、コンパイル済みパッケージとテスト結果をGOCACHEの下に保存します。ダウンロードしたモジュールソースはGOMODCACHEの下、通常は$GOPATH/pkg/modに保存されます。実際の場所は次で確認できます。

sh
go env GOCACHE GOMODCACHE GOPATH

GOCACHEとGOMODCACHEを同じディレクトリに指定しないでください。両者は異なるレイアウトとライフサイクル規則を持つためです。CIでは、Goのバージョン、対象プラットフォーム、依存関係ファイルをキーとして両方をキャッシュしつつ、go.sumを認証済みモジュールコンテンツの記録として扱えます。

func main()を含むpackage mainは実行可能ファイルをビルドし、インポート可能なライブラリ境界にはなりません。再利用可能な処理は別のパッケージに置き、フラグの解析とプロセス終了の判断はmainに保持します。

エラー、パニック、クリーンアップ #

組み込みのerrorインターフェースにはError() stringが含まれます。呼び出し側が局所的な判断を行える場合、関数はエラーを返します。

go
f, err := os.Open(path)
if err != nil {
    return fmt.Errorf("open %q: %w", path, err)
}
defer f.Close()

呼び出し側がerrors.Isまたはerrors.Asを通じて原因にアクセスできるようにする場合は、%wでラップします。パニックは、現在の抽象化内部で回復不能な不変条件違反が発生したことを示します。ライブラリは、通常のエラー結果の代わりにパニックを使用すべきではありません。HTTPサーバーなどの境界では、1つのリクエストを分離し、スタックをログに記録して、失敗をエラーレスポンスに変換するためにパニックを回復できます。回復しても、部分的に変更された状態が有効になるわけではありません。

deferは関数値と引数を即座に評価し、その後、周囲の関数が戻るかパニックしたときに、遅延呼び出しを後入れ先出しの順序で実行します。遅延クロージャは名前付き戻り値を検査できます。特に書き込み可能なファイルでは、クリーンアップ時のエラーも明示的に処理する必要があります。

go
func writeFile(path string, data []byte) (err error) {
    f, err := os.Create(path)
    if err != nil {
        return err
    }
    defer func() {
        if closeErr := f.Close(); err == nil {
            err = closeErr
        }
    }()

    _, err = f.Write(data)
    return err
}

Closeは、遅延した書き込みエラーやメタデータエラーを報告することがあります。読み取り専用のクローズエラーが操作結果に影響する可能性は低くなりますが、リソース契約で必要とされる場合、アプリケーションはそれらを記録できます。

宣言、シャドーイング、カンマok形式 #

:=は、現在の字句ブロック内で少なくとも1つの新しい変数を宣言します。ネストした宣言は、外側のエラーをシャドーイングできます。

go
var err error
for {
    var message []byte
    message, err = readMessage()
    if err != nil {
        break
    }
    _ = message
}
return err

ループ内でmessage, err := readMessage()を使用すると、新しいerrが宣言され、外側の値は変更されません。宣言は使用箇所の近くに置きつつ、結果をブロックの外部へ持ち出す必要がある場合は代入を使用します。

3つの言語操作にはカンマok形式があります。

go
value, present := m[key]       // map lookup
value, open := <-ch            // channel receive
value, matches := x.(Target)   // type assertion

ブール値を省略すると、型アサーションの失敗はパニックになります。一方、存在しないマップエントリやクローズ済みチャネルからの受信はゼロ値を生成します。したがって、ブール値によって実際のゼロ値と、不在またはクローズを区別できます。

定数宣言内では、iotaはゼロから始まる定数インデックスです。

go
type Permission uint8

const (
    Read Permission = 1 << iota
    Write
    Execute
)

挿入によって後続の値が変わる可能性があるため、iotaベースの列挙を永続化または送信する場合は、すべての値を意図的に割り当てるか、互換性を別の方法で管理してください。

定義型、エイリアス、参照型に似た値 #

エイリアスは同じ型に別の表記を導入します。定義は、指定された基底型を持つ独立した名前付き型を作成します。

go
type Bytes = []byte
type UserID string

Bytesと[]byteは同一です。UserIDとstringは、どちらの代入規則も適用されない場合、変換が必要です。また、UserIDにはメソッドを宣言できます。

スライス、マップ、チャネル、関数、インターフェースは、実行時状態への参照を含み、コピーコストの低いディスクリプターです。スライスを値で渡すと、要素ではなくポインター、長さ、容量がコピーされます。マップまたはチャネルの値をコピーしても、同様に背後の状態は複製されません。これらの値へのポインターが必要なのは、関数が呼び出し側のディスクリプターを置き換える必要がある場合、または追加の状態を表す場合だけです。

Goにはクラスのサブタイピング関係がありません。具象型はメソッドを持つことでインターフェースを満たし、埋め込みは選択したフィールドとメソッドを昇格させますが、一方の構造体をもう一方のサブタイプにするわけではありません。メソッドセットはTと*Tで異なります。値レシーバーを持つメソッドは両方のメソッドセットに属し、ポインタレシーバーのメソッドは*Tに属します。

テンプレートの解析と実行 #

text/templateは任意のテキストを生成します。html/templateは同じ中核言語を実装しますが、HTML、属性、URL、JavaScript、CSSのコンテキストに応じて信頼できない値をエスケープします。HTML出力にはhtml/templateを使用してください。信頼できないテキストをtemplate.HTMLなどの信頼済みコンテンツ型に変換すると、この保護を回避できます。

テンプレートを解析する前に、名前付き式で使用する関数を登録し、実行エラーを確認してください。

go
const source = `{{define "row"}}<li>{{.Name}}</li>{{end}}
<ul>{{range .Items}}{{template "row" .}}{{else}}<li>empty</li>{{end}}</ul>`

tmpl, err := template.New("page").Option("missingkey=error").Parse(source)
if err != nil {
    return err
}
if err := tmpl.Execute(output, data); err != nil {
    return err
}

アクションは{{と}}の間に現れます。ドット(.)は現在の引数です。パイプラインは、あるコマンドの最終値を次のコマンドに渡します。if、with、rangeは条件付きで実行され、rangeは各要素についてドットを変更します。define、template、blockは名前付きテンプレートを作成し、呼び出します。{{$root := .}}などの変数は、ドットが変わったときに値を保持します。

テンプレート呼び出しは、1つのパイプライン引数を受け取ります。

gotemplate
{{template "card" .}}

複数の論理値を渡すには、構造体、マップ、スライス、またはヘルパーの結果でラップします。フィールド名と所有権をGoコード内で明示できるため、型付きビューモデル構造体を優先します。

go
type CardData struct {
    User    User
    Enabled bool
}

評価に失敗した場合、Template.Executeはすでに部分的な出力を書き込んでいる可能性があります。呼び出し側が全か無かの出力を必要とする場合は、まずbytes.Bufferにレンダリングしてください。解析後、呼び出し側がテンプレートを変更せず、ライターが独立していれば、テンプレートは並行して実行できます。

エディターとツールの境界 #

一般的なファイル名のサフィックスは.tmpl、.gotmpl、または.htmlなどのコンテンツサフィックスです。エディターのサポートは拡張子と埋め込み言語の文法に依存します。ファイル名によってGoテンプレートの意味論が変わることはありません。gofmt、go vet、go testでGoコードをフォーマットおよびテストし、テンプレートソース内のリフレクションされたフィールド名やメソッド名はGoコンパイラーが型チェックできないため、テストで代表的なテンプレートデータを実行してください。

権威あるリファレンスは、Go言語仕様、goコマンド、およびtext/templateとhtml/templateのパッケージドキュメントです。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)