Konteks
Saya baru-baru ini tertarik untuk menghasilkan kode yang diformat lebih baik. Dan yang lebih baik saya maksudkan "mengikuti aturan yang didukung oleh cukup banyak orang untuk menganggapnya sebagai praktik yang baik" (karena tentu saja tidak akan ada satu cara "terbaik" yang unik untuk kode, tentu saja).
Hari-hari ini, aku kebanyakan kode Ruby jadi aku mulai menggunakan linter (Rubocop) untuk memberikan saya beberapa info tentang "kualitas" dari kode saya (ini "kualitas" yang didefinisikan oleh proyek berbasis masyarakat ruby-style-panduan ).
Perhatikan bahwa saya akan menggunakan "kualitas" seperti dalam "kualitas Formating", tidak begitu banyak tentang efisiensi kode, bahkan jika dalam beberapa kasus, efisiensi kode sebenarnya adalah dipengaruhi oleh bagaimana kode ditulis.
Bagaimanapun, melakukan semua itu, saya menyadari (atau setidaknya, ingat) beberapa hal:
- Beberapa bahasa (terutama Python, Ruby, dan semacamnya) memungkinkan untuk membuat satu baris kode yang hebat
- Mengikuti beberapa pedoman untuk kode Anda dapat membuatnya secara signifikan lebih pendek dan masih sangat jelas
- Namun, mengikuti pedoman ini terlalu ketat dapat membuat kode menjadi kurang jelas / mudah dibaca
- Kode dapat menghormati beberapa pedoman hampir dengan sempurna dan masih berkualitas buruk
- Keterbacaan kode sebagian besar subyektif (seperti dalam "apa yang saya temukan jelas benar-benar tidak jelas bagi sesama pengembang")
Itu hanya pengamatan, bukan aturan mutlak tentu saja. Anda juga akan mencatat bahwa keterbacaan kode dan pedoman berikut mungkin tampak tidak berhubungan pada titik ini, tetapi di sini pedoman adalah cara untuk mempersempit jumlah cara untuk menulis ulang satu potong kode.
Sekarang, beberapa contoh, untuk membuatnya lebih jelas.
Contohnya
Mari kita ambil contoh penggunaan sederhana: kami memiliki aplikasi dengan User
model " ". Seorang pengguna memiliki opsional firstname
dan surname
dan wajibemail
.
Saya ingin menulis metode " name
" yang akan mengembalikan lalu memberi nama ( firstname + surname
) pada pengguna jika setidaknya ada firstname
atau surname
tidak, atauemail
sebagai nilai cadangan jika tidak.
Saya juga ingin metode ini mengambil " use_email
" sebagai parameter (boolean), memungkinkan untuk menggunakan email pengguna sebagai nilai fallback. use_email
Parameter " " ini seharusnya default (jika tidak dilewatkan) sebagai " true
".
Cara paling sederhana untuk menulis itu, di Ruby, adalah:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
Kode ini adalah cara yang paling sederhana dan mudah dipahami untuk melakukannya. Bahkan untuk seseorang yang tidak "berbicara" Ruby.
Sekarang mari kita coba untuk mempersingkat:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
Ini lebih pendek, masih mudah dipahami, jika tidak mudah (klausa penjaga lebih alami untuk dibaca daripada blok bersyarat). Penjaga klausa juga membuatnya lebih sesuai dengan pedoman yang saya gunakan, jadi menang-menang di sini. Kami juga mengurangi level indentasi.
Sekarang mari kita gunakan beberapa sihir Ruby untuk membuatnya lebih pendek:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
Bahkan lebih pendek dan mengikuti pedoman dengan sempurna ... tetapi jauh lebih tidak jelas karena kurangnya pernyataan pengembalian membuatnya agak membingungkan bagi mereka yang tidak terbiasa dengan praktik ini.
Di sinilah kita dapat mulai mengajukan pertanyaan: apakah itu benar-benar layak? Jika kita mengatakan "tidak, buat itu bisa dibaca dan tambahkan ' return
'" (mengetahui ini tidak akan mematuhi pedoman). Atau haruskah kita berkata, "Tidak apa-apa, itu cara Ruby, belajar bahasa sialan!"?
Jika kita mengambil opsi B, mengapa tidak membuatnya lebih pendek:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Ini dia, satu-liner! Tentu saja lebih pendek ... di sini kita mengambil keuntungan dari kenyataan bahwa Ruby akan mengembalikan nilai atau yang lain tergantung mana yang didefinisikan (karena email akan didefinisikan dalam kondisi yang sama seperti sebelumnya).
Kita juga bisa menulisnya:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
Ini singkat, tidak terlalu sulit untuk dibaca (maksud saya, kita semua telah melihat seperti apa bentuk one-liner yang jelek), Ruby yang bagus, itu sesuai dengan pedoman yang saya gunakan ... Tapi tetap saja, dibandingkan dengan cara pertama menulis itu, itu jauh lebih mudah dibaca dan dimengerti. Kami juga dapat berpendapat bahwa baris ini terlalu panjang (lebih dari 80 karakter).
Pertanyaan
Beberapa contoh kode dapat menunjukkan bahwa memilih antara kode "ukuran penuh" dan banyak versi yang diperkecil (turun ke garis satu yang terkenal) bisa jadi sulit karena, seperti yang bisa kita lihat, satu-baris bisa tidak menakutkan tetapi tetap saja, tidak ada yang akan mengalahkan kode "ukuran penuh" dalam hal keterbacaan ...
Jadi, inilah pertanyaan sebenarnya: ke mana harus berhenti? Kapan pendek, cukup pendek? Bagaimana cara mengetahui kapan kode menjadi "terlalu pendek" dan kurang dapat dibaca (dengan mengingat bahwa itu cukup subjektif)? Dan lebih dari itu: bagaimana cara selalu kode sesuai dan menghindari pencampuran satu-liners dengan potongan "ukuran penuh" kode ketika saya hanya merasa seperti itu?
TL; DR
Pertanyaan utama di sini adalah: ketika datang untuk memilih antara "sepotong kode yang panjang tapi jelas, dapat dibaca dan dimengerti" dan "kuat, lebih pendek namun lebih sulit untuk dibaca / dipahami satu-liner", mengetahui keduanya adalah yang teratas dan yang bawah skala dan bukan hanya dua opsi: bagaimana mendefinisikan di mana perbatasan antara "cukup jelas" dan "tidak sejelas yang seharusnya"?
Pertanyaan utama bukanlah klasik "One-liners vs keterbacaan: mana yang lebih baik?" tetapi "Bagaimana menemukan keseimbangan di antara keduanya?"
Edit 1
Komentar dalam contoh kode dimaksudkan untuk "diabaikan", mereka di sini untuk mengklarifikasi apa yang terjadi, tetapi tidak diperhitungkan ketika mengevaluasi keterbacaan kode.
sumber
return
kata kunci yang ditambahkan . Ketujuh karakter itu menambah kejernihan di mataku.[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... karenafalse.blank?
mengembalikan true dan operator ternary menghemat beberapa karakter ... ¯ \ _ (ツ) _ / ¯return
seharusnya ditambahkan kata kunci ?! Tidak memberikan informasi apa pun . Ini kekacauan murni.Jawaban:
Apa pun kode yang Anda tulis, terbaca adalah yang terbaik. Pendek adalah yang terbaik kedua. Dan dapat dibaca biasanya berarti cukup singkat sehingga Anda dapat memahami kode tersebut, pengidentifikasi yang dinamai dengan baik, dan mengikuti idiom umum bahasa tempat kode tersebut ditulis.
Jika ini adalah bahasa agnostik, saya pikir ini pasti akan berdasarkan opini, tetapi dalam batas-batas bahasa Ruby saya pikir kita bisa menjawabnya.
Pertama, fitur dan cara penulisan Ruby yang idiomatis adalah menghilangkan
return
kata kunci saat mengembalikan nilai, kecuali kembali lebih awal dari suatu metode.Fitur dan idiom lain yang digabungkan adalah menggunakan
if
pernyataan tambahan untuk meningkatkan keterbacaan kode. Salah satu ide mengemudi di Ruby adalah menulis kode yang dibaca sebagai bahasa alami. Untuk ini, kita pergi ke _why Poignant Guide to Ruby, Bab 3 .Dengan ini, kode contoh # 3 paling idiomatis untuk Ruby:
Sekarang ketika kita membaca kodenya, tertulis:
Yang sangat dekat dengan kode Ruby yang sebenarnya.
Ini hanya 2 baris kode aktual, jadi cukup singkat, dan menganut idiom bahasa.
sumber
use_email
sebelum kondisi lain karena itu adalah variabel daripada panggilan fungsi. Tapi sekali lagi, interpolasi string mengubah perbedaan.do send an email if A, B, C but no D
, mengikuti premis Anda akan alami untuk mengetikkan 2 jika / blok lain , ketika mungkin akan lebih mudah untuk kodeif not D, send an email
. Hati-hati pada saat membaca bahasa alami dan mengubahnya menjadi kode karena dapat membuat Anda menulis versi baru dari "Neverending story" . Dengan kelas, metode, dan variabel. Lagipula bukan masalah besar.if !D
lebih baik, tidak apa-apa karenaD
memiliki nama yang bermakna. Dan jika!
operator tersesat di antara kode lain, maka memiliki pengenal yang dipanggilNotD
akan sesuai.Saya tidak berpikir Anda akan mendapatkan jawaban yang lebih baik daripada "menggunakan penilaian terbaik Anda". Singkatnya Anda harus berusaha untuk kejelasan daripada sesak . Seringkali, kode terpendek juga merupakan kode yang paling jelas, tetapi jika Anda fokus hanya untuk mencapai kejelasan singkat mungkin menderita. Ini jelas merupakan kasus dalam dua contoh terakhir, yang membutuhkan lebih banyak upaya untuk memahami daripada tiga contoh sebelumnya.
Pertimbangan penting adalah audiens kode. Keterbacaan tentu saja sangat tergantung pada orang yang membaca. Apakah orang yang Anda harapkan membaca kode (di samping Anda) benar-benar mengetahui idiom bahasa Ruby? Yah pertanyaan ini bukanlah sesuatu yang dapat dijawab oleh orang-orang acak di internet, ini hanya keputusan Anda sendiri.
sumber
Bagian dari masalah di sini adalah "apa itu readibility". Bagi saya, saya melihat contoh kode pertama Anda:
Dan saya merasa sulit untuk membaca karena penuh dengan komentar "berisik" yang hanya mengulangi kode. Lepaskan mereka:
dan sekarang jauh lebih mudah dibaca. Saat membacanya, saya pikir "hmm, saya ingin tahu apakah Ruby mendukung operator ternary? Dalam C #, saya dapat menuliskannya sebagai:
Apakah hal seperti itu mungkin terjadi di ruby? Sedang mengerjakan posting Anda, saya melihat ada:
Semua barang bagus. Tapi itu tidak bisa dibaca oleh saya; hanya karena saya harus menggulir untuk melihat seluruh baris. Jadi mari kita perbaiki:
Sekarang saya senang. Saya tidak sepenuhnya yakin tentang bagaimana sintaks bekerja, tetapi saya dapat memahami apa yang dilakukan oleh kode.
Tapi itu hanya aku. Orang lain memiliki ide yang sangat berbeda tentang apa yang membuat sepotong kode menjadi enak dibaca. Jadi, Anda perlu tahu audiens Anda saat menulis kode. Jika Anda seorang pemula yang benar-benar mengajar, maka Anda ingin membuatnya tetap sederhana dan mungkin menulisnya seperti contoh pertama Anda. Jika Anda bekerja di antara sekumpulan pengembang profesional dengan pengalaman ruby bertahun-tahun, maka tulis kode yang memanfaatkan bahasa dan singkatkan. Jika ada suatu tempat di antara keduanya, maka bidiklah di suatu tempat di antaranya.
Satu hal yang saya akan katakan: waspadalah "kode pintar", seperti pada contoh terakhir Anda. Tanyakan kepada diri sendiri, apakah
[firstname, surname].all?(&:blank?)
menambahkan sesuatu selain membuat Anda merasa pintar karena itu memamerkan keahlian Anda, bahkan jika sekarang agak sulit untuk dibaca? Saya akan mengatakan contoh ini sepertinya tidak sepenuhnya masuk ke dalam kategori itu. Jika Anda membandingkan lima nilai, saya akan melihatnya sebagai kode yang baik. Jadi sekali lagi, tidak ada garis absolut di sini, hanya berhati-hati menjadi terlalu pintar.Jadi dalam ringkasan: keterbacaan mengharuskan Anda mengetahui audiens Anda dan menargetkan kode Anda sesuai dan menulis kode ringkas, tetapi jelas; jangan pernah menulis kode "pintar". Tetap pendek, tapi jangan terlalu pendek.
sumber
return "#{firstname} #{surname}".strip
Ini mungkin pertanyaan di mana sulit untuk tidak memberikan jawaban berdasarkan opini, tapi, inilah dua sen saya.
Jika Anda menemukan bahwa membuat kode lebih pendek tidak berdampak keterbacaan, atau bahkan memperbaikinya, lakukan saja. Jika kode menjadi kurang dapat dibaca, maka Anda perlu mempertimbangkan jika ada alasan yang cukup baik untuk membiarkannya seperti itu. Melakukannya hanya karena lebih pendek, atau keren, atau hanya karena Anda bisa, adalah contoh alasan buruk. Anda juga perlu mempertimbangkan jika membuat kode lebih pendek akan membuatnya kurang dimengerti untuk orang lain yang bekerja dengan Anda.
Jadi apa alasannya? Ini memang panggilan penilaian, tetapi contohnya mungkin seperti optimasi kinerja (setelah pengujian kinerja, tentu saja, tidak di muka). Sesuatu yang memberi Anda manfaat yang bersedia Anda bayar dengan keterbacaan yang menurun. Dalam hal ini, Anda dapat mengurangi kelemahan dengan memberikan komentar yang membantu (yang menjelaskan apa yang dilakukan kode, dan mengapa itu harus dibuat agak samar). Lebih baik lagi, Anda dapat mengekstrak kode itu ke fungsi terpisah dengan nama yang bermakna, sehingga hanya satu baris di situs panggilan yang menjelaskan apa yang terjadi (melalui nama fungsi) tanpa masuk ke perincian (namun, orang memiliki perbedaan pendapat tentang ini, jadi ini adalah panggilan penilaian lain yang harus Anda buat).
sumber
Jawabannya agak subyektif, tetapi Anda harus bertanya pada diri sendiri dengan semua kejujuran yang bisa Anda kumpulkan, apakah Anda akan dapat memahami kode itu ketika Anda kembali ke sana dalam satu atau dua bulan.
Setiap perubahan harus meningkatkan kemampuan rata-rata orang untuk memahami kode. Agar kode dapat dipahami, sebaiknya gunakan pedoman berikut:
Yang mengatakan, ada kalanya kode diperluas membantu dengan memahami apa yang terjadi lebih baik. Salah satu contoh dengan itu berasal dari C # dan LINQ. LINQ adalah alat yang hebat dan dapat meningkatkan keterbacaan dalam beberapa situasi, tetapi saya juga mengalami beberapa situasi di mana itu jauh lebih membingungkan. Saya memiliki beberapa umpan balik dalam peer review yang menyarankan untuk mengubah ekspresi menjadi sebuah loop dengan pernyataan yang sesuai jika orang lain dapat mempertahankannya dengan lebih baik. Ketika saya menurut, mereka benar. Secara teknis, LINQ lebih idiomatis untuk C #, tetapi ada kasus-kasus di mana ia menurunkan pemahaman dan solusi yang lebih verbal meningkatkannya.
Saya mengatakan semua itu untuk mengatakan ini:
Ingat, Anda atau seseorang seperti Anda harus mempertahankan kode itu nanti. Lain kali Anda bertemu mungkin berbulan-bulan di telepon. Bantulah diri Anda sendiri dan jangan mengejar mengurangi jumlah baris dengan biaya untuk dapat memahami kode Anda.
sumber
Keterbacaan adalah properti yang ingin Anda miliki, memiliki-banyak-satu-liner tidak. Jadi alih-alih "one-liners vs readability" pertanyaannya seharusnya:
Kapan one-liners meningkatkan keterbacaan, dan kapan mereka merusaknya?
Saya percaya one-liner baik untuk dibaca ketika mereka memenuhi dua syarat ini:
Misalnya, katakanlah
name
itu bukan nama ... yang bagus untuk metode Anda. Bahwa menggabungkan nama depan dan nama keluarga, atau menggunakan email sebagai ganti nama, bukanlah hal yang wajar untuk dilakukan. Jadi, alih-alihname
hal terbaik yang bisa Anda dapatkan ternyata menjadi panjang dan rumit:Nama panjang seperti itu menunjukkan bahwa ini sangat spesifik - jika lebih umum Anda bisa menemukan nama yang lebih umum. Jadi membungkusnya dengan metode tidak membantu dengan keterbacaan (terlalu lama) atau KERING (terlalu spesifik untuk digunakan di tempat lain), jadi lebih baik membiarkan kode di sana.
Tetap saja - mengapa membuatnya satu liner? Mereka biasanya kurang terbaca dari kode multiline. Di sinilah kita harus memeriksa kondisi kedua saya - aliran kode di sekitarnya. Bagaimana jika Anda memiliki sesuatu seperti ini:
Kode multiline itu sendiri dapat dibaca - tetapi ketika Anda mencoba membaca kode di sekitarnya (mencetak berbagai bidang), konstruksi multiline mengganggu aliran. Ini lebih mudah dibaca:
Aliran Anda tidak terganggu, dan Anda dapat fokus pada ekspresi spesifik jika perlu.
Apakah ini kasusmu? Tentu saja tidak!
Kondisi pertama kurang relevan - Anda sudah menganggapnya cukup umum untuk pantas mendapatkan metode, dan muncul dengan nama untuk metode yang jauh lebih mudah dibaca daripada penerapannya. Jelas Anda tidak akan mengekstraknya ke suatu fungsi lagi.
Adapun kondisi kedua - apakah itu mengganggu aliran kode sekitarnya? Tidak! Kode yang mengelilinginya adalah deklarasi metode, yang memilih tujuan
name
itu sendiri. Logika memilih nama tidak mengganggu aliran kode di sekitarnya - itu adalah tujuan kode sekitarnya!Kesimpulan - jangan membuat seluruh fungsi tubuh menjadi satu garis
One-liner bagus jika Anda ingin melakukan sesuatu yang sedikit rumit tanpa mengganggu alurnya. Deklarasi fungsi sudah mengganggu aliran (sehingga tidak akan terganggu ketika Anda memanggil fungsi itu), jadi menjadikan seluruh fungsi tubuh satu-baris tidak membantu keterbacaan.
Catatan
Saya mengacu pada "penuh sesak nafas" fungsi dan metode - fungsi tidak inline atau ekspresi lambda yang merupakan biasanya bagian dari kode sekitarnya dan kebutuhan untuk muat dalam aliran itu.
sumber