Jika Anda selalu memberikan data minimum yang diperlukan ke dalam fungsi dalam kasus seperti ini

81

Katakanlah saya memiliki fungsi IsAdminyang memeriksa apakah pengguna adalah admin. Misalkan saja pengecekan admin dilakukan dengan mencocokkan id pengguna, nama, dan kata sandi dengan semacam aturan (tidak penting).

Di kepala saya ada dua kemungkinan tanda tangan fungsi untuk ini:

public bool IsAdmin(User user);
public bool IsAdmin(int id, string name, string password);

Saya paling sering menggunakan jenis tanda tangan kedua, berpikir bahwa:

  • Fungsi tanda tangan memberi pembaca lebih banyak info
  • Logika yang terkandung di dalam fungsi tidak harus tahu tentang Userkelas
  • Biasanya menghasilkan kode sedikit lebih sedikit di dalam fungsi

Namun saya terkadang mempertanyakan pendekatan ini, dan juga menyadari bahwa pada titik tertentu akan menjadi sulit. Jika misalnya fungsi akan memetakan antara sepuluh bidang objek yang berbeda ke dalam bool yang dihasilkan, saya jelas akan mengirim seluruh objek. Tetapi terlepas dari contoh nyata seperti itu saya tidak bisa melihat alasan untuk meneruskan objek yang sebenarnya.

Saya akan menghargai argumen apa pun untuk gaya apa pun, serta pengamatan umum apa pun yang Anda tawarkan.

Saya memprogram dalam gaya berorientasi objek dan fungsional, jadi pertanyaannya harus dilihat mengenai setiap dan semua idiom.

Anders Arpi
sumber
40
Di luar topik: Karena penasaran, mengapa status "adalah admin" pengguna bergantung pada kata sandi mereka?
stakx
43
@ syb0rg Salah. Dalam C # itulah konvensi penamaan .
Magnattic
68
@ syb0rg Benar, dia tidak menentukan bahasanya jadi saya pikir itu agak aneh untuk memberitahunya bahwa dia melanggar konvensi bahasa tertentu.
magnattic
31
@ syb0rg Pernyataan umum bahwa "nama fungsi tidak boleh dimulai dengan huruf besar" salah, karena ada bahasa di mana seharusnya. Ngomong-ngomong, aku tidak bermaksud menyinggung perasaanmu, aku hanya berusaha menjernihkan ini. Ini semakin banyak di luar topik untuk pertanyaan itu sendiri. Saya tidak melihat perlunya, tetapi jika Anda ingin melanjutkan mari pindahkan debat ke obrolan .
Magnattic
6
Sama seperti poin umum, menggulung keamanan Anda sendiri biasanya merupakan hal yang buruk (tm) . Sebagian besar bahasa dan kerangka kerja memiliki sistem keamanan / izin yang tersedia dan ada alasan bagus untuk menggunakannya bahkan ketika Anda menginginkan sesuatu yang lebih sederhana.
James Snell

Jawaban:

123

Saya pribadi lebih suka metode pertama saja IsAdmin(User user)

Ini jauh lebih mudah digunakan, dan jika kriteria Anda untuk IsAdmin berubah di kemudian hari (mungkin berdasarkan peran, atau isActive), Anda tidak perlu menulis ulang tanda tangan metode Anda di mana-mana.

Mungkin juga lebih aman karena Anda tidak mengiklankan properti apa yang menentukan apakah pengguna adalah seorang Admin atau tidak, atau membagikan properti kata sandi di mana-mana. Dan syrion membuat poin bagus bahwa apa yang terjadi ketika Anda idtidak cocok dengan name/ password?

Panjang kode di dalam suatu fungsi seharusnya tidak terlalu penting memberikan metode melakukan tugasnya, dan saya lebih suka memiliki kode aplikasi yang lebih pendek dan lebih sederhana daripada kode metode pembantu.

Rachel
sumber
3
Saya pikir masalah refactoring adalah poin yang sangat bagus - terima kasih.
Anders Arpi
1
Saya akan membuat argumen metode tanda tangan jika Anda tidak melakukannya. Ini sangat membantu ketika ada banyak ketergantungan pada metode Anda yang dikelola oleh programmer lain. Ini membantu menjauhkan orang dari satu sama lain. +1
jmort253
1
Saya setuju dengan jawaban ini, tetapi izinkan saya menandai dua situasi di mana pendekatan lain ("data minimum") mungkin lebih disukai: (1) Anda ingin meng-cache hasil metode. Lebih baik tidak menggunakan seluruh objek sebagai kunci cache, atau harus memeriksa properti objek pada setiap doa. (2) Anda ingin metode / fungsi dijalankan secara tidak sinkron, yang mungkin melibatkan membuat serialisasi argumen dan, katakanlah, menyimpannya dalam sebuah tabel. Lebih mudah dan lebih sederhana untuk mencari bilangan bulat daripada gumpalan objek saat mengelola pekerjaan yang tertunda.
GladstoneKeep
Obsesi primitif anti-pola bisa mengerikan untuk pengujian unit.
Samuel
82

Tanda tangan pertama lebih unggul karena memungkinkan enkapsulasi logika terkait pengguna dalam Userobjek itu sendiri. Tidaklah bermanfaat memiliki logika untuk pembuatan tuple "id, name, password" yang bertebaran tentang basis kode Anda. Lebih jauh lagi, itu memperumit isAdminfungsi: apa yang terjadi jika seseorang melewati sebuah idyang tidak cocok dengan name, misalnya? Bagaimana jika Anda ingin memeriksa apakah Pengguna yang diberikan adalah administrator dari konteks di mana Anda seharusnya tidak mengetahui kata sandi mereka?

Selanjutnya, sebagai catatan pada poin ketiga Anda mendukung gaya dengan argumen tambahan; mungkin menghasilkan "kode kurang di dalam fungsi," tetapi di mana logika itu pergi? Itu tidak bisa hilang begitu saja! Alih-alih, itu tersebar di setiap tempat di mana fungsi dipanggil. Untuk menyimpan lima baris kode di satu tempat, Anda telah membayar lima baris per penggunaan fungsi!

asthasr
sumber
45
Mengikuti logika Anda, tidak user.IsAdmin()akan jauh lebih baik?
Anders Arpi
13
Ya, tentu saja.
asthasr
6
@ AndersHolmström Tergantung, apakah Anda menguji apakah pengguna adalah admin secara umum, atau hanya admin untuk halaman / formulir saat ini. Jika secara umum, maka ya itu akan lebih baik, tetapi jika Anda hanya menguji IsAdminuntuk bentuk atau fungsi tertentu, itu harus tidak terkait dengan pengguna
Rachel
40
@Anders: tidak seharusnya tidak. Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Hak Istimewa atau sistem Keamanan harus. Sesuatu yang tergabung erat dengan sebuah kelas tidak berarti itu adalah ide yang baik untuk menjadikannya bagian dari kelas itu
Marjan Venema
11
@MarjanVenema - gagasan bahwa kelas Pengguna tidak boleh "memutuskan" apakah sebuah instance adalah admin menurut saya sebagai sedikit pemujaan. Anda benar-benar tidak bisa membuat generalisasi itu begitu kuat. Jika kebutuhan Anda rumit, maka mungkin bermanfaat untuk merangkumnya dalam "sistem" yang terpisah, tetapi dengan mengutip KISS + YAGNI, saya ingin membuat keputusan yang benar-benar dihadapkan dengan kompleksitas itu, dan bukan sebagai aturan umum :-). Dan catat bahwa meskipun logikanya bukan "bagian" dari Pengguna, masih bisa berguna sebagai masalah kepraktisan untuk memiliki passthrough pada Pengguna yang hanya mendelegasikan ke sistem yang lebih kompleks.
Eamon Nerbonne
65

Yang pertama, tetapi tidak (hanya) karena alasan yang diberikan orang lain.

public bool IsAdmin(User user);

Ini jenis aman. Terutama karena Pengguna adalah tipe yang Anda tentukan sendiri, ada sedikit atau tidak ada kesempatan untuk mengubah atau mengubah argumen. Ini jelas beroperasi pada Pengguna dan bukan suatu fungsi generik yang menerima int dan dua string. Ini adalah bagian besar dari titik menggunakan bahasa tipe-aman seperti Java atau C # yang terlihat seperti kode Anda. Jika Anda bertanya tentang bahasa tertentu, Anda mungkin ingin menambahkan tag untuk bahasa itu ke pertanyaan Anda.

public bool IsAdmin(int id, string name, string password);

Di sini, setiap int dan dua string akan dilakukan. Apa yang mencegah Anda mengubah nama dan kata sandi? Yang kedua adalah lebih banyak pekerjaan untuk digunakan dan memungkinkan lebih banyak kesempatan untuk kesalahan.

Agaknya, kedua fungsi mengharuskan user.getId (), user.getName (), dan user.getPassword () dipanggil, baik di dalam fungsi (dalam kasus pertama) atau sebelum memanggil fungsi (dalam yang kedua), sehingga jumlah kopling adalah sama dengan cara yang sama. Sebenarnya, karena fungsi ini hanya valid pada Pengguna, di Java saya akan menghilangkan semua argumen dan menjadikan ini metode instan pada objek Pengguna:

user.isAdmin();

Karena fungsi ini sudah tergabung erat dengan Pengguna, masuk akal untuk menjadikannya bagian dari apa yang dilakukan pengguna.

PS Saya yakin ini hanya sebuah contoh, tetapi sepertinya Anda menyimpan kata sandi. Anda seharusnya hanya menyimpan hash kata sandi yang aman secara kriptografis. Selama login, kata sandi yang diberikan harus di hash dengan cara yang sama dan dibandingkan dengan hash yang disimpan. Mengirim kata sandi di sekitar dalam teks biasa harus dihindari. Jika Anda melanggar aturan ini dan Ismin (int Str) adalah untuk mencatat nama pengguna, maka jika Anda mengubah nama dan kata sandi dalam kode Anda, kata sandi dapat dicatat sebagai gantinya yang menciptakan masalah keamanan (jangan menulis kata sandi untuk log ) dan argumen lain yang mendukung melewati objek alih-alih bagian komponennya (atau menggunakan metode kelas).

GlenPeterson
sumber
11
Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Hak Istimewa atau sistem Keamanan harus. Sesuatu yang tergabung erat dengan kelas tidak berarti itu adalah ide yang baik untuk menjadikannya bagian dari kelas itu.
Marjan Venema
6
@MarjanVenema Pengguna tidak memutuskan apa pun. Objek / tabel Pengguna menyimpan data tentang setiap pengguna. Pengguna yang sebenarnya tidak bisa mengubah segalanya tentang diri mereka sendiri. Bahkan ketika pengguna mengubah sesuatu yang diizinkan, hal itu dapat memicu peringatan keamanan seperti email jika mereka mengubah alamat email mereka atau ID pengguna atau kata sandi mereka. Apakah Anda menggunakan sistem di mana pengguna secara ajaib diizinkan untuk mengubah segala sesuatu tentang jenis objek tertentu? Saya tidak terbiasa dengan sistem seperti itu.
GlenPeterson
2
@GlenPeterson: apakah Anda sengaja salah paham? Ketika saya mengatakan pengguna, saya tentu saja berarti kelas pengguna.
Marjan Venema
2
@GlenPeterson Benar-benar keluar dari topik, tetapi beberapa putaran fungsi hashing dan kriptografis akan melemahkan data.
Gusdor
3
@ Marsjanen: Saya tidak sengaja menjadi sulit. Saya pikir kami hanya memiliki perspektif yang sangat berbeda dan saya ingin memahami Anda dengan lebih baik. Saya telah membuka pertanyaan baru di mana ini bisa dibahas lebih baik: programmers.stackexchange.com/questions/216443/…
GlenPeterson
6

Jika bahasa pilihan Anda tidak lulus struktur berdasarkan nilai, maka (User user)varian tampaknya lebih baik. Bagaimana jika suatu hari Anda memutuskan untuk menghapus nama pengguna dan hanya menggunakan ID / kata sandi untuk mengidentifikasi pengguna?

Juga, pendekatan ini pasti mengarah pada pemanggilan metode yang panjang dan berlebihan, atau ketidakkonsistenan. Apakah memberikan 3 argumen tidak apa-apa? Bagaimana dengan 5? Atau 6? Mengapa dipikir-pikir itu, jika melewati suatu objek murah dalam hal sumber daya (bahkan lebih murah daripada melewati 3 argumen, mungkin)?

Dan saya (semacam) tidak setuju bahwa pendekatan kedua memberi pembaca lebih banyak info - secara intuitif, pemanggilan metode bertanya "apakah pengguna memiliki hak admin", bukan "apakah kombinasi id / nama / kata sandi yang diberikan memiliki hak admin".

Jadi, kecuali melewati Userobjek akan menghasilkan menyalin struktur besar, pendekatan pertama lebih bersih dan lebih logis bagi saya.

Maciej Stachowski
sumber
3

Saya mungkin akan memiliki keduanya. Yang pertama sebagai API eksternal, dengan beberapa harapan stabilitas waktu. Yang kedua sebagai implementasi pribadi yang disebut oleh API eksternal.

Jika suatu saat nanti saya harus mengubah aturan pengecekan, saya hanya akan menulis fungsi pribadi baru dengan tanda tangan baru yang disebut oleh yang eksternal.

Ini memiliki keuntungan dari kemudahan untuk mengubah implementasi batin. Juga sangat biasa bahwa pada suatu waktu Anda mungkin membutuhkan kedua fungsi yang tersedia pada waktu yang sama dan memanggil satu atau yang lain tergantung dari beberapa perubahan konteks eksternal (misalnya Anda mungkin telah hidup bersama Pengguna lama dan Pengguna baru dengan bidang yang berbeda).

Mengenai judul pertanyaan, dengan cara dalam kedua kasus Anda memberikan informasi minimum yang diperlukan. Pengguna tampaknya adalah Struktur Data Terkait Aplikasi terkecil yang berisi informasi. Tiga kolom id, kata sandi, dan nama tampaknya merupakan data implementasi terkecil yang benar-benar dibutuhkan, tetapi ini sebenarnya bukan objek tingkat Aplikasi.

Dengan kata lain Jika Anda berurusan dengan database, Pengguna akan menjadi catatan, sedangkan id, kata sandi dan login akan menjadi bidang dalam catatan itu.

Kriss
sumber
Saya tidak melihat alasan metode pribadi. Mengapa mempertahankan keduanya? Jika metode pribadi membutuhkan argumen yang berbeda, Anda masih harus memodifikasi yang publik. Dan Anda akan mengujinya melalui yang publik. Dan tidak, itu tidak biasa bahwa Anda membutuhkan keduanya pada suatu waktu nanti. Anda menebak di sini - YAGNI.
Piotr Perak
Anda berasumsi misalnya bahwa struktur data Pengguna tidak memiliki bidang lain dari awal. Itu biasanya tidak benar. Fungsi kedua memperjelas bagian dalam mana dari pengguna diperiksa untuk melihat apakah itu admin. Kasus penggunaan umum mungkin dari jenis: jika waktu pembuatan pengguna sebelum tanggal tertentu, maka panggilan aturan lama, jika tidak memanggil aturan baru (yang mungkin tergantung pada bagian lain dari Pengguna, atau tergantung pada yang sama tetapi menggunakan aturan lain). Memisahkan kode dengan cara ini Anda akan mendapatkan dua fungsi minimal: API konseptual minimal dan fungsi implementasi minimal.
Kriss
dengan kata lain cara ini akan langsung terlihat dari tanda tangan fungsi internal bagian mana dari Pengguna yang digunakan atau tidak. Dalam kode produksi itu tidak selalu jelas. Saat ini saya sedang mengerjakan basis kode besar dengan riwayat pemeliharaan beberapa tahun dan pemisahan seperti ini sangat berguna untuk pemeliharaan jangka panjang. Jika Anda tidak bermaksud mempertahankan kode, itu memang tidak berguna (tetapi bahkan menyediakan API konseptual yang stabil juga mungkin tidak berguna).
Kriss
Kata lain: Saya pasti tidak akan menguji unit metode internal melalui yang eksternal. Itu praktik pengujian yang buruk.
Kriss
1
Ini adalah praktik yang baik, dan ada nama untuk itu. Ini disebut "prinsip tanggung jawab tunggal". Itu dapat diterapkan untuk kedua kelas dan metode. Memiliki metode dengan tanggung jawab tunggal adalah cara yang baik untuk membuat kode yang lebih modular dan dapat dipelihara. Dalam kasus ini, satu tugas memilih kumpulan data dasar dari objek pengguna, dan tugas lainnya memeriksa kumpulan data dasar untuk melihat apakah pengguna adalah administrator. Dengan mengikuti prinsip ini, Anda mendapatkan bonus untuk bisa menulis metode dan menghindari duplikasi kode. Ini juga berarti, seperti yang dinyatakan oleh @ Kriss bahwa Anda mendapatkan tes unit yang lebih baik.
diegoreymendez
3

Ada satu titik negatif untuk mengirim kelas (tipe referensi) ke metode, dan itu adalah, Kerentanan Mass-Assignment . Bayangkan bahwa alih-alih IsAdmin(User user)metode, saya punya UpdateUser(User user)metode.

Jika Userkelas memiliki properti Boolean yang dipanggil IsAdmin, dan jika saya tidak memeriksanya selama eksekusi metode saya, maka saya rentan terhadap penugasan massal. GitHub diserang oleh tanda tangan ini pada tahun 2012.

Saeed Neamati
sumber
2

Saya akan menggunakan pendekatan ini:

public bool IsAdmin(this IUser user) { /* ... */ }

public class User: IUser { /* ... */ }

public interface IUser
{
    string Username {get;}
    string Password {get;}
    IID Id {get;}
}
  • Menggunakan tipe antarmuka sebagai parameter untuk fungsi ini memungkinkan Anda untuk meneruskan objek Pengguna, menentukan objek tiruan Pengguna untuk pengujian, dan memberi Anda lebih banyak fleksibilitas seputar penggunaan dan pemeliharaan fungsi ini.

  • Saya juga menambahkan kata kunci thisuntuk menjadikan fungsi metode ekstensi dari semua kelas IUser; dengan cara ini Anda dapat menulis kode dengan cara yang lebih ramah OO dan menggunakan fungsi ini dalam permintaan Linq. Simpler masih akan mendefinisikan fungsi ini di IUser dan Pengguna, tapi saya berasumsi ada beberapa alasan mengapa Anda memutuskan untuk meletakkan metode ini di luar kelas itu?

  • Saya menggunakan antarmuka khusus IIDalih-alih intkarena ini memungkinkan Anda untuk mendefinisikan kembali properti ID Anda; misalnya jika Anda merasa perlu untuk mengubah dari intke long, Guidatau sesuatu yang lain. Itu mungkin berlebihan, tetapi selalu baik untuk mencoba membuat hal-hal fleksibel sehingga Anda tidak terkunci dalam keputusan awal.

  • NB: Objek IUser Anda dapat berada dalam ruang nama dan / atau perakitan yang berbeda dengan kelas Pengguna, sehingga Anda memiliki opsi untuk memisahkan kode Pengguna Anda dari kode IsAdmin Anda, dengan mereka hanya berbagi satu pustaka yang direferensikan. Apa yang Anda lakukan di sana adalah panggilan bagaimana Anda mencurigai barang-barang ini digunakan.

JohnLBevan
sumber
3
IUser tidak berguna di sini dan hanya menambah kompleksitas. Saya tidak mengerti mengapa Anda tidak dapat menggunakan Pengguna nyata dalam pengujian.
Piotr Perak
1
Ini menambah fleksibilitas masa depan untuk usaha yang sangat sedikit, jadi sementara tidak menambah nilai saat ini tidak ada salahnya. Secara pribadi saya lebih suka untuk selalu melakukan ini karena itu menghindari harus memikirkan kemungkinan masa depan untuk semua skenario (yang sangat tidak mungkin mengingat sifat persyaratan yang tidak dapat diprediksi, dan akan membutuhkan lebih banyak waktu untuk mendapatkan jawaban yang sebagian bagus daripada yang seharusnya. tetap di antarmuka).
JohnLBevan
@Peri Kelas pengguna sungguhan mungkin rumit untuk dibuat, mengambil antarmuka memungkinkan Anda untuk mengejeknya sehingga Anda dapat menjaga tes Anda tetap sederhana
jk.
@JohnLBevan: YAGNI !!!
Piotr Perak
@ jk .: jika sulit untuk membangun Pengguna maka ada sesuatu yang salah
Piotr Perak
1

Penafian:

Untuk balasan saya, saya akan fokus pada masalah gaya dan melupakan apakah ID, login dan kata sandi biasa adalah seperangkat parameter yang baik untuk digunakan, dari sudut pandang keamanan. Anda harus dapat memperkirakan jawaban saya untuk set data dasar apa pun yang Anda gunakan untuk mencari tahu tentang hak istimewa pengguna ...

Saya juga anggap user.isAdmin()setara IsAdmin(User user). Itu pilihan bagi Anda untuk membuat.

Balasan:

Rekomendasi saya hanya untuk pengguna saja:

public bool IsAdmin(User user);

Atau gunakan keduanya, di sepanjang baris:

public bool IsAdmin(User user); // calls the private method below
private bool IsAdmin(int id, string name, string password);

Alasan untuk metode publik:

Saat menulis metode publik, biasanya ide yang baik untuk memikirkan bagaimana mereka akan digunakan.

Untuk kasus khusus ini, alasan mengapa Anda biasanya memanggil metode ini adalah untuk mencari tahu apakah pengguna tertentu adalah administrator. Itu metode yang Anda butuhkan. Anda benar-benar tidak ingin setiap pemanggil harus memilih parameter yang tepat untuk dikirim (dan risiko kesalahan karena duplikasi kode).

Alasan untuk metode pribadi:

Metode pribadi yang hanya menerima set minimum parameter yang diperlukan terkadang bisa menjadi hal yang baik. Terkadang tidak terlalu banyak.

Salah satu hal baik tentang pemisahan metode ini adalah Anda dapat memanggil versi pribadi dari beberapa metode publik di kelas yang sama. Misalnya jika Anda ingin (untuk alasan apa pun) memiliki logika yang sedikit berbeda untuk Localuserdan RemoteUser(mungkin ada pengaturan untuk menghidupkan / mematikan admin jarak jauh?):

public bool IsAdmin(Localuser user); // Just call private method
public bool IsAdmin(RemoteUser user); // Check if setting is on, then call private method
private bool IsAdmin(int id, string name, string password);

Selain itu, jika karena alasan apa pun Anda perlu menjadikan metode pribadi ini publik ... Anda bisa. Ini semudah hanya mengubah privateke public. Kelihatannya ini bukan keuntungan besar untuk kasus ini, tetapi terkadang memang benar.

Juga, ketika pengujian unit Anda akan dapat membuat lebih banyak tes atom. Biasanya sangat baik tidak harus membuat objek pengguna penuh untuk menjalankan tes pada metode ini. Dan jika Anda ingin jangkauan penuh, Anda dapat menguji kedua panggilan.

diegoreymendez
sumber
0
public bool IsAdmin(int id, string name, string password);

buruk karena alasan yang tercantum. Itu tidak berarti

public bool IsAdmin(User user);

secara otomatis baik. Khususnya jika User adalah obyek yang memiliki metode seperti: getOrders(), getFriends(), getAdresses(), ...

Anda mungkin mempertimbangkan untuk refactoring domain dengan jenis UserCredentials yang hanya berisi id, nama, kata sandi, dan meneruskannya ke IsAdmin alih-alih semua data Pengguna yang tidak dibutuhkan.

tkruse
sumber