谷歌SRE那本书中所讲到一点,就是搞业务运维或说SRE,最头疼的就是工作量随着服务规模的增长线性增加。其实个人觉得这一点放到开发中也是一样的,随着需求的增加,services、views目录下的文件越来越多,编写它们的时候都是copy&paste,维护它们的时候也是一样的任务量复制。
就其根源,个人觉得就是我们所谓“开发”对工程规范的漠视,导致维护过程中问题的累积。这里也记录一点过去积累的好的编码习惯(针对golang而言)
1,创建一个较为复杂的对象时,把New函数参数可选
比如说
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写这么有什么好处。但是后来发现,代码结构、可读性是真的非常重要。特别运维开发这块,国内现在就是一个起步过程,运维开发存在很多任务都是对之前代码的重构与功能维护。
我相信干过一段时间你就知道阅读别人代码的痛苦吧,可能搞懂逻辑就要花好几天,但是写代码不过一两天、两三天而已。
2,多写注释,如果一个函数参数很多,甚至可以给参数加注释,特别是我们一个操作对象,可能有n多个配置,如
func someOperation(true, true, false, false, true,...)
并且这个函数可能会在一个代码文件中出现多次,可能每次这些布尔配置参数还不一样,所以有必要加一点说明
func someOperation(true /* 允许重试/, true, false, false / 不记失败日志等*/,...)
3,减少作用域
... 上文中已经存在err 错误对象, 这个err可能并不希望被覆盖掉
if f, err := someOpt(); err != nil {
return err
}
f.xxx()
4,开关策略(这条是摘抄来的)
生产环境上的所有操作都是需要严格按照策略进行,那么就需要添加一个开关机制,我之前项目并未应用这种机制,确实带来了不少麻烦,所以这里可以摘录一下
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)
}
// 具体实现结构和实现方法
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)
}
// 这里开始调用
if switches.AsyncProcedure.IsOn() {
// do sth
}else{
// do other sth
}
并且这里可以看到的,switch对象中的name是通过点进行分隔的,这也是SRE中所说的,所有配置信息都应有清晰的命名标准,并且所有成员都可以了解到这些配置项
网友评论