杂谈代码整洁.docx

上传人:安*** 文档编号:71086441 上传时间:2023-02-01 格式:DOCX 页数:23 大小:28.34KB
返回 下载 相关 举报
杂谈代码整洁.docx_第1页
第1页 / 共23页
杂谈代码整洁.docx_第2页
第2页 / 共23页
点击查看更多>>
资源描述

《杂谈代码整洁.docx》由会员分享,可在线阅读,更多相关《杂谈代码整洁.docx(23页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、杂谈代码整洁russheProgramsaremeanttobereadbyhumansandonlyincidentallyforcomputerstoexecute.DonaldKnuth“代码始终是写给人看的只是恰好能被计算机执行。什么是好的代码部分干净核心逻辑简洁。本文是一篇总结笔记是以往工作学习中关于怎样实现“部分干净的一些见闻、教训、团队理论以及一些考虑。写出整洁代码不仅需在函数、类级别上用功也应该理解一些其他主题如工程架构、设计原那么等软件工程是复杂(complex)的只有各个方面都处理得干干净净才能在整体上做到代码整洁。十分感谢旭哥授我思想与技术。指导原那么消除重复别离关注点统

2、一抽象层次程序员终其一生所做得事大抵不超过这几个层次函数与类包与模块(依赖)效劳(系统)与效劳域产品在各个层面这十五个字都足以作一些指导或者参考。消除重复重复的代码会让系统臃肿难以维护增加程序员的心智负担。消除重复的手段不外乎封装抽取函数、类等。代码重复完完全全重复的代码应该抽取出公共的函数。同一段代码出现两次及以上就应该抽取出函数。构造重复代码固然不一样但构造类似也应该抽取。构造重复可以推导出一些高级技术如继承体系泛型模板方法(templatemethod四人帮23种设计形式之一)高阶函数lambda可惜的是这些在golang里支持不够各有喜忧。经过重复假如总是重复做同一件事应该使其自动化。

3、别离关注点物以类聚人以群分代码也是一样。关注点一样的代码应该在一起天然具有亲以及性这句话的另一个含义对关注点不同的代码天然具有隔离性互相之间不应该太深化解析。别离主线以及支线这是最应该注意的十分是在业务代码开发中。主要业务逻辑是主线应该突出主线淡化支线按照人的思维这样才是好理解的。例如旋律音以及伴奏音应该突出旋律而淡化伴奏。假使伴奏音以及旋律音差不多强喧宾夺主这样的音乐一定是难听的因为我们听不出旋律。代码也是这样应该突出主线使核心逻辑一目了然。例如在下单的逻辑中可能的主线是检查库存、检查余额、生成订单。那这个下单方法里就应该只有3行代码而不应该有诸如权限判断、性能记录等假如出现就会有2行代码是

4、跟主线无关的造成不必要的干扰不要造成无谓的心智负担应该解放心智去完成更复杂的事情。别离主线以及支线的技术如AOPinterceptor、filter等别离技术以及业务技术型代码常常是公用的如日期计算、日志记录、性能测量、数据库链接、根底工具类。这些应该以及业务逻辑分开相信这点大众都没有疑问。按业务性质别离对业务开发来讲业务知识永远都是第一位的。一个技术程度很高的程序员但是对业务不理解他也发挥不了全部程度就像杀鸡用牛刀施展不了全部功力。不同业务应该分开在模块级、效劳级甚至更高的产品级这也应该是共识。但是在一个系统内部推荐也应该按业务分成不同的包同一业务下的对象是天然亲以及的同样也是对不同业务的对

5、象是隔离的。别离变化快慢的代码变化快的代码以及常年不变的代码分开。别离性能上下的代码重I/O的代码以及重CPU的代码理应分开方便合理分配资源其他诸如此类的代码应该注意分开。统一抽象层次将有关认识与那些在实际中以及他们同在的所有其他认识隔分开这就是抽象所有具有普遍性的认识都是这样得到的。JohnLocke?关于人类理解的随笔?怎么理解抽象抽象的反面是详细详细是细节可见抽象是细节的反面抽象刻画了统一的画像描绘才能是对事物在某些方面的特征的提取总结。总之抽象表达的是意图另一个理解就是它不表达细节。“Tom要成为世界首富这句话的抽象层次就很高意图很明显但是关于Tom怎样成为世界首富、用什么货币衡量等细

6、节一概不知。抽象层次高偏意图语义(代码在上下文中表达的语义)明晰信息量小抽象层次低偏实现语义模糊信息量大。两个原那么同一抽象层次上的对象才能直接对话同一抽象层次上的对象之间存在着严密合作典型的函数构造一个好的函数构造应该这样像一棵树一样层次清楚。一方面每一个层次都只有25个步骤一般而言我们做一件事也就25个步骤分解过多太少都不好太少没必要分解过多记不住增加心智负担。实际上更多的情况我们都喜欢3这个数字例如在会议总结时总结3点足够了更多估计不会有过多人愿意继续集中注意力听超过3点的总结。所以一个好的函数不应该超过5行我们之所以做不到除了抽象层次划分不准确之外还有很大一局部原因是表达才能缺乏毕竟英

7、语不是我们的母语。(函数10行代码是我在过去工作中合代码的及格线20行是红线。)另一方面只有叶子节点才表达实现非叶节点都应该表达意图。以“把大象装进冰箱为例不外乎三步翻开冰箱门放进大象关闭冰箱门所以关于怎样把大象切成碎片不应该出如今上面应该在步骤2的后续调用中。由这个函数构造还可以得见好的程序读起来应该像自然语言极少局部像数学语言(偏算法)不好的程序读起来就像是程序。当我们读一段程序一眼看去它就像是程序那不是它太好它就是不够好的。一直认为写作才能才是成为优秀程序员最重要的才能。隔离与隐藏信息隐藏是抽象的一种手段。通过信息隐藏来暴露只想让外界知道的东西表达意图。隔离是实现信息隐藏的重要手段。隐藏

8、与隔离有一个天然的好处例如我们有一个包我们只提供数个public的方法包内的其他对象、方法都只是包可见的这样我们可以随意修改内部实现只要保证那些public方法的行为不变。十分是对于复杂系统假如做不好隔离与隐藏到处都是public方法到后面谁都不敢随意改动代码谁也不知道哪位大哥在方法上加了一个if-else分支。编码tips以下都是一些简单实用的技术以怎样写出整洁代码很多是出自?代码整洁之道?一些是出自过去团队的经历。1.类类应该足够小最初级的程序员可能会在一个Controller里做完所有的业务逻辑最终会使这个类成为GodClass。一个类太大代码过多会使类的构造不明晰职责混乱维护代码时花费

9、很多时间去寻找修改位置。譬如我们所见的世界由分子、原子甚至更小的粒子排列组合而成所以才有缤纷多彩的各色物质(对象)但假如构成物质的最小粒子就是人那还能组合出什么其他物质呢代码也是如此类应该足够小才能发挥排列组合的威力。单一职责类的职责应该单一即“SOLID五大原那么的S职责单一意味着“只有一个理由可以修改它。另外类名一般而言应该是名词且描绘其职责。假如无法为一个类名以准确的名称这个类大概就太长了。类名越含混该类越有可能拥有太多的权责。?CleanCode?内聚内聚的含义是类的每一个字段都应该被某个(些)方法所使用到。假如不能到达这个结果应该考虑是否类的字段应该拆分出去成为新的类。严格控制访问权

10、限注意信息隐藏OCP访问权限应该能小那么小。能private就不要package能package就不要protected。这样做能使我们更好的遵循OCP原那么。最稳定的系统是从不修改的系统。2.函数尽可能小经过漫长的试错经历告诉我函数就应该小?CleanCode?应该控制在10行以内至多20行除非是细节代码。这是完全可以做到的做不到的原因可能有函数功能过多职责不单一函数抽象层次划分不清语言支持不够等。前面已经讲过做一件事大概也就25步每一步一个函数加上可能的条件判断10行是一个比拟合理的数字。而且函数越小功能越集中越便于取一个好名字。单一职责一个函数只做一件事。这一点很容易理解难的是我们怎样确

11、定函数做的那件事是什么。一千个读者就有一千个哈姆雷特同样的不同的人对一个函数的理解也有所不同对于做一件事的步骤拆分可以能有所不同。对此一个可靠的判断准那么是函数的内容(函数体内的代码)只是做了函数所在抽象层级的步骤那这个函数就是只做了一件事。函数所在抽象层级根据对业务的理解应该用良好的函数名加以示意。单一抽象层次一个函数应该只在一个抽象层次上。计算机世界都是层层叠加的例如存放器-高速缓存-主存-硬盘-网络(可参见?CSAPP?第六章)再如硬件-机器指令-汇编-C-C-JVM-Java-Servlet-Spring-SpringBoot。严格制止跨层次搞事。我们应该熟悉业务根据业务上的一次用例划

12、分抽象层次使每一个函数都只在某一个抽象层次上不要跨层次。还是以把大象装进冰箱为例最顶层的函数是ff里就只应该有s1,s2,s3三个函数。s2a,s2b里的实当代码那么不应该出如今f里。同理在s2函数里只应该有s2a,s2b函数而不应该有抽象层次更低(更详细)的s2a,s2a的实当代码等。绿色局部是最低抽象层次的详细实现这局部是无法拆分且难以控制代码行数的因为有些情况下做一件事就是有很多细节实现步骤。参数尽量少最理想是0个其次是1个2个最多3个参数不要超过3个参数除非你有非常特殊的理由。?CleanCode?参数带了极大的语义干扰而且也难于测试。一个典型的不好的设计就是用bool作为公开函数的参

13、数因为bool变量天然地会使人想到这个函数不会只做一件事它分情况处理bool入参的命名稍有歧义就会使人困惑。例如funcGoToWork(rainingbool)ifraining/开车去else/走路去更推荐的做法是将bool参数的函数私有另外公开两个语义明晰的函数。funcWalkToWork()goToWork(false)funcDriveToWork()goToWork(true)/私有funcgoToWork(rainingbool)ifraining/开车去else/走路去任何时候我们维护代码最关心的都是对外可访问的函数这些函数应该尽我们所能使其整洁。另一个例子在JUnit里曾有

14、这样的方法不知给多少初学者带来困扰assertEquals(expected,actual)对使用者来讲完全没有必要去记忆两个参数的相对位置。相较而言assertJ里的连接式接口就要友好得多assertThat(actual).isEqualTo(expected)golang里可以返回多个返回值但这绝不可以滥用。试看funcfunc1(/*params*/)(string,string,string,string,string)/函数职责不单一功能过多funcfunc2(/*此处多达6个参数*/)/函数职责不单一功能过多这样多入参、多返回值给调用方造成很大困扰调用方需要反复分辨每个参数、返回

15、值的对应关系。不能因为眼前就只有自己调用自己写的函数而这样放纵我们写的代码终究是会由别人接手的。无副作用一般而言函数应该是无副作用的对于调用方来讲它就是一个黑盒给定输入产生输出。仅此而已。不要让调用方去考虑我这次调用会不会产生输出以外的其他结果。例如应该尽量防止这种情况一个函数以指针作为参数返回一个结果的同时还修改了指针所指向的内容。一个函数的作用要么是get要么是post即要么函数无修改的get一个结果要么就是单纯修改而不返回修改以外的结果。jdk里有一个典型的反例各种集合的add/set总返回了一个bool值就会出现这样的代码/numbersisalistif(numbers.add(1)

16、/对于新手这可能就是一个让人迷惑的地方可见无副作用也不是绝对的强如JDK也有不得已的折衷处理。if嵌套不应超过2层if不要嵌套超过2层这初听起来有些强人所难仿佛要求每个职业篮球运发动都应该以乔丹的才能作为基准。可人的天性就是不喜欢考虑的喜欢简单。在此再一次强调统一抽象层次if嵌套过多一定要考虑是不是函数做的事情过多跨层次在搞事情。我们应该用一些高标准去检验自己的代码想方法去知足这个经过才会有所成长否那么除了收获经历以外不会有进阶的成长(其实人生又何尝不是如此)。消除多层if嵌套的一些手段提早返回将嵌套if铺陈开来使不知足条件的分支提早返回碰到第三个if直接将其抽取为函数(简单粗暴)lambda

17、在Java里利用stream的扁平化处理使filter、map等语法元素都可以接收简单的函数进而防止在for里加if判断。对于集合的遍历处理都应该尽量先采用stream的做法这种流水线的思想在一个步骤里就剔除了不知足条件的对象然后流转到下一个步骤。语义以及实现间隔不为0时应该抽取函数好的代码读起来就应该像自然语言而不是像程序这就要求在高抽象层次时函数应该表达意图而只有在叶子结点抽象层次最低的实现局部才表达实现这个地方的代码更像是程序。所以在代码中的某个位置我们本应该表达意图却写了细节实当代码这就应该抽取出函数。以下面这段代码为例。tom:Personiftom.Age18/doyourbuss

18、iness一般认为这是表达tom是否成年度但实际的业务含义中却是判断tom是否可以申领C1驾照。即使是想表达是否成年度这样也要使大脑经过一层转换由Age18推理一次才能得出结论这是表达是否成年度这是典型的“代码prase语义不要小看这层parse对人脑的开销十分是所见之处都是这样的代码会让我们的大脑长期忙于“线程切换活动造成的思维停顿让人非常沮丧此外假如一个日本人看到这段代码一定不会想是表达是否成年度这个语义因为他们的法定成年度年度龄是20岁(2022年度4月1日起改为18岁)这是代码不灵敏的表达。推荐的做法是iftom.isAdult()/doyourbussinessfunc(p*Pers

19、on)isAdult()boolreturnp.Age18这样在isAdult方法里还可以更改实现也更灵敏很多时候假如我们程序写得好实现比拟灵敏就可以沉着的应对经常变化的需求假如需求略微变化一下现有代码就顶不住了就应该思量实现是否足够好。代码应该表达意图十分是if条件分支里不要让人再去推理直接表达语义。就像人走路相比于一马平川我们不会更喜欢岔路但凡岔路就应该明确指明道路而不是在路口打个机锋才让你考虑十年度然后顿悟才选择出了某一条路。童子军军规走的时候比来的时候干净一点。代码中假如我们能经常注意这一点那我们每时每刻都在改善代码。世界是朝着熵增的方向开展的譬如一个房间即使我们完全不去干扰它久而久之

20、它也会变得更加混乱代码也是这样它终究会变得越来越混乱、难以修改、难以维护。假如我们不注意这一点反而每次来都扔一点垃圾久而久之就会成为“破窗直至“破楼。hardcode任何时候都不应该在代码中直接出现hardcodehardcode难以表达语义且难以管理。3.命名与注释命名是一个哲学问题我们所知的一切都是命名存在、宗教、知识、伦理.没有命名我们所知的一切所谓知识都将崩塌。ThereareonlytwohardthingsinComputerScience:cacheinvalidationandnamingthings.PhilKarlson “计算机世界只有两个难题缓存失效以及命名。(可读一读

21、?CSAPP?关于存储层次构造的描绘对此会深有体会。)坊间流传着一句话给变量命名犹如给自己亲女儿命名一般只因如此就不会随意命名了。命名的一般原那么无外乎完好、简洁、准确等。顾名思义、望文知义、无歧义清楚明白无歧义地表达含义不要让别人猜你的意思。在API设计里有一条原那么即是“DontLetMeThink命名也应该如此乃至日常工作沟通中也应当如此。名副其实cat:Dog表达语义防止误导userList实际实现是一个Setusers这个命名会更好语义更明晰userList有一些语义干扰。命名不应该表达实现(如List实现数据构造等)而应该表达语义。使用读得出来的名字慎重使用缩写人看代码实际是在默读

22、代码包括你如今看到这句话的时候心里也是在默念出来的。如xxCmd这样的命名一定会在脑海中多了一次parse对于一些更不常见的缩写这种情况更严重。前面提过这种脑内parse会使大脑忙于“线程切换思维停顿更是让人沮丧。团队统一业务术语DDD的一个重要理念就是同一术语在一个团队内部就应该统一术语从运营产品到开发测试等都应该对某一个业务专有词不产生任何歧义。我见过过多因为产品以及开发对某一个词的理解不同而“大打出手的事。注释好的代码是自注释的。命名固然重要但也无需开展成为圣战。4.单元测试应该重视单元测试。单元测试保证软件质量以及代码质量。单元测试是我们所写函数的第一个调用者假如发现单元测试很难写那不

23、用讲函数实现绝对是有问题的或抽象层次划分不清或依赖复杂等。假如连我们自己调自己的方法都用得这么不爽那可想而知其他调用者十分是网络接口。这是为什么单元测试可以保证代码质量它可以检验我们的代码是否写得足够好。单元测试对于修改代码或者重构的重要性无可替代对于拥有一组完善单测的函数我们可以随意更改只要让修改后的函数通过单测就几乎是平安修改的单元测试铺了一张平安网让我们像走钢丝一样地写代码不至于失足跌入深渊万劫不复。关于单元测试有很多理论最著名的可能莫过于TDD我们虽不至于按TDD的理论来开发但我们应该善用单元测试来检验我们的函数实现是否合理实现得好的函数单测一定是好写的逆否亦然。一些tips不能依赖真

24、实依赖这是大忌。如依赖真实数据库且数据库出错并不能检验单测所测函数逻辑失败而是外部造成的应该mock且对一般对象也应该尽量使用mock对象否那么即为集成测试途径应该尽可能全不能有条件分支任何条件分支都应该新开单测单测也应该像业务代码一样干净整洁realBug测试是必要的发生过一次的事情很有可能会反复发生我们选择题第一次选错了第二次还是很可能选择上次的那个错误答案.其他话题以下这些话题单独拎出来都是一个很大的主题这里只是抛砖引玉简单谈谈一些以及整洁代码相关的感悟以及理论实是整洁代码需要各个方面的努力而非仅代码一途用功。心智负担与复杂Complexityiscausedbytwothings:de

25、pendenciesandobscurity.软件开发的复杂性由两样东西带来依赖以及晦涩。这两者都会加重心智负担。消除心智负担一定程度上意味着增加可读性以及可维护性。其实我们所做的一切都是在征服复杂度。人脑终究是有限的我们眼所能见、脑所能别的资源几乎都是有限的。征服复杂度代码写好了升职加薪业余时间没有bug找上门进步生活质量我们所做的一切不就是为了这个吗复杂是我们软件生涯的一生之敌。分层分包分层是除“模块化之外最古老的架构形式冯诺依曼计算机模型是模块化的架构但同时计算机世界也是层层叠加的。分层分包的本质就是隔离人处理难题的才能是有限的无法同时处理很多复杂的事情所以不把所有东西都放在同一层次譬如

26、行政体系也是分层的。隔离使得各个层次职责更明晰更容易管理。分层的原那么是只能上层调用下层而不能反过来反之容易导致循环依赖。分包的原那么是同一个包中的对象天然是亲以及的同时对包外的对象是不亲以及(隔离)的。从分层的理念理解那么controller/api层的request不应该一直传递到service层甚至是dao层然而这种现象却是非常常见。业务层不应该对界面层有所解析而是相反界面层调用业务层来完成一次用户用例。但凡进入业务层就不应该有界面层的对象而应该在界面层转换成业务对象进而使业务层只处理它所能知的业务对象。这种跨层次的信息传递无异于乡长直接向省长汇报工作。传统MVC的分层对于简单业务而言是

27、简单实用的。但是其对于复杂业务系统的架构才能特别有限一个service包里有上百个xxxService类业务表达才能有限假如所有对外效劳都可以叫做service那为何要区分餐厅、医院、商场统一叫效劳不就好了而且很多时候往往就是一些无法准确划分职责的类干脆就合并到Service类里这让Service类成了一个大杂烩直至成为GodClass最终退化成经过式代码只是机械的代码堆积没有层次清楚、职责清楚的对象没有设计感。对于业务复杂的系统DDD微效劳经典四层分层是一个更好的理论重视业务、重视OO整个系统设计感十足对象林立可以做一些解析。但是对于业务简单的系统那么不应该为了炫技而使用技术。因地制宜学会取

28、舍。此外关于dao业务复杂情况下应该防止使用。dao的表达才能同样很弱dao里的方法很难表达意图语义表达才能很弱findByXXX实际是没有业务语义的例如findByAge承受参数18还是上面的例子并不是选择成年度的业务意义。此外dao难以管理。例如一个dao里有上百个findByXXX方法假如业务需要新增方法一般最省事的做法就是直接又加一个findByXXX方法这样下去dao会越来越膨胀并趋于崩坏。业务复杂情况应该使用repositoryrepository通过组合规格(specification)来表达查询语义repository是仓储的概念类似一个ADT只有有限几个经过仔细设计的方法类比

29、一个map就理解了。关于更多为何不使用dao而应该使用repository的知识可参考s:/thinkinginobjects/2021/08/26/dont-use-dao-use-repository/设计原那么遵循良好的设计原那么能使代码更整洁当然意义不仅于此。有关设计原那么的资料很多我们也应该对此有所解析。常见设计原那么如SOLIDADPREPCCPCRPSDPSAPDRYKISSYAGNISLAPPOLALoD代码的非功能特性只完成功能的代码是最根底的代码。好的代码还应该尽量完成代码的非功能特性有兴趣可以解析下不外乎可操作性强健性可测试性可维护性易用性可重用性其实还有些主题是无法避而不谈的如错误处理但限于篇幅以及才能只能推荐读两遍?CleanCode?。最后人生不过是“看山是山看山不是山看山仍是山代码也是如此不要着相。最近其他好文腾讯程序员视频号最新视频欢送点赞

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 工程图纸

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com