需要引用:预处理器使用是不好的OO练习

我相信,像#if UsingNetwork这样的预处理器指令的使用是不好的OO实践 – 其他同事则不然。 我认为,当使用IoC容器(例如Spring)时,如果相应编程,可以轻松配置组件。 在这种情况下,可以通过IoC容器设置有效的IsUsingNetwork ,或者如果“使用网络”实现的行为不同,则应该实现并注入该接口的另一个实现(例如: IServiceServiceImplementationNetworkingServiceImplementation )。

有人可以提供OO-Gurus的 引用书籍中的参考资料 ,基本上读取“如果你尝试配置应该通过IoC容器配置的行为,预处理器使用是不好的OO实践”?

我需要这种引用来说服同事重构……

编辑:我确实知道并同意在编译期间使用预处理程序指令更改目标平台特定代码很好,这就是预处理程序指令的用途。 但是,我认为应该使用运行时配置而不是compiletime-configuration来获得良好的设计和可测试的类和组件。 换句话说:使用#defines和#if超出它们的意图将导致难以测试代码和设计糟糕的类。

有没有人沿着这些方向阅读并能给我这样的话我可以参考?

亨利斯宾塞写了一篇名为#ifdef Considered Harmful的论文。

此外,Bjarne Stroustrup本人在他的“ C ++的设计和演变”一书的第18章中对预处理器的使用表示不满,并希望完全消除它。 但是,Stroustrup也认识到#ifdef指令和条件编译的必要性,并继续说明在C ++中没有它的好选择。

最后,Pete Goodliffe在他的“ Code Craft:The Writing of Writing Excellent Code”一书的第13章中给出了一个例子,即使用于其原始目的,#ifdef也会使你的代码变得混乱。

希望这可以帮助。 但是,如果你的同事一开始不听合理的论点,我怀疑书的引用会帮助说服他们;)

恕我直言,你谈的是C和C ++,而不是一般的OO实践。 而C不是面向对象的。 在这两种语言中,预处理器实际上都很有用。 只需正确使用它。

我认为这个答案属于C ++ FAQ:[29.8]你是说预处理器是邪恶的吗? 。

是的,这正是我所说的:预处理器是邪恶的。

每个#define宏在每个源文件和每个范围内有效地创建一个新关键字,直到该符号为#undef d。 预处理器允许您创建一个始终被替换的#define符号,与该符号出现的{...}范围无关。

有时我们需要预处理器,例如每个头文件中的#ifndef/#define包装器,但是应该尽可能避免使用它。 “邪恶”并不意味着“永远不会使用”。 你有时会使用邪恶的东西,特别是当它们是“两个邪恶中的较小者”时。 但他们仍然是邪恶的:-)

我希望这个来源足够权威:-)

C#中的预处理程序指令具有非常明确定义和实际的用例。 你专门讨论的那些,称为条件指令,用于控制代码的哪些部分被编译,哪些不是。

不编译代码部分和控制对象图通过IoC连接的方式之间存在非常重要的区别。 让我们看一个现实世界的例子:XNA。 当您开发计划在Windows和XBox 360上部署的XNA游戏时,您的解决方案通常至少有两个可在IDE中切换的平台。 它们之间会有一些差异,但其中一个区别在于XBox 360平台将定义条件符号XBOX360,您可以在源代码中使用以下习语:

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

当然,您可以使用策略设计模式和通过IoC进行实例化来控制这些差异,但条件编译至少提供三个主要优势:

  1. 您不提供不需要的代码。
  2. 您可以在该代码的正确上下文中看到两个平台的特定于平台的代码之间的差异。
  3. 没有间接开销。 编译适当的代码,另一个不编译,就是这样。

“预处理器是邪恶的化身,也是地球上所有痛苦的原因” -Robert(OO Guru)

预处理器#ifdef的一个问题是它们有效地复制了编译版本的数量,理论上,你应该测试它们,以便你可以说你交付的代码是正确的。

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

好的,现在我可以生成“Debug”版本和“Release”版本。 这对我来说没问题,我总是这样做,因为我有断言和调试跟踪,只在调试版本中执行。

如果有人来写(现实生活中的例子)

  #ifdef MANUALLY_MANAGED_MEMORY ... 

他们写了一个宠物优化,它们传播到四到五个不同的类,然后突然你有四种可能的方法来编译你的代码。

如果你只有另一个#ifdef依赖代码,那么你将有八个可能的版本生成,更令人不安的是,其中四个将是可能的发布版本。

当然运行时if()的循环和其他任何东西都会创建你必须测试的分支 – 但是我发现要保证配置的每个编译时变化都是正确的要困难得多。

这就是为什么我认为,作为一个策略,除了Debug / Release版本之外的所有#ifdef都应该是临时的 ,即你在开发代码中做一个实验,你很快就会决定它是否保持单向或者另一个。

Bjarne Stroustrap在这里提供了他的答案(一般来说,不是IoC特有的)

那么,使用宏有什么问题?

(摘抄)

宏不遵守C ++范围和类型规则。 这通常是微妙而不那么微妙的问题的原因。 因此,C ++提供了更好地适应C ++其余部分的替代方法,例如内联函数,模板和命名空间。

C#中预处理的支持非常小……接近无用。 那是邪恶吗?

预处理器与OO有什么关系吗? 当然它是用于构建配置。

例如,我有一个精简版和我的应用程序的专业版。 我可能想要在lite上排除一些代码,而不得不求助于构建非常相似的代码版本。

我可能不想发送精简版本,它是具有不同运行时标志的专业版本。

托尼

IMO重要的是区分#if和#define。 两者都有用,两者都可以过度使用。 我的经验是#define比#if更容易被过度使用。

我花了10多年时间做C和C ++编程。 在我工作的项目(DOS / Unix / Macintosh / Windows的商用软件)中,我们主要使用#if和#define来处理代码可移植性问题。

我花了足够的时间与C ++ / MFC一起学习在过度使用时厌恶#define – 我相信在大约1996年的MFC中就是这种情况。

然后我花了7年多时间研究Java项目。 我不能说我错过了预处理器(虽然我肯定错过了当时Java没有的枚举类型和模板/generics等内容)。

我从2003年开始使用C#。我们已经大量使用#if和[条件(“DEBUG”)]来进行调试构建 – 但是#if只是一种更方便,更有效的方法来做同样的事情。我们用Java做的事情。

outlook未来,我们已经开始为Silverlight准备我们的核心引擎。 虽然我们所做的一切都可以在没有#if的情况下完成,但使用#if可以减少工作量,这意味着我们可以花更多的时间添加客户要求的function。 例如,我们有一个值类,它封装了一个系统颜色,用于存储在我们的核心引擎中。 下面是前几行代码。 由于System.Drawing.Color和System.Windows.Media.Color之间的相似性,顶部的#define在普通的.NET和Silverlight中为我们提供了许多function而无需重复代码:

 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 { ///  /// Represents a Color in the SpreadsheetGear API and provides implicit conversion operators to and from System.Drawing.Color and / or System.Windows.Media.Color. ///  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)); } ... 

对我来说,最重要的是有许多语言function可以被过度使用,但这不足以让这些function退出或制定严格的规则禁止使用它们。 我必须说,在用Java编程这么长时间之后转到C#有助于我理解这一点,因为微软(Anders Hejlsberg)更愿意提供可能对大学教授不具吸引力的function,但这使我在工作中更有成效并最终使我能够在有限的时间内建立一个更好的小部件,任何人都有一个发货日期。

使用#if而不是IoC或其他一些机制来控制基于配置的不同function可能违反了单一责任原则,这是“现代”OO设计的关键。 这是一系列关于面向对象设计原则的文章。

由于#if的不同部分中的部分通过定义涉及系统的不同方面,因此您现在将至少两个不同组件的实现细节耦合到使用#if的代码的依赖关系链中。

通过重构这些问题,您创建了一个类,假设它已完成并经过测试,将不再需要打开,除非公共代码被破坏。

在您的原始情况下,您需要记住#if的存在,并且在任何三个组件中的任何一个随时间变化的可能副作用发生变化时都要考虑到它。

在c#/ VB.NET中,我不会说它的邪恶。

例如,在调试Windows服务时,我将以下内容放在Main中,以便在调试模式下,我可以将该服务作为应用程序运行。

   _  _ 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,请纠正我,但是当一个简单的布尔就足够了,你似乎在抱怨其他人使用预处理器。 如果是这样的话,不要诅咒预处理器,该死的那些以这种方式使用它们。

编辑:回复:第一个评论。 我不知道这个例子如何联系在这里。 问题是预处理器被误用,而不是它是邪恶的。

我再举一个例子。 我们有一个应用程序在启动时在客户端和服务器之间进行版本检查。 在开发中,我们经常有不同的版本,不想进行版本检查。 这是邪恶的吗?

我想我想说的是,即使改变程序行为,预处理器也不是邪恶的。 问题是有人滥用它。 说那有什么问题? 你为什么试图解雇语言function?

很久以后编辑 :FWIW:我在几年内没有使用预处理器指令。 我确实使用了带有特定arg set(“-c”= Console)的Environment.UserInteractive,以及我在这里从某处获取的一个巧妙的技巧,以阻止应用程序但仍等待用户输入。

 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 

预处理器代码注入是编译器对数据库的触发器。 并且很容易找到关于触发器的这种断言。

我主要想到#define用于内联短表达式,因为它节省了函数调用的开销。 换句话说,它是过早的优化。

告诉你的同事的一个快点是:如果在这样的语句中使用符号,预处理器会在数学语句中打破运算符优先级。

我没有关于预处理器指令的使用的大师声明,并且无法添加对着名指令的引用。 但是我想给你一个微软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的风险,每个人都会同意你。

不需要大师…只是一个大师。 😉

我想问一个新问题,但看起来很适合这里。 我同意拥有一个完整的预处理器可能对Java来说太过分了。 C世界中的预处理器有一个明确的需求,在Java世界中根本没有涉及:我希望编译器根据调试级别完全忽略调试打印输出。 现在我们依赖于“良好实践”,但实际上这种做法难以实施,仍然存在一些冗余CPU负载。

在Java风格中,可以通过使用一些指定的方法来解决,例如debug(),warning()等,为其调用代码生成条件。

实际上,这将是Log4J与语言的一些整合。