Rasional untuk memilih variabel lokal daripada variabel instan?

109

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?

Alexander
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
maple_shaft

Jawaban:

170

Apa tujuan, alasan ilmiah untuk mendukung variabel lokal daripada variabel instan?

Lingkup bukan keadaan biner, ini adalah gradien. Anda dapat menentukan peringkat ini dari yang terbesar hingga yang terkecil:

Global > Class > Local (method) > Local (code block, e.g. if, for, ...)

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:

  • Ini memaksa Anda untuk memikirkan tanggung jawab kelas saat ini dan membantu Anda tetap berpegang pada SRP.
  • Hal ini memungkinkan Anda untuk tidak perlu menghindari konflik penamaan global, misalnya jika dua atau lebih kelas memiliki Nameproperti, Anda tidak dipaksa untuk awalan mereka seperti FooName, BarName, ... Jadi menjaga nama variabel Anda sebagai bersih dan singkat mungkin.
  • Ini mendeklarasikan kode dengan membatasi variabel yang tersedia (misalnya untuk Intellisense) dengan variabel yang relevan secara kontekstual.
  • Ini memungkinkan beberapa bentuk kontrol akses sehingga data Anda tidak dapat dimanipulasi oleh beberapa aktor yang tidak Anda kenal (misalnya kelas berbeda yang dikembangkan oleh kolega).
  • Itu membuat kode lebih mudah dibaca ketika Anda memastikan bahwa deklarasi variabel-variabel ini mencoba untuk tetap sedekat mungkin dengan penggunaan variabel-variabel ini.
  • Tidak mau mendeklarasikan variabel dalam ruang lingkup yang terlalu luas sering merupakan indikasi dari pengembang yang tidak begitu memahami OOP atau bagaimana mengimplementasikannya. Melihat variabel yang terlalu luas cakupannya bertindak sebagai tanda merah bahwa mungkin ada sesuatu yang salah dengan pendekatan OOP (baik dengan pengembang pada umumnya atau basis kode secara spesifik).
  • (Komentar oleh Kevin) Menggunakan penduduk setempat memaksa Anda untuk melakukan hal-hal dalam urutan yang benar. Dalam kode asli (variabel kelas), Anda bisa salah pindah 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.

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.

Dan sebaliknya, apakah ada kerugian untuk refactoring yang mungkin saya abaikan?

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:

private byte[] getEncodedData() {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
}

private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
}

// and so on...

Saya setuju dengan apa yang telah Anda lakukan dalam processmetode ini, tetapi Anda seharusnya memanggil submethod pribadi daripada mengeksekusi tubuh mereka secara langsung.

public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = getEncodedData();
    EncryptionInfo encryptionInfo = getEncryptionInfo();

    //and so on...

    return cryptoService.encryptResponse(payloadOfResponse);
}

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).

Flater
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
maple_shaft
79

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.

Alex
sumber
20
Setuju! Variabel anggota tersebut hanyalah argumen fungsi implisit. Sebenarnya ini lebih buruk karena sekarang tidak ada tautan eksplisit antara nilai variabel tersebut dan penggunaan fungsi (dari POV eksternal)
Rémi
1
Saya akan mengatakan ini bukan apa yang dimaksud buku itu. Berapa banyak fungsi yang membutuhkan data input nol untuk dijalankan? Ini salah satu bit yang saya pikir buku itu salah.
Qwertie
1
@ Qwertie Jika Anda memiliki objek, data yang berfungsi mungkin sepenuhnya dienkapsulasi di dalamnya. Fungsi suka process.Start();atau myString.ToLowerCase()tidak seharusnya tampak terlalu aneh (dan memang yang paling mudah dipahami).
R. Schmitz
5
Keduanya memiliki satu argumen: implisit this. Seseorang bahkan dapat berdebat argumen ini diberikan secara eksplisit - sebelum titik.
BlackJack
47

Jawaban lain telah menjelaskan manfaat variabel lokal dengan sempurna, jadi yang tersisa hanyalah bagian dari pertanyaan Anda:

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.

Itu harus mudah. Cukup arahkan dia ke kutipan berikut dalam Kode Bersih Paman Bob:

Tidak Memiliki Efek Samping

Efek sampingnya adalah kebohongan. Fungsi Anda berjanji untuk melakukan satu hal, tetapi juga melakukan hal-hal tersembunyi lainnya. Terkadang ia akan membuat perubahan tak terduga ke variabel kelasnya sendiri. Kadang-kadang itu akan membuat mereka ke parameter yang dilewatkan ke fungsi atau ke sistem global. Dalam kedua kasus itu, mereka adalah kesalahan-kesalahan yang licik dan merusak yang sering mengakibatkan penggabungan temporal yang aneh dan keteraturan keteraturan.

(contoh dihilangkan)

Efek samping ini menciptakan kopling sementara. Artinya, checkPassword hanya dapat dipanggil pada waktu-waktu tertentu (dengan kata lain, saat aman untuk menginisialisasi sesi). Jika itu disebut rusak, data sesi mungkin hilang secara tidak sengaja. Kopling temporal membingungkan, terutama ketika disembunyikan sebagai efek samping. Jika Anda harus memiliki kopling sementara, Anda harus membuatnya jelas atas nama fungsi. Dalam hal ini kita mungkin mengganti nama fungsi checkPasswordAndInitializeSession, meskipun itu tentu saja melanggar "Lakukan satu hal."

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.

meriton
sumber
3
Dalam "dunia prefek", ini akan menjadi jawaban kedua yang terdaftar. Jawaban pertama untuk situasi ideal yang didengarkan rekan kerjanya - tetapi jika rekan kerjanya adalah orang yang fanatik, jawaban ini di sini akan menangani situasi tanpa terlalu banyak kesulitan.
R. Schmitz
2
Untuk variasi yang lebih pragmatis pada gagasan ini, Sederhananya jauh lebih mudah untuk bernalar tentang negara lokal daripada negara contoh atau global. Efek samping dan efek samping yang terdefinisi dengan ketat dan jarang menyebabkan masalah. Sebagai contoh, banyak fungsi sortir beroperasi di tempat melalui efek samping, tetapi ini mudah dipikirkan dalam lingkup lokal.
Beefster
1
Ah, si tua baik "dalam aksiomatik yang kontradiktif, mungkin membuktikan apa pun". Karena tidak ada kebenaran dan kebohongan IRL yang keras, setiap dogma harus memasukkan pernyataan yang menyatakan hal-hal yang berlawanan, yaitu bertentangan.
ivan_pozdeev
26

"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).

gnasher729
sumber
16
Berpikir untuk diri sendiri tentu saja bagus. Tetapi paragraf pembuka Anda secara efektif mencakup pelajar atau junior yang menolak masukan dari guru atau senior; yang terlalu jauh.
Flater
9
@Flater Setelah memikirkan input dari guru atau senior, dan melihat mereka salah, menolak input mereka adalah satu-satunya hal yang benar. Pada akhirnya, ini bukan tentang menolak, tetapi tentang mempertanyakannya dan hanya menolaknya jika itu terbukti salah.
glglgl
10
@ChristianHackl: Saya sepenuhnya setuju dengan tidak mengikuti tradisionalisme secara membabi buta, tapi saya juga tidak akan membubarkannya. Jawaban yang tampaknya menyarankan untuk menghindari pengetahuan yang diperoleh demi pandangan Anda sendiri, dan itu bukan pendekatan yang sehat untuk diambil. Mengikuti pendapat orang lain dengan buta, tidak. Mempertanyakan sesuatu ketika Anda tidak setuju, jelas ya. Segera menolaknya karena Anda tidak setuju, tidak. Ketika saya membacanya, paragraf pertama dari jawaban setidaknya tampaknya menyarankan yang terakhir. Itu sangat tergantung pada apa arti gnasher dengan "berpikir untuk diri sendiri", yang perlu dijabarkan.
Flater
9
Pada platform berbagi kebijaksanaan, ini sepertinya tidak pada tempatnya ...
drjpizzle
4
TIDAK PERNAH juga TIDAK PERNAH argumen yang bagus ... Saya sepenuhnya setuju bahwa ada aturan untuk memandu, bukan untuk meresepkan. Namun, aturan tentang aturan hanyalah subkelas dari aturan, jadi Anda berbicara keputusan Anda sendiri ...
cmaster
14

Apa tujuan, alasan ilmiah untuk mendukung variabel lokal daripada variabel instan? Sepertinya saya tidak bisa meletakkan jari saya di atasnya. Intuisi saya memberi tahu saya bahwa kopling tersembunyi adalah buruk dan bahwa ruang lingkup yang sempit lebih baik daripada yang luas. Tapi apa ilmu yang mendukung hal ini?

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.

John Bollinger
sumber
7
Sejauh ini, ini adalah satu-satunya jawaban yang menyebutkan thread-safety dan concurrency. Itu luar biasa mengingat contoh kode spesifik dalam pertanyaan: contoh dari SomeBusinessProcess tidak dapat dengan aman memproses beberapa permintaan terenkripsi sekaligus. Metode 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.
Joshua Taylor
9

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.

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest request) {
    checkNotNull(encryptedRequest);

    return encryptResponse
      (routeTo
         ( destination()
         , requestData(request)
         , destinationEncryption()
         )
      );
  }

  private byte[] requestData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo destinationEncryption() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI destination() {
    return router.getDestination().getUri();
  }

  private EncryptedObject routeTo(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }

  private void encryptResponse(EncryptedObject payloadOfResponse) {
    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

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.

Kain0_0
sumber
4
Menghilangkan semua variabel tidak selalu membuat kode lebih mudah dibaca.
Pharap
Hilangkan semua variabel sepenuhnya dengan gaya pointfree en.wikipedia.org/wiki/Tacit_programming
Marcin
@Pharap Benar, kurangnya variabel tidak menjamin keterbacaan. Dalam beberapa kasus bahkan membuat proses debug lebih sulit. Intinya adalah bahwa nama-nama yang dipilih dengan baik, penggunaan ekspresi yang jelas, dapat mengkomunikasikan ide dengan sangat jelas, namun tetap efisien.
Kain0_0
7

Saya hanya akan menghapus variabel-variabel ini dan metode pribadi sama sekali. Inilah refactor saya:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    return cryptoService.encryptResponse(
        serviceClient.handle(router.getDestination().getUri(),
        cryptoService.decryptRequest(encryptedRequest, byte[].class),
        cryptoService.getEncryptionInfoForDefaultClient()));
  }
}

Untuk metode pribadi, mis. Lebih router.getDestination().getUri()jelas dan lebih mudah dibaca daripada getDestinationURI(). Saya bahkan akan mengulanginya jika saya menggunakan baris yang sama dua kali di kelas yang sama. Untuk melihatnya dengan cara lain, jika ada kebutuhan untuk getDestinationURI(), maka itu mungkin milik beberapa kelas lain, bukan di SomeBusinessProcesskelas.

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:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;

  public Response process(Request request) {
    return serviceClient.handle(router.getDestination().getUri());
  }
}

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.

imel96
sumber
Suara keras saya, meskipun banyak yang akan ragu di sini. Tentu saja beberapa abstraksi, masuk akal. (BTW metode yang disebut "proses" mengerikan.) Tapi di sini logikanya minimal. Namun pertanyaan OP adalah pada gaya kode keseluruhan, dan di sana kasusnya mungkin lebih kompleks.
Joop Eggen
1
Salah satu masalah yang jelas dengan merantai semuanya menjadi satu metode panggilan adalah keterbacaan belaka. Ini juga tidak berfungsi jika Anda membutuhkan lebih dari satu operasi pada objek tertentu. Juga, ini hampir tidak mungkin untuk di-debug karena Anda tidak dapat melewati operasi dan memeriksa objek. Walaupun ini bekerja pada tingkat teknis, saya tidak akan mengadvokasi ini karena secara besar-besaran mengabaikan aspek non-runtime pengembangan perangkat lunak.
Flater
@Flater Saya setuju dengan komentar Anda, kami tidak ingin menerapkan ini di mana-mana. Saya mengedit jawaban saya untuk memperjelas posisi praktis saya. Yang ingin saya tunjukkan adalah bahwa dalam praktiknya kami hanya menerapkan aturan yang sesuai. Chaining method call baik-baik saja dalam hal ini, dan jika saya perlu men-debug, saya akan meminta tes untuk metode dirantai.
imel96
@JoopEggen Ya, abstraksi masuk akal. Dalam contoh tersebut, metode pribadi tidak memberikan abstraksi, pengguna kelas bahkan tidak tahu tentang mereka
imel96
1
@ imel96 itu lucu bahwa Anda mungkin salah satu dari sedikit orang di sini yang memperhatikan bahwa penggabungan antara ServiceClient dan CryptoService membuatnya berguna untuk fokus pada menyuntikkan CS ke SC daripada ke SBP, menyelesaikan masalah mendasar pada tingkat arsitektur yang lebih tinggi ... itulah IMVHO poin utama dari cerita ini; terlalu mudah untuk melacak gambaran besar sambil fokus pada detail.
vaxquis
4

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 ):

public EncryptedResponse process(EncryptedRequest encryptedRequest) {

    byte[] requestData = decryptRequest(encryptedRequest);
    EncryptedObject responseData = handleRequest(router.getDestination().getUri(), requestData, cryptoService.getEncryptionInfoForDefaultClient());
    EncryptedResponse response = encryptResponse(responseData);

    return response;
}

// define: decryptRequest(), handleRequest(), encryptResponse()
pengguna673679
sumber
3

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.

kap
sumber
1

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.

drjpizzle
sumber
0

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:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    return getEncryptedResponse(
            passRequestToServiceClient(getDestinationURI(), getEncodedData(encryptedRequest) getEncryptionInfo())
        );
  }

  private EncryptedResponse getEncryptedResponse(EncryptedObject encryptedObject) {
    return cryptoService.encryptResponse(encryptedObject);
  }

  private byte[] getEncodedData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI getDestinationURI() {
    return router.getDestination().getUri();
  }

  private EncryptedObject passRequestToServiceClient(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}
Roman Weis
sumber
0

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.

Vilx-
sumber
Itu memang terbukti menjadi rintangan utama bagi saya untuk memahami alurnya. Contoh ini cukup kecil, tetapi yang lain menggunakan 35 variabel instance (!) Untuk hasil antara, tidak menghitung selusin variabel instance yang berisi dependensi. Cukup sulit untuk mengikuti, karena Anda harus melacak apa yang telah ditetapkan sebelumnya. Beberapa bahkan digunakan kembali kemudian, membuatnya lebih sulit. Untungnya argumen yang disajikan di sini akhirnya meyakinkan kolega saya untuk setuju dengan refactoring.
Alexander