一、对象和数据结构
1、对象和数据结构之间的二分原理
过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象便于在于不改动既有函数的前提下添加新类。所以,面向对象较难的事,对于过程式代码却较容易,反之亦然。
2、得墨忒耳律
著名的得墨忒耳律认为,模块不该了解它所操作对象的内部情形。这意味着对象不应通过存取器暴露其内部结构,因为这样更像是暴露而非隐藏其内部结构。即方法不应调用由任何函数返回的对象的方法。
例如:final String outputDir=ctxt.getOptions().getScratchDir().getAbsolutePath();
上面的代码违反了得墨忒耳律,因为它调用了getOptions()返回值的getScratch()函数,有调用了getScratch()返回值的getAbsolutePath()方法。
3、数据传送对象
最为精练的数据结构是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或DTO。DTO是非常有用的结构,尤其实在与数据库通信、或解析套接字传递的消息之类场景中。
例如:
public class Address{
private String street;
private String city;
private String state;
public Address(String street,String city,String state){
this.street=street;
this.city=city;
this.state=state;
}
public String getStreet(){
return street;
}
public String getCity(){
return city;
}
public Stirng getState(){
return state;
}
}
二、错误处理
1、Try-Catch-Fanlly语句
某种意义上上,try代码块就像事物,catch代码块将程序维持在一种持续状态,无论代码块中发生了什么均如此。所以在编写可能抛出异常的代码块时最好先写出try-catch-finally语句。这样能帮助你定义代码用户应该期待什么,无论try代码块中执行的代码出什么错都一样。不过,当我们在应用程序中定义异常类时,最重要考虑应该是他们如何被捕获。
来看一个不太好的异常分类例子,它覆盖了调用可能抛出的所有异常:
ACMEPort port=new ACMEPort(12);
try{
port.open();
}catch(DeviceResponseException e){
reportPortError(e);
logger.log("Device response exception",e);
}catch(ATM1212UnlockException e){
reportPortError(e);
logger.log("Unlock exception",e);
}catch(DeviceResponseException e){
reportPortError(e);
logger.log("Device response exception",e);
}finally{
....
}
语句中包含了很多重复代码,此时可以通过打包调用API、确保它返回通过异常类型,从而简化代码。
LocalPort port=new LocalPort(12);
try{
port.open();
} catch{
reportError(e);
logger.log(e.getMessage(),e);
}finally{
...
}
三、单元测试
1、整洁的测试
TDD三大定律
定律一:在编写不能通过的单元测试前,不能编写生产代码。
定律二:只可编写刚好无法通过的单元测试,不能编译也不算通过。
定律三:只可编写刚好足以通过当前失效测试的生产代码。
这样编写程序,我们每天就会编写数十个测试,每月编写数百个测试,每年编写数千个测试。以至于测试将覆盖所有生产代码。测试代码量足以匹敌生产代码量。所以,整洁的测试是十分必要的,他可以使你的代码可拓展,可维护,可复用。然而,整洁的代码有什么要素呢?可读性!
例如:
public void testGetPageHeirarchyAsXml() thorws Exception{
makePages("PageOne","PageOne.ChildOne","PageTwo");
submitRequest("root","type:pages");
assertResponseIsXML();
assertResponseContains(
"PageOne ","PageTwo ","ChildOne "
);
}
public void testSymblicLinksAreNotInXmlPageHierarchy() throws Exception{
WikiPage page=makePage("PageOne");
makePages("PageOne.childOne","PageTwo");
addLinkTo(page,"PageTwo","SymPage");
submitRequest("root","type:pages");
assertResponseIsXML();
assertResponseContains(
"PageOne ","PageTwo ","ChildOne "
);
assertResponseDoesNotContain("SymPage");
}
public void testGetDataAsXml() throws Exception{
makePageWithContent("TestPageOne","test page");
submitRequest("root","type:pages");
assertResponseIsXML();
assertResponseContains("test page",");
}
2、F、I、R、S、T
整洁的测试还遵循一下5条原则
1、快速(Fast):测试应该够快。测试运行缓慢,我们就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻易清理代码。最终,代码就会腐坏。
2、独立(Independent):测试应该相互独立。某个测试不应为下一个测试设定条件。你应该可以单独运行每一个测试,及以任何顺序测试。当测试相互依赖时头一个没通过就会导致一连串测试失败,使得问题诊断变得困难,隐藏了下级错误。
3、可重复(Repeatable):测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本运行测试。如果不能在任意环境中重复,你就总会有个解释其失败的接口。
4、自足验证(Self-Validating):测试应该有布尔值输出。无论是否通过,你都不应该查看日志文件来确认测试是否通过。你不应该手工对比两个不同文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主管,二运行测试也需要更长的手工操作时间。
5、及时(Timely):测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,你会发现生产代码难以测试。
四 类
1、单一职权原则
单一职权原则认为:类或模块应该有且只有一条加以修改的理由。鉴别职权(修改的理由)常常帮助我们在代码中认识到并创建出更好的抽象。
public class Version{
public int getMajorVersionNumber();
public int getMinorVersionNumber();
public int getBuilderNumber();
}
权原则是OO设计中最为重要的概念之一,也是较为容易理解和遵循的概念之一。每达到一定规模的系统都会包括大量逻辑和复杂性。管理这张复杂性的首要目标就是加以组织,一遍开发者知道到哪能找到东西,并且在某个特定时间只需要理解直接有关的复杂性。反之,拥有巨大、多目的类系统,总是让我们在目前并不需要了解的一大堆东西中艰难跋涉。
2、内聚
创建一种极大化内聚类是既不可取也不可能的;另一方面,我们希望内聚性保持在较高的位置。内聚性高意味着类中方法和变量相互依赖、互相结合成一个逻辑整体。
public class Stack{
private int topOfStack=0;
List elements=new LinkedList();
public int size(){}{
return topOfStack;
}
public void push(int element){
topOfStack++;
element.add(element);
}
public int pop() throws PoppedWhenEmpty{
if(topOfStack==0){
throw new PoppedWhenEmpty();
int element =element.get(--topOdStack);
element.remove(topOfStack);
return element;
}
}
}
五 、系统
1、将系统的构造与使用分开
软件系统应将启始过程和启始过程之后运行时逻辑分离开,在启始过程中构建应用对象,也会存在互相缠结的依赖关系。
然而,多数应用程序都没有做分离处理。例如:
public Service getService(){
if(service==null){
serivce=new MyServiceImpl(...);
return service;
}
}
这是典型的延迟初始化/赋值,也有一些好处。在真正用到对象之前,无需操心这种架空构造,启始时间也会更短,而且还能保证永远不会返回null值。然而,我们也得到MyServiceImpl及其构造器所需一切的硬编码依赖。不分解这些依赖关系就无法编译,即使在运行时永不使用这种类型的对象。如果MyServiceImpl
是个重型对象,则测试也会是个问题。
未完。。。