├── LICENSE ├── README.md ├── examples └── groupevent │ ├── README.md │ ├── api │ └── apidoc.md │ ├── cmd │ ├── eventpopdserver │ │ ├── README.md │ │ ├── main.go │ │ └── router │ │ │ ├── handler │ │ │ ├── basehandler.go │ │ │ ├── eventhandler.go │ │ │ └── memberhandler.go │ │ │ └── router.go │ ├── eventserver │ │ ├── README.md │ │ ├── main.go │ │ └── router │ │ │ ├── handler │ │ │ ├── basehandler.go │ │ │ ├── eventhandler.go │ │ │ └── memberhandler.go │ │ │ └── router.go │ ├── eventtimer │ │ ├── main.go │ │ └── updater │ │ │ └── event.go │ └── internal │ │ └── cmdinternal.go │ ├── config │ └── conf.yml │ ├── eventpopdserver │ ├── eventserver │ ├── go.mod │ ├── go.sum │ ├── internal │ ├── eventpopdserver │ │ ├── README.md │ │ ├── event │ │ │ ├── data.go │ │ │ └── event.go │ │ └── member │ │ │ ├── data.go │ │ │ └── member.go │ ├── eventserver │ │ ├── README.md │ │ ├── biz │ │ │ ├── event │ │ │ │ └── event.go │ │ │ └── member │ │ │ │ └── member.go │ │ └── data │ │ │ ├── event.go │ │ │ └── member.go │ ├── eventtimer │ │ └── event.go │ └── pkg │ │ ├── cfg │ │ └── cfg.go │ │ └── db │ │ └── db.go │ └── pkg │ └── middleware │ └── httpset.go ├── make-a-full-stack-coder └── makeafullstackcoder.md ├── package-oriented-design └── packageorienteddesign.md ├── package-style-guideline └── packagestyleguideline.md └── wechatsubscriberqr.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Young 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paper-code 2 | 3 | 对一些好的技术文章结合自己的实践经验进行翻译、举例说明等或自己的经验分享。主要包括架构设计、模式设计、模型设计、重构及源码解析等。 4 | 5 | ## 目录 6 | 7 | [Go 包的组织和命名](https://github.com/danceyoung/paper-code/blob/master/package-style-guideline/packagestyleguideline.md) 8 | 9 | [Go 面向包的设计及架构分层](https://github.com/danceyoung/paper-code/blob/master/package-oriented-design/packageorienteddesign.md) 10 | 11 | [为什么程序员要有技术的广度性](https://github.com/danceyoung/paper-code/blob/master/make-a-full-stack-coder/makeafullstackcoder.md) 12 | -------------------------------------------------------------------------------- /examples/groupevent/README.md: -------------------------------------------------------------------------------- 1 | # 大学团体活动 2 | 3 | ## 团体举办活动,学生参加活动 4 | 5 | - 举办方~~~~ 6 | 团体举办活动,查看活动列表 7 | - 参与方 8 | 参与活动,查看活动列表 9 | 10 | ## 2个服务 11 | 12 | - restful api服务,线上举办,参与,查看活动 13 | - 定时器,定时更新活动的状态:未开始、活动中、已过期 14 | 15 | ## API文档 16 | 17 | [api文件夹下](https://github.com/danceyoung/paper-code/blob/master/examples/groupevent/api/apidoc.md) 18 | -------------------------------------------------------------------------------- /examples/groupevent/api/apidoc.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ## Rest ful API 4 | 5 | ### 创建活动 6 | 7 | **简要描述:** 8 | 9 | - 举办方创建活动 10 | 11 | **请求URL:** 12 | 13 | - `/events/new` 14 | 15 | **请求方式:** 16 | 17 | - POST 18 | 19 | **参数:** 20 | 21 | 22 | | 参数名 | 必选 | 类型 | 说明 | 23 | | :- | :- | :- | - | 24 | | name | 是 | string | 活动名称 | 25 | | start_date | 是 | string | 开始日期 | 26 | | expired_on | 是 | string | 结束日期 | 27 | | member_count_limit | 是 | int | 报名人数上限 | 28 | | address | 是 | string | 活动地址 | 29 | | desc | 是 | string | 活动描述 | 30 | 31 | **请求示例** 32 | 33 | ``` 34 | { 35 | "name":"极限编程大赛", 36 | "start_date":"2020-10-10", 37 | "expired_on":"2021-01-01", 38 | "member_count_limit":20, 39 | "address":"红湖酒店咖啡厅某个角落", 40 | "desc":"什么是极限编程,什么是敏捷开发,什么是架构设计,什么是设计模式,什么是领域驱动开发" 41 | } 42 | ``` 43 | 44 | **返回示例** 45 | 46 | ``` 47 | { 48 | "code": 0, 49 | "msg":"", 50 | "data": null 51 | } 52 | ``` 53 | 54 | ### 参加活动 55 | 56 | **简要描述:** 57 | 58 | - 学生参加活动 59 | 60 | **请求URL:** 61 | 62 | - `/event/join?event-id=111` 63 | - 更符合rest ful规则的应该是/event/111/join, 64 | 65 | **请求方式:** 66 | 67 | - POST 68 | 69 | **参数:** 70 | 71 | 72 | | 参数名 | 必选 | 类型 | 说明 | 73 | | :- | :- | :- | - | 74 | | name | 是 | string | 姓名 | 75 | | g_m | 是 | string | 性别 | 76 | | student_id | 是 | string | 学号 | 77 | | college | 是 | string | 学院名称 | 78 | | level | 是 | string | 年级 | 79 | | profession | 是 | string | 专业 | 80 | 81 | **请求示例** 82 | 83 | ``` 84 | { 85 | "name":"Young Ding", 86 | "g_m":"g", 87 | "student_id":"20042222555", 88 | "college":"information college", 89 | "level":"2", 90 | "profession":"info manage" 91 | } 92 | ``` 93 | 94 | **返回示例** 95 | 96 | ``` 97 | { 98 | "code": 0, 99 | "msg":"", 100 | "data": null 101 | } 102 | ``` 103 | 104 | ### 活动列表 105 | 106 | **简要描述:** 107 | 108 | - 获取活动列表 109 | 110 | **请求URL:** 111 | 112 | - `/events?student-id=111` 113 | - 更符合rest ful规则的应该是/member/111/events 114 | 115 | **请求方式:** 116 | 117 | - GET 118 | 119 | **参数:** 120 | 121 | 122 | | 参数名 | 必选 | 类型 | 说明 | 123 | | :- | :- | :- | - | 124 | | student-id | 是 | string | 学号 | 125 | 126 | **返回示例** 127 | 128 | ``` 129 | { 130 | "code":0, 131 | "msg":"", 132 | "data":[ 133 | { 134 | "id":1, 135 | "name":"极限编程大赛", 136 | "start_date":"2020-10-10", 137 | "expired_on":"2021-01-01", 138 | "member_count_limit":20, 139 | "address":"红湖酒店咖啡厅某个角落", 140 | "desc":"什么是极限编程,什么是敏捷开发,什么是架构设计,什么是设计模式,什么是领域驱动开发" 141 | } 142 | ] 143 | } 144 | ``` 145 | 146 | 147 | 148 | ### 活动成员列表 149 | 150 | **简要描述:** 151 | 152 | - 获取活动的成员列表 153 | 154 | **请求URL:** 155 | 156 | - `/members?event-id=111` 157 | - 更符合rest ful规则的应该是/event/111/members, 158 | 159 | **请求方式:** 160 | 161 | - GET 162 | 163 | **参数:** 164 | 165 | 166 | | 参数名 | 必选 | 类型 | 说明 | 167 | | :- | :- | :- | - | 168 | | event-id | 是 | string | | 169 | 170 | **返回示例** 171 | 172 | ``` 173 | { 174 | "code":0, 175 | "msg":"", 176 | "data":[ 177 | { 178 | "name":"Young Ding", 179 | "g_m":"g", 180 | "student_id":"20042222555", 181 | "college":"information college", 182 | "level":"2", 183 | "profession":"info manage" 184 | } 185 | ] 186 | } 187 | ``` 188 | 189 | 欢迎使用ShowDoc! 190 | 191 | 欢迎使用ShowDoc! 192 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/README.md: -------------------------------------------------------------------------------- 1 | * `cmd/eventserver` 2 | 3 | 引用到的`internal/eventserver`的代码包的设计,主要是常用通用的一个架构分层:数据层,业务逻辑层,展示层,服务层等. 4 | 5 | * `cmd/eventpopdserver` 6 | 7 | 引用到的`internal/eventpopdserver`的代码包的设计,单纯是面向包的设计,个人理解就是面向功能进行设计包和分层. 8 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | _ "paper-code/examples/groupevent/cmd/eventpopdserver/router" 7 | _ "paper-code/examples/groupevent/internal/pkg/cfg" 8 | ) 9 | 10 | func main() { 11 | log.Println(http.ListenAndServe(":8080", nil)) 12 | } 13 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/router/handler/basehandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | type BaseHandler struct{} 10 | 11 | func (bh *BaseHandler) responseWith(rw http.ResponseWriter, resbody interface{}, err error) { 12 | if err != nil { 13 | temp := make(map[string]interface{}) 14 | temp["code"] = -1 15 | temp["msg"] = err.Error() 16 | temp["data"] = nil 17 | tempbytes, err := json.Marshal(temp) 18 | if err != nil { 19 | panic(err) 20 | } 21 | io.WriteString(rw, string(tempbytes)) 22 | } else { 23 | temp := make(map[string]interface{}) 24 | temp["code"] = 0 25 | temp["msg"] = "" 26 | temp["data"] = resbody 27 | tempbytes, err := json.Marshal(temp) 28 | if err != nil { 29 | panic(err) 30 | } 31 | io.WriteString(rw, string(tempbytes)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/router/handler/eventhandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "paper-code/examples/groupevent/internal/eventpopdserver/event" 9 | ) 10 | 11 | // How errors are handled: 12 | 13 | //Allowed to panic an application. 14 | //Wrap errors with context if not being handled. 15 | //Majority of handling errors happen here. 16 | 17 | type EventHandler struct { 18 | BaseHandler 19 | } 20 | 21 | func (eventH *EventHandler) NewAEvent(rw http.ResponseWriter, req *http.Request) { 22 | if req.Method != http.MethodPost { 23 | panic(errors.New("method request is mismatched")) 24 | } 25 | 26 | bodybytes, err := ioutil.ReadAll(req.Body) 27 | if err != nil { 28 | panic(err) 29 | } 30 | var dest event.EventM 31 | err = json.Unmarshal(bodybytes, &dest) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = event.NewAEvent(dest) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | eventH.responseWith(rw, nil, nil) 42 | } 43 | 44 | func (eventH *EventHandler) JoinAEvent(rw http.ResponseWriter, req *http.Request) { 45 | if err := req.ParseForm(); err != nil { 46 | panic(err) 47 | } 48 | 49 | bodybytes, err := ioutil.ReadAll(req.Body) 50 | if err != nil { 51 | panic(err) 52 | } 53 | var dest event.MemberM 54 | err = json.Unmarshal(bodybytes, &dest) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | err = event.JoinAEvent(req.Form.Get("event-id"), dest) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | eventH.responseWith(rw, nil, nil) 65 | } 66 | 67 | func (eventH *EventHandler) Events(rw http.ResponseWriter, req *http.Request) { 68 | if err := req.ParseForm(); err != nil { 69 | panic(err) 70 | } 71 | events, err := event.EventsBy(req.Form.Get("student-id")) 72 | eventH.responseWith(rw, events, err) 73 | } 74 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/router/handler/memberhandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "paper-code/examples/groupevent/internal/eventpopdserver/member" 7 | ) 8 | 9 | type MemberHandler struct { 10 | BaseHandler 11 | } 12 | 13 | func (mh *MemberHandler) Members(rw http.ResponseWriter, req *http.Request) { 14 | log.Println(req.ParseForm()) 15 | members, err := member.MembersBy(req.Form.Get("event-id")) 16 | mh.responseWith(rw, members, err) 17 | } 18 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventpopdserver/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "paper-code/examples/groupevent/cmd/eventpopdserver/router/handler" 6 | 7 | "paper-code/examples/groupevent/pkg/middleware" 8 | ) 9 | 10 | // "paper-code/examples/groupevent/pkg/middleware" 11 | 12 | const prepath string = "/group-event-popd-serviced" 13 | 14 | func init() { 15 | 16 | http.Handle(prepath+"/events/new", middleware.HandlerConv(new(handler.EventHandler).NewAEvent)) 17 | http.Handle(prepath+"/event/join", middleware.HandlerConv(new(handler.EventHandler).JoinAEvent)) 18 | http.Handle(prepath+"/events", middleware.HandlerConv(new(handler.EventHandler).Events)) 19 | http.Handle(prepath+"/members", middleware.HandlerConv(new(handler.MemberHandler).Members)) 20 | } 21 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/README.md: -------------------------------------------------------------------------------- 1 | * `cmd/eventserver` 2 | 3 | 引用到的`internal/eventserver`的代码包的设计,主要是常用通用的一个架构分层:数据层,业务逻辑层,展示层,服务层等. 4 | 5 | * `cmd/eventpopdserver` 6 | 7 | 引用到的`internal/eventpopdserver`的代码包的设计,单纯是面向包的设计,个人理解就是面向功能进行设计包和分层. 8 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | _ "paper-code/examples/groupevent/cmd/eventserver/router" 7 | _ "paper-code/examples/groupevent/internal/pkg/cfg" 8 | ) 9 | 10 | func main() { 11 | log.Println(http.ListenAndServe(":8080", nil)) 12 | } 13 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/router/handler/basehandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | type BaseHandler struct{} 10 | 11 | func (bh *BaseHandler) responseWith(rw http.ResponseWriter, resbody interface{}, err error) { 12 | if err != nil { 13 | temp := make(map[string]interface{}) 14 | temp["code"] = -1 15 | temp["msg"] = err.Error() 16 | temp["data"] = nil 17 | tempbytes, err := json.Marshal(temp) 18 | if err != nil { 19 | panic(err) 20 | } 21 | io.WriteString(rw, string(tempbytes)) 22 | } else { 23 | temp := make(map[string]interface{}) 24 | temp["code"] = 0 25 | temp["msg"] = "" 26 | temp["data"] = resbody 27 | tempbytes, err := json.Marshal(temp) 28 | if err != nil { 29 | panic(err) 30 | } 31 | io.WriteString(rw, string(tempbytes)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/router/handler/eventhandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "paper-code/examples/groupevent/internal/eventserver/biz/event" 9 | ) 10 | 11 | // How errors are handled: 12 | 13 | //Allowed to panic an application. 14 | //Wrap errors with context if not being handled. 15 | //Majority of handling errors happen here. 16 | 17 | type EventHandler struct { 18 | BaseHandler 19 | } 20 | 21 | func (eventH *EventHandler) NewAEvent(rw http.ResponseWriter, req *http.Request) { 22 | if req.Method != http.MethodPost { 23 | panic(errors.New("method request is mismatched")) 24 | } 25 | 26 | bodybytes, err := ioutil.ReadAll(req.Body) 27 | if err != nil { 28 | panic(err) 29 | } 30 | var dest event.EventM 31 | err = json.Unmarshal(bodybytes, &dest) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | err = event.NewAEvent(dest) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | eventH.responseWith(rw, nil, nil) 42 | } 43 | 44 | func (eventH *EventHandler) JoinAEvent(rw http.ResponseWriter, req *http.Request) { 45 | if err := req.ParseForm(); err != nil { 46 | panic(err) 47 | } 48 | 49 | bodybytes, err := ioutil.ReadAll(req.Body) 50 | if err != nil { 51 | panic(err) 52 | } 53 | var dest event.MemberM 54 | err = json.Unmarshal(bodybytes, &dest) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | err = event.JoinAEvent(req.Form.Get("event-id"), dest) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | eventH.responseWith(rw, nil, nil) 65 | } 66 | 67 | func (eventH *EventHandler) Events(rw http.ResponseWriter, req *http.Request) { 68 | if err := req.ParseForm(); err != nil { 69 | panic(err) 70 | } 71 | events, err := event.EventsBy(req.Form.Get("student-id")) 72 | eventH.responseWith(rw, events, err) 73 | } 74 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/router/handler/memberhandler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "paper-code/examples/groupevent/internal/eventserver/biz/member" 7 | ) 8 | 9 | type MemberHandler struct { 10 | BaseHandler 11 | } 12 | 13 | func (mh *MemberHandler) Members(rw http.ResponseWriter, req *http.Request) { 14 | log.Println(req.ParseForm()) 15 | members, err := member.MembersBy(req.Form.Get("event-id")) 16 | mh.responseWith(rw, members, err) 17 | } 18 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventserver/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "paper-code/examples/groupevent/cmd/eventserver/router/handler" 6 | "paper-code/examples/groupevent/pkg/middleware" 7 | ) 8 | 9 | const prepath string = "/group-event-serviced" 10 | 11 | func init() { 12 | http.Handle(prepath+"/events/new", middleware.HandlerConv(new(handler.EventHandler).NewAEvent)) 13 | http.Handle(prepath+"/event/join", middleware.HandlerConv(new(handler.EventHandler).JoinAEvent)) 14 | http.Handle(prepath+"/events", middleware.HandlerConv(new(handler.EventHandler).Events)) 15 | http.Handle(prepath+"/members", middleware.HandlerConv(new(handler.MemberHandler).Members)) 16 | } 17 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventtimer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "paper-code/examples/groupevent/cmd/eventtimer/updater" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | //execute updating when 00:00:00 everyday 12 | h, m, s := time.Now().Clock() 13 | firstDuration, _ := time.ParseDuration(fmt.Sprintf("%dh%dm%ds", 23-h, 60-m, 60-s)) 14 | resetDuration, _ := time.ParseDuration(fmt.Sprintf("%dh%dm%ds", 24, 0, 0)) 15 | timer := time.NewTimer(firstDuration) 16 | for { 17 | select { 18 | case <-timer.C: 19 | timer.Reset(resetDuration) 20 | log.Println("update: ", time.Now()) 21 | updater.UpdateEventStatus() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/eventtimer/updater/event.go: -------------------------------------------------------------------------------- 1 | package updater 2 | 3 | import ( 4 | "log" 5 | "paper-code/examples/groupevent/internal/eventtimer" 6 | ) 7 | 8 | func UpdateEventStatus() { 9 | log.Println("updating status of status...") 10 | err := eventtimer.UpdateEventStatus() 11 | if err != nil { 12 | panic(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/groupevent/cmd/internal/cmdinternal.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | func CmdInternalFunc() {} 4 | -------------------------------------------------------------------------------- /examples/groupevent/config/conf.yml: -------------------------------------------------------------------------------- 1 | dev: 2 | mysql: root:root@tcp(127.0.0.1:3306)/group_event?charset=utf8 3 | test: 4 | mysql: root:root@tcp(127.0.0.1:3306)/group_event?charset=utf8 5 | prod: 6 | mysql: root:root@tcp(127.0.0.1:3306)/group_event?charset=utf8 7 | -------------------------------------------------------------------------------- /examples/groupevent/eventpopdserver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danceyoung/paper-code/a1c7f5c110be820c02a88314b66f82420cfa775f/examples/groupevent/eventpopdserver -------------------------------------------------------------------------------- /examples/groupevent/eventserver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danceyoung/paper-code/a1c7f5c110be820c02a88314b66f82420cfa775f/examples/groupevent/eventserver -------------------------------------------------------------------------------- /examples/groupevent/go.mod: -------------------------------------------------------------------------------- 1 | module paper-code/examples/groupevent 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.5.0 7 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 8 | ) 9 | -------------------------------------------------------------------------------- /examples/groupevent/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 2 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 3 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 4 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 5 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 6 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventpopdserver/README.md: -------------------------------------------------------------------------------- 1 | * `cmd/eventserver` 2 | 3 | 引用到的`internal/eventserver`的代码包的设计,主要是常用通用的一个架构分层:数据层,业务逻辑层,展示层,服务层等. 4 | 5 | * `cmd/eventpopdserver` 6 | 7 | 引用到的`internal/eventpopdserver`的代码包的设计,单纯是面向包的设计,个人理解就是面向功能进行设计包和分层. 8 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventpopdserver/event/data.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "paper-code/examples/groupevent/internal/pkg/db" 5 | ) 6 | 7 | const insertaeventsql string = "INSERT INTO events(name,start_date,expired_on,member_count_limit,address,`desc`) VALUES (?,?,?,?,?,?)" 8 | const insertajoinsql string = `INSERT INTO event_members (event_id, student_name, student_id, g_m, college, level, profession) VALUES (?,?,?,?,?,?,?);` 9 | const selecteventsbystudentidsql string = "SELECT events.name,events.start_date,events.expired_on,events.member_count_limit,events.address,events.`desc`FROM event_members, events where events.id=event_members.event_id and student_id=?" 10 | 11 | func newAEvent(name, startDate, expiredOn string, countLimit int, address, desc string) error { 12 | _, err := db.NewDB().Exec(insertaeventsql, name, startDate, expiredOn, countLimit, address, desc) 13 | if err != nil { 14 | return err 15 | } 16 | return nil 17 | } 18 | 19 | func joinAEvent(eventId string, name, gm, studentId, college, level, profession string) error { 20 | _, err := db.NewDB().Exec(insertajoinsql, eventId, name, studentId, gm, college, level, profession) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | func eventsBy(studentId string) ([]map[string]interface{}, error) { 28 | rows, err := db.NewDB().Query(selecteventsbystudentidsql, studentId) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer rows.Close() 33 | var result []map[string]interface{} 34 | for rows.Next() { 35 | var ( 36 | name, address, desc string 37 | startDate, expiredOn string 38 | countLimit int 39 | ) 40 | err = rows.Scan(&name, &startDate, &expiredOn, &countLimit, &address, &desc) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | temp := make(map[string]interface{}) 46 | temp["name"] = name 47 | temp["startDate"] = startDate 48 | temp["expiredOn"] = expiredOn 49 | temp["countLimit"] = countLimit 50 | temp["address"] = address 51 | temp["desc"] = desc 52 | result = append(result, temp) 53 | 54 | } 55 | 56 | return result, nil 57 | } 58 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventpopdserver/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | 6 | "time" 7 | ) 8 | 9 | //how errors are handled: 10 | 11 | //1 NOT allowed to panic an application. 12 | //2 Wrap errors with context if not being handled. 13 | //3 Minority of handling errors happen here. 14 | 15 | // how recovering panics: 16 | 17 | //Can not recover from panics 18 | 19 | type EventM struct { 20 | Name string `json:"name"` 21 | StartDate string `json:"start_date"` 22 | ExpiredOn string `json:"expired_on"` 23 | MemberCountLimit int `json:"member_count_limit"` 24 | Address string `json:"address"` 25 | Desc string `json:"desc"` 26 | } 27 | 28 | func NewAEvent(ev EventM) error { 29 | if _, err := time.Parse("2006-01-02", ev.StartDate); err != nil { 30 | return errors.New("date param is invalid") 31 | } 32 | if _, err := time.Parse("2006-01-02", ev.ExpiredOn); err != nil { 33 | return errors.New("date param is invalid") 34 | } 35 | 36 | if len(ev.Name) == 0 || len(ev.Address) == 0 || len(ev.Desc) == 0 || ev.MemberCountLimit == 0 { 37 | return errors.New("params are not enough") 38 | } 39 | 40 | if err := newAEvent(ev.Name, ev.StartDate, ev.ExpiredOn, ev.MemberCountLimit, ev.Address, ev.Desc); err != nil { 41 | return errors.New("a error was accurred when new a event. " + err.Error()) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | type MemberM struct { 48 | Name string `json:"name"` 49 | Gm string `json:"g_m"` 50 | StudentId string `json:"student_id"` 51 | College string `json:"college"` 52 | Level string `json:"level"` 53 | Profession string `json:"profession"` 54 | } 55 | 56 | func JoinAEvent(eventId string, m MemberM) error { 57 | if len(eventId) == 0 || len(m.StudentId) == 0 { 58 | return errors.New("join a event: params are not enough") 59 | } 60 | if err := joinAEvent(eventId, m.Name, m.Gm, m.StudentId, m.College, m.Level, m.Profession); err != nil { 61 | return errors.New("join a event: a error was accurred, " + err.Error()) 62 | } 63 | return nil 64 | } 65 | func EventsBy(studentId string) ([]map[string]interface{}, error) { 66 | if len(studentId) == 0 { 67 | return nil, errors.New("events by: params are not enough") 68 | } 69 | 70 | return eventsBy(studentId) 71 | } 72 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventpopdserver/member/data.go: -------------------------------------------------------------------------------- 1 | package member 2 | 3 | import "paper-code/examples/groupevent/internal/pkg/db" 4 | 5 | const selectmemberssql string = "select student_name, student_id, g_m, college, level, profession from event_members where event_id=?" 6 | 7 | func membersBy(eventid string) ([]map[string]string, error) { 8 | rows, err := db.NewDB().Query(selectmemberssql, eventid) 9 | if err != nil { 10 | return nil, err 11 | } 12 | 13 | defer rows.Close() 14 | var result []map[string]string 15 | for rows.Next() { 16 | var ( 17 | studentName, studentId, gm, college, level, profession string 18 | ) 19 | err := rows.Scan(&studentName, &studentId, &gm, &college, &level, &profession) 20 | if err != nil { 21 | return nil, err 22 | } 23 | temp := make(map[string]string) 24 | temp["studentName"] = studentName 25 | temp["studentId"] = studentId 26 | temp["gm"] = gm 27 | temp["college"] = college 28 | temp["level"] = level 29 | temp["profession"] = profession 30 | result = append(result, temp) 31 | } 32 | return result, nil 33 | } 34 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventpopdserver/member/member.go: -------------------------------------------------------------------------------- 1 | package member 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | func MembersBy(eventid string) ([]map[string]string, error) { 8 | if len(eventid) == 0 { 9 | return nil, errors.New("members by: params are not enough") 10 | } 11 | 12 | return membersBy(eventid) 13 | } 14 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventserver/README.md: -------------------------------------------------------------------------------- 1 | * `cmd/eventserver` 2 | 3 | 引用到的`internal/eventserver`的代码包的设计,主要是常用通用的一个架构分层:数据层,业务逻辑层,展示层,服务层等. 4 | 5 | * `cmd/eventpopdserver` 6 | 7 | 引用到的`internal/eventpopdserver`的代码包的设计,单纯是面向包的设计,个人理解就是面向功能进行设计包和分层. 8 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventserver/biz/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "errors" 5 | "paper-code/examples/groupevent/internal/eventserver/data" 6 | 7 | "time" 8 | ) 9 | 10 | //how errors are handled: 11 | 12 | //1 NOT allowed to panic an application. 13 | //2 Wrap errors with context if not being handled. 14 | //3 Minority of handling errors happen here. 15 | 16 | // how recovering panics: 17 | 18 | //Can not recover from panics 19 | 20 | type EventM struct { 21 | Name string `json:"name"` 22 | StartDate string `json:"start_date"` 23 | ExpiredOn string `json:"expired_on"` 24 | MemberCountLimit int `json:"member_count_limit"` 25 | Address string `json:"address"` 26 | Desc string `json:"desc"` 27 | } 28 | 29 | func NewAEvent(ev EventM) error { 30 | if _, err := time.Parse("2006-01-02", ev.StartDate); err != nil { 31 | return errors.New("date param is invalid") 32 | } 33 | if _, err := time.Parse("2006-01-02", ev.ExpiredOn); err != nil { 34 | return errors.New("date param is invalid") 35 | } 36 | 37 | if len(ev.Name) == 0 || len(ev.Address) == 0 || len(ev.Desc) == 0 || ev.MemberCountLimit == 0 { 38 | return errors.New("params are not enough") 39 | } 40 | 41 | if err := data.NewAEvent(ev.Name, ev.StartDate, ev.ExpiredOn, ev.MemberCountLimit, ev.Address, ev.Desc); err != nil { 42 | return errors.New("a error was accurred when new a event. " + err.Error()) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | type MemberM struct { 49 | Name string `json:"name"` 50 | Gm string `json:"g_m"` 51 | StudentId string `json:"student_id"` 52 | College string `json:"college"` 53 | Level string `json:"level"` 54 | Profession string `json:"profession"` 55 | } 56 | 57 | func JoinAEvent(eventId string, m MemberM) error { 58 | if len(eventId) == 0 || len(m.StudentId) == 0 { 59 | return errors.New("join a event: params are not enough") 60 | } 61 | if err := data.JoinAEvent(eventId, m.Name, m.Gm, m.StudentId, m.College, m.Level, m.Profession); err != nil { 62 | return errors.New("join a event: a error was accurred, " + err.Error()) 63 | } 64 | return nil 65 | } 66 | func EventsBy(studentId string) ([]map[string]interface{}, error) { 67 | if len(studentId) == 0 { 68 | return nil, errors.New("events by: params are not enough") 69 | } 70 | 71 | return data.EventsBy(studentId) 72 | } 73 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventserver/biz/member/member.go: -------------------------------------------------------------------------------- 1 | package member 2 | 3 | import ( 4 | "errors" 5 | 6 | "paper-code/examples/groupevent/internal/eventserver/data" 7 | ) 8 | 9 | func MembersBy(eventid string) ([]map[string]string, error) { 10 | if len(eventid) == 0 { 11 | return nil, errors.New("members by: params are not enough") 12 | } 13 | 14 | return data.MembersBy(eventid) 15 | } 16 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventserver/data/event.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "paper-code/examples/groupevent/internal/pkg/db" 5 | ) 6 | 7 | const insertaeventsql string = "INSERT INTO events(name,start_date,expired_on,member_count_limit,address,`desc`) VALUES (?,?,?,?,?,?)" 8 | const insertajoinsql string = `INSERT INTO event_members (event_id, student_name, student_id, g_m, college, level, profession) VALUES (?,?,?,?,?,?,?);` 9 | const selecteventsbystudentidsql string = "SELECT events.name,events.start_date,events.expired_on,events.member_count_limit,events.address,events.`desc`FROM event_members, events where events.id=event_members.event_id and student_id=?" 10 | 11 | func NewAEvent(name, startDate, expiredOn string, countLimit int, address, desc string) error { 12 | _, err := db.NewDB().Exec(insertaeventsql, name, startDate, expiredOn, countLimit, address, desc) 13 | if err != nil { 14 | return err 15 | } 16 | return nil 17 | } 18 | 19 | func JoinAEvent(eventId string, name, gm, studentId, college, level, profession string) error { 20 | _, err := db.NewDB().Exec(insertajoinsql, eventId, name, studentId, gm, college, level, profession) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | func EventsBy(studentId string) ([]map[string]interface{}, error) { 28 | rows, err := db.NewDB().Query(selecteventsbystudentidsql, studentId) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer rows.Close() 33 | var result []map[string]interface{} 34 | for rows.Next() { 35 | var ( 36 | name, address, desc string 37 | startDate, expiredOn string 38 | countLimit int 39 | ) 40 | err = rows.Scan(&name, &startDate, &expiredOn, &countLimit, &address, &desc) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | temp := make(map[string]interface{}) 46 | temp["name"] = name 47 | temp["startDate"] = startDate 48 | temp["expiredOn"] = expiredOn 49 | temp["countLimit"] = countLimit 50 | temp["address"] = address 51 | temp["desc"] = desc 52 | result = append(result, temp) 53 | 54 | } 55 | 56 | return result, nil 57 | } 58 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventserver/data/member.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "paper-code/examples/groupevent/internal/pkg/db" 5 | ) 6 | 7 | const selectmemberssql string = "select student_name, student_id, g_m, college, level, profession from event_members where event_id=?" 8 | 9 | func MembersBy(eventid string) ([]map[string]string, error) { 10 | rows, err := db.NewDB().Query(selectmemberssql, eventid) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | defer rows.Close() 16 | var result []map[string]string 17 | for rows.Next() { 18 | var ( 19 | studentName, studentId, gm, college, level, profession string 20 | ) 21 | err := rows.Scan(&studentName, &studentId, &gm, &college, &level, &profession) 22 | if err != nil { 23 | return nil, err 24 | } 25 | temp := make(map[string]string) 26 | temp["studentName"] = studentName 27 | temp["studentId"] = studentId 28 | temp["gm"] = gm 29 | temp["college"] = college 30 | temp["level"] = level 31 | temp["profession"] = profession 32 | result = append(result, temp) 33 | } 34 | return result, nil 35 | } 36 | -------------------------------------------------------------------------------- /examples/groupevent/internal/eventtimer/event.go: -------------------------------------------------------------------------------- 1 | package eventtimer 2 | 3 | import ( 4 | "log" 5 | "paper-code/examples/groupevent/internal/pkg/db" 6 | ) 7 | 8 | const updateeventsstatussql string = `UPDATE events 9 | SET 10 | status = (CASE 11 | WHEN DATEDIFF(NOW(), start_date) < 0 THEN 'deactived' 12 | WHEN 13 | DATEDIFF(NOW(), start_date) > 0 14 | AND DATEDIFF(NOW(), expired_on) < 0 15 | THEN 16 | 'actived' 17 | ELSE 'expired' 18 | END) 19 | WHERE 20 | id > 0` 21 | 22 | func UpdateEventStatus() error { 23 | log.Println("start update mysql...") 24 | _, err := db.NewDB().Exec(updateeventsstatussql) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /examples/groupevent/internal/pkg/cfg/cfg.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | type configM struct { 12 | envmode string 13 | currcfg map[string]interface{} 14 | } 15 | 16 | var cfgm configM 17 | 18 | func init() { 19 | filebytes, err := ioutil.ReadFile("./config/conf.yml") 20 | if err != nil { 21 | panic(err) 22 | } 23 | out := make(map[string]interface{}) 24 | err = yaml.Unmarshal(filebytes, &out) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | cfgm = configM{envmode: "dev"} 30 | if temp := os.Getenv("ENV_MODE"); temp != "" { 31 | cfgm.envmode = temp 32 | } 33 | cfgm.currcfg = out[cfgm.envmode].(map[string]interface{}) 34 | log.Println("config is ", cfgm) 35 | } 36 | 37 | func String(key string) string { 38 | if v, ok := cfgm.currcfg[key]; ok { 39 | return v.(string) 40 | } 41 | 42 | return "" 43 | } 44 | -------------------------------------------------------------------------------- /examples/groupevent/internal/pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | 7 | "paper-code/examples/groupevent/internal/pkg/cfg" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | ) 11 | 12 | // How errors are handled: 13 | //NOT allowed to panic an application. 14 | //NOT allowed to wrap errors. 15 | //Return only root cause error values. 16 | 17 | var db *sql.DB 18 | 19 | func NewDB() *sql.DB { 20 | return db 21 | } 22 | 23 | func init() { 24 | tempdb, err := sql.Open("mysql", cfg.String("mysql")) 25 | if err != nil { 26 | log.Println(err) 27 | return 28 | } 29 | 30 | tempdb.SetMaxIdleConns(200) 31 | tempdb.SetMaxOpenConns(20) 32 | log.Println("init mysql successfully") 33 | db = tempdb 34 | } 35 | -------------------------------------------------------------------------------- /examples/groupevent/pkg/middleware/httpset.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func HandlerConv(handlerF func(http.ResponseWriter, *http.Request)) http.Handler { 9 | return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 10 | fmt.Println("url request is ", req.URL.RequestURI()) 11 | rw.Header().Set("Content-Type", "application/json") 12 | handlerF(rw, req) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /make-a-full-stack-coder/makeafullstackcoder.md: -------------------------------------------------------------------------------- 1 | # 为什么具备技术的广度性 2 | 3 | references: 4 | 5 | https://cult.honeypot.io/reads/become-a-polyglot-developer/ 6 | 7 | https://www.businessinsider.com/the-top-coding-languages-with-the-highest-salary-2020-4 8 | 9 | “我是一个objective-c程序员,不用swift, react native同样很好,很方便地解决问题“ 10 | 11 | “我喜欢Java,接受不了javascript等脚本语言“ 12 | 13 | “我喜欢面向对象,不喜欢函数式编程“ 14 | 15 | ... 16 | 17 | 我们有时在交流技术的时候,时不时能听到这样的声音,其实也没什么毛病。因为现在的技术太多了,我们能很自由地选择喜欢的语言,不去接触其他语言。 18 | 19 | 但是目前的趋势和数据证明,架构师、全栈或具备更多技术的程序员更具有优势,只掌握一门技术的程序员不再具备更多优势,除非你是真正的这门技术专家,然而真正的技术专家能有几个,都是万里挑一。再者就是因为我们写的更多代码是业务代码,基本是代码的搬运工,虽然也不好搬运。当然你同时又具备深度性,那绝对更能把代码写得更整洁、扩展性、可读性等。 20 | 21 | 学习和掌握更多的技术,自己达到一定程度的技术广度性和深度性,能让你在职场或找工作时更有优势。同时也会让你更轻松、更快地在不同技术之间进行切换和运用,因为万变不离其宗,一些根本的东西是一样的,比如算法、数据结构、内存、线程、网络协议等。 22 | 23 | 达到了一定的技术广度性,你解决问题的思路更广度,思维更前瞻,也会让你更清晰地知道自己所要研究的某一些或技术的某一块,因为你知道了每种语言的使用场景,优缺点等,甚至在A语言中很难的知识点,在B语言中确实最普通的知识点。比如面向对象的运行时和javascript中的运行时。 24 | 25 | 每种技术都有其使用场景和特性,甚至有的特性是其他语言不具备的。特别是微服务的盛行,也决定了一个产品可以根据其不同的业务划分模块,每个模块又根据其场景(高并发、运算密集、模型算法等)来选择不同的技术,比如高并发的可以选择Golang来开发,模型算法可以选择Python, R来开发对应的微服务,即用对的技术来做对的事情。所以这也要求一个程序员需要具备技术的广度,并且有一定的深度。 26 | 27 | 所谓全栈工程师,个人觉得至少在纯技术上要具备前端、后端、移动端app的开发能力,且在某一块有一定的深度。当然如果你具备来运维开发等的能力,那是再牛B不过了。 28 | 29 | 但是在短期内一个程序员既要达到一定的广度,又要达到一定的深度,这是很难或矛盾的。俗话说“十年磨一剑“,虽然现在获取信息的方式更多了,但想在某个领域成为佼佼者,岂能一朝一夕的事情,都是在不断的积累、沉淀才能厚积薄发。 30 | 31 | 至于广度、深度先选择哪个,这需要根据你自己的职业生涯所需来决定。 32 | 33 | 待续。。。 34 | -------------------------------------------------------------------------------- /package-oriented-design/packageorienteddesign.md: -------------------------------------------------------------------------------- 1 | # Go 面向包的设计和架构分层 2 | 3 | ## 序 4 | 5 | 本篇内容主要讲解golang项目的面向包设计准则和基础的架构分层。 6 | 7 | 信息来自原文 8 | 9 | * [Ardan Labs: Package-Oriented-Design](https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html), 10 | * [Github: golang standard project layout](https://github.com/golang-standards/project-layout), 11 | * [Microsoft: Design Fundamentals - Layout Application Guideline](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ee658109(v=pandp.10)) 12 | 13 | 内容进行翻译、加工、整合及结合个人的实践经验,并附有一个真实的例子来解释本篇内容。 14 | 15 | * [group event](https://github.com/danceyoung/paper-code/tree/master/examples/groupevent) 16 | 17 | 当然你也可以直接阅读英文原文。 18 | 19 | 当然高手如云,只是懒得写罢了。 20 | 21 | 百年太久,只争朝夕,不负韶华,不枉少年,来日怎方长。 22 | 23 | ## 前 24 | 25 | 一个基本的go项目一般会有`cmd`, `internal`, `pkg`三个基础目录来分层,当然这不是官方`go`核心开发团队定义的标准。但这个确实是目前`go`生态系统中比较常见的布局形式,不管从之前的和还是现在开发项目的分层来看。这些基础目录同样适用更大的项目,并且还有一些小的增强功能。 26 | 27 | 如果你创建一个项目来学习go或你开发的是一个PoC或很小的项目,这种分层就没必要使用了,可能一个`main.go`文件就够了,即把数据、业务逻辑、规则、路由等等全部放在这个文件即可,也是所谓的**反模式**。但是随着业务不断变化而让你的项目也不断扩大,你就有必要考虑这种分层模式了,否则你会欠下凌乱无序、不易扩张、不可维护的**技术债**。当你有更多的团队成员开发同一个项目,就更需要更多的架构了,这个时候介绍一个常用的基本模式来管理包和库显得那么重要的原因了。如果你有一个开源的项目,并且别人会导入你项目的代码,这时你的项目中有一个私有(通常叫`internal`)的包是非常重要的,这时clone下来你的项目,就仅仅保留自己想要的,删除其他不用的包或代码,比如`internal`文件夹的内容。至于用到那些模式和目录,视你的项目情况而定,比如`vendor`就不是一定有的。 28 | 29 | 之前go项目的三方依赖包的管理最早有`vendor, go dep`等等,但都不是官方的,使用起来也不是尽善尽美,并不能像java项目maven那样的粒度管理三方依赖包。但随着go 1.14正式发布,`go modules`管理三方依赖包的工具也正式发布了。请尽量使用go modules, 除非你有一定不用他的理由。用go modules,你就不用关心GOPATH和非要把你的项目放在go workspace文件夹了。 30 | 31 | 这样的项目架构分层只是一种通用的模式,他不会给go的面向包的设计强加什么东西。面向包设计的理念让开发者在一个 go 项目中确定包的组织和必须要遵守的设计准则。它定义了一个 go 项目应该是什么样的及怎么架构和分层一个 go 项目。它最终的目的是为了提高项目的可读性、代码整洁性和可交流性,便于团队成员沟通。一个很好的大家都理解的架构本身就是一种通用的沟通语言。 32 | 33 | 面向包设计不局限于项目本身的结构,更多为了表达一个实现合理面向包设计的项目结构是多么的重要。下面将介绍一个面向包设计的项目、之前提到过的相关的准则和基础的架构分层。 34 | 35 | ## 项目架构分层 36 | 37 | 每个公司都会有一个工具包的项目和不同业务的应用项目 38 | 39 | ### 工具包项目 40 | 41 | 考虑到工具包作为公司的一个标准类库,所以应该仅有一个。里面的所有包都需要设计为高可移植性。这些包可以在任何一个项目中都能使用,并且提供的都是很实用、具体的但又非常基础的功能。为了达到这样的目标,工具包项目不能有一个包依赖三方的 vendor。因为如果有包依赖三方包,那就得不断的构建编译随着那些三方包的更新。 42 | 43 | 同时也不建议把工具包项目的部分包直接复制到你的应用项目中,因为这样本身增加了你对这些包管理、更新的工作,当然你如果真这样做也没毛病。 44 | 45 | ### 应用项目 46 | 47 | 应用项目是包含了很多需要部署在一起的程序集,包括服务、命令行工具和后台运行的程序。每个项目都对应一个含有其所有源代码的仓库,包括所有依赖的三方包。你需要几个应用项目,视情况以你而定,当然是越少越好。 48 | 49 | 每个应用项目通常包含三个根目录,分别是 `cmd, internal, pkg, vendor`。在 internal 文件里也会包含 `pkg` 目录,但是它和 internal 里其他的包有着不同的设计约束。 50 | 51 | 一个典型的应用项目结构应该是这样的: 52 | 53 | ``` 54 | 55 | paper-code/examples/groupevent 56 | ├── cmd/ 57 | │ └── eventtimer/ 58 | │ └── update/ 59 | │ └── main.go 60 | │ └── eventserver/ 61 | │ └── router/ 62 | │ └── handler/ 63 | │ └── router.go 64 | │ └── tests/ 65 | │ └── main.go 66 | ├── internal/ 67 | │ └── eventserver/ 68 | │ └── biz/ 69 | │ └── event/ 70 | │ └── member/ 71 | │ └── data/ 72 | │ └── service/ 73 | │ └── eventpopdserver/ 74 | │ └── event/ 75 | │ └── member/ 76 | │ └── pkg/ 77 | │ └── cfg/ 78 | │ └── db/ 79 | │ └── log/ 80 | └── vendor/ 81 | │ ├── github.com/ 82 | │ │ ├── ardanlabs/ 83 | │ │ ├── golang/ 84 | │ │ ├── prometheus/ 85 | │ └── golang.org/ 86 | ├── go.mod 87 | ├── go.sum 88 | ``` 89 | 90 | #### cmd/ 91 | 92 | 项目中的所有你将要编译成可执行程序的入口代码都放在`cmd/` 文件夹里,这些代码和业务没有关系。每个程序对应一个文件夹,文件夹的名称应该以程序的名称命名。一般在名称后面加上`d` 代表该程序是一个守护进程运行。 93 | 每个文件夹必须有一个`main`包的源文件,该源文件的名称也最好命名成可执行程序的名称,当然也可以保留main文件名。在此会导入和调用`internal/`和`pkg/`等其他文件夹中相关的代码。 94 | 95 | 示例 96 | 97 | ``` 98 | ├── cmd/ 99 | │ └── eventtimer/ 100 | │ └── update/ 101 | │ └── main.go 102 | │ └── eventserver/ 103 | │ └── router/ 104 | │ └── handler/ 105 | │ └── router.go 106 | │ └── tests/ 107 | │ └── main.go 108 | ``` 109 | 110 | * 该项目包含线上业务服务eventserver(提供restful API)、定时器eventtimer(定时更新数据的状态)二个应用程序。`cmd`文件夹对应有2个文件夹,并且每个文件夹下面都有一个`main`包的源文件,至于名称可以直接用main,也可以对应文件夹的名称。 111 | * 每个文件夹下的源文件里的代码和业务逻辑基本没任何关系。比如rest ful的eventserver,里面仅包含router的配置和相关的handler。 112 | 113 | #### internal/ 114 | 115 | 在go语言中,变量,函数,方法等的存取权限只有exported(全局)和unexported(包可见,局部)2种。 116 | 117 | 在项目中不被复用,也不能被其他项目导入,仅被本项目内部使用的代码包即私有的代码包都应该放在`internal`文件夹下。该文件夹下的所有包及相应文件都有一个项目保护级别,即其他项目是不能导入这些包的,仅仅是该项目内部使用。 118 | 119 | 如果你在其他项目中导入另一个项目的`internal`的代码包,保存或`go build` 编译时会报错`use of internal package ... not allowed`,该特性是在go 1.4版本开始支持的,编译时强行校验。 120 | 121 | ``` 122 | 1 package main 123 | 2 124 | 3 import ( 125 | 4 "paper-code/examples/groupevent/cmd/eventserver/router/handler" 126 | 5 "paper-code/examples/groupevent/cmd/internal" 127 | 6 "paper-code/examples/groupevent/internal/eventpopdserver/event" 128 | 7 "paper-code/examples/groupevent/pkg/middleware" 129 | 8 ) 130 | 9 131 | 10 func main() { 132 | 11 middleware.HandlerConv(nil) 133 | 12 134 | 13 event.EventsBy("") 135 | 14 136 | 15 eh := new(handler.EventHandler) 137 | 16 eh.Events(nil, nil) 138 | 17 139 | 18 internal.CmdInternalFunc() 140 | 19 } 141 | 142 | ``` 143 | 144 | > 此代码片段为另一个项目导入paper-code/example/groupevent的代码包 145 | 146 | > 第6行的导入就会提示`use of internal package paper-code/examples/groupevent/internal/eventpopdserver/event not allowed` 147 | 148 | > 第5行的导入也会提示同样的错误 149 | 150 | > 第7行导入就可以的,因为导入的pkg代码包 151 | 152 | 当然你也不要局限根目录下的`internal`目录,你也可以在任何一个目录中创建`internal`,规则都适用。比如上面的例子`第5行的导入也会提示同样的错误:use of internal package paper-code/examples/groupevent/cmd/internal not allowed` 153 | 154 | 另外在同一个项目中,`internal`包的导入规则是:`.../a/b/c/internal/d/e/f` 仅仅可以被`.../a/b/c`下的目录导入,`.../a/b/g`则不允许。 155 | 156 | 你可以在`internal`文件夹添加其他的架构分层目录来区分可分享、不可分享的代码,比如`internal/myapp`是你项目中某个程序的不可分享的代码;`internal/pkg/`是你项目中的程序都可以分享的代码。也可以添加数据层、业务逻辑层的代码,这个属于在项目中更通用的一个架构分层,和这里的包设计并不冲突,即上层模块可以直接访问下层模块,反之不然。 157 | 158 | #### internal/pkg/ 159 | 160 | 在同一个项目中不同程序需要访问,但又不能让其他项目访问的代码包,需要放在这里。这些包是比较基础但又提供了很特殊的功能,比如数据库、日志、用户验证等功能。 161 | 162 | #### pkg/ 163 | 164 | 如果你把代码包放在根目录的`pkg`下,其他项目是可以直接导入`pkg`下的代码包的,即这里的代码包是开放的,当然你的项目本身也可以直接访问的。但是如果你要把代码放在`pkg`下,还想需要三思而后行吧,有没必要这样做,毕竟`internal`目录是最好的方式保护你的代码并且被go编译器强制校验`internal`的代码包不可分享的。如果你的项目是一个开源的并且让其他人使用你封装的一些函数等,这样做是合适的,如果你自己或公司的某一个项目,个人的经验,基本上用不上`pkg` 165 | 166 | #### vendor/ 167 | 168 | vendor文件夹包含了所有依赖的三方的源代码,它是go项目最早的依赖包的管理方式。目前大都用的go mod的依赖包管理,相对vendor,能指定版本,并且你不用特意手动下载更新依赖包,通过正常的go build, go run命令会自动处理。这样会减少项目本身的容量大小。 169 | 170 | 你可以用命令 `go mod vendor`来创建你项目的vendor目录。如果你项目中既要用到之前的vendor,又要用到go mod,你可以使用 `-mod=vendor`参数进行编译,但是在go1.14就不用了,当你用go build时,会自动检查项目根目录下有无vendor,并进行编译。 171 | 172 | 这里不过多介绍go mod的用法和特性。 173 | 174 | ## 面向包的设计和验证 175 | 176 | 面向包设计的准则可以验证项目中包设计的是否合理,下面这些步骤可以帮你发现包设计的问题。 177 | 178 | ### 包的位置 179 | 180 | * `kit` 181 | 被不同应用项目导入的基础包 182 | * `cmd` 183 | 支持编译不同二进制程序的包,比如Restful路由程序,需要相关router, handler包和main入口包。 184 | * `internal` 185 | 项目内部使用的包,包括crud, service(facade)和业务逻辑的包。 186 | * `internal/pkg` 187 | 为本项目内部使用的基础包,包括数据库、认证和序列化等操作的包。 188 | * `pkg` 其他项目可以访问pkg的代码包 189 | 190 | ### 依赖包导入 191 | 192 | * 根据业务合理设计包的粒度。 193 | * 在一个包中导入另一个包中的类型,是不合适的。 194 | go源码里面的网络方面的`Request, Response, Header`等都在`http`包下面 195 | 196 | go的设计本身不建议建一个model模块,里面全是一个个结构体。因为这样设计,让其他人看代码,可能不知道这些结构体在哪被使用,修改了结构体,也不知道影响面有多大。 197 | * 在同一个目录级别下的包互相导入,是不合适的。 198 | 199 | go更多是按照功能职责进行包的设计,所以同一目录级别下的包是不能互相导入的。除非你采用了在其他语言的架构分层是可以导入的,但也仅限上层可以导入下层的代码包,比如服务层、展现层、业务逻辑层、数据持久化层。 200 | 201 | ``` 202 | ├── internal/ 203 | │ └── eventserver/ 204 | │ └── biz/ 205 | │ └── event/ 206 | │ └── member/ 207 | │ └── data/ 208 | │ └── service/ 209 | │ └── eventpopdserver/ 210 | │ └── event/ 211 | │ └── member/ 212 | ``` 213 | 214 | 215 | > eventserver下的biz, data就是按照业务逻辑层、数据层这样的架构分层进行的设计。这样biz里面的代码包就可以导入同一目录级别下的data下的代码包,反之不然。 216 | > 217 | 218 | > eventpopdserver下的event, member是按照功能职责进行的设计,2者不能互相导入。 219 | > 220 | 221 | > 架构大致上分2种,一个就是通用分层(presenter, service, business, data ...)的架构分层,另一种就是按照功能职责进行分层,go倾向于后者。 222 | > 223 | * 如果真有上面的需求 224 | 225 | 请检查你对领域知识的理解、领域模型设计和包的设计。 226 | 227 | 如果情非得已,那么将被导入的包移动到你的包里面。 228 | * `cmd/`可以导入其他目录中的代码包。 229 | * `internal/`中的包不能导入`cmd/`中的包。 230 | * `internal/pkg/`中的包不能导入`cmd/`, `internal/`中的包。 231 | * `pkg/`中的包不能导入`cmd/`, `internal/`中的包。 232 | 233 | ### 应用级别的策略 234 | 235 | 比如给restful api的handler写中间件、定时更新等策略。 236 | 237 | 在`Kit`, `internal/pkg/, pkg/`中是不允许写这些策略的,也不允许日志的打印,因为这些都是某种意义上共用通用的代码包。在这里数据库的配置、日志文件的配置应该和运行时环境的改变是松散耦合的,可以通过环境变量来修改配置。 238 | 239 | 在`cmd/`, `internal/`是可以写中间件和定时器等。 240 | 241 | ### 数据的发送和接收 242 | 243 | * 在语意上要确定好一个类型发送和接受的方式,即值类型还是引用类型。 244 | 比如golang的`http`包中的`Request`结构体,在http中是以引用类型使用的。可以查看`http`包下面的`server`源码,里面包含了各种用法,如果你想自己写路由,server的几个函数和类型是必须要用的,这里不过多介绍。 245 | * 如果你用一个接口类型的变量接收一个返回值,则更多的目的应该是调用接口的方法即行为,而不是值本身。如果不是这样,请直接用具体的类型。 246 | 247 | ### 错误处理 248 | 249 | 错误处理包括错误信息的日志输出,分析和解决错误,并且保证程序能恢复如果发生了错误。 250 | 251 | * `Kit` 252 |

不允许使用`panic`终止程序或抛出错误。
不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。

253 | * `cmd/` 254 |

允许使用`panic`终止程序或抛出错误。
如果有错误发生且不处理,可以根据此时的业务或逻辑上下文包装一下错误,让更上层的处理错误的函数能知道是哪里抛出的错误。
当然大多数的错误都应该在这里处理。

255 | * `internal/` 256 |

不允许使用`panic`终止程序或抛出错误。
如果有错误发生且不处理,可以根据此时的业务或逻辑上下文包装一下错误,让更上层的处理错误的函数能知道是哪里抛出的错误。
当然大多数的错误都应该在这里处理。

257 | * `internal/pkg/` 258 |

不允许使用`panic`终止程序或抛出错误。
259 | 不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。
260 | * `pkg/`不允许使用`panic`终止程序或抛出错误。
不允许再次包装错误信息,原本原样的把系统错误或框架的错误返回即可。
261 | 262 | ### 测试 263 | 264 | * `cmd/` 265 | 266 | 允许使用第三方的测试包。
267 | 可以独立创建一个test包来管理单元测试的文件。
268 | 这里更多是集成测试而不是单元测试。 269 | * `kit/`, `internal/`, `internal/pkg/,pkg/` 270 | 271 | 强烈推荐使用golang的testing包。
272 | test文件可以直接创建在对应包下面。
273 | 这里更多是单元测试而不是集成测试。 274 | 275 | ### 捕获错误 276 | 277 | * `cmd/` 278 | 279 | 可以捕获任何错误,且保证程序100%能恢复。 280 | * `kit/, internal/, internal/pkg/,pkg/` 281 | 282 | 不能捕获错误,除非发生错误时,有对应的线程可以处理,或通知到程序。 283 | 284 | ## 不建议的目录 285 | 286 | * `src/` 287 | 288 | src目录在java开发语言的项目中是一个常用的模式,但是在go开发项目中,尽量不要使用src目录。 289 | * `model/` 290 | 291 | 在其他语言开发中一个非常通用的模块叫model,把所有类型都放在model里。但是在go里不建议的,因为go的包设计是根据功能职责划分的。比如一个User 模型,应该声明在他被用的功能模块里。 292 | * `xxs/` 293 | 294 | 带复数的目录或包。虽然go源码中有strings包,但更多都是用单数形式。 295 | 296 | ## 结论 297 | 298 | 在实际go项目开发中,一定要灵活运用,当然也可以完全不按照这样架构分层、包设计的规则,一切以项目的大小、业务的复杂度、个人专业技能认知的广度和深度、时间的紧迫度为准。 299 | 300 | 最后以软件大师 [Kent Beck](https://baike.baidu.com/item/Kent%20Beck/13006051?fr=aladdin) 在《重构Refactoring》一书中描述的结尾。 301 | 302 | * 先让代码工作起来-如果代码不能工作,就不能产生价值 303 | * 然后再试图将它变好-通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。 304 | * 最后再试着让它运行得更快-按照性能提升的需求来重构代码。 305 | 306 | 谢谢 307 | 308 | 最后推荐一个工具,几秒内快速创建一个符合本文论述的一个go项目 [goslayer](https://github.com/danceyoung/goslayer) 309 | -------------------------------------------------------------------------------- /package-style-guideline/packagestyleguideline.md: -------------------------------------------------------------------------------- 1 | # Go 包的组织和命名 2 | 3 | > https://rakyll.org/style-packages/ 4 | > 5 | > https://blog.golang.org/package-names 6 | > 7 | > 当看了很多不同地方的英文blog,发现知识的思想、原理还是来自官方的英文文档,就如中文搜索出来的知识,最终发现都是来自某篇文章,而某篇文章又是来自官方的内容,说这话的目的,是希望开发者要提高英文水平,不断研读官方的英文文献和不断实践。本篇文章特别对包的命名、组织,基于官方文档进行整理。 8 | 9 | 10 | 11 | Go和其他语言一样,也涉及到命名和代码的组织。组织良好的代码具有很强的交流性、可读性和易用性,和设计良好的API一样重要。当其他用户阅读你代码的时候,包的位置、命名和结构分层的设计是他首先接触的东西。 12 | 13 | 本文通过比较通用且很好的实践例子来为你介绍Go包的规范,包括包的组织和包的命名,并不是一定要遵守的规则。实际中你要根据自己的需要来挑选最适合你项目的解决方案。 14 | 15 | ## 包的组织 16 | 17 | 所有的go代码都被组织在包中。一个包其实就是包含很多`.go`文件的一个路径,这样就实现了代码的组织和互相隔离,阅读代码从一个包开始。要想写好go代码,要不断理解包和基于包多实践。 18 | 19 | ### 使用多个文件 20 | 21 | 你要根据业务功能逻辑,充分地把代码分在不同的文件中,然后再根据整体的上下文放在同一个包中,这样才有更好的可读性。 22 | 23 | 比如,`http`包就是根据http的不同功能分成不同的对应文件,就像下面所示的四个文件一样。 24 | 25 | ``` 26 | - doc.go // package documentation 27 | - headers.go // HTTP headers types and code 28 | - cookies.go // HTTP cookies types and code 29 | - http.go // HTTP client implementation, request and response types, etc. 30 | ``` 31 | 32 | ### 类“以类聚“ 33 | 34 | 一个小的规范,尽量把类型声明在被使用的文件中,这样就会让其他人根据功能更容易的找到对应的类型,而不是放在一个叫`model`的包中。 35 | 36 | 比如`header`的结构体应该声明在`header.go`文件中。 37 | 38 | ``` 39 | package http 40 | 41 | // Header represents an HTTP header. 42 | type Header struct {...} 43 | ``` 44 | 45 | 另外注意到`Header`的结构体在`header.go`文件的最上面,golang本身并没有强性要求这样做,但把基础的核心的类型放在对应文件的最上面,是一个很好的做法。 46 | 47 | ### 按照功能职责设计 48 | 49 | 在golang中是按照功能职责设计的,即每个包都要有一定的真实业务含义。 50 | 51 | 但在其他语言中会把类型统一放在`model`包中,甚至只有类型的定义,这样我们很难找到某个类型是在哪里、哪些文件中被使用,且一旦修改了某个类型,我们不知道影响的范围。 52 | 53 | 所以在golang中我们不会如下这样设计的 54 | 55 | ``` 56 | package models // DON'T DO IT!!! 57 | 58 | // User represents a user in the system. 59 | type User struct {...} 60 | ``` 61 | 62 | 在该示例中,我们应该把`User`声明在他被使用的包中,像下面这样 63 | 64 | ``` 65 | package mngtservice 66 | 67 | // User represents a user in the system. 68 | type User struct {...} 69 | 70 | func UsersByQuery(ctx context.Context, q *Query) ([]*User, *Iterator, error) 71 | 72 | func UserIDByEmail(ctx context.Context, email string) (int64, error) 73 | ``` 74 | 75 | ### 提供例子来解释不清晰的引用包 76 | 77 | 有些情况下,你并不能在一个独立的包中提供所有要使用的类型。比如你想在一个包中实现一个接口,但是这个接口所在的包和你这个包不是同一个包,或者有些类型是在第三方包中,这样就会很不清晰。此时就需要提供例子来说明这些包是怎么一起用的。例子会提高那些不太容易被发现包的透明度。 78 | 79 | ### 不要在main包中导出任何标识符 80 | 81 | 一个标识符可以被导出(首字母大写),以允许其他包应用他。 82 | 83 | 但是`main`包不允许其他包导入,所以在`main`包导出标识符,没有任何意义。也有些例外,就是`main`包将要被构建入a .so, or a .a or Go plugin. 84 | 85 | ## 包的命名 86 | 87 | 一个好的包名称,会有很好的可读性,要见文识义,就是使用者能从包名里看出包的代码逻辑和使用意图。因为包的名称表达了包内容的上下文语意,让使用者很容易理解这个包是干什么的及怎么用的。对代码的维护者来说,也更易维护。 88 | 89 | ### 包应该被命名什么样 90 | 91 | 包的名称应该要小写,不要用下划线your_package或驼峰yourPackage的命名样式。且应该是一个简短的名词,比如 time,list,http 92 | 93 | 所以在其他编程语言中的命名规范可能就不适合Go了,比如 computeServiceClient,priority_queue这样的 94 | 95 | 如果一个单词无法表达包的含义,可以用多个单词,但需要简写每个单词,且简写后的名称对编程人员来说,都是耳闻能详的,如果不能,就不要这样做了。比如 strconv(string conversion),syscall(system call),fmt(formatted i/o) 96 | 97 | 同时也要尽量避免这样的单词,即编程人员经常用到的单词,比如编程人员声明一个Buffer类型的变量buf。 98 | 99 | 不必担忧包名和其他源码库冲突,唯一性不是必须的,包名仅仅是导入后默认使用的名称。虽然不太容易发生引入不同地方的同一个名称的包名,但如果真发生了冲突,你可以在导入时重命名一下包名,比如下面这样。 100 | 101 | ```bash 102 | import ( 103 | "interview/log" //自己项目中的log包 104 | golog "log" //为了避免冲突,可以把标准log包起个别名golog 105 | ) 106 | ``` 107 | 108 | ### 包内容的命名 109 | 110 | 包的名字和包里面内容的名字是耦合的(包括类型、变量、常量、方法、函数等),是被使用者放在一起使用的,所以当你设计包的时候,尽量站在使用者的角度。 111 | 112 | #### 避免重复或不清晰 113 | 114 | 使用者使用某个类型、函数等时,是把包名作为前缀的,比如 http.Server。这里http是包名,Server是类型名,所以没必要把Server命名成HTTPServer。直接使用http.Server更清晰,也不重复的表达。 115 | 116 | #### 函数名 117 | 118 | 通常,如果在包pkg里有一个函数的返回值是pkg.Pkg(*pkg.Pkg),那么这个函数的名称不应该含有Pkg字样,比如标准包的 119 | 120 | ```bash 121 | start := time.Now() // start is a time.Time 122 | t, err := time.Parse(time.Kitchen, "6:06PM") // t is a time.Time 123 | ctx = context.WithTimeout(ctx, 10*time.Millisecond) // ctx is a context.Context 124 | ip, ok := userip.FromContext(ctx) // ip is a net.IP 125 | ``` 126 | 127 | 包time的函数Now,Parse返回Time类型的值,就不应该写成NowTime, ParseTime,可以比较一下time.Now() 和time.NowTime(),很明显time.Now()更直接、简练。 128 | 129 | 如果包pkg有一个函数New返回pkg.Pkg,这个函数就是很好很标准的函数名字。 130 | 131 | ```bash 132 | q := list.New() // q is a *list.List 133 | ``` 134 | 135 | 如果包pkg有一个函数返回的是类型T,而不是Pkg,这个函数名应该包含类型T的字样,这样对使用者来说更清晰明了。 136 | 137 | ```bash 138 | d, err := time.ParseDuration("10s") // d is a time.Duration 139 | elapsed := time.Since(start) // elapsed is a time.Duration 140 | ticker := time.NewTicker(d) // ticker is a *time.Ticker 141 | timer := time.NewTimer(d) // timer is a *time.Timer 142 | ``` 143 | 144 | 不要担忧在不同的包里起了相同的类型名称,因为在使用某个类型的时候是需要包名作为前缀的,这样是不太会引起混淆或歧义的,但是也有尽量的避免。就像标准包的不同包里都有Reader类型的,jpeg.Reader, bufio.Reader, csv.Reader. 145 | 146 | 如果你起不好一个名称,可能你的逻辑边界是不清晰不准确的,你需要重新梳理你的业务需求、整个架构或代码的组织,直到让使用者或代码维护者能更容易理解和维护。 147 | 148 | ### 不好的包命名及解决方案 149 | 150 | 不好的命名让使用者很难理解这个包是什么,该怎么使用,同时也让代码维护者难于维护。 151 | 152 | 避免一个泛泛的名称,比如`common`,`utils`. 153 | 154 | ``` 155 | package util 156 | func NewStringSet(...string) map[string]bool {...} 157 | func SortStringSet(map[string]bool) []string {...} 158 | ``` 159 | 160 | 使用者使用的时候,是这样的 161 | 162 | ```bash 163 | set := util.NewStringSet("c", "a", "b") 164 | fmt.Println(util.SortStringSet(set)) 165 | ``` 166 | 167 | 我们应该把这2个函数从util里分离出来单独成一个包,比如strset,这样更能表达这2个函数的意思。 168 | 169 | ```bash 170 | package strset 171 | func New(...string) map[string]bool {...} 172 | func Sort(map[string]bool) []string {...} 173 | ``` 174 | 175 | 此时使用者使用的时候,是这样的 176 | 177 | ```bash 178 | set := stringset.New("c", "a", "b") 179 | fmt.Println(stringset.Sort(set)) 180 | ``` 181 | 182 | 是不是很清晰了,然后还可以进一步提高,让代码看起来更简短 183 | 184 | ```bash 185 | package strset 186 | type Set map[string]bool 187 | func New(...string) Set {...} 188 | func (s Set) Sort() []string {...} 189 | ``` 190 | 191 | 使用者也更简单了 192 | 193 | ```bash 194 | set := stringset.New("c", "a", "b") 195 | fmt.Println(set.Sort()) 196 | ``` 197 | 198 | **不要把千万逻辑代码归一个包**。 有许多其他编程思想(或有惯性思维)的程序员会把一些逻辑上没相关,但属于某个架构层次的代码放在一块,比如都放在model,interface,api这样的包中。这样一个仅有一个的好处就是你能很快的找到代码的入口,但是这样的写法和上面util包没什么区别,随着代码量的增大,业务逻辑或代码逻辑的边界会越来越混乱,使用者也更难使用。此时需要按照业务的功能职责或纯技术的职责把那些包里的文件分离开来。 199 | 200 | **避免复数的形式。** 在golang中,尽量避免以复数的形式命名,虽然go内置的包也有复数形式strings, errors。这样的规范会让其他开发语言的程序员感到很奇怪,但这就是go的设计哲学。比如不能命名为`httputils`,而要命名为`httputil` 201 | 202 | ``` 203 | package httputils // DON'T DO IT, USE SINGULAR FORM!! 204 | ``` 205 | 206 | ## 总结 207 | 208 | 在Go中,命名一个好的名称是很重要的一件事,希望开发者花时间来理解业务逻辑,组织代码结果,命名一个简短,简练,见文识义的名称。这样对使用者或代码维护者都是有好处的。 209 | -------------------------------------------------------------------------------- /wechatsubscriberqr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danceyoung/paper-code/a1c7f5c110be820c02a88314b66f82420cfa775f/wechatsubscriberqr.png --------------------------------------------------------------------------------