和数据结构/Lab有关的部分没整理
Java属于类型严格的语言,(后期出现的泛型也可以理解为一种类型约束。
8 primitive types
○ boolean, byte, char, short, int, long, float and double
object types
○ String, BigInteger
其中,基本类型以小写字母开头,对象类型以大写字母开头并以驼峰命名法
Operations are functions that take input values and produce output values
其中,+ - * /
又被重载overloaded以适应不同数据类型(如字符串)
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等动态类型语言只会在运行时抛错
String, Arrays, List, ArrayList, Method, JUnit…
主要关于类型检查错误
can catch:
主要关于特定值引起的错误或空指针错误
can catch:
有些错误应当被动态检查出来,但是没有,如:
public声明代表可以被程序里的任意处的代码使用
static声明代表生命周期伴随class类,而不是实例对象object,调用的话也是用class直接调用
/**
* 总体描述
* 例子
* param开头给出input参数
* return开头给出正常输入下的输出
* /
public static List hailstone(int n){
写好代码是communicate with the computer,写好注释是communicate with other people
Collection,List,Map…
List和Map都是接口,只定义了要做什么,而没有提供实现的代码。因而使用时需要自己抉择用哪种implementation
List list1 = new ArrayList<>();
List list2 = new LinkedList<>();
内存中对象的可视化图
Primitive values are represented by bare constants
变量符号直接指向基本值
The incoming arrow is a reference to the value from a variable or an object field
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
更改对象的值只会改变对象内的某一个元素的指向
当对象不可变时(immutable),更改对象的值也会变更变量符号指向的地址
此类不可变的值在snapshot里被标注为双圆圈边框。
虽然String不可变,但是StringBuilder是一个可变的对象。
当使用final声明时,变量符号不可被二次赋值。此时箭头使用双线箭头表示
可以对参数或本地变量进行final声明
对参数声明final后,方法体中不能再次对这个变量符号进行赋值
对本地变量声明final后,后续也不能reassigned,直到超出作用范围(scope)
对对象声明final后,变量符号不能再次赋值,但是对于可变的对象(如List),可以改变对象内的内容。
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
在写代码前开始写test code,通过阅读problem description和method signature,可以得知输入和期望的输出
test需要考虑corner case,如空列表,一个元素的列表,满列表。
如何写出火爆全网的垃圾代码
Don’t Repeat Yourself (DRY)
不要在不同的地方写功能性一样的代码块,这样可能导致维护者只维护了其中之一
Comments where needed
保持良好的注释习惯,也可以记录stackoverflow上的帖子
不要书写逐句翻译的注释
Fail fast
代码应当尽可能早的抛出异常(而不应该是以正常形式返回错误答案
One purpose for each variable
避免没有必要的复用,同一个名字但意义发生变化会令阅读者感到困惑
Use good names
将变量名起的易懂一点
In Java:
○ methodsAreNamedWithCamelCaseLikeThis
○ variablesAreAlsoCamelCase
○ CONSTANTS_ARE_IN_ALL_CAPS_WITH_UNDERSCORES
○ ClassesAreCapitalized
○ packages.are.lowercase.and.separated.by.dots
Don’t use global variables
比较正确的方法是使用 public static 来定义一个只有一个实例的变量
少使用可变的全局变量以防造成全局污染
Return results, don’t print them
底层方法或函数不要print出结果,而应该直接return,只有高层的代码与用户终端互动
Use whitespace for readability
缩进应保持一致(Tab or Space)
Testing means running the program on carefully selected inputs and checking the results
ways unfortunately don’t work well in the world of software::
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:
Test-First Programming步骤:
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相乘
切割:以正负数为间隔切割,考虑数值特别大/小的情况:
如何排列组合a和b:
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
白盒测试不应该要求超出spec之外的东西,比如spec里说某种情况下会抛出异常,那么测试的时候就不要去规定这个异常的种类。
Coverage: how thoroughly it exercises the program
There are three common kinds of coverage:
Path>Branch>Statement
通常测试需要保证程序中的每个可到达语句至少被一个测试用例执行
软件工具可以生成每个语句的执行次数,只要在黑盒测试里多加点case使其覆盖了所有语句即可变成白盒测试。
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. 集成测试确保了模块间乃至系统的正确工作,确保调用者与被调用者传递/返回对方所期待的值
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:
So automated regression testing is a best-practice of modern software engineering
如何使用Java Visualizer P29
Specification is a contract
The implementer is responsible for meeting the contract, and
A client that uses the method can rely on the contract
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。注释只能谈论对外可见的,不能谈论局部变量。
观察以上两个函数,其共同点是如果数组中有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
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
有的语言强制约束并严格检查前置条件和后置条件,Java没有这么绝,但是类型检查能提供对于前置后置条件的类型的检查的功能。对于Java,类型之外的部分需要以自然语言写明白。
Java的函数声明
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的情况下,更好地探寻代码实现时的每一部分
a method contains mutate
static boolean addAll(List list1, List 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 toLowerCase(List list)
requires: nothing
effects: returns a new list t where t[i] = list[i].toLowerCase()
还有个隐藏前置,除非spec里强调,否则方法不应mutate object。“param is not modified”
P34
如果一个指针(变量名)未指向任何对象,则称为空指针。
不能给基本数据类型的变量赋值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作为其内的元素,但要注意使用时会抛出上述空指针异常。
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 list1)
谷歌认为null使人“懵逼”(unpleasantly ambiguous),比如Map取值,如果没有相应键值对,取出来为null;如果本身存储的值就是null,取出来还是null。null此时既能代表成功又能代表失败
spec包括:方法的name, parameter types, return types, exceptions (which may trigger)
为什么抛异常:
As a general rule, you’ll want to use unchecked exceptions to signal bugs and checked exceptions to signal special results
IndexOutOfBoundsException, 列表下标出界
NullPointerException,空指针调用
ArithmeticException, 整数除零
NumberFormatException, 如用parseInt转换一个非数字含义字符串
这些错误通常代表代码的逻辑错误
主要存在于失败的情况:
如查找,如果数组中不存在被查找值,则返回-1;如果字典中不存在被查找键值对,则返回null。
上述方法缺点:需要逻辑检查返回值来判断成功与否,特殊值不容易界定。
所以使用抛出异常与异常捕获。
//advertise the exception in method's signature
↓
LocalDate 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 try
↓
try{
//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
}
抛出异常的时候可以顺便抛出信息
throw new NotFoundException("here explain the reason of Exception");
这样捕获的时候就能顺便打印出来看看
catch(NotFoundException e){
sysout(e.getMessage());
}
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:
非检查型异常不强制要求这个异常会被检查,捕获并处理,多用于signal bugs。因为这种异常是程序员逻辑失误引起的,可以避免,(总不能每次查找都得catch = =
Throwable is the class of objects that can be thrown or caught
例子:
Queue.pop() throws an unchecked EmptyQueueException when the queue is empty
Url.getWebPage() throws a checked IOException when it can’t retrieve the web page
int integerSquareRoot(int x) throws a checked NotPerfectSquareException when x has no integral square root,
从这个角度来看,返回null就是一刀切(
文档注释(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,一种解释是隐式前提要求传入的参数非空,所以因为空参数抛出任何错误都可以。
既然下标越界会抛出异常,是否可以通过捕获越界异常来终止对数组的遍历:
正常写法 reasonable loop idiom:
for (int i=0;i
或
for (T x:a){
x.f();
}
通过捕获异常并忽略异常来终止循环 misguided exception-based loop :
try{
int i=0;
while(true){
a[i++].f();
}
}catch(ArrayIndexOutOfBoundsException e){ }
缺点:
【P43
通过以下两个实现来讨论(findFirst, findLast)
This specification is deterministic: when presented with a state satisfying the
precondition, the outcome is completely determined
确定性较强(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),代表 代码的行为 完全随机。
Operational specifications 操作性规范 give a series of steps that the method performs
Declarative specifications 声明式规划 don’t give details of intermediate steps
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
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
上面的比起下面的,precondition和postcondition都要弱于下面。因此他俩不可比较Incomparable。
(以彩票举例,两千多张号码里选出五张 和 一千多张里选出两张 不可比较业务能力,这里将数量举例为估值来模糊量化行为,毕竟spec的前置后置之间没有线性关系)
把代码方法体区域想象成长方形,方法想象成区域里的点,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张彩票里挑出一张必出头奖的人屈指可数,所以这些能力强的人的区域就会少一些。
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
effects: opens a file named filename
打开已经创建的还是未创建的?前置太宽泛。文件没法被百分百打开,后置太肯定。Precondition or Postcondition:
【38
静态检查(Static checking):如方法的入参
动态检查(dynamic checking):如访问数组的下标越界
不可变类型(Immutability):用final声明,代表指针(reference)指向一个固定的对象(object),对象本身的属性/值仍可以改变。
尽可能在错误发生时就指出异常,不要使错误传递,既方法的前置条件不满足时,直接抛出异常。
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:
What not to Assert:
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;
Incremental Development增量开发,写一部分检测一部分。
Unit test:单元测试
Regression test:新功能被添加后进行系统测试
Modularity模块化,将系统划分成许多小组件。
Encapsulation封装,组件之间隔离
public/private,能用局部变量就不用全局变量
P34
抽象一个具象的方法能够从更高层次解构其逻辑,而不担心具体的实现。
抽象数据类型使用户能自定义数据类型
ADT增删查改(t代表原生数据类型,T代表抽象数据类型,*代表0或多次,+代表1或多次,|代表或:
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()等方法。
这些方法应当少且简单(封装),且应适应许多数据类型(抽象)
P43
● Suppose we call a method m() of an object using a variable with:
● 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
An invariant is a property of a program that is always true, for every possible runtime state of the program
Immutablility不可改变:指对象一经创建,其自身的属性不可被改变,通常用private关键字来阻止类外部的代码来访问类里的变量, final来保证变量赋值后不可再次赋值。
后续问题是,用private final 声明的实例变量如果是可变对象,则仍然可以更改其内的值。这样,为了封装而暴露的get接口会返回一个其内部值可以被改变的实例变量出去,解决方法:get接口返回一个实例变量的深拷贝,这样外部修改这个被返回出去的变量时不会影响到类里这个实例变量。
可变对象在构造函数里也可能会引发上述问题,(多个构造函数传入同一个可变对象,这个对象动一次会改变所有构造函数生成的对象们里的实例变量)所以如果构造函数传入一个可变对象,则保存到类实例变量里的时候应该做深拷贝。
如果深拷贝成本过大,在前置条件里声明某个参数对象为immutability也能规避问题。
也可以是用Collections.unmodifiableList()来把对象封装成不可变的形式。
P22
P1