ブレークポイントを使用してデバッグする

1. 始める前に

すでにほとんどの初心者のデベロッパーの方が、ログ ステートメントを使用したデバッグについてご存知でしょう。ユニット 1 を完了されていれば、スタック トレースを読んでエラー メッセージを調べる方法もご存知でしょう。これらはどちらも強力なデバッグツールですが、最新の IDE には、さらにデバッグ プロセスの効率性を高める機能が用意されています。

このレッスンでは、Android Studio に統合されているデバッガ、アプリの実行を一時停止する方法、コードを 1 行ずつ実行してバグの正確な原因を特定する方法について学習します。また、Watches という機能を使用する方法や、特定のログ ステートメントを追加せずに特定の変数をトラッキングする方法についても学習します。

前提条件

  • Android Studio でプロジェクトを操作する方法を理解している。
  • Kotlin でのロギングに精通している。

学習内容

  • 実行中のアプリにデバッガをアタッチする方法。
  • ブレークポイントを使用して実行中のアプリを一時停止し、コードを 1 行ずつ検査する。
  • ブレークポイントに条件式を追加して、デバッグ時間を短縮する。
  • [Watches] ペインに変数を追加して、デバッグを支援する。

必要なもの

  • Android Studio がインストールされているパソコン

2. 新しいプロジェクトの作成

大規模で複雑なアプリをデバッグするのではなく、空のプロジェクトで開始し、バグのあるコードを混入させることで、Android Studio のデバッグツールを試します。

まず、新しい Android Studio プロジェクトを作成します。

  1. [Select a Project Template] 画面で [Blank Activity] を選択します。

a949156bcfbf8a56.png

  1. アプリに「Debugging」という名前を付け、言語を Kotlin に設定し、それ以外は変更しないでおきます。

9863157e10628a87.png

  1. 新しい Android Studio プロジェクトが表示され、MainActivity.kt というファイルが表示されます。

e3ab4a557c50b9b0.png

バグを混入させる

ユニット 1 のデバッグのレッスンの「ゼロ除算」の例を思い出してください。ループの最後の反復処理で、アプリがゼロ除算を実行しようとすると、ゼロ除算が不可能なため、アプリは java.langArithmeticException でクラッシュします。このバグはスタック トレースを調べることで検出、修正され、この前提はログ ステートメントを使用して検証されました。

すでにご存知のように、この例はブレークポイントを使用する方法を示すために使用されます。ブレークポイントを使用すると、ログ ステートメントを追加してアプリを再実行することなく、コードを 1 行ずつ実行できます。

  1. MainActivity.kt を開き、コードを次のコードに置き換えます。
package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

public val TAG = "MainActivity"

class MainActivity : AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       division()
   }

   fun division() {
       val numerator = 60
       var denominator = 4
       repeat(5) {
           Log.v(TAG, "${numerator / denominator}")
           denominator--
       }
   }

}
  1. アプリを実行します。その結果、アプリが想定どおりにクラッシュすることを確認します。

9468226e5f4d5729.png

3.ブレークポイントを使用してデバッグする

ロギングについて学習した際、戦略的にログを配置してバグを特定できるようにする方法と、バグが修正されたことを確認する方法を学習しました。しかし、混入させていないバグに直面した場合、ログ ステートメントをどこに配置すべきか、またはどの変数を出力すべきかが明確ではない場合があります。多くの場合、この情報は実行時にのみ見つかります。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}

このような場合にブレークポイントが役立ちます。スタック トレース内の情報に基づくバグの原因がよくわからない場合でも、特定のコード行に対する停止標識として機能するブレークポイントを追加できます。ブレークポイントに達すると、実行が一時停止するので、実行時に他のデバッグツールを使って何が起こっているか、何が問題なのかを詳しく確認できます。

デバッガをアタッチする

Android Studio はバックグラウンドで Android Debug Bridge(ADB)というツールを使用しています。これは Android Studio に統合されたコマンドライン ツールであり、実行中のアプリにブレークポイントなどのデバッグ機能を提供します。多くの場合、デバッグ用のツールをデバッガといいます。

アプリにデバッガを使用する(またはアタッチする)には、これまでのように [Run] > [Run] でアプリを実行するのではなく、[Run] > [Debug 'app'] でアプリを実行します。

340a8e850b3c86d3.png

プロジェクトにブレークポイントを追加する

ブレークポイントの動作を確認する手順は次のとおりです。

  1. 一時停止する行番号の横にあるガターをクリックして、ブレークポイントを追加します。行番号の横に丸印が表示され、行がハイライト表示されます。

629ac33dfb3873e.png

  1. [Run] > [Debug ‘app'] またはツールバーの a71c8b295db5927d.png アイコンを使用し、デバッガをアタッチした状態でアプリを実行します。アプリの起動時に、次のような画面が表示されます。

3bd9cbe69d5a0d0e.png

アプリが起動すると、ブレークポイントがハイライト表示されます(有効な場合)。

928fc1194966c9.png

これまで [Logcat] ウィンドウが表示されていた画面下部に、新しい [Debug] タブが開きました。

447d9743c118babd.png

左側には、関数のリスト(スタック トレースで表示されたリストと同じ)が表示されます。右側には、現在の関数(division())内の個々の変数の値を確認できるペインが表示されます。上部には、一時停止中にプログラムを操作できるボタンもあります。最もよく使用するのは Step Over ボタンで、これによりハイライト表示された 1 行のコードが実行されます。

48219b96d5ab6ba6.png

コードをデバッグする手順は次のとおりです。

  1. ブレークポイントに達した後、19 行目(numerator 変数の宣言)がハイライト表示されていますが、まだ実行されていません。Step Over ボタン(fbdcba647f9844c4.png)を使用して 19 行目を実行します。これで 20 行目がハイライト表示されます。

eaacf76805166461.png

  1. 22 行目にブレークポイントを設定します。これは除算が行われた場所であり、スタック トレースが例外を報告した行です。

1f18ab31dc58a1a7.png

  1. [Debug] ウィンドウの左側にある Resume Program ボタン 616d16841834ae2a.png を使用し、次のブレークポイントに移動して division() 関数の残りの部分を実行します。

3a9c3edc893f9720.png

  1. 実行は 17 行目で停止します。なお、この時点で 17 行目は未実行です。

aa56331ad870cd40.png

  1. 各変数の値 numeratordenominator が宣言の横に表示されています。変数の値は、[Variables] タブのデバッグ ウィンドウで確認できます。

5b3515c5580ee7dd.png

  1. デバッグ ウィンドウの左側にある Resume Program ボタンをさらに 4 回押します。毎回ループが一時停止し、numeratordenominator の値が確認されます。最後の反復処理で、numerator60denominator0 になるはずです。60 を 0 で割ることはできません。

56ea223612b88125.png

これで、バグを引き起こすコード行と理由がわかりました。前と同様に、コードの繰り返し回数を 5 から 4 に変更することで、バグを修正できます。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(4) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}

4. ブレークポイントの条件を設定する

前のセクションでは、分母がゼロになるまで、ループの反復処理を順次実行する必要がありました。さらに複雑なアプリでは、バグに関する情報が少ないと面倒な場合があります。しかし、分母が 0 の場合にのみアプリがクラッシュするなどの前提がある場合は、その前提が満たされたときにだけ達するようにブレークポイントを変更でき、ループの反復処理を順に実行する必要がなくなります。

  1. 必要に応じて、繰り返しループで 45 に変更してバグを再度混入させます。
repeat(4) {
    ...
}
  1. repeat ステートメントがある行に新しいブレークポイントを配置します。

47fcc3aeb814a9d7.png

  1. 赤いブレークポイント アイコンを右クリックします。ブレークポイントが有効になっているかどうかなどのオプションを含むメニューが表示されます。無効なブレークポイントは引き続き存在しますが、実行時にはトリガーされません。また、true と評価されるとブレークポイントをトリガーする Kotlin の式を追加することもできます。たとえば、式 denominator > 3 を使用した場合、ブレークポイントはループの最初の反復処理でのみトリガーされます。アプリがゼロ除算を行う可能性がある場合にのみブレークポイントをトリガーするには、式を denominator == 0 に設定します。ブレークポイントのオプションは次のようになります。

76045ef783d5389b.png

  1. [Run] > [Debug 'app'] を使用してアプリを実行し、ブレークポイントに達することを確認します。

74ba264a6eab7db7.png

分母はすでに 0 です。ブレークポイントは条件が満たされたときにのみトリガーされるため、コードの実行にかかる時間と労力を節約できます。

153be06e8a19e61d.png

  1. 前述のように、バグはループの実行回数が多すぎることによって発生します(分母が 0 に設定されている場合)。

ウォッチを追加する

デバッグ中に特定の値をモニタリングする場合、[Variables] タブで検索して見つける必要はありません。Watches というものを追加して、特定の変数をモニタリングできます。このような変数は、デバッグペインに表示されます。実行が一時停止され、その変数が対象範囲内の場合、この変数は [Watches] ペインに表示されます。これにより、大規模なプロジェクトで作業した場合にデバッグの効率が向上します。関連するすべての変数を 1 か所でトラッキングできます。

  1. デバッグビューの [Variables] ペインの右側に、[Watches] という別の空のペインが表示されます。左上にある追加 c97b1f6a879b0563.png ボタンをクリックします。[New Watch] というメニュー オプションが表示されます。

4d27cc7c5222377b.png

  1. 表示された項目に変数の名前(denominator)を入力し、Enter キーを押します。
  2. [Run] > [debug 'app'] でアプリを再実行し、ブレークポイントに達すると [Watches] ペインに分母の値が表示されることを確認します。

5. 完了

まとめ:

  • ブレークポイントを設定すると、アプリの実行を一時停止できる。
  • 実行を一時停止すると、「ステップ オーバー」でコードを 1 行だけ実行できる。
  • Kotlin の式に基づいてのみブレークポイントをトリガーする条件ステートメントを設定できる。
  • Watches で、デバッグ時に目的の変数をグループ化できる。

さらに詳しく