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
610
range over funcのエラー処理
makki_d
1
1.6k
GoとテストとインプロセスDB
makki_d
3
530
君は古の言語M4を知っているか (LT)
makki_d
0
350
型パラメータが使えるようになったのでLINQを実装してみた
makki_d
2
1.4k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
0
200
ホットリロードツールの作り方
makki_d
0
1.1k
JavaプログラムをGoに移植するためのテクニック――継承と例外
makki_d
4
4.1k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
4
700
Other Decks in Programming
See All in Programming
生成AIでGitHubソースコード取得して仕様書を作成
shukob
0
630
PHPで作るWebSocketサーバー ~リアクティブなアプリケーションを知るために~ / WebSocket Server in PHP - To know reactive applications
seike460
PRO
2
770
QA環境で誰でも自由自在に現在時刻を操って検証できるようにした話
kalibora
1
140
『改訂新版 良いコード/悪いコードで学ぶ設計入門』活用方法−爆速でスキルアップする!効果的な学習アプローチ / effective-learning-of-good-code
minodriven
28
4.2k
「とりあえず動く」コードはよい、「読みやすい」コードはもっとよい / Code that 'just works' is good, but code that is 'readable' is even better.
mkmk884
6
1.4k
カスタムエフェクトプラグインで Atom Craft をいい感じにする@ADX / ADX LE勉強会 vol.1
cox2
0
110
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
4
1.1k
PSR-15 はあなたのための ものではない? - phpcon2024
myamagishi
0
410
Fixstars高速化コンテスト2024準優勝解法
eijirou
0
190
“あなた” の開発を支援する AI エージェント Bedrock Engineer / introducing-bedrock-engineer
gawa
5
460
ESLintプラグインを使用してCDKのセオリーを適用する
yamanashi_ren01
2
250
テストコード書いてみませんか?
onopon
2
340
Featured
See All Featured
The Illustrated Children's Guide to Kubernetes
chrisshort
48
49k
YesSQL, Process and Tooling at Scale
rocio
170
14k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Bash Introduction
62gerente
610
210k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.2k
How to Think Like a Performance Engineer
csswizardry
22
1.3k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Designing for humans not robots
tammielis
250
25k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
3.6k
GraphQLとの向き合い方2022年版
quramy
44
13k
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関数
おしまい 質問の他、もっと良い書き方の提案などあればお願いします。