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.7k
JavaプログラムをGoに移植するためのテクニック――継承と例外
Go Conference'19 Summer in Fukuoka
MakKi
July 13, 2019
Tweet
Share
More Decks by MakKi
See All by MakKi
眼鏡と視力についての誤解を解く
makki_d
0
110
標準ライブラリの動向とイテレータのパフォーマンス
makki_d
3
660
range over funcのエラー処理
makki_d
1
1.6k
GoとテストとインプロセスDB
makki_d
3
590
君は古の言語M4を知っているか (LT)
makki_d
0
400
型パラメータが使えるようになったのでLINQを実装してみた
makki_d
2
1.4k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
0
220
ホットリロードツールの作り方
makki_d
0
1.1k
JavaプログラムをGoに移植するためのテクニック――継承と例外
makki_d
4
4.3k
Other Decks in Programming
See All in Programming
Cursor/Devin全社導入の理想と現実
saitoryc
29
22k
ビカム・ア・コパイロット
ymd65536
1
150
OpenTelemetryで始めるベンダーフリーなobservability / Vendor-free observability starting with OpenTelemetry
seike460
PRO
0
120
エンジニアが挑む、限界までの越境
nealle
1
340
Rubyの!メソッドをちゃんと理解する
alstrocrack
2
370
Design Pressure
hynek
0
150
知識0からカンファレンスやってみたらこうなった!
syossan27
5
290
データベースの技術選定を突き詰める ~複数事例から考える最適なデータベースの選び方~
nnaka2992
3
2.3k
Beyond_the_Prompt__Evaluating__Testing__and_Securing_LLM_Applications.pdf
meteatamel
0
120
GitHub Copilot for Azureを使い倒したい
ymd65536
1
340
設計の本質:コード、システム、そして組織へ / The Essence of Design: To Code, Systems, and Organizations
nrslib
10
3.9k
CRUD から CQRS へ ~ 分離が可能にする柔軟性
tkawae
0
110
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
Thoughts on Productivity
jonyablonski
69
4.6k
Intergalactic Javascript Robots from Outer Space
tanoku
271
27k
The Invisible Side of Design
smashingmag
299
50k
Building Adaptive Systems
keathley
41
2.5k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.3k
Documentation Writing (for coders)
carmenintech
71
4.8k
Building Applications with DynamoDB
mza
94
6.4k
4 Signs Your Business is Dying
shpigford
183
22k
A Modern Web Designer's Workflow
chriscoyier
693
190k
Why Our Code Smells
bkeepers
PRO
336
57k
It's Worth the Effort
3n
184
28k
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関数
おしまい 質問の他、もっと良い書き方の提案などあればお願いします。