Использование #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
Имейте в виду, что некоторые IDE ничего не оценивают в #ifdefблоках, если блок выключен. Мы столкнулись со случаями, когда код может легко устареть и не скомпилироваться, если вы обычно не строите все пути.
Берин Лорич
Посмотрите на этот ответ, который я дал на другой вопрос. В нем изложены некоторые способы сделать много #ifdefsменее громоздким.
user1118321

Ответы:

9

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

Карл Билефельдт
источник
gitособенно искусен в подобных вещах. Может быть, не так много svn, hgили другие, но это еще можно сделать.
Твальберг
Это была моя первоначальная мысль тоже. "Хотите возиться с чем-то другим?" git branch!
Уэс
42

Когда вы держите молоток, все выглядит как гвоздь. Это заманчиво, когда вы знаете, как #ifdefработает его использование в качестве своего рода средства для получения пользовательского поведения в вашей программе. Я знаю, потому что я сделал ту же ошибку.

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

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

Я испытал искушение и сдался. Я написал #ifdefразделы в своем коде, чтобы различать различные варианты поведения. Не заблуждайтесь, сначала это не было чем-то сверх Внесены очень незначительные изменения в поведение, что позволило мне распространять версии программы среди наших клиентов, и мне не нужно иметь более одной версии базы кода.

Со временем это все равно стало адским обслуживанием, потому что программа больше не велась единообразно. Если я хотел протестировать версию программы, я должен был обязательно знать, кто был клиентом. Код, хотя я и пытался сократить его до одного или двух заголовочных файлов, был очень загроможден, и благодаря подходу быстрого исправления #ifdefтакие решения распространялись по всей программе, как злокачественный рак.

С тех пор я усвоил урок, и вы тоже должны. Используйте его, если вам абсолютно необходимо, и используйте его строго для изменений платформы. Лучший способ приблизиться к различиям в поведении между программами (и, следовательно, клиентами) - это изменить только конфигурацию, загруженную при запуске. Программа остается последовательной, и ее становится легче читать и отлаживать.

Нил
источник
Что касается отладочных версий, таких как «если отладка, определите переменную x ...», кажется, что это может быть полезно для таких вещей, как ведение журнала, но тогда это также может полностью изменить работу вашей программы, когда включена отладка, а когда нет. ,
WHN
8
@SNB Я думал об этом. Я все еще предпочитаю изменять конфигурационный файл и делать его более подробным. В противном случае что-то пойдет не так с производственной программой, и вы не сможете отладить ее без полной замены исполняемого файла. Даже в идеальных ситуациях это менее чем желательно. ;)
Нил
О, да, было бы гораздо лучше, если бы не пришлось перекомпилировать для отладки, я не думал об этом!
WHN
9
Для экстремального примера того, что вы описываете, посмотрите на 2-й абзац подзаголовка «Проблема ремонтопригодности» в этой статье о том, почему MS несколько лет назад достигла точки, когда им пришлось переписывать большую часть своей среды выполнения C с нуля. , blogs.msdn.microsoft.com/vcblog/2014/06/10/…
Дэн Нили
2
@snb Большинство библиотек журналов предполагают механизм уровня журналирования. Если вы хотите, чтобы определенная информация регистрировалась во время отладки, вы регистрируете ее с низким уровнем ведения журнала (обычно «Debug» или «Verbose»). Затем приложение имеет параметр конфигурации, который сообщает ему, какой уровень регистрировать. Так что ответом остается конфигурация для этой проблемы. Это также имеет огромное преимущество - возможность включать этот низкий уровень ведения журнала в клиентской среде .
jpmc26
21

Временно нет ничего плохого в том, что вы делаете (скажем, перед регистрацией): это отличный способ протестировать различные комбинации техник или игнорировать часть кода (хотя это говорит о проблемах само по себе).

Но предупреждаю: не держите ветки #ifdef, это немного более расстраивает, чем тратит мое время на чтение одной и той же вещи, реализованной четырьмя различными способами, только чтобы выяснить, какой из них я должен читать .

Чтение #ifdef требует усилий, так как вы должны помнить, чтобы пропустить его! Не усложняй, чем должен быть.

Используйте #ifdefs как можно реже. Как правило, есть способы сделать это в среде разработки для постоянных различий, таких как сборки Debug / Release или для разных архитектур.

Я написал функции библиотеки, которые зависели от включенных версий библиотеки, которые требовали разбиения #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();
}

Все в зависимости от того, какие подходы определены или нет. Что это делает, совершенно неясно на первый взгляд.

Питер Б
источник