避免或接受破坏编辑和继续的C#构造?

时间:2010-10-05 15:57:57

标签: c# debugging lambda anonymous-methods edit-and-continue

我开发并维护一个用C#2.0编写的大型(500k + LOC)WinForms应用程序。它是多用户,目前部署在大约15台机器上。该系统的开发正在进行中(可以被认为是一个永久的测试版),并且很少有人能够保护用户免受可能在每周构建中引入的潜在新错误的影响。

出于这个原因,我发现自己变得非常依赖于调试器中的编辑和继续。它不仅有助于寻找漏洞和修复漏洞,而且在某些情况下还可以进行持续开发。我发现能够在正在运行的应用程序的上下文中执行新编写的代码非常有价值 - 不需要重新编译并为新代码添加特定的入口点(必须添加虚拟菜单选项,按钮等等)应用程序并记住在下一个生产版本之前删除它们 - 所有内容都可以在不停止流程的情况下实时进行测试和测试。

我坚持编辑并继续这么高的考虑,我积极地编写代码以完全兼容它。例如,我避免:

  • 匿名方法和内联代理(除非完全无法重写)
  • 通用方法(稳定,不变的实用程序代码除外)
  • 以'任何CPU'为目标进行项目(即从不以64位执行)
  • 在声明点初始化字段(初始化移动到构造函数)
  • 编写使用yield的枚举器块(实用程序代码除外)

现在,我完全清楚C#3和4中的新语言功能在很大程度上与编辑和继续(lambda表达式,LINQ等)不兼容。这是我拒绝将项目迁移到更新版本的框架的原因之一。

我的问题是,避免使用这些更高级的构造以支持非常非常容易调试的代码是否是一种好习惯?这种发展是否合法,还是浪费?另外,重要的是,这些构造中的任何一个(lambda表达式,匿名方法等)是否会产生性能/内存开销,这些代码可以避免编写良好,编辑和继续兼容的代码? ...或者做C#编译器的内部工作是否使这些高级构造比手动编写的“扩展”代码运行得更快?

10 个答案:

答案 0 :(得分:21)

不想听起来陈腐 - 最好是编写单元/集成测试而不是依赖Edit-Continue。

这样,你花费了一次力,而且每隔一段时间都是'免费的'......

现在我不建议你追溯性地为你的所有代码编写单位;相反,每次你必须修复一个错误,首先要编写一个测试(或更常见的多个测试)来证明修复。

正如@Dave Swersky在评论中提到的那样,Mchael Feathers的书,Working Effectively with Legacy Code是一个很好的资源(在你写这篇文章5分钟之后,这是遗产吗?)

所以是的,我认为避免新的C#结构有利于允许编辑和继续是错误的;但我也认为仅仅为了它而接受新结构是错误的,特别是如果它们导致更难理解代码。

答案 1 :(得分:6)

我爱'编辑并继续'。我发现它是交互式开发/调试的一个巨大推动因素,当它不起作用时我也觉得很烦人。

如果“编辑并继续”有助于您的开发方法,那么请务必做出选​​择以促进它的发展,并牢记您放弃的价值。

我的一个烦恼是,使用lambda表达式编辑函数中的任何内容都会破坏“编辑并继续”。如果我绊倒它,我可能会写出lambda表达式。我正在使用lambda表达式。我可以用它们更快地做一些事情,但是如果我最后把它们写出来,它们就不会节省我的时间。

在我的情况下,我不想使用lambda表达式。如果它们妨碍我将它们包装在一个函数中,以便我可以“编辑并继续”使用它们的代码。如果他们是无偿的,我可以写出来。

您的方法不需要是黑白的。

答案 2 :(得分:4)

想要澄清这些事情

  

最好避免使用这些更高级的结构来支持非常非常容易调试的代码?

编辑并继续不是真正的调试,它正在开发中。我之所以这样区别,是因为新的C#功能非常可调试。该语言的每个版本都增加了对新语言功能的调试支持,以使它们尽可能简单地进行调试。

  

可以在不停止流程的情况下实时检查和测试所有内容。

这句话具有误导性。 “编辑”和“继续”可以验证更改是否修复了一个非常具体的问题。验证更改是否正确并且不会破坏许多其他问题要困难得多。即因为编辑和继续不修改磁盘上的二进制文件,因此不允许单元测试等项目。

总的来说,虽然是的,我认为避免新的C#结构有利于允许编辑和继续是错误的。编辑和继续是一个很棒的功能(当我第一次在C ++时代遇到它时真的很喜欢它)。但它作为生产服务器助手的价值并不能弥补新的C#功能恕我直言的生产收益。

答案 3 :(得分:4)

  

我的问题是,避免使用这些更高级的构造以支持非常非常容易调试的代码是否是一种好习惯

我认为,只要你强迫自己编写代码:

  1. 表达力不强
  2. 重复(避免使用通用方法)
  3. 非便携式(从不调试和测试64位??!?!?)
  4. 您的总体维护成本增加远远超过调试器中“编辑并继续”功能的丢失。

    我会尽可能编写最好的代码,而不是使IDE的功能有效的代码。

答案 4 :(得分:2)

虽然您的方法没有任何本质上的错误,但它确实限制了IDE理解的表达量。您的代码反映了功能,而不是语言的功能,因此您在开发世界中的整体价值会降低,因为您将不再学习其他提高生产力的技术。避免使用编辑并继续使用LINQ对于我个人来说感觉就像是一个巨大的机会成本,但矛盾的是你必须先获得一些使用它的经验才能有这种感觉。

此外,正如其他答案中所提到的,对代码进行单元测试会删除 need 以始终运行整个应用程序,从而以不同的方式解决您的困境。如果您无法在IDE中右键单击并仅测试您关心的3行代码,那么您在开发过程中就会做太多工作。

答案 5 :(得分:1)

您应该真正引入持续集成,这可以帮助您在部署软件之前查找和消除错误。特别是大项目(我认为500k相当大)需要某种验证。

http://www.codinghorror.com/blog/2006/02/revisiting-edit-and-continue.html

关于具体问题:不要避免使用这些结构而不依赖于您疯狂的调试技巧 - 尽量避免错误(在已部署的软件中)。改为编写单元测试。

答案 6 :(得分:1)

我还参与了非常大的永久性测试项目。

我使用匿名方法和内联委托来保留一些相对简单的使用位置 - 一个逻辑接近其唯一的使用位置。

我使用泛型方法和类来重用和可靠。

我已经在构造函数中初始化了尽可能完整的类,以维护类不变量并消除由无效状态中的对象引起的错误的可能性。

我使用了枚举器块来减少创建枚举类所需的代码量到几行。

所有这些都有助于在可靠的状态下维持一个快速变化的大型项目。

如果我无法编辑并继续,我会进行编辑并重新开始。这大部分时间花费我几秒钟,在讨厌的情况下花费几分钟。值得一提的是,通过重用来提高代码和提高可靠性的能力使我节省了很多时间。

我会做很多工作,以便更容易找到错误,但不会更容易发现错误。

答案 7 :(得分:0)

您可以尝试Test Driven Development。我发现完全避免使用调试器非常有用。您从一个新测试(例如单元测试)开始,然后您只运行此单元测试来检查您的开发 - 您不需要整个应用程序一直运行。这意味着您不需要编辑并继续!

我知道TDD是当前的热门话题,但它确实适合我。如果我需要使用调试器,我将其视为个人失败:)

答案 8 :(得分:0)

依靠编辑和续。听起来好像花在设计新功能上的时间很少,更不用说单元测试了。我发现这很糟糕,因为你可能最终会进行大量的调试和修复bug,有时你的错误修复会导致更多错误,对吗?

然而,很难判断您是否应该使用语言功能,因为这还取决于许多其他因素:项目需求,发布截止日期,团队技能,重构后代码可管理性成本,仅举几例。

希望这有帮助!

答案 9 :(得分:0)

您似乎遇到的问题是:

  

重建你的应用程序需要很长时间,   再次启动并找到位   您正在处理的UI。

正如大家所说,单元测试将有助于减少运行应用程序以查找/修复无UI代码上的错误的次数;但是它们对UI的布局等问题没有帮助。

在过去,我编写了一个测试应用程序,它将快速加载我正在处理的UI并用虚拟数据填充它,以减少周期时间。

将任何UI代码分离到可以使用单元测试进行测试的其他类中,将允许您在这些类中使用所有C#构造。然后,您可以将UI代码中使用的构造限制为自己。

当我开始编写大量单元测试时,我对“编辑并继续”的使用失败了,除了UI代码之外,我几乎不能使用它。