中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 软件工程 > 系统分析 > 系统设计 > 架构设计
设计不佳的代价——顺序耦合性Bug案例一则
作者:未知 时间:2005-08-07 11:17 出处:系统分析之窗 责编:chinaitpower
              摘要:设计不佳的代价——顺序耦合性Bug案例一则

设计不佳的代价——顺序耦合性Bug案例一则


原创作者:wakeful
转载请注明:来自Sawin系统分析之窗
最后修改时间:2005-5-26

 

设计不佳的代价——顺序耦合性Bug案例一则

(wenyu@china.com)

200311

本文已发表于《CSDN开发高手》200312

 

Bug发现得越晚,修正的成本就越高。本文的例子就是一个很好的证明。

本文首先对顺序耦合性做一个简单介绍;然后讲述笔者在实际工作中遇到的一个“顺序耦合性Bug”,这种Bug通过代码走查很难发现,常需求诸于跟踪调试才行;最后通过重构改进设计,从根本上解除顺序耦合性隐患。

 

一、顺序耦合性简介

耦合性(Coupling)是两个元素之间的一种关系,为了保持正确性,需要两个元素同时进行变化。耦合性可以分为静态耦合性、动态耦合性和差异耦合性3大类。

顺序耦合性是静态耦合性的一种,为了保持正确性,两个元素必须以特定的顺序出现。

例如,下列两条语句的顺序,反映了变量“先定义后使用”的要求,不能颠倒。

int i;

i = 6 ;

再例如,下列两条语句的顺序,也不能颠倒。

FILE * fp = fopen( “a.txt” );

fcolse( fp );

 

二、遭遇顺序耦合性Bug

1项目简介

这是一个多媒体编辑软件,要求编辑的结果可以保存为“项目”,以便以后打开或进一步编辑。经简化的软件架构如下图所示。问题领域层(PD)对多媒体编辑领域进行了抽象,数据管理层(DM)负责数据的保存和读取,用户界面层(UI)负责为用户提供图形化用户界面。并且,DM层和UI层并不相互依赖,它们都仅依赖于PD层,这样的软件架构将PD层、DM层和UI层最大限度地独立开来,有利于应对需求的变更。很自然地,由3个相对独立的开发小组,分别负责上述3个子系统。

 

 

2、新的需求

原先版本的保存和打开功能,都是针对专有格式的二进制“项目”文件。在新的版本中,要求支持XML格式的“项目”文件。

由于软件架构设计的合理性,支持这项新需求的工作相当简单。首先,PD小组和DM小组一起,制定XML文件的格式,形成文档。然后,DM小组指派XML高手,写一个叫做CXmlSaver的类去实现CProjectSaver接口,该类按照XML文件格式文档,将Project Model保存到XML中去;同理,还要写一个叫做CxmlLoader的类去实现CProjectLoader接口;如下图所示。最后,UI小组做少量工作,增加相关功能的界面。

 

 

3、发现Bug

哈,我就是DM小组中被指派写CXmlLoader的家伙。但是很不幸,我的下列代码有Bug,不能将XML文件中的Video File的信息正确地LoadProject Model中的Video File节点。

void CXmlLoader::LoadVideoFile(CVideoFile * inVideo)

{

...

inVideo->SetStartTime( GetNodeAttr(Video, "startTime") );

inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );

inVideo->SetDuration( GetNodeAttr(Video, "duration") );

...

}

 

 

 

 

 

 

 

 

 

 


4、跟踪调试

这到底是怎样回事呢?我进行了一番代码走查,并没有发现什么不合理的地方。

 

于是,我在inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );处设置断点,然后跟踪到void CVideoFile::SetEndTime(double inEndTime)里面去。我看到了下面的代码。

CVideoFile::CVideoFile()

{

       ...

       mDuration = 0;

       ...

}

void CVideoFile::SetEndTime(double inEndTime)

{

       if (inEndTime > mDuration)

              mEndTime = mDuration;

       else if (inEndTime > 0)

              mEndTime = inEndTime;

       else

              mEndTime = 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


查看一下变量当前的值。呀,此处的mDuration还保持着构造函数中为它赋的初值0哟,而inEndTime的值是324.6。于是,if (inEndTime > mDuration)成立,所以mEndTime被设置为mDuration的值0

 

再跟踪到inVideo->SetDuration( GetNodeAttr(Video, "duration") );里面看看,我看到了下面的代码。

void CVideoFile::SetDuration(double inDuration)

{

       if (inDuration > 0)

              mDuration = inDuration;

       else

              mDuration = 0;

}

 

 

 

 

 

 

 

 

 

 


看一下变量当前的值。咦,这里inDuration的值是600.0,倒是比刚才要设置的EndTime的值324.6大,显然,刚才inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );中,应该用这个Duration值来进行合法性检查的。喔,调用inVideo->SetDuration( GetNodeAttr(Video, "duration") );晚了,应该在inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );之前就调用的。

 

原因找到了,只需调整一下语句的顺序,Bug就被修正了。代码如下所示。

void CXmlLoader::LoadVideoFile(CVideoFile * inVideo)

{

...

inVideo->SetDuration( GetNodeAttr(Video, "duration") );

inVideo->SetStartTime( GetNodeAttr(Video, "startTime") );

inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );

...

}

 

 

 

 

 

 

 

 

 

 

 


三、通过重构改进设计

从表面上看,Bug已经被修正。但是,顺序耦合性的隐患还在;一不留神,同样的Bug还会出现。下面,我们通过重构改进设计,从根本上解除顺序耦合性隐患。

 

1分析原因

先来分析一下CVideoFile的设计思想。作为Project Model的一部分,PD小组实现了一个严格检查合法性的CVideoFile,任何时候都不允许一个Video File的结束时间比其总时间大。

再看问题出在哪里。从CVideoFile本身提供的接口,看不出顺序耦合性的存在——我无从知道必须先调用CVideoFile::SetDuration(),后调用CVideoFile::SetEndTime()

 

2解决策略

设计一个进行严格检查合法性的CVideoFile,是合理的,应当保留。

但是CVideoFile的接口设计的不合理。我们应当改进CVideoFile的接口设计,消除其中的顺序耦合性隐患,使用户在不知道先设置Duration后设置EndTime的情况下,也不会犯错误。

下面我们通过步步地重构,来达到这个目的。

 

3运用重构的“Extract Method”成例

重构的“Extract Method”成例建议,如果一个代码段拥有完整的意义,则应当将它们单独抽出,放入一个方法,并根据其意义为方法命名。

下列使用CVideoFile的代码段,正是这样的情况;而且,如果不加改进,很容易出现顺序耦合性Bug

void CXmlLoader::LoadVideoFile(CVideoFile * inVideo)

{

...

inVideo->SetDuration( GetNodeAttr(Video, "duration") );

inVideo->SetStartTime( GetNodeAttr(Video, "startTime") );

inVideo->SetEndTime( GetNodeAttr(Video, "endTime") );

...

}

 

 

 

 

 

 

 

 


但是,我们决定对CVideoFile本身进行“Extract Method”重构,而不是对CVideoFile的客户代码,因为这样所有使用CVideoFile的客户代码都可以从中受益。我们为CVideoFile增加了这样一个公共方法:

void CVideoFile::SetTribleTime(double inStartTime,

 double inEndTime, double inDuration)

{

SetDuration(inDuration);

SetStartTime(inStartTime);

SetEndTime(inEndTime);

}

 

 

 

 

 

 

 

 

 

 


4运用重构的“Hide Method”成例

重构的“Hide Method”成例建议,如果一个方法并不被除所属类之外的其它类使用,那么该方法应当是private方法。

作为CVideoFile的设计者,我希望客户程序调用SetTribleTime()去设置相关属性,并不希望客户程序直接调用SetDuration()SetStartTime()SetEndTime()中的任何一个。所以我将这3个方法置为private的。

 

5运用重构的“Introduce Parameter Object”成例

重构的“Introduce Parameter Object”成例建议,如果一个方法的多个参数可以很自然地划分在一起,就应当用一个对象来代替它们。

方法void CVideoFile::SetTribleTime(double inStartTime, double inEndTime, double inDuration)3个参数显然满足这样的条件,所以,我们可以引入一个CTribleTime类来代替它们,代码如下。

class CTribleTime

{

double  duration;

double  startTime;

double  endTime;

};

void CVideoFile::SetTribleTime(CTribleTime & inTribleTime)

{

SetDuration(inTribleTime.duration);

SetStartTime(inTribleTime.startTime);

SetEndTime(inTribleTime.endTime);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


6其它改进

现在的设计并不完美,因为我们在进行多媒体编辑的时候,可能只需要设置Start Time,这时代码会下面所示,很繁琐。

CTribleTime t;

t.startTime = 200;

t.endTime = video.GetEndTime();

t.duration = video.GetDuration();

video.SetTribleTime( t );

 

 

 

 

 

 

 

 


下面,我们为CVideoFile增加一个GetTribleTime()方法,通过它可以方便地取得CVideoFile3Time的当前值。最后,我们仅设置Start Time的代码看起来这样。

CTribleTime t = video.GetTribleTime();

t.startTime = 200;

video.SetTribleTime( t );

 

 

 

 

 

 


另外,我们可以将CVideoFile3个成员变量mStartTimemEndTimemDuration改用一个CTribleTime mTribleTime实现;删除GetDuration()GetStartTime()GetEndTime()3个方法;等等。

 

参考文献:

《重构——改善既有代码的设计(影印版)》 Martin Fowler

UML面向对象设计基础》 Meilir Page-Jones包晓露等译

 

 

【作者介绍】 wakeful

温昱,架构设计师,资深咨询顾问,松耦合空间(http://lcspace.nease.net)创办人。擅长面向对象、架构和框架设计,对设计模式、UML和软件工程有深入研究。
作者Email地址:xinxiu123@sina.com

 
关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有