Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
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
550
range over funcのエラー処理
makki_d
1
1.4k
GoとテストとインプロセスDB
makki_d
3
510
君は古の言語M4を知っているか (LT)
makki_d
0
330
型パラメータが使えるようになったのでLINQを実装してみた
makki_d
2
1.3k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
0
190
ホットリロードツールの作り方
makki_d
0
1k
JavaプログラムをGoに移植するためのテクニック――継承と例外
makki_d
4
4.1k
mallocしただけでメモリが確保できるって本当ですか?
makki_d
4
680
Other Decks in Programming
See All in Programming
Seamless Flutter Native Integration: FFI & Pigeon - Dreamwalker (JaichangPark / 박제창) @FlutterKaigi2024
itsmedreamwalker
0
120
42 best practices for Symfony, a decade later
tucksaun
1
140
Develop iOS apps with Neovim / vimconf_2024
uhooi
1
320
Modular Monolith Monorepo ~シンプルさを保ちながらmonorepoのメリットを最大化する~
yuisakamoto
11
4k
SymfonyCon Vienna 2025: Twig, still relevant in 2025?
fabpot
3
930
Leveling Up Developer Tooling for the Modern Rails & Hotwire Era @ Ruby Türkiye, November 2024
marcoroth
0
160
暇に任せてProxmoxコンソール 作ってみました
karugamo
1
190
Symfony Mapper Component
soyuka
2
550
今からはじめるAndroidアプリ開発 2024 / DevFest 2024
star_zero
0
770
競技プログラミングで 基礎体力を身につけよう / You can get basic skills through competitive programming
mdstoy
0
150
テストコード文化を0から作り、変化し続けた組織
kazatohiei
2
920
CSC509 Lecture 13
javiergs
PRO
0
160
Featured
See All Featured
Docker and Python
trallard
41
3.1k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
665
120k
StorybookのUI Testing Handbookを読んだ
zakiyama
27
5.3k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
Optimising Largest Contentful Paint
csswizardry
33
3k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
43
9.3k
Documentation Writing (for coders)
carmenintech
65
4.5k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
Mobile First: as difficult as doing things right
swwweet
222
8.9k
Measuring & Analyzing Core Web Vitals
bluesmoon
4
160
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
4
410
For a Future-Friendly Web
brad_frost
175
9.4k
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関数
おしまい 質問の他、もっと良い書き方の提案などあればお願いします。