2.41. Debugging

Sebagian besar skrip yang gagal pada kamera gagal dengan salah satu dari tiga cara: mereka memunculkan eksepsi, menghasilkan nilai yang salah, atau hang. Masing-masing memiliki seperangkat alat yang berbeda.

2.41.1. Membaca traceback

Ketika sebuah skrip memunculkan eksepsi dan tidak ada yang menanganinya, REPL atau IDE mencetak sebuah traceback -- catatan rantai panggilan dari skrip terluar hingga ke baris yang memunculkan eksepsi.

Traceback dibaca dari bawah ke atas:

  • Baris paling bawah menyebutkan kelas eksepsi dan pesannya (ValueError: invalid literal for int()...).

  • Setiap blok File "...", line N, in <name> di atasnya adalah sebuah frame -- satu panggilan lebih dalam saat Anda naik ke atas.

  • Frame paling atas adalah tempat skrip dimulai; frame paling bawah adalah tempat error muncul.

Baca bagian bawah terlebih dahulu untuk mengetahui apa yang salah, lalu naik ke atas untuk melihat bagaimana skrip sampai ke sana. Nomor baris menunjuk pada lokasi sumber yang tepat dalam skrip.

2.41.3. Menjelajahi sebuah objek

Dua fungsi bawaan menjawab "apa yang bisa saya lakukan dengan hal ini":

  • dir() -- mengembalikan daftar setiap nama yang didefinisikan pada sebuah objek: metode, atribut, dunder, semuanya.

  • help() -- mencetak docstring (dan di CPython, tanda tangan) dari sebuah fungsi, metode, atau kelas.

Gunakan keduanya bersama: dir menemukan namanya, help menjelaskan apa yang dilakukannya.

2.41.3.1. Menemukan nama dengan dir

>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
 '__eq__', '__ge__', ..., 'append', 'clear', 'copy',
 'count', 'extend', 'index', 'insert', 'pop', 'remove',
 'reverse', 'sort']

Bagian pertama dari daftar adalah metode dunder, diwarisi dari setiap objek; nama-nama yang perlu dipindai biasanya ada setelahnya. dir bekerja pada apa saja -- kelas, instans, modul, tipe bawaan:

>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']

Bentuk kedua itulah cara untuk mengetahui nama-nama tingkat teratas mana yang sebenarnya diekspos oleh sebuah modul tanpa meninggalkan REPL.

2.41.3.2. Mencarinya dengan help

Setelah dir menemukan kandidat, help mendeskripsikannya:

>>> help(str.split)
split(sep=None, maxsplit=-1)
    Return a list of the words in the string, ...

Pada MicroPython, help lebih ringkas dibandingkan CPython -- terkadang hanya tanda tangan, terkadang satu baris docstring, terkadang tidak ada untuk fungsi C bawaan. Ini tetap menjadi pengingat yang cepat ketika tooltip IDE tidak tersedia di dekat.

2.41.4. Ketika sesuatu hang

Skrip yang tidak kembali lebih sulit didiagnosis daripada yang memunculkan eksepsi. Penyebab umum:

  • Loop while yang kondisinya tidak pernah menjadi false. Tambahkan print dari variabel loop setiap iterasi; jika nilainya tidak berubah, isi loop memiliki bug.

  • Pemanggilan pemblokiran yang menunggu input yang tidak pernah tiba -- pembacaan dari antrian kosong, sleep tanpa akhir. Apit panggilan tersebut dengan print untuk melihat baris mana yang membuat skrip terjebak.

  • Rekursi tak terbatas. Traceback ketika akhirnya muncul (dengan RecursionError) biasanya langsung menunjuk ke sana.

Pemulihan paling efektif untuk skrip yang hang adalah tombol stop IDE, yang mengirimkan KeyboardInterrupt ke skrip melalui USB. Interupsi muncul sebagai traceback di baris yang sedang berjalan -- seringkali tepat di baris yang tidak kembali.

Catatan

Jika hang tidak dapat diatasi dengan semua diagnostik -- skrip tampak benar, traceback interupsi menunjuk ke fungsi bawaan atau kode firmware daripada skrip Anda, atau kode yang sama berhasil di build firmware sebelumnya -- penyebabnya mungkin bug firmware daripada bug skrip. Kurangi skrip menjadi reproducer terkecil yang masih hang dan buat laporan di forum OpenMV. Sertakan versi firmware, board yang digunakan, dan skrip yang telah dikurangi.

2.41.5. Hapus diagnostik sebelum pengiriman

Print strategis selama pengembangan sangat berguna; seratus panggilan print yang tersisa dalam skrip produksi memenuhi output dan menggunakan heap yang seharusnya bisa digunakan oleh pekerjaan nyata. Ketika bug sudah diperbaiki, hapus print-nya (atau lindungi di balik flag debug yang dapat dimatikan).

Untuk diagnostik yang harus tetap ada dalam jalur kode jangka panjang, beralih dari print() ke modul logging. Modul ini melampirkan sebuah level pada setiap pesan (debug, info, warning, error) dan membiarkan satu pengaturan untuk menyembunyikan yang tenang dalam produksi:

import logging

log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")

Mengatur level logger ke logging.WARNING membuat pemanggilan info dan debug hampir tidak memerlukan biaya (string pesan tidak pernah dibuat), tanpa harus mengomentari baris. Hal itu membuat logging menjadi alat yang tepat untuk diagnostik permanen; print mentah cocok untuk yang sementara.