复习(3)

Programing for/with reuse:

源码级别复用:白盒复用:源代码可见,可修改和扩展

黑盒复用:源代码不可见,不能修改

模块级别复用:类与接口。继承,委托。

库级别复用:API、包。

系统级别复用:框架:一组具体类、抽象类、及其之间的连接关系

Liskov替代原理LSP:

子类型多态:客户端可用统一的方式处理不同类型的对象

LSP:更强的不变量;更弱的前置条件;更强的后置条件

强行为子类型化:前置条件不能强化;后置条件不能弱化;不变量要保持;子类型方法参数:逆变;子类型方法的返回值:协变;异常类型:协变。

协变、反协变:

协变:

父类型到子类型:越来越具体specific

返回值类型:不变或变得更具体

异常的类型:也是如此。

反协变:

父类型到子类型:越来越具体specific

参数类型:要相反的变化,要不变或越来越抽象

数组的子类型化

协变的:如T[]中的任意元素可以实T的子类

泛型的子类型化

不可协变:类型擦除

Vector<? extends 类型1> x = new Vector<类型2>();

类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类:

Vector<? super 类型1> x = new Vector<类型2>();

类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类

Delegation委托:

 委派/委托:一个对象请求另一个对象的功能,委派是复用的一种常见形式

Comparator和Comparable

Dependency委派:Delegation关系通过方法的参数传递建立起来

Association委派:Delegation关系通过固有的field建立起来

Composition: 更强的association,但难以变化:Composition是Association的一种特殊类型,其中Delegation关系通过类内部field初始化建立起来,无法修改

Aggregation: 更弱的association,可动态变化:Aggregation也是Association的一种特殊类型,其中Delegation关系通过客户端调用构造函数或专门方法建立起来

CRP原则

利用接口和delegation编程(CRP)

接口的组合:

interface Ducklike extends Flyable, Quackable {}接口的组合,定义了行为的组合

public class Duck implements Ducklike从组合接口中派生具体类

白盒框架的原理与实现

框架:一组具体类、抽象类、及其之间的连接关系

白盒框架,通过代码层面的继承进行框架扩展,通常用继承,子类,重载

黑盒框架,通过实现特定接口/delegation进行框架扩展,通常用委托/组合

可维护性的常见度量指标

圈复杂度:度量代码的结构复杂度。

代码行数:指示代码中的大致行数。

可维护性指数:计算介于0和100之间的索引值,表示维护代码的相对容易性。高价值意味着更好的可维护性。

继承的层次数:表示扩展到类层次结构的根的类定义的数量。等级越深,就越难理解特定方法和字段在何处被定义或重新定义。

类之间的耦合度:通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。

单元测试的覆盖度:指示代码库的哪些部分被自动化单元测试覆盖。

聚合度与耦合度

模块之间的联系是耦合,模块之间联系越紧密,耦合性越强。追求低耦合。

模块内部元素的联系是内聚,元素结合越紧密,聚合度越高。追求高内聚。

SOLID

SRP单一责任原则

责任:变化的原因。不应有多于1个的原因使得一个类发生变化。

OCP开放-封闭原则

对扩展性的开放

对修改的封闭:模块自身的代码是不应被修改的

典型的违反OCP的例子是大量的使用 if-else / switch-case 语句

LSP替换原则

子类型必须能够替换其基类型

派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异

DIP依赖转置原则

抽象的模块不应依赖于具体的模块

具体应依赖于抽象

ISP 接口聚合原则

客户端不应依赖于它们不需要的方法

“胖”接口具有很多缺点,它不够聚合

语法、正则表达式:

连接x ::= y z

重复x ::= y*

选择x ::= y | z

x ::= y? an x is a y or is the empty string

x ::= y+ an x is one or more y (equivalent to x ::= y y* )

x ::= [a-c] is equivalent to x ::= 'a' | 'b' | 'c'

x ::= [^a-c] is equivalent to x ::= 'd' | 'e' | 'f' | ... (all other characters)

. 匹配任何字符除了换行符

\d [0-9]

\s 匹配所有空格字符

\w 匹配所有字母和数字

\.,\( , \), \*  具体符号,转义

[]外的^表示从开始行开始匹配

使用正则语言表达式时,出现\要换成\\

设计模式adapter、decorator、strategy、template method、iterator/iterable、factory method、visitor

factory method

当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具创建的实例时,用工厂方法。

定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

Adapter

将某个类/接口转换为client期望的其他形式。

Decorator

为对象增加不同侧面的特性,对每一个特性构造子类,通过委派机制增加到对象上。

Strategy

有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例.

template method:

做事情的步骤一样,但具体方法不同,共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现.

使用继承和重写实现模板模式.

iterator/iterable:

Iterable接口:实现该接口的集合对象是可迭代遍历的.

Iterator接口:迭代器

Iterator pattern:让自己的集合类实现Iterable接口,并实现自己的

独特Iterator迭代器(hasNext, next, remove),允许客户端利用这

个迭代器进行显式或隐式的迭代遍历

Visitor:

为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT

Strategy vs visitor:

二者都是通过delegation建立两个对象的动态联系

但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。

而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。

区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

健壮性和正确性:

健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度

总是假定用户恶意、假定自己的代码可能失败

把用户想象成白痴,可能输入任何东西

正确性:程序按照spec加以执行的能力,是最重要的质量指标!

正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)

Throwable:

异常类都继承自Throwable类

Throwable两个子类Error和Exception

Error:程序员通常无能为力,一旦发生,想办法让程序优雅的结束。种类:用户输入错误、设备错误、物理限制

Exception:你自己程序导致的问题,可以捕获、可以处理。

分类:RuntimeException和其他

RuntimeException:运行时异常,是程序源代码中引入的故障所造成的

非运行时异常,是程序员无法完全控制的外在问题所导致的。

Checked异常、Unchecked异常

Unchecked 异常:Error + RuntimeException,不需要在编译的时候用try…catch等机制处理,可以不处理,编译没问题,但执行时出现就导致程序失败,代表程序中的潜在bug。

Checked异常:必须捕获并指定错误处理器handler,否则编译无法通过。从异常发生的现场获取详细的信息,利用异常返回的信息来明确操作失败的原因,并加以合理的恢复处理

Checked异常的处理机制:

程序员必须在方法的spec中明确写清本方法会抛出的所有checked exception,以便于调用该方法的client加以处理。

在spec中声明异常:@throw 关键字 。。。

当异常抛出时,方法中正常执行的代码被终止

如果异常发生前曾申请过某些资源,那么异常发生后这些资源要被恰当的清理

不管程序是否碰到异常,finally都会被执行(通常用于清理资源)

自定义异常类

包含更多“案发现场信息”的异常类定义和辅助函数

抛出异常的时候,将现场信息记入异常

在异常处理时,利用这些信息给用户更有价值的帮助

断言的作用、应用场合

断言:在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。

断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能(因为默认断言无效,否则断言很影响性能)。

可以应用在:内部不变量、表示不变量、控制流不变量、方法的前置条件、方法的后置条件。

断言主要用于开发阶段,避免引入和帮助发现bug,实际运行阶段,不再使用断言。

Java缺省关闭断言,如果要断言有效,要记得打开(-ea)。

Assertion vs. Exception:

断言用于Correctness,使用断言处理绝不应该发生”的情况,如果来自于自己所写的其他代码,可以使用断言来帮助发现错误

错误/异常处理用于Robustness,使用异常来处理你“预料到可以发生”的不正常情况,如果参数来自于外部(不受自己控制),使用异常处理。

断言和异常处理都可以处理同样的错误。

防御式编程的基本思路

对来自外部的数据源要仔细检查,例如:文件、网络数据、用户输入等

对每个函数的输入参数合法性要做仔细检查,并决定如何处理非法输入

类的public方法接收到的外部数据都应被认为是脏的,需要处理干净再传递到private方法——隔离舱,“隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用断言。

黑盒测试用例的设计

等价类划分

基于等价类划分的测试:将被测函数的输入域划分为等价类,从等价类中导出测试用例。

针对每个输入数据需要满足的约束条件,划分等价类。

每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合。

边界值分析

边界值分析方法是对等价类划分方法的补充。

在等价类划分时,将边界作为等价类之一加入考虑

基本类型的边界,如 INT_MAX、INT_MIN

集合中的第一个和最后一个元素

以注释的形式撰写测试策略

测试策略:根据什么来选择测试用例非常重要,需要在程序中显式记录下来

目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分

JUnit测试用例写法

测试用例:输入 + 执行条件 + 期望结果

测试的动机:让代码出错,出错越快越好

编写测试的过程:先写规约,再写符合规约的测试用例,

写代码、执行测试、有问题再改、再执行测试用例,直到通过它

测试驱动开发(TDD):将需求转化为具体的测试用例,然后软件经过改进,通过新的测试

测试覆盖度

代码覆盖度:已有的测试用例有多大程度覆盖了被测程序

代码覆盖度越低,测试越不充分,但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高

语句覆盖:每⼀条语句至少执行一次
分支覆盖:判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次
路径覆盖:每条可能执行到的路径至少执行一次

测试效果:路径覆盖 > 分支覆盖 > 语句覆盖
测试难度:路径覆盖 > 分支覆盖 > 语句覆盖

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值