当前位置: 首页 > 科技观察

偷看了同事的代码,发现了代码优雅的秘诀

时间:2023-03-16 13:57:41 科技观察

简介对于一个软件平台来说,软件平台代码的好坏直接影响到平台的整体质量和稳定性。同时,也会影响写代码的同学的创作热情。试想一下,如果你从gitclone出来的项目代码杂乱无章,代码晦涩难懂,难以快速上手,并且有一种push重写的冲动,那么程序员最初想在这个项目中写出好代码的热情就没有了。.反之,如果clone下的代码结构清晰,代码优雅易懂,那你写代码的时候就不好意思写烂代码了。相信工作过的同学都深有体会其中的区别,那么我们看了这么多的代码,什么样的代码才是好代码呢?他们有什么共同的特点或原则吗?本文阐述了优雅代码设计原则的概念,和你谈谈如何写出好的代码。代码设计原则好的代码是经过设计、重构和迭代的。我们收到需求,走完大纲设计后,就要开始编码了。但在实际编码之前,我们还需要设计领域层次和代码结构设计。那么如何才能设计出更加优雅的代码结构呢?有一些高手总结出来的优雅的代码设计原则,我们分别来看一下。所谓SRP(SingleResponsibilityPrinciple)原则SRP就是单一职责原则。从字面意思看似乎很容易理解,一看就知道是什么意思。但是看到它并不一定意味着我们会使用它。有时我们认为自己可以做到,但实际应用时会遇到这样那样的问题。究其原因,是现实中我们没有把问题想透,没有进行深入的思考,知识只是知识,没有转化为我们的能力。比如这里说的单一职责原则,指的是谁的单一职责,是类还是模块还是域?一个域可以包含多个模块,一个模块也可以包含多个类。这些都是问题。为了方便说明,这里按类来描述单一职责的设计原则。对于一个类,如果它只负责完成一个职责或功能,那么我们可以说这个类符合单一职责原则。请回想一下,其实我们在实际编码过程中一直有意无意地使用单一职责设计原则。因为实际上它符合我们思考问题的方式。你为什么这么说?想想我们整理衣柜的时候,为了取衣服方便,夏天的衣服放在柜子里,冬天的衣服放在柜子里。这样换季的时候,我们只需要去对应的柜子直接拿衣服就可以了。否则,如果冬装和夏装放在同一个柜子里,我们就很难找到衣服了。在软件代码设计中,我们也需要采用这样的分类思维。在设计类时,需要设计一个粒度小、功能单一的类,而不是设计一个大而全的类。比如在学生管理系统中,如果一个类有创建或删除动作等学生信息操作,以及课程创建和修改动作,那么我们可以认为这个类不符合单一职责的设计原则是的,因为它把两个不同业务域的业务混合在一起了,所以我们需要把这个大而全的类拆分成学生和课程两个业务域,这样粒度更细,更具有凝聚力。笔者根据自己的经验,总结出拆分单一职责需要考虑的几个原则,希望能有一个简单的判断标准,供大家判断是否需要拆分:1。不同的业务域需要拆分,就像上面的例子一样,另外,如果与其他类的依赖过多,也要考虑是否拆分;2、如果我们在类中写代码的时候发现私有方法有一定的通用性,比如判断ip是否合法,解析xml等,那么我们可以考虑将这些方法抽离出来,组成一个公共的工具类,这样其他类也可以方便的使用。另外,单一职责的设计思想不仅仅用在代码设计上,我们在拆分微服务的时候也会在一定程度上遵循这个原则。OCPOCP(开闭原则)对修改关闭,对扩展开放。个人认为这是设计原则中最难的原则。不仅理解有一定的门槛,而且在实际编码过程中也不容易做到。首先,我们要弄清楚这里所说的修改和扩展的区别在哪里。说实话,刚开始看到这个原理的时候,总觉得修改和开启是一个意思?想了想,心里有点懵。后来在不断的项目实践中,对这个设计原则的理解逐渐加深。设计原则中所说的修改是指对原有代码的修改,扩展是指在原有代码的基础上进行能力的扩展,不修改原有代码。这是修改和扩展最大的区别。一个需要修改原来的代码逻辑,一个不需要。这就是为什么它被称为对修改关闭但对扩展开放的原因。弄清楚了修改和扩展的区别之后,我们来思考一下为什么要关闭修改而对扩展开放呢?我们都知道软件平台是不断更新迭代的,所以我们需要在原有的代码上进行延续。发展。那么就会涉及到一个问题。如果我们的代码设计不好,可扩展性不强,那么每次迭代功能时,都会修改原来已有的代码。如果有修改,可能会引入bug,导致系统平台不一致。稳定。因此,为了平台的稳定性,我们需要关闭修改。但是如果要增加新的功能怎么办呢?也就是通过扩展,所以需要对扩展开放。这里我们用一个例子来说明,不然可能还是有点抽象。在一个监控平台中,我们需要监控服务占用的CPU、内存等运行信息。第一版代码如下。公共类警报{私有AlarmRulealarmRule;私人警报通知警报通知;公共警报(AlarmRulealarmRule,AlarmNotifyalarmNotify){this.alarmRule=alarmRule;this.alarmNotify=alarmNotify;}publicvoidcheckServiceStatus(StringserviceName,intifcpury){(cpu>alarmRule.getRule(ServiceConstant.Status).getCpuThreshold){alarmNotify.notify(serviecName+alarmRule.getRule(ServiceConstant.Status).getDescription)}if(memory>alarmRule.getRule(ServiceConstant.Status).getMemoryThreshold){alarmNotify.notify(serviecName+alarmRule.getRule(ServiceConstant.Status).getDescription)}}}代码逻辑很简单,就是判断是否满足触发告警的条件根据相应报警规则中的阈值满足通知。如果此时有需求,需要增加一个判断条件,即根据服务对应的状态判断是否需要报警通知。我们来看看比较低的修改方法。我们在checkServiceStatus方法中增加了服务状态参数,在方法中增加了判断状态的逻辑。公共类警报{私有AlarmRulealarmRule;私人警报通知警报通知;公共警报(AlarmRulealarmRule,AlarmNotifyalarmNotify){this.alarmRule=alarmRule;this.alarmNotify=alarmNotify;}publicvoidcheckServiceStatus(StringserviecName,intcpu,intmemory,intstatus){if(cpu>alarmRule.getRule(ServiceConstant.Status).getCpuThreshold){alarmNotify.notify(serviecName+alarmRule.getRule(ServiceConstant.Status).getDescription)}if(memory>alarmRule.getRule(ServiceConstant.Status).getMemoryThreshold){alarmNotify.notify(serviecName+alarmRule.getRule(ServiceConstant.Status).getDescription)}if(status==alarmRule.getRule(ServiceConstant.Status)。getStatusThreshold){alarmNotify.notify(serviecName+alarmRule.getRule(ServiceConstant.Status).getDescription)}}}很明显这种修改方法非常不好,为什么这么说什么?首先,如果修改了方法参数,调用方法的地方可能也需要修改。另外,如果方法有单元测试方法,单元测试用例也必须修改。在原有测试代码中增加新的逻辑也会增加引入bug的风险,所以我们需要避免这种修改。那么modification怎么才能体现modification的关闭和extension的开启呢?首先,我们可以先把关于服务状态的属性抽象成一个ServiceStatus实体,在相应的检查方法中将ServiceStatus作为入参,这样以后如果有服务状态的属性增加的话,只需要在ServiceStatus中添加即可,不需要修改方法中的参数和调用方法的地方,单元测试的方法也不需要修改。@DatapublicclassServiceStatus{字符串服务名称;内部处理器;内部记忆;intstatus;}另外,在检测方法中,如何修改才能体现可扩展性?而不是在检测方法中加入处理逻辑。更好的实现方式是通过抽象检测方式,具体实现在各个实现类中。这样即使增加了检测逻辑,也只需要扩展检测实现方法,而不需要修改原有代码的逻辑,从而实现了代码的可扩展性。LSPLSP(LiskovSubstitutionPrinciple)里氏代换原则,我觉得这个设计原则比前面两个设计原则简单。其内容是子类对象(objectofsubtype/derivedclass)可以替换父类对象(objectofbase/parentclass)在程序(program)中出现的任何地方,并保证原程序的逻辑行为(行为)保持不变并且正确性不会受到影响。李氏代换原则是用来指导如何在继承关系中设计子类的原则。理解李式替换原则的核心是理解“designbycontract,designaccordingtoagreement”这几个字。父类定义了函数的“契约”(或协议),子类可以改变函数内部的实现逻辑,但不能改变函数原有的“契约”。这里的约定包括:函数声明要实现的功能;关于输入、输出和异常的约定;甚至评论中列出的任何特殊说明。我们如何判断是否违反了LSP?我认为有两个关键点可以作为判断的依据。一是子类是否改变了父类声明要实现的业务功能,二是是否违反了父类的输入、输出和异常。投掷规定。ISPISP(InterfaceSegregationPrinciple)接口隔离原则,简单的理解就是只把它需要的接口给调用者,不需要的不要强加给他。这里我们举个栗子,下面是关于商品的界面,包括创建商品、删除商品、根据商品ID获取商品、更新商品的界面。如果我们需要提供一个接口,根据商品类别获取商品,应该怎么办?很多同学会说这不容易。我们可以直接在这个接口中添加一个接口,根据分类查询商品。大家想想这个方案有没有问题。公共接口ProductService{booleancreateProduct(Productproduct);booleandeleteProductById(longid);产品getProductById(longid);intupdateProductInfo(Productproduct);}publicclassUserServiceImplimplementsUserService{//...}这种解决方案看起来没什么问题,但是如果你进一步思考,外部系统只需要一个根据产品类别查询产品的功能,但是我们提供的接口其实包括删除和更新商品的接口。如果这些接口被其他系统错误调整,产品信息可能会被删除或错误更新。因此,我们可以隔离这些第三方调用的接口,不至于出现接口能力的误调用和无序扩散。公共接口ProductService{booleancreateProduct(Productproduct);booleandeleteProductById(longid);产品getProductById(longid);intupdateProductInfo(Productproduct);}publicinterfaceThirdSystemProductService{ListgetProductBypublicType(inttype)};UserServiceImplimplementsUserService{//...}LODLOD(LawofDemeter)即Demeter法则。这是我们要介绍的最后一个代码设计法则。单从名字上,感觉有点不明朗。看不懂到底是什么意思。我们可以看看原文是如何描述这个设计原则的。每个单元对其他单元的了解应该有限:只有与当前单元“密切”相关的单元。或者:每个单元应该只和它的朋友交谈;不要和陌生人说话。按照我自己的理解,这个Dimit设计原则的核心思想或者说最希望达到的目标就是尽可能的减少代码修改对原有系统的影响。因此,需要实现能够实现高内聚、低耦合的类、模块或服务。不应该有直接依赖的类之间应该没有依赖关系;在有依赖关系的类之间,尽量只依赖必要的接口。迪米特定律就是降低类之间的耦合度,让类之间尽可能独立。每个类都应该对系统的其他部分知之甚少。一旦发生变化,更少的类需要知道变化。比如这就像抗日战争时期的地下组织。它相互关联,聚合在一起,但与外界保持尽可能少的联系,即低耦合。小结本文总结了软件代码设计的五个主要原则。按照我自己的理解,这五大原则是程序员代码设计的内功,而二十三种设计模式其实是内功产生的编程动作。因此,深刻理解五大设计原则是我们善用设计模式的基础,也是我们在设计代码结构时需要遵循的一些通用规范。只有在设计代码-》遵循规范-》编写代码-》重构的循环中不断磨练,才能写出优雅的代码。