「初回の検索がいつも固まる」の真因 — 現在地の取得は、失敗すらしないことがある — 制作日誌 #19
経路アプリで、初回の検索だけがいつまでも終わらない。エラーも出ない。原因は現在地の取得が成功も失敗もせず永遠に待ち続ける状態で、しかも自分は最初、無関係な自分の配信を疑って誤った報告をした。
経路アプリに「初回の検索だけ、いつも固まるか異常に遅い」という症状があった。2回目からは普通に動く。エラーの表示も出ない。ただ、ぐるぐる回り続ける。
症状の並べ方
原因を探す前に、症状を全部書き出した。
- 初回だけ遅い。2回目以降は速い
- エラーが出ない。文言すら出ない
- 読み込み中の表示が消えない
このうち「エラーが出ない」が、いま思えば一番の手がかりだった。処理が失敗しているなら、何らかの文言が出るはずだ。何も出ないということは、失敗していないということになる。
サーバーに届いているかを先に見た
クライアント側を読み始める前に、サーバー側を確認した。ここは意識的にそうしている。
- サーバーの利用記録を見る → 経路の計算が2日前から1件も増えていない
- 実況ログを流して、その場で検索を叩いてみる → リクエストが1件も届かない
これでサーバーは無実だと確定した。原因は、クライアントが通信を始めるより手前にある。この一手で、探す範囲が一気に狭まった。
固まる系の不具合では、この切り分けが効く。「サーバーまで届いているか」を先に見ると、犯人の側が半分に絞れる。
真因:成功も失敗もしない
犯人は、現在地を取得する処理だった。
位置情報を取る関数は、屋内・機内モード・シミュレータなど、位置が定まらない状況で成功もせず失敗もせず、永遠に待ち続けることがある。既知の挙動だ。
これを素朴に待つコードを書くと、こうなる。
- 成功しないので、次の処理へ進まない
- 失敗もしないので、例外にならない
- 例外にならないので、後片付けの処理も走らない
- 結果、読み込み表示が消えず、エラーも出ず、永久に回り続ける
症状の3点が、これで全部説明できる。「初回だけ」も説明がつく。電源を入れてから最初の測位は時間がかかるが、2回目以降は端末が直前の位置を持っているので即座に返るからだ。
自分が最初にやった誤り
ここが今回いちばん残しておきたい部分だ。
症状の報告を受けたとき、私は直前に自分がアプリの中身を更新していた。だから「私の更新が原因の可能性が高い」と、最初にそう報告してしまった。
実際に該当箇所の変更履歴を調べると、その行を最後に触ったのは4日前の別の変更だった。私の更新は無関係だった。
時間的な近さは原因ではない。 直前に自分が触っていると、どうしてもそこを疑いたくなるし、疑うこと自体は悪くない。悪いのは、裏を取る前に原因として報告してしまうことだ。誤った原因を先に共有すると、その後の調査が全員そちらに引っ張られる。
直し方
- 現在地の取得は、時間で打ち切る包みを必ず通すようにした(10秒で打ち切り、取れなければ「取れなかった」を返す)
- 生の関数を直接呼ぶ場所を作らないというルールにして、既存の呼び出しを全部置き換えた。同じアプリ2本で合計7箇所あった
- 権限の確認は呼び出し側に残した。「許可されていない」と「測位できなかった」は、出すべき文言が違う
- 打ち切ったあと、黙って代用しない。直前の位置や直線距離で埋め合わせると、間違った数字を正しい顔で見せることになる。止めて理由を伝える
最後の点は判断が分かれるところだと思う。だが**「近い値を勝手に出す」ことの危険さ**は、経路アプリでは特に大きい。合っているように見えて実は違う、という状態が一番たちが悪い。
同じ罠は、座標から住所を引く処理にもあった。そちらも同じ包みを通してある。
同じ形の罠を洗い出す
今回の修正のあと、「外部に聞いて返事を待つ処理」を全部洗い出した。同じ形をしているなら、同じ罠があるからだ。
| 待つ相手 | 返事が来ないことがあるか | 対応 |
|---|---|---|
| 現在地の取得 | ある(今回) | 10秒で打ち切り |
| 座標から住所を引く | ある | 同じ包みを通した |
| 自前サーバーへの通信 | ある | 通信の仕組みに打ち切りあり |
| ストアの購入処理 | ある | 画面側で操作を戻せるようにした |
| 端末内の保存領域 | ほぼ無い | そのまま |
こうして並べると、「打ち切りが要る処理」と「要らない処理」の線引きがはっきりする。線引きは単純で、自分のアプリの外に聞きに行くかどうかだ。外に聞く処理は、返事が来ない可能性を必ず持っている。
逆に、この線を引いていない状態でコードを書くと、「たまたま返事が速い環境」でだけ動くアプリになる。開発中の自分の端末は、たいてい環境が良い。開発機で再現しない不具合の多くは、この線引きの漏れから来るというのが今回の結論だった。
学んだこと
- 「エラーが出ない」は手がかり。 失敗していないなら、待っている
- 固まったら、まずサーバー側の記録を見る。 届いていなければクライアント、届いていればサーバー。1回の確認で半分に絞れる
- 時間で打ち切らない待ちを書かない。 外部(OS・通信・センサー)に聞く処理には、必ず打ち切りを付ける
- 直前の自分の変更を疑うのはよいが、報告の前に変更履歴で裏を取る。 推測を原因として共有しない
- 同じ罠は必ず複数箇所にある。 1箇所直したら、同じ呼び方をしている場所を全部洗う。今回は7箇所だった
Comments (0)
No comments yet. Be the first to leave one.