メインコンテンツまでスキップ

go言語の名前空間について

Sato Taichi

package 宣言における名前付けのアプローチが、go と Java では考え方が随分違っていて面食らったので忘れないうちに書いておきます。

go 言語のテンプレート API#

go 言語にはテンプレーティングする API が標準で二種類あります。

Template という struct がそこらじゅうで出てくるのですけども、何か混ざったような印象を受けるのです。

html/templateの API はtext/templateを利用して実装しているので、ある種の重複は当然あってしかるべきなのですけども、何か違和感がありました。

コードを引っ張りだしてみます。

package template
import (    "fmt"    "reflect"    "text/template/parse")
// Template is the representation of a parsed template. The *parse.Tree// field is exported only for use by html/template and should be treated// as unexported by all other clients.type Template struct {    name string    *parse.Tree    *common    leftDelim  string    rightDelim string}
package template
import (    "fmt"    "io"    "io/ioutil"    "path/filepath"    "sync"    "text/template"    "text/template/parse")
// Template is a specialized Template from "text/template" that produces a safe// HTML document fragment.type Template struct {    escaped bool    // We could embed the text/template field, but it's safer not to because    // we need to keep our version of the name space and the underlying    // template's in sync.    text *template.Template    // The underlying template's parse tree, updated to be HTML-safe.    Tree       *parse.Tree    *nameSpace // common to all associated templates}

何とディレクトリパスが違うのに package で宣言されている名前は同じなのです。

多分、きっと java ならディレクトリ階層を名前空間に反映させる必要がありますのでtext.templateとかhtml.template等と言う名前になるでしょう。ちなみに、go 言語では識別子に.(ドット)は使えません。

go 言語の package 宣言は絶対的なものではない#

go 言語における package 宣言は、それを import する側にとっては単にデフォルトの名前であって何かあれば好きな様に変えられるものを宣言するのです。

これは、それぞれのパッケージを import するコードを書くと分かります。

まずは、シンプルにhtml/templateだけを import するコード。

package main
import (    "html/template"    "os")
func main() {    type Hoge struct {        Name string    }    v := &Hoge{"Joe<script>alert('hoge')</script>"}    const letter = "Dear {{.Name}}\n"    handle(template.Must(template.New("hoge").Parse(letter)).Execute(os.Stdout, v))}
func handle(e error) {    if e != nil {        panic(e)    }}

ここで、登場するtemplateが肝ですね。html/templateには、package templateとあるので import するとデフォルトで、templateが対象リソースを main.go に取り込んだ際に使える名前空間になります。

では、text/templatehtml/templateを同時に使う場合、どうなるのでしょうか。

import する際に利用する名前を利用者側が指定します。つまり、こういうコードになります。

package main
import (    ht "html/template"    "os"    tt "text/template")
func main() {    type Hoge struct {        Name string    }    v := &Hoge{"Joe<script>alert('hoge')</script>"}    const letter = "Dear {{.Name}}\n"    handle(tt.Must(tt.New("hoge").Parse(letter)).Execute(os.Stdout, v))    handle(ht.Must(ht.New("moge").Parse(letter)).Execute(os.Stdout, v))}
func handle(e error) {    if e != nil {        panic(e)    }}

名前空間に対する考え方の違いについて#

Java では慣習としてドメイン名を反転してパッケージ名の一部に含める様になっています。FQDN で表すとcom.example.system.HogeControllerの様にウンザリする程長い名前のクラスがそこらじゅうに転がっています。

今回の様に、複数のパッケージに同じ名前のクラスが存在する場合、Java では少々面倒な事になります。

java で継承の仕組みを使わずに似た様なコードを書くと、こんな風になるでしょう。

public class Main {    public static void main(String[] args) {        class Hoge {            String name;        }
        Hoge v = new Hoge();        v.name = "Joe<script>alert('hoge')</script>";        String letter = "Dear {{.Name}}\n";        new html.template.Template("hoge").parse(letter).Execute(System.out, v);        new text.template.Template("moge").parse(letter).Execute(System.out, v);    }}

ここで問題にしたいのは、名前空間を指定するやり方です。java ではクラス名が重複する際、FQDN を使って厳密にどちらを利用するのか指定します。

これは、最初に宣言されたパッケージ名が出来る限り尊重されるのであるから、パッケージ名を決める人は慎重に名前を選択して下さい、という事です。

つまり、Java では package 宣言は大きな慎重さを必要とされる宣言なのです。

それに対して go 言語では名前が衝突するなら import 時に、そのソースコード内でユニークになる様に参照する名前を変えて下さい、というやり方です。

これは、最初に宣言されたパッケージ名は、それ程尊重されないという事です。例え不都合な名前が付けられていたとしても、import する側がより妥当な名前を付ければいいのです。

つまり、go の package 宣言は大胆にそれぞれが分かり易いシンプルな名前を付けておけば良いのです。

最後に、Effective Go の当該部分を引用しておきます。

The package name is only the default name for imports; it need not be unique across all source code, and in the rare case of a collision the importing package can choose a different name to use locally. In any case, confusion is rare because the file name in the import determines just which package is being used.

java っぽい名前付けについては明確に否定しています。

Another convention is that the package name is the base name of its source directory; the package in src/pkg/encoding/base64 is imported as "encoding/base64" but has name base64, not encoding_base64 and not encodingBase64.

現状のコードに対する疑問メモ#

今回の話題とは直接関係ないのですけども、text/template/helper.goの中身が、ごそっとhtml/template/template.go#L278辺りにコピペされている様に見えるんですけども、ちょっとダサくないですか?

参考資料