封装判断
即在判断条件很复杂时,可以将判断抽取成一个函数,再为该函数进行合理命名,提高代码的可读性
函数参数
当函数中的参数过多时,考虑是否可以对参数进行封装
// 绘制一条直线
Line makeLine(double startX, double startY, double endX, double endY);
// 上述参数中X和Y时作为一组概念被共同传递,因此可以为这组概念提供了一个新的抽象Point
Line makeLine(Point start, Point end);
精简函数体
一个函数的函数体尽量小,如果一个函数体过长,如 3000 行,会比较难以理解
函数职责单一
// 如给员工发工资的简单方法
public void pay(List<Employee> employees){
for (Employee e: employees){
if(e.isPayDay()){
Money pay = e.calculatePay();
e.deliverPay(pay);
}
}
}
// 这段代码非常短小,但实际上做了三件事情:遍历所有雇员,检查是否该发工资,支付薪水
// 按照SRP的原则进行拆分:
public void pay(List<Employee> employees){
for (Employee e: employees){
payIfNecessary(e);
}
}
private void payIfNecessary(Employee e) {
if(e.isPayDay()){
calculateAndDeliverPay(e);
}
}
private void calculateAndDeliverPay(Employee e) {
Money pay = e.calculatePay();
e.deliverPay(pay);
}
精简辅助代码
辅助代码:是程序运行中必不可少的代码,但又不是处理业务逻辑的核心代码,如判空、打印日志、鉴权、降级和缓存检查等
优化判空:为了不抛出NPE(Null Pointer Exception),经常需要 if(obj == null) return; 的代码。这样的判空代码一多时,会影响阅读代码的流程性
// 获取属性值
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
// 因为任何访问对象或属性的调用都可能导致NPE,所以如果要确保不抛出异常,每次都需要进行明确的检查
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
// 在jdk8时可以使用Optional进行包装
String isocode = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");
组合模式和SLAP
- 组合模式:要求将一个大函数拆成多个子函数的组合
- SLAP:Single Level of Abstration Principle,抽象层次一致性,该原则要求函数体中的内容必须在同一个抽象层次上,如果高层次抽象和底层细节杂糅在一起,就会显得凌乱
示例:
// 假设存在泡咖啡的原始需求,其步骤为
// 1.导入咖啡粉
// 2.加入沸水
// 3.搅拌
public void makeCaffee(){
// 倒入咖啡粉
pourCoffeePowder();
// 加入沸水
pourwater();
// 搅拌
stir();
}
// 如果新增需求,如添加不同的咖啡粉,以及选择不同的风味,则代码会编程如下
public void makeCoffee(boolean isMilkCoffee, boolean isSweetTooth, CoffeeType type) {
//选择咖啡粉
if (type == CAPPUCCINO) {
pourCappuccinoPowder();
}
else if (type == BLACK) {
pourBlackPowder();
}
else if (type == MOCHA) {
pourMochaPowder();
}
else if (type == LATTE) {
pourLattePowder();
}
else if (type == ESPRESSO) {
pourEspressoPowder();
}
//加入沸水
pourWater();
//选择口味
if (isMilkCoffee) {
pourMilk();
}
if (isSweetTooth) {
addSugar();
}
//搅拌
stir();
}
按照组合函数和 SLAP 原则,我们需要在入口函数中只显示业务处理的主要步骤,具体的实现细节通过私有方法进行封装,并通过抽象一致性来保证,一个函数中的抽象在同一个水平上,而不是高层抽象和实现细节混杂在一起
// 因此可以将上述代码重构为:
public void makeCoffee(boolean isMilkCoffee, boolean isSweetTooth, CoffeeType type) {
//选择咖啡粉
pourCoffeePowder(type);
//加入沸水
pourWater();
//选择口味
flavor(isMilkCoffee, isSweetTooth);
//搅拌
stir();
}
private void flavor(boolean isMilkCoffee, boolean isSweetTooth) {
if (isMilkCoffee) {
pourMilk();
}
if (isSweetTooth) {
addSugar();
}
}
private void pourCoffeePowder(CoffeeType type) {
if (type == CAPPUCCINO) {
pourCappuccinoPowder();
}
else if (type == BLACK) {
pourBlackPowder();
}
else if (type == MOCHA) {
pourMochaPowder();
}
else if (type == LATTE) {
pourLattePowder();
}
else if (type == ESPRESSO) {
pourEspressoPowder();
}
}
优秀示范:
- Spring 的 AbstractApplicationContext 的 refresh() 函数
减少代码冗余
减少代码冗余,即合理使用函数式编程