文章加密

;

2024年10月3日 星期四

[TS] any v.s. unknown

unknown

我們不能夠對 unknown 型別的東西進行任何除了讀寫和比較以外的操作

功能一:保證變數的值不會被不小心地操作到

例子:

function compareArrayAtEven(arr1: unknown[], arr2: unknown[]): boolean {
    let diff = arr1.length - arr2.length;
    if(diff > 1 || diff < -1) return false;
    for(let i = 0; i < arr1.length && i < arr2.length; i++) {
        if(arr1[i] !== arr2[i] + 2) return false; // TypeScript 會在這邊報錯
    }
    return true;
}


功能二:負責把關來自外界的輸入

預防各種執行階段中的不可預期現象發生

例子:

export function splitString(arg: unknown): string[] {
    if(typeof arg == 'string') {
        // 檢查完了之後才做我們真正要做的事情,例如:
        return arg.split(',');
        // 此處 TypeScript 會正確地知道 arg 的型別為字串,
        // 因為剛才我們用 typeof 檢查過了。
        // 對於更複雜的型別檢查,可以用 instanceof 關鍵字、
        // 或是使用 TypeScript 的 TypeGuard 來達成,
        // 這是題外話,這邊先不深入討論。
    } else {
        // 否則看要怎麼進行錯誤處理;這邊的方法是傳回預設值
        return [];
        // 或者也可以丟出一個 Error 包含了我們自訂的錯誤訊息
    }
}


養成把對外 API 的參數宣告成 unknown 的好習慣,並且避免使用 TypeScript 中的 as 關鍵字來作型別斷言(取而代之地,應該使用 instanceof 或自訂的 TypeGuard 來確認型別)

any

any 隨便做都行,沒鑑別度

很大的程度上來說,是的;我個人現在是「TypeScript 程式碼中應該要幾乎沒有 any 才對」主張的擁護者。對於很多我接手的程式碼,我都會先搜尋出所有使用到 any 的地方,而它們大多都可以直接換成 unknown 而不用作額外的修改——而如果換成 unknown 之後某個地方因此就出現了編譯錯誤,那幾乎在所有的情況中,那都是突顯出了程式碼具有潛在的異味(bad smell),而釐清為什麼改成 unknown 之後有編譯錯誤、往往能讓程式碼變得更加健全

只有一種情況是我會勉強接受 any 的使用的,那就是當我們引入了一個第三方的型別,我們很清楚其規格、但是我們偏偏又沒有該型別的完整定義檔、而我們自己去寫定義檔又很浪費時間的時候。然而,這樣的使用有幾個前提是應該要遵守的:

  1. 使用了 any 型別的物件應該要充分地被封裝起來,使得使用它的程式碼都非常清楚該變數要怎麼操作。如果有很多程式碼依賴於該物件,那應該要提供一個有良好定義型別的介面來讓其它程式碼間接操作該物件,而不是讓所有程式碼直接取用它。
  2. 對於該物件傳回的值,應該馬上用型別斷言或型別檢查來確定其型別,而不是繼續讓傳回值維持 any 的狀態。

如果沒辦法做到這兩點,那最好還是花一點時間自己把該型別當中會用到的東西宣告一下,這不僅能讓程式碼更有條理,也可以避免很多潛在的手殘可能性。

沒有留言:

張貼留言