作为一个程序员,我最不喜欢做的事情,除了开会,可能就是看别人的代码了。有时,当我接手一个新项目时,我会打开代码看一看。如果我身体不好,我可能会气得晕倒。风格各异,没有注释,连最基本的排版和缩进都做不到。这些代码的意思可能是为了证明一句话:跑起来也不是不可能。这时候大部分程序员的想法是:实在不想改这段烂代码,还不如直接重写。但是有时候,当我们看着一些著名的开源项目时,我们会感叹代码写得这么好,这么优雅。为什么好呢?这很难说,但无论如何都很好。所以,本文试图分析好的代码有哪些特点,可以用什么原则来写出优秀的代码。初级阶段,先说基本原理,只要你是程序员,不管是高级还是初级,都会考虑进去。这只是部分列表,还有更多。我将选择四个简单的例子来说明。格式统一,命名规范清晰,注释清晰,避免重复代码。下面以Python代码为例:格式统一。格式包括很多方面,比如import语句,需要按如下顺序编写:Python标准库模块Python第三方模块各部分之间用空行分隔。importosimportsysimportmsgpackimportzmqimportfoo另一个例子,添加适当的空格,像下面的代码;i=i+1submitted+=1x=x*2-1hypot2=x*x+y*yc=(a+b)*(a-b)代码都压在一起了,影响阅读。i=i+1submitted+=1x=x*2-1hypot2=x*x+y*yc=(a+b)*(a-b)加上空格后,立马感觉清晰了很多。另外,像Python的缩进,其他语言中花括号的位置,不管是放在行尾还是换行开始,都需要保证风格统一。统一风格,代码看起来会更干净。命名约定好的命名不需要注释,只要看命名就可以知道变量或函数的作用。比如下面这段代码:a='zhangsan'b=0a可能还是会猜到,但是代码量大的时候,如果满屏都是a,b,c,d,就不会爆炸了地方。变量名稍作改动,语义更清晰:username='zhangsan'count=0,命名风格要统一。如果用camelCase,就全用camelCase,用underline,用underline,有的不用humpCase,有的用underline,看起来很分裂。清晰的注释看别人的代码,最大的愿望就是清晰的注释,但是自己写代码的时候,从来不写。但是评论越多越好。我总结了以下几点:评论不限于中文或英文,但最好不要中英文混用。比较重要的代码段可以用双等号隔开,以突出它们的重要性。例如:#========================================#很重要的功能,是一定要谨慎使用!!!#=========================================deffunc(arg1,arg2):"""在这里写一个函数的一句话总结(如:计算平均值)。这里是具体的描述。参数----------arg1:intarg1arg2的具体描述:intarg2返回值的具体描述------int返回值的具体描述见--------otherfunc:其他相关函数等..例子--------例子使用doctest格式,`>>>`后面的代码可以作为测试用例被文档测试工具自动运行>>>a=[1,2,3]>>>print[x+3forxina][4,5,6]"""避免重复代码随着项目规模的增长,开发人员的增加,代码量肯定会增加。难免会出现大量的重复代码。这些代码实现的功能都是一样的。虽然不影响项目的运行,但是重复代码的危害是很大的。最直接的影响就是一旦出现问题,需要改很多代码。一旦漏掉,就会导致BUG。例如,以下代码:importtimedeffunA():start=time.time()foriinrange(1000000):passend=time.time()print("funAcosttime=%fs"%(end-开始))deffunB():start=time.time()foriinrange(2000000):passend=time.time()print("funBcosttime=%fs"%(end-start))if__name__=='__main__':funA()funB()funA()和funB()都有输出函数运行时间的代码,适合把这些重复的代码抽象出来。例如写一个装饰器:defwarps():defwarp(func):def_warp(*args,**kwargs):start=time.time()func(*args,**kwargs)end=time.time()print("{}costtime={}".format(getattr(func,'__name__'),(end-start)))return_warpreturnwarp这样通过装饰器方法实现同样的功能。如果以后需要修改,直接改装饰器就行了,一劳永逸。到了高级阶段,代码写久了,肯定会对自己有更高的要求,而不仅仅是这些基本规范的格式和注解。但在这个过程中,也有一些问题需要注意,下面会详细讨论。首先要说的是“秀技”。当自己对代码越来越熟悉的时候,总想写一些高级的用法。但现实的结果是,代码往往是过度设计的。我不得不说说我的个人经历。有一段时间,我特别迷恋各种高级用法。有一次我写了一段很长的SQL,很复杂,甚至还包含了递归调用。更有“显身手”嫌疑的Python代码,往往一行代码包含N个以上的魔术方法。写完之后,他露出了满意的笑容,觉得自己的功力还真不错。结果就是各种骂,更重要的是,一个星期下来,我已经看不懂了。其实代码并不是用的方法越高级越好,而是要找到最合适的。代码越简单,逻辑越清晰,越不容易出错。而且在一个团队中,你的代码不是你一个人维护的,降低别人阅读和理解代码的成本也很重要。脆弱性第二点要关注的是代码的脆弱性,小改动是否会导致大故障。代码中是否充满了硬代码?如果是,那不是一个优雅的实现。很可能每次性能优化或配置更改都需要修改源代码。甚至需要重新打包部署到线上,非常麻烦。然而,这些硬代码被提取并设计为可配置的。当需要更改时,直接更改配置即可。接下来,是否有参数的验证?还是容错处理?一个API被第三方调用,如果第三方没有按要求传参,程序会不会崩溃?例如:page=data['page']size=data['size']不如下面这样:page=data.get('page',1)size=data.get('size',10)继续,项目依赖的库是否及时更新?积极及时的升级可以避免跨大版本??升级,因为跨大版本升级往往会出现很多问题。另外,当遇到一些安全漏洞时,升级是一个很好的解决方案。最后,单元测试是否完美?覆盖率高吗?说实话,程序员喜欢写代码,但往往不喜欢写单元测试。这是一个非常不好的习惯。只有完善的、高覆盖率的单元测试,才能提高项目整体的健壮性,才能将因修改代码导致bug的可能性降到最低。重构随着代码量越来越大,重构是每个开发者必须面对的功课。MartinFowler将其定义为:在不改变软件外部行为的情况下改变软件的内部结构,使其更易于理解和修改。重构的好处很明显,提高代码质量和性能,提高未来的开发生产力。但是重构的风险也非常高。如果代码逻辑不清晰,回归测试做不好,那么重构必然会带来很多问题。这就需要在开发过程中特别注意代码质量。除了上面提到的一些规范外,还需要关注面向对象编程的原则是否被滥用,接口的设计是否过度耦合等一系列问题。那么,在开发过程中,是否有一个指导原则可以用来避免这些问题呢?当然有,再往下看。进阶,最近刚看完一本书,鲍勃大叔的《架构整洁之道》。感觉很好,学到了很多东西。整本书基本上都是在讲述软件设计的一些理论知识。大致分为三个部分:编程范式(结构化编程、面向对象编程、函数式编程)、设计原则(主要是SOLID)、软件架构(里面讲了很多TakayaJianling的内容)。总的来说,本书的内容可以让你从微观(代码层面)和宏观(架构层面)层面全面了解整个软件设计。其中,SOLID指的是面向对象编程和面向对象设计的五个基本原则。在开发过程中正确应用这五个原则可以使软件维护和系统扩展变得更加容易。五个基本原则是:单一职责原则(SRP)开闭原则(OCP)里氏替换原则(LSP)接口隔离原则(ISP)依赖倒置原则(DIP)单一职责原则(SRP)一个类应该只有一个一、改变的理由。–RobertCMartin软件系统的最佳结构高度依赖于系统组织的内部结构,因此每个软件模块只有一个改变的理由。这个原则很容易被误解。很多程序员认为每个模块只能做一件事,其实不然。例如:如果有一个类T,它包含两个函数,分别是A()和B(),当需要修改A()时,可能会影响B()的功能。这不是一个好的设计,表明A()和B()耦合在一起。开闭原则(OCP)软件实体应该对扩展开放,但对修改关闭。–BertrandMeyer,Object-OrientedSoftwareConstruction如果一个软件系统要容易改变,那么它的设计必须允许新的代码来修改系统的行为,而不仅仅是通过修改原来的代码。通俗的解释是设计的类是opentoextensionandclosedtomodification,即可以扩展,不能修改。看下面的代码示例,可以简单明了地解释这个原理。voidDrawAllShape(ShapePointerlist[],intn){inti;for(i=0;i
