這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Festival & Fuck, Coding, Inner depth, Sister and Others.
某些文章會提到《為什麼Go語言這麼不受待見》,《真的沒必要浪費心思在 Go 語言上》,《我為什麼放棄Go語言》,《Why worse is better》等話題。經常重溫這些話題,每次都會有新發現。最忌手裡有了一個語言,心裡便容不下另一個語言。
忽略細節、文法或者設計,Go語言各種好用。考慮到這些因素,Go被噴出翔都不為過。
本文不打算在細節、文法或者設計上扯淡,只舉些例子,說一說如何用Go語言寫出還湊合的代碼。
類、對象、屬性,可能還夾雜著一點設計模式
//代碼來自 https://github.com/xgdapg/xconn/blob/master/xconn.go,已驗證//Conn 對應一個tcp串連type Conn struct { //原生TCP串連 conn net.Conn //發送資料的channel(類似隊列) send chan *MsgData //訊息處理方法 msgHandler MsgHandler //tcp緩衝區 recvBuffer []byte //訊息一次封裝,後續會進行二次封裝 msgPacker MsgPacker //健全狀態檢查周期 pingInterval uint //健全狀態檢查方法 pingHandler PingHandler //停止健全狀態檢查channel pingStop chan bool //自訂關閉串連時所調用方法 closeHandler CloseHandler}//MsgHandler 為自訂處理訊息type MsgHandler interface { HandleMsg(*MsgData)}//MsgPacker 為自訂拆包解包方式,為了效能可以直接將此段與recvLoop端合并type MsgPacker interface { PackMsg(*MsgData) []byte UnpackMsg([]byte) *MsgData}//MsgData 自訂訊息類型type MsgData struct { Data []byte Ext interface{}}//PingHandler 為自訂心跳命令type PingHandler interface { HandlePing()}//CloseHandler 為串連斷開時的自訂動作type CloseHandler interface { HandleClose()}//NewConn 為處理新串連方式:啟動兩個協程,一個只負責讀,一個只負責寫,//也可以認為開啟了三個協程,第三個協程負責進行定時ping操作func NewConn(conn net.Conn) *Conn { c := &Conn{ conn: conn, send: make(chan *MsgData, 64), msgHandler: nil, recvBuffer: []byte{}, msgPacker: nil, pingInterval: 0, pingHandler: nil, pingStop: nil, closeHandler: nil, } go c.recvLoop() go c.sendLoop() return c}//recoverPanic 程式panic情況下的處理方法(例如向已經關閉的tcp串連寫資料會造成panic)func recoverPanic() { if err := recover(); err != nil { //fmt.Println(err) }}//SetMsgHandler 為 Getter Setter 方法func (this *Conn) SetMsgHandler(hdlr MsgHandler) { this.msgHandler = hdlr}//SetMsgPacker 為 Getter Setter 方法func (this *Conn) SetMsgPacker(packer MsgPacker) { this.msgPacker = packer}//SetPing 為 Getter Setter 方法func (this *Conn) SetPing(sec uint, hdlr PingHandler) { this.pingInterval = sec this.pingHandler = hdlr if this.pingStop == nil { this.pingStop = make(chan bool) } if sec > 0 { go this.pingLoop() }}//SetCloseHandler 為 Getter Setter 方法func (this *Conn) SetCloseHandler(hdlr CloseHandler) { this.closeHandler = hdlr}//pingLoop 為定時健全狀態檢查操作func (this *Conn) pingLoop() { defer recoverPanic() for { select { case <-this.pingStop: return case <-time.After(time.Duration(this.pingInterval) * time.Second): this.Ping() } }}//RawConn 返回原始的tcp串連func (this *Conn) RawConn() net.Conn { return this.conn}//recvLoop 用於處理接收到的tcp包,並進行拆包等操作,然後調用recvMsg方法進行處理func (this *Conn) recvLoop() { defer recoverPanic() defer this.Close() buffer := make([]byte, 2048) //一次封包協議:四個位元組(int32)表示包長度,根據包長度截取訊息長度作為包。 for { bytesRead, err := this.conn.Read(buffer) if err != nil { return } this.recvBuffer = append(this.recvBuffer, buffer[0:bytesRead]...) for len(this.recvBuffer) > 4 { length := binary.BigEndian.Uint32(this.recvBuffer[0:4]) readToPtr := length + 4 if uint32(len(this.recvBuffer)) < readToPtr { break } if length == 0 { if this.pingHandler != nil { this.pingHandler.HandlePing() } } else { buf := this.recvBuffer[4:readToPtr] go this.recvMsg(buf) } this.recvBuffer = this.recvBuffer[readToPtr:] } }}//recvMsg 為代理,實際執行的是背景HandleMsg方法。func (this *Conn) recvMsg(data []byte) { defer recoverPanic() msg := &MsgData{ Data: data, Ext: nil, } //調用UnackMsg對資訊進行二次解包 if this.msgPacker != nil { msg = this.msgPacker.UnpackMsg(data) } if this.msgHandler != nil { this.msgHandler.HandleMsg(msg) }}//sendLoop 用於發送資料包func (this *Conn) sendLoop() { defer recoverPanic() for { msg, ok := <-this.send if !ok { break } go this.sendMsg(msg) }}//sendMsg 用於發送資料包,實際先調用PackMsg進行資訊持久化,然後二次封包,轉換為本架構能接受的形式func (this *Conn) sendMsg(msg *MsgData) { defer recoverPanic() sendBytes := make([]byte, 4) if msg != nil { data := msg.Data if this.msgPacker != nil { data = this.msgPacker.PackMsg(msg) } length := len(data) binary.BigEndian.PutUint32(sendBytes, uint32(length)) sendBytes = append(sendBytes, data...) } this.conn.Write(sendBytes)}//Close 關閉串連func (this *Conn) Close() { defer recoverPanic() this.conn.Close() close(this.send) if this.pingStop != nil { close(this.pingStop) } if this.closeHandler != nil { this.closeHandler.HandleClose() }}//SendMsg 用於發送資料func (this *Conn) SendMsg(msg *MsgData) { this.send <- msg}//SendData 用於發送資料func (this *Conn) SendData(data []byte) { this.SendMsg(&MsgData{Data: data, Ext: nil})}//Ping 用於健康監測func (this *Conn) Ping() { go this.sendMsg(nil)}
作為一個專用於處理TCP連結的架構,實際上xconn(上文中的代碼)進行了兩次封裝,連訊息發送、資訊拆包封包、甚至接收資訊都進行了二次封裝。
實際代碼中,可以進行簡化操作,將二次的部分簡化為一次。
將代碼寫得和上面一樣工整,便已經超越大部分猿了。
上個例子中作者用到了recover,用的很克制,卻又恰到好處。
至於defer和panic
Go語言的try catch?
import ( "fmt" "github.com/manucorporat/try")func main() { try.This(func() { panic("my panic") }).Finally(func() { fmt.Println("this must be printed after the catch") }).Catch(func(e try.E) { // Print crash fmt.Println(e) })}
以上代碼純屬搞笑,個人不建議工程項目中使用如此寫法,但是這種做法可以借鑒。
工程代碼:(用於Go與資料庫Transaction)
//代碼來自《Go語言遊戲項目應用情況彙報》func (db *Database) Transaction(work func()) { db.lock.Lock() defer db.lock.UnLock() //事務控制 defer func() { if err := recover; err == nil { db.commit(info) } else { db.rollback() //選擇性拋出panic panic(TransError{err}) } }() //執行傳入的函數 work()}
《Go語言遊戲項目應用情況彙報》是我所能找到的,為數不多的幾個敢開放部分工程代碼的分享。整體代碼比較整潔,適於新手學習。
以上對err的處理方法寫法,和《Errors are values》有異曲同工之妙。
err的一種處理方式
示範代碼:來自《Errors are vales》
//這種寫法強烈不推薦!!!!這就是許多人說的Go程式一大半都在check error_, err = fd.Write(p0[a:b])if err != nil { return err}_, err = fd.Write(p1[c:d])if err != nil { return err}_, err = fd.Write(p2[e:f])if err != nil { return err}// and so on//重要的事情說兩遍:不推薦,但是個人小項目這樣寫,完全沒問題。
推薦如下寫法:
var err errorwrite := func(buf []byte) { if err != nil { return } _, err = w.Write(buf)}write(p0[a:b])write(p1[c:d])write(p2[e:f])// and so onif err != nil { return err}
也推薦如下寫法:
func (ew *errWriter) write(buf []byte) { if ew.err != nil { return } _, ew.err = ew.w.Write(buf)}
其他:
正名:為什麼選擇Go語言。
答:因為簡單,並且也不會別的。
建議:盡量選擇Go1.6及以上版本,避免GC造成程式STW。
至於GC效能,可以參考 Go1.6中的gc pause已經完全超越JVM了嗎?。
原文禁止轉載,因此提煉出關鍵字:從效果看XXX,但XXX;並且,XXX,XXX。
So, Why worse is better?