超级实用开发技巧

6 篇文章 0 订阅


最近经常逛github,看到里面一些好的项目和经验,最近看到了一个leader写的内容,对我很有启发,这半年看了不少源码,但是在实际开发中却很好使用起来,总结来说,总是想把人家的开发方式往我自己的业务开发上面套,只学到了皮毛,却没有理解别人为啥要这么做,导致实际场景中不能使用(因为自己的场景实在简单,只是些CURD),但是这篇作者的文章却给了我启发。原作者文章链接如下:(https://xwjie.github.io/rule/util.html#%E7%89%A9%E7%90%86%E4%B8%8A%E7%8B%AC%E7%AB%8B%E5%AD%98%E6%94%BE)

1.编码风格比技术更加重要

简单的删除controller如下:

@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
  Map<String, Object> data = new HashMap<String, Object>();

  boolean result = false;
  try {
    // 语言(中英文提示不同)
    Locale local = "zh".equalsIgnoreCase(lang) ? Locale.CHINESE : Locale.ENGLISH;

    result = configService.delete(id, local);

    data.put("code", 0);

  } catch (CheckException e) {
    // 参数等校验出错,这类异常属于已知异常,不需要打印堆栈,返回码为-1
    data.put("code", -1);
    data.put("msg", e.getMessage());
  } catch (Exception e) {
    // 其他未知异常,需要打印堆栈分析用,返回码为99
    log.error(e);

    data.put("code", 99);
    data.put("msg", e.toString());
  }

  data.put("result", result);

  return data;
}

可以看到上面的代码写了很多和业务逻辑无关的内容,如返回值时Map,兼容了正常和错误的情况,处理了语言问题。如语言和异常处理的封装,完全可以抽出公共模块来做

大佬改成如下:

@PostMapping("/delete")
public ResultBean<Boolean> delete(long id) {
  return new ResultBean<Boolean>(configService.delete(id));
}

很简单吧,用到的技术是AOP,也不高深,但是人家就能想到。技术无所谓高低,看你怎么用。

2.工具类编写

  • 使用父类接口
    举个例子,假设我们需要判断一个arrayList是否为空的函数,一开始是这样的。
    public static boolean isEmpty(ArrayList<?> list) {
    return list == null || list.size() == 0;
    }
    这个时候需要考虑一下,参数的类型能不能使用父类,我们看到只使用了size方法,我们可以知道size方法在list接口上有,于是我们修改成这样。
    public static boolean isEmpty(List<?> list) {
    return list == null || list.size() == 0;
    }
    后面发现,size方法在list的父类/接口Collection上也有,那么我们可以修改为最终这样
    public static boolean isEmpty(Collection<?> list) {
    return list == null || list.size() == 0;
    }
    到了这步,Collection没有父类/接口有size方法了,修改就可以结束了。最后我们需要把参数名字改一下,不要再使用list。改完后,所有实现了Collection的对象都可使用,最终版本如下:
    public static boolean isEmpty(Collection<?> collection) {
    return collection == null || collection.size() == 0;
    }

  • 使用重载编写衍生接口
    举个例子:现在需要编写一个方法,输入是一个utf-8格式的文件的文件名,把里面内容输出到一个list《 String》。我们刚刚开始编写的时候,是这个样子的
    public static List readFile2List(String filename) throws IOException {
    List list = new ArrayList();

      File file = new File(filename);
    
      FileInputStream fileInputStream = new FileInputStream(file);
    
      BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream, 
         "UTF-8"));
    
      // XXX操作
    
      return list;
    }
    

    很显然,编码格式有可能被修改,可传的参数有File,FileInputStream等类型,所以我们需要都支持
    package plm.common.utils;

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.commons.io.IOUtils;
    
    /**
     * 工具类编写范例,使用重载编写不同参数类型的函数组
     * 
     * @author 晓风轻 https://github.com/xwjie/PLMCodeTemplate
     *
     */
    public class FileUtil {
    
      private static final String DEFAULT_CHARSET = "UTF-8";
    
      public static List<String> readFile2List(String filename) throws IOException {
        return readFile2List(filename, DEFAULT_CHARSET);
      }
    
      public static List<String> readFile2List(String filename, String charset)
        throws IOException {
        FileInputStream fileInputStream = new FileInputStream(filename);
        return readFile2List(fileInputStream, charset);
      }
    
      public static List<String> readFile2List(File file) throws IOException {
        return readFile2List(file, DEFAULT_CHARSET);
      }
    
      public static List<String> readFile2List(File file, String charset)
        throws IOException {
        FileInputStream fileInputStream = new FileInputStream(file);
        return readFile2List(fileInputStream, charset);
      }
    
      public static List<String> readFile2List(InputStream fileInputStream)
        throws IOException {
        return readFile2List(fileInputStream, DEFAULT_CHARSET);
      }
    
      public static List<String> readFile2List(InputStream inputStream, String charset)
        throws IOException {
        List<String> list = new ArrayList<String>();
    
        BufferedReader br = null;
        try {
          br = new BufferedReader(new InputStreamReader(inputStream, charset));
    
          String s = null;
          while ((s = br.readLine()) != null) {
            list.add(s);
          }
        } finally {
          IOUtils.closeQuietly(br);
        }
    
        return list;
      }
    
    }
    

    多想一步,根据参数变化编写各种类型的入参函数,需要保证函数主要代码只有一份。

3.抽象接口定义

最开始我们写的代码如下

User user = new User();
BeanUtils.copyProperties(userInputDTO,user);

上面的代码很好的简化和优化了代码,但是他的语义是有问题的,我们需要体现一个转化过程

public interface DTOConvert<S,T> {
    T convert(S s);
}

public class UserInputDTO {
	private String username;
	private int age;
	public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    public User convertToUser(){
        UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert();
        User convert = userInputDTOConvert.convert(this);
        return convert;
    }

    private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
        @Override
        public User convert(UserInputDTO userInputDTO) {
            User user = new User();
            BeanUtils.copyProperties(userInputDTO,user);
            return user;
        }
    }

}

调用方法如下:

User user = userInputDTO.convertToUser();
User saveUserResult = userService.addUser(user);

比之前简洁了不少,再查工具类后,GUAUA实现了我们自定义的DTOConver接口

public abstract class Converter<A, B> implements Function<A, B> {
    protected abstract B doForward(A a);
    protected abstract A doBackward(B b);
    //其他略
}

他可以支持正向转化和逆向转化,继续修改我们的转换代码

public class UserDTO {
    private String username;
    private int age;

	//setget方法省略

    public User convertToUser(){
            UserDTOConvert userDTOConvert = new UserDTOConvert();
            User convert = userDTOConvert.convert(this);
            return convert;
    }

    public UserDTO convertFor(User user){
            UserDTOConvert userDTOConvert = new UserDTOConvert();
            UserDTO convert = userDTOConvert.reverse().convert(user);
            return convert;
    }

    private static class UserDTOConvert extends Converter<UserDTO, User> {
            @Override
            protected User doForward(UserDTO userDTO) {
                    User user = new User();
                    BeanUtils.copyProperties(userDTO,user);
                    return user;
            }

            @Override
            protected UserDTO doBackward(User user) {
                    UserDTO userDTO = new UserDTO();
                    BeanUtils.copyProperties(user,userDTO);
                    return userDTO;
            }
    }
}

最终调用方式如下:

@PostMapping
 public UserDTO addUser(UserDTO userDTO){
         User user =  userDTO.convertToUser();
         User saveResultUser = userService.addUser(user);
         UserDTO result = userDTO.convertFor(saveResultUser);
         return result;
 }

4.巧用lombok

  • bean中的链式风格
    @Accessors(chain = true)
    @Setter
    @Getter
    public class Student {
    private String name;
    private int age;
    }
    调用方式(但是仅限在new的时候可以用,如果给实例赋值则不能使用)
    Student student = new Student()
    .setAge(24)
    .setName(“zs”);

  • bean的必输字段如name,一般方法是将name字段包装成一个构造方法,只有传入name这样的构造方法,才能创建一个Student对象。lombok可以用@RequiredArgsConstructor解决
    @Accessors(chain = true)
    @Setter
    @Getter
    @RequiredArgsConstructor(staticName = “ofName”)
    public class Student {
    @NonNull private String name;
    private int age;
    }
    测试代码:
    Student student = Student.ofName(“zs”);

  • 使用builder,builder模式大家都知道,lombok中有一个注解可以替代@Builder
    一般的builder模式如下:
    public class Student {
    private String name;
    private int age;

        public String getName() {
                return name;
        }
    
        public void setName(String name) {
                this.name = name;
        }
    
        public int getAge() {
                return age;
        }
    
        public void setAge(int age) {
                this.age = age;
        }
    
        public static Builder builder(){
                return new Builder();
        }
        public static class Builder{
                private String name;
                private int age;
                public Builder name(String name){
                        this.name = name;
                        return this;
                }
    
                public Builder age(int age){
                        this.age = age;
                        return this;
                }
    
                public Student build(){
                        Student student = new Student();
                        student.setAge(age);
                        student.setName(name);
                        return student;
                }
        }
    
    }
    

    调用方式:
    Student student = Student.builder().name(“zs”).age(24).build();
    这样的builder代码,实在是恶心难受,于是可以用lombok替代
    @Builder
    public class Student {
    private String name;
    private int age;
    }
    调用方式:
    Student student = Student.builder().name(“zs”).age(24).build();

5.try-with-resource语句调用

JDK7之后,Java多了个新的语法:try-with-resources语句,

可以理解为是一个声明一个或多个资源的 try语句(用分号隔开),

一个资源作为一个对象,并且这个资源必须要在执行完关闭的,

try-with-resources语句确保在语句执行完毕后,每个资源都被自动关闭 。

任何实现了** java.lang.AutoCloseable的对象, 包括所有实现了 java.io.Closeable** 的对象

, 都可以用作一个资源。作者:程序鱼链接:https://www.jianshu.com/p/258c5ce1a2bd来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

private void handle(String fileName) {
    //括号里面的流会被自动关闭
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        String line;
        while ((line = reader.readLine()) != null) {
            ...
        }
    } catch (Exception e) {
        ...
    }
}

6.减少Null != XXX 的写法

尽所周知,好多变量都有可能为null,所有就写了满屏的null != xxx 的判断,这种判断多了,就很碍眼了,怎么能够减少这种写法,还能不影响业务逻辑

返回值为null有两种含义

  • null代表着一种实际意义,这个值就存在有null的情况
  • null值为错误值,后续操作不允许此值为null

如上两种情况,第一种情况,后续代码就是要兼容为null的情况,第二种情况就要想办法避免返回null值,或者是否需要抛出一个异常。

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) { //再找不到匹配的action的时候,我们返回默认的action,防止获取action的后续代码出现null的情况
      return DO_NOTHING;
    }
  }}

7.正确使用equals方法

Objects的equals的方法经常报空指针异常,所以我们都知道需要使用常量或者确定的对象来调用equals方法。举个例子

// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
String str = null;
if (str.equals("SnailClimb")) {
  ...
} else {
  ..
}

运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。:

"SnailClimb".equals(str);// false 

不过更推荐使用 java.util.Objects#equals(JDK7 引入的工具类)。

Objects.equals(null,"SnailClimb");// false

我们看一下java.util.Objects#equals的源码就知道原因了。

public static boolean equals(Object a, Object b) {
        // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
        return (a == b) || (a != null && a.equals(b));
    }

注意:

  • 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
  • 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中null == null将返回true。
  • 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值