基於快速失敗的軟體開發 - levelup

banq發表於2021-07-06

本文介紹了fail-fast 原理、它的優點、如何應用它以及我的個人經驗。儘管看起來違反直覺,但快速失敗會使您的應用程式更加健壯。使用快速失敗原則,錯誤和故障會更快出現,這使得它們更容易修復。
如果本文啟發您在程式碼庫中應用快速失敗原則,您可以立即開始使用它。即使您將該原則應用於單個檔案,其積極效果也已經很明顯。
您的自動駕駛汽車會在啟動過程中檢測到出現故障的感測器。你希望接下來發生什麼?
  1. 汽車在感測器損壞的情況下行駛,讓我們看看它能走多遠。
  2. 汽車拒絕移動並通知您感測器損壞。


我猜你選擇了選項二。我們可以將此選項稱為快速失敗選項。沒有什麼可怕的事情發生,因為汽車會盡快停止正常執行。在軟體開發過程中可以應用類似的概念,這就是本文的全部內容。
 

聽起來是個可怕的主意
軟體開發人員使用許多技術來防止他們的程式失敗。一些例子包括:

  • 回退
  • 稍後重試
  • 優雅降級

使用這些技術的程式會在發生錯誤後繼續工作,但以後可能會以意想不到的方式失敗。它們會導致緩慢或無聲的故障。

默默地失敗就像把頭埋在沙子裡。它沒有解決根本問題,只是假裝問題不存在。—弗拉基米爾·霍里科夫

不要把頭埋在沙子裡,而是要快速失敗!快速失敗的軟體會盡早失敗並儘可能地可見²。這聽起來像是一個可怕的想法,但事實並非如此。
 

故障緩慢的自動駕駛汽車
讓我們假設介紹中的自動駕駛汽車在感測器損壞的情況下嘗試自動駕駛。它可以毫無問題地駛離停車場,但一旦我們到達開闊的道路,災難就會襲來。損壞的攝像頭導致汽車墜毀。車子全毀了,我們很幸運沒有人受傷。
鑑於汽車的當前狀態,確定碰撞原因是一個費力的過程。專家們只有在經過漫長而昂貴的調查後才發現損壞的感測器。
快速失敗的汽車不會讓這樣的事故發生。感測器不會自行修復,但故障排除很容易,因為汽車會向您顯示問題所在。
也許這是一個牽強附會的例子來說明問題。然而,類似的概念也適用於故障緩慢軟體。慢故障軟體也可能以意想不到的方式崩潰,因為小問題沒有被注意到,使除錯成為一個複雜而昂貴的過程。
 

快速失敗的優勢
故障快速軟體中的錯誤和故障會更早地出現,使它們更容易重現和修復,這使得軟體開發更加愉快。

  • 節約成本

進入生產環境的錯誤和故障更少,因為它們會更快出現¹。據 IBM 稱,及早發現錯誤可以節省大量成本,因為一旦錯誤進入生產環境,修復錯誤的成本可能會高出五倍⁴。
  • 防止資料損壞

快速失敗原則可防止軟體進入無效狀態,因此無法在資料庫中持久化無效狀態⁵。這是一個巨大的優勢,因為資料損壞可能難以修復。
  • 沒有銀彈

你不能透過快速失敗來解決所有問題,錯誤和失敗仍然會發生。快速失敗原則僅有助於儘快發現問題,從長遠來看使您的應用程式更加健壯。
儘管快速失敗有很多優點,但使用優雅降級等慢速失敗原則也有其時間和地點。
 

實踐中的原則
我們已經看到快速失敗並不是一個可怕的想法,那麼我們如何應用它呢?
儘管快速失敗原則與語言無關,但我是一名 Java 開發人員,因此本節使用 Java 程式碼示例。

  • 斷言

在出現問題之前,我們不應該失敗。為了確定是否出現問題,我們使用斷言。
斷言是快速故障應用程式的基本構建塊。斷言是驗證條件的簡單程式碼段,如果驗證失敗,它們會丟擲異常。¹。
大多數程式語言都有可用的斷言庫。如果沒有,編寫自己的斷言方法很容易。我們可以在下面看到一個簡單的例子。

public final class Assert {

    private Assert() {
        // Hide constructor to avoid instantiation
    }

    public static void notEmpty(final String obj, final String errorMessage) {
        if (obj == null || obj.isEmpty()) {
            throw new IllegalStateException(errorMessage);
        }
    }
}


好的斷言庫允許您傳遞錯誤訊息,該訊息將作為異常的一部分顯示。此訊息起著至關重要的作用,因為它允許您傳達錯誤的內容,從而使問題易於解決。
  • 戰術斷言放置

為每個單獨的變數賦值用斷言向你的程式碼傳送垃圾郵件是沒有意義的。相反,問問自己斷言如何有助於使常見問題易於發現和修復:

放置你的斷言,以便軟體更早地失敗——接近原始問題——使問題易於發現。—吉姆·肖爾
我發現快速失敗斷言最有用的地方是建立不可變物件。顧名思義,不可變物件在我們例項化它們後永遠不會改變。透過確保我們使用有效值例項化一個不可變物件,我們在以後使用它時不必擔心它包含無效資料。

public class Person {
    private final String firstName;
    private final String lastName;

    public Person(String firstName, String lastName) {
        Assert.notEmpty(firstName, "firstName cannot be empty");
        Assert.notEmpty(lastName, "lastName cannot be empty");

        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters, equals(), and hashCode() omitted for brevity
}


另一個使用快速失敗斷言的教科書示例是在啟動引數的初始化期間。如果缺少啟動引數或包含無效資料,您可以使用斷言來確保應用程式無法啟動。
  • 無需自毀

 聽起來您的應用程式應該在遇到每個錯誤時自毀,但這並不是快速失敗的重點。
假設您的應用程式公開了一個 REST 端點。我們不應在收到無效請求時關閉應用程式。相反,我們應該忽略傳入的請求並向客戶端傳送錯誤訊息。我們仍然可以將這種方法視為快速失敗,因為我們會盡快失敗並傳達簡單的錯誤訊息。


 

相關文章