編寫高質量程式碼的30條黃金守則-Day 01(首選隱式型別轉換)

位元飛發表於2020-08-10

編寫高質量程式碼的30條黃金守則-Day 01(首選隱式型別轉換),本文由位元飛原創釋出,轉載務必在文章開頭附帶連結:https://www.byteflying.com/archives/6455

該系列文章由位元飛原創釋出,計劃用三個月時間寫完全30篇文章,為大家提供編寫高質量程式碼的一般準則。

1、概述

隱式型別轉換是微軟為了 C# 支援匿名型別而加入的,使用 var 通常可以使程式碼的可讀性更強,甚至是幫我們解決一些嚴重的效能問題。為了清楚的明白 var 的作用機制,我們首先來看看編譯器為 var 做了哪些工作?

2、編譯器為var關鍵字做了什麼?

首先 var 為語法糖,編譯器在編譯時根據右值推斷出表示式型別,再由編譯器將推斷出的表示式型別寫入到 IL 中,所以如下2段程式碼在 IL 中完全一致。

string foo = "SomeString";
var foo = "SomeString";

編譯期間,編譯器根據右值 “SomeString” ,可以推斷出這個表示式(右值)的型別為 string 型別,於是將 var 替換為 string ,再將它寫到IL中,於是以上兩段初始化 foo 的程式碼結果完全一致。

我們再來看一下兩段程式碼的IL:

編寫高質量程式碼的30條黃金守則-Day 01(首選隱式型別轉換)

本文示例的原始碼

DnSpy 的反編譯結果

編寫高質量程式碼的30條黃金守則-Day 01(首選隱式型別轉換)

Microsoft 技術支援文件中 ldstr 的解釋

注意:string 也是語法糖,編譯時,string 被替換為 System.String 寫進IL。

於是我們得到了一個重要的結論:

var 為語法糖,在編譯期間就已經被編譯器所決定,開發人員無法為編譯器決定型別。

隱式型別轉換為上述程式碼帶來了良好的可讀性,任何一名開發人員都會知道第2行程式碼的 var 的型別,它讓我們更加的關注程式碼片段中我們所需要關注的部分,而不是把重點放在它的型別上。因為大多數時候,這都是沒有意義的。

3、隱式型別轉換所帶來的良好可讀性

為了明白良好可讀性的問題,我們先來看一個程式碼片段:

var foo = new SomeType();

以上程式碼清晰明瞭,對於維護程式碼的人來說,它沒有增加任何的理解成本,foo 的型別就是 SomeType 型別。很多優秀開源專案中的大量被使用的工廠模式,也提供了類似的方法,如下程式碼片段:

var huaWei = PhoneFactory.CreatePhone();

一個簡單的靜態工廠類 PhoneFactory ,公開了 CreatePhone 方法,閱讀這段程式碼的開發人員,在幾乎沒有增加理解成本的情況下,很清楚的知道 huaWei 代表手機工廠類所生產的一個手機物件。但是下面的程式碼,情況可能就稍有不同了:

var result = someObject.DoSomething(someParameter);

你無法輕鬆的知道 result 的型別和它所表達的意義,事實上,它的不良好的可讀性,表現在以下幾個方面:

1、在此處,result 這個變數名並不是最好的選擇;

2、someObject 的含義不明;

3、DoSomething 含糊不清;

4、無法明確的知道 someParameter 程式碼什麼。

如果換成以下程式碼,情況會好很多:

var mostPopularPhone = someObject.DoSomething(someParameter);

情況有所好轉,意思也更清楚。結合語義上下文,var 的型別不言自明。但是在這種情況下,我依然建議大家將程式碼改為以下形式:

Phone mostPopularPhone = someObject.DoSomething(someParameter);

這被我寫在之前所在公司的開發手冊上,我相信我的經驗一定是正確的。

讓我們再來看一個新的示例:

var score = GetSomeNumber();

var rate = score / 100;

rate 的型別由變數 score 決定,然後開發者無法一眼看出 score 的型別,所以這是一個不良好的可讀性的程式碼片段,我們應該改為:

var score = GetSomeNumber();

double rate = score / 100;

怎麼樣,是不是看到這樣的程式碼,心裡舒服多了?因為你的理解成本更低了,心情舒暢了,一下子搬磚都能搬到5樓了。

於是,我們有了兩點總結:

1、當含義明確,在程式碼上下文較為清楚時(簡單的變數定義或工廠方法),建議優先使用 var;

2、在其它複雜情況下,儘量直接寫出 var 的型別。

隱式型別轉換所帶來的絕非僅僅是良好的可讀性,它有時可能會幫我們消除一些難以發現的Bug,這又是怎麼回事呢?

4、隱式型別轉換幫我們解決嚴重的效能問題

人自以為自己是世界上最聰明的生物,事實上並非如此,有時候,編譯器比我們聰明得多,也可靠得多。

我們看看以下兩個程式碼片段:

public IEnumerable<string> GetPhoneStartsWith1(string prefix) {
    IEnumerable<string> phones =
                    from r in db.Phones
                    select r.PhoneName;

    var result = phones.Where(r => r.StartsWith(prefix));
    return result;
}

public IEnumerable<string> GetPhoneStartsWith2(string prefix) {
    var phones =
        from r in db.Phones
        select r.PhoneName;

    var result = phones.Where(r => r.StartsWith(prefix));
    return result;
}

以上兩段程式碼有何不同?GetPhoneStartsWith1 方法中的 phones 原先的返回型別應當為 IQueryable<string>,但在這裡被顯式宣告的 phones 的 IEnumerable<string> 強制轉換了,熟悉 EF 的朋友們一定知道,IQueryable<T> 為延遲載入,本身並不會立刻查詢資料庫,事實上它只生成了一個表示式樹,在最終需要使用資料的時候才會真正執行查詢動作。

於是 GetPhoneStartsWith1 方法將資料庫中的可能的所有資料全部取回本地,再由 var result = phones.Where(r => r.StartsWith(prefix)); 執行本地過濾,消耗了太多網路資源,並且使用了 .Net 的資料過濾機制。

GetPhoneStartsWith2 方法則不然,phones 的型別被編譯器推斷為 IQueryable<string> ,並不會因此執行查詢操作,真正的查詢動作由 var result = phones.Where(r => r.StartsWith(prefix)); 執行,也就是說,它的資料過濾動作由資料庫引擎負責運算,最終只將符合條件的資料傳送回本地,既節省了網路傳遞成本,又節省了運算成本,豈不是一舉兩得?

5、總結

  1. 當含義明確,在程式碼上下文較為清楚時(簡單的變數定義或工廠方法),建議優先使用 var;
  2. 在其它複雜情況下,儘量直接寫出 var 的型別;
  3. 儘可能地相信編譯器,大多數時候,它比我們優秀得多。

開發人員應牢記以上開發守則,否則,人民群眾會仇恨你,你的朋友和家人也會嘲笑你,唾棄你

該系列文章由位元飛原創釋出,計劃用三個月時間寫完全30篇文章,為大家提供編寫高質量程式碼的一般準則。

本文由 位元飛 原創釋出,歡迎大家踴躍轉載。

轉載請註明本文地址:https://www.byteflying.com/archives/6455

相關文章