Подтвердить что ты не робот

Требуется ссылка: использование препроцессора - плохая практика OO

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

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

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

Изменить: я знаю и согласен с тем, что использование директив препроцессора для изменения специфического кода целевой платформы во время компиляции является прекрасным, и для этого предназначены директивы препроцессора. Тем не менее, я думаю, что для создания хорошо разработанных и тестируемых классов и компонентов следует использовать конфигурацию времени выполнения, а не конфигурацию compiletime. Другими словами: использование #defines и #if сверх того, что они предназначены, приведет к сложному тестированию кода и плохо спроектированных классов.

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

Существует очень важное различие между некомпилированием частей кода и контролем того, как ваш объектный граф подключается через 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, который получает экземпляр, но условная компиляция предлагает по крайней мере три основных преимущества:

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

Ответ 4

"Препроцессор - это воплощение зла и причина всей боли на земле" -Роберт (OO Guru)

Ответ 5

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

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

Хорошо, теперь я могу создать версию "Debug" и версию "Release". Это нормально для меня, я всегда это делаю, потому что у меня есть утверждения и трассировки отладки, которые выполняются только в отладочной версии.

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

  #ifdef MANUALLY_MANAGED_MEMORY
  ...

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

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

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

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

Ответ 6

Bjarne Stroustrap дает свой ответ (вообще, не специфический для IoC) здесь

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

(выдержка)

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

Ответ 7

В С#/VB.NET я бы не сказал своего зла.

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

    <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

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

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

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

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

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

Много позже EDIT: FWIW: я не использовал для этого препроцессорные директивы через несколько лет. Я использую Environment.UserInteractive с определенным набором arg ( "-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

Ответ 8

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

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

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

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

Я работаю на С# с 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));
        }
        ...

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

Ответ 9

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

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

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

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

Ответ 10

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

Является ли препроцессор чем-то связанным с OO? Конечно, это для конфигурации сборки.

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

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

Тони

Ответ 11

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

В основном я думаю о #define, который используется для встраивания короткого выражения, поскольку он сохраняет накладные расходы на вызов функции. Другими словами, это преждевременная оптимизация.

Ответ 12

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

Ответ 13

У меня нет заявления гуру относительно использования препроцессорных директив в моем уме и не могу добавить ссылку на знаменитую. Но я хочу дать вам ссылку на простой образец, найденный в Microsoft MSDN.

#define A
#undef B
class C
{
#if A
   void F() {}
#else
   void G() {}
#endif
#if B
   void H() {}
#else
   void I() {}
#endif
}

Это приведет к простому

class C
{
   void F() {}
   void I() {}
}

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

Для меня гораздо проще создавать различные реализации и вставлять их в вызывающего абонента вместо переключения определяет для создания "новых" определений классов. (... и из-за этого я понимаю, почему вы сравниваете использование препроцессоров с использованием IoC вместо этого). Помимо ужасной читаемости кода с использованием инструкций препроцессора, я редко использовал определения препроцессора, потому что они увеличивают сложность тестирования вашего кода, потому что они приводят к нескольким путям (но это проблема наличия нескольких реализаций, введенных внешним IoC-Container).

Сама Microsoft использовала множество определений препроцессора в win32 api, и вы можете узнать/запомнить уродливое переключение между вызовами метода char и w_char.

Возможно, вы не должны говорить "Не используйте". Скажите им "Как использовать" и "Когда использовать его". Я думаю, что все согласятся с вами, если вы придумаете хорошие (более понятные) альтернативы и сможете описать риски использования препроцессоров /makros.

Не нужно гуру... просто будьте гуру.; -)

Ответ 14

Я хотел задать новый вопрос, но похоже, что он подходит здесь. Я согласен с тем, что наличие полномасштабного препроцессора может быть слишком большим для Java. Существует одна явная необходимость, которая покрывается препроцессором в мире C и вообще не рассматривается в мире Java: Я хочу, чтобы отладочные распечатки полностью игнорировались компилятором в зависимости от уровня отладки. Теперь мы полагаемся на "хорошую практику", но на практике эта практика трудно обеспечить, и все еще остается избыточная загрузка ЦП.

В стиле Java, который может быть разрешен с помощью некоторых определенных методов, таких как debug(), warning() и т.д., для которых вызваны условия генерации кода.

На самом деле это будет немного интеграции Log4J в язык.