分類: 程式設計

  • 如何使用 Python 進行探索性數據分析

    本文旨在了解 EDA、其在數據科學中的作用,並掌握統計分析、特徵選擇和可視化的關鍵技術。EDA 是數據分析的關鍵步驟。一旦收集了數據,下一步是清理和準備數據,並在進行複雜分析之前確定其有效性。因此,EDA 是數據分析的第一個也是最關鍵的步驟。它幫助我們

    • 了解數據集的結構和特徵。
    • 偵測遺漏值、離群值和異常值。
    • 識別模式、關係和分佈。
    • 決定機器學習模型的特徵選擇和工程化。

    了解您的數據

    資料分析的第一步是了解您的資料。讓我們從載入我們的資料集並檢查其資料結構開始。 就本文而言,我將使用 PyCharm,我的資料集是來自 Kaggle 的 Titanic 資料集 — https://www.kaggle.com/datasets/yasserh/titanic-dataset

    import pandas as pd
    
    df = pd.read_csv("Titanic-Dataset.csv")

    請注意,我正在處理的 CSV 檔案位於專案資料夾中。如果您的檔案位於其他位置,請輸入完整的檔案路徑。一旦我們載入了資料集,讓我們將其列印出來,以確認是否已正確載入。

    print(df.head())

    我正在使用 head()來只列印數據集的前 5 行,從中我可以檢查數據集是否已正確載入以及我的數據集中有什麼類型的列。

    查看欄位詳情

    print(df.info())

    我們可以看到欄位、它們的資料類型以及每個欄位有多少非空值。例如,在上述資料集中,共有 891 筆資料,但艙位欄位只有 204 筆資料。作為分析師,您可以決定是否考慮這些資料。Pandas 有另一個函數叫做 describe()非常有用,可以一目了然地獲得資料的更廣泛概念。

    print(df.describe())

    您可以查看每一列的計數、平均值、標準差、最小值和最大值等。一旦對數據有基本的了解,下一步就是清理數據集。

    數據清洗 — 處理缺失值

    早期我們發現該數據集存在缺失值,這是大多數數據集中的常見情況。在本節中,我們將重點關注如何查找和處理缺失數據。

    print(df.isnull().sum())

    本行為純文字輸入,翻譯至繁體中文。若翻譯不必要(如專有名詞、代碼等),請直接返回原文。 這是找出每個欄位中空白條目總數的最佳方法。這是我們泰坦尼克號數據集的輸出結果。

    我們可以看到大多數列沒有空值。這並不意味着這些列中的所有數據都有效或有用,這只是意味着這些列沒有空值。但是,Age、Cabin 和 Embarked 列有空值。您可以採取多種方式處理這一問題。如果這種做法會產生小影響,您可以刪除包含空值的行。或者,如果數據很重要,您可以填充空值。例如,在我們的數據集中,如果我們刪除包含 Cabin 列空值的 687 行,我們將只剩下 204 行數據。但如果我們的數據集有 50,000 個條目,也許影響會更小?這完全取決於您的數據集、其大小、我們想要刪除的數據類型以及對我們分析的總體影響。作為數據分析師,這些都是您必須做出的關鍵決策,這些決策最終將決定您的分析是否有效。

    為示範目的,讓我們假設我們決定丟棄 Embarked 欄包含空值的值,

    df = df.dropna(subset=['Embarked'])
    

    對於另外兩個欄位,我將以預設值填補缺失的值。對於「年齡」這個數值欄位,我將以資料集的平均年齡來填補缺失的值。

    df['Age'].fillna(df['Age'].mean(), inplace=True)

    小屋是一個字母數字列。對此,我將使用預設值”未知”。

    df['Cabin'].fillna('Unknown', inplace=True)

    讓我們再次運行 df.isnull().sum()來查看我們的工作是否成功。

    我們的數據集中現在沒有任何遺失的值。

    數據清洗 — 檢測和處理異常值

    統計學中,異常值是與其他觀察值有顯著差異的數據點。異常值可能由於多種原因而產生,例如新穎數據、測量變異性和實驗錯誤。如果確實是實驗錯誤,則應從分析中刪除該數據。但首先很重要的是確定您的數據集是否存在異常值,然後處理它們。

    辨認離群值的一種方法是使用箱形圖。如果您熟悉統計學,使用和解讀箱形圖將很容易。讓我們先建立一個非常簡單的箱形圖。

    sns.boxplot(x=df["Survived"])
    plt.show()

    倖存的欄位應該只有兩個值,要不就是 1,要不就是 0。讓我們現在檢查箱型圖吧。

    我們從這個圖表可以看出,沒有任何異常值。如果存在異常值,它們會以點的形式表示在盒子外部。以”Fare”列為例,

    我們可以看到這個圖表中有幾個異常值,也有一個極端異常值。是否保留或移除異常值取決於資料分析師的需求,不應盲目移除異常值,因為它們可能是真實的資料點。一旦確定了異常值並決定是否要移除它們,您可以使用四分位距 (IQR) 來執行該操作。當然,您也可以手動移除它們。

    統計學中,四分位距(IQR)被定義為數據第 75 百分位與第 25 百分位之間的差值。

    Q1 = df["Fare"].quantile(0.25)  
    Q3 = df["Fare"].quantile(0.75)  
    IQR = Q3 - Q1  
    df_filtered = df[(df["Fare"] >= (Q1 - 1.5 * IQR)) & (df["Fare"] <= (Q3 + 1.5 * IQR))]

    現在箱型圖看起來如何不同。因此,在去除異常值之前,非常重要要先思考這些是實驗錯誤還是真實數據點。

    了解數據分佈

    除了離群值之外,了解數據分布情況還可以窺探數據如何分佈,這有助於決定轉換、標準化等處理方式。首先,我將使用直方圖來視覺化數據分布。

    sns.histplot(df["Fare"], bins=30, kde=True)
    plt.show()

    請注意,我正在使用原始資料框架,未對票價欄應用四分位範圍,因此可以展示更大的分佈。

    我們可以看到最高數量的記錄位於 0 至 100 之間,並有少數介於 200 至 500 之間的離群值。另一種識別數據分佈的方法是檢查偏斜度。

    print(df["Fare"].skew())

    如果您獲得的值為>1 或<-1,則數據存在偏斜。因此,理想值應介於-1 和 1 之間。我對上述數據集獲得的值為 4.801440211044194。這意味著我的數據集存在偏斜。

    請記住我們在應用四分位距後的 df_filtered 資料集?我對該資料集應用了 skew() 函數,得到的值為 1.4306715336945985。該資料集仍然偏斜,但與先前版本相比,已不再極度偏斜。

    您可以使用對數轉換來修正偏斜數據。它通過取值的對數來修正偏斜,本質上是壓縮大值並拉伸小值,有助於平衡分佈。現在這完全取決於您正在處理的數據類型,因為它不適用於所有類型的數據。

    df["Fare"] = np.log1p(df["Fare"])

    在運行 df[“Fare”].skew()後,我得到了值 0.40010918935230094,這意味著我們的數據集現在適度歪斜。

    特徵關係和相關性

    特徵工程是 EDA 的重要組成部分。讓我們先看看數據中的特徵關係和相關性是什麼。數據中的特徵關係是指數據集中不同變量之間的連接或關聯,本質上描述了一個特徵的變化如何影響或與另一個特徵的變化相關。通常使用相關分析等統計方法來衡量變量之間關係的強度和方向。一個基本的例子就是一個包含學習時數和成績的數據集。

    根據我們的數據集,我們可以看到,學生學習的時間越長,在考試中獲得的成績就越高。這意味着學習時間和成績之間存在着關係。這就是我們所說的特徵關係和相關性。

    我們可以使用熱圖來識別您數據集中數值之間的相關性。

    print(df.corr(numeric_only=True))
    sns.heatmap(df.corr(), annot=True, cmap="coolwarm")
    plt.show()

    相關矩陣返回了我們數據集的以下輸出。我在應用任何 IQR 或對數轉換之前使用了該數據集。

    現在讓我們解釋這些結果。相關性的值從-1 到 1。

    • 正正正正正
    • 負 1 表示完美的負相關(一個增加,另一個減少)
    • 0 表示無相關性(特徵是獨立的)

    在先前的學生考試成績範例中,我們可以說,學習時數與考試成績呈現正相關,而缺席日數則可能與考試成績呈現負相關。有了這些認知,我們可以嘗試從上述熱圖中,找出資料集中任何變數間的特徵關係。

    讓我們進一步嘗試使用散佈圖來理解特徵之間的關係。從上面的熱力圖中我可以確定票價和乘客等級之間存在關係。

    sns.scatterplot(x=df["Fare"], y=df["Pclass"])
    plt.show()

    如果您想要檢查多個特徵,則我們可以使用對配圖。

    sns.pairplot(df)  
    plt.show()

    目前為止,我們已經探討了數值特徵之間的關係。現在讓我們來看看類別特徵。由於類別特徵並非數值型,因此我們需要以不同的方式分析它們。我們可以如下統計類別特徵的唯一值數量:

    print(df["Cabin"].value_counts())

    這將告訴我們船上的獨特艙房名稱。

    正如我們之前討論過的,我們通常使用條形圖來可視化類別數據。讓我們看看有多少男性和多少女性登上了這艘船。

    sns.countplot(x=df["Sex"])
    plt.show()

    乍看之下,我們可以看到船上乘客中男性比女性多。

    如果我們想探索更多,我們可以進一步了解機器學習中的 Python 庫,如 sklearn,學習特徵選擇和工程。我們可以選擇最重要的特徵進行分析,將分類變量轉換為數值以用於機器學習模型。我打算到此結束本文,希望能夠撰寫更多關於使用 Python 進行數據分析的文章,重點關注更高級的 Python 庫和機器學習技術。

  • 只有資深的 React 工程師才知道的事情

    React 對初學者來說可能會有些棘手。然而,理解一些底層原理或技巧可以讓成為高級 React 工程師變得更容易。

    1. useEffect 的清理回調函數在每次渲染時都會執行

    大多數人認為它只在組件卸載時執行,但這並不正確。

    在每次渲染時,來自前一次渲染的清理回調會在下一個效果執行之前執行

    讓我們來看一個例子:

    function SomeComponent() {
      const [count, setCount] = useState(0)
    
      useEffect(() => {
        console.log('The current count is ', count)
        return () => {
          console.log('The previous count is ', count)
        }
      })
    
      return <button onClick={() => setCount(count + 1)}>Click me</button>
    }

    這會記錄以下內容:

    // Component mounts
    The current count is 0
    
    // Click
    The previous count is 0
    The current count is 1
    
    // Click
    The previous count is 1
    The current count is 2
    
    // Component unmounts
    The previous count is 2

    這對於創建「取消訂閱然後立即重新訂閱」的模式非常有用,這也是 useEffect 唯一應該被使用的情況。

    當然,如果我們添加了依賴數組,這些東西只會在依賴項改變時被調用。

    2. useEffect 是一個低階工具,應該僅在類似庫的程式碼中使用

    初級 React 開發者經常在不必要的情況下使用 useEffect,這可能會使程式碼更加複雜,產生閃爍或微妙的錯誤。

    最常見的情況是同步不同的 useStates,而實際上你只需要一個 useState:

    function MyComponent() {
      const [text, setText] = useState("Lorem ipsum dolor sit amet")
    
      // You don't need to do this !!!
      const [trimmedText, setTrimmedText] = useState("Lorem ip...")
    
      useEffect(() => {
        setTrimmedText(text.slice(0, 8) + '...')
      }, [text])
    }
    
    
    function MyBetterComponent() {
      const [text, setText] = useState("Lorem ipsum dolor sit amet")
    
      // Do this instead:
      // (each time text changes, the component will re-render so trimmedText
      //  will be up-to-date)
      const trimmedText = text.slice(0, 8) + '...'
    }

    3. 使用 key 屬性來重置內部狀態

    當元素上的 key prop 發生變化時,該元素的渲染不會被解釋為更新,而是被視為卸載並重新掛載一個具有全新狀態的全新組件實例。

    function Layout({ currentItem }) {
      /* When currentItem changes, we want any useState inside <EditForm/>
         to be reset to a new initial value corresponding to the new item */
      return (
        <EditForm
          item={currentItem}
          key={currentItem.id}
        />
      )
    }

    4. 不要將伺服器狀態放在 useState 中

    伺服器狀態大致上是你的資料庫在頁面加載時存在於前端記憶體中的一個快照。

    通常由伺服器狀態管理器管理,例如 react-query 或 Apollo。

    如果你將其中任何一部分放入 `useState`,當查詢刷新或發生變異時,`useState` 的內容將不會更新。

    5. ReactElement 與 ReactNode

    ReactElement 僅代表一個標記片段,而 ReactNode 可以是任何 React 可以渲染的東西,例如 ReactElement,但也包括 stringnumberbooleanarraynullundefined 等。

    // this is a ReactElement
    const a = <div/>
    
    // these are ReactNodes
    1
    "hello"
    <div/>
    [2, <span/>]
    null

    始終將children屬性類型設為ReactNode,這樣你就不會限制該組件可以放入什麼樣的子元素。

    JSX.Element 是 TypeScript 的一個內部功能(並非由 React 庫定義),主要針對庫開發者。除此之外,它等同於 ReactElement

  • 12 個我在工作中學到的生產級 Python 代碼風格

    在工作中,我們編寫的程式碼盡可能地易於人類閱讀。這意味著以下(非詳盡的)列表:

    • 變數名稱具有意義且較長(而非使用 a、b 和 c)
    • 函數名稱具有意義且較長
    • 大量的評論和文件解釋代碼
    • 類型提示無處不在
    • 字串似乎更長且更冗長
    • 等等等等

    這意味著代碼風格將不可避免地演變以適應上述情況。

    因此,以下是我在過去幾年的工作中學到的一些生產級 Python 代碼風格。

    1) 使用括號進行元組解包

    這是一些普通的元組解包:

    在生產級別的程式碼中,我們通常不會使用像 a 或 b 這樣的變數名稱——相反,我們的變數名稱會更長且更具描述性。

    因此,我們可能會使用括號來幫助元組解包,像這樣:

    注意到這樣一來,我們的元組解包可以容納更長(且更具描述性)的變數名稱

    一個更現實的例子:

    2) 多行列表推導式

    以下是普通列表推導式的樣子:

    在生產代碼中,我們通常不使用變量名 i——我們的變量通常更長且更具描述性,以至於我們無法將整個列表推導式塞進一行代碼中。

    我會如何重寫上述代碼:

    一個更現實的例子:

    3) 使用括號組合字串

    生產級的字串通常由於其冗長而無法放入一行中。因此,我們使用括號將它們組合起來。

    注意 — 在括號內,使用引號的字串字面值會自動合併,我們不需要使用 + 運算子來實現這一點

    4) 使用括號輔助的多行方法鏈接

      正常的方法鏈接:

    在生產級別的程式碼中,方法名稱大多數時候會更長,並且我們會將更多的方法鏈接在一起。

    再次,我們使用括號將所有這些內容放入多行中,而不是縮短任何方法名稱或變數名稱。

    請注意,如果我們在括號內進行方法鏈接,則不需要使用 \ 字符來進行明確的換行

    5) 索引嵌套字典

    正常索引嵌套字典的方式:

    這裡有一些問題:

    • 生產級代碼中的字典有更多層次的嵌套
    • 字典鍵的名稱變得更長了
    • 我們通常無法將整個嵌套索引代碼壓縮成一行。

    因此,我們將其拆分為多行,如下所示:

    如果這還不夠,我們將索引代碼拆分為更多行:

    或者如果我們仍然覺得這難以閱讀,我們可以這樣做:

    6) 撰寫易讀且資訊豐富的函數

    我們學生時代如何撰寫函式:

    包含此類代碼的 PR 很可能會被拒絕

    • 函數名稱不具有描述性
    • 參數變數名稱不佳
    • 沒有類型提示,所以我們第一眼無法知道每個參數應該是什麼數據類型
    • 沒有類型提示,所以我們也不知道我們的函數應該返回什麼
    • 沒有文件字串,所以我們必須推斷我們函數的功能

    以下是我們在生產級 Python 代碼中編寫函數的方式

    • 函數名稱應具有描述性
    • 參數名稱應該更具描述性,而不是例如 a, b, c
    • 每個參數都應該有類型提示
    • 函數的返回類型也應包括在內
    • 一個詳細描述函數功能、接收參數及其輸出的文檔字符串應作為三引號內的字符串包含。

    7) 盡可能減少縮排層級

    這是一個 for 迴圈。如果我們的條件被滿足,我們就做某事。

    一些同事和高級工程師可能會對這段代碼吹毛求疵 — 它可以通過減少縮進層次來寫得更好。

    讓我們重寫這個,同時減少 do_something() 的縮進層級

    注意到,僅僅通過使用 `if not condition` 而不是 `if condition`,`do_something()` 的縮進級別就減少了 1 級。

    在生產級別的程式碼中,可能會有更多的縮排層次,而如果層次太多,我們的程式碼就會變得令人煩躁且難以閱讀。因此,這個技巧讓我們能夠使程式碼稍微整潔一些,更易於人類閱讀,

    8) 帶有括號的布林條件

    這是一個使用 and 關鍵字連接三個條件的 if 語句。

    在生產級別的代碼中,條件變得更長,且可能會有更多的條件。因此,我們可以通過將這個龐大的條件重構為一個函數來解決這個問題。

    或者如果我們判斷沒有必要僅僅為此編寫一個新函數,我們可以使用括號來編寫我們的條件語句。

    這樣一來,我們就不必為了這一個條件語句而被迫編寫新的函數或變量,同時我們還能保持代碼的整潔和可讀性。

    有時候我可能真的更喜歡這樣寫,不過這只是基於個人偏好:

    9) 防止 None 值

    正常代碼,訪問對象的某些嵌套屬性。

    這段代碼存在一些可能導致我們的 PR 被拒絕的問題:

    • 如果 dog 是 None,我們會得到一個錯誤
    • 如果 dog.owner 是 None,我們也會得到一個錯誤
    • 基本上,這段程式碼並未防止 `dog` 或 `dog.owner` 可能為 `None` 的情況。

    在生產級代碼中,我們需要積極防範此類情況。以下是我如何重寫這段代碼的方式。

    Python 中的 and 和 or 運算符是短路運算符,這意味著一旦它們得到明確的答案,就會停止評估整個表達式。

    • 如果 dog 是 None,我們的表達式在 “if dog” 處終止
    • 如果 dog 不是 None,但 dog.owner 是 None,我們的表達式會在 “if dog and dog.owner” 處終止
    • 如果我們沒有任何 None 值,dog.owner.name 會成功被存取,並用於與字串 “bob” 進行比較

    這樣一來,我們就能額外防範狗或狗主人可能為 None 值的情況。

    10) 防止遍歷 None 值

    這是我們如何遍歷某些可迭代對象(例如列表、字典、元組等)的方式

    這個問題的一個缺點是它無法防止 mylist 為 None——如果 mylist 恰好是 None,我們會得到一個錯誤,因為無法遍歷 None。

    以下是我如何改進這段程式碼的方式:

    表達式“mylist or None”

    • 如果 mylist 為真值(例如非空可迭代對象),則返回 mylist
    • 如果 mylist 為假值(例如 None 或空的可迭代對象),則返回[]

    因此,如果 mylist 為 None,表達式“mylist or None”會返回[],這樣我們就不會遇到不想要的異常。

    11) 內部函數以 _ 開頭

    這是一個範例類別。在這裡,run 方法使用了其他方法 clean 和 transform

    在生產級代碼中,我們力求盡可能明確,因此嘗試區分內部方法和外部方法。

    • 外部方法——供其他類別和物件使用的方法
    • 內部方法 — 供類別本身使用的方法

    按照慣例,內部方法最好以下劃線 _ 開頭

    如果我們要重寫上述代碼,我們會得到:

    注意 — 在方法名稱前加上底線並不會隱藏它使其對其他類別和物件不可見。事實上,在功能上沒有任何區別。

    12) 用於常見功能的裝飾器

    這是一個包含 3 個函式的類別,每個函式執行不同的操作。然而,請注意,不同函式之間存在相似的步驟——try-except 區塊以及日誌功能。

    減少重複程式碼的一個好方法是撰寫一個包含共同功能的裝飾器函數。

    這樣一來,如果我們想要更新共用程式碼(try-except 和 logging 程式碼),我們不再需要在三個地方進行更新——我們只需要更新包含共用功能的裝飾器程式碼。

  • 為什麼單例模式既有用又危險

    什麼是單例模式?

    單例模式是一種創建型設計模式,它限制一個類只能有一個實例,並提供一個全局訪問點來獲取該實例。換句話說,無論你嘗試創建該物件多少次,你總是會得到同一個單一實例。當整個系統中需要精確地使用一個物件來協調操作時,這種模式非常有用。典型的實現方式是將類的構造函數設為私有,並暴露一個靜態方法或屬性來獲取唯一的實例。

      單例設計模式

    現實世界類比

    想像你身處一個巨大的機場,裡面有數百個登機門、成千上萬的旅客,以及無數的航班起降。儘管如此複雜,只有一座空中交通管制塔負責管理整個機場的運作。

    所有航班、飛行員和航空公司都必須與這座單一塔台溝通,以獲得降落和起飛的許可,進行協調並避免災難。如果有多個獨立的塔台控制同一機場的不同部分,系統將陷入混亂,指令衝突且可能導致撞機。

    這正是單例模式的工作原理——它確保存在一個中心實例(空中交通管制塔),該實例協調所有通信和指令,防止系統中的不一致和衝突。

    它解決的問題

    一個展示單例模式的類圖 | 單例模式 — 維基百科

    單例模式主要解決軟件設計中的兩個問題

    1. 控制實例化(單一實例): 它確保一個類別在整個程式中只有一個實例。這在你需要單一的協調點或單一的共享資源時非常重要。例如,你可能只想要一個數據庫連接或一個打印機假脫機程式的實例,以防止衝突的存取。通過防止額外的實例化,單例模式避免了多個實例同時修改同一資源可能導致的不一致。
    2. 全球存取點:它提供了一個眾所周知的全球存取點來存取該實例,而不是使用全域變數。在沒有單例模式的情況下,人們可能會使用全域變數來共享一個物件(如全域配置或日誌記錄器)。但全域變數可能會被覆寫或造成命名空間污染。單例模式提供了一種受控的方式來提供全域存取,同時保護實例不被替換或重複。這意味著在程式碼的任何地方,都可以存取單例實例(通常是通過像getInstance()這樣的靜態方法或類似方法),確保程式的所有部分都使用相同的物件。

    透過解決這兩個問題,單例模式確保存在單一的資源管理器並且易於存取。這對於協調應用程式中需要共享資訊或資源的不同部分的行動至關重要。

    單例模式的優勢

    儘管圍繞著過度使用的爭議,單例模式在正確的上下文中確實具有一些明顯的優勢:

    • 單例強制執行:保證只有一個實例被創建。這避免了在管理共享資源時產生衝突,因為所有代碼都使用同一個實例。例如,如果你的應用程序使用單一的配置管理器或單一的緩存,單例模式確保所有模塊都引用那個對象(防止配置不匹配或緩存重複)。
    • 全球訪問便利性:單例實例可以在全球範圍內訪問,這簡化了從程序任何位置與其的互動。您不需要不斷傳遞實例;代碼的任何部分都可以調用,例如Singleton.getInstance()來檢索它。這可以使某些設計更為簡化(類似於應用程序如何全局訪問應用程序範圍的記錄器或數據庫連接器)。
    • 懶初始化:單例模式可以實現為僅在首次需要時初始化實例(懶加載)。這樣可以提高啟動性能和資源使用效率。例如,如果單例對象創建成本較高(比如加載大型配置或連接數據庫),你可以將這部分成本延遲到實際需要時才支付。許多語言允許在首次使用時創建單例,而有些語言(如 Dart 和 Java)甚至會自然地懶初始化靜態字段,因此你不必在不需要時支付這部分成本。
    • 受控存取/安全性:通過將存取集中於單一實例,您可以集中進行某些檢查或鎖定。在多執行緒情境中,正確實作的單例模式可以確保對共享資源的執行緒安全存取。相較於管理多個物件,您只需處理一個,因此協調執行緒存取(透過同步或鎖定)會更為簡便。(然而,實際是否達到執行緒安全取決於實作方式;該模式本身並不保證執行緒安全,除非設計時即考慮此點。)
    • 避免全域命名空間污染:與真正的全域變數不同,單例模式通常位於其類別命名空間中,不會被其他變數意外覆寫。這意味著您可以維護一個更乾淨的全域命名空間。該模式將全域實例封裝在一個類別中,這比讓許多不相關的全域物件四處飄散更有組織性。

    請記住,這些優勢在確實需要單例模式時才適用。它們可以使代碼更簡單、更高效當一個實例真正適合當前問題時

    缺點及如何減輕其影響

    單例模式經常受到批評,尤其是在被過度使用時。以下是單例模式的關鍵缺點,以及可能的緩解措施的說明:

    隱藏依賴(全域狀態)

    單例模式將全局狀態引入應用程序中。由於任何代碼都可以從任何地方訪問單例實例,它實際上就像一個全局變量。這可能使得追踪程序中哪些部分使用或修改其狀態變得困難,從而可能導致不可預測的行為或錯誤。

    緩解
    為了避免意外,清楚地記錄單例模式的功能並考慮限制直接訪問。一些開發者只在介面背後使用單例模式;代碼的部分依賴於介面,而單例模式實現了它。這樣你可以在測試或需要時替換它。此外,盡量保持單例模式的狀態最小化,以減少全局狀態的複雜性。

    緊密耦合

    使用單例模式的組件會與該具體實例緊密耦合。由於單例是全局可訪問的,代碼的許多部分可能會直接調用Singleton.instance。這使得以後更改或替換該類變得困難,因為許多部分都依賴於它。這也違反了依賴注入的原則,因為類隱式地依賴於一個全局實例。

    緩解
    一個解決方案是依賴於抽象。例如,讓單例類實現一個接口,並讓消費者依賴於該接口而不是具體的單例。然後,您可以通過該接口提供單例實例,甚至可以根據測試或未來需求替換為不同的實現。通過針對接口或基類進行編碼,即使在運行時只有一個實現,您也能鬆散耦合。

    困難的單元測試

    單例模式可能會使單元測試變得更加困難。由於單例是全局的,測試可能會無意中使用真實的單例,而你可能更希望使用模擬或偽造的對象。此外,如果一個測試改變了單例的狀態,它可能會影響之後運行的其他測試(因為實例會持續存在)。

      緩解
    為了進行隔離測試,您可能允許單例被重置或在測試模式下注入不同的實例。另一種方法是避免在業務邏輯中直接調用單例;而是注入其接口(如上所述),以便您可以在測試中提供一個虛擬實現。一些依賴注入框架也可以在測試中覆蓋單例。至少,如果合適的話,確保您的單例類有一個方法來重置或替換其實例(以便在測試之間進行清理)。

    生命週期與資源管理

    單例通常存在於應用程式的整個生命週期。這意味著在許多情況下,它永遠不會被垃圾回收,直到應用程式關閉。如果它持有大量資源(文件、網絡連接、大塊內存),這些資源在進程結束之前不會被釋放,這可能會帶來問題。此外,如果你需要重新初始化或重新加載單例(例如,重新加載配置),要乾淨地實現可能會很複雜。

    緩解
    設計單例以在需要時釋放外部資源(例如,單例資料庫連接可以提供一個close(),應用程式在關閉時呼叫)。在某些情況下,考慮是否真的需要單例,或者是否可以使用範圍生命週期。像網頁伺服器或 Flutter 應用程式這樣的環境可能會重新啟動或熱重載模組;在這些情況下,要小心單例是否被適當地重新建立。

    有限的擴展性

    由於單例模式的實現方式(使用靜態實例),它們不容易擴展或繼承。通常,您無法以整個系統可以透明地使用子類別的方式來對單例進行子類別化——代碼被硬編碼為使用確切的單例類別。此外,在正常情況下,您無法在不修改單例類別本身的情況下為特殊情況創建子類別的第二個實例。這違反了開閉原則(類別在這方面不開放擴展)。

    緩解
    如果您預見到需要變體或子類,Singleton 可能不是合適的模式。一個可能的解決方案是使用註冊表或multiton(一個受控的實例映射)來允許通過鍵識別的幾個實例,而不是一個固定的實例(更多關於 multiton 的內容見下文)。或者,使用可以返回不同實現的工廠方法(例如,Singleton 的靜態訪問器可以根據配置返回子類的實例,儘管這增加了複雜性)。

    違反單一職責原則(SRP)

    一個經典的批評是,單例類別有兩個責任:一個是它們的主要邏輯,另一個是管理它們的唯一實例。換句話說,一個類別在完成其主要工作確保只有一個實例存在時,承擔了兩個角色。這被認為是一種設計上的異味,因為一個類別理想情況下應該只有一個變更的理由。

    緩解
    解決這個問題最直接的方法是分離關注點——讓物件的創建和生命週期由外部管理。例如,使用工廠或建構器物件來管理單一實例。該工廠將處理「只有一個實例」的邏輯,而類別本身則專注於其主要工作。我們在下面的 SRP 部分會進一步討論這一點。如果使用依賴注入框架,你可以配置它將類別視為單例(單一實例),而無需類別本身實現該模式——再次分離職責。

    總之,單例模式應謹慎使用。通過精心設計(如使用介面、依賴注入或外部化實例控制),可以緩解許多這些缺點。然而,如果過度使用或不當使用,單例模式確實可能導致難以維護且緊密耦合的代碼。在選擇單例解決方案之前,務必權衡這些利弊。

    使用單例模式的限制

    使用單例模式時,有一些重要的限制和考慮需要牢記在心:

    • 每個應用程式(或進程)一個實例: 單例模式保證了在單一運行時內只有一個實例。如果你有多個進程或一個分散式系統,每個進程可能會有自己的單例。例如,在多進程應用程式(或在像 Flutter 這樣的環境中,每個隔離區都有自己的記憶體)中,你不會獲得跨進程共享的單一實例——每個進程只有一個。因此,「單一實例」僅限於一個程式或容器的範圍內。
    • 執行緒安全:在多執行緒環境中,您必須確保單例模式的初始化是執行緒安全的。如果兩個執行緒同時嘗試創建實例,可能會意外地創建兩個實例。許多語言預設提供了使靜態初始化執行緒安全的方法(例如,Java 的類加載器,或 Dart 的懶初始化)。如果沒有,您需要實現鎖定或使用雙重檢查鎖定等技術。如果不這樣做,可能會破壞單例保證,並引入錯誤或性能問題(例如,如果所有執行緒在沒有適當設計的情況下都通過一個對象,可能會導致競爭)。在並發情況下,實現不佳的單例模式可能會成為瓶頸。
    • 生命週期與應用程式生命週期綁定:如前所述,單例通常從創建開始存在直到程式終止。這意味著你通常無法在程式執行過程中銷毀並重新創建單例(至少在不添加特殊方法的情況下無法做到)。如果你的應用程式需要“重置”狀態(例如,軟重啟),單例可能會在你不需要的時候保留狀態。這種長生命週期也意味著,如果沒有妥善管理,它所持有的任何記憶體或資源實際上都是全域性的洩漏。在受限環境中,考慮提供一種明確釋放資源的方式,或者避免對需要卸載的重型物件使用單例。
    • 記憶體與效能考量:單例模式可能會引入輕微的記憶體開銷(如果急切創建,即使在不主動使用時也存在)。然而,與創建許多相同物件的實例相比,它可能會節省記憶體。關鍵在於,如果單例持有大量數據,這些記憶體將永遠不會被釋放。如果創建成本高昂,單例通過不重複創建來節省時間。但如果它很少使用,保持其存活可能是浪費的。權衡始終保留它的成本與按需構建的成本。
    • 實例數量的不靈活性:一旦你將某物實作為單例,如果後續需求變更,允許更多實例,這可能是一個非平凡的重新構建。程式碼庫可能充斥著假設單一實例的呼叫。這是設計中的一個約束;你將自己鎖定在一個實例上。確保這是概念上邏輯且固有的限制(例如「這個應該永遠只有一個」),而不僅僅是當時的便利。如果有任何疑問,考慮採取更靈活的方法(如使用實例註冊表或明確傳遞物件)。
    • 不是單一職責原則的替代品:僅為了避免傳遞物件而使用單例模式可能是一個警示信號。如果你發現你將某個東西設為單例主要是因為「代碼的許多部分都需要它」,而不是因為它邏輯上必須是單一的,那麼你可能只是為了方便而使用這種模式,並創建了不必要的全局狀態。這種方便日後可能會變成維護的頭痛問題。始終確保單例模式的使用是由問題本身(例如單一配置、單一緩存)所合理化的,而不是僅僅為了在物件傳遞上偷懶。

    本質上,Singleton 的約束在於你獲得了全域、長效、單例存取——你應該基於這一點來進行設計。考慮這如何適應你應用程式的生命週期,以及任何未來的場景是否可能需要更多的靈活性。

    避免違反單一職責原則

    Singleton 的一個顯著問題是它可能違反單一職責原則(SRP)。該類不僅完成其主要工作,還控制自己的實例化和生命週期(強制執行“僅一個實例”的規則)。理想情況下,一個類“不應該關心它是否是單例;它應該只關注其業務職責”。

    如何避免這種違規? 關鍵在於將實例管理的關注點與類別的核心邏輯分離。以下是一些方法:

    • 工廠或建造者: 與其讓類別在其程式碼中強制執行單例模式,不如使用一個獨立的工廠物件來創建或檢索單一實例。工廠會檢查實例是否存在,並根據情況返回現有實例或創建新實例。主類別可以擁有一個僅由工廠呼叫的內部構造函數。這樣一來,類別本身只負責一個職責(其業務邏輯),而工廠則負責限制實例化的過程。例如,一個DatabaseConnection類別可能是普通的(並不知道自己是單例),而一個DatabaseConnectionFactory則確保只會創建並分發一個連接。
    • 依賴注入容器:在現代應用程式中,依賴注入(DI)框架或服務定位器可以管理單例。您宣告某個服務或類別在應用程式中應具有單例範圍。然後,DI 容器確保只創建一個實例,並在需要的地方注入該實例。類別本身對此並不知情;它只是由容器正常實例化。這種方法清晰地分離了職責:容器管理物件的生命週期,而類別只需完成其工作。許多框架(如 Angular、Spring 等)允許將類別配置為單例,而無需類別自行實現該模式。
    • 靜態單例管理器:在較簡單的情況下,您可以使用一個獨立的靜態輔助類來持有單例實例。例如,與其將獲取實例的邏輯寫在類內部,不如擁有一個獨立的管理器,甚至只是在另一個類中的一個靜態字段。然而,這基本上就是依賴注入容器更清晰形式化的內容。

    通過採用這些方法,你遵循了單一職責原則(SRP),因為類別只有一個變更的理由:其領域邏輯的變更,而不是其單一實例管理方式的變更。正如一篇微軟文章所述,「如果你想限制某個類別的實例化能力,建立一個工廠…現在,創建的職責與業務實體的職責被分離開來。」。這讓你能在需要時獲得單一實例的好處,而不會讓類別承擔額外的責任。

    總之,您仍然可以通過外部化單例的強制執行來實現類似單例的行為,同時保持單一職責原則。這使您的代碼更清晰,通常也更易於測試,因為您可以根據需要替換該創建邏輯(例如,在測試中替換不同的實例或稍後更改實例化策略)。

    範例:Dart 中的單例模式

    讓我們來示範如何在 Dart 中實現並使用單例模式。與其使用日誌記錄器,不如考慮一個資料庫連接管理器,我們希望在整個應用程式中只使用一個資料庫連接實例。

    class DatabaseConnection {
      // Private named constructor
      DatabaseConnection._internal();
      // The single instance, stored in a static field
      static final DatabaseConnection _instance = DatabaseConnection._internal();
      // Factory constructor that returns the static instance
      factory DatabaseConnection() {
        return _instance;
      }
      // Example method to simulate a database query
      void query(String sql) {
        print('Executing query: \\$sql');
      }
    }

    解釋:

    1. 私有建構函式: DatabaseConnection._internal() 確保外部程式碼無法直接實例化這個類別。
    2. 靜態實例:_instance 是一個靜態欄位,用於保存該類的單一實例,確保只存在一個對象。
    3. 工廠構造函數:每次調用 factory DatabaseConnection() 構造函數時,它都會返回單一實例,從而保持單例模式。

    用法:

    void main() {
      var db1 = DatabaseConnection();
      var db2 = DatabaseConnection();
      db1.query("SELECT * FROM users");
      print(db1 == db2);  // true, both references point to the same instance
    }

    如果你執行這段程式,比較db1 == db2將會返回true,表示這兩個變數都引用了同一個單例實例。

    這種模式確保應用程式的所有部分都使用相同的數據庫連接對象,防止了不必要的多重連接創建,並確保了資源管理的效率。然而,在實際應用中,應考慮額外的保護措施,如連接池。

    通過使用這個單例模式,代碼的任何部分都可以調用 DatabaseConnection().query("SQL command") ,而無需擔心傳遞數據庫實例的問題。然而,在處理多線程環境或 Dart 中的隔離時應小心,因為每個隔離都有自己的內存空間。

    結論

    單例模式提供了一種確保類別只有一個實例並為該實例提供易於全局訪問的方法。它對於某些場景非常有用,例如資源或配置的集中管理。我們探討了它的定義、類比、它解決的問題以及其優缺點。雖然單例可以簡化對共享資源的訪問(並通過避免重複對象來減少內存佔用),但它也帶來了隱藏依賴、緊密耦合以及測試和擴展困難等缺點。現代最佳實踐敦促對單例保持謹慎:優先考慮清晰的依賴管理,並僅在真正需要一個實例時才使用單例。

    如果使用單例模式,應該以乾淨、負責的方式來實現——例如,將單例的核心邏輯與實例化機制分開,以尊重像 SRP 這樣的設計原則。我們還討論了與單例相關的模式(單態、多例等)以及它們的比較。

    總之,Singleton 是一種強大的模式,但應謹慎應用。將其保留在邏輯上需要唯一物件的場合,並注意其影響。如有疑問,考慮使用更具明確架構的替代方案來達成類似目標。但當你遇到那些罕見的單例問題時,Singleton 模式仍然是軟體設計工具箱中的一個方便工具。

    你對單例模式有什麼看法?在專案中使用它時是否遇到過挑戰?在下方評論區分享你的想法和經驗吧!如果你覺得這篇文章有幫助,別忘了留下一些掌聲,與朋友分享,並關注以獲取更多關於軟體設計和架構的見解。你的支持是我們持續創作內容的動力!

  • 什麼是容器運行時?

    Kubernetes 已成為容器編排的首選平台,雖然我們大多數人都能輕鬆地在上面部署和管理應用程式,但理解其背後的魔法有時感覺就像在解讀象形文字。其中一個這樣的概念就是容器運行時。它到底是什麼,為什麼你應該關心?讓我們從基礎開始拆解它。

    什麼是容器運行時?

    什麼是容器運行時?

    簡單來說,容器運行時是一段負責運行容器的軟體。它承擔了拉取容器鏡像、啟動和停止容器以及管理容器生命週期的重任。你可以把它想像成 Kubernetes 汽車引擎蓋下的引擎——你可能不會直接看到它,但沒有它,你就無法前進。

    現實生活的類比

    想像你在一家速食餐廳。你點了餐(容器映像),廚師(容器運行時)準備你的餐點並將其放在托盤上(運行中的容器)。你並不真的在乎櫃檯後面發生了什麼,只要你的漢堡出現就好,但沒有廚師,你只能盯著一個空托盤。

    為什麼我們需要容器運行時?

    容器運行時抽象化了管理隔離進程的複雜性。Kubernetes 使用容器運行時來保持工作負載的一致性、高效性和可移植性。不同的容器運行時可能會影響性能、兼容性和安全性。

    Kubernetes 中流行的容器運行時

    讓我們來看看一些流行的容器運行時,並了解它們如何融入 Kubernetes 生態系統。

    Docker

    容器運行時的鼻祖。它就像那輛一直以來都可靠的老車。Kubernetes 最初依賴 Docker 作為其默認運行時。然而,隨著 Kubernetes 的發展,Docker 被替換為更為精簡的選擇(就像用一輛時髦的新跑車取代你的舊轎車)。

    containerd

    containerd 是從 Docker 本身發展出來的運行時,並成為 Kubernetes 的首選運行時。它更精簡、更快,並且純粹專注於運行容器,而沒有 Docker 所攜帶的額外包袱。

    CRI-O

    專為 Kubernetes 設計,CRI-O 是一款輕量級運行時,與 Kubernetes 的容器運行時接口(CRI)兼容。這就像是升級到混合動力車——高效、專為特定目的打造,並完全兼容現代標準。

    Kubernetes 如何使用運行時?

    Kubernetes 通過容器運行時介面(CRI)與容器運行時進行互動。這使得 Kubernetes 能夠與不同的運行時協作,而不被綁定到特定的運行時。每個節點上的 kubelet 會與運行時通信以管理容器。

    選擇正確的運行時

    在選擇容器運行時,請考慮性能、安全性、兼容性和社區支持等因素。雖然 containerd 通常是預設選項,但 CRI-O 以 Kubernetes 為中心的設計使其成為純 K8s 環境的理想選擇,而 gVisor 和 Kata 則更適合注重安全性的設置。

    結論

    容器運行時可能不是 Kubernetes 中最耀眼的部分,但它們對於讓你的工作負載實際運行至關重要。可以把它們想像成默默無聞的英雄,讓你的集群保持運轉。下次你深入研究 Kubernetes 設置時,不妨向那些默默承擔重擔的容器運行時致以一點敬意。

  • 那麼,Rust 到底有多快呢

    大約六個月前,當我加入Blitz時,我們已經將我們的 Elixir 後端遷移到 Rust 的過程中走了一半。我不認為 Elixir 作為一種語言有任何問題,但其實現方式並不是最高效的,並且它的成本相當高。無論如何,遷移背後的思路是,用 Rust 重寫將會顯著更快且更便宜。所以我首先問自己的問題就是這篇文章標題所提到的。Rust 到底有多快?嗯,從我的測試中得知,它確實非常快。

    告白

    讓我們暫停一下,來個誠實的開發者告白時間:我從未在專業領域中使用過 Rust 或 Elixir。我的後端履歷讀起來就像 Stack Overflow 的「熱門語言」部分——Java、Python、Go、JavaScript/TypeScript、PHP、C#——但不知何故,我成功地避開了這兩種特別受歡迎的技術。

    Rust 繼續被譽為最受推崇的程式語言,去年獲得了 83%的高分,而我也做了典型的開發者會做的事——在午餐時以 1.5 倍速觀看 Fireship 和 Prime 的 Rust 教學影片。你知道的,就是那種「我總有一天該好好學學這個」的研究。至於 Elixir?嗯,我只能說,如果沒有 ChatGPT 充當我的個人翻譯,我根本看不懂 Elixir 的程式碼。「這個|>符號是在幹嘛?是打錯字還是語法的一部分?」🤔

    但這正是 Blitz 這次遷移之旅如此迷人的原因。在過渡中期加入,讓我得以近距離見證前後效果,而不帶有建立原始系統的偏見。這就像在翻新過程中搬進一間房子——你能看到「之前」的照片和驚人的揭幕,同時避開大部分的施工灰塵。

    設定

    讓我們面對現實吧——網路上的基準測試就像健身前後對比照:完美得令人懷疑,而且常常缺少關鍵的背景資訊。那些「Rust 快 100 倍!」的標題並不能說服我,我敢打賭你也同樣持懷疑態度。這就是為什麼我不僅分享結果,還要給你完整的配方,讓你可以自己動手做測試。

    完整原始碼已在 GitHub 上提供,我真誠地希望你能嘗試證明我是錯的!發現錯誤了嗎?認為你最喜歡的語言應該出現在比較中嗎?請給我發送 PR 或創建一個 issue。如果我早茶後心情特別好,我甚至可能會合併它。

    但在我們深入探討那些讓我的 CPU 和腦袋都轉不停的數字之前,讓我先解釋一下我為這些基準測試設定的基本規則:

    • 不允許執行緒或平行處理。抱歉了,Go 的粉絲們——這裡沒有 goroutines!這是一場純粹的單執行緒對決,旨在測量無並行技巧下的原始語言性能。
    • 極簡的函式庫,最大化的裸機計算。 我們正在測試語言,而非套件生態系統。程式碼盡可能地堅持使用標準函式庫,以保持比較的公平性。
    • 記憶體密集型但無需 I/O 操作。 這項測試著重於記憶體存取模式,但避免磁碟或網路 I/O。我們測量的是處理能力,而不是語言在等待硬碟時能多好地閒置。
    • 垃圾回收的考量。 這個測試旨在避免語言可能花費一半時間清理記憶體而非進行實際工作的情況。沒有人想看我們來看拳擊賽時,卻在看清潔工打掃。

    將這些基準測試視為程式語言中的受控實驗室實驗——或許是人為的,但揭示了影響現實世界性能的基本特徵。歡迎在你的電腦上試試看。

    基準

    讓我帶你走過我完全業餘的基準測試冒險。首先——我用 Python 寫了最初的基準測試,這仍然是我的編程舒適區,儘管它的聲譽是比在糖漿中游泳的樹懶還要慢。

    是的,我確實用 ChatGPT 來潤飾我的程式碼。如果你因此評判我,那我只能假設你還在用手攪拌奶油,並且拒絕使用計算機。各位,現在是 2025 年了——善用可用的工具吧!我對「純粹編碼」的自負在我凌晨兩點的第三次除錯會議中煙消雲散了。

    那麼這個基準測試到底做了什麼呢?它非常簡單:

    1. 生成隨機的 8 字符字符串(10,000 個!)
    2. 將它們塞進字典/哈希映射/無論你的語言怎麼稱呼它
    3. 檢查我們是否意外地多次創建了相同的字串(你好,生日悖論!)
    4. 沖洗並重複三次
    5. 計算平均運行時間

    這個基準的美妙之處在於它觸及了編程任務的甜蜜點:字串操作、記憶體分配、字典/哈希操作,以及一點隨機性——同時避免了 I/O 操作或執行緒的棘手複雜性。

    我隨後開始了相當於「程式碼的 Google 翻譯」的語言轉換工作,將這個基準轉換成多種語言,包括神秘的 Elixir(對我來說仍然像外星象形文字)和備受矚目的 Rust。結果如何?只能說它們並不算是驚天動地。如果你在科技界待過超過一週,你可能已經猜到了哪些語言在性能光譜上落在哪個位置——也許有一兩個令人驚訝的例外。

    將這視為較少「嚴謹的學術研究」,而更多是「有時間的開發者出於好奇」。但有時正是這些實驗教會我們最有趣的教訓。

    結果

    好的,鼓聲請準備!經過了這麼多的準備,讓我們來深入探討那些精彩的性能數據。我原本懷著宏大的願景,想要為這篇部落格文章創造出美麗、專業的圖表。然後我記起自己是一名開發者,不是設計師,而我的藝術能力頂多就是 ASCII 藝術。所以,我做了任何一位在 2025 年理性的工程師會做的事——我把它外包給了 ChatGPT。這 AI 可能贏不了任何設計獎,但至少它不會像我的設計師朋友那樣,對我糟糕的顏色搭配指指點點。

    原始性能結果

    我也把它上傳到這裡的儲存庫了。

      原始結果

    這是一張給視覺型人的圖表:

      原始數據圖表

    讓我帶你了解這些數字所揭示的內容:

    解釋型語言:PHP、Ruby 和 Python 就像是老友重逢一樣,彼此之間相處融洽,但也不刻意搶風頭。它們的表現…正如你所預期的解釋型語言那樣。它們雖然在速度上不佔優勢,但卻是可靠的工作馬,能夠完成任務,只是比編譯型語言消耗更多的 CPU 資源。

    編譯型重量級選手:Rust 和 Go 絕對大放異彩。我們說的是「讓其他語言開始懷疑人生」的性能水平。Rust 略勝 Go 一籌,再次證明了它作為速度狂魔的聲譽。我並不驚訝 Microsoft 為 TypeScript 選擇了 Go,但為什麼不是 Rust 呢?也許他們不喜歡開發者在編譯時花那麼多時間喝咖啡 😁

    意外的發現:有兩種語言出乎意料。Java 的表現遠超我預期——顯然,自從我上次關注以來,JVM 已經進行了大幅度的優化。另一方面,Elixir 的速度比我預期的要慢,我懷疑是否在這方面做錯了什麼,希望有 Elixir 專家能幫我解答,但等等,他們在哪裡呢?

    對數比例:因為有些語言就是快那麼多

    如上所見,我將所有數據繪製在線性尺度上,但性能差距如此顯著,以至於有些語言在圖表上幾乎看不出來。這就像比較噴射機與自行車的速度——技術上它們都是交通工具,但這種比較並不十分公平。

    僅僅為了樂趣,我切換到了對數刻度,這讓我們能更直觀地看到相對性能差異。即使在這個刻度上,解釋型語言和編譯型語言之間的鴻溝依然顯著。

    這實際上意味著什麼

    令我著迷的不僅僅是哪種語言「勝出」,而是差異的幅度。我們談論的不是邊際收益——我們看到的是可能從根本上改變你如何架構解決方案或你能用相同硬體實現什麼的性能提升。

    當 Rust 處理某件事的速度比 Python 快 20 到 30 倍時,這不僅僅是一個技術上的註腳——這意味著需要 20 台伺服器與僅需一台的區別。或者是在 300 毫秒與 10 毫秒內處理一個請求的差異。在規模上,這些差異不僅重要——它們成為了決定性的優勢。

    話雖如此,開發速度、生態系統的豐富性以及團隊的熟悉度仍然非常重要。但如果原始處理能力是你夜不能寐的原因,我認為這些數字已經不言自明。Rust 以速度著稱並非只是行銷噱頭。

    我對你的想法很感興趣,請在下方留言告訴我,如果你對更多業餘基準測試感興趣,你可能會對我下面的 Rust vs Go 文章感興趣。