Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaプログラムをGoに移植するためのテクニック――継承と例外

Avatar for MakKi MakKi
May 18, 2019

 JavaプログラムをGoに移植するためのテクニック――継承と例外

Go Conference 2019 Spring

Avatar for MakKi

MakKi

May 18, 2019
Tweet

More Decks by MakKi

Other Decks in Programming

Transcript

  1. 自己紹介 • 牧内大輔 • MakKi / @makki_d • 所属:KLab株式会社 ◦

    エンジニアリングマネージャー ◦ オンライン対戦の中継サーバを Goで書いたり • gozxing ◦ https://github.com/makiuchi-d/gozxing ◦ 1D/2Dバーコードリーダーライブラリ ◦ JavaのZXingをGoに移植
  2. ソフトウェア資産を活かしたい • Goは比較的新しい言語 ◦ 2009年11月10日に発表 • JavaやC++などのソフトウェア資産をGoでも利用したい ◦ Goの標準ライブラリはかなり充実している ◦

    その範疇外のライブラリも世の中にはたくさんある • PureなGoで書かれている価値 ◦ クロスプラットフォーム対応のしやすさなど • Go向けに設計し直すのは大変 ◦ 言語構造、思想の違いが大きい ◦ できることなら設計や構造をそのまま利用したい
  3. Java: クラスの定義 • クラスは型 • データと振る舞いを記述 ◦ データ=フィールド ◦ 振る舞い=メソッド

    • コンストラクタで初期化 class Dog { public int Age; public Dog(int age) { Age = age; } public void Bark() { System.out.print("ワン"); } } ... Dog dog = new Dog(3); dog.Bark()
  4. Go: 構造体とレシーバ関数 • フィールドをまとめた構造体 ◦ 型として定義 • 初期化関数 ◦ コンストラクタの代わり

    ◦ 慣例的にNewで始める • メソッドはレシーバ関数 ◦ ポインタレシーバ ◦ オブジェクトに対する副作用 type Dog struct { Age int } func NewDog(age int) *Dog { return &Dog{age} } func (dog *Dog) Bark() { fmt.Print("ワン") } ... dog := NewDog(3) dog.Bark()
  5. Java: 継承による拡張 • 派生クラス ◦ フィールド、メソッドを継承 ◦ 独自のフィールド、メソッドを追加 class HotDog

    extends Dog { public int Temperature; public HotDog(int age, int temp) : Dog(age) { Temperature = temp; } public void Breath() { System.out.print("ハッハッ"); } } ... HotDog dog = new HotDog(3, 25); dog.Bark()
  6. Go: 構造体の埋め込み • 親となる構造体を埋め込む ◦ 無名のフィールド ◦ 親のフィールドやメソッドが使える ▪ あたかも派生構造体のもの

    • 独自のフィールド、メソッド ◦ 普通に追加できる type HotDog struct { *Dog Temperature int } func NewHotDog(age, temp int) *HotDog { return &HotDog{NewDog(age), temp} } func (dog *HotDog) Breath() { fmt.Print("ハッハッ") } ... dog := NewHotDog(3, 25) dog.Bark()
  7. Java: メソッドのオーバーライド • 派生クラスでオーバーライド ◦ 親クラスの挙動を一部変更する ◦ abstractメソッドの場合もある ▪ 派生クラスでの実装を期待

    class Dog { ... 略 ... public void BarkTwice() { Bark(); Bark(); } } class AmericanDog extends Dog { public void Bark() { System.out.print("Bow"); } } ... AmericanDog dog = new AmericanDog(3); dog.BarkTwice() // BowBow
  8. Go: 埋め込みだけではオーバーライドできない • レシーバの型は元の型のまま ◦ 埋め込まれた型のレシーバが呼ばれる ◦ 親クラスからは自身のメソッドになる BarkTwiceの中のdogは*Dogなので *DogのBarkが呼ばれる

    C++で言う仮想関数テーブルが必要 func (dog *Dog) BarkTwice() { dog.Bark() dog.Bark() } type AmericanDog struct { *Dog } ... 略 ... func (dog *AmericanDog) Bark() { fmt.Print("Bow"); } ... dog := NewAmericanDog(3); dog.BarkTwice() // ワンワン
  9. Go: 仮想関数をフィールドに持つ • Goでは関数も変数に入れられる ◦ 関数変数をフィールドとして持てる • 派生クラスで上書きできる • 親クラスは関数フィールドを呼ぶ

    func NewDog(age int) *Dog { dog := &Dog{age} dog.VtBark = dog.Bark return dog } func NewAmericanDog(age int) *AmericanDog { dog := &AmericanDog{NewDog(age)} dog.VtBark = dog.Bark return dog } func (dog *AmericanDog) Bark() { fmt.Print("Bow"); } ... dog := NewAmericanDog(3); dog.BarkTwice() // BowBow type Dog struct { ... 略 ... VtBark func() } func (dog *Dog) BarkTwice() { dog.VtBark() dog.VtBark() } }
  10. Go: 仮想関数をフィールドに持つ 関数変数で持つ場合の問題点 • 仮想関数の数だけ代入が増える • 全部実装されている保証がない • 代入忘れが発生しうる interfaceとして持ってはどうか

    • 代入が増えない • 実装されていることが保証される type TrainedDog struct { *Dog VtOte func() VtOkawari func() VtOsuwari func() } func NewTrainedDog() *TrainedDog { dog := &TrainedDog{Dog()} dog.VtBark = dog.Bark dog.VtOte = dog.Ote dog.VtOkawari = dog.Okawari dog.VtOsuwari = dog.Osuwari return dog }
  11. Go: interfaceによる仮想関数テーブル • type AmericanDog struct { *Dog } func

    NewAmericanDog(age int) *AmericanDog { dog := &AmericanDog{NewDog(age)} dog.Vt = dog return dog } func (dog *AmericanDog) Bark() { fmt.Print("Bow"); } ... dog := NewAmericanDog(3); dog.BarkTwice() // BowBow type DogVT interface { Bark() } type Dog struct { Vt DogVT Age int } func NewDog(age int) *Dog { dog := &Dog{Age: age} dog.Vt = dog return dog } func (dog *Dog) BarkTwice() { dog.Vt.Bark() dog.Vt.Bark() } } ... 略 ...
  12. Go: interfaceによる多態 • interfaceを満たせばその型 ◦ その型振る舞いを持つ ◦ その型の変数に代入可能 ◦ 構造体埋め込みで自動的に満たせる

    • 別途interfaceを定義 • Javaより型制約がゆるい ◦ 移植する上では問題ない type IDog interface { Bark() } var dog IDog dog = NewHotDog(2, 20) dog.Bark() dog = NewAmericanDog(3) dog.Bark()
  13. Java: 例外機構 • throwで例外送出 • try-catchで捕捉 int div(int a, int

    b) { if (b == 0) { throw new Exception("zero division") } return a / b; } ... try{ avg = div(sum, count); } catch(Exception e){ // 何か処理 }
  14. Go: errorを返す • 例外機構は無い • 明示的にerrorを返す ◦ 正常終了ならnil • 複数戻り値を利用

    ◦ 慣例的にerrorは最後 例外を返しうるメソッドすべての 戻り値にerrorを追加していく func div(a, b int) (int, error) { if b == 0 { return 0, errors.New("zero division") } return a / b, nil } ... avg, err = div(sum, count) if err != nil { // 何か処理 }
  15. Java: 例外種別による処理分け • 継承による階層構造 • catchで捕捉する例外型を指定 ◦ 捕捉しないものは更に上に伝播 try{ doSomething();

    } catch(IOException e){ // 何か処理 } catch(RuntimeException e){ // 何か処理 } Exception IOException RuntimeException EOFException SocketException
  16. Go: interface継承による例外階層 • Javaでの継承関係と同じ ◦ errorを継承すればerrorとして返せる • 実体はerrorを埋め込んだ構造体 type Exception

    interface { error exception() } type IOException interface { Exception ioException() } type EOFException interface { IOException eofException() } type eofException struct { error } func (_ eofException) exception() {} func (_ eofException) ioException() {} func (_ eofException) eofException() {} Exception IOException RuntimeException EOFException SocketException
  17. Go: 例外種別の判定 • 型アサーションによる判定 • 型スイッチによる判定 ◦ 複数catchがある場合に便利 欠点 •

    ラップされると型が変わる ◦ xerrorsのErrorfの ": %w" ◦ 型で判定できなくなる func DoSomething() error { return eofException{ errors.New("エラー"), } } ... err := DoSomething() if err != nil { if _, ok := err.(IOException); ok { // 何か処理 } } switch err.(type) { case nil: break case EOFException, SocketException: // 何か処理 }
  18. Go: xerrors.Asによる判定 • xerrors.Asの実装 ◦ 次の条件に合うまで Unwrap() ▪ targetの型に代入可能 ▪

    err.As(target) がtrue • 代入可能かを利用した判定 ◦ targetにはinterface型も渡せる ◦ interface継承の例外でも使える • Asメソッドによる判定 ◦ 今回は省略 ◦ 例外の型ごとに柔軟に設定できる func DoSomething() error { return eofException{ errors.New("エラー"), } } func DoSomething2() error { err := DoSomething() return xerrors.Errorf("エラー2: %w", err) } ... err := DoSomething2() if err != nil { var exception IOException if xerrors.As(err, &exception) { // 何か処理 } }