Analisis statis volume besar kode Python: pengalaman Instagram. Bagian 2

Hari ini kami menerbitkan bagian kedua dari terjemahan materi yang ditujukan untuk analisis statis volume besar kode Python sisi-server di Instagram.



β†’ Bagian pertama

Programmer yang sudah bosan dengan linting


Mempertimbangkan bahwa kita memiliki sekitar seratus aturan linting kita sendiri, penghitungan rekomendasi yang dikeluarkan oleh aturan ini dengan cepat dapat dengan cepat menghabiskan waktu pengembang. Akan lebih baik menghabiskan waktu yang dihabiskan untuk meluruskan gaya kode atau menyingkirkan pola usang untuk membuat sesuatu yang baru dan untuk mengembangkan proyek.

Kami menemukan bahwa ketika programmer melihat terlalu banyak notifikasi yang berasal dari linter, mereka mulai mengabaikan semua pesan ini. Ini juga berlaku untuk pemberitahuan penting.
Misalkan kita memutuskan untuk mendeklarasikan fungsi fn usang dan menggunakan fungsi dengan nama yang lebih baik, add . Jika Anda tidak memberi tahu pengembang tentang hal ini, mereka tidak akan tahu bahwa mereka tidak perlu lagi menggunakan fungsi fn . Lebih buruk lagi, mereka tidak tahu apa yang harus digunakan daripada fungsi ini. Dalam situasi ini, Anda dapat membuat aturan linter. Tetapi basis kode besar apa pun sudah akan berisi banyak aturan. Akibatnya, kemungkinan notifikasi linter penting akan hilang di tumpukan notifikasi bug minor.


Linter terlalu nitpicking dan "sinyal berguna" dapat dengan mudah tersesat dalam "noise"

Apa yang akan kita lakukan dengan ini?

Anda dapat secara otomatis memperbaiki banyak masalah yang terdeteksi oleh linter. Jika linter itu sendiri dapat dibandingkan dengan dokumentasi yang muncul di mana diperlukan, maka koreksi otomatis semacam itu sedikit refactoring kode yang dieksekusi di mana ia diperlukan. Mengingat banyaknya pengembang yang bekerja di Instagram, hampir mustahil untuk melatih mereka masing-masing dalam teknik penulisan kode terbaik kami. Menambahkan kemampuan koreksi kode otomatis ke sistem memungkinkan kami untuk mendidik pengembang tentang teknik baru ketika mereka tidak mengetahui teknik ini. Ini membantu kami dengan cepat memperbarui pengembang. Koreksi otomatis, di samping itu, memungkinkan kami untuk membuat pemrogram fokus pada hal-hal penting, daripada berfokus pada perubahan kode kecil yang monoton. Secara umum, dapat dicatat bahwa koreksi kode otomatis lebih efektif dan berguna dalam hal pengembang pelatihan daripada pemberitahuan linter sederhana.

Jadi, bagaimana cara membuat sistem untuk koreksi kode otomatis? Lint berbasis pohon sintaks memberi kita informasi tentang simpul disfungsional. Akibatnya, kita tidak perlu membuat logika untuk mendeteksi masalah, karena kita sudah memiliki aturan yang sesuai untuk linter! Karena kita tahu simpul mana yang tidak cocok untuk kita, dan tentang di mana kode sumbernya berada, kita dapat, tanpa risiko merusak sesuatu, misalnya, mengganti nama fungsi fn dengan add . Ini sangat cocok untuk memperbaiki satu pelanggaran aturan yang dieksekusi karena pelanggaran tersebut terdeteksi. Tetapi bagaimana jika kita memperkenalkan aturan baru untuk linter, yang berarti ada ratusan fragmen kode dalam basis kode yang tidak mematuhi aturan ini? Bisakah semua ketidakkonsistenan ini diperbaiki terlebih dahulu?

Mod Kode


Codemod hanyalah cara untuk menemukan masalah dan membuat perubahan pada kode sumber. Codemods berbasis skrip. Codemod dapat dianggap sebagai "steroid refactoring". Rentang tugas yang dipecahkan oleh mode kode sangat luas: dari yang sederhana, seperti mengubah nama variabel dalam suatu fungsi, hingga yang kompleks, seperti menulis ulang suatu fungsi sehingga diperlukan argumen baru. Ketika bekerja dengan codemod, konsep yang sama digunakan seperti operasi linter. Tetapi alih-alih memberi tahu programmer tentang masalah, seperti yang dilakukan linter, mode kode secara otomatis menyelesaikan masalah ini.

Bagaimana cara menulis kode? Pertimbangkan sebuah contoh. Di sini kami ingin berhenti menggunakan get_global . Dalam situasi ini, Anda dapat menggunakan linter, tetapi tidak akan diketahui berapa lama waktu yang diperlukan untuk memperbaiki seluruh kode, di samping itu, tugas ini akan didistribusikan di antara banyak pengembang. Pada saat yang sama, bahkan jika proyek menggunakan sistem koreksi kode otomatis, mungkin butuh beberapa waktu untuk memproses semua kode.


Kami ingin menjauh dari menggunakan get_global dan menggunakan variabel instan sebagai gantinya

Untuk mengatasi masalah ini, kita bisa, bersama-sama dengan aturan linter yang mendeteksi itu, menulis kode kode. Kami percaya bahwa membiarkan pola dan API lama meninggalkan kode secara bertahap akan mengganggu pengembang dan menurunkan keterbacaan kode. Kami lebih memilih untuk segera menghapus kode usang, dan tidak melihat bagaimana kode itu secara bertahap menghilang dari proyek.

Mengingat volume kode kami dan jumlah pengembang aktif, ini seringkali berarti secara otomatis menghilangkan desain yang usang. Jika kami dapat dengan cepat menghapus kode dari pola yang usang, ini berarti kami dapat mempertahankan produktivitas semua pengembang Instagram.

Jadi, bagaimana cara membuat codemod? Bagaimana cara mengganti hanya fragmen kode yang menarik minat kami, sambil menjaga komentar, indentasi, dan yang lainnya? Ada alat yang didasarkan pada pohon sintaksis tertentu (seperti apa yang dibuat LibCST) yang memungkinkan Anda untuk memodifikasi kode dengan presisi bedah dan menyimpan semua konstruksi tambahan di dalamnya. Akibatnya, jika kita perlu mengubah nama fungsi dari fn untuk add di pohon di bawah ini, maka kita dapat menulis add alih-alih fn di simpul Name , dan kemudian menulis pohon ke disk!


Anda dapat melakukan mode kode dengan menulis tambahkan ke simpul Nama daripada fn. Kemudian pohon yang diubah dapat ditulis ke disk. Anda dapat membaca lebih lanjut tentang ini di dokumentasi LibCST.

Sekarang kita sudah terbiasa dengan mod kode, mari kita lihat contoh praktisnya. Karyawan Instagram bekerja keras untuk membuat basis kode proyek diketik sepenuhnya. Kodmody serius membantu mereka dalam hal ini.

Jika kita memiliki sekumpulan fungsi yang tidak diketik yang perlu diketik, kita dapat mencoba menghasilkan tipe yang dikembalikan oleh mereka dengan inferensi tipe yang biasa! Misalnya, jika suatu fungsi mengembalikan nilai hanya dari satu tipe primitif, kami cukup menetapkan jenis nilai pengembalian ini ke fungsi. Jika fungsi mengembalikan nilai tipe logis, misalnya, jika membandingkan sesuatu dengan sesuatu atau memeriksa sesuatu, maka kita dapat menetapkannya bool tipe nilai pengembalian. Kami menemukan bahwa dalam perjalanan kerja praktis dengan basis kode Instagram, ini adalah operasi yang cukup aman.


Mencari tahu jenis nilai yang dikembalikan oleh fungsi

Tetapi bagaimana jika fungsi tidak secara eksplisit mengembalikan nilai apa pun, atau secara implisit mengembalikan None ? Jika fungsi tidak secara eksplisit mengembalikan apa pun, maka dapat ditetapkan jenis None .

Ini, tidak seperti contoh sebelumnya, bisa lebih berbahaya karena adanya pola umum yang digunakan pengembang. Misalnya, dalam metode kelas dasar, Anda bisa melempar pengecualian NotImplemented , dan dalam metode subclass yang menimpa metode ini, Anda bisa mengembalikan string. Penting untuk dicatat bahwa semua teknik ini heuristik, tetapi hasil penerapannya sering kali ternyata benar. Akibatnya, mereka dapat dianggap berguna.


Fungsi Yang Tidak Mengembalikan Apa-apa

Memperluas Modul Kode dengan Pyre


Mari kita melangkah lebih jauh. Instagram menggunakan Pyre, sistem pengecekan tipe statis full-blown mirip dengan mypy. Menggunakan Pyre memungkinkan kita memeriksa jenis dalam basis kode. Bagaimana jika kita menggunakan data yang dihasilkan oleh Pyre untuk memperluas kemampuan codemod? Berikut ini adalah contoh dari data tersebut. Sangat mudah untuk melihat bahwa hampir semua yang Anda butuhkan untuk memperbaiki anotasi jenis secara otomatis!

 $ pyre Ζ› Found 2 type errors! testing/utils.py:7:0 Missing return annotation [3]: Returning `SomeClass` but no return type is specified. testing/utils.py:10:0 Missing return annotation [3]: Returning `testing.other.SomeOtherClass` but no return type is specified. 

Pyre selama pekerjaan melakukan analisis terperinci dari urutan eksekusi setiap fungsi. Akibatnya, alat ini kadang-kadang dengan probabilitas yang sangat tinggi dapat membuat asumsi bahwa fungsi yang tidak dicatat harus dikembalikan. Ini berarti bahwa jika Pyre percaya bahwa fungsi mengembalikan tipe sederhana, kami menetapkan fungsi ini tipe kembali. Namun, sekarang, secara potensial, kita perlu memproses perintah impor juga. Ini berarti bahwa kita perlu tahu apakah ada sesuatu yang diimpor atau dideklarasikan secara lokal. Nanti kita akan menyentuh topik ini sebentar.

Manfaat apa yang kita dapatkan dari secara otomatis menambahkan informasi jenis yang mudah ditampilkan dalam kode? Nah, tipe adalah dokumentasi! Jika fungsi sepenuhnya diketik, maka pengembang tidak perlu membaca kodenya untuk mengetahui fitur panggilannya dan fitur menggunakan apa yang dikembalikan.

 def get_description(page: WikiPage) -> Optional[str]:    if page.draft:        return None    return page.metadata["description"]  # <-    ? 

Banyak dari kita telah menemukan kode Python yang serupa. Basis kode Instagram juga memiliki sesuatu yang serupa. Jika fungsi get_description tidak diketik, maka Anda perlu melihat beberapa modul untuk mengetahui apa yang dikembalikan. Pada saat yang sama, bahkan jika kita berbicara tentang fungsi yang lebih sederhana, jenis nilai pengembalian yang mudah diturunkan, varian yang diketiknya dirasakan lebih mudah daripada yang tidak diketik.

Selain itu, Pyre tidak memverifikasi operasi yang benar dari badan fungsi jika fungsi tersebut tidak sepenuhnya dijelaskan. Dalam contoh berikut, panggilan ke some_function akan gagal. Akan menyenangkan untuk mengetahui hal ini sebelum kode masuk ke produksi.

 def some_function(in: int) -> bool:    return in > 0 def some_other_function():    if some_function("bla"): # <-             print("Yay!") 

Dalam hal ini, kami dapat mencari tahu tentang kesalahan yang sama setelah kode masuk ke produksi. Faktanya adalah some_other_function tidak memiliki anotasi tipe return. Jika kita mencatatnya menggunakan mekanisme heuristik kita menggunakan tipe None disimpulkan secara otomatis, maka kita akan menemukan masalah dengan jenisnya sebelum dapat menyebabkan masalah. Ini, tentu saja, adalah contoh buatan, tetapi di Instagram masalah seperti itu serius. Jika Anda memiliki jutaan baris kode, maka Anda, dalam proses peninjauan kode, mungkin akan kehilangan hal-hal yang tampak sangat jelas dalam contoh sederhana.

Di Instagram, metode di atas berdasarkan tipe yang dideduksi secara otomatis memungkinkan pengetikan sekitar 10% dari fungsi. Akibatnya, orang tidak lagi harus secara manual mengedit ribuan fungsi. Kelebihan dari kode yang diketik jelas, tetapi ini, dalam konteks percakapan kami, mengarah ke keuntungan penting lainnya. Basis kode yang diketik sepenuhnya membuka kemungkinan yang lebih besar untuk memproses kode menggunakan codemods.

Jika kami memercayai anotasi jenis, itu artinya Pyre dapat membuka kemungkinan tambahan untuk kami. Mari kita lihat kembali contoh di mana kita mengganti nama fungsinya. Bagaimana jika entitas yang kita beri nama baru diwakili oleh metode kelas dan bukan fungsi global?


Fungsi adalah metode kelas

Jika Anda menggabungkan informasi jenis yang diterima dari Pyre dan mode kode yang mengubah nama fungsi, Anda dapat, secara tak terduga, melakukan koreksi ke tempat fungsi dipanggil dan di mana ia diumumkan! Dalam contoh ini, karena kita tahu apa yang ada di sisi kiri konstruk a.fn , kita juga tahu bahwa mengubah konstruk ini menjadi a.add .

Analisis statis lebih maju



Python memiliki empat jenis cakupan: ruang lingkup global, ruang lingkup tingkat kelas dan fungsi, ruang lingkup bersarang

Analisis cakupan memungkinkan kita untuk menggunakan codemod yang lebih kuat. Ingat salah satu contoh di atas, di mana kita berbicara tentang fakta bahwa menambahkan anotasi jenis juga dapat berarti perlunya bekerja dengan perintah impor? Jika sistem menganalisis ruang lingkup, ini berarti bahwa kita dapat mengetahui jenis mana yang digunakan dalam file yang ada di dalamnya berkat perintah impor, yang dideklarasikan secara lokal, dan mana yang hilang. Demikian pula, jika Anda tahu bahwa variabel global tumpang tindih oleh argumen fungsi, Anda dapat menghindari secara tidak sengaja mengubah nama argumen tersebut saat mengganti nama variabel global.

Ringkasan


Dalam upaya kami untuk memperbaiki semua kesalahan dalam kode Instagram, kami memahami satu hal. Terdiri dari fakta bahwa pencarian kode yang perlu diperbaiki seringkali lebih penting daripada perbaikan itu sendiri. Pemrogram sering harus menyelesaikan tugas-tugas sederhana - seperti mengganti nama fungsi, menambahkan argumen ke metode, atau membagi modul menjadi beberapa bagian. Semua ini lumrah, tetapi ukuran basis kode kami berarti bahwa seseorang tidak akan dapat menemukan setiap baris yang perlu diubah. Itulah mengapa sangat penting untuk menggabungkan kemampuan codemod dengan analisis statis yang andal. Ini memungkinkan kita untuk lebih percaya diri menemukan bagian-bagian dari kode yang perlu diubah, yang berarti memungkinkan kita untuk membuat mode kode lebih aman dan lebih kuat.

Pembaca yang budiman! Apakah Anda menggunakan mod kode?


Source: https://habr.com/ru/post/id473770/


All Articles