怎样做好代码设计

代码设计

代码设计,是程序员做项目时,在coding之前非常重要的一个步骤,可以说关系到整个系统、整个团队的研发质量和效率。一般说代码设计,可能涵盖以下几种:
1、整体设计
2、架构设计
3、领域模型设计
4、数据库设计
5、API设计
6、代码实现设计

代码设计的前提是,项目组成员已经完成正式的需求评审,并经过充分思考:
1、这个需求是为什么业务目标服务的?
2、这个需求描述的内容,是否为服务该目标最合适的方式(包括研发性价比、项目周期等)?
3、prd本身是否逻辑自洽?
4、是否每个内容点都可实现?
5、实现的技术方案是什么?
6、是否做过类似的功能,合并吗?复用吗?拆解吗?

整体设计与架构设计

项目的整体设计,有时会涵盖系统架构设计,这里要区分一下,系统架构设计并不完全等同于代码架构设计。

整体设计首先要考虑的,是当前项目是要做一个全新的项目,还是要做原有项目基础上改造、迭代;项目组的积累中,是否有可以复用的地方(模块或成熟方案),是否有可以通过改造以符合新项目需求的可能。

其次再考虑,如果是新起项目,要如何搭建整体实施方案,内容一般包括:
1、硬件部署与资源申请:硬件和资源,是要和业务需求结合来制定的,比如业务最大访问、TPS/QPS等,要切实讨论得出一个数据范围,以确定系统是否做高并发方案。另外还要考虑容灾等问题,以此制定系统高可用的设计。
2、分析项目特别突出的业务、技术难点:如千人千面的UI和查询,或灵活配置的业务模式,类似这种需求的项目,会在模块模型设计上做额外处理,可能是将各种规则单独做一层规则引擎,也可能是在数据建模时增加更多维度;再比如超大的QPS,可能要在整体架构上,添加额外的中间层,异步收集数据等功能;还有就是依赖于审核的迭代上线周期(IOS、微信小程序)等,都要在整体设计中考虑进去。
3、内外部信息交换通讯:整体设计系统,要明确划定项目范围,哪些是本系统的功能,哪些功能、数据依赖其它系统或模块。要明确此次项目对外交互系统的访问关系和访问条件。
4、数据的持久化存储方案,如何选择硬件,如何选择软件,一般有常规解决方案。特别要考虑和需求结合的一点是业务数据的生命周期,这也是数据归档方案等重要依据。

整体设计中非常重要的一部分是系统架构设计,要在业务边界确定的前提下进行。

首先,从业务视角进行拆解功能,定义系统由哪些模块构成。
其次,根据业务复杂度、模块功能性划分,选择合理的软件架构模式及方案。
比如,业务需求上,有非常复杂的流程处理,但各个业务子模块之间相对独立,则可以选择事件驱动型架构,基本上用状态机、工作流就可以满足功能了;再比如做微服务架构设计上,也不一定都做集中式负载均衡,如果整体功能需求对消息流转要求比较高,则可以考虑中心消息型服务(比如LinkedIn)。

以上两步更多是从需求上分析,偏近于从业务角度设计系统。后面两步要从技术上做架构设计。

下一步要根据模块划分和架构方案选择合适的开发语言和技术框架。生产上一般考虑优选Java语言+Spring框架,但也有许多混搭的成熟方案,有些基于JVM的开源框架中对多语言的支持还是比较好的。

再下一步要将系统拆解,一般考虑划分出展现层、(通讯层)服务层、数据层,再根据需求为每一层设计合理的服务及组件。如展现层上web还是mobile,服务层是用Spring MVC还是Spring Cloud做微服务,数据层使用Mysql还是Oracle等。另外还有项目服务等通用组件及各种业务、技术中间件、及整体项目通用的技术方案选型,如日志(ELK等)、监控告警,访问授权,token认证等。

整体设计要在需求明确(不一定所有细节完全敲定)的前提下尽心,一般形成定案要在正式需求评审会之后。
整体设计由团队leader或项目owner完成。
整体设计文档,优先考虑使用物理部署图、逻辑架构图、业务流程图等描述系统架构。

另:特别建议在需求评审会后,首先由研发lead组织召开设计讨论会议。目的是让项目成员尽早的参与到项目之中,并使用讨论到方式,更好的理解项目及整体设计方案。
一般设计讨论可用头脑风暴方式进行会议。有重大分歧时,可以投票表决。
此讨论会议召开前,需要leader提前设定议题,会议上设有1名主持人(可leader兼任或者team member轮流担任),按照议题集中,轮流发言再集体讨论方式得出结论。
主要议题包括:整体设计方向、项目人员大致分工、待解决疑问和处理人。

领域模型设计与数据库设计

在整体架构设计完成后,要针对已经拆分的系统模块做模型设计,尤其是在项目需求中有重要功能的部分要重点设计。

领域建模要深度结合需求,从业务角度出发,用一种自顶而下的方式来建立模型。
领域建模方法很多,最重要的原则是在项目模型建立之前,要先做概念的统一整理,要对特定概念的名词做专业命名及能力约束。在此基础上,再进行重叠功能的归并和抽象。需要注意的是,此处制定的模型,和业务需求、数据库设计、代码类设计,都是一脉相承的,但并不完全等同。比如需求中有订单Order的概念,在设计订单Order都有哪些元素构成,可以实现什么操作时会发现,要在数据库和代码中,拆分为OrderHeader和OrderDetail。

在领域模型中,还有一个重点,是要标注清楚各抽象概念之间的数据关系和约束。一般会比较关注数据之间是一对一、一对多、多对多等关系,并在此基础上,结合业务流程泳道做系统模块依赖关系图、数据流图等。

数据库一般结合领域模型设计,是领域模型持久化存储的映射转化(ORMapping)。在项目数据库设计中,除了常规关注的范式、mysql约束等(单独写过mysql应用的usage,此文略),额外关注:
1、表字段在整体系统中的规范定义和统一使用。比如相同的概念,在各个表字段中的定义要一致,(在代码应用中也应保持一致)。
2、数据库事务的应用方案。
3、冗余字段与代码简洁实用的平衡。
4、特别说明,数据库一般仅作数据的持久化存储,不要将业务逻辑的处理放到数据库中进行。
5、生产业务数据delete,应该仅作逻辑删除,不做物理删除。
6、表设计要和性能结合起来,特别当DB成为性能瓶颈时,要特别斟酌索引设计的合理性。

模型设计一般为团队leader或项目owner完成,数据库设计一般为leader带领team member一起完成。
数据库设计一般要先于具体功能代码实现,在做此设计后,要针对存储方案和底层数据结构设计,做double check和集中评审,评审内容包括存储介质选型、表结构设计能否满足技术方案、存取性能和存储空间能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等。在评审一致通过后,沉淀为文档。

API设计与代码实现设计

在完成整体设计后,要将所有功能拆分成独立可调用的API。
这里的设计要考虑系统实现和业务需求的结合。系统API拆分,一定是从需求实现角度考虑,基于领域能力做的,且要充分考虑后续需求的变化演进,尽量使更多后续功能变化在既有规定服务API的框架内继续演化。
此外还要关注非功能性需求,比如安全性、可用性、可扩展性等。
最后,在这个步骤要关注的点,除了系统主干正向流程外,还有逆向流程、异常流程、业务边界等方面的接口定义。

API是系统模块对外提供的服务,现行系统基本满足前后端分离的框架使用条件,所以一般可以简单理解为前端和系统交互的入口。但实际系统设计中,API不仅仅提供给前端使用,所以每个API的实现设计是系统模块设计中非常重要的环节。
API的调用方一般会考虑给展示层多端调用(web、mobile等),还要考虑相同的API是否可以给其它系统模块使用,最后一层设计是,是否用相同的API对外提供openAPI服务。设计中应当特别考虑此类API的功能聚合、分离,多场景适用性等。以订单列表查询为例,设计一个订单列表query接口:
1、给前端页面查询,前端分页每次传参。这个场景最大的特点是,查询页面会设计较多分类查询的筛选条件,且此类设计实现经常可能是查询条件直接投射到DB上。
2、在整体系统中,给其它模块使用,如用户模块或报表模块。这个场景的特点是,如果其它模块功能为同步调用,则QPS不可预测,比如用户模块使用了这个订单query接口,那么这个接口的性能就会成为用户模块的瓶颈。还有一种可能是其它模块用此接口异步访问同步数据,就很有可能采用定时任务方式,固定分页,并发调用查询,如每5分钟调用一次,每次调用并发20,每个访问查询500分页数据。
3、给openAPI使用。如果开放给前端的query服务提供给开放平台直接使用或包装后直接访问,则容易出现的场景是,每次调用查询不确定分页,很有可能一个大分页(如十万)就打到DB上,这样即使索引匹配也容易造成数据库缓存区拥堵。
遇到这类需求,1、要考虑一个API接口是否可以满足所有需求,是否对数据访问做权限隔离。即,考虑所有的服务都集中到一个API上,还是定向拆分,将一个内部实现core,分别投射到多个API上。2、不同访问端如果有不同的QPS需求,还都考虑到,单个特大QPS接口,可以横向合并,即,不根据业务约束,而是把所有大访问的接口拆出来,给到单独技术架构和硬件部署的服务里。3、是否内部实现上一致,是否使用缓存、中间层方案等。4、数据库设计尤其是索引设计要和接口设计(尤其是筛选条件)保持一致。

API的设计还有一个维度的考虑,是基于数据交互考虑。当两个系统模块要使用API交互数据时,定义的API要充分考虑使用场景,并做技术选型:
1、数据是推还是拉?
2、同步推送还是异步通知回调?
3、通过接口还是MQ?是否需要削峰?
4、是否需要保证强一致性?
5、crontab定时发起还是任务队列发起?需要延时队列、死信队列吗?

API拆分完成后,要做代码实现设计。此设计主要关注每个API的内部实现,将一系列领域模型转换为系统对象的类设计。这里面有3个图可以辅助设计:
1、如果一个API复杂度较高,调用链路上的涉及对象较多,可以使用时序图来表达并且明确各调用环节的输入与输出,以反映对象间的交互与协作关系。时序图对完成设计评审、辅助项目开发有很大作用。
2、如果某个业务对象的状态较多,可以使用状态图来表达并且明确状态变化的各个触发条件。首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。状态图对测试用例有很大帮助。
3、如果系统中模型类超过较多,且存在复杂的依赖关系,可以使用类图来表达并且明确类之间的关系。类图对复杂系统设计,尤其是灵活配置、路由映射、设计模式应用等,有一定帮助。
类的设计要充分考虑单一原则。应当优先使用聚合/组合的方式来实现。不得已使用继承的话,要使父类能够出现的地方子类一定能够出现。根据依赖倒置原则,尽量依赖抽象类与接口,有利于系统的扩展与维护。
在设计抽象时,要考虑以下问题:代码直观吗(好的代码自注释性很强),它的编写巧妙吗?实现细节可能隐去了吗?程序编写是立足于问题域而不是计算机科学或语言结构域吗?
程序开发有一个场景比较典型,写第一版需求时,仅仅是一个简单功能,实现也比较简单,但后续功能增加很多,变化很大,每次在原有类定义基础上增加功能,倒置代码冗余,尤其容易造成if-else太多。此时要考虑提前预估功能,做扩展性设计,或者在每次功能迭代中,做小版本重构。比如订单明细查询,在定义查询接口(interface)后,需求要增加一个千人千面功能,不同用户访问返回的内容条目不一样。如果用if-else或switch写,会比较不好管理,代码也容易混乱,这里可以新设计一个接口,做不同内容配置,然后组合使用,或者采用其它设计模式。
设计模式的目的,是辅助程序员更好的实现代码抽象,将现实业务逻辑,映射到抽象维度的代码语言上。一般生产上经常用到工厂抽象工厂、模板方法、策略、状态等。选择合适的设计模式和数据结构,有助于提升代码的清晰简洁度。

这个层次的代码设计一般交给team member完成,并输出接口定义、接口详细设计、包括一些数据库的DDL等。

设计评审与文档中心

在项目实施之前,设计评审是非常重要的环节。研发lead应该组织内部设计讨论,每人的接口设计要通过peer或backup的review,更好的方式是通过集中评审。
因为再好的设计,也要确保项目组所有成员,理解正确且一致。这个不仅仅是lead对团队的灌输,要确保组员之间对同一个业务概念的理解也是正确且一致的。要确保每人的接口设计,都符合整体设计需要。要确保项目级别领域定义符合更上层定义(如公司级别命名),要确保项目级别领域定义统一、代码实现中英文命名统一。大型项目,在dev内部设计通过后,也可以由项目经理组织产品、研发、测试各方展开设计评审,此处尤其要和prd结合,为整理测试用例服务。

接口细节的实现也应当是动手coding之前先做设计,并建议形成文档,研发lead要提前组织好开发文档中心,整组统一设计文档格式。
一般常规内容包括:
•新项目背景、或常规迭代项目里程碑
•项目管理的时间节点(需求评审时间、设计时间、提测时间、上线时间点)
•本期项目概要设计说明
•分工(API、完成人、预估工时、实际工时等)
•详细设计:接口实现设计、DB设计、缓存设计等
•上线计划等等

其它
许多需求是case-by-case的,很难有统一的方法,需要程序员多思考、多积累、多学习。以下给出一些Java代码的普及知识及书单以供参考:
《重构:改善既有代码的设计》、《代码整洁之道》
《编程珠玑》、《编程珠玑续》
《Java经典实例》、《Effective Java》
《深入理解Java虚拟机》
《Java核心技术卷I、II》、《Java编程思想》
《算法导论》、《算法(第4版)》
《大话数据结构》、《大话设计模式》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342