今回は、システムのデバッグについて 聞いていけたらなと思うんですけど、
結構デバッグって熟練者と初学者で かなり差が出るところだなと思ってて。
デバッガーとか、などのそういうツール含めて いろんな手法があるのかなと思ってるんですけど、
国明さんが普段やってるデバッグ方法というか、 おすすめのやり方というか技法みたいなのがあったら教えて欲しいなと思ってます。
はい。まずちょっと、私はデバッグについては そこまで熟練者ではないと思っているんですけど、
いくつかこだわりはあるので、 私レベルの話で申し訳ないですけど、
ちょっと話をしていこうかなと思います。
私はおすすめの手法をちょっと話す前に、 これやったら負けだなと思うのがいくつかあって、
先にそれを説明します。
一つはプリントFデバッグ。 もう一つはステップ実行です。
代わりにプリントFデバッグとか ステップ実行しないとしたらどうするのかっていうと、
再現テストを書くっていうことをやるんですね。
実際再現テスト書いても何もわからんってなることは結構あって、 その時ステップ実行することはあるんですけど、
その時はデバッグアビリティの低い設計しちゃったなっていうふうに 反省するっていうふうな感じの流れですね。
なるほど。これは今、 負けというものがいくつかあるの。
一つ目でプリントFデバッグっていうのが、 まず出てきたんですけど、
これギグって僕自身なっちゃったんですけど、 だいたい僕がデバッグをするってなると、
プリントFデバッグというか、 何らかをログに出して確認するみたいなことを絶対にやってるので、
ギグってなりました。多分これ聞いてる人も多いんじゃないかなと思って、
一番多いデバッグ方法がプリントFというか、 ログに出すみたいな方法なのかなと思ってるんですけど、
それはやったら負けっていうような方針でやってるんですね。
そうですね。ただログに出すってこと自体は私は否定はしてなくて、
例えばデバッグとかトレースレベルって言われてる、 一番低いレイヤーでログを出すこと自体はいいことだと思ってます。
ただいっぱいそうなるとすごいアプリケーションが ログを吐くことになるのでパフォーマンスが落ちたりだとか、
あとは大量のもういらなくなったログみたいのも結構あるはずで、
そういうものとかっていうのがお掃除されてないと 見るのつらいみたいなことよくあるんですけど、
でもそれは見るのつらいは最近AIがあるんで だいぶ楽になってきたと思っていて、
なんで私が言ってるプリントFデバッグっていうのは ロガーのことは指してなくて、
その場でプリントFって書いてOKだったら消すみたいな、 それです、ビルドスナッシュで。
テンポラリーなチェックみたいなことですよね。
それは明らかに非効率ですねっていうのがある。
なるほど。ログに仕込んでプロダクション環境にリリースして、 それでちょっとログを確認するみたいな言うことはまでは否定しないけど、
否定しないです。
手元でプリントFだけでやって、ちょっと確認してっていうところだと、 この後の話にも繋がるかなと思うんですけど、
そもそもその設計が良くないよねみたいな、 その確認方法だと。
それは微妙だよねみたいなところに繋がりそうっていうところなんですね。
まず私の心情として、ログを出さなきゃいけない時って どういう時かっていうとバグった時なんですよね、大体。
バグってる時とか、あれなんか自分の想定と違ってるぞ っていう時がほとんどになっていて、
そうなっちゃってるってことは大体テストが足りてないんですよ、そもそも。
だからそもそもテストをしなかったっていう反省がまずあるっていうのが1個あって、
でもそうなっちゃったらしょうがなくて、そして再現テスト書いても何もわからんってなる時が結構あるんで、
その時はステップ実行するんですよね。
でもその時はもう敗北感でこう。
反省をするっていうことですね、自的の面で。
これはそうやっちまったなっていうのがあって、なぜやっちまったかなっていうと、
1つはうまく作ってあるシステムってログが吐かなくてもここまでは動いてるってことが確信できるという状況を作れるんですよね。
例えば関数ABCを順番に叩くみたいなものがあったとした時に、
ABCがちゃんとテストされてて、これで例外とかは特に作ってないんだよなっていう時は大体動くことを確信しているのがあるんですよね。
そういうところってなかなかバグが出ないんですよ。
確かに。
ただバグが出るってのはどういうところかっていうと、
何かアドホックに作っちゃった部分とか、
分かんないっていうか適当にちょっと作ってみたら何か思ったとおりに動かないぞみたいなところがよくあるとか、
テストしきれない量の状態を作っちゃったとか、
そういうところにステップ実行とかしなきゃいけないところが出てくるんで、
それはだいぶ設計の敗北だと思ってるっていう感じですね。
確かに今の話聞いてバグが出そうなところ、
これ多分コードの臭いみたいな話にも通するところあるのかなと思うんですけど、
なんかモヤっとしてるところですよね、今のニュアンスでいうと。
ここの部分なんか意味わかんないけど、
ごちゃごちゃ書いてしまったと。
で、前者の分かりきっている部分、
AとBとCをちゃんとステップ実行して、
で、ちゃんと動いてるよねみたいないうのが確実に分かるような設計になっている場合って、
割と理論整然としているというか、
ちゃんと整理ができてるよね、
で、この部分だったら絶対大丈夫だよねっていう安心感が持てている部分ですよね。
その安心感がない部分が、
ちょっとなんかここはモヤっとしてる、
なんかごちゃっとしててよくわかんないなっていう時に、
設計が良くないよねっていう、
そもそもダメだよねっていうような形になっているという。
そうですね。
これはちょっと私のそんなに経験が多い部分じゃないんで、
ちょっと直感的にはもしかしたら違うっていう方もいるかもしれないんですけど、
私がこれまでバグが出やすかった部分ってのはどういうところかっていうと、
アルゴリズムとドメインの知識が一緒くたになっちゃった部分、
完全一体となっちゃった部分っていうのが結構多いなと思ってて、
アルゴリズムは純粋にアルゴリズムですよね。
どんなところに適応するかっていうのは別として、
こういう手順でこういうふうな条件のものが解けるとかっていうふうなことを教えてくれるわけですけど、
そこにドメインの知識がいっぱい入っちゃって、
で、なんかこうアルゴリズムでは済まなくなっている。
そのなんかドメイン的な例えばここにプッシュ通知を送らなきゃいけないよねとかっていうのが、
アルゴリズムの中に入っちゃってるみたいなっていうのが、
だいたいバグるケースだなっていうのが多いなと思っていて、
それは本来はアルゴリズム部分と、
要するによくテストされたアルゴリズム部分と、
ドメインがそのアルゴリズムの部分をちょっとだけ監視したりとかをして、
イベントを通知する部分とかっていうのをうまく分けてあげることが本来できたはずで、
それができてないとそういうふうな不幸なことが起こるっていう。
なんとなく想像できました。
純粋関数になってないってことですよね。
そうそうそうそう。
外部へ依存というか、
何らかの副作用があるような関数の中に、
アルゴリズムというかそういう計算部分が入ってしまっているので、
本来であれば分離可能なのに分離がされていない、
本当にごちゃごちゃしているような状況ってことですね。
そういうのが多いですね。
例えばすごく分かりやすい例だと並べ替えっていうのはすごく分かりやすくて、
並べ替えってドメインの知識を使うと、
例えば名前で並べ替えたいとか、いろんな何とかで並べ替えたいとか、
これを素朴に実装しちゃうと偉いことになるわけですよね。
だから人類はどうしたかというと相当関数を用意したわけですよ。
これがまさにアルゴリズムが分離された例で、
相当っていうアルゴリズムはどういう順番で並べ替えるかというのは、
外部から知識を与えてあげて、
これが代償比較の関数を与えてあげることで並べ替えをするわけですけど、
ドメインの知識ってのは実は代償比較でしかないってことをうまくくくり出したんですよね。
そうすることで並べ替えっていうのがアルゴリズムの部分と
ドメインの知識の部分にうまく分離することができたっていうのがまさにこの例になっていて、
で、やっぱりうまくいってない部分って大体それができてないんですよね。
アルゴリズムはめちゃくちゃだし、
そしてドメインの知識と混ぜ一体になっちゃってる。
こういうのが本当に辛くて、そしてテストするときもすごく辛いんですよね。
今の例でいうと、並び替えの例でいうと、
アルゴリズム、相当アルゴリズムいろんなバブル相当だったりクイック相当だったりとかいろんなものありますよね。
そのバブル相当だったらバブル相当でいいんですけど、
そのバブル相当のアルゴリズムを書いている部分にデータを操作する処理まで入ってしまってるみたいなイメージですよね。
実際にその中その関数の中で全部データベース、
データベースはあんまないかもしれないですけど、
データベースへの書き込みがなぜか行われていたりだったりとか、
そういったデータの操作まで入ってしまっているみたいなときにごちゃごちゃしがちだけど、
そのデータを操作するという部分はくくり上げて別の関数にしてしまって、
アルゴリズムの部分は純粋なところで切り出すみたいなことになっていれば、
そこまで変なことにはならないよね。
てかテストしやすいよねってことですね。
テストしやすいですね。
どういう風にテストするかっていうと相当関数とかっていうのの、
相当のアルゴリズムの部分のテストはすごく簡単なんですよ。
いっぱいランダムな要素を用意した、配列を用意してあげて、
そこに相当関数を入れてあげるわけじゃないですか。
並べ替えの代償比較関数を入れてあげて、
この代償比較関数がちゃんと矛盾した答えを返さなければ、
矛盾してるって言ってるのは、
例えばAとBがあって、あるときはAがBよりも大きいって言ってるのに、
あるときは逆が大きいって言ったりするのを一つの相当関数の中で、
それを二つやってしまうと、わけわかんなくなっちゃうわけですよね。
並べ替えの代償基準が壊れちゃって、
そうすると当然並べ替えは変な感じになっちゃうわけですよ。
そういうところとかっていうのはないような相当関数を、
もし適当に作れたら、
順に相当前のやつと相当後のやつっていうのを比較してあげて、
まず要素数は絶対同じはずだよねと。
あとはその並べ替え関数を順に前からやっていったら、
全部先頭の方が小さいってなるよね。
っていう風に言えるようなことが作れるので、
そうしてあげるとテストはすごく簡単なんですよ。
ランダムにいっぱい作ってあげて、ガーっていっぱい回してあげるっていう、
プロパティベースのテスティングができるんで、
こうするとほぼこの相当アルゴリズムは間違ってないっていうことがわかるわけですよね。
その上でその代償比較の演算子、
さっき言った代償比較が矛盾してないかとかっていう部分を、
ドメインの方でテストしてあげるってことをやってあげれば、
この両方が組み合わせれば大体動くでしょっていう、
どこまで持っていけるはずなんですよね。
そこまで持っていけてようやく安心できるっていう。
もしこれが混ぜ一体になっちゃうと、
すごい大変なことが起こるんですよ。
何かっていうと、
このプロパティベースのテスティングにドメインの知識が入るんです。
地獄ですね。
しづらいっていうか、
モックが挟まっちゃいますよね。
絶対。
モックとかいろんなのが挟まって地獄みたいになりますね。
やっぱりテストが書きやすいっていうのは、
そういった利点もある。
わかりやすくちゃんとバグらないっていうところの利点があるっていうところなんですけど、
ちょっと話戻って、
一番最初デバッグこれやったらKのところで、
プリントFデバッグとステップ実行をやらないように、
できる限りやらないようにしつつ、
代わりに再現テストを書くっていうところなんですけど、
デバッグするときって何らか変なバグが起きてしまっただったりとか、
ここがどうなってるのかちょっとわからないみたいな状況じゃないですか。
一番最初は。
そのとき再現テストって書けないかなと思うんですよね。
書けますかね。
インプットがわかれば書けるって感じですかね。
インプットがわかってここまでは動いてるっていうのは分解しながらテストしていくんですよね。
だいたいソフトウェアってすごく大きな部品から小さな部品までいっぱいあって、
これの10くらいくらいかな最初はまずは。
10くらいくらいでテストを書いていって、
ここまではあってる。
ここまではあってる。
ここまではあってるっていうのはテストケースを追加していくわけですよ。
インプットを。
そうするとだいたいテストができて、
ここの部品で間違ってたのねってわかったんで、
もっと細かくテスト書いていくっていうふうなことをやって追い詰めていくっていう、
ことをできるのが一番いいやり方ですね。
なるほど、理解できました。
ここで言って再現テストを書くっていう行為の中に、
若干テスト環境でのステップ実行が含まれてますね。
そうです。そういうことなんですよ。
多分そこにはプリンタイフデバッグも入りそうですよね。
テスト実行してログに出すみたいな。
ステップがちゃんと分かれてるんだったら単体の関数としてテストができるはずなんですよ。
だけど渾然一体となって、
この関数の読み出し後じゃないとこの関数は読めないみたいな感じの関数がすごく多かったりすると、
テストがすごい大変になって、大抵サボりますよね、書くの。
そうなっちゃうと一つの大きな塊じゃないとテストできなくなっちゃう。
そうなると結局プリンタイフデバッグみたいなのが必要になってきちゃう。
ステップ実行とか必要になってきちゃう。
だからもっと細かく分解してあげて、
その細かいリードでテストを一個ずつ書けるようになってるってことがすごく重要だってことなんですね。
理解できました。
デバッグするときの環境がテスト環境である。
そこがコードによって再現できる。
再現っていうかシミュレーションできるっていうところが一番重要な環境としてそもそもあるっていうところなんですね。
理解できました。
ステップ実行がどうしても必要になってしまって正当化されるところっていうのは私は2つくらいしかないかなと思っていて。
1つはパフォーマンス上どうしてもそこの部分の設計を崩さざるを得なかったっていうシーンが結構あると思っていて。
パフォーマンスステップはすごく重要ですよね。
ホットスポットの設計で細かく分ければ分けるほど大抵オーバーヘッドが増えてパフォーマンス上不利になるんですよね。
なので一色単にしたりとか2つのことを同時に1つでやって、
パフォーマンスは良いんだけど設計としてはちょっときついんでそういうところとかは残念ながらやっぱりログだったりだとかあるいはステップ実行みたいな必要になっちゃうんですけど、
そういう箇所って多くはないんですよ大抵ホットスポットで。
そういう意外のところはすごく細かくテストができるようにここをテストしたいと思った瞬間にそこにすぐにテストかける状態になってるっていうのが一番理想で。
それをなるべく作っていくことが大事。
プリンターフレーバーでパフォーマンスかけなかったらやっちゃったなっていうのはもう反省みたいな感じですね。
確かにパフォーマンス面は確かにそうですね。
例えば何らかのサービスでホーム画面に絶対みんながアクセスするようなホーム画面にあるような機能で500ミリセックぐらいで絶対に返さないといけないみたいなビジネス要件というか技術的な要件がある時には
そこで引かれるSQLとかORマッパ経由せずにSQL自家書きしないといけないとかそういうのはあると思うんですよね絶対に。
なんだけど他の画面では違うよねってことですよね。
設定画面でSQL自家書きしてんじゃないよとかそういうのはあるってことですよね。
そういうところではちゃんと設計でカバーしつつ本当にもうカリカリにチューニングしないといけないホーム画面みたいなところは
もうそこはしょうがないとそこは割り切ってその代わり分かりやすいようにちゃんとテストをそこは充実しておくみたいなことですよね。
そうですねただそこのテストも充実しすぎると結合テストがめっちゃあるみたいな状況になっちゃうんであんまり良くないんですよね。
難しいですこの塩梅は。
テストがぶっ壊れる可能性がすごく高くなってしまう。
エンドとエンドのテストみたいな感じになってしまう。
テストがすぐに壊れるのは悪いことばっかりではなくて一つは単にそのコード変えづらくなりますよね。
それはすごくいいことなんですよ。変えづらくなるってことはパフォーマンス重要な部分を頻繁に変えられるってすごい怖いことで
そこの部分に行ってそのペナリティがつくことでパフォーマンスが高い部分はパフォーマンスをなかなかキープできるっていう状況になるのはそんなに悪いことじゃないと個人的には思ってるんですよね。
もちろんちゃんと早く変えられるにはこうしたことはないんだけどパフォーマンステストってすげーコストかかるんですよね。
パフォーマンス落ちてないとかっていうテストとかってだからあんまり書き換えてほしくないっていうのがあって
テストがすぐに落ちるからあんまりここ変えたくないんだよなってなること自体は悪くないって思いますね。
いや確かにそうだなと思いました。
あとちなみに今までここまで話してたのテーマは割とよくあるウェブサーバーとかのデバッグだったりとか
あとはすごいちっちゃいツールCLIとかのツールとかでも同じかなと思うんですけど
逆にこれIOSのアプリだったりとかクライアント側のアプリのデバッグとか
あとは今言ってたパフォーマンスの計測みたいなのも若干デバッグ
IOSで言うとメモリのデバッグとかあるじゃないですかデバッグというかXコードに付属の
ああいうのみたいなところで言うと何か変わりあったりします?
結構変わりがあって
でもヘンテコさんはすでにさっき言及してたんでそこに尽きるんですけど
バックエンドのサーバーとかは大体状態が全てデータベースの中に入るものが多い
データベースとかKBSとかそういうものの中に入ってくるんで
アプリケーションサーバーの中自体はその状態を更新したりとかあるいは引いたりとかして
アプリケーションサーバー自体にはほとんどデータが入っていないって感じなんですけど
フロントエンドとか要するにモバイルアプリとかウェブのフロントエンドとかっていうのは別で
バックエンドと通信はするんだけど手元にキャッシュとなっている状態が絶対あるんですよね
その状態の管理っていうのがバックエンドではなかったものになってて
なのでアプリケーション内のデータベース以外のところにどうしても状態が入っちゃうっていうのが
このフロントエンドの特徴だと思ってるんですね
そうなっちゃうとすごく大事になってくるのが状態をどこで管理するかっていう部分なんですね
テストのしやすさで言うと状態がある方がテストがめっちゃ難しい
大抵テストケースが爆発するので状態がある方が難しいってことは
全部が全部状態のあるところで作っちゃうと地獄を見るわけですよ
全部が全部テストケース数が爆発しちゃうってことになる
そうするとテストケース数が少なくなるべくできるように
状態のある場所と状態のない場所を分けて設計するってことがすごく大事になってくるわけですね
実際私がiOSのアプリとか作るときには小さな状態機械
セフトで言うとアクターとかって言われてるものとか作ってあげて
そのアクターの中でその状態が変わるっていうのを作ってあげることが多くて
そこに状態が変わったときにイベントが出るように作ってくるんですよ
そうするとエラー状態になりましたとか状態遷移がありましたっていうのは全部ログに出せるんですね
そうするとすごくわかりやすくなります
実際私それですごく役に立った事例があって
Appleのレビューがなんかお前のところで課金できなかったぞって言われちゃった
審査の時のレビュー
そう審査の時のレビュー
ほうって思って見ていたらログがあるんですよ状態遷移がこういう風になったのって
履歴を辿っていったっていうのが内部のそのやつで
Appleの課金システムってのは当時はストアキットっていうのがあって
今は2ですよね確か
ストアキットがあってそのストアキットが状態変化しながらやってくるんですよ
なんだけどそれをちゃんと自分たちが状態を定義した状態機械と一緒に連動していくんで
今ストアキットの状態はこうなった今こうなった今こうなったっていうログが全部
状態これからこれに変わったっていうログが出てたので
あ速攻で今サンドボックス環境の検証サーバーがバグってるっていうのがすぐわかって
なるほどここかっていう風に一瞬で特定できたんです
もうステップ事故一切なかったっていう例があります
なのでこういう風にその手元のその状態の変化っていうのをログに出しておくってことです
結構いいことで私はそれをすごくお勧めしますね
やっぱり考え方としてクライアントサイドとバックエンドっていうのは全然違うことだよね
違いますね
そうですねクライアント側で保持しているデータベースはまた別で
ブラウザだったらローカルストレージとかかもしれないし
クライアント側だったらiOSアンドロイドだったら昔から使われているSKライトだったりとか
コアデータだったりとかそういったものが別で存在するので
その状態を監視するないしはちゃんと管理しないといけないっていう観点が入ってくる
否定の定義をするからってことなんですよね
今言ってもらったその1個に状態を定義するというか
1つのデータソースで全ての状態を表現できるようにしておくってことですよね今の話って
それとも限らないんですが実際1つのデータソースで管理するっていう考え方は
WebのFluxとかReduxとかっていう考え方で出てきたもので
シングルデータストアにどんどん状態変わっていくって考えた人がいるわけですよね
あれ自体はどういうふうに状態変化していったかって分かりやすいのがあるんですが
実は別にそうしなくてもよくて
いくつかの状態機械さっき言ったアクター
Swiftレベルで言うとアクターとかって呼ばれているレベルのものが複数あって
それぞれが独自にログを入っていても
今全体としてどういう状態にあるってことはログから逆算ができるので
全然それで大丈夫っていうのがありますね
私はどっちかというとシングルデータストアがあんまり好きじゃないんで
そうなんですね
そうなんですよ
じゃあ別に揃える必要性はないんだけども
集約させておくルールとしては必要っていう話ですね
状態遷移するときはこういう状態遷移のパターンで書いてねっていう風にして
それはちゃんとクラッシュリティクスとかに非致命性のエラーとかとして
送信するんだよっていう風にしておくとすごくいいって感じですね
ですよねそれがないと
ただバラバラにグローバル変数を書き換えているだけみたいな
感じになってトレースアビリティがないよねっていう
そうですね
トレースできるように絶対に設計しておいて
かつその時にそのユーザーから例えばお問い合わせあった時に
そのユーザーのステートを確実に見れるようにしておかないといけない
そうです
ってことですよね
いろんなツールあると思うのでその時にしておくと
再現できやすいよねってことですよね
そういうことです
そうバックエンドはデータベースとかKBS見ればわかるじゃないですか
クエリウッデーはすぐわかるわけですよどんな状態になっているか
だけどクライアント側には無数のいろんなクライアント一つにつき一つの状態があって
っていう状態なんでそれは観測しきれないんですよね
そこはログみたいなすごく圧縮された形式でこういう風な繊維をたどって
エラーに至りましたっていうログが残しておかないとわからないので
それは結構残しておく方がいいですね
それがその情報があればデバッグもしなくていいというか
一発で再現テストかけますよねってことですね
その時はこの状態の時にサーバーからエラーレスポンスが返ってきて
エラー状態に陥ったっていうのはすぐわかったので
なるほどなという風になったんですよね
で再現テスト書いてじゃあここの部分は直して再修正みたいな感じで
サーバー側の修正で済んじゃったのでクライアント側は何も修正が必要なかった
シンプルに解決できたんですね
で私はさっきシングルデータストアが好きじゃないって話をしたんですけど
理由が一つあってウェブではシングルデータストアにする意味があったんですよ
なぜかというと一発でレンダリングを完了させたいってケースがあったんですよ
それは何かっていうとグーグルからの検索のインデックスとかとかをやる時に
ボットが尋ねてくるわけですよね
その時に例えばSPAとかで組んじゃうと最初リアクトとかで何も描画されてない
空のHTMLみたいなのが返ってきちゃって
ここからJavaScriptでレンダリングしてきますよとかっていうのがあって
それって一応グーグルボットの数少ない
15回に1回とかだったかな
とかでJavaScriptを解釈するグーグルボットが来てくれるんで
その時にインデックスが乗るんだけど
だけど15分の14回はテキストしか解釈していないからほぼ空のやつが返ってしまう
そんな実装というかそんなボットなんですね
今もどうかは分からないですけど
今もどうかは分からないです
私が当時やってた時はそういうふうなヒントでやってくるんで
なので一発でリアクトとかのHTMLをレンダリングしてほしいってニーズがあったんですよ
だからその時に一つ一つの状態機械が分かれちゃってると
こいつに状態入れてこいつに状態入れてこいつに状態入れて
はい再現みたいなすごい大変だったんですよね
なんだけどシングルデータストアだったら一発で再現できるじゃないですか
このデータストアにこのJSONみたいなやつ入れればバーンっていければ
一発で利用されるみたいな
これができるからFlexとかっていうのはシングルデータストアにしたかった
でもぶっちゃけそういうニーズがなければ
シングルデータストアであるイメージって私はあんまないと思ってるんですよ
別々でいいじゃんって
その方がだって例えばシングルデータストアにした瞬間に複数ページとか
ここだけリダックスでやるとめんどくさいからやりたくないんだけどな
っていうことができなくなっちゃうんですよね
なので私はシングルデータストアは設計を狭めちゃうと思ってるので
必要じゃない限りはシングルデータストアにしてない方がいいと思ってます
なるほどそういう考えがあるっていうことなんですね
そうですねちょっと脱線でしたけど
iOSとかAndroidとかの場合はそういう制約ないですもんね
一発で何かやりたいみたいなところもないし
何だったら早いところから先にUIレンダリングするみたいな考えの方が
もしかしたらあるのかもしれないですよね
そのデータストアが分かれてて早いところからUI描画していって
出してあげた方がユーザー体験としてはいいよねみたいな設計判断もあり
ただ私はちょっとAndroidはあんま詳しくなくて
iOSの方しか分からないんですけど
iOSはLinuxを真似たTCAって言われてる
The Composable Architectureって言われてるやつがあって
あれはそのなぜかシングルデータストアにしちゃってますね
私はディープリンクとかでちょっとあると嬉しい以上に
意味はないかなと思ってるんで
どうしてあれがあんなにありがたがられてるかはよく分からないんですが
なるほど
まあそういうものもありつつも
設計やっぱ思想次第っていうようなところはありますね
そうですね
多分単に真似してみましたに近いなってちょっと思っちゃって
あとはよくTCAとかリラックスとかする人が
口おそろいっていうのはテストしやすいって言うんですよ
状態がデータストアに全部入っちゃってるから
自分たちはテストしやすいじゃんって言うんですけど
私は逆にシングルデータストアはむしろテストしにくいと思っていて
なぜかというと全然関係ない部分っていっぱいあるじゃないですか状態って
今私は
どうぞどうぞ
状態を機構像みたいに彼らが管理するわけですけど
この枝の枝の枝の部分だけをテストしたいって時に
シングルデータストアだと全部巨大なデータストアが1個あって
ここだけをテストするっていうのをセットアップがしづらいんですよね
なるほど
Mockアップする時のMockのデータが
無駄なデータが存在するってことですね
そうですねはい
一応その部分的なこの部分にデータの木に着目させてテストするってことも
できなくはないらしいんですけど
テストのやっぱりできなくはないってあたりに
皆さんが漂っていて
制限されるんですよねテストの書き方に
例えば今回はちょっと親の状態までちょっといじっちゃうから
親も含めたテストしたいんだけどって時に
とたんにフレームワークとリードミーを調べて
こうやっていかないとテストできない
ここでイシューがこんなに立っててここはテストできない
このフレームワークを使わないとみたいな
ギャーってなるんで
だったらねもっと素朴に作っちゃった方がテストしやすいじゃんって
すごい思うというか今思い出したところでありがちなのが
ログインとかログアウトしている状態非ログイン
ログイン済みの状態があるようなシステムの場合
例えばホーム画面でデータを表示したいと
その時にこのデータはログイン中に表示したい
このデータはログインしてない時に表示したいみたいなテストを書くと
書くじゃないですか絶対に
その時にシングルデータソースの場合は
ログインをしているというユーザー構造のデータを入れないといけないんですよね
絶対にMockのところに
それをシングルデータソースじゃない場合は別に
ログインしているという状態にしておけばいいだけ
なのにデータ構造としてユーザーの名前とかを絶対入れないといけない
みたいな感じになってしまうみたいなことですね
それはインターフェース分離原則という有名な設計原則があって
必要ないのにそれを入力させるテストってクソだろっていうのが
すごく端的に言ったインターフェース分離原則で
それに反射してるんですよね
だからシングルデータソースは結構テストしにくい
確かに多くから少なくて
絶対そういう不要なデータまでMockしないといけないというのが絶対発生してしまいますよね
そうなんですよねそこがもどかしいところシングルデータストアだと思ってます
テストしやすいには段階があると思っていて
シングルデータストアってテストしやすさというと中ぐらいだと思うんですよ
一番テストしにくいのはもうカオスなコード状態も入り乱れて
これはもう明らかにテストしにくいねと
それよりかは確かにシングルデータストアの方がテストしやすいんだが
実はもっとテストしやすくできるよっていうところがあると思って
それがもっとちっちゃくデータストアを作っていくっていう
ほうが一番それがテストしやすいっていうのがありますね
納得しかなかったんですけど今の話を聞いて
着目してる観点が若干違うよねっていうところで
シングルデータソースがいいよねっていう時に判断するところって
テストが書きやすいじゃないんですよねきっと
その時に判断してるので
状態を定義しやすいよね
これだったら1個のJSONでいけるよねみたいな
そのぐらいなんですよね
でもその先に踏み込んでないんですよね
そこで思考停止して
だったらそこから分割してデータを区切った方がいいよねっていうところですね
そうですね
確かにと思いました
最後にこれ聞いてみたいなところがあるんですけど
何か問題がありましたと作ったものに対して
その時に結構いいやり方だなと個人的に思ってるのが
こういう感じのことが起こってるかなみたいな仮説を立てるみたいな
仮説立てた方がいいみたいなのはよくある話じゃないですか
普通に別の話でも
そういう仮説の立て方みたいないうのであったりしますかね
仮説の立て方についてそれが実は私は苦手としている部分で
私は熟練者じゃないと思ってるのはそこなんですけど
上手な人って今こういう結果になってるってことは
中間がこうなっていたはずだみたいな仮説の立て方がすごく上手いんですよね
そこの断面を切るのがすごく上手で
そこの部分に計測プローブを入れてあげる
例えばログを入れたりとかテストを入れてあげると
ここでしくったなってわかるっていうのは
すごく上手にやるなと思っていて
ただ私がそこがあんまり得意じゃないっていうのは
理由あると思ってるんですよ
それは私がその能力あまり必要としてこなかったから
っていうのは結構あるなと思っていて
さっき言った設計でちゃんとテストしやすいとか
あるいはどんな風に何が起こってたのかが分かりやすい
っていうふうなことが実現できてると
あんまり高度な仮説を立てなくても分かるんですよね
ここまで間違ってたなっていうのは
だから問題はここの中にあるはずだな
こいつだみたいなことになるんで
何も分からんみたいなことがなかなかなくて
なんで仮説を立てなくてよかった
幸運にもそうだったっていうのはあると思ってます
ただ例えばOSの開発者とか
パフォーマンスが重要なところとかを書いてる人とかっていうのは
デバッグできるポイントも少ないし
プリントFデバッグみたいなことをどこに書くんだって話がありますよね
っていうのもあるし
パフォーマンスがやっぱり重要だと
どうしてもコンポーネントをくっつけて
一緒くたにやるってことがあるんで
そうなると仮説がやっぱり重要になってくる
私はそれがあまり求められる職場にいなかったので
あんまり発達してこなかったっていうところはありますね
確かにリラックスカーネルを開発する
みたいな現場において
どうやってデバッグするんだよみたいな
そもそもデバッグ環境というかテスト環境を
構築するのも結構難しいかなと思いつつ
そういう環境の中だったら
手探りの中で仮説を立てないといけない
経験が物を言うみたいな
ところが結構ありがちかなと思うんですけど
情報がかなりあると
その中においてだったら
特段仮説
すごい仮説みたいな
ものは立てる必要性もなかったというような
そうですね
大抵はここからここまでのどっかが間違ってるっていうのが
大抵そこは分かるわけですよね
トップレベルのどっかから分かる
それを二分探索みたいに
じゃあここまでの関数は合ってるか
みたいな部分をやるわけですよ
最初のニューロコンポーネントで試してみて
そうするとここからは大丈夫だみたいな
二分探索みたいにできちゃうんですよね
そうすると思考停止で原因分かっちゃうんですよ結構
それがやっぱり私は理想
どっちかと言えばそれが理想だと思っていて
ただ理想ができないのがさっき言ったように
デバッグで使えるツールが乏しいとか
あとはパフォーマンスで設計がそういう風にはできないとか
っていうケースを除けばそうなっていることが理想だと思っている
あとちなみにとはいえ絶対仮説立ててるでしょうと思う
ところが一個あるんですけど
それが障害が起きたとき
なんか今言ったトップレベルは分かってる
っていう話だったんですけど
トップレベルが分かってないとき
絶対何かしら切り分けないといけない
ここの部分はこうだから
なんかログを漁ってみようみたいな言うとき
絶対仮説立ててるかなと思うんですけど
仮説と言うか何らか考えてるなとは思うんですけど
それはですね私にとって仮説ではなくて
切り分けって言い方は結構知ってる
切り分けなんですね
切り分けですね
どのレイヤーまで合ってるかってことを確かめるっていうのがあって
例えば私のすごく印象深かったバグのうちの一つに
書いてあるはずのコードが一行消えるっていうバグがあって
意味わかんないですけど
意味わかんないですよね
でなんか普通に確かステージング環境とかでは動いて
じゃあリリースしようと思ってリリースしたら
なんかエラーがめっちゃ出てますなんだこれって言ったら
あれなんか書いたはずの一行が消えてるぞって
なんならリリースされたときの
その差分から一行丸ごと消えてるぞみたいなのがあって
あの時はそう切り分けだったんですよ
うんうんうんうん
確かに
まず配信されてるJavaScriptはおかしい
ここがおかしいですね
で配信前のJavaScriptステージング環境とかのやつは合ってる
だからここの間に何かがあったんですよ
なるほど
ミニファイとかで削られてる
そうそうミニファイとかで削られた
実際そのミニファイで削られてるんですけど
その理由が結構酷くて
当時いじってたシステムっていうのが
JavaScriptの文字エンコーディングが複数だったんですよ
UTF-8で書き始めたとかって
それまではEUCJPとかで書かれてて
最悪
それがJavaScriptってくっつけてミニファイするんですよね
文字コードが複数あるようの
単純な文字列結合でくっつけてやってしまった
なるほど
そうして何が起こったかっていうと
LINEコメントの末尾の開業が文字分けしてですね
後ろの一義を送っちゃったコメントとして解釈されちゃって
一義を切り切ったんです
面白い話なんですけど
追い詰めるためにはさっき言った通りここまでは合ってる
ここから間違ってるっていうその切り分けで
どこなんだどこなんだって
最終的には私が配信されてるJavaScriptとかの
バイネルエディターで確かにこれは文字分けするって分かって
っていう風に切るわけだけど
その時はミニファイされていくまでに作られていく中間成果物があったんで
その中間成果物のどこでその一義が消えたのかっていう
2分の1探索をしたんですよ
そしたらここの文字列結合の部分の後の
LINEコメントのストリップの部分で消えたっていうのが分かったんで
じゃあなんでだって
これ文字分けだったんだってそこまで追い詰める問題が分かる
仮説じゃなくて切り分けですね
じゃあアプローチの仕方なのかもしれないですね
今の例だと僕の場合だったらもしかしたらその現場にいたら
一番最初もしかして仮説でミニファイの時に削られてる
みたいな言うのは仮説層ですよねなんとなく
なんですけど切り分けとしてじゃあどこまではステージング環境までは
ちゃんと配信されているプロダクションでは配信されてなさそう
じゃあそこからステージングの一歩先はどうなってるんだっけ
何してるんだっけみたいなアプローチの仕方は切り分けですよね
アプローチの仕方ですよね結果同じことはしている
そうです結果的に同じことをしているんですね
確かにと思いました
さっきのはビルドプロセスのどこでっていう切り分けの仕方しましたけど
他にも例えば通信レイヤーのどこでみたいのもよくあって
例えばどうしてもここが404とか全く通信が通らない時があった時に
どこまでは通ってるんだってことを確認したくなりますよね
例えばどのレイヤーまでHDTVレイヤーなのか
それともTCPレイヤーなのかっていうところを探っていくっていうのがあって
それって結構いろんなコマンドを叩いていくと分かってたりするんで
そこでこのレイヤーまでは合ってる
ここのレイヤーから間違ってるっていうのが分かる
っていうと犯人がすごく絞られてきたここかなっていう風に
すごく仮説に立てる能力が弱くても
ここだったら仮説はこれぐらいしかないだろうみたいなところまで絞り込めるんで
そうしてあげるとあんまりすごい高度な仮説を考える能力がなくても
うまくいっちゃうところがあるんだと思います
分かりますね
やっぱり多分切り分けと仮説
本当に小さい仮説を立てていくみたいなアプローチの方法なんだろうな
というのを若干聞いてて思いましたね
今の話で言うともしかしたらCDNのところに何か問題があったりだったりとか
あとは中国からのアクセスでそもそもネットワーク的に弾かれていたとかね
そういうこともあり得るわけで
そういう切り分けの仕方をどんどんしていくってことを実際に検証しながら
仮説を立てなきゃいけないシーンもあると思うんですよ
それは失敗できる環境を手元に用意できなかった時です
なるほど
今言った例えば中国のやつとかってのはまさにその例で
手元で失敗しないんですよ
だから仮説を確かめようがないんですよね
だけどそういう時はもう当てずっぽうでもいいから仮説を立てて
このログを送ってくださいみたいなことをその仮説に基づいて
この仮説だったらここで分かるはずだみたいなのとか
リモートでデバッグするみたいなことしかできなくなるんで
それはもう仮説を立てざるを得ない
なるほど
じゃあ検証環境を用意できるかいないかですね
そこはすごく大きいですね
検証環境が用意できるのであれば別に仮説を立てずに
ロジックで詰めていく
切り分けていくことが絶対できるはずってことですね
ただ中国の例えば中国リージョンのAWS
インスタンス立てるのとかも結構大変ですよね
誰かアリババとかの契約しないといけないんですけど別途
そういうネットワーク地理的なところからのやつは
検証環境がそもそもできないから仮説を立てて
これもしかして中国からアクセスしてないみたいな仮説を立てて
ちょっとネットワーク切り替えてくださいだったりとかね
そういうことをお願いする人生があるってことですね
いや確かにその通りです
結構今今回話聞いてやっぱ設計に陥るところ
重要なのは設計だしそもそも検証環境
多分再現テストを書くテスト環境も検証環境の一種だと思うんですよね
やっぱり再現ができるように検証環境を用意できる限りして
その中で問題点は切り分けつつ検証していって
それが分かったらちゃんとテストを書いて保証していく
しそもそもそんなことはしないように設計的にいいようにしていきましょう
ということですねまとめとしても
かなりいい話聞けたかなと思うんですけど
結構いい時間になってきたので今回はここまでにしたいなと思います
もっと詳しく教えてくださいラジオ略してくわラジでは
スーパーエンジニアである国明さんに
いっぱいエンジニアであるへんとかが技術的な質問していく番組になってます
今後もいろんなこと聞いてきたらなと思ってますので
お聞きのプラットフォームでチャンネル登録や高評価やフォローの方お願いします
またこんなことを聞いてほしいみたいなことありましたら
コメントをくださいって聞きますのでよろしくお願いします
テックスなどではハッシュタグくわラジでつぶやいてください
感想などもぜひお待ちしております
それでは今回もありがとうございました
ありがとうございました