Pylint: tes terperinci dari penganalisa kode

Ketika Luke bekerja dengan Flake8 dan mengawasi Pylint, ia mendapat kesan bahwa 95% kesalahan yang dihasilkan oleh Pylint adalah salah. Pengembang lain memiliki pengalaman berbeda dalam berinteraksi dengan analisis ini, sehingga Luke memutuskan untuk melihat situasi secara rinci dan mempelajari karyanya pada 11 ribu baris kodenya. Selain itu, ia memuji manfaat Pylint, melihatnya sebagai tambahan untuk Flake8.



Luke ( Luke Plant ) - salah satu pengembang Inggris, yang artikelnya dengan analisis penganalisa kode populer baru-baru ini kami temui. Linters mempelajari kode, membantu Anda menemukan bug, dan membuatnya sesuai gaya dengan standar dan kode yang ditulis oleh pengembang di tim Anda. Yang paling umum adalah Pylint dan Flake8. Kami berada di Leader-IDKami juga menggunakannya, jadi kami dengan senang hati menerjemahkan artikelnya. Kami berharap ini akan memperkaya pengalaman Anda dengan alat-alat ini.

Pengaturan awal dan basis uji


Untuk percobaan ini, saya mengambil bagian dari kode dari salah satu proyek saya dan meluncurkan Pylint dengan pengaturan dasar. Kemudian ia mencoba menganalisis hasilnya: peringatan mana yang bermanfaat dan mana yang salah.

Sedikit bantuan tentang proyek tempat kode diambil:

  • Aplikasi reguler yang ditulis dalam bahasa Django (mis. Di dalam Python yang sama). Django memiliki kekhasan sendiri, dan, sebagai suatu kerangka, ia memiliki keterbatasan, tetapi memungkinkan Anda untuk menulis kode Python normal. Perpustakaan lain yang menggunakan templat (fungsi callback atau templat desain Metode Templat) juga memiliki beberapa kelemahan sebagai kerangka kerja.
  • Terdiri dari 22.000 baris kode. Sekitar 11.000 garis melewati Pylint (9.000 jika ada celah yang dihilangkan). Bagian dari proyek ini terutama terdiri dari pandangan dan kode uji.
  • Untuk menganalisis kode untuk proyek ini, saya sudah menggunakan Flake8, setelah memproses semua kesalahan yang diterima. Inti dari percobaan ini adalah untuk mengevaluasi manfaat Pylint sebagai tambahan pada Flake8.
  • Proyek ini memiliki cakupan tes kode yang baik, tetapi karena saya adalah satu-satunya penulis, saya tidak memiliki kesempatan untuk menggunakan peer review.

Saya berharap bahwa analisis ini akan berguna bagi pengembang lain untuk memutuskan menggunakan Pylint dalam tumpukan untuk memeriksa kualitas kode. Saya berasumsi bahwa jika Anda sudah menggunakan sesuatu seperti Pylint, Anda akan menggunakannya secara sistematis untuk kontrol kualitas kode yang diperlukan untuk meminimalkan masalah.
Jadi, Pylint telah mengeluarkan 1.650 klaim ke kode saya.

Di bawah ini, saya mengelompokkan semua komentar penganalisa berdasarkan jenis dan memberikan komentar saya kepada mereka. Jika Anda memerlukan deskripsi rinci tentang pesan-pesan ini, lihat bagian yang sesuai dari daftar fungsi Pylint.

Bug


Pylint menemukan persis satu bug dalam kode saya. Saya menganggap bug sebagai kesalahan yang terjadi atau berpotensi muncul selama pengoperasian program. Dalam hal ini, saya menggunakan pengecualian - broad-except.Artinya except Exception, bukan hanya pengecualian , yang ditangkap Flake8. Ini akan menghasilkan perilaku runtime dengan beberapa pengecualian. Jika kesalahan ini pernah muncul pada saat dijalankan (bukan fakta bahwa itu muncul), maka perilaku kode yang salah tidak akan memiliki konsekuensi serius, meskipun ...

Total: 1

Berguna


Selain kesalahan itu, Pylint menemukan beberapa lagi yang saya kategorikan berguna. Kode tidak jatuh dari mereka, tetapi mungkin ada masalah selama refactoring, dan pada prinsipnya, kesalahan di masa depan ketika memperluas daftar fitur dan mendukung kode.

Tujuh dari mereka adalah too-many-locals/ too-many-branches/ too-many-local-variables. Mereka termasuk dalam tiga bagian kode saya yang tidak terstruktur dengan baik. Akan menyenangkan memikirkan strukturnya, dan saya yakin saya bisa melakukan yang lebih baik.

Kesalahan lain:

  • unused-argumentΓ— 3 - salah satunya benar-benar tiang, dan kode dieksekusi dengan benar secara acak. Dua argumen lain yang tidak perlu dan tidak terpakai akan menyebabkan masalah di masa depan jika saya menggunakannya.
  • redefined-builtin Γ— 2
  • dangerous-default-value Γ— 2 - bukan bug, karena saya tidak pernah menggunakan nilai default, tetapi akan lebih baik untuk memperbaikinya kalau-kalau.
  • stop-iteration-return Γ— 1 - di sini saya belajar sesuatu yang baru untuk diri saya sendiri, saya tidak akan pernah menemukannya sendiri.
  • no-self-argument Γ— 1

Total: 16

Suntingan kosmetik


Saya akan kurang memperhatikan hal-hal ini. Mereka tidak signifikan atau tidak mungkin. Di sisi lain, koreksi mereka tidak akan berlebihan. Beberapa dari mereka adalah gaya yang kontroversial. Saya berbicara tentang beberapa tiang yang serupa di bagian lain, tetapi yang tercantum di sini juga cocok untuk konteks ini. Dengan menggunakan Pylint secara teratur, saya akan memperbaiki "kekurangan" ini, tetapi dalam kebanyakan kasus saya tidak akan mengkhawatirkannya.

invalid-nameΓ— 192

Ini pada dasarnya adalah nama variabel huruf tunggal. Digunakan dalam konteks yang tidak menyeramkan, misalnya:


atau


Banyak yang ada dalam kode tes:

  • len-as-condition Γ— 20
  • useless-object-inheritance Γ— 16 (warisan Python 2)
  • no-else-return Γ— 11
  • no-else-raise Γ— 1
  • bad-continuation Γ— 6
  • redefined-builtin Γ— 4
  • inconsistent-return-statements Γ— 1
  • consider-using-set-comprehension Γ— 1
  • chained-comparison Γ— 1

JUMLAH: 252

Tak berguna


Ini adalah ketika saya punya alasan untuk menulis kode seperti yang saya tulis, bahkan jika di beberapa tempat sepertinya tidak biasa. Dan, menurut pendapat saya, lebih baik meninggalkannya dalam bentuk ini, meskipun beberapa tidak akan setuju atau lebih suka gaya penulisan kode yang berbeda yang berpotensi menghindari masalah.

  • too-many-ancestors Γ— 76

Ada dalam kode uji di mana saya menggunakan banyak kelas pengotor untuk meletakkan utilitas atau bertopik.

  • unused-variable Γ— 43

Ditemukan hampir sepanjang waktu dalam kode tes, di mana saya memecahkan rekor:


... dan tidak menggunakan elemen apa pun. Ada beberapa cara untuk mencegah Pylint melaporkan kesalahan (misalnya, beri nama unused). Tetapi jika Anda meninggalkannya dalam bentuk yang saya tulis, itu akan dapat dibaca, dan orang-orang (termasuk saya) akan dapat memahami dan mendukungnya.

  • invalid-name Γ— 26

Ini adalah kasus di mana saya menetapkan nama yang sesuai dalam konteks, tetapi yang tidak memenuhi standar penamaan Pylint. Sebagai contoh, db (ini adalah singkatan umum untuk database) dan beberapa nama non-standar lainnya, yang, menurut pendapat saya, lebih mudah dimengerti. Tapi Anda mungkin tidak setuju dengan saya.

  • redefined-outer-name Γ— 16

Terkadang nama variabel dieja dengan benar untuk konteks internal dan eksternal. Dan Anda tidak akan pernah harus menggunakan nama eksternal dari konteks internal.

  • too-few-public-methods Γ— 14

Contohnya termasuk kelas dengan data yang dibuat menggunakan attrs , di mana tidak ada metode publik, dan kelas yang mengimplementasikan antarmuka kamus, tetapi yang diperlukan untuk memastikan metode bekerja dengan benar__getitem__

  • no-self-use Γ— 12

Mereka muncul dalam kode tes, di mana saya sengaja menambahkan metode ke kelas dasar tanpa parameter self, karena dengan cara ini lebih mudah untuk mengimpornya dan membuatnya tersedia untuk test case. Beberapa di antaranya bahkan dibungkus dalam fungsi yang terpisah.

  • attribute-defined-outside-init Γ— 10

Dalam kasus ini, ada alasan bagus untuk menulis kode apa adanya. Pada dasarnya, kesalahan ini terjadi pada kode uji.

  • too-many-localsΓ— 6, too-many-return-statementsΓ— 6, too-many-branchesΓ— 2, too-many-statementsΓ— 2

Ya, fitur ini terlalu panjang. Tetapi melihat mereka, saya tidak melihat cara yang baik untuk membersihkan dan memperbaikinya. Salah satu fiturnya sederhana, meski panjang. Itu memiliki struktur yang sangat jelas dan tidak ditulis miring, dan segala cara untuk menguranginya yang dapat saya pikirkan akan mencakup lapisan yang tidak berguna dari fungsi yang tidak perlu atau tidak nyaman.

  • arguments-differ Γ— 6

Hal ini terutama disebabkan oleh penggunaan * args dan ** kwargs dalam metode yang diganti, yang memungkinkan Anda melindungi diri Anda dari perubahan tanda tangan metode dari pustaka pihak ketiga (tetapi dalam beberapa kasus pesan ini mungkin menunjukkan bug nyata).

  • ungrouped-imports Γ— 4

Saya sudah menggunakan isort untuk mengimpor

  • fixme Γ— 4

Ya, ada beberapa hal yang perlu diperbaiki, tetapi saat ini saya tidak ingin memperbaikinya.

  • duplicate-code Γ— 3

Kadang-kadang Anda menggunakan sejumlah kecil kode boilerplate, yang hanya perlu, dan ketika tidak ada banyak logika nyata di badan fungsi, peringatan ini terbang.

  • broad-except Γ— 2
  • abstract-method Γ— 2
  • redefined-builtin Γ— 2
  • too-many-lines Γ— 1

Saya mencoba mencari cara alami untuk memecahkan modul ini, tetapi tidak bisa. Ini adalah salah satu contoh di mana Anda dapat melihat bahwa linter adalah alat yang salah. Jika saya memiliki modul dengan 980 baris kode, dan saya menambahkan 30 lainnya, saya melewati batas 1000 baris, dan pemberitahuan dari linter tidak akan membantu saya di sini. Jika 980 baris baik-baik saja, mengapa 1010 buruk? Saya tidak ingin memperbaiki modul ini, tetapi saya ingin linter tidak menghasilkan kesalahan. Satu-satunya solusi pada saat ini yang saya lihat adalah membuat entah bagaimana agar orang itu diam, dan ini bertentangan dengan tujuan akhir.

  • pointless-statement Γ— 1
  • expression-not-assigned Γ— 1
  • cyclic-import Γ— 1

Kami menemukan siklus dengan memindahkan bagian-bagiannya ke salah satu fungsi. Saya tidak dapat menemukan cara yang lebih baik untuk menyusun kode berdasarkan pembatasan.

  • unused-import Γ— 1

Saya sudah menambahkan # NOQAketika menggunakan Flake8 sehingga kesalahan ini tidak muncul.

  • too-many-public-methods Γ— 1

Jika di kelas tes saya ada 35 tes, bukan 20 tes yang diatur, apakah ini benar-benar masalah?

  • too-many-arguments Γ— 1

Total: 243

Tidak dapat diperbaiki


Kategori ini mencerminkan kesalahan yang tidak dapat saya perbaiki walaupun saya mau, karena batasan eksternal, seperti kebutuhan untuk mengembalikan atau meneruskan kelas ke perpustakaan atau kerangka kerja pihak ketiga yang harus memenuhi persyaratan tertentu.

  • unused-argument Γ— 21
  • invalid-name Γ— 13
  • protected-access Γ— 3

Termasuk akses ke "objek internal yang terdokumentasi", seperti sys._getframedi stdlib dan Django Model._meta.

  • too-few-public-methods Γ— 3
  • too-many-arguments Γ— 2
  • wrong-import-position Γ— 2
  • attribute-defined-outside-init Γ— 1
  • too-many-ancestors Γ— 1

Total: 46

Pesan palsu


Hal-hal di mana Pylint jelas salah. Dalam kasus ini, ini bukan kesalahan Pylint: faktanya adalah Python dinamis, dan Pylint berusaha menemukan hal-hal yang tidak dapat dilakukan dengan sempurna atau andal.

  • no-member Γ— 395

Terkait dengan beberapa kelas dasar: dari Django dan yang saya buat sendiri. Pylint tidak dapat mendeteksi variabel karena dinamisme / metaprogramming.

Banyak kesalahan terjadi karena struktur kode tes (saya menggunakan templat dari Django-functest, yang dalam beberapa kasus dapat diperbaiki dengan menambahkan kelas dasar tambahan menggunakan metode "abstrak" yang memanggil NotImplementedError) atau, mungkin, mengubah nama banyak kelas uji (I Saya tidak melakukannya, karena dalam beberapa kasus ini akan membingungkan).

  • invalid-name Γ— 52

Masalah muncul terutama karena Pylint menerapkan aturan konstan PEP8 , mengingat bahwa setiap nama tingkat atas yang didefinisikan dengan =adalah "konstan". Mendefinisikan dengan tepat apa yang kita maksudkan dengan konstanta lebih sulit daripada yang terlihat, tetapi ini tidak berlaku untuk beberapa hal yang secara inheren adalah konstanta, misalnya fungsi. Selain itu, aturan tersebut tidak boleh diterapkan pada cara yang kurang dikenal untuk membuat fungsi, misalnya:


Beberapa contoh masih bisa diperdebatkan karena kurangnya definisi tentang apa yang disebut konstanta. Sebagai contoh, haruskah instance kelas didefinisikan pada tingkat modul, yang mungkin atau mungkin tidak memiliki keadaan bisa berubah, dianggap konstan? Misalnya, dalam ungkapan ini:


  • no-self-use Γ— 23

Metode Pylint salah menyatakan bisa menjadi fungsi untuk banyak kasus di mana saya menggunakan warisan untuk melakukan berbagai implementasi, masing-masing, saya tidak dapat mengubahnya menjadi fungsi.

  • protected-access Γ— 5

Pylint salah menilai siapa β€œpemilik” (fragmen kode saat ini membuat atribut yang dilindungi objek dan menggunakannya secara lokal, tetapi Pylint tidak melihat ini).

  • no-name-in-module Γ— 1
  • import-error Γ— 1
  • pointless-statement Γ— 1

Pernyataan ini benar-benar menghasilkan hasilnya:


Saya menggunakan ini untuk sengaja menyebabkan kesalahan yang tidak biasa yang tidak mungkin ditemukan oleh tes. Saya tidak menyalahkan Pylint karena tidak mengenalinya ...

Total: 477

Subtotal


Kami belum berada di garis akhir, tetapi sekarang saatnya untuk mengelompokkan hasil kami:

  1. Blok "Bagus" - "Bug" dan "Utilitas" - di sini Pylint pasti membantu: 17.
  2. "Netral" - "Perubahan kosmetik" - manfaat kecil dari Pylint, kesalahan tidak akan menyebabkan kerusakan: 252.
  3. "Buruk" - "Tidak berguna", "Tidak dapat diperbaiki", "Ketidaktepatan" - di mana Pylint menginginkan perubahan dalam kode, di mana itu tidak diperlukan. Termasuk di mana pengeditan tidak dapat dilakukan karena dependensi eksternal atau di mana Pylint menganalisis kode dengan salah: 766.

Rasio antara baik dan buruk sangat kecil. Jika Pylint adalah kolega saya yang membantu meninjau kode, saya akan memintanya pergi.

Untuk menghapus pemberitahuan palsu, Anda dapat menghilangkan kelas kesalahan keluar (yang kemudian akan meningkatkan kegunaan dari menggunakan Pylint) atau menambahkan komentar khusus ke kode. Saya tidak ingin melakukan yang terakhir, karena:

  1. Ini membutuhkan waktu!
  2. Saya tidak suka menumpuk komentar yang ada untuk membungkam orang yang mabuk.

Saya dengan senang hati akan menambahkan komentar semu ini ketika akan ada nilai tambah yang pasti dari linter. Selain itu, saya cemas tentang komentar, jadi penyorotan sintaksis saya menampilkannya dengan jelas: seperti yang direkomendasikan dalam artikel ini . Namun, di beberapa tempat saya telah menambahkan # komentar NOQAuntuk meredam Flake8, tetapi dengan mereka untuk satu bagian Anda hanya dapat menambahkan lima kode kesalahan.

Docstrings


Masalah yang tersisa yang ditemukan Pylint adalah garis dokumentasi yang hilang. Saya menempatkan mereka dalam kategori terpisah karena:

  1. Mereka sangat kontroversial, dan Anda mungkin memiliki kebijakan yang sangat berbeda mengenai hal-hal seperti itu.
  2. Saya tidak punya waktu untuk menganalisis semuanya.

Secara total, Pylint menemukan 620 galangan yang hilang (dalam modul, fungsi, metode kelas). Tetapi dalam banyak kasus, saya benar. Contohnya:

  • Ketika semuanya jelas dari namanya. Contohnya:
  • Ketika docstring sudah didefinisikan - misalnya, jika saya mengimplementasikan antarmuka router database Django . Menambahkan baris Anda sendiri dalam kasus ini bisa berbahaya.

Dalam kasus lain, baris deskripsi ini tidak akan mengganggu kode saya. Dalam sekitar 15-30% kasus yang ditemukan oleh Pylint, saya akan berpikir, "Ya, saya perlu menambahkan dokumen di sini, terima kasih Pylint untuk pengingatnya."

Secara umum, saya tidak suka alat yang membuat menambahkan tali galangan di mana-mana dan di mana-mana, karena saya pikir dalam hal ini akan menjadi buruk. Lebih baik tidak menulisnya sama sekali. Situasi serupa dengan komentar buruk:

  • membacanya adalah buang-buang waktu: mereka tidak memiliki informasi tambahan atau mengandung informasi yang salah,
  • mereka membantu secara tidak sadar menyoroti dokumen dalam teks, karena mengandung informasi yang berguna (jika Anda menuliskannya dalam case).

Peringatan tentang garis-garis dokumentasi yang hilang itu menjengkelkan: untuk menghapusnya, Anda perlu menambahkan komentar secara terpisah, yang membutuhkan waktu yang sama dengan menambahkan dok itu sendiri. Plus itu semua menciptakan kebisingan visual. Sebagai hasilnya, Anda akan mendapatkan baris dokumentasi yang tidak perlu atau tidak berguna.

Kesimpulan


Saya percaya bahwa asumsi awal saya tentang ketidakgunaan Pylint ternyata benar (dalam konteks basis kode yang saya gunakan Flake8). Agar saya dapat menggunakannya, indikator yang mencerminkan jumlah positif palsu harus lebih rendah.

Selain membuat kebisingan visual, waktu tambahan diperlukan untuk memilah atau memfilter pemberitahuan palsu, dan saya tidak ingin menambahkan semua ini ke proyek. Pengembang junior akan lebih rendah hati untuk melakukan pengeditan untuk menghapus komentar Pylint. Tetapi ini akan menyebabkan mereka melanggar kode kerja, tidak menyadari bahwa Pylint salah, atau pada kenyataan bahwa sebagai hasilnya Anda akan memiliki banyak refactoring sehingga Pylint dapat memahami kode Anda dengan benar.

Jika Anda menggunakan Pylint dalam sebuah proyek sejak awal atau dalam sebuah proyek di mana tidak ada dependensi eksternal, saya pikir Anda akan memiliki pendapat yang berbeda dan jumlah pemberitahuan palsu akan lebih sedikit. Namun, ini dapat menyebabkan biaya waktu tambahan.

Pendekatan lain adalah dengan menggunakan Pylint untuk sejumlah jenis kesalahan. Namun, hanya ada sedikit, tanggapan yang ternyata benar atau sangat jarang salah (dalam istilah relatif dan absolut). Diantaranya adalah: dangerous-default-value, stop-iteration-return, broad-exception, useless-object-inheritance.

Bagaimanapun, saya harap artikel ini membantu Anda ketika mempertimbangkan apakah akan menggunakan Pylint atau dalam perselisihan dengan rekan kerja.

All Articles