Parameter Yang Dinamai Membuat Kode Lebih Mudah Dibaca, Lebih Keras Untuk Menulis
Ketika saya membaca sepotong kode, parameter bernama dapat memperkenalkan konteks yang membuat kode lebih mudah dimengerti. Perhatikan misalnya konstruktor ini: Color(1, 102, 205, 170)
. Apakah maksudnya itu? Memang, Color(alpha: 1, red: 102, green: 205, blue: 170)
akan jauh lebih mudah dibaca. Tapi sayangnya, Compiler mengatakan "tidak" - ia ingin Color(a: 1, r: 102, g: 205, b: 170)
. Saat menulis kode menggunakan parameter bernama, Anda menghabiskan waktu yang tidak perlu mencari nama yang tepat - lebih mudah untuk melupakan nama-nama persis dari beberapa parameter daripada melupakan urutannya.
Ini sekali menggigit saya ketika menggunakan DateTime
API yang memiliki dua kelas saudara untuk poin dan durasi dengan antarmuka yang hampir sama. Sementara DateTime->new(...)
menerima second => 30
argumen, yang DateTime::Duration->new(...)
diinginkan seconds => 30
, dan serupa untuk unit lain. Ya, itu benar-benar masuk akal, tetapi ini menunjukkan kepada saya bahwa parameter bernama ≠ kemudahan penggunaan.
Nama Buruk Bahkan Tidak Membuatnya Lebih Mudah Dibaca
Contoh lain bagaimana parameter bernama bisa jelek mungkin bahasa R. Sepotong kode ini menciptakan plot data:
plot(plotdata$n, plotdata$mu, type="p", pch=17, lty=1, bty="n", ann=FALSE, axes=FALSE)
Anda melihat dua argumen posisi untuk baris data x dan y , dan kemudian daftar parameter bernama. Ada banyak lagi opsi dengan standar, dan hanya yang terdaftar yang standarnya ingin saya ubah atau tetapkan secara eksplisit. Setelah kami mengabaikan bahwa kode ini menggunakan angka ajaib, dan dapat mengambil manfaat dari menggunakan enum (jika R punya!), Masalahnya adalah bahwa banyak dari nama parameter ini agak tidak dapat diuraikan.
pch
sebenarnya karakter plot, mesin terbang yang akan ditarik untuk setiap titik data. 17
adalah lingkaran kosong, atau sesuatu seperti itu.
lty
adalah tipe garis. Inilah 1
garis yang kuat.
bty
adalah tipe kotak. Mengaturnya untuk "n"
menghindari kotak yang ditarik di sekitar plot.
ann
mengontrol tampilan anotasi sumbu.
Untuk seseorang yang tidak tahu arti setiap singkatan, opsi ini agak membingungkan. Ini juga mengungkapkan mengapa R menggunakan label ini: Bukan sebagai kode yang mendokumentasikan diri, tetapi (menjadi bahasa yang diketik secara dinamis) sebagai kunci untuk memetakan nilai ke variabel yang benar.
Sifat Parameter Dan Tanda Tangan
Tanda tangan fungsi mungkin memiliki properti berikut:
- Argumen dapat dipesan atau tidak disusun,
- dinamai atau tidak disebutkan namanya,
- diperlukan atau opsional.
- Tanda tangan juga bisa kelebihan beban berdasarkan ukuran atau jenis,
- dan dapat memiliki ukuran yang tidak ditentukan dengan varargs.
Bahasa yang berbeda mendarat pada koordinat yang berbeda dari sistem ini. Dalam C, argumen diperintahkan, tidak disebutkan namanya, selalu diperlukan, dan dapat menjadi varargs. Di Jawa situasinya serupa, kecuali bahwa tanda tangan dapat kelebihan beban. Di Objective C, tanda tangan diperintahkan, dinamai, diperlukan, dan tidak dapat kelebihan karena itu hanya gula sintaksis sekitar C.
Bahasa yang diketik secara dinamis dengan varargs (antarmuka baris perintah, Perl, ...) dapat meniru parameter bernama opsional. Bahasa dengan ukuran tanda tangan berlebihan memiliki sesuatu seperti parameter opsional posisi.
Bagaimana Tidak Untuk Menerapkan Parameter Yang Bernama
Ketika memikirkan parameter bernama, kita biasanya mengasumsikan parameter bernama, opsional, tidak berurutan. Menerapkan ini sulit.
Parameter opsional dapat memiliki nilai default. Ini harus ditentukan oleh fungsi yang dipanggil dan tidak boleh dikompilasi ke dalam kode panggilan. Jika tidak, default tidak dapat diperbarui tanpa mengkompilasi ulang semua kode dependen.
Sekarang pertanyaan penting adalah bagaimana argumen sebenarnya diteruskan ke fungsi. Dengan parameter yang dipesan, args dapat dikirimkan dalam register, atau dalam urutan yang melekat pada stack. Ketika kami mengecualikan register untuk sesaat, masalahnya adalah bagaimana cara menempatkan argumen opsional yang tidak diurutkan ke dalam tumpukan.
Untuk itu, kita perlu beberapa urutan atas argumen opsional. Bagaimana jika kode deklarasi diubah? Karena urutannya tidak relevan, pemesanan ulang dalam deklarasi fungsi tidak boleh mengubah posisi nilai pada stack. Kami juga harus mempertimbangkan jika menambahkan parameter opsional baru dimungkinkan. Dari perspektif pengguna, ini kelihatannya demikian, karena kode yang tidak menggunakan parameter sebelumnya harus tetap bekerja dengan parameter baru. Jadi ini tidak termasuk pemesanan seperti menggunakan urutan dalam deklarasi atau menggunakan urutan alfabet.
Pertimbangkan ini juga dalam terang subtyping dan Prinsip Pergantian Liskov - dalam output yang dikompilasi, instruksi yang sama harus dapat memanggil metode pada subtipe dengan parameter yang mungkin baru bernama, dan pada supertipe.
Kemungkinan Implementasi
Jika kita tidak dapat memiliki urutan yang pasti, maka kita memerlukan beberapa struktur data yang tidak teratur.
Implementasi yang paling sederhana adalah dengan hanya memberikan nama parameter beserta nilainya. Ini adalah bagaimana params yang dinamai diemulasi dalam Perl atau dengan alat baris perintah. Ini menyelesaikan semua masalah ekstensi yang disebutkan di atas, tetapi bisa menjadi pemborosan ruang - bukan pilihan dalam kode kinerja-kritis. Juga, memproses params yang dinamai ini sekarang jauh lebih rumit daripada sekadar mengeluarkan nilai dari stack.
Sebenarnya, persyaratan ruang dapat dikurangi dengan menggunakan penggabungan string, yang dapat mengurangi perbandingan string di kemudian hari menjadi perbandingan pointer (kecuali jika tidak dapat dijamin bahwa string statis sebenarnya dikumpulkan, dalam hal ini kedua string harus dibandingkan dalam detail).
Sebagai gantinya, kami juga bisa meneruskan struktur data pintar yang berfungsi sebagai kamus argumen bernama. Ini murah di sisi penelepon, karena set kunci secara statis dikenal di lokasi panggilan. Ini akan memungkinkan untuk membuat fungsi hash yang sempurna atau untuk menghitung ulang sebuah trie. Callee masih harus menguji keberadaan semua nama parameter yang mungkin agak mahal. Sesuatu seperti ini digunakan oleh Python.
Jadi itu terlalu mahal dalam banyak kasus
Jika suatu fungsi dengan parameter bernama harus dapat diperluas dengan benar, pemesanan definitif tidak dapat diasumsikan. Jadi hanya ada dua solusi:
- Buat urutan params bernama bagian dari tanda tangan, dan larang perubahan nanti. Ini berguna untuk mendokumentasikan kode diri, tetapi tidak membantu dengan argumen opsional.
- Berikan struktur data nilai kunci ke callee, yang kemudian harus mengekstrak informasi yang berguna. Ini sangat mahal sebagai perbandingan, dan biasanya hanya terlihat dalam bahasa scripting tanpa penekanan pada kinerja.
Perangkap Lainnya
Nama-nama variabel dalam deklarasi fungsi biasanya memiliki beberapa makna internal dan bukan bagian dari antarmuka - bahkan jika banyak alat dokumentasi masih akan menunjukkannya. Dalam banyak kasus Anda ingin nama yang berbeda untuk variabel internal dan argumen bernama yang sesuai. Bahasa yang tidak memungkinkan memilih nama yang terlihat secara eksternal dari parameter bernama tidak mendapatkan banyak dari mereka jika nama variabel tidak digunakan dengan konteks panggilan dalam pikiran.
Masalah dengan emulasi argumen bernama adalah kurangnya pemeriksaan statis pada sisi pemanggil. Ini sangat mudah untuk dilupakan ketika melewati kamus argumen (melihat Anda, Python). Hal ini penting karena lewat kamus adalah solusi umum, misalnya dalam JavaScript: foo({bar: "baz", qux: 42})
. Di sini, baik jenis nilai maupun keberadaan atau ketiadaan nama-nama tertentu dapat diperiksa secara statis.
Mengemulasi Parameter Bernama (Dalam Bahasa yang Diketik Secara Statis)
Cukup menggunakan string sebagai kunci, dan objek apa pun sebagai nilai tidak terlalu berguna di hadapan pemeriksa tipe statis. Namun, argumen bernama dapat ditiru dengan struct, atau objek literal:
// Java
static abstract class Arguments {
public String bar = "default";
public int qux = 0;
}
void foo(Arguments args) {
...
}
/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});