谷歌SRE那本書中所講到一點熔酷,就是搞業(yè)務(wù)運維或說SRE爽室,最頭疼的就是工作量隨著服務(wù)規(guī)模的增長線性增加温自。其實個人覺得這一點放到開發(fā)中也是一樣的帘靡,隨著需求的增加知给,services、views目錄下的文件越來越多描姚,編寫它們的時候都是copy&paste涩赢,維護它們的時候也是一樣的任務(wù)量復(fù)制。
就其根源轩勘,個人覺得就是我們所謂“開發(fā)”對工程規(guī)范的漠視筒扒,導(dǎo)致維護過程中問題的累積。這里也記錄一點過去積累的好的編碼習慣(針對golang而言)
1绊寻,創(chuàng)建一個較為復(fù)雜的對象時花墩,把New函數(shù)參數(shù)可選
比如說
type MyOperation struct {
keyone string
valone int
someConfig bool
someConfig2 bool
storage *db.Connection
}
type Opt interface {
apply(*MyOperation)
}
type optFunc func(o *MyOperation)
func (f optFunc) apply(o *MyOperation) {
f(o)
}
func WithStorage(src *db.Connection) Opt {
return optFunc(func(o *MyOperation) {
o.storage = src
}
}
func WithConfig(conf bool) Opt {
...
}
func NewOperation(mustKey string, ... Opt) (*MyOperation, error) {
op := &MyOperation {
keyone : mustKey,
}
for _, optFunc := range Opt {
optFunc(op)
}
return op, nil
}
我剛開始的時候很不理解,把一個NewOperation寫這么有什么好處榛斯。但是后來發(fā)現(xiàn)观游,代碼結(jié)構(gòu)、可讀性是真的非常重要驮俗。特別運維開發(fā)這塊懂缕,國內(nèi)現(xiàn)在就是一個起步過程,運維開發(fā)存在很多任務(wù)都是對之前代碼的重構(gòu)與功能維護王凑。
我相信干過一段時間你就知道閱讀別人代碼的痛苦吧搪柑,可能搞懂邏輯就要花好幾天聋丝,但是寫代碼不過一兩天、兩三天而已工碾。
2弱睦,多寫注釋,如果一個函數(shù)參數(shù)很多渊额,甚至可以給參數(shù)加注釋况木,特別是我們一個操作對象,可能有n多個配置旬迹,如
func someOperation(true, true, false, false, true,...)
并且這個函數(shù)可能會在一個代碼文件中出現(xiàn)多次火惊,可能每次這些布爾配置參數(shù)還不一樣,所以有必要加一點說明
func someOperation(true /* 允許重試/, true, false, false / 不記失敗日志等*/,...)
3奔垦,減少作用域
... 上文中已經(jīng)存在err 錯誤對象屹耐, 這個err可能并不希望被覆蓋掉
if f, err := someOpt(); err != nil {
return err
}
f.xxx()
4,開關(guān)策略(這條是摘抄來的)
生產(chǎn)環(huán)境上的所有操作都是需要嚴格按照策略進行椿猎,那么就需要添加一個開關(guān)機制惶岭,我之前項目并未應(yīng)用這種機制,確實帶來了不少麻煩犯眠,所以這里可以摘錄一下
package switches
var (
xxxSwitchManager = SwitchManager{switches: make(map[string]*Switch)}
AsyncProcedure = &Switch{Name: "xxx.msg.procedure.async", On: true}
// 使能音視頻
EnableRealTimeVideo = &Switch{Name: "xxx.real.time.video", On: true}
)
func init() {
xxxSwitchManager.Register(AsyncProcedure,
EnableRealTimeVideo)
}
// 具體實現(xiàn)結(jié)構(gòu)和實現(xiàn)方法
type Switch struct {
Name string
On bool
listeners []ChangeListener
}
func (s *Switch) TurnOn() {
s.On = true
s.notifyListeners()
}
func (s *Switch) notifyListeners() {
if len(s.listeners) > 0 {
for _, l := range s.listeners {
l.OnChange(s.Name, s.On)
}
}
}
func (s *Switch) TurnOff() {
s.On = false
s.notifyListeners()
}
func (s *Switch) IsOn() bool {
return s.On
}
func (s *Switch) IsOff() bool {
return !s.On
}
func (s *Switch) AddChangeListener(l ChangeListener) {
if l == nil {
return
}
s.listeners = append(s.listeners, l)
}
type SwitchManager struct {
switches map[string]*Switch
}
func (m SwitchManager) Register(switches ...*Switch) {
for _, s := range switches {
m.switches[s.Name] = s
}
}
func (m SwitchManager) Unregister(name string) {
delete(m.switches, name)
}
func (m SwitchManager) TurnOn(name string) (bool, error) {
if s, ok := m.switches[name]; ok {
s.TurnOn()
return true, nil
} else {
return false, errors.New("switch " + name + " is not registered")
}
}
func (m SwitchManager) TurnOff(name string) (bool, error) {
if s, ok := m.switches[name]; ok {
s.TurnOff()
return true, nil
} else {
return false, errors.New("switch " + name + " is not registered")
}
}
func (m SwitchManager) IsOn(name string) (bool, error) {
if s, ok := m.switches[name]; ok {
return s.IsOn(), nil
} else {
return false, errors.New("switch " + name + " is not registered")
}
}
func (m SwitchManager) List() map[string]bool {
switches := make(map[string]bool)
for name, switcher := range m.switches {
switches[name] = switcher.On
}
return switches
}
type ChangeListener interface {
OnChange(name string, isOn bool)
}
// 這里開始調(diào)用
if switches.AsyncProcedure.IsOn() {
// do sth
}else{
// do other sth
}
并且這里可以看到的按灶,switch對象中的name是通過點進行分隔的,這也是SRE中所說的阔逼,所有配置信息都應(yīng)有清晰的命名標準兆衅,并且所有成員都可以了解到這些配置項