Menghilangkan "destruktor" di C mengambil YAGNI terlalu jauh?

9

Saya sedang mengerjakan aplikasi embedded media dalam C menggunakan teknik mirip OO. "Kelas" saya adalah modul .h / .c menggunakan struct data dan struct pointer fungsi untuk meniru enkapsulasi, polimorfisme, dan injeksi dependensi.

Sekarang, orang akan mengharapkan myModule_create(void)fungsi untuk datang dengan myModule_destroy(pointer)rekan. Tetapi proyek yang tertanam, sumber daya yang dipakai secara realistis tidak boleh dirilis.

Maksud saya, jika saya memiliki 4 port serial UART dan saya membuat 4 instance UART dengan pin dan pengaturan yang diperlukan, sama sekali tidak ada alasan untuk ingin menghancurkan UART # 2 di beberapa titik selama runtime.

Jadi mengikuti prinsip YAGNI (Anda tidak akan membutuhkannya), haruskah saya menghilangkan destruktor? Ini tampak sangat aneh bagi saya, tetapi saya tidak bisa memikirkan manfaatnya bagi mereka; sumber daya dibebaskan ketika perangkat mati.

Asics
sumber
4
Jika Anda tidak memberikan cara untuk membuang objek, Anda menyampaikan pesan yang jelas bahwa mereka telah menciptakan seumur hidup "tak terbatas". Jika ini masuk akal untuk aplikasi Anda, saya katakan: lakukan.
glampert
4
Jika Anda akan melangkah terlalu jauh dalam menyambungkan jenisnya dengan case use khusus Anda, mengapa bahkan memiliki myModule_create(void)fungsi? Anda bisa membuat hard-code contoh spesifik yang Anda harapkan untuk digunakan ke antarmuka yang Anda tampilkan.
Doval
@Doval saya memikirkannya. Saya magang menggunakan bagian dan bit kode dari penyelia saya jadi saya mencoba menyulap dengan "melakukannya dengan benar", bereksperimen dengan gaya OO dalam C untuk pengalaman, dan konsisten dengan standar perusahaan.
Asics
2
@ glemert kuku itu; Saya akan menambahkan bahwa Anda harus membuat masa pakai tak terbatas yang diharapkan bersih dalam dokumentasi buat fungsi.
Blrfl

Jawaban:

11

Jika Anda tidak memberikan cara untuk membuang objek, Anda menyampaikan pesan yang jelas bahwa mereka telah menciptakan seumur hidup "tak terbatas". Jika ini masuk akal untuk aplikasi Anda, saya katakan: lakukan.

Glampert benar; tidak perlu untuk destruktor di sini. Mereka hanya akan membuat kode mengasapi dan jebakan bagi pengguna (menggunakan objek setelah destruktornya disebut perilaku tidak terdefinisi).

Namun, Anda harus yakin benar-benar tidak perlu membuang benda-benda itu. Misalnya, apakah Anda perlu memiliki objek untuk UART yang saat ini tidak digunakan?

Demi
sumber
3

Cara termudah yang saya temukan untuk mendeteksi kebocoran memori adalah keluar dari aplikasi Anda dengan bersih. Banyak kompiler / lingkungan menyediakan cara untuk memeriksa memori yang masih dialokasikan ketika aplikasi Anda keluar. Jika salah satu tidak disediakan, biasanya ada cara untuk menambahkan beberapa kode tepat sebelum keluar yang dapat menemukannya.

Jadi, saya pasti akan menyediakan konstruktor, destruktor dan logika shutdown bahkan dalam sistem embedded yang "secara teoritis" tidak boleh keluar untuk kemudahan deteksi kebocoran memori saja. Pada kenyataannya, deteksi kebocoran memori bahkan lebih penting jika kode aplikasi tidak boleh keluar.

Celup
sumber
Perhatikan bahwa ini tidak berlaku jika satu-satunya waktu Anda melakukan alokasi adalah saat startup, yang merupakan pola yang saya pertimbangkan secara serius dalam perangkat kendala memori.
CodesInChaos
@ Kode: Tidak ada alasan untuk membatasi diri Anda. Meskipun Anda mungkin menghasilkan desain hebat yang mengalokasikan memori pada saat startup, ketika orang-orang setelah Anda datang yang tidak mengetahui rahasia skema besar ini atau gagal melihat pentingnya, mereka akan mengalokasikan memori pada terbang dan pergi desain Anda. Lakukan dengan benar dan alokasikan / hapuskan alokasi dan verifikasi bahwa apa yang Anda implementasikan benar-benar berfungsi. Jika Anda benar-benar memiliki perangkat yang dibatasi oleh memori, maka yang biasanya dilakukan adalah mengganti operator / malloc baru dan mempertahankan blok alokasi.
Dunk
3

Dalam perkembangan saya, yang banyak menggunakan tipe data buram untuk mendorong pendekatan seperti OO, saya juga bergulat dengan pertanyaan ini. Pada awalnya, saya jelas berada di kubu untuk menghilangkan destruktor dari perspektif YAGNI, serta perspektif "kode mati" MISRA. (Aku punya banyak ruang sumber daya, itu bukan pertimbangan.)

Namun ... kurangnya destruktor dapat membuat pengujian lebih sulit, seperti pada pengujian unit / integrasi otomatis. Secara konvensional, setiap tes harus mendukung pengaturan / teardown sehingga objek dapat dibuat, dimanipulasi, lalu dihancurkan. Mereka dihancurkan untuk memastikan titik awal yang bersih dan tidak ternoda untuk pengujian selanjutnya. Untuk melakukan ini, kelas membutuhkan destruktor.

Oleh karena itu, dalam pengalaman saya, "tidak" di YAGNI ternyata menjadi "adalah" dan saya akhirnya menciptakan destruktor untuk setiap kelas apakah saya pikir saya akan membutuhkannya atau tidak. Bahkan jika saya melewatkan pengujian, setidaknya ada destructor yang dirancang dengan benar ada untuk jorok miskin yang mengikutiku akan memilikinya. (Dan, untuk nilai yang jauh lebih rendah, itu membuat kode lebih dapat digunakan kembali sehingga dapat digunakan di lingkungan di mana ia akan dihancurkan.)

Sementara itu alamat YAGNI, itu tidak membahas kode mati. Untuk itu, saya menemukan bahwa kompilasi makro kondisional seperti #define BUILD_FOR_TESTING memungkinkan destructor dihilangkan dari build produksi akhir.

Melakukannya dengan cara ini: Anda memiliki destructor untuk pengujian / penggunaan kembali di masa depan, dan Anda memenuhi tujuan desain YAGNI dan aturan "no dead code".

Greg Willits
sumber
Hati-hati dengan # ifdefing kode tes / prod Anda. Ini cukup aman ketika diterapkan ke seluruh fungsi, seperti yang Anda jelaskan, karena jika fungsi tersebut sebenarnya diperlukan, kompilasi akan gagal. Namun, menggunakan #ifdef inline di dalam suatu fungsi jauh lebih berisiko karena Anda sekarang menguji codepath yang berbeda dari apa yang sedang berjalan di prod.
Kevin
0

Anda dapat memiliki destruktor no-op, sesuatu seperti

  void noop_destructor(void*) {};

kemudian atur destructor yang Uartmungkin digunakan

  #define Uart_destructor noop_destructor

(tambahkan pemeran yang cocok jika diperlukan)

Jangan lupa mendokumentasikan. Mungkin Anda menginginkannya

 #define Uart_destructor abort

Atau, kasus khusus dalam kode umum memanggil destruktor kasus ketika fungsi pointer destruktor adalah NULLuntuk menghindari memanggilnya.

Basile Starynkevitch
sumber