(经验有限,时间仓促,请轻喷。)前端
在咱们刚刚开始学习写代码的古老时代,或许会有下面这种习惯。vue
/// <summary> /// author:zhangsan /// </summary> class ZhangsanTest { private void TestGetData() { int a, b, c; } private int ZhangsanGet(int s1, int s2) { int s3 = s1 + s2; return s3; } private List<string> GetData() { return null; } }
这是一个喜欢用本身的姓名来命名类和方法的做者,在他的代码中,常常能够看到这样奇怪的对象定义,并且他还喜欢用a,b,c,d,e,f或者s1,s2这样的命名,仿佛他的代码自带混淆特效。这样的代码嗅起来会不会以为充斥着奇怪的味道?
另外,有没有发现有许多开发者喜欢用 GetData() 来定义获取数据的方法?而后这个方法就成为一个万金油的方法,无论是爬虫采集、或者数据绑定,不管是 C# 写的后端或者 Java 写的后端代码,或者用 vue 写的前端代码,仿佛在任何场景、任何数据应用均可以看到这样的方法。
若是一个项目中,有十几个地方都出现了这个** GetData() **方法,那种感受必定很是难受。程序员
随着技能的增加,或许咱们会学到一些新的代码概念,例如,Model、DTO 是常常容易弄混淆的一种概念,可是在某些代码中,出现了下面的命名方式就有点使人窒息了。web
public class XXXModelDto { public int Id { get; set; } public string Name { get; set; } public string Alias { get; set; } }
这是大概是一位对概念严重消化不良的资深开发者,竟然同时把 Model 和 DTO 复用在一个对象上,
(固然,一个开发者定义变量的背后必定有他的动机)。
他究竟是想要的是用来在 MVC 模式解决数据传输和对象绑定的模型对象?仍是用于传输数据的 DTO 呢?
--其实他定义这个对象,是为了定义存储数据对象的实体( Entity )。数据库
近年来开发者素质愈来愈高,因此许多优秀开发者会倾向于使用翻译软件来翻译变量名,而后用英语来命名,可是即使如此,许多政务项目老是能嗅出一些奇怪的味道。
例如前不久看到一条这样的短信:(原图已经消失)json
xxx公积金中心提醒您:您于{TQSJ}日进行了{TQCZ}操做,帐上剩余金额为{SYJE}元。
这是个bug将xxx公积金中心的某些秘密透露在你们面前。做为一个严谨的项目,竟然使用中文首字母大写命名法,这让习惯于大驼峰、小驼峰的我看了以后尴尬癌犯了,很不舒服。可是这也是许多政务信息化项目的中字段命名的规范,并且在这种状况下,每每会输出一份很是规范的数据库字段对照表,确保中文和首字母的语义不让人产生歧义。
因此特定语境下,变量和方法自己没有严格的规定,可是必定要使用恰当的语境概念,对于这样的特定场景,尽可能维护一份实时更新的术语表吧。后端
彷佛在对外提供接口时,使用下列接口状态码是一种比较常见的惯例。提供统一格式的 code 状态码以及返回的消息和成功返回结果时的填充数据,可以让开发者高效的完成接口对接,无需关心http状态码背后的含义。api
{"code":"100101","message":"success","data":{},"count":""}
上面这是一种经典的流派,还有一种流派则会使用http状态码来返回指定的数据,事实上 http 协议自己已经提供了许多状态码,例以下面的这些你们都很是熟悉的状态码。
可是这些状态码为啥不够?主要是为了减小先后端、服务上下游之间接口对接的难度,也是一种提升效率的方式。 可是 http 状态码是一种通用的格式,应尽可能使用这种方式,而不该该经过解析正常响应后的 json 来判断是否正确操做。服务器
200 :正常响应 标准成功代码和默认选项。 201 :建立对象。 适用于存储行为。 204 :没有内容。 当一个动做成功执行,但没有任何内容能够返回。 206 :部份内容。 当您必须返回分页的资源列表时颇有用。 400 :请求不正确 没法经过验证的请求的标准选项。 401 :未经受权 用户须要进行身份验证。 403 :禁止 用户已经过身份验证,但没有执行操做的权限。 404 :找不到资源自动返回。 500 :内部服务器错误。 理想状况下,您不会明确地返回此消息,可是若是发生意外中断,这是您的用户将会收到的。 503 :服务不可用 至关自我解释,还有一个不会被应用程序显式返回的代码。
我曾经跟小组中一位大佬交流他的一段代码,他的这段代码大概是这样的。数据结构
/// <summary> /// 流程处理 /// </summary> public void FlowProcess(int auditType) { switch (auditType) { case 1://经过 //此处省略经过场景下的50行代码 break; case 2://不经过 //此处省略不经过场景下的50行代码 break; case 3://再审经过 //此处省略再审经过场景下的50行代码 break; case 4://再审不经过 //此处省略再审不经过场景下的50行代码 break; } }
(读者卒。)
且不说这位大佬的代码是写得好或者很差,仅仅就这200多行代码的4个大switch读起来大概会让人便秘难受吧。因而在我读完这段代码以后,我冒死向他请教这么写代码的缘由,他说我这个流程处理就是一个简单的用例场景,哪里还有什么能够优化的余地?
我跟他介绍了20分钟代码封装的必要性,因而,他把代码写成了这样。
/// <summary> /// 流程处理 /// </summary> public void FlowProcess(int auditType) { switch (auditType) { case 1://经过 AuditOK(); break; case 2://不经过 AuditNotOK(); break; case 3://再审经过 ReAuditOK(); break; case 4://再审不经过 ReAuditNotOK(); break; } } public void AuditOK() { //此处省略经过场景下的50行代码 } public void AuditNotOK() { //此处省略不经过场景下的50行代码 } public void ReAuditOK() { //此处省略再审经过场景下的50行代码 } public void ReAuditNotOK() { //此处省略再审不经过场景下的50行代码 }
这酸爽使人简直难以置信。(事实上这个新鲜出炉的遗留应用,正是这样一点点堆积了许多总代码行超过千行的类文件)
《代码整洁之道》书上有一个相似的例子,大概与上文相似,Robert 大叔给出了这样的建议:
对于switch 语句,个人规矩是若是只出现一次,用于建立多态对象,并且隐藏在某个集成关系中,在系统中其余部分看不到,就还能容忍。固然也要就事论事,有时我也会部分或所有违反这条规矩。
上文我给出的示例,有点像面向过程的代码风格,而 Robert 大叔在他的书中写下的示例是这样的(抽象工厂模式的示例)。
这清爽的感受,让人很舒服啊。
固然,原示例是一个流程处理的例子,彷佛你们的流程处理代码都习惯于使用这种面向过程风格的写法,反正要加断定条件,就加一个 case 就能够了。
而在某些特定状况下,甚至用 if / else 来写逻辑判断更简单,因而咱们常常在某些销量很好的快速开发平台中,看到这样的例子。
这些典型的面向过程风格的代码,确实读起来彷佛更加简单、并且也易于实现。
Robert 大叔是这样说的:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象代码便于在不改动既有函数的前提下添加新类。 反过来说也说得通:过程式代码难以添加新数据结构,由于必须修改全部函数,面向对象代码难以添加新函数,由于必须修改全部类。
因此到底是使用面向过程式代码,仍是面向对象式代码?没有万试万灵的灵丹妙药。
一旦开始初步掌握面向对象开发的基本原则,因而咱们就会新建许多各类不一样的模型对象。尤为是在webapi接口开发过程当中,更是如此。
切勿浪费较多东西去作,用较少的东西,一样能够作好的事情。
假设有一段代码是这样的。
public class GrandParent { public Father Son { get; set; } public string Name { get; set; } public Father GetSon() { return Son; } } public class Father { public Me Son { get; set; } public string Name { get; set; } public Father GetSon() { return Son; } } public class Me { public Son Son { get; set; } public string Name { get; set; } public Son GetSon() { return Son; } } public class Son { public GrandSon GrandSon { get; set; } public string Name { get; set; } public GrandSon GetSon() { return GrandSon; } } public class GrandSon { public string Name { get; set; } public string GetSon() { return Name; } }
会不会为了得到某些数据,而写出这样的代码呢?
return new GrandParent().GetSon().GetSon().GetSon().Name;
这样就是典型的对得墨忒耳律的违背。这个原则指出:
模块不该了解它所操做对象的内部情形。 更准确的说,得墨忒耳律认为,类C的方法f只应该调用如下对象的方法: C(自己) 由方法f建立的对象。 做为参数传递给f的对象; 由C的实体变量持有的对象。 对象不该调用由任何函数返回的对象的方法。换言之,只跟朋友说话,不与陌生人说话。
在上文中我举的例子,祖父只跟本身的亲儿子(Father)说话,而不跟孙子说话。
在软件测试的概念里,圈复杂度用来衡量一个模块断定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。
听说在Oracle数据库中有一些屎山代码,是经过一堆标识量来判断某些特定逻辑的,大概是这样的。
(示例仅供参考,因为资源限制,未能考证,还请大佬指正一二。)
/// <summary> /// 一个高复杂度的方法 /// </summary> public string HighCCMethod() { int flag = 1; int flag1 = 2; int flag2 = 3; int flag3 = 4; int flag4 = 5; if (flag == 1) { //do something if (flag1 == 2) { //dosomething if (flag2 == 3) { //dosomething if (flag3 == 4 && flag4 == 5) { return "编译器 die"; } } } } return "..."; }
这是一个圈复杂度很是复杂的方法,我想任何一个读到这样代码的开发者都会对本身的人生充满了积极而乐观的判断,那就是“活着比一切都好”。
对于这样的代码,咱们应该尽量的下降代码的圈复杂度,让程序知足基本可读的需求。
public void UploadImg() { int flag = 3; //标识量为3标识什么意思我也不知道,我在网上看到的。 if (flag == 3) { //dosomething } //uploadfile(); }
我曾经参加过一个使用objectc编写的应用的,其中有一段代码是这样的,这个flag大概是魔法值,做者未经考证直接就在代码中使用了。而后一直流传下来,成为一段佳(gui)话(hua)。
还有这样的注释。傻傻分不清楚。
/// <summary> /// 为true标识为真,为false标识为假 /// </summary> public bool IsTrue { get; set; } /// <summary> /// 是否可见,为true标识可见,为false标识不可见 /// </summary> public bool IsVisible { get; set; }
还有这样的。
//do something Thread.Sleep(3000); //项目经理说此处要暂停3000毫秒,以便做为下次性能改进的需求点
Robert大叔如是说:
什么也比不上放置良好的注释来得有用。什么也比不会乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。
固然不少中国程序员自称其变量命名是自注释的,例如大概是这样的。万能的 Is 命名法,只要是判断状态皆可用。
(每一个程序员可以成功的生存下来都不容易,他必定有异于常人的本事。)
public bool IsShow { get; set; } public bool IsGet { get; set; } public bool IsUsed { get; set; }
CRUD开发者或许常常会看到这样的代码,例如,若是我要对某一个对象的状态( Status)进行更改,可能会这么作:
public class ShotGun1 { public void Method1() { DataStatus dataStatus = new DataStatus(); dataStatus.Flag = 1 * 3; dataStatus.Status = "1234"; } } public class ShotGun2 { public void Method2(int i, int status) { DataStatus dataStatus = new DataStatus(); dataStatus.Flag = 1 * 3; dataStatus.Status = "1234"; } }
这种霰弹式代码中,一处代码规则的变化,可能会须要对许多处代码进行同步修改,使得咱们的代码异常的难以维护。
有时候可能会遇到这样的代码,在方法中定义一些文本的状态码,而后调用方法时,再去判断这个状态码的内容,当返回错误码时,要求调用者当即处理错误。
public class XXXApi { public CurrentUser CurrentUser { get; } public string DoSomething() { if (GetCurrentUid() == "用户为空") { //do something } else { //dosomething } return ""; } public string GetCurrentUid() { if (CurrentUser == null) { return "用户为空"; } return ""; } } public class CurrentUser { public string Uid { get; set; } }
不如直接抛出异常,让异常处理机制进行处理吧。
public string GetCurrentUid() { if (CurrentUser == null) throw new NoLoginExecption(""); return ""; }
即使是简单的CRUD应用系统,优秀的开发者也能更好的处理应用程序模块间的边界。某种意义上讲,应用程序内部的边界看起来或许没有明确的界限之分,可是稍不留心就可能致使应用程序间关系过于紊乱,让其余开发者捉摸不透。
例如,假设有一段代码是这样的,在用户操做类中,加入了一个获取应用数据的方法,确实会让人很费解吧。(而应用领域驱动设计的思惟,或许是一种不错的模式。)
public class UserService { public string GetAppData(string config) { //do something } }
相对而言,或许应用间的边界彷佛能相对清晰的分析出来?并不是如此。
在当今时代,咱们不多开发彻底与其余应用系统没有任何关联的独立软件,这意味着咱们或许无时无刻都得与其余第三方应用进行接口行为或数据的交换。这让咱们必须确保采起措施让外来代码干净利落地整合进本身的代码中。
假设有一段代码是这样的:
public class UserService { public string GetAppData(string config) { //do something } public string UploadOssData(string file) { OssConfig oss = new OssConfig(); OssSdk ossSdk = OssSdk.CreateInstance(); ossSdk.UploadFile(file); } }
在《代码整洁之道》书中,Robert大叔推荐应该第三方接口进行隔离,经过Map那样包装或者使用适配器模式将咱们的接口转换成第三方提供的接口。让代码更好地与咱们沟通,在边界两边推进内部一致的用法,当第三方代码有改动时修改点也会更少。
写代码是开发者的基础技能,不管你是.NET 开发者,或者 Java 开发者,你都在努力用代码实现本身的梦想。如同韩磊老师在译做《代码整理之道》封面上总结全书,写下的那句诗
“细节之中自有天地,整洁成就卓越代码”。
卓越代码历来不只仅只是功能完善、代码齐全,作好细节,每一个细节就是一方小天地。优雅的代码,不只仅只是开发者的我的能力的体现,更是开发者的立足之本。努力改善坏习惯,提升代码质量,时刻消除异味,时刻提升本身,更有助于我的技能的全面发展。
本文来自: 溪源 | 长沙.NET技术社区