1. 程式人生 > 其它 >騰訊大佬總結的程式碼重構原則,看完再也不怕面試官問啦!

騰訊大佬總結的程式碼重構原則,看完再也不怕面試官問啦!

前言

上一篇開閉原則最有用的程式碼改動是基於 “修改” 的方式來實現新功能的。如果我們遵循開閉原則,也就是 “對擴充套件開放、對修改關閉”。那如何通過 “擴充套件” 的方式,來實現同樣的功能呢?

重構

我們先重構一下之前的 Alert 程式碼,讓它的擴充套件性更好一些。重構的內容主要包含兩部分:

  1. 第一部分是將 check () 函式的多個入參封裝成 ApiStatInfo 類;

  2. 第二部分是引入 handler 的概念,將 if 判斷邏輯分散在各個 handler 中。

具體的程式碼實現如下所示:

public class Alert {

  private List<AlertHandler> alertHandlers = new 
ArrayList
<>(); public void addAlertHandler(AlertHandler alertHandler) { this.alertHandlers.add(alertHandler); } public void check(ApiStatInfo apiStatInfo) { for (AlertHandler handler : alertHandlers) { handler.check(apiStatInfo); } } } public class ApiStatInfo {// 省略 constructor/getter/setter 方法 private String api; private long requestCount; private long errorCount; private long durationOfSeconds; } public abstract class AlertHandler { protected AlertRule rule; protected Notification notification; public AlertHandler(AlertRule rule, Notification notification) { this.rule = rule; this.notification = notification; } public abstract void check(ApiStatInfo apiStatInfo); } public class TpsAlertHandler extends AlertHandler { public TpsAlertHandler(AlertRule rule, Notification notification) { super(rule, notification); } @Override public void check(ApiStatInfo apiStatInfo) { long tps = apiStatInfo.getRequestCount()/ apiStatInfo.getDurationOfSeconds(); if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) { notification.notify(NotificationEmergencyLevel.URGENCY, "..."); } } } public class ErrorAlertHandler extends AlertHandler { public ErrorAlertHandler(AlertRule rule, Notification notification){ super(rule, notification); } @Override public void check(ApiStatInfo apiStatInfo) { if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) { notification.notify(NotificationEmergencyLevel.SEVERE, "..."); } } }

上面的程式碼是對 Alert 的重構,我們再來看下,重構之後的 Alert 該如何使用呢?具體的使用程式碼我也寫在這裡了。

其中,ApplicationContext 是一個單例類,負責 Alert 的建立、組裝(alertRule 和 notification 的依賴注入)、初始化(新增 handlers)工作。


public class ApplicationContext {

  private AlertRule alertRule;

  private Notification notification;

  private Alert alert;

  public void initializeBeans() {

    alertRule = new AlertRule(/*. 省略引數.*/); // 省略一些初始化程式碼

    notification = new Notification(/*. 省略引數.*/); // 省略一些初始化程式碼

    alert = new Alert();

    alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));

    alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));

  }

  public Alert getAlert() { return alert; }

  // 餓漢式單例

  private static final ApplicationContext instance = new ApplicationContext();

  private ApplicationContext() {

    instance.initializeBeans();

  }

  public static ApplicationContext getInstance() {

    return instance;

  }

}

public class Demo {

  public static void main(String[] args) {

    ApiStatInfo apiStatInfo = new ApiStatInfo();

    //... 省略設定 apiStatInfo 資料值的程式碼

    ApplicationContext.getInstance().getAlert().check(apiStatInfo);

  }

}

現在,我們再來看下,基於重構之後的程式碼,如果再新增上面講到的那個新功能,每秒鐘介面超時請求個數超過某個最大閾值就告警,我們又該如何改動程式碼呢?主要的改動有下面四處。

  1. 第一處改動是:在 ApiStatInfo 類中新增新的屬性 timeoutCount。
  2. 第二處改動是:新增新的 TimeoutAlertHander 類。
  3. 第三處改動是:在 ApplicationContext 類的 initializeBeans () 方法中,往 alert 物件中註冊新的 timeoutAlertHandler。
  4. 第四處改動是:在使用 Alert 類的時候,需要給 check () 函式的入參 apiStatInfo 物件設定 timeoutCount 的值。

改動之後的程式碼如下所示:


public class Alert { // 程式碼未改動... }

public class ApiStatInfo {// 省略 constructor/getter/setter 方法

  private String api;

  private long requestCount;

  private long errorCount;

  private long durationOfSeconds;

  private long timeoutCount; // 改動一:新增新欄位

}

public abstract class AlertHandler { // 程式碼未改動... }

public class TpsAlertHandler extends AlertHandler {// 程式碼未改動...}

public class ErrorAlertHandler extends AlertHandler {// 程式碼未改動...}

// 改動二:新增新的 handler

public class TimeoutAlertHandler extends AlertHandler {// 省略程式碼...}

public class ApplicationContext {

  private AlertRule alertRule;

  private Notification notification;

  private Alert alert;

  public void initializeBeans() {

    alertRule = new AlertRule(/*. 省略引數.*/); // 省略一些初始化程式碼

    notification = new Notification(/*. 省略引數.*/); // 省略一些初始化程式碼

    alert = new Alert();

    alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));

    alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));

    // 改動三:註冊 handler

    alert.addAlertHandler(new TimeoutAlertHandler(alertRule, notification));

  }

  //... 省略其他未改動程式碼...

}

public class Demo {

  public static void main(String[] args) {

    ApiStatInfo apiStatInfo = new ApiStatInfo();

    //... 省略 apiStatInfo 的 set 欄位程式碼

    apiStatInfo.setTimeoutCount(289); // 改動四:設定 tiemoutCount 值

    ApplicationContext.getInstance().getAlert().check(apiStatInfo);

}

重構之後的程式碼更加靈活和易擴充套件。如果我們要想新增新的告警邏輯,只需要基於擴充套件的方式建立新的 handler 類即可,不需要改動原來的 check () 函式的邏輯。而且,我們只需要為新的 handler 類新增單元測試,老的單元測試都不會失敗,也不用修改。

重點回顧

如何理解 “對擴充套件開放、對修改關閉”?

新增一個新的功能,應該是通過在已有程式碼基礎上擴充套件程式碼(新增模組、類、方法、屬性等),而非修改已有程式碼(修改模組、類、方法、屬性等)的方式來完成。關於定義,我們有兩點要注意。第一點是,開閉原則並不是說完全杜絕修改,而是以最小的修改程式碼的代價來完成新功能的開發。第二點是,同樣的程式碼改動,在粗程式碼粒度下,可能被認定為 “修改”;在細程式碼粒度下,可能又被認定為 “擴充套件”。

更多原創閱讀java實驗室