那些不可貌相的代码规范

用户头像
双儿么么哒
关注
发布于: 2020 年 08 月 09 日
那些不可貌相的代码规范

大家好, 我是废材, 废材最近忙着准备上线,各种bug接踵而至。修bug的过程中就读自己写的代码, 一边思考为什么有这个bug, 一边审视自己的代码,哎, 真是惨不忍睹。 那些不假思索就写出的代码, 自己都看不懂了。为了能让自己不坑队友,也为了以后能不假思索的就写出高质量的代码。我就把买了已经落灰的《代码整洁之道》拿出来,也把年前看来一一半的专栏也打开了,开始细细咀嚼, 现在的项目大多是基于Spring MVC的贫血模型,所以根据自己平时的使用经验, 总结出很重要但是却特别容易被忽略的几个注意点,希望这个总结的过程能帮助自己以后更好的掌握这些知识。

现在分享出来,并不是说我说的就一定是对的、好的, 全当抛砖引玉,跟大家交流交流,如果你在实际工作中也遇到什么很重要的代码规范或者是对我的总结有任何补充和异议,都可以跟我交流。共同进步。



1、关于命名

   想一想你的名字是怎么来的, 父母翻遍新华字典, 大仙算了一卦求的,知识渊博的长老赐予的等等。 你起名字的时候为什么如此重视, 轮到你的类, 函数就随便给个ABC, 这未免有点不讲究了。 那名字改怎么起呢, 有几个小tips可以参考:

  • 命名最重要的是要准确达意,一看就知道他是做什么的,不要使用生僻的英文单词,不要使用非通用的的缩写。不要使用特别近似的名字, 名字可以长一点,重要的是含义准确。

  • 要使用可搜索, 可读的名字,这点真的很重要, 如果名称不可读,你和别人交流都说不出名字,岂不是很尴尬,可搜索是因为我们统称会根据平时的编程习惯去搜索可用的方法, 比如我们在用java 集合时,如果我们不确定有什么方法,就可以.getXXX , 所以一个可搜索的名字可以帮助大家更好的使用。

  • 接口和抽象类的命名 : 对于接口命名一般加前缀“I”,表示一个 Interface。比如 IUserService,对应的实现类命名为 UserServiceImpl。但也有的接口命名不加前缀另一种是不加前缀,比如 UserService,对应的实现类加后缀“Impl”。对于抽象类的命名一般是带上前缀“Abstract”, 当然不加前缀也行, 团队统一就好。

  • 类名称一般是名词或名字短语,函数名一般是动词或动词短语。

2、关于注释

我们在看源代码的时候会发现有好多注释,比如一个函数的注释是下面这样的, 告诉你改函数做了什么, 参数及返回值的含义等等。但是在实际的工作中,如果不是要封装一个工具包之类, 只是普通的业务代码,不需要有复杂的注释,如果命名有含义,完全可以知道函数做了什么。 如果函数拆分的好,也完全可以知道是怎么做的。 那什么时候有注释呢?我倾向以下几点:

  • 业务逻辑太复杂, 可以用注释辅助说明

  •  由于历史原因留下的坑, 可以增加注释提醒后人。

  • TODO注释, 开发或重构过程中难免有些需要后续优化或修改的地方, 加上TODO方便后续修改完善。

3、类和函数大小

在贫血模型下,如果我们用了mybatis或mybatis plus 这种框架,并且做到类的职责单一, 就很容易做到类不会过大。如果我们的类上千行或者函数上百行, 那就要注意了,因为有可能函数逻辑太复杂, 很容易函数还没看完已经忘记前面做了啥, 别人阅读的过程中就需要不断的回退回去看,阅读体验就变差。那我们把函数都拆分的很小可以吗? 我觉得也不行, 因为这就意味着一个函数里面可能封装了无数的小函数, 别人阅读时就要不停的跳转,岂不是也心累。

但是多大算是好的呢, 感觉没法定量,因为这个跟个人的习惯和技术能力有关, 但是对也我个人而言(比较菜还比较懒),感觉函数如果你的编译器一屏能够展示下最好了,读起来就还比较方便。 而对于类, 我真的是靠感觉,如果读的过程中我已经头大了, 想要找个函数找不到,不知道用哪个的时候, 就要警惕了, 因为不仅阅读体验不好,还会带来其他副作用,后面会讲到。



4、一行代码要多长

说完了代码的高度, 我们再来说说代码的长度,不知道大家有没有遇到这种情况, 一行代码一屏幕不够宽,无法展示,我们需要滑动鼠标才能看到。这种是不是也不方便阅读。我见过一种比较极端的例子, 使用setter chain构造一个bean,如下所示:

SystemUser systemUser = new SystemUser().setUsername(createUserParam.getUsername()).setUserInfo(createUserParam.getUserInfo()).setUserEmail(createUserParam.getUserEmail()).setPassword(createUserParam.getUserPassword()).setHeadImg(createUserParam.getHeadImg());

但是如果改成这样:

SystemUser systemUser = new SystemUser()
.setUsername(createUserParam.getUsername())
.setUserInfo(createUserParam.getUserInfo())
.setUserEmail(createUserParam.getUserEmail())
.setPassword(createUserParam.getUserPassword())
.setHeadImg(createUserParam.getHeadImg());



是不是读起来一目了然了。



5、使用空行分割代码块

虽然我们控制了函数的大小, 但是如果把整个函数都没有空隙的放在一起, 感觉就想没有说话不喘气,给人很累的感觉。如果我们能使用空行将函数内部根据逻辑分割开,阅读时也会很轻松,当然如果可以也可以抽取成小的函数。

同理, 在类中也可以这么做, 来区分静态成员变量,普通成员变量,函数等 逻辑相关的放在一起等等,都是很好的使用体验。



6、类中成员的排序

类中成员的排序我们主要关注的是函数的顺序, 有的人喜欢按照作用域范围来排序,有的人喜欢按照调用关系来组织。 团队一致就好。 我个人喜欢先按照作用域范围来排序。后来发现将私有函数按照调用顺序排序也是不错的体验。所以就两者结合了。



7、函数职责单一

在学习设计原则的时候, 有学习过类的设计要职责单一, 应用在函数中还真是平时不太会注意的地方, 我理解的函数职责单一就是一次只干一件事,这里给出专栏中提到的一个例子,真的是能多单一就多单一:

public boolean checkUserIfExisting(String telephone, String username, String email) {​
if (!StringUtils.isBlank(telephone)) {   
User user = userRepo.selectUserByTelephone(telephone);  
return user != null;
}
if (!StringUtils.isBlank(username)) {
User user = userRepo.selectUserByUsername(username);
return user != null;
}​
if (!StringUtils.isBlank(email)) {
User user = userRepo.selectUserByEmail(email);
return user != null;
}
return false;
}​
// 拆分成三个函数
public boolean checkUserIfExistingByTelephone(String telephone);
public boolean checkUserIfExistingByUsername(String username);
public boolean checkUserIfExistingByEmail(String email);



8、函数参数的使用

函数参数的使用是我们平时遇到最常见的了,有的函数有六、七个参数, 有的函数传了一整个对象就为了用一个属性, 有的明明对象都传进去了,还单独传一个对象的属性, 有的是上面几种的大混合,读起来真的是害死强迫症。

书中建议的是函数的参数2~3个为宜, 如果太多可能考虑是不是函数的职责不单一, 也可以考虑将参数封装成对象。 都能很好的提高代码的可读性。



9、函数参数当成输出参数

你一定也遇到过将输入参数当作输出参数的迷惑场景, 如下的代码, 他是将s添加到什么东西后面吗, 还是把什么东西添加到s后面, 我们得查看函数自身才能判断到底做了什么。



appendFooter(s);​public void appendFooter(Stringbuffer report)



report.appendFooter();



10、使用函数参数进行逻辑控制

有时我们会用布尔参数来控制函数内部的逻辑,首先在函数调用过程中传true or false , 别人不知道什么意思,其次如果需要通过参数判断逻辑很可能违反了函数职责单一的原则,所以此时可以将函数拆分成两个函数。比如: 

public void addScoreForUser(long userId, boolean isNewUser){};
// 拆分成两个函数
public void addScoreForNewUser(long userId) {};
public void addScoreForOldUser(long userId){};



当然如果函数是私有的, 都是内部使用, 就还可以酌情考虑,毕竟不会对外面的调用产生影响。



11、使用解释性变量

我们在写代码时经常会遇到两种头大的情形, 一种是魔法数,一种是复杂的条件表达式。这两种情况代码的可读性都不太好, 我们可以使用解释性变量来提高可读性,代码示例如下:

public double CalculateCircularArea(double radius) {  return (3.1415) * radius * radius;}​
// 常量替代魔法数字
public static final Double PI = 3.1415;
public double CalculateCircularArea(double radius) {
return PI * radius * radius;
}
​Assert.isTrue(activity.getStartDate().before(now) && activity.getEndDate().after(now), “不在有效期内”)
Boolean isValidateDate = activity.getStartDate().before(now) && activity.getEndDate().after(now);
Assert.isTrue(isValidateDate, “不在有效期内”)



当然实际中的条件表达式可能比这个好要复杂,我们也可以提取函数啦封装条件, 代码如下: 

private boolean isValidateDate(activity) {
return activity.getStartDate().before(now) && activity.getEndDate().after(now);
}



12、关于过深的嵌套层次

代码嵌套层次过深往往是因为 if-else、switch-case、for 循环过度嵌套导致的。专栏中建议嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套。常见的思路有4种:

  •   去掉多余的 if 或 else 语句, 比如:

if(StringUtils.isBlack(userName)) {    
retrun null;
else {  //  此处的else可以去掉
}



  •   使用编程语言提供的 continue、break、return 关键字,提前退出嵌套

// 重构前的代码
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList != null && substr != null){
for (String str : strList) {
if (str != null && str.contains(substr)) {
matchedStrings.add(str);       
// ....
}
}
}
return matchedStrings;
}​
// 重构后的代码:使用continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList != null && substr != null){
for (String str : strList) {
if (str == null || !str.contains(substr)) {
continue;
}
matchedStrings.add(str);
// 此处还有10行代码...
}
}
return matchedStrings;
}​



  •   调整执行顺序来减少嵌套

// 重构前的代码
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList != null && substr != null) {
for (String str : strList) {
if (str != null) {
if (str.contains(substr)) {
matchedStrings.add(str);
}
}
}
}
return matchedStrings;
}
// 重构后的代码:先执行判空逻辑,再执行正常逻辑
public List<String> matchStrings(List<String> strList,String substr) {
if (strList == null || substr == null) { //先判空
return Collections.emptyList();
}
List<String> matchedStrings = new ArrayList<>();
for (String str : strList) {
if (str != null) {
if (str.contains(substr)) {
matchedStrings.add(str);
}
}
}
return matchedStrings;
}



  •   将部分嵌套逻辑封装成函数调用

// 重构前的代码
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList != null && substr != null){
for (String str : strList) {
if (str != null && str.contains(substr)) {
matchedStrings.add(str);
// ....
}
}
}
return matchedStrings;
}
// 重构后的代码:使用continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
List<String> matchedStrings = new ArrayList<>();
if (strList != null && substr != null){
for (String str : strList) {
if (str == null || !str.contains(substr)) {
continue;
}
matchedStrings.add(str);
// 此处还有10行代码...
}
}
return matchedStrings;
}



13、大胆删除无用的代码

 开发过程中就是会有迭代, 需求变化了, 代码就是会变化。 如果代码没有了及时清理掉。 历史都在git上了,大可不必担心以后是不是还会变回来。不要使用注释注掉不用的代码, 也不要把没用的死代码还留着,也不要贪图简单就引入好多没用到的工具类。



14、不要重复造轮子

我们现在有很多可用的工具包, 比如StringUtil, ObjectUtil, BeanUtil等等, 在我们想要用的时候,先去找找项目中是不是已经有了, 没有在去自己写一个。

此外, 还记得我们前面讲类的大小时我们讲到, 如果类太大, 我们找一个想用的函数都找不到时会有副作用吗, 重复造轮子就是一个。找不到别人就只能再写一个。



15、及时清理你的的TODO

 在上面讲注释的时候我们提到可以适当添加TODO注释来提示自己后续啊还要做的更改, 但是加上容易, 去掉难呀, 所以一定别忘了在完成你当前阶段性工作后回去扫描一下你的TODO, 及时清理掉。



16、关于代码重复

专栏前面讲设计原则是条一条 DRY原则(Don’t Repeat Yourself). 现在我们使用的IDE也会提示我们代码重复, 看到提示时留心一下,如果有办法消除就尽量消除。



17、把你的代码分层

我们都知道在MVC架构下, 我们会有controller 层, Service 层, Mapper层, 既然采用了这种结构, 就遵循他的设计,保持统一, 给你所有的mapper都加一层Service, 不要有的有, 有的没有, 方便别人搜索。

除了上面提到的对于复杂的参数验证我还喜欢加一层Validator层, 如果参数验证比较简单, 我会在Controller  层验证,比如只验证操作的对象是否存在。但是如果参数的验证很复杂, 我们就单独写一个Validator,专门封装参数验证。



18、关于项目约定

我们前面讲到那么多的代码规范, 都是说书中和专栏中提到的比价好的经验总结, 但是所有的一切都低于团队的项目约定, 如果项目中有约定就按照约定来,这样大家都同步了。不仅开发过程中代码的冲突减少了, 质量提高了, code review的过程也有统一的标准了, 多人写出的代码看起来出自一人之手。



通过学习的过程, 废材发现自己写的代码真的是千疮百孔,正所谓磨刀不误砍柴工,相信学会这些基础,也会在日后的工作中帮助我们提升自己的代码质量, 好了不说了,我去修炼内功了。








发布于: 2020 年 08 月 09 日 阅读数: 66
用户头像

双儿么么哒

关注

废材姑娘 2018.01.24 加入

大家叫我双儿,梦想着成为韦小宝的老婆 欢迎关注我的个人公众号----废材姑娘,回复“双儿”加我微信,让我们一起探索多彩的世界。

评论

发布
暂无评论
那些不可貌相的代码规范