软件构造期末拾遗:1——6章

三维八视图

 第二章:Testing and Testing-First Program

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

测试用例根据spec编写,在实现功能之前就把测试用例编写完成。

重点:通过partitioning确定测试用例

1.笛卡尔乘积形式

2.每个取值至少覆盖一次

第三章:Software Construction Process and Configuration Management

1.软件开发生命周期SDLC

2.版本控制

利用Git进行版本控制

Git的工作原理:

Git中的对象图:

当产生新提交时,对于非发生变化的文件,仍然令commit的树指向这个文件避免重复存储。

第四章:Data Type and Type Checking

1.Data type in program languages

Data type

类型:一组values,和能在这些values上进行的操作。(统称为类型)

变量:被命名的地址,用以存储某一特定类型的值。

Java中的两大数据类型:基本数据类型和引用数据类型

Java有八种基本数据类型(primitive data type):

        byte、short、int、long、float、double、boolean、char

所有对象均可被视为引用数据类型,以及String等基本数据类型的封装。

Operators & Operations

operator:操作符是执行简单运算的符号

operation:操作时接受输入并产生输出的函数

2.Static VS. Dynaminc data type checking

static checking

静态检测:检测类型与值是否匹配。

所有变量的类型在声明时已经知晓,例如:

        List<String> str = new ArrayList<>;

str在静态检测时确定类型为List<String>,至于ArrayList的类型需要程序执行到赋值这一步时才会变更。因此如果在编译阶段尝试调用ArrayList中存在当List中不存在的方法,则会无法通过静态检测。

dynamic checking

在运行时才能发现的错误。

3.重点:Mutability  and Immutability

赋值:赋值的本质是在内存的特定区域里开辟一段空间,写入特定值,并将该空间与变量名(引用)关联到一起。

Immutability and mutability

可变:与变量名相关联的内存空间中的值可以被修改

不可变:上述的值不可被修改,重新赋值代表着另外开辟一处内存空间,将新的值存入。

改变一个变量的值:将该变量当前指向的存储空间中写入另一个值

Immutability

immutable type:一旦被创建,其值就不可被改变,但是引用指向的空间可以改变。

immutable reference:不允许改变引用指向的地址

可变对象与不可变对象有数据类型本身决定,可变引用与不可变引用有final关键字决定

Advantages of mutable types

对不可变类型进行频繁修改的话会产生大量临时copy,浪费内存和时间

对可变类型可以直接进行修改。

Risks of mutable types

风险实例1:将可变值作为参数传递给方法

潜在错误,不会引起报错。且难以被发现。

风险实例2:将可变值返回给客户端

4.Snapshot diagrams

快照图:描绘运行阶段某一时刻的状态。

最基本的快照图显示一个变量名和一个指向变量的值的箭头:

mutating values vs. reassigning variables

改变值 和 重新分配

不可变类型只能被重新分配,不能被改变值。

注意:在Java中,在方法内部为一个已经存在的变量重新声明并不会开辟另一个新的内存空间。它仅仅产生一个别名,并指向已存在的内存空间。

如果是按引用传递,那么n实际上就是i,n的更改就是i产生的更改。

当对象有多个引用(别名)时:

5.mutation and contracts

mutable objects make contracts complex

对同一个可变对象多次进行引用,可能导致程序中的多个位置都依赖这个对象,也都有可能改变这个对象。

从spec的角度,契约就不能仅仅在某一处生效了。涉及可变对象的契约依赖于使用它的别名的每个人的行为。

mutable objects reduce immutability

可变对象使得客户端与实现者之间的契约变得十分复杂,并减少了它们更改的自由。

例如:构建一个程序,在MIT的数据库中查找用户并返回前9位的标识符

如果客户想模糊ID前五位以保证隐私:

而实现者想提高运算速度,引入cache以记录已经查找的用户名:

这两处修改会产生一个微妙的错误:如果用户查找并返回一个ID时,客户端与实现者的cache都指向了同一个同一个ID。这意味着客户端的模糊函数实际上覆盖了cache中的内容。因此,如果未来再次调用getMitId访问同一个用户,返回的将是删除了前五位的ID。

为避免这种情况,必须在规约中加入:用户不允许修改返回的ID

这时非常麻烦的规约。

解决方法可以通过防御性拷贝,但是当数据量十分大时将会产生大量的资源浪费。

6.补充:Java Point and Pass by Value

第五章:Designing Specification

1.Specification structure

Behavioral equivalence:行为等价:用一种实现代替另一种实现。

规约可以在不必了解代码的情况下获知模块的功能(信息隐藏),充当客户端与实现者之间的防火墙。

structure of specification

抽象来说,spec包括

        函数签名:给出函数的名称、参数类型、返回类型

        require子句:对参数的附加限制

        effects子句:描述函数的返回值、异常和其他情况

以上三部分构成函数的前置条件(precondition)和后置条件(postcondition)

前置条件:客户端的义务。

后置条件:函数实现者的义务。函数被调用后应具有的条件。

如果前置条件在调用时成立,那么后置条件必须在调用完成时成立。

spec in Java

规约应包括函数的参数和返回值,但不应该谈论方法内部的局部变量和私有变量。

2.补充

Avoid null

null:变量不指向任何对象。在Java中,无论一个变量是什么类型,它都可以是null。

注:null与空串、空数组并不一样。空串和空数组本质上仍是指向其数据结构的引用,仍然可以调用类的方法或变量。而null则什么也不指向。

约定:在参数和返回值中不允许出现null,除非spec中明确说明null的含义及处理方法。

Deterministic vs. underdetermined

确定的规约和不确定的规约:

这个函数的规约是确定的:

        当输入满足前置条件时,输出完全确定

        只能有一个返回值和一个最终状态

        对一个有效输入不能出现多个有效输出

另一个不确定规约:

如果val不止出现一次,它没有明确返回哪一个索引值。这个spec允许对相同的输出产生多个有效输出。

Declarative vs. operational

陈述性规约:仅给出最终结果的状态及其与初始状态的关系

操作性规约:给出函数执行的一系列步骤。

Stronger vs. weaker

如果规约S2的实现集是S1的子集,那么S2比S1更强。

更强:约束更多、更紧

更弱:约束更少、更松

如果S2比S1更强,那么:

        S2的前置条件比S1更弱:减少客户的负担

        S2的后置条件比S1更强:增加实现者的承诺

那么S2的所有实现都可以代替S1,那么S2这个规约可以代替S1。

第六章:Abstract Data Type

1.User-defined Types

用户定义类型

ADT的本质:操作本身完全定义了数据类型,从数据结构、内存存储、具体实现中抽象出来。

例如:定义ADT:Bool有如下操作:

                true :Bool; false:Bool

                and:Bool *Bool -> Bool

                or:Bool*Bool->Bool

                not:Bool->Bool

这种ADT几乎可以用任何数据类型表示。它仅由其操作定义,无关其表示方式。

2.Classfying Types and Operations

抽象类型的操作分为以下四类:

  • creators:创建这个类型的新对象。creator可以接受其他类型的值作为参数,但不能接受该类型的对象。
  • producers:创建这个类型的新对象。当需要接受一个或多个该类型的对象作为参数。例如contact() in String:接受两个String并返回一个String
  • observers:接受该类型的对象并返回不同类型的对象
  • mutators:改变对象的值。

creator通常用构造函数实现,但也可仅仅是一个静态方法。(工厂)

mutator通常返回void类型,但也可以返回其他类型。

类型的操作构成了它的抽象,public字段,对客户可见。

类中为实现这个类型设置的字段,及帮助实现复杂数据结构的相关类,构成了representation,private字段,对用户不可见。

3.Representation Independence

抽象类型的使用独立于其rep。

只要ADT中的方法仍满足其规约,即便rep的具体实现发生了更改,ADT仍然能正常运行。因为其操作的只依赖于规约,与rep的实现没有关系。

4.Invariant

不变量

好的ADT最重要的属性:保留其不变量。

ADT通过隐藏不变量中的变量,以保持其不变性,并且只允许具有良好定义规约的操作访问。

representation exposure

将rep直接暴露给客户端,例如:

将上述变量均修改为private字段,使其只能在类中访问。

同时在类中定义getter,实现对其的访问。

考虑以下代码:

当客户端调用这个函数,并尝试直接修改timestamp时,原对象的timestamp将被修改。即private字段被不期望地外部修改。

采用防御式拷贝消除解决这个问题。

考虑以下代码:

出现这种状况的原因是构造函数每次直接对传入的Date对象进行调用。即所有的timestamp指向的是同一个Date对象。

对构造函数进行防御性编程即可解决这一问题。

Rep Invariant and Abstraction Function

AF:

将Abstraction values视为:这个类型应当具有的值。不考虑具体的表达方式。

抽象值集合本身(A)不能决定AF或RI的定义方式。它们由人为定义。例如字符集(CharSet)可以由String作为rep,也可以由vector作为rep,这两种情况下的AF显然不同。

即便AF确定了,RI的选择仍然是任意的。同样,即便RI完全相同,也可以用不同的AF映射。

Check the Rep Invariant

在定义RI后,就应该考虑对RI的检查。即在类中编写checkRep函数用断言判断rep是否在运行阶段都满足RI定义。checkRep函数应该为private,因为RI不应该暴露给客户端,同理对它的检查也不应该暴露。

在每一个creator、producer、mutator之后都应该调用checkRep函数。

Beneficial Mutation

ADT中对不可变性修正为:抽象值不可变化。

只要rep仍映射到同一个不可变的抽象值,实现者就可以随意更改这个可变的rep。

例如创建一个类用分数表示有理数,其中重写toString函数,要求输出的是分数的最简形式。

可以简单地在RI里仅仅约束分母不为零,之后再toString里再进行转换。保证creator的效率。

这个observer更改了rep,但是更改之后的rep仍映射到同一个抽象值,那么就视为不变。

因为AF是多对一的映射,上述变化仅仅将某个象的原象从一个值换到了另一个值,对应的象不变。这种mutate就是beneficial mutation。

ADT‘s Spec

ADT的规约应该只讨论对客户端可见的内容。包括参数、返回值、异常等。

规范应将rep的值描述为对应的A中的抽象值。

不应该提及rep的细节、代表空间中的元素。有利于rep独立性和信息隐藏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值