2016年にJavaFXで作成したツール(TKoolFacetileMaker2)のリファクタリングをした
2020/5/4 #java #gradle #javafx #TKoolFacetileMaker2
内容
2020/5/4から4年前、当時学生のころに趣味で作ったGUIツールのリファクタリングの話を書く
背景
2016年の7月にRPGツクールの非公式フォーラムにTKoolFacetileMaker2というツールを公開した
https://github.com/jiro4989/TKoolFacetileMaker2
これを作成した当時はJava8がまだまだ新しかった時代だった記憶
https://gyazo.com/27bbbdef5cf96f6f1f88dd3fa7577b19
4年も経った今はJavaも14になった
仕事でJavaを書かなくなり、NimやGoへの興味関心が強くなった
結果メンテを事実上放置していた
4年経ってなお使っている人がちらほらいるのがメール通知から知っている
メンテナンスをする義務はないが、技術者として古くなったものをそのまま残しておきたくない気持ちが強くなってきた
僕がこのツールを使うことはほぼないだろうけれど、重い腰をあげてメンテナンスすることにした
抱えていた問題
実は2018年ごろに一度Kotlinでリファクタリングしようとしていたが挫折した
このツールを作った当時はまだ学生だったこともあり、色々作りに問題があった
例えば
antという古いビルドツールでビルドしている
依存ライブラリの自動取得ができない
パッケージやクラス命名規則が定まっておらず、Javaプロジェクトで一般的でない
パッケージ名だとドメインの逆順がJavaでは一般的
そうなってなくて奇妙
MVCスタイルに則っておらず、ロジックが至るところに散らかっている
コントローラークラスにロジックが固まっていたりそうでなかったりする
控えめに言って、クソ
プロジェクト構造がJavaプロジェクトの一般的な構造でなく、IntelliJに取り込めない
src/main/javaとかはない
resourcesがソースコードと一緒に存在する
テストコードがない
依存ライブラリの一部のコードが残っておらず、そもそもビルドできない
自作のオレオレライブラリに依存しているがオレオレライブラリのソースコードが残っていない
ビルド手順が残っておらずビルド方法がわからない
依存ライブラリが行方不明で取得方法もわからずビルドの仕方がわからない
色々問題があったが、一気にやろうとして挫折した
gradleに移しつつkotlinに書き直す
Oracle Java8からJava11に上げるとJavaFXがJDKから分離されたことでビルド方法が変わる
tkfmを作り直すにあるとおり、Javaは捨てて別言語と別フレームワークに移行できないか検討した
色々あって挫折
結局リファクタリングしてJavaのままメンテナンスを続ける方針に固まった
理由
GUIフレームワークとしてJavaFXは非常に良くできている
他の言語のフレームワークにはいろいろ問題があった
ドキュメント不足
機能不足
バグが多い
ビルドが難しい
WinかMacかLinuxのどれかで動かないことがある
など
最近のJavaは高機能なのでkotlinに移行しなくても良さそう
kotlin + javafx + gradleでビルドするの難しすぎ
問題の分解
前述の問題を分解して、一つ一つ潰していく方針にした
GitHubのIssuesに問題、やることを明確にした
いっぺんにやろうとすると死ぬ
後でやるから今はやらないという割り切り
ストーリーだてて進める
gradleでビルドできるようにする
この時点では手動ビルドでいい
CIでビルドできるようにする
常にビルド/リリースできるようにする
コードフォーマットする
Googleコーディングスタイルに則る
Javaをアップグレードする
Java8からJava14へ
リファクタリングする
テストコードを書く前提で実装する
MVCにする
テストコード整備する
CIで自動テストする
ドキュメント整備する
開発者向けのドキュメント(自分用)のを書く
クラスファイル単位でkotlinに移植する
初めはやろうと思っていたけれど、やっぱりやめた
そんなにKotlinにしても変らなさそうな気がしてきた
マルチパラダイムにするとコンパイルするのが難しくなる
大事にしたこと
とにかく「ビルドできる状態を維持する」が最優先
CIで自動ビルドできる状態を整えてからがスタート
壊れたことを早期発見できないと死ぬ
ローカル環境でビルドする状態だと環境が変わったときにビルドできなくなるリスクが有る
CI環境でビルドしてリリースできれば、いつでもリリースできる
何なら他人に引き継ぐことも可能
CIで自動テストする前提で実装する
UIとロジックを分離する
ロジック部分だけテストできるようにする
ロジックのモデル化
2020/5/11 完了した
リファクタリング Milestone · GitHub
CIで自動ビルド、自動リリースできるようになった
https://github.com/jiro4989/TKoolFacetileMaker2/releases
GitHubActionsでリリースドラフトを公開するとバイナリがリリースされる
JacocoというGradleプラグインでテストカバレッジを取得できるようにした
CodeCovでテストカバレッジを可視化した
Modelなどのロジック部分のテスト網羅率を90%以上にした
https://codecov.io/gh/jiro4989/TKoolFacetileMaker2
https://gyazo.com/b3a3cdb3967a3a1e12e9b950caf4e3fa
UI部分のテストは難しそうだったので諦めた
コード行数
不要と判断した機能はばっさり削除したので大分減った
JavaFXに元々備わっているプロパティバインドという機能をフル活用した
これで値をセットしてまわるということをしなくてよくなった
リファクタリング前
テストコードはなかった
code:sh
$ find src/ -name '*.java' | xargs wc -l
31 src/application/fileList/FileListHBox.java
189 src/application/fileList/FileListHBoxController.java
31 src/application/imageViewer/ImageViewerBorderPane.java
288 src/application/imageViewer/ImageViewerBorderPaneController.java
92 src/application/Main.java
471 src/application/MainController.java
44 src/application/options/Numberings.java
104 src/application/options/Options.java
40 src/application/options/OptionsStage.java
165 src/application/options/OptionsStageController.java
69 src/application/options/Separators.java
36 src/application/outputViewer/MyButton.java
122 src/application/outputViewer/MyImageView.java
33 src/application/outputViewer/OutputViewerAnchorPane.java
130 src/application/outputViewer/OutputViewerAnchorPaneController.java
25 src/application/TKoolVersion.java
29 src/application/version/VersionStage.java
42 src/application/version/VersionStageController.java
1941 total
完了後
main
code:sh
$ find src/main/java/ -name '*.java' | xargs wc -l
8 src/main/java/com/jiro4989/tkfm/data/CropSize.java
42 src/main/java/com/jiro4989/tkfm/data/Position.java
38 src/main/java/com/jiro4989/tkfm/data/Rectangle.java
67 src/main/java/com/jiro4989/tkfm/Main.java
422 src/main/java/com/jiro4989/tkfm/MainController.java
235 src/main/java/com/jiro4989/tkfm/model/CroppingImageModel.java
21 src/main/java/com/jiro4989/tkfm/model/ImageFileModel.java
52 src/main/java/com/jiro4989/tkfm/model/ImageFilesModel.java
211 src/main/java/com/jiro4989/tkfm/model/PropertiesModel.java
133 src/main/java/com/jiro4989/tkfm/model/TileImageModel.java
15 src/main/java/com/jiro4989/tkfm/util/ImageUtil.java
6 src/main/java/com/jiro4989/tkfm/Version.java
1250 total
test
テストケース数は90になった
code:sh
$ find src/test -name '*.java' | xargs wc -l
13 src/test/java/com/jiro4989/tkfm/data/CropSizeTest.java
29 src/test/java/com/jiro4989/tkfm/data/PositionTest.java
26 src/test/java/com/jiro4989/tkfm/data/RectangleTest.java
212 src/test/java/com/jiro4989/tkfm/model/CroppingImageModelTest.java
28 src/test/java/com/jiro4989/tkfm/model/ImageFileModelTest.java
64 src/test/java/com/jiro4989/tkfm/model/ImageFilesModelTest.java
143 src/test/java/com/jiro4989/tkfm/model/PropertiesModelTest.java
120 src/test/java/com/jiro4989/tkfm/model/TileImageModelTest.java
43 src/test/java/com/jiro4989/tkfm/util/ImageUtilTest.java
13 src/test/java/com/jiro4989/tkfm/VersionTest.java
691 total
かかった時間
設計の時間も含めて66時間かかった
たかだか2000行のプログラムに66時間もかかると思ってなかった...
GWが全部消えた+GW終わった後もやってた
https://gyazo.com/383b82ee40de5682b72d3ec3b7d152eb
苦戦したこと
gradle
そもそもgradleを書いたことがなかった
groovyも書いたことがない
書き方や概念を理解する必要があった
mavenは書いたことあるのでmavenでも良かったが、スクリプト言語のが好みなのでgradleにした
JavaFX分離問題
Oracle Java8のころはJDK内にJavaFXが含まれていた
Java9以降でJavaで無料で開発するにはOpenJDKを使う必要がある
OpenJDKも同様にJavaFXがJDKから分離している
JavaFX SDKとJavaFX JMODSを使用してモジュール機構を使う必要がある
Java8で書いていた当時には存在しなかったモジュール機能の理解に苦戦
カスタムJRE
PCにJREをインストールして動かすスタイルはもう古い
アプリケーションごとにJREを内包するスタイルが今後は一般的になる
動作するのに最低限必要なランタイムのみ含むカスタムJREを作成し、アプリケーションと一緒に配布する
jlinkというツールを使う
前述のモジュールと合わせて学習する必要があった
作ってみたら50MBあったが、やむなし
最近の転送速度やストレージ容量なら許容できる(たぶん)
オレオレライブラリのソースコードがない
僕が配布している実行可能JARからクラスファイルを抜いてきてjadでデコンパイルして生成されたコードから復元した
自分の配布物をデコンパイルする日がくるとは...
その他
開発
当初はVim + VimLSPで開発していたが途中からVSCodeに変更した
最近はVagrantで個人の開発環境を仮想化している
ホストPCのVSCodeからリモートSSHでVagrant上のソースコードを編集するようにした
VimLSPの補完よりも強力で滅茶苦茶書きやすかった
ホストPCの環境は汚さないのでよい
日本語入力もホストPCの日本語環境を使用するので問題なし
感想
やってよかった
機能的には増えたどころかむしろ減った
けれど保守性は上がった
自動テストできるようになった
いつでもリリースできるようになった
Java8を使い続けないといけない呪縛から開放された
Java14にできて良かった
ユーザの環境にJREをインストールしなくてよくなった
4年前の反省
使わない機能は実装しない
保守コストが増加するだけで、良いことなし
UIとロジックの密結合は避ける
特に意識してロジックが画面と結合しないようにした
テストコードを書こう
正しい挙動がコードに残る
正しい挙動を再認識できる
テストを書く前提の実装になる
品質が向上する
テストカバレッジをとろう
CodeCovで可視化したことでテストしていない分岐を発見できた
自動テスト環境を作ろう
今回はまっさきに自動ビルド/テスト環境をGitHubActionsで構築したが正解だった
挙動が変わったことにすぐに気づける
自動テスト環境は早く構築すればするほど効果でる
自動リリース環境を作ろう
ビルド/リリース方法は忘れる
リリースできる環境は変わる
ホストPCはOSレベルで変わる
ホストPCだけでビルドしてると「気づかない依存」をしているかもしれない
今回だと「ソースコードの残っていないオレオレライブラリ」がまさにそれ
CI環境でリリースできるようにしていれば、依存がかならず何処かにコード化される
誰でも何時でもビルド/リリースできるようにCI環境上に構築しておくべき
4年前の僕の実装はクソだった
Twitterに散々自分のコードの文句を垂れながした
ごみ
ばか
うんこ
このツール以外にもJavaFXで作ったツールがいくつかあるので、同様のリファクタリングをしていきたい
コツを掴んだ
ただ他のツールは更にユーザ数が少ないので、やってもほとんど自己満足で終わる