需要引用:使用预处理器是不好的 OO 实践
我相信,使用像 #if usingNetwork
这样的预处理器指令是不好的 OO 实践 - 其他同事则不然。 我认为,当使用 IoC 容器(例如 Spring)时,如果进行相应的编程,可以轻松配置组件。 在这种情况下,IoC 容器可以设置属性 IsUsingNetwork
,或者,如果“使用网络”实现的行为不同,则应该实现并注入该接口的另一个实现(例如:IService
、ServiceImplementation
、NetworkingServiceImplementation
)。
有人可以提供OO-Gurus 的引用或书籍中的参考,基本上是“如果你尝试配置应该通过 IoC 配置的行为,那么预处理器的使用是不好的 OO 实践”容器”?
我需要此引用来说服同事重构...
编辑:我确实知道并同意在编译期间使用预处理器指令来更改目标平台特定的代码是很好的,这就是预处理器指令的用途。 但是,我认为应该使用运行时配置而不是编译时配置来获得良好设计和可测试的类和组件。 换句话说:使用 #defines 和 #if 超出它们的用途将导致难以测试代码和设计糟糕的类。
有人读过这些内容并可以给我以便我参考吗?
I believe, that the usage of preprocessor directives like #if UsingNetwork
is bad OO practice - other coworkers do not.
I think, when using an IoC container (e.g. Spring), components can be easily configured if programmed accordingly. In this context either a propery IsUsingNetwork
can be set by the IoC container or, if the "using network" implementation behaves differently, another implementation of that interface should be implemented and injected (e.g.: IService
, ServiceImplementation
, NetworkingServiceImplementation
).
Can somebody please provide citations of OO-Gurus or references in books which basically reads "Preprocessor usage is bad OO practice if you try to configure behaviour which should be configured via an IoC container"?
I need this citations to convince coworkers to refactor...
Edit: I do know and agree that using preprocessor directives to change targetplatform specific code during compilation is fine and that is what preprocessor directives are made for. However, I think that runtime-configuration should be used rather than compiletime-configuration to get good designed and testable classes and components. In other words: Using #defines and #if's beyond what they are meant for will lead to difficult to test code and badly designed classes.
Has anybody read something along these lines and can give me so I can refer to?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
Henry Spencer 写了一篇名为 #ifdef 被认为有害的论文。
另外,Bjarne Stroustrup 本人在其著作的第 18 章C++ 的设计与演化 ,不赞成使用预处理器并希望完全消除它。 然而,Stroustrup 也认识到 #ifdef 指令和条件编译的必要性,并继续说明在 C++ 中没有好的替代方案。
最后,Pete Goodliffe 在他的书的第 13 章Code Craft:编写优秀代码的实践,给出了一个示例,即使用于其最初目的,#ifdef 也会使您的代码变得一团糟。
希望这可以帮助。 然而,如果你的同事一开始就不听合理的论点,我怀疑书中的引言能否帮助说服他们;)
Henry Spencer wrote a paper called #ifdef Considered Harmful.
Also, Bjarne Stroustrup himself, in the chapter 18 of his book The Design and Evolution of C++, frowns on the use of preprocessor and wishes to eliminate it completely. However, Stroustrup also recognizes the necessity for #ifdef directive and the conditional compilation and goes on to illustrate that there is no good alternative for it in C++.
Finally, Pete Goodliffe, in chapter 13 of his book Code Craft: The Practice of Writing Excellent Code, gives an example how, even when used for its original purpose, #ifdef can make a mess out of your code.
Hope this helps. However, if your co-workers won't listen to reasonable arguments in the first place, I doubt book quotes will help convince them ;)
C# 中的预处理器指令具有非常明确的定义和实际用例。 您特别讨论的那些称为条件指令,用于控制代码的哪些部分被编译,哪些部分不被编译。
不编译部分代码和通过 IoC 控制对象图的连接方式之间存在非常重要的区别。 让我们看一个现实世界的例子:XNA。 当您开发计划在 Windows 和 Xbox 360 上部署的 XNA 游戏时,您的解决方案通常至少有两个可以在 IDE 中切换的平台。 它们之间存在一些差异,但这些差异之一是 Xbox 360 平台将定义一个条件符号 XBOX360,您可以通过以下习惯用法在源代码中使用该符号:
当然,您可以使用以下方式分解这些差异:一种策略设计模式并通过 IoC 进行控制,其中一个被实例化,但条件编译至少提供了三个主要优点:
Preprocessor directives in C# have very clearly defined and practical uses cases. The ones you're specifically talking about, called conditional directives, are used to control which parts of the code are compiled and which aren't.
There is a very important difference between not compiling parts of code and controlling how your object graph is wired via IoC. Let's look at a real-world example: XNA. When you're developing XNA games that you plan to deploy on both Windows and XBox 360, your solution will typically have at least two platforms that you can switch between, in your IDE. There will be several differences between them, but one of those differences will be that the XBox 360 platform will define a conditional symbol XBOX360 which you can use in your source code with a following idiom:
You could, of course, factor out these differences using a Strategy design pattern and control via IoC which one gets instantiated, but the conditional compilation offers at least three major advantages:
恕我直言,您谈论的是 C 和 C++,而不是一般的 OO 实践。 而且C不是面向对象的。 在这两种语言中,预处理器实际上都很有用。 只要正确使用即可。
我认为这个答案属于 C++ FAQ:[29.8] 你是说预处理器是邪恶的?。
我希望这个来源足够权威:-)
IMHO, you talk about C and C++, not about OO practice in general. And C is not Object-oriented. In both languages the preprocessor is actually useful. Just use it correctly.
I think this answer belongs to C++ FAQ: [29.8] Are you saying that the preprocessor is evil?.
I hope this source is authoritative enough :-)
IMO 区分 #if 和 #define 很重要。 两者都可能有用,也可能被过度使用。 我的经验是#define 比#if 更有可能被过度使用。
我花了 10 多年时间进行 C 和 C++ 编程。 在我从事的项目(用于 DOS / Unix / Macintosh / Windows 的商业软件)中,我们主要使用 #if 和 #define 来处理代码可移植性问题。
我花了足够的时间使用 C++/MFC,以学会在 #define 被过度使用时厌恶它 - 我相信 1996 年左右的 MFC 就是这种情况。
然后我花了 7 年多的时间从事 Java 项目。 我不能说我错过了预处理器(尽管我肯定错过了 Java 当时没有的枚举类型和模板/泛型等东西)。
我自 2003 年以来一直在 C# 领域工作。我们在调试构建中大量使用了 #if 和 [Conditional("DEBUG")] - 但 #if 只是一种更方便、稍微更有效的方法来执行相同的操作我们在 Java 中所做的事情。
展望未来,我们已经开始为 Silverlight 准备我们的核心引擎。 虽然我们所做的一切都可以在没有 #if 的情况下完成,但使用 #if 的工作量会减少,这意味着我们可以花更多的时间来添加客户要求的功能。 例如,我们有一个值类,它封装了系统颜色以存储在我们的核心引擎中。 下面是前几行代码。 由于 System.Drawing.Color 和 System.Windows.Media.Color 之间的相似性,顶部的 #define 为我们提供了普通 .NET 和 Silverlight 中的许多功能,而无需重复代码:
对我来说,底线是有许多语言功能可能会被过度使用,但这并不足以成为忽略这些功能或制定严格规则禁止其使用的充分理由。 我必须说,在使用 Java 编程这么长时间后转向 C# 帮助我体会到了这一点,因为 Microsoft (Anders Hejlsberg) 更愿意提供可能不会吸引大学教授的功能,但这些功能使我的工作效率更高最终使我能够在任何有发货日期的人有限的时间内构建一个更好的小部件。
IMO it is important to differentiate between #if and #define. Both can be useful and both can be overused. My experience is that #define is more likely to be overused than #if.
I spent 10+ years doing C and C++ programming. In the projects I worked on (commercially available software for DOS / Unix / Macintosh / Windows) we used #if and #define primarily to deal with code portability issues.
I spent enough time working with C++ / MFC to learn to detest #define when it is overused - which I believe to be the case in MFC circa 1996.
I then spent 7+ years working on Java projects. I cannot say that I missed the preprocessor (although I most certainly did miss things like enumerated types and templates / generics which Java did not have at the time).
I've been working in C# since 2003. We have made heavy use of #if and [Conditional("DEBUG")] for our debug builds - but #if is just a more convenient, and slightly more efficient way of doing the same things we did in Java.
Moving forward, we have started to prepare our core engine for Silverlight. While everything we are doing could be done without #if, it is less work with #if which means we can spend more time adding features that our customers are asking for. For example, we have a value class which encapsulates a system color for storage in our core engine. Below are the first few lines of code. Because of the similarity between System.Drawing.Color and System.Windows.Media.Color, the #define at the top gets us a lot of functionality in normal .NET and in Silverlight without duplicating code:
The bottom line for me is that there are many language features which can be overused, but this is not a good enough reason to leave these features out or to make strict rules prohibiting their use. I must say that moving to C# after programming in Java for so long helps me to appreciate this because Microsoft (Anders Hejlsberg) has been more willing to provide features which might not appeal to a college professor, but which make me more productive in my job and ultimately enable me to build a better widget in the limited time anybody with a ship date has.
预处理器 #ifdef 的一个问题是,它们实际上复制了编译版本的数量,理论上,您应该彻底测试,以便可以说您交付的代码是正确的。
好的,现在我可以生成“调试”版本和“发布”版本。 这对我来说没问题,我总是这样做,因为我有断言和调试跟踪,它们仅在调试版本中执行。
如果有人来写(现实生活中的例子)
,他们编写了一个宠物优化,并将其传播到四个或五个不同的类,那么突然你就有四种可能的方法来编译你的代码。
如果您有另一个依赖于 #ifdef 的代码,那么您将有八个可能的版本可以生成,更令人不安的是,其中四个将是可能的发布版本。
当然,运行时 if()(如循环等)会创建必须测试的分支 - 但我发现要保证配置的每个编译时变体保持正确要困难得多。
这就是为什么我认为,作为一项政策,除了调试/发布版本之外的所有 #ifdef 都应该是临时的,即您正在开发代码中进行实验,然后您将决定,很快,如果它保持这样或那样的话。
One problem with the preprocessor #ifdef's is that they effectively duplicate the number of compiled versions that, in theory, you should test thorougly so that you can say that your delivered code is correct.
Ok, now I can produce the "Debug" version and the "Release" version. This is ok for me, I always do it, because I have assertions and debug traces which are only executed in the debug version.
If someone comes and writes (real life example)
And they write a pet optimization which they propagate to four or five different classes, then suddenly you have FOUR possible ways to compile your code.
If only you have another #ifdef-dependant code then you'll have EIGHT possible versions to generate, and what's more disturbing, FOUR of them will be possible release versions.
Of course runtime if()'s, like loops and whatever, create branches that you have to test - but I find it much more difficult to guarantee that every compile time variation of the configuration remains correct.
This is the reason why I think, as a policy, all #ifdef's except the one for Debug/Release version should be temporary, i.e. you're doing an experiment in development code and you'll decide, soon, if it stays one way or the other.
Bjarne Stroustrap 在此提供了他的答案(一般而言,并非特定于 IoC)
那么,使用宏有什么问题?
(摘录)
Bjarne Stroustrap provides his answer (in general, not specific to IoC) here
So, what's wrong with using macros?
(excerpt)
C# 中对预处理的支持非常少......几乎毫无用处。 那是邪恶吗?
预处理器与面向对象有什么关系吗? 当然是用于构建配置。
例如,我的应用程序有一个精简版和一个专业版。 我可能想排除精简版上的一些代码,而不必构建非常相似的代码版本。
我可能不想发布精简版,它是具有不同运行时标志的专业版。
托尼
The support of preprocessing in C# is highly minimal.... verging on useless. Is that Evil?
Is the Preprocessor anything to do with OO? Surely it's for build configuration.
For instance I have a lite version and a pro-version of my app. I might want to exclude some code on the lite withour having to resort to building very similar versions of the code.
I might not want to ship a lite version which is the pro version with different runtime flags.
Tony
使用 #if 而不是 IoC 或其他一些机制来根据配置控制不同的功能可能违反了单一职责原则,而该原则是“现代”OO 设计的关键。 这里是有关 OO 设计原则的一系列广泛文章。
由于 #if 的不同部分中的部分根据定义涉及系统的不同方面,因此您现在将至少两个不同组件的实现细节耦合到使用 #if 的代码的依赖关系链中。
通过重构这些问题,您创建了一个类,假设它已完成并经过测试,将不再需要破解,除非公共代码被破坏。
在您原来的情况下,您需要记住 #if 的存在,并在三个组件中的任何一个发生更改时考虑到重大更改可能产生的副作用。
Using a #if instead of an IoC or some other mechanism for controlling different functionality based on configuration is probably a violation of the Single Responsibility Principle, which is the key for 'modern' OO designs. Here is an extensive series of articles about OO design principles.
Since the parts in the different sections of the #if by definition concern themselves with different aspects of the system, you are now coupling the implementation details of at least two different components into the dependency chain of your code that uses the #if.
By refactoring those concerns out, you have created a class that, assuming it is finished and tested, will no longer need to be cracked open unless the common code is broken.
In your original case, you'll need to remember the existence of the #if and take it into account any time any of the three components change with respect to possible side-effects of a breaking change.
在 C# / VB.NET 中,我不会说它是邪恶的。
例如,在调试 Windows 服务时,我将以下内容放在 Main 中,以便在调试模式下,我可以将该服务作为应用程序运行。
这是配置应用程序的行为,当然不是邪恶的。 至少,它不像尝试调试服务启动例程那么邪恶。
如果我读错了你的OP,请纠正我,但你似乎在抱怨其他人使用预处理器,而一个简单的布尔值就足够了。 如果是这样的话,不要诅咒预处理器,而是诅咒那些以这种方式使用它们的人。
编辑
回复:第一条评论。 我不明白这个例子与这里有什么关系。 问题在于预处理器被滥用,而不是它是邪恶的。
我再举一个例子。 我们有一个应用程序在启动时在客户端和服务器之间进行版本检查。 在开发中,我们经常会有不同的版本,并且不想做版本检查。 这是邪恶的吗?
我想我想说的是,即使在改变程序行为时,预处理器也不是邪恶的。 问题是有人滥用它。 这样说有什么错吗? 您为什么要尝试忽略语言功能?
编辑2
FWIW,我已经有几年没有为此使用预处理器指令了。 我确实使用
Environment.UserInteractive
与特定的参数集(“-c”=控制台),以及我从此处的某个地方学到的一个巧妙的技巧,以便不阻止应用程序但仍然等待用户输入。In C# / VB.NET, I would not say it is evil.
For example, when debugging windows services, I put the following in Main so that when in Debug mode, I can run the service as an application.
This is configuring the behavior of the application, and is certainly not evil. At the very least, it is not as evil as trying to debug a service startup routine.
Please correct me if I read your OP wrong, but it seems that you are complaining about others using a preprocessor when a simple boolean would suffice. If that is the case, do not damn the preprocessors, damn those using them in such fashion.
Edit
Re: first comment. I do not get how that example ties in here. The problem is that the preprocessor is being misused, not that it is evil.
I'll give you another example. We have an application that does version checking between client and server on startup. In development, we often have different versions and do not want to do a version check. Is this evil?
I guess what I am trying to say is that the preprocessor is not evil, even when changing program behavior. The problem is that someone is misusing it. What is wrong with saying that? Why are you trying to dismiss a language feature?
Edit 2
FWIW, I haven't used preprocessor directives for this in a few years. I do use
Environment.UserInteractive
with a specific arg set ("-c" = Console), and a neat trick I picked up from somewhere here on SO to not block the application but still wait for user input.预处理器代码注入对于编译器来说就像触发器对于数据库一样。 而且很容易找到有关触发器的此类断言。
我主要认为#define 用于内联短表达式,因为它节省了函数调用的开销。 换句话说,这是不成熟的优化。
Preprocessor code injection is to the compiler what triggers are to the database. And it's pretty easy to find such assertions about triggers.
I mainly think of #define being used to inline a short expression because it saves the overhead of a function call. In other words, it's premature optimization.
告诉你的同事的一个快速要点是:如果在数学语句中使用符号,预处理器会破坏数学语句中的运算符优先级。
One quick point to tell your coworkers is this: the preprocessor breaks operator precedence in mathematical statements if symbols are used in such statements.
我心中没有关于预处理器指令使用的权威声明,也无法添加对著名指令的引用。 但我想为您提供一个在 Microsoft 的 MSDN。
这会导致结果很简单
,我认为它不太容易阅读,因为你必须查看顶部才能看到此时到底定义了什么。 如果您在其他地方定义了它,这会变得更加复杂。
对我来说,创建不同的实现看起来要简单得多
将它们注入调用者中,而不是切换定义来创建“新”类定义。 (...正因为如此,我明白为什么您将预处理器定义的使用与 IoC 的使用进行比较)。 除了使用预处理器指令的代码可读性极差之外,我很少使用预处理器定义,因为它们会增加测试代码的复杂性,因为它们会导致多个路径(但这也是由外部 IoC 容器注入多个实现的问题)。
Microsoft 本身在 win32 api 中使用了大量预处理器定义,您可能知道/记得 char 和 w_char 方法调用之间的丑陋切换。
也许你不应该说“不要使用它”。 告诉他们“如何使用它”和“何时使用它”。 我认为如果您提出好的(更好理解的)替代方案并且可以描述使用预处理器定义/makros 的风险,每个人都会同意您的观点。
不需要大师...只要成为大师即可。 ;-)
I have no guru statement regarding to the usage of preprocessor directives in my mind and can not add a reference to a famous one. But I want to give you a link to a simple sample found at Microsoft's MSDN.
This will result in the simple
and I think it is not very easy to read because you have to look at the top to see what exactly is defined at this point. This is getting more complex if you have defined it elsewhere.
For me it looks much simpler to create different implementations and
inject them into a caller instead of switching defines to create "new" class definitions. (... and because of this I understand why you compare the usage of preprocessor defintions with the usage of IoC instead). Beside the horrible readability of code using preprocessor instructions, I rarely used preprocessor definitions because they increase the complexity of testing your code because they result in multiple paths (but this is a problem of having multiple implementations injected by external IoC-Container, too).
Microsoft itself has used a lot of preprocessor definitions within the win32 api and you might know/remember the ugly switching between char and w_char method calls.
Maybe you should not say "Don't use it". Tell them "How to use it" and "When to use it" instead. I think everyone will agree with you if you're coming up with good (better understandable) alternatives and can describe the risks of using preprocessor defines/makros.
No need for a guru... just be a guru. ;-)
我想问一个新问题,但看起来很适合这里。 我同意拥有一个成熟的预处理器对于 Java 来说可能是太多了。 有一个明显的需求在 C 世界中由预处理器满足,而在 Java 世界中根本没有满足:
我希望编译器根据调试级别完全忽略调试打印输出。 现在我们依靠“良好实践”,但实际上这种实践很难执行,并且仍然存在一些冗余的 CPU 负载。
在 Java 风格中,可以通过一些指定的方法(如 debug()、warning() 等)来解决,这些方法的调用会生成条件性代码。
实际上,这将是将 Log4J 集成到该语言中。
I wanted to ask a new question but looks like it fits here. I agree that having a full-blown preprocessor may be too much for Java. There is one clear need that is covered by precoprocessor in C world and not covered at all in Java world:
I want debug printouts being completely ignored by compiler depending on debug level. Now we rely on "good practice" but in practice this practice is hard to enforce and still some redundant CPU load remains.
In Java style that could be solved by having some designated methods like debug(), warning() etc. for which calls the code is generated conditionality.
Actually that would be about a bit of integration of Log4J into the language.