Використання #ifdef для переключення між різними типами поведінки під час розвитку


28

Чи є хорошою практикою використовувати #ifdef під час розробки для переключення між різними типами поведінки? Наприклад, я хочу змінити поведінку існуючого коду, у мене є кілька ідей, як змінити поведінку, і необхідно переключитися між різними реалізаціями, щоб перевірити і порівняти різні підходи. Зазвичай зміни коду є складними і впливають на різні методи в різних файлах.

Я зазвичай ввожу кілька ідентифікаторів і роблю щось подібне

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;
}

Це дозволяє швидко перемикатися між різними підходами і робити все лише в одній копії вихідного коду. Це хороший підхід для розвитку чи є краща практика?



2
Оскільки ви говорите про розробку, я вважаю, що ви повинні робити все, що вам зручно, для переключення та експерименту з різними реалізаціями. Це більше схоже на особисті переваги під час розвитку, а не на кращу практику вирішення конкретної проблеми.
Емерсон Кардосо

1
Я рекомендую використовувати схему стратегії або хороший поліморфізм, оскільки це допомагає зберігати єдину точку підключення для поведінки, що перемикається.
pmf

4
Майте на увазі, що деякі ІДЕ нічого не оцінюють у #ifdefблоках, якщо блок вимкнено. Ми зіткнулися з випадками, коли код може легко стати застарілим, а не скластись, якщо ви не будуєте звичайно всі шляхи.
Берін Лорич

Подивіться на цю відповідь, яку я дав на інше запитання. Він викладає деякі способи зробити безліч #ifdefsгроміздких.
користувач1118321

Відповіді:


9

Я вважаю за краще використовувати гілки управління версіями для цього випадку використання. Це дозволяє вам відрізнятися між реалізаціями, підтримувати окрему історію для кожної, і коли ви прийняли рішення і вам потрібно видалити одну з версій, ви просто відкинете цю гілку, замість того, щоб пройти редагування, схильне до помилок.


gitособливо спритний у подібних речах. Може бути , не так багато і у випадку з svn, hgабо інші, але він все ще може бути зроблено.
twalberg

Це теж була моя початкова думка. "Хочете возитися з чимось іншим?" git branch!
Уес Толеман

42

Коли ти тримаєш молоток, все виглядає як цвях. Як тільки ви дізнаєтесь, як #ifdefпрацює, використовувати це як своєрідний засіб для отримання власної поведінки у вашій програмі стає заманливо . Я знаю, бо зробив ту саму помилку.

Я успадкував успадковану програму, написану на MFC C ++, яка вже використовувалась #ifdefдля визначення значень для платформи. Це означало, що я можу скласти свою програму для використання на 32-бітній платформі або 64-бітній платформі, просто визначивши (або в деяких випадках не визначаючи) конкретні макро значення.

Тоді виникла проблема в тому, що мені потрібно писати власну поведінку для клієнта. Я міг би створити філію і створити окрему базу коду для клієнта, але це призвело б до пекла технічного обслуговування. Я міг би також визначити значення конфігурації, які слід прочитати програмою при запуску, і використовувати ці значення для визначення поведінки, але мені доведеться тоді створити власні налаштування, щоб додати належні значення конфігурації до файлів конфігурації для кожного клієнта.

Я спокусився, і я поступився. Я написав #ifdefрозділи в своєму коді, щоб розрізнити різні поведінки. Не помиляйтесь, спочатку це було нічого над вершиною. Були внесені дуже незначні зміни в поведінці, які дозволили мені розповсюджувати версії програми нашим клієнтам, і мені не потрібно мати більше однієї версії бази коду.

З часом це все одно стало пеклам технічного обслуговування, оскільки програма більше не вела себе послідовно. Якщо я хотів протестувати версію програми, я повинен був обов’язково знати, хто такий клієнт. Код, хоча я намагався зменшити його до одного або двох файлів заголовків, був дуже захаращений, і підхід для швидкого виправлення, що #ifdefпередбачав, означав, що такі рішення поширюються по всій програмі, як злоякісний рак.

З тих пір я засвоїв свій урок, і ти повинен також. Використовуйте його, якщо ви обов'язково, і використовуйте його строго для змін платформи. Найкращий спосіб підійти до відмінностей у поведінці між програмами (а отже, і клієнтами) - це змінити лише конфігурацію, завантажену при запуску. Програма залишається послідовною, і вона стає легшою для читання, а також налагодження.


що стосується версій налагодження, як-от "якщо налагодження, визначте змінну x ...", схоже, це може бути корисним для таких речей, як реєстрація, але тоді це також може повністю змінити, як працює ваша програма, коли налагоджена налагодження і коли її немає .
whn

8
@snb Я думав про це. Я все ще віддаю перевагу можливості змінити файл конфігурації та зробити його журналом з більш детальною інформацією. Інакше щось не вдається з програмою у виробництві, і у вас немає можливості налагодити її, не повністю замінивши виконуваний файл. Навіть в ідеальних ситуаціях це менш ніж бажано. ;)
Ніл

О так, було б набагато ідеальніше не потрібно перекомпілювати на налагодження, я не думав про це!
whn

9
Для крайнього прикладу того, що ви описуєте, подивіться у другому параграфі під заголовком "Проблема ремонтопридатності" у цій статті про те, чому МС досягли того, коли їм довелося переписати більшість своїх циклів виконання C з нуля кілька років тому . blogs.msdn.microsoft.com/vcblog/2014/06/10/…
Dan Neely

2
@snb Більшість бібліотек журналів передбачають механізм рівня журналу. Якщо ви хочете, щоб певна інформація була зареєстрована під час налагодження, ви записуєте її з низьким рівнем реєстрації (зазвичай "Налагодження" або "Вербоза"). Тоді програма має параметр конфігурації, який повідомляє, на якому рівні потрібно ввійти. Отже, відповідь - це конфігурація цієї проблеми. Це також має величезну користь від можливості включити цей низький рівень реєстрації в клієнтському середовищі .
jpmc26

21

Тимчасово немає нічого поганого в тому, що ви робите (скажімо, перед реєстрацією): це прекрасний спосіб перевірити різні комбінації прийомів або проігнорувати розділ коду (хоча це говорить про проблеми самі по собі).

Але слово попередження: не тримайте гілок #ifdef, там трохи більше засмучує, ніж витрачати час на читання тієї ж речі, реалізованої чотирма різними способами, лише щоб з'ясувати, який я повинен читати .

Читання над #ifdef вимагає зусиль, оскільки вам потрібно насправді запам'ятати, щоб пропустити його! Не робіть це складніше, ніж це має бути.

Використовуйте #ifdefs так само економно, як тільки можете. Як правило, ви можете це зробити у своєму середовищі розробки для постійних відмінностей, наприклад, налагодження / випуск версій або для різних архітектур.

Я написав функції бібліотеки, які залежали від включених версій бібліотеки, для яких потрібні #ifdef розбиття. Тож часом це може бути єдиний спосіб, або найпростіший, але навіть тоді ви повинні засмучуватися щодо їх збереження.


1

Використання #ifdefs подібного робить код дуже важким для читання.

Отже, ні, не використовуйте #ifdefs так.

Можливо, є безліч аргументів, чому б не використовувати ifdefs, для мене цього достатньо.

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

Можна зробити багато речей, які він може зробити:

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

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

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

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

Все залежно від того, які підходи визначені чи ні. Що це робить, абсолютно не зрозуміло на перший погляд.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.