一些能够提升代码质量的编程规范

算是读书笔记吧

极客时间--设计模式之美


命名

命名的一个原则就是以能准确达意为目标,长短主要取决于给谁看。
学会换位思考,假设自己不熟悉这块代码,从代码阅读者的角度去考量命名是否足够直观。

Github

或者有两个网站可以推荐:
CODELF可以搜索到很多Git上的代码命名
Free App可以搜索到很多App以及Web国际化翻译

长度

  1. 默认的、大家都比较熟知的词,可以用缩写
  2. 作用于小的,比如临时变量用短命名
  3. 作用域比较大的,比如全局变量、类名用长的命名

这里的短,并不是让你用a1、a2这种无法辨认的名字
而是比如用sec 表示 second、str 表示 string、num 表示 number、doc 表示 document这样的缩写。

利用上下文简化命名

public class User {
  private String userName;
  private String userPassword;
  private String userAvatarUrl;
  //...
}
//利用上下文简化为:
User user = new User();
user.getName(); // 借助user对象这个上下文
public void uploadUserAvatarImageToAliyun(String userAvatarImageUri);
//利用上下文简化为:
public void uploadUserAvatarImageToAliyun(String imageUri);

命名可读

选用的单词。最优先的是能大家看懂、实在不行最起码要能读出来

命名可搜索

比如数组是用array,还是list。插入是用insertXXX,还是addXXX。
这个更多的依赖统一规约,能减少很多不必要的麻烦

特殊前缀

抽象类、接口、常量的前缀在项目里能够统一


注释

类的注释要写得尽可能全面、详细。
让人一眼就能看明白这个类的大致作用和实现思路,而不需要进去查阅大量的代码

public函数/接口声明

如果他会给其他业务方使用,写上注释吧。是个好习惯

普通函数声明

通常我们可以通过良好的命名替代注释
但是对于一些特殊细节,比如参数和错误的特殊描述,需要进行注释

函数内部

函数内部的通常也是通过良好的命名以及解释性变量让代码自解释

但是对于内部比较复杂的函数,总结性注释可以很好的对函数进行拆分,使逻辑更清晰

public boolean isValidPasword(String password) {
  // check if password is null or empty
  if (StringUtils.isBlank(password)) {
    return false;
  }

  // check if the length of password is between 4 and 64
  int length = password.length();
  if (length < 4 || length > 64) {
    return false;
  }
    
  // check if password contains only a~z,0~9,dot
  for (int i = 0; i < length; ++i) {
    char c = password.charAt(i);
    if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.')) {
      return false;
    }
  }
  return true;
}

注释的维护成本

注释本身有一定的维护成本,所以并非越多越好。撰写精炼的注释,也是一门艺术


函数、类的大小

大小这个东西很主观,就像对于“放盐少许”中的“少许”,即便是大厨也很难告诉你一个特别具体的量值。

函数代码行数,不要超过一个显示屏的垂直高度

一般来讲大概是50

超过一屏之后,在阅读代码的时候,为了串联前后的代码逻辑,就可能需要频繁地上下滚动屏幕,阅读体验不好不说,还容易出错

类的大小,以不影响阅读为准

当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数过多了。

善用空行分割单元块

对于比较长的函数,如果

  1. 逻辑上可以分为几个独立的代码块
  2. 不方便将这些独立的代码块抽取成小函数的情况下:

通过添加空行的方式,让这些不同模块的代码之间,界限更加明确。


一些具体的函数编写技巧

这些技巧,可以显著的提升同一份代码的质量

将代码拆分成更小的单元块

要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节
让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性,聚焦于业务


// 重构前的代码
public void invest(long userId, long financialProductId) {
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  calendar.set(Calendar.DATE, (calendar.get(Calendar.DATE) + 1));
  if (calendar.get(Calendar.DAY_OF_MONTH) == 1) {
    return;
  }
  //...
}

// 重构后的代码:提炼函数之后逻辑更加清晰
public void invest(long userId, long financialProductId) {
  if (isLastDayOfMonth(new Date())) {
    return;
  }
  //...
}

public boolean isLastDayOfMonth(Date date) {
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  calendar.set(Calendar.DATE, (calendar.get(Calendar.DATE) + 1));
  if (calendar.get(Calendar.DAY_OF_MONTH) == 1) {
   return true;
  }
  return false;
}

避免函数参数过多

函数包含 3、4 个参数的时候还是能接受的
再多,就需要重构代码进行优化

通常我们会将参数封装成对象


public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);

// 将参数封装成对象
public class Blog {
  private String title;
  private String summary;
  private String keywords;
  private Strint content;
  private String category;
  private long authorId;
}
public void postBlog(Blog blog);

不过,这样在使用的时候,不同环境下的参数定义可能随着迭代就不那么明确了,有利有弊吧。

迭代时,bool值传入方法作为隔离依据

这个情况对于很多开发人员都很常见,尤其在修改迭代他人代码的时候。
这种打补丁的方式,随着补丁的增多,对项目也是毁灭性的。

void configXXXX() {
   //基本操作...
}

//迭代后
void configXXXX(isModeA , isModeB) {
   //基本操作...
  if (isModeA) {
    //特殊操作...
  }
  //基本操作...
  if (isModeB) {
    //特殊操作
  }

  //基本操作...
  if (isModeA && !isModeB) {
    //特殊操作...
  } else if (isModeA){
    //特殊操作...
  }
  //基本操作...
}

函数设计要职责单一

不要设计一个大而全的函数,而在函数的内部做一大堆的盘点。
最起码,具体的功能实现函数不要耦合其他职责的逻辑

比如字符串的校验时,电话、用户名、邮箱三者的实现逻辑(将来)很可能是不同的,要分开定义。

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);

移除过深的嵌套层次

通常是if-else、switch-case、for 循环过度嵌套导致
我们可以最开始按照执行顺去去编写多层代码,功能开发完毕函数稳定之后,再回过头来着手移除

  • 去掉多余的 else 语句
public double caculateTotalAmount(List<Order> orders) {
  if (orders == null || orders.isEmpty()) {
    return 0.0;
  } else { // 此处的else可以去掉
    double amount = 0.0;
    for (Order order : orders) {
      if (order != null) {
        amount += (order.getCount() * order.getPrice());
      }
    }
    return amount;
  }
}
  • 去掉多余的if
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语句可以合并在一起
        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);
        // 此处还有10行代码...
      }
    }
  }
  return matchedStrings;
}

// 重构后的代码:使用return和continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList == null || substr == null){ 
    return matchedStrings;
  }
  for (String str : strList) {
    if (str == null || !str.contains(substr)) {
      continue; 
    }
    matchedStrings.add(str);
    // 此处还有10行代码...
  }
  return matchedStrings;
}
  • 将部分嵌套逻辑封装成函数调用

对于无法通过逻辑顺序进行优化的代码,可以通过把局部功能封装成小的函数来减轻主函数的嵌套层级。


// 重构前的代码
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }
  
  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    if (password.length() < 8) {
      // ...
    } else {
      // ...
    }
  }
  return passwordsWithSalt;
}

// 重构后的代码:将部分逻辑抽成函数
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }

  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    passwordsWithSalt.add(appendSalt(password));
  }
  return passwordsWithSalt;
}

private String appendSalt(String password) {
  String passwordWithSalt = password;
  if (password.length() < 8) {
    // ...
  } else {
    // ...
  }
  return passwordWithSalt;
}

解释性变量

  • 常量取代魔法数字

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;
}
  • 解释性变量来解释复杂表达式
if (date.after(SUMMER_START) && date.before(SUMMER_END)) {
  // ...
} else {
  // ...
}

// 引入解释性变量后逻辑更加清晰
boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END);
if (isSummer) {
  // ...
} else {
  // ...
} 
  • 多个复合条件的bool,最好加上注释
// ((团购 &&  非折扣 && 有销售价格) || 有团购列表)
boolean showGroupBuyButton = ((isGroupbuy && !isSale && (price>0)) || groupBuyList.count)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章