Berurusan dengan tidak mengetahui nama parameter suatu fungsi saat Anda memanggilnya


13

Inilah masalah pemrograman / bahasa yang ingin saya dengarkan.

Kami telah mengembangkan konvensi yang diikuti oleh sebagian besar programmer yang bukan merupakan bagian dari sintaks bahasa tetapi berfungsi untuk membuat kode lebih mudah dibaca. Ini tentu saja selalu merupakan masalah perdebatan, tetapi setidaknya ada beberapa konsep inti yang menurut sebagian besar programmer menyenangkan. Memberi nama variabel Anda dengan tepat, memberi nama secara umum, membuat baris Anda tidak terlalu panjang, menghindari fungsi panjang, enkapsulasi, hal-hal itu.

Namun, ada masalah yang saya belum menemukan orang mengomentari dan mungkin saja yang terbesar dari kelompok itu. Masalah argumen menjadi anonim saat Anda memanggil suatu fungsi.

Fungsi berasal dari matematika di mana f (x) memiliki makna yang jelas karena fungsi memiliki definisi yang jauh lebih ketat yang biasanya dilakukannya dalam pemrograman. Fungsi murni dalam matematika dapat melakukan jauh lebih sedikit daripada yang mereka bisa dalam pemrograman dan mereka adalah alat yang jauh lebih elegan, mereka biasanya hanya mengambil satu argumen (yang biasanya angka) dan mereka selalu mengembalikan satu nilai (juga biasanya angka). Jika suatu fungsi mengambil banyak argumen, mereka hampir selalu hanya merupakan dimensi ekstra dari domain fungsi. Dengan kata lain, satu argumen tidak lebih penting daripada yang lain. Mereka secara eksplisit dipesan, tentu, tetapi selain itu, mereka tidak memiliki pemesanan semantik.

Namun dalam pemrograman, kami memiliki lebih banyak fungsi mendefinisikan kebebasan, dan dalam hal ini saya berpendapat itu bukan hal yang baik. Situasi umum, Anda memiliki fungsi yang didefinisikan seperti ini

func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}

Melihat definisi tersebut, jika fungsinya ditulis dengan benar, sangat jelas apa itu. Saat memanggil fungsi, Anda bahkan mungkin memiliki beberapa keajaiban penyelesaian intellisense / kode yang terjadi di IDE / editor Anda yang akan memberi tahu Anda apa argumen selanjutnya. Tapi tunggu. Jika saya membutuhkannya ketika saya benar-benar menulis panggilan, bukankah ada sesuatu yang kita lewatkan di sini? Orang yang membaca kode tidak memiliki manfaat dari IDE dan kecuali mereka melompat ke definisi, mereka tidak tahu yang mana dari dua persegi panjang yang dilewati karena argumen digunakan untuk apa.

Masalahnya bahkan lebih jauh dari itu. Jika argumen kami berasal dari beberapa variabel lokal, mungkin ada situasi di mana kita bahkan tidak tahu apa argumen kedua karena kita hanya melihat nama variabel. Ambil contoh baris kode ini

DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

Ini dikurangi ke berbagai luasan dalam bahasa yang berbeda tetapi bahkan dalam bahasa yang diketik dengan ketat dan bahkan jika Anda memberi nama variabel Anda dengan masuk akal, Anda bahkan tidak menyebutkan jenis variabelnya ketika Anda meneruskannya ke fungsi.

Seperti biasanya dengan pemrograman, ada banyak solusi potensial untuk masalah ini. Banyak sudah diterapkan dalam bahasa populer. Parameter yang dinamai dalam C # misalnya. Namun, semua yang saya tahu memiliki kelemahan signifikan. Memberi nama setiap parameter pada setiap panggilan fungsi tidak mungkin menghasilkan kode yang dapat dibaca. Hampir terasa seperti mungkin kita mengatasi kemungkinan yang diberikan oleh pemrograman teks biasa. Kami telah pindah dari teks HANYA di hampir setiap area, namun kami masih kode yang sama. Informasi lebih lanjut diperlukan untuk ditampilkan dalam kode? Tambahkan lebih banyak teks. Ngomong-ngomong, ini menjadi sedikit singgung jadi saya akan berhenti di sini.

Satu balasan yang saya dapatkan pada potongan kode kedua adalah bahwa Anda mungkin akan terlebih dahulu membongkar array ke beberapa variabel bernama dan kemudian menggunakannya tetapi nama variabel dapat berarti banyak hal dan cara namanya tidak selalu memberi tahu Anda cara seharusnya. ditafsirkan dalam konteks fungsi yang disebut. Dalam lingkup lokal, Anda mungkin memiliki dua persegi panjang bernama leftRectangle dan rightRectangle karena itulah yang mereka wakili secara semantik, tetapi tidak perlu diperluas ke apa yang mereka wakili ketika diberikan ke suatu fungsi.

Bahkan, jika variabel Anda dinamai dalam konteks fungsi yang dipanggil daripada Anda memperkenalkan informasi kurang dari Anda berpotensi dengan panggilan fungsi dan pada tingkat tertentu jika memang mengarah ke kode kode yang lebih buruk. Jika Anda memiliki prosedur yang menghasilkan persegi panjang yang Anda simpan di rectForClipping dan kemudian prosedur lain yang menyediakan rectForDrawing, maka panggilan sebenarnya ke DrawRectangleClipped hanyalah upacara. Baris yang berarti tidak ada yang baru dan ada agar komputer tahu apa yang sebenarnya Anda inginkan meskipun Anda sudah menjelaskannya dengan penamaan Anda. Ini bukan hal yang baik.

Saya benar-benar ingin mendengar perspektif baru tentang ini. Saya yakin saya bukan orang pertama yang menganggap ini masalah, jadi bagaimana cara mengatasinya?


2
Saya bingung tentang apa masalah sebenarnya ... sepertinya ada beberapa ide di sini, tidak yakin yang mana poin utama Anda.
FrustratedWithFormsDesigner

1
Dokumentasi untuk fungsi harus memberi tahu Anda apa yang dilakukan argumen. Anda mungkin keberatan bahwa seseorang yang membaca kode mungkin tidak memiliki dokumentasi, tetapi kemudian tidak benar - benar tahu apa yang kode lakukan dan makna apa pun yang mereka ambil dari membacanya adalah dugaan yang berpendidikan. Dalam konteks apa pun di mana pembaca perlu tahu bahwa kode itu benar, dia akan membutuhkan dokumentasi.
Doval

3
@Darwin Dalam pemrograman fungsional semua fungsi masih hanya memiliki 1 argumen. Jika Anda perlu memberikan "argumen berganda", parameternya biasanya berupa tuple (jika Anda ingin dipesan) atau catatan (jika Anda tidak menginginkannya). Selain itu, mudah untuk membuat versi fungsi khusus kapan saja, sehingga Anda dapat mengurangi jumlah argumen yang diperlukan. Karena hampir semua bahasa fungsional menyediakan sintaks untuk tupel dan catatan, menggabungkan nilai tidak menimbulkan rasa sakit, dan Anda mendapatkan komposisi secara gratis (Anda dapat mengaitkan fungsi yang mengembalikan tupel dengan yang mengambil tupel.)
Doval

1
@Bergi Orang cenderung menggeneralisasi lebih banyak dalam FP murni jadi saya pikir fungsinya sendiri biasanya lebih kecil dan lebih banyak. Aku bisa saja pergi. Saya tidak punya banyak pengalaman bekerja pada proyek nyata dengan Haskell dan geng.
Darwin

4
Saya pikir jawabannya adalah "Jangan beri nama variabel Anda 'deserializedArray'"?
whatsisname

Jawaban:


10

Saya setuju bahwa cara fungsi sering digunakan dapat menjadi bagian yang membingungkan dari penulisan kode, dan terutama membaca kode.

Jawaban untuk masalah ini sebagian tergantung pada bahasa. Seperti yang Anda sebutkan, C # memiliki parameter bernama. Solusi Objective-C untuk masalah ini melibatkan nama metode yang lebih deskriptif. Misalnya, stringByReplacingOccurrencesOfString:withString:adalah metode dengan parameter yang jelas.

Di Groovy, beberapa fungsi mengambil peta, memungkinkan sintaksis seperti berikut:

restClient.post(path: 'path/to/somewhere',
            body: requestBody,
            requestContentType: 'application/json')

Secara umum, Anda dapat mengatasi masalah ini dengan membatasi jumlah parameter yang Anda berikan ke suatu fungsi. Saya pikir 2-3 adalah batas yang baik. Jika tampaknya suatu fungsi membutuhkan lebih banyak parameter, itu menyebabkan saya untuk memikirkan kembali desain. Tetapi, ini bisa lebih sulit untuk dijawab secara umum. Terkadang Anda mencoba melakukan terlalu banyak dalam suatu fungsi. Terkadang masuk akal untuk mempertimbangkan kelas untuk menyimpan parameter Anda. Juga, dalam praktiknya, saya sering menemukan bahwa fungsi yang mengambil banyak parameter biasanya memiliki banyak dari mereka sebagai opsional.

Bahkan dalam bahasa seperti Objective-C, masuk akal untuk membatasi jumlah parameter. Salah satu alasannya adalah banyak parameter bersifat opsional. Sebagai contoh, lihat rangeOfString: dan variasinya di NSString .

Pola yang sering saya gunakan di Jawa adalah menggunakan kelas gaya-lancar sebagai parameter. Sebagai contoh:

something.draw(new Box().withHeight(5).withWidth(20))

Ini menggunakan kelas sebagai parameter, dan dengan kelas gaya fasih, membuat kode mudah dibaca.

Cuplikan Java di atas juga membantu pengaturan urutan parameter yang mungkin tidak terlalu jelas. Kami biasanya menganggap dengan koordinat bahwa X datang sebelum Y. Dan saya biasanya melihat tinggi sebelum lebar sebagai sebuah konvensi, tetapi itu masih tidak begitu jelas ( something.draw(5, 20)).

Saya juga melihat beberapa fungsi seperti drawWithHeightAndWidth(5, 20)tetapi bahkan ini tidak dapat mengambil terlalu banyak parameter, atau Anda akan mulai kehilangan keterbacaan.


2
Perintah, jika Anda melanjutkan pada contoh Java, memang bisa sangat rumit. Sebagai contoh, bandingkan konstruktor berikut dari awt: Dimension(int width, int height)dan GridLayout(int rows, int cols)(jumlah baris adalah tinggi, artinya GridLayoutmemiliki tinggi terlebih dahulu dan Dimensionlebarnya).
Pierre Arlaud

1
Ketidakkonsistenan semacam itu juga sangat dikritik dengan PHP ( eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design ), misalnya: array_filter($input, $callback)versus array_map($callback, $input), strpos($haystack, $needle)versusarray_search($needle, $haystack)
Pierre Arlaud

12

Sebagian besar diselesaikan dengan penamaan fungsi, parameter, dan argumen yang baik. Anda sudah menjelajahi itu dan menemukan kekurangannya. Sebagian besar kekurangan tersebut dimitigasi dengan menjaga fungsi tetap kecil, dengan sejumlah kecil parameter, baik dalam konteks panggilan dan konteks yang dipanggil. Contoh khusus Anda bermasalah karena fungsi yang Anda panggil sedang mencoba melakukan beberapa hal sekaligus: tentukan persegi panjang dasar, tentukan daerah kliping, gambar, dan isi dengan warna tertentu.

Ini seperti mencoba menulis kalimat hanya menggunakan kata sifat. Masukkan lebih banyak kata kerja (panggilan fungsi) di sana, buat subjek (objek) untuk kalimat Anda, dan lebih mudah dibaca:

rect.clip(clipRect).fill(color)

Bahkan jika clipRectdan colormemiliki nama yang buruk (dan mereka tidak seharusnya), Anda masih bisa membedakan tipe mereka dari konteksnya.

Contoh deserialisasi Anda bermasalah karena konteks panggilan mencoba melakukan terlalu banyak sekaligus: deserialisasi dan menggambar sesuatu. Anda perlu menetapkan nama yang masuk akal dan memisahkan kedua tanggung jawab dengan jelas. Minimal, sesuatu seperti ini:

(rect, clipRect, color) = deserializeClippedRect()
rect.clip(clipRect).fill(color)

Banyak masalah keterbacaan disebabkan oleh upaya untuk menjadi terlalu ringkas, melompati tahap peralihan yang dibutuhkan manusia untuk memahami konteks dan semantik.


1
Saya suka ide merangkai beberapa panggilan fungsi untuk mengklarifikasi makna, tetapi bukankah itu hanya menari di sekitar masalah? Ini pada dasarnya "Saya ingin menulis kalimat, tetapi bahasa yang saya tuntut tidak akan membiarkan saya jadi saya hanya bisa menggunakan padanan terdekat"
Darwin

@Darwin IMHO tidak seperti ini bisa diperbaiki dengan membuat bahasa pemrograman lebih alami. Bahasa alami sangat ambigu dan kita hanya bisa memahaminya dalam konteks dan sebenarnya tidak pernah bisa yakin. Merangkai panggilan fungsi jauh lebih baik karena setiap istilah (idealnya) memiliki dokumentasi dan sumber yang tersedia dan kami memiliki tanda kurung dan titik-titik yang membuat struktur menjadi jelas.
maaartinus

3

Dalam praktiknya, ini diselesaikan dengan desain yang lebih baik. Sangat luar biasa untuk fungsi yang ditulis dengan baik untuk mengambil lebih dari 2 input, dan ketika itu terjadi, itu tidak biasa bagi banyak input untuk tidak dapat diagregasi ke dalam beberapa ikatan kohesif. Ini membuatnya cukup mudah untuk memecah fungsi atau parameter agregat sehingga Anda tidak membuat fungsi melakukan terlalu banyak. Satu memiliki dua input, menjadi mudah untuk memberi nama dan lebih jelas tentang input mana yang.

Bahasa mainan saya memiliki konsep frasa untuk menangani hal ini, dan bahasa pemrograman lain yang lebih fokus pada bahasa alami memiliki pendekatan lain untuk menghadapinya, tetapi mereka semua cenderung memiliki kelemahan lain. Plus, bahkan frasa sedikit lebih dari sintaks yang bagus membuat fungsi memiliki nama yang lebih baik. Akan selalu sulit untuk membuat nama fungsi yang baik ketika dibutuhkan banyak input.


Frasa benar-benar tampak seperti langkah maju. Saya tahu beberapa bahasa memiliki kemampuan serupa tetapi JAUH dari luas. Belum lagi, dengan semua kebencian makro yang berasal dari puritan C (++) yang tidak pernah menggunakan macro dilakukan dengan benar, kita mungkin tidak pernah memiliki fitur seperti ini dalam bahasa populer.
Darwin

Selamat datang di topik umum bahasa khusus domain , sesuatu yang saya benar-benar berharap lebih banyak orang akan memahami keuntungan dari ... (+1)
Izkata

2

Dalam Javascript (atau ECMAScript ), misalnya, banyak programmer terbiasa

melewatkan parameter sebagai seperangkat properti objek bernama dalam objek anonim tunggal.

Dan sebagai praktik pemrograman, program ini beralih dari pemrogram ke perpustakaan mereka dan dari sana ke pemrogram lain yang tumbuh menyukainya dan menggunakannya serta menulis lebih banyak perpustakaan dll.

Contoh

Alih-alih menelepon

function drawRectangleClipped (rectToDraw, fillColor, clippingRect)

seperti ini:

drawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])

, yang merupakan gaya yang valid dan benar, Anda memanggil

function drawRectangleClipped (params)

seperti ini:

drawRectangleClipped({
    rectToDraw: deserializedArray[0], 
    fillColor: deserializedArray[1], 
    clippingRect: deserializedArray[2]
})

, yang valid dan benar serta menyenangkan sehubungan dengan pertanyaan Anda.

Tentu saja, harus ada kondisi yang cocok untuk ini - dalam Javascript ini jauh lebih layak daripada di, katakanlah, C. Dalam javascript, ini bahkan melahirkan notasi struktural yang sekarang banyak digunakan yang tumbuh populer sebagai mitra yang lebih ringan untuk XML. Ini disebut JSON (Anda mungkin sudah pernah mendengarnya).


Saya tidak cukup tahu tentang bahasa itu untuk memverifikasi sintaks tapi, secara keseluruhan saya suka posting ini. Tampak cukup elegan. +1
IT Alex

Cukup sering, ini digabungkan dengan argumen normal, yaitu, ada 1-3 argumen diikuti oleh params(sering berisi argumen opsional dan sering sendiri opsional) seperti misalnya, fungsi ini . Ini membuat fungsi dengan banyak argumen cukup mudah untuk dipahami (dalam contoh saya ada 2 argumen wajib dan 6 opsi).
maaartinus

0

Anda harus menggunakan objektif-C, di sini adalah definisi fungsi:

- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

Dan ini dia digunakan:

[someObject performSelector:someSelector withObject:someObject2 withObject:someObject3];

Saya pikir ruby ​​memiliki konstruksi yang sama dan Anda dapat mensimulasikannya dalam bahasa lain dengan daftar nilai-kunci.

Untuk fungsi yang kompleks di Java, saya ingin mendefinisikan variabel dummy dalam fungsi kata-kata. Untuk contoh kiri-kanan Anda:

Rectangle referenceRectangle = leftRectangle;
Rectangle targetRectangle = rightRectangle;
doSomeWeirdStuffWithRectangles(referenceRectangle, targetRectangle);

Sepertinya lebih banyak pengkodean, tetapi misalnya Anda dapat menggunakan leftRectangle dan kemudian memperbaiki kode nanti dengan "Ekstrak variabel lokal" jika Anda pikir itu tidak akan dimengerti oleh pengelola kode di masa mendatang, yang mungkin atau mungkin bukan Anda.


Tentang contoh Java itu, saya menulis dalam pertanyaan mengapa saya pikir itu bukan solusi yang baik. Apa pendapatmu tentang itu?
Darwin

0

Pendekatan saya adalah membuat variabel lokal sementara - tetapi tidak hanya memanggil mereka LeftRectangedan RightRectangle. Sebaliknya, saya menggunakan nama yang agak panjang untuk menyampaikan lebih banyak makna. Saya sering mencoba untuk membedakan nama-nama sebanyak mungkin, misalnya tidak memanggil keduanya something_rectangle, jika peran mereka tidak terlalu simetris.

Contoh (C ++):

auto& connector_source = deserializedArray[0]; 
auto& connector_target = deserializedArray[1]; 
auto& bounding_box = deserializedArray[2]; 
DoWeirdThing(connector_source, connector_target, bounding_box)

dan saya bahkan mungkin menulis fungsi atau template pembungkus satu-liner:

template <typename T1, typename T2, typename T3>
draw_bounded_connector(
    T1& connector_source, T2& connector_target,const T3& bounding_box) 
{
    DoWeirdThing(connector_source, connector_target, bounding_box)
}

(abaikan ampersand jika Anda tidak tahu C ++).

Jika fungsi melakukan beberapa hal aneh tanpa deskripsi yang baik - maka mungkin perlu di refactored!

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.