20

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

出于这个原因,除其他外,我发现自己变得非常依赖调试器中的编辑并继续。它不仅有助于寻找错误和修复错误,而且在某些情况下也有助于正在进行的开发。我发现能够从正在运行的应用程序的上下文中执行新编写的代码非常有价值——无需重新编译并向新代码添加特定的入口点(必须添加虚拟菜单选项、按钮等)应用程序并记住在下一次生产构建之前删除它们) - 一切都可以在不停止过程的情况下实时尝试和测试。

我非常重视编辑并继续,以至于我积极编写代码以完全兼容它。例如,我避免:

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

现在,我完全意识到 C# 3 和 4 中的新语言特性在很大程度上与编辑并继续(lambda 表达式、LINQ 等)不兼容。这是我拒绝将项目升级到更新版本的框架的原因之一。

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

4

10 回答 10

21

不想听起来陈词滥调 - 编写单元/集成测试而不是依赖 Edit-Continue 是一种好习惯。

这样,您只需花费一次精力,而每隔一次都是“免费的”……

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

正如@Dave Swersky 在评论中提到的那样,Mchael Feathers 的书,有效地使用遗留代码是一个很好的资源(它是在你写完它 5 分钟后遗留下来的,对吧?)

所以是的,我认为避免使用新的 C# 结构来支持编辑和继续是错误的;但我也认为仅仅为了新结构而拥抱新结构是错误的,尤其是当它们导致更难理解的代码时。

于 2010-10-05T16:00:44.313 回答
6

我喜欢“编辑并继续”。我发现它是交互式开发/调试的巨大推动力,当它不起作用时我也觉得它很烦人。

如果“编辑并继续”有助于您的开发方法,那么一定要做出选择来促进它,记住您所放弃的东西的价值。

我最讨厌的一个问题是使用 lambda 表达式编辑函数中的任何内容都会破坏“编辑并继续”。如果我绊倒它,我可能会写出 lambda 表达式。我对 lambda 表达式持怀疑态度。我可以用它们更快地做一些事情,但如果我稍后把它们写出来,它们并不能节省我的时间。

就我而言,当我真的不需要时,我会避免使用 lambda 表达式。如果它们妨碍我,我可以将它们包装在一个函数中,以便我可以“编辑并继续”使用它们的代码。如果他们是无偿的,我可以写出来。

您的方法不必非黑即白。

于 2012-06-06T14:51:49.690 回答
4

想稍微澄清一下这些事情

避免使用这些更高级的结构来支持非常非常容易调试的代码是一种好习惯吗?

编辑并继续不是真正的调试,它正在开发。我做出这种区分是因为新的 C# 特性非常易于调试。该语言的每个版本都添加了对新语言功能的调试支持,以使它们尽可能易于调试。

一切都可以在不停止过程的情况下实时试用和测试。

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

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

于 2010-10-05T16:05:15.977 回答
4

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

我会争辩说,任何时候你强迫自己编写代码:

  1. 较少表现力
  2. 更长
  3. 重复(避免通用方法)
  4. 不可移植(从不调试和测试 64 位??!?!?)

您增加的整体维护成本远远超过调试器中“编辑并继续”功能的损失。

我会尽可能写出最好的代码,而不是让你的 IDE 的某个功能发挥作用的代码。

于 2010-10-05T16:07:14.297 回答
2

虽然您的方法本质上没有任何问题,但它确实将您限制在 IDE 所理解的表达量上。你的代码反映了它的能力,而不是语言的能力,因此你在开发世界中的整体价值降低了,因为你阻碍了自己学习其他提高生产力的技术。避免使用 LINQ 以支持 Edit-and-Continue 对我个人而言感觉像是巨大的机会成本,但矛盾的是,您必须先获得一些经验,然后才能有这种感觉。

此外,正如其他答案中提到的那样,对代码进行单元测试无需一直运行整个应用程序,从而以不同的方式解决您的困境。如果您不能在 IDE 中右键单击并仅测试您关心的 3 行代码,那么您已经在开发过程中做了太多工作。

于 2010-10-05T16:16:31.717 回答
1

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

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

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

于 2010-10-05T16:06:39.643 回答
1

我也参与过非常大的永久性测试项目。

我已经使用匿名方法和内联委托来保持一些相对简单的使用逻辑位靠近它们的唯一使用位置。

我使用通用方法和类来实现重用和可靠性。

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

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

所有这些都有助于将快速变化的大型项目保持在可靠状态。

如果我不能编辑并继续,我会编辑并重新开始。这在大多数情况下会花费我几秒钟,在令人讨厌的情况下会花费几分钟。通过重用更好地推理代码的能力和更高的可靠性为我节省了几个小时,这是值得的。

我会做很多事情来使查找错误变得更容易,但如果它也可以更容易地找到错误,我就不会这样做。

于 2010-10-05T16:09:25.713 回答
0

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

我知道 TDD 是当前的流行词,但它确实对我有用。如果我需要使用调试器,我将其视为个人失败:)

于 2010-10-05T16:05:01.813 回答
0

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

但是,很难判断你是否应该使用语言特性,因为这还取决于许多其他因素:项目要求、发布期限、团队技能、重构后的代码可管理性成本等等.

希望这可以帮助!

于 2010-10-05T16:12:32.597 回答
0

您似乎遇到的问题是:

重建您的应用程序、重新启动它并获得您正在处理的 UI 部分需要很长时间。

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

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

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

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

于 2010-10-05T17:54:50.147 回答