Saya mencoba mencari alternatif untuk penggunaan variabel global dalam beberapa kode lama. Tetapi pertanyaan ini bukan tentang alternatif teknis, saya terutama khawatir tentang terminologi .
Solusi yang jelas adalah melewatkan parameter ke dalam fungsi alih-alih menggunakan global. Dalam basis kode warisan ini, itu berarti bahwa saya harus mengubah semua fungsi dalam rantai panggilan lama antara titik di mana nilai akhirnya akan digunakan dan fungsi yang menerima parameter pertama.
higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)
di mana newParam
sebelumnya merupakan variabel global dalam contoh saya, tetapi itu bisa menjadi nilai hardcoded sebelumnya. Intinya adalah bahwa sekarang nilai newParam diperoleh pada higherlevel()
dan harus "bepergian" ke sana level3()
.
Saya bertanya-tanya apakah ada nama untuk situasi / pola di mana Anda perlu menambahkan parameter ke banyak fungsi yang hanya "meneruskan" nilai yang tidak dimodifikasi.
Mudah-mudahan, menggunakan terminologi yang tepat akan memungkinkan saya untuk menemukan lebih banyak sumber daya tentang solusi untuk mendesain ulang dan menggambarkan situasi ini kepada rekan kerja.
Jawaban:
Data itu sendiri disebut "tramp data" . Ini adalah "bau kode", yang menunjukkan bahwa satu potong kode berkomunikasi dengan sepotong kode lain dari kejauhan, melalui perantara.
Refactoring untuk menghapus variabel global sulit, dan menginjak-injak data adalah salah satu metode untuk melakukannya, dan seringkali cara termurah. Memang ada biayanya.
sumber
Saya tidak berpikir ini, dengan sendirinya, adalah anti-pola. Saya pikir masalahnya adalah bahwa Anda memikirkan fungsi sebagai rantai ketika Anda benar-benar harus menganggap masing-masing sebagai kotak hitam independen ( CATATAN : metode rekursif adalah pengecualian penting untuk saran ini.)
Sebagai contoh, katakanlah saya perlu menghitung jumlah hari antara dua tanggal kalender jadi saya membuat fungsi:
Untuk melakukan ini, saya kemudian membuat fungsi baru:
Maka fungsi pertama saya menjadi sederhana:
Tidak ada yang anti-pola tentang ini. Parameter metode daysBetween diteruskan ke metode lain dan tidak pernah direferensikan dalam metode tetapi mereka masih diperlukan untuk metode itu untuk melakukan apa yang perlu dilakukan.
Apa yang akan saya rekomendasikan adalah melihat setiap fungsi dan mulai dengan beberapa pertanyaan:
Jika Anda melihat tumpukan kode tanpa tujuan tunggal yang dibundel ke dalam metode, Anda harus mulai dengan menguraikannya. Ini bisa membosankan. Mulailah dengan hal-hal termudah untuk ditarik dan pindah ke metode terpisah dan ulangi sampai Anda memiliki sesuatu yang masuk akal.
Jika Anda hanya memiliki terlalu banyak parameter, pertimbangkan Method to Object refactoring .
sumber
BobDalgleish telah mencatat bahwa pola (anti-) ini disebut " data gelandangan ".
Dalam pengalaman saya, penyebab paling umum dari data gelandangan berlebihan adalah memiliki banyak variabel status tertaut yang harus benar-benar dienkapsulasi dalam objek atau struktur data. Kadang-kadang, bahkan mungkin perlu untuk bersarang banyak objek untuk mengatur data dengan benar.
Untuk contoh sederhana, pertimbangkan gim yang memiliki karakter pemain yang dapat disesuaikan, dengan properti seperti
playerName
,playerEyeColor
dan sebagainya. Tentu saja, pemain juga memiliki posisi fisik di peta permainan, dan berbagai properti lainnya seperti, katakanlah, tingkat kesehatan saat ini dan maksimum, dan sebagainya.Dalam iterasi pertama dari permainan seperti itu, mungkin ini merupakan pilihan yang masuk akal untuk membuat semua properti ini menjadi variabel global - lagipula, hanya ada satu pemain, dan hampir semua hal dalam permainan itu entah bagaimana melibatkan pemain. Jadi keadaan global Anda mungkin mengandung variabel seperti:
Tetapi pada titik tertentu, Anda mungkin perlu mengubah desain ini, mungkin karena Anda ingin menambahkan mode multipemain ke dalam gim. Sebagai upaya pertama, Anda bisa mencoba membuat semua variabel tersebut lokal, dan meneruskannya ke fungsi yang membutuhkannya. Namun, Anda kemudian mungkin menemukan bahwa tindakan tertentu dalam game Anda mungkin melibatkan rantai panggilan fungsi seperti, katakan:
... dan
interactWithShopkeeper()
fungsinya meminta penjaga toko memanggil pemain dengan nama, jadi Anda sekarang tiba-tiba harus lulusplayerName
sebagai data gelandangan melalui semua fungsi itu. Dan, tentu saja, jika penjaga toko berpikir bahwa pemain bermata biru naif, dan akan membebankan harga yang lebih tinggi untuk mereka, maka Anda harus melewatiplayerEyeColor
seluruh rantai fungsi, dan sebagainya.The tepat solusi, dalam hal ini, tentu saja untuk menentukan objek pemain yang merangkum nama, warna mata, posisi, kesehatan dan properti lainnya dari karakter pemain. Dengan begitu, Anda hanya perlu meneruskan objek tunggal itu ke semua fungsi yang melibatkan pemain.
Juga, beberapa fungsi di atas dapat secara alami dibuat menjadi metode objek pemain itu, yang secara otomatis akan memberi mereka akses ke properti pemain. Di satu sisi, ini hanyalah gula sintaksis, karena memanggil metode pada objek secara efektif melewatkan instance objek sebagai parameter tersembunyi ke metode tersebut, tetapi itu membuat kode terlihat lebih jelas dan lebih alami jika digunakan dengan benar.
Tentu saja, permainan tipikal akan memiliki lebih banyak kondisi "global" daripada hanya pemain; misalnya, Anda hampir pasti memiliki semacam peta tempat permainan berlangsung, dan daftar karakter non-pemain bergerak di peta, dan mungkin item diletakkan di situ, dan sebagainya. Anda bisa melewatkan semua itu di sekitar sebagai objek gelandangan juga, tapi itu lagi akan mengacaukan argumen metode Anda.
Alih-alih, solusinya adalah membuat objek menyimpan referensi ke objek lain yang memiliki hubungan permanen atau sementara dengannya. Jadi, misalnya, objek pemain (dan mungkin objek NPC juga) mungkin harus menyimpan referensi ke objek "dunia game", yang akan memiliki referensi ke level / peta saat ini, sehingga metode seperti
player.moveTo(x, y)
tidak perlu secara eksplisit diberi peta sebagai parameter.Demikian pula, jika karakter pemain kami memiliki, misalnya, anjing peliharaan yang mengikuti mereka, kami secara alami akan mengelompokkan semua variabel keadaan yang menggambarkan anjing menjadi satu objek, dan memberikan objek pemain referensi ke anjing (sehingga pemain dapat , katakan, panggil anjing dengan nama) dan sebaliknya (agar anjing tahu di mana pemain berada). Dan, tentu saja, kami mungkin ingin membuat pemain dan objek anjing menjadi dua subclass dari objek "aktor" yang lebih umum, sehingga kami dapat menggunakan kembali kode yang sama untuk, katakanlah, memindahkan keduanya di sekitar peta.
Ps. Meskipun saya telah menggunakan game sebagai contoh, ada beberapa jenis program lain di mana masalah seperti itu muncul juga. Namun, dalam pengalaman saya, masalah mendasar cenderung selalu sama: Anda memiliki banyak variabel terpisah (baik lokal maupun global) yang benar-benar ingin dikelompokkan bersama menjadi satu atau lebih objek yang saling terkait. Apakah "data gelandangan" yang mengganggu fungsi Anda terdiri dari pengaturan opsi "global" atau kueri basis data yang di-cache atau vektor negara dalam simulasi numerik, solusinya selalu untuk mengidentifikasi konteks alami dari data tersebut, dan menjadikannya sebagai objek (atau apa pun yang setara terdekat dalam bahasa pilihan Anda).
sumber
foo.method(bar, baz)
danmethod(foo, bar, baz)
, ada alasan lain (termasuk polimorfisme, enkapsulasi, lokalitas, dll) untuk memilih yang pertama.Saya tidak mengetahui nama spesifik untuk ini, tapi saya rasa perlu disebutkan bahwa masalah yang Anda uraikan hanyalah masalah menemukan kompromi terbaik untuk lingkup parameter seperti itu:
sebagai variabel global, cakupannya terlalu besar ketika program mencapai ukuran tertentu
sebagai parameter murni lokal, cakupannya mungkin terlalu kecil, ketika itu mengarah ke banyak daftar parameter berulang di rantai panggilan
jadi sebagai trade-off, Anda sering dapat membuat parameter seperti variabel anggota dalam satu atau beberapa kelas, dan itulah yang saya sebut desain kelas yang tepat .
sumber
Saya percaya pola yang Anda gambarkan adalah injeksi ketergantungan yang tepat . Beberapa komentator berpendapat bahwa ini adalah sebuah pola , bukan anti-pola , dan saya cenderung setuju.
Saya juga setuju dengan jawaban @JamesJames, di mana ia mengklaim bahwa itu adalah praktik pemrograman yang baik untuk memperlakukan setiap fungsi sebagai kotak hitam yang mengambil semua inputnya sebagai parameter eksplisit. Artinya, jika Anda menulis fungsi yang membuat sandwich selai kacang dan jelly, Anda bisa menuliskannya sebagai
tetapi akan lebih baik untuk menerapkan injeksi ketergantungan dan menulisnya seperti ini sebagai gantinya:
Sekarang Anda memiliki fungsi yang dengan jelas mendokumentasikan semua dependensinya dalam tanda tangan fungsinya, yang sangat bagus untuk dibaca. Lagi pula, memang benar bahwa untuk
make_sandwich
Anda memerlukan akses keRefrigerator
; jadi tanda tangan fungsi lama pada dasarnya tidak jujur dengan tidak mengambil kulkas sebagai bagian dari inputnya.Sebagai bonus, jika Anda melakukan hierarki kelas dengan benar, hindari mengiris, dan sebagainya, Anda bahkan dapat menguji
make_sandwich
fungsi dengan mengirimkan sebuahMockRefrigerator
! (Anda mungkin perlu mengujinya dengan cara ini karena lingkungan pengujian unit Anda mungkin tidak memiliki akses kePhysicalRefrigerator
s.)Saya mengerti bahwa tidak semua penggunaan injeksi ketergantungan memerlukan pipa yang bernama sama parameter berbagai tingkatan bawah panggilan stack, jadi aku tidak menjawab persis pertanyaan Anda bertanya ... tapi jika Anda sedang mencari untuk membaca lebih lanjut tentang hal ini, "injeksi ketergantungan" jelas merupakan kata kunci yang relevan untuk Anda.
sumber
Refrigerator
menjadiIngredientSource
, atau bahkan menggeneralisasi gagasan "sandwich" menjaditemplate<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&)
; itu disebut "pemrograman generik" dan itu cukup kuat, tapi tentu saja ini jauh lebih misterius daripada yang ingin dicapai OP saat ini. Jangan ragu untuk membuka pertanyaan baru tentang tingkat abstraksi yang tepat untuk program sandwich. ;)make_sandwich()
.Ini cukup banyak definisi kopling buku teks , satu modul memiliki ketergantungan yang sangat mempengaruhi yang lain, dan yang menciptakan efek riak ketika diubah. Komentar dan jawaban lain benar bahwa ini adalah peningkatan dari global, karena kopling sekarang lebih eksplisit dan lebih mudah bagi programmer untuk melihat, daripada subversif. Itu tidak berarti itu tidak boleh diperbaiki. Anda harus bisa refactor untuk melepas atau mengurangi kopling, meskipun jika sudah ada di sana sementara itu bisa menyakitkan.
sumber
level3()
perlunewParam
, itu sudah pasti, tetapi entah bagaimana bagian kode yang berbeda harus berkomunikasi satu sama lain. Saya tidak perlu menyebut parameter fungsi kopling buruk jika fungsi itu menggunakan parameter. Saya pikir aspek masalah dari rantai adalah kopling tambahan yang diperkenalkanlevel1()
danlevel2()
yang tidak ada gunanyanewParam
kecuali untuk meneruskannya. Jawaban bagus, +1 untuk pemasangan.Meskipun jawaban ini tidak secara langsung menjawab pertanyaan Anda, saya merasa saya akan lalai untuk membiarkannya berlalu tanpa menyebutkan bagaimana memperbaikinya (karena seperti yang Anda katakan, ini mungkin anti-pola). Saya harap Anda dan pembaca lain bisa mendapatkan nilai dari komentar tambahan ini tentang cara menghindari "data gelandangan" (seperti yang dinamai Bob Dalgleish dengan sangat membantu untuk kami).
Saya setuju dengan jawaban yang menyarankan melakukan sesuatu yang lebih OO untuk menghindari masalah ini. Namun, cara lain untuk juga membantu mengurangi berlalunya argumen ini secara mendalam tanpa hanya melompat ke " hanya lulus kelas di mana Anda dulu melewati banyak argumen! " Adalah dengan refactor sehingga beberapa langkah proses Anda terjadi di tingkat yang lebih tinggi daripada yang lebih rendah satu. Misalnya, inilah beberapa kode sebelum :
Perhatikan bahwa ini menjadi lebih buruk, semakin banyak hal yang harus dilakukan
ReportStuff
. Anda mungkin harus memberikan contoh Reporter yang ingin Anda gunakan. Dan segala macam dependensi yang harus diserahkan, berfungsi ke fungsi bersarang.Saran saya adalah untuk menarik semua itu ke tingkat yang lebih tinggi, di mana pengetahuan tentang langkah-langkah membutuhkan hidup dalam metode tunggal alih-alih tersebar di rantai panggilan metode. Tentu saja akan lebih rumit dalam kode nyata, tetapi ini memberi Anda ide:
Perhatikan bahwa perbedaan besar di sini adalah Anda tidak harus melewati ketergantungan melalui rantai panjang. Bahkan jika Anda meratakan tidak hanya satu tingkat, tetapi beberapa tingkat dalam, jika level tersebut juga mencapai beberapa "perataan" sehingga proses tersebut dilihat sebagai serangkaian langkah di tingkat itu, Anda akan membuat peningkatan.
Meskipun ini masih bersifat prosedural dan belum ada yang berubah menjadi objek, ini adalah langkah yang baik untuk memutuskan jenis enkapsulasi yang dapat Anda capai dengan mengubah sesuatu menjadi kelas. Metode yang dirantai dalam-dalam dalam skenario sebelum menyembunyikan detail dari apa yang sebenarnya terjadi dan dapat membuat kode sangat sulit untuk dipahami. Walaupun Anda bisa berlebihan dan akhirnya membuat kode tingkat tinggi tahu tentang hal-hal yang tidak seharusnya, atau membuat metode yang melakukan terlalu banyak hal sehingga melanggar prinsip tanggung jawab tunggal, secara umum saya telah menemukan bahwa meratakan hal-hal sedikit membantu kejelasan dan dalam membuat perubahan tambahan menuju kode yang lebih baik.
Perhatikan bahwa saat Anda melakukan semua ini, Anda harus mempertimbangkan kemampuan tes. Panggilan metode berantai benar-benar membuat pengujian unit lebih sulit karena Anda tidak memiliki titik masuk dan titik keluar yang baik dalam rakitan untuk irisan yang ingin Anda uji. Perhatikan bahwa dengan perataan ini, karena metode Anda tidak lagi menggunakan banyak dependensi, mereka lebih mudah untuk diuji, tidak memerlukan banyak ejekan!
Saya baru-baru ini mencoba menambahkan tes unit ke kelas (yang saya tidak tulis) yang mengambil sekitar 17 dependensi, yang semuanya harus diejek! Saya belum menyelesaikan semuanya, tapi saya membagi kelas menjadi tiga kelas, masing-masing berurusan dengan salah satu kata benda terpisah yang berkaitan, dan membuat daftar ketergantungan turun menjadi 12 untuk yang terburuk dan sekitar 8 untuk yang yang terbaik.
Testability akan memaksa Anda untuk menulis kode yang lebih baik. Anda harus menulis unit test karena Anda akan menemukan bahwa hal itu membuat Anda memikirkan kode Anda secara berbeda dan Anda akan menulis kode yang lebih baik sejak awal, terlepas dari seberapa sedikit bug yang mungkin Anda miliki sebelum menulis unit test.
sumber
Anda tidak benar-benar melanggar Hukum Demeter, tetapi masalah Anda serupa dengan itu dalam beberapa hal. Karena inti pertanyaan Anda adalah untuk menemukan sumber daya, saya sarankan Anda membaca tentang Hukum Demeter dan melihat seberapa banyak saran itu berlaku untuk situasi Anda.
sumber
Ada beberapa contoh di mana yang terbaik (dalam hal efisiensi, pemeliharaan dan kemudahan implementasi) memiliki variabel tertentu sebagai global daripada overhead yang selalu melewati segala sesuatu di sekitar (misalkan Anda memiliki 15 atau lebih variabel yang harus bertahan). Jadi masuk akal untuk menemukan bahasa pemrograman yang mendukung pelingkupan yang lebih baik (sebagai variabel statis pribadi C ++) untuk mengurangi potensi kekacauan (dari namespace dan hal-hal yang dirusak). Tentu saja ini hanya pengetahuan umum.
Tapi, pendekatan yang dinyatakan oleh OP sangat berguna jika seseorang melakukan Pemrograman Fungsional.
sumber
Tidak ada anti-pola di sini, karena penelepon tidak tahu tentang semua level di bawah ini dan tidak peduli.
Seseorang memanggil HigherLevel (params) dan mengharapkan HigherLevel untuk melakukan tugasnya. Apa yang dilakukan level yang lebih tinggi pada param bukanlah urusan para penelepon. HigherLevel menangani masalah dengan cara terbaik yang bisa dilakukan, dalam hal ini dengan meneruskan params ke level1 (params). Benar-benar oke.
Anda melihat rantai panggilan - tetapi tidak ada rantai panggilan. Ada fungsi di bagian atas yang melakukan tugasnya sebaik mungkin. Dan ada fungsi lainnya. Setiap fungsi dapat diganti kapan saja.
sumber