Go通過(guò)不變性優(yōu)化程序詳解
正文
不變性的概念非常簡(jiǎn)單,在您創(chuàng)建結(jié)構(gòu)體后,就永遠(yuǎn)無(wú)法修改它。這個(gè)概念聽(tīng)起來(lái)非常簡(jiǎn)單,但您的程序想利用它從中收益并不是那么容易。接下來(lái)我們?cè)?Go 中,使用不變性概念,來(lái)讓您的代碼更具有可讀性和穩(wěn)定性。
減少對(duì)全局或外部狀態(tài)的依賴
當(dāng)我們使用相同的參數(shù),執(zhí)行相同的函數(shù)兩次,我們的預(yù)期,應(yīng)該得到相同的結(jié)果。但是當(dāng)我們的函數(shù)中依賴外部狀態(tài)或全局變量時(shí),函數(shù)可能會(huì)輸出不同的結(jié)果。我們最好避免這種情況。
函數(shù)的參數(shù)總是給定的,那我們調(diào)用,總是可以返回相同的函數(shù)。如果您有一個(gè)共享全局變量用于函數(shù)內(nèi)部的某些內(nèi)容,請(qǐng)考慮將該變量作為參數(shù)傳遞,而不是直接函數(shù)內(nèi)部使用它。
這可以讓您的函數(shù)返回值更加可預(yù)測(cè),并且更加易于測(cè)試,整個(gè)代碼的可讀性也會(huì)得到提高,因?yàn)檎{(diào)用者會(huì)知道,哪些值會(huì)影響函數(shù)的行為,參數(shù)的作用不就是會(huì)影響返回值的嗎?
讓我們看一個(gè)例子。
package main
import (
"fmt"
"math/rand"
"time"
)
var randNum int
func main() {
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
randNum = r1.Intn(100)
fmt.Println(Add(1, 1))
}
func Add(a, b int) int {
return a + b + randNum
}
Add 函數(shù)中使用了全局變量 randNum 作為計(jì)算的一部分,從函數(shù)簽名中并沒(méi)有體現(xiàn)這一點(diǎn)。更好的方法是,全局變量 randNum 應(yīng)該作為參數(shù)傳遞,如下所示。
func Add(a, b, randNum int) int {
return a + b + randNum
}
這樣更具有可預(yù)測(cè)性,而且我們?nèi)绻枰薷娜雲(yún)?,影響的作用域也僅在 Add 函數(shù)中。
僅導(dǎo)出結(jié)構(gòu)體的函數(shù),而不是成員變量
我們知道,Go 結(jié)構(gòu)體中的成員變量,如果首字母為大寫,那么該成員變量對(duì)外可見(jiàn)(這是編譯器決定的)?;氐轿覀兊牟┛停瑑H導(dǎo)出結(jié)構(gòu)體函數(shù),而不是成員變量,目的是希望成員變量的數(shù)據(jù)被保護(hù),保證成員變量的有效的狀態(tài)!因?yàn)檫@可以讓您的代碼更加可靠,您不必維護(hù)每個(gè)修改該成員變量的操作,因?yàn)檫@些操作都將無(wú)效。
舉一個(gè)例子
ackage main
import (
"fmt"
)
type AK47 struct {
bullet int
}
func NewAK47(bullet int) AK47 {
return AK47{bullet: bullet}
}
func (a AK47) GetBullet() int {
return a.bullet
}
func (a AK47) SetBullet(bullet int) {
a.bullet = bullet
}
func main() {
ak47 := NewAK47(30)
fmt.Println(ak47.GetBullet())
ak47.SetBullet(20)
fmt.Println(ak47.GetBullet())
}
我們定義了一個(gè)結(jié)構(gòu)體 AK47,這把槍有一個(gè)成員變量 bullet 子彈數(shù),它是非導(dǎo)出字段,我們還定義了一個(gè)構(gòu)造函數(shù) NewAK47 和一個(gè) GetBullet 函數(shù)。
一旦創(chuàng)建了 AK47,就無(wú)法更改它的成員變量 bullet 了。此時(shí)您可能會(huì)有疑惑,如果我們需要修改成員變量呢?別急,您可以試試下面的方法。
在函數(shù)中使用復(fù)制值,而不是使用指針
在上一個(gè)副標(biāo)題中,我們提到了一個(gè)概念,在創(chuàng)建結(jié)構(gòu)體后永遠(yuǎn)不要更改它。然而在實(shí)際中,我們經(jīng)常需要修改結(jié)構(gòu)體中的成員變量。
我們?cè)谑褂貌蛔冃缘耐瑫r(shí),仍然可以維護(hù)實(shí)例化結(jié)構(gòu)體的多個(gè)狀態(tài),這并不意味著我們打破了結(jié)構(gòu)體創(chuàng)建后不要更改它,我們更改的是它的副本,也就是復(fù)制后的結(jié)構(gòu)體。復(fù)制后的結(jié)構(gòu)體?難道我們需要去實(shí)現(xiàn)很多復(fù)制結(jié)構(gòu)體每個(gè)字段的函數(shù)嗎?
當(dāng)然不,我們可以利用 Go 的特性,在調(diào)用函數(shù)時(shí),入?yún)⑹菑?fù)制值的行為。對(duì)于需要修改結(jié)構(gòu)體中成員變量的操作,我們可以創(chuàng)建一個(gè)函數(shù),該函數(shù)接收結(jié)構(gòu)體為參數(shù),并且返回一個(gè)修改后的結(jié)構(gòu)體副本。
我們可以在不改變調(diào)用方結(jié)構(gòu)體的情況下,修改該副本的任何內(nèi)容,這意味著對(duì)于原結(jié)構(gòu)體沒(méi)有任何副作用,并且該結(jié)構(gòu)體的值仍然是可預(yù)測(cè)的。
不知道您有沒(méi)有用過(guò) Go 標(biāo)準(zhǔn)庫(kù)的 Slice 切片,其中的 append 函數(shù)就使用了這個(gè)方法。讓我們接著用 AK47 來(lái)實(shí)現(xiàn)這個(gè)方法
代碼如下
package main
import (
"fmt"
)
type AK47 struct {
bullet int
}
func NewAK47(bullet int) AK47 {
return AK47{bullet: bullet}
}
func (a AK47) GetBullet() int {
return a.bullet
}
func (a AK47) AddBullet(ak47 AK47) AK47 {
newAK47 := NewAK47(a.GetBullet() + ak47.GetBullet())
return newAK47
}
func main() {
ak47 := NewAK47(30)
add := NewAK47(20)
fmt.Println(ak47.GetBullet())
ak47 = ak47.AddBullet(add)
fmt.Println(ak47.GetBullet())
}如您所見(jiàn),我們通過(guò) AddBullet 函數(shù)增加槍的子彈,但實(shí)際上并沒(méi)有更改傳入的結(jié)構(gòu)體中的任何成員變量。最后,返回了一個(gè)帶有更新字段的新 AK47 結(jié)構(gòu)體。
與復(fù)制值相比,指針更有優(yōu)勢(shì),尤其是當(dāng)您的結(jié)構(gòu)體成員變量、內(nèi)容非常大時(shí)時(shí),這種方法,通過(guò)復(fù)制的方式修改數(shù)據(jù),可能會(huì)導(dǎo)致性能問(wèn)題。您應(yīng)該問(wèn)自己,這么做是否值得,例如您正在編寫并發(fā)代碼?
總結(jié)
您在使用不變量時(shí),請(qǐng)務(wù)必先權(quán)衡利弊。實(shí)現(xiàn)本篇博客中所描述的方法,需要大量的代碼。但是,如果我們?cè)诰帉懖l(fā)代碼時(shí),不考慮共享變量的不可變性,往往會(huì)出現(xiàn)與預(yù)期不符的情況,例如內(nèi)存競(jìng)態(tài)問(wèn)題?其實(shí)我想說(shuō)的就是線程安全問(wèn)題 : - )
實(shí)現(xiàn)不變性,也可能出現(xiàn)嚴(yán)重的性能問(wèn)題!這是一把雙刃劍。請(qǐng)不要過(guò)早的優(yōu)化代碼。
以上就是Go通過(guò)不變性優(yōu)化程序詳解的詳細(xì)內(nèi)容,更多關(guān)于Go 程序不變性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言使用kafka-go實(shí)現(xiàn)Kafka消費(fèi)消息
本篇文章主要介紹了使用kafka-go庫(kù)消費(fèi)Kafka消息,包含F(xiàn)etchMessage和ReadMessage的區(qū)別和適用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12
Go?runtime?調(diào)度器之系統(tǒng)調(diào)用引起的搶占
本文解析了在Go語(yǔ)言中,當(dāng)goroutine執(zhí)行的系統(tǒng)調(diào)用時(shí)間過(guò)長(zhǎng)時(shí),系統(tǒng)如何通過(guò)監(jiān)控和搶占機(jī)制來(lái)處理,以維持運(yùn)行效率和資源分配的平衡,通過(guò)具體的示例和流程圖,詳細(xì)展示了系統(tǒng)調(diào)用過(guò)程中的搶占操作,感興趣的朋友跟隨小編一起看看吧2024-09-09
解決Go中攔截HTTP流數(shù)據(jù)時(shí)字段丟失的問(wèn)題
在開(kāi)發(fā)高并發(fā)的Web應(yīng)用時(shí),尤其是在處理HTTP代理和流數(shù)據(jù)攔截的場(chǎng)景下,遇到數(shù)據(jù)丟失的問(wèn)題并不罕見(jiàn),最近,在一個(gè)項(xiàng)目中,我遇到了一個(gè)棘手的問(wèn)題:在攔截并轉(zhuǎn)發(fā)HTTP流數(shù)據(jù)的過(guò)程中,某些數(shù)據(jù)字段因?yàn)樘幚磉^(guò)快而被丟失,所以本文給大家介紹如何解決這個(gè)問(wèn)題2024-08-08
模塊一 GO語(yǔ)言基礎(chǔ)知識(shí)-庫(kù)源碼文件
這篇文章主要介紹了模塊一 GO語(yǔ)言基礎(chǔ)知識(shí)-庫(kù)源碼文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Go語(yǔ)言實(shí)現(xiàn)websocket推送程序
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)websocket推送程序,WebSocket是基于TCP的一個(gè)雙向傳輸數(shù)據(jù)的協(xié)議,和HTTP協(xié)議一樣,是在應(yīng)用層的,他的出現(xiàn),是為了解決網(wǎng)頁(yè)進(jìn)行持久雙向傳輸數(shù)據(jù)的問(wèn)題2023-01-01
Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析
這篇文章主要為大家介紹了Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Golang使用Gin框架實(shí)現(xiàn)HTTP上傳文件過(guò)程介紹

