《后端技术面试 38 讲》学习笔记 Day 02
08丨软件设计的方法论:软件为什么要建模?
原文摘抄
所谓软件建模,就是为要开发的软件建造模型。模型是对客观存在的抽象,我们常说的数学建模,就是用数学公式作为模型,抽象表达事务的本质规律
通过建模,我们可以把握事物的本质规律和主要特征,正确建造模型和使用模型,以防在各种细节中迷失方向。
4+1 视图模型认为,一个完整的软件设计模型,应该包括 5 部分的内容:
- 逻辑视图:描述软件的功能逻辑,由哪些模块组成,模块中包含哪些类,其依赖关系如何。
- 开发视图:包括系统架构层面的层次划分,包的管理,依赖的系统与第三方的程序包。开发视图某些方面和逻辑视图有一定重复性,不同视角看到的可能是同一个东西,开发视图中一个程序包,可能正好对应逻辑视图中的一个功能模块。
- 过程视图:描述程序运行期的进程、线程、对象实例,以及与此相关的并发、同步、通信等问题。
- 物理视图:描述软件如何安装并部署到物理的服务上,以及不同的服务器之间如何关联、通信。
- 场景视图:针对具体的用例场景,将上述 4 个视图关联起来,一方面从业务角度描述,功能流程如何完成,一方面从软件角度描述,相关组成部分如何互相依赖、调用。
在实践中,通常用来进行软件建模画图的工具是 UML,建模的时候,也不一定要把 5 种视图都画出来。因为不同的软件类型其特点和设计关注点各不相同,只要能向相关人员准确传递出自己的设计意图就可以了。
UML,即统一建模语言,是目前最常用的建模工具,使用 UML 可以实现 4+1 视图模型。
此外,语言还有个特点,就是有方言,而对于 UML,就我观察,不同公司,不同团队使用 UML 都有自己的特点,并不需要拘泥于 UML 的规范和语法,只要不引起歧义,在使用 UML 过程中对 UML 语法元素适当变通正是 UML 的最佳实践,这正是 UML 的“方言”。
架构师的核心工作就是做好软件设计,软件设计是软件开发过程中的一个重要环节。
使得软件在开发之初就对软件未来蓝图有个清晰的认识,从而使整个软件开发过程处于可控的范围之内
心得体会
- 架构师不仅是将业务领域抽象为模型,也是业务、产品、技术人员之间的强扭带,从宏观的角度把控。
- 4+1视图有较多重复部分,实际中会有一定省略,但是全的话会更立体。
工作体验
- 工作中往往是前期催的紧,文档其实都是走流程,内容较多架子经不起推敲。想了下,也没有专业的架构师进行评审,编写。当然流程也不够合理。
- 架构师还是一个实践出真知的岗位,光靠理论知识可能会缺点火候–软考差2分记
09丨软件设计实践:如何使用UML完成一个设计文档?
原文摘抄
- 软件建模与设计过程可以拆分成需求分析、概要设计和详细设计三个阶段。
- UML 规范包含了十多种模型图,常用的有 7 种:类图、序列图、组件图、部署图、用例图、状态图和活动图。
- 根据场景的不同,灵活在需求分析、概要设计和详细设计阶段绘制对应的模型图,可以实实在在地做好软件建模,搞好系统设计,做一个掌控局面、引领技术团队的架构师。
心得体会
- 软件建模的设计文档中很重要,也需要灵活使用,目的就是更好的讲清软件的需求、设计,降低语言沟通成本,降低沟通难度,更加直观。
工作体验
- 有参与过小作坊式的、规范的、初步形成规范的软件开发过程。小作坊是真的黑,需要所有人员“精通”才能玩的转;规范的流程就会比较漫长,工作量也大,但是按部就班不会出大问题,都是有据可依;初步形成规范的就能避免一些大坑,例如需求理解完全和开发出来的并不是一个东西,但是在实现上经常会有一些各种问题,因为详细设计这一步和概要设计合并了,且书写文档不规范,甚至有人拿写好的代码贴进来作为设计文档,可读性并不高。
10 | 软件设计的目的:糟糕的程序员比优秀的程序员差在哪里?
原文摘抄
- 如果仅仅是看过程,糟糕的程序员和优秀的程序员之间,差别并没有那么明显。但是从结果看,如果最后的结果是失败的,那么产出就是负的,和成功的项目比,差别不是 100 倍,而是无穷倍。
- 僵化性:软件代码之间耦合严重,难以改动,任何微小的改动都会引起更大范围的改动
- 脆弱性:比僵化性更糟糕的是脆弱性,僵化导致任何一个微小的改动都能引起更大范围的改动,而脆弱则是微小的改动容易引起莫名其妙的崩溃或者 bug,出现 bug 的地方看似与改动的地方毫无关联
- 牢固性:是指软件无法进行快速、有效地拆分。想要复用软件的一部分功能,却无法容易地将这部分功能从其他部分中分离出来。
- 粘滞性:需求变更导致软件变更的时候,如果糟糕的代码变更方案比优秀的方案更容易实施,那么软件就会向糟糕的方向发展
- 晦涩性代码首先是给人看的,其次是给计算机执行的。如果代码晦涩难懂,必然会导致代码的维护者以设计者不期望的方式对代码进行修改,导致系统腐坏变质。
- 人们为了改善软件开发中的这些问题,使程序更加灵活、强壮、易于使用、阅读和维护,总结了很多设计原则和设计模式,遵循这些设计原则,灵活应用各种设计模式,就可以避免程序腐坏,开发出更强大灵活的软件。
心得体会
- 软件开发不仅要看过程,更重要的是看结果,但是看未来才是设计模式。
- 经历多次需求变化还没有什么大变化的代码,从设计之初应该下了大功夫
工作体验
-
有幸遇到过几种糟糕的程序员:
一种是半年经验用了十年的老程序员,写了3个月的代码,被专家评审5分钟就说全部换人重写,甚至重写后过了2个月,我还在一个BUG的调试中发现是他修改了代码。(他在做坏事的一路上很有经验,他告诉我复制完代码要写别人的名字,要不是git,我都要去找顶上的作者了。)
一种是真的很菜,不能够胜任当前的工作,是项目组的技术负责人说“项目组总是需要几个搬砖的”,才准许进入项目组。而他功能写不出来,就让他帮忙修改引入项目的存量soanr,又导致一些致命Bug。填他的坑真的完全是负面影响。有意思的是,后续盘点发现他并不是乙方的开发人员,而是又经过两层外包中间商包装过加入进来的。这样的经历让我知道,作为面试官,在工作能力上是不能够有妥协的,宁缺毋滥,除非候选人的学习积极性等表现出来能让人信服进入项目组之后可以快速适应。
11丨软件设计的开闭原则:如何不修改代码却能实现需求变更?
原文摘抄
开闭原则
开闭原则说:软件实体(模块、类、函数等等)应该对扩展是开放的,对修改是关闭的。
粗暴一点说,当我们在代码中看到 else 或者 switch/case 关键字的时候,基本可以判断违反开闭原则了。
实现开闭原则的关键是抽象。当一个模块依赖的是一个抽象接口的时候,就可以随意对这个抽象接口进行扩展,这个时候,不需要对现有代码进行任何修改,利用接口的多态性,通过增加一个新实现该接口的实现类,就能完成需求变更。不同场景进行扩展的方式是不同的,这时候就会产生不同的设计模式,大部分的设计模式都是用来解决扩展的灵活性问题的。
开闭原则可以说是软件设计原则的原则,是软件设计的核心原则,其他的设计原则更偏向技术性,具有技术性的指导意义,而开闭原则是方向性的,在软件设计的过程中,应该时刻以开闭原则指导、审视自己的设计:当需求变更的时候,现在的设计能否不修改代码就可以实现功能的扩展?
心得体会
- 开闭原则我想应该在软件之前就存在这种思想吧,对机械制造来说,修改已有的机器结构,生产设备,影响的成本应该是巨大的。
- 开闭原则的关键是抽象,核心思想应该是兼容性。把具体的事务抽象,站在高的角度,看的更多。就像高阶的领导发出的是号召,低阶的领导者制定规范、边界、行为准则等,最后由不同的执行者,根据当地实际情况,在思想指导的框架上,制定接地气的不同的行动方案。
- 开闭原则执行的好坏,与一开始了解的是否全面,看到的是否更长远有关,本章中,反复修改设计模式,才使得一次次的需求增加都满足开闭原则。
工作体验
- 做一款无码的平台,对抽象要求高。能够发现初级的开发者,接到需求后,形成的开发文档、代码,其命名都是十分具体的,甚至会图方便,在模板方法类、抽象父类中直接增加具体的方法。这种行为不仅破坏了开闭原则,也使得后续的阅读,修改会遭到具体的行为的制约。这就需要高级工程师在设计评审,代码评审时多加注意,对关键代码的改动,不得半点马虎。
- 在开闭原则的实施中,落实到具体代码后,我们往往会将一些成员变量、方法,修改其可见性,可修改性。对外提供服务参数化。
12 | 软件设计的依赖倒置原则:如何不依赖代码却可以复用它的功能?
原文摘抄
依赖倒置原则是这样的:
- 高层模块不应该依赖低层模块,二者都应该依赖抽象。
- 抽象不应该依赖具体实现,具体实现应该依赖抽象。
依赖倒置原则通俗说就是,高层模块不依赖低层模块,而是都依赖抽象接口,这个抽象接口通常是由高层模块定义,低层模块实现。遵循依赖倒置原则有这样几个编码守则:应用代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。不要继承具体类,如果一个类在设计之初不是抽象类,那么尽量不要去继承它。对具体类的继承是一种强依赖关系,维护的时候难以改变。不要重写(override)包含具体实现的函数。
软件开发有时候像变魔术一样,常常表现出违反常识的特性,让人目眩神晕,而这正是软件编程这门艺术的魅力所在
心得体会
-
依赖倒置的原则,说的更普通就是依赖的东西都改成定义的接口。
-
定义的接口,属于掌握权的获取,由依赖(使用)这个接口的高层模块决定这个接口的具体行事规范。接口的具体实现者,只能按照接口、高层模块定下的标准进行实施。
-
所谓依赖倒置,就是标准权的归属在于高层模块。具体实现的底层模块是谁对于高层模块并不关注。就像公司高管只关心项目完成,是谁做的不重要。就像我们只关心JDK的版本标准即可,具体由哪个厂商发型的jvm实现往往并不重要。
制定行业的标准,就像定义了接口。就像票交所定义好了一整套报文的规范,每个银行去对接时,只要遵守规范,那它的实现厂商,是恒生,是力铭,是金证,还是其他,对票交所这种“高层模块”,它并不care。
依赖倒置,说明是谁在承载谁。是各家银行在票交所的平台上进行票据交易,票交所少了一家银行照样没问题。项目里,Spring少了一个业务系统毫无影响,但是业务系统却是依赖Spring提供的组装能力。
-
依赖倒置的转换就像,从前打车,你需要在路边等到有车经过,你是依赖车的;现在打车,我们是网上下单的,这辆车不经过,不接单,总是会有车接单的。这样的模式改变,主动权就从司机到了顾客手上。这也是核心的权力交替,也是符合社会现象的。
工作体验
- 依赖倒置工作中其实一直在用,定义接口其实就是这样的一种行为。
- 前端通过HTTP接口的定义,依赖了后端。
- 后端WEB层通过Service接口依赖具体实现,Service通过DAO接口依赖存储。当然,在DDD中,会更加复杂,而DDD也是依赖倒置的一种很好的模式。在DOMAIN层,完善的定义了自身的一切业务,并实现了逻辑,对外仅通过接口暴露、依赖。