Menggunakan #ifdef untuk beralih di antara berbagai jenis perilaku selama pengembangan


28

Apakah ini praktik yang baik untuk menggunakan #ifdef selama pengembangan untuk beralih di antara berbagai jenis perilaku? Sebagai contoh, saya ingin mengubah perilaku kode yang ada, saya punya beberapa ide bagaimana mengubah perilaku dan perlu beralih di antara implementasi yang berbeda untuk menguji dan membandingkan berbagai pendekatan yang berbeda. Biasanya perubahan dalam kode sangat kompleks dan berpengaruh pada metode yang berbeda dalam file yang berbeda.

Saya biasanya memperkenalkan beberapa pengidentifikasi dan melakukan sesuatu seperti itu

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

Ini memungkinkan untuk beralih di antara berbagai pendekatan dengan cepat dan melakukan semuanya hanya dalam satu salinan kode sumber. Apakah ini pendekatan yang baik untuk pembangunan atau apakah ada praktik yang lebih baik?



2
Karena Anda berbicara tentang pengembangan, saya yakin Anda harus melakukan apa pun yang menurut Anda mudah dilakukan untuk beralih dan bereksperimen dengan implementasi yang berbeda. Ini lebih seperti preferensi pribadi selama pengembangan, bukan praktik terbaik untuk memecahkan masalah tertentu.
Emerson Cardoso

1
Saya akan merekomendasikan menggunakan pola strategi atau polimorfisme yang baik, karena ini membantu menjaga titik plug-in tunggal untuk perilaku switchable.
PMF

4
Ingatlah bahwa beberapa IDE tidak mengevaluasi apa pun dalam #ifdefblok jika blok dimatikan. Kami mengalami kasus di mana kode dapat dengan mudah menjadi basi dan tidak dapat dikompilasi jika Anda tidak secara rutin membangun semua jalur.
Berin Loritsch

Lihat jawaban ini yang saya berikan pada pertanyaan lain. Ini menjabarkan beberapa cara untuk membuat banyak yang #ifdefskurang rumit.
user1118321

Jawaban:


9

Saya lebih suka menggunakan cabang kontrol versi untuk use case ini. Itu memungkinkan Anda untuk berbeda di antara implementasi, mempertahankan sejarah yang terpisah untuk masing-masing, dan ketika Anda telah membuat keputusan dan perlu menghapus salah satu versi, Anda hanya membuang cabang itu alih-alih melalui pengeditan rawan kesalahan.


gitsangat mahir dalam hal semacam ini. Mungkin tidak begitu banyak dengan svn, hgatau yang lain, tetapi masih bisa dilakukan.
twalberg

Itu juga pemikiran awal saya. "Mau dipusingkan dengan sesuatu yang berbeda?" git branch!
Wes Toleman

42

Saat Anda memegang palu, semuanya terlihat seperti paku. Sangat menggoda setelah Anda tahu cara #ifdefmenggunakannya sebagai semacam cara untuk mendapatkan perilaku khusus dalam program Anda. Saya tahu karena saya melakukan kesalahan yang sama.

Saya mewarisi program lama yang ditulis dalam MFC C ++ yang sudah digunakan #ifdefuntuk mendefinisikan nilai platform-spesifik. Ini berarti saya dapat mengkompilasi program saya untuk digunakan pada platform 32-bit atau platform 64-bit hanya dengan mendefinisikan (atau dalam beberapa kasus tidak mendefinisikan) nilai makro tertentu.

Masalahnya kemudian muncul bahwa saya perlu menulis perilaku khusus untuk klien. Saya bisa membuat cabang dan membuat basis kode terpisah untuk klien, tetapi itu akan membuat neraka pemeliharaan. Saya bisa juga menetapkan nilai konfigurasi untuk dibaca oleh program saat startup dan menggunakan nilai-nilai ini untuk menentukan perilaku, tetapi saya kemudian harus membuat pengaturan khusus untuk menambahkan nilai konfigurasi yang tepat ke file konfigurasi untuk setiap klien.

Saya tergoda, dan saya menyerah. Saya menulis #ifdefbagian-bagian dalam kode saya untuk membedakan berbagai perilaku. Jangan salah, awalnya tidak ada yang berlebihan. Perubahan perilaku yang sangat kecil dibuat yang memungkinkan saya untuk mendistribusikan kembali versi program kepada klien kami dan saya tidak perlu memiliki lebih dari satu versi basis kode.

Seiring waktu ini menjadi perawatan neraka pula karena program tidak lagi berperilaku secara konsisten di seluruh papan. Jika saya ingin menguji versi program, saya harus tahu siapa kliennya. Kode, meskipun saya mencoba menguranginya menjadi satu atau dua file header, sangat berantakan dan pendekatan perbaikan cepat yang #ifdefdisediakan berarti solusi tersebut menyebar ke seluruh program seperti kanker ganas.

Sejak saya telah belajar pelajaran saya, dan Anda juga harus. Gunakan jika Anda benar-benar harus, dan gunakan secara ketat untuk perubahan platform. Cara terbaik untuk mendekati perbedaan perilaku antara program (dan karena itu klien) adalah mengubah hanya konfigurasi yang dimuat saat startup. Program ini tetap konsisten dan keduanya menjadi lebih mudah dibaca serta untuk debug.


bagaimana dengan versi debug, seperti "jika debug, tentukan variabel x ..." ini sepertinya bermanfaat untuk hal-hal seperti pencatatan, tetapi kemudian juga dapat benar-benar mengubah cara kerja program Anda ketika debug diaktifkan dan ketika tidak .
whn

8
@ LNB Saya memikirkan hal itu. Saya masih lebih suka bisa mengubah file konfigurasi dan membuatnya log dengan lebih detail. Kalau tidak, ada sesuatu yang salah dengan program dalam produksi, dan Anda tidak memiliki cara men-debug itu tanpa sepenuhnya mengganti yang dapat dieksekusi. Bahkan dalam situasi yang ideal, ini kurang dari yang diinginkan. ;)
Neil

Oh ya, itu akan jauh lebih ideal untuk tidak perlu mengkompilasi ulang ke debug, saya tidak memikirkannya!
whn

9
Untuk contoh ekstrem dari apa yang Anda gambarkan, lihat paragraf ke-2 di bawah subjudul "Masalah Pemeliharaan" dalam artikel ini tentang mengapa MS mencapai titik di mana mereka harus menulis ulang sebagian besar runtime C mereka dari awal beberapa tahun yang lalu. . blogs.msdn.microsoft.com/vcblog/2014/06/10/...
Dan Neely

2
@snb Sebagian besar pustaka logging mengandaikan mekanisme level logging. Jika Anda ingin info tertentu dicatat selama debugging, Anda mencatatnya dengan tingkat logging rendah (biasanya "Debug" atau "Verbose"). Kemudian aplikasi memiliki parameter konfigurasi yang memberitahukan level apa yang akan dicatat. Jadi jawabannya masih konfigurasi untuk masalah ini. Ini juga memiliki manfaat yang sangat besar untuk dapat mengaktifkan tingkat penebangan rendah ini di lingkungan klien .
jpmc26

21

Untuk sementara tidak ada yang salah dengan apa yang Anda lakukan (katakan, sebelum check-in): ini adalah cara yang bagus untuk menguji berbagai kombinasi teknik, atau mengabaikan bagian kode (meskipun itu berbicara tentang masalah di dalam dan dari dirinya sendiri).

Tapi satu kata peringatan: jangan biarkan cabang #ifdef ada sedikit lebih membuat frustrasi daripada membuang-buang waktu saya membaca hal yang sama menerapkan empat cara yang berbeda, hanya untuk mencari tahu mana yang harus saya baca .

Membaca sebuah #ifdef membutuhkan usaha karena Anda harus benar-benar ingat untuk melewatinya! Jangan membuatnya lebih sulit dari yang seharusnya.

Gunakan #ifdefs semudah mungkin. Pada umumnya ada cara-cara yang dapat Anda lakukan ini dalam lingkungan pengembangan Anda untuk perbedaan permanen , seperti Debug / Rilis membangun, atau untuk arsitektur yang berbeda.

Saya telah menulis fitur pustaka yang bergantung pada versi pustaka yang disertakan, yang membutuhkan #ifdef splits. Jadi kadang-kadang itu mungkin satu-satunya cara, atau yang termudah, tetapi meskipun begitu Anda harus kesal tentang menjaga mereka.


1

Menggunakan # ifdefs seperti itu membuat kode sangat sulit dibaca.

Jadi, tidak, jangan gunakan #ifdefs seperti itu.

Mungkin ada banyak argumen mengapa tidak menggunakan ifdefs, bagi saya ini sudah cukup.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

Dapat melakukan banyak hal yang dapat dilakukannya:

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

Semua tergantung pada pendekatan apa yang didefinisikan atau tidak. Apa yang dilakukannya sama sekali tidak jelas pada tampilan pertama.

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.