一、萌芽
作为一只编程经验并不怎么丰富的程序猿来讲,我一直觉得架构师是一个比较神秘的职业,架构设计就更加的高大上了。经过今年的几个项目,之前曾发文叙述我的从MVC到MVP项目重构实战经验,也曾说过我准备对目前手底下的项目进行重构。但是,前段时间,我改变了我的想法。开发模式的重构,仅仅只是换了一个套路,也许在重构的过程中对业务的逻辑进行了一次梳理,也是在基于前人的代码设计上进行了一些优化。但是,这远远还不够,这不是我理想中的开发场景。在项目开发的过程中,也发现存在许多的问题,但是都是一些零散的问题,我很多时候希望能够改变现状,更加优雅地编程,然后实际的情况却是陷入了迭代功能开发和bug修复的死循环。现在回过头来想想,我理想中应该是开发应该是一种由规划和设计指导的开发,那么架构设计就显的尤为重要了。
二、初识架构
1、阅读《架构之美》之论架构
仅看完了《架构之美》的第一部分:论架构,对架构有了一个大概的认识。下图是这部分的知识点概要:
书中很受启发的概念:
- 架构是一种折中,一种取舍。架构师要学会的就是平衡,质量与成本之间的平衡;
- 架构师首先关注的不是系统的功能,而是满足需求,满足品质;
- 架构设计要做的是让关注点分离,并且对每个关注点进行设计一定的结构,该结构都有利于解答这一个关注点所定义的问题;
- 好的架构应该是可以指导产品、开发、测试人员都对这一设计感到非常的舒适可靠,该设计覆盖了所有该软件系统相关利益的人员以及其中的关注点;
- 只设计你知道需要的东西,多余的设计是一种浪费;
- 架构几乎影响了该系统相关的所有的人和事,决定了该软件系统是否健康。
2、分析行业内各个APP的架构演进
这里仅仅通过Google搜索各个app在架构演进方面的一些文章,从中分析他们为什么要演进?怎么演进?带来了哪些好处?
简单的整理如下:
(1)架构为什么需要演进
- 项目需求扩张,旧的架构不适应新的需求
- 开发团队人员增加,协作要求变高
- 新技术引入
- 更高的软件质量要求
(2)他们是怎么演进的
- 饿了么移动APP的架构演进 2016-01-20
- 模块解耦
- 共有组件
- 业务组件
- Excalibur映射系统,注册机制
- 引入Hybrid框架
- React-Native & Hot Patch
- 模块解耦
- 携程移动App架构优化之旅 2016-04-07
- 工程结构调整、业务之前解耦,产品间相互独立,页面跳转使用事件总线或者URL总线方式
- 统一基础功能业务组件,SDK化
- 性能数据采集、监控
- Native的插件化和HotFix
- 糯米移动组件架构演进之路 2016-05-24
- 组件化
- 统一服务入口
- Hybrid框架优化
- 苏宁11.11:苏宁易购移动端的架构优化实践 2016-11-11
- 分层解耦,解决纵向解耦
- 中介型引用结构(protocol和url方式),横向解耦
- H5容器优化
- 网络链路优化
- 移动App性能监控系统
(3)带来的好处
- 进行了模块化的解耦,产品相对独立,应对需求变化、技术更新更加灵活,团队协作更加方便,并减少了许多是无用功,也给团队留下了一些技术积累;
- 进行了必要的统一规范,组织结构更加清晰,系统更加健康;
- 引入了新的技术框架,,产品获得更好的体验;
- 进行了系统的优化工作,软件的品质更高,体验更好;
3、Google搜索关键字:架构设计
搜索引擎对于我们来说是最棒的学习工具,我通过搜索架构设计等关键字,阅读了一些文章,并仔细研读Keegan小钢的博客文章《小钢的架构思考》系列。这几篇文章在发表之初曾阅读过,但是当时并不怎么理解,大概是对架构还没有一个大概的认识。在请教一位前辈的时候,他和说了对架构的一个理解,并再次推荐了这几篇文章。所以我再次阅读了好几遍。下图是文中关于架构设计的知识概要。
(1)知识概要
(2)个人小结
架构分为三个阶段:规划、设计、构建,每个阶段架构的设计有不同职能。在规划阶段,考虑的是产品的需求、质量的需求,技术的可行性分析以及预研。在设计阶段,考虑的如果将一个复杂的系统拆分,并设计如何进行组织这些拆分的模块。在构建阶段,考虑的就是具体的实施问题,并且要保证一定的伸缩扩展性,因为架构是不断演进的。文中引用了《软件架构设计》一书的一个模型图,我觉得有必要在此贴出来。最近也在思考软件模块化的设计,模块化的设计也许各有理解,在此先不做讨论。如下图:
这张图我起初理解不是很透彻,我曾尝试自己去画一些图来表达我的一些想法。但是,当我再次回过头看到这张图的时候,才恍然大悟。
架构的设计可以从两个维度来考虑,一是架构思维,二是架构原则。思维是我们的思考方式,是我们解决问题的方法。原则是我们思考问题的方向,是我们解决问题的一些标准。
三、架构的定义
对于架构的定义,业界都各有看法,也曾微信私信请教过一些行业内有丰富经验的前辈。《软件架构设计》一书则将架构定义总结为组成派和决策派:
- 组成派:架构=组件+交互:软件系统的架构将系统描述为计算组件及组件之间的交互。
- 决策派:架构=重要决策集:软件架构是在一些重要方面所作出的决策的集合。
keegan小钢在《小钢的架构思考:什么是架构》文中提到:软件架构是规划、设计和构建软件的过程和结果。《架构之美》一书在1.1.3架构的含义中提出:架构说明了设计和构建一个系统所使用的结构。《Software Architecture in Practice,Second Edition》中提出:一个程序或计算机的软件架构是系统的一种结构或一组结构,它包含软件元素、这些元素的外部可见的属性,以及元素之间的关系。
本人并没有经历过大型的软件系统,做过的也只是移动端App的开发,所以个人也只敢从移动端app的架构设计出发,给出我一个狭义的理解。我认为,移动端的app架构是一种基于产品和技术的进行统筹管理最终所形成的共识。可能大部分的app开发尤其是小团队app的开发大多是由产品驱动的开发,需求来了,那就技术实现。需求变了,那就改。毕竟,应用层的开发是对业务负责的,必须保证正常的发布。所以大多数的情况下,程序猿不得不在产品经理面前妥协,这样对于开发人员的工作就会变的很被动。所以,就出现了程序猿和产品狗的撕逼笑谈。这种现象的原因在于项目各个相关利益人员没有对产品和技术达成共识,这正是移动端架构设计所要解决的问题。
四、产品
产品,是我们产品经理们设计的结果,也是开发人员开发的最终成果,是前后两种人群的共同目标。作为软件的架构设计者应该充分理解产品的设计理念,除了明白已经设计的功能业务,还得具有一定的预见,掌握产品的发展趋势。下面主要从开发的角度来谈一谈产品。
1、产品设计(做什么)
作为一名开发人员,我不能很专业的来谈产品的设计,但是这里我还是希望以一个开发人员的角度来讲产品设计。什么是产品设计,我觉得可以从以下几个方面来思考。
(1)、用户群体(什么人用)
顾名思义,我们设计开发出来的产品最终是让人用的。所以首先我们得定位产品的用户是谁?用户群体代表了我们产品的市场。所以产品做的好不好,最终市场说了算,用户说了算。离开了用户,不理解用户,不注重用户的体验,一切都是无用功。
(2)、核心理念(要做什么)
你知道你最每天累死累活一行一行敲出来的是什么样的产品吗?可能对于我们开发人员很容易陷入到一个版本一个版本的迭代当中,这些永无止境的工作叫人忘记了思考,忘记了问下产品人员,我们最终是要做的什么?一年后我们的产品将是个什么样的状态,我们的最终愿景是什么?我们将会怎么一步步去实现我们最终的愿景?可能你会说,这些和我开发有什么关系。接到需求,我把他开发出来实现不就ok了。然而,在我们的小组里不乏有这些怨言,产品人员不断的修改,我TM代码改过来改过去。我们的leader在这方面很强调我们开发人员应该拥有自己的主动权,可以反驳产品不合理的设计。但是,前提是你至少理解产品的核心理念,我们最终要做什么。
2、迭代计划(计划怎么做)
对于一个产品,用户的需求是很多的,而且随着时间不断改变的。需求可以分两种,一种是人性本能的需求,还有一种,是我们的产品催生的需求。这两种需求都是合理的,也正是我们需要满足用户的。对于各式各样的需求我们怎么有计划的去实现呢?在敏捷开发中,我们将这些需求放到一个“需求池”中,然后进行计划安排在不同版本的迭代中。这个工作,不仅是产品人员去决定,开发人员也应该起一定的决策作用。产品人员需要从产品的角度去考虑,开发人员需要从技术实现的角度去考虑,最终的计划应该是两者的共同决策。特别注意的是,根据产品的特性,技术人员也应该提出技术方面的需求。合理的迭代计划可以保证正常的开发节奏,完成迭代目标。
3、开发资源(用什么做)
(1)、开发团队配置(人)
在《人件》一书中提到了软件开发中人的因素很重要,合理配置开发团队是非常重要的。一个App的开发团队至少需要5个角色,即产品、交互、UI、软件、测试。不同角色也分不同的层次,比如软件分初级、中级、高级。不同角色、不同层次合理搭配,才能够获得更高的工作效率,保证产品开发顺利进行。
(2)、数据内容配置(物)
产品最终呈现给用户的是数据,数据分两种。一种是私有的数据,是由开发商自己生产的数据。一种是平台自生的数据,是由用户生产的。如果是自己生产的数据,就得考虑数据来源,数据的覆盖率,数据的准确性,合法性等。如果是用户生产数据,就得考虑用户生产数据的动力、入口以及数据安全性、传播性等。
(3)、开发投入预算(钱)
万事俱备,只欠东风。完成一款app开发,需要一支专业的开发团队,这里的人力成本也是很高的。当然我这里只谈开发的预算,至于运营就不说了。我们得考虑,开发周期多长、需要多少人、后期维护怎么办。比如一个APP需要5个人开发,2个月的时间,开发两个版本,按照每人1W的工资来计算的话,也需要 10W。这估计是最低级别的算法了。所以,如果是创业公司,我们在组建开发团队的时候,也得看看预算是多少,多少钱能办多大事,当然如果是那些拿到投资无所谓的老板来时得另算了,不过今年死的太多的公司以前都是大手笔。钱烧完了,也就没有了。如果是大公司,可能不带这么抠门的。不过也应该去考虑,开发团队会消耗公司多少资源,我们能否获得相应或者更高的产出。
(4)、第三方资源
目前的开发而言,很多资源是可以寻找第三方合作的。比如,服务器、云存储、支付接口、登录接口、内容数据以及开发过程中的一些开源框架等等。我们需要选择、商务谈判直到集成到自己的APP中。
4、产品质量(做的怎么样)
(1)、用户体验
现在对于我们来说,用户体验是一个说烂了的词。那是因为,用户体验真的很重要,决定了一个产品的成败。产品开发完成后,最终到达用户的手中。产品好不好,用户说了算。哪些因素影响到用户体验呢?我想大概可以从5个角色各自的职责出发来看,产品的设计是否直达用户痛点?交互是否符合人的喜好、习惯,UI是否让用户觉得舒适?软件的性能好不好?软件的缺陷是否多不多?
(2)、软件性能
从技术的角度来讲,我们可以通过软件的性能来分析一个软件产品的质量。今年许多的技术文章都在谈性能优化,软件的性能主要从软件的启动速度、流畅度、内存、功耗、流量、apk体积等几个方面来评判。如果想做好一个应用,性能优化应该纳入到日常的开发中持续进行。具体如何优化,这里就不再多说了。
(3)、产品安全
产品的安全性可以从两个角度来看,产品的生产商和产品的最终用户。对于生产商而言,有许多的内容是需要受到法律保护的,有许多的敏感信息,核心技术、网络接口等是不可以泄露的。对于用户而言,我们肯定在本地或者服务器存储了大量的用户信息,比如账号密码,一些信息一旦泄露将严重伤害到用户的个人利益。所以,为了保护自己以及用户利益,我们必须要生产一个安全可靠的产品。那么对于一个应用端的开发者而言,我们的编译出的apk最终会到用户手中。所以,我们需要通过代码混淆、数据加密、权限限制等一些技术手段来保护我们的应用。
(4)、质量评测
一个应用做的好不好,我认为可以主要从上述用户体验、软件性能、产品安全三个维度来进行评判。那么,我们该如何组织这些评判工作呢?我们有在进行这些工作吗?就目前而言,我相信大多数的产品、开发、测试人员都或多或少的参与到这些工作当中,但是也许没有将一些数据量化、没有系统的组织这些工作。目前大部分的应用都集成了行为采集,产品的下载量、用户的活跃度等也都是体现产品用户体验的主要参数。开发团队内部一直在进行性能优化的工作,比如异常修复、bug修复、内容泄露,过度绘制,apk瘦身。我们也进行了代码混淆、数据加密、apk签名加密的工作。但是,你知道你的产品质量如何吗?相比同类产品来,你哪些做的好,哪些做的不好吗?所以,我觉得将上述这些零碎的工作有系统的组织起来,将一些影响因素进行量化,让我们更加清楚的了解我们的产品质量是一件非常有意义的事情。
5、风险规避
(1)、人力变动风险
人是善变的,尤其对于IT来说,人员的流动就更加的频繁了,公司内部的调整,员工跳槽等等。所以,对于一直开发团队,必须要考虑到人员变动的风险。如果,某某不在了,项目是否可以正常运行。开发团队之间是否能够交叉熟悉各自之间的业务。
(2)、上层决策风险
是否经历过一个项目做到一大半业务被停掉了的情况?而这个时候,你做的是个半吊子。如果出现了这种情况,我们该怎么办?假设就在刚才你的老板说你现在的项目不做了,那么如何才能最大程度的挽回损失?如何进行项目的收尾工作?而不至于在项目又突然重启的时候接收的是一个烂摊子。
(3)、项目延期风险
我们在项目开发的时候会进行评审,然后按照迭代计划开发,但是在开发过程中一定会有许多问题影响我们的预期,比如需求变动、技术难题等等。项目延期在软件项目的开发中是普遍存在的问题,对于某些迭代而言,可能并不对整个项目造成重大影响,但是这个问题是一定需要考虑的。并且,我们应该严格的掌控项目的进度,平衡这些问题,保证能够按时交付产品。
(4)、软件缺陷风险
我们应该随时能够提供一个稳定的版本,这是我们的leader所要求的。软件的缺陷存在是正常的,我们不停的写bug,也在不停的修改bug,对于那些隐藏很深的bug也许没有让测试测出来,最后流通到用户的手中,这个时候我们如何完成紧急修复?如何快速响应能给到用户一个稳定可靠的版本。这些是我们需要考虑的,任何时候,都应该有PlanB。
(5)、人为失误风险
前段时间,公司内由于操作失误,上架更新一个apk的时候不小心发错了机型,导致使用该机型的用户升级后程序无法使用。然后,由于这个机型缺少维护,找不到代码,仅仅只能找到一个apk文件,然后只能考虑反编译升级等等。我想,类似于这类的人为失误还有很多,比如代码提交错误,集成路径出错等等。人总有一不小心的时候,所以,我们在设计的时候,应该将这些因素考虑进去,如何在出现失误的时候主动警告,如何在用户错误已经发生的时候启动紧急方案,将不良影响降到最低。
6、产品交付
(1)、测试版本
在敏捷迭代开发中,我们基本上能够一周提交两个测试版本。我们开发一部分、修复一部分,都可以提交一个可测试的版本,这样可以最大程度的降低开发风险,有利于软件的稳定性。
(2)、灰度机制
如果你产品的用户量够大,这个时候发布新的版本就得慎重考虑,用户才是你的产品的检验员。目前基本都是使用灰度发布的策略,先给少量的用户发布,看看用户的反馈,而后逐步发布给所有用户。
(3)、版本管理
我们在开发过程中有许多的版本,也有很多分法。如debug和release版本,有的时候还需要给内容提供测试数据的data版本,还有的时候上一个版本还没有正式发布我们就需要开发下一个版本的功能。我们如何去管理各个版本的代码以及如何通过版本名来区分这些版本?我们需要制定一定的管理规范,并且这一规范是否在开发团队中达成共识,就显得非常重要。
五、技术开发
前面啰嗦了很多,终于写到这里了。对于一个开发人员来说,怎么做才是我们的关键问题所在。只会Android开发,所以以下只讨论Android。我主要从以下几个方面来谈一谈怎么做这个问题。
1、技术选型
(1)、 开发平台
移动端的开发目前主要是两大阵营Android、iOS,其他的就不多说了。
(2)、 开发工具
- 编译工具:Eclipse&Ant、AndroidStudio&Gradle,作为Android开发者,目前毫无疑问应该选择AndroidStudio&Gradle;
- 代码仓库:Git 、SVN ,工具有海龟、AndroidStudio也集成了VCS;
- Maven仓库:可以使用nexus创建自己的maven私服;
- 持续集成:Jinkens、Buildbot、Travis CI、Strider、Integrity;
(3)、 开发语言
Java、Kotin、Grovvy、SQL等等;
(4)、 开发模式
MVC、MVP、MVVM、clean等,各有优缺点,在此不做详细说明;
(5)、 开源框架
都说了不要重复造轮子,因为你造的轮子不一定不人家的好用,对于我们开发者而言,有一件非常好的事情就是我们有太多的开源免费的第三方库供我们使用,这样给我们省去了大量的工作,做到更加高效的开发。但是,如何选择,是否引入使我们需要考虑的一个问题。下面列出一些常用的第三方库,更多请点击。
- 网络:OKHttp、 android-asyn-http、 volley、 Retrofit
- 事件总线:otto、 EventBus
- 依赖注入:Dagger、 RoboGuice、 ButterKnife
- 图片:Fresco、Glide、 Picasso
- 数据库:GreenDao、 Ormlite、LitePal
- Json解析: Gson、Jackson、 FastJson
- 响应式编程: RxJava、 RxAndroid
- 异常统计平台:腾讯Bugly、Crashlytics
- 性能优化: blockcanary、 leakcanary
(6)、 新兴技术
软件开发而言,新技术的发展相当迅速,然而我们实际落地到项目中却需要很长的时间,因为新的技术刚出来一是需要学习成本,二是需要承担新技术不够成熟,存在缺陷带来的一些风险。当然,我们应该积极的引入好的新的东西,跟得上时代的步伐才好。下面列举的一些也许都算不上新的东西,但是也是近年来大家所追捧的新技术。
- AndroidSupport:DataBinding、MaterialDesign等;
- 混合开发:ReactNative、Hybrid、Weex等;
- 编程语言:Java8、Kotlin;
- 热修复:AndFix、HotFix、Tinker等;
- 构建:InstantRun、Freeline
2、业务拆分
我们在进行业务拆分的时候,我认为可以将业务分成三类:
(1)、常用基础业务
基础业务主要是我们的app的一些基础功能,像我们公司有BFC团队给我们开发了文件上传下载、网络请求、行为采集、账号系统等SDK,免除我们一些重复的劳动工作。怎么去定义什么业务才是基础业务呢?我觉得可以这么去区分。如果你的业务在行业普通的应用app都有需要,那么这些这些就是具有普遍适用性的基础业务。我们根据不同的功能进行拆分。
(2)、通用技术业务
通用技术业务我觉得是和自己app相关并且有技术性很强的业务,可能是你应用的核心技术部分,比如美颜这一类软件的图片处理,小猿搜题这类的图片识别等就是一项通用技术型业务。通用技术业务的特点就是在和你同一类的app都会有需要使用的技术,我们可以根据不同的技术领域进行拆分。
(3)、特定功能业务
特定功能业务就是属于你自己app的特定功能了,一般可以按照功能进行拆分成不同模块。比如说我目前的一键搜(类似于小猿搜题)主要有搜题、查单词、翻译三大功能。那么就可以分拆为三大块。搜题要经过拍照、框题、图片处理、网络请求等步骤,每个步骤都可以看成一块小业务,以此进行拆分。特定功能业务大部分仅适用于你自身的APP。
以上的说法仅从自身的经验出发来进行描述,在我们实际的开发中可能会有一些特殊情况,或者有不同的拆分分方法。总之,业务的拆分还需要根据实际情况来。
3、架构设计(关注点分离、抽象)
(1)、核心概念
关注点分离
世上本没有架构,关注点一分离就有了架构,我们将一个软件系统的开发从多个维度将我们的工作进行拆分,对于每个领域进行设计,将各个领域有系统的组织起来,这种组织结构就是架构。然而如何将一个复杂的系统将关注点进行合理的分离,这个是非常有挑战的。
抽象
抽象,这是在请教一位前辈时最后给我强调的一点。如果你对app是跟着交互走、一个页面一个页面写的,那么很显然,你没有对你的业务进行抽象,而只是在实现。作为java的设计思想也很强调抽象的概念。那抽象到底是什么呢?抽象就是你要做什么!更简单的理解就是,写interface而不是class。不知道大家有没有这样的经历,在我们的MVP的开发当中,我们有个Model,也有一个IModel,但是我们写完了Model才知道怎么写IModel,最后成了粘贴复制的体力劳动。如果你是这么做的,你可以自己思考下,假如我们先写是IModel,而不是Model,那就是怎么样的体验呢?这就是将你的业务进行抽象。在架构的设计当中,你只需要知道你要做些什么?而不需要去过多的关注你具体怎么去实现它,这才是设计。
(2)、设计思维
面向过程(Procedure Oriented)
众所周知,在C语言的开发中,我们的逻辑大多是根据任务的流程走。这是面向过程的典型例子。面向过程关注的是工作的流程、一步一步的完成任务。
面向对象(Object Oriented)
Java语言作为面向对象开发的典型代表,这是我们所熟知的。我们将计算机按照人的思维来进行设计,每一个对象都持有自己的属性,并且持有自己的操作方法。对象之间有继承,组合等关系,通过组织这些关系来完成我们的程序。就像社会的人和物一样,人与人之间各种复杂的关系组合完成了社会各项活动的运转。
面向切面 (Aspect-Oriented)
面向切面是为弥补面向对象中的一些缺陷而生的,我们将某些功能封装到一起,提供对外的接口,方便在任何地方调用。就如SharedPreferences, Json, Xml, File, Device, System, Log, 格式转换等,这些通常会在until包里边。它就相当于一个横截面,我们可以随时面向这个横截面完成操作,而自己的逻辑里边不再需要重复的设计。
面向服务
面向服务是将系统进行拆分,分成一个个独立的程序或组件,并对外提供某一项服务。每项服务之间通过某种协议进行通信,并进行分开部署,如HTTP,从而达到松耦合的目的。
以上四种思维重点在于看待问题的角度不同,不同的角度解决问题的方案就不一样,当然各种角度各有优劣。那么对于在android开发中是否都只是按照OOP原则来设计呢?很显然不是。面对不同的需求,不同的场景,我们需要及时调整自己的思维,灵活运用,寻找最适合的角度,拿出最优的设计方案,这才是我们所追求的。
(3)、设计原则
高内聚
怎么理解高内聚?我认为我们在拆分时某一细分领域只完成单一的功能,其内部的事情自己处理。从表面来看比如一个model的class,对外提供了一个接口,那么他有一个输入,一个输出。单独看这个接口而言,它是高内聚的。当然,其内部的组织结构有可能千差万别,所以内聚的形式又各有不同。所以我们将他们分类为功能内聚、顺序内聚、时间内聚等等。
低耦合
耦合指的是模块之间存在依赖关系,关系相互依赖就会相互制衡,这是必然的。所以,如果耦合度太高的话,将会导致牵一发而动全身的后果,这个使我们不想看到的,也极大的影响的程序版本的迭代以及bug的修复。根据依赖关系的不同,我们分为了非直接耦合、数据耦合、内容耦合、开关耦合、控制耦合、外部耦合等等。我们要完成一个系统的开发,必须要将各个模块有效的组织起来,这种组织关系便无法避免存在了耦合,我们要做的是尽量减少这些依赖关系,尤其避免交叉依赖,将耦合度降低到最低,把我们的程序设计的更加的灵活。
适度设计
我们在设计的时候如果考虑不周,那么设计不够,不能满足现有或者可预知的需求,从发展的眼光来看,会导致后期的开发中出现很多的问题。如果想的太多,很容易进行过度的设计,从而将一个简单的系统设计的很复杂,那么就给当前的开发将增加了许多无意义的工作,降低了开发效率。那么怎样的设计才是合理的设计呢?我认为能够同时满足现有的需求和可预知的需求,并且面对架构的调整时能够很方便的进行扩展。这样的设计,是非常好的设计。如何才能达到这样的效果呢?我个人觉得在对系统进行设计时,关注点分离的颗粒度需要把握好,系统不过就是将不同单一小模块进行组织而已,那么这些细小的模块就是架构设计的基础,这就好比建房的那些砖头。这些砖头是什么呢?他么可以是是一个对外提供接口的公共方法,也可以是私有的内部方法,也可以是某某持用的成员变量。当然往大里看,他可以是某一个功能模块。在上述行业内各个app的架构演进中,都很强调进行模块化的改造。所以,分离好你的系统,才能够灵活的组织起来,以不变应万变。
(4)、设计方案
指导模型下图在文中已经提到,这里再次引入,因为这张图对我的启发真的很大,也表达出了我心之所想。面对一个复杂的系统,我们怎么样去分离,怎么样去组织,我认为这张图已经传达出了其中的精髓,所以我认为这是架构设计的指导模型,无论你是什么MVC、MVP、MVVM之类的,都可以从中去理解。
模型分解
根据实践开发中个人的理解,我将此图再次进行了简化如下:
横向分块
根据上图的简化模型,我们可以这么理解,在横向我们根据业务功能进行模块划分。比如主题商店,我们可以分为壁纸、铃声等等模块,每个模块间解耦。同时,在每一层的业务间再次进行分块,比如壁纸在数据层就有图片的请求、加载、缓存、裁剪处理等等。
纵向分层
接下来我们在对每个模块的业务根据职责分为展现层、业务层、数据层。数据层主要负责数据的获取、封装等工作,业务层主要更加上层的需要调配各数据层最终将数据返回给展现层,展现层的工作就是将数据展现在UI界面上,并且响应人的各种指令切换UI,操作新的数据。
接口通信
在横向来看,我们将业务进行了分块,保证块与块之间相互之间没有任何依赖,保证了绝对的解耦。从纵向来看,每个层级之间的依赖很明显是无法避免的,所以我们可以保证上层仅依赖下层的接口,从而达到降低其耦合度的目的。
如上图所示,通过上述横向分块、纵向分层、接口通信这三大步骤之后,我们可以将一个系统进行了很好的分解,并得到一个理想模型。当然,这是一个理想的模型。在我们的实际开发中可能无法避免一些交叉等特殊情况,我们还需要从实际情况出发。但是有一点,我们可以保证接口的分离,已达到更低的耦合度的目的。
统一管理
统一管理,是对于我们的设计中有一些东西是需要统一管理起来的。通过上述原则,我们将一个复杂的系统进行了拆解,已达到架构设计中将关注点分离的目的。然而在实际的开发中,我们除了要进行业务的分拆,也需要对某些业务进行统一的管理。比如说一些模式的开关管理,比如说我们在进行网络请求时需要在测试环境和正式环境之间的切换,我们可以将这些模式切换的开关放到一个地方,方便我们进行管理,而不要去到各个地方去修改。再比如说我们的请求url地址,是否可以写到一起进行统一的管理。还有在某些应用中会通过一个中间人来进行统一管理数据的流通、页面的跳转,这也是一个可以尝试的方案,详细请看苏宁易购移动端的架构优化实践文中提到的模块管理器、Url跳转管理器。统一管理的意思就是将分拆的某一类小的模块某一些特性放到某一处进行统一的管理。但是这样会存在一个问题,比如前面举例说到的统一开关管理,这造成了开关耦合,如何去避免呢?我觉得可以将开关默认写到自己的模块里边,并公开出修改的接口,方便上层进行统一的修改,以达到统一管理的目的。这样的话,即使这个模块拆离出来,也不会受到影响。但是,这样的话,其安全性受到了一定的影响。架构设计总是这样,你总需要选择一个折中适合自己的方案。
我们通过上述横向分块、纵向分层的方法将一个系统切成不同的小块,这些小块负责某一单一的职责,然后通过接口将块与块之间进行了间接性的连接,依赖的是接口而不是实例,以弱化这种模块间通信造成的耦合。当然,上述模型仅仅只是一个理想状态的模型,如果是一个非常复杂的系统,那么层级之间也能拆分出更多的层级。比如,在数据层,我们在MVP模式的开发下使用Model来完成,当Model层的业务变得非常复杂时,有部分人会考虑拆分出Data层放在最底层,最为最基础的数据操作等。最后,为了方便我们对模块进行组合并进行管理,我们可以考虑在小模块中开放出接口,供上层进行统一的控制管理。最后,我想说的时,我们在进行业务分离拆解时可以考虑按照上述的方案来做,最终还得根据实际情况来进行设计。
4、开发实现
当完成我们的设计工作后,我们进入了开发编码阶段,在这个阶段主要表达我们的设计,并最终取得实实在在的成果。当进入这个阶段之前,我们的设计不能仅仅是一份文档,而应该是开发人员和架构设计者达成的某种共识。再好的设计,也需要获得良好的表达和实现。下面主要谈一谈在实现过程中需要考虑的问题。
(1)、项目分包
项目的分包结构体现一个软件的架构,我们在进行分包的时候总有一种困惑。因为我们存在多种分法,比如我们可以分为根据类的功能分为activity、fragment、adapter、util等,有的时候,我们又根据功能模块分,比如一键搜中有查单词模块、有搜题模块,同时又存在网络请求,软件升级等小的外围通用功能模块。存在的问题就是模块之间又存在一些可以复用的东西,那么我们进行拆分明显出现了代码的冗余。如果按照两种方案同时分,那就肯定存在了架构的混乱。我们该如何达到这两种的平衡?我认为,这个也需要更加项目的大小而来,如果是非常小的项目,也不存业务扩展的可能,我们就可以采用上述的第一种方案,简单的分类就好。但是,对于较大的项目,我建议使用第二种方案。下面,我简单列一个模型仅供参考:
+ app
+main
+com.jfg
+common //常用基础业务
+util
+wedget
+base
+function //通用技术业务
+camera
+sensor
+moudule //特定功能业务
+mouduleA
+model
+presenter
+view
+mouduleB
+model
+presenter
+view
+mouduleC
+demo //主程序
+app
+activity
如上所示,我们根据开始的项目业务拆分分包如上,将常用的基础业务放到common包里边,这个包在大多数情况是不变的,并且为app提供基础性的服务,不过我们尽量不要放到这个common包里边,如果这个common包变得足够大的时候,就一定要思考是不是该拆分了。因为common给人的感觉就是什么都是,那就让我们无法快速认知这个包所担当的职责。我们可以这样理解,common包是面向切面而设计的一些业务,但也不是绝对的。接下来我们先聊module这个包,实际这里是将业务进行了模块化的分拆,如上我们拆分出了moudleA和moudleB,这两者之间要求没有任何的联系。但是,我们会存在一个问题,那就是moudleA和moudleB某些业务是一样的,我们拆开显得重复了许多体力活。这应该是大多数开发者面对的困扰,这种该怎么去平衡呢?我是这么考虑的。如果,moudle和moudleB存在重叠的业务,我们将这些业务提取到function包或者common包中,这样降低了业务的层级。我们允许moudle包的各模块业务依赖于function和common为我们提供的基础服务。为了更好的区分模块A和模块B虽然重叠但在逻辑上是各自属于各自的,我们有两种方法来做。第一种是将两种业务进行一定的抽象,实现的过程还是放到各个moudle业务中。第二种方案定义两个接口类,各自定义各自的接口。在具体的实现类中实现了这两个接口类的方法,内部在进行相同的逻辑操作。这样,对外看来,逻辑上moudleA和moduleB是分离的。总之,如何分包还得权衡利弊,尽量以一种思维来进行划分,以避免设计混乱。
(2)、抽象接口
如果说在架构设计中抽象很重要,你可能有些迷糊,但是如果要你先写interface或者abstract class 而不是class时,你就可能感觉得到抽象的意义。我们将一个系统分解成几个大的模块,一个模块查分成不同的层级,每个层级再次拆分成不同的细节业务。最后,我们很清晰的知道我们要完成某一项功能需要做哪些事?对的,做哪些事就就是一个个接口,我们在编码时先写接口再写实现有利于帮助我们对业务进行拆分和抽象。我们都知道做一件事情一般情况都需要提供一些条件,做完了会有返回结果。这些都可以在接口的设计中完成。我们需要注意是一个接口只做一件事情,如果有两件事非常相似也要尽量拆分而不是合并。在接口命名方面做到见名知意,怎么去评判,就是如果你的接口没有注释也同样能让人知道你的接口是做什么的就好。
(3)、数据存储
数据存储常用的有SQLite、SharedPreference、文件等,缓存是否也可以算是一种。这里想强调的就是要注意数据存储的规范性以及安全性,如果是数据库还有必要考虑其扩展性,如果不满足需求将会需要进行升级。
(4)、性能管理
这里源自于对性能优化的一点体会,对于服务端的开发我们很珍惜服务器资源,应该是看的见的需要银子买的。然而,对于客户端的开发我们常常忽略了这一点。虽然手机设备现在拥有大内存,但是如何写出一个优秀的程序,性能也是一个非常重要的指标。性能优化处理,那是我们在更正错误,那么之后应该是少犯错误。性能体验不够好,无非就是对机器设备的内存、CPU、GPU资源无节制的使用,造成资源的浪费,当机器设备无法承受时就会应用就会出现卡段、死机、异常等不良反应,严重影响了应用的体验。我们要做的就是要有很强的性能管理意识,对于内存、CPU、GPU等资源按需借用,并做到有借有还,即用完后记得释放资源。
(5)、特殊处理
我们在开发的过程中,总有那么多问题并不是按照正常思路出牌的,这些得归功于我们强大的测试团队。不同的手法,就能得到不同的结果,然后就给了我们一堆的bug。所以,我们在软件的开发中需要特别注意一些特殊情况的处理,这些最终往往还是逻辑上的死角。以下简单总结了一些:
功能冲突
功能冲突可以分为两种,一个是应用内部的功能冲突,二是应用之间的功能冲突。应用内冲突比如A功能和B功能都使用了某资源文件,如果在同时使用就会出现问题,我们通常加同步锁来防止这种冲突。应用外的冲突有很多,比如多媒体、闹钟、日历、铃声、电话等都肯能引起这些冲突,比如你正在播放一段视频,这个时候来了一个电话,那我该优先哪一个呢?还有当闹钟响起的时候,弹出一个界面是竖屏的,那么他就会强制将当前的界面变为竖屏,而如果你这个时候如果是横屏的话该怎么办呢?类似于这类还有很多,以后再细细总结。
极限操作
我们的测试人员喜欢对着某一个按钮狂点、或者在机器上安装无数的应用使内存爆满,或者在磁盘里边塞满各种文件。这些场景虽然并不是理性的用户所出现的,但实际也是程序的缺陷。所以,我们要注意对这些问题进行处理。
网络问题
不可用的网络,信号很弱的网络,网路在wifi和流量之间切换,2G网络和4G网络,网络请求超时等都需要我们针对实际情况进行处理,比如切回到流量的时候进行下载是否有提醒用户。这些处理也算是各个应用的标配了,就不再多说了。
为null处理
这应该是最常见的问题了,我们平时改bug或者从后台异常抓取的大多数都是空指针异常。首先,我们得搞清楚为null的原因是什么?然后我们需要进行为null的判断,并警告。
(6)、Log打印
这里把Log打印单独拿出来是应为我觉得很需要重视。Log是用来干嘛的?很显然是用来帮助我们查找问题的,然后我们大多数的情况下是问题来了再去加打印,并且TAG五花八门的,是有错误的地方用Log.d,而只是查看信息却是用的Log.e,我们查问题的时候要去阅读很多的代码逻辑,最后再定位到位置。我们在修复bug的时候花了大量的时候再阅读代码,这个太影响工作效率了。如果我们在编码之初就对Log有了很好的规范设计,有异常的地方就用Log.e,可能出现问题的地方就用Log.w等等,关键点的信息用Log.i,临时调试的用Log.d这样区分不是很好吗?我们在控制台一样就能够分辨自己需要的信息。我们应该充分应用这个工具,帮助我们快速定位问题。
(7)、软件重构
重构是因为业务的需要,也是因为对代码更高的质量要求,重构无处不在。我们不要为了重构而重构,也不要一直停滞不前,该重构时不重构,欠下太多的技术债务。重构有小到一个方法的重构,也有大到整个系统架构的重构。重构是软件迭代升级的一个必要过程,也是我们能够满足当前需要,并且能够适应未来的发展,获得良好的扩展性的必要手段。重构并不难,也不是什么大事,重点在于重构背后的目的、思想、设计是否清晰。
(8)、兼容适配
兼容适配的问题是我们开发一个头疼的问题,Android设备无法八门的屏幕尺寸、层次不齐的Android系统版本。除了进行针对性的处理,还得提醒产品设计人员在设计之初就得考虑兼容性问题。
5、软件测试
软件测试是我们开发中非常重要的一到工序,除了能够客观的感应我们所开发的软件质量水平,最终目的还是在于帮助开发人员修复软件缺陷,提高软件的质量。除了开发人员提交测试之前的自测,我们需要专业的测试人员来进行测试。人工测试的效率相对较低,所以我们应该考虑通过技术手段完成自动化的测试,如单元测试等。这样有利于软件的稳定,也同样的有助于开发人员提高代码质量。
6、开发规范
(1)、设计一致
什么样的设计才是有规范的设计呢?我个人认为一个项目保持一致的设计思想就是有规范的设计。我们可以这样去理解,在某一类的情况下,我们按照某一类似的方案来解决这一类问题。面对复杂的系统,我们一种设计思想也许无法满足架构的需求,但是我们只需要明白,这种事情这么干,那种事情那么干,相互之间灵活的组合却也不存在交叉,给人设计思路清晰的感觉。
(2)、编码清晰
什么样的代码才是高质量的代码?我觉得结构简单,逻辑清晰,一看就懂的代码就是一份高质量的代码。所以,我们可以抛开那些编码规范、命名规范等等,重新去审视自己的代码,看看读起来是不是很舒服。如果来了一个新同事,对你对项目一无所知,是否能够很快速的理解你的思维?最后几点有必要提出,一是慎用设计模式,二是尽量少写文档注释,三是遵循Java的面向对象六大设计原则。我想这三点就是编码的原则,遵守这些原则,形成一种习惯,自然而然就是一种规范。
(3)、文档有效
什么样的文档才是有效的文档,我认为能说明核心问题的文档就是有效的文档。作为软件开发人员,我们没有耐心去阅读一份复杂啰嗦的文档。文档只需要给我们呈现代码无法说明的问题,帮助我们快速理解项目结构,备忘重点问题,追溯历史记录。文档的形式又很多,比如我们Git的提交记录、tag,项目结构图、核心功能流程图等。总之,文档是给人看的,以尽量少的文档来说明核心问题就好。
7、日常工作
作为一名合格的软件开发人员,我们有许多的日常工作,如Bug修复、异常处理、Monkey、性能优化、代码质量改善等待。这些事情并一定要求你每天都要做,但是每天都得关注,做到心中有数。如此,才能够培养一个很好的编程习惯,写出优秀的代码,优秀的程序。
六、统筹规划
1、开发驱动
一个项目的正常运转一定是由某一方主导项目的进行,不断的提出产品需求,并组织项目成员分工合作,完成产品的开发交付,以下是两种驱动开发模型。
TDD:测试驱动开发(Test-Driven Development)
测试驱动开发指的是由测试主导开发的工作,从产品的使用出发,对产品的功能提出测试要求,组织项目中各个角色完成开发任务。
BDD:行为驱动开发(Behavior Driven Development)
敏捷开发便是行为驱动的开发,更加强调产品的设计,鼓励项目中各个角色提出自己对项目开发的观点,已获得更优秀的产品功能,完成产品的开发。
2、敏捷开发
下图引自网络上的一张关于敏捷开发的图片,目前我们团队基本也是根据这个模型进行敏捷开发的。我觉得在敏捷开发中更加强调的各个角色之间的随时沟通和快速响应,我们并不对整个系统进行一份完整而详细的设计,而是进行阶段的设计开发工作,并谋求不断的迭代更新。在敏捷开发的过程中,普遍存在的问题就是沟通不及时、产品变动大,所以如何动态的进行统筹管理变的非常重要。我们通过需求评审、交互评审、视觉评审、Bug评审等由各个有关角色的人员参加评审会议,共同完成决策,以保证软件开发的顺利进行。不得不说,这个过程中还是存在许多的问题,沟通的问题,产品变动的问题,产品功能细节开发过程的中流程性的问题等等,在此不详细说了,后期有时间再进行总结。
3、达成共识
敏捷开发中有一个特点就是,产品开发决策是由项目各个角色成员共同完成的,各个角色领域的问题又是由该角色自身最终拍板。那么随之而来的就出现了一个问题,各个角色所决定的问题又被其他的角色所制衡。比如说,产品经理设计的功能在开发人员而言存在技术难题,交互的设计存在逻辑上的漏洞。我想,这些问题是一个产品不能按照预期时间完成的真实原因。那么,哪一个角色来统筹管理这些问题呢?通常,我们有架构师、项目经理、老板等等角色来对项目进行把控和管理。
软件架构的设计来源于产品的需求,并决定了产品的最终形态。然而处于敏捷开发中的我们而言,产品的需求变化很快,同时要求我们开发人员能够快速响应这种变化。那么,如何保持软件的整体架构和产品的设计同步更新就显的非常重要。
对于开发者而言,我们首先要对项目有一个整体的认知,随时掌握产品的动态。这些包括产品的设计、迭代计划、开发资源、质量要求、交付要求、风险规避方案等。然后根据产品的需求变化,动态调整自己的软件架构,这些工作包括,技术选型、架构设计、代码重构、文档更新等,以适应新的需求,并把这种架构共享给相关人员以达成共识。这种共识,是需要对产品和技术进行统筹规划才能够完成的。也只有项目各个相关利益人员能够达成这种共识,我们的沟通才会更有效,才能做出各方人员都满意的产品。
七、总结
写到最后,我回过头来看看,才发现对架构的整体认识站在了决策派的一边,即这种共识便是在产品规划、设计、开发阶段一些重要方面所作出的决策的集合。这些决策是我们项目各个角色成员通过计划会、评审会、迭代会等达成的共识,并最终表现在我们的产品上。产品、交互、UI、开发、测试,每个角色对自己所在领域负责的同时也需要随时掌握其他领域的信息,以便自己做出正确的决策。但是在对软件技术实现的架构设计上,我就偏向了组成派,将软件系统的业务进行了拆分和组合,已达成系统运转。我必须承认,此刻我的认识还是不够通透,在本文中各个方面大多还是泛泛而谈,每个问题深入下去还有许多可以去研究的,希望能够在以后的工作能够有更深刻的领悟。
八、 参考文献
- 《小钢的架构思考系列》
- 饿了么移动APP的架构演进
- 携程移动App架构优化之旅
- 糯米移动组件架构演进之路
- 苏宁易购移动端的架构优化实践
- Android App 整体架构设计的思考
- android开发一般都使用什么框架?
- 手机天猫解耦之路
- 什么是 Agile Software Development(敏捷软件开发)?
- 《架构之美》
- 《软件架构设计》
后记
经过这段时间对架构的学习和思考,我发现这些理论不仅可以运用在软件架构中,同样也适用于工作、生活当中。试着将我们的工作任务进行分离,减少每项任务之间的交叉,避免东一榔头西一棒子,有条理的完成各项工作。这样一来,你的工作目标是清晰的,也更加容易的完成既定目标。同样的,我们可以通过这一理论还规划自己的人生。将我们的事业、家庭、兴趣爱好分离,再对每一项分离出更细的关注点,每个关注点在不同的阶段定下自己小目标。这样,我们对自己就可以有了更清晰的认识,认识自己已经拥有什么?最终想要什么?计划怎么做?当前怎么做?当然,并不是每一步都是能够按照既定目标走的,我们需要不断的更新对自己的认识,做出更加合适的决策。