美文网首页
2022-05-01代码重构 -- 大小规模重构

2022-05-01代码重构 -- 大小规模重构

作者: 竹blue | 来源:发表于2022-05-01 15:54 被阅读0次

    大规模高层次重构

    解耦代码

    “解耦”为何如此重要?

    过于复杂的代码往往在可读性、可维护性上都不友好。解耦保证代码松耦合、高内聚,是控制代码复杂度的有效手段。代码高内聚、松耦合,也就是意味着,代码结构清晰、分层模块化合理、依赖关系简单、模块或类之间的耦合小,那代码整体的质量就不会差。

    代码是否需要“解耦”?

    间接的衡量标准有很多,比如,看修改代码是否牵一发而动全身。直接的衡量标准是把模块与模块、类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。

    如何给代码“解耦”?

    给代码解耦的方法有:封装与抽象、中间层、模块化,以及一些其他的设计思想与原则,比如:单一职责原则、基于接口而非实现编程、依赖注入、多用组合少用继承、迪米特法则等。当然,还有一些设计模式,比如观察者模式。

    小规模低层次重构

    编码规范

    命名

    • 命名的关键是能准确达意。对于不同作用域的命名,我们可以适当地选择不同的长度。

    • 我们可以借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名。

      public class User {
          private String userName;
          private String name;//借助User对象上下文简化命名
      
          public void uploadUserAvatarImageToAliyun(String userAvatarImageUri);
          public void uploadUserAvatarImageToAliyun(String imageUri); // 借助函数上下文简化命名
      }
      
    • 命名要可读、可搜索。不要使用生僻的、不好读的英文单词来命名。

    • 命名要符合项目的统一规范,也不要用些反直觉的命名。

    • 接口有两种命名方式:一种是在接口中带前缀“I”;另一种是在接口的实现类中带后缀“Impl”。对于抽象类的命名,也有两种方式,一种是带上前缀“Abstract”,一种是不带前缀。这两种命名方式都可以,关键是要在项目中统一。

    注释

    • 注释的内容主要包含这样三个方面:做什么、为什么、怎么做。对于一些复杂的类和接口,我们可能还需要写明“如何用”。

      /**
      * (what) Bean factory to create beans. 
      * 
      * (why) The class likes Spring IOC framework, but is more lightweight. 
      *
      * (how) Create objects from different sources sequentially:
      * user specified object > SPI > configuration > default object.
      */
      public class BeansFactory {
        // ...
      }
      
    • 类和函数一定要写注释,而且要写得尽可能全面详细。函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码可读性。

            /**
           * 密码校验
           *
           * 对于逻辑比较复杂的代码或者比较长的函数,如果不好提炼、不好拆分成小的函数调用,
           * 那我们可以借助总结性的注释来让代码结构更清晰、更有条理。
           * 
           * @param password
           * @return
           */
          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,A~Z
              for (int i = 0; i < length; i++) {
                  char c = password.charAt(i);
                  if(!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')){
                      return false;
                  }
              }
      
              return true;
          }
      

    代码

    • 风格函数、类多大才合适?函数的代码行数不要超过一屏幕的大小,比如 50 行。类的大小限制比较难确定。
    • 一行代码多长最合适?最好不要超过 IDE 的显示宽度。当然,也不能太小,否则会导致很多稍微长点的语句被折成两行,也会影响到代码的整洁,不利于阅读。善用空行分割单元块。
    • 对于比较长的函数,为了让逻辑更加清晰,可以使用空行来分割各个代码块。四格缩进还是两格缩进?我个人比较推荐使用两格缩进,这样可以节省空间,尤其是在代码嵌套层次比较深的情况下。不管是用两格缩进还是四格缩进,一定不要用 tab 键缩进。
    • 大括号是否要另起一行?将大括号放到跟上一条语句同一行,可以节省代码行数。但是将大括号另起新的一行的方式,左右括号可以垂直对齐,哪些代码属于哪一个代码块,更加一目了然。
    • 类中成员怎么排列?在 Google Java 编程规范中,依赖类按照字母序从小到大排列。类中先写成员变量后写函数。成员变量之间或函数之间,先写静态成员变量或函数,后写普通变量或函数,并且按照作用域大小依次排列。

    编码技巧

    • 将复杂的逻辑提炼拆分成函数和类。

      // 1. 把代码分割成更小的单元块 也可理解为 将复杂的逻辑提炼拆分成函数和类
          public void invest(long userId, long financialProductId){
      
              //判断当前时间是否为本月最后一天
              Calendar instance = Calendar.getInstance();
              instance.setTime(new Date());
              instance.set(Calendar.DATE,(instance.get(Calendar.DATE)+1));
              if(instance.get(Calendar.DAY_OF_MONTH) == 1){
                  return;
              }
          }
      
          //优化后代码
          public void invest(long userId, long financialProductId){
      
              //判断当前时间是否为本月最后一天
              if(isLastDayOfMonth(new Date())){
                  return;
              }
          }
      
          private boolean isLastDayOfMonth(Date date){
              Calendar instance = Calendar.getInstance();
              instance.setTime(date);
              instance.set(Calendar.DATE,(instance.get(Calendar.DATE)+1));
              return instance.get(Calendar.DAY_OF_MONTH) == 1;
          }
      
    • 通过拆分成多个函数或将参数封装为对象的方式,来处理参数过多的情况。

      public class User {
      
          public User getUser(String userName, String telephone, String email) {
              return null;
          }
          // 方法一:根据函数职责单一的特性,将函数拆分成多个
          public User getUsetByName(String name) {return null;}
      
          public User getUsetByTelephone(String Telephone) {return null;}
      
          public User getUsetByEmail(String email) {return null;}
        
            // 场景二: 将参数封装为对象的方式
          public User getUser(UserReqest userReqest) {
              return null;
          }
      }
      
      @Data
      class UserReqest{
          private String userName;
          private String telephone;
          private String email;
      }
      
    • 函数设计要职责单一。

    • 移除过深的嵌套层次,方法包括:去掉多余的 if 或 else 语句,使用 continue、break、return 关键字提前退出嵌套,调整执行顺序来减少嵌套,将部分嵌套逻辑抽象成函数。

      /**
       * KeywordDemo类
       * 编程规范之移除过深的嵌套层次
       *
       */
      public class KeywordDemo {
          //案例一:去掉多余的 if 或 else 语句
          public List<String> matchStrings(List<String> list, String word) {
              List<String> matchedList = new ArrayList<>();
              if (list != null && !list.isEmpty()) {
                  for (String string : list) {
                      if (string != null) {
                          if (string.contains(word)) { // 跟上面的if 合并在一起。
                              matchedList.add(string);
                          }
                      }
                  }
              }
              return matchedList;
          }
      
          //案例二:使用编程语言提供的 continue、break、return 关键字,提前退出嵌套。
      
          //重构前代码。
          public List<String> matchStrings_old2(List<String> list, String word) {
              List<String> matchedList = new ArrayList<>();
              if (list != null && !list.isEmpty()) {
                  for (String string : list) {
                      if (string != null && string.contains(word)) {
                          matchedList.add(string);
                      }
                  }
              }
              return matchedList;
          }
      
          //重构后代码。
          public List<String> matchStrings_new(List<String> list, String word) {
              List<String> matchedList = new ArrayList<>();
              if (list != null && !list.isEmpty()) {
                  for (String string : list) {
                      if (string == null || !(string.contains(word))) {
                          continue; // 使用continue提前退出循环。
                      }
                      matchedList.add(string);
                  }
              }
              return matchedList;
          }
      
          //案例三:调整执行顺序来减少嵌套
          //重构前代码
          public List<String> matchStrings_old3(List<String> list, String word) {
              List<String> matchedList = new ArrayList<>();
              if (list != null && !list.isEmpty()) {
                  for (String string : list) {
                      if (string == null || !(string.contains(word))) {
                          continue; // 使用continue提前退出循环。
                      }
                      matchedList.add(string);
                  }
              }
              return matchedList;
          }
      
          //重构后代码
          public List<String> matchStrings_new3(List<String> list, String word) {
              if (list == null || list.isEmpty()) { // 先判空
                  return Collections.emptyList();
              }
              List<String> matchedList = new ArrayList<>();
      
              for (String string : list) {
                  if (string == null || !(string.contains(word))) {
                      continue; // 使用continue提前退出循环。
                  }
                  matchedList.add(string);
              }
      
              return matchedList;
          }
      
          //案例四:将部分代码封装成函数
          //重构前代码
          public List<String> appendSalts(List<String> passwords){
              if(passwords == null && passwords.isEmpty()){
                  return Collections.emptyList();
              }
      
              List<String> passwordSalts = new ArrayList();
              for (String password : passwords) {
                  if(password == null){
                      continue;
                  }
                  if(password.length() < 8){
                      //执行长度 < 8的逻辑
                  }else{
                      //执行长度 > 8的逻辑
                  }
              }
              return passwords;
          }
      
          //重构后的代码
          public List<String> appendSalts_new(List<String> passwords){
              if(passwords == null && passwords.isEmpty()){
                  return Collections.emptyList();
              }
      
              List<String> passwordSalts = new ArrayList();
              for (String password : passwords) {
                  //将代码封装成函数
                  passwordSalts.add(appendSalt(password));
              }
              return passwords;
          }
      
          private String appendSalt(String password) {
              if (password.length() < 8) {
                  //执行长度 < 8的逻辑
              } else {
                  //执行长度 > 8的逻辑
              }
              return password;
          }
      }
      
    • 用字面常量取代魔法数。

      //案例一:常量取代魔法数字
          //重构前代码
          public double CalculateCircularArea(double radius){
              return (3.1415) * radius * radius;
          }
      
          //重构后代码
      
          private static final double PI = 3.1415;
          public double CalculateCircularArea_new(double radius){
              return PI * radius *radius;
          }
      
    • 用解释性变量来解释复杂表达式,以此提高代码可读性。

       //案例二:解释性变量来解释复杂表达式。
          private static final Date SUMMER_START = null;
          private static final Date SUMMER_END = null;
          public boolean validateDate(Date date){
              if(date.after(SUMMER_START) && date.before(SUMMER_END)){
                  return true;
              }
              return false;
          }
      
          //重构后代码
          public boolean validateDate_new(Date date){
              // 引入解释性变量
              if(isSummer(date)){
                  return true;
              }
              return false;
          }
      
          private boolean isSummer(Date date) {
              return date.after(SUMMER_START) && date.before(SUMMER_END);
          }
      

    统一编码规范

    项目、团队,甚至公司,一定要制定统一的编码规范,并且通过 Code Review 督促执行,这对提高代码质量有立竿见影的效果。

    -- 来源王争老师的《设计模式之美》

    相关文章

      网友评论

          本文标题:2022-05-01代码重构 -- 大小规模重构

          本文链接:https://www.haomeiwen.com/subject/ghrpyrtx.html