Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JavaプログラムをGoに移植するためのテクニック――継承と例外
Search
MakKi
July 13, 2019
Programming
1
1.6k
JavaプログラムをGoに移植するためのテクニック――継承と例外
Go Conference'19 Summer in Fukuoka
MakKi
July 13, 2019
Tweet
Share
More Decks by MakKi
See All by MakKi
標準ライブラリの動向とイテレータのパフォーマンス
makki_d
3
480
range over funcのエラー処理
makki_d
0
1.3k
GoとテストとインプロセスDB
makki_d
2
480
君は古の言語M4を知っているか (LT)
makki_d
0
310
型パラメータが使えるようになったのでLINQを実装してみた
makki_d
2
1.3k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
0
180
ホットリロードツールの作り方
makki_d
0
1k
JavaプログラムをGoに移植するためのテクニック――継承と例外
makki_d
4
4k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
4
670
Other Decks in Programming
See All in Programming
Macとオーディオ再生 2024/11/02
yusukeito
0
200
Vue3の一歩踏み込んだパフォーマンスチューニング2024
hal_spidernight
3
3.1k
Vue.js学習の振り返り
hiro_xre
2
130
cXML という電子商取引の トランザクションを支える プロトコルと向きあっている話
phigasui
3
2.3k
VR HMDとしてのVision Pro+ゲーム開発について
yasei_no_otoko
0
100
JaSST 24 九州:ワークショップ(は除く)実践!マインドマップを活用したソフトウェアテスト+活用事例
satohiroyuki
0
260
Honoの来た道とこれから
yusukebe
19
3k
プロジェクト新規参入者のリードタイム短縮の観点から見る、品質の高いコードとアーキテクチャを保つメリット
d_endo
1
1k
とにかくAWS GameDay!AWSは世界の共通言語! / Anyway, AWS GameDay! AWS is the world's lingua franca!
seike460
PRO
1
550
macOS でできる リアルタイム動画像処理
biacco42
7
1.9k
Progressive Web Apps für Desktop und Mobile mit Angular (Hands-on)
christianliebel
PRO
0
110
リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果
katty0324
5
3.6k
Featured
See All Featured
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
How to Think Like a Performance Engineer
csswizardry
19
1.1k
Bash Introduction
62gerente
608
210k
KATA
mclloyd
29
13k
Faster Mobile Websites
deanohume
304
30k
Side Projects
sachag
452
42k
Automating Front-end Workflow
addyosmani
1365
200k
Why You Should Never Use an ORM
jnunemaker
PRO
53
9k
GraphQLとの向き合い方2022年版
quramy
43
13k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5k
Navigating Team Friction
lara
183
14k
Fireside Chat
paigeccino
32
3k
Transcript
Javaプログラムを Goに移植するためのテクニック ――継承と例外 Go Conference'19 Summer in Fukuoka 牧内大輔 @makki_d
自己紹介 • 牧内大輔 • MakKi / @makki_d • 所属:KLab株式会社 ◦
エンジニアリングマネージャー ◦ オンライン対戦の中継サーバを Goで書いたり ◦ PHP/Python/C#その他必要とあればなんでも • gozxing ◦ https://github.com/makiuchi-d/gozxing ◦ 1D/2Dバーコードリーダーライブラリ ◦ JavaのZXingをGoに移植
会社紹介 KLab(くらぶ)株式会社 • 今日は休日出勤扱い(旅費・宿泊費も会社もち) • モバイルオンラインゲームの開発運用 • 福岡オフィスあります(KBCビル) • KLab福岡Meetup
Twitter:@KLab_KFM Go言語の活用事例 • オンライン対戦同期サーバ • MMORPGのゲームサーバ • インフラ管理系ツール • その他細かいツール類やslackbotなど
なぜJavaをGoに移植するのか
ソフトウェア資産を活かしたい • Goは比較的新しい言語 ◦ 2009年11月10日に発表 • JavaやC++などのソフトウェア資産をGoでも利用したい ◦ Goの標準ライブラリはかなり充実している ◦
その範疇外のライブラリも世の中にはたくさんある • PureなGoで書かれている価値 ◦ クロスプラットフォーム対応のしやすさなど • Go向けに設計し直すのは大変 ◦ 言語構造、思想の違いが大きい ◦ できることなら設計や構造をそのまま利用したい
今日のお話 • Java製1D/2Dバーコードライブラリ「ZXing」をGoに移植しました ◦ https://github.com/makiuchi-d/gozxing • 元のJavaコードとGoのコードを見比べながら 実際に使っているテクニックをいくつか紹介します ◦ クラスと継承
▪ メソッドオーバーライド ◦ 例外 ▪ 例外型の階層構造 ▪ 例外種別判定
諸注意 この発表は、Javaの構造をそのままGoに移植するためのテクニックの紹介です。 元コードの構造をそのまま残すため、Goでは推奨されない書き方が出てきます。 コード断片を表示しますが、見にくかったらごめんなさい。 Javaを題材としていますが、C++やC#のように同様のクラスシステムと 例外機構を持つ言語にも適用できると思います。 もっと良い書き方があれば教えてください。
クラスと継承
Goに移植するときのポイント • 構造体とレシーバ関数でクラスを表現 • 構造体埋め込みで継承っぽい動作 • Goではメソッドオーバーライドができない
メソッドオーバーライドできない • レシーバの型は元の型のまま ◦ 埋め込まれた型のレシーバが呼ばれる ◦ 親クラスからは自身のメソッドになる BarkTwiceの中のdogは*Dogなので *DogのBarkが呼ばれる C++で言う仮想関数テーブルが必要
type Dog struct {} func (dog *Dog) Bark() { fmt.Print("ワン") } func (dog *Dog) BarkTwice() { dog.Bark() dog.Bark() } type AmericanDog struct { *Dog } func (dog *AmericanDog) Bark() { fmt.Print("Bow"); } dog := &AmericanDog{&Dog{}} dog.BarkTwice()
Java: OneDReader, Code128Readerの実装 public abstract class OneDReader implements Reader {
@Override public Result decode(BinaryBitmap image, 略) throws NotFoundException, FormatException { ... Result result = decodeRow(rowNumber, row, hints); ... } public abstract Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException; } public final class Code128Reader extends OneDReader { @Override public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException { ... } }
Java: OneDReader, Code128Readerの実装 public abstract class OneDReader implements Reader {
@Override public Result decode(BinaryBitmap image, 略) throws NotFoundException, FormatException { ... Result result = decodeRow(rowNumber, row, hints); ... } public abstract Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException; } public final class Code128Reader extends OneDReader { @Override public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException { ... } } Readerインターフェイスの実装 派生クラスのdecodeRow()呼び出し 派生クラスでdeocdeRowの実装を要求 派生クラスでのdecodeRowの実装 派生クラス
Go: OneDReader, Code128Readerの実装 type RowDecoder interface { DecodeRow(rowNumber int, row
*gozxing.BitArray, 略) (*gozxing.Result, error) } type OneDReader struct { RowDecoder } func NewOneDReader(rowDecoder RowDecoder) *OneDReader { return &OneDReader{rowDecoder} } func (this *OneDReader) Decode(image *gozxing.BinaryBitmap, 略) (*gozxing.Result, error) { ... result, e := this.DecodeRow(rowNumber, row, hints) ... }
Go: OneDReader, Code128Readerの実装 type RowDecoder interface { DecodeRow(rowNumber int, row
*gozxing.BitArray, 略) (*gozxing.Result, error) } type OneDReader struct { RowDecoder } func NewOneDReader(rowDecoder RowDecoder) *OneDReader { return &OneDReader{rowDecoder} } func (this *OneDReader) Decode(image *gozxing.BinaryBitmap, 略) (*gozxing.Result, error) { ... result, e := this.DecodeRow(rowNumber, row, hints) ... } Readerインターフェイスの実装 埋め込んだRowDecoderのDecodeRowの呼び出し オーバーライドするメソッドを集めた インターフェイス(仮想関数テーブル) 仮想関数テーブルの埋め込み 初期化関数で実装を要求
Go: OneDReader, Code128Readerの実装 type RowDecoder interface { DecodeRow(rowNumber int, row
*gozxing.BitArray, 略) (*gozxing.Result, error) } type OneDReader struct { RowDecoder } func NewOneDReader(rowDecoder RowDecoder) *OneDReader { return &OneDReader{rowDecoder} } func (this *OneDReader) Decode(image *gozxing.BinaryBitmap, 略) (*gozxing.Result, error) { ... result, e := this.DecodeRow(rowNumber, row, hints) ... } Readerインターフェイスの実装 埋め込んだRowDecoderのDecodeRowの呼び出し オーバーライドするメソッドを集めた インターフェイス(仮想関数テーブル) 仮想関数テーブルの埋め込み 初期化関数で実装を要求 type code128Reader struct { *OneDReader } func NewCode128Reader() gozxing.Reader { this := &code128Reader{} this.OneDReader = NewOneDReader(this) return this } func (*code128Reader) DecodeRow(略) (*gozxing.Result, error) { ... }
Go: OneDReader, Code128Readerの実装 type RowDecoder interface { DecodeRow(rowNumber int, row
*gozxing.BitArray, 略) (*gozxing.Result, error) } type OneDReader struct { RowDecoder } func NewOneDReader(rowDecoder RowDecoder) *OneDReader { return &OneDReader{rowDecoder} } func (this *OneDReader) Decode(image *gozxing.BinaryBitmap, 略) (*gozxing.Result, error) { ... result, e := this.DecodeRow(rowNumber, row, hints) ... } Readerインターフェイスの実装 埋め込んだRowDecoderのDecodeRowの呼び出し オーバーライドするメソッドを集めた インターフェイス(仮想関数テーブル) 仮想関数テーブルの埋め込み 初期化関数で実装を要求 type code128Reader struct { *OneDReader } func NewCode128Reader() gozxing.Reader { this := &code128Reader{} this.OneDReader = NewOneDReader(this) return this } func (*code128Reader) DecodeRow(略) (*gozxing.Result, error) { ... } 継承元を埋め込み 仮想関数テーブルとして自身を渡す 派生クラスのDecodeRow実装 派生クラス
メソッドオーバーライドのテクニック • 継承元構造体 ◦ 仮想関数テーブルとなるインターフェイスを持つ ▪ 派生構造体のメソッドを呼び出せる ◦ 初期化関数で実装を要求できる •
派生構造体 ◦ 継承元構造体を埋め込む ▪ 継承元のフィールドやメソッドを利用できる ◦ 継承元構造体の初期化関数に自身を渡す ▪ メソッドオーバーライド
例外
JavaからGoへの書き換え try { alignmentPattern = findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i); break;
} catch (NotFoundException e) { // try next round } alignmentPattern, e = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i) if e == nil { break } if _, ok := e.(gozxing.NotFoundException); !ok { return nil, e }
JavaからGoへの書き換え try { alignmentPattern = findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i); break;
} catch (NotFoundException e) { // try next round } alignmentPattern, e = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i) if e == nil { break } if _, ok := e.(gozxing.NotFoundException); !ok { return nil, e } NotFoundExceptionだけ握りつぶす(それ以外は送出) NotFoundException以外を送出 例外非発生でbreak (for文の中) nilチェックしてOKならbreak 戻り値にerrorを追加
JavaからGoへの書き換え try { ... } catch (FormatException | ChecksumException e)
{ // 特定の例外種別専用の処理 ... } ... switch e.(type) { case nil: // break case gozxing.FormatException, gozxing.ChecksumException: // 特定の例外種別専用の処理 default: return nil, e }
JavaからGoへの書き換え try { ... } catch (FormatException | ChecksumException e)
{ // 特定の例外種別専用の処理 ... } ... switch e.(type) { case nil: // break case gozxing.FormatException, gozxing.ChecksumException: // 特定の例外種別専用の処理 default: return nil, e } 2種類の例外だけ捕捉して特別処理 2種類の例外だけ捕捉して特別処理 他の例外は明示的に送出 例外非発生なら明示的になにもしない
Goに移植するときのポイント • 例外はthrowではなく戻り値として送出する ◦ error型 ◦ 例外を投げうるメソッドにはすべて戻り値を足す ◦ 呼び出し側でnilチェック •
型による例外種別判定 ◦ 型アサーション、タイプスイッチ ◦ 元のJavaの設計そのまま • 例外の階層的なグルーピング ◦ 継承による型の階層関係
interface継承による例外型の階層関係の模倣 • Javaでの継承関係と同じにする ◦ errorを継承してerrorとしても扱う • 実体はerrorを埋め込んだ構造体 type Exception interface
{ error exception() } type ReaderException interface { Exception readerException() } type NotFoundException interface { ReaderException notFoundException() } type notFoundException struct { error } func (_ notFoundException) exception() {} func (_ notFoundException) readerException() {} func (_ notFoundException) notFoundException(){} Exception ReaderException WriterException NotFoundException FormatException
型による例外種別判定 • 型アサーションによる判定 • 型スイッチによる判定 ◦ 複数catchがある場合などに便利 欠点 • ラップされると型が変わる
◦ xerrorsのErrorfの ": %w" ◦ 型で判定できなくなる func DoSomething() error { return notFoundException{ xerrors.New("エラー"), } } ... err := DoSomething() if err != nil { if _, ok := err.(ReaderException); ok { // 何か処理 } } switch err.(type) { case nil: // break case NotFoundException: // 何か処理 }
xerrors.As関数による判定も可能 • xerrors.As関数の実装 ◦ 次の条件に合うまで Unwrap() ▪ targetの型に代入可能 ▪ err.As(target)
がtrue • 代入可能かを利用した判定 ◦ targetにはinterface型も渡せる ◦ interface継承の例外型で使える • Asメソッドによる判定 ◦ 今回は省略 ◦ 例外の型ごとに柔軟に設定できる func DoSomething() error { return notFoundException{ errors.New("エラー"), } } func DoSomething2() error { err := DoSomething() return xerrors.Errorf("エラー2: %w", err) } ... err := DoSomething2() if err != nil { var exception ReaderException if xerrors.As(err, &exception) { // 何か処理 } }
まとめ
今日紹介したテクニック • クラスと継承 ◦ メソッドオーバーライドの模倣 ▪ 仮想関数テーブルインターフェイス • 例外機構 ◦
例外型の階層関係の模倣 ▪ インターフェイス継承 ◦ 型による例外種別判定 ▪ 型アサーションやタイプスイッチ ▪ xerrors.As関数
おしまい 質問の他、もっと良い書き方の提案などあればお願いします。