56

阅读《代码整洁之道》总结 - 编码砖家

 4 years ago
source link: https://www.cnblogs.com/xiaoyangjia/p/11245235.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

 很早就阅读过《代码整洁之道》(英文版Clean Code),当时博主是个青涩的菜鸟,正在为团队创造着混乱的代码。多年的工作中,屡次被别人的代码坑的苦不堪言,回想起当年我留下的代码,肯定也坑害了后来的同僚。当阅读JDK源码或者其他优秀开源工程时,叹服作者代码构建之精良,他们都有共同的特点:精确的变量名、恰到好处的设计模式、详细而不赘述的注释等等。如今重读本书,总结一下内容并加上自己的一些见解与大家分享。

代码是团队沟通方式

 工作的沟通,不只是电子邮件或者面对面语言交流,代码也是沟通方式之一。用代码实现需求,只是万里长征走完了第一步,必须让代码表达自己的设计思想。试想一下,你负责的功能被另外一个同事接手,如果你的代码结构清晰、注释合理,他就不用频繁的询问代码疑点,不用打断你的工作。编写代码的时候,应该考虑到别人的阅读感受,减少阅读障碍,为整个团队创造代码,而不是你自己。

让营地比来时更干净

 这是美国童子军规的谚语,美国童子军相当于半军事化管理的青少年夏令营。夏令营结束后孩子们离开营地,要打扫卫生保持整洁,让营地比来时更干净。在软件开发过程中,可以理解为不要破坏规则,不要引入混乱。如果团队已经制定了代码规范,比如类名必须有子系统前缀比如BiOrderService (Bi指BI业务部门),就继续遵循下去;再比如,团队已经提供了公共库比如MD5的加密,那就不要再次引入新的MD5库。很多新手程序员接活儿后,看到不喜欢的规范就另起炉灶,需要某些工具类也不询问老司机公共库有没有,直接引入自己熟悉的库,造成兼容性或者其他问题。

合适的命名

 合适的命名是头等大事,正如给新生儿起个好名字那样重要。不合适的命名通常是词不达意、误导观众、过度缩写等,由于英文并非我们的母语,找个合适的单词命名似乎真的很难。我建议是先把业务弄清楚,组织会议定下常用业务领域的单词,禁止组员各自发明。比如代码里使用canteen表示饭堂,那就不要再发明DinnerHall,既啰嗦又误导同僚。

看看反例:
// 手机号
String phone = “13421800409”;
// 获取地址
private String getDiZhi();
//修改密码
private void modifyPassword(String password1 ,String password2)
看看正例:
// 手机号 mobileNo比phone更精确
String mobileNo= “13421800409”;

// 避免英文拼音混杂
private String getAddress();

// 参数的命名要区分意义
private void modifyPassword(String oldPassowrd,String newPassword)

短小的方法

  方法有多短小才合适没有定论,但是长达500行的一个方法,绝对让阅读者起杀人之心。过长的方法,让阅读者不知道从何看起,看了前面忘记后面。将复杂的方法,拆分成逻辑相对简单的短方法。

看看反例:
//  获取个人信息
private UserDTO getUserDTO(Integer userId)
{
    //获取基本信息 
    … 此处写了10行

    //获取最近的一次订单信息
    … 此处写了30行

   // 获取钱包余额、可用优惠券张数等
    … 此处写了30行

   return userDTO;
}

看看正例:
//  获取个人信息
private UserDTO getUserDTO(Integer userId)
{
    //获取基本信息 
    UserDTO userDTO= getUserBasicInfo(userId);

    //获取最近的一次订单信息
    userDTO.setUserLastOrder(getUserLastOrder(userId));

    // 获取钱包、可用优惠券张数等
    userDTO.setUserAccount(getUserAccount(userId));  
    return userDTO;
}

private UserDTO getUserBasicInfo(Integer userId);
private UserLastOrder getUserLastOrder(Integer userId);
private UserAccount getUserAccount(Integer userId);

减少if/else嵌套

  为什么要减少嵌套,难道嵌套看上去不时尚吗?我曾经看到某位同事的一段代码嵌套达到9层,他自己再去维护的时候都看晕了。代码过度嵌套的结果是只有原作者才能读懂,接盘侠一脸茫然。

看看反例:
// 修改用户密码,这个例子只有3层嵌套,很温柔了
public boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
      if (userId != null && StringUtils.isNotBlank(newPassword) && SpringUtils.isNotBlank(oldPassword)) {
	User user = getUserById(userId);
	if (user != null) {
	     if (user.getPassword().equals(oldPassword) {
	          return updatePassword(userId, newPassword)
	     }
	 }
      }
}
看看正例:
// 修改用户密码 
Public Boolean modifyPassword(Integer userId, String oldPassword, String newPassword) {
     if (userId == null || StringUtils.isBlank(newPassword) || StringUtils.isBlank(oldPassword)) {
            return false;
     }
     User user = getUserById(userId);
     if(user == null) {
           return false;
      }
     if(!user.getPassword().equals(oldPassword) {
           return false;	
     }
     return updatePassword(userId, newPassword);
}

正例采用卫语句减少了嵌套,但是并非所有场景都适合这样改写。如果不适合,可以将关联性高的逻辑抽取成一个独立的方法减少嵌套。

抽离try/catch

  大家有没有见过一个超长的方法,从头到尾被一个try/catch照顾着?博主经历过的项目中,这种不负责的写法比比皆是。并非每行代码都会抛出错误,只要将会抛出错误的业务放在一个独立的方法即可。

看看反例:
//  获取个人信息
private UserDTO getUserDTO(Integer userId)
{
   try { 
       //获取基本信息 
       ... 此处写了10行
       //获取最近的一次订单信息.
       ...此处写了20行
       // 获取钱包、可用优惠券张数等
       ...此处写了20行
    }catch (Exception e) {
        logger.error(e);
        return null;
    }
}
   return userDTO;
}

看看正例:
//  获取个人信息
private UserDTO getUserDTO(Integer userId)
{
    //获取基本信息 
    UserDTO userDTO= getUserBasicInfo(userId);

    //获取最近的一次订单信息
    userDTO.setUserLastOrder(getUserLastOrder(userId));

    // 获取钱包、可用优惠券张数等
    userDTO.setUserAccount(getUserAccount(userId));  
    return userDTO;
}
private  UserDTO getUserBasicInfo(Integer userId);
private  UserLastOrder getUserLastOrder(Integer userId);
private  UserAccount getUserAccount(Integer userId){
      try{
          // TODO
      } catch ( Exception e) 
       { //TODO }
}

封装多个参数

 如果方法参数将超过3个,建议放在类中包装起来,否则再增加参数时,由于语义的强耦合会导致调用方语法错误。在后台管理中的分页查询接口,常常会有很多查询参数,而且有可能增加,封装起来是最好的。

看看反例:
// 分页查询订单 6个参数
public Page<Order> queryOrderByPage(Integer current,Integer size,String productName,Integer userId,Date startTime,Date endTime,Bigdecimal minAmount ,Bigdecimal maxAmount) {

}
看看正例:
@Getter
@Setter
public class OrderQueryDTO extends PageDTO {
 private String productName;
 private Integer userId;
 private Date startTime;
 private Date endTime;
 private Bigdecimal minAmount ;
 private Bigdecimal maxAmount;
}
// 分页查询订单 6个参数
Public Page<Order> queryOrderByPage(OrderQueryDTO orderQueryDTO) {

}

第三方库

Lombok

Lombok组件通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法
举例如下:
@Setter 注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
@Getter 使用方法同上,区别在于生成的是getter方法。
@ToString 注解在类,添加toString方法。
@EqualsAndHashCode 注解在类,生成hashCode和equals方法。
@NoArgsConstructor 注解在类,生成无参的构造方法。
@RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
@AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。
@Data 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。

常规写法:
Public class Order {
     private Integer userId;
     
     public Integer getUserId() {
          return userId;
    } 

    public void setUserId(Integer userId) {
          return this.userId = userId; 
 }
}
采用Lombok:
@Getter
@Setter
Public class Order {
     private Integer userId;
}

Apache Commons系列

  Apache Commons系列组件给我们提供了关于字符串、集合、IO操作等工具方法。这些组件是个大宝库,提供了不少轮子。

组件 介绍
beanUtils JavaBean进行各种操作,克隆对象、属性等等
codec 处理常用的编码方法的工具类包,例如DES、SHA1、MD5、Base64等.
collections java集合框架操作
configuration java应用程序的配置管理类库
io io工具的封装
lang Java基本对象方法的工具类包 如StringUtils、ArrayUtils等等.
logging 提供的日志接口
net 提供了客户端和服务器端的数据验证框架
看看例子:
例1: 判断集合是否为空:
CollectionUtils.isEmpty(null): true
CollectionUtils.isEmpty(new ArrayList()): true
CollectionUtils.isEmpty({a,b}): false

例2: 判断集合是否不为空:
CollectionUtils.isNotEmpty(null): false
CollectionUtils.isNotEmpty(new ArrayList()): false
CollectionUtils.isNotEmpty({a,b}): true

例3:2个集合间的操作: 
集合a: {1,2,3,3,4,5}
集合b: {3,4,4,5,6,7}
CollectionUtils.union(a, b)(并集): {1,2,3,3,4,4,5,6,7}
CollectionUtils.intersection(a, b)(交集): {3,4,5}
CollectionUtils.disjunction(a, b)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.disjunction(b, a)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.subtract(a, b)(A与B的差): {1,2,3}
CollectionUtils.subtract(b, a)(B与A的差): {4,6,7}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK