代码重构实践 —— 代码改进的一个案例分析

原创 2004年06月15日 10:58:00

      前几天,我接手了一个使用 DELPHI 开发的项目,其中,较迫切的任务是需要解决原有几处代码的性能问题。其中,有一处代码较为典型,因此,特地将其详细问题、解决思路与相关想法整理出来,供大家参考讨论。
      在设计进行数据处理工作的代码时,我们常常会发现有这样的需要:将一些数据按树状的样式显示到屏幕上,方便用户查看或其他操作。举例说:我们有可能想在TREEVIEW控件中显示地区,然后将隶属该区的供应商加入到对应的地区中;或者又是显示客户,然后将隶属于该客户的订单号加入到对应的客户中(这是简要的流程,在应用的时候,会有各种变化,受到其他的业务规则影响——而往往又是这些东西迷惑了人们)。 
      在上述的项目中也碰到这样的情况,原先的代码按下面的思路进行设计:首先,将“地区”数据查询到一个数据对象中,并通过迭代运算将该部分数据加到树图上;然后,分别查询各个“地区”的“供应商”数据,再将该部分数据分别放到“地区”节点之下。下面是其主要的代码:
      adqArea.SQL.TEXT := 'SELECT Name FROM Area ORDER BY Name'; // 查询“地区”的SQL语句
      adqArea.Open;      // 打开“地区”记录集
      while not adqArea.EOF do     // 遍历“地区”数据
      begin
          …… // 将当前记录的“地区”写到树图控件上;
          adqProvider.SQL.TEXT  := 'SELECT Provider FROM Provider ORDER BY Provider WHERE AreaName = ' + QuotedStr(adqArea.FieldByName('Name').AsString); // 查询符合当前“地区”条件的“供应商”的SQL语句。 
          adqProvider.Open;     // 打开“供应商”记录集
          while not adqProvider.EOF do
          begin
              …… // 将当前记录的“供应商”加到树图控件中相对应的“地区”节点下;
              adqProvider.Next;
          end;
          adqArea.Next;
      end;

      这种代码与我们平时考虑问题的方式相近,我们会很自然而然地想起这样做。不过,它存在着非常明显的性能缺陷,在类似这样的代码中,数据查询的次数 = “地区”的个数 + 1。不消说,这样在网络查询的时候,服务器与网络都需要进行多次的计算或传输工作,花费很多的时间资源或带宽资源;此外还有,数据对象需要多次进行属性的设置与进行打开、关闭等操作,也有一定程度上的资源消耗。因此,结果最显然不过了,屏幕显示的速度肯定不会快,换句说,性能将会达不到实际应用的速度指标要求。
      深入分析该应用的要求来看,其实这是将客户数据以从属于地区的形式在树状视图上排列显示、又或者是将订单数据以从属于客户的形式排列到树状视图中的应用。一句话,就是将按物理顺序排列的数据进行加工,按某种逻辑顺序拼成一棵树。大家知道,因为数据输入顺序的原因,在数据库的物理空间中,“供应商”数据的位置都会是以“地区”作“犬牙交错”状的顺序进行排列,象下表所示的一样:
  
      -----------------------------------------------------------------------
    物理位置顺序                  供应商                  地区
                1                               张三              中国
                2                               李四              美国
                3                               王五              中国
                4                               郑六              新加坡
                5                               李七              中国
                …
                …
                …

      而在我们的应用中,要求的是将相同“地区”的“供应商”组织起来以树图的方式进行显示和操作,归结起来,这是一个数据组织方式的应用问题。它让我联想起“空间”与“时间”的概念来,如果能够针对上面代码中多次查询的缺陷进行优化,利用速度快的内存存储需要的数据,减少查询的次数,就可以提高应用时候的性能了。按这个思路出发,有以下三个方面问题需要考虑或解决:首先,将所有符合条件的数据一次性地查询到内存中的数据对象去;其次,这些数据应该按逻辑顺序进行组织排列,以方便下一步的运算;第三、在遍历数据对象进行运算时,通过比较当前行与上一行数据中的标志值,检测到逻辑顺序变化,并以此作控制,分别进行树图中父节点与子节点的绘制工作。所以,我就设计了下述的主要代码(为突出主题,忽略了一些数据检测和错误处理的代码):
      设定一个变量 PreviousAreaName,用以记录当前数据之前一行数据的“地区”名称;
      adqProvider.SQL.TEXT  := 'SELECT Provider, Area.Name FROM Provider RIGHT JION Area ON AreaName = Area.Name ORDER BY Area.Name';  // 查询所有的地区及包含的供应商数据。
      adqProvider.Open;
      PreviousAreaName := '';
      while not adqProvider.EOF do
      begin
          if PreviousAreaName <> adqProvider.FieldByName('Name').AsString then
          begin
              …… // 将当前记录的“地区”加到树的节点中去作为新的父节点
              PreviousAreaName = adqProvider.FieldByName('Name').AsString;
          end;
          if not adqProvider.FieldByName('Provider').IsNull then  // 由于 RIGHT JION 的缘故,某些地区的“供应商”的值可能为空值;
              …… // 将当前记录的“供应商”加到树图控件中的当前“地区”父节点下;
          adqProvider.Next;
      end;
 
      这些代码做到了上述考虑的三个方面的要求,让人高兴的是,该部分功能程序的显示速度有了数量级的提升,由原来11秒的时间提升到1秒的时间。因此说,通过深入分析程序流程,重新设计了程序的处理方式,我们实现了优化性能的目标。总结起来,关键就在于新的程序能够将需要的数据一次性地加载到本地内存里进行运算,大大地减少了较慢的网络传输速度影响。
      进一步地看,这种解决方式与“预先加载数据”的数据处理方式类似,即将数据从速度较慢的媒介中一次性地加载到速度较快的内存中,进行操作,从而达到提高响应速度的目的。该方式普遍存在于各种的数据应用环境中,象 WINDOWS 系统在启动的时候将系统图标库与注册表数据加载到内存中,因此它能够让应用程序可以在极短的时间内就可以取得需要的系统图标或注册表数据。
      推广开来,在数据处理中的程序设计中,我们都可以借鉴这种批量获取数据的处理方式。当然,不是所有情况都适合使用“批量加载数据”的处理方式,如果数据量特别大的时候,我们还是有必要再将该部分的数据细分成几个小的部分,至于如何处理就要根据实际情况而定了。
      以上这些是当前重构工作、代码改进的成果,主要是从优化性能的角度出发来考虑的;今后,我们可以从OOP的角度、接口等程序架构方面继续作一些改进工作。
      有道是功夫在于一个“勤”字,要设计出性能良好的代码,就要勤于研究与改进现成的代码,不断地积累良好的经验。本文在这方面作一个小的尝试,抛砖引玉,期望各位同道也能多谈谈自己一些好的设计经验或心得,相互促进共同提高。

QQ:       272568028
E-MAIL:gjguoji@tom.com
欢迎朋友们联络,深入研究与探讨。

改善既有代码的设计(一)----------小案例展示重构的意义

本书很好的一点就是上来没有讲历史渊源这一类的催人入睡的课题,而是先用一个小案例来展示重构的过程和意义,这也是我看着本书没有至于前言的主要原因,看完了本案例,才会觉得代码真是一项艺术,与难度无关,更多的...
  • u010568407
  • u010568407
  • 2016年07月22日 11:17
  • 942

《重构-改善代码既有的设计》重构,第一个案例

起点:编写3个类的代码 1、第一个类-影片(Movie):package com.lee.test.aFirstExample;public class Movie { /** * ...
  • limuzi13
  • limuzi13
  • 2016年11月16日 17:15
  • 528

一个C++程序重构的例子——糟糕的代码

由于工作中常用c++的原因,在看《Refactoring — Improving the Design of Exsiting Code》这本书时,将java的例子写成了c++程序,略做总结,以深理解...
  • tongqiao
  • tongqiao
  • 2012年09月01日 21:08
  • 1784

代码重构意义和方法

摘要:很多人认为重构浪费时间,影响项目进度,其实重构不仅可以让我们的代码更加强壮而且还可以加快我们的项目进度。就和我们盖一个高楼大厦一样,我们的架构和地基越好,我们的楼房会越坚固和牢靠。 一、什么是...
  • jinglijun
  • jinglijun
  • 2014年11月20日 13:31
  • 6030

谈谈代码重构

开发人员可能听到过"bad smell"这个词,这个词用在软件编码中是什么意思呢? 代码还有smell吗?当然没有,正如计算机病毒,bug等词一样,这只是个形象的说法。这个词在这里的意思是代码实现了需...
  • weiky626
  • weiky626
  • 2007年05月10日 10:03
  • 31334

C++代码重构——从C global到C++ template

如何实现代码的从算法正确到优秀的面向对象(或模板)封装?这或许是在写C++代码时经常需要考虑的。本文以有界队列为例,描述了一种C++代码从C算法到C++模板的重构方法。这种方法简单可行,实现了逻辑(算...
  • xusiwei1236
  • xusiwei1236
  • 2014年04月18日 21:57
  • 4242

Hive日志分析实践例子

日志格式为: 36.248.169.9 - - [22/Sep/2013:01:21:45 +0800] "GET /mapreduce/hadoop-terasort-analyse/ HTTP/...
  • lzlchangqi
  • lzlchangqi
  • 2014年01月23日 22:03
  • 1851

常用的代码重构方法

一.提取子函数 说白了就是一个大函数里,可以根据不同功能分成几个小函数,因为说不定,其他函数也可能会用到其中的函数 二.把大家都要用的方法放到父类中     所有对象都要执行同一个方法,那就把这个方法...
  • u011889786
  • u011889786
  • 2016年07月09日 10:13
  • 4429

为什么要重构&如何实施代码重构?

代码重构简介:(英语:Code refactoring)重构就是在不改变软件系统外部行为的前提下,改善它的内部结构。 为什么要重构(Refactoring)??? 为什么要这么做?投入精力...
  • ztx643702008
  • ztx643702008
  • 2016年08月29日 22:01
  • 2558

一个可供参考的企业应用容器化实践案例

本文分为两个部分,第一部分比较常规,介绍如何用OpenShift搭建自动化测试、开发环境。第二部分介绍了在容器使用过程中遇到的问题,以及应对方案。作者在原有的OpenShift Router仅支持7层...
  • M2l0ZgSsVc7r69eFdTj
  • M2l0ZgSsVc7r69eFdTj
  • 2018年01月26日 00:00
  • 41
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:代码重构实践 —— 代码改进的一个案例分析
举报原因:
原因补充:

(最多只允许输入30个字)