项目中如何组织代码?

2021-06-21   出处: kislayverma.com  作/译者:Kislay Verma/lukeaxu

        在企业代码库中,您遇到最多的组织代码的方式是什么?我见到最多的是把类(假设是Java领域)按照技术栈进行分层。例如在MVC模式中,所有的Controller放在一起,所有的Service放在一起,所有的Repository放在一起,所有的POJO放在一起。我们把这种组织代码的方式称为栈式风格(“stack” style of organizing code)

        后面我会解释为什么栈式风格是一种糟糕的组织代码的方式。在那之前,这里介绍另外一种组织代码的方式。

        一种更好的方式是按照代码文件所代表的逻辑实体进行组织。我们把这种组织代码文件的方式称为实体风格(“entity” style of organizing code)。这种方式可以保证与某一概念相关的类会放在一起。将逻辑相关的实体放在一起后,更方便了人类理解而不是方便机器理解,编译器才不会管文件究竟放在什么位置。这种方式也会让开发人员更明确实际的系统边界在哪里,从而做出更明智的选择。开发人员不必去纠结某项功能是SomethingRepository还是SomethingElseRepository,而是从逻辑实体的概念上考虑它究竟是属于Something 还是SomethingElse.

        现在看一下为什么我认为实体模式比栈式模式更胜一筹。

抽象不恰当(Improper abstraction)

        人们不会按层阅读代码。从来没有人说“给我展示这个系统的所有API”或“给我这个系统触发的所有查询”。人们是顺着业务领域的边界阅读代码。例如在酒店管理系统中,人们会考虑房间、客人和价格等。 

        由于栈式风格的代码文件是按照技术栈分层组织的,因此很难从代码仓库本身去理解系统的逻辑模型。栈式风格显现的是技术栈的边界,我们无法从代码中理解“名词”以及它们之间的关系,为此你必须向下再深挖一层。对于阅读代码的新人来说,这种对“逻辑”结构的混乱可能会引起很多摩擦。

        在我们的酒店管理示例中,实体风格将所有与客人相关的代码(不考虑技术分层)放在一个包中,所有与房间相关的代码放在另一个包中,依此类推。这些包内部可以各自再使用栈式风格组织代码,或者只有几个处于同一级别的类。这使得在开发人员能够在一个地方轻松找到与客人相关的所有内容。

内聚低(Poor cohesion)

        栈式风格的一个常见的论点是它将模块划分到技术栈的不同层。例如,Controller与Service、Service与Repository等明显分离。要在技术栈的不同层找到相关的类,您需要转到代表这些层的包,这有利于不同层之间的解耦。

        这个论点的问题在于它专注于解耦,而忽略了另一个关键属性:内聚。究竟在哪些类之间要增加内聚,哪些类之间要减少耦合呢?虽然现在所有Service类都放在一起,但我们能否就依此判定Service之间高内聚,Service类与对应的Model类或Reposirtory类之间低耦合是合理的呢?我们是否允许Reposirtory类之间相互依赖,但与服务层的业务逻辑解耦呢?显而易见,答案是否定的!将这样的系统重构为更小的系统绝对是一场噩梦,因为您必须在技术栈的每一层去解耦类,它违背了 MVC 风格的初衷。

        另一方面,实体风格在增加内聚的同时也为栈式风格解耦留出空间。如果所有与酒店相关的类都相互依赖(在技术上或概念上)也是可以的,因为它们都与一个共同的工作任务相关。实体风格使未来可能发生的重构更容易,因为逻辑边界更加清晰。

修改困难(Hard to change)

        在应用栈式风格组织的代码库中,如果要进行一项业务上的修改,开发人员需要修改很多包中的代码。例如,为实体及其CRUD API增加一个属性,所有的包都会被修改。这造成了许多负担,因为开发人员必须修改许多哪怕与之仅有一丁点关系的其他地方的代码,而不是仅仅修改围绕这一核心逻辑的代码。

        在实体风格中,做出的修改仅发生在其所属的逻辑边界内。这让修改变得更加容易,因为我们只在代码库中的一小部分中进行修改。如果你做出的修改跨越多个顶级包,这说明了你正在跨越先前定义的逻辑界限,此时应该注意潜在的耦合问题。

系统设计受限(Limits design choices)

        按技术栈或功能组织代码限制了人们思考系统设计的方式。例如,如果约定业务逻辑放入Service,开发人员就会拒绝思考和使用适当的设计模式,而宁愿将所有内容都塞进Service中,从而创建长达数千行的噩梦类。即使使用良好的设计原则,代码的组织方式也会带来限制,因为每个新的“Type”都必须在一个独特的包中。例如,我想在Service层使用工厂模式,那么我必须新创建一个名为Factory的包层次结构,此后所有工厂都应该放在那里,无论工厂是否彼此有关。

        正如我之前提到的,实体风格不假设每个逻辑包在内部是如何分组的。每个逻辑包内部可以是栈式风格,也可以根据需要拥有任意多种类型的包,而不会影响在另一个逻辑包中所做的选择。

        这里的一个问题是如何组织涉及多个实体的业务。例如,涉及多个业务实体的工作流。这两种风格都没有提供一种简洁的代码组织方式,但我认为实体风格在这方面做得更好一些,因为它强制在所有实体包之外创建一个新包。这突出表明此工作流是一个新的概念,并且可能是应该独立开发的系统边界。这个想法将相关的实体组合在一起,不受单一概念约束的实体仍然可以拥有自己实体包(原文:The idea is to group similar concepts together, but things not bound to a single concept can still have their own logical homes in the base(本段概述:实体风格下,每个实体都有一个实体包,这样一项业务修改只需要修改与之关联的实体包中的代码文件。但是有的业务会涉及到多个实体,开发过程中就不可避免的涉及多个实体包中代码的修改。实体风格建议,为这种业务建立一个新包,新包将组合相关的实体,新包以后可能会独立为一个新的业务模块,被组合的实体(译文中:不受单一概念约束的实体)可以拥有自己的实体包)

        代码组织方式是我觉得我们考虑得还不够多的东西。这类似于代码库级别的康威定律。我很想听到更多关于如何组织代码以及你认为它会如何影响或塑造开发人员行为、心理或效率的信息。请在评论中留言!

        

{测试窝译文,译者:lukeaxu}


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
252° /2525 人阅读/0 条评论 发表评论

登录 后发表评论