Kapan kode terpuji?



Topik kode ideal sering menyebabkan kontroversi di antara programmer berpengalaman. Jauh lebih menarik untuk mendapatkan pendapat Direktur Pengembangan Parallels RAS Igor Marnat. Di bawah potongan, pendapat penulisnya tentang topik yang dideklarasikan. Selamat menikmati!



Sebagai pengantar, saya ingin membahas pertanyaan mengapa saya memutuskan untuk menulis artikel pendek ini. Sebelum menulisnya, saya mengajukan pertanyaan dari judul ke beberapa pengembang. Saya bekerja dengan sebagian besar dari mereka selama lebih dari lima tahun, dengan sedikit lebih sedikit, tetapi saya percaya profesionalisme dan pengalaman mereka tanpa syarat. Semua pengalaman dalam pengembangan industri selama lebih dari sepuluh tahun, semua orang bekerja di perusahaan Rusia dan internasional, produsen perangkat lunak.

Beberapa rekan merasa sulit untuk menjawab (sebagian orang masih berpikir), yang lain menyebut satu atau dua contoh sekaligus. Bagi mereka yang memberi contoh, saya mengajukan pertanyaan klarifikasi - "Apa, sebenarnya, yang menyebabkan kekaguman ini?" Jawabannya konsisten dengan hasil fase selanjutnya dari penelitian kecil saya. Saya mencari di web untuk jawaban atas pertanyaan ini dalam formulasi berbeda dekat dengan judul artikel. Semua artikel menjawab kira-kira dengan cara yang sama seperti kawan-kawan saya menjawab.

Jawaban pengembang, serta kata-kata dari artikel yang ditemukan, terkait dengan keterbacaan dan struktur kode, keanggunan konstruksi logis, penggunaan semua fitur bahasa pemrograman modern dan mengikuti gaya desain tertentu.

Ketika saya mengajukan pertanyaan tentang "kode ilahi" untuk diri saya sendiri, jawabannya segera muncul, dari alam bawah sadar. Saya langsung memikirkan dua contoh kode yang sudah lama saya kerjakan (lebih dari sepuluh tahun yang lalu), tetapi saya masih merasakan kekaguman dan rasa hormat. Setelah mempertimbangkan alasan untuk mengagumi mereka masing-masing, saya merumuskan beberapa kriteria, yang akan dibahas di bawah. Saya akan membahas contoh pertama secara sepintas, tetapi saya ingin menganalisis yang kedua secara lebih rinci. Ngomong-ngomong, pada tingkat yang berbeda-beda, semua kriteria ini dipertimbangkan dalam buku pedoman masing-masing " Kode Sempurna " oleh Steve McConnell, tetapi artikel ini terasa lebih pendek.

Contoh tahun 90an


Contoh pertama yang akan saya sebutkan berkaitan dengan implementasi protokol modem v42bis. Protokol ini dikembangkan pada akhir 80-an - awal 90-an. Ide yang menarik, yang diwujudkan oleh pengembang protokol, adalah implementasi kompresi aliran informasi selama transmisi melalui saluran komunikasi (telepon) yang tidak stabil. Perbedaan antara kompresi aliran dan kompresi file sangat mendasar. Saat mengompresi file, pengarsipan memiliki kemampuan untuk menganalisis set data sepenuhnya, menentukan pendekatan optimal untuk mengompresi dan mengkodekan data, dan menulis seluruh data ke file tanpa khawatir tentang kemungkinan data dan kehilangan metadata. Ketika membuka ritsleting, pada gilirannya, kumpulan data dapat diakses kembali sepenuhnya, integritas dipastikan oleh sebuah checksum. Dengan kompresi in-line, pengarsipan hanya dapat mengakses jendela data kecil, tidak ada jaminan tidak ada kehilangan data, kebutuhan untuk menginstal ulang koneksi dan menginisialisasi proses kompresi adalah umum.

Para penulis algoritma menemukan solusi yang elegan, deskripsi yang mengambil beberapa halaman secara harfiah . Bertahun-tahun telah berlalu, tetapi saya masih terkesan dengan keindahan dan keanggunan dari pendekatan yang diusulkan oleh para pengembang algoritma.

Contoh ini masih tidak merujuk ke kode seperti itu, tetapi lebih kepada algoritma, jadi kami tidak akan membahasnya lebih detail.

Linux adalah kepala segalanya!


Saya ingin menganalisis contoh kedua dari kode sempurna secara lebih rinci. Ini adalah kode kernel Linux. Kode yang, pada saat penulisan, mengontrol pekerjaan 500 superkomputer dari 500 teratas , kode yang berjalan pada setiap telepon kedua di dunia dan yang mengendalikan sebagian besar server di Internet.

Sebagai contoh, pertimbangkan file memory.c dari kernel Linux , yang termasuk dalam subsistem manajemen memori.

1. Sumber mudah dibaca. Mereka ditulis menggunakan gaya yang sangat sederhana yang mudah diikuti dan sulit untuk membingungkan. Karakter huruf besar hanya digunakan untuk arahan preprocessor dan makro, semuanya ditulis dalam huruf kecil, kata-kata dalam nama dipisahkan oleh garis bawah. Ini mungkin gaya pengkodean termudah yang mungkin, kecuali karena kurangnya gaya sama sekali. Pada saat yang sama, kode tersebut dapat dibaca dengan sempurna. Pendekatan indentasi dan komentar terlihat dari setiap bagian dari file kernel apa pun, misalnya:

static void tlb_remove_table_one(void *table) {     /*      * This isn't an RCU grace period and hence the page-tables cannot be      * assumed to be actually RCU-freed.      *      * It is however sufficient for software page-table walkers that rely on      * IRQ disabling. See the comment near struct mmu_table_batch.      */     smp_call_function(tlb_remove_table_smp_sync, NULL, 1);     __tlb_remove_table(table); } 


2. Tidak ada banyak komentar dalam kode, tetapi yang biasanya bermanfaat. Sebagai aturan, mereka menggambarkan bukan tindakan yang sudah jelas dari kode (contoh klasik dari komentar yang tidak berguna adalah "cnt ++; // penghitung kenaikan"), tetapi konteks tindakan ini - mengapa di sini dilakukan apa yang dilakukan, mengapa dilakukan, mengapa di sini, dengan asumsi apa yang digunakan, dengan apa tempat lain dalam kode itu terhubung. Sebagai contoh:

 /** * tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down * @tlb: the mmu_gather structure to initialize * @mm: the mm_struct of the target address space * @start: start of the region that will be removed from the page-table * @end: end of the region that will be removed from the page-table * * Called to initialize an (on-stack) mmu_gather structure for page-table * tear-down from @mm. The @start and @end are set to 0 and -1 * respectively when @mm is without users and we're going to destroy * the full address space (exit/execve). */ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,            unsigned long start, unsigned long end) 


Penggunaan lain dari komentar di kernel adalah untuk menggambarkan sejarah perubahan, biasanya di awal file. Sejarah kernel telah ada selama hampir tiga puluh tahun, dan hanya menarik untuk membaca beberapa tempat, Anda merasakan bagian dari cerita ini:

 /* * demand-loading started 01.12.91 - seems it is high on the list of * things wanted, and it should be easy to implement. - Linus */ /* * Ok, demand-loading was easy, shared pages a little bit tricker. Shared * pages started 02.12.91, seems to work. - Linus. * * Tested sharing by executing about 30 /bin/sh: under the old kernel it * would have taken more than the 6M I have free, but it worked well as * far as I could see. * * Also corrected some "invalidate()"s - I wasn't doing enough of them. */ 


3. Kode kernel menggunakan makro khusus untuk memverifikasi data. Mereka juga digunakan untuk memeriksa konteks di mana kode bekerja. Fungsionalitas makro ini mirip dengan pernyataan standar, dengan perbedaan bahwa pengembang dapat menimpa tindakan yang dilakukan jika kondisinya benar. Pendekatan umum untuk pemrosesan data di kernel - segala sesuatu yang berasal dari ruang pengguna diperiksa, jika ada data yang salah, nilai yang sesuai dikembalikan. Dalam hal ini, WARN_ON dapat digunakan untuk mengeluarkan catatan ke log kernel. BUG_ON biasanya sangat berguna ketika men-debug kode baru dan memulai kernel pada arsitektur baru.

Makro BUG_ON biasanya menyebabkan isi register dan tumpukan dicetak dan menghentikan seluruh sistem atau proses dalam konteks di mana panggilan yang sesuai terjadi. Makro WARN_ON hanya mengeluarkan pesan ke log kernel jika kondisinya benar. Ada juga macro WARN_ON_ONCE dan sejumlah lainnya, yang fungsinya jelas dari namanya.

 void unmap_page_range(struct mmu_gather *tlb, ….     unsigned long next;    BUG_ON(addr >= end);    tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, …    unsigned long end = addr + size;    int err;    if (WARN_ON(addr >= end))        return -EINVAL; 


Pendekatan di mana data yang diperoleh dari sumber yang tidak dapat diandalkan diperiksa sebelum digunakan, dan respons sistem terhadap situasi "tidak mungkin" diramalkan dan ditentukan, sangat menyederhanakan proses debugging dan operasi. Anda dapat mempertimbangkan pendekatan ini sebagai implementasi dari prinsip gagal awal dan keras.

4. Semua komponen utama dari kernel menyediakan pengguna dengan informasi tentang status mereka melalui antarmuka yang sederhana, sistem file virtual / proc /.

Misalnya, informasi status memori tersedia di file / proc / meminfo

 user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal:    2041480 kB MemFree:      65508 kB MemAvailable:   187600 kB Buffers:      14040 kB Cached:      246260 kB SwapCached:    19688 kB Active:     1348656 kB Inactive:     477244 kB Active(anon):  1201124 kB Inactive(anon):  387600 kB Active(file):   147532 kB Inactive(file):  89644 kB …. 


Informasi di atas dikumpulkan dan diproses dalam beberapa file sumber dari subsistem manajemen memori. Jadi, bidang MemTotal pertama adalah nilai bidang totalram dari struktur sysinfo, yang diisi dengan fungsi si_meminfo dari file page_alloc.c .

Jelas, mengatur pengumpulan, penyimpanan, dan menyediakan akses kepada pengguna ke informasi tersebut membutuhkan upaya dari pengembang dan beberapa overhead dari sistem. Pada saat yang sama, manfaat dari memiliki akses yang mudah dan mudah ke data tersebut sangat berharga baik dalam proses pengembangan dan dalam pengoperasian kode.

Pengembangan hampir semua sistem harus dimulai dengan sistem untuk mengumpulkan dan memberikan informasi tentang keadaan internal kode dan data Anda. Ini akan sangat membantu dalam proses pengembangan dan pengujian, dan, kemudian, dalam operasi.

Seperti yang dikatakan Linus , β€œprogrammer buruk khawatir tentang kode. Pemrogram yang baik khawatir tentang struktur data dan hubungan mereka. "

5. Semua kode dibaca dan didiskusikan oleh beberapa pengembang sebelum melakukan. Riwayat perubahan kode sumber dicatat dan tersedia. Perubahan pada jalur apa pun dapat ditelusuri kembali ke kemunculannya - apa yang berubah, oleh siapa, kapan, mengapa, masalah apa yang dibahas oleh pengembang. Misalnya, perubahan https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef di memory code.c terinspirasi olehbbb.us Optimalisasi kecil kode telah dilakukan (panggilan untuk mengaktifkan proteksi penulisan memori tidak terjadi jika memori sudah dilindungi tulis).

Selalu penting bagi pengembang yang bekerja dengan kode untuk memahami konteks di sekitar kode ini, dengan asumsi apa kode itu dibuat, apa dan kapan itu berubah, untuk memahami skenario apa yang mungkin dipengaruhi oleh perubahan yang akan dibuatnya.

6. Semua elemen penting dari siklus hidup kode kernel didokumentasikan dan dapat diakses , dimulai dengan gaya pengkodean dan berakhir dengan konten dan jadwal untuk rilis versi kernel yang stabil . Setiap pengembang dan pengguna yang ingin bekerja dengan kode kernel dalam satu kapasitas atau yang lain memiliki semua informasi yang diperlukan untuk ini.

Saat-saat ini tampak penting bagi saya, pada dasarnya, mereka menentukan antusiasme saya terhadap kode inti. Jelas, daftarnya sangat pendek dan dapat diperluas. Tetapi poin-poin yang tercantum di atas, menurut pendapat saya, berhubungan dengan aspek-aspek kunci dari siklus hidup dari setiap kode sumber dari sudut pandang pengembang yang bekerja dengan kode ini.

Apa yang ingin saya katakan sebagai kesimpulan. Pengembang inti cerdas dan berpengalaman, mereka telah berhasil. Terbukti oleh miliaran perangkat Linux

Jadilah sebagai pengembang kernel, gunakan praktik terbaik dan baca Kode Lengkap!

Z.Y. Omong-omong, kriteria kode ideal apa yang Anda miliki secara pribadi? Bagikan pemikiran Anda dalam komentar.

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


All Articles