这可能是每个人迟早在开发过程中都必须面对的事情。

您已有别人编写的现有代码,并且必须扩展它以在新的要求下工作。 br />
有时很简单,但有时模块具有中等到较高的耦合度以及中等到低的内聚力,因此,当您开始触摸任何东西时,所有内容都会中断。而且,当您再次使用新旧场景时,您不会感觉到它已正确修复。

一种方法是编写测试,但实际上,在我所看到的所有情况下,几乎是不可能的(依赖于GUI,缺少规范,线程,复杂的依赖关系和层次结构,最后期限等)。

所以一切都归结为良好的牛仔编码方法。但是我拒绝相信没有其他系统的方法可以使一切变得更容易。

有人知道更好的方法吗,或者在这种情况下应该使用的方法学名称吗?

评论

您可以访问该代码的原始作者吗?如果是这样,您可以尝试从中获取一些见识。

查阅迈克尔·费瑟斯(Michael Feathers)的书《有效使用旧版代码》:amazon.com/gp/aw/d/0131177052

另一本非常好的书是.Net中的Brownfield应用程序开发

#1 楼

首先,有点累,这个网站上的每个人都认为其他人编写的任何东西都是垃圾。

理解代码很困难,诚然,一些不良的编程实践使它变得更加困难,但是,对于任何合理的选择复杂的系统即使了解写得很好的代码,也很难理解所使用的内部结构和惯用语。

系统通常运行二十多年。编程方法,最佳实践,设计理念和时尚每两年发生一次变化,并且程序员以不同的速度选择改进的样式。因此,在2007年被认为是最新技术和出色的代码示例,如今看起来已经过时且古怪。作为一项练习,我建议您挖掘出三年前编写的一些代码,几乎可以保证您会畏缩。

所以首先您需要抑制初始WTF响应。告诉自己,该系统运行良好且足够长,足以使其成为您的问题,因此必须对此有所了解。

尝试掌握原始的编码器样式,使用的惯用法,研究代码中的怪异位,看它们是否属于模式。

如果所需的更改很小,请遵循原始的编码风格,这样,有人只需要习惯就可以领取代码。一组特质。

如果所需的更改很大,并且更改集中在几个功能或模块中,则趁此机会重构这些模块并清理代码。

以上所有这些都不会重构与立即更改请求无关的工作代码。这会花费太多时间,会引入错误,并且您可能会无意中踩上了需要数年时间才能完善的业务规则。您的老板会讨厌您因为缓慢地交付小更改而讨厌您,而您的用户会讨厌您使运行了多年的系统崩溃而没有问题。

评论


+1,尤其是最后一段/句子。如此真实...

–亨氏
2012年2月16日在8:20

“如果所需的更改很小,那么请遵循原始的编码样式,这样,在您只需要习惯一组特质后,有人就可以接听代码。”这是该线程的最佳建议。

– BernardMarx
2012年2月16日在16:18



+1,我想补充一下,在更改代码之前,请先尝试对其进行测试。

– MarkJ
2012年2月16日在18:58

@MarkJ-我同意,获得一套不错的单元测试会极大地帮助您。这可能比听起来要困难得多,因为在进行了一系列增量更改和快速修复之后,除了“按照旧版本进行操作”之外,可能没有准确的功能规范。

–詹姆斯·安德森(James Anderson)
2012年2月17日在2:03

@MarkJ:确切地讲,您如何知道需要测试的每个极端情况,以确保通过“运行”它可以完全覆盖范围。对于一个琐碎的程序或带有简单输入的程序,我接受这种方法可能有效。对于一个真正的程序,我有严重的怀疑。

–mattnz
2012年3月22日,下午1:53

#2 楼

基本上,您可以采用三种方法:




从头开始编写。它可能适用于小型应用程序,但不能用于大型代码库。

请注意,对于中小型代码库,这是不惜一切代价避免的最坏情况。 >
有一个很好的理由,不要从头开始重写:通常希望对旧代码库进行测试,包含使代码在某些情况下可以工作的技巧,等等,并且在重写所有内容时,您将引入一堆已经解决的错误。

在您谈论的情况下,这是一个无效的参数,因为您提到该代码的质量很低:未经测试,缺少规范,从未重构等等。另一方面,请勿只从头重写规则,而必须将其限制为经过质量检查(QA)测试的高质量代码。


使用牛仔编码方法:更改所有内容,然后测试。这可能会失败或成功,主要取决于上下文。



示例1:您正在修改关键业务应用程序。您知道,如果您破坏现有代码中的某些内容,您的客户会生气,并且该客户确信实际产品不仅性能良好,而且编写正确(或者客户只是不明白什么是糟糕的意大利面条代码,编写良好的代码)。

在这里,您不能使用牛仔编码,因为您不得引入新的错误。这意味着您需要使用其他两种方法之一。


示例2:您的客户告诉您,他与以前的开发人员的经历是一场灾难。该产品甚至无法在一半时间内工作,并且完全无法使用。客户了解修改如此糟糕的源代码可能会引入更多错误。

另一方面,牛仔编码可以是一个不错的解决方案,如果您确定这样做会减少与其他两种解决方案相比的总体成本。




首先重构初始代码,添加单元测试(以及在特定情况下所需的其他测试),将其记录下来,等等,然后添加您的修改。当代码不太烂并且有一定价值时,这可能是一个好方法。例如,如果我知道代码是由我的技术熟练的同事在时间压力和低预算约束下编写的,那么我肯定会使用这种方法,因为:


代码的某些部分巧妙地完成了,
您将从重构的代码中学到很多东西。
即使在时间压力和低预算约束下,熟练的开发人员编写的代码仍然是一定水平的代码。这意味着,如果在某种情况下有使应用程序正常工作的技巧,熟练的开发人员仍会在注释中解释为什么添加以下代码行。



我认为没有一个完美的解决方案:比较每个项目或任务的全部三个,评估其各自的成本并根据情况选择最佳的解决方案。

通常,请遵循以下这些方法两个规则:


规则1:编写代码的开发人员越熟练,则必须使用的重构与从头开始重写越多。
规则2:越大是项目,您必须使用更多的重构与重写。

重构比您更熟练的人编写的大型数据库。从头重写一小段代码或由非熟练程序员编写的代码。

评论


+1-但是经验并不总是“好”……我从上级中看到了很棒的代码以及糟糕的代码:P

–比利·奥尼尔(Billy ONeal)
2012年2月16日在2:58

@Billy ONeal:IMO,一个受时间/金钱限制的熟练开发人员,仍然会编写与经验不足的程序员不同级别的代码。在制作原型时(即,我将确定要扔掉的代码,并且必须以最低的成本完成),我将跳过注释,样式,适当的体系结构,适当的模式等,但是我仍然会尽一切可能这是我的专业水平,不需要花费额外的金钱和时间。但是,这是一个非常不同的方面,我很乐意在一个单独的问题中进行讨论。

– Arseni Mourzenko
2012-2-16的3:27

当有经验的人使用终身制而不是熟练的时,您会被两种方式搞砸! 10年作为第一年毕业的事情并不等于10年的技能(经验)!如果您的意思是熟练,那么说熟练的,有经验的人对不同的人意味着不同的事情,并且以负面的方式多次出现。

–user7519
2012-2-16在3:28



Jarod的观点是我的-经验丰富的意思是“已经有很长的编程时间了”,而熟练的意思是“是一个好的程序员”。这两者之间肯定有关联,但是我见过的一些最糟糕的代码是由据说经验丰富的人编写的。

–比利·奥尼尔(Billy ONeal)
2012-2-16的3:35

@Jarrod Roberson:谢谢您的解释,我没有意识到这两个术语之间的含义差异,因此无法有效地使用它们,就像它们是相同的一样。

– Arseni Mourzenko
2012年2月16日在4:14

#3 楼

这基本上就是我的日常工作。在某些情况下,由于您提到的原因而编写小型测试太不切实际了。虽然您应该始终进行严格的测试,但是覆盖所有基础通常是不可能的。我发现最好的方法是向外螺旋。

与其从一开始就进行重写,不如对元素进行深入的了解。如果有长函数,请尝试将它们切成逻辑块。解耦/分离较小的元素,最不复杂的非圆形依赖关系。启动一个您很了解的并行库,然后开始以通用位迁移小函数。

执行此操作,您将慢慢分解系统,并处理越来越大的代码块,这也将变得更加模块化。

文件像毒品一样,您会上瘾。我并不是说要在您的代码中写小说,但是可能是代码没有全部记录在案,因此.NET或Doxygen样式也是如此:函数的功能,输入,输出(以及是否需要全局变量或您正在使用或更改的属性)。

最后一招:定义默认行为。如果整个程序中都有选项或参数,则定义默认值,并实现一种以简单的方式部署所有默认值的方法(我使用一个简单的INI文件完成了此操作。非常有用)。然后,您可以添加其他选项和功能,作为与原始系统绑定的新选项。

评论


+1比我的答案更好,更简洁。并行库也是一个好主意。当我不得不重构遗留代码时,它在某些项目中对我有帮助。

– Arseni Mourzenko
2012年2月16日在4:08

实际上,您对几种有效的方法更加执着。我所拥有的只是指针和技巧...

– MPelletier
2012-2-16在4:44

#4 楼

与“泥浆大球”一起工作时,对我而言,唯一有效的方法是让(更多)测试人员并在项目中安排全面的专业质量保证。


所有方法拥有良好的代码,我就学习文档以学习原理,通读代码(尤其是通过单元测试)来了解设计细节,编写自己的单元测试以涵盖所做的更改改进代码后,单元测试将保持不变或进行较小的更改,文档不需要进行任何重大的返工等。

对于错误的代码,事情似乎完全相反。我从文档中获得的信息大部分都是晦涩且过时的浪费。通读代码只会使我费解的控制流程和反直觉的补丁使我的大脑融化,这些补丁与较早的补丁甚至早在很久以前就已经应用在一起,可以迅速解决设计错误。

对于单元测试,这些做与我在好的代码中使用它们相反的事情,例如破坏合理的更改并未能发现我犯的真正错误。哪一个是痛苦的,但并不令人惊讶-从设计不良的测试单元开始,还会有什么期望?单元测试通常无法工作以保留不良代码,而不是帮助您改进设计-例如,在我最近的维护项目中,我经常删除仅从过时的无意义单元测试中引用的大块代码。顺便说一句,我在编写新代码时会用到这些知识:当我发现我的单元测试往往变得过于复杂/脆弱时,这表明需要修复自己的设计中的某些问题。


现在,回到对我有用的东西上。第一件事不是让管理人员欺骗您是优秀的开发人员,而是您可以处理这种言论。自己思考,认真思考,您会发现,尽管烟雾屏开发得很好,但整个过程还是向质量保证方面倾斜。


首先问自己-他们为什么不扔掉这个泥巴大球?为什么他们要花钱雇用您来维护它?通常,原因是-事情对用户来说效果很好,并且您希望它对用户来说效果很好。他们认为这是一个黑匣子,他们没有研究(糟糕的)代码,他们看到的只是有用的功能。

现在,如果您曾经使用过专业的质量检查,您将很快意识到这只是测试人员倾向于处理软件的方式。黑匣子,功能,从用户角度来看的质量-这些都是质量保证主题和主题。


好吧,如果您到目前为止已读过,您可能会想知道这对我到底有多有效?

第一件事是-每当我完成一些错误修正功能后,我都将问题跟踪器中的各个项目重新分配给质量检查人员以确认我的更改。这样,我无需在繁琐的功能测试上浪费时间,只需快速检查一下候选代码是否已准备好进行质量检查即可。

如果我真的很幸运(不经常发生),我就不必担心了。至于最坏的情况,也没有那么糟。如果我做错了什么,测试人员会在一两天后回到我那里,清楚地说明了到底出了什么问题以及如何重现该错误,并对其回归测试套件进行了扩展,可以轻松地发现此类错误(如果发生)再次。真的不错,你不觉得吗?

QA附带的下一件好事是定期回归测试周期。


某些((一线)经理可以尝试要说服您可以自动完成此任务-别相信它们。使用好的代码可以很好地工作,只是无法使用Big Ball Of Mud来解决问题。

测试执行很麻烦,分析测试结果很费力,维护问题和回归数据库需要大量关注(如果您还打算同时关注开发,则需要更多的关注)。让专业的测试人员为您做到这一点,而不是浪费时间试图成为所有工作的重担,让您的大脑和时间可以进行设计和编码。只需利用良好的旧劳动分工即可。


定期测试周期(imNSho最好是每周一次,尽管每月一次对我也很有效)让您做不可能的事-有效的重构。

有效,我的意思是,如果事情需要说一星期的编码和一星期的修复回归错误,那么您可以预期,您将只花这笔钱-按计划的两周时间-而不会花费额外的时间浪费在破坏大脑的测试执行和复杂的失败分析上-因为测试人员会为您解决问题。除了上面提到的主要好处之外,还有其他一些更小巧但令人愉悦的奖励。


使用测试仪,您可以找人讨论设计。正如我所写,文档和代码在处理“泥泞的大球”时无济于事-如果您独自工作,将产生真空。一个运行您的代码并从不同角度查看它的测试人员将是一个很好的合作伙伴,可以跳出您的想法并探索可能的更改。
通过测试人员,您可以得到帮助您了解设计问题的人员。当只有开发人员抱怨代码质量时,这通常听起来像是闭门造车的主观WTF。 ,而不是每20个新功能中有10个回归错误的component A,通讯突然变成了另一种游戏。

最后但并非最不重要的一点是,专业的质量检查有助于增进您对值得投资进行设计改进的工作量的了解。正如我已经提到的那样,管理并不能很好地理解代码质量的WTF。

但是如果有专业的质量检查人员,他们通常会收集所有数据,那么您可能会想到一些可能会飞速增长的东西通过经理批准的带有魔术印章的秘密单元,我批准了。


去年,我们仅在修复产品中的回归错误上就浪费了大约6个工时。现在,给开发团队一两个星期的时间来分析是否有办法将浪费减少一半呢?




评论


-1:嗯?您的回答很长,让我感到困惑。似乎您建议OP严重依赖质量检查。 //您可以修改和缩短吗?

– Jim G.
2012-3-21的3:35



@吉姆看来您正确理解了我的答案-我建议OP严重依赖质量检查。鉴于似乎没有任何修改。

– gna
2012年3月21日在7:09

这是一个很好的答案。显然,这不是一个人可以独自完成的事情,但是我知道这在某些实际项目中效果很好。可能不是每种情况的解决方案,但是那是什么呢?

– StackExchange令人不安
15年11月23日在11:42

#5 楼


一种方法是编写测试,


我相信您一半回答了您自己的问题。您可以通过多种方式查看此问题,但是根据经验,我发现它似乎总是倾向于应用某些标准敏捷实践,并以与您将要采用的相同方式系统地进行所有操作任何难以解决的技术问题。

我个人就是这样处理这种情况的方式:


开始学习一段时间,做笔记,画图图,并问自己(和/或附近的“当地专家”)很多问题。您需要阅读代码,并对手头的问题有个全面的了解。
确定几个明显的问题区域,并评估将其弄乱的风险。进行一些更改,以查看失败的发生方式,并了解需要花更多的精力才能遵循您的峰值所遵循的路径,或者完全执行其他操作。
选择一个问题并编写测试。使用现有的测试(如果可用),如果没有,请编写新的测试以尽可能保护业务逻辑。
将测试写成失败,然后修复测试通过,然后将代码更改为失败仔细检查,然后恢复您的代码以再次通过。利用最后的“代码失败”步骤,可以逐步检查代码并找出您可能会错过的明显地方。


设计和计划更改,以使您相信代码应该如何可以解决此问题。在这里,您需要返回到需求和规范(假设您有一个),并按照您的用户故事或功能或通常使用的任何方法编写测试。
将新测试编写为匹配您的新故事(又名修订版)
只需几步即可重构代码。首先提取方法,然后再提取类,然后再移动方法和类,然后合并/删除类。始终首先执行所有容易的重构,因为这将使以后更困难的重构更加容易。如果您需要一点帮助或灵感来指导自己的工作,我建议您同时阅读《重构和重构到模式》。

重构时,您的目标是使原始测试通过并保持目标。一旦重构完成,您的新测试就会通过。执行此操作时,您可能会发现自己丢掉了一些原始测试,或者可能很幸运地将新旧测试同时通过了。



我发现这个过程很适合我。这很耗时,有时可能很乏味,但是它有效并且总是给我带来很好的效果。鉴于时间和资源,我从未发现无法改进的代码。事实是,您可能会发现,在您进行初步调查和发现峰值时,您会根据估计所需的时间和精力来确定一些非常困难的问题甚至是制止因素,并且很难估计这种事情可以肯定您可能会发现从头开始会更好,或者您会发现该代码对于您所在的公司而言具有巨大的商业价值。给出最佳估计以完成工作,如果您对问题域有充分的​​了解,请给出最佳估计以重新开始工作。向您的老板介绍一些案例和方案,然后让管理层决定将钱花在哪里。这样可以消除热量,并将热量牢固地带回去。

就像我说的那样,这种方法对我有用,对您来说可能不一定完全一样。您可能希望调整该过程,或者着眼于以另一种方式解决问题。关键是,您只能通过横向思考而不将代码混乱视为简单的失败原因来解决这些难题。而是将其视为困难的问题和挑战,对于任何问题,总有系统的方法来解决。您只需要找到适合您的方法即可。就个人而言,当我面临这样的挑战时,一旦我赢得了一些胜利,我就为将设计不佳的应用程序变成精美的产品感到非常自豪。

#6 楼

我从事复杂代码库已有一年多的时间了。看看我的见解是否可以为您提供帮助:

您的见解是正确的,当您到达代码的不同部分时,您会忘记前面的部分。这可能是一个永无止境的循环。在这里要摘取的重要教训是,如果没有所有部件正常工作,产品将无法工作。即使一部分出现故障,该产品也无法正常工作。从另一个角度看待它:如果您对零件进行了显着改进,那么它仍然可能无法改善产品的性能,这是您在这里的主要目标。

因此,起初:不要成为开发人员。成为测试人员。

不要试图部分地理解。当所有零件在一起时,了解整个产品及其工作原理。在生产环境(即非开发环境-没有调试点)中,测试产品。然后,就像每个测试人员一样,将您遇到的问题记录到错误跟踪器中。为它分配严重性和优先级。由于该软件已经存在了一段时间,因此请查看是否已经创建了一个错误跟踪器。如果已经有一个,那么您很幸运。添加到其中并花一些时间并验证每个现有的。在本周期的最后,您将从用户的角度(绝对不能错过它)和质量检查的角度了解产品。当然,您甚至可能意识到,有一行代码可以解决该错误,而编写该代码的人却没有这样做,因为那时没有真正的需求。

第二步:穿上设计师的衣服斗篷

将产品分为几个部分(不是按字面意思或根据您的方便,而是根据它们如何协同工作)。可能是您现在为止的工作,或者现有知识可能会发挥作用。然后,尝试了解它们之间以及如何与10个依赖库一起工作。然后,对于每个跟踪的错误,写出您的注释来标识代码的实体(例如:此更改涉及修改类X,Y,Z等)。大概到此步骤结束时,您将获得FEW提示,以了解当前体系结构存在哪些问题以及可以改进的方面。

然后,您可以确定当前体系结构/设计是否足够,并且您可以改进软件,或者如果产品需要更好的设计或更改现有设计。

纸牌屋

此外,由于复杂的产品附带很多的代码,我们可能无法选择一些东西并进行调整或改进。这是因为整个系统可以相互交织,因此更改其中一个类别就相当于更改一张纸牌在纸牌屋中的位置,而您永远不知道哪一端会折断。以我的经验,这是事实。我选择了一部分,改进了代码,没有意识到它与代码其他部分的约定,最终放弃了代码并意识到了我的错误。因此,与其尝试理解各个部分,不如尝试并理解它是一个整体。

优先考虑您的问题

您需要牢记要改进的地方:


您想要产品更快吗?


您当然可以。但这是最主要的问题吗?慢吗?如果是,请创建性能标准,确定瓶颈并在这些方面进行改进。再次测试。


您想提高可用性吗?


那么,它几乎是API / UI方面。 br />
您要提高安全性吗?


这就是您应该探索的边界。

我仅提供了3个示例,但还有更多示例需要寻找。

最新和最佳文档

我在其中一篇文章中读到,最新最好的文档就是代码本身。即使您今天创建了大量的文档,它仍然是一段时间后的历史。因此,代码是您的最新文档。因此,每当浏览某些代码时,请在其中的注释中写下您的理解。在传递代码库时,请告诫他们不要仅依赖注释!

#7 楼

根据需要编写尽可能多的文档以解释当前代码。一旦确定了解其逻辑,就可以准备对其进行更改了。记录要添加的内容,并确保涵盖所有情况。

编写回归测试。手动测试或自动运行测试都没关系,您需要保留原始功能,而回归测试可以帮助您。

如果没有足够的规格,请向人们询问更多信息并写下来。

制定一个更改代码的计划,并将其分为不同的阶段。