Bagaimana cara memudahkan pemeliharaan kode event driven?

16

Saat menggunakan komponen berbasis peristiwa saya sering merasa sakit pada fase pemeliharaan.

Karena semua kode yang dieksekusi terpecah-pecah, akan sangat sulit untuk mengetahui bagian kode apa yang akan digunakan saat runtime.

Ini dapat menyebabkan masalah halus dan sulit untuk debug ketika seseorang menambahkan beberapa penangan acara baru.

Edit dari komentar: Bahkan dengan beberapa praktik yang baik di papan tulis, seperti memiliki aplikasi bus acara luas dan penangan mendelegasikan bisnis ke bagian lain dari aplikasi, ada saat ketika kode mulai menjadi sulit dibaca karena ada banyak penangan terdaftar dari berbagai tempat (terutama benar ketika ada bus).

Kemudian diagram urutan mulai terlihat rumit, waktu yang dihabiskan untuk mencari tahu apa yang terjadi meningkat dan sesi debug menjadi berantakan (breakpoint pada manajer penangan saat iterasi pada penangan, terutama yang menyenangkan dengan penangan async dan beberapa pemfilteran di atasnya).

//////////////
Contoh

Saya memiliki layanan yang mengambil beberapa data di server. Pada klien kami memiliki komponen dasar yang memanggil layanan ini menggunakan panggilan balik. Untuk memberikan titik ekstensi kepada pengguna komponen dan untuk menghindari penggabungan antara berbagai komponen, kami meluncurkan beberapa peristiwa: satu sebelum permintaan dikirim, satu ketika jawabannya kembali dan satu lagi jika terjadi kegagalan. Kami memiliki satu set penangan dasar yang sudah didaftarkan sebelumnya yang memberikan perilaku standar komponen.

Sekarang pengguna komponen (dan kami juga pengguna komponen) dapat menambahkan beberapa penangan untuk melakukan beberapa perubahan pada perilaku (memodifikasi kueri, log, analisis data, pemfilteran data, pemijatan data, animasi mewah UI, rangkaian kueri berurutan berantai) , Masa bodo). Jadi beberapa penangan harus dieksekusi sebelum / setelah beberapa orang lain dan mereka terdaftar dari banyak titik masuk yang berbeda dalam aplikasi.

Setelah beberapa saat, dapat terjadi bahwa selusin atau lebih penangan terdaftar, dan bekerja dengan itu bisa membosankan dan berbahaya.

Desain ini muncul karena menggunakan warisan mulai berantakan total. Sistem acara digunakan pada jenis komposisi di mana Anda belum tahu apa yang akan menjadi komposit Anda.

Akhir contoh
//////////////

Jadi saya bertanya-tanya bagaimana orang lain menangani kode semacam ini. Baik saat menulis maupun membacanya.

Apakah Anda memiliki metode atau alat yang memungkinkan Anda menulis dan memelihara kode seperti itu tanpa banyak kesulitan?

Guillaume
sumber
Maksud Anda, selain refactoring keluar logika dari event handler?
Telastyn
Dokumentasikan apa yang terjadi.
@ Telastyn, saya tidak yakin untuk sepenuhnya memahami apa yang Anda maksud dengan 'selain refactoring keluar logika dari event handler'.
Guillaume
@ Torbjoern: lihat pembaruan saya.
Guillaume
3
Sepertinya Anda tidak menggunakan alat yang benar untuk pekerjaan itu? Maksud saya, jika pesanan itu penting, maka Anda seharusnya tidak menggunakan acara polos untuk bagian-bagian aplikasi tersebut. Pada dasarnya jika ada batasan urutan kejadian dalam sistem bus tipikal, ini bukan sistem bus tipikal lagi, tetapi Anda masih menggunakannya seperti itu. Yang berarti Anda harus mengatasi masalah itu dengan menambahkan beberapa logika tambahan untuk menangani pesanan. Setidaknya, jika saya memahami masalah Anda dengan benar ..
stijn

Jawaban:

7

Saya telah menemukan bahwa memproses peristiwa menggunakan setumpuk peristiwa internal (lebih khusus, antrian LIFO dengan penghapusan sewenang-wenang) sangat menyederhanakan pemrograman yang digerakkan oleh peristiwa. Ini memungkinkan Anda untuk membagi pemrosesan "peristiwa eksternal" menjadi beberapa "peristiwa internal" yang lebih kecil, dengan kondisi yang jelas di antaranya. Untuk informasi lebih lanjut, lihat jawaban saya untuk pertanyaan ini .

Di sini saya menyajikan contoh sederhana yang diselesaikan dengan pola ini.

Misalkan Anda menggunakan objek A untuk melakukan beberapa layanan, dan Anda memberinya panggilan balik untuk memberi tahu Anda ketika sudah selesai. Namun, A sedemikian rupa sehingga setelah menelepon kembali, Anda mungkin perlu melakukan beberapa pekerjaan lagi. Bahaya muncul ketika, dalam panggilan balik itu, Anda memutuskan bahwa Anda tidak membutuhkan A lagi, dan Anda menghancurkannya dengan cara apa pun. Tetapi Anda dipanggil dari A - jika A, setelah panggilan balik Anda kembali, tidak dapat dengan aman mengetahui bahwa itu dihancurkan, kerusakan dapat terjadi ketika upaya untuk melakukan pekerjaan yang tersisa.

CATATAN: Benar bahwa Anda dapat melakukan "penghancuran" dengan beberapa cara lain, seperti mengurangi refcount, tetapi itu hanya mengarah ke status perantara, dan kode tambahan dan bug dari penanganannya; lebih baik bagi A untuk berhenti bekerja sepenuhnya setelah Anda tidak membutuhkannya lagi selain melanjutkan dalam kondisi perantara.

Dalam pola saya, A hanya akan menjadwalkan pekerjaan lebih lanjut yang perlu dilakukan dengan mendorong acara internal (pekerjaan) ke dalam antrian LIFO loop acara, kemudian melanjutkan untuk memanggil callback, dan kembali ke loop acara segera . Sepotong kode ini tidak lagi berbahaya, karena A hanya mengembalikan. Sekarang, jika callback tidak menghancurkan A, pekerjaan yang didorong pada akhirnya akan dieksekusi oleh loop acara untuk melakukan pekerjaan ekstra (setelah panggilan balik dilakukan, dan semua pekerjaan yang didorong, secara rekursif). Di sisi lain, jika callback menghancurkan A, fungsi destruktor atau deinit A dapat menghapus pekerjaan yang didorong dari tumpukan peristiwa, secara implisit mencegah pelaksanaan pekerjaan yang didorong.

Ambroz Bizjak
sumber
7

Saya pikir logging yang tepat dapat membantu cukup besar. Pastikan bahwa setiap peristiwa yang dilemparkan / ditangani dicatat di suatu tempat (Anda dapat menggunakan kerangka kerja logging untuk ini). Ketika Anda debugging, Anda dapat berkonsultasi log untuk melihat tepat urutan eksekusi kode Anda ketika bug terjadi. Seringkali ini benar-benar akan membantu mempersempit penyebab masalah.

Oleksi
sumber
Ya, itu saran yang sangat membantu. Jumlah log yang dihasilkan kemudian bisa menakutkan (saya ingat memiliki waktu yang sulit untuk menemukan penyebab siklus dalam proses pengolahan bentuk yang sangat kompleks.)
Guillaume
Mungkin salah satu solusinya adalah mencatat informasi menarik ke file log terpisah. Anda juga dapat menetapkan UUID untuk setiap acara, sehingga Anda dapat melacak setiap acara dengan lebih mudah. Anda juga dapat menulis beberapa alat pelaporan yang memungkinkan Anda untuk mengekstrak informasi spesifik dari file log. (Atau sebagai alternatif, gunakan sakelar dalam kode untuk mencatat informasi yang berbeda ke file log yang berbeda).
Giorgio
3

Jadi saya bertanya-tanya bagaimana orang lain menangani kode semacam ini. Baik saat menulis maupun membacanya.

Model pemrograman berbasis peristiwa menyederhanakan pengkodean sampai batas tertentu. Ini mungkin telah berevolusi sebagai pengganti pernyataan Select (atau case) besar yang digunakan dalam bahasa yang lebih tua dan mendapatkan popularitas di lingkungan pengembangan Visual awal seperti VB 3 (Jangan mengutip saya tentang sejarah, saya tidak memeriksanya)!

Model menjadi sakit jika urutan peristiwa penting dan ketika 1 aksi bisnis dibagi di banyak peristiwa. Gaya proses ini melanggar manfaat dari pendekatan ini. Di semua biaya, cobalah untuk membuat kode tindakan dienkapsulasi dalam acara yang sesuai dan jangan meningkatkan acara dari dalam acara. Itu kemudian menjadi jauh lebih buruk daripada Spaghetti yang dihasilkan dari GoTo.

Terkadang pengembang sangat ingin memberikan fungsionalitas GUI yang membutuhkan ketergantungan peristiwa seperti itu, tetapi sebenarnya tidak ada alternatif nyata yang secara signifikan lebih sederhana.

Intinya di sini adalah bahwa teknik ini tidak buruk jika digunakan secara bijak.

Tidak mungkin
sumber
Apakah ada alternatif desain mereka untuk menghindari 'lebih buruk daripada Spaghetti'?
Guillaume
Saya tidak yakin ada cara mudah tanpa menyederhanakan perilaku GUI yang diharapkan, tetapi jika Anda mencantumkan semua interaksi pengguna Anda dengan jendela / halaman dan untuk masing-masing menentukan dengan tepat apa yang akan dilakukan program Anda, Anda dapat mulai mengelompokkan kode ke satu tempat dan mengarahkan beberapa acara untuk sekelompok subs / metode yang bertanggung jawab atas pemrosesan permintaan yang sebenarnya. Juga memisahkan kode yang hanya melayani GUI dari kode yang melakukan pemrosesan back end dapat membantu.
NoChance
Kebutuhan untuk memposting pesan ini datang dari refactoring ketika saya pindah dari neraka warisan ke sesuatu yang didasarkan pada peristiwa. Pada beberapa titik itu benar-benar lebih baik, tetapi pada beberapa yang lain itu sangat buruk ... Karena saya sudah punya masalah dengan 'peristiwa menjadi liar' di beberapa GUI, saya bertanya-tanya apa yang dapat dilakukan untuk meningkatkan pemeliharaan seperti kode iris acara.
Guillaume
Pemrograman berbasis event jauh lebih tua dari VB; itu hadir dalam GUI SunTools, dan sebelumnya saya sepertinya ingat bahwa itu dibangun ke dalam bahasa Simula.
kevin cline
@ Guillaume, saya kira Anda sudah selesai merancang layanan. Deskripsi saya di atas sebenarnya didasarkan pada acara GUI kebanyakan. Apakah Anda (benar-benar) membutuhkan jenis pemrosesan ini?
NoChance
3

Saya ingin memperbarui jawaban ini karena saya mendapatkan beberapa momen eureka sejak setelah "perataan" dan "perataan" aliran kontrol dan telah merumuskan beberapa pemikiran baru tentang masalah ini.

Efek Samping Kompleks vs. Arus Kontrol Kompleks

Apa yang saya temukan adalah bahwa otak saya dapat mentolerir efek samping yang kompleks atau aliran kontrol seperti grafik yang kompleks seperti yang biasanya Anda temukan dengan penanganan peristiwa, tetapi bukan kombinasi keduanya.

Saya dapat dengan mudah beralasan tentang kode yang menyebabkan 4 efek samping yang berbeda jika sedang diterapkan dengan aliran kontrol yang sangat sederhana, seperti forloop berurutan . Otak saya dapat mentolerir loop berurutan yang mengubah ukuran dan mereposisi elemen, menghidupkannya, menggambar ulang, dan memperbarui beberapa jenis status tambahan. Itu cukup mudah untuk dipahami.

Saya juga dapat memahami aliran kontrol yang kompleks seperti yang mungkin Anda dapatkan dengan peristiwa kaskade atau melintasi struktur data yang mirip grafik jika hanya ada efek samping yang sangat sederhana yang terjadi dalam proses di mana pesanan tidak menjadi masalah sedikit pun, seperti elemen penandaan untuk diproses secara ditangguhkan dalam loop sekuensial sederhana.

Di mana saya tersesat dan bingung dan kewalahan adalah ketika Anda memiliki aliran kontrol yang kompleks yang menyebabkan efek samping yang kompleks. Dalam hal ini aliran kontrol yang kompleks membuat sulit untuk memprediksi terlebih dahulu di mana Anda akan berakhir sementara efek samping yang kompleks membuatnya sulit untuk memprediksi dengan tepat apa yang akan terjadi sebagai hasil dan dalam urutan apa. Jadi kombinasi dari dua hal ini yang membuatnya sangat tidak nyaman di mana, bahkan jika kodenya berfungsi dengan baik sekarang, sangat menakutkan untuk mengubahnya tanpa takut menyebabkan efek samping yang tidak diinginkan.

Aliran kontrol yang kompleks cenderung mempersulit kapan / di mana hal-hal akan terjadi. Itu hanya menjadi benar-benar menyebabkan sakit kepala jika aliran kontrol yang kompleks ini memicu kombinasi kompleks dari efek samping di mana penting untuk memahami kapan / di mana hal-hal terjadi, seperti efek samping yang memiliki semacam ketergantungan urutan di mana satu hal harus terjadi sebelum yang berikutnya.

Sederhanakan Aliran Kontrol atau Efek Samping

Jadi apa yang Anda lakukan ketika Anda menghadapi skenario di atas yang sangat sulit untuk dipahami? Strateginya adalah menyederhanakan aliran kontrol atau efek samping.

Strategi yang dapat diterapkan secara luas untuk menyederhanakan efek samping adalah mendukung pemrosesan yang ditangguhkan. Menggunakan contoh peristiwa GUI sebagai contoh, godaan normal mungkin untuk menerapkan kembali tata letak GUI, memposisikan ulang dan mengubah ukuran widget anak, memicu kaskade aplikasi tata letak lainnya dan mengubah ukuran dan memposisikan ulang hierarki, bersama dengan mengecat ulang kontrol, mungkin memicu beberapa kontrol acara unik untuk widget yang memiliki perilaku mengubah ukuran kustom yang memicu lebih banyak acara yang mengarah ke siapa-tahu-di mana, dll. Alih-alih mencoba melakukan ini semua dalam satu pass atau dengan melakukan spam pada antrian acara, salah satu solusi yang mungkin adalah menurunkan hierarki widget dan tandai widget apa yang perlu tata letak mereka diperbarui. Kemudian di pass kemudian, ditangguhkan yang memiliki aliran kontrol berurutan langsung, menerapkan kembali semua tata letak untuk widget yang membutuhkannya. Anda kemudian dapat menandai widget apa yang perlu dicat ulang. Sekali lagi dalam kelanjutan penangguhan berurutan dengan aliran kontrol yang mudah, cat ulang widget yang ditandai sebagai perlu digambar ulang.

Ini memiliki efek penyederhanaan aliran kontrol dan efek samping karena aliran kontrol menjadi disederhanakan karena itu bukan cascading peristiwa rekursif selama grafik traversal. Sebaliknya kaskade terjadi dalam loop sekuensial ditangguhkan yang kemudian dapat ditangani dalam loop sekuensial ditangguhkan lainnya. Efek samping menjadi sederhana di mana ia diperhitungkan karena, selama aliran kontrol seperti grafik yang lebih kompleks, yang kami lakukan hanyalah menandai apa yang perlu diproses oleh loop berurutan yang ditangguhkan yang memicu efek samping yang lebih kompleks.

Ini memang datang dengan beberapa pemrosesan overhead tetapi mungkin kemudian membuka pintu untuk, katakanlah, melakukan ini ditangguhkan secara paralel, berpotensi memungkinkan Anda untuk mendapatkan solusi yang lebih efisien daripada yang Anda mulai jika kinerja menjadi perhatian. Secara umum kinerja seharusnya tidak terlalu menjadi perhatian dalam banyak kasus. Yang paling penting, walaupun ini mungkin tampak seperti perbedaan yang diperdebatkan, saya merasa jauh lebih mudah untuk memikirkannya. Itu membuatnya jauh lebih mudah untuk memprediksi apa yang terjadi dan kapan, dan saya tidak bisa melebih-lebihkan nilai yang dapat dimiliki untuk dapat lebih mudah memahami apa yang terjadi.


sumber
2

Apa yang berhasil bagi saya adalah membuat setiap acara berdiri sendiri, tanpa merujuk ke acara lain. Jika mereka datang secara tidak sinkron, Anda tidak punya urutan, jadi cobalah mencari tahu apa yang terjadi dalam urutan apa yang tidak ada gunanya, selain tidak mungkin.

Yang akhirnya Anda lakukan adalah sekelompok struktur data yang dibaca dan dimodifikasi serta dibuat dan dihapus oleh selusin utas tanpa urutan tertentu. Anda harus melakukan pemrograman multi-thread yang sangat baik, yang tidak mudah. Anda juga harus berpikir multi-utas, seperti pada "Dengan peristiwa ini, saya akan melihat data yang saya miliki pada saat tertentu, tanpa memperhatikan apa itu mikrodetik sebelumnya, tanpa memperhatikan apa yang baru saja mengubahnya , dan tanpa memperhatikan apa yang 100 thread menunggu saya untuk melepaskan kunci akan lakukan untuk itu. Kemudian saya akan membuat perubahan berdasarkan ini bahkan dan apa yang saya lihat. Kemudian saya selesai. "

Satu hal yang saya lakukan adalah memindai Koleksi tertentu dan memastikan bahwa referensi dan koleksi itu sendiri (jika bukan threadsafe) dikunci dengan benar dan disinkronkan dengan benar dengan data lain. Ketika lebih banyak peristiwa ditambahkan, tugas ini tumbuh. Tapi kalau aku sedang melacak hubungan antara peristiwa, bahwa tugas akan tumbuh jauh lebih cepat. Plus kadang-kadang banyak penguncian dapat diisolasi dengan metode sendiri, sebenarnya membuat kode lebih sederhana.

Memperlakukan setiap utas sebagai entitas yang sepenuhnya independen sulit (karena multi-threading hard-core) tetapi dapat dilakukan. "Dapat diskalakan" mungkin kata yang saya cari. Dua kali lebih banyak acara hanya membutuhkan dua kali lebih banyak pekerjaan, dan mungkin hanya 1,5 kali lebih banyak. Mencoba mengoordinasikan lebih banyak acara yang tidak sinkron akan mengubur Anda dengan cepat.

RalphChapin
sumber
Saya telah memperbarui pertanyaan saya. Anda sepenuhnya benar tentang urutan dan hal-hal async. Bagaimana Anda menangani kasus di mana acara X harus dijalankan setelah semua acara lainnya diproses? Sepertinya saya harus membuat manajer Handler yang lebih kompleks, tetapi saya juga ingin menghindari untuk mengekspos terlalu banyak kompleksitas kepada pengguna.
Guillaume
Di set data Anda, miliki sakelar dan beberapa bidang yang ditetapkan saat peristiwa X dibaca. Ketika semua yang lain diproses, Anda memeriksa sakelar dan tahu Anda harus menangani X dan Anda memiliki data. Switch dan data harus berdiri sendiri, sebenarnya. Ketika diatur, Anda harus berpikir, "Saya harus melakukan pekerjaan ini," bukan "Saya harus menyerahkan X." Masalah selanjutnya: bagaimana Anda tahu acara itu selesai? dan bagaimana jika Anda mendapatkan 2 acara X atau lebih? Kasus terburuk, Anda dapat menjalankan utas perawatan perulangan yang memeriksa situasi dan dapat bertindak atas inisiatif sendiri. (Tidak ada input selama 3 detik? Sakelar X diatur? Kemudian jalankan kode shutdown.
RalphChapin
2

Kedengarannya seperti Anda mencari Mesin Negara & Kegiatan yang Didorong Aktivitas .

Namun, Anda mungkin juga ingin melihat Contoh Alur Kerja Mesin Markup Negara .

Di sini Anda adalah gambaran singkat implementasi mesin negara. Sebuah mesin negara alur kerja terdiri dari negara. Setiap negara bagian terdiri dari satu atau lebih penangan acara. Setiap pengendali acara harus berisi penundaan atau IEventActivity sebagai aktivitas pertama. Setiap pengendali acara juga dapat berisi aktivitas SetStateActivity yang digunakan untuk transisi dari satu kondisi ke kondisi lainnya.

Setiap alur kerja mesin keadaan memiliki dua properti: InitialStateName dan CompletedStateName. Ketika instance dari alur kerja mesin negara dibuat, itu dimasukkan ke properti InitialStateName. Ketika mesin negara mencapai properti CompletedStateName, itu menyelesaikan eksekusi.

Yusubov
sumber
2
Sementara ini secara teoritis dapat menjawab pertanyaan, akan lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini, dan menyediakan tautan untuk referensi.
Thomas Owens
Tetapi jika Anda memiliki lusinan penangan yang terhubung ke setiap negara bagian, Anda tidak akan begitu baik ketika mencoba memahami apa yang sedang terjadi ... Dan setiap komponen berbasis peristiwa tidak dapat digambarkan sebagai mesin negara.
Guillaume
1

Kode yang digerakkan oleh acara bukan masalah sebenarnya. Sebenarnya saya tidak punya masalah mengikuti logika dalam kode yang digerakkan secara rata-rata, di mana call-back didefinisikan secara eksplisit atau in-line call-back digunakan. Misalnya panggilan gaya generator di Tornado sangat mudah diikuti.

Apa yang sebenarnya sulit di-debug adalah panggilan fungsi yang dibuat secara dinamis. Pola (anti?) Yang saya sebut Pabrik Panggilan-Kembali dari Neraka. Namun, fungsi pabrik semacam ini sama sulitnya untuk di-debug dalam aliran tradisional.

vartec
sumber