您的位置 首页 java

JAVA代码规范与编写高质量代码的建议(2)

目录

  • 6. 行为参数化传递代码
  • 7. 接口验证优化与自定义注解
  • 8. 原型模式提高创建对象的性能
  • 9. CompleteFuture异步编程提高RPC效率
  • 10. AOP 实现日志记录

6. 行为参数化传递代码

软件工程中一个众所周知的问题就是,不管你做什么,用户的需求肯定会变。比方说,有个应用程序是帮助农民了解自己的库存。这位农民可能想要一个查找库存中所有绿色苹果的功能。但到了第二天,他可能会告诉你:“其实我还想找出所有重量超过150克的 苹果 。”过了两天,农民又跑回来补充道:“要是我可以找出所有既是绿色,重量也超过150克的苹果,那就太棒了。”你要如何应对这样不断变化的需求?理想的状态下,应该把你的工作量降到最少。此外,类似的新功能实现起来还应该很简单,而且易于长期维护。

行为参数化就是可以帮助你处理频繁变更的需求的一种 软件开发 模式

就农场库存程序而言,你必须实现一个从列表中筛选绿苹果的功能

6.1 初试牛刀:筛选绿苹果

 /**
 * @author Marion
 * @date 2022/5/13 18:08
 */public class Farm001 {
    /**
     * v1. 筛选绿色苹果
     */    public  static  List<Apple> filterGreenApples(List<Apple> list) {
        List<Apple> ret = new ArrayList<>();
        for (Apple apple : list) {
            if (apple.getColor().equals(ColorEnums.GREEN.name())) {
                ret.add(apple);
            }
        }
        return ret;
    }
}  

6.2 再展身手:把颜色作为参数

     /**
     * v2. 颜色作为参数
     */    public static List<Apple> filterRedApples(List<Apple> list, ColorEnums color) {
        List<Apple> ret = new ArrayList<>();
        for (Apple apple : list) {
            if (apple.getColor().equals(color.name())) {
                ret.add( Apple );
            }
        }
        return ret;
    }  

太简单了,对吧?让我们把例子再弄得复杂一点儿。这位农民又跑回来和你说:“要是能区分轻的苹果和重的苹果就太好了。重的苹果一般是重量大于150克。”

 /**
 * v2: 筛选重量苹果
 * @param flag true-比较颜色 false-比较重量
 */public static List<Apple> filterGreenApples(List<Apple> list, ColorEnums color, int wight,  boolean  flag) {
    List<Apple> ret = new ArrayList<>();
    for (Apple apple : list) {
        if (flag) {
            if (apple.getColor().equals(color.name())) {
                ret.add( apple );
            }
        } else {
            if (apple.getWeight() > wight) {
                ret.add(apple);
            }
        }
    }
    return ret;
}  

6.3 行为参数化

你在上一节中已经看到了,你需要一种比添加很多参数更好的方法来应对变化的需求。让我们后退一步来看看更高层次的抽象。一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个boolean值。我们把它称为 谓词 (即一个返回boolean值的函数)。让我们定义一个接口来对选择标准建模:

 @FunctionalInterface
public interface ApplePredicate<Apple> {
    boolean filter(Apple apple);
}  

你可以把这些标准看作filter方法的不同行为。你刚做的这些和“策略设计模式”相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法

6.4 第五次尝试:使用匿名类

 /**
 * v3: 筛选绿色
 */Farm003.filterApples(apples, new ApplePredicate<Apple>() {
    @Override
    public boolean filter(Apple apple) {
        return apple.getColor().equals(ColorEnums.GREEN.name());
    }
});

Farm003.filterApples(apples, new ApplePredicate<Apple>() {
    @Override
    public boolean filter(Apple apple) {
        return apple.getColor().equals(ColorEnums.GREEN.name()) && apple.getWeight() > 100;
    }
});  
 
/**
 * v3: 筛选重量苹果
 */public static List<Apple> filterApples(List<Apple> list, ApplePredicate<Apple> predicate) {
    List<Apple> ret = new ArrayList<>();
    for (Apple apple : list) {
        if (predicate.filter(apple)) {
            ret.add(apple);
        }
    }
    return ret;
}  

第一,它往往很笨重,因为它占用了很多空间

第二,很多程序员觉得它用起来很让人费解

6.5 第六次尝试:使用 Lambda表达式

 Farm003.filterApples(apples, apple -> apple.getColor().equals(ColorEnums.GREEN.name()));
Farm003.filterApples(apples, apple -> apple.getColor().equals(ColorEnums.GREEN.name()) && apple.getWeight() > 100);  

6.6 第七次尝试:将List类型抽象化

 @FunctionalInterface
public interface ApplePredicate<T> {
    boolean filter(T apple);
}  

6.7 第八次尝试:使用 Stream 流过滤

 
/**
 * v4: 筛选重量苹果
 */public static List<Apple> filterApples(List<Apple> list, ColorEnums color) {
    return list.stream()
            .filter(v -> v.getColor().equals(color.name()))
            .collect(Collectors.toList());
}  

7. 接口验证优化与自定义注解

validation主要是校验用户提交的数据的合法性,比如是否为空,密码是否符合规则,邮箱格式是否正确等等,校验框架比较多,用的比较多的是 hibernate -validator, 也支持国际化,也可以自定义校验类型的注解,这里只是简单地演示校验框架在SpringBoot中的简单集成,要想了解更多可以参考 hibernate-validator。

 /**
 * @author Marion
 * @date 2022/5/13 14:38
 */@Data
@NoArgs Constructor 
@AllArgsConstructor
@Builder
public class UserQuery {

     private  Long id;

    @NotBlank
    @Length(min = 4, max = 10)
    private String name;

    @NotBlank
    @Email
    private String email;

    /**
     * ^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$
     */    @NotBlank
    //@Pattern(regexp = "^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message = "手机号格式不正确")
    @Phone
    private String phone;

    @Min(value = 18)
    @Max(value = 200)
    private int age;

    @NotBlank
    @Length(min = 4, max = 12, message = "昵称4-12位")
    private String nickname;
}  
 /**
 * 全局异常处理器增加对APIException的拦截,并修改异常时返回的数据格式
 * @author Marion
 * @date 2022/5/13 14:47
 */@RestControllerAdvice
public class Global Exception Handler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AppResponse validationException(MethodArgumentNotValidException e) {
        System.out.println(e.getMessage());
        FieldError fieldError = e.getBindingResult().getFieldError();
        return AppResponse.builder()
                .code(400)
                .message(fieldError.getDefaultMessage())
                .build();
    }
}  

自定义验证注解

 /**
 * @author Marion
 * @date 2022/5/13 15:10
 */@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {

    String message() default "手机号格式不正确";

    Class<?>[] groups() default {};

    Class<?  extends  Payload>[] payload() default {};
}  
 /**
 * @author Marion
 * @date 2022/5/13 15:43
 */public class PhoneValidator implements ConstraintValidator<Phone,  String > {
    @Override
    public  void  initialize(Phone constraintAnnotation) {

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
         if  (!StringUtils.isEmpty(value)) {
            return isPhone(value);
        }
        return false;
    }

    private boolean isPhone(String phone) {
        return phone.matches("^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$");
    }
}  

8. 原型模式提高创建对象的性能

  1. 对于构造参数耗时多的使用原型模式更快
  2. 轻量级对象new效率高于原型模式
 /**
 * @author Marion
 * @date 2022/5/13 16:16
 */@Data
@NoArgsConstructor
public class UserDTO implements Cloneable {

    public UserDTO(int id, String name) {
        if (name.length() != 0) {
            name = name.substring(0, 1);
        }
        this.id = id;
        this.name = name;
    }

    private int id;

    private String name;

    @Override
    public Object clone() {
        UserDTO userDTO = null;
        try {
            userDTO = (UserDTO) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return userDTO;
    }
}  

benchmark进行性能测试

 /**
 * 原型模式创建对象
 * 1. 轻量级对象new效率高于原型模式
 * @author Marion
 * @date 2022/5/13 16:16
 */public class Demo008 {

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @Warmup(iterations = 1, timeUnit = TimeUnit.SECONDS)
    @Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Fork(1)
    @Threads(1)
    public static void newUserDTO() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            UserDTO userDTO = new UserDTO(1, "张三");
            userDTO.setName("张三");
        }
        long end = System.currentTimeMillis();
        System.out.println("newUserDTO="  + (end -start) + "ms");
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @Warmup(iterations = 1, timeUnit = TimeUnit.SECONDS)
    @Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Fork(1)
    @Threads(1)
    public static void cloneUserDTO() {
        long start = System.currentTimeMillis();
        UserDTO userDTO = new UserDTO(1, "张三");
        for (int i = 0; i < 10000000; i++) {
            UserDTO clone =  (UserDTO) userDTO.clone();
            clone.setName("李四");
        }
        long end = System.currentTimeMillis();
        System.out.println("cloneUserDTO="  + (end -start) + "ms");
    }

    public static void main(String[] args) throws RunnerException {
        OptionsBuilder builder = new OptionsBuilder();
        Options build = builder. include (Demo008.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(build).run();
    }

}
  

9. CompleteFuture异步编程提高 RPC 效率

很多语言(如JavaScript)提供了异步 回调 ,一些 Java 中间件(如 Netty 、Guava)也提供了异步回调API,为开发者带来了更好的异步编程工具。Java 8提供了一个新的、具备异步回调能力的工具类——CompletableFuture,该类实现了Future接口,还具备函数式编程的能力。

使用CompletableFuture进行多个RPC调用

 /**
 * @author Marion
 * @date 2022/5/13 17:41
 */@Service
public class ShopService {

    /**
     * v1: 串行执行
     */    public void goods() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        System.out.println(userInfo());
        System.out.println(goodsInfo());
        System.out.println(commentInfo());

        stopWatch.stop();
        System.out.println("[goods] execute time=" + stopWatch.getTotalTimeMillis() + "ms");
    }

    public void syncGoods() throws ExecutionException, InterruptedException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        CompletableFuture<String> s1 = CompletableFuture.supplyAsync(this::userInfo);
        CompletableFuture<String> s2 = CompletableFuture.supplyAsync(this::goodsInfo);
        CompletableFuture<String> s3 = CompletableFuture.supplyAsync(this::commentInfo);

        CompletableFuture<Object> objectCompletableFuture = s1.thenCombine(s2, (o1, o2) -> o1 + ":" + o2)
                .thenCombine(s3, (o1, o2) -> o1 + ":" + o2);
        String ret = (String) objectCompletableFuture.get();
        System.out.println(ret);

        stopWatch.stop();
        System.out.println("[goods] execute time=" + stopWatch.getTotalTimeMillis() + "ms");
    }

    public String userInfo() {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "userInfo";
    }

    public String goodsInfo() {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "goods";
    }

    public String commentInfo() {
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "commentInfo";
    }
}  

10. AOP实现日志记录

通过AOP进行接口输入输出参数、执行时间打印

 package com.marion.codestandard.demo010;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @author Marion
 * @date 2022/5/13 18:17
 */@Aspect
@Component
public class AopLog {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定义切点
     */    @Pointcut(value = "execution(* com.marion.codestandard.demo010.*.*(..))")
    public void aopWebLog() {
    }

    /**
     * 使用环绕通知
     */    @Around("aopWebLog()")
    public Object myLogger(ProceedingJoinPoint pjp) throws Throwable {
        startTime.set(System.currentTimeMillis());
        //使用ServletRequestAttributes请求上下文获取方法更多
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String className = pjp.getSignature().getDeclaringTypeName();
        String methodName = pjp.getSignature().getName();
        //使用数组来获取参数
        Object[] array = pjp.getArgs();
        ObjectMapper mapper = new ObjectMapper();
        //执行函数前打印日志
        logger.info("调用前:{}:{},传递的参数为:{}", className, methodName, mapper.writeValueAsString(array));
        logger.info("URL:{}", request.getRequestURL().toString());
        logger.info("IP地址:{}", request.getRemoteAddr());
        //调用整个目标函数执行
        Object obj = pjp.proceed();
        //执行函数后打印日志
        logger.info("调用后:{}:{},返回值为:{}", className, methodName, mapper.writeValueAsString(obj));
        logger.info("耗时:{}ms", System.currentTimeMillis() - startTime.get());
        return obj;
    }
}  

项目代码

编写高质量代码:改善Java程序的151个建议

微信公众号【后端研发Marion】

文章来源:智云一二三科技

文章标题:JAVA代码规范与编写高质量代码的建议(2)

文章地址:https://www.zhihuclub.com/185725.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图