1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KMP移行、いちばん高くつくのは「置換」ではありませんでした

1
Posted at

私は、プログラミングの経験がありません。一行も書けません。

そんな人間が、Androidアプリを Kotlin Multiplatform(KMP)で iOS に移植しようとしています。コードは全部、生成AI(Claude)に書いてもらう。私がやるのは、日本語で相談して、「OK」と答えることくらいです。

(先に白状しておきます。この記事自体、私の作業ログをもとにAIに草稿を書かせ、私が事実を一つずつ確かめて直したものです。ただ、出てくる数字もコードの断片も、出どころは全部、自分のアプリの実際の調査結果。そこだけは、嘘のないようにしました。)

……と、ここまでは「AIですごい」という、よくある話です。今日したいのは、その先の話。

移植を始める前に、AIにコードを全部「棚卸し」させたら、思っていたのと違う地雷が3つ出てきた、という話です。同じことをこれからやる方の、転ばぬ先の杖になれば幸いです。

「できる」と「安く済む」は、別の話

まず、調べ方から白状します。

いきなり移植を始めるのが怖かったので、先にAIへ全数調査をお願いしました。6つの観点(バージョン群・日時ライブラリ・DI・DB・UI・プラットフォーム境界)で実コードを片っ端から数え、そのうえで**「わざと粗探しをする検証」を3本**ぶつける。要件も設計も、まず文書にしてすり合わせてから動かす。スペック駆動、というやつです。

出てきた結論は、こうでした。

KMP移行は技術的に成立する。ブロッカーは無い。

やった、と思いました。DBは日時をぜんぶ整数で持っていたのでマイグレーション不要、DAOも素直、DIはシングルトン一色で難所が少ない。下地は良い、と。

ところが。

同じ報告書に、6つの観点のどれにも入っていなかった追加コストが3つ、こっそり書き足されていました。「わざと粗探しをする検証」が見つけた分です。これが、本当の地雷でした。

地雷その1:java.time より手前で、コンパイルが割れる

KMP移行というと、みんな java.timekotlinx-datetime に置き換える話をします。うちも main 側で 139行・62ファイル、テスト側で 82行・47ファイル。確かに大仕事です。

でも、割れるのは、その手前でした。

共通コード(commonMain)には、Java の標準ライブラリがありません。当たり前と言えば当たり前なのですが、盲点でした。具体的に、こんなものが引っかかります。

  • String.format(...) —— 約20箇所。「%d:%02d」みたいな時間表示が、そこら中にある
  • java.math.BigDecimal —— 金額計算のど真ん中(時給×時間の、お金の話です)
  • java.text.NumberFormat —— 「¥3,600」の3桁カンマ
  • System.currentTimeMillis() / java.util.UUID / java.util.Locale

どれも、Androidでは息をするように使えるやつです。それが、共通化した瞬間に一斉に「無いよ」と言われる。日時ライブラリを直す前に、まずこいつらの受け皿を自作しないと、そもそもコンパイルが通らない。

たとえば、こんな地味な置き換えが、あちこちで発生します。

// Android なら、これで通る
val label = String.format("%d:%02d", hour, minute)   // → "9:05"

// commonMain に String.format は無い。自作ヘルパーへ
val label = "$hour:${minute.toString().padStart(2, '0')}"

一箇所ならどうということはありません。でも、これが約20箇所。「置き換えるファイル数」を数えて安心していたら、数えていた列の手前に落とし穴があった、という話です。

地雷その2:テストが、iOSでは動かない

これが一番、ため息が出ました。

うちのテストは、mockk というモック用のライブラリを 108箇所・37ファイルで使っています。テストの背骨です。

この mockkKotlin/Native(=iOS側)では動きません。

つまり、モジュールをKMP化するたびに、既存のテストを引っ越しさせないといけない。

core:domain/
  src/
    test/             ← いままで、ここに置いてあったテストを…
    androidUnitTest/  ← こっちへ、まるごとお引っ越し(mockk はここでしか動かない)

中身は生きるのですが、置き場所を全モジュールぶん動かす。地味です。地味ですが、これがどの観点の見積りにも入っていなかった。

移行の工数って、こういう「本編じゃないところ」に溜まっているんですね。作業の見積もりで、いつも足りなくなるやつです。

地雷その3:機械で置換すると、"直して"壊す

そして、一番こわいのがこれでした。クラッシュしないバグです。

うちのアプリ、「週の始まり」が2つあります。

  • ホーム画面のカレンダーは、日曜始まり
  • レポートと目標集計は、月曜〜日曜の週

これ、バグじゃありません。わざと非対称にしてあるんです。ところが、日時の置き換えを「機械的な作業」だと思って共通ヘルパーを自作すると、良かれと思ってどちらか一方に「揃えて」しまう。揃えた瞬間、集計が静かに1日ずれる。誰も気づかない。

似た地雷が、タイムゾーンにもありました。ZoneId.systemDefault()38箇所・18ファイル。日付の境界が1日ずれれば、集計も、目標の達成率も、検索結果も、金額も、まとめて狂う。しかもクラッシュしないから、ユーザーは「なんか数字が変だな」としか思わない。

置き換え自体は、機械が7割やってくれます。日時で機械70〜75%、DI移行で約8割、UIで約7割。手を動かす量は、思ったより少ない。

でも、事故は、残りの3割ではなく、「機械にやらせて検証をサボったところ」で起きる。

報告書の結論欄に、こう書いてありました。「最大のコストは、置換ではなく検証」。移行の見積もりを、置換する行数で立ててはいけなかったんです。

おまけ:ログを一行出そうとしたら、アプリが即死した

ここまでは、手を動かす前の「棚卸し」で見つけた地雷でした。ここからは、実際に手を動かして踏んだ、いちばんバカバカしいやつを一つ。

いま、iOS側の土台を実装しています。設定の保存、通知、ポモドーロの音と振動。AVFAudio や CoreHaptics といった、iOSネイティブの仕組みを叩いて、シミュレータ上でテストが通るところまでは来ました。

その途中のことです。「保存した値が、ちゃんと残っているか」を確認したくて、ログを一行、出そうとしました。ObjC の作法どおり、Kotlin/Native のコードから、こう呼びます。

// Kotlin/Native から NSLog を呼ぶ
NSLog("%@", kotlinString)

アプリが、起動した瞬間に落ちました。

画面が出るより前、いちばん最初の初期化のところで、EXC_BAD_ACCESS。値を確認するどころか、そもそも起動しない。しかも、ログを出そうとして落ちているので、落ちた理由を知るためのログも、出ない。 鶏が先か、卵が先か。

犯人は、これでした。Kotlin/Native の String は、ObjC の可変長引数へ自動では変換されない。「文字列は %s じゃなくて %@」——ObjC では鉄則のこの一般則が、Kotlin/Native では通用しない。知らなければ、一生わからないやつです。

結局、判定ログは NSLog をやめて、ファイルに直接書いて事なきを得ました。

ログを、一行、出したいだけだったのに。……こういうのが、いちばん時間を溶かすんですよね。

「勘で始めなくてよかった」

もうひとつ、棚卸しの効能を。

わざと粗探しをする検証は、最初のざっくり調査の数字を、片っ端から訂正していきました。

  • 「影響ファイル49個」→ 実は 12個(テスト込みで数えていた)
  • 「画面遷移33箇所」→ 実は 25箇所
  • ライブラリの目標バージョンも、いくつか古い数字を掴まされていた

もし最初の勘のまま突っ込んでいたら、多めに見積もった不安で怯えるか、少なめの数字で楽観して溺れるか、どっちかでした。コードを一行も書けない私にできることは、たぶん、書くことではなくて、この「棚卸しをAIにやらせて、その数字を疑う」ことのほうなんだと思います。

「作る前に、机の上に全部並べる」。スペック駆動というのは、要するにそういうことでした。

移植は、まだ途中です。iOS側の土台——設定の保存、通知、音と振動——は、シミュレータでテストが通るところまで来ました。自動のテストが緑になる。数字が揃う。それはそれで、うれしい。

でも、本当に胸に来たのは、そこではありませんでした。

先日、長年使っている自分のiPhoneに、そのアプリを入れてみました。Macに繋いで、署名して、放り込む。この「繋いで署名する」ところだけは、いまだにAIにはできません。ケーブルは、人間が挿すしかないので。

見慣れたその画面に、自分の作ったアプリが、映りました。

うれしい、が半分。残りの半分は、妙な責任感でした。私はずっと、アプリを「使う側」の人間だったんです。それが気づけば、「提供する側」に回っている。長年使っていたiPhoneの画面で動いているアプリを眺めながら、そのことの重さを、少しだけ、噛みしめました。

まだ、地雷は残っているでしょう。残り3割の手作業と、その先の検証。ここが本番です。

……もう一度「勘で始めろ」と言われたら、丁重にお断りしますが(笑)。

この記事のアプリについて

この移植をしているのは、ActLog という時間管理アプリです。ポモドーロで測った時間が、そのまま作業記録として残る。案件ごとに集計して、CSV・PDFで出せる。フリーランスの工数管理にも、勉強や運動の記録にも。いまはAndroidで公開していて、この記事のとおり、iOS版を作っている最中です。

「コードを一行も書けないおっさんが、AIと組んでどこまでやれるか」の、実況中継のようなものです。よろしければ、のぞいてやってください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?