GoでOSS書いてみました

GoでOSS書いてみました

表題の通りこの記事では Go で書いた OSS である constructor を紹介します。

constructor

constructorGo製のGoのコードを生成してくれるシンプルなCLIツールになっています。 constructorはいわゆるコンストラクタ関数を生成してくれます。

コンストラクタについてはEffective Goにも書かれているので気になる方はこちらもどうぞ。

例えば struct FooBar という stirng型のフィールドが定義されている場合を考えます。

type Foo struct {
  Bar string
}

これをファイルに保存して、 constructor generate を実行します。すると下記のようなコンストラクタが自動で生成されます。

$ constructor generate

で、下のような Foo に対応したコードが生成されます。

func NewFoo(bar string) Foo {
    return Foo {
        Bar: bar,
    }
}

constructor を使う流れ、大まかな機能はこれで以上です。

なんで作ったの

今回このOSSを書いたモチベーションはGoの構造体を安全に new したかったからです。ここでいう安全とはstructを使用する側は定義されているフィールドをすべて使用してインスタンス化したい。そうでない場合はコンパイルエラーになってほしい。このコンパイルエラーで検知できる状態を安全と定義しています。確認のためにベタにstructを書いた場合に置き得る想定している課題を書いていきます。例には先程の Foo を使用します。

func main() {
  foo := Foo {
    Bar: "#main",
  }
}

こちらは Fooインスタンスを作って変数に代入しています。このFooインスタンス化をしているコードがいくつかプロジェクト内に存在していたとしましょう。ここで Foo にフィールドを一つ追加します。

type Foo struct {
  Bar string
  Piyo int // <- New field
}

Piyo というフィールドを追加しました。さて、このフィールドは 0 埋めじゃない値が入っている必要がある。そういうフィールドにする必要があると仮定します。この場合各 Foo を使っている該当コードを検知する術はありますでしょうか。grepだったりUnitTestかもしれませんね。しかしこれらは見つけるのにはいささか人に依存した方法になります。つまりミスや対応し忘れを招く可能性が大きくなります。 constrructor はこの問題の早期発見、またはどのフィールドが RequiredOptional かの判断材料を提供できるライブラリになっています。

constructor で生成されるコードは基本はstructが持っているフィールドを網羅する形で関数が引数を取って、構造体に代入して返す関数を作ります。constructor generate を再度実行することで Piyo フィールドを追加したときも先程の NewFoo の関数は引数を取る数が変わることになります。

func NewFoo(bar string, piyo int) Foo {
    return Foo {
        Bar: bar,
        Piyo: piyo,
    }
}

最初からこの NewFoo を使って Fooインスタンス化していた場合のことを想像しましょう。 Piyo フィールドが増えた場合は関数の引数が足りなくてコンパイルエラーになります。つまりGoのプログラムが実行できない明らかな間違い状態にすることができます。残念ながら 0 埋めじゃない値が入っているというロジカル部分の保障はできませんが、それでも単純な間違いを一つ減らすことができたことに価値があると思います。

func main() {
  foo := NewFoo(
    "#main",
    1000,
  )
}

しかし、これだと初期化したくないフィールドをコンストラクタに含めたくない気持ちも、まあわかります。(実はGoでも初期化したくないには出会ったこと無いのですが) そこでコンストラクタの対象から外せる ignore_constructorというフィールドにつけられるタグを用意してあります。下記のように対象から外したいフィールド(Piyo)にタグをつけるだけで NewFoo メソッドの引数から除外されます。

type Foo struct {
  Bar string
  Piyo int `ignore_constructor:true`
}
func NewFoo(bar string) Foo {
    return Foo {
        Bar: bar,
    }
}

補足

constructor を使うで3つの形式のフォーマットのファイルが必要になってきます。

  1. .goファイルです。コンストラクタを作る上で.goで書かれたstructが必要です。
  2. .yamlファイルです。どのファイルから読み込んで、どこに出力するか等の設定情報が必要です。
  3. .tplファイルです。NewFoo関数をどのように構成するかのテンプレートファイルが必要です。詳しくはtemplate

2, 3 のファイルは constructor setup コマンドで設定ファイルの雛形を手に入れたり、テンプレートを入手することができます。

READMEもぜひ見てみてください。

最後に

最後に簡単に自分のGoの経歴を簡単に。1年ほどiOSアプリエンジニアをやりながらGoでAPIサーバーを書いていました。つい最近の3週間ほど前からメインの業務を切り替えてiOSエンジニアからGoでAPIサーバーを書くことになりました。そこでGoになれるようにライブラリ作ってみるか。とかそういうモチベーションもあり今回のライブラリを作りました。もしかしたらまだGoに慣れていないところもあるのでなにか違うことがあればbannzaiあてに連絡でもください。

さて、ところで私はiOSエンジニア界隈ではOSSを作っては発表していたらいつのまにかスター乞食とあだ名がついてしまい親しみのある代名詞があります。今後はGo界隈にもOSS作っては発表しに行くかもしれませんね。

それはさておき、このライブラリが便利!最高!クール!bannzaiいいぞ!bannzai応援しているぞ!Go界隈へようこそ!この乞食!と思ったそこの貴方

スターください

おしまい \(^o^)/