1. 程式人生 > 實用技巧 >為什麼golang中不存在三元運算子

為什麼golang中不存在三元運算子

三元運算子廣泛存在於其他語言中,比如:

python:

val = trueValue if expr else falseValue

javascript:

const val = expr ? trueValue : falseValue

c、c++:

const char *val = expr ? "trueValue" : "falseValue";

然而,被廣泛支援的三目運算子在golang中卻是不存在的!如果我們寫出類似下面的程式碼:

val := expr ? "trueValue" : "falseValue"

那麼編譯器就該抱怨了:invalid character U+003F '?'。意思是golang中不存在?這個運算子,編譯器不認識而且非字母數字下劃線也不能用做變數名,自然也就當作是非法字元了。

然而這是為什麼呢,其實官方給出了解釋,這裡簡單引用一下:

The reason ?: is absent from Go is that the language's designers had seen the operation used too often to create impenetrably complex expressions. The if-else form, although longer, is unquestionably clearer. A language needs only one conditional control flow construct.

golang中不存在?:運算子的原因是因為語言設計者已經預見到三元運算子經常被用來構建一些極其複雜的表示式。雖然使用if進行替代會讓程式碼顯得更長,但這毫無疑問可讀性更強。一個語言只需要有一種條件判斷結構就足夠了。

毫無疑問,這是在golang“大道至簡”的指導思想下的產物。

這段話其實沒問題,因為某些三元運算子的使用場景確實會降低程式碼的可讀性:

const status = (type===1?(agagin===1?'再售':'已售'):'未售')

const word = (res.distance === 0) ? 'a'
    : (res.distance === 1 && res.difference > 3) ? 'b'
    : (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) ? 'c'
    : 'd';

乍一看確實很複雜,至少第二個表示式不花個20秒細看可能沒法理清控制流程(想象一下當縮排錯位或是完全沒有縮排的時候)。

如果把它們直接轉化成if語句是這樣的:

let status = ''
if (type === 1) {
    if (again === 1) {
        status = '再售'
    } else {
        status = '已售'
    }
} else {
    status = '未售'
}

let word = ''
if (res.distance === 0) {
    word = 'a'
} else {
    if (res.distance === 1 && res.difference > 3) {
        word = 'b'
    } else {
        if (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) {
            word = 'c'
        } else {
            word = 'd'
        }
    }
}

看起來並沒有多少的改善,特別是例2,三層巢狀,不管是誰review到這段程式碼難免不會抱怨你幾句。

然而事實上這些程式碼是可以簡化的,考慮到三元運算子總是會給變數一個值,因此最後的else其實可以看作是變數的預設值,於是程式碼可以這麼寫:

let status = '未售'
if (type === 1) {
    if (again === 1) {
        status = '再售'
    } else {
        status = '已售'
    }
}

let word = 'd'
if (res.distance === 0) {
    word = 'a'
} else {
    if (res.distance === 1 && res.difference > 3) {
        word = 'b'
    } else {
        if (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) {
            word = 'c'
        }
    }
}

其次,對於例2,顯然可以使用else if來清除巢狀結構:

let word = 'd'
if (res.distance === 0) {
    word = 'a'
} else if (res.distance === 1 && res.difference > 3) {
    word = 'b'
} else if (res.distance === 2 && res.difference > 5 && String(res.key).length > 5) {
    word = 'c'
}

現在再來看,顯然使用if語句的版本的可讀性更高,邏輯也更清晰(通過去除巢狀)。

然而事實也不盡然。除了用三元運算子表達流程控制之外,事實上更常見更廣泛的一個應用是如下這樣的表示式:

const val = expr ? trueValue : falseValue

const func = (age) => age > 18 ? '成年人' : '未成年人'

類似上述通過一個簡短的條件表示式來確定變數的值,在開發中的出現頻率是相當高的。這時三元運算子的意圖更清晰,可讀性也較if語句更高,特別是配合匿名函式(lambda表示式)使用可以極大簡化我們的程式碼。

對此python的解決之道是之支援上述的簡化版三元表示式,同時表示式不支援巢狀,達到了揚長避短的目的。不過代價是編譯器的相關實現會複雜化。

而對於golang來說一個簡單的能只通過單遍掃描即可完成ast構建的編譯器是其保持急速的構建速度的祕訣之一,為了這樣簡單的功能增加編譯器實現的複雜度是不可接受的。同時由於golang“大道至簡”的哲學,能用現有語法結構解決的問題,自然不會再新增新的語法。

不過還是有辦法的,雖然不推薦:

func If(cond bool, a, b interface{}) {
    if cond {
        return a
    }

    return b
}

age := 20
val := If(age > 18, "成年人", "未成年人").(string)

不推薦這麼做是有幾個原因:

  1. 使用介面導致效能下降
  2. 需要強制的型別斷言
  3. 不管三元表示式還是if語句,對於不會到達的分支是不會計算的,也就是惰性計算;而給函式傳遞引數時每一個表示式都會被計算

最後總結一下:

三元運算子的優點:

  • 對於簡短的表示式使用三元運算子表意更清晰,特別是在習慣了線性閱讀三元運算子表示式之後
  • 不需要中間狀態(例如第一個例子中的let變數可以替換為const,程式碼更健壯),心智負擔更低
  • 沒有中間狀態也就意味著更少或完全沒有副作用,程式碼更易跟蹤和維護

但三元運算子也有明顯的缺點:

  • 對於複雜邏輯程式碼可讀性較差(例如第一個例子中的status,需要在trueValue的位置進行進一步的條件判斷時)
  • 容易被濫用,很多人將其用於替代if語句或是簡化複雜的if巢狀,這會導致上一條中所描述的結果
  • 條件分支只能為表示式,不支援多條語句

所以這是一個見仁見智的問題,總之只能入鄉隨俗了。

參考

https://juejin.im/post/6844903561759850510

https://www.it-swarm.dev/zh/javascript/替代js中的巢狀三元運算子/1055944752/

https://golang.org/doc/faq#Does_Go_have_a_ternary_form