我认真写下9段如翔一般的代码,只为等你来品鉴

    640?wx_fmt=jpeg

     

 溪源 | 长沙.NET技术社区

640?wx_fmt=jpeg

开篇

我们总是很容易就能写出满足某个特定功能的代码,却很难写出优雅代码。又最欣赏那些优雅的代码,因为优雅代码更能体现一个开发者的积累。

就像写一篇散文,有的就像初学者不得其门而入,遣词造句都非常困难,然后纠纠结结,最终不了了之。或者啰哩吧嗦,看起来说了一堆,其实就像是村妇闲聊,毫无重点,不过是口水文而已。

好代码应该是这样的,如涓涓细流、如同一首诗,一篇优美的故事,将作者编写代码时的情感慢慢铺垫开来,或是高潮迭起,此起彼伏,或是平铺直述,却蕴含道理。我始终相信优秀的代码是有灵魂的,代码的灵魂就是作者的逻辑思维。

编写整洁代码 or 非整洁代码,就像平时生活中是否注意爱护环境的一点点小习惯,一旦坏味道代码没有及时处理,就会成为破窗效应,然后逐渐的代码越写越烂,最终这些代码要么以重构收场,要么就被抛弃。

我们见过太多没有毫无质量可言的代码,许多时候开发者们由于能力原因、或者时间有限,写了许多能够满足当前工作的代码,然后就弃置高阁,不再理会。于是,代码写之前的只有自己和上帝能理解代码的意思,而写完了之后,只有上帝能懂了;还有一些开发者说:我只会写代码,不会优化代码,他们仿佛特别勤奋,每天都会比其他人都热衷于熬工时,但是写出的代码,实际上是一个个难以维护的技术债。而且许多代码的作者总喜欢找各种借口来抵赖,例如喜欢说代码出了问题都是底层框架太垃圾了、或者别人的代码封装得太差。他们总是抱怨这抱怨那,但是即便有优秀的框架、技术,就一定能写出优秀的代码么?

在这里笔者列举了平时看到过一些自认为不太整洁的代码,以及与《代码整洁之道》(Clean Code · A Handbook of Agile Software Craftsmanship)一书中相对应的范例,欢迎大家一起来拍砖。

(经验有限,时间仓促,请轻喷。)

一些栗子

  • 1、命名规则

    • 1.1 变量命名和方法命名

在我们刚刚开始学习写代码的古老时代,或许会有下面这种习惯。

这是一个喜欢用自己的姓名来命名类和方法的作者,在他的代码中,经常可以看到这样奇怪的对象定义,而且他还喜欢用a,b,c,d,e,f或者s1,s2这样的命名,仿佛他的代码自带混淆特效。这样的代码嗅起来会不会觉得充斥着奇怪的味道?

另外,有没有发现有许多开发者喜欢用 GetData() 来定义获取数据的方法?然后这个方法就成为一个万金油的方法,不管是爬虫采集、或者数据绑定,无论是 C# 写的后端或者 Java 写的后端代码,或者用 vue 写的前端代码,仿佛在任何场景、任何数据应用都可以看到这样的方法。

如果一个项目中,有十几个地方都出现了这个 GetData() 方法,那种感觉一定非常难受。

  • 1.2 Model、Dto 傻傻分不清楚

随着技能的增长,或许我们会学到一些新的代码概念,例如,Model、DTO 是经常容易弄混淆的一种概念,但是在某些代码中,出现了下面的命名方式就有点令人窒息了。

这位大概是一位对概念严重消化不良的资深开发者,居然同时把 Model 和 DTO 复用在一个对象上,

(当然,一个开发者定义变量的背后一定有他的动机)。

他到底是想要的是用来在 MVC 模式解决数据传输和对象绑定的模型对象?还是用于传输数据的 DTO 呢?

--其实他定义这个对象,是为了定义存储数据对象的实体( Entity )。

  • 1.3特殊情况术语和字段对照表非常重要

近年来开发者素质越来越高,所以许多优秀开发者会倾向于使用翻译软件来翻译变量名,然后用英语来命名,但是即便如此,许多政务项目总是能嗅出一些奇怪的味道。

例如前不久看到一条这样的短信:(原图已经消失)

xxx公积金中心提醒您:您于{TQSJ}日进行了{TQCZ}操作,账上剩余金额为{SYJE}元。

这是个bug将xxx公积金中心的某些秘密透露在大家面前。作为一个严谨的项目,居然使用中文首字母大写命名法,这让习惯于大驼峰、小驼峰的我看了之后尴尬癌犯了,很不舒服。但是这也是许多政务信息化项目的中字段命名的规范,而且在这种情况下,往往会输出一份非常规范的数据库字段对照表,确保中文和首字母的语义不让人产生歧义。

所以特定语境下,变量和方法本身没有严格的规定,但是一定要使用恰当的语境概念,对于这样的特定场景,尽量维护一份实时更新的术语表吧。

  • 2、状态码返回值

    • 2.1业务逻辑状态码

似乎在对外提供接口时,使用下列接口状态码是一种比较常见的惯例。提供统一格式的 code 状态码以及返回的消息和成功返回结果时的填充数据,能够让开发者高效的完成接口对接,无需关心http状态码背后的含义。

  • 2.2用 http 状态码为什么不够?

上面这是一种经典的流派,还有一种流派则会使用http状态码来返回指定的数据,事实上 http 协议本身已经提供了许多状态码,例如下面的这些大家都非常熟悉的状态码。

但是这些状态码为啥不够?主要是为了减少前后端、服务上下游之间接口对接的难度,也是一种提高效率的方式。但是 http 状态码是一种通用的格式,应尽量使用这种方式,而不应该通过解析正常响应后的 json 来判断是否正确操作。

  • 3、switch 语句与判断语句

    • 3.1 面向过程式或面向对象式

我曾经跟小组中一位大佬交流他的一段代码,他的这段代码大概是这样的。

且不说这位大佬的代码是写得好或者不好,仅仅就这200多行代码的4个大switch读起来大概会让人便秘难受吧。于是在我读完这段代码之后,我冒死向他请教这么写代码的原因,他说我这个流程处理就是一个简单的用例场景,哪里还有什么可以优化的余地?

我跟他介绍了20分钟代码封装的必要性,于是,他把代码写成了这样。

这酸爽令人简直难以置信。(事实上这个新鲜出炉的遗留应用,正是这样一点点堆积了许多总代码行超过千行的类文件)

《代码整洁之道》书上有一个类似的例子,大概与上文类似,Robert 大叔给出了这样的建议:

对于switch 语句,我的规矩是如果只出现一次,用于创建多态对象,而且隐藏在某个集成关系中,在系统中其他部分看不到,就还能容忍。当然也要就事论事,有时我也会部分或全部违反这条规矩。

上文我给出的示例,有点像面向过程的代码风格,而 Robert 大叔在他的书中写下的示例是这样的(抽象工厂模式的示例)。

       640?wx_fmt=png      

这清爽的感觉,让人很舒服啊。

  • 3.2 孰优孰劣?

当然,原示例是一个流程处理的例子,似乎大家的流程处理代码都习惯于使用这种面向过程风格的写法,反正要加判定条件,就加一个 case 就可以了。

而在某些特定情况下,甚至用 if / else 来写逻辑判断更简单,于是我们经常在某些销量很好的快速开发平台中,看到这样的例子。

       640?wx_fmt=png      

这些典型的面向过程风格的代码,确实读起来似乎更加简单、而且也易于实现。

Robert 大叔是这样说的:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类。

反过来讲也说得通:过程式代码难以添加新数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为必须修改所有类。

所以究竟是使用面向过程式代码,还是面向对象式代码?没有万试万灵的灵丹妙药。

  • 4、奥卡姆剃刀定律、得墨忒耳律

    • 4.1“如非必要,勿增实体”

一旦开始初步掌握面向对象开发的基本原则,于是我们就会新建许多各种不同的模型对象。尤其是在webapi接口开发过程中,更是如此。

切勿浪费较多东西去做,用较少的东西,同样可以做好的事情。

  • 4.2 得墨忒耳律

假设有一段代码是这样的。

会不会为了获得某些数据,而写出这样的代码呢?

这样就是典型的对得墨忒耳律的违背。这个原则指出:

模块不应了解它所操作对象的内部情形。

更准确的说,得墨忒耳律认为,类C的方法f只应该调用以下对象的方法:

C(本身)

由方法f创建的对象。

作为参数传递给f的对象;

由C的实体变量持有的对象。

对象不应调用由任何函数返回的对象的方法。换言之,只跟朋友说话,不与陌生人说话。

在上文中我举的例子,祖父只跟自己的亲儿子(Father)说话,而不跟孙子(Me)说话。

  • 5、圈复杂度

在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。

据说在Oracle数据库中有一些屎山代码,是通过一堆标识量来判断某些特定逻辑的,大概是这样的。

(示例仅供参考,由于资源限制,未能考证,还请大佬指正一二。)

这是一个圈复杂度非常复杂的方法,我想任何一个读到这样代码的开发者都会对自己的人生充满了积极而乐观的判断,那就是“活着比一切都好”。

对于这样的代码,我们应该尽可能的降低代码的圈复杂度,让程序满足基本可读的需求。

  • 6、注释

我曾经参加过一个使用objectc编写的应用的,其中有一段代码是这样的,这个flag大概是魔法值,作者未经考证直接就在代码中使用了。然后一直流传下来,成为一段佳(gui)话(hua)。

还有这样的注释。傻傻分不清楚。

还有这样的。

Robert大叔如是说:

什么也比不上放置良好的注释来得有用。什么也比不会乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。

当然很多中国程序员自称其变量命名是自注释的,例如大概是这样的。万能的 Is 命名法,只要是判断状态皆可用。

(每个程序员能够成功的生存下来都不容易,他一定有异于常人的本事。)

  • 7、霰(xian 第四声)弹式修改

CRUD开发者或许经常会看到这样的代码,例如,如果我要对某一个对象的状态( Status)进行更改,可能会这么做:

这种霰弹式代码中,一处代码规则的变化,可能会需要对许多处代码进行同步修改,使得我们的代码异常的难以维护。

  • 8、异常

有时候可能会遇到这样的代码,在方法中定义一些文本的状态码,然后调用方法时,再去判断这个状态码的内容,当返回错误码时,要求调用者立即处理错误。

不如直接抛出异常,让异常处理机制进行处理吧。

9、边界

9.1 模块间的边界

即便是简单的CRUD应用系统,优秀的开发者也能更好的处理应用程序模块间的边界。某种意义上讲,应用程序内部的边界看起来或许没有明确的界限之分,但是稍不留心就可能导致应用程序间关系过于紊乱,让其他开发者捉摸不透。

例如,假设有一段代码是这样的,在用户操作类中,加入了一个获取应用数据的方法,确实会让人很费解吧。

9.2应用间的边界

相对而言,或许应用间的边界似乎能相对清晰的分析出来?并非如此。

在当今时代,我们很少开发完全与其他应用系统没有任何关联的独立软件,这意味着我们或许无时无刻都得与其他第三方应用进行接口行为或数据的交换。这让我们必须确保采取措施让外来代码干净利落地整合进自己的代码中。

假设有一段代码是这样的:

在《代码整洁之道》书中,Robert大叔推荐应该第三方接口进行隔离,通过Map那样包装或者使用适配器模式将我们的接口转换成第三方提供的接口。让代码更好地与我们沟通,在边界两边推动内部一致的用法,当第三方代码有改动时修改点也会更少。

总结

写代码是开发者的基础技能,无论你是.NET 开发者,或者 Java 开发者,你都在努力用代码实现自己的梦想。如同韩磊老师在译作《代码整理之道》封面上总结全书,写下的那句诗

“细节之中自有天地,整洁成就卓越代码”。

卓越代码从来不仅仅只是功能完善、代码齐全,做好细节,每个细节就是一方小天地。优雅的代码,不仅仅只是开发者的个人能力的体现,更是开发者的立足之本。努力改善坏习惯,提高代码质量,时刻消除异味,时刻提高自己,更是个人技能的全面发展的必然要求。

640?wx_fmt=png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313917.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P2596 [ZJOI2006]书架(fhq treap)

P2596 [ZJOI2006]书架 我们用fhq treap来完成这一题 对于一个新插入的节点我们取权值为其索引值,其所记录的valuevaluevalue是其当前索引所在位置。 操作一:把索引为valuevaluevalue的点放到平衡树前面,分别别得到三颗子树x,y,zx, y, zx,y…

「标签管理」用数据管理思维去管理你的日常电子化资料、文件、笔记等

最近一时兴起,研究了一些文件管理的方法论和笔记管理类的知识,自己想到一些不错的方法及落地方案,可能对一部分朋友来说,这些方法和工具的落地会有一些共鸣,故简单给大家做一下分享。跨界应用:数据表结构应…

Java之JMS

一:JMS简介   JMS即Java消息服务(Java Message Service),是一个Java平台定义的关于面向消息中间件的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。JMS是一个与具体平台无…

程序员过关斩将--更加优雅的Token认证方式JWT

点击上方“蓝字”带你去看小星星菜菜,上次你讲的cookie和session认证方式,我这次面试果然遇到了结果怎么样?结果面试官问我还有没有更好的方式?看来你又挂了别说了,伤心呀。到底还有没有更好的方式呢?你猜&…

Java之JMX

JMX,即Java Management Extensions,Java管理扩展。是一个为应用程序、设备、系统等植入管理功能的框架。   JMX提供了一种简单、基础的方法,用来管理应用、设置、服务等资源。由于JMX是动态的,你可以使用JMX技术来监控和管理处于…

平衡树(模板 and 题目)记录

平衡树 替罪羊树 #include <bits/stdc.h>using namespace std;const double alpha 0.725; const int N 2e6 10;struct Spgtree {int ls[N], rs[N], val[N], num[N], fac[N], sz[N], sum[N], cnt, root;int top, stk[N];void update(int rt) {fac[rt] fac[ls[rt]] …

[NewLife.XCode]分表分库(百亿级大数据存储)

NewLife.XCode是一个有15年历史的开源数据中间件&#xff0c;支持netcore/net45/net40&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xf…

Java -- 泛型

1、什么是泛型&#xff1f; 泛型&#xff08;Generics&#xff09;是把类型参数化&#xff0c;运用于类、接口、方法中&#xff0c;在调用时传入具体的类型。 泛型就是参数化类型 适用于多种数据类型执行相同的代码泛型的类型在使用时指定泛型归根到底就是“模板” 优点&…

HDU 6703 array(主席树 + set)

array 给一个全排列&#xff0c;接下来有两种操作&#xff1a; 一、把pospospos位置上的值10,000,00010,000,00010,000,000。 二、查询[1,r][1, r][1,r]区间&#xff0c;没有出现的且≥k\geq k≥k的最小值是多少。 考虑用主席树 set 求解&#xff0c; 先建立一颗主席树&a…

你的技术债还了吗?

什么是技术债&#xff1f;技术债是由沃德坎宁安在1992年提出&#xff0c;指我们在软件架构或代码编写过程中有意无意地做了错误的决策。随着时间的累积&#xff0c;这种错误会越来越多&#xff0c;就像背负了很多债务一样。技术债的危害技术债同财务债一样&#xff0c;是有利息…

拿 C# 搞函数式编程

最近闲下来了&#xff0c;准备出一个 C# 搞 FP 的合集。本合集所有代码均以 C# 8 为示例。可能你说&#xff0c;为什么要这么做呢&#xff1f;回答&#xff1a;为了好玩。另外&#xff0c;意义党们请 gun cu ke&#xff01;C# 有委托&#xff0c;而且有 Func<> 和 Action…

P3250 [HNOI2016]网络(利用堆建线段树 + 树剖)

P3250 [HNOI2016]网络 做法有点神奇&#xff01;&#xff01;&#xff01;利用堆作为节点建立一颗线段树&#xff0c;用堆维护线段树上点的信息。 说说查询操作&#xff0c;我们的目的是要查询&#xff0c;没有经过这个点的事件最大值&#xff0c;考虑如何维护。 我们定义线…

CNCF发布K8s项目历程报告,35k贡献者有你吗?

云原生计算基金会 CNCF 首次发布了 Kubernetes 项目历程报告。Kubernetes 托管于 CNCF&#xff0c;它是目前使用最广泛的容器编排平台&#xff0c;通常被称为“云端 Linux”&#xff0c;CNCF 介绍此报告旨在客观地评估 Kubernetes 项目的状态以及 CNCF 如何影响 Kubernetes 的发…

NUMTRYE - Number Theory (Easy)

NUMTRYE - Number Theory (Easy) Hard 版本就是用 pollard_rho 分解质因子。 f(n)∏(pi2ei11)f(n) \prod(p_i ^{2e_i 1} 1)f(n)∏(pi2ei​1​1)&#xff0c;g(n)∑i1nngcd⁡(n,i)g(n) \sum\limits_{i 1} ^{n} \frac{n}{\gcd(n, i)}g(n)i1∑n​gcd(n,i)n​&#xff0c;pip…

API 和 SPI

简介&#xff1a; API&#xff1a;Application Programming Interface应用程序接口 SPI&#xff1a;Service Provider Interface服务商提供接口 JDK中有描述&#xff0c; the API is the description of classes/interfaces/methods/… that you call and use to achieve a go…

编程语言这一年

最近开源中国&#xff08;OSCHINA&#xff09;在庆祝 11 周年生日&#xff0c;编辑部借着这个机会梳理了一下这一年来我们追过的那些开源界/开发界的热点新闻&#xff0c;算作一个阶段性小结。&#xff08;其实只有 9 个月&#xff5e;&#xff09;开源中国是目前国内为数不多深…

(CCPC 2020 网络选拔赛)HDU 6900 Residual Polynomial(分治 + NTT)

Residual Polynomial 写出所有的fi(x)f_i(x)fi​(x)出来&#xff0c;fi,jf_{i, j}fi,j​表示fi(x)f_i(x)fi​(x)的第jjj项系数 {f1,0f1,1f1,2…f1,n−1f1,nf2,0f2,1f2,2…f2,n−1f2,nf3,0f3,1f3,2…f3,n−1f3,n⋮⋮⋮⋱⋮⋮fn−1,0fn−1,1fn−1,2…fn−1,n−1fn−1,nfn,0fn,1f…

使用Elastic APM监控你的.NET Core应用

前言在应用实际的运维过程中&#xff0c;我们需要更多的日志和监控来让我们对自己的应用程序的运行状况有一个全方位的了解。然而对于大部分开发者而言&#xff0c;平时大家所关注的更多的是如何更优雅的实现业务&#xff0c;或者是如何让应用的响应速度更快等等与编码相关的技…

HDU 6755 Fibonacci Sum(二次剩余 + 二项式展开)

Fibonacci Sum 斐波那契通项有an15((152)n−(1−52)n)(15)k∑i0n((152)ic−(1−52)ic)kA152,B1−52(15)k∑i0n∑j0k(−1)k−jCkjAicjBic(k−j)(15)k∑j0k(−1)k−jCkj(∑i0nAcjiBc(k−j)i)斐波那契通项有a_n \frac{1}{\sqrt 5}\left((\frac{1 \sqrt 5}{2}) ^ n - (\frac{1 - …

Java 8 新的时间处理API

一&#xff1a;时间日期API的演进&#xff0c;及存在的问题 JDK 1.0 时期&#xff1a; 对于日期和时间的支持只能依赖于java.util.Date类。它的最小精度是毫秒起始年份为1900年&#xff0c;起始月份为0。20180822表示为new Date (118,7,22)返回值使用JVM默认时区&#xff1a;…