Rekan kerja saya memberi tahu saya harus ada logika sesedikit mungkin dalam getter dan setter.
Namun, saya yakin bahwa banyak hal dapat disembunyikan di getter dan setter untuk melindungi pengguna / programer dari detail implementasi.
Contoh apa yang saya lakukan:
public List<Stuff> getStuff()
{
if (stuff == null || cacheInvalid())
{
stuff = getStuffFromDatabase();
}
return stuff;
}
Contoh bagaimana pekerjaan memberi tahu saya untuk melakukan sesuatu (mereka mengutip 'Kode Bersih' dari Paman Bob):
public List<Stuff> getStuff()
{
return stuff;
}
public void loadStuff()
{
stuff = getStuffFromDatabase();
}
Berapa banyak logika yang sesuai dalam setter / pengambil? Apa gunanya getter dan setter kosong kecuali pelanggaran data yang disembunyikan?
public List<Stuff> getStuff() { return stuff; }
StuffGetter
antarmuka, terapkanStuffComputer
yang menghitung, dan bungkus di dalam objekStuffCacher
, yang bertanggung jawab untuk mengakses cache atau meneruskan panggilan ke tempatStuffComputer
yang dibungkusnya.Jawaban:
Cara kerja memberitahu Anda untuk melakukan sesuatu adalah timpang.
Sebagai aturan praktis, cara saya melakukan hal-hal adalah sebagai berikut: jika mendapatkan barang-barang secara komputasi murah, (atau jika sebagian besar kemungkinan hal itu akan ditemukan dalam cache,) maka gaya getStuff Anda () baik-baik saja. Jika mendapatkan barang-barang itu diketahui mahal secara komputasional, begitu mahal sehingga biaya iklannya diperlukan pada antarmuka, maka saya tidak akan menyebutnya getStuff (), saya akan menyebutnya calculStuff () atau sesuatu seperti itu, untuk menunjukkan bahwa akan ada beberapa pekerjaan yang harus dilakukan.
Dalam kedua kasus tersebut, cara kerja memberitahu Anda untuk melakukan sesuatu adalah lumpuh, karena getStuff () akan meledak jika loadStuff () belum dipanggil sebelumnya, jadi mereka pada dasarnya ingin Anda memperumit antarmuka Anda dengan memperkenalkan kompleksitas urutan operasi untuk itu. Urutan operasi cukup banyak tentang jenis kompleksitas terburuk yang dapat saya pikirkan.
sumber
loadStuff()
fungsi dipanggil sebelumgetStuff()
fungsi juga berarti bahwa kelas tidak benar-benar mengabstraksi apa yang terjadi di bawah tenda.Logika yang diterima tidak masalah.
Tetapi mendapatkan data dari database jauh lebih banyak daripada "logika". Ini melibatkan serangkaian operasi yang sangat mahal di mana banyak hal bisa salah, dan dengan cara yang tidak menentukan. Saya akan ragu melakukan itu secara implisit dalam rajin rajin.
Di sisi lain, sebagian besar ORM mendukung pemuatan malas koleksi, yang pada dasarnya adalah apa yang Anda lakukan.
sumber
Saya pikir bahwa menurut 'Kode Bersih' harus dibagi sebanyak mungkin, menjadi sesuatu seperti:
Tentu saja, ini omong kosong, mengingat bahwa bentuk yang indah, yang Anda tulis, melakukan hal yang benar dengan sepersekian kode yang sekilas dipahami oleh siapa pun:
Seharusnya bukan sakit kepala penelepon bagaimana barang-barang itu berada di bawah tenda, dan terutama tidak boleh sakit kepala penelepon untuk mengingat untuk memanggil hal-hal dalam "urutan yang benar" sewenang-wenang.
sumber
List<Stuff>
, hanya ada satu cara untuk mendapatkannya.hasStuff
adalah kebalikan dari kode bersih.Perlu ada logika sebanyak yang diperlukan untuk memenuhi kebutuhan kelas. Preferensi pribadi saya sesedikit mungkin, tetapi ketika mempertahankan kode, Anda biasanya harus meninggalkan antarmuka asli dengan getter / setter yang ada, tetapi menaruh banyak logika di dalamnya untuk memperbaiki logika bisnis yang lebih baru (sebagai contoh, "pelanggan" "rajin dalam lingkungan pasca-911 harus memenuhi" kenali pelanggan Anda "dan peraturan OFAC , dikombinasikan dengan kebijakan perusahaan yang melarang penampilan pelanggan dari negara-negara tertentu agar tidak muncul [(seperti Kuba atau Iran]).
Dalam contoh Anda, saya lebih suka sampel Anda dan tidak suka sampel "paman bob" karena versi "paman bob" mengharuskan pengguna / pengelola untuk mengingat untuk memanggil
loadStuff()
sebelum mereka memanggilgetStuff()
- ini adalah resep untuk bencana jika ada satu pun dari pengelola Anda lupa (atau lebih buruk, tidak pernah tahu). Sebagian besar tempat saya bekerja dalam dekade terakhir masih menggunakan kode yang lebih dari satu dekade, jadi kemudahan pemeliharaan adalah faktor penting untuk dipertimbangkan.sumber
Anda benar, kolega Anda salah.
Lupakan semua aturan tentang apa yang harus atau tidak harus dilakukan oleh metode get. Kelas harus menyajikan abstraksi sesuatu. Kelas Anda sudah terbaca
stuff
. Di Jawa sudah biasa menggunakan metode 'get' untuk membaca properti. Miliaran garis kerangka telah ditulis berharap dibacastuff
dengan menelepongetStuff
. Jika Anda memberi nama fungsi AndafetchStuff
atau selain dari itugetStuff
, maka kelas Anda akan tidak kompatibel dengan semua kerangka kerja itu.Anda mungkin mengarahkan mereka ke Hibernate, di mana 'getStuff ()' dapat melakukan beberapa hal yang sangat rumit, dan melempar RuntimeException pada kegagalan.
sumber
stuff
. Keduanya menyembunyikan detail untuk membuatnya lebih mudah menulis kode panggilan.Kedengarannya seperti ini mungkin sedikit perdebatan murni versus aplikasi yang mungkin dipengaruhi oleh bagaimana Anda lebih suka mengontrol nama fungsi. Dari sudut pandang yang diterapkan, saya lebih suka melihat:
Sebagai lawan:
Atau lebih buruk lagi:
Yang cenderung membuat kode lain jauh lebih berlebihan dan lebih sulit dibaca karena Anda harus mulai mengarungi semua panggilan serupa. Selain itu, memanggil fungsi-fungsi loader atau sejenisnya menghancurkan seluruh tujuan bahkan menggunakan OOP karena Anda tidak lagi diabstraksi dari rincian implementasi objek yang sedang Anda kerjakan. Jika Anda memiliki
clientRoster
objek, Anda tidak perlu peduli tentang caragetNames
kerjanya, seperti yang Anda lakukan jika Anda harus memanggilloadNames
, Anda harus tahu bahwagetNames
memberi AndaList<String>
nama klien.Jadi, sepertinya masalah ini lebih tentang semantik dan nama terbaik untuk fungsi untuk mendapatkan data. Jika perusahaan (dan lain-lain) memiliki masalah dengan
get
danset
awalan, lalu bagaimana memanggil fungsi sesuatu sepertiretrieveNames
bukan? Dikatakan apa yang sedang terjadi tetapi tidak menyiratkan bahwa operasi akan instan seperti yang diharapkan dari suatuget
metode.Dalam hal logika dalam metode accessor, jaga agar seminimal mungkin karena secara umum tersirat bersifat instan dengan hanya interaksi nominal yang terjadi dengan variabel. Namun, itu juga umumnya hanya berlaku untuk tipe sederhana, tipe data kompleks (yaitu
List
) Saya merasa lebih sulit untuk merangkum dengan benar di properti dan umumnya menggunakan metode lain untuk berinteraksi dengan mereka sebagai lawan dari mutator dan accessor yang ketat.sumber
Memanggil seorang pengambil harus menunjukkan perilaku yang sama seperti membaca bidang:
sumber
foo.setAngle(361); bar = foo.getAngle()
.bar
bisa saja361
, tetapi mungkin juga sah1
jika sudut terikat pada suatu rentang.stuff
, pengambil akan mengembalikan nilai yang sama. (3) Memuat malas seperti yang ditunjukkan dalam contoh tidak menghasilkan efek samping "terlihat". (4) masih bisa diperdebatkan, mungkin poin yang valid, karena memperkenalkan "lazy loading" sesudahnya dapat mengubah kontrak API sebelumnya - tetapi kita harus melihat kontrak itu untuk membuat keputusan.Seorang pengambil yang memanggil properti dan metode lain untuk menghitung nilainya sendiri juga menyiratkan ketergantungan. Misalnya, jika properti Anda harus dapat menghitung sendiri, dan melakukan hal itu mengharuskan anggota lain ditetapkan, maka Anda harus khawatir tentang referensi-nol yang tidak disengaja jika properti Anda diakses dalam kode inisialisasi di mana semua anggota tidak perlu disetel.
Itu tidak berarti 'tidak pernah mengakses anggota lain yang bukan bidang dukungan properti dalam pengambil' itu hanya berarti memperhatikan apa yang Anda maksudkan tentang apa status objek yang diperlukan, dan jika itu sesuai dengan konteks yang Anda harapkan properti ini untuk diakses di.
Namun, dalam dua contoh konkret yang Anda berikan, alasan saya memilih satu di atas yang lain sama sekali berbeda. Pembuat Anda diinisialisasi pada akses pertama, misalnya, Inisialisasi Malas . Contoh kedua diasumsikan diinisialisasi pada beberapa titik sebelumnya, misalnya, Inisialisasi Eksplisit .
Kapan tepatnya inisialisasi terjadi mungkin atau mungkin tidak penting.
Misalnya itu bisa sangat sangat lambat, dan perlu dilakukan selama langkah beban di mana pengguna mengharapkan penundaan, daripada kinerja tiba-tiba cegukan ketika pengguna pertama kali memicu akses (yaitu, klik kanan pengguna,, menu konteks muncul, pengguna sudah diklik kanan lagi).
Juga, kadang-kadang ada titik yang jelas dalam eksekusi di mana segala sesuatu yang dapat mempengaruhi / mengotori nilai properti yang di-cache terjadi. Anda bahkan mungkin memverifikasi bahwa tidak ada dependensi yang berubah dan melempar pengecualian nanti. Dalam situasi ini, masuk akal juga untuk men-cache nilai pada titik itu, bahkan jika itu tidak terlalu mahal untuk dihitung, hanya untuk menghindari membuat eksekusi kode lebih kompleks dan lebih sulit untuk diikuti secara mental.
Yang mengatakan, Inisialisasi Malas masuk akal dalam banyak situasi lain. Jadi, seperti yang sering terjadi dalam memprogramnya sulit untuk turun ke aturan, itu turun ke kode konkret.
sumber
Lakukan saja seperti yang dikatakan @MikeNakis ... Jika Anda baru saja mendapatkan barang-barangnya maka itu baik-baik saja ... Jika Anda melakukan sesuatu yang lain buatlah fungsi baru yang melakukan pekerjaan itu dan buat publik.
Jika properti / fungsi Anda hanya melakukan apa yang dikatakan namanya maka tidak ada banyak ruang untuk komplikasi yang tersisa. Kohesi adalah kunci IMO
sumber
loadFoo()
ataupreloadDummyReferences()
ataucreateDefaultValuesForUninitializedFields()
hanya karena implementasi awal kelas Anda membutuhkannya.Secara pribadi, saya akan mengekspos persyaratan Stuff melalui parameter di konstruktor, dan memungkinkan kelas mana saja yang Instantiating barang untuk melakukan pekerjaan mencari tahu di mana itu harus berasal. Jika barang nol, itu harus mengembalikan nol. Saya lebih suka untuk tidak mencoba solusi pintar seperti OP asli karena ini adalah cara mudah untuk menyembunyikan bug jauh di dalam implementasi Anda di mana sama sekali tidak jelas apa yang mungkin terjadi ketika ada sesuatu yang rusak.
sumber
Ada masalah yang lebih penting daripada hanya "kesesuaian" di sini dan Anda harus mendasarkan keputusan Anda pada mereka . Terutama, keputusan besar di sini adalah apakah Anda ingin mengubah orang untuk memotong cache atau tidak.
Pertama, pikirkan jika ada cara untuk mengatur ulang kode Anda sehingga semua panggilan pemuatan yang diperlukan dan manajemen cache dilakukan di konstruktor / initializer. Jika ini memungkinkan, Anda dapat membuat kelas yang invariannya memungkinkan Anda lakukan pada pengambil sederhana dari bagian 2 dengan keamanan pengambil aktif dari bagian 1. (Skenario menang-menang)
Jika Anda tidak dapat membuat kelas seperti itu, putuskan apakah Anda memiliki tradeoff dan perlu memutuskan apakah Anda ingin mengizinkan konsumen untuk melewatkan kode pemeriksaan cache atau tidak.
Jika penting bahwa konsumen tidak pernah melewati pemeriksaan cache dan Anda tidak keberatan dengan hukuman kinerja, maka gabungkan cek di dalam pengambil dan buat mustahil bagi konsumen untuk melakukan hal yang salah.
Jika OK untuk melewati pemeriksaan cache atau sangat penting bahwa Anda dijamin O (1) kinerja dalam pengambil kemudian gunakan panggilan terpisah.
Seperti yang mungkin telah Anda catat, saya bukan penggemar besar filosofi "clean-code", "membagi semuanya menjadi fungsi-fungsi kecil". Jika Anda memiliki banyak fungsi ortogonal yang dapat dipanggil dalam urutan apa pun yang membelahnya akan memberi Anda kekuatan yang lebih ekspresif dengan sedikit biaya. Namun, jika fungsi Anda memiliki dependensi pesanan (atau hanya benar-benar berguna dalam urutan tertentu) maka membaginya hanya meningkatkan jumlah cara Anda dapat melakukan hal-hal yang salah, sambil menambahkan sedikit manfaat.
sumber
Menurut pendapat saya, Getters seharusnya tidak memiliki banyak logika di dalamnya. Mereka seharusnya tidak memiliki efek samping dan Anda tidak boleh mendapatkan pengecualian dari mereka. Kecuali tentu saja, Anda tahu apa yang Anda lakukan. Sebagian besar getter saya tidak memiliki logika di dalamnya dan hanya pergi ke lapangan. Tetapi pengecualian untuk itu adalah dengan API publik yang perlu sesederhana mungkin untuk digunakan. Jadi saya punya satu pengambil yang akan gagal jika pengambil lain belum dipanggil. Solusinya? Baris kode seperti
var throwaway=MyGetter;
pada pengambil yang bergantung padanya. Saya tidak bangga akan hal itu, tetapi saya masih tidak melihat cara yang lebih bersih untuk melakukannyasumber
Ini terlihat seperti read from cache dengan lazy loading. Seperti yang telah dicatat orang lain, memeriksa dan memuat mungkin termasuk dalam metode lain. Memuat mungkin perlu disinkronkan agar Anda tidak mendapatkan dua puluh utas yang memuat sekaligus.
Mungkin tepat untuk menggunakan nama
getCachedStuff()
untuk pengambil karena tidak akan memiliki waktu eksekusi yang konsisten.Bergantung pada cara kerja
cacheInvalid()
rutinnya, pemeriksaan untuk null mungkin tidak diperlukan. Saya tidak akan mengharapkan cache valid kecualistuff
telah diisi dari database.sumber
Logika utama yang saya harapkan untuk dilihat dalam getter yang mengembalikan daftar adalah logika untuk memastikan daftar tidak dapat dimodifikasi. Karena kedua contoh Anda berpotensi memecahkan enkapsulasi.
sesuatu seperti:
sebagai untuk caching di pengambil, saya pikir ini akan baik-baik saja tetapi saya mungkin tergoda untuk keluar dari logika cache jika membangun cache membutuhkan waktu yang signifikan. yaitu tergantung.
sumber
Bergantung pada kasus penggunaan yang tepat, saya ingin memisahkan caching saya ke kelas yang terpisah. Buat
StuffGetter
antarmuka, terapkanStuffComputer
yang menghitung, dan bungkus di dalam objekStuffCacher
, yang bertanggung jawab untuk mengakses cache atau meneruskan panggilan ke tempatStuffComputer
yang dibungkusnya.Desain ini memungkinkan Anda dengan mudah menambahkan caching, menghapus caching, mengubah logika derivasi yang mendasarinya (misalnya mengakses DB vs mengembalikan data tiruan), dll. Ini agak bertele-tele, tetapi layak sementara untuk proyek yang cukup maju.
sumber
IMHO itu sangat sederhana jika Anda menggunakan desain berdasarkan kontrak. Putuskan apa yang harus disediakan oleh pengambil Anda dan kode yang sesuai (kode sederhana atau logika kompleks yang mungkin terlibat atau didelegasikan di suatu tempat).
sumber