テストステ論

京大卒の高テス協会会長がテストステロンに関する情報をお届けします

【React Native】etilesというゲームアプリをScala.jsからTypeScriptで書き直した理由【TypeScript】

etiles(イータイルズ)というのは、おれが作ってるパズルゲームで、自札のNxNタイルを裏返したり入れ替えたりして、答えにマッチさせるというゲームです。今はTypeScriptとReact-Nativeで書かれています。

インストールお願いします。

https://play.google.com/store/apps/details?id=jp.gr.java_conf.etiles.android

今回は、このアプリをTypeScriptで書き直した話をします。

etiles akira$ tokei src/
--------------------------------------------------------------------------------
 Language             Files        Lines         Code     Comments       Blanks
--------------------------------------------------------------------------------
 TypeScript              23         1685         1589           26           70
--------------------------------------------------------------------------------
 Total                   23         1685         1589           26           70
--------------------------------------------------------------------------------

なぜScala.js + PlayFabだったか

おれはもともとこれをScala.jsをAltJSとして採用して、ゲームバックエンドとしてはPlayFabというRESTで会話出来るゲームサービスを使って作りました。これをバージョン1とすると、バージョン1の最終更新は去年の8月くらい。

なぜPlayFabを使ったか

まず、なぜPlayFabを使ったかというと、AndroidiOS向けにアプリをリリースしたかったから。Google Play Game ServicesはもともとiOS向けにもサポートしていたようだけど、何年か前にそれを打ち切った。

ここが国民のほとんどがAndroidを使ってるような国だったら良かったと思うんだけど、残念ながら日本はiOSの比率が世界でもっとも高い国で、てっとり早くユーザを集めるためにはiOSを切るという発想は出来なかった。実際にiOSユーザから、iOS版が出るならやるという声ももらっていた。また、当初は学校などでAndroidiOSスマホを持ち寄って近距離通信対戦することなども考えていたから、iOSをサポートしなければならなかった。

そういうサービスはいくつかあるんだけど、もっともまともそうなものがPlayFabで、マイクロソフトに買収もされていたから今後も続くと読んだ。

なぜをScala.jsを使ったか

おれが当時働いていた会社では、Scala.jsを使ってReact-Nativeを書いていた。バックエンドはAkkaで書かれており、Scala.jsを使っていたのは、単純に表現力が豊かというのもあるし、Scalaのスキルセットがあればアプリも出来るようにしたかったこと、アプリとサーバでいくつかの部品を共有したかったということが理由だそうだ。単にScalaが好きというのもあるかもしれない。

おれが入った時にはすでにある程度のコードはあって、おれに選択権はなかったけど、おれはReactもReact-Nativeも書いたことはなかったし、JavaScriptすらも書いたことがなかったが、Scalaは読み書き出来たので、なんとなくコードを読んで学んでいくことが出来た。そういう意味では、Scalaが書ければアプリが書けるようにするという目論見は正しかったのだと思う。

ちなみに、Scala.jsとJavaScriptAPIをつなぎ合わせるためにはファサードという定義ファイルのようなものが必要になる。React-Native向けの大量のファサードScalaらしいAPIで提供しているsriというのがあり、これを使っていた。実際には、sriは破壊的なバージョンアップを一度行っていて、会社で使っていたのは古い方だった。

GitHub - scalajs-react-interface/sri

おれが自分のアプリでも同じツールを使おうと思ったのは、同じツールを使うことによって仕事で使うスキルの向上も出来るし、新しいsriを先取りして知っておきたいと思ったからでもある。

PlayFabをやめた理由

はじめに、PlayFabをやめた理由を話す。これは大きくPlayFabがあまり良いものではなかったからというのと、iOSを見限ったからというのが理由だ。

PlayFabがだめだった

PlayFabはGoogle Game Servicesのように、すでにあるユーザ情報を使って快適にログインを行う方法がない。

ベスト・プラクティスとして言われているのは、まず新規ユーザは名前なしでユニークなIDを割り振った状態で自動的に作成し、あとでアプリ内のユーザ管理画面から新規ユーザに名前やパスワードを入れてもらうというものだ。このユニークなIDはAsyncStorageなどでデバイスに保存しておいて、次回の自動ログインに使う。

これが意外にめんどうだった。まず、AndroidiOSでデバイスIDの仕様も違う。従っておれはUUIDを自分で生成していた。など、きちんとやろうとすると結構コード量が多くなった。

リーダーボードも、リーダーボードの情報をとってくるAPIしかないため画面は自前で作る必要があったり、順序がBigger is Betterしか存在しないため、タイムが小さい方が良いというイータイルズでは、固定の最大値を決めて反転させるための工夫も必要だった。

Google Game Servicesを使っていれば全部任せることが出来てコードは少なくなるのにと思っていた。

アイコンやユーザ名を自由に決められてしまうのも良くなかった。荒らしのような名前がリーダーボードを汚していたし、エロ画像のようなアイコンを使ってる人もいた。これはアプリがBANされるリスクを負うことになるし、そういうリスクを避けるためにも、ユーザ管理をグーグルに完全に任せてしまう方が得策だと思っていた。

iOSを見限った

バージョン1で、イータイルズに熱中する人が存在するということはわかった。何かしらの魅力があり、一定の人は猛烈にプレイするということがわかった。これは世界共通だと思ったし、日本に限る必要がないと思った。だから今後はローカライズをちゃんとして、Android比率の高い韓国や中国でヒットさせることを狙っていく。

あと、Apple Developer Programに年会費を払うのが嫌だし、次にマックブックを買うのも嫌だ。このマシンは30万もしたわりには本当にふつうで何の特徴もないからだ。

iOSのライブラリの管理法も嫌いだった。Podもそうだし、色々と良いツールはあるようだけど、使わないことを前提にすると、Androidの方がデバイス固有部分への変更が少ないからReact-Nativeのバージョンを今後も上げていきやすいというのはいえる。何より、XCodeとかいうしょうもないIDEをビルドのためだけに入れなきゃいけないというのが気に食わなかった。Androidならば全部コマンドで済む。こちらの方がおれには合ってる。

日本でのシェアも少しずつ下降傾向にあるし、2年割などに規制が入ってきな臭い感じもしてきたので、少しずつSIMフリースマホに移行する人が増えてくるのではないかと期待している。iPhoneというプロダクト自体、変わり映えのしない、クールとは言えないものに成り下がってきたし、次第に人も離れていくでしょう。

あとは、ベゼルの形状など、iPhoneは特殊なものが多く、対応させるために余計なコードが必要になった。こういうのも嫌だし、AppleStoreに提出する時の作業の多さ、承認までの期間の長さも嫌だった。

iPhoneを買うようなユーザが、イータイルズのような3D要素もないゲームを遊んでくれるかというのも疑問だった。

Scala.jsをやめた理由

sriのアップデートが遅くて、最新のReact-Nativeに取り残されていった

sriは小さなコミュニティによってメンテナンスされているライブラリで、お世辞にも更新速度が早いとは言えなかった。Scala.jsのバージョンが上がった時や、React-Nativeのバージョンが上がった時にどんどんアップデートをしなければいけないし、大掛かりな再編もチャットでは議論されているが、それが実行される兆しはない。それに、されてしまったらアプリ側は修正を余儀なくされる。

結果として、新しいReact-Nativeについていくことが難しくなったり、そもそもライブラリ自体に振り回される運命にあるなら、Scala.jsを使うメリットを上回ったデメリットがあると思った。React-Nativeのバージョンを上げられないことで、react-native-firebaseなど一部のライブラリのバージョン要求を満たすことが出来なくなっていて、ほぼディスコンのライブラリを使っている部分もあった。

GoogleストアがAPIレベル26を要求した時、これに対応したのはおれが辞めたあとだったが、React-Nativeのバージョンを上げるために大変な苦労があったかと思う。

流行りのTypeScriptを書いてみたかった

おれが会社にいた時にも、アプリのコードをScala.jsで書くのはやりすぎなのではないかという疑問がエンジニアの中にはあった。ふつうにJavaScriptとflowで良いのではと思うことが多かったからだ。

Scala.jsで書くと辛いのは、自分でファサードを書かないといけない場合が多いというのもそうだが、コードの挙動がおかしくて生成されたJavaScriptを読まないといけない時に、当時のコードでも10万行以上のコードが生成されていたから、その機械的に生成されたコードを読んでバグを見つけるのはそれなりにきつかった。

それに実際にReact-Nativeのコードを書いてみると、Scalaで書けることのメリットが少ないことが多い。サーバーサイドをScalaで書くのはとてもメリットがあったが、モバイルの方をScalaで書くのはそれに比べるとメリットが少なかったと感じた。

今回、TypeScriptを選んだのは、VSCodeでも使われている流行りの言語だということや、sriとは違ってtypesの更新は非常に早いこと。実際にReact-Nativeも最新の0.59.5を使っている。また、Scala.jsと違いJavaScriptとほぼ1対1のコードを生成するため、バグ修正も簡単だった。JavaScriptとの乖離が少ないため、TypeScript定義ファイル自体も、ライブラリデザインの好みが介入する余地がない。

TypeScriptを使ってみてどうだったか

正直、JavaScriptをハードに書いたことがないのでそれと比べて良いか悪いかということは論じることが出来ないが、Scala.jsと比べた場合、確かにモダンなプログラミング(パターンマッチなど)は存在しないため、「こう書きたいけど書けない」という場合はあり、満足が行かないこともあったが、総合的に見るとそのような箇所は多くはなく、また、実際にハードにバグることはなかったのでキレるほどではなかった。

仮に定義ファイルがない場合には定義ファイルを自分で書くという方針もあるが、型をanyとみなして生のJavaScriptを書くという方針もありえる。実際に、react-native-firebaseを使う際には定義ファイルがない部分があったため、この抜け道を使ったが、ライブラリを使う部分というのは大抵は型の保護などいらずサンプル通りに書くだけという場合が多いから、大して問題には感じなかった。敢えていうと補完が効かなくなるが、これもトータルで考えると些末だと思った。

総じていうと、好きでもないが嫌いでもない。今後のメンテを考えると、今回の書き直しは正しい判断だったと思う。