软件构造考前复习整理(软构)

对错误理解的纠正:路径覆盖并不是我前面认为的if等语句中的每个表达式取值为真或假都被覆盖到,这个叫条件覆盖。路径覆盖和分支覆盖的不同:分支覆盖只要求每个if/while等语句的真假两种情况都被覆盖,那么如果一个while里有一个if语句,只要我们的测试用例能让这个while循环中的if为真过至少一次且为假过至少一次,我们就实现了分支覆盖,但想要路径覆盖就必须所有可能的路径都被覆盖到,如果有n次循环,且每种真假组合的情况都有可能的话,那就有2^n种路径。

软件构造的阶段划分和各阶段的构造活动

广义的软件构造包括programming(编码),build(构建),code review/static code analysis(代码评审),Dynamic code analysis/profiling(性能分析),Testing(测试),Debugging(调试),Refactor(重构),是一个环状结构。狭义的软件构造只包括build的过程。
programming:编程语言,如JAVA;建模语言,如UML;配置语言,如XML,JSON。
code review/static code analysis:结对评审,走查,正式的评审会议,自动化评审。利用工具进行静态代码分析(CheckStyle,SpotBugs,PMD for JAVA)
Dynamic code analysis/profiling:VisualVM,AppPerfect JAVA Profiler
Debugging and Testing:v字测试:
在这里插入图片描述
调试往往是成功测试的后续环节,调试是最后的手段。
Refactor:在不改变功能的前提下优化代码
Build:从build-time到run-time的转化。build的工具:maven,gradle,Eclipse IDE等。有了build工具还需要有build Script(类似于编程语言,告诉工具如何一步一步build)
广义的软件构造过程:Design->Programming/refactoring->Debugging->Testing->Build->Release
狭义的软件构造(Build):Validate(验证)->Compile->Link->Test->Package->Install->Deploy

Git相关

SCI:软件配置项,软件中发生变化的基本单元,如文件
SCM:软件配置管理
基线:软件持续变化过程中的"稳定时刻",如对外发布的版本
CMDB:存储软件各配置项随时间发生变化的信息和基线。其实就是仓库
VCS:version control system ,版本控制系统。分本地VCS(仓库存储在本地),集中式VCS(仓库存储于独立服务器),分布式VCS(仓库存储于独立服务器和开发者的本地机器)
.git目录存储本地的CMDB,工作目录就是本地文件系统,暂存区隔离工作目录和Git仓库。每个文件有3种状态:已修改,已缓存(通过add指令),已提交(commit指令)。
Git存储发生变化的文件,而非代码行,不变化的文件不重复存储。
git status回答当前哪些更新还没有暂存,哪些更新已暂存好准备下次提交。
git diff:工作目录中当前文件和暂存区快照之间的差异
git diff --cached:已缓存的文件和上次提交的快照之间的差异。
git commit -a:之前已经跟踪过(add)的文件,直接暂存并提交,即之前add过,修改后不用重新add,直接commit -a。
git rm:从暂存区域、已跟踪文件清单、和工作目录中删除指定文件

外部质量指标

正确性,鲁棒性,可拓展性,可复用性,兼容性(compatibility),高效性,可移植性(portability),易用性,功能性,及时性,可验证性,完整性,可修复性,经济性

内部质量指标

LOC:代码行数
CC:圈环复杂度
可读性,易理解性…

软件构造中5个主要的质量标准

easy to understand
cheap for develop(复用性)
ready for change ,easy to extend(可维护性)
safe from bug(鲁棒性,正确性)
efficient to run

4-8讲内容

基本数据类型只有值,没有ID,存放在栈中。对象数据类型既有值又有ID,存放在堆中。
类型检查:静态检查好于动态检查好于无检查。静态检查是关于类型的检查,不考虑值;动态检查是关于值的检查。
snapshot diagram:基本数据类型用一个箭头指向,对象数据类型除了被箭头指向还要被一个椭圆圈起来。不可变对象使用双线椭圆。不可变引用使用双线箭头。
list.add(s1)是把s1(一个String类型)指向的对象加到list里,而不是把s1这个引用加到list里,s1后面做了+操作指向了其他对象的话,list里放的仍然是原来的对象。

5个OO的设计原则:SOLID

SRP: Single Responsibility Principle:单一责任原则
OCP: Open-Closed Principle:开放-封闭原则 对拓展开放,对修改封闭
LSP: Liskov Substitution Principle :Liskov替换原则
DIP:Dependency Inversion Principle:依赖转置原则 客户依赖抽象,不能依赖于具体。
ISP:Interface Segregation Principle:接口聚合原则

创建模式

工厂方法

感觉没啥好说的。

结构模式

适配器模式

我们前面使用的继承和委托机制都是适配器模式,适配器模式的一个很形象的描述就是你可能需要把一个三项插头插到一个双项插座上,这时就需要一个三项插头能插进去,然后伸出双项插头的东西,这就是适配器了。反映到我们的程序设计中就是客户想使用的接口和实际实际实现功能的接口不兼容,这时我们新增加一个接口,与用户想使用的接口兼容,并通过委托或继承机制来借助实际实现功能的接口完成任务

装饰器模式

为对象增加不同维度的特性,每一个特性构造子类,通过委托机制增加到对象上,以递归方式实现。
我发现这和我实验3中选择的第5种局部共性的处理方式是差不多的。不同在于我在实验中选择的方式是接口是独立的,和基类IntervalSet没有关系,只是内部进行一个委托;但装饰器模式是先写一个实现基类接口(假设是Stack)的抽象类装饰器(设为StackDecorator),并在它的里面定义一个protected的基类接口(Stack)变量stack,然后每一个维度的接口都拓展这个装饰器接口(StackDecorator),StackDecorator和它的子类的构造函数里都要有一个Stack参数i,然后在构造函数里写this.stack=i。来完成所有的装饰器都指向同一个实体。
最后我们想具体使用某种装饰状态的变量时可以进行如下操作:

IceCream a = new PlainIceCream();
IceCream b = new CandyTopping(a);
IceCream c = new PeanutTopping(b);
IceCream d = new NutsTopping(c); 

我觉得这也是我实验中选择的方法不能重写insert而装饰器模式可以随意重写的原因。实验中每一个维度的局部共性的初始化使用的是this,虽然局部共性里实现时使用的是IntervalSet,但就像上一篇文章提到的那样,它实际执行哪个类的方法,和它被定义为的类型无关,和它里面实际存的类型有关,所以就相当于重写的insert委托了它自己,这当然不是我们想看到的;而decorator模式构造函数里写的是this.stack=i,然后在使用时依次加装饰,每次一个新的装饰里重写了某个方法时,如果它委托了IceCream的方法,那它委托的实际是加了前面所有装饰的一个对象,这形成了一种链状结构,是没有递归调用的冲突的。
装饰器模式是在运行阶段组合特征,继承是在编译阶段组合特征。

行为模式

策略模式

解决的问题:当有多种不同算法实现同一任务,client需要根据需要动态切换算法,而不是写死在代码里。
解决办法:为不同的算法构造抽象接口,在运行时利用委托机制动态传入用户倾向的算法实例。

模板模式

解决的问题:做事情的步骤一样,但具体方法不同。
解决办法:共性的步骤在抽象类内公共实现,差异化的步骤在各个子类内实现。模板方法定义一个算法的步骤,并允许子类为一个或多个步骤提供实现。
广泛应用于框架中。

迭代模式

解决的问题:客户希望对放入容器或集合类的一组ADT对象进行遍历访问,而不关心容器的具体类型,即不管对象被放进哪里,都应该提供同样的遍历方式。
解决办法:让字节的集合类实现Iterable接口,并实现自己的Iterator迭代器(包括hasNext,next,remove3个方法),这个迭代器是实现

Iterator<E>

接口的。所以注意Iterator是个接口而不是实现类。

visitor模式

对特定类型object的特定操作(visit),在运行时将两者动态绑定到一起,该操作可以被灵活修改,无需更改被visit的类。本质是将数据和作用于数据上的某种操作分离开来。为ADT预留一个将来可拓展功能的接入点(accept函数),外部实现的功能代码可以在不改变ADT本身的情况下在需要时通过委托接入ADT。
定义一个项目元素的接口,里面有一个accept函数,参数是一个visitor,类通过实现这个接口来表明自己是可visit的,实现的内容就是委托给这个visitor来做,即调用它的visit函数。然后我定义一个visitor的接口,它里面有针对各种类的visit函数,然后我在这个接口的实现类里去实现visit的内容,这样就实现了一个类和对它的visit分离。
visitor和strategy的区别:visitor模式外部定义的操作与ADT关系不大,只是对ADT的访问,ADT只需开放accept(visitor)函数即可。而strategy是把ADT内部的一些重要功能委托给外部来做。

鲁棒性和正确性

对鲁棒性和正确性的度量

从外部观察者角度:MTBF,平均故障间隔时间(平均无故障运行时间),是指相邻两次故障之间的平均工作时间。MTBF用于描述可修复系统的平均无故障运行时间,MTTF(故障前平均时间)描述不可修复系统的故障前平均时间。
内部观察角度:残余缺陷率,即每千行遗留bug数量。

unchecked和checked异常

Error和Exception都是Throwable的子类。Error的所有子类和Exception里的RuntimeException(运行时异常,是程序源代码引入的故障造成的,在代码中提前验证可避免。而非运行时异常是程序员无法控制的外在问题导致的,即使提前验证,如文件是否存在,也无法完全避免失效发生)的所有子类都是unchecked异常,剩下的是checked异常。
Error是内部错误,程序员无能为力,一旦发生,只能让程序优雅的结束。我们写的程序中也不应抛出Error对象。
unchecked异常编译器不会帮我们检查,而会在运行时发生错误而使程序停止,比如数组越界或parseInt的参数字符串不符合格式。
checked异常编译器会帮我们检查:比如我们在方法A的声明中用throws说明了它会抛出某种异常,那么方法B调用方法A的话,就要也用throws声明或用try catch捕获该异常。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值