声明:本文为CSDN原创投稿文章,未经许可,禁止任何形式的转载。
子曰,知之为知之,不知为不知,是知也。
知道自己不知道也是一种知道,但作为开发人员,面对一个系统时,无论是开发新功能还是维护老系统,我们更多的是处在一种茫然无助,不知道如何下手,甚至不知道自己不知道的状态中。虽然系统开发的实践已经超过半个世纪了,在各个方面都取得了长足的进步,解决了很多难题,但我们在开发效率方面的提高明显跟不上系统规模的膨胀。虽然各种新想法、新方案层出不穷,但始终都没成为大家心目中的那枚银弹。
本文试图从分析开发人员面临的困难与挑战入手,剖造根因,探索解决之道,最终通过提供工具来落实解决方案。也许没办法提供一枚完美的银弹,但钢弹铁弹也能打败怪物。
一
系统开发面临的挑战
对于一个拥有多年实践经验的开发人员来说,软件开发的本质其实是软件维护,因为任何一个系统从开发的第二天开始,就会面临一个理解的问题,摆在我们眼前的始终是如何理解昨天的系统和构建今天与明天的系统。
理解系统的途径无非是阅读文档和代码。但无论公司大小,只要对开发工作有所积累,都会发现通过文档和代码进行理解存在巨大的困难。虽然是老生常谈,但为了深入理解问题并针对性地提出解决办法,我们还是先花点时间聊聊。
1.1文档迷宫
首先,人人都讨厌写文档。人的思维的速度是任何事物都无法匹敌的,嘴巴跟不上大脑,手更跟不上。因此我们往往发现找不到文档或即使找到,文档质量也很差。产品经理负责的需求文档可能好点,因为他们必须写,但开发人员负责的设计和说明文档其质量大家心里都有数。
其次,开发本质上是个翻译的过程。从最开始的想法到最终用户看到的实现,中间要经历多次的翻译过程:需求–设计–代码
哪里有翻译,哪里就有误解。由于各个环节参与的人之间存在概念,惯用语等各方面的差异,存在误解是必然的,不误解是侥幸的。并且由于在各个环节之间的抽象程度不一样,在环节之间还存在细节的增强与丢失。这就是为什么文档往往缺乏关键的实现细节。
常见的情况是很多需求确认的内容会在口头,电话或邮件中表述,但没有反映到文档里面,虽然最初参与沟通和系统实现的人按照这种需求做了,但后继维护的人就无法找到原始的需求来源。
最后,很隐蔽,但很关键的一点是文档之间的无关联性。需求文档与设计文档,设计文档与代码本质是割裂的,没有关联的。任意文档的改动不会引起其他文档的自动同步。
这事实上决定了文档是不可信的。即使找到一些文档,这些文档也都很少反映最新的需求和系统现状。也许最初的文档写的很工整,与实际系统大致吻合,但几个版本以后,文档一般都会变得要么支离破碎,要么结构混乱,最惨的是根本就忘了更新。
这是现实,讨论对错没有什么意义。最终悲催的开发人员只能依赖源码,从源码反推需求。也就是那句著名的“Talkischap,showmthcod”。
1.2源码泥潭
通过看代码理解系统是没有办法的办法。小公司,小模块里几千,上万行代码的系统我们也许可以这么做,但面对一个百万,千万代码行级别的系统,我们本质上无法通过阅读代码来进行理解的。
经常会听到某某技术公司的CEO/CTO说其会看代码,以表示自己多么的hands-on。我们做个简单的算术,假定一个人1天可以看完1万行代码,万代码需要近3个月时间,很难想象一个高层人士3个月什么事情也不做,只是看代码,并且代码每天都在变化。靠对细节的观察反推宏观系统,这种思路是错误的。
系统的动态几乎不可能通过人工的静态分析推测出来。看代码只能检查一些很表面的东西,几乎所有的代码检查都以对格式或命名之类的细节争论收场。当然不能说看代码完全没用,在一个很小的团队内部,大家都对系统很熟悉的情况下,这样做可能是有效的;但放到crosstam的情况下,代码rviw最终会变为形式或者一个socialactivity而已。
当然我们最终还是得看源码的。谈到看代码,大家心里想的一定是能不看就不看,因为大多数的源码真的是惨不忍睹。对于任何一个有点积累的公司来说,到处都是超长的类,超长的方法;超大,超长,超宽的嵌套条件分支;硬编码的对象组装逻辑等等,更别谈各种新兴的AOP和字节码技术埋伏在各种和你眼前的代码完全无关的想不到的角落。这样的源码要么完全让人无法读懂,要么即使看完了也不敢说自己真的明白怎么回事。
系统开发并不仅仅意味着写代码,问题的规模不同,解决的手段也不同。不幸的是我们开发任何规模的系统用的办法都似乎是同一个原始的手段–写代码。对于一个小系统,直接上代码也许没问题,但对于大多数工业级别的系统,由于规模不同,这么做只能是低效的甚至是失败的。
从整个系统的角度来看,代码仅仅是最底层实现的细节。代码可以完成很多事情,但不意味着代码是解决所有开发问题最有效的手段。对于写给人类看的代码来说,代码只适合描述简短的顺序逻辑,分支和嵌套结构。
通过看代码来理解一个系统是最低效的方式,好比一个人试图通过双脚走遍一个森林的每个角落来理解整个森林一样,本质上是不可能完成的任务。
二
X-sris简介
问题意味着机会,思路决定着出路。如果承认提高系统开发效率的关键在于如何快速高效的理解系统,那么我们的解决问题的方向和判断标准就很明确。
先排除不正确的思路,我认为有如下几个:
堆砌流程。流程越多越痛苦。这个无需多说。真正该做的事是现有流程的简化与自动化。流程本质上跟提高理解系统和构建系统的效率无关。系统开发的主要工作是理解并构建一个可以运行的系统,而不是构建一套工作流程。参与到流程中的时间必然会挤占掉实际开发的有效时间。
开发更多的管理工具。大多数工具做的是和开发无关的周边管理工作,例如编译,持续集成,源码管理,发布等等。同流程一样,管理工具越多,开发工作越艰难。经常发现为了统一目前过多的解决方案,大家头脑一热就又做了一个类似的。往往很多系统之间对关键数据都对不上号。这个道理跟航海时用一个钟表还是两个钟表的问题一致。
贸然引进与目前类似的新语言/框架/标准。公司级别的开发实践和个人的开发实践是两个完全不同概念。个人如同武林高手,多学习新东西是自我成长的必要,艺多不压身,最好成为全才;公司如同*队作战,讲究的是快速培训,快速上手,团队合作,同一标准,简洁高效,对人的要求是中众就行。引入过多的语言/框架/标准会导致对人员要求不必要的提高。因此对公司开发而言必须要对引入新东西保持警惕。如果没有新东西,不符合之前提到的标准,对开发效率没有很大的提高,又或者仅仅只是重复解决已经被现有方法解决了的问题,这种引入就是失败的,只会增大整个系统的复杂度,增加理解的难度,进一步拉低效率。
要构建一个可以快速高效理解的系统,正确的思路主要是为系统构建提供一种简单易懂,无需翻译的方式。其次由于不同领域的模型千差万别,无法通过一个统一的模型来描述,针对不同的问题领域,我们需要合适的模型和专用的建模工具。因此正确的方案需要做到以下几个方面:
2.1图形化编辑
俗话说百闻不如一见。由于人天生对图形比对文字理解来的更快,更自然。因此一个高效的工具必须首先是可视化的。从系统顶层模型就开始通过可视化工具来构建,直到不得不借助代码来实现的细节层面之上为止。同时由于某些问题领域可能具有相当的复杂度,往往一张图不能完全表达整个系统,所以必要时需要支持子图,要做到主图与子图之间的相互关联。通过这种方式做到从抽象到具体的层级递进,让人拥有看系统的“鹰眼”。既可以查看高层的结构,又可以快速定位到某个特定的抽象层次。
2.2基于模型而不是代码
很多代码其实描述的是业务模型或者数据模型,虽然能运行,但是是无法理解或者实现上非常笨拙。正确的做法是将业务模型和数据模型从代码里面解放出来,模型就用能最直接表示模型特点的方式来描述。实践中有一种做法是试图通过代码生成来完成模型到实现的关联,但大部分场景会存在上面提到的细节丢失问题,导致无法有效做到双向同步。要做到模型的有效性,最好的做法是直接使用模型而不是代码生成,并且在开发和运行时是同一个。
图1
通过以上两点把系统开发的方法从简单的堆砌代码提升到基于可视化模型的层次。方法不同,层次不同,描述系统的的广度和难易程度就不同,理解系统的效率将大大提高。
那么情形值得我们抽象成特定的模型?根据实际工作中的痛点,行为,决策和状态这几个模型值得抽象出来。
2.2.1行为模型
站在用户和开发的角度,一个系统是由其提供的服务来定义。系统有哪些服务,完成一个服务需要执行那些步骤,按照什么路径执行,是理解系统的关键。行为模型可以通过流程图来可视化表达。流程图可以清晰的描述一个服务是如何一步步的完成。从代码的角度而言,引入流程图可以消灭大部分的粘合,判断代码。有利于把一个单体系统拆分为易于理解的子系统,并进一步拆分为具体的步骤。
也许有人会问为什么不用对象图或时序图,原因如下:
对象图显示实体间的关系而不是动作如何完成,对系统动态理解没帮助
时序图仅能描述特定执行路径,而无法直观表述分支/循环,对系统动态的描述不完整,也不友好
X-Sris工具集里面的XrossUnit就是利用流程图构造系统的工具。
2.2.2决策模型
一个决定受哪些因素影响,每个因素的可能取值有哪些,按照什么顺序考虑因素获得决策。决策模型可以通过决策树来可视化表达。这种方式可以直观的表达复杂逻辑判断的分支和判断标准。可以用来取代复杂嵌套的if/ls。
X-Sris工具集里面的XrossDcision就是利用决策树构造决策模型的工具。
2.2.3状态模型
一个实体具有哪些状态,状态之间如何转移。状态模型可以通过状态机来可视化表达。可以代替复杂的hard-cod的状态判断和动作触发。
X-Sris工具集里面的XrossStat就是利用状态机描述模型状态的工具。
三
X-sris简介
工欲善其事必先利其器。X-sris是一套轻量级的,易于学习,易于使用,易于测试,易于交流的框架。目的是解决大规模软件开发中沟通不畅,文档不新,分工不当,进度不明等难题。它包括3个组件:
XrossUnit:用流程图描述服务如何按步骤完成
XrossDcision:用决策树为复杂决策建模
XrossStat:用状态机管理业务状态变迁
这三个组件互相之间没有任何耦合,根据实际需要,在一个系统里面即可以单独使用,也可以配合使用。这些组件对运行的平台也没有要求,即可以运行在容器里面,也可以单独运行在应用程序里面。
另外还有一个正在开发中的基于SEDA的微服务框架XEDA,属于运行平台级别。整体的范围的关系如下:
图2
四
Xcorssunit
4.1简介
XrossUnit是一个基于流程图的灵活的系统构建器,又称xUnit。用户在Eclips里面通过XrossUnit编辑器创建系统服务,编辑服务流程。运行时通过XrossUnit工厂类来获得并执行定义的流程。
图3
XrossUnit支持行为组件和结构组件。行为组件又称为单元,是XrossUnit名字中Unit的来源。行为组件通过接口定义规范对数据处理的方式,是构成模型的基本元素。结构组件提供预定义的结构,方便在行为组件之上用更大的粒度构造流程结构。结构组件可以指定自己表现为那种行为。
XrossUnit的范围包括流程图模型和其中的配置信息,不包括组件内部的代码实现。组件内部代码需要通过对行为组件的接口实现来完成。
XrossUnit关联了模型与代码,XrossUnit依据模型规划的路径自动调度各个行为组件。想要看任意组件的代码仅仅需要双击即可进入到实现类的内部。
行为组件通过接口定义,包括:
Procssor。仅对输入的Contxt进行处理,没有返回值
Convrtr。将输入Contxt转换为输出Contxt
Validator。对Contxt进行tru/fals判断
Locator。对Contxt进行定位分类判断
行为组件即可以单独使用,也可以相互组合为更大的结构。
结构组件为按照一定结构预定义的一系列行为组件,包括:
Chain。顺序执行一系列单元
If-ls。根据Validator的判断,决定执行两个分支中的哪一个
Branch。根据Locator的判断,决定执行多个分支中的哪一个
Whil循环。根据Validator的判断,决定是否执行包含的单元,并在执行结束后回到Validator再次判断
Dowhil循环。在执行完包含的单元后根据Validator判断决定是否再次执行
Dcorator。对任意单元进行修饰,在单元执行前后做额外动作
Adaptr。对任意单元做行为上的转变,可以用于复用现成组件
XrossUnit编辑器提供了直观的编辑方法,可以将现有已有单元或结构通过的简单的对象组合来生成新的结构。例如需要在一个单元原来的处理流程上面需要添加一个新的流程判断,我们可以在界面上面选择Validator再单击需要添加分支判断的单元或结构就会在原有基础上增加一个分支结构。编辑器本身支持undo/rdo功能。通过这种方式可以快速的从无到有的构建一个复杂的系统,同时保证系统易于理解。
XrossUnit编辑器提供自动排版,用户仅仅需要在图上添加修改单元,编辑器会自动根据组件间的关系对布局做调整,无需人工干预。无论谁来生成,模型都是一致的,始终保持图的清晰,准确与美观。
XrossUnit还支持配置,可以在应用或构建单元层次上面配置参数,方便在不同场景下复用同一个模型。
4.2XrossUnit使用方式
1)构建系统蓝图。可以一个人或大家一起边讨论,边通过编辑器画出要开发的系统的流程图。每个组件都会有缺省的桩实现。因此图画好了,这个系统就可以马上运行。
图4
2)实现组件单元。针对流程图中的每个行为组件实现相应的接口,提供实际的处理能力。
图5
3)关联蓝图和代码。在编辑器里面双击行为组件,指定实现类的名字。
图6
4)生成实例并运行。
图7
4.3XrossUnit实际示例
我们看一个实际例子来说明其优势,这是一个实际使用了XrossUnit构建系统的一部分例子。
系统顶层主流程:
图8
主流程中请求签名验证对应的子图:
图9
主流程中中业务处理对应的子图:
图10
主流程中返回值处理对应的子图:
图11
具体实现的代码不方便,但可以看到流程图可以有效地描述和分解系统。这难道不是你们渴望已久的工具吗?
4.4XrossUnit的优势
趁手的工具是原则保证的利器,通过上面的例子我们可以看到使用XrossUnit:
首先可以做到快速组建系统:
自顶向下分解,组件化设计,流水线式开发
最优化设计复用
在流程模型与代码之间快速切换。无需脱离开发环境
其次可以自然做到系统开发提倡的高内聚,低耦合原则。通过没有工具的保证,这些原则只能沦为口号。
通过名字描述功能
通过配置调整行为
通过Contxt限定数据
每个unit仅仅完成明确描述的功能,有效控制代码复杂度
最后这种构造系统的方式非常易于单元化测试。
单接口设计,无选择,无歧义的实现
通过构造Contxt,轻松模拟测试数据
可以在组件级别快速提供mockobjct
4.5XrossUnit不是什么
不是又一个Spring。
Spring是从整体如何由局部构成的观点构建系统。Spring的做法其实是完成类图/对象图的模型化,如前所述,这种图无法描述系统动态。xUnit是从请求如何被处理的行为观点构建系统。
不是工作流
工作流处理多角色在多请求之间的任务/路径管理,是更大范畴的可视化。xUnit细化的是单个请求级别的响应路径/处理单元。
不是一个可视化的编程语言
可视化的编程语言的做法是解释和生产代码。xUnit将粘合代码抽取为模型,在业务层组装行为和结构单元,xUnit的系统定位如下图:
图12
4.6XrossUnit常见问题
1)为什么使用单元来完成代码也能做的事情?
因为问题的大小决定手段的选择,想象下面工作的复杂度
i.“HlloWorld”
ii.一个WbSrvic
iii.一个小的WbApp
iv.一个淘宝,bay,ctrip规模的网站
简单的系统可以直接用代码实现,复杂的系统无法这么做。单打独斗和大规模开发是完全不同的实践。大规模开发的关键是系统理解,不但要最初开发的人理解,后面维护的人和测试,产品等相关人员也都要理解。随着开发规模的不断膨胀,最初方法的效果一定会发生变化,手段必然要求变化。由于人总是习惯于当前的做法,这种变化容易被人忽略,有些人可能意识到了问题所在,但没兴趣改变。还有人希望改变,但或者由于工作负担实在太大,没时间停下来思考,或者不具备相关的技术,无法做出改进。
目前流行的微服务的产生本质上也是回应这种规模带来的变化,但没有可视化的支持,微服务除了把一个请求放大到多个请求外,一样没出路。
2)为什么不用现有的命令框架
常见的命令框架如Srvlt,JEE里面的SssionBan,EntityBan,MssagBan等等,描述的粒度仅限于系统入口服务。缺乏入口服务级别下的内部细节表示。我们可以观察到尽管有大量的小的仅仅只有一页代码的