刚入职,接手新项目,面对一个全新项目,怎么快速研究它?
很多人直接看源码,一头扎入代码,很快就迷失其中,最初那股子探索精神,也会逐渐被迷茫所替。有多少次你满怀激情打开一个开源项目,结果多半坚持不了就放弃。问题出在哪?迷茫是因为缺少对软件整体了解,如同不带地图指南针就闯入热带雨林,迷路只是早晚。阅读源码是必经一步,却不是第一步。应先从了解软件设计开始。
1 模型、接口和实现
好比你看代码:
模型
先看有哪些类及之间关系
接口
然后打开一个具体类,看提供哪些方法
实现
最后,再打开一个具体方法,看怎么写的
1.1 模型
一个软件的核心部分,也称之为抽象。设计最关键的就是构建出模型。而理解一个设计中的模型,可助我们建立对这个软件整体的认知。
如:
编写分布式计算代码,需考虑怎样在不同节点上调度计算
使用MapReduce,只要考虑如何把计算分开(Map),最后汇总(Reduce)
到了Spark,注意力就集中在要做怎样的计算
它们在解决同样问题,只是抽象层次逐步提高,越来越接近要解决的问题,越来越少考虑计算在不同的机器如何执行,大大降低理解门槛。知道模型的重要性,目光甚至可不局限在某一软件。若把同一个领域不同阶段的多个模型联系起来,还能看到软件发展趋势。
1.2 接口
决定软件通过怎样方式,暴露模型提供的能力。
是我们与这个软件交互的入口。
一个程序库的接口就是它的API,但对同样模型,每个人会设计出不同API,而不同API有不同表达能力。比如:Guava对JDK的一些API重新封装,就为简化开发,而很多优秀的做法后来又被JDK学了回去
一个工具软件一般会提供命令行接口,比如Unix命令行工具就是典型的命令行接口
一个业务系统的接口,就是对外暴露的各种接口,比如,它提供的各种REST API,也可能是提供了RPC给其它系统的调用。
……
想深入源码,了解一个软件,可从一个接口进入到软件,看它怎样完成各种基本功能。
1.3 实现
软件提供的模型和接口在内部如何实现,这是软件能力得以发挥的根基。
一个业务系统收到一个请求之后,是把信息写到DB,还是转发给其它系统
一个算法实现,是选择调用已有程序库,还是自己实现
一个系统中的功能,哪些应该做成分布式,哪些应该由一个中央节点统一处理
一段业务处理,是应该做成单线程,还是多线程
当资源有竞争,是每个节点自己处理,还是交由一个中间件统一处理
不同系统之间的连接,该采用哪种协议,是自己实现,还是找个中间件
……
所以,做每一个技术决策都应该结合自己所开发应用的特点,并不存在一个通用的解决方案。实际工作中,许多人以为的设计其实是这里的实现。“实现”很重要,须建立在模型和接口的基础上。一个系统的设计,模型最核心。若模型变了,这个软件便不再是这个软件,而接口通常反映的就是模型。所以,模型和接口的稳定度都要比实现高,实现则随软件发展而不断调整。
模型:需求
接口:可以提供哪些功能
实现:实现模型和接口的办法,语言,框架等技术
在使用类似springboot+mybatis开发的时候,mybatis-generator生产的mapper,service,service imp,在配合controller,就可以对数据库的数据进行增删改查,然后就可以实现一些CMS啊电商之类的业务需求,似乎都不需要自己定义新的接口和抽象,请问这是因为业务过于简单的原因吗?
不,这是因为你把业务逻辑混在增删改查里。
2 案例
2.1 Redis
随使用Redis增多,对Redis有进一步的需求。所以,从6.0开始,它开始支持多线程版本,以便于更好地满足需求。但即便Redis改成多线程,它还是那个Redis,它的模型和接口还是稳定不变,只是实现变了。
2.2 CRM
模型,通常包含两类要素:
基本元素
CRM的基本元素就包括项目、客户、合同和回款
这些元素之间的关系
相互之间的主要关系通常是客户报备,进入立项环节(评估投入产出),再签约,最后进入回款
这是基本模型。这个模型(系统)的接口,就是要为BD提供从客户报备到签约、回款的整个流程管理。
实现就是要考虑如何用消息在这些模块之间传递数据,状态控制、数据查重锁定等。
3 设计三步走
严格区分模型、接口和实现,是因为这三者关注点不同,而很多人讨论所谓“设计”,经常把它们混为一谈。
你们团队开会是不是经常有种很混乱感觉?问题就在于你们把不同层面内容混在一起,一起吃做大锅饭,最后那是人吃的吗?
正确做法是在讨论设计时,遵循顺序:先模型,再接口,最后实现。了解一个设计亦如此。
模型没弄清楚,就讨论细节,难分清哪些东西核心,须保留,哪些东西可替换。
若清楚模型,就知道哪些内容在系统中广泛适用,哪些内容须隔离。即分清模型会帮助你限制实现的使用范围。
如下是一个简化后架构图,订单服务完成处理后,通过MQ把消息发给支付服务,支付处理后,再通过MQ把消息发给物流:
这张图问题在哪?把模型和实现混淆。图中的订单、支付和物流,说的都是模型层,但RabbitMQ就把实现层拉进来。RabbitMQ只是实现这个功能时的一个技术选型,即若随业务发展,它不能很好扮演角色,就可替换掉,而整个设计不变。
所以,实现这段代码时,须封装MQ相关代码,不能在系统各处随意调用,因为它属于实现,可能随时被替换。
了解设计时,要按层次去了解,因为设计是分层的。每打开一个层次,需要了解它的内部时,还要按模型、接口和实现顺序研究。
如RocketMQ设计模型https://github.com/apache/rocketmq/blob/master/docs/cn/concept.md。
如os,了解它的内部,就知道它有内存管理、进程调度、文件系统等模块。可按照模型、接口和实现去理解每个模块,如进程管理:
进程管理的核心模型就包括进程模型和调度算法
接口包括,进程的创建、销毁以及调度算法的触发等
不同调度算法就是具体实现
os难以学习,很大程度上就在于,很多人没有搞清楚其中各个概念之间的关系。
即便层层展开到最后,到了一个具体类,甚至是一个具体数据结构,依然可以按照模型、接口和实现结构理解,如很多Java面试题常问到的HashMap:
其模型就是哈希表
它定义了一些接口,比如,get、put等
它的实现原来是用标准的HashMap实现,后来则借鉴了红黑树
再如,当使用一个新库或框架,先看接口,看对外提供功能是否满足要求,然后才是具体实现。 对于模型,想学习开源软件的架构时,再关注。
当能一层层理解设计,就像一棵知识树逐渐展开,每个知识节点在展开时,都会有下级的更具体内容。脑中有这样一棵设计树,就掌握了整个系统地图,再有新需求来,就不会盲目改代码。
4 总结
了解一个软件设计,从三个部分入手:
模型,也称为抽象,软件核心部分,该系统与其它系统有所区别的关键
接口,通过怎样方式将模型提供的能力暴露,是我们与这个软件交互的入口
实现,就是软件提供的模型和接口在内部是如何实现的,是软件能力得以发挥的根基
了解设计的顺序:
模型=》接口=》实现。了解设计,需要一层一层地展开,在每个层次都按照模型、接口和实现进行理解,在头脑中形成一棵设计树。
了解设计,先模型,再接口,最后是实现。