后端开发规范

2016-03-15 fishedee 后端

1 概述后端开发规范

2 问题在整整一年的php开发过程中,我们发现了以下问题:

  1. php的语法过于松散,容易写出难以理解而且不规范代码(同一个函数的返回类型可能是多个的)2. php遇上重IO任务时容易导致全站卡顿(七牛挂机,刷点赞数据中的等待,爬虫与同步操作)3. php的动态类型特性在运行时容易出现莫名奇妙的问题(controller校验不仔细,导致错误直达到model层)新框架是该换为go的beego框架来开发,针对性解决问题
  2. 严格的缩进进制和括号标准,不规范的代码不能通过编译,提升代码可读性2. go是强类型的语法,大部分问题在编译时已经被编译器发现,提升代码健壮性3. go是轻量级线程语法,为web开发而生,执行IO任务时会自动让出CPU控制权,从而提升代码性能。站在巨人的肩膀上,我们的新框架是在beego插件上组合而成的。 # 3 组件 ## 3.1 路由 Screen Shot 2016-05-19 at 1.47.46 P 固定一个控制器写一个InitRoute,路由会自动将控制器的方法映射出去的。 ## 3.2 控制器Screen Shot 2016-05-19 at 2.12.53 P 控制器的写法略为蛋疼,名字后面加上view名字来定义一个controller的方法 ### 3.2.1 命名Controrller的类均是以Controller结尾的单词,例如ClientController,ClassifyController等等### 3.2.2 声明所有的Controller均应继承自web.Controller,或者是web.Controller的子类,才能正常使用到我们的自动路由功能。 ### 3.2.3 方法web.Controller会新增三个常用的方法,分别是
  • CheckGet(校验Get参数)* CheckPost(校验Post参数)* AutoRender(将controller输出自动引导到view层输出)## 3.3 模型

Screen Shot 2016-05-19 at 2.13.42 P 模型的写法跟原来的php写法差不多 ### 3.3.1 命名Model的类均是以Model结尾的单词,例如ClientAoModel,ClassifyAoModel等等。 ### 3.3.2 声明所有的Model均应继承自web.Model,或者是web.Model的子类,才能正常使用到后面的工具库 ### 3.3.3 方法无

3.4 测试

Screen Shot 2016-05-19 at 2.16.28 P

测试是一个全新的模块,主要承载着model层的单元测试,数据测试与性能测试的职责。

3.4.1 命名

Test的类均是以Test结尾的单词,例如ClientAoTest,ClassifyAoTest等等。

3.4.2 声明

所有的Test均应继承自web.Test,或者是web.Test的子类,才能正常使用到后面的工具库

3.4.3 方法

web.Test会新增三个常用的方法,分别是

  • AssertEqual(校验数据相等)
  • AssertError(校验错误相等)
  • Concurrent(模拟并发行为)
  • Benchmark(性能测试)
  • RandomInt(生成随机数)
  • RandomString(生成随机字符串)
  • RequestReset(重置请求) ## 3.5 数据 数据是新增的写法,是强类型的go所特有的东西。 ### 3.5.1 命名Data的类均是以Data结尾的单词,例如ClientData,ClientAddressData等等。而且,同一个包下的data都应该放在同一个文件中 ### 3.5.2 声明无 ### 3.5.3 方法无 ## 3.6 枚举Screen Shot 2016-05-19 at 2.22.47 P 枚举跟php类型类似,不过enum是个实例,而不是类型,这是要特别注意区分的。 ### 3.6.1 命名Enum的类均是以Enum结尾的单词,例如ClientTypeEnum,ClientStateEnum等等。而且,同一个包下的enum都应该放在同一个文件中 ### 3.6.2 声明var xxxx struct,这是go中的特别写法,声明与定义struct写在一起了。刚声明出来的实例是没有数据的,需要用我们的特别的方法InitEnumStruct来初始化enum结构体。另外,所有的enum结构体必须继承自EnumStruct ### 3.6.3 方法
  • Names(获取enum内的常量map)
  • Datas(获取enum内的数据列表)

4 工具库

凡是继承Web.Controller,web.Model与web.Test的实例都会获得工具库的功能,而工具库的配置则在conf/app.conf文件下,具体的用法就不说了

4.1 数据库

this.DB

4.2 登录态

this.Session

4.3 缓存

this.Cache

4.4 日志

this.Log

4.5 监控

this.Monitor

4.6 定时器

this.Timer

4.7 队列

this.Queue # 5 逻辑 ## 5.1 错误与异常错误与异常在go中是严格区分的,错误就是业务错误,是由用户触发的错误,异常就是程序错误,是由不可预料的问题触发的。例如,数据库崩溃,七牛崩溃,微信服务器崩溃,这些都是不可抗力现象,不是由用户触发的错误,应该定义为异常。而,输入的不是整数,权限不足,这些都是用户触发的现象,应该定义为错误。在go中,错误为error,异常为panic。不过由于error是手动传递的,写法比较蛋疼,所以框架中引入了throw的写法,用异常逻辑来写error,同时也会区分panic与error。### 5.1.1 写法 数据库错了就是panic,取出数据不存在为Throw ### 5.1.2 库函数特别注意,gopath里面的库函数不允许panic与throw,因为作为一个库,在不确定具体业务的情况下,是无法定义错误是业务错误,还是异常错误的。所以的库函数应该只返回error。就像xorm中只返回error,而不panic一样。例如,写一个http库,定义一个post操作,用来封装上传表单数据的。在上传七牛图片时,用这个post方法时,如果失败了应该是panic在上传地址数据到金象系统时,用这个post方法时,如果失败了应该是throw,(因为同步地址数据到金象系统上只是一个旁路逻辑)因为,同一个http库,在不同的业务中是不清楚究竟是panic还是throw的,所以库只能保守地返回error,由调用方来确定是throw还是panic ### 5.1.3 谁是异常一般来说,只有如下情况时需要报出panic异常的,其他情况下都应该报出throw异常。Panic:数据库异常,redis异常,七牛异常,opensearch异常,手机验证码异常。Throw:第三方业务系统(金象系统),以及其他模块。 ## 5.2 并发业务逻辑中禁止使用全局变量,除非该变量一开始就被初始化了,例如enum。Go的多个goroutine之间时并发关系,一个不小心就有数据冲突,会造成很严重data race问题,而且难于排查。同理,业务逻辑中禁止使用多线程。那么,既然没有全局变量与多线程,业务逻辑中也没有必要使用锁与channel

当然,库函数没有这个限制,相应的,库函数由于使用了并发导致data race问题由库的作者负责。 ## 5.3 数组由于数组是定长的,slice是不定长的。定长的数组会让暴露业务逻辑,所以禁止使用数组,而只能用slice。例如金像系统中是6块积木拼接成1个蛋糕,如果积木写死为[6]int,那么当有一天金像改为9块积木,或者变数的积木时,接口就得需要变化,因为积木的数据类型已经改变了。但是,如果积木一开始就是用[]int,就没有这个问题。写死的数组长度会暴露业务逻辑的实现。 ## 5.4 无副作用编写逻辑时,无副作用是一个很重要的原则,所有函数必须按值传递,而且永远不能修改输入参数! 例如,上面UserLoginModel的Get函数中,调用方只是想获取一下getImageCount的数值,而且输入参数也是按值传递,可是没想到,调用了以后连自己的Image数组里面的数据都改变了。这跟GetImageCount接口的承诺相违背的,它只是计算Count,按值传递,从来没有提示过调用方是会修改输入参数的。 解决方法,很简单,GetImageCount里面不要去修改输入参数就可以了。这样调用方调用GetImageCount接口时结果是可预测的,而不是无法理解的。## 5.5 部署现网均需要用supervisor来启动,崩溃后自动重启,避免编程失误导致全站挂机。禁止fishcmd run –watch的方式启动,崩溃后无法重启,而且启动有严重延迟。

6 命名> 好的代码自己会说话

不记得是在哪里听过的话,说得真是有道理。比起满篇代码注释,还不如在命名上多花点功夫,让代码自己告诉你有什么用。我们有一套约定成俗的命名规范,例如。

问题模块QuestionAo

增删改查问题

功能 名字 参数
搜索问题 Search (where,limit)
添加问题 Add (questionData)
删除问题 Del (questionId)
修改问题 Mod (questionId,questionData)
获取问题 Get (questionId)
批量获取问题 GetBatch (questionIds)

增删改查问题下的答案信息

功能 名字 参数
搜索答案 SearchAnswer (where,limit)
添加答案 AddAnswer (data)
删除答案 DelAnswer (answerId)
修改答案 ModAnswer (answerId,answerData)
获取答案 GetAnswer (answerId)
批量获取答案 GetBatchAnswer (answerIds)

部分情况下,我们需要用非主键获取或修改信息的

功能 名字 参数
获取单个用户的问题 GetByClientId (clientId)
获取多个用户的问题 GetByClientIds (clientIds)
获取单个问题下的答案 GetAnswerByQuestionId (questionId,limit)
获取多个问题下的答案 GetAnswerByQuestionIds (questionIds,limit)
获取单个问题下的答案数 GetAnswerNumByQuestionId (questionId,limit)
获取多个问题下的答案数 GetAnswerNumByQuestionIds (questionIds,limit)
删除单个问题下的所有答案 DelAnswerByQuestionId (questionId)

尽可能将修改变为一个动作,而不是直接的setState

功能 名字 参数
支持答案 LikeAnswer (answerId,clientId)
反对答案 HateAnswer (answerId,clientId)
评论答案 CommentAnswer (answerId,clientId,text)

相关文章