专栏首页Linyb极客之路如何写出漂亮的代码?写代码应该遵守的原则和规范

如何写出漂亮的代码?写代码应该遵守的原则和规范

写代码的一些原则

  • 命名规范,含义明确。不同含义的变量和方法在词语、大小写、词组上要有有效的区分。
  • Never repeat yourself。常量、方法块、处理逻辑尽量少做重复,可以抽象出共用代码的最好抽象出来
  • 代码块职责单一,功能原子化。一段代码、一个方法最好只做一件事情,可以是控制某个流程,组装某个信息等,混在一起不利于后续的修改。
  • 开放扩展,关闭修改。稳定的东西尽量沉到下层,变化暴露到上层。这样做的目的是后续维护代码,尽量多添加,少修改。功能原子化也是为了达到这个目的,只有当功能足够小,耦合性足够低的时候,在添加新功能的时候,才可以不去修改老的,而主要通过扩展的方式增加业务
  • 高内聚,低耦合。达到这个目的不简单,一个基本思路就是尽量让代码处理的事情原子化,然后根据相关性的强弱选择不同的聚合方式。以个人的体会来说,方法用来聚合同一个数据的操作,类用来聚合相关性比较强的一组数据或者操作,模块用来聚合概念上有相似性的数据或者操作,层则用来聚合使用方式相同的数据或者操作。

具体的措施

以下是一些具体的措施:

一、命名

  • 常量:所有字母大写,不同单词用下划线隔开,如METHOD_TIME_THREAD;
  • 变量名和方法名:使用小驼峰式命名结构,第一个单词字母小写,后续单词的第一个字母大写,单词直接拼接在一起,如outNo,orderId;
  • 接口名和类名:大驼峰式命名结构,所有单词的第一个字母都大写;
  • 数据库字段、网络请求字段:所有字母小写,单词之间用下划线隔开,如out_no,order_id。

二、常量

  • 对于固定的字符串、整数,尽量使用常量,避免“魔法”字符串和整数(称之为魔法的意思是,可能稍一不注意,东西取错了都不知道)
  • 常量是无状态的,所以常量类和常量的命名上,最好别带太强的业务信息,可以直接使用字面意思命名,否则当别人使用该常量的时候,名字会很奇怪。
  • 常量类尽量集中存放,但不要分的太细。集中放是因为常量算是公共资源,理论上所有代码都要共用,放的越集中就容易让后续开发者发现,不至于重新定义一模一样的常量。不要分的太细也是为了让后续开发者容易识别,如果每个业务都分一套常量,到时候常量类就会迅速膨胀,而且起不到常量的作用

这里举一个例子:

HashMap<String,String> contentMap = new HashMap<String,String>();
//放入一个数据的时候是这样的:
contentMap.put("orderId",orderId);
// 取出数据的时候是这样的:
String orderId = contentMap.get("orderId");

这里的“orderId”字符串就称之为魔法字符串,其实很容易写错。而且假如以后这个参数改个名字叫:movieOrderId,那这些字符串就得改很多处,而且不能通过搜索特定字符串一次性替换(因为很多变量名也叫orderId,而且并不见得所有的“orderId”都应该改)。而如果使用常量来代表“orderId”字符串,就在key形成了一致性约束,以后改名字的时候,只需要改常量的内容,put和get操作就自动一致了。这是一个简单的变量“高内聚”

三、删掉未使用代码

  • 当前不用的代码一定要删掉,一是不整洁,二是影响后续rd的判断,不是自己写的删也不合适,用也不合适。
  • 没想好的代码不要写一半放在原地,应该删掉代码,写个todo提示

四、代码布局

  • 根据重要程度对变量、方法排个基本次序。人在阅读理解的时候,会遵从类似金字塔的逻辑,所以要让读代码的人先看到重要的,再看次要的。
  • 相关的代码要集中在一起,不相关的代码要用空行隔开,这样便于快速浏览代码的逻辑。
  • 变量顺序:静态常量、成员变量,所有变量按照重要性顺序排列。这样做是因为人的大脑有首因效应,对最先看到的事物记忆最深刻。
  • 方法顺序:构造方法,静态方法、私有成员方法、共有成员方法,也需要按照重要性顺序排列,具体顺序可以合理权衡。理论上,静态方法,多为工具类型的方法,和当前类有强绑定的关系,可以放在靠前的位置,如果没有强绑定的关系,可以考虑抽离到外部类,避免和当前类含糊不清。私有成员方法和共有成员方法需要根据方法的重要性确定其顺序,比如提供重要服务的接口需要放最前,而get、set方法,肯定是要放最后。
  • 方法内代码根据功能分块,例子(不同的功能要隔开,有关系的功能最好集中在一起,用序号标识出流程)

五、注释

  • 重要的系统尽量多加,比如支付、订单这种,改动风险较高,能说明的最好是说明下。多写注释的原因主要在于代码是英文为主,以中文为母语的人常常会将同一个单词理解成不同的含义,比如voucher这种多义词,既可以理解成单据、凭证,也可以理解为代金卷。特别是对于简写和隐形约定,很难做到每个人都理解,所以尽量多写些注释吧
  • 重要流程、算法逻辑、特殊判断一定要加注释,
  • 接口入参、返回值的限定,可能出现的各种情况最好用注释说明

六、日志

  • 日志要分类,最好一个业务一个日志,混在一起会难以统计和排查问题
  • 日志要分级,error,info,debug要分清。error日志就是要认真对待的,出任何问题都要小心谨慎;info日志是打提示信息,比如请求参数、返回内容、重要流程的节点信息等;
  • 日志的重点要清晰,一般模式是:先打印日志的目的(比如异常就说什么异常,提示就说什么参数的提示),然后接上重要的参数信息,参数可用你代码中使用到的参数,这样看到日志后,可以更快速的对应到具体的代码上,方便排查问题。

七、异常和中断处理

  • 尽量统一处理异常和中断,不要到处都是try-catch模块或者if-else的判断返回,一般是在业务接口上统一处理异常,下层逻辑在出现非预期的情况时,尽量抛到上层来统一处理。这里提供一种参考的异常处理和代码中断模式。我们先可以继承RuntimeException,实现一个自己的异常类TransException,由于RuntimeException是可以不捕获的异常,所以当TransException抛出来的时候,从下到上的方法都不需要throw,也不需要try-catch,这样我们就可以在最上层catch住,统一处理异常。
  • 分析上面两个类,异常类包括:错误枚举(包括错误码,错误信息)、详细的错误信息。当我们遇到可掌握的异常时,可以构造一个特定的TransException对象,将特定的错误枚举,具体的异常信息(比如具体的参数异常信息,错误堆栈,可展示的下层业务异常信息等)填到详细信息里边,然后在任意地方throw,由上层统一打印错误信息,组装返回内容,而不用一层一层的return false。
  • 出了未知异常,尽量掌控所有可以掌控的异常。达到这个目的主要有以下做法:1、操作数据前要检查,有问题要细化错误场景;2、try-catch异常尽量精细,不要try-catch太多代码。要么是一个流程,要么是一个数据操作,catch住异常要很清楚的知道这是什么类型的异常,将错误场景细化

八、模块划分

模块划分的目的是为了“高内聚”,把具有相同意义的代码和数据放在一起,一是方便了阅读和查找,二是可以将稳定的代码沉到下层,变化抽象到上层。下图是我们之前商量过的代码分层框架, 不一定要完全按照这种模式来,但可以有意的按照这种思路去把代码分离开。

从下到上来说各层代表含义:

  • domain:主要是领域对象,包括枚举、常量、VO对象,服务之间传递信息所需要的model等等
  • dao:数据库对应的DO对象,DAO对象等,专门负责处理数据库的东西
  • service:主要负责提供原子服务,包含外部rpc服务的封装,相对原子的数据处理过程,对dao层的操作等等
  • thrift service(server impl):thrift server 端,基于 basic service,实现 thrift service,一般需要将 domain model 封装为 thrift model。
  • thrift service(client repackage):thrift client 端,基于 thrift service,重新封装一层 service,一般包含:thrift model 与 domain model 之间的转换、Thrift Exception 异常转换等。
  • biz:主要处理各种业务流程,组合service层的原子服务达到某个目的,处理某个业务
  • provider:不直面客户端的也可以没有这个模块,主要负责接收外部请求,做权限管理(比如验证登陆)、参数校验,然后调用biz层的接口完成做具体业务处理
  • task:用于处理定时任务、外部调度任务的模块,负责接受任务消息或者调度请求,然后调用biz层的接口完成某件任务
  • common:主要是一些工具类(时间、MD5工具等等),以及一些对象转换的conventor

这样分模块的目的是为了减少代码的耦合性,把相关的数据和代码抽象的更集中,每当你想用某个常量、枚举、对象的时候,你大致扫一下domain就知道当下有什么东西,不致于针对同一个东西写好几份代码。而对于service来说,站在原子服务的角度来说,因为service足够小,所以增加新业务时,service是可以不修改的,而只需要添加服务就可以,biz层也只需要添加逻辑,组合service的服务就可以达到目的,这样就达到了“开放扩展,关闭修改”的目的,降低修改代码带来的风险。

九、使用模型

处理外部数据时,尽量使用自己的业务模型,除非特别简单的http回应,其它的处理都是应该封装自己的model的。

为什么要把外部数据映射成model呢?

  • 原因一:让看代码的人,知道你当下的业务在处理的过程中,到底和哪些参数相关,他要重点关注哪些参数。如果没有model,直接操作外部数据,就会将这些知识散乱的分布在各处的代码,一旦业务有变化,就不知道加减参数会不会对之前的业务有影响
  • 原因二:转换成model也是为了在一开始就对所需要的参数进行合法性检查,业务能不能做最好在获取参数时,就直接判断,不能走了一些流程后,突然发现参数不可用,然后再中断流程,这样会消耗服务的性能。而转换model也是为了对所有参数进行一次性的合法性检查。
  • 原因三:外部数据映射成model,会在添加或者删除数据时造成一定的约束,对比操作json,添加和删除都较为随意,既不能在类型上进行约束,也不能在对应关系上强制约束,后续开发者为了图省事,写着写着就乱的没法用了。

模型要有业务含义,在传递给服务时,一定要和该服务有很强的业务关系,不要为了简单使用DO,或者参数差不多、但业务不相关的model图省事。对于没有形成强业务绑定的model,经常会因为一个业务的修改,导致当下业务也需要联动的修改。要降低耦合性

避免超级model,做任何业务,都把参数往一个model中塞。这样一是让看代码的人毫无头绪,不知道到底哪些参数和当下业务相关,二是让修改的人很担心,去掉一个参数会不会影响到多个业务。所以尽量避免使用公共model,如果确实参数重合度比较高的,可以考虑model之间的有意义的继承。

多用模型,少用hashmap。hashmap当时它相当于一个自由模型,啥都可以塞,啥都可以取,使用上是方便,但是没有套上任何业务信息,业务越大,修改越多,就会越不可控。每次修改都会造成一些隐患。

作者:王洋 来源:http://ningg.top/develop-series-refactor-best-practice/

本文分享自微信公众号 - Linyb极客之路(gh_c420b2cf6b47),作者:王洋

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringCloud认证和鉴权的6种方案

    开始我们接触的时候权限认证 无从下手,但是当接触之后会发现 权限认证时一件很简单的事情,但是我们 方案众多又该如何选择呢,下面会分别对每种方案进行简单的阐述

    lyb-geek
  • 使用springboot2.1.x+redis来实现session共享采坑实录

    在springboot2.0版本之前,大家使用redis+springboot来实现session分布式共享的方式可能如下

    lyb-geek
  • 并发编程之ReentrantLock

    一、简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”。 ReentrantLock通过自定义队列同步器(AQS-Abstr...

    lyb-geek
  • 如何培养良好的编程习惯?

    首先你的项目结构要清晰,养成良好的文档结构分类习惯是很有必要的,举个最简单的web网站项目。

    Bug生活2048
  • 教程 | 如何利用TensorFlow.js部署简单的AI版「你画我猜」图像识别应用

    我们将使用卷积神经网络(CNN)来识别不同类型的手绘图像。这个卷积神经网络将在 Quick Draw 数据集(https://github.com/google...

    机器之心
  • EMNLP 2018 | 结合通用和专用NMT的优势,CMU为NMT引入「语境参数生成器」

    神经机器翻译(NMT)无需单独训练或调整系统的任何部分就可以直接建模源语言到目标语言的映射。这使得 NMT 快速发展,并在许多大规模环境中成功应用 (Wu et...

    机器之心
  • TensorFlow 发布新版本v1.9(附应用实践教程)

    【人工智能头条导读】TensorFlow 是一个开放源代码软件库,用于进行高性能数值计算。借助灵活的架构,用户可以轻松地将计算工作部署到多种平台(CPU、GPU...

    用户1737318
  • 7位数年薪抢人,DeepMind与Facebook人才大战又开火

    在加拿大蒙特利尔的一座共享办公大楼里,一间角落办公室里遍布着满是代码的黑色大屏幕。显示器并排堆放着,一个挨着一个,对于Facebook AI实验室(FAIR)加...

    新智元
  • flask使用富文本编辑器ckeditor

    为了使用CKEditor,我们首先要在模板中引入CKEditor的JavaScript等资源文件。推荐的做法是自己编写资源引用语句,你可以在CKEditor提供...

    菲宇
  • 原理解析Service Mesh与ESB、API管理与消息代理的关系

    作者:Christian Posta 译者:海松 原题:Application Network Functions With ESBs, API Manag...

    yuanyi928

扫码关注云+社区

领取腾讯云代金券