タオルケット体操

サツバツいんたーねっとでゲームとかガジェットのレビューとかをします

Flutter所感

Sponsored link

諸事情によりしばらくFlutterでアプリ作って感じたことをいくつか。

良いところ

1. ちゃんと動く

みなさんも今までに出ては消えていくiOS, Android両方で動くアプリ作れるよ系ソリューションで色々なお気持ちを発生させてきたかとおもいますが、Flutterの出来の良さはピカイチ感があります。Flutter Engineすごーい!

大抵のアプリが必要とするような機能(当然全てではない。例えばパスワード管理との連携とかは存在しない)であれば、各プラットフォームネイティブに手を入れることなくちゃんと動く。自前レンダリングと聞いて心配していたパフォーマンスも普通に悪くない。なんて素晴らしいんでしょう。
Flutterの良さはそこに尽きるとおもいます。

2. すぐ動く

いろいろな意味で。

まずコンパイルがそこそこ早いです。
そしてSDKが用意していくれているWidgetの種類がかなり豊富で便利なのでちょっとした画面なら本当に簡単に作れてしまいます。
IDEが自動生成するXML(そしてgitを使うとコンフリクトがやばい)に苦しめられることもありません。

3. 周辺ツールが良くできている

最初の導入も、わざと公式と違う依存性の少ない手順で入れた*1のですがそこまでハマることなくセットアップできました。

僕はVSCodeでコーディングしていますが、プラグインの出来が素晴らしいです。
エミュレータの作成、デバッグ、そしてテスト連携とIDEに求められる機能は全て揃っています。動作も軽快です。

もちろん、基本的にこのへんの機能は公開されておりコマンドラインからも同等のことが可能です。VimやEmacsで開発したくなったらプラグインを作るのは簡単だとおもいます。

またHot reloadやInspectorを備えたDev Toolなどの機能が続々と追加されており、まるでブラウザで開発してるかのような快適DXが得られます。

XCodeが酷すぎるのを差し引いても注目に値する完成度だとおもいます。

4. フロントエンドの知識が流用できる

Reactにインスパイアされているので、現代フロントエンドの知識があれば学習コストは限りなく低いです。
Steamというクラスを軸にしたBlocというアーキテクチャで組んでいくのが主流らしいですが、Observableとfluxがちゃんと理解できている普通のフロントエンドパーソンなら問題なく吸収できるはずです。
ちなみにBLoCはクソです。

実際、いま僕はFlutterを学習し始めて数週間ほどですが、書き方や動かし方については初日から問題なく理解できました。

5. フレームワークの押し付けがない

「Blocで書いた方がいいよ〜〜」くらいのことは言われてますが、基本的にはユーザーの自由です。選択の自由があるのはいいことです。
というかBLoCはあからさまに欠陥品なのでこんなもんを強制されたらたまったもんじゃありません。

イマイチなところ

1. モバイルの知識があまり活用できないかもしれない

これはフロントエンドの知識で開発できるという部分と表裏ですね。
最近はSwift界隈でもReduxをフォローしたり、宣言的UIをサポートしたりという流れがあるようですけども、やはりFlutterのやり方は今までモバイルを中心に開発していたプログラマーだと学習コストがかかってしまうかもしれません。

もちろんFlutterがカバーしていない部分を自分で作ったりだとか、リリースや審査関係……みたいな領域に関しては活用できまくりですね。

2. デザインのコントロールが難しい

これもすぐ作れる部分との裏表ですね。
Flutterが標準で用意してくれるWidgetはマテリアルデザインにのっとっています。その辺のコントロールは大変ですが……
しかしデザインを完璧にコントロールしたい人はそもそもネイティブ以外の選択肢をとっちゃいけないですね。

3. ドキュメントの量と質が飛び抜けているわけではない

Googleという企業が推進しているプロダクトなので、公式ドキュメントはしっかりとしています。
しかしそこから一歩踏み込んだ内容となると……
特に(これはフロントエンドとかもそうで、別にFlutterに限った話ではないですが)日本語の情報は絶望的です。例外はmono氏のブログ記事くらいでしょうか https://medium.com/@mono0926
とはいえ英語の情報量も十分ではなく……あとベストプラクティスとかを探していると高確率でYouTubeにあるカンファレンスの録画のリンクだけあったりします。っらぃょ……

言語問わず、世の中にあるBlocの実装例なども、せいぜいがTODOアプリ作ってみました程度のものなのでちょっと複雑な状態遷移を管理をしようとすると破綻する感じです。
そこをどうやっつけてReal Worldに適用していくか……というのを自分で考えていける人じゃないと規模の大きいアプリを作るのは難しいかなという印象(これはFlutter以外もそうかもしれないけど)。

というかモバイル界隈にもFlux/Reduxの流れがきていた中で、なぜあえてBlocという根本はほぼ同じ構造をもった違う名前のパターンを提唱する必要があったのか。
一応、SingleStateとしてアプリケーションドメインを反映させることが推奨されているRedux(守ってる人ほとんどみないけどね)と、「Complex Enough」Widgetがあれば状態を逃せ、みたいなアプローチのBlocで思想の違いがあるようだけど、なぜそうなのかを説明してくれている文章をみつけられていない。

4. CD*2がイケてない

Reactを書いてる人でここを否定する人はいないとおもうんですが、ぶっちゃけていってしまうとFlutterのWidgetは劣化Reactです。細かい不満は色々とありますが、特にいけてないのがReactのやり方を踏襲しながら思想は受け継いでいないとか、負を再生産してたりとかその辺ですかね。
みなさんクラス継承を強いる形式のフレームワークで辛いおもいをしたことがあるかとおもいます。Reactもまだクラスベースを引きずっていた時代に、継承ベースで実装の再利用をしたがる人々の説得に死ぬほど苦労した思い出がありますが、UIでそれを行った場合の負は筆舌に尽くしがたいものがあります。なのになぜ……
あとStatefullWidgetを構築するときに、StatefullWidgetが状態をもってStateがviewをbuildするキモさとか、setStateが実質ただのforceUpdateだったりとか。

あとimmutabilityに関する言及が少ない(僕が見落としている可能性はあり)のも気になります。予備知識のない初学者は確実に地雷を踏みます。断言してもいいです。Reactほど明快に説明してくれているライブラリでさえ半分以上のプログラマーはpropsやstateを直接いじろうとしますからね。

5. Dart

フォローをいれます。
ビルドの速さ、HotUIなどのDXを支える機能などにDartの動的な性質が貢献しているのは間違い無いとおもいます。まだバックエンドの部分についても知識はありませんが、FlutterのUIがわりとキビキビ動くあたりパフォーマンスも悪くない言語なのだとおもいます(とおもったけどFlutterの描画の中核を担うSkiaはC++で実装されてるからDartがパフォーマンスに貢献してる部分がどの程度なのか不明かもしれぬ)。

それをわかった上でなお言いたいのですが、Dartは最悪です。Objective-Cのほうがまだ書いていて楽しいです*3
Dartチームは何かとJavaScriptを批判していて(そこがまたカンにさわる)のですが、それで出てきたのがJavaもどきの砂糖がけというのがなんとも。別に古臭い構文が好きなら構わないし、Javaに似てればマジョリティの学習コストが減るというのはそうかもしれませんねという感じですが、その割に使い勝手の微妙な構文が多いです。Goのような強い"気"を感じません。またDartも2になって様々な"歴史的経緯"を抱えており、致命的な欠陥があり言語設計において指標にすべきではないとか散々馬鹿にしてきたJavaScriptと同じように初学者にとっては非直感的な仕様やプラクティスが多いように感じます。

型についても、モバイルネイティブやフロントエンドで主流になっているSwift, KotlinそしてTypeScriptなどのシステムと比べて明らかに劣っています。というか10年前のC#と比較しても普通にイマイチだとおもいます。

まず変数がnullを許容しています。入り込んだnullが原因でアプリをクラッシュするのを久しぶりに体験したので新鮮な気持ちになりました。

そしてコードの信頼性について、現代であればコンパイラが行うべきことをlinterに任せている部分が多いです。その最たるものは @required ですね。
Dartの名前付き引数はデフォルトでoptionalなので必須なものは @required を付与しなければならないのですが、この@はPythonのdecoratorではなくただのアノテーションのための記法で、このrequiredも単に Linterのためにメタデータを提供しているだけ のようです。
Linterの設定をしていないプロジェクトや、設定していてもちゃんと運用していないプロジェクトでは @required を付与して安心していたら、使う側で渡し忘れてクラッシュ……という事故が起きうる*4わけです。
ちなみにFlutterの公式リポジトリの設定ではrequiredへの渡し忘れは "warning" です。なので管理体制やテストがしっかりしてれば問題ないのかもしれません(皮肉ですよ!念の為)。 https://github.com/flutter/flutter/blob/master/analysis_options.yaml#L27

またDartはoverloadを許容していません。
これは別に悪くないです。overloadはトレードオフが存在する機能なので、あれば良いと言うものではありません。TypeScriptだって、JavaScriptとの相互運用性という課題さえなければoverloadがないほうが使いやすくなると僕はおもっているくらいです。ただ、明らかに仕様をJavaに寄せていくことを軸にしているDartがoverloadをサポートしないということに関しては意図が見えにくいです。そして、どうもDart(Flutter)関係のモジュールはこのoverloadの欠如に起因する品質の低さみたいなものが散見されます。
まずDartには代数的データ型的なものや、TypeScriptでいうUnion Types( string | number と書くと両方の型を許容する型を作れる)をサポートしていません*5。なので様々な型がありえる引数をとりたい場合は dynamic で宣言する必要があります。
Dartで書かれたコードはこれをabuseして、dynamicで宣言した型を内部で型検査してifで分岐させるなど、手癖の悪いスタイルが目に付き、クラッシュしたときの挙動を追うのに苦労することがまれによくあります。最悪の例としては、Genericsの型引数自体をifの分岐にかけて挙動を変えるコード*6なんかもあります。
overloadを許容せずにベーシックな型サポートしかない言語でコーディングするなら、綺麗なOOPをするか、それとも引数のタイプごとに新しいメソッドを生やしていくかの二択しかないはずです。そこをサボってすぐにdynamicを使いたがるDartライブラリが多すぎます。

どうやら元祖正しい漸進的型付けというのはランタイムの型検査を含めたものをいうらしく、その点でDartは正しく漸進的型付け*7っぽいです。
問題はここの挙動が微妙な点。

*8例えば Map<String, String> を期待している関数に間違って Map<String, dynamic> で宣言されている値を渡すと _TypeError (type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, String>') っていう型エラーがランタイムに出てきます。これは、かりに Map<String, dynamic> で宣言している変数の中身が Map<String, String> であっても発生します。
そもそも発生している事態がトリッキーなのであまり理解が追いついていないのですが、この手の直感に反するランタイムの型エラー(コンパイルは問題なく通る)は、特にJSONを扱う部分で発生しがちです。
僕はめんどくさがりなので、コンパイル時にあれこれと検査してくれてまともにコーディングしてる限りはケアレスミスが発生しにくい静的型付けの言語の方が基本的には好きなんですが、ちゃんと「コンパイルを説得して通したコードから型に関するエラーがランタイムに発生する」のは本当にストレスです。これはSiriが微妙にアホでイライラする現象と似ているとおもいます。

とにかく欠点をあげればキリがないくらい僕はこの言語が嫌いです。あまりに前時代的です。
Flutter開発の最大の欠点はDartの存在そのものです。生JavaScriptで書いた方がマシです。

6. Google

フロントエンドの人間であればGoogleの技術に良い印象を抱く人は少ないはずです。AngularやWebComponentsのpolyfill周りでのヤンチャが直撃した人は尚更でしょう。

まとめ

2020年現在

  • そこまで攻めたところのないモバイルアプリを
  • 両プラットフォームを対象
  • サクッと開発したい

のであればFlutterは超おすすめです。だけど導入は自己責任な!
バックエンドのエンジンは非常によくできているとおもいます。コミュニティの盛り上がり方やGoogleの力の入れようをみても、今までのいくつかのプロジェクトのようにポイっと捨てたりする可能性は少ないとおもいます。

Dartに色々と文句をつけましたが、ぶっちゃけプロダクト開発で大事なのは言語よりプラットフォームと周辺ツールの安定性です。その上でちゃんとした言語が乗っていたらまぁそれは素晴らしいことなんでしょうが、長年ジャバScriptでコーディングしてきた私は精神が訓練されているので大丈夫です。

ちなみにflutter-webは絶対に流行らないで欲しい。
フロントエンドはいまようやくTypeScriptとReactっていう十分満足がいく組み合わせで安定を得たんだよ!

今気がついたけどreact-flutterみたいな感じでskiaのレンダリングをできるブリッジがあれば完璧なのでは?

以上です。

*1:どうせCIの設定とかしてるとこの辺でハマるし、僕は最初に片付けておきたい派

*2:Coding Experience。いま僕が作った用語です

*3:というかObjective-C、嫌いじゃない

*4:ここまで読んで「Linterの設定をしないで開発するとかありえないでしょ」とおもった貴方。いままで上位10%くらいの幸せな職場でだけ働いてきたんですね。皮肉抜きで羨ましいです。たいていのプログラマーは強制されない限りlinterの設定はしません

*5:サポートしていないこと自体は別に良いです

*6:DIO、テメーのことだからなッ!!

*7:2.0以降は静的と名乗っているんだっけ?

*8:検証や理解が不十分なので何か根本的な勘違いをしていたら教えて欲しい