Tentukan nama parameter opsional meskipun tidak diperlukan?

25

Pertimbangkan metode berikut:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

Dan panggilan berikut:

var ids = ReturnEmployeeIds(true);

Untuk pengembang yang baru mengenal sistem, akan sangat sulit menebak apa yang trueterjadi. Hal pertama yang akan Anda lakukan adalah mengarahkan kursor ke nama metode atau pergi ke definisi (tidak ada yang merupakan tugas besar sedikit pun). Tetapi, demi keterbacaan, apakah masuk akal untuk menulis:

var ids = ReturnEmployeeIds(includeManagement: true);

Apakah ada tempat yang, secara formal, membahas apakah secara eksplisit menentukan parameter opsional atau tidak ketika Anda membutuhkannya?

Artikel berikut membahas konvensi pengkodean tertentu: https://msdn.microsoft.com/en-gb/library/ff926074.aspx

Sesuatu yang mirip dengan artikel di atas akan sangat bagus.

JᴀʏMᴇᴇ
sumber
2
Ini adalah dua pertanyaan yang agak tidak berhubungan: (1) Haruskah Anda menuliskan argumen opsional untuk kejelasan? (2) Haruskah seseorang menggunakan booleanargumen metode, dan bagaimana?
Kilian Foth
5
Ini semua bermuara pada preferensi / gaya / pendapat pribadi. Secara pribadi, saya suka nama parameter ketika nilai yang diteruskan adalah literal (variabel mungkin sudah menyampaikan informasi yang cukup melalui namanya). Tapi, itu pendapat pribadi saya.
MetaFight
1
mungkin menyertakan tautan itu sebagai contoh sumber dalam pertanyaan Anda?
MetaFight
4
Jika itu menambahkan sesuatu, saya menjalankannya melalui StyleCop & ReSharper - tidak ada yang memiliki pandangan yang jelas tentang itu. ReSharper hanya mengatakan parameter bernama bisa dihapus dan kemudian mengatakan parameter bernama bisa ditambahkan. Saran gratis karena suatu alasan saya kira ...: - /
Robbie Dee

Jawaban:

28

Saya akan mengatakan bahwa di dunia C #, enum akan menjadi salah satu opsi terbaik di sini.

Dengan itu Anda akan dipaksa untuk menguraikan apa yang Anda lakukan dan setiap pengguna akan melihat apa yang terjadi. Itu juga aman untuk ekstensi di masa depan;

public enum WhatToInclude
{
    IncludeAll,
    ExcludeManagement
}

var ids = ReturnEmployeeIds(WhatToInclude.ExcludeManagement);

Jadi, menurut saya di sini:

enum> enum opsional> bool opsional

Sunting: Karena diskusi dengan LeopardSkinPillBoxHat di bawah ini mengenai [Flags]enum yang dalam kasus khusus ini mungkin cocok (karena kita secara khusus berbicara tentang memasukkan / mengecualikan hal-hal), saya malah mengusulkan menggunakan ISet<WhatToInclude>sebagai parameter.

Ini adalah konsep yang lebih "modern" dengan beberapa keunggulan, sebagian besar berasal dari fakta bahwa itu cocok dalam koleksi LINQ, tetapi juga yang [Flags]memiliki maksimal 32 grup. Kelemahan utama menggunakanISet<WhatToInclude> adalah seberapa buruk set didukung dalam sintaks C #:

var ids = ReturnEmployeeIds(
    new HashSet<WhatToInclude>(new []{ 
        WhatToInclude.Cleaners, 
        WhatToInclude.Managers});

Beberapa di antaranya mungkin dimitigasi oleh fungsi pembantu, baik untuk menghasilkan set dan untuk membuat "set shortcut" seperti

var ids = ReturnEmployeeIds(WhatToIncludeHelper.IncludeAll)
NiklasJ
sumber
Saya cenderung setuju; dan secara umum telah menggunakan enum dalam kode baru saya jika ada kemungkinan samar bahwa kondisi akan diperluas di masa depan. Mengganti bool dengan enum ketika sesuatu menjadi tri-state akhirnya menciptakan diff yang sangat berisik dan membuat kode menyalahkan jauh lebih membosankan; ketika Anda akhirnya harus me-retrofit kode Anda lagi setahun ke depan untuk opsi ke-3.
Dan Neely
3
Saya suka enumpendekatannya, tapi saya bukan penggemar berat pencampuran Includedan Excludesama enumseperti itu lebih sulit untuk memahami apa yang terjadi. Jika Anda ingin ini benar-benar dapat diperluas, Anda dapat membuatnya [Flags] enum, membuat semuanya IncludeXxx, dan menyediakan IncludeAllyang diatur untuk Int32.MaxValue. Kemudian Anda dapat memberikan 2 ReturnEmployeeIdskelebihan - yang mengembalikan semua karyawan, dan yang mengambil WhatToIncludeparameter filter yang bisa merupakan kombinasi dari enum flags.
LeopardSkinPillBoxHat
@LeopardSkinPillBoxHat Ok, saya setuju dengan pengecualian / termasuk. Jadi ini adalah sedikit contoh buat-buat, tapi saya bisa menyebutnya IncludeAllButManagement. Pada poin Anda yang lain, saya tidak setuju - saya pikir itu [Flags]adalah peninggalan dan hanya harus digunakan untuk alasan warisan atau kinerja. Saya pikir a ISet<WhatToInclude>adalah konsep yang jauh lebih baik (sayangnya C # kekurangan gula sintaksis untuk membuatnya bagus untuk digunakan).
NiklasJ
@ NiklasJ - Saya suka Flagspendekatan karena memungkinkan pemanggil untuk lulus WhatToInclude.Managers | WhatToInclude.Cleanersdan bitwise atau mengekspresikan dengan tepat bagaimana pemanggil akan memprosesnya (yaitu manajer atau pembersih). Gula sintaksis intuitif :-)
LeopardSkinPillBoxHat
@LeopardSkinPillBoxHat Tepat, tapi itulah satu-satunya kelebihan metode itu. Kerugian meliputi misalnya jumlah pilihan terbatas dan tidak kompatibel dengan konsep seperti LINQ lainnya.
NiklasJ
20

apakah masuk akal untuk menulis:

 var ids=ReturnEmployeeIds(includeManagement: true);

Dapat diperdebatkan jika ini adalah "gaya yang baik", tapi IMHO ini setidaknya bukan gaya yang buruk, dan berguna, selama baris kode tidak menjadi "terlalu panjang". Tanpa parameter bernama, itu juga gaya umum untuk memperkenalkan variabel yang menjelaskan:

 bool includeManagement=true;
 var ids=ReturnEmployeeIds(includeManagement);

Gaya itu mungkin lebih umum di antara programmer C # daripada varian parameter bernama, karena parameter bernama bukan bagian dari bahasa sebelum Versi 4.0. Efeknya pada keterbacaan hampir sama, hanya perlu satu baris kode tambahan.

Jadi rekomendasi saya: jika Anda berpikir menggunakan enum atau dua fungsi dengan nama yang berbeda adalah "berlebihan", atau Anda tidak ingin atau tidak dapat mengubah tanda tangan untuk alasan yang berbeda, silakan dan gunakan parameter bernama.

Doc Brown
sumber
Mungkin ingin dicatat bahwa Anda menggunakan memori tambahan pada contoh kedua
Alvaro
10
@ Alvaro Itu sangat tidak mungkin benar dalam kasus sepele seperti itu (di mana variabel memiliki nilai konstan). Bahkan jika itu, hampir tidak layak disebut. Sebagian besar dari kita tidak menggunakan RAM 4KB akhir-akhir ini.
Chris Hayes
3
Karena ini adalah C #, Anda dapat menandai includeManagementsebagai lokal constdan menghindari menggunakan memori tambahan sama sekali.
Jacob Krall
8
Kawan, saya tidak akan menambahkan apa pun pada jawaban saya yang mungkin hanya mengalihkan perhatian dari apa yang menurut saya penting di sini.
Doc Brown
17

Jika Anda menemukan kode seperti:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

Anda bisa menjamin apa yang ada di dalamnya {}akan seperti:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
    if (includeManagement)
        return something
    else
        return something else
}

Dengan kata lain, boolean digunakan untuk memilih antara dua tindakan: metode ini memiliki dua tanggung jawab. Ini dijamin bau kode . Kita harus selalu berusaha untuk memastikan bahwa metode hanya melakukan satu hal; mereka hanya memiliki satu tanggung jawab. Karena itu, saya berpendapat bahwa kedua solusi Anda adalah pendekatan yang salah. Menggunakan parameter opsional adalah tanda bahwa metode melakukan lebih dari satu hal. Parameter Boolean juga merupakan tanda ini. Memiliki keduanya pasti harus mengatur bel alarm berbunyi. Apa yang harus Anda lakukan karena itu, adalah refactor dua set fungsi yang berbeda menjadi dua metode terpisah. Berikan nama metode yang menjelaskan apa yang mereka lakukan, misalnya:

public List<Guid> GetNonManagementEmployeeIds()
{

}

public List<Guid> GetAllEmployeeIds()
{

}

Ini akan meningkatkan keterbacaan kode dan memastikan Anda lebih baik mengikuti prinsip-prinsip SOLID.

EDIT Seperti yang telah ditunjukkan dalam komentar, kasus di sini bahwa kedua metode ini kemungkinan akan memiliki fungsionalitas bersama, dan bahwa fungsionalitas bersama kemungkinan besar akan dibungkus dengan fungsi pribadi yang akan memerlukan parameter boolean untuk mengontrol beberapa aspek dari apa yang dilakukannya. .

Dalam hal ini, haruskah nama parameter diberikan, meskipun kompiler tidak memerlukannya? Saya menyarankan tidak ada jawaban sederhana untuk itu dan bahwa penilaian diperlukan berdasarkan kasus per kasus:

  • Argumen untuk menentukan nama, adalah bahwa pengembang lain (termasuk Anda dalam waktu enam bulan) harus menjadi audiens utama kode Anda. Kompiler jelas merupakan audiens sekunder. Jadi selalu tulis kode untuk memudahkan orang lain membaca; daripada hanya memasok apa yang dibutuhkan oleh kompiler. Contoh bagaimana kita menggunakan aturan ini setiap hari, adalah bahwa kita tidak mengisi kode kita dengan variabel _1, _2 dll, kita menggunakan nama yang bermakna (yang tidak berarti apa-apa bagi kompilator).
  • Argumen balasannya adalah bahwa metode pribadi adalah detail implementasi. Orang bisa mengharapkan siapa pun yang melihat metode seperti di bawah ini, akan menelusuri kode untuk memahami tujuan parameter boolean karena mereka berada di dalam kode internal:
public List<Guid> GetNonManagementEmployeeIds()
{
    return ReturnEmployeeIds(true);
}

Apakah file sumber, dan metodenya, kecil dan mudah dipahami? Jika ya, maka parameter yang disebutkan mungkin berjumlah noise. Jika tidak, mungkin akan sangat membantu. Jadi terapkan aturan sesuai keadaan.

David Arno
sumber
12
Saya mendengar apa yang Anda katakan, tetapi Anda agak merindukan apa yang saya tanyakan. Dengan asumsi skenario yang paling tepat untuk menggunakan parameter opsional (skenario ini memang ada), apakah boleh menyebutkan nama parameter meskipun itu tidak perlu?
JᴀʏMᴇᴇ
4
Semua benar, tetapi pertanyaannya adalah tentang panggilan ke metode seperti itu. Parameter flag juga tidak menjamin cabang dalam metode ini sama sekali. Mungkin saja diteruskan ke lapisan lain.
Robbie Dee
Ini tidak salah, tetapi terlalu menyederhanakan. Dalam kode nyata, Anda akan menemukan public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }, jadi membaginya menjadi dua metode mungkin berarti kedua metode tersebut akan memerlukan hasil perhitungan pertama sebagai parameter.
Doc Brown
4
Saya pikir apa yang kita semua katakan adalah wajah publik dari kelas Anda mungkin harus mengekspos dua metode (masing-masing dengan satu tanggung jawab) tetapi, secara internal, masing-masing metode tersebut dapat secara wajar meneruskan permintaan ke metode pribadi tunggal dengan parameter bool bercabang .
MetaFight
1
Saya bisa membayangkan 3 kasus untuk perilaku yang berbeda antara dua fungsi. 1. Ada pra-pemrosesan yang berbeda. Suruh fungsi publik pra-proses argumen ke dalam fungsi pribadi. 2. Ada post-processing yang berbeda. Mintalah fungsi publik memposting proses dari fungsi pribadi. 3. Ada perilaku menengah yang berbeda. Ini dapat diteruskan sebagai lambda ke fungsi pribadi, dengan asumsi bahasa Anda mendukungnya. Sering kali, ini mengarah pada abstraksi baru yang dapat digunakan kembali yang tidak benar-benar terjadi pada Anda sebelumnya.
jpmc26
1

Untuk pengembang yang baru mengenal sistem, akan sangat sulit menebak apa yang sebenarnya dilakukan. Hal pertama yang akan Anda lakukan adalah mengarahkan kursor ke nama metode atau pergi ke definisi (keduanya tidak merupakan tugas besar sedikit pun)

Ya benar. Kode dokumentasi diri adalah hal yang indah. Seperti yang telah ditunjukkan oleh jawaban lain, ada cara untuk menghapus ambiguitas panggilan ReturnEmployeeIdsdengan menggunakan enum, nama metode yang berbeda, dll. Namun terkadang Anda tidak dapat benar-benar menghindari kasus ini tetapi Anda tidak ingin pergi dan menggunakan mereka di mana-mana (kecuali jika Anda suka verbositas visual basic).

Misalnya, mungkin membantu mengklarifikasi satu panggilan tetapi belum tentu yang lain.

Argumen bernama dapat membantu di sini:

var ids = ReturnEmployeeIds(includeManagement: true);

Tidak menambahkan banyak kejelasan tambahan (saya akan menebak bahwa ini adalah parsing enum):

Enum.Parse(typeof(StringComparison), "Ordinal", ignoreCase: true);

Ini sebenarnya dapat mengurangi kejelasan (jika seseorang tidak memahami apa kapasitas dalam daftar):

var employeeIds = new List<int>(capacity: 24);

Atau di tempat yang tidak penting karena Anda menggunakan nama variabel yang bagus:

bool includeManagement = true;
var ids = ReturnEmployeeIds(includeManagement);

Apakah ada tempat yang, secara formal, membahas apakah secara eksplisit menentukan parameter opsional atau tidak ketika Anda membutuhkannya?

Bukan AFAIK. Mari kita membahas kedua bagian: satu-satunya waktu Anda perlu secara eksplisit menggunakan parameter bernama adalah karena kompiler mengharuskan Anda untuk melakukannya (umumnya itu untuk menghapus ambiguitas pernyataan). Jadi selain itu Anda bisa menggunakannya kapan pun Anda mau. Artikel dari MS ini memiliki beberapa saran tentang kapan Anda mungkin ingin menggunakannya tetapi tidak terlalu definitif https://msdn.microsoft.com/en-us/library/dd264739.aspx .

Secara pribadi, saya menemukan saya paling sering menggunakannya ketika saya membuat atribut khusus atau mematikan metode baru di mana parameter saya dapat berubah atau ditata ulang (atribut b / c dapat dicantumkan dalam urutan apa pun). Selain itu saya hanya menggunakannya ketika saya memberikan inline nilai statis, jika tidak, jika saya melewati variabel saya mencoba menggunakan nama variabel yang baik.

TL; DR

Pada akhirnya itu tergantung pada preferensi pribadi. Tentu saja ada beberapa kasus penggunaan ketika Anda harus menggunakan parameter bernama tetapi setelah itu Anda perlu membuat panggilan penilaian. IMO - Gunakan di mana saja yang Anda percaya akan membantu mendokumentasikan kode Anda, mengurangi ambiguitas, atau memungkinkan Anda untuk melindungi tanda tangan metode.


sumber
0

Jika parameternya jelas dari nama (sepenuhnya memenuhi syarat) metode dan keberadaannya wajar untuk apa yang seharusnya dilakukan metode ini, Anda tidak boleh menggunakan sintaks parameter bernama. Misalnya, di ReturnEmployeeName(57)dalamnya sudah sangat jelas itu 57adalah ID karyawan, jadi berlebihan untuk membubuhi keterangan dengan sintaks parameter bernama.

Dengan ReturnEmployeeIds(true), seperti yang Anda katakan, kecuali jika Anda melihat deklarasi / dokumentasi fungsi tidak mungkin untuk mencari tahu apa trueartinya - jadi Anda harus menggunakan sintaks parameter bernama.

Sekarang, pertimbangkan kasus ketiga ini:

void PrintEmployeeNames(bool includeManagement) {
    foreach (id; ReturnEmployeeIds(includeManagement)) {
        Console.WriteLine(ReturnEmployeeName(id));
    }
}

Sintaks parameter bernama tidak digunakan di sini, tetapi karena kita meneruskan sebuah variabel ke ReturnEmployeeIds , dan variabel itu memiliki nama, dan nama itu kebetulan berarti hal yang sama dengan parameter yang kita lewat itu (ini sering terjadi - tetapi tidak selalu!) - kita tidak perlu sintaks parameter bernama untuk memahami artinya dari kode.

Jadi, aturannya sederhana - jika Anda dapat dengan mudah memahami hanya dari metode panggil apa arti parameter, jangan gunakan parameter yang disebutkan. Jika Anda tidak bisa - itu adalah kekurangan dalam keterbacaan kode, dan Anda mungkin ingin memperbaikinya (tidak dengan harga berapa pun, tentu saja, tetapi Anda biasanya ingin kode tersebut dapat dibaca). Perbaikan tidak harus dinamai parameter - Anda juga dapat menempatkan nilai dalam variabel terlebih dahulu, atau menggunakan metode yang disarankan dalam jawaban lain - selama hasil akhirnya membuatnya mudah untuk memahami apa yang dilakukan parameter.

Idan Arye
sumber
7
Saya tidak setuju yang ReturnEmployeeName(57)membuatnya segera jelas itu 57adalah ID. GetEmployeeById(57)di sisi lain ...
Hugo Zink
@HugoZink Saya awalnya ingin menggunakan sesuatu seperti itu untuk contoh, tapi saya sengaja mengubahnya ReturnEmployeeNameuntuk menunjukkan kasus di mana bahkan tanpa secara eksplisit menyebutkan parameter dalam nama metode, cukup masuk akal untuk mengharapkan orang untuk mencari tahu apa itu. Bagaimanapun, perlu dicatat bahwa tidak seperti itu ReturnEmployeeName, Anda tidak dapat secara wajar mengganti nama ReturnEmployeeIdsmenjadi sesuatu yang menunjukkan makna di balik parameter.
Idan Arye
1
Bagaimanapun, sangat tidak mungkin bahwa kita akan menulis ReturnEmployeeName (57) dalam program nyata. Jauh lebih mungkin bahwa kami akan menulis ReturnEmployeeName (employeeId). Mengapa kami harus meng-hardcode ID karyawan? Kecuali jika itu adalah ID programmer dan ada semacam penipuan yang terjadi, seperti, tambahkan sedikit ke gaji karyawan ini. :-)
Jay
0

Ya, saya pikir itu gaya yang bagus.

Ini paling masuk akal untuk konstanta Boolean dan integer.

Jika Anda memasukkan variabel, nama variabel harus memperjelas artinya. Jika tidak, Anda harus memberi variabel nama yang lebih baik.

Jika Anda memasukkan string, artinya seringkali jelas. Seperti jika saya melihat panggilan ke GetFooData ("Oregon"), saya pikir seorang pembaca dapat menebak bahwa parameternya adalah nama negara.

Tidak semua string jelas, tentu saja. Jika saya melihat GetFooData ("M") maka kecuali saya tahu fungsinya, saya tidak tahu apa artinya "M". Jika itu semacam kode, seperti M = "karyawan tingkat manajemen", maka kita mungkin harus memiliki enum daripada literal, dan nama enum harus jelas.

Dan saya kira string bisa menyesatkan. Mungkin "Oregon" dalam contoh saya di atas merujuk, bukan ke negara bagian, tetapi ke produk atau klien atau apa pun.

Tapi GetFooData (true) atau GetFooData (42) ... itu bisa berarti apa saja. Parameter yang dinamai adalah cara yang luar biasa untuk membuatnya mendokumentasikan diri. Saya telah menggunakan mereka untuk tujuan itu pada sejumlah kesempatan.

Jay
sumber
0

Tidak ada yang super jelas alasan yang mengapa harus menggunakan sintaks jenis ini.

Secara umum, cobalah untuk menghindari memiliki argumen boolean yang di kejauhan mungkin terlihat sewenang-wenang. includeManagementakan (kemungkinan besar) mempengaruhi hasilnya. Tapi argumennya sepertinya membawa "sedikit berat".

Penggunaan enum telah dibahas, tidak hanya akan terlihat seperti argumen "membawa lebih banyak berat", tetapi juga akan memberikan skalabilitas pada metode ini. Namun ini mungkin bukan solusi terbaik dalam semua kasus, karena ReturnEmployeeIds-metode Anda harus berskala bersama WhatToInclude-enum (lihat NiklasJ jawaban ). Ini mungkin memberi Anda sakit kepala nanti.

Pertimbangkan ini: Jika Anda skala WhatToInclude-enum, tetapi bukan -metode ReturnEmployeeIds. Mungkin kemudian melempar ArgumentOutOfRangeException(kasus terbaik) atau mengembalikan sesuatu yang sama sekali tidak diinginkan ( nullatau kosong List<Guid>). Yang dalam beberapa kasus mungkin membingungkan programmer, terutama jika AndaReturnEmployeeIds berada di perpustakaan kelas, di mana kode sumber tidak tersedia.

Orang akan berasumsi bahwa itu WhatToInclude.Traineesakan berhasil jika WhatToInclude.Alldilihat, melihat bahwa peserta pelatihan adalah "bagian" dari semuanya.

Ini tentu saja tergantung pada bagaimana ReturnEmployeeIds penerapannya.


Dalam kasus di mana argumen boolean dapat diajukan, saya mencoba memecahnya menjadi dua metode (atau lebih, jika diperlukan), dalam kasus Anda; orang dapat mengekstrak ReturnAllEmployeeIds, ReturnManagementIdsdan ReturnRegularEmployeeIds. Ini mencakup semua pangkalan dan sangat jelas bagi siapa pun yang mengimplementasikannya. Ini juga tidak akan memiliki masalah "penskalaan", yang disebutkan di atas.

Karena hanya ada dua hasil untuk sesuatu yang memiliki argumen boolean. Menerapkan dua metode, membutuhkan sedikit usaha ekstra.

Kode kurang, jarang lebih baik.


Dengan kata, ada yang beberapa kasus di mana secara eksplisit menyatakan argumen meningkatkan keterbacaan. Pertimbangkan misalnya. GetLatestNews(max: 10). GetLatestNews(10)adalah masih cukup jelas, deklarasi eksplisit maxakan membantu menjernihkan setiap kebingungan.

Dan jika Anda benar-benar harus memiliki argumen boolean, penggunaan yang tidak dapat disimpulkan dengan hanya membaca trueatau false. Maka saya akan mengatakan itu:

var ids = ReturnEmployeeIds(includeManagement: true);

.. benar-benar lebih baik, dan lebih mudah dibaca daripada:

var ids = ReturnEmployeeIds(true);

Karena dalam contoh kedua, truemungkin berarti apa pun . Tetapi saya akan mencoba untuk menghindari argumen ini.

mati maus
sumber