软件设计之道:那些值得借鉴的实践案例(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-16 05:22:55

点击下载

作者:麦思博(北京)软件技术有限公司

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

软件设计之道:那些值得借鉴的实践案例

软件设计之道:那些值得借鉴的实践案例试读:

前言

“TOP 100”全球软件案例研究峰会,已举办了4届,累计分享了来自全球一线互联网企业研究团队的400个软件研发优秀案例,与会者上万人。《软件设计之道∶那些值得借鉴的实践案例》精选了第4届100个优秀案例中的22篇软件设计、架构、开发方面的文章进行分享,以飨读者。

为了方便软件开发人员、互联网从业者的学习借鉴,我们将22篇文章分为如下三篇。

第一篇 工程实践

在软件研发行业,每年都会涌现出几个“爆品”,有些是短时间内被快速传播,获得大量用户,有些是具有技术研发里程碑意义的全新实践。这些“爆品”的出现或者是因为产品功能的有效创新,或者是对用户体验的极致迎合,而在背后,是对软件研发方式的严谨思考。

这一篇的内容精选2015年出现的技术实践案例,从创新实践到团队管理,让我们从案例中找寻软件研发人的“工匠精神”。

第二篇 互联网高可用架构变迁

每一名软件研发从业者都希望自己参与的产品能够被海量用户所使用,希望通过代码与技术智慧为更多人提供服务。架构,是产品形态支撑的脊柱,在海量用户使用的背后必然离不开高可用架构的承载。2015年,滴滴出行成为当之无愧的“明星”,在市场竞争中脱颖而出,而每一款产品在用户快速增长的过程中,都少不了架构的变革,为每一位用户提供稳定的功能。

这一篇收录了滴滴出行、魅族、当当、小米、Hulu、微博等平台的架构实践案例,从成熟平台中挖掘架构变迁的奥秘。

第三篇 云计算和大数据

随着中国互联网产业的发展,每天都可以产生海量的用户交互数据,当数据的重要性被越来越多的人所认知,大数据时代已然到来。大数据与云计算像是一枚硬币的正反两面,密不可分,两者相互依托,产生更多可能。目前,越来越多的企业重视大数据与云计算的技术,这也对软件研发从业者提出了新的挑战。

这一篇收录了云存储、云计算、云迁移、云管理、云智慧及广告大数据等技术实践案例,从这些案例中寻找云计算与大数据背后的技术逻辑。

本书精选的案例,在编写过程中尽量保持了原作者的叙述风格和思想观点,让读者了解最新的先进案例,分享知识干货,以利于知识的扩散与传播。

本书在编写过程中,感谢各位作者的奉献和支持。希望读者通过阅读能有所收益,在未来的第5届或第6届“TOP 100”峰会中崭露头角,实现成为研发团队带头人的梦想,展示出更加优秀的案例新篇。编 者2016年10月↘第一篇工程实践第1章让大象跳舞——Office移动版的性能开发周期杨 珂

作者姓名:杨珂

作者职位:微软Office总部的性能团队程序经理

作者简介:浙江大学攻读本科与博士学位,曾在微软亚洲研究院实习,做过香港科技大学访问学者,并在微软SQL任软件开发工程师。2012年加入Office总部的共享性能团队,现为微软Office总部性能团队的程序经理,西雅图创业俱乐部成员。一、背景介绍

2015年全新推出的微软Office移动版,如图1-1所示,重写了代码,以适应触控、小屏幕、移动、云端、协作等需求。移动版包括Word、Excel、Powerpoint、OneNote等应用,支持Android、iOS、Windows三大平台上的手机和平板设备。

Office移动版推出以来,超过2亿下载,Android/iOS/Windows商店评分均超过Google Docs,iWork等主要竞争产品。它的功能集之丰富接近PC版,却能流畅运行于廉价手机、平板上,这无异于让“大象”跳舞。例如,我们主打的一款低价手机Lumia 635内存大小仅为512MB;Word/Excel/Powerpoint三个Windows Phone应用安装后的文件占硬盘大小仅110MB。

我们是怎样让Office这头功能巨大的“大象”在移动设备上轻快流畅地跳起舞来的?这有赖于我们的“性能开发周期”流程。本文将代表个人观点为读者分享这一流程。关于性能编程技术,请读者参考MSDN文档:Performance(Windows Store Apps)。图1-1 Office移动版二、问题提出(一)性能是什么

性能包括两方面:一个是速度要快,一个是资源要省。可以总结为:“又要马儿跑得快,又要马儿不吃草”。性能在桌面软件、移动应用、网页应用和后端等不同领域的含义有所不同。表1-1所示列举了一些典型含义。表1-1 性能的含义举例(二)性能有多重要

我称性能为“看不见的关键体验”。在我看来,性能是软件的竞争利器。iOS在很多方面比Android操作系统更受用户青睐,其原因之一是iOS感觉更流畅,虽然iPhone 4S只有512MB内存,但是用户感受比一些1GB内存的Android手机还快。又比如微信在很多类似产品中脱颖而出,其原因之一是微信发消息、语音、图片时感觉太快了。

研究表明(App Attention Span Study 2014),“近90%受访者会因为App性能差而卸载;性能是造成App和网页用户沮丧的头号原因;65%的人对App性能的要求越来越高;约1/3移动银行App用户会因为App性能不好而换银行;如果一个产品或服务的App性能比竞争产品好,29%的人宁愿多付钱选择它。”可以说,性能者,“死生之地,存亡之道,不可不察也。”三、解决思路

为了确保产品的性能,有必要用流程来保证。性能开发周期如图1-2所示。在软件开发流程的每一步,嵌入相关性能环节。注意这个流程在几天发布一次的快速迭代开发中也是适用的,因为每一步的性能工作可以做大也可以做小,重要的是在原则上有这些环节。

一旦功能需求确定,就应确定性能目标。

•在系统设计之前就要确立性能目标,是因为性能目标有可能影响到系统如何架构。

•系统设计之后就要做性能风险分析,因为很多性能风险是由系统设计带来的或决定的。

•在写代码之前就需要做性能风险分析,因为对性能风险的解决方案有可能影响到如何写代码。

•写功能代码之后要写相关的性能日志,这样测试时可以同时进行性能测试。

•在发布后不仅要运营用户增长,还要同时运营用户性能体验,两者关联起来有助于我们看到性能对增长的影响。图1-2 性能开发周期

接下来,我将对性能周期里的每一环节详细阐述,最后再探讨一下怎样建设一个性能团队的途径和方法。四、实践过程(一)确立性能目标

确立性能目标的一般流程如图1-3所示。首先从功能需求出发,确定关键场景,再从关键场景中抽取几个比较重要的关键指标,最后对这些指标确定性能目标值。关键场景是功能需求中用户最常用,体验最重要,最体现产品价值的那几个场景式功能。关键指标在上一节性能的含义中有所举例。目标值的确定比较有讲究,它来自用户期待,产品的目标永远是“超出用户期待”。而用户期待来自以下多个渠道:(1)用户在产品评论中的性能反馈和要求;(2)竞品的类似功能的性能;(3)用户设备统计值,比如大部分用户来自1GB内存的Android手机;(4)要和公司产品线上姊妹产品当前的性能值保持相当;(5)不能比产品上一版差。图1-3 确立性能目标

举个例子,对Word移动版,一个关键场景是Word应用的启动;其中一个关键指标是Word启动到响应用户交互的时间;它的目标值为1秒。

注意:目标值对自动化测试和用户数据有不同含义。对自动化测试,我们可以确定一个或几个典型参考设备和环境,比如iPhone 4s,要求在实验室这个参考设备上启动时间永远不能超过1秒;而对于真实用户,他们的设备和环境参差不齐,同样是iPhone 4s,却可能因为系统上装了其他软件或移动网络条件不同而产生不同的启动时间,所以对用户数据的目标一般使用统计分布值,比如要求90%的用户启动时间都不能超过1秒。

性能目标一旦确定,应被写入功能需求作为产品的成功条件之一。作为“长期靶心”,不应该轻易改变。(二)分析性能风险

一旦系统设计确定,就要在写代码之前进行性能风险分析,因为对风险的解决方案有可能影响到如何写代码。分析从系统设计出发,头脑风暴可能的性能逆境,穷举可能的对响应时间和系统资源的性能威胁,也就是不达标的风险。对每种风险,研究缓解方案和对策。

比如,系统设计是在Word启动时执行跨网络的任务,而一个可能的逆境是网络条件差,导致的风险是Word启动响应时间有可能超过1秒的目标值。对此研究方案对策,最后的解决方案是推迟相关的网络任务到启动响应之后。如图1-4所示。图1-4 分析性能风险

方案对策有三个不同的战略级别:(1)最有效的是优化架构设计,包括精简加载模块,重组文件,UI/工作消息调度,闲时处理任务,按需启动/下载/升级……。这项工作直接导致系统设计的修改,这也是为什么不能把风险分析推迟到写代码阶段的原因。(2)其次是根据硬件和环境而进行功能的缩放,硬件包括用户设备的CPU、内存、分辨率、硬盘大小、电池电量,等等;环境包括网络延时、网络带宽、网络套餐,等等;根据动态测量设备和环境,功能的缩放包括关闭动画、多分辨率图片图标、精简加载的资源,等等。(3)最低一级是优化代码,包括编译/链接优化、按配置优化(Profile-Guided Optimization)、二进制符号分析、重复源码分析,等等。(三)写性能日志

性能日志就像printf(见图1-5),乍一看没有太多要注意的,但是好的printf其实学问很深,对debug极其重要。

性能日志要记录哪些内容呢?(1)时间:这样能通过收集的开始-结束时间的差值得知一个场景所花时间是否超过性能目标值,也能通过时间找到这一时间其他的相关日志,进行综合分析。(2)资源:记录内存,网络,读写等资源的开销。(3)关联:关联当前代码行和别处的有关代码。有几类关联,第一类是记录调用关系,哪个方法call了我,我又call了谁;第二类是跨线程异步关联,哪个线程call了我,我又在等待哪个线程完成;第三类是跨客户-服务器关联,我是哪个设备,而服务器在哪个数据中心。

注意:日志要真实反映用户的体验。比如响应时间要在UI真正空闲下来可以响应用户输入之后才算结束,否则到时候收集上来的数据显示响应很快,但用户那边真实的体验却很慢,这就是自己骗自己了。

另外要注意的是性能日志的性能本身,这听起来比较讽刺。要测量日志本身消耗了多少CPU,内存,读写,网络使用资源。日志应该本着“少而足够”的原则。日志应该是能够分级输出的,比如是内测还是发布时输出,是今后一直输出还是就当前版本输出,等等,应用由开关统一控制。日志还要能分离元数据,不需要在每条日志里都交代这是什么用户设备,是哪个应用版本,是哪个用户对话;而应该对每个用户设备只记录一次设备信息,对每个应用只记录一次应用版本,对每次对话只记录一次对话上下文,而这次对话里发生的十条日志都只需要有个关联ID,能让我通过JOIN找到对话、应用、设备的元数据就行。

最后,日志越少不仅消耗资源越少,而且debug时噪声越少,看日志越干净,越高效。输出用户日志时要请隐私法律方面的专家进行隐私评审,避免输出能定位到具体某个用户的隐私信息,缠上不必要的法律纠纷。图1-5 写性能日志(四)自动化性能测试

产品加入新功能常常伴随着更多的时间开销和系统资源开销,而我们要想知道产品的新版本是否仍然能满足性能目标,最简单的方法是在可重复、可控制的实验室环境下,用固定参考设备测试几个关键场景的性能。

当新代码加入时,要判断代码的性质是否有可能影响性能,如果是则开启自动化性能测试。自动化性能测试的流程如图1-6所示。例如,新代码是在Word的启动路径上,从而有可能影响性能。部署的机器包括手机和平板设备,相关的场景包括Word的冷启动和热启动。收集的日志统计了启动的耗时、内存、CPU、读写等指标。对比结果发现,耗时比上一版有明显的倒退,并且根据事件日志分析定位到了一行网络调用代码。从而生成bug和报表,交给开发人员。

开发人员应该能一键打开相关分析界面,包括相关代码的历史版本对照,可视化性能分析工具,等等,从而高效地理解问题并修改代码。要把性能倒退的bug放在高优先级,才能不让性能日渐流失,成为功能的牺牲品。图1-6 自动化性能测试(五)性能运营

自动化性能测试能够在实验室纯净的设备环境中可重复、可控制地测试几个关键场景的性能,为上线把关,然而不能得知世界上千差万别的设备环境上真实用户的体验。因此,需要在上线后做用户性能运营。

1.性能运营的流程

性能运营的流程如图1-7所示。(1)用户使用产品时在他们的设备上生成性能日志。在正式发布之前首先进行内部上线,由企业内部员工内测使用,生成的日志更少隐私限制,能提供原始丰富的debug信息,能在发布之前及时解决问题,所以内测非常重要。(2)无论内部或外部的用户生成的日志,在他们的设备上根据产品里指定的收集规则来收集日志,得到用户真实体验到的时间,资源使用等性能指标,并上传到公司的数据中心。收集到的指标和性能目标比较,达不到性能目标则说明出了问题。前面提到写性能日志时要注意日志的性能和隐私问题,这里在收集、上传、更新收集规则时也要注意性能问题,可以对条件、频率和采样率进行限制。比如,每隔1分钟收集一次日志,只有Word空闲时才能每隔十分钟上传1次日志,每隔1天检查收集规则,采样率是只收集千分之一的用户,以保证大部分用户不受收集上传的影响。(3)数据中心根据上传的性能结果生成报表,由运营人员监控。报表的内容分三个层次:第一是产品的性能有没有问题(Whether);第二是当确定“有问题”的时候定位问题的影响范围,比如发生时间、地区、相关设备类型,等等,也就是“问题的上下文(Where/When/Who/Which)”;第三是运营人员把确定了的问题交给开发人员进行技术诊断,也就是“为什么会有问题(Why)”。开发人员收到问题的上下文后,根据需要进一步要求收集在这个上下文里更多的用户细节数据,比如Call stack,从而找到bug,改善代码。图1-7 性能运营

2.性能运营举例

这里对报表举两个例子。

第一个例子是用户性能日志的报表。

首先,运营查看“用户启动耗时直方图”。假设既定性能目标是80%用户要在1秒内启动,而报表结果显示80%的用户启动时间超过了1.5秒,则说明有了问题。接下来运营查看“内存大小和启动时间”散点图,发现在启动超过1秒的用户主要是内存1GB的手机用户。这说明内存使用需要优化。散点图上显示的是启动时间的中位数,实际上我们可以画多个分位数来刻画分布曲线,比如50%、75%、90%、95%。同样地,运营查看国家/地区、日期/版本号等上下文数据和启动时间之间的相关性,从而确定问题的范围和性质,交给开发人员。开发人员通过要求收集更详细的日志,得到代码每一部分影响到的启动秒数,从而发现瓶颈代码,尝试进行优化。

第二个例子是用户在应用商店等渠道对产品性能文字反馈的报表。

首先,客服或运营收集并挖掘用户的文字反馈信息,如图1-8所示,得到性能差评占所有评价的百分比随时间或版本变化的曲线,当发现新版本导致更多性能差评时,则说明性能有了问题。接下来运营查看差评里具体的热门话题榜,发现收集信息慢是抱怨最多的一项。经过一系列定位问题的范围,运营交给开发人员去诊断收集信息慢的原因。开发人员找到和差评相关联的客户端以及服务器端的详细信息,从而定位相关的代码并尝试进行优化。图1-8 用户对产品性能文字反馈的报表

这里小结一下实验室自动化测试,用户日志监控以及用户文字反馈这三种不同的性能衡量手段,如图1-9所示。本地测试在发现问题的及时性,信息的深度和可行动性方面最好,但离用户真实体验较远,也不能发现未知问题。监控用户文字反馈能够近距离了解用户的感受和需求,发现未知问题的能力很强,但有些问题等到此时发现已经得罪了用户,而且得到的信息可能不足以帮助定位出问题的代码。用户日志监控的效果介于测试和反馈之间,弥补了两者各自的不足。要想有效地衡量用户性能体验,三者缺一不可。图1-9 自动化测试,用户日志监控和用户反馈监控的比较(六)性能开发周期小结

性能开发周期的好处有如下3点。(1)避免了性能问题导致用户体验致命缺陷,产品失败/落后竞品;(2)避免了开发后期由于性能问题而不得不重改架构,浪费巨大;(3)避免了不清楚真实用户性能体验,能更好地帮助商业决策。

因此,可以说,性能是用户体验的关键,需要用流程来保证。因而有了如下的性能开发周期。(1)团队和产品线总体从一开始就有量化的用户体验目标和系统资源用量预算。(2)架构师和开发人员能及早发现系统设计中的性能瓶颈问题,提前修改设计。(3)随着开发深入,性能测试防止新的功能不以牺牲性能目标为代价。(4)用户性能运营使得运营人员和开发人员高效合作,解决用户性能问题。(七)怎样建设一个性能团队

我通过在Office性能团队的工作,关于如何建设一个性能团队有些心得。一个性能团队的人员组成,每个人员负责的方方面面,以及他们和功能团队之间的工作关系,可以用图1-10所示来表示。图1-10 如何建设一个性能团队示意图五、案例启示

Office性能团队的价值在于:(1)帮助功能团队掌握了技能,预除了隐患,倒逼了设计;(2)帮助Office有效地、系统化地、稳定地改进性能。

那么我们做了哪些对的事呢?(1)从开发早期就渗透,贯穿了产品全周期。(2)了解产品功能和程序架构,从而深入地进行性能分析和debug。(3)了解功能团队桌前行为,从而提供了贴心工具服务。(4)不间断警觉地监测,从而使性能天天向上。(5)信念:性能是关键体验,竞争利器!感染了功能团队。(6)早期就得到了高层的“尚方宝剑”:功能团队的合作承诺。

另外,我们对Office功能团队也带来了影响。几年以前,一般认为,像性能这样的基础工作,做得再好也往往不像开发出产品新功能那样显眼,因此,大家容易到了开发后期或性能出现问题时才开始解决。然而那时程序架构早已不容易更改,后续版本的性能优化也比较难,如果出了性能问题,解决的代价往往比较高昂。

现在,Office功能团队已经能够充分意识到性能对产品就像地基一样重要和优先,把产品的性能,安全等基础质量和自己的业绩评估挂钩并且层层挂钩下去,在产品设计一开始就充分评审讨论基础的质量,在开发周期全程留出人力密切监视基础的质量。功能团队开始关注性能,从被动看数据到主动向我们要数据。

总之,好的性能乘以好的功能才能保证产品的成功,两者缺一不可。让我们通过性能开发周期来保证软件的成功以获得持久发展吧!第2章软件设计发展路线和设计能力提升训练营实践丁 辉

作者姓名:丁辉

作者职位:中兴通讯敏捷教练

作者简介:中兴通讯公司级敏捷教练和代码大全、代码设计训练营教练,12年软件开发经验,8年项目管理和流程改进经验,指导并参与多个团队由传统研发模式向敏捷研发模式转型(其中超过100人的大型团队成功项目级敏捷转型5个)。在敏捷导入、指导团队转型、CI、核心技术实践、自组织团队建设等方面具有丰富的实战经验;对如何提升员工代码设计能力和提升代码内在质量、遗留代码重构、架构设计等方面也有较多理解和解决思路;精益创业教练,曾指导多个创业团队产品设计、团队运作、技术架构。一、为什么要做软件设计

在探讨软件设计发展路线之前,我们先要看为什么要做软件设计?

软件设计的初衷是为了降低软件开发中的成本,软件开发中的成本除了管理、人力、机器设备硬件等成本外,还有一项占比非常巨大的成本,那就是软件本身的维护成本,这部分成本分为内在成本(领域本质复杂度带来的硬成本,该成本不能消减)和偶发成本(对软件本质复杂度应对不当,没有软件设计或者软件设计不合理带来额外复杂度而引入的成本)。

偶发成本的大小对软件的维护成本至关重要,在达到相同外部质量的情况下,在相同的开发时间内,偶发成本大的软件一定要投入更多的额外工作量,这些基本上都是开发人员长期加班和返工的原因,它们很容易耗尽开发人员的精力和对软件研发的激情。

另外,偶发成本对软件企业利润的影响也是非常巨大的,甚至很多软件项目的维护费用、差旅费用已经大大地超过软件项目本身能给企业带来的利润,造成很多负价值软件的出现。

从企业看,提升软件设计能力,降低软件中的偶发成本是一个软件企业成败的关键要素之一;从个人看,以软件研发为终身职业的程序员,提升自身代码设计能力至关重要,这关乎所在团队代码后续的内在质量和维护成本。很多程序员之所以过早丧失对软件编程的激情,很多时候都是陷入代码难以维护的焦油坑中而导致的。二、软件设计发展路线

提升软件设计能力,需要熟知软件设计的发展路线,掌握各种软件设计方法,达到“熟读唐诗三百首”的地步;另外就是要能够熟知各种设计方法的应用场景,在充分理解业务的前提下,找到一种合适的设计,恰当的去应对需求域的问题。

软件设计从低到高的发展路线依次为:面向过程、OO(面向对象)、设计模式、设计原则、DDD、DCI、DSL,时间跨度距离DSL的提出也将近有40年的时间了。设计思路和设计方法的演进也变得循序渐进,有章可循。这也使我们了解和学习软件设计的过程必须遵照依次递进的方式进行,不能高低跳跃。

下面就软件设计发展的过程逐个展开来讨论这方面的问题。(一)面向过程设计

面向过程编程中,把系统状态用数据结构来表示,首先设计稳定的全局数据结构,并且把数据结构完全暴露出来,行为操作围绕这些数据结构进行,当需要增加新的业务逻辑时,只需求增加对应的处理过程即可。

缺点如下:(1)全局数据结构发生较大变化时,会造成所有依赖该数据结构的处理过程的修改,这是件非常巨大的工作,很多时候是不可接受的。(2)状态作用域往往过大,造成依赖范围的过度扩大,当变化来临时,状态被波及的可能性也会加大,从而造成代码的高耦合,波及影响泛滥化,造成代码的维护成本增高。(3)内部状态的暴露,也使别人很容易窥探内部实行逻辑。(4)往往不经意间会增加对全局数据使用的泛滥(比如前段时间某大型汽车制造商控制程序的全局变量泛滥问题),在多线程并发情况下,状态的一致性维护也会带来隐患。(5)全局数据还会导致对状态修改的不可控性,从而导致问题定位、修复难度加大。

当然,如果状态对应的全局数据结构比较稳定,或者在只增加的前提下变化很少,并且对全局数据的访问制定整个项目组共同遵守的一定的规则,那面向过程编程还是属于一种比较直观、高效的编程方式,也是很多电信软件或嵌入式操作系统软件等基于C语言开发系统中比较常用的方式。(二)面向对象编程

面向对象编程是OOP或ADT编程的统称,它是为了解决面向过程编程的问题孕育而生的,其强调功能模块对外暴露的接口相对稳定,而对接口内部实现的数据结构进行封装和隐藏,从而解决面向过程编程中数据结构不稳定造成客户代码大规模变更的问题。

同时,使用泛型技术使客户代码依赖到抽象上(接口、超类或类型)等,从而使客户代码对数据结构依赖变成了对相对稳定的接口依赖,接口一般代表需求,其稳定性相对较高,接口实现一般通过依赖注入方式注入客户端,当接口实现中数据结构变更时,客户代码可以不受影响。比如stack,对外提供push和pop接口,内部实现可以是数组,也可以变成动态链表,由于是对接口的依赖,使用方对push和pop调用不受影响。(三)设计模式

面向对象讲究的是类、职责和协作,协作的方式五花八门,方式多种多样,无章可循,如何用好OO设计就成了难点。直到1995年,GoF总结并提出了23种设计模式,设计模式有点像建筑业上的风格,大家可以看到历史上不同时期的建筑风格都是类似的,这样就相对固化了大家对好坏的统一认知,类、职责和协作可以借鉴这种风格,根据用途按照一定的方式进行设计和组织。设计模式分为三类:创建模式(5种)、结构模式(7种)、行为模式(11种)。

设计模式的学习有以下3个要点。(1)要熟练掌握类图,重点是类与类之间的关系,同时要习惯于多画类图,多用类图的方式进行思考,这是个过程,需要培养和锻炼。(2)能够掌握每种模式的构成件以及构成件之间的相互关系,这个必须记熟并弄清楚。(3)能够识别每种模式中稳定部分和变化部分,特别是每种模式可以应对的需求变化方向以及其应用局限。(4)必须亲手应用每个模式,画出类图并编写出可运行代码,体会其优缺点和如何应对需求变化方向。(5)最后一点要注意,模式不是香水,绝不是喷到哪都是合适的。(6)模式的应用一定要合乎自然,所有学习设计模式最终目的是为了忘记设计模式,设计出代码后不经意间符合了某种某事,而不是相反。(四)设计原则

设计模式刚刚提出来的时候,曾经出现过模式泛滥的问题,一些模式稍微经过一些变化和组合,就变成了一些新模式被提出来,几乎每隔一段时间就会有人宣称有新模式的诞生。另外,模式的应用也变得越来越随意和勉强。为了解决这些问题,设计原则被提了出来,设计原则是提炼总结设计模式背后的共性,当然这个提炼不是基于功能复用级别的抽象,而是有足够高度的概括。它的背后是对设计模式中依赖管理的方式进行了一定程度的汇总、概括和抽象,以SOLID五原则+TDA+LoD等为主,其诠释了设计模式背后的分离关注点、缩小依赖范围和向稳定方向依赖等要素,从而提升代码的复用度,实现高内聚、低耦合的系统结构。

学习完设计原则再回头看看设计模式,就会有种看山不是山,看水不是水的感觉。但是这里再次强调设计原则很关键,不是说设计模式不重要,也不是说直接可以跳过设计模式来学设计原则,这其实是空中楼阁不可取。只有从设计模式过渡到设计原则,才能理解深刻和清晰,因为设计模式就是设计原则最好的载体,设计原则是沉浸在设计模式中的。(五)DDD

DDD(领域驱动开发),一是为了解决粒度更大系统分层分模块设计问题,二是为了拉近问题域和解决域直接的距离,缩小分析模型和设计模型的隔阂,增加架构和代码对领域知识的显性表示,从而增加系统的可理解性。DDD的本质就是一个“分”字。

首先对业务需求进行分解,分出一个个的bounded context,然后识别出一个个bounded context之间的关系,接着在bounded context中分领域,领域中再分成核心子领域和支撑子领域,之所以要划分bounded context之后再划分领域,是因为不同的领域概念在不同的bounded context中有不同的语义。比如,money在印钞厂这个bounded context中是有唯一标示的,但是在交易这个bounded context中就是一个值,不需要唯一标示。之所以不同,就是因为每个概念在bounded context中的语义不同,这样就会分出业务逻辑架构。

然后在每个领域中进行分层,利用仓储、工厂、服务、聚合、实体、值对象等分成应用逻辑架构,最后再确定这些逻辑单元的物理部署关系,形成物理架构。DDD就是通过不停的“分”把整个架构设计有条不紊地展开。DDD的整体就是一个合理“分”的过程,它为“分”提供一系列体系化、结构化的手段,让大家系统设计时“分”得更加合理和高效。“分”是开始,分完后能够合起来协作才是关键,这就要求我们在一个个子领域中,找到一个个领域模型,将这些模型能够表达出来,然后找出领域中模型之间的关系,通过这些关系将模型连接起来,形成协作关系。这样有了领域概念和领域关系后,领域知识就可以最大可能地显性化在架构和代码中,从而增加代码和架构的可理解性。(六)DCI

DCI可以看作是面向对象的一个补丁,面向对象中,更强调数据和行为的统一,重点解决面向过程编程中的贫血模型问题,即数据隐藏和数据封装不够的问题。但是设计方法往往都是解决一个问题,经验不足就会引入新的问题,根据职责划分对象后,与对象相关职责都会划入该对象中,这样极容易造成功能过度膨胀的万能类,表面上看增加了内聚,实际上增加了耦合。引入DCI框架后,通过引入Role概念,根据Role来细分职责,这样对万能类来说,职责就通过Role划分成一个个洋葱环的结构,从而使每个职责更加的单一。

另外就是DCI引入了Context概念,对象在某个特定Context中才扮演某种Role,通过Context和Role能够最大限度利用读者的背景业务知识,这种代码结构方式更加符合人的心智模型,更加自然和易于理解。(七)DSL

语言的发展,其实是个语义不断提升的过程。从机器语言到汇编语言,再到C、C++、java,再到目前丰富多样的python、go、scalar、erlang等语言,都是一个不断进行语义提升的过程,目的都是为了追求问题域和实现域语义匹配合理的过程。

当然,这些语言基本都是通用编程语言,而我们要解决的往往都是某个专业领域的问题,特定领域中问题的描述都是使用领域语言,语义层次都比较高,用通用语言直接开发代码和功能来解决这些业务领域问题,功能往往得以实现,但是经常会造成通用语言语义和业务领域语言层次高低相差太大,从而造成代码可理解性大幅降低的问题,也就是语义不匹配的问题,这种问题使代码可理解性和可维护性大幅降低。特别在异步消息处理、多线程等时间变换场景下,流程的处理会变得散乱不堪,代码阅读理解非常不易。一些事务、并发、伸缩、容错等横切关注点的问题也容易散落到代码各处,很难有个合乎问题本质复杂度的解决架构。

为了应对这个问题,我们可以针对特定业务应用场景,开发一门该领域的专用语言,当然开发一门语言还是很困难的,幸运的是我们只需要实现一门“窄”语言,只是完整语言的一个小片段,能够解决我们特定的业务问题即可,这个难度相对还是很小的。

设计DSL的前提是对领域能够进行深入的了解和认知,能够识别领域中的原子和组合关系,然后抽取一套离散化和符号化的助记符来描述领域业务,这些助记符可以使用宿主语言本身来进行,即内DSL;也可以使用文本助记符来描述,即外DSL。

针对这些DSL,通过操作语义进行一步步规约,就可以生成interpreter进行执行;也可以通过指使语义,转换成一门宿主语言进行编译执行;还可以通过指使语义转换成宿主语言能够识别的码流,用宿主语言自定义一个VM(虚拟机)进行执行。

这样整个子系统的开发就可以分为架构层和DSL应用层,从而使系统具备更好的可理解性和扩展性,同时自定义的interpreter、compiler和VM都可以扩展、替换,真正做到了业务领域语义层次的合理降解,这种基于语言级别的抽象,也为整个系统的长期演进提供坚实的基础。

要想成为一个“自由程序员”,成为一个高水平的代码设计人才或者职业选手,必须全面、系统、纵向了解代码设计发展的路线,把握软件设计的发展规律,并能够掌握甚至精通路线上每个节点,从面向过程、OO、设计模式、设计原则、DDD、DCI、DSL和系统架构设计一路走上来,环环紧扣、承上启下、第次鳞进,才能逐步成长为一个代码设计的专家。三、理论与演练结合提高软件设计能力

软件设计发展路线的这些内容属于系统工程理论的范畴,涉及对系统的本质复杂度的理解和把握能力,是鉴别是否为高层次程序员乃至架构师的重要标志,也是软件设计的核心和难点所在。

本次主题就是介绍如何通过系统介绍软件设计发展路线给有志于提升软件研发能力的软件开发人员以参考,以及我们沿着软件设计发展路线来快速提升软件开发人员能力的一些实践,比如我们沿着软件发展的节点开展的系列训练营,通过理论和演练结合的方式引导大家学习软件设计,在快速提升开发人员软件能力方面收到良好的效果。

总之,本文通过对软件设计方式方法的一些总结,能够启发大家找到自我快速持续提升的思路,希望能给大家职业发展提供一点启迪。第3章代码评审,从武侠式到军团式的转变程 鹏

作者姓名:程鹏

作者职务:敏捷教练

作者简介:十二年开发领域经验,五年架构设计经验,作为公司敏捷推行组的核心成员,2009年起推行敏捷。公司内部敏捷培训主讲师,多个版本的敏捷教练,参与多次架构级重构,指导大量模块级重构项目。在技术中感悟艺术,在工作中修炼心灵。

所在团队规模:200人一、背景

本文中的案例来自一个全新建立的Android团队,开发一个几乎全新的项目。笔者作为团队的技术教练,在几个月的时间内,密集性地参与了大约30场以上的代码评审,获得了很多经验教训,收益颇丰。二、导言

据说,“每个中国人的心中都有一个武侠梦”,如图3-1所示。在梦中,大侠的武功必须是最牛的,大家都束手无策的时候,大侠轻飘飘一出手,救民众于水火,还没来得及惊叹,大侠已绝尘而去,只留下天边的一片烟霞……图3-1 武侠梦

在真实残酷的军团作战时,如图3-2所示,战略、战术、武器装备、操控熟悉度才是决定战争胜负的关键,个体的战斗力高低只能成为统计样本上的一点方差波动。怎么取长补短、互相配合,是为将获胜之道,也是为兵生存之道……图3-2 集团军作战

武侠有它的艺术价值,军团也有它的现实价值,两者各安其分就好。如果弄错了场景,张冠李戴,那就是悲剧了。

现实中,却不乏以武侠式思维投身于军团式大规模软件项目的例子。本文以软件开发中的代码评审案例,来分析武侠式的问题,并介绍有效的改变措施。三、反思

说起代码评审,上至项目经理,下至程序员,一般都是认可它是一个有收益的开发活动。也就是说,大家内心假定,做代码评审比起不做代码评审,在项目整体上,能够节省时间。但是,在现实中,我们的代码评审真的能够做到这样吗?投入的时间是实打实地消耗掉了,评审中发现的问题,真的能够节省后期的开发时间吗?如图3-3所示。而且,节省出的时间,真的比代码评审的投入更大吗?图3-3 代码评审是否真的能节约时间

另外,代码评审也是一个协作模式的开发活动。对于团队协作的好坏,我们都有主观感受,有的团队合作氛围好,有的团队合作氛围差。这个好和差,我们可以粗略地定义为,团队合作氛围好是1+1的结果大于了2;团队合作氛围差就是1+1的结果小于了2。那么,现实中,我们的代码评审真的达到了1+1大于2的效果吗?

因此,在以下的案例中,我们会不断地反思以下三个问题:

•代码评审的时间成本有多高?

•发现问题的价值收益有多大?

•团队合作的氛围如何形成?四、案例

1.集体走读代码,检验逻辑

如图3-4所示,一群人坐在会议室,一行行走读代码,用人脑仿真电脑,运算出代码执行的结果,企图发现问题。这个情景,像不像一群侠客,切磋比武,最后武功最高者获胜。图3-4 团队聚在一起

一个开发团队,每个人员并不是完全通用的,而是各有分工,不同的人对不同的软件模块熟悉程度并不一样,往往是编写代码的人,对这一块儿最熟悉。在这种情况下,以此为题来比武,不仅有失公平,往往其他人连题目都不能完全理解透彻。想一想吧,编码者花了几天的时间思考分析,却期望评审人一两个小时就能理解,还比编码者看得更深,能发现问题,那不是要求评审人是绝世高手么?要么就只能期望编码者非常的差才有可能。

用人脑仿真电脑,检验代码的正确性,也存在着浪费。功能的正确性,通过测试用例就可以发现了,而且用例验证的方式更有效也更准确。用人脑验证正确性,并不如测试用例靠谱,人脑验完了,仍然需要测试用例验,这不是无谓的重复么?

军团式作战时,每个士兵应该怎么做?一个战斗小组,每个士兵有自己的专长和分工,有枪法准的,有掷弹远的,还有跑动快的,有善于侦察的,有善于突击的,还有善于掩护的,每个人不需要在别人的专长上胜人一筹,而是要互相补位,弥补对方的盲区。

高效的代码评审方式,应该是多发散发散,看看需求的盲点在哪儿,看看代码逻辑的盲点在哪儿,而不是检查代码作者已经实现的功能的正确性。如果发现逻辑复杂的代码,与其阅读代码进行逻辑分析,不如问问编码者是如何验证正确性的,编码者的验证方法往往能透露更多的信息。

在考虑性价比的条件下,代码评审应该关注:

•需求理解偏差;

•架构一致性,除正确性以外的其他软件属性;

•设计思路上的盲点,场景缺失;

•接口核对;

•可测试性,如何验证功能正确;

•简洁性,方案中应该去除人为复杂性,仅留下本质复杂性;

•后期难以检测的问题,如非法指针、内存泄露、资源没有释放等。

在考虑性价比的条件下,自动化测试应该关注:

•发现人对计算机运算结果的预期错误;

•逻辑复杂度比较高的功能;

•容易受影响、需要频繁回归的功能。

在考虑性价比的条件下,手工测试应该关注:

•用户界面方面的细节问题。

2.新手太多,开成了学习会

在新组建的团队,一般是新老搭配(见图3-5),如果老手与新手水平差距太大,代码评审往往开成了学习会。高山之巅,云雾之中,绝世高手一名,江湖幸运小虾米一名,秘传武功的镜头,和这个神似。图3-5 老手+新手的搭配

问题是,如果以学习为目的,甚至只是兼顾学习,代码评审很容易就和项目的利益背道而驰。项目与培训,有很多天然相反的东西。项目通常是时间紧迫的;而培训往往需要宽松的氛围。项目的第一目标是获取利润,投入的资源希望立马就会有收获;而培训的目标是提升能力,收益往往要通过长期的积累,才能缓慢释放出来。项目管理中,非常重视规避风险,因为风险往往成为项目失败的主因;而培训中,往往需要频繁的试错,学员才能有效掌握技能,这和人类学习的固有模式有关。以军事为喻,这种方式的代码评审,在平时集训时尚可理解,在作战时那纯粹要崩溃。

还有一个问题,以编码为职业的程序员,有多少人专研过培训技术?一个好的培训师,需要掌握演讲、沟通、辅导、认知心理等方面的知识,并且有相应量的训练。比如,我们在程序员做的培训中经常见到这样的场景,讲师讲得头头是道,学员有少部分聚精会神,但大部分注意力都不集中。这个现象,就是不了解人类的认知心理所致。知识性的东西,可以通过讲解来传授,儿童在学校中主要通过这种方式学习。面对知识讲解,成年人是缺乏耐心的。只有能解决他手头的问题,成年人才会密切关注;如果与当前工作没有直接的关系,则只会做泛泛地了解。单纯地讲解知识,培训就很容易变得沉闷乏味,听众精神涣散,学习效果根本无从谈起。

最后一个问题,那就是新手往往并不知道自己最急需的是什么。所以,笔者见到有些新手学习热情很高,什么代码评审会议都去旁听,投入的时间很多,每样东西也都了解了一点,却无法上手完成一个任务。这就是缺乏学习计划的后果。如果这样漫无目的地培养,什么时候才能给项目带来产出呢?

不是说代码评审绝对就与培训水火不容,无法调和。不过,显然的是,不通过极其精心的策划,很难找到兼顾两者的方案。如果既不了解如何高效评审,又不了解如何高效培训,妄图兼顾两者,结果就是两者皆失——既没有评审出代码的有效问题,新手也没有学到多少东西。

新建一个军团,如何紧急动员,快速提升战斗力?人类有几千年的历史可以借鉴,直接照搬吧。首先要给新兵划分兵种,先突击掌握一种武器;掌握武器的过程不是通过讲解,而是通过模拟或者实弹训练;作战时安排老兵手把手地带新兵;新兵一犯错,立马纠正,不能等到新兵完成一整套动作后才集中回顾。

翻译成软件术语,就是培养新手程序员的要点:

•明确每个新人的发展方向,明确当前的学习范围,避免铺得太大;

•知识性的东西不需要讲解,各人自学就行,否则时间开销太大,而对应的收效甚微;

•技能性的东西,架构的意图,更适合手把手的传递;

•要在编码前的构思阶段,就密集地审核,不能等到新人完成代码了才集中评审。

3.争论细节问题,相持不下

评审代码,开会讨论,难免有所争论。可是有些细节问题无关紧要,长时间拉锯,相持不下,对立两方筋疲力尽,宝贵的时间飞速流逝。两名武林高手,为了家国天下,决战于子夜(见图3-6),大家自然看得热血澎湃。但如果只是为了芝麻绿豆大点事呢?是不是有点行为艺术的感觉。图3-6 两大高手对决

如果争论的问题有价值,那么充分的技术争论,能提前发现设计风险,可是,如何判断争论的问题是否重要呢?有多个方案可供评选,能摆脱方案的局限性,正确取舍呢?笔者认为,这两个决策都应该是管理者来做(或者是实际承担管理者角色的项目经理、开发组长、架构师等人)。如果以自己不太懂技术为由,让几个平级的程序员自由争论、博弈、妥协,或者投票表决,得出的混杂方案往往还不如任何一个程序员单独做出的方案。管理者可以不懂技术,但要能判断谁懂技术。如果管理者不能正确决策,甚至放弃决策,那么过多的选择往往成为团队的灾难。

管理者看懂了人,看清了形势,做好了决策,是不是就可以当众直接宣布呢?也不行!直接宣布谁对谁错,非常容易打击程序员的积极性。在这个环节,管理者要给予失败方特别的心理保护,尽量用不明显的方式在私下完成决策。总的原则是,管理者内心的是非观要清楚,但对外界要适当地含糊。

武侠中的名门正派,内在强调自身品德修养,外在看重伸张正义。从微观层面看并没有什么错,但是,在宏观层面,人的数量一多,就无法避免不出状况,这就需要管理者运用手段去补救。到了军团这个数量级,面对紧急状态,那就只能依赖军事命令了。自组织、引导还是管控,没有绝对的优劣,无非是针对团队的规模和成熟度的选择。五、心理

以上从程序员的行为上分析了若干案例,要想深入理解这些行为的成因,以便从根源上改善,就得分析其背后的心理因素(见图3-7)。图3-7 无法忽视的心里压力

集体走读代码,检验逻辑。这个事情之所以发生,是因为在项目进度的压力下,编码者自然会倾向于节省自己的时间。让大家一起帮他检验逻辑,可以节省他的后期测试、解单时间。虽然前面我们也分析过,这样做在整体上其实很低效,但作为个体,编码者很难站在整体的角度来思考。所以管理者要识别整体性的浪费,引导代码评审会议的主题。

新手太多,开成了学习会。其心理因素是,盲目热衷于能力提升,新手不了解能力提升的计划性,老手不了解能力提升的有效性,忽视项目的利益。管理者要提升能力建设的针对性和有效性,纠正偏离目标的人员。

争论细节问题,相持不下。这是因为,程序员理工科的出身,对外在正确性的容易偏执,忽视其他人的感受。管理者要平衡内心的是非判断和外部的人际关系,做好润滑工作。六、启示

程序员这个行业崇尚技术,在技术上比较理性,但在团队合作上却比较感性,带有不少武侠式的思维。代码评审看起来像是纯技术的交流,其实仍然是人和人之间的合作,如果不重视程序员的心理,很容易出现各种误区。

在新组建的团队中,工作节奏和工作习惯还在形成中,代码评审是高互动、又很难标准化的开发活动,必须有合适的引导和控制,建立起各人良好的职业习惯,形成团队融洽的氛围,才能形成军团式的战斗力。

有一个兰博当然好,或者你立志于成为一个兰博也不错。但是现实中,我们往往在没有兰博的团队中战斗。有了正确的观念,有了精密的配合,没有兰博,一群普通的士兵一样能够赢得胜利(见图3-8)。所谓英雄,就是一群平凡的人,却做出了不平凡的事……图3-8 英雄群体第4章猿题库iOS客户端架构设计蓝晨钰

作者姓名:蓝晨钰

作者职位:猿题库iOS团队负责人

作者简介:多年移动客户端开发经验。2013年加入猿题库负责iOS客户端开发,见证了猿题库从无到拥有数千万用户的成长,也历经猿题库两年来五个大版本、数十个小版本的迭代。

猿题库是一个拥有数千万用户的创业公司,从2013年题库项目起步到2015年,团队保持了极高的生产效率,使我们的产品完成了五个大版本和数十个小版本的高速迭代。在如此快速的开发过程中,如何保证代码的质量,降低后期维护的成本,以及为项目越来越快的版本迭代速度提供支持,成为了我们关注的重要问题。这篇文章将阐明我们在猿题库iOS客户端的架构设计。一、MVC

MVC,Model-View-Controller,我们从这个古老而经典的设计模式入手。采用MVC这个架构的最大的优点在于其概念简单,易于理解,几乎任何一个程序员都会有所了解,几乎每一所计算机院校都教过相关的知识。而在iOS客户端开发中,MVC作为官方推荐的主流架构,不但SDK已经为我们实现好了UIView、UIViewController等相关的组件,更是有大量的文档和范例供我们参考学习,可以说是一种非常通用而成熟的架构设计。

但MVC也有它的坏处。由于MVC的概念过于简单朴素,已经越来越难以适应如今客户端的需求,大量的代码逻辑在MVC中并没有定义得很清楚究竟应该放在什么地方,导致它们很容易就会堆积在Controller里,成为了人们所说的Massive View Controller。二、MVVM

MVVM,Model-View-ViewModel,一个从MVC模式中进化而来的设计模式,最早于2005年被微软的WPF和Silverlight的架构师John Gossman提出。在iOS开发中实践MVVM的话,通常会把大量原来放在ViewController里的视图逻辑和数据逻辑移到ViewModel里,从而有效地减轻了ViewController的负担。另外通过分离出来的ViewModel获得了更好的测试性,我们可以针对ViewModel来测试,解决了界面元素难于测试的问题。MVVM通常还会和一个强大的绑定机制一同工作,一旦ViewModel所对应的Model发生变化时,ViewModel的属性也会发生变化,而相对应的View也随即产生变化。

同样,MVVM也有它的缺点:

一个首要的缺点是,MVVM的学习成本和开发成本都很高。MVVM是一个年轻的设计模式,大多数人对它的了解都不如MVC熟悉,基于绑定机制来进行编程需要一定的学习才能较好地上手。同时在iOS客户端开发中,并没有现成的绑定机制可以使用,要么使用KVO,要么引入类似ReactiveCocoa这样的第三方库,使得学习成本和开发成本进一步提高。

另一个缺点是,数据绑定使Debug变得更难了。数据绑定使程序异常会快速地传递到其他位置,在界面上发现的Bug有可能是由ViewModel造成的,也有可能是由Model层造成的,传递链越长,对Bug的定位就越困难。

同时还必须指出的是,在传统的MVVM架构中,ViewModel依然承载的大量的逻辑,包括业务逻辑,界面逻辑,数据存储和网络相关,使得ViewModel仍然有可能变得和MVC中ViewController一样臃肿。三、在两种架构中权衡而产生的架构

两种架构的优点都想要,缺点又都想避开,我们在两种架构中权衡了它们的优缺点,设计出了一个新的架构,起了一个名字叫:MVVM without Binding with DataController,架构图如图4-1:图4-1 MVVM without Binding with DataController架构图

ViewModel

先来看右边视图相关的部分,传统的MVC当中ViewController中有大量的数据展示和样式定制的逻辑,我们引入MVVM中ViewModel的概念,将这部分视图逻辑移到了ViewModel当中。在这个设计中,每一个View都会有一个对应的ViewModel,其包含了这个View数据展示和样式定制所需要的所有数据。同时,我们不引入双向绑定机制或者观察机制,而是通过传统的代理回调或是通知来将UI事件传递给外界。而ViewController只需要生成一个ViewModel并把这个装配给对应的View,并接受相应的UI事件即可。

这样做有几个好处:首先是View的完全解耦合,对于View来说,只需要确定好相应的ViewModel和UI事件的回调接口即可与Model层完全隔离;而ViewController可以避免与View的具体表现打交道,这部分职责被转交给了ViewModel,有效地减轻了ViewController的负担;同时我们弃用了传统绑定机制,使用了传统的易于理解的回

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载