Apakah kode seperti ini "kecelakaan kereta api" (melanggar Hukum Demeter)?


23

Menjelajahi beberapa kode yang saya tulis, saya menemukan konstruksi berikut yang membuat saya berpikir. Sekilas, sepertinya cukup bersih. Ya, dalam kode aktual getLocation()metode ini memiliki nama yang sedikit lebih spesifik yang lebih baik menggambarkan dengan tepat lokasi yang didapatnya.

service.setLocation(this.configuration.getLocation().toString());

Dalam hal ini, serviceadalah variabel instan dari tipe yang dikenal, dideklarasikan dalam metode. this.configurationberasal dari diteruskan ke konstruktor kelas, dan merupakan turunan dari kelas yang mengimplementasikan antarmuka spesifik (yang mengamanatkan getLocation()metode publik ). Oleh karena itu, tipe kembalinya ekspresi this.configuration.getLocation()diketahui; khusus dalam hal ini, itu adalah java.net.URL, sedangkan service.setLocation()menginginkan a String. Karena dua jenis String dan URL tidak langsung kompatibel, beberapa jenis konversi diperlukan agar pas dengan pasak persegi di lubang bundar.

Namun , menurut Hukum Demeter seperti dikutip dalam Kode Bersih , metode f di kelas C seharusnya hanya memanggil metode pada C , benda yang dibuat oleh atau lulus sebagai argumen untuk f , dan benda diadakan di variabel misalnya dari C . Apa pun di luar itu (final toString()dalam kasus khusus saya di atas, kecuali jika Anda mempertimbangkan objek sementara yang dibuat sebagai hasil dari pemanggilan metode itu sendiri, di mana seluruh UU tampaknya diperdebatkan) dilarang.

Adakah alasan yang masuk akal mengapa panggilan seperti di atas, mengingat kendala yang ada, harus dihilangkan atau bahkan dilarang? Atau apakah saya hanya menjadi terlalu rewel?

Jika saya harus menerapkan metode URLToString()yang hanya panggilan toString()pada URLobjek (seperti yang dikembalikan oleh getLocation()) berlalu untuk itu sebagai parameter, dan kembali hasilnya, saya bisa membungkus getLocation()panggilan di dalamnya untuk mencapai hasil yang sama persis; efektif, saya hanya akan memindahkan konversi satu langkah ke luar. Apakah itu entah bagaimana membuatnya diterima? ( Tampaknya bagi saya, secara intuitif, bahwa seharusnya tidak membuat perbedaan apa pun, karena semua yang dilakukan adalah menggerakkan sedikit hal. Namun, menurut surat Hukum Demeter yang dikutip, itu akan dapat diterima, karena saya kemudian akan beroperasi secara langsung pada parameter ke suatu fungsi.)

Apakah ada bedanya jika ini tentang sesuatu yang sedikit lebih eksotis daripada memanggil toString()tipe standar?

Saat menjawab, perlu diingat bahwa mengubah perilaku atau API dari tipe servicevariabel tidak praktis. Juga, demi argumen, katakanlah bahwa mengubah tipe kembalinya getLocation()juga tidak praktis.

Jawaban:


34

Masalahnya di sini adalah tanda tangan setLocation. Ini diketik dengan ketat .

Untuk menguraikan: Mengapa itu diharapkan String? A Stringmewakili segala jenis data tekstual . Ini bisa berpotensi apa pun kecuali lokasi yang valid.

Bahkan, ini menimbulkan pertanyaan: apa itu lokasi? Bagaimana saya tahu tanpa melihat kode Anda? Jika itu URLdari yang saya akan tahu lebih banyak tentang apa yang diharapkan metode ini.
Mungkin akan lebih masuk akal untuk menjadi kelas khusus Location. Ok, saya tidak akan tahu pada awalnya, apa itu, tetapi pada beberapa titik (mungkin sebelum menulis this.configuration.getLocation()saya akan mengambil satu menit untuk mencari tahu apa metode ini dikembalikan).
Memang, dalam kedua kasus saya perlu mencari tempat lain untuk memahami, apa yang diharapkan. Namun dalam kasus terakhir, jika saya mengerti, apa Locationitu, saya bisa menggunakan API Anda, dalam kasus sebelumnya, jika saya mengerti, apa Stringitu (yang bisa diharapkan), saya masih tidak tahu apa yang diharapkan API Anda.

Dalam skenario yang tidak mungkin, bahwa suatu lokasi adalah segala jenis data tekstual, saya akan menginterpretasikan ulang ini ke segala jenis data, yang memiliki representasi tekstual . Mengingat fakta, yang Objectmemiliki toStringmetode, Anda bisa melakukannya, meskipun ini menuntut lompatan kepercayaan dari klien kode Anda.

Anda juga harus mempertimbangkan, bahwa ini adalah Java yang sedang Anda bicarakan, yang memiliki sedikit fitur berdasarkan desain. Itulah yang memaksa Anda untuk benar-benar menelepon toStringdi akhir.
Jika Anda mengambil C # misalnya, yang juga diketik secara statis, maka Anda benar-benar dapat menghilangkan panggilan itu dengan mendefinisikan perilaku untuk pemeran implisit .
Dalam bahasa yang diketik secara dinamis, seperti Objective-C, Anda tidak benar-benar membutuhkan konversi juga, karena selama nilainya berperilaku seperti string, semua orang senang.

Orang bisa berargumen, bahwa panggilan terakhir untuk toStringkurang dari panggilan, daripada hanya kebisingan yang dihasilkan oleh permintaan Jawa untuk kesaksian. Anda memanggil metode, yang dimiliki oleh objek Java apa pun , oleh karena itu Anda tidak benar-benar menyandikan pengetahuan tentang "unit jauh" dan dengan demikian tidak melanggar Prinsip Paling Sedikit Pengetahuan. Tidak ada cara, tidak peduli apa yang getLocationkembali, itu tidak memiliki toStringmetode.

Tapi tolong, jangan gunakan string, kecuali mereka benar-benar pilihan paling alami (atau kecuali Anda menggunakan bahasa, yang bahkan tidak memiliki enum ... ada di sana).


Saya cenderung setuju dengan Anda, tetapi dia sudah mengatakan dia tidak dapat memodifikasi API layanan.
jhocking

1
@jhocking: Dia tidak mengatakan dia tidak bisa. Dia bilang itu tidak praktis. Saya tidak setuju. Mencoba menerapkan praktik terbaik pada kode yang mengatasi cacat dalam desain API benar-benar masuk akal jika solusi semacam itu adalah satu-satunya pilihan yang mungkin. Namun di sini pengaturan lurus API adalah pilihan terbaik. Menghapus kekurangan selalu lebih baik daripada mengatasinya.
back2dos

well +1 tetap untuk "kurang dari panggilan sebenarnya kebisingan yang dihasilkan oleh permintaan untuk explicitness Jawa"
jhocking

1
Saya akan memberikan +1 ini meskipun hanya untuk "mengetik dengan ketat". Dalam kode saya, saya mencoba untuk melewati jenis yang mengekspresikan makna / maksud sebanyak mungkin, tetapi ketika bekerja dengan perpustakaan dan API pihak ketiga, kadang-kadang Anda agak terjebak menggunakan apa yang diputuskan oleh pembuat API tersebut. Dan IIRC, lokasi bisa memang menjadi "setiap jenis data tekstual", tapi untuk pelaksanaan Saya bekerja pada, lokasi non-URL sama sekali tidak berarti.
CVn

1
Adapun untuk mengubah API; ini adalah perangkat lunak komputer, jadi hampir selalu mungkin untuk mengubah keadaan. (Jika tidak ada yang lain, Anda selalu dapat menulis lapisan abstraksi.) Namun, kadang-kadang melakukannya melibatkan terlalu banyak upaya baik jangka pendek dan jangka panjang untuk dibenarkan. Maka, sangat mungkin untuk melakukan perubahan seperti itu, tetapi masih tidak praktis.
CVn

21

Hukum Demeter adalah pedoman desain, bukan hukum yang harus diikuti secara agama.

Jika Anda merasa bahwa kelas Anda cukup terpisah daripada tidak ada yang salah dengan garis this.configuration.getLocation()terutama jika, seperti yang Anda katakan, tidak praktis untuk mengubah bagian lain dari API.

Saya cukup yakin bahwa klien akan sangat bahagia bahkan jika Anda membuat Hukum Demeter berkeping-keping, selama Anda memberikan apa yang dia inginkan dan tepat waktu. Yang bukan alasan untuk membuat perangkat lunak yang buruk, tetapi pengingat untuk bersikap pragmatis saat mengembangkan perangkat lunak.


1
Karena this.configurationmerupakan variabel instan dari jenis antarmuka yang dikenal, memanggil metode di atasnya yang didefinisikan oleh antarmuka itu tampaknya baik-baik saja bahkan menurut interpretasi yang ketat. Ya, saya tahu itu adalah pedoman, seperti KISS, SOLID, YAGNI, dan sebagainya. Ada sangat sedikit jika memang ada "hukum" (dalam arti hukum) dalam pengembangan perangkat lunak umum.
CVn

4
+1 untuk menjadi pragmatis ;-)
Treb

1
Saya tidak berpikir Hukum Dementer seharusnya berlaku dalam kasus seperti ini - konfigurasi pada dasarnya adalah sebuah wadah. Di setiap API saya pernah bekerja dengan melihat ke dalam wadah adalah perilaku normal dan yang diharapkan.
Loren Pechtel

2

Satu-satunya hal yang dapat saya pikirkan untuk tidak menulis kode seperti ini adalah bagaimana jika this.configuration.getLocation()mengembalikan null? Itu tergantung pada kode Anda di sekitarnya dan audiens target menggunakan kode ini. Tetapi seperti yang dikatakan Marco - hukum Demeter adalah aturan praktis - itu baik untuk diikuti, tetapi jangan mematahkan punggung Anda dengan melakukan hal yang tidak perlu.


Jika this.configuration.getLocation()akan mengembalikan nol, maka kita (a) kemungkinan besar tidak akan pernah sejauh itu, atau (b) sesuatu yang sangat buruk telah terjadi untuk sementara, dalam hal ini saya ingin kode gagal. Jadi sementara ini jelas merupakan poin yang valid secara umum, dalam kasus khusus ini cukup aman untuk mengatakan bahwa itu tidak berlaku. Juga, mengatasi semua ini dan banyak lagi adalah handler pengecualian yang dirancang khusus untuk menghadapi kegagalan yang tak terduga.
CVn

2

Mengikuti Hukum Demeter dengan ketat berarti Anda harus mengimplementasikan metode dalam objek konfigurasi seperti:

function getLocationAsString() {
  return getLocation().toString();
}

tetapi secara pribadi saya tidak akan repot karena ini adalah situasi kecil, dan lagi pula tangan Anda agak terikat karena API Anda tidak dapat berubah. Aturan pemrograman adalah tentang apa yang harus Anda lakukan ketika Anda punya pilihan, tetapi kadang-kadang Anda tidak punya pilihan.


1
Itu sama dengan yang disarankan di sini: c2.com/cgi/wiki?TrainWreck "Buat metode yang mewakili perilaku yang diinginkan dan memberi tahu klien apa yang harus dilakukan. Ini mengikuti prinsip" katakan, jangan tanya "."
heltonbiker
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.