私は、プログラミングの経験がありません。一行も書けません。
そんな人間が、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.time を kotlinx-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ファイルで使っています。テストの背骨です。
この mockk、Kotlin/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と組んでどこまでやれるか」の、実況中継のようなものです。よろしければ、のぞいてやってください。