一次重构经历

Posted SimbaStar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一次重构经历相关的知识,希望对你有一定的参考价值。

最近做了挺多从不同的网页抓取数据的工作,重复多了之后,有了重构的想法,使用的语言是java。

1. 以前的做法:

因为是一个功能性程序,所以把它当做了过称式程序,没有建立特别的类:

public static void main(String[] args) throws IOException, SQLException {
  fetchData("http://...", "A");
}

private static void fetchData(String url, String timePoint) throws IOException, SQLException {
  String content = getHttpContent(url);   // 网页内容
  Date dataDate = getDataDate(content);   // 时间
  List<MainBoard> boardList = getBoardList(content, dataDate); // 解析数据集
  storeDB(boardList, dataDate, timePoint);  // 写入数据库
}

而一些变量值也写死在程序中:

Connection dbConn = null;
dbConn = DriverManager.getConnection("jdbc:sqlserver://192.168.1.1:123;databaseName=AAAA", "12345", "12345");
Statement stmt = dbConn.createStatement();

用于获取时间的getBoardList()函数内部,通过正则表达式和遍历比较取出数据,返回相关的数据类。

storeDB函数负责写入数据库:

String sql = "delete from TABLE where dataDate=‘" + simpleDateFormat.format(date) + "‘ and timePoint=‘" + timePoint + "‘";
stmt.execute(sql);

for (MainBoard board : boardList) {
  String ss = "insert into TABLE values" + "(‘" + board.getCode() + "‘," +
  "‘" + board.getStockName() + "‘," +
  board.getSH() + "," +
  board.getDollar() + "," +
  "‘" + timePoint + "‘," +
  "‘" + simpleDateFormat.format(board.getDataDate()) + "‘," +
    "‘" + timeFormat.format(board.getUpdateTime()) + "‘)";
  stmt.executeUpdate(ss);
}

最初的这个结构基本上可以看成是纯过程化,且没有根据功能放进不同的类文件。如果后续需求需要抓取更多的不同类型的网页,则代码会臃肿、混乱。

每有一个新的格式,上述的getDataDate、getBoardList、storeDB和MainBoard都需要更换,并且会产生空数据类。

2. 重构,抽象:

重复的多了之后,就有了提高代码架构的需求。先补充了理论知识,《重构》和《Head First》结合着看。

在《重构》中看到这么一段描述:

将过程化设计转向对象设计:
1.针对每一个记录类型,转变为只含访问函数的哑数据对象。
2.针对每一处过程化风格,将该处代码提炼到一个独立类中。
3.针对每一段长长的程序,将它分解,再将分解后的函数分别移到它所相关的哑数据类中。
4.重复上述步骤。

原来对象设计是以数据对象为基础,再把与此数据相关的操作或代码提炼成函数,放入此数据对象中。

以前编写程序时,是以过程化为主。

所以思维上的第一个变化是:

把程序抽象的看成是一个数据加工厂,加工厂由许多个模块/部门组合而成。数据看成是一个流,流过程序这个加工厂,被不同部门处理,被转换,但是最终都会有一个存储和展示形式(也就是载体)。

   从更高的层次观察数据的处理是优化结构的重要方式,把相同的步骤/动作抽象出来,把具体的实现细节留给不同的类。

  所以,程序的运行可以分为 数据流 和 对数据流的处理。每次有需求变更时,因为模块的接口是定义好的,修改不同模块的实现即可。

隐约有了抽象的思想后,再次结合程序重新思考代码的组织方式。

观察到哑数据对象,也就是只含有数据变量和相应取值/设值方法的类。结合实践发现把和此数据类相关的操作放入哑数据类中,确实是比较好的组织方式。

比如,上面的storeDB函数接收哑数据Data类作为参数,构造数据库语句。那么这里就可以把这个函数放入Data类中:

class Data {
    // ... 
    public String generateInsertSql() {
     String ss = "insert into TABLE values" + "(‘" + board.getCode() + "‘," +
        "‘" + board.getStockName() + "‘," +
        board.getSH() + "," +
        board.getDollar() + "," +
        "‘" + timePoint + "‘," +
        "‘" + simpleDateFormat.format(board.getDataDate()) + "‘," +
         "‘" + timeFormat.format(board.getUpdateTime()) + "‘)";
      return ss;
   }
}

 思考为什么这个组织方式好于以前的形式?原因之一就是这样更符合人的思维方式。

 再以这样的方式思考组织程序,继而思维产生了第二个变化:

把类看成以数据为中心,附属着对这个数据的各种操作作为函数。而程序就可以看成是各个附带着行为的数据之间的交互。这样以前总结出的数据流就分化为一个个的个体,对数据的加工操作附属于不同的个体。

 感觉似乎摸到了面向对象的门道,决定再去知乎上看看大家的讨论,发现https://www.zhihu.com/question/19701980这篇蛮具有启发意义的。

 进而更进一步认识了面向对象:

一个操作或一件事由谁来完成,强调的是“谁”。由此,程序变化为一群“活物”之间的交互。

 回到程序实践,从过程化结构中抽象出三个主体:UrlConstructor,HttpService,SqlConstructor。分别代表产生url字符串、读取网页内容、构造sql语句。UrlConstructor还有一个功能是从网页内容里解析提取目标数据。

 这时流程变为,UrlConstructor构造出url,然后HttpService接收url并取得网页数据,交由UrlConstructor解析处理,并由SqlConstructor产生sql语句,最后又DB对象写入数据库。

 但是这时又产生了一个新的困惑:虽然结构上比以前抽象的一些,但是感觉依然需要一些结构化的语句来处理对象间的交互。如何消除这部分影响?继续学习,发现一个讨论http://bbs.csdn.net/topics/40441744算是解释了心中的疑惑。

 思维再次发生了变化:

 面向对象是一种思维,和语言无关。不是写了顺序执行的代码就是面向过程,面向对象强调的是以什么样的思维来组织程序。

 用c也可以写出面向对象的程序,而组织的不好,用Java写出来的也会是面向过程的程序。所以,如果组织的好,有顺序执行代码也是面向对象的。

 

比较常见的例子就是全局变量,在函数中使用了全局变量也就破坏了类的封装性,就不是面向对象编程了。好的做法是,全局变量都作为参数传入成员函数中,实现封装。这么做也可以方便单元测试,和提高清晰度。

 上述思维也很好的解释了面向对象的三大要素:封装,继承,多态。封装即把数据和操作当成一个整体,对外只暴露接口。继承和多态是的程序可以方便扩展,调用者无需关注实现细节,进而灵活应对需求变更。这方面的讨论可以看下https://www.zhihu.com/question/20275578里面尤其是“invalid s”的回答。

 至此,终于理解了依赖倒置,依赖注入还有控制反转等一些以前没有领悟的概念。见https://www.zhihu.com/question/31021366核心思想即是面向接口编程。

 在理清了面向对象思想之后,才算是可以初窥设计模式的门径。如装饰器模式、工厂模式、观察者模式等,从面向对象的角度去理解会非常快速。设计模式有几个原则:1.面向接口编程;2.对扩展开放,对修改封闭;3.组合大于继承。使用设计模式往往会遇到一些问题需要权衡各方面做决定。

但是反过来说,并不是面向对象编程就一定要往设计模式上面靠。“设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结”,设计模式一般都有一个适用场景,超出这个范围,也不见得还有效。

 设计模式之上就是框架的设计,这个暂时不做深究。

 接着再说说重构,重构我认为也可以算是一种思维,需要固化在脑中,某种意义上编程==重构。

 重构我认为有几个关键思想:1.重复代码移动到统一的地方;2.如果需要修改,目标是只修改某一个地方;3.一个变化只影响一个类;4.一个类只受一个变化的影响。等等~~

 因为“代码首先是为人写的,其次才是为计算机写的”。

 

 最重要的是努力实践,现在我对面向对象思维的理解也才刚开始,以后肯定会回过头反复思考再实践。希望能越来越熟练,高效。

 

 最后借着这次重构,记录下关于java正则性能的感想。

 因为写正则表达式一般都会使用到通配符如:

 

<td .*><a.*>(.*)</a></td>

 

 而正则并不意味着查找效率就高。通配符可能会导致匹配时间增加,所以一些简单的表达式使用诸如indexOf()这类的函数自己实现的话性能可能会提高一些。从测试结果看,简单表达式自己时间会提高一小部分性能,但是对比读取网页的实践微乎其微。所以如何使用正则?是自己实现还是用复杂的表达式,需要先进行测试,再做决定。

 

 

以上是关于一次重构经历的主要内容,如果未能解决你的问题,请参考以下文章

一次订单系统重构实践

如何重构这个 Java 代码片段

一次排查页面重复请求的经历

提效小技巧——记录那些不常用的代码片段

日订单量达到100万单后,我们做了订单中心重构

使用片段清除回栈