封装判断

即在判断条件很复杂时,可以将判断抽取成一个函数,再为该函数进行合理命名,提高代码的可读性

函数参数

当函数中的参数过多时,考虑是否可以对参数进行封装

  1. // 绘制一条直线
  2. Line makeLine(double startX, double startY, double endX, double endY);
  3. // 上述参数中X和Y时作为一组概念被共同传递,因此可以为这组概念提供了一个新的抽象Point
  4. Line makeLine(Point start, Point end);

精简函数体

一个函数的函数体尽量小,如果一个函数体过长,如 3000 行,会比较难以理解

函数职责单一

  1. // 如给员工发工资的简单方法
  2. public void pay(List<Employee> employees){
  3. for (Employee e: employees){
  4. if(e.isPayDay()){
  5. Money pay = e.calculatePay();
  6. e.deliverPay(pay);
  7. }
  8. }
  9. }
  10. // 这段代码非常短小,但实际上做了三件事情:遍历所有雇员,检查是否该发工资,支付薪水
  11. // 按照SRP的原则进行拆分:
  12. public void pay(List<Employee> employees){
  13. for (Employee e: employees){
  14. payIfNecessary(e);
  15. }
  16. }
  17. private void payIfNecessary(Employee e) {
  18. if(e.isPayDay()){
  19. calculateAndDeliverPay(e);
  20. }
  21. }
  22. private void calculateAndDeliverPay(Employee e) {
  23. Money pay = e.calculatePay();
  24. e.deliverPay(pay);
  25. }

精简辅助代码

辅助代码:是程序运行中必不可少的代码,但又不是处理业务逻辑的核心代码,如判空、打印日志、鉴权、降级和缓存检查等

优化判空:为了不抛出NPE(Null Pointer Exception),经常需要 if(obj == null) return; 的代码。这样的判空代码一多时,会影响阅读代码的流程性

  1. // 获取属性值
  2. String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
  3. // 因为任何访问对象或属性的调用都可能导致NPE,所以如果要确保不抛出异常,每次都需要进行明确的检查
  4. if (user != null) {
  5. Address address = user.getAddress();
  6. if (address != null) {
  7. Country country = address.getCountry();
  8. if (country != null) {
  9. String isocode = country.getIsocode();
  10. if (isocode != null) {
  11. isocode = isocode.toUpperCase();
  12. }
  13. }
  14. }
  15. }
  16. // 在jdk8时可以使用Optional进行包装
  17. String isocode = Optional.ofNullable(user)
  18. .flatMap(User::getAddress)
  19. .flatMap(Address::getCountry)
  20. .map(Country::getIsocode)
  21. .orElse("default");

组合模式和SLAP

  • 组合模式:要求将一个大函数拆成多个子函数的组合
  • SLAP:Single Level of Abstration Principle,抽象层次一致性,该原则要求函数体中的内容必须在同一个抽象层次上,如果高层次抽象和底层细节杂糅在一起,就会显得凌乱

示例:

  1. // 假设存在泡咖啡的原始需求,其步骤为
  2. // 1.导入咖啡粉
  3. // 2.加入沸水
  4. // 3.搅拌
  5. public void makeCaffee(){
  6. // 倒入咖啡粉
  7. pourCoffeePowder();
  8. // 加入沸水
  9. pourwater();
  10. // 搅拌
  11. stir();
  12. }
  13. // 如果新增需求,如添加不同的咖啡粉,以及选择不同的风味,则代码会编程如下
  14. public void makeCoffee(boolean isMilkCoffee, boolean isSweetTooth, CoffeeType type) {
  15. //选择咖啡粉
  16. if (type == CAPPUCCINO) {
  17. pourCappuccinoPowder();
  18. }
  19. else if (type == BLACK) {
  20. pourBlackPowder();
  21. }
  22. else if (type == MOCHA) {
  23. pourMochaPowder();
  24. }
  25. else if (type == LATTE) {
  26. pourLattePowder();
  27. }
  28. else if (type == ESPRESSO) {
  29. pourEspressoPowder();
  30. }
  31. //加入沸水
  32. pourWater();
  33. //选择口味
  34. if (isMilkCoffee) {
  35. pourMilk();
  36. }
  37. if (isSweetTooth) {
  38. addSugar();
  39. }
  40. //搅拌
  41. stir();
  42. }

按照组合函数和 SLAP 原则,我们需要在入口函数中只显示业务处理的主要步骤,具体的实现细节通过私有方法进行封装,并通过抽象一致性来保证,一个函数中的抽象在同一个水平上,而不是高层抽象和实现细节混杂在一起

  1. // 因此可以将上述代码重构为:
  2. public void makeCoffee(boolean isMilkCoffee, boolean isSweetTooth, CoffeeType type) {
  3. //选择咖啡粉
  4. pourCoffeePowder(type);
  5. //加入沸水
  6. pourWater();
  7. //选择口味
  8. flavor(isMilkCoffee, isSweetTooth);
  9. //搅拌
  10. stir();
  11. }
  12. private void flavor(boolean isMilkCoffee, boolean isSweetTooth) {
  13. if (isMilkCoffee) {
  14. pourMilk();
  15. }
  16. if (isSweetTooth) {
  17. addSugar();
  18. }
  19. }
  20. private void pourCoffeePowder(CoffeeType type) {
  21. if (type == CAPPUCCINO) {
  22. pourCappuccinoPowder();
  23. }
  24. else if (type == BLACK) {
  25. pourBlackPowder();
  26. }
  27. else if (type == MOCHA) {
  28. pourMochaPowder();
  29. }
  30. else if (type == LATTE) {
  31. pourLattePowder();
  32. }
  33. else if (type == ESPRESSO) {
  34. pourEspressoPowder();
  35. }
  36. }

优秀示范:

  • Spring 的 AbstractApplicationContext 的 refresh() 函数

减少代码冗余

减少代码冗余,即合理使用函数式编程