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

    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=jpeg

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

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

相关文章

一次业务网关用ASP.NET Core 2.1重构的小结

前言对于API网关,业界貌似对它进行下划分,有下面几个分类/场景。面向Web App面向Mobile App面向Partner OpenAPI面向Partner ExternalAPI其他。。。在18年8月份的时候,有幸用.NET Core 2.1重构了一个对外的业务网关项目,这个项目的…

推荐几个华为,字节跳动、蚂蚁金服等大佬的公众号

每一个公众号都是一个特色的图书馆,为我们的学习提供优质的服务,珍贵的资源,耐心看完,认真选择适合自己的良师益友吧。Python爱好者社区Python爱好者社区,这里有分类整理好的历史优秀文章数千篇供你学习,内…

使用Ingress来负载分发微服务

目录 使用Ingress来负载分发微服务 Demo规划 准备Demo并完成部署 创建部署(Deployment)资源 创建服务(Service)资源 创建Ingress资源并配置转发规则 使用Ingress来负载分发微服务NodePort Service存在太多缺陷,不适合…

并发和并行及多线程基本概念

并发(Concurrent) 在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 并发,本质上是一个物理…

XUnit 依赖注入

XUnit 依赖注入Intro现在的开发中越来越看重依赖注入的思想,微软的 Asp.Net Core 框架更是天然集成了依赖注入,那么在单元测试中如何使用依赖注入呢?本文主要介绍如何通过 XUnit 来实现依赖注入, XUnit 主要借助 SharedContext 来…

程序员自家种水果,新鲜包邮配送!

点击上面“蓝字”关注我们!上次猕猴桃的活动一经推出,得到了广大粉丝的支持,我感到十分欣慰,非常感谢大家对我的信任。好多小伙伴,买了一箱尝过后又下单了好几箱。事实证明,品质才是销量的最佳保证。有些粉…

实现一个简单的基于码云(Gitee) 的 Storage

实现一个简单的基于码云(Gitee) 的 StorageIntro上次在 asp.net core 从单机到集群 一文中提到存储还不支持分布式,并立了一个 flag基于 github 或者 开源中国的码云实现一个 storage于是这两天就来填坑了。。实现了一个简单的基于开源中国的码云的 storage准备工作…

Java线程的6种状态

线程的概念,以及线程的创建方式,见我之前写的博文 本篇文章主要讲Java线程的6种状态 6种状态:初始状态(new) 、可运行状态(Runnable)、运行状态(Running)、阻塞状态&am…

.NET Core 微信小程序支付——(统一下单)

最近公司研发了几个电商小程序,还有一个核心的电商直播,只要是电商一般都会涉及到交易信息,离不开支付系统,这里我们统一实现小程序的支付流程(与服务号实现步骤一样)。目录1、开通小程序的支付能力2、商户…

用.NET写“算命”程序

前言“算命”,是一种迷信,我父亲那一辈却执迷不悟,有时深陷其中,有时为求一“上上签”,甚至不惜重金,向“天神”保佑。我曾看到过有些算命网站,可以根据人的生辰八字,来求得这个人一…

ASP.NET Core 3.0 迁移避坑指南

一.前言.NET Core 3.0将会在 .NET Conf 大会上正式发布,截止今日发布了9个预览版,改动也是不少,由于没有持续关注,今天将前面开源的动态WebApi项目迁移到.NET Core 3.0还花了不少时间踩坑,给大家分享一下我在迁移过程中…

打不死我的,终将使我强大!DevOps黑客马拉松参赛心得

(IDCF DevOps黑客马拉松到底是个啥活动?)长得丑活得久、长得帅也惹人爱!大家好,我是刘威。隆正信息的业务架构师-花名逸云。非常荣幸可以参加在北京举办的第一届DevOps黑客马拉松比赛。黑客马拉松不是突然冒出来的&…

Java线程池面试题

1、什么是线程池 java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。 假设一个服…

「标签管理」使用标签管理有道云笔记资料

因着大家对文件标签化比较高难道,需要熟悉一个标签工具软件,所以今天暂时来介绍个简单一些的网络资料的标签化管理,使用有道云笔记作为落地工具,同理在OneNote、印象笔记上原理类似。有道云笔记免费功能够用为了选择哪个笔记产品&…

我如何吸引Elastic创始人一起对高并发写入进行优化?

导语:在腾讯金融科技数据应用部的全民 BI 项目里,我们每天面对超过 10 亿级的数据写入,提高 ES 写入性能迫在眉睫,在最近的一次优化中,有幸参与到了 Elasticsearch 开源社区中。背景为了更便捷地分析数据,腾…

微软+开源,那些亲爱的以及热爱的

微软 Reactor 社区空间开幕式暨 Azure Meetup 社区活动已于9月7日在上海圆满结束!但是…如何构建一个可持续发展的社区未来的路,仍然很长...你应该知道的微软 Reactor微软 Reactor 是微软为构建开发者社区而提供的一个社区空间,以“予力多元…

ASP.NET Core 2.2 项目升级至 3.0 备忘录

.NET Core 3.0及ASP.NET Core 3.0 前瞻ASP.NET Core 3.0 迁移避坑指南将 ASP.NET Core 2.2 迁移至 ASP.NET Core 3.0 需要注意的地方记录在这篇随笔中。TargetFramework 改为 netcoreapp3.0 <TargetFramework>netcoreapp3.0</TargetFramework>从 Web 项目&#xff…

Java 死锁

目录&#xff1a; 什么是死锁&#xff1f;死锁是怎么产生的&#xff1f;怎么排查死锁&#xff1f;死锁的预防拓展&#xff1a;Java CPU 100%排查 一 什么是死锁&#xff1f; 注&#xff1a;线程和进程都可能会产生死锁&#xff0c;以下以线程为例 死锁是指两个或两个以上的…

.NET Core 微信小程序退款——(统一退款)

点击上方“dotNET名人堂”&#xff0c;选择“设为星标”用学习的姿态&#xff0c;步入工作的状态继上一篇".NET Core 微信小程序支付——&#xff08;统一下单&#xff09;后"&#xff0c;本文将实现统一退款功能&#xff0c;能支付就应该能退款嘛&#xff0c;一般涉…

Mysql数据库锁机制

一&#xff1a;概念介绍 MySQL数据库锁管理机制&#xff1a; SQL层实现的锁机制    Meta-data元数据锁&#xff1a;在table cache缓存里实现的&#xff0c;为DDL&#xff08;Data Definition Language&#xff09;提供隔离操作。一种特别的meta-data元数据类型&#xff0c;…