Требуется цитата: использование препроцессора - плохая практика - PullRequest
16 голосов
/ 23 января 2009

Я считаю, что использование директив препроцессора, таких как #if UsingNetwork, является плохой практикой ОО - другие коллеги этого не делают. Я думаю, что при использовании контейнера IoC (например, Spring) компоненты могут быть легко настроены, если запрограммированы соответствующим образом. В этом контексте либо контейнер * IoC может установить свойство IsUsingNetwork, либо, если реализация «с использованием сети» ведет себя по-другому, должна быть реализована и внедрена другая реализация этого интерфейса (например: IService, ServiceImplementation, NetworkingServiceImplementation).

Может ли кто-нибудь предоставить цитаты из OO-Gurus или ссылок в книгах , которые в основном гласят "Использование препроцессора - плохая практика OO, если вы пытаетесь настроить поведение, которое должно быть настроено с помощью Контейнер IoC "?

Мне нужны эти цитаты, чтобы убедить сотрудников провести рефакторинг ...

Редактировать: Я знаю и согласен, что использование директив препроцессора для изменения кода, специфичного для целевой платформы, во время компиляции - это хорошо, и именно для этого созданы директивы препроцессора. Однако я думаю, что для получения хорошо спроектированных и тестируемых классов и компонентов следует использовать конфигурацию времени выполнения, а не конфигурацию времени компиляции. Другими словами: использование #defines и # if вне того, для чего они предназначены, приведет к трудностям при тестировании кода и плохо спроектированным классам.

Кто-нибудь читал что-то в этом духе и может дать мне, чтобы я мог сослаться?

Ответы [ 14 ]

23 голосов
/ 29 января 2009

Генри Спенсер написал статью под названием # ifdef считать опасным .

Кроме того, сам Бьярн Страуструп в главе 18 своей книги Дизайн и эволюция C ++ недоволен использованием препроцессора и желает полностью исключить его. Тем не менее, Страуструп также признает необходимость директивы #ifdef и условной компиляции и продолжает иллюстрировать, что в C ++ нет хорошей альтернативы.

Наконец, Пит Гудлифф, в главе 13 своей книги Создание кода: практика написания превосходного кода , приводит пример того, как, #ifdef, даже будучи использованным в его первоначальном назначении, может создать беспорядок вашего кода.

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

14 голосов
/ 23 января 2009

ИМХО, вы говорите о C и C ++, а не о практике OO в целом. И C не является объектно-ориентированным. На обоих языках препроцессор действительно полезен. Просто используйте его правильно.

Я думаю, что этот ответ принадлежит C ++ FAQ: [29.8] Вы говорите, что препроцессор злой? .

Да, именно это я и говорю: препроцессор - зло.

Каждый #define макрос эффективно создает новое ключевое слово в каждом источнике файл и каждый объем до этого символа #undef д. Препроцессор позволяет вы создаете символ #define, который всегда заменяется независимо от {...} область, где этот символ появляется.

Иногда нам нужен препроцессор, такие как #ifndef/#define оболочка в каждом заголовочном файле, но это должно Избегайте, когда можете. "Злой" не означает «никогда не использовать». Вы будете использовать злые вещи иногда, особенно когда они "меньше двух зло. "Но они все еще злые: -)

Надеюсь, этот источник достаточно авторитетен: -)

11 голосов
/ 23 января 2009

Директивы препроцессора в C # имеют очень четко определенные и практические варианты использования. Те, о которых вы конкретно говорите, называются условными директивами, используются для управления тем, какие части кода скомпилированы, а какие нет.

Существует очень важное различие между некомпилированием частей кода и контролем того, как ваш объектный граф подключается через IoC. Давайте посмотрим на реальный пример: XNA. Когда вы разрабатываете игры XNA, которые вы планируете развертывать как на Windows, так и на XBox 360, ваше решение обычно будет иметь как минимум две платформы, между которыми вы можете переключаться, в вашей IDE. Между ними будет несколько различий, но одно из них будет то, что платформа XBox 360 будет определять условный символ XBOX360, который вы можете использовать в своем исходном коде со следующей идиомой:

#if (XBOX360)
// some XBOX360-specific code here
#else
// some Windows-specific code here
#endif

Конечно, вы могли бы выделить эти различия, используя шаблон разработки стратегии и управление через IoC, для которого создается экземпляр, но условная компиляция предлагает как минимум три основных преимущества:

  1. Вы не отправляете код, который вам не нужен.
  2. Вы можете увидеть различия между специфичным для платформы кодом для обеих платформ в законном контексте этого кода.
  3. Там нет косвенных накладных расходов. Соответствующий код компилируется, другой - нет, и все.
9 голосов
/ 23 января 2009

«Препроцессор - воплощение зла и причина всей боли на земле» -Роберт (О.О. Гуру)

4 голосов
/ 28 января 2009

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

  #ifdef DEBUG
  //...
  #else
  //...

Хорошо, теперь я могу создать версию «Отладка» и версию «Релиз». Это нормально для меня, я всегда так делаю, потому что у меня есть утверждения и трассировки отладки, которые выполняются только в отладочной версии.

Если кто-то приходит и пишет (пример из реальной жизни)

  #ifdef MANUALLY_MANAGED_MEMORY
  ...

И они пишут оптимизацию для домашних животных, которую распространяют на четыре или пять разных классов, и вдруг у вас есть ЧЕТЫРЕ возможных способа компилировать ваш код.

Если только у вас есть еще один # ifdef-зависимый код, то у вас будет восемь возможных версий для генерации, и что еще более тревожно, ЧЕТЫРЕ из них будут возможными выпусками версий.

Конечно, время выполнения if (), например циклы и все остальное, создает ветки, которые вы должны протестировать, - но мне гораздо сложнее гарантировать, что каждое время компиляции вариации конфигурации остается правильным .

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

3 голосов
/ 28 января 2009

Бьярн Страустрап дает свой ответ (в общем, не относящийся к IoC) здесь

Итак, что не так с использованием макросов?

(отрывок)

Макросы не подчиняются области действия C ++ и введите правила. Это часто является причиной тонкие и не очень тонкие проблемы. Следовательно, C ++ обеспечивает альтернативы, которые лучше соответствуют остальные части C ++, такие как встроенные функции, шаблоны и пространства имен.

2 голосов
/ 28 января 2009

Использование #if вместо IoC или какого-либо другого механизма для управления различными функциями на основе конфигурации, вероятно, является нарушением принципа единой ответственности, который является ключом для «современных» конструкций ОО. Вот обширная серия статей о принципах проектирования ОО.

Поскольку части в различных разделах #if по определению связаны с различными аспектами системы, вы теперь связываете детали реализации по крайней мере двух различных компонентов в цепочке зависимостей вашего кода, который использует #if .

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

В исходном случае вам нужно будет помнить о существовании #if и учитывать его при каждом изменении любого из трех компонентов в отношении возможных побочных эффектов критического изменения.

2 голосов
/ 27 января 2009

IMO важно различать #if и #define. Оба могут быть полезны, и оба могут быть чрезмерно использованы. Мой опыт показывает, что #define более вероятно будет использован чрезмерно, чем # if.

Я потратил 10+ лет на программирование на C и C ++. В проектах, над которыми я работал (коммерчески доступное программное обеспечение для DOS / Unix / Macintosh / Windows), мы использовали #if и #define в первую очередь для решения проблем переносимости кода.

Я потратил достаточно времени на работу с C ++ / MFC, чтобы научиться ненавидеть #define при его чрезмерном использовании - что, как я полагаю, имеет место в MFC примерно в 1996 году.

Затем я более 7 лет работал над проектами Java. Я не могу сказать, что пропустил препроцессор (хотя я наверняка пропустил такие вещи, как перечисляемые типы и шаблоны / шаблоны, которых у Java не было в то время).

Я работаю в C # с 2003 года. Мы широко использовали #if и [Conditional ("DEBUG")] для наших отладочных сборок, но #if - это просто более удобный и немного более эффективный способ делать то же самое, что мы делали в Java.

Двигаясь вперед, мы начали готовить наш основной движок для Silverlight. Хотя все, что мы делаем, можно было бы сделать без #if, с #if меньше работать, что означает, что мы можем тратить больше времени на добавление функций, которые запрашивают наши клиенты. Например, у нас есть класс значений, который инкапсулирует системный цвет для хранения в нашем ядре. Ниже приведены первые несколько строк кода. Из-за сходства между System.Drawing.Color и System.Windows.Media.Color, #define вверху дает нам много функциональных возможностей в обычном .NET и в Silverlight без дублирования кода:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
#if SILVERLIGHT
using SystemColor = System.Windows.Media.Color;
#else
using SystemColor = System.Drawing.Color;
#endif

namespace SpreadsheetGear.Drawing
{
    /// <summary>
    /// Represents a Color in the SpreadsheetGear API and provides implicit conversion operators to and from System.Drawing.Color and / or System.Windows.Media.Color.
    /// </summary>
    public struct Color
    {
        public override string ToString()
        {
            //return string.Format("Color({0}, {1}, {2})", R, G, B);
            return _color.ToString();
        }

        public override bool Equals(object obj)
        {
            return (obj is Color && (this == (Color)obj))
                || (obj is SystemColor && (_color == (SystemColor)obj));
        }
        ...

Суть для меня в том, что существует множество языковых функций, которые могут быть использованы чрезмерно, но это не является достаточной причиной, чтобы исключать эти функции или устанавливать строгие правила, запрещающие их использование. Я должен сказать, что переход на C # после программирования на Java так долго помогает мне оценить это, потому что Microsoft (Андерс Хейлсберг) более охотно предоставляет функции, которые могут не понравиться профессору колледжа, но которые делают меня более продуктивным в моей работе и, в конечном итоге, позволит мне создать лучший виджет за ограниченное время, которое есть у любого человека с датой отправки.

2 голосов
/ 23 января 2009

В c # / VB.NET я бы не сказал, что это зло.

Например, при отладке служб Windows я помещаю следующее в Main, чтобы в режиме отладки я мог запустить службу как приложение.

    <MTAThread()> _
    <System.Diagnostics.DebuggerNonUserCode()> _
    Shared Sub Main()


#If DEBUG Then

        'Starts this up as an application.'

        Dim _service As New DispatchService()
        _service.ServiceStartupMethod(Nothing)
        System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite)

#Else

        'Runs as a service. '

        Dim ServicesToRun() As System.ServiceProcess.ServiceBase
        ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
        System.ServiceProcess.ServiceBase.Run(ServicesToRun)

#End If

    End Sub

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

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

EDIT: Re: первый комментарий. Я не понимаю, как этот пример связан здесь. Проблема в том, что препроцессор используется не по назначению, а не в том, что это зло.

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

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

Намного позже РЕДАКТИРОВАТЬ : FWIW: я не использовал для этого директивы препроцессора в течение нескольких лет. Я использую Environment.UserInteractive с определенным набором аргументов ("-c" = Console) и аккуратным приемом, который я взял где-то здесь, на SO, чтобы не блокировать приложение, но все же ждать ввода пользователя.

Shared Sub Main(args as string[])
  If (Environment.UserInteractive = True) Then
    'Starts this up as an application.
    If (args.Length > 0 AndAlso args[0].Equals("-c")) Then 'usually a "DeriveCommand" function that returns an enum or something similar
      Dim isRunning As Boolean = true
      Dim _service As New DispatchService()
      _service.ServiceStartupMethod(Nothing)
      Console.WriteLine("Press ESC to stop running")
      While (IsRunning)
        While (Not (Console.KeyAvailable AndAlso (Console.ReadKey(true).Key.Equals(ConsoleKey.Escape) OrElse Console.ReadKey(true).Key.Equals(ConsoleKey.R))))
           Thread.Sleep(1)
         End While                        
         Console.WriteLine()
         Console.WriteLine("Press ESC to continue running or any other key to continue shutdown.")
         Dim key = Console.ReadKey();
         if (key.Key.Equals(ConsoleKey.Escape))
         {
           Console.WriteLine("Press ESC to load shutdown menu.")
           Continue
         }
         isRunning = false
      End While
      _service.ServiceStopMethod()
    Else
      Throw New ArgumentException("Dont be a double clicker, this needs a console for reporting info and errors")
    End If
  Else

    'Runs as a service. '
    Dim ServicesToRun() As System.ServiceProcess.ServiceBase
    ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
    System.ServiceProcess.ServiceBase.Run(ServicesToRun)
  End If
End Sub
2 голосов
/ 23 января 2009

Поддержка предварительной обработки в C # крайне минимальна .... граничит с бесполезным. Это зло?

Имеет ли препроцессор какое-либо отношение к ОО? Конечно, это для конфигурации сборки.

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

Возможно, я не захочу поставлять облегченную версию, которая является профессиональной версией с разными флагами времени выполнения.

Tony

...