Basis kode yang saya kerjakan sering menggunakan variabel instan untuk berbagi data antara berbagai metode sepele. Pengembang asli bersikeras bahwa ini mematuhi praktik terbaik yang dinyatakan dalam buku Kode Bersih oleh Paman Bob / Robert Martin: "Aturan fungsi pertama adalah bahwa mereka harus kecil." dan "Jumlah argumen ideal untuk suatu fungsi adalah nol (niladik). (...) Argumennya sulit. Mereka membutuhkan banyak kekuatan konseptual."
Sebuah contoh:
public class SomeBusinessProcess {
@Inject private Router router;
@Inject private ServiceClient serviceClient;
@Inject private CryptoService cryptoService;
private byte[] encodedData;
private EncryptionInfo encryptionInfo;
private EncryptedObject payloadOfResponse;
private URI destinationURI;
public EncryptedResponse process(EncryptedRequest encryptedRequest) {
checkNotNull(encryptedRequest);
getEncodedData(encryptedRequest);
getEncryptionInfo();
getDestinationURI();
passRequestToServiceClient();
return cryptoService.encryptResponse(payloadOfResponse);
}
private void getEncodedData(EncryptedRequest encryptedRequest) {
encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
}
private void getEncryptionInfo() {
encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
}
private void getDestinationURI() {
destinationURI = router.getDestination().getUri();
}
private void passRequestToServiceClient() {
payloadOfResponse = serviceClient.handle(destinationURI, encodedData, encryptionInfo);
}
}
Saya akan mengubahnya menjadi menggunakan variabel lokal berikut:
public class SomeBusinessProcess {
@Inject private Router router;
@Inject private ServiceClient serviceClient;
@Inject private CryptoService cryptoService;
public EncryptedResponse process(EncryptedRequest encryptedRequest) {
checkNotNull(encryptedRequest);
byte[] encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
EncryptionInfo encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
URI destinationURI = router.getDestination().getUri();
EncryptedObject payloadOfResponse = serviceClient.handle(destinationURI, encodedData,
encryptionInfo);
return cryptoService.encryptResponse(payloadOfResponse);
}
}
Ini lebih pendek, menghilangkan kopling data implisit antara berbagai metode sepele dan membatasi ruang lingkup variabel ke minimum yang diperlukan. Namun terlepas dari manfaat ini, saya masih belum bisa meyakinkan pengembang asli bahwa refactoring ini dibenarkan, karena tampaknya bertentangan dengan praktik Paman Bob yang disebutkan di atas.
Karena itu pertanyaan saya: Apa tujuan, alasan ilmiah untuk mendukung variabel lokal daripada variabel instan? Sepertinya saya tidak bisa meletakkan jari saya di atasnya. Saya intuisi memberitahu saya bahwa kopling tersembunyi buruk dan bahwa ruang lingkup yang sempit adalah lebih baik daripada yang luas. Tapi apa ilmu yang mendukung hal ini?
Dan sebaliknya, apakah ada kerugian untuk refactoring yang mungkin saya abaikan?
sumber
Jawaban:
Lingkup bukan keadaan biner, ini adalah gradien. Anda dapat menentukan peringkat ini dari yang terbesar hingga yang terkecil:
Sunting: apa yang saya sebut "ruang lingkup kelas" adalah apa yang Anda maksud dengan "variabel instan". Sepengetahuan saya, itu sama saja, tapi saya seorang C # dev, bukan Java dev. Demi singkatnya, saya telah memasukkan semua statika ke dalam kategori global karena statika bukan topik pertanyaan.
Semakin kecil cakupannya, semakin baik. Alasannya adalah bahwa variabel harus hidup dalam ruang lingkup sekecil mungkin . Ada banyak manfaat untuk ini:
Name
properti, Anda tidak dipaksa untuk awalan mereka sepertiFooName
,BarName
, ... Jadi menjaga nama variabel Anda sebagai bersih dan singkat mungkin.passRequestToServiceClient()
ke bagian atas metode, dan itu masih akan dikompilasi. Dengan penduduk setempat, Anda hanya bisa membuat kesalahan itu jika Anda melewati variabel yang tidak diinisialisasi, yang semoga cukup jelas sehingga Anda tidak benar-benar melakukannya.Masalahnya di sini adalah bahwa argumen Anda untuk variabel lokal valid, tetapi Anda juga telah membuat perubahan tambahan yang tidak benar dan menyebabkan perbaikan yang disarankan gagal tes bau.
Meskipun saya memahami saran "tidak ada variabel kelas" dan ada manfaatnya, Anda sebenarnya juga telah menghapus metode itu sendiri, dan itu adalah ballgame yang sama sekali berbeda. Metode seharusnya tetap, dan sebagai gantinya Anda harus mengubahnya untuk mengembalikan nilainya daripada menyimpannya dalam variabel kelas:
Saya setuju dengan apa yang telah Anda lakukan dalam
process
metode ini, tetapi Anda seharusnya memanggil submethod pribadi daripada mengeksekusi tubuh mereka secara langsung.Anda ingin lapisan abstraksi ekstra, terutama ketika Anda mengalami metode yang perlu digunakan kembali beberapa kali. Bahkan jika saat ini Anda tidak menggunakan kembali metode Anda , masih merupakan praktik yang baik untuk membuat submethod yang relevan, bahkan jika hanya untuk membantu keterbacaan kode.
Terlepas dari argumen variabel lokal, saya segera memperhatikan bahwa perbaikan yang Anda sarankan jauh lebih mudah dibaca daripada yang asli. Saya mengakui bahwa penggunaan variabel variabel secara sembarangan juga mengurangi pembacaan kode, tetapi tidak pada pandangan pertama dibandingkan dengan Anda telah menumpuk semua logika dalam satu metode (sekarang bertele-tele).
sumber
Kode asli menggunakan variabel anggota seperti argumen. Ketika dia mengatakan untuk meminimalkan jumlah argumen, apa yang sebenarnya dia maksudkan adalah untuk meminimalkan jumlah data yang diperlukan metode agar berfungsi. Menempatkan data itu ke dalam variabel anggota tidak meningkatkan apa pun.
sumber
process.Start();
ataumyString.ToLowerCase()
tidak seharusnya tampak terlalu aneh (dan memang yang paling mudah dipahami).this
. Seseorang bahkan dapat berdebat argumen ini diberikan secara eksplisit - sebelum titik.Jawaban lain telah menjelaskan manfaat variabel lokal dengan sempurna, jadi yang tersisa hanyalah bagian dari pertanyaan Anda:
Itu harus mudah. Cukup arahkan dia ke kutipan berikut dalam Kode Bersih Paman Bob:
Artinya, Paman Bob tidak hanya mengatakan bahwa suatu fungsi harus mengambil beberapa argumen, ia juga mengatakan bahwa fungsi harus menghindari berinteraksi dengan negara non-lokal bila memungkinkan.
sumber
"Itu bertentangan dengan apa yang dipikirkan paman seseorang" TIDAK PERNAH merupakan argumen yang bagus. TIDAK PERNAH. Jangan mengambil kebijaksanaan dari paman, pikirkan sendiri.
Yang mengatakan, variabel instan harus digunakan untuk menyimpan informasi yang sebenarnya diperlukan untuk disimpan secara permanen atau semi permanen. Informasi di sini bukan. Sangat mudah untuk hidup tanpa variabel instan, sehingga mereka bisa pergi.
Tes: Tulis komentar dokumentasi untuk setiap variabel instan. Bisakah Anda menulis sesuatu yang tidak sepenuhnya sia-sia? Dan menulis komentar dokumentasi untuk empat pengakses. Mereka sama-sama tidak ada gunanya.
Yang terburuk adalah, asumsikan cara mendekripsi perubahan, karena Anda menggunakan cryptoService yang berbeda. Daripada harus mengubah empat baris kode, Anda harus mengganti empat variabel instan dengan yang berbeda, empat getter dengan yang berbeda, dan mengubah empat baris kode.
Tetapi tentu saja versi pertama lebih disukai jika Anda dibayar oleh baris kode. 31 baris, bukan 11 baris. Tiga kali lebih banyak baris untuk ditulis, dan dipertahankan selamanya, untuk dibaca ketika Anda sedang men-debug sesuatu, untuk beradaptasi ketika perubahan diperlukan, untuk menggandakan jika Anda mendukung cryptoService kedua.
(Ketinggalan poin penting yang menggunakan variabel lokal memaksa Anda melakukan panggilan dengan urutan yang benar).
sumber
Variabel instan adalah untuk merepresentasikan properti objek host mereka, bukan untuk merepresentasikan properti spesifik untuk utas komputasi yang memiliki cakupan lebih sempit daripada objek itu sendiri. Beberapa alasan untuk menarik perbedaan yang tampaknya belum terliput berkisar seputar konkurensi dan reentrancy. Jika metode bertukar data dengan menetapkan nilai variabel instan, maka dua utas bersamaan dapat dengan mudah mengalahkan nilai satu sama lain untuk variabel instans tersebut, menghasilkan bug yang intermiten dan sulit ditemukan.
Bahkan satu utas saja dapat menghadapi masalah di sepanjang garis itu, karena ada risiko tinggi bahwa pola pertukaran data bergantung pada variabel instan membuat metode tidak reentran. Demikian pula, jika variabel yang sama digunakan untuk menyampaikan data di antara pasangan metode yang berbeda, maka ada risiko bahwa utas tunggal yang menjalankan bahkan rangkaian permohonan metode non-rekursif akan mengalami bug yang berputar di sekitar modifikasi tak terduga dari variabel contoh yang terlibat.
Untuk mendapatkan hasil yang benar dalam skenario seperti itu, Anda perlu menggunakan variabel terpisah untuk berkomunikasi antara masing-masing pasangan metode di mana satu memanggil yang lain, atau yang lain agar setiap metode implementasi memperhitungkan semua detail implementasi semua yang lain metode yang digunakannya, baik secara langsung maupun tidak langsung. Ini rapuh, dan sisiknya buruk.
sumber
public EncryptedResponse process(EncryptedRequest encryptedRequest)
ini tidak disinkronkan, dan panggilan serentak kemungkinan besar akan menghancurkan nilai-nilai variabel instan. Ini adalah poin yang bagus untuk diangkat.Membahas hanya
process(...)
, contoh rekan kerja Anda jauh lebih terbaca dalam arti logika bisnis. Sebaliknya, contoh balasan Anda membutuhkan lebih dari sepintas untuk mengekstrak makna apa pun.Karena itu, kode bersih dapat dibaca dan berkualitas baik - mendorong negara bagian keluar ke ruang yang lebih global hanyalah perakitan tingkat tinggi, jadi nol untuk kualitas.
Ini adalah rendisi yang menghilangkan kebutuhan untuk variabel pada ruang lingkup apa pun. Ya kompiler akan menghasilkan mereka tetapi bagian yang penting adalah bahwa ia mengontrol itu sehingga kode akan menjadi efisien. Sementara juga relatif terbaca.
Hanya titik penamaan. Anda ingin nama terpendek yang bermakna dan memperluas informasi yang sudah tersedia. yaitu. destinationURI, 'URI' sudah dikenal dengan tipe tanda tangan.
sumber
Saya hanya akan menghapus variabel-variabel ini dan metode pribadi sama sekali. Inilah refactor saya:
Untuk metode pribadi, mis. Lebih
router.getDestination().getUri()
jelas dan lebih mudah dibaca daripadagetDestinationURI()
. Saya bahkan akan mengulanginya jika saya menggunakan baris yang sama dua kali di kelas yang sama. Untuk melihatnya dengan cara lain, jika ada kebutuhan untukgetDestinationURI()
, maka itu mungkin milik beberapa kelas lain, bukan diSomeBusinessProcess
kelas.Untuk variabel dan properti, kebutuhan umum bagi mereka adalah untuk menyimpan nilai yang akan digunakan nanti. Jika kelas tidak memiliki antarmuka publik untuk properti, mereka mungkin tidak boleh properti. Jenis terburuk dari penggunaan properti kelas mungkin untuk melewati nilai antara metode pribadi dengan cara efek samping.
Bagaimanapun, kelas hanya perlu dilakukan
process()
dan kemudian objek akan dibuang, tidak perlu menyimpan status apa pun dalam memori. Potensi refactor selanjutnya adalah mengeluarkan CryptoService dari kelas itu.Berdasarkan komentar, saya ingin menambahkan jawaban ini berdasarkan praktik dunia nyata. Memang, dalam ulasan kode, hal pertama yang saya pilih adalah untuk memperbaiki kelas dan memindahkan pekerjaan mengenkripsi / mendekripsi. Setelah selesai, maka saya akan bertanya apakah metode dan variabel diperlukan, apakah mereka dinamai dengan benar dan seterusnya. Kode akhir mungkin akan lebih dekat dengan ini:
Dengan kode di atas, saya rasa tidak perlu refactor lebih lanjut. Seperti halnya aturan, saya pikir perlu pengalaman untuk mengetahui kapan dan kapan tidak menerapkannya. Aturan bukanlah teori yang terbukti bekerja di semua situasi.
Peninjauan kode di sisi lain memiliki dampak nyata pada berapa lama sebelum sepotong kode dapat berlalu. Trik saya adalah memiliki lebih sedikit kode dan membuatnya mudah dimengerti. Nama variabel dapat menjadi titik diskusi, jika saya dapat menghapusnya, pengulas bahkan tidak perlu memikirkannya.
sumber
Jawaban Flater mencakup masalah pelingkupan dengan cukup baik, tapi saya pikir ada masalah lain di sini juga.
Perhatikan bahwa ada perbedaan antara fungsi yang memproses data dan fungsi yang hanya mengakses data .
Yang pertama menjalankan logika bisnis yang sebenarnya, sedangkan yang kedua menghemat pengetikan dan mungkin menambah keamanan dengan menambahkan antarmuka yang lebih sederhana dan lebih dapat digunakan kembali.
Dalam kasus ini, tampaknya fungsi akses data tidak menyimpan pengetikan, dan tidak digunakan kembali di mana pun (atau akan ada masalah lain dalam menghapusnya). Jadi fungsi-fungsi ini seharusnya tidak ada.
Dengan hanya mempertahankan logika bisnis dalam fungsi yang disebutkan, kami mendapatkan yang terbaik dari kedua dunia (di suatu tempat antara jawaban Flater dan jawaban imel96 ):
sumber
Hal pertama dan terpenting: Paman Bob kadang-kadang tampak seperti pengkhotbah, tetapi menyatakan bahwa ada pengecualian untuk peraturannya.
Seluruh gagasan Clean Code adalah untuk meningkatkan keterbacaan dan untuk menghindari kesalahan. Ada beberapa aturan yang saling melanggar.
Argumennya tentang fungsi adalah bahwa fungsi niladik adalah yang terbaik, namun hingga tiga parameter dapat diterima. Saya pribadi berpikir bahwa 4 juga ok.
Ketika variabel instan digunakan, mereka harus membuat kelas yang koheren. Itu berarti, variabel harus digunakan dalam banyak, jika tidak semua metode non-statis.
Variabel yang tidak digunakan di banyak tempat kelas, harus dipindahkan.
Saya tidak akan menganggap versi asli atau refactored optimal, dan @Flater menyatakan dengan sangat baik apa yang dapat dilakukan dengan nilai pengembalian. Ini meningkatkan keterbacaan dan mengurangi kesalahan untuk menggunakan nilai kembali.
sumber
Variabel lokal mengurangi ruang lingkup karenanya membatasi cara-cara di mana variabel dapat digunakan dan karenanya membantu mencegah kelas kesalahan tertentu, dan meningkatkan keterbacaan.
Variabel instan mengurangi cara-cara di mana fungsi dapat dipanggil yang juga membantu mengurangi kelas kesalahan tertentu, dan meningkatkan keterbacaan.
Mengatakan satu benar dan yang lain salah mungkin merupakan kesimpulan yang valid dalam satu kasus tertentu, tetapi sebagai saran umum ...
TL; DR: Saya pikir alasan Anda mencium terlalu banyak semangat adalah, terlalu banyak semangat.
sumber
Terlepas dari kenyataan bahwa metode yang dimulai dengan get ... tidak boleh kembali batal, pemisahan level abstraksi dalam metode diberikan dalam solusi pertama. Meskipun solusi kedua lebih mencakup tetapi masih sulit untuk berpikir tentang apa yang terjadi dalam metode ini. Tugas variabel lokal tidak diperlukan di sini. Saya akan menyimpan nama metode dan refactor kode untuk sesuatu seperti itu:
sumber
Kedua hal melakukan hal yang sama dan perbedaan kinerja tidak terlalu mencolok, jadi saya tidak berpikir ada argumen ilmiah . Itu datang ke preferensi subyektif kemudian.
Dan saya juga cenderung menyukai cara Anda lebih baik daripada rekan Anda. Mengapa? Karena saya pikir lebih mudah untuk membaca dan memahami, terlepas dari apa yang dikatakan oleh beberapa penulis buku.
Kedua cara mencapai hal yang sama, tetapi caranya lebih tersebar. Untuk membaca kode itu, Anda perlu bolak-balik antara beberapa fungsi dan variabel anggota. Tidak semua terkondensasi di satu tempat, Anda perlu mengingat semua yang ada di kepala Anda untuk memahaminya. Ini beban kognitif yang jauh lebih besar.
Sebaliknya, pendekatan Anda mengemas semuanya jauh lebih padat, tetapi tidak untuk membuatnya tidak bisa ditembus. Anda baru saja membacanya baris demi baris, dan Anda tidak perlu menghafal begitu banyak untuk memahaminya.
Namun jika dia terbiasa kode yang ditata sedemikian rupa, saya dapat membayangkan bahwa baginya mungkin sebaliknya.
sumber