NoVerify: linter PHP yang bekerja cepat

Ada utilitas analisis statis yang bagus untuk PHP: PHPStan, Mazmur, Phan, Exakat. Linter melakukan pekerjaannya dengan baik, tetapi sangat lambat, karena hampir semua ditulis dalam PHP (atau Java). Untuk penggunaan pribadi atau proyek kecil, ini normal, tetapi untuk situs dengan jutaan pengguna, ini merupakan faktor penting. Linter lambat memperlambat pipa CI dan membuatnya mustahil untuk menggunakannya sebagai solusi yang dapat diintegrasikan ke dalam editor teks atau IDE.



Situs dengan jutaan pengguna adalah VKontakte. Pengembangan dan penambahan fungsi baru, pengujian dan perbaikan bug, ulasan - semua ini harus berjalan cepat, dalam kondisi tenggat waktu yang sulit. Oleh karena itu, linter yang bagus dan cepat yang dapat memeriksa basis kode untuk 5 juta baris dalam 5-10 detik adalah hal yang tidak tergantikan. 

Tidak ada linter yang cocok di pasaran, jadi Yuri Nasretdinov (kamu keren) dari VKontakte menulis bantuannya kepada tim pengembangan - NoVerify. Ini adalah linter untuk PHP, yang ditulis dalam Go. Ini bekerja 10-30 kali lebih cepat daripada analog, dapat menemukan sesuatu yang tidak diperingatkan PhpStorm, dengan mudah memperluas dan mengintegrasikan dengan baik ke dalam proyek-proyek di mana mereka belum pernah mendengar analisis statis sebelumnya. Iskander Sharipov

akan bercerita tentang film ini . Di bawah kucing: bagaimana memilih kain dan lebih suka menulis sendiri, mengapa NoVerify begitu cepat dan bagaimana itu diatur di dalam, mengapa itu ditulis dalam Go, apa yang dapat ditemukan dan bagaimana ia berkembang, kompromi apa yang harus Anda buat untuk itu dan apa yang dapat dibangun berdasarkan itu.


Iskander Sharipov (dengan mudah) bekerja di infrastruktur backend VKontakte dan sangat mengenal NoVerify. Di masa lalu, ia terlibat dalam Go-compiler di tim Intel. Dia tidak menulis dalam PHP, tetapi ini adalah bahasa favoritnya untuk analisis statis - ia memiliki banyak hal yang bisa salah.

Catatan. Untuk memahami latar belakang, bacalah artikel oleh Yuri Nasretdinov, penulis NoVerify on Habré, dengan latar belakang dan perbandingan dengan beberapa linter yang ada, yang biasanya ditulis dalam PHP. Semua pernyataan dalam arah PHP (dalam artikel oleh Yuri dan di sini) adalah lelucon. Iskander menyukai PHP, semua orang menyukai PHP.

Pengembangan produk


Di VKontakte, ini adalah pengembangan situs web di KPHP. Kecepatan penting untuk VKontakte: memperbaiki bug, menambah dan mengembangkan fungsi baru dari fase pertama hingga yang terakhir. Tetapi kecepatan disertai dengan kesalahan , terutama ketika ada tenggat waktu yang sulit - kita sedang terburu-buru, gugup dan membuat lebih banyak kesalahan daripada dalam situasi yang tenang.

Kesalahan memengaruhi pengguna . Kami tidak ingin mereka menderita, karena itu kami mengontrol kualitas. Tetapi kontrol kualitas memperlambat pengembangan . Ini kami juga tidak mau, jadi efeknya harus diminimalkan.

Untuk melakukan ini, kami dapat melakukan lebih banyak ulasan kode tanpa gagal, menyewa lebih banyak pengujidan menulis lebih banyak tes. Tapi semua ini otomatis buruk: review harus dilakukan, dan tes harus ditulis.

Tugas utama tim saya berbeda.

Kumpulkan metrik, analisis, dan perbaiki dengan cepat . Jika terjadi kesalahan, kami ingin segera mengembalikannya, memahami apa yang salah, memperbaikinya dan dengan cepat menambahkan kode kerja kembali ke produksi.

Pantau kerasnya pipa sehingga kode yang tidak berfungsi sama sekali tidak dapat diproduksi - Anda tidak perlu mengembalikannya. Di sini linter datang untuk menyelamatkan - analisa kode statis. Kami akan membicarakan ini.

Pilih linter


Mari kita pilih linter yang kita tambahkan ke pipeline. Kami mengambil pendekatan sederhana - kami merumuskan persyaratan.

Linter harus bekerja dengan cepat . Ada beberapa langkah dalam saluran kami: pengoperasian linter seharusnya tidak memakan banyak waktu dan pengembang yang menyita waktu, sementara ia menunggu umpan balik.

Dukungan untuk cek "mereka" . Kemungkinan besar, linter tidak memiliki semua yang kita butuhkan - kita harus menambahkan cek kita sendiri. Mereka harus menemukan masalah khas dari basis kode kami, memeriksa kode dari sudut pandang proyek kami. Tidak semua ini dapat (atau mudah) dicakup oleh tes.

Dukungan untuk pemeriksaan "milik sendiri". Kita dapat menulis banyak tes, tetapi akankah mereka didukung dengan baik? Misalnya, jika kita menulis pada ekspresi reguler, mereka akan menjadi lebih rumit ketika Anda perlu mempertimbangkan konteks, semantik, dan sintaksis bahasa. Karena itu, tes bukanlah suatu pilihan.

Sebagian besar linter yang kami ulas ditulis dalam PHP. Tetapi mereka tidak menyampaikan permintaan. Linters di PHP (belum ada kompilasi AOT) bekerja 10-20 kali lebih lambat daripada di bahasa lain - file terbesar kami dapat dianalisis selama puluhan detik. Ini memperlambat alur kerja terlalu banyak dan terlalu banyak - ini adalah kesalahan fatal . Apa yang dilakukan pengembang dalam kasus ini? Mereka menulis sendiri.

Oleh karena itu, kami menulis linter PHP NoVerify kami di Go. Kenapa di situ? Spoiler: bukan hanya karena Jura memutuskan demikian.

Noverify


Go adalah kompromi yang baik antara kecepatan pengembangan dan produktivitas.
"Bukti" pertama dalam gambar dengan "infografis": kecepatan eksekusi yang baik, dukungan mudah. Kita kehilangan kecepatan perkembangan, tetapi dua poin pertama lebih penting bagi kita.


Angka-angka diambil dari kepala, mereka tidak didukung oleh apa pun.

Untuk "bukti" kedua, ia berdebat lebih sederhana.


PHP lebih lambat, Go lebih cepat, dan sebagainya. 

Kami memilih Go karena tiga alasan.

Pergi sebagai bahasa untuk utilitas mudah dipelajari di tingkat dasar . Di tim pengembangan PHP, pasti, seseorang mendengar tentang Go, memandang Docker, tahu bahwa itu ditulis dalam Go, bahkan mungkin melihat sumbernya. Dengan pemahaman dasar, setelah satu atau dua minggu belajar Go intensif, mereka akan dapat menulis kode di atasnya.

Go cukup efektif . Bahkan seorang pemula tidak akan dapat membuat banyak kesalahan, karena Go memiliki penyetelan yang baik dan banyak linter. Di Go, kode rata-rata sedikit lebih baik daripada di bahasa lain, karena ada jauh lebih sedikit cara untuk menembak kaki Anda sendiri.

Aplikasi Go mudah dirawat.Go adalah bahasa pemrograman yang cukup matang yang hampir semua alat pengembang yang Anda inginkan tersedia.

Kami akan memverifikasi NoVerify dengan persyaratan kami.

  • NoVerify beberapa kali lebih cepat daripada alternatif .

  • Untuk itu, Anda dapat menulis ekstensi , baik Open Source, dan Anda sendiri. Penting bahwa kami dapat memisahkan cek ini, dan Anda dapat menulis sendiri.
  • Mudah untuk diuji dan dikembangkan. Sebagian, karena distribusi Go standar memiliki kerangka kerja standar dengan profil dan pengujian. Ini terutama digunakan untuk pengujian unit. Khususnya tangkas dapat digunakan untuk integrasi, seperti yang kita lakukan - kami memiliki pengujian integrasi yang ditulis melalui tes Go.

Kompromi Integrasi


Mari kita mulai dengan masalahnya. Saat Anda memulai linter apa pun untuk pertama kalinya pada proyek lama yang tidak menggunakan analisis apa pun, kemungkinan besar Anda akan melihatnya.



Oh kode saya! Tidak ada yang akan memperbaiki begitu banyak kesalahan. Saya ingin menutup proyek, menghapus linter dan tidak pernah menjalankannya lagi. Apa yang harus dilakukan untuk menghindari ini?

Mengintegrasikan


Jalankan dalam mode diff . Kami tidak ingin menjalankan semua pemeriksaan pada keseluruhan proyek dengan sejuta kesalahan pada setiap langkah CI. Mungkin Anda tahu tentang baseline: di NoVerify, ini di luar kotak, Anda tidak perlu menyematkan utilitas terpisah. Kami segera mempertimbangkan bahwa rezim semacam itu diperlukan.

Tambahkan warisan (vendor) ke pengecualian . Lebih mudah untuk tidak menyentuh beberapa hal, mengesampingkan bahkan dengan cacat, agar tidak memodifikasinya sendiri dan tidak meninggalkan bekas dalam sejarah.

Kami mulai dengan subset cek . Anda tidak dapat menghubungkan semua yang termasuk gaya. Untuk memulainya, ia menemukan bug nyata: kami akan menemukan, memperbaiki, dan beralih ke sesuatu yang baru.

Kami mengumpulkan umpan balik dari kolega. Bagaimana memahami kapan saatnya menyalakan sesuatu yang lain? Tanya rekan kerja. Begitu mereka senang bahwa kesalahan telah hilang dan hampir tidak ada yang ditemukan, nyalakan sesuatu yang lain - saatnya untuk bekerja.

Pengaturan git


Mode Diff berarti Anda memiliki sistem kontrol versi - Git. Jika Anda memiliki SVN, maka instruksi tidak akan membantu, buka Git.

Kami memasang pre-push hook dengan linter dan memeriksa sebelum kami memulai kodenya. Kami memeriksa mesin lokal dengan opsi --no-verifyuntuk memotong linter . Mungkin akan lebih nyaman menggunakan kait pra-terima dan mematikan linter sisi-server, tetapi karena alasan historis, banyak hal terjadi dalam VK dalam kait pra-tekan, sehingga NoVerify dibuat di sana.

Setelah ditekan, pemeriksaan CI diluncurkan. NoVerify memiliki dua mode operasi: dengan analisis penuh dan tanpa itu. Pada CI, Anda kemungkinan besar ingin (dan dapat) menjalankan--git-full-diff- mesin di CI dapat dimuat lebih keras dan memeriksa bahkan file-file yang belum berubah. Pada mesin lokal, kita dapat menjalankan analisis yang tidak terlalu ketat, tetapi lebih cepat dari hanya file yang diubah (5-15 detik lebih cepat). 

Positif palsu




Pertimbangkan contoh di bawah ini: dalam fungsi ini, sesuatu yang mengandung bidang diterima, tetapi jenisnya tidak dijelaskan dengan cara apa pun. Bukan fakta bahwa ada bidang sama sekali ketika suatu fungsi dipanggil dari konteks yang berbeda. Dalam versi yang ketat, linter bisa mengeluh: "Tidak jelas apa jenisnya, bagaimana saya bisa mengembalikan bidang tanpa cek?" Tapi ini belum tentu kesalahan.

function get_foo($obj) {
    return $obj->foo;
    ^^^
}

Warning:
Property "foo" does not exist

Positif palsu mengganggu. 
Ini adalah alasan utama untuk meninggalkan linter. Orang memilih opsi lain yang menemukan kesalahan lebih sedikit, tetapi menghasilkan lebih sedikit kesalahan positif.

Mereka sering mendorong ketika sesuatu bekerja, tetapi ini bukan kesalahan. Banyak yang memiliki mekanisme untuk memotong linter - berjalan dengan bendera tanpa memeriksa linter. Di negara kita, bendera ini disebut no-verifykait pre-push. Kami sering menggunakannya dan nama itu diabadikan atas nama linter.

Pilih-pilih


Properti linter lainnya. Misalnya, banyak yang tidak mengerti alias. Dalam PHP, sizeofini adalah analog count: ia tidak menghitung ukuran, tetapi mengembalikan jumlah elemen. Model mental pengembang C memiliki sizeofarti yang berbeda. Jika dalam basis kode ada sizeof, kemungkinan besar, berarti count. Tapi ini nitpicking.

$len = sizeof($x);
    ^^^^^^

Warning:
use "count" instead of "sizeof"

Apa hubungannya dengan itu?


Bersikap tegas dan paksa untuk memerintah segala sesuatu tanpa kecuali . Menerapkan aturan, menuntut, menaati, dan tidak membiarkannya mengelak - tidak pernah berhasil. Agar kekakuan bekerja, tim harus terdiri dari orang-orang yang sama: karakter, tingkat budaya, kesederhanaan dan persepsi kualitas kode. Jika tidak demikian, akan terjadi kerusuhan. Lebih mudah untuk mengumpulkan tim dari klon Anda daripada memaksa untuk mengikuti semua aturan. 

Jangan menghalangi dorongan / komit pada komentar seperti memperbaiki sizeofpada count. Kemungkinan besar, ini bukan kesalahan, tetapi memilih-nit dan tidak memengaruhi kode. Tetapi 99% jawaban akan diabaikan (oleh tim) dan akan selalu ada tambahan di dalam kode sizeof.

Izinkan beberapa tingkat konfigurasi untuk tim dan pengembang yang berbeda.Anda dapat mengkonfigurasi konfigurasi untuk setiap perintah agar mereka yang tidak ingin perubahan sizeofuntuk counttidak bisa melakukan ini. Biarkan semua orang mengikuti aturan. Pilihan yang baik, tetapi konsistensi akan melorot, dan dalam beberapa direktori kode akan sedikit lebih buruk.

Jalankan pemeriksaan seperti itu sebulan sekali, di subbotnik . Pemeriksaan dapat dijalankan tidak setiap waktu pada CI atau kait pra-dorong, tetapi dalam Cron rutin sebulan sekali. Jalankan dan edit semua yang Anda temukan setelah pengembangan aktif. Tetapi pekerjaan ini membutuhkan sumber daya untuk otomatisasi dan verifikasi.

Tidak melakukan apapun. Menonaktifkan pemeriksaan gaya juga merupakan opsi.

Kompromi




Akan selalu ada kompromi antara pengembang yang bahagia dan orang yang bahagia. Sangat mudah untuk membuat linter senang: mode paling ketat dan tidak ada solusi. Mungkin setelah itu tidak ada yang akan tetap berada di tim, jadi jika linter mengganggu pekerjaan, ini adalah masalah.
Tindakan yang bermanfaat di atas segalanya.

NoVerifikasi Detail Teknis


Pemeriksaan pribadi VKontakte. Noverify dituliskan sesuatu seperti ini. Dalam GitHub, repositori NoVerify dibagi menjadi dua bagian: kerangka kerja yang digunakan untuk mengimplementasikan linter, dan memisahkan cek, vklints . Hal ini dilakukan agar linter memuat cek pihak ketiga: Anda dapat menulis modul terpisah di Go dan mereka mendaftarkan diri dalam kerangka kerja. Setelah mulai dari biner NoVerify, kerangka memuat semua set cek terdaftar dan mereka bekerja secara keseluruhan. 



NoVerify adalah pustaka dan biner (linter).

Cek kami disebut vklints . Mereka menemukan bahwa mereka tidak melihat PhpStorm dan Open Source NoVerify - kesalahan penting yang tidak cocok untuk penggunaan umum.

Apa itu vklints?

Memeriksa spesifik menggunakan fungsi tertentu , kelas, dan bahkan variabel global yang tidak mengikuti konvensi kami. Ini adalah sesuatu yang tidak dapat digunakan di tempat-tempat khusus karena berbagai alasan yang dijelaskan dalam panduan gaya.

Pemeriksaan gaya tambahan. Mereka tidak sesuai dengan apa yang diterima di komunitas PHP, tidak dijelaskan dalam Rekomendasi Standar PHP atau bahkan bertentangan, tetapi bagi kami itu adalah standar. Tidak ada gunanya menambahkan mereka ke Open Source karena Anda tidak ingin mengikuti mereka.

Persyaratan perbandingan yang ketat untuk beberapa jenis . Sebagai contoh, kami memiliki pemeriksaan yang membutuhkan perbandingan string dengan operator perbandingan ===. Secara khusus, ini memerlukan melewati bendera untuk perbandingan fungsi yang ketat untuk membandingkan string.

Kunci susunan yang mencurigakan.Kesalahan menarik lainnya: kadang-kadang, ketika pengembang melakukan, mereka dapat menekan kombinasi tombol sebelum menyimpan file. Karakter-karakter ini terkadang tetap dalam string atau dalam sepotong kode. Suatu ketika, kunci array adalah huruf Rusia "Y". Kemungkinan besar, pengembang menekan CTRL-S dalam tata letak Rusia, menyimpan file dan berkomitmen. Terkadang kami menemukan kunci-kunci seperti itu dalam array, tetapi kesalahan baru tidak akan lagi berlalu.

Aturan dinamis adalah mekanisme ekstensi NoVerify yang lebih sederhana yang dijelaskan dalam PHP. Artikel terpisah telah ditulis tentang ini: cara menambahkan cek ke NoVerify tanpa menulis satu baris kode Go .

Bagaimana NoVerify Bekerja


Untuk mem-parsing PHP Anda membutuhkan parser . Kami tidak dapat menggunakan parser PHP di PHP: itu lambat, dari Go, itu hanya dapat digunakan melalui pembungkus dalam C. Oleh karena itu, kami menggunakan parser di Go.

Parser ini memiliki beberapa masalah. Sayangnya, ia hanya dapat bekerja dengan UTF-8 dan kami harus membedakan antara UTF-8 dan bukan UTF-8 . Selain UTF-8, Windows-1251 sering ditemukan di proyek PHP Rusia. Kami juga memiliki file seperti itu. Bagaimana kita mengenalinya? 

File encodings.xmlmencantumkan semua jalur di mana file dengan UTF-8 berada. Jika kita menemukan file di luar jalur ini, maka dengan cepat kita streaming ke UTF-8 dengan streaming streaming (tanpa mengkonversi terlebih dahulu).


Parsing dan analisis


Selesai dalam beberapa langkah. Yang pertama - kita memuat metadata dari phpstorm-stubs . Ini adalah data yang terlihat seperti kode PHP, tetapi tidak pernah dieksekusi dan menjelaskan jenis input / output dari fungsi standar. Metadata phpStorm memiliki arahan override yang berguna untuk linter ... Ini memungkinkan kita untuk menggambarkan, misalnya, bahwa kita menerima array tipe T[]dan mengembalikan tipe (berguna untuk fungsi array_pop).


Phpstorm-stubs dimuat terlebih dahulu. Kami menggunakan metadata sebagai informasi tipe awal - yayasan. Yayasan ini diserap oleh linter dan kami mulai menganalisis sumbernya.

Kami memuat master saat ini sebelum penyerapan. Kami memeriksa kode dalam dua mode:

  • perubahan lokal : mengenai baseline kami menemukan kesalahan baru dalam kode;
  • kami menunjukkan rentang revisi : revisi pertama dan terakhir, dan di antaranya semuanya inklusif - ini adalah kode baru, dan segala sesuatu yang "sebelumnya" sudah tua.

Selanjutnya adalah tahap analisis.



Analisis AST . Kami sekarang memiliki metadata, ketik informasi. Kami mengambil semua sumber PHP, parsim dan menganalisis langsung di atas AST - kami tidak memiliki representasi perantara saat ini. Menganalisis AST mentah sangat tidak nyaman, terutama jika Anda bergantung pada perpustakaan dan tipe data yang diwakilinya. 



Hasil analisis disimpan dalam cache . Ini digunakan dalam analisis ulang, yang jauh lebih cepat.

Laporan dan pemfilteran . Lalu kami membuat laporan atau peringatan dua kali : pertama kami menemukan peringatan untuk versi kode yang lama (sebelum baseline), kemudian untuk yang baru. Laporan difilter berdasarkan perbandingan (beda) - kami mencari peringatan yang muncul di versi kode yang baru dan meneruskannya kepada pengguna. Dalam beberapa analisis statis, ini disebut "mode dasar".



Analisis kode ganda (dalam mode diff) sangat lambat. Tapi kami mampu membelinya - NoVerify masih puluhan kali lebih cepat daripada penghubung PHP lainnya. Pada saat yang sama, ia memiliki cadangan untuk akselerasi tambahan, setidaknya 30 persen.

Bagaimana cara kita menganalisis file? Dalam PHP, Anda dapat memanggil suatu fungsi sebelum didefinisikan - Anda perlu mengetahui informasi tentang fungsi ini sebelum menganalisisnya. Oleh karena itu, pertama-tama kita membahas seluruh file dalam AST, mengindeks, mengidentifikasi tipe-tipe semua fungsi, mendaftarkan kelas-kelas, dan baru kemudian menganalisisnya. 



Analisis adalah pass kedua melalui file . Kebanyakan interpreter dan kompiler juga bekerja dengan dua lintasan dan banyak lagi. Agar tidak "memindai" file untuk kedua kalinya, Anda harus memiliki deklarasi sebelum digunakan, seperti dalam C, misalnya.

Ketikkan inferensi


Bagian yang paling menarik adalah bahwa kesalahan paling sering ditemui di sini. Itu masih tidak sesuai dengan kebenaran dari sistem tipe PHP, yang sulit untuk didefinisikan secara formal.

Seperti apa modelnya.


Model semantik (demo).

Jenis jenis:

  • Yang diharapkan adalah apa yang kami jelaskan di komentar. Kami mengharapkan beberapa tipe dalam program, tetapi ini tidak berarti bahwa mereka benar-benar digunakan di dalamnya.
  • Aktual - nyata yang ada di program. Misalnya, jika kita menetapkan angka pada sesuatu, maka jelas bahwa intatau float(jika ini adalah angka floating-point) akan menjadi tipe aktual. 

Jenis yang sebenarnya tampaknya "lebih kuat" - mereka nyata, benar. Tapi kadang-kadang kita bisa mendapatkan tipe hanya dengan anotasi.

Anotasi (dalam tipe yang diharapkan) dapat dibagi menjadi dua kategori: kepercayaan dan ketidakpercayaan . Sebagai contoh, phpstorm-stubs termasuk dalam kategori pertama. Mereka dianggap dimoderasi (tanpa kesalahan) sebelum kita menggunakannya. Yang tidak dapat diandalkan adalah yang ditulis pengembang lain, karena mereka mungkin memiliki kesalahan.

Tipe aktual juga dapat dibagi menjadi beberapa bagian: nilai, pernyataan, predikat, dan tipe petunjuk, yang memperluas kemampuan PHP 7. Tetapi ada masalah yang tidak bisa dipecahkan oleh petunjuk jenis.

Diharapkan vs Aktual


Katakanlah sebuah kelas Foomemiliki pewaris. Dari kelas turunan, kita dapat memanggil metode yang tidak ada di Foo, karena turunan memanjang induk. Tetapi jika kita mendapatkan ahli waris Foodari new static()dengan penjelasan ini dari jenis kembali (dari self), maka masalah akan timbul. Kami dapat memanggil metode ini, tetapi IDE tidak akan meminta Anda - Anda harus menentukan static(). Ini adalah pengikat statik yang terlambat di PHP , ketika Foopewaris kelas tidak dapat kembali

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual = Foo
// expected = static

Saat kami menulis new static(), tidak hanya kelas yang bisa kembali new Foo. Misalnya, jika suatu Fookelas diwarisi dari bar, maka mungkin ada new bar. Oleh karena itu, kita memerlukan setidaknya dua jenis informasi. Tidak ada yang berlebihan - keduanya dibutuhkan.

Oleh karena itu, tipe aktual di sini adalah self- untuk juru bahasa PHP. Tetapi agar IDE dan linter bekerja, kita perlu static. Jika kita memanggil kode ini dari konteks kelas pewaris, kita perlu mengetahui informasi bahwa ini bukan kelas dasar yang sama dan memiliki lebih banyak metode.

class Foo {
    /** @return static */
    public function newStatic() : self {
        return new static();
    }
}
// actual -  PHP 
// expected -   IDE/

Ketikkan petunjuk


Mengetik statis dan mengetik tip bukanlah hal yang sama.
Anda mungkin pernah mendengar bahwa Anda hanya dapat memeriksa batas fungsi. Di perbatasan, kami memeriksa input dan output, di mana input adalah argumen fungsi. Di dalam fungsi, Anda dapat melakukan omong kosong: berikan foonilai int, meskipun Anda menggambarkannya T. Anda dapat mengeluh bahwa Anda melanggar tipe yang Anda nyatakan, tetapi untuk PHP tidak ada kesalahan

declare(strict_types=1);
    function f(T $foo) {
        $foo = 10; //  int
        return $foo;
}

Contoh lebih sulit - kita kembali foo? Pada awal fungsi, kami menentukan bahwa fooitu adalah T, dan tidak ada informasi tentang pengembalian. 

declare(strict_types=1);
function f(T $foo) {
    $foo = 10; //  int
    return $foo;
}
// ? 1. f -> int
// ? 2. f -> T|int
// ? 3. f -> T

Jenis mana yang benar? Dua yang pertama, kami akan menganalisis perbedaan di antara mereka. PhpStorm dan linter menghasilkan opsi kedua. Terlepas dari kenyataan bahwa itu selalu kembali int, jenisnya disimpulkan T|int- "penyatuan" jenis. Ini adalah tipe yang dapat menetapkan kedua nilai ini: pertama kami memiliki informasi tentang tipe T, kemudian kami menugaskannya 10, sehingga jenis variabel fooharus kompatibel dengan kedua jenis ini.

Anotasi


Komentar dan anotasi mungkin berbohong.
Dalam contoh di bawah ini, kami menulis bahwa kami mengembalikan nomor, tetapi mengembalikan string. Jika linter hanya bekerja pada level anotasi dan ketik petunjuk, maka kami akan menganggapnya selalu kembali int. Tetapi tipe Aktual hanya membantu menjauh dari ini: di sini, tipe yang Diharapkan adalah ini int, dan tipe Aktual adalah string. Linter tahu bahwa string dikembalikan dan dapat memperingatkan Anda bahwa Anda berjanji untuk kembali int. Pemisahan ini penting bagi kami.

/** @return int */
function f() { return "I lied!"; }

Warisan anotasi. Di sini, maksud saya bahwa kelas yang mengimplementasikan beberapa jenis antarmuka memiliki metode. Metode ini memiliki komentar, dokumentasi, tipe yang baik - diperlukan untuk mengimplementasikan antarmuka. Tetapi tidak ada komentar dalam implementasi: hanya @inheritdocada atau tidak sama sekali.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @inheritdoc */
    public function foo() { return "10"; }
}

Apa yang mengembalikan metode ini? Tampaknya apa yang dijelaskan dalam - antarmuka dikembalikan int, tetapi sebenarnya sebuah string. Ini tidak baik: PHP sama saja, tetapi konvergensi penting bagi kami.

Ada dua opsi untuk memperbaiki kode ini. Yang jelas adalah kembaliint . Tetapi mungkin Anda perlu mengembalikan jenis yang berbeda. Apa yang harus dilakukan? Tulis bahwa kami mengembalikan string . Dalam hal ini, informasi jenis eksplisit diperlukan untuk IDE dan linter untuk menganalisis kode dengan benar.

interface IFoo {
    /** @return int */
    public function foo();
}
class Fooer implements IFoo {
    /** @return string */
    public function foo() { return "10"; }
}

Informasi ini tidak diperlukan sama sekali jika orang menulis komentar, tidak @inheritdoc. PhpStorm tidak perlu memahami jenis apa yang Anda miliki. Tetapi jika jenisnya tidak dijelaskan dengan benar, akan ada masalah.

PhpStorm dan linter memiliki set bug disjoint ketika kita menggunakan file yang sama untuk metadata (tipe). Jika kita memperbaiki semua yang kita butuhkan di phpstorm-stubs dari repositori JetBrains, maka IDE kemungkinan besar akan rusak. Jika Anda membiarkan semuanya secara default, tidak semuanya akan bekerja dengan benar

untuk kami. Oleh karena itu, kami memiliki garpu kecil -  VKCOM / phpstorm-stubs . Beberapa tambalan telah ditambahkan untuk memperbaiki sesuatu yang tidak sesuai. Saya tidak bisa merekomendasikannya untuk PhpStorm, tetapi perlu bagi linter untuk bekerja.

Sumber terbuka


Noverify adalah proyek sumber terbuka. Itu diposting di GitHub .

Instruksi singkat "jika terjadi kesalahan."

Jika ada yang rusak atau tidak dimulai. Reaksi yang salah adalah membenci dan menghapus NoVerify. Reaksi yang benar: mengeluarkan tiket di GitHub dan membicarakan masalah Anda. Kemungkinan besar, itu akan diselesaikan dalam 1-2 hari.

Anda kehilangan beberapa fitur. Reaksi yang salah: hapus NoVerify dan tulis linter Anda sendiri (walaupun menulis linter Anda sendiri selalu keren). Reaksi yang benar: untuk mengeluarkan tiket di GitHub dan, mungkin, kami akan menambahkan fungsi baru. Ini lebih rumit dengan fitur daripada dengan kesalahan - diskusi muncul, dan masing-masing tim memiliki visi implementasi yang berbeda dalam tim. Namun pada akhirnya, mereka masih diimplementasikan.

Jika Anda tertarik pada pengembangan proyek atau jika Anda hanya ingin berbicara tentang analisis statis, buka ruang obrolan kami - noverify_linter .

PHP-, , , , PHP Russia.

, . , , . telegram- @PHPRussiaConfChannel. , .

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


All Articles