我已经从事软件开发工作多年了。我的经验是,随着越来越多的开发人员参与产品开发,项目变得更加复杂和难以维护。
似乎在开发的某个阶段,软件趋向于变得“骇客”和“骇客”,尤其是当没有一个定义架构的团队成员在公司工作时。
我感到沮丧的是,必须更改某些内容的开发人员很难了解架构的概况。因此,存在解决问题或以与原始体系结构相适应的方式进行更改的趋势。结果是代码变得越来越复杂,甚至变得难以理解。
关于如何保持源代码多年来的可维护性,是否有任何有用的建议?
我已经从事软件开发工作多年了。我的经验是,随着越来越多的开发人员参与产品开发,项目变得更加复杂和难以维护。
似乎在开发的某个阶段,软件趋向于变得“骇客”和“骇客”,尤其是当没有一个定义架构的团队成员在公司工作时。
我感到沮丧的是,必须更改某些内容的开发人员很难了解架构的概况。因此,存在解决问题或以与原始体系结构相适应的方式进行更改的趋势。结果是代码变得越来越复杂,甚至变得难以理解。
关于如何保持源代码多年来的可维护性,是否有任何有用的建议?
Answers:
避免代码腐烂的唯一真正的解决方法就是编写好代码!
如何编写良好的代码是另一个问题。即使您是一个优秀的程序员,单独工作也很难。在一个异构团队中,这变得更加困难。在外包(子)项目中……祈祷吧。
通常的良好做法可能会有所帮助:
单元测试是你的朋友。实施它们会降低耦合度。这也意味着可以很容易地识别和重构程序的“ hacky”部分。这也意味着可以快速测试所有更改,以确保它们不会破坏现有功能。这应该鼓励您的开发人员修改现有方法,而不是因为担心破坏代码而重复代码。
单元测试还可以作为代码的额外文档,概述了每个部分应该做什么。通过广泛的单元测试,程序员无需了解程序的整个体系结构即可进行更改并使用现有的类/方法。
作为一个好的副作用,单元测试也有望减少您的错误计数。
这里的每个人都会很快提到code rot,我完全理解并同意这一点,但是它仍然怀念这里更大的图景和更大的问题。代码腐烂不只是发生。此外,还提到了单元测试,这很好,但是并不能真正解决问题。一个可以具有良好的单元测试覆盖范围和相对无错误的代码,但是仍然烂掉了代码和设计。
您提到开发人员在项目上的实现难以实现功能,并且错过了整个体系结构的整体图景,因此对系统进行了修改。负责实施和影响设计的技术领导者在哪里?此过程中的代码审查在哪里?
您实际上并没有遭受代码腐烂的困扰,但是却遭受了团队腐烂的困扰。事实是,软件的原始创建者不再在团队中也没关系。如果现有团队的技术主管完全,真正地理解底层设计,并且擅长担任技术主管,那么这将不是问题。
我们可以做几件事:
让一个人全面负责建筑。选择该人员时,请确保他们具有开发和维护体系结构的远见和技能,并具有帮助其他开发人员遵循体系结构的影响力和权威。该人员应是经验丰富的开发人员,应得到管理层的信任,并应得到同行的尊重。
创建一种文化,所有开发人员都拥有该体系结构的所有权。所有开发人员都需要参与开发和维护体系结构完整性的过程。
开发易于传达架构决策的环境。鼓励人们谈论设计和架构-不仅在当前项目的背景下,而且在总体上也是如此。
最佳的编码实践使体系结构更容易从代码中看到-花费时间进行重构,注释代码,开发单元测试等。诸如命名约定和简洁的编码实践之类的东西可以在架构交流中起到很大的帮助,因此作为一个团队,您需要花一些时间来制定和遵循自己的标准。
确保所有必要的文档清晰,简洁,最新并且可访问。公开高层和低层架构图(将它们固定在墙上会有所帮助)并且可以公共维护。
最后(作为一个自然的完美主义者),我需要认识到架构完整性是一个值得追求的目标,但是可能还有更重要的事情-例如组建一个可以协同工作并实际交付有效产品的团队。
我处理此问题的方法是从根目录进行剪裁:
我的解释将使用Microsoft / .NET的术语,但将适用于任何平台/工具箱:
在编写单元测试时,通过重构清除烂掉的代码。只要您满足以下条件,就可以为您触摸的所有代码支付(此)设计债务:
通过以下方法极大地加快了测试优先开发周期:
通过以下方式重构代码以使用低耦合(具有高度内部凝聚力的单元):
有机增长良好;大的前期设计是不好的。
拥有一个对当前设计知识丰富的领导者。如果没有,请阅读项目的代码,直到您有足够的知识。
阅读重构书籍。
我为自1999年以来就一直在持续开发的产品开发一个代码库,因此您可以想象到现在它已经相当复杂了。我们代码库中最大的黑手根源是无数次,我们不得不将其从ASP Classic移植到ASP.NET,从ADO 移植到ADO.NET,从回发到Ajax,切换UI库,编码标准等。
总而言之,我们已经做了合理的工作来保持代码库的可维护性。我们所做的主要贡献在于:
1)不断进行重构-如果您不得不修改一些难以理解或难以理解的代码,那么您将需要花费额外的时间来清理它,并在计划中留有余地。单元测试使这件事不那么令人恐惧,因为您可以更轻松地对回归进行测试。
2)保持整洁的开发环境-警惕删除不再使用的代码,并且不要在项目目录中保留备份副本/工作副本/实验代码。
3)在项目生命周期中使用一致的编码标准-让我们面对现实,我们对编码标准的看法随着时间的推移而发展。我建议您在项目的整个生命周期中都坚持使用您开始使用的编码标准,除非您有时间回过头来对所有代码进行改造以符合新标准。很高兴您现在掌握了匈牙利表示法,但可以将这堂课应用到新项目中,而不仅仅是在新项目上切换中游。
由于您已使用项目管理标记了该问题,因此我尝试添加一些非代码点:)
营业额计划-假设整个开发团队在维护阶段就已经消失了-没有值得花时间的开发人员想要永远维护他/她的系统。有空时就开始准备移交材料。
一致性/统一性不够强调。这将阻止“独自行动”的文化,并鼓励新的开发人员提出疑问。
保持其主流地位-使用的技术,设计模式和标准-因为团队的新开发人员(任何级别)都将有更多的机会快速启动并运行。
文档-特别是体系结构-制定决策的原因以及编码标准。还要保留参考/注释/路线图以记录业务领域-您会惊讶于公司业务向没有领域经验的开发人员解释他们所做的工作有多么困难。
清楚地制定规则-不仅要为您当前的开发团队服务,还要考虑将来的维护开发人员。如果这意味着在每个页面上都提供指向相关设计和编码标准文档的超链接,那就这样吧。
确保体系结构(尤其是代码层)明确划分和分开-随着新技术的出现,这将有可能允许替换代码层,例如,用HTML5 jQuery UI等替换Web Forms UI ,这可能买一年左右的长寿。
高度可获取的代码的一个特性是函数纯度。
纯度意味着函数对于相同的参数应返回相同的结果。也就是说,它们不应依赖于其他功能的副作用。此外,如果它们本身没有副作用,这将很有用。
该特性比耦合/内聚特性更容易看到。您不必全力以赴地实现它,我个人认为它更有价值。
当您的函数是纯函数时,其类型本身就是一个很好的文档。另外,就参数/返回值而言,编写和阅读文档要比提及某些全局状态(可能由其他线程O_O访问)要容易得多。
作为广泛使用纯度来帮助维护的示例,您可以看到GHC。这是一个大约有20年历史的大型项目,正在进行大型重构,并且仍在引入新的主要功能。
最后,我不太喜欢“保持简单”的观点。在对复杂事物进行建模时,您不能使程序保持简单。尝试制作一个简单的编译器,您生成的代码可能最终会变得很慢。当然,您可以(并且应该)使单个功能简单,但是结果整个程序将不简单。
除了其他答案之外,我还建议您使用图层。不是太多,但足以分隔不同类型的代码。
对于大多数应用程序,我们使用内部API模型。有一个内部API连接到数据库。然后是一个UI层。不同的人可以在每个级别上工作,而不会破坏或破坏应用程序的其他部分。
另一种方法是让每个人都阅读comp.risks和The Daily WTF,以便他们了解不良设计和不良编程的后果,并且如果看到自己的代码发布在Daily WTF上,他们将感到恐惧。
由于许多答案似乎都集中在庞大的团队上,甚至从一开始就如此,因此,我将把自己的观点作为一个由两人组成的开发团队(如果包括设计师在内,则为三个)组成一家初创公司。
显然,简单的设计和解决方案是最好的选择,但是当您真正付钱付薪的人时,您不一定有时间考虑最优雅,简单和可维护的解决方案。考虑到这一点,我的第一个重点是:
文档不是注释,代码应该主要是自我记录,而是设计文档,类层次结构和依赖关系,体系结构范例等。任何有助于新手甚至现有程序员理解代码库的内容。另外,记录最终弹出的那些奇怪的伪库,例如“将此类添加到用于此功能的元素中”,可能会有所帮助,因为它还会阻止人们重写功能。
但是,即使您确实有严格的时间限制,我也要记住另一件好事:
避免黑客和快速修复。除非快速解决方案是实际解决方案,否则总要找出某个问题的根本问题,然后再进行解决。除非您确实有一个“在接下来的2分钟内解决该问题,否则您将被解雇”的方案,那么立即进行修复是一个更好的主意,因为您稍后将不修复代码,而只是移至下一个任务。
我个人最喜欢的提示更多是引用,尽管我不记得来源:
“代码仿佛跟在你后面的人是一个知道你住在哪里的杀人心理变态者”
/** Gets the available times of a clinic practitioner on a specific date. **/
或)写一行/** Represents a clinic practitioner. **/
。
您不应该修改已经开发和测试的代码:任何此类代码都是密封的。相反,可以通过子类扩展现有的类,或者使用它们编写包装器,装饰器类或使用您认为合适的任何模式。但是不要更改工作代码。
只是我的2美分。
做个侦察兵。始终让代码清洁器保持原状。
修复破损的窗户。当您使用3.0版时,所有这些注释都会“在2.0版中进行更改”。
当存在重大黑客攻击时,请作为一个团队设计一个更好的解决方案,然后再做。如果您不能作为一个团队来修复黑客,那么您对系统的了解就不够。“请大人帮助。” 周围的最年长的人可能以前见过。尝试绘制或提取系统图。尝试以交互图的形式绘制或提取特别难用的用例。这不能解决问题,但至少您可以看到它。
将设计推向特定方向的不再是什么假设呢?某些混乱背后可能隐藏着一个小的重构。
如果您解释了系统的工作原理(甚至只是一个用例),并且发现自己不得不一遍又一遍地向子系统道歉,那就是问题所在。行为将使系统的其余部分变得更简单(无论实现起来看起来有多困难)。要重写的经典子系统是一种会以其操作语义和实现污染所有其他子系统的子系统。“哦,在将值输入到froo子系统之前,必须对这些值进行涂灰,然后在从froo获得输出时再次取消对它们的涂灰。也许在从用户和存储中读取所有值后,应该对它们进行涂灰,而系统的其余部分是错误的吗?当存在两个或多个不同的粉化时,这会变得更加令人兴奋。
花一个星期的时间来团队消除警告,以便可以看到真正的问题。
将所有代码重新格式化为编码标准。
确保您的版本控制系统已绑定到错误跟踪器。这意味着将来的更改是不错且负责任的,您可以找出原因。
做一些考古。查找原始设计文档并进行审查。它们可能位于办公室角落的那台旧PC上,废弃的办公空间或文件柜中,没人打开过。
在Wiki上重新发布设计文档。这有助于使知识制度化。
为发布和构建编写类似清单的过程。这使人们不必思考,因此他们可以专注于解决问题。尽可能自动构建。
尝试持续集成。构建失败的时间越早,项目花费的时间就越少。
如果您的团队负责人不做这些事情,那对公司不利。
尝试确保所有新代码在覆盖范围内进行正确的单元测试。因此问题不会变得更糟。
尝试对一些未经单元测试的旧位进行单元测试。这有助于减少对变更的恐惧。
如果可以的话,使您的集成和回归测试自动化。至少有一个清单。飞行员很聪明,可以拿到很多薪水,他们使用清单。他们很少也搞砸。
我来称它为“温彻斯特神秘屋效应”。就像房子一样,它的开始非常简单,但是多年来,许多不同的工人在没有总体计划的情况下增加了如此多的奇特功能,以至于没人真正了解它。为什么这个楼梯无处可去,为什么那扇门只能以一种方式打开?谁知道?
限制这种影响的方法是从一个良好的设计开始,该设计要具有足够的灵活性以应对扩展。已经对此提出了一些建议。
但是,通常您会从事已经造成破坏的工作,而且对于一个好的设计而言,如果不执行昂贵且有潜在风险的重新设计和重写,为时已晚。在这种情况下,最好尝试找到方法来限制混乱,同时将其拥抱到一定程度。一切都必须经过巨大的,丑陋的,单例的“经理”类,或者数据访问层与UI紧密耦合,这可能会烦恼您的设计敏感性,但要学会处理它。在该框架内进行防御性代码编码,并尝试在程序员的“鬼魂”过去时出现意外情况。
代码重构和单元测试非常好。但是,由于这个长期运行的项目正在遭受黑客攻击,因此这意味着管理层不会放下脚步清理腐败。该团队必须引入黑客手段,因为有人没有分配足够的资源来培训人员和分析问题/请求。
维护长期运行的项目与单独的开发人员一样,是项目经理的责任。
人们不会引入黑客,因为他们喜欢它。他们是受环境所迫。
我只想提出一个非技术性问题和(也许)务实的方法。
如果您的经理不关心技术质量(可管理的代码,简单的体系结构,可靠的基础结构等等),那么就很难改善项目。在这种情况下,有必要对上述经理进行培训,并说服他们“投入”精力进行可维护性和解决技术债务。
如果您梦with以求这些书中的代码质量,那么您还需要一位对此感到担忧的老板。
或者,如果您只是想驯服“ Frankenstein项目”,这些是我的提示:
以我的经验,编程是熵而不是新兴的(至少在流行的命令式结构范例中)。当人们编写代码以“正常工作”时,趋势就是失去组织。现在组织代码需要时间,有时甚至还不止于工作。
除了功能实现和错误修复之外,还请花费时间进行代码清理。
令我惊讶的是,众多答案中没有一个突出了显而易见的内容:使该软件包含众多小型独立库。使用许多小型库,您可以构建大型而复杂的软件。如果需求发生变化,则不必扔掉整个代码库,也不必研究如何修改大型honking代码库来执行除当前正在执行的操作之外的其他操作。您只需要确定需求更改后这些库中哪些仍然有用,以及如何将它们组合在一起以具有新功能即可。
使用那些库中的任何编程技术,使该库的使用变得容易。注意,例如,任何支持功能指针的非面向对象的语言都支持实际上的面向对象的编程(OOP)。因此,例如在C语言中,您可以执行OOP。
您甚至可以考虑在许多项目之间共享那些小的独立库(git子模块是您的朋友)。
不用说,每个小型独立的库都应进行单元测试。如果某个特定的库不可进行单元测试,则说明您做错了。
如果您使用C或C ++,并且不喜欢拥有许多小.so文件的想法,则可以将所有库链接到一个更大的.so文件中,或者可以进行静态链接。Java也是如此,只需将.so更改为.jar。
简单:将大部分代码的维护成本降低到零,直到您拥有可维护数量的活动部件为止。无需更改的代码不会产生维护成本。我建议旨在使代码真正具有零维护成本,而不是试图在许多小的繁琐的重构迭代中降低成本。立即使其成本为零。
好吧,诚然,这比听起来要难得多。但是开始并不难。您可以获取一部分代码库,对其进行测试,如果接口设计一团糟,则可以在其上构建一个不错的接口,并同时开始扩展代码库中可靠,稳定(如无需更改的原因)的部分缩小不可靠和不稳定的零件。感觉像是噩梦般维护的代码库通常不会将需要更改的活动部分与不需要更改的部分区分开,因为认为所有内容都不可靠且易于更改。
我实际上建议一路将代码库的组织分为“稳定”部分和“不稳定”部分,而稳定部分是要重建和更改的巨大PITA(这是一件好事,因为它们不需要如果它们确实属于“稳定”部分,则进行更改和重建)。
导致可维护性困难的不是代码库的大小。这是需要维护的代码库的大小。每当我说使用操作系统的API时,我就依赖数百万行代码。但这并不会增加产品的维护成本,因为我不必维护操作系统的源代码。我只是使用代码,它可以工作。我只使用而不需要维护的代码不会对我造成任何维护成本。