封装判断
即在判断条件很复杂时,可以将判断抽取成一个函数,再为该函数进行合理命名,提高代码的可读性
函数参数
当函数中的参数过多时,考虑是否可以对参数进行封装
// 绘制一条直线Line makeLine(double startX, double startY, double endX, double endY);// 上述参数中X和Y时作为一组概念被共同传递,因此可以为这组概念提供了一个新的抽象PointLine 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() 函数
减少代码冗余
减少代码冗余,即合理使用函数式编程
