CPT204笔记

和数据结构/Lab代码有关的部分没整理

数据结构部分可以看看这

2024本课程改课纲了,请勿按照这个复习

week01

1. 类型Types

Java属于类型严格的语言,(后期出现的泛型也可以理解为一种类型约束。
8 primitive types
○ boolean, byte, char, short, int, long, float and double
object types
○ String, BigInteger

其中,基本类型以小写字母开头,对象类型以大写字母开头并以驼峰命名法

2. 操作Operations

Operations are functions that take input values and produce output values

  1. 操作符operator
  2. 方法method of an object
  3. 函数function

其中,+ - * /又被重载overloaded以适应不同数据类型(如字符串)

3. Static Typing 和 Static Checking

Static typing is a particular kind of static checking, which means checking for bugs at compile time
The types of all variables have to be known at compile time (before the program runs), and the compiler can therefore deduce the types of all expressions as well
静态意味着可在编译时检查如数据类型错误,而减少项目中的报错

而如python和javascript等动态类型语言只会在运行时抛错

4. Java数据类型复习

String, Arrays, List, ArrayList, Method, JUnit…

week02

1. Checking

  1. Static checking: the bug is found before run
  2. Dynamic checking: found automatically when the code is executed
  3. No checking: end up with wrong answers or you have to watch for it yourself.

1.1. Static Checking

主要关于类型检查错误
can catch:

  1. syntax errors,如使用一个为声明的变量
  2. wrong names,无效方法,如Math.sine(3)
  3. wrong number of arguments
  4. wrong argument types,如Math.sin(“3”)
  5. wrong return types

1.2. Dynamic Checking

主要关于特定值引起的错误或空指针错误
can catch:

  1. illegal argument values,如1/0
  2. unrepresentable return values,如x/y(0),而y(0)返回的是0
  3. out-of-range indexes
  4. calling a method on a null object reference

1.3. No checking

有些错误应当被动态检查出来,但是没有,如:

  1. Integer division.整数除法,整数类型的 5/2=2
  2. Integer overflow整数溢出,超出范围的数字会溢出
  3. Special values in floating-point types,特殊类型的浮点数,如NaN,POSITIVE_INFINITY,NEGATIVE_INFINITY。当对负数开根或除0时会返回这些特殊数字而不是抛出动态异常,如1.0/0

2. Public and Static

public声明代表可以被程序里的任意处的代码使用
static声明代表生命周期伴随class类,而不是实例对象object,调用的话也是用class直接调用

3. Javadoc comments

在这里插入图片描述

/**
 * 总体描述
 * 例子
 * param开头给出input参数
 * return开头给出正常输入下的输出
 * /
public static List<Integer> hailstone(int n){

写好代码是communicate with the computer,写好注释是communicate with other people

4. Java数据类型复习

Collection,List,Map…
List和Map都是接口,只定义了要做什么,而没有提供实现的代码。因而使用时需要自己抉择用哪种implementation

List<String> list1 = new ArrayList<>();
List<String> list2 = new LinkedList<>();

5. Snapshot Diagrams

内存中对象的可视化图

5.1. 基本值

Primitive values are represented by bare constants
变量符号直接指向基本值
The incoming arrow is a reference to the value from a variable or an object field
在这里插入图片描述

5.2. 对象

An object value is a circle labeled by its type, can write field names inside it, with arrows pointing out to their values, also can include their declared types
在这里插入图片描述

5.3. 重新赋值 Reassigning Variables

重新赋值会改变变量符号指向的目标
在这里插入图片描述

5.4. 更改值 Mutating Values

更改对象的值只会改变对象内的某一个元素的指向
在这里插入图片描述
当对象不可变时(immutable),更改对象的值也会变更变量符号指向的地址
此类不可变的值在snapshot里被标注为双圆圈边框。
在这里插入图片描述
虽然String不可变,但是StringBuilder是一个可变的对象。
在这里插入图片描述

5.5. Immutable References不可变引用

当使用final声明时,变量符号不可被二次赋值。此时箭头使用双线箭头表示
在这里插入图片描述
可以对参数或本地变量进行final声明
对参数声明final后,方法体中不能再次对这个变量符号进行赋值
对本地变量声明final后,后续也不能reassigned,直到超出作用范围(scope)
对对象声明final后,变量符号不能再次赋值,但是对于可变的对象(如List),可以改变对象内的内容。

5.6. Reference 和 Value

Reference为对内存中元素的引用,Value为内存中元素。

we can have an immutable reference to a mutable value (for example: final StringBuilder sb) whose value can change even though we’re always pointing to the same object
We can also have a mutable reference to an immutable value (for example: String s), where the value of the variable can change because it can be re-pointed to a different object

6. Test-Driven Development and Corner Cases

在写代码前开始写test code,通过阅读problem description和method signature,可以得知输入和期望的输出

test需要考虑corner case,如空列表,一个元素的列表,满列表。

week03

如何写出火爆全网的垃圾代码

1. Coding Rules

  1. Don’t Repeat Yourself (DRY)
    不要在不同的地方写功能性一样的代码块,这样可能导致维护者只维护了其中之一
    在这里插入图片描述

  2. Comments where needed
    保持良好的注释习惯,也可以记录stackoverflow上的帖子
    不要书写逐句翻译的注释
    在这里插入图片描述

  3. Fail fast
    代码应当尽可能早的抛出异常(而不应该是以正常形式返回错误答案

  4. Avoid magic numbers
    避免凭空出现的数字以及硬编码
    在这里插入图片描述

  5. One purpose for each variable
    避免没有必要的复用,同一个名字但意义发生变化会令阅读者感到困惑
    在这里插入图片描述
    在这里插入图片描述

  6. Use good names
    将变量名起的易懂一点
    In Java:
    ○ methodsAreNamedWithCamelCaseLikeThis
    ○ variablesAreAlsoCamelCase
    ○ CONSTANTS_ARE_IN_ALL_CAPS_WITH_UNDERSCORES
    ○ ClassesAreCapitalized
    ○ packages.are.lowercase.and.separated.by.dots

  7. Don’t use global variables
    比较正确的方法是使用 public static 来定义一个只有一个实例的变量
    少使用可变的全局变量以防造成全局污染

  8. Return results, don’t print them
    底层方法或函数不要print出结果,而应该直接return,只有高层的代码与用户终端互动

  9. Use whitespace for readability
    缩进应保持一致(Tab or Space)

2. Testing and Bugs

Testing means running the program on carefully selected inputs and checking the results

2.1. Why Software Testing is Hard

ways unfortunately don’t work well in the world of software::

  1. Exhaustive testing,穷举几乎不可能
  2. Haphazard testing,凭感觉试一试也不可能
  3. Random or statistical testing,随机测试也不好使,软件可能在一个点上出错

2.2. Systematic Testing

A test case is a particular choice of inputs, along with the expected output behavior required by the specification
A test suite is a set of test cases for an implementation

Designing a test suite with three desirable properties:

  1. Correct, A correct test suite is a legal client of the specification, and it accepts all legal implementations of the spec without complaint
    正确的测试套件是函数注释的合法成员,它应当包含函数注释的所有合法实现,意思就是测试的输入要符合函数/方法的spec里给出的约束,不能说函数要求输入一个数字我们测试的时候输入进去字符串然后说函数有问题–只有当输入正确时,我们才去讨论函数的正确性
  2. Thorough,A thorough test suite finds actual bugs in the implementation, caused by mistakes that programmers are likely to make
  3. Small,数量少

Test-First Programming步骤:

  1. Spec:Write a specification for the function
  2. Test:Write tests that exercise the specification
  3. Implement:Write the actual code
2.2.1. Choosing Test Cases by Partitioning切割

We want to pick a set of test cases that is small enough to run quickly, yet large enough to validate the program

Divide the input space into subdomains, each consisting of a set of inputs
其中每个subdomains包含相似的输入,这些输入在函数内会有相似的输出
在这里插入图片描述
例子:

现有BigInteger类(一个可以使用无穷大数字的类),和一个乘法函数可以将两个BigInteger相乘并返回结果

/**
 * @param val another BigInteger
 * @return a BigInteger whose value is (this * val).
 */
public BigInteger multiply(BigInteger val)

将上方法抽象为a和b相乘
切割:以正负数为间隔切割,考虑数值特别大/小的情况:

  • 0
  • 1
  • -1
  • small positive integer
  • small negative integer
  • huge positive integer
  • huge negative integer
    然后从这些subdomain里面两两取出来a和b,排列组合
    在这里插入图片描述
    此外,还需要注意测试需要包含边界情况:
  1. 例如0是正负数的边界
  2. int或double数据类型的上/下界
  3. 空数据
  4. 首/尾的数据

如何排列组合a和b:

  1. Full Cartesian product:如上就是7x7=49,全排列,多个维度的subdomain同理。
  2. Cover each part:Every part of each dimension is covered by at least
    one test case, but not necessarily every combination,只要测试用例用到了每个subdomain就可以(想到八皇后问题…下图是不是少个-1
    在这里插入图片描述

Recursion

  1. First, check that the problem can be reduced into smaller and easier subproblem(s)
    ○ that is, the solution to a problem depends on the solution(s) to smaller instance(s) of the same problem
  2. Next, find the Base Case :
    ○ Find the simplest instance that can be directly solved
  3. Finally, compute the Recursive Step :
    ○ Try a couple of cases to see the recursive relation among instances
    ○ Formulate generally for n, trust Recursion God to solve the smaller instances
    ○ Make sure to reach the base case

week04

1. Test

1.1. Black-box vs White-box Testing

Black-box testing means choosing test cases only from the specification, not the implementation of the method
在这里插入图片描述

White-box testing (also called glass box testing) means choosing test cases with knowledge of how the method is actually implemented

  • For example, if the implementation selects different algorithms depending on the input, then you should partition according to those domains
    在这里插入图片描述

白盒测试不应该要求超出spec之外的东西,比如spec里说某种情况下会抛出异常,那么测试的时候就不要去规定这个异常的种类。

1.2. Document Your Testing Strategy

  1. your test strategy at the top of your test class
  2. how each test case was chosen, including white-box tests:
    在这里插入图片描述

1.3. Coverage

Coverage: how thoroughly it exercises the program
There are three common kinds of coverage:

  1. Statement coverage: is every statement run by some test case?测试是否让每行代码都运行过
  2. Branch coverage: for every if or while statement in the program, are both the true and the false direction taken by some test case?每个分支是否都运行过一遍
  3. Path coverage: is every possible combination of branches — every path through the program — taken by some test case?

Path>Branch>Statement

通常测试需要保证程序中的每个可到达语句至少被一个测试用例执行
软件工具可以生成每个语句的执行次数,只要在黑盒测试里多加点case使其覆盖了所有语句即可变成白盒测试。

1.4. Unit vs Integration Testing

unit test: test that tests an individual module(method/class) in isolation. 单元测试确保了模块的正确工作,测试是完全隔离的,不需要思考模块间的作用,每个单元测试应该只聚焦于一个声明(specification)
integration test: tests a combination of modules, or even the entire program. 集成测试确保了模块间乃至系统的正确工作,确保调用者与被调用者传递/返回对方所期待的值

  • for example,现在有方法A,其内调用了方法B和C,对这三个方法进行单元测试,则应该先单独测B和C,最后测A
  • 由于包含关系,方法A无法完全隔离测试,但是可以在测试A时,使用stub versions of B and C,stub versions会直接返回人为规定的靠谱的结果。
  • A stub for a class is often called a mock object
  • 集成测试不能代替单元测试,方法B和C的输入都是A提供的,这使得集成测试的B和C的输入集被约束,如果后面在别的方法D里调用B或C,换了个输入集,可能又会报错–单元测试的重要性。

1.5. Automated Regression Testing

Automated testing means running the tests and checking their results automatically
○ A test driver should not be an interactive program that prompts you for inputs and prints out results for you to manually check
○ Instead, a test driver should invoke the module itself on fixed test cases and automatically check that the results are correct. 运行固定的测试用例
○ The result of the test driver should be either “all tests OK” or “these tests failed: …”
○ A good testing framework, like JUnit, helps you build automated test suites. But you still have to come up with good test cases yourself

Regressing: introducing other bugs when you fix new bugs or add new features
Regression Testing:

  1. Whenever you find and fix a bug, add the input that elicited the bug to your automated test suite–regression test for that bug
  2. Running all your tests after every change

So automated regression testing is a best-practice of modern software engineering

2. Recursive Linked List

如何使用Java Visualizer P29

week05

1. About Specification

Specification is a contract
The implementer is responsible for meeting the contract, and
A client that uses the method can rely on the contract

1.1. Why Spec?

  1. Many of the nastiest bugs in programs arise because of misunderstandings about behavior at the interface between two pieces of code 避免错误理解接口引起的bug
  2. Specifications are good for the client of a method because they spare the task of reading code 节省阅读详细代码所耗费的时间
    在这里插入图片描述
    如果只有code,那么需要阅读constructor, compareMagnitude, subtract and trustedStripLeadingZeroInts just as a starting point

1.2. Spec , Implementer&Client

在这里插入图片描述

  1. specification就像防火墙,对于调用这个方法的client而言,不需要理解里面的computation是如何运作的- shields the implementer from the details of the usage of the unit
  2. The weaker spec can rule out centain states in which a method migth be called. 规范调用环境
    The restriction on the input might allow to skip expensive check. 约束输入,精力投入实现而不是输入检查

This firewall results in decoupling, allowing the code of the unit and the code of a client to be changed independently, so long as the changes respect the specification.
这种设计实现了解耦,调用和实现之间互相不干扰

A specification of a method can talk about the parameters and return value of the method, but it should never talk about local variables of the method or private fields of the method’s class。注释只能谈论对外可见的,不能谈论局部变量。

1.3. Behavioral Equivalence

在这里插入图片描述
观察以上两个函数,其共同点是如果数组中有val,则返回val下标
不同点,如果数组中没有val,分别返回的总长度和-1.

额外约束下的情况,如果数组中只出现一次val,则这两个函数返回的是一样的

The notion of equivalence is in the eye of the beholder — that is, the client是否相等取决于调用者的视角
In this case, our specification might be 对输入情况做约束,并抽出相同点:

static int find(int[] arr, int val)
 requires: val occurs exactly once in arr
 effects: returns index i such that arr[i] == val

1.4. Spec Structure

  1. precondition -> requires,属于调用者(client, caller of the method)责任
  2. postcondition ->effects,属于实现者(implementer)责任
if( precondition holds for the invoking state){
	obey the postcondition,
	returning appropriate values, throwing specified exceptions, modifying or not modifying objects...
}

if the precondition holds when the method is called, then the postcondition must hold when the method completes
如果调用者的前提条件成立,则应当根据spec实现各种返回或报错以实现后置条件前置条件不成立,it is free to do anything

2. Spec in Java

有的语言强制约束并严格检查前置条件和后置条件,Java没有这么绝,但是类型检查能提供对于前置后置条件的类型的检查的功能。对于Java,类型之外的部分需要以自然语言写明白。

Java的函数声明

  1. precondition:参数@param
  2. postcondition:返回@return,异常抛出@throws
    在这里插入图片描述
    注意@param后紧邻的得是入参的名字。

3. Test and Specifications

  1. 白箱测试也得遵循specification
  2. 具体实现可能提供了比spec里更严格的后置条件,但test case不能依靠这个行为,必须还是依靠spec
    比如一个方法:
static int find(int[] arr, int val)
 requires: val occurs in arr
 effects: returns index i such that arr[i] == val

输入要求val存在于数组中,可能有多个val,如果代码实现的是返回从前往后数第一个val,测试用例依旧不能检测【返回的数字是否是从前往后数第一个val的下标】,而应该检测【返回的数字对应的值是不是查找的val】
在这里插入图片描述
又或者代码写了如果数组里面没val,会抛异常,测试的时候因为spec没提这点,所以还是不能测这项。

不能超出spec的白盒测试意义何在
It means you are trying to find new test cases that exercise different parts of the implementation, but still checking those test cases in an implementation-independent way。为了在不超出spec的情况下,更好地探寻代码实现时的每一部分

4. Specifications and Mutating Methods

a method contains mutate

static boolean addAll(List<T> list1, List<T> list2)
 requires: list1 != list2
 effects: modifies list1 by adding the elements of list2 to the end of it, 
		  and returns true iff list1 changed as a result of call

对于可变对象,spec的后置条件额外给出了点:如何变,前置条件则约束了两个输入不能指向同一内存对象(简化了实现,只需要将元素挨个append到另一个末尾。如果指向同一内存对象,这种实现方法会无限循环。)
这里还有个隐藏前置,输入不为空指针–“param is not null"

a method does not mutate

static List<String> toLowerCase(List<String> list)
 requires: nothing
 effects: returns a new list t where t[i] = list[i].toLowerCase()

还有个隐藏前置,除非spec里强调,否则方法不应mutate object。“param is not modified”

5. SLList

P34

week06

1. Null

1.1. Null Reference空指针

如果一个指针(变量名)未指向任何对象,则称为空指针。
不能给基本数据类型的变量赋值null
Primitives cannot be null and the compiler will reject such attempts with static errors

int size=null  //illegal
double depth=null; //illegal
String name=null; //legal,可以给对象类型赋值null
name.length() //,但是运行调用对象方法时会抛出NullPointerException

空指针与空字符串或空列表不同。
存储对象类型的列表可以将null作为其内的元素,但要注意使用时会抛出上述空指针异常。

1.2. Null Values in Java 空值

java隐式地禁止函数/方法中的传入值/返回值为null.
(同时这也是spec的一个隐藏前置/后置条件,如果方法允许空值,则应显示(explicitly)地标明。

There are extensions to Java that allow you to forbid null directly in the type declaration:

static boolean addAll(@NonNull List<T> list1)

谷歌认为null使人“懵逼”(unpleasantly ambiguous),比如Map取值,如果没有相应键值对,取出来为null;如果本身存储的值就是null,取出来还是null。null此时既能代表成功又能代表失败

2. Exceptions

spec包括:方法的name, parameter types, return types, exceptions (which may trigger)

为什么抛异常:

  1. signal bugs
  2. special results

As a general rule, you’ll want to use unchecked exceptions to signal bugs and checked exceptions to signal special results

2.1. Signaling Bugs

IndexOutOfBoundsException, 列表下标出界
NullPointerException,空指针调用
ArithmeticException, 整数除零
NumberFormatException, 如用parseInt转换一个非数字含义字符串

这些错误通常代表代码的逻辑错误

2.2. Special Results

主要存在于失败的情况:
如查找,如果数组中不存在被查找值,则返回-1;如果字典中不存在被查找键值对,则返回null。

上述方法缺点:需要逻辑检查返回值来判断成功与否,特殊值不容易界定。

所以使用抛出异常与异常捕获。

2.3.1. throw和try catch

			//advertise the exception in method's signatureLocalDate lookup(String name) throws NotFoundException{
	if(...){//the condition that triggers throwing exc
		throw new NotFoundException();//an object of type NotFoundException is thrown
	}
}
//method that may throw exception is called inside trytry{
	//codes here executed always
	LocalDate birthdate=birthdays.lookup("Alyssa");
	//codes here executed if lookup does not throw a NotFoundException object
}catch(NotFoundException e){
	//codes here executed if lookup throws a NotFoundException object
}
  1. try catch应捕获具体的异常类型,否则广泛的异常可能会掩盖掉真正出错的地方

2.3.2. Exception Message

抛出异常的时候可以顺便抛出信息

throw new NotFoundException("here explain the reason of Exception");

这样捕获的时候就能顺便打印出来看看

catch(NotFoundException e){
	sysout(e.getMessage());
}
  1. All exceptions may have a message associated with them in the constructor,否则会抛出异常中的null,干扰判断

2.4.1. Checked Exception 检查型异常

callee must declare/advertise it 被调用者必须声明自己会抛出异常
caller must handle it (with try-catch) 调用者使用trycatch捕获或继续抛出异常

Checked exceptions are called that because they are checked by the compiler:

  1. 如果一个方法可能会抛异常,在声明时加上throws来声明会抛异常
  2. 如果一个方法可能会捕获异常,那么这个方法必须使用try catch来捕获并处理异常,或者自身声明异常,否则异常会传递到调用者。
  3. 如果可能抛出异常而这个异常没有被捕获,则编译不通过

2.4.2. Unchecked Exception 非检查型异常

非检查型异常不强制要求这个异常会被检查,捕获并处理,多用于signal bugs。因为这种异常是程序员逻辑失误引起的,可以避免,(总不能每次查找都得catch = =

  1. 声明时不用加上throws
  2. 不强制要求try catch来捕获并处理异常
  3. 对于非检查型异常,编译器不会检查是否有try catch来捕获或是否有throws 声明。

ref:(非)检查型异常
ref: Java 异常分类
在这里插入图片描述

2.5. Java Exception Hierarchy

Throwable is the class of objects that can be thrown or caught

  1. Throwable’s implementation records a stack trace at the point where the exception was thrown, along with an optional string describing the exception
  2. any object used in a throw or catch statement, or declared in the throws clause of a method, must be a subclass of Throwable

在这里插入图片描述

  1. Exception is the normal base class of checked exceptions
  2. The compiler applies static checking to methods using these exceptions
  3. RuntimeException and its subclasses are unchecked exceptions
  4. Error and its subclasses should be considered unrecoverable, and should not be caught by your code
    在这里插入图片描述

2.6. Creating Exception

  1. 继承Exception来创建检查型异常
  2. 继承RuntimeException来创建非检查型异常
  3. Don’t subclass Error or Throwable, because these are reserved by Java itself

2.7. Rules of Exceptions

  1. unchecked exceptions for signal bugs (unexpected failures) OR if you expect that clients will usually write code that ensures the exception will not happen, because there is a convenient and inexpensive way to avoid the exception,如果用户有意识且能够简单地避免产生special results,则用非检查型异常会维护用户的代码简洁度
  2. use checked exceptions for special results (i.e., anticipated situations)

例子:

Queue.pop() throws an unchecked EmptyQueueException when the queue is empty

  • because it’s reasonable to expect the caller to avoid this with a call like Queue.size() or Queue.isEmpty()

Url.getWebPage() throws a checked IOException when it can’t retrieve the web page

  • because it’s not easy for the caller to prevent this

int integerSquareRoot(int x) throws a checked NotPerfectSquareException when x has no integral square root,

  • because testing whether x is a perfect square is just as hard as finding the actual square root, so it’s not reasonable to expect the caller to prevent it

从这个角度来看,返回null就是一刀切(

2.8. Exception in Spec

文档注释(Javadoc comment)里的@param,@return,@throws叫clause
方法签名(method signature)后的throws叫throws declaration
signal unexpected failures = bugs

spec里后置条件应该包括可能的异常

/**
* Compute the integer square root.
* @param x value to take square root of
* @return square root of x
* @throws NotPerfectSquareException if x is not a perfect square
*/
int integerSquareRoot(int x) throws NotPerfectSquareException

检查型异常写@throws,非检查型异常最好不写@throws,bug不属于方法后置条件,所以bug不写throws(For example, NullPointerException never be mentioned in a spec,一种解释是隐式前提要求传入的参数非空,所以因为空参数抛出任何错误都可以。

2.9. Abuse of Exception

既然下标越界会抛出异常,是否可以通过捕获越界异常来终止对数组的遍历:

正常写法 reasonable loop idiom

for (int i=0;i<a.length;i++){
	a[i].f();
}

for (T x:a){
	x.f();
}

通过捕获异常并忽略异常来终止循环 misguided exception-based loop

try{
	int i=0;
	while(true){
		a[i++].f();
	}
}catch(ArrayIndexOutOfBoundsException e){ }

缺点:

  1. JVM对于捕获异常的性能不是很好
  2. 如果正常循环时,f()方法抛出了一个ArrayIndexOutOfBoundsException异常,该异常被这层catch捕获,使得循环被错误地终止,且这种错误会被catch被忽略,使得错误对于程序员是无感知的。

SLList

【P43

week 08

1. Comparing Specs

  • Deterministic确定性: does the spec define only a single possible output for a given input, or does it allow the implementor to choose from a set of legal outputs?
  • Declarative声明性: does the spec just characterize what the output should be, or does it explicitly say how to compute the output?
  • Strong泛用性: does the spec have a small set of legal implementations, or a large set?

通过以下两个实现来讨论(findFirst, findLast)

1.1. Deterministic vs Underdetermined

This specification is deterministic: when presented with a state satisfying the
precondition, the outcome is completely determined

  • Only one return value and one final state are possible
  • There are no valid inputs for which there is more than one valid output

确定性较强(determined)的声明,对于符合以上条件的前置,会给出完全确定的后置
比如对于声明如下,findFirst和findLast都是符合的

static int findExactlyOne(int[] arr, int val)
	requires: val occurs exactly once in arr
	effects: return index i such that arr[i] == val

而确定性较弱(underdetermined)的声明,不满足上述条件,无法给出唯一确定的后置
allows multiple valid outputs for the same input
比如对于以下声明,返回的index i是不确定的,任何能使arr[i]==val的都可以
当然,就这个spec而言,findFirst和findLast也都是符合的
确定性较弱留给了实现者较大的发挥空间

static int findAnyIndex(int[] arr, int val)
	requires: val occurs in arr
	effects: return index i such that arr[i] == val

最后一种叫无确定性声明(nondeterministic),代表 代码的行为 完全随机。

1.2. Declarative vs Operational Specs

Operational specifications 操作性规范 give a series of steps that the method performs

  • 伪代码类型
  • lab里的spec,for educative purpose
  • dont use the spec comment to explain the implementation for a maintainer, can use comments in body of the method to do it

Declarative specifications 声明式规划 don’t give details of intermediate steps

  • 只给初始状态和结束时状态
  • more preferable
  • dont need to expose implementation details

以下三个都是声明式
在这里插入图片描述

1.3. Stronger vs Weaker

compare the behaviors of two specifications to decide whether it’s safe to replace the old spec with the new spec
假如要改变一方法的实现或spec,可以比较两个spec的泛用性的强弱来决定是否能将旧的spec替换

A specification S2 is stronger than or equal to a specification S1 if

  • S2’s precondition is weaker than or equal to S1’s, and
  • S2’s postcondition is stronger than or equal to S1’s for the states that satisfy S1’s precondition
  • 这两处加粗weaker和stronger指能处理的数据的范围,范围越大约weaker,范围越小约stronger
  • 较强的泛用性能够涵盖较弱的泛用性
  • If this is the case, then an implementation that satisfies S2 can be used to satisfy S1 as well, and it’s safe to replace S1 with S2 in your program

placing fewer demands on a client will never upset them; and you can always strengthen the postcondition, which means making more promises
另一方面考虑,既是尽可能少的要求客户,而自己做出多一点保证

加一点我自己的理解,假如现在有两个人A和B,分别告诉你他能让你彩票中头奖:A能够从500份号码里面挑出来5份,买了这5份你就能中头奖;B能够从100份号码里面挑出来50份,买了这50份就能中头奖。相比之下当然是A的业务能力更强一些,那A自然就能代替B(在猜彩票这一方面。

在这里插入图片描述

左:由原来只能出现一次换成出现最少一次,前置范围变广(weak),整体相比strong
右:由原来返回值等于val的下标i,换成返回值等于val的最小的下标i,后置范围变小(strong),整体相比strong

1.3.1. Incomparable

在这里插入图片描述
上面的比起下面的,precondition和postcondition都要弱于下面。因此他俩不可比较Incomparable。

(以彩票举例,两千多张号码里选出五张 和 一千多张里选出两张 不可比较业务能力,这里将数量举例为估值来模糊量化行为,毕竟spec的前置后置之间没有线性关系)

2. Diagraming Specification

把代码方法体区域想象成长方形,方法想象成区域里的点,spec描述了空间内一片符合的实现区域。
区域内既是符合前置后置的方法实现(precondition,postcondition)

以下展示了findFirst和findLast都在findOneOrMoreAntIndex范围内
圆圈可以理解为防火墙,调用者在外观测使用,内部实现时可以实现范围内的任意一点
在这里插入图片描述

S2 is Stronger than S1 - S2 have a smaller region in this diagram, every implementation that satisfies S2 also satisfies S1

个人理解,这里可以理解为能力处于同一阈值的人划在一个区域,比如能从2000张彩票里挑出一张必出头奖的人屈指可数,所以这些能力强的人的区域就会少一些。

  1. S2’s precondition is weaker than S1’s, or
  2. S2’s postcondition is stronger than S1’s - S2 is less freedom

Another specification S3 that is neither stronger nor weaker than S1 might overlap (such that there exist implementations that satisfy only S1, only S3, and both S1 and S3) or might be disjoint

  • In both cases, S1 and S3 are incomparable
  • 区域部分重合属于是不可比较的情况

3. Design Good Spec

  1. form of spec: be succinct, clear, and wellstructured, easy to read.
  2. content of the specification:
    1. shouldn’t have lots of different cases, be coherent
      条理清晰
      -do not use Long argument lists, deeply nested if-statements, and boolean flags are all signs of trouble
      -do not use global variables and print something as result
      -be better to use two separate procedures
    2. Results of a Call should be Informative
      -do not return null in a map for you do not know whether it is a value of it or it means no matchs key
    3. The Specification should be Strong Enough
      -Spec should give clients a strong enough guarantee in the general case — it needs to satisfy their basic requirements
      -谁不喜欢能带自己中彩票头奖的人呢
    4. The Specification should be Weak Enough
      -effects: opens a file named filename打开已经创建的还是未创建的?前置太宽泛。文件没法被百分百打开,后置太肯定。
      -Instead, the specification should say something much weaker: that it attempts to open a file, and if it succeeds, the file has certain properties
    5. The Specification should use Abstract Types where possible
      -Writing our specification with abstract types gives more freedom to both the client and the implementer
      -often means using an interface type, like Map or Reader, instead of specific implementation types like HashMap or FileReader
    6. 只把需要公开的代码设为public,辅助方法以及私有变量设为private

Precondition or Postcondition:

  1. make sure the precondition has been met before proceeding
  2. the most common use of preconditions is to demand a property precisely because it would be hard or expensive for the method to check it
  3. 严苛的前置条件会给调用者带来负担(额外检查),而宽松的前置又会给实现者带来负担(方法内检测,运行时间增加:二分搜索如果检测数组为已排序状态,时间复杂度直接上一个台阶)
  4. java api中前置不符合会抛出异常,以阻止错误传播,及时告知程序员错误位置。综合考虑检测成本,如果是别人会调用的公用方法最好还是抛异常。

4. Array-based list

【38

week09

1. Avoid debugging

1.1. Make bugs impossible by design

静态检查(Static checking):如方法的入参
动态检查(dynamic checking):如访问数组的下标越界
不可变类型(Immutability):用final声明,代表指针(reference)指向一个固定的对象(object),对象本身的属性/值仍可以改变。

1.2. Localize bugs to a small part of the program

尽可能在错误发生时就指出异常,不要使错误传递,既方法的前置条件不满足时,直接抛出异常。

defensive programming
采用断言assertion,将不满足前置条件后的处理抽象出来,既可以给人看,也可以在运行时确保前置条件满足。

java最简单的断言:assert x>=0;
如果断言false,则会抛出AssertionError异常
还可以附加错误消息:assert (x>=0): "x is " + x;
由于断言会增加开销,所以默认不检查。传递-ea来启用断言

Juint检查断言,以下代码运行时抛出异常,符合测试预期,测试通过:

@Test(expected=AssertionError.class)
public void xxx{
	asser false;
}

What to Assert:

  1. Method argument requirements:检查前置条件
  2. Method return value requirements:检查结果的正确性
  3. Covering all cases:switch都不满足,Assert.fail()

What not to Assert:

  1. Runtime assertions are not free
  2. Avoid trivial assertions, just as you would avoid uninformative comments
  3. This assertion doesn’t find bugs in your code
  4. Never use assertions to test conditions that are external to your program, such as the existence of files, the availability of the network, or the correctness of input typed by a human user
  5. when the program is released to users
  6. should not have side-effects
assert list.remove(x);//If assertions are disabled, the entire expression is skipped
↓
boolean found=list.remove(x);//Write it like this instead
assert found;

1.3.

Incremental Development增量开发,写一部分检测一部分。
Unit test:单元测试
Regression test:新功能被添加后进行系统测试

Modularity模块化,将系统划分成许多小组件。

Encapsulation封装,组件之间隔离
public/private,能用局部变量就不用全局变量

2. Mutability, Immutability, Iterator

P34

week10

1. ADT (Abstract data types)

抽象一个具象的方法能够从更高层次解构其逻辑,而不担心具体的实现。

抽象数据类型使用户能自定义数据类型

ADT增删查改(t代表原生数据类型,T代表抽象数据类型,*代表0或多次,+代表1或多次,|代表或:

  • Creator, 创建, t* → T,如valuesOf(), 静态创建方法被称为factory method
  • Producer,从已有的T创建,如concat(), T+, t* → T
  • Observer,观察属性,如size(),T+, t* → t
  • Mutator,改变已有的值,如add(),T+, t* → void | t | T

在这里插入图片描述

Representation Independence
This means that the use of an abstract type is independent of its representation : the actual data structure or data fields or instance variable used to implement it, so that changes in representation have no effect on code outside the abstract type itself

对于抽象数据类型,只关注其方法,而不关注其内部实现的逻辑,如List接口有ArrayList和LinkList两种实现类,我们只关注其get(),size()等方法。
这些方法应当少且简单(封装),且应适应许多数据类型(抽象)

在这里插入图片描述

2. Inheritance

P43

2.1. Dynamic Method Selection for Overridden Methods

● Suppose we call a method m() of an object using a variable with:

  • static type X and dynamic type Y

● First, the compiler records the X’s method m(), to be used in run-time
● At run-time, if Y overrides the method m(), then Y’s method m() is used instead

  • this is known as dynamic method selection

week 11

1. Invariant 不变量

An invariant is a property of a program that is always true, for every possible runtime state of the program

1.1. Immutability as Invariant

Immutablility不可改变:指对象一经创建,其自身的属性不可被改变,通常用private关键字来阻止类外部的代码来访问类里的变量, final来保证变量赋值后不可再次赋值。

后续问题是,用private final 声明的实例变量如果是可变对象,则仍然可以更改其内的值。这样,为了封装而暴露的get接口会返回一个其内部值可以被改变的实例变量出去,解决方法:get接口返回一个实例变量的深拷贝,这样外部修改这个被返回出去的变量时不会影响到类里这个实例变量。

可变对象在构造函数里也可能会引发上述问题,(多个构造函数传入同一个可变对象,这个对象动一次会改变所有构造函数生成的对象们里的实例变量)所以如果构造函数传入一个可变对象,则保存到类实例变量里的时候应该做深拷贝。

如果深拷贝成本过大,在前置条件里声明某个参数对象为immutability也能规避问题。
也可以是用Collections.unmodifiableList()来把对象封装成不可变的形式。

Comparability

P22

week12

  1. hash codes and hash functions, Hash Table
  2. comparator (anonymous class)

P1

  • 15
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值