一過性のバグの防止

このページでは、一過性のバグを防ぐためのアーキテクチャパターンと、開発者が守るべきヒントを説明します。

一般的な根本原因

私たちは、一過性のバグにアドレスする際に頻繁に出てくる根本的な原因がいくつかあることに気づきました。

  • バックエンドまたはフロントエンドの状態管理を改善する必要があります。
  • フロントエンドのコードの改善が必要。
  • テストカバレッジの不足。
  • レース条件。

フロントエンド

レスポンスの順番に頼らない

複数のリクエストを扱うとき、レスポンスの順番がトリガーの順番と同じだと思いがちです。

これは必ずしもそうではなく、順番が入れ替わったときにのみバグが発生する可能性があります。

  • diffs_metadata.json (ライター)
  • diffs_batch.json (より重い)

あなたの機能が両方のデータを必要とする場合、その作業を行う前に2つのデータの読み込みが終了していることを確認してください。

手動でテストする場合は、低速接続をシミュレートしてください。

ブラウザの開発者ツールにネットワーク条件テンプレートを追加し、低速接続と高速接続を切り替えられるようにします。

  • タートル
    • ダウン:50kb/秒
    • 上り:20kb/秒
    • レイテンシー:10000ms

折りたたみ要素

イベント・リスナーを設定する場合、イベント委譲を使用できない場合は、関連するすべてのイベント・リスナーが展開されたコンテンツに設定されていることを確認してください。

その展開されたコンテンツがいつであるかを含めて:

  • 不可視(display: none;).JavaScriptの中には、測定時など、正しく動作するために要素が表示されている必要があるものがあります。
  • 動的コンテンツ(AJAX/DOM操作)。

アサーションを使用して、満たされていない条件によって引き起こされる一時的なバグを検出します。

一過性のバグは、アプリケーションの状態が 1 つ以上の条件を満たすという前提で実行されるコードのコンテキストで発生します。サーバー側の API レスポンスに常に属性グループが含まれることを想定した機能を書いたり、アプリケーションが新しい状態に正常に遷移したときにのみオペレーションが実行されることを想定した機能を書いたりします。

一時的なバグはデバッグが困難です。なぜなら、条件が満たされていないことをユーザーや開発者に警告するメカニズムがないからです。これらの条件は、通常、コードの中で明示的に表現されません。このような状況で有用なデバッグテクニックは、アサーションを置いて仮定を明示することです。アサーションは、どの条件が満たされないとバグが発生するのかを検出するのに役立ちます。

状態変化に対する事前条件のアサーション

一過性のバグにつながる一般的なシナリオは、ユーザーオペレーションが完了した場合にのみ状態を変化させるべきポーリングサービスがある場合です。この事前条件を明示するには、アサーションを使用します:

// This action is called by a polling service. It assumes that all pre-conditions
// are satisfied by the time the action is dispatched.
export const updateMergeableStatus = ({ commit }, payload) => {
  commit(types.SET_MERGEABLE_STATUS, payload);
};

// We can make any pre-condition explicit by adding an assertion
export const updateMergeableStatus = ({ state, commit }, payload) => {
  console.assert(
    state.isResolvingDiscussion === true,
    'Resolve discussion request must be completed before updating mergeable status'
  );
  commit(types.SET_MERGEABLE_STATUS, payload);
};

APIコントラクトのアサーション

アサーションを使用するもうひとつの便利な方法は、サーバー側のエンドポイントから返されたレスポンスペイロードが API コントラクトを満たしているかどうかを検出することです。

非決定論的バグを診断・修正し、デバッグしやすいソフトウェアを書くためのテクニックを探ります

バックエンド

ロック付きSidekiqジョブ

Sidekiqで非同期処理を行う場合、同じ引数を持つ2つのジョブが同時に処理される可能性があります。正しく処理されないと、古い状態や不正確な状態になる可能性があります。

例えば、オブジェクトの状態を更新するワーカーを考えてみましょう。Worker がオブジェクトの状態 (例えば#update_state) を更新する前に、適切な状態 (例えば#check_state) をチェックする必要があります。

同時に2つのジョブが処理されている場合、オペレーション順序は次のようになる可能性があります:

  1. (作業者A)コール#check_state
  2. (ワーカーB)コール#check_state
  3. (ワーカーB)コール#update_state
  4. (作業者A)コール#update_state

この例では、Worker B は更新されたステータスを設定するためのものです。しかし、Worker A#update_state を呼び出すのが少し遅すぎました。

これは、データベースロックかGitlab::ExclusiveLease を利用することで回避することができます。これにより、ジョブを冪等としてマークすることもできます。

リトライ機構の処理

オブジェクト/レコードが再チェック可能な失敗状態になることがあります。

オブジェクトが再チェック可能な状態にある場合、適切なメッセージがユーザーに表示され、ユーザーが何をすべきかがわかるようにしてください。また、再試行機能がトリガーされたときに、状態を正しくリセットできるようにしてください。

エラーロギング

エラーログは必ずしも一過性のバグを直接防ぐものではありませんが、デバッグの助けになります。

コーディングをしていると、例外が発生することを予期して、その例外をレスキューすることがあります。

エラーをレスキューしたときにログを記録しておくと、そのエラーが一過性のバグを引き起こしていて、ユーザーがそれを見る可能性がある場合に役立ちます。バグレポートを調査しているとき、エンジニアはエラーが発生したときのログを調べる必要があるかもしれません。ログに記録されたエラーは、何か問題が発生したことを示すシグナルである可能性があります。