중단점을 사용하여 디버그

1. 시작하기 전에

이제 대부분의 초보 개발자는 로그 구문을 사용한 디버깅 방법을 알 것이며, 1단원을 완료했다면 스택 트레이스를 읽고 오류 메시지를 조사하는 방법도 배웠을 것입니다. 둘 다 강력한 디버깅 도구이지만 최신 IDE는 디버깅 프로세스의 효율을 한층 높여주는 기능을 더 많이 제공합니다.

이 강의에서는 Android 스튜디오의 통합 디버거를 소개하고 앱 실행을 일시중지하는 방법과 한 번에 코드 한 줄씩 실행하여 정확한 버그 원인을 가려내는 방법을 알아봅니다. 특정 로그 구문을 추가하지 않고 Watches라는 기능을 사용하여 특정 변수를 추적하는 방법도 살펴봅니다.

기본 요건

  • Android 스튜디오에서 프로젝트를 탐색하는 방법을 알고 있습니다.
  • Kotlin으로 로깅하는 방법을 잘 알고 있습니다.

학습할 내용

  • 실행 중인 앱에 디버거를 연결하는 방법
  • 중단점을 사용하여 실행 중인 앱을 일시중지하고 코드를 한 번에 한 줄씩 검사
  • 중단점에 조건식을 추가하여 디버깅 시간 절약
  • Watches 창에 변수를 추가하여 디버깅 지원

필요한 항목

  • Android 스튜디오가 설치된 컴퓨터

2. 새 프로젝트 만들기

복잡한 대규모 앱을 디버그하는 대신 빈 프로젝트로 시작하고 버그 코드를 삽입하여 Android 스튜디오의 디버깅 도구 사용 방법을 보여드리겠습니다.

먼저 새 Android 스튜디오 프로젝트를 만듭니다.

  1. Select a Project Template(프로젝트 템플릿 선택) 화면에서 Blank Activity(빈 활동)를 선택합니다.

a949156bcfbf8a56.png

  1. 앱의 이름을 Debugging으로 지정하고 언어가 Kotlin으로 설정되어 있으며 다른 모든 부분이 변경되지 않았는지 확인합니다.

9863157e10628a87.png

  1. MainActivity.kt라는 파일을 표시하는 새 Android 스튜디오 프로젝트가 표시됩니다.

e3ab4a557c50b9b0.png

버그 삽입

1단원의 디버깅 강의에서 예로 든 0으로 나누기를 기억하시나요? 루프의 최종 반복에서 앱이 0으로 나누기를 실행하려고 하면 java.langArithmeticException과 함께 비정상 종료됩니다. 0으로 나누기는 불가능하기 때문입니다. 스택 트레이스를 검사하여 이 버그를 찾아 수정했고 로그 구문을 사용하여 가정을 확인했습니다.

이미 잘 알고 계실 이 예를 통해 중단점을 사용하는 방법을 보여드리겠습니다. 중단점은 코드를 한 번에 한 줄씩 단계별로 실행하므로 먼저 로그 구문을 추가하고 앱을 다시 실행할 필요가 없습니다.

  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 스튜디오는 Android 디버그 브리지(ADB)라는 도구를 사용합니다. Android 스튜디오에 통합되어 있는 ADB는 중단점과 같은 디버깅 기능을 실행 중인 앱에 제공하는 명령줄 도구입니다. 디버깅 도구를 디버거라고도 합니다.

디버거를 앱에 사용하거나 연결하기 위해서는 이전과 같이 단순하게 Run > Run(실행 > 실행)으로 앱을 실행하면 안 됩니다. 대신 Run > Debug 'app'(실행 > '앱' 디버그)을 사용하여 앱을 실행합니다.

21d706a854ebe710.png

프로젝트에 중단점 추가

작동 중인 중단점을 보려면 다음 단계를 실행합니다.

  1. 일시중지할 줄 번호 옆의 여백을 클릭하여 중단점을 추가합니다. 줄 번호 옆에 점이 표시되고 줄이 강조표시됩니다.

6b6c2cd97bdc08ba.png

  1. Run > Debug 'app'(실행 > '앱' 디버그) 또는 툴바의 f6a141c7f2a4e444.png 아이콘을 사용하여 연결된 디버거를 통해 앱을 실행합니다. 앱이 실행되면 다음과 같은 화면이 표시됩니다.

3bd9cbe69d5a0d0e.png

앱이 실행되면 중단점이 활성화되면서 강조표시됩니다.

a4860e59534f216a.png

이전에 Logcat 창을 표시했던 화면 하단에 새로운 Debug 탭이 열렸습니다.

ce37d2791db7302.png

왼쪽에는 스택 트레이스에 표시된 목록과 동일한 함수 목록이 있습니다. 오른쪽에는 현재 함수(예: division())의 개별 변수 값을 확인할 수 있는 창이 있습니다. 상단에는 일시중지된 동안 프로그램을 탐색할 수 있는 버튼도 있습니다. 가장 많이 사용하는 버튼은 강조표시된 코드 줄 하나를 실행하는 Step Over(다음 줄로 이동)입니다.

a6c07c89e81abdc5.png

코드를 디버그하려면 다음 단계를 따르세요.

  1. 중단점에 도달하면 줄 19(numerator 변수 선언)가 강조표시되지만 아직 실행되지는 않았습니다. Step Over 1d02d8134802ee64.png(다음 줄로 이동) 버튼을 사용하여 줄 19를 실행합니다. 이제 줄 20이 강조표시됩니다.

aa8f7063c836af0d.png

  1. 줄 22에 중단점을 설정합니다. 이 줄에서 나눗셈이 발생했으며 스택 트레이스가 예외를 보고했습니다.

85758e1d4e69f9eb.png

  1. Debug(디버그) 창 왼쪽에 있는 Resume Program 8119afebc5492126.png(프로그램 계속) 버튼을 사용하여 다음 중단점으로 이동하고 나머지 division() 함수를 실행합니다.

433d1c2a610b7945.png

  1. 줄 17을 실행하기 전에 실행이 중지됩니다.

2a16075287a03058.png

  1. 변수 numeratordenominator의 값이 각각 선언 옆에 표시됩니다. 변수 값은 Variables(변수) 탭의 디버그 창에서 확인할 수 있습니다.

ebac20924bafbea5.png

  1. 디버그 창 왼쪽에 있는 Resume Program(프로그램 계속) 버튼을 4번 더 누릅니다. 매번 루프가 일시중지되고 numeratordenominator의 값을 확인합니다. 마지막 반복에서 numerator60이고 denominator0이어야 합니다. 60을 0으로 나눌 수는 없습니다.

246dd310b7fb54fe.png

이제 버그를 일으키는 정확한 코드 줄과 정확한 원인을 알게 되었습니다. 이전과 마찬가지로 코드 반복 횟수를 5에서 4로 변경하여 버그를 수정할 수 있습니다.

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

4. 중단점 조건 설정

이전 섹션에서는 분모가 0이 될 때까지 루프의 각 반복을 단계별로 실행해야 했습니다. 좀 더 복잡한 앱에서는 버그에 관한 정보가 적으면 지루한 작업이 될 수 있습니다. 그러나 '분모가 0일 때만 앱이 비정상 종료된다'와 같이 가정을 하면 루프의 각 반복을 단계별로 실행할 필요 없이 이러한 가정이 충족될 때만 중단점에 도달하도록 수정할 수 있습니다.

  1. 필요하면 반복 루프에서 45로 변경하여 버그를 다시 삽입합니다.
repeat(4) {
    ...
}
  1. repeat 문을 사용하여 줄에 새 중단점을 배치합니다.

2da539348a57af97.png

  1. 빨간색 중단점 아이콘을 마우스 오른쪽 버튼으로 클릭합니다. 몇 가지 옵션(예: 중단점 사용 설정 여부)이 있는 메뉴가 표시됩니다. 사용 중지된 중단점은 있지만 런타임에 트리거되지 않습니다. true로 평가되면 중단점이 트리거되는 Kotlin 표현식을 추가하는 옵션도 있습니다. 예를 들어 denominator > 3 표현식을 사용한 경우 중단점은 루프의 첫 번째 반복에서만 트리거됩니다. 앱이 0으로 나눌 가능성이 있는 경우에만 중단점을 트리거하려면 표현식을 denominator == 0으로 설정합니다. 중단점 옵션은 다음과 같이 표시됩니다.

890b999988aef59b.png

  1. Run > Debug 'app'(실행 > '앱' 디버그)을 사용하여 앱을 실행하고 중단점에 도달하는지 확인합니다.

482ec7c2a23040ef.png

분모가 이미 0인 것을 확인할 수 있습니다. 조건이 충족될 때만 중단점이 트리거되므로 코드를 단계별로 실행하는 데 드는 시간과 노력이 절약됩니다.

9f4e29b5a91fdb12.png

  1. 이전과 마찬가지로 분모가 0으로 설정된 루프가 너무 여러 번 실행되어 버그가 발생합니다.

Watches 추가

디버깅하는 동안 특정 값을 모니터링하려는 경우 Variables(변수) 탭에서 검색하여 찾지 않아도 됩니다. Watches라는 항목을 추가하여 특정 변수를 모니터링할 수 있습니다. 이러한 변수는 디버그 창에 표시됩니다. 실행이 일시중지될 때 변수가 범위 내에 있으면 Watches 창에 표시됩니다. 그러므로 대규모 프로젝트 작업에서 더 효율적으로 디버깅할 수 있습니다. 모든 관련 변수를 한곳에서 추적할 수 있게 됩니다.

  1. 디버그 뷰의 변수 창 오른쪽에는 Watches라는 빈 창이 하나 더 있습니다. 왼쪽 상단에 있는 더하기 d6fb21f8b9c67448.png 버튼을 클릭합니다. New Watch(새 Watch)라는 메뉴 옵션이 표시될 수 있습니다.

990d62a98ec9ba5b.png

  1. 제공된 필드에 변수 이름 denominator를 입력하고 Enter를 클릭합니다.
  2. Run > debug 'app'(실행 > '앱' 디버그)을 사용하여 앱을 다시 실행하고 중단점에 도달하면 Watches 창에 분모 값이 표시되는지 확인합니다.

5. 축하합니다

학습 내용을 요약하면 다음과 같습니다.

  • 중단점을 설정하여 앱 실행을 일시중지할 수 있습니다.
  • 실행이 일시중지되면 'Step Over'(다음 줄로 이동)를 사용하여 코드를 한 줄만 실행할 수 있습니다.
  • Kotlin 표현식에 기반하여 중단점만 트리거되도록 조건문을 설정할 수 있습니다.
  • Watches를 사용하면 디버깅할 때 관심 있는 변수를 그룹화할 수 있습니다.

자세히 알아보기