软件设计要素初探:一些子主题

“软件设计要素初探” 一文,尝试从软件设计的总体角度,综合讨论了软件设计的各类要素。本文主要探讨一些稍小的设计子主题,主要包括:错误处理、结构性难题、总体与兼容、设计取舍、设计与重构、设计与质量、设计与细节、维护与扩展、测量技术。

html

错误处理

错误处理关乎系统的健壮性,且是全局性设计问题。一个总体的错误处理架构主要包括两部分:前端

  • 参数的严格校验、规范而易于理解的错误码和错误消息、无遗漏的异常捕获和转译、警告和错误日志输出;python

  • 一致的错误处理机制、不一样级别错误的处理策略。编程

第一部分并不须要复杂的技术,更可能是规范、细心;第二部分则须要有总体的考虑,错误处理策略是采用忽略、捕获并处理、捕获并转译、直接向上传递,须要仔细的思考和权衡。设计模式

通常来讲,关键环节出错,快速返错,毫不姑息和存侥幸心理;非关键环节出错,考虑是否能够忽略、设置默认值、兼容处理;依赖的底层服务出错,进行捕获并转译异常,并保留底层出错缘由供问题排查;在高层更能合适地处理,则采用向上传递异常。不管采起何种策略,打印合理的错误日志是必要之径。 可参阅文章:“如何使错误日志更加方便排查问题”安全

若过程当中涉及未完成的部分数据存储,必要的时候须要回滚或补偿措施、自动恢复机制。宁肯为空,也不返回错误数据,误导用户。多线程

错误处理体现了思考问题的缜密程度。一个流程里,有哪些可能出现的错误和异常场景? 对于每种错误和异常该如何有效地处理? 须要多仔细推敲,这可比智力题更有挑战。架构

数据健壮性

系统健壮性主要可分为流程健壮性和数据健壮性。流程健壮性是系统的某一环节出错了如何处理,是设计错误处理的一般考虑。数据健壮性是系统健壮性的另外一种情形。在订单导出中尤其重要。因为历史遗留问题,必然有一些订单有脏数据,有一些订单的数据不符合当前设计。这样,在订单导出时,要保证两个:a. 字段之间的导出相互独立,一个字段出错不影响其余字段导出; b. 不一样的订单和商品之间的信息导出相互独立,一个商品的出错不影响其余商品的导出;c. 脏数据也能合理显示。 不要吝啬 try-ctch 语句,捕获异常后不要吝啬加上一句 logger.warn 或 logger.error. 实际能够编写一个通用函数来捕获各类异常。Python可参阅文章:“python使用装饰器捕获异常”, Java 可以使用函数接口来实现相似功能。并发

防护式编程是保证系统健壮性的核心技巧。不假定系统的流程或数据是可靠的,而是假定它不可靠或调用失败或数据为空会怎样。对空值和临界值敏感,对于预计到的情形,宁肯if-else代码难看一点(可重构优化),也不要抛出NPE不利于问题排查;对于操做,宁肯报错,也不作出根据错误假定做出错误操做函数


结构性难题

软件开发中遇到的技术问题一般是结构性难题。现有的设计,每每建立一种相对可扩展的空间结构,便于维护者更好地实现常见需求和功能。与此同时,设计也创造了不可见的束缚。当需求难以知足时,就是发现设计束缚的最佳时机。

例子一,顺序结构简单可控,可是不能充分利用多核的潜力,一个核心工做的时候,其余核心干等着,这是束缚; 并发结构可同时释放多个核心的能力,束缚是难以控制多个核心同时工做的时序;对等应用结构可以避免单点故障,但是当多个对等应用同时操做一个不可重复的互斥资源时,则必须进行并发同步控制。

例子二,针对一对一的实体关联的设计,简单易用易理解;但是当实体关联扩展到一对多或多对多的时候,就显得不灵活了。 在设计之初,及时与产品沟通和仔细斟酌,为一对多或多对多的场景要预留余地。好比周期购发货。初期设计是一个订单一个周期购商品,每一个订单有多个期次的发货(期次对应订单维度);如今须要改形成一个订单多个周期购商品,每一个商品都有多个期次的发货(期次对应商品维度)。若是事先已经考虑到一个订单对应多个周期购商品的发货,而且期次对应商品,那么这次就比较天然地兼容多商品多期次发货了。

例子三,一个字段的同步一般来源于一个表,但是有的字段来源于多个表,须要根据不一样业务场景进行覆写。这就须要设计一个更具通用和可扩展的同步覆写机制来处理。或者从另外一个角度思考,若不但愿把同步作得太复杂,则须要应用程序提供一个可扩展的数据聚合机制,从不一样的数据源中获取和聚合数据。订单导出其实是一个数据聚合应用,须要从多个分散的业务表中获取数据,而这些业务表每每不是为了聚合而设计的。所以,导出的前置工做是设计通用可扩展的数据同步机制将所需数据从业务表同步到数据存储中心,而后设计通用可扩展的数据聚合机制获取数据,格式化并输出。

例子四,订单导出应用原来只支持固定字段的商品维度的CSV导出,如今须要支持指定字段集合的订单(须要去重)和商品两个维度的CSV/Excel导出,势必要重构原来的代码结构,支持更灵活的导出能力。可使用策略模式来分离和组合不一样的维度和格式导出。

经常为了简便而硬编码。硬编码当然能解一时之需,却容易诱发设计束缚。

几乎全部设计结构都具有某种优势,同时也具有某种束缚力。要让应用从束缚力中解脱出来,则必须借助多种结构的组合,取长补短,才能实现最终的大设计。


总体与兼容

总体与兼容是结构性难题的常见情形。软件功能之间每每不是孤立的。好比订单管理中,商家根据各类条件筛选后导出待发货的订单,而后使用批量发货的功能将订单发货,接着在订单详情页查看订单是否如预期发货,或者从新再导出一次看看是否已经发货。发货后还可能修改物流。这样,订单搜索/导出/发货/详情,其实是一个组合操做场景。当要支持新业务好比周期购时,这些都是必须总体化考虑的。单从某个功能模块的实现来讲并不困难,困难的是将四个组件的功能服务串联成一个紧密联系的逻辑严谨的总体。

兼容现有系统则是开发者最头疼的任务类型,尤为是系统已经发展到比较大的时候。事实上,每次代码变动都是一次现有系统兼容,只是有些兼容看起来使人深为苦恼甚至“不堪回首”。一般是因为原有设计对关注点没有解耦清晰,没有考虑到新需求的状况,没有预留足够的余地,兼容起来必须努力寻找空间,更像是一种英勇的突围行动。兼容系统的比较好的一种方式是,先分析清楚原有系统的业务逻辑和设计思路,而后尝试从一种更通用的角度去容纳原有的设计,有时会须要改动很多,须要可靠的回归测试来护航。所以,测试用例的充分覆盖程度,每每决定了系统可否大胆进行重构,实现更佳的更安全的设计优化,而不是简单增长一坨条件分支语句。


设计取舍

设计须要大量取舍,须要优秀的判断力。理解是一回事,设计和取舍是另外一回事。可以是一回事,应该是另外一回事。设计应该尽量简洁实用。哪些须要进行权衡呢?我的遇到的情形主要有三点可参考:

(1) 为了更优的体验而把事情弄复杂。现代产品设计强调用户体验,有时用力过猛,反倒容易忽视简单性带来的隐形良好体验。人容易犯错。复杂而完美的事情,尽管每每蕴含了不少“贴心的思考”,可这些“贴心的思考”在真实场景未必成立或只是偶尔出现,或者受到更多限制而没法施展,甚至起到反制做用,使人困扰和沮丧。相反,简单的事物,尽管不完美,却预留了不少空间,人们总能想到“奇妙方法”应对现实的阻挠因素并广为传播,而这些“奇妙方法”反而成了事实上的解决方案。所以,产品设计应尽可能简单而预留余地,流程尽可能直线式单一化且短小,消减分支和重试,减小诱发出问题的潜在因素。而对于系统设计而言,要尽可能作到关注点正交分离解耦,更好地支持产品的灵活性;而即便可以作到产品的灵活性,也要仔细斟酌是否必须作到如此。

举个例子,使用第三方配送,容许商家一个订单多个包裹配送、每一个包裹容许第三方配送失败后切换快递配送、容许商家屡次拆分包裹和重组包裹。这些要求诚然是比较合理的,也足够灵活,却忽视了兼容现有系统带来的连锁问题:快递配送后有修改物流,结合容许屡次拆分和重组包裹,意味着商家可反复修改物流,会增长发货和显示复杂度;商家拆分和重组包裹后,基于以前商品组合计算的支付金额可能不正确;前端展现上须要作很多兼容工做,容易出BUG。所以,灵活性当然很美好,实际上一则商家未必用得上那么多功能,二则某个功能失败后会衍生出更多的组合场景难以覆盖彻底,三则须要复杂度较高的设计和大量的研发测试成本,四则出了问题难以排查和修复,耗费商家、客服和技术研发的时间。实际上,一个订单有多个包裹是直观无疑的认识,而大多数场景只须要一个包裹配送;“容许第三方配送失败后切换快递配送”是产品针对“配送员不接单如何处理”的依托现有功能的解决方案,并非原始需求,尽管如此仍是合理的;“容许商家屡次拆分包裹和重组包裹” 是产品针对问题“多包裹配送及包裹配送失败如何处理”想出来的解决方案,也不是原始需求。 和产品同窗对需求时,要仔细识别哪些是原始需求和问题,哪些是用于解决需求和问题的带有方案性质的伪需求,还要仔细识别出兼容现有系统设计实现所引起的潜在问题。需求和问题是产品出,方案是研发出而通过产品承认。上线后,研发和产品一块儿关注效果和反馈,并改进优化。

(2) 过分设计可扩展性。预先思考过多,想作的更灵活,结果实际发展方向并不是所料,增长了系统设计复杂度、研发成本却没有收到实效。作订单导出时,为了根据不一样业务更灵活地输出报表字段,将报表字段按照业务划分,分别定义主字段集和不一样业务须要的子字段集,而生成报表的最终字段集须要主字段集合与若干子字段集组合而成,略显复杂,实际没用到。而按照指定字段集输出,倒是正确的思考方向。凡基础而必要的老是正确的方向。

真实需求是分别按照订单维度和商品维度来进行导出,不一样业务可指定不一样维度以及所需的报表字段集。新版设计是将订单维度的字段与商品维度的导出字段分别定义,将字段格式化定义与输出字段集指定解耦,将订单维度导出与商品维度导出作成两种不一样的策略,这样,不管按照订单维度仍是商品维度,不管指定什么字段集合,均可以按照指定需求输出报表。这是按照真实需求作出的有效设计。有效的设计应当是根据需求对原有设计结构进行重构优化,获得更灵活的设计,而不是开始就想不少,作得复杂。

(3) 设计不可偷懒。尽管设计要提倡节制,但是也不能偷懒,在必要的地方设计不足。一旦设计不足,很快就会受到“惩罚”。一个产品初始诞生时,经常从最简单的情形考虑,好比一对一的情形。而设计却要考虑,未来是否会发展到一对多的情形,并留下设计余地。若是心存侥幸以为不太会有这种可能或者掩耳盗铃,那么现实不久后就会来一次生动的教训课。

“如无必要,勿增实体”,或可做为设计基本原则之一。设想去掉它,是否是会运转得很是艰难或形成资损?

设计与重构

好设计不是一蹴而就的。好设计是可演化的。需求与问题是好设计的催化剂。持续地对设计进行小步重构优化,使设计更有活力和适应力。

对设计作小步重构,首要是抽离出关注点。推敲关注点的行为和目标,抽象成适当的接口并实现成小组件。抽离出关注点后,须要将关注点有序地融入到总体流程中。能够想象,应用由许多小组件组成,每一个小组件实现某个关注点的接口;而应用经过若干主流和支流串联起全部的小组件而实现其功能和服务。简而言之,“关注点-接口-组件-流-应用”。设计软件有时像设计游乐园,先构思各类微小景致(微组件),而后设计四通发达曲径通幽的小路(流)串联起全部的微小景致。

好比,原来只支持商品维度的CSV导出,如今须要支持订单维度和商品维度的CSV/Excel导出。首先抽离关注点。订单/商品维度是数据维度,而CSV/Excel是输出维度。数据维度关注将报表原始数据列表转化为对应报表字段的报表行列表;输出维度关注生成报表文件以及将报表行列表添加到报表文件中。根据关注点定义相应的维度策略接口和文件输出策略接口,而后分别实现订单/商品维度报表行生成组件和CSV/Excel文件输出组件,最后在导出流程中根据参数选择相应的策略来调用相应组件的接口,替换原来的实现。分离关注点,并定义关注点的合适接口,是设计与重构的基本功。

设计重构主要包含两个层面: 代码层面,使用设计模式进行对象交互结构的重构,使功能和服务实现更具柔性,容易修改和扩展;模型层面,使用深化和显化领域概念,优化存储设计,使得领域模型更加清晰。

设计与质量

好设计不只仅是知足软件功能,还必须知足预期软件质量指标。软件质量指标可参阅文章:“Web服务端软件的服务品质概要”。在进行软件设计时,务必明确软件预期的质量指望,并在功能实现以后进行衡量和评估。

设计与细节

问题是一系列关注点的聚合体,细节则是具体的小的关注点,设计则是针对系列关注点的一致性处理机制。“关注点分离”,是设计的基本原则。

为何会产生“细节”呢?当设计可以覆盖到问题的全部关注点时,细节就被囊括到设计所考虑的范围内;而一旦问题的关注点有所变化发展时,设计就没法覆盖到全部关注点,就产生了所谓的“细节”以及特殊处理,特殊处理带来的就是一堆堆的条件分支语句。为何设计没法覆盖到全部的关注点呢? 由于设计经常是一些通用套路,而这些套路每每是比较大的聚合体,很实用但未必灵活。 理想状况下, 设计也应该包含一系列正交的小的子设计,经过子设计的组合来构建更大的设计,覆盖更多的关注点,这样,细节及特殊处理就会更少, 代码也更具可维护性。

所以,从细节反推设计的不足,可促进设计的优化,覆盖更多的关注点。


维护与扩展

可维护、可扩展是软件可以持续健康发展的重要质量属性。可维护的要点是,持续对代码进行小步重构精练;可扩展的要点就是:识别、分离和组合关注点。在可扩展的基础上,进一步可实现可配置化。“Java8Map示例:一个略复杂的数据映射聚合例子及代码重构”展现了一个例子,经过分离和配置关注点(经过itemIdConf配置来关联各个表的对应数据),消除了一大段的if-elseif-elseif 语句的,开心!使用适当的配置,辅以枚举、Map,可有效改善或消除代码里大段的if-else, switch语句。


测量技术

从结构角度看软件,软件就是一种虚拟建筑。牢固的实体建筑须要仔细的测量和组装,持续稳定运行的虚拟建筑也须要仔细的测量。测量指标是设计可行性和可靠性的关键衡量。缺失测量指标的设计是不可信的。性能(响应速度与吞吐量)、占用资源(CPU与内存)、稳定性(成功率)、可用性(故障时长及比例)、峰值与平均负载能力(压力表现)是常见的软件测量指标。

最基本的方式是,循环调用服务接口若干次,统计平均/最大/最小响应速度;多线程并发调用服务接口,设置并发数及占用资源上限,测量其吞吐量;持续指定时间多线程并发调用服务接口,统计成功次数及成功率。压测则须要找QA同窗帮忙部署正规的压测环境,而后使用专门的工具和方法进行测量。

相关文章
相关标签/搜索