我被要求进行代码审查并报告在我们的新产品中添加新功能的可行性,这是我迄今尚未亲自处理过的新产品.我知道很容易挑选别人的代码,但我会说它的形状很糟糕(尽量尽可能客观).我的代码审查中的一些亮点:
滥用线程: QueueUserWorkItem
和线程一般使用很多,而线程池代表有无信息的名称,如PoolStart
和PoolStart2
.线程之间也缺乏适当的同步,特别是访问除UI线程之外的线程上的UI对象.
幻数和魔术字符串:代码中定义了一些Const
和Enum
它们,但是大部分代码都依赖于字面值.
全局变量:许多变量被声明为全局变量,可能会也可能不会被初始化,具体取决于所遵循的代码路径以及发生的顺序.当代码也在线程之间跳转时,这会变得非常混乱.
编译器警告:主解决方案文件包含500多个警告,总数不为我所知.我从Visual Studio收到警告,它无法再显示警告.
半完成的课程:代码已经处理并添加到这里和那里,我认为这导致人们忘记了之前所做的事情,所以有一些看似半完成的类和空的存根.
未在此处发明:该产品复制了其他产品使用的公共库中已存在的功能,例如数据访问助手,错误记录助手和用户界面助手.
关注点分离:当我们阅读有关典型的"UI - >业务层 - >数据访问层"3层架构时,我认为有人正在颠覆这本书.在此代码库中,UI层直接访问数据库,因为业务层部分实现但由于未充分充实而被忽略,并且数据访问层控制UI层.大多数低级数据库和网络方法都在对主窗体的全局引用上运行,并直接显示,隐藏和修改窗体.在实际使用相当薄的业务层的情况下,它也倾向于直接控制UI.大多数这种低级代码也使用MessageBox.Show
发生异常时显示错误消息,并且大多数吞下原始异常.这当然使得在尝试重构之前开始编写单元测试来验证程序的功能有点复杂.
我只是在这里表面上看,但我的问题很简单:花时间重构现有的代码库,一次关注一个问题,或者你会考虑从头开始重写整个事情是否更有意义?
编辑:为了澄清一点,我们确实有项目的原始要求,这就是为什么重新开始可能是一个选项.另一种表达我的问题的方法是:代码能否达到维持成本的成本大于倾销和重新开始的成本?
在没有任何违法行为的情况下,从头开始重写代码库的决定是新手软件开发人员常见且严重的管理错误.
有许多缺点需要警惕.
重写阻止新功能被冷开发数月/年.很少,如果有任何公司能够承受这么久的停滞不前.
大多数开发时间表都难以确定.这次重写也不例外.现在,通过延迟开发来扩大前一点.
通过痛苦经历在现有代码库中修复的错误将被重新引入.Joel Spolsky在本文中有更多的例子.
第二系统效应成为受害者的危险- 总而言之,"人们在尝试做他们所做的所有事情之前只设计了一次的东西"上次没有做到的事情,将项目加载到所有他们在制作第一版时推迟的事情,即使其中大部分也应该在第二版推迟."
一旦这个昂贵,繁重的重写完成,下一个继承新代码库的团队可能会使用相同的借口进行另一次重写.程序员讨厌学习别人的代码.没有人写出完美的代码,因为完美是如此主观.找到任何真实世界的应用程序,我可以给你一个诅咒的起诉书和从头开始重写的理由.
无论你是否最终从头开始重写,现在开始重构阶段是一个很好的方式,让我们真正坐下来理解问题,以便重写将更加顺利,如果真正要求,以及给现有的代码库一个诚实的外观真的看看是否需要重写.
要实际废弃并重新开始?
如果当前代码没有按照您的意愿执行,那么更改成本会很高.
我相信现在有人会把Joel关于网景的文章与Netscape联系起来扔掉他们的代码,以及它是如何可怕和一个巨大的错误.我不想详细讨论它,但是如果你链接那篇文章,在你这样做之前,请考虑一下:IE引擎,允许MS快速发布IE 4,5,5.5和6的引擎继承,完全摧毁Netscape的IE引擎......这是新的.Trident在放弃IE 3引擎后是一个新引擎,因为它没有为他们未来的开发工作提供合适的基础.MS做了Joel说你绝对不能做的事情,这是因为MS这样做是为了让他们拥有一个允许他们完全消除Netscape的浏览器.所以,请...在你联系Joel并说"哦,你永远不应该这样做,这是一个可怕的想法"之前,只是默想一下这个想法.
我发现有用的经验法则是,如果给出一个代码库,如果我必须重写超过25%的代码以使其工作或根据新的要求修改它,你也可以重写从头开始.
原因是你到目前为止只能修补一段代码; 超过一定程度,它会更快完成.
有一个潜在的假设,即你有一个机制(例如彻底的单元和/或系统测试),它会告诉你重写的版本是否与原版在功能上相同(需要的地方).
如果需要更多的时间来阅读和理解代码(如果可能的话),那么就要重写整个应用程序,我会说废弃并重新开始.
要非常小心:
你确定你不只是懒惰而且不打扰阅读代码
与其他人制作的垃圾相比,你是否会对你所写的优秀代码感到傲慢.
记住经过测试的工作代码比想象中的编写代码更有价值
用我们主持的主人和霸主的话来说,乔尔 - 你永远不应该做的事情,
放弃工作代码并不总是错的 - 但你必须确定原因.
我看到一个应用程序在引入生产后的两年内重新构建,其他应用程序用不同的技术重写(一个是C++ - 现在是Java).在我看来,这两项努力都没有成功.
我更喜欢更糟糕的软件进化方法.如果您可以"组件化"您的旧应用程序,以便您可以引入新的要求并与旧代码接口,那么您可以放松自己进入新环境,而无需"出售"零价值(从商业角度)投资重写.
建议的方法 - 为您希望与之接口的功能编写单元测试1)确保代码按预期运行; 2)为您可能希望在旧基础上执行的任何重构提供安全网.
糟糕的代码是常态.我认为IT因为支持重写/重新架构等而从业务中获得了不好的说法.他们付钱并"信任"我们(作为一个行业)提供可靠,可扩展的代码.遗憾的是,业务压力经常导致快捷方式导致代码无法维护.有时它是糟糕的程序员......有时是糟糕的情况.
要回答你的改写问题...代码维护成本可能超过重写成本...答案显然是肯定的.但是,我没有在你的例子中看到任何东西,这让我相信这是你的情况.我认为这些问题可以通过测试和重构来解决.
就业务价值而言,我认为由于代码的内部状态,可以为重写提供真实案例极为罕见.如果产品面向客户并且目前正在生效并带来资金(即不是已封存或未发布的产品),请考虑:
您已经有客户使用它.他们熟悉它,并且可能围绕它建立了一些自己的资产.(与之相关的其他系统;基于它的产品;他们必须改变的流程;他们可能需要重新培训的员工).所有这些都花费了客户的钱.
从长远来看,重写它可能比进行困难的更改和修复成本更低.但你无法量化,除非你的应用程序不比Hello World复杂.重写意味着重新测试和重新部署,可能是您客户的升级途径.
谁说重写会更好?你能诚实地说你的公司现在正在编写闪亮的代码吗?将原始代码转换为意大利面的做法是否得到纠正?(即使主要罪魁祸首是单一开发人员,他的同事和管理层在哪里,通过评论,测试等确保质量?)
就技术原因而言,如果原始版本有一些技术依赖性已经成为问题,我建议可能是重大改写的时候了.例如,现在不支持的第三方依赖等
总的来说,我认为最明智的举措是逐个重构(非常小的部分,如果真的那么糟糕),并逐步改进内部架构而不是一个大的下降.
关于这一个的两个思路:你有原始要求吗?您是否确信原始要求是准确的?测试计划或单元测试怎么样?如果你有这些东西,它可能会更容易.
戴上我的客户帽,系统是否正常工作还是不稳定?如果你有一些不稳定的东西你就会有改变的论据; 否则你最好一点一点地重构它.
我认为沙子中的线条是基本维护时间为25% - 比它应该长50%.有一段时间,维护遗留代码变得过于昂贵.许多因素有助于最终决定.时间和成本是我认为最重要的因素.
如果有干净的界面并且您可以清晰地描绘模块边界,那么可能值得逐个模块或逐层重构它,以便您可以将现有客户迁移到更清晰,更稳定的代码库,并且随着时间的推移,您可以重构每个模块,你将重写一切.
但是,基于代码审查,听起来不会有任何干净的界限.
我想知道那些投票支持报废和重新开始的人是否已经成功地重构了一个大型项目,或者至少看到一个他们认为可以使用重构的状况不佳的大型项目?
如果有的话,我在相反的方面犯错:我看过4个大项目,这是一个混乱,我提倡重构而不是重写.在一对夫妇中,只剩下一行原始代码,并且主要界面在很大程度上发生了变化,但是这个过程从未涉及整个项目无法像原来那样运行,超过一周.(并且行李箱顶部从未被打破).
也许一个项目存在严重破坏,试图重构它将注定要失败,或者我重构的先前项目之一可能会更好地通过"干净的重写"服务,但我不确定我知道如何识别它.