デバッグの概要

ソフトウェアを使用したことがあれば、バグに遭遇したことがあるはずです。バグとは、アプリのクラッシュや、ある機能が想定どおりに動作しないなど、意図しない動作を引き起こすソフトウェアのエラーのことをいいます。経験に関係なく、デベロッパーは誰でもコードを記述する際にバグを混入させます。Android デベロッパーにとって最も重要なスキルは、バグを特定して修正することです。アプリのリリース全体がバグの修正に特化していることも珍しくありません。たとえば、次に示す Google マップのバージョン詳細をご覧ください。

9d5ec1958683e173.png

バグを修正するプロセスのことをデバッグといいます。著名なコンピュータ サイエンティストである Brian Kernighan 氏は、「最も効果的なデバッグツールは依然として、慎重な検討と、適切に配置された print ステートメントである」と述べています。確かにそのとおりかもしれませんが、高度なデバッグツールを使用すると、迅速かつ簡単にバグを見つけられます。デバッグのスキルは、プログラミングのスキルと同様に時間をかけて身につけるものですが、Android Studio に組み込まれているデバッグツールは、早く慣れるに越したことはありません。このレッスンでは、Android Studio に統合されているデバッガを確認し、スタック トレースの読み方や、ブレークポイントを使用してコードをステップ実行する方法について学習します。

前提条件

  • Android Studio でプロジェクトを操作する方法を理解している。

学習内容

  • 実行中のアプリにデバッガをアタッチする方法。
  • スタック トレース内の意味のある情報を見つける方法。
  • ブレークポイントを使用して実行中のアプリを一時停止し、コードを 1 行ずつ検査する。

必要なもの

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

大規模で複雑なアプリをデバッグするのではなく、空のプロジェクトで開始し、バグのあるコードを意図的に混入することで、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. MainActivity.kt を開き、以下の関数を追加します。このコードは 2 つの数で始まり、repeat を使用して numerator を denominator で除算した結果を 5 回出力します。repeat ブロック内のコードが実行されるたびに、denominator の値が 1 ずつ減少します。最後となる 5 回目の繰り返しでは、ゼロで割ろうとします。
fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        println(numerator / denominator)
        denominator--
    }
}
  1. onCreate()division() 関数を呼び出します。新しい onCreate() 関数は次のようになります。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    division()
}
  1. アプリを実行します。onCreate() は最初に表示されるとすぐに実行されるため、起動直後にアプリがクラッシュすることが予想されます。アプリがクラッシュすると、エラーの特定に役立つ情報が表示されます。
  2. Android Studio の下部にある [Logcat] タブを開きます(このタブを初めて開く場合は読み込みに時間がかかることがあります)。

e4d025b0363eaa63.png

  1. [Logcat] ウィンドウには多くの出力が表示されるため、非表示部分を確認するために画面を少しスクロールする必要があるかもしれません。アプリの出力のみが表示されるようにするには、左上のプルダウンをエミュレータ(または物理デバイス)の名前に設定し、プロセスをアプリ(com.example.debugging)に設定します。

5c008135b1804091.png

  1. エラー メッセージの検索ボックス(Windows の場合は Ctrl+F キー、Mac の場合は Command+F キー)に「RuntimeException」と入力して Enter キーを押します。

9468226e5f4d5729.png

スタック トレースの仕組み

例外を説明するテキストのブロックをスタック トレースといいます。スタック トレースには、例外が発生するまでに呼び出されたすべての関数が、最近呼び出されたものから順に表示されます。出力の全体は次のとおりです。

Process: com.example.debugging, PID: 23296
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.ArithmeticException: divide by zero
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.debugging.MainActivity.division(MainActivity.kt:17)
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:10)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

大量のテキストです。幸いなことに、エラーを正確に絞り込むために必要なテキストは通常、このうちの一部だけです。上から順に説明します。

  1. java.lang.RuntimeException:
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.ArithmeticException: divide by zero

最初の行には、アプリがアクティビティを開始できなかったということが書かれています。これが、アプリがクラッシュした原因です。次の行に、もう少し詳しい情報が書かれています。具体的には、アクティビティを開始できなかった原因は、ArithmeticException です。さらに具体的には、ArithmeticException の型が「ゼロ除算」("divide by zero")でした。

  1. Caused by:
Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:17)

「Caused by」まで下にスクロールすると、「ゼロ除算」エラーがあったことがここにも書かれています。こちらには、エラーが発生した関数(division())と行番号(17)も示されています。

このバグは意図的に混入したものであるため、驚くようなことではありません。しかし、未知のエラーの原因を特定する必要がある場合には、例外のタイプ、関数名、行番号を正確に把握することは非常に有益です。

前の例では、エラーが発生した関数やコード行など、エラーに関する具体的な情報がスタック トレースでどのように提供されるかを確認しました。実際のバグに対処する際は、これだけでは修正方法を決定できない場合があります。たとえば、バグを混入したコード内の場所(17 行目)やエラーの正確な内容(ゼロ除算)はわかっていても、コードがなぜゼロ除算なのかがわからない場合があります。その場合は、division() 関数で除算が行われる前に println() ステートメントを追加することで、分母として使用されている denominator の値を出力します。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        println(denominator)
        println(numerator / denominator)
        denominator--
    }
}

もちろん、問題がもっと複雑であることが判明した場合は、println() ステートメントを追加し、有用な情報が見つかるまでアプリを再実行する必要があります。コードが複雑になるにつれ、追跡も難しくなります。

そこでブレークポイントというものが登場します。適切に配置された print ステートメントの価値については Brian Kernighan 氏の言うとおりかもしれませんが、ブレークポイントは同様の目的を果たします。ただし、これは実行中のアプリで一時停止ボタンを押すようなものです。ブレークポイントは、コードのほぼすべての行に設定できます。ブレークポイントに達すると、すべての実行が停止され、変数の値を調べたり、1 行ずつ実行することでコードをステップ実行したりできます。ブレークポイントを使用するには、デバッガというものを使用してアプリを実行する必要があります。

デバッガのアタッチ

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

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

21d706a854ebe710.png

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

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

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

6b6c2cd97bdc08ba.png

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

3bd9cbe69d5a0d0e.png

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

a4860e59534f216a.png

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

ce37d2791db7302.png

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

a6c07c89e81abdc5.png

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

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

58f4bb135d5b756e.png

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

88d7d810a29965aa.png

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

433d1c2a610b7945.png

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

1f6aedcf2a48c492.png

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

ebac20924bafbea5.png

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

246dd310b7fb54fe.png

これで、バグを引き起こすコード行だけでなく、理由もわかりました。5 回目の反復処理で、denominator の値が 0 になるためでした。このエラーを修正するには、denominator0. でない場合にのみ除算を実行する if ステートメントを追加します。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        if (denominator != 0) {
            println(numerator / denominator)
        }
        denominator--
    }
}

コードの繰り返し回数を 5 から 4 に変更してもかまいません。

fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        println(numerator / denominator)
        denominator--
    }
}

いずれの方法でも、ランタイム例外を発生させずにコードを実行できます。実際に試してご確認ください。

これまでのレッスンでは、Kotlin の println() ステートメントを使用してテキスト出力を生成しました。Android アプリでは、Log を使用して出力をロギングすることをおすすめします。出力をロギングするための関数は複数あり、Log.e()Log.d() といった形式で、パラメータを 2 つ取ります。1 つ目のパラメータは「タグ」という、ログメッセージのソースを明示する文字列(そのメッセージをロギングしたクラスの名前など)です。2 つ目は実際のログメッセージです。

異なる文字で名付けられた異なるログ関数が存在するのは、それぞれが異なるログレベルに対応しているためです。出力する情報の種類に応じ、異なるログレベルを使用して Logcat 出力をフィルタできます。よく使用する主なログレベルは 5 つあります。

ログレベル

ユースケース

ERROR

ERROR ログには、アプリがクラッシュした理由など、なんらかの重大な問題が発生したことが報告されます。

Log.e(TAG, "The cake was left in the oven for too long and burned.").

WARN

WARN ログには、エラーほど重大ではないものの、より重大なエラーを回避するために修正する必要がある点が報告されます。たとえば、非推奨の関数(使用が推奨されず、新しい代替手段の使用が望ましい)を呼び出した場合などです。

Log.w(TAG, "This oven does not heat evenly. You may want to turn the cake around halfway through to promote even browning.")

INFO

INFO ログからは、オペレーションが正常に完了したことなどの有用な情報を確認できます。ログレベルの println() ステートメントです。

Log.i(TAG, "The cake is ready to be served.").println("The cake has cooled.")

DEBUG

DEBUG ログには、問題の調査に役立つ情報が記載されます。

Log.d(TAG, "Cake was removed from the oven after 55 minutes. Recipe calls for the cake to be removed after 50 - 60 minutes.")

VERBOSE

その名のとおり、詳細ログです。何をもってデバッグログとするか詳細ログとするかは多少主観的ですが、一般に、詳細ログは機能を実装した後は削除してよい類のものです。一方、デバッグログは実装後もデバッグに役立つ可能性があるものです。

Log.v(TAG, "Put the mixing bowl on the counter.")Log.v(TAG, "Grabbed the eggs from the refrigerator.")Log.v(TAG, "Plugged in the stand mixer.")

各タイプのログレベル、特に INFO、DEBUG、VERBOSE をどのような場合に使用するかについて、定まったルールはないという点にご注意ください。ソフトウェア開発チームは、各ログレベルをどのような場合に使用するかについて独自のガイドラインを作成できます。VERBOSE のような特定のログレベルをまったく使用しないことにしてもかまいません。

それぞれのログレベルが Logcat でどのように表示されるか、見ていきましょう。

  1. MainActivity.kt で、クラス宣言の前に TAG という定数を追加し、その値をクラス名 MainActivity に設定します。
private const val TAG = "MainActivity"
  1. 次に示すように、MainActivity クラスに logging() という新しい関数を追加します。
fun logging() {
    Log.e(TAG, "ERROR: a serious error like an app crash")
    Log.w(TAG, "WARN: warns about the potential for serious errors")
    Log.i(TAG, "INFO: reporting technical information, such as an operation succeeding")
    Log.d(TAG, "DEBUG: reporting technical information useful for debugging")
    Log.v(TAG, "VERBOSE: more verbose than DEBUG logs")
}
  1. onCreate()division() の呼び出し(前の例より)を、logging() の呼び出しに置き換えます。新しい onCreate() メソッドは次のようになります。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    logging()
}
  1. アプリを実行して Logcat で出力を確認します。必要に応じて、com.example.debugging プロセスからのログのみが表示されるように出力をフィルタします。出力をフィルタして、「MainActivity」タグのあるログのみを表示することもできます。そのためには、Logcat ウィンドウの右上にあるプルダウン メニューから [Edit Filter Configuration] を選択します。

5fa189e6b18a966a.png

  1. 次に示すように、[Log Tag] に「MainActivity」と入力し、フィルタの名前を作成します。

6dbba17eb5df15eb.png

  1. 「MainActivity」タグのあるログメッセージのみが表示されるようになりました。

4061ca006b1d278c.png

ログレベルに対応する文字がクラス名の前に入っています(W/MainActivity など)。また、WARN ログは青色で表示され、先ほどの例の致命的なエラーと同様に ERROR ログは赤色で表示されています。

  1. デバッグ出力をプロセスでフィルタする場合と同様に、出力をログレベルでもフィルタできます。デフォルトでは [Verbose] に設定されており、VERBOSE ログと、それより上のログレベルが表示されます。プルダウン メニューから [Warn] を選択すると、WARN レベルと ERROR レベルのログのみが表示されます。

c4aa479a8dd9d4ca.png

  1. 今度はプルダウンを [Assert] に変更し、ログが表示されないことを確認します。これにより、ERROR レベル以下がすべて除外されます。

169a0bc232f77734.png

println() ステートメントを少し深刻に捉えすぎているように思われるかもしれませんが、大規模なアプリを構築すると、Logcat の出力は多くなります。さまざまなログレベルを使用することで、最も有用な関連情報を選び出せます。Android 開発では、println() よりも Log を使用する方がよいとされています。適切なログレベルを選択することは、お客様ほどコードに精通していない開発チームの他のメンバーにもメリットがあり、バグの特定と解決がずっと簡単になります。

デフォルトでは、プロジェクトの作成に使用した Blank Activity テンプレートによってアクティビティが 1 つ追加され、TextView が画面の中央に配置されます。これまでに学習したように、Layout Editor で ID を設定し、findViewByID() でビューにアクセスすると、コードからビューを参照できます。別のバグを説明するために、ビューにアクセスしてみましょう。

  1. activity_main.xml を開き、Hello, world! TextView を選択して、ID を hello_world に設定します。

8a5dede436e2718e.png

  1. ActivityMain.kt に戻り、onCreate()setContentView() 呼び出しの前に、TextView を取得してテキストを「Hello, debugging!」に変更するコードを追加します。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val helloTextView: TextView = findViewById(R.id.hello_world)
    helloTextView.text = "Hello, debugging!"
    setContentView(R.layout.activity_main)
    division()
}
  1. アプリを再度実行し、起動直後にまたクラッシュすることを確認します。com.example.debugging でログをフィルタします。

cdb335255d798a0a.png

例外は Logcat の最後部に表示されます(表示されない場合は「RuntimeException」で検索できます)。出力は次のようになります。

com.example.debugging E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.debugging, PID: 5516
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.IllegalStateException: findViewById(R.id.hello_world) must not be null
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.IllegalStateException: findViewById(R.id.hello_world) must not be null
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:10)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

前のように、上部に「Unable to start activity」と表示されています。MainActivity が起動する前にアプリがクラッシュしているため、当然です。次の行には、エラーについてもう少し詳しく記載されています。

java.lang.IllegalStateException: findViewById(R.id.hello_world) must not be null

スタック トレースのさらに下方には次の行もあり、関数呼び出しと行番号を示しています。

Caused by: java.lang.IllegalStateException: findViewById(R.id.hello_world) must not be null
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:10)

このエラーは正確には何を意味し、「null」値とは正確には何でしょうか。これはわざとらしい例であり、アプリがクラッシュした原因についてはすでにご存じかもしれませんが、今後見たことのないエラー メッセージに遭遇することもあるはずです。その場合、エラーを最初に確認した人はお客様ではない可能性があります。経験豊富なデベロッパーであっても、他の人が問題をどのように解決したのか確認するために、エラー メッセージを Google で検索することはよくあります。このエラーを検索すると、StackOverflow(デベロッパーがバグのあるコードや一般的なプログラミングのトピックについて質問したり答えたりできるサイト)から複数の検索結果が得られます。

efa074b344d1704c.png

回答をいくつか読むと、このエラーには複数の異なる原因があり得ることがわかります。しかし、この例をユニット 1 のコードと比較すると、setContentView() を呼び出す前に意図的にビューにアクセスしようとしていたことがわかります。ビューがまだ存在しないときにビューにアクセスしようとすると、ランタイム例外が発生します。

コードを更新してエラーを修正します。

  1. findViewById() の呼び出しと、helloTextView のテキストを設定する行を、setContentView() の呼び出しの下に移動します。新しい onCreate() メソッドは次のようになります。
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
   val helloTextView: TextView = findViewById(R.id.hello_world)
   helloTextView.text = "Hello, debugging!"
   division()
}
  1. 次にアプリを再実行して、コードがクラッシュしなくなったことと、テキストが想定どおりに更新されることを確認します。

e52adf1c7bf6f792.png

まとめ:

  • デバッグは、コードのバグをトラブルシューティングするプロセスである。
  • スタック トレースからは、例外の原因となった関数、例外が発生した行番号など、例外に関する情報が得られる。
  • ブレークポイントを設定すると、アプリの実行を一時停止できる。
  • 実行を一時停止すると、「ステップ オーバー」でコードを 1 行だけ実行できる。

詳細