需要引用:使用预处理器是错误的OO实践我认为当使用像Spring这样的IoC容器时,如果相应地编程,组件可以很容易地配置。在这种情况下,IoC容器可以设置一个有效的IsUsingNetwork,或者如果“使用网络”实现的行为不同,则应该实现并注入该接口的另一个实现(例如:IService、ServiceImplementation、NetworkingServiceImplementation)。有人可以提供OO-Gurus的引述或一本书中的参考,该书基本上是“如果您尝试配置应通过IoC容器配置的行为,则预处理器的使用是不好的OO实践”?我需要这种引用来说服同事重构...编辑:我知道并同意在编译期间使用预处理器指令更改目标平台特定代码是好的,这就是预处理器指令的用途。但是,我认为对于设计良好且可测试的类和组件,应该使用运行时配置而不是编译时配置。换句话说:超出其意图使用#defines和#ifs将导致难以测试代码和设计不良的类。有没有人按照这些思路阅读并可以给我这样的东西,我可以参考?HenrySpencer写了一篇名为#ifdefConsideredHarmful的论文。此外,BjarneStroustrup本人在他的“C++的设计与演化”一书的第18章中表达了对预处理器的使用的不满,并希望完全消除它。然而,Stroustrup也认识到需要#ifdef指令和条件编译,并继续指出C++中没有它也有很好的选择。最后,PeteGoodliffe在他的书“CodeCraft:TheWritingofWritingExcellentCode”的第13章中给出了一个示例,即使用于其最初目的,#ifdef也会使您的代码混乱。希望这可以帮助。但是,如果您的同事首先不听取合理的论点,我怀疑引用书籍是否有助于说服他们;)恕我直言,您说的是C和C++,而不是一般的OO实践。而且C不是面向对象的。在这两种语言中,预处理器实际上都是有用的。只要正确使用它。我认为这个答案属于C++FAQ:[29.8]Areyousayingthepreprocessorisevil?.是的,这正是我所说的:预处理器是邪恶的。每个#define宏有效地在每个源文件和每个作用域中创建一个新关键字,直到符号为#undefd。预处理器允许您创建始终被替换的#define符号,无论它出现在{...}范围内。有时我们需要预处理器,例如每个头文件中的#ifndef/#define包装器,但应尽可能避免。“邪恶”并不意味着“从不使用”。您有时会使用邪恶的东西,尤其是当它们是“两害相权取其轻”时。但它们仍然是邪恶的:-)我希望这个来源足够权威:-)C#中的预处理器指令具有非常明确的定义和实际用例。您具体谈论的那些称为条件指令,用于控制代码的哪些部分被编译,哪些不被编译。不编译部分代码与控制对象图通过IoC连接的方式之间存在非常重要的区别。让我们看一个真实世界的例子:XNA。当您开发计划在Windows和XBox上部署的XNA游戏时360,您的解决方案通常至少有两个可在IDE中切换的平台。它们之间会有一些区别,但其中一个是XBox360平台会定义条件符号XBOX360,你可以在源代码中使用以下成语:#if(XBOX360)//一些XBOX360特定的代码在这里#else//一些Windows特定的代码在这里#endif当然,您可以使用Strategy设计模式和通过IoC实例化来控制这些差异,但条件编译至少提供三个主要优点:您不提供不需要的代码。您可以在该代码的正确上下文中看到两个平台特定于平台的代码之间的差异。没有开销。适当的代码编译,另一个不编译,仅此而已。“预处理器是邪恶的化身,是地球上所有痛苦的根源”-Robert(OO大师)预处理器#ifdefs的一个问题是它们有效地复制了编译版本的数量,理论上,你应该测试它们,这样你就可以说你提供的代码是正确的。#ifdefDEBUG//...#else//...好的,现在我可以生成“调试”构建和“发布”构建。这对我来说没问题,我一直这样做,因为我有只在调试版本中执行的断言和调试跟踪。如果有人过来写(现实生活中的例子)#ifdefMANUALLY_MANAGED_MEMORY...他们写了一个宠物优化并且他们传播到四个或五个不同的类,然后你突然有四种可能的方法来编译你的代码。如果您只有一个其他#ifdef依赖代码,那么您将有八个可能的构建,更令人不安的是,其中四个可能是发布构建。当然,if()的运行时循环和其他任何创建的分支都必须测试——但我发现很难保证对配置的每个编译时更改都是正确的。这就是为什么我认为,作为一种策略,除了Debug/Release构建之外的所有#ifdef都应该是临时的,即你在你的开发代码中做一个实验,然后你很快决定它是保持一种方式还是另一种方式。BjarneStroustrap在这里提供了他的答案(一般来说,不是IoC特定的)那么,使用宏有什么问题?(摘录)宏不遵守C++范围和类型规则。这通常是微妙和不那么微妙的问题的原因。因此,C++提供了更适合C++其余部分的替代方案,例如内联函数、模板和名称空间。在C#中对预处理的支持非常少……接近于无用。那是邪恶的吗?预处理器与OO有什么关系吗?当然是为了构建配置。例如,我的应用程序有精简版和专业版。我可能想在lite上排除一些代码,而不得不求助于构建一个非常相似的代码版本。我可能不想发送精简版,它是具有不同运行时标志的专业版。TonyIMO区分#if和#define很重要。两者都很有用,而且都可能被过度使用。我的经验是#define比#if更容易被过度使用。我花了10多年时间从事C和C++编程。在我做的项目中(DOS/Unix/Macintosh/Windows的商业软件),我们主要使用#if和#define来处理代码的可移植性问题。我花了足够多的时间在C++/MFC上学习如何厌恶过度使用#define——我相信大约在1996年的MFC中就是这种情况。然后我花了7年多的时间从事Java项目。我不能说我错过了预处理器(尽管我当然错过了Java当时没有的枚举类型和模板/泛型之类的东西)。自2003年以来,我一直在使用C#。我们已经大量使用#if和[condition("DEBUG")]进行调试构建-但#if只是一种更方便、更有效的方式来完成同样的事情。我们用Java做的事情。展望未来,我们已经开始为Silverlight准备我们的核心引擎。虽然我们所做的一切都可以在没有#if的情况下完成,但使用#if可以减少工作量,这意味着我们可以花更多的时间来添加客户要求的功能。例如,我们有一个值类,它封装了一个系统颜色,用于存储在我们的核心引擎中。下面是前几行代码。由于System.Drawing.Color和System.Windows.Media.Color之间的相似性,顶部的#define为我们提供了普通.NET和Silverlight中的许多功能,而无需重复代码:usingSystem;使用System.Collections.Generic;使用系统文本;使用系统诊断;#ifSILVERLIGHT使用SystemColor=System.Windows.Media.Color;#else使用SystemColor=System.Drawing.Color;#endifnamespaceSpreadsheetGear.Drawing{//////表示SpreadsheetGearAPI中的颜色,并提供与System.Drawing.Color和/或System.Windows.Media.Color之间的隐式转换运算符。///publicstructColor{publicoverridestringToString(){//返回字符串.Format("Color({0},{1},{2})",R,G,B);返回_color.ToString();}publicoverrideboolEquals(objectobj){return(objisColor&&(this==(Color)obj))||(obj是SystemColor&&(_color==(SystemColor)obj));}...对我来说,底线是有许多语言功能可能会被过度使用,但这还不足以弃用这些功能或制定严格的规则禁止使用它们。我必须说,在使用Java编程了很长时间之后转向C#帮助我理解了这一点,因为Microsoft(AndersHejlsberg)更愿意提供可能对大学教授没有吸引力的功能,但它让我在工作中更有效率并最终被允许我可以在任何人有发货日期的有限时间内构建一个更好的小部件。使用#if而不是IoC或其他一些机制来根据配置控制不同的功能可能会违反单一职责原则,这是“现代”OO设计的关键。这是一个关于面向对象设计原则的系列文章。由于#if的不同部分中的部分根据定义引用系统的不同方面,因此您现在可以使用#if将至少两个不同组件的实现细节耦合到代码的依赖链中。通过重构这些关注点,您创建了一个类,假设它是完整的和经过测试的,将永远不需要打开,除非公共代码被破坏。在您的原始情况下,您需要记住#if的存在,并在三个组件中的任何一个随时间发生变化并产生可能的副作用时考虑它。在c#/VB.NET中,我不会说它是邪恶的。例如,在调试Windows服务时,我将以下内容放在Main中,以便在调试模式下,我可以将该服务作为应用程序运行。__SharedSubMain()#IfDEBUGThen'将其作为应用程序启动。Dim_serviceAsNewDispatchService()_service.ServiceStartupMethod(Nothing)System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite)#Else'作为服务运行。'DimServicesToRun()AsSystem.ServiceProcess.ServiceBaseServicesToRun=NewSystem.ServiceProcess.ServiceBase(){NewDispatchService}System.ServiceProcess.ServiceBase.Run(ServicesToRun)#EndIfEndSub这是配置应用程序的行为是当然不是邪恶的。至少,它不像尝试调试服务启动例程那样邪恶。如果我误读了您的OP,请纠正我,但是当一个简单的布尔值就足够时,您似乎在抱怨其他人使用预处理器。如果是这样的话,不要诅咒预处理器,诅咒那些以这种方式使用它们的人。编辑:回复:第一条评论。我不知道这个例子在这里有什么关系。问题是预处理器被滥用了,而不是它是邪恶的。我再举一个例子。我们有一个应用程序在启动时在客户端和服务器之间进行版本检查。在开发中,我们经常有不同的版本,不想做版本检查。是邪恶的吗?我想我想说的是预处理器即使改变了程序行为也不是邪恶的。问题是有人滥用它。这么说有什么不对?你为什么要尝试触发语言功能?稍后编辑:FWIW:几年来我没有使用过预处理器指令。我确实将Environment.UserInteractive与特定的arg集(“-c”=Console)一起使用,并使用了我从此处某处获得的巧妙技巧来阻止应用程序但仍等待用户输入。SharedSubMain(argsasstring[])If(Environment.UserInteractive=True)Then'将其作为应用程序启动。If(args.Length>0AndAlsoargs[0].Equals("-c"))Then'通常是一个“DeriveCommand”函数,它返回一个枚举或类似的东西DimisRunningAsBoolean=trueDim_serviceAsNewDispatchService()_service.ServiceStartupMethod(Nothing)Console.WriteLine("PressESCtostoprunning")While(IsRunning)While(Not(Console.KeyAvailableAndAlso(Console.ReadKey(true).Key.Equals(ConsoleKey.Escape)OrElseConsole.ReadKey(true).Key.Equals(ConsoleKey.R))))Thread.Sleep(1)EndWhileConsole.WriteLine()Console.WriteLine("PressESCtocontinuerunningoranyotherkeytocontinueshutdown.")Dimkey=控制台.ReadKey();if(key.Key.Equals(ConsoleKey.Escape)){Console.WriteLine("PressESCtoloadshutdownmenu.")Continue}isRunning=falseEndWhile_service.ServiceStopMethod()ElseThrowNewArgumentException("Dontbeadouble唱首歌,这需要一个用于报告信息和错误的控制台”)EndIfElse'作为服务运行。'DimServicesToRun()AsSystem.ServiceProcess.ServiceBaseServicesToRun=NewSystem.ServiceProcess.ServiceBase(){NewDispatchService}System.ServiceProcess。ServiceBase.Run(ServicesToRun)EndIfEndSubPreprocessor代码注入是对数据库的编译器触发器,很容易找到关于触发器的这种断言。我主要想到内联短表达式的#define,因为它节省了函数调用的开销。换句话说,这是过早的优化。快速告诉您的同事:如果您在此类语句中使用符号,预处理器会破坏数学语句中的运算符优先级。我没有关于预处理器指令使用的主要声明,并且不能添加对著名指令的引用。但我想给你一个链接,指向一个在MicrosoftMSDN上找到的简单示例。#defineA#undefBclassC{#ifAvoidF(){}#elsevoidG(){}#endif#ifBvoidH(){}#elsevoidI(){}#endif}这将导致简单的类C{voidF(){}voidI(){}}而我不不要认为它的可读性很强,因为你必须查看顶部才能看到此时到底定义了什么。如果您在其他地方定义它,这会变得更加复杂。对我来说,创建不同的实现并将它们注入调用者似乎更简单,而不是切换定义以创建“新”类定义。(...所以我理解为什么将预处理器定义的使用与IoC的使用进行比较)。除了使用预处理器指令的代码的可读性很差之外,我很少使用预处理器定义,因为它们会增加测试代码的复杂性,因为它们会导致多条路径(但这也是注入多个实现的IoC-Container问题之外的情况)。Microsoft自己在win32api中使用了很多预处理器定义,您可能知道/记得char和w_char方法调用之间的丑陋切换。也许你不应该说“不要使用它”。告诉他们“如何使用”和“何时使用”。如果您提出好的(更好理解的)替代方案并且可以描述使用预处理器定义/makros的风险,我认为每个人都会同意您的看法。不需要大师……只要一个大师。?我想问一个新问题,但它似乎很适合这里。?我同意拥有一个完整的预处理器对于Java来说可能有点矫枉过正。C世界中的预处理器有一个在Java世界中根本没有涵盖的明确需求:我希望编译器根据调试级别完全忽略调试打印输出。现在我们依靠“良好实践”,但在实践中这很难实现并且仍然存在一些冗余的CPU负载。在Java风格中,这可以通过使用一些指定的方法来解决,例如调用代码生成条件的debug()、warning()等。实际上,它将是Log4J与该语言的某种集成。以上是C#学习教程:需要引用:预处理器的使用不好。不代表立场,如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处:
