Pola umum untuk menemukan bug mengikuti skrip ini:
- Amati keanehan, misalnya, tidak ada output atau program menggantung.
- Temukan pesan yang relevan di log atau output program, misalnya, "Tidak dapat menemukan Foo". (Berikut ini hanya relevan jika ini adalah jalur yang diambil untuk menemukan bug. Jika tumpukan jejak atau informasi debug lainnya tersedia, itu cerita lain.)
- Temukan kode tempat pesan dicetak.
- Debug kode antara tempat pertama Foo masuk (atau harus masuk) gambar dan di mana pesan dicetak.
Langkah ketiga adalah tempat proses debugging sering terhenti karena ada banyak tempat dalam kode di mana "Tidak dapat menemukan Foo" (atau string templated Could not find {name}
) dicetak. Bahkan, beberapa kali kesalahan ejaan membantu saya menemukan lokasi sebenarnya jauh lebih cepat daripada yang saya lakukan sebelumnya - itu membuat pesan unik di seluruh sistem dan sering di seluruh dunia, sehingga mesin pencari yang relevan langsung mengenai.
Kesimpulan yang jelas dari ini adalah bahwa kita harus menggunakan ID pesan unik secara global dalam kode, mengkodekannya sebagai bagian dari string pesan, dan mungkin memverifikasi bahwa hanya ada satu kemunculan setiap ID dalam basis kode. Dalam hal pemeliharaan, apa yang menurut komunitas ini merupakan pro dan kontra yang paling penting dari pendekatan ini, dan bagaimana Anda akan menerapkan ini atau memastikan bahwa penerapannya tidak pernah diperlukan (dengan asumsi bahwa perangkat lunak akan selalu memiliki bug)?
Jawaban:
Secara keseluruhan ini adalah strategi yang valid dan berharga. Inilah beberapa pemikiran.
Strategi ini juga dikenal sebagai "telemetri" dalam arti bahwa ketika semua informasi tersebut digabungkan, mereka membantu "melakukan pelacakan" jejak eksekusi dan memungkinkan pemecah masalah untuk memahami apa yang coba dicapai oleh pengguna / aplikasi, dan apa yang sebenarnya terjadi .
Beberapa data penting yang harus dikumpulkan (yang kita semua tahu) adalah:
Seringkali, pendekatan logging tradisional gagal, karena kegagalan untuk melacak pesan log tingkat rendah kembali ke perintah tingkat tertinggi yang memicu itu. Jejak tumpukan hanya menangkap nama-nama fungsi yang lebih tinggi yang membantu menangani perintah tingkat tertinggi, bukan rincian (data) yang kadang-kadang diperlukan untuk mengkarakterisasi perintah itu.
Biasanya perangkat lunak tidak ditulis untuk mengimplementasikan persyaratan keterlacakan semacam ini. Ini membuat menghubungkan pesan tingkat rendah ke perintah tingkat tinggi menjadi lebih sulit. Masalahnya terutama lebih buruk dalam sistem multi-utas secara bebas, di mana banyak permintaan dan respons dapat tumpang tindih, dan pemrosesan mungkin diturunkan ke utas yang berbeda dari utas penerima-permintaan semula.
Dengan demikian, untuk mendapatkan nilai maksimal dari telemetri, perubahan arsitektur perangkat lunak secara keseluruhan akan diperlukan. Sebagian besar antarmuka dan panggilan fungsi perlu dimodifikasi untuk menerima dan menyebarkan argumen "pelacak".
Bahkan fungsi utilitas perlu menambahkan argumen "pelacak", sehingga jika gagal, pesan log akan membiarkan dirinya dikorelasikan dengan perintah tingkat tinggi tertentu.
Kegagalan lain yang akan membuat penelusuran telemetri menjadi sulit adalah referensi objek yang hilang (null pointer atau referensi). Ketika sebagian data penting hilang, mungkin mustahil melaporkan apa pun yang berguna untuk kegagalan tersebut.
Dalam hal menulis pesan log:
sumber
Bayangkan Anda memiliki fungsi utilitas sepele yang digunakan di ratusan tempat dalam kode Anda:
Jika kami melakukan seperti yang Anda sarankan, kami mungkin menulis
Kesalahan yang bisa terjadi adalah jika inputnya nol; ini akan menghasilkan pembagian dengan pengecualian nol.
Jadi katakanlah Anda melihat 27349262 dalam output atau log Anda. Di mana Anda melihat untuk menemukan kode yang melewati nilai nol? Ingat, function-- dengan ID uniknya-- digunakan di ratusan tempat. Jadi Anda sementara Anda tahu bahwa pembagian dengan nol terjadi, Anda tidak tahu siapa
0
itu.Menurut saya jika Anda akan repot-repot mencatat ID pesan, Anda sebaiknya juga mencatat jejak stack.
Jika verbosity dari jejak stack adalah apa yang mengganggu Anda, Anda tidak perlu membuangnya sebagai string seperti yang diberikan runtime kepada Anda. Anda dapat menyesuaikannya. Misalnya, jika Anda ingin jejak stack disingkat hanya pergi ke
n
level, Anda bisa menulis sesuatu seperti ini (jika Anda menggunakan c #):Dan gunakan seperti ini:
Keluaran:
Mungkin lebih mudah daripada mempertahankan ID pesan, dan lebih fleksibel.
Curi kode saya dari DotNetFiddle
sumber
SAP NetWeaver melakukan ini selama beberapa dekade.
Ini telah terbukti menjadi alat yang berharga ketika kesalahan pemecahan masalah dalam raksasa kode raksasa yang merupakan sistem SAP ERP khas.
Pesan kesalahan dikelola dalam repositori pusat tempat setiap pesan diidentifikasi berdasarkan kelas pesan dan nomor pesannya.
Saat Anda ingin mengeluarkan pesan kesalahan, Anda hanya menyatakan variabel kelas, jumlah, tingkat keparahan dan spesifik pesan. Representasi teks dari pesan dibuat saat runtime. Anda biasanya melihat kelas dan nomor pesan dalam konteks apa pun di mana pesan muncul. Ini memiliki beberapa efek rapi:
Anda dapat secara otomatis menemukan baris kode apa pun di basis kode ABAP yang membuat pesan kesalahan tertentu.
Anda dapat mengatur breakpoint debugger dinamis yang memicu ketika pesan kesalahan tertentu dihasilkan.
Anda dapat mencari kesalahan dalam artikel basis pengetahuan SAP dan mendapatkan hasil pencarian yang lebih relevan daripada jika Anda mencari "Tidak dapat menemukan Foo".
Representasi teks dari pesan dapat diterjemahkan. Jadi dengan mendorong penggunaan pesan, bukan string, Anda juga mendapatkan kemampuan i18n.
Contoh sembulan galat dengan nomor pesan:
Mencari kesalahan itu di repositori kesalahan:
Temukan di basis kode:
Namun, ada kekurangannya. Seperti yang Anda lihat, baris kode ini tidak mendokumentasikan diri sendiri lagi. Saat Anda membaca kode sumber dan melihat
MESSAGE
pernyataan seperti yang ada di tangkapan layar di atas, Anda hanya dapat menyimpulkan dari konteks apa arti sebenarnya. Juga, terkadang orang menerapkan penangan kesalahan khusus yang menerima kelas dan nomor pesan pada saat runtime. Dalam hal ini kesalahan tidak dapat ditemukan secara otomatis atau tidak dapat ditemukan di lokasi di mana kesalahan terjadi. Solusi untuk masalah pertama adalah membuatnya menjadi kebiasaan untuk selalu menambahkan komentar dalam kode sumber memberitahu pembaca apa arti pesan tersebut. Yang kedua diselesaikan dengan menambahkan beberapa kode mati untuk memastikan pencarian pesan otomatis berfungsi. Contoh:Tetapi ada beberapa situasi di mana ini tidak mungkin. Misalnya ada beberapa alat pemodelan proses bisnis berbasis UI di mana Anda dapat mengonfigurasi pesan kesalahan agar muncul ketika aturan bisnis dilanggar. Implementasi alat-alat itu sepenuhnya didorong oleh data, sehingga kesalahan ini tidak akan muncul dalam daftar tempat-digunakan. Itu berarti terlalu mengandalkan daftar tempat digunakan ketika mencoba menemukan penyebab kesalahan bisa menjadi ikan haring merah.
sumber
Masalah dengan pendekatan itu adalah bahwa itu mengarah ke penebangan yang lebih rinci. 99,9999% darinya Anda tidak akan pernah melihatnya.
Sebagai gantinya, saya sarankan menangkap negara pada awal proses Anda dan keberhasilan / kegagalan proses.
Ini memungkinkan Anda untuk mereproduksi bug secara lokal, melangkah melalui kode dan membatasi login Anda ke dua tempat per proses. misalnya.
Sekarang saya dapat menggunakan kondisi yang sama persis pada mesin dev saya untuk mereproduksi kesalahan, melangkah melalui kode di debugger saya dan menulis unit test baru untuk mengkonfirmasi perbaikan.
Selain itu, saya dapat jika diminta menghindari lebih banyak pencatatan dengan hanya mencatat kegagalan atau mempertahankan status di tempat lain (basis data? Antrian pesan?)
Jelas kami harus ekstra hati-hati dalam mencatat data sensitif. Jadi ini berfungsi dengan baik jika solusi Anda menggunakan antrian pesan atau pola penyimpanan acara. Sebagai log hanya perlu mengatakan "Pesan xyz Gagal"
sumber
Saya akan menyarankan bahwa logging bukan cara untuk menyelesaikan hal ini, melainkan bahwa keadaan ini dianggap luar biasa (ini mengunci program Anda) dan pengecualian harus dilemparkan. Katakan kode Anda:
Sepertinya Anda memanggil kode tidak diatur untuk berurusan dengan fakta bahwa Foo tidak ada dan Anda mungkin berpotensi:
Dan ini akan mengembalikan jejak stack bersama dengan pengecualian yang dapat digunakan untuk membantu debugging.
Sebagai alternatif, jika kami berharap bahwa Foo dapat menjadi nol ketika diambil kembali dan itu baik-baik saja, kami perlu memperbaiki situs yang memanggil:
Fakta bahwa perangkat lunak Anda hang atau bertindak 'aneh' dalam keadaan yang tidak terduga tampaknya salah bagi saya - jika Anda memerlukan Foo dan tidak dapat menanganinya tidak ada di sana, maka tampaknya lebih baik untuk crash daripada mencoba melanjutkan sepanjang jalan yang mungkin merusak sistem Anda.
sumber
Pustaka logging yang benar menyediakan mekanisme ekstensi, jadi jika Anda ingin mengetahui metode asal pesan log, mereka bisa melakukannya di luar kotak. Itu memang berdampak pada eksekusi karena proses ini membutuhkan menghasilkan jejak stack dan melewatinya sampai Anda keluar dari perpustakaan logging.
Yang mengatakan, itu benar-benar tergantung pada apa yang ingin ID Anda lakukan untuk Anda:
Semua hal ini dapat dilakukan di luar kotak dengan perangkat lunak logging yang tepat (yaitu tidak
Console.WriteLine()
atauDebug.WriteLine()
).Secara pribadi, yang lebih penting adalah kemampuan merekonstruksi jalur eksekusi. Itulah yang ingin dicapai oleh alat seperti Zipkin . Satu ID untuk melacak perilaku satu tindakan pengguna di seluruh sistem. Dengan meletakkan log Anda di mesin pencari pusat, Anda tidak hanya dapat menemukan tindakan berjalan terpanjang, tetapi memanggil log yang berlaku untuk satu tindakan (seperti tumpukan ELK ).
ID buram yang berubah dengan setiap pesan tidak terlalu berguna. ID yang konsisten digunakan untuk melacak perilaku melalui seluruh rangkaian layanan ... sangat berguna.
sumber