我正在参加「掘金·启航计划」
防御式编程背景
定义
防御性编程(Defensive programming)是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要用于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。
所以说防御式编程的主要思想是:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。
更一般地说,其核心想法是要承认程序都会有问题,都需要被修改,聪明的程序员应该根据这一点来编程序。
手段
防御式编程就是不信任任何外部输入参数,在程序未执行前解决掉所有的风险点,主要有以下几点:
- 检查所有外部输入数据的值
- 检查子程序所有数据参数的值
- 决定如何处理错误的输入数据
目的
- 提高工程质量,减少bug和问题
- 提高代码可读性,使得代码更容易理解
- 让程序能通过预期的行为来处理不可预期的用户操作
举个例子,下面的代码就是关于防御式编程一个简单的例子
public void doSomething(DomainA a) {
if (a == null) {
return ; //log some errorA
}
assignAction;
if (a.getB() == null) {
return ; //log some errorB
}
otherAction;
if (!(a.getB().getC instanceof DomainC)) {
return ;//log some errorC
}
doSomethingA();
doSomethingB();
doSomthingC();
}
后续我们一起看看防御式编程练习,进一步理解 Java 中如何优雅的防御式解决空指针的异常。
防御式编程练习
reference:【灰蓝 Java 训练】如何处理空值
总结:防御 NullPointerException 的方法有很多,针对不同类型可选方案也有许多。比如:
- Optional 可选值类型
- @NonNull @Nullable 注解
- StringUtils、ObjectUtils、BooleanUtils、CollectionUtils 等工具类
1. 代码纠错
// "POST".equals(xMethod)
if (xMethod.equals("POST")) {
doPost();
}
// Objects.equals(xArg1, xArg2)
if (xArg1.equals(xArg2)) {
System.out.println("arg1 == arg2");
}
2. 代码纠错
// 不可变集合不能为 null
// @NonNull 参数 (其它方式暂时没想到)
var map1 = Map.of("abc", 10, "def", 20, xStr, 30);
// 不可变集合不能为 null
// var set = new HashSet<Integer>();
// CollectionUtils.addAll(set, list1);
var list1 = Arrays.asList(1, 2, -3, 9, null, 15);
var set1 = Set.copyOf(list1);
// 不可变集合不能为 null != xStr
// ObjectUtils.isNotEmpty(xStr) && ...
if (Map.of("hello", 1, "world", 2).containsKey(xStr)) {
System.out.println("either 'hello' or 'world'");
}
3. 加注可空性注解
public static <T, U> U mapSome(T x, Function<T, U> mapper) {
return x == null ? null: mapper.apply(x);
}
@Nullable
public static <T, U> U mapSome(@Nullable T x,@NonNull Function<T, U> mapper) {
// 返回值以及参数可以加注解做校验 Idea 会做出对应的提醒
return x == null ? null: mapper.apply(x);
}
// Optional 重构
public static <T, U> U mapSome1( T x, Function<T, U> mapper) {
// Option 实现 boolean ? x1: x2;
return (U) Optional.ofNullable(x).orElse((T) mapper.apply(x));
}
4. Optional 重构
Integer xInt = Math.random() > 0.8 ? null : Math.random() > 0.5 ? 5 : 12;
// 重构以下代码
var x1 = (xInt == null || xInt % 2 != 0) ? null : xInt / 2;
if (x1 != null) {
System.out.println(x1);
}
// 重构
Optional.ofNullable(xInt).filter(x -> x % 2 == 0).map( y -> y / 2).ifPresent(System.out::println);
5. 用 Optional
重构 getTitledContent()
// 重构一下代码
@NonNull
public static String getUpperTitle(@Nullable Post post) {
if (post == null || post.getTitle() == null) {
log.warning("no title")
return "- UNTITLED -";
}
return post.getTitle().toUpperCase();
}
public class Post {
@Nullable
public String getTitle();
}
// 重构后
private static String getUpperTitle1(Post post) {
return Optional.ofNullable(post).map(Post::getTitle).orElse("- UNTITLED -").toUpperCase();
}
// 更正
@NonNull
private static String getUpperTitle2(@Nullable Post post) {
var temp = Optional.ofNullable(post).flatMap(item -> Optional.ofNullable(item.getTitle()));
if (temp.equals(Optional.empty())) {
log.warning("no title");
}
return temp.orElse("- UNTITLED -").toUpperCase();
}
// 更正 (不知道对不对 但是只找到这一种方式)
private static String getUpperTitle1(Post post) {
return Optional.ofNullable(post).
map(Post::getTitle).
orElseGet(() -> {log.warning("no title");return "- UNTITLED -";}).
toUpperCase();
}
6. 用 StringUtils
将 getChoice()
的实现重构成一行代码
String getChoice(String choice, boolean highest) {
if (choice != null && !choice.isEmpty())
return choice;
if (highest)
return "High";
return "Low";
}
// 结果为
private static String getChoice1(String choice, boolean highest) {
return StringUtils.isNotEmpty(choice) ? choice : highest ? "High": "Low";
}
7. 使用 Common Collections 重构 getIdsString()
private static final List<Integer> IMPLICIT_IDS = List.of(101, 111, 191);
public static String getIdsString(@Nullable Collection<@NonNull Integer> ids) {
if (ids == null) {
return IMPLICIT_IDS.stream()
.map(Object::toString)
.collect(Collectors.joining());
}
return Stream.concat(ids.stream(), IMPLICIT_IDS.stream())
.map(Object::toString)
.collect(Collectors.joining());
}
// 重构后
import org.apache.commons.collections4.CollectionUtils;
public static String getIdsString1(@Nullable Collection<@NonNull Integer> ids) {
ids = CollectionUtils.emptyIfNull(ids);
return Stream.concat(ids.stream(), IMPLICIT_IDS.stream())
.map(Object::toString)
.collect(Collectors.joining());
}
8. 使用 BooleanUtils
重构
public static String toHex(int n, @Nullable Boolean useUpper) {
String s = Integer.toString(n, 16);
return useUpper != null && useUpper ? s.toUpperCase() : s;
}
// 重构后
public static String toHex1(int n, Boolean useUpper) {
String s = Integer.toString(n, 16);
return BooleanUtils.toBoolean(useUpper) ? s.toUpperCase() : s;
}
9. 使用 ObjectUtils
重构
public static Instant tomorrowOf(@Nullable Instant x) {
if (x == null) {
log.debug("the base Date is null");
x = Instant.now();
}
return x.plus(Duration.ofDays(1));
}
// 重构后
public static Instant tomorrowOf1( Instant x) {
return ObjectUtils.isEmpty(x) ? Instant.now().plus(Duration.ofDays(1)) : x.plus(Duration.ofDays(1));
}
// 更正 更正
public static Instant tomorrowOf2( Instant x) {
if (ObjectUtils.isEmpty(x)) {
log.debug("the base Date is null");
x = Instant.now();
}
return x.plus(Duration.ofDays(1));
}
// 更正
public static Instant tomorrowOf3( Instant x) {
map(obj -> obj.plus(Duration.ofDays(1))).
orElseGet(() -> {log.debug("the base Date is null");return Instant.now().plus(Duration.ofDays(1));});
}
// 更正 2021-12-15 20:40
public static Instant tomorrowOf41( Instant x) {
return ObjectUtils.getFirstNonNull(
() -> x,
() -> {
log.debug("the base Date is null");
return Instant.now();
}).plus(Duration.ofDays(1));
public static Instant tomorrowOf42( Instant x) {
return ObjectUtils.getIfNull(x,
() -> {
log.debug("the base Date is null");
return Instant.now();
}).plus(Duration.ofDays(1));
}