宠文网

奔跑吧,程序员

宠文网 > 科普学习 > 奔跑吧,程序员

6.5不要重复自己

书籍名:《奔跑吧,程序员》    作者:叶夫根尼.布里克曼
    《奔跑吧,程序员》章节:6.5不要重复自己,宠文网网友提供全文无弹窗免费在线阅读。!



系统中的每一项知识都必须具有单一、无歧义、权威的表示。

——Andrew  Hunt、David  Thomas,《程序员修炼之道》

避免重复是实现整洁代码最根本的原则之一。有点讽刺的是,避免重复的思想不断以不同的名称重复出现,比如不要重复自己(DRY)、单点真理、一次并且只有一次。重复可能出现在技术实现的任何一个地方,包括架构、代码、测试、过程、需求和文档,它可能是以下几个原因引起的。

·  我们需要用多种方式表示相同的信息,比如在数据库模式、数据库访问层、HTML标记和CSS中列出相同的列。

·  语言限制:比如在Java中指定getter和setter。

·  缺少反规范化:比如从数据库的表中导出数据。

·  缺少时间,导致需要复制和粘贴代码。

·  没有意识到这个问题,比如多个开发人员在一个大型代码库中创建自己的StringUtil类,因为他们不知道类似的类已经存在(也因为他们不知道开源库甚至有更好的版本)。

重复不仅因为要多次实现相同的事情而浪费了时间,而且还妨碍了我们对代码的理解和维护。如果代码不DRY,那么我们每需要回答一个有关代码问题,就可能必须去查看多个地方;我们每需要做修改,就必须确保不会错过任何一个副本;如果有一个副本不同步,就会导致矛盾和bug的出现。

如果发现自己在一次次地编写相同的代码,或者只是做一小点改变就会涉及代码库中的一半内容,那么就要想想办法让代码变得更加DRY。特别当我们必须一次次地重复相同的过程,那么就需要实现一种自动化的过程;如果不只在一个地方有相同的逻辑,那么就需要实现抽象,以便共享单一的实现。

举个例子,来看看BookParser代码是如何构造JSON的:

String  csvLine,  json  =  "[";  while  ((csvLine  =  csvReader.readLine())  !=  null)  {  //  ...  json  +=  "{";  json  +=  "title:\""  +  title  +  "\",";  json  +=  "author:\""  +  author  +  "\",";  json  +=  "pages:\""  +  pages  +  "\",";  json  +=  "category:\""  +  category  +  "\"";  json  +=  "},";  }  json  +=  "]";

这里有很多重复:将JSON元素放在括号([]  或  {})中的代码出现了多次,在JSON对象中创建键-值项的代码也出现了多次。所有这些重复导致了几个bug,你发现了吗?其中一个是经典的复制  /  粘贴错误,pages变量是一个Integer,在插入JSON的时候不应该放在引号中:

json  +=  "pages:\""  +  pages  +  "\",";

第二个bug是JSON数组最后一个元素的后面多了一个逗号:

json  +=  "},";

我们可以修复这些bug,但最大的重复仍然没有去掉:JSON和CSV是通用的数据格式,我们没理由从头编写代码去处理它们。重新发明轮子是最常见和不必要的重复,只要有可能,就应该使用开源库去代替(阅读5.3节了解更多信息)。例如,可以使用Java的Jackson库,让代码更加DRY和稳定。因为Java是基于类的面向对象语言,表示数据的规范做法就是创建一个类:

public  class  Book  {  private  String  title;  private  String  author;  private  int  pages;  private  Category  category;  //  (省略了构造函数和getter方法)  }

可以构造出Book对象的一个List,代替手动生成JSON的String:

List  books  =  new  ArrayList<>();  while  ((csvLine  =  csvReader.readLine())  !=  null)  {  csvFields  =  csvLine.split(",");  String  title  =  csvFields[TITLE.ordinal()];  String  author  =  csvFields[AUTHOR.ordinal()];  Integer  pages  =  Integer.parseInt(csvFields[PAGES.ordinal()]);  Category  category  =  Category.valueOf(csvFields[CATEGORY.ordinal()]);  books.add(new  Book(title,  author,  pages,  category));  }

Jackson库可以使用类中的字段名作为JSON中的键,将大部分的Java类转换为等效的JSON表示。使用Jackson的ObjectMapper类,我们只需要两行代码就可以将上面的Book对象的List转换到JSON文件中:

ObjectMapper  mapper  =  new  ObjectMapper();  mapper.writeValue(outputJson,  books);

与之类似,也可以使用Apache  Commons  CSV库简化CSV的解析过程。CSVParser类可以使用parse方法读取一个CSV文件并用withHeader方法把标签赋给每一列:

List  records  =  CSVFormat  .DEFAULT  .withHeader(TITLE.name(),  AUTHOR.name(),  PAGES.name(),  CATEGORY.name())  .parse(new  FileReader(inputCsv))  .getRecords();

我们现在不用手动敲逗号去分隔每一行,也不用为列的下标烦恼。我们可以遍历从CSVParser中得到的记录,按照名称读取每一列的内容:

for  (CSVRecord  record  :  records)  {  String  title  =  record.get(TITLE);  String  author  =  record.get(AUTHOR);  Integer  pages  =  Integer.parseInt(record.get(PAGES));  Category  category  =  Category.valueOf(record.get(CATEGORY));  books.add(new  Book(title,  author,  pages,  category));  }

我们不再需要手动编写有很多bug的代码,而是利用流行的、久经考验的开源库。这样代码会更短,看起来更像惯用的Java,bug更少,重复也更少。下面是整个convertCsvToJson函数:

public  void  convertCsvToJson(File  inputCsv,  File  outputJson)  throws  IOException  {  List  books  =  new  ArrayList<>();  List  records  =  CSVFormat  .DEFAULT  .withHeader(TITLE.name(),  AUTHOR.name(),  PAGES.name(),  CATEGORY.name())  .parse(new  FileReader(inputCsv))  .getRecords();  for  (CSVRecord  record  :  records)  {  String  title  =  record.get(TITLE);  String  author  =  record.get(AUTHOR);  Integer  pages  =  Integer.parseInt(record.get(PAGES));  Category  category  =  Category.valueOf(record.get(CATEGORY));  books.add(new  Book(title,  author,  pages,  category));  }  ObjectMapper  mapper  =  new  ObjectMapper();  mapper.writeValue(outputJson,  books);  }