代码整洁之道总结(二)

一、对象和数据结构
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(
         "<name>PageOne</name>","<name>PageTwo</name>","<name>ChildOne</name>"
    );
   }
   public void testSymblicLinksAreNotInXmlPageHierarchy() throws Exception{
      WikiPage page=makePage("PageOne");
      makePages("PageOne.childOne","PageTwo");
      addLinkTo(page,"PageTwo","SymPage");
      submitRequest("root","type:pages");
      assertResponseIsXML();
       assertResponseContains(
         "<name>PageOne</name>","<name>PageTwo</name>","<name>ChildOne</name>"
    );
    assertResponseDoesNotContain("SymPage");
}
public void testGetDataAsXml() throws Exception{
   makePageWithContent("TestPageOne","test page");
    submitRequest("root","type:pages");
      assertResponseIsXML();
       assertResponseContains("test page","<Test");
}

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<Integer> elements=new LinkedList<Inetger>();
          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
是个重型对象,则测试也会是个问题。

未完。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值