软件开发原则-控制代码行

原则

编写代码单元应该少于15行,方便阅读、理解、测试、重构,超过15行应提取拆分,直至少于15行。

应用

下面我们通过例子来应用此原则。原代码如下:

func initEngine() *gin.Engine {
    router := gin.Default()
    m := melody.New()
    m.Config.MaxMessageSize = 10024
    m.Config.MessageBufferSize = 5120
    Rooms = make(map[string]RoomInfo)
    router.GET("/v", func(c *gin.Context) {
        m.HandleRequest(c.Writer, c.Request)
    })
    m.HandleConnect(func(session *melody.Session) {
        log.Info("[ws] connect:", session)
    })

    m.HandleDisconnect(func(session *melody.Session) {
        log.Info("[ws] disconnect", session)
    })

    m.HandleMessage(func(session *melody.Session, msg []byte) {
        reg, exists := session.Get("reg")
        log.Info("[ws] message:", string(msg), "reg:", reg)
        if string(msg) == WebSocketClientBye {
            if exists {
                sp := (reg).(*WSMessage)
                if removeRoom(sp.RoomID, sp.ClientID) != "" {
                    log.Warnf("Socket removeRoom error:%v", reg)
                }
            }
            log.Info("Socket bye", reg)
            return
        }
        message := &WSMessage{}
        err := json.Unmarshal(msg, message)
        if err != nil {
            log.Error("Json error :", err)
            return
        }
        switch message.CMD {
        case "register":
            log.Debug("register")
            session.Set("reg", message)
            return
        default:
            log.Debug("socket default json:", message)
            // check
            p := (reg).(*WSMessage)
            roomID := p.RoomID
            my := p.ClientID
            m.BroadcastFilter(msg, func(s *melody.Session) bool {
                if reg, exists = s.Get("reg"); exists {
                    sp := (reg).(*WSMessage)
                    if r, ok := Rooms[roomID]; ok {
                        for _, c := range r.Clients {
                            if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                                log.Info("[ws] <----sent---->", c, my, string(msg))
                                return true
                            }
                        }
                    } else {
                        log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
                    }
                }
                return false
            })
        }

    })
    return router
}

这是一个初始化并实现WebSocketWeb功能的服务器,省略部分代码,并删除了一些调试信息。注释少的可怜,如果初次到这样的代码一定会觉得头大。在重构过程中发现使用此原则会让代码结构清晰,这也是我们重构的目的。下面简单介绍使用的方法:

  • 提取函数

当函数内容太多的时候一定是把所有功能塞到一起了,应该想办法把相同的功能抽离出新的函数。

原代码:

m.BroadcastFilter(msg, func(s *melody.Session) bool {
                if reg, exists = s.Get("reg"); exists {
                    sp := (reg).(*WSMessage)
                    if r, ok := Rooms[roomID]; ok {
                        for _, c := range r.Clients {
                            if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                                log.Info("[ws] <----sent---->", c, my, string(msg))
                                return true
                            }
                        }
                    } else {
                        log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
                    }
                }
                return false
            })

重构代码:

// SendRoomBroadcast 发送房间广播
func (wss *WebSocketServer) SendRoomBroadcast(roomID, my string, msg []byte) {
    wss.Server.BroadcastFilter(msg, func(s *melody.Session) bool {
        if reg, exists := s.Get("reg"); exists {
            sp := (reg).(*WSMessage)
            if r, ok := Rooms[roomID]; ok {
                for _, c := range r.Clients {
                    if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                        return true
                    }
                }
            } else {
                log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
            }
        }
        return false
    })
}
  • 封装类

当代码整理差不多后,会发现有很多相同的功能,当相同的功能太多的时候可以考虑封装成类。

// WebSocketServer 结构体
type WebSocketServer struct {
    Server *melody.Melody // websocket
}

// NewWebSocketServer 初始化WebSocketServer
func NewWebSocketServer(router *gin.Engine) *WebSocketServer {
    wss := &WebSocketServer{
        Server: melody.New(),
    }
    // 配置buffer size
    wss.Server.Config.MaxMessageSize = 10024
    wss.Server.Config.MessageBufferSize = 5120
    // 升级为websocket
    router.GET("/v", func(c *gin.Context) {
        wss.Server.HandleRequest(c.Writer, c.Request)
    })
    // 处理连接
    wss.Server.HandleConnect(wss.handleConnect)
    // 处理断开连接
    wss.Server.HandleDisconnect(wss.handleDisconnect)
    // 处理消息
    wss.Server.HandleMessage(wss.handleMessage)
    return wss
}

func (wss *WebSocketServer) handleExit(reg interface{}, exists bool, msg []byte) bool {
    if string(msg) == WebSocketClientBye {
        if exists {
            sp := (reg).(*WSMessage)
            if removeRoom(sp.RoomID, sp.ClientID) != "" {
                log.Warnf("Socket removeRoom error:%v", reg)
            }
        }
        log.Info("Socket bye", reg)
        return true
    }
    return false
}

// SendRoomBroadcast 发送房间广播
func (wss *WebSocketServer) SendRoomBroadcast(roomID, my string, msg []byte) {
    wss.Server.BroadcastFilter(msg, func(s *melody.Session) bool {
        if reg, exists := s.Get("reg"); exists {
            sp := (reg).(*WSMessage)
            if r, ok := Rooms[roomID]; ok {
                for _, c := range r.Clients {
                    if (sp.ClientID == c.ClientID) && (c.ClientID != my) {
                        return true
                    }
                }
            } else {
                log.Warnf("[ws] only myself???? roomID:%d,my:%v,msg:%v", roomID, my, string(msg))
            }
        }
        return false
    })
}

func (wss *WebSocketServer) handleMessage(session *melody.Session, msg []byte) {
    reg, exists := session.Get("reg")
    log.Info("[ws] message:", string(msg), "reg:", reg)
    if wss.handleExit(reg, exists, msg) {
        return
    }
    message := &WSMessage{}
    err := json.Unmarshal(msg, message)
    if err != nil {
        log.Error("Json error :", err)
        return
    }
    switch message.CMD {
    case "register":
        log.Debug("register")
        session.Set("reg", message)
        return
    default:
        log.Debug("socket default json:", message)
        wss.SendRoomBroadcast((reg).(*WSMessage).RoomID, (reg).(*WSMessage).ClientID, msg)
    }
}

func (wss *WebSocketServer) handleConnect(session *melody.Session) {
    log.Info("[ws] connect:", session)
}
func (wss *WebSocketServer) handleDisconnect(session *melody.Session) {
    log.Info("[ws] disconnect", session)
}

总结

这个原则足够简单,只要有耐心就能办到。还是建议大家拿起自己手中的代码体验一下,在重构过程中慢慢体会,不过一定要写测试哦,要不就变成裸奔啦。欢迎拍砖:)

Leave a Reply

Your email address will not be published. Required fields are marked *