大学期间都写过xx管理系统、xx小程序,当时非常随心所欲,反正不用真正用上,怎么开心怎么来。闲聊下学生项目和工作项目中考虑的不同点,顺带缅怀下我过去不久的学生时代。 PS:文章也是粗略地讲了一些个人认为要注意的点,可能不正确,可能正确。记得带着你的思考来看就好。
特征对比
学生项目
用户量少(无用户量)、独立开发、随便瞎搞。
工作项目
用户量大、多人协作、讲究可用性。
项目开发流程
学生项目
哪有什么规范流程,通常就是拍脑袋——“这个点不错,可以做。”,然后一个策划、一个前端、一个后台,撸起袖子就是干。举个最像模像样的例子,爱宠App,这是我从大二下做到大三上的一个比赛项目。当时这个项目包括了2个前端、2个后台、2个策划,整个开发流程也有点“敏捷迭代”的味道。
具体开发流程为,每两周有一次评审会,会议内容包括:总结上个迭代的工作、总结迭代中存在的问题、得出下一个迭代的工作。
这个步骤往往在学校食堂的鸟笼中进行,想起来还让人有点怀念。最精彩的要数总结问题的阶段,堪称“进度严重拖后但一定不是我的问题”大型甩锅现场。前端说策划图给得慢、图标、色号不给定,后台协议天天变;后台说需求天天变,而且不严谨,他也没办法;策划说我又画图又想需求又写文档,我尽量加油。
由此可见,最惨的还是策划(狗头保命)。
工作项目
工作后的流程就非常规范了。大致如下:
产品定方案、写需求文档 -> 产品、开发过需求 -> 确定可行,开始开发 -> 开发完成,测试、验收 -> 上线
可能还有一些前置方案(市场调研、和老板过方案等)等,跟开发相关的流程大致如此。然而,唯一不变的就是变化。需求,还是说变就变。
下面进入正题。从后台开发的角度,来简聊聊对比以前瞎搞的项目,工作中的项目都考虑了什么。
存储设计实现
学生项目
一般都基于MySQL存储。开发原则:单机扛流量、索引随便建、数据随便写、事务随便用。
基本上就是这样,CRUD没在怕的。毕竟10qps都可能达不到。
工作项目
这里只简略聊下几个存储设计需要考虑的点:
- 分表规则。
若数据量不大,可以忽略。但数据量大了,就要考虑分表。分表的关键是数据分布均匀,同时要考虑后续数据膨胀的情况。例如,用户订单表一般可以以用户id作为分表规则,根据业务量大小分成n张表存储。
- 索引。
先说普通索引。普通索引考虑不当,可能会有慢sql。慢sql的危害很大,会导致db连接异常,严重的可能拖垮整个db。因此,查询都要考虑走索引,且要为合适的索引。
再说唯一索引,唯一索引可以从存储层面保证数据正确性,作为兜底策略存在。这样即使逻辑层重复调用,也能保证数据唯一性。
- 并发控制。
对于分布式存储系统来说,需要考虑并发时的数据一致性。如果并发度不高,可以考虑乐观锁的实现方式。类似于MySQL的MVCC,给每行数据加个版本号,更新的时候带上获取记录的版本号,如果不一致则说明数据被修改过了,更新失败。如果并发度高,则要考虑别的实现。
- 冗余数据。
以前太天真,对冗余数据不屑一顾。可以查出来的,干嘛要冗余?实际上,获取某个数据可能要经过多次rpc,成本远远大于冗余存储。因此,我们需要比较冗余存储和查询成本,得出适合的方案。
- 统计字段。
我们的系统,能简单,就简单,尽量降低复杂度。因此,尽量避免统计类字段,有统计的地方,就会有并发控制的要求。非实时展示的数据,不要直接在数据中累计,可以通过后续使用计算任务来获取。
除了列举的,还有很多需要注意的细节。只有自己踩过坑才知道。这一条条的,都是泪。
另外,感兴趣的同学可以学习下领域驱动设计(DDD),因为我刚接触,是个渣渣,因而不在此误人子弟。如果想要菜鸟互啄(或者你想啄我),欢迎找我。
业务逻辑实现
学生项目
这又得提出我的名作(呸),爱宠App。当时年少轻狂,写出来的代码让人见了不禁高声直呼,“好一坨shi”。
例如:写n个自定义异常,命名开心就好,随心所欲地抛;参数命名随意,a、b、c、d轮完还有i、j、k、f;重复代码一大堆,完美坐实ctrl+c,ctrl+v工程师名称等等。代码杀伤力太强,我已经不敢打开来看了。
工作项目
虽然我的代码写得还是不怎么样,但相比学生时代,也是坨成熟的shi了,不至于让人看了想提刀杀人。如果不想被兄弟砍,可以注意下述几点。
- 代码规范。
依稀记得友人的描述,“打开几千行代码的文件,能找到五六个同事经手的痕迹。”
简直就是,男人看了会沉默,女人看了会落泪。既然我们都不想接手这样的陈年老码,那就不要让别人在接手代码时也经受一遍灵魂的痛击。从我做起,有规范按照规范,无规范,尽量和原有代码风格融为一体。
- 接口幂等性。
即,请求接口x次的结果和请求一次的结果一样。这在主调函数依赖被调函数的返回状态来进行重试的场景下尤为重要,返回不正确可能会导致主调函数不断重试,造成多余请求,若系统没有作相应处理,可能会被拖垮。
例如:mq调用一个aosvr的服务,若返回0,则认为服务执行成功,不再调用;若返回非0,则认为系统错误,继续调用。此时用一个唯一id请求ao服务,调用成功,返回0。若此时发送了其余同样参数的请求,ao服务处理逻辑为获取记录已存在,返回非0错误码。则mq会误以为ao服务发生系统错误,从而继续重试。
这对mq来说,可能会造成大量消息堆积在队列,造成出队阻塞;对ao服务,会被一直请求,阻塞其他正常请求,降低系统性能。
- 代码可读性。
依稀记得另一个友人的描述,“变量命名用拼音就算了,还用拼音缩写,这谁顶得住啊。”
想必这就是大家最害怕的阴间代码了。没有注释,变量乱命名,几行高深莫测夹杂位运算符和魔法值的计算函数,行云流水间充斥着一股“老子就是牛逼,看不懂的都是傻逼”的自信。
0202年了,在讲求团队协作的今天,为了你好我好大家好,我一般都会做下述操作:
在注释方面:
- 在函数开头注释函数用途、主要执行步骤;
- 在每个主要步骤前添加注释;
- 在复杂步骤中添加注释。
在其他方面:
- 函数、变量命名仔细斟酌(如果我拿捏不准,会google翻译一下,或者在unbug.github.io/codelf/ 搜索一下)
- 复杂逻辑拆分为子函数,尽量降低圈复杂度。
- 执行步骤短的条件处理逻辑放前面,长的放后面。例如:遇到异常立刻返回的放前面。
- 同一个业务逻辑,用尽可能容易理解的方式写出来。(在努力做到ing)
接口协议设计
学生项目
一般来说,都是“无风格”的接口协议。如果说用了RESTful API,99.9%的可能性是假的。一般都是/xx/xx/doSomething之类的,只用GET和POST方法,API简洁明了即可。
工作项目
内部调用的接口,基本和学生项目设计差不多,只要内部达成一致、容易理解即可。外部调用的接口,要做到规范统一,减少沟通成本,大部分会根据RESTful API的规范来设计。下面说的也是对外API需要注意的点。
- 身份认证和数据加解密
具体可参考微信支付apiv3开发规范:wechatpay-api.gitbook.io/wechatpay-a… ,里面对于公钥、私钥、证书等概念都有明确解释。
以前没有接触过的同学可能会被绕晕,可以从这四个问题入手:为什么需要身份认证?怎么进行身份认证?为什么需要数据加解密?怎么进行数据加解密?
把这几个问题弄清楚,对于身份认证和数据加解密就有基本概念了。
- 宽接口和窄接口
以前设计接口,会为了接口的可扩展性,预留一些字段;或者同一个接口根据条件获取不同类型的数据。
先说第一点,预留字段的思想可以有,但是要确认是否真的必要,接口应该只返回必要的信息。可以和产品同学确认后期的诉求,确定字段是否保留;以及和使用方沟通,确认后期是否会有获取的可能性。
第二点,不符合单一职责的思想。我们的接口应该足够简单清晰,只做一个功能。否则接口臃肿混乱,可扩展性差,那就不好了。
- 返回明确的错误信息
随便找个微信支付或者支付宝的接口文档,都能看到限定了不同错误的返回状态码和错误信息。
从调用方的角度来看,如果调用失败,但是报了个模凌两可的错,那是多么让人崩溃。因此,错误码一定要足够清晰,起码要让别人定位到具体的出错步骤。