time 
设为首页】【收藏本站
当前位置: 主页 > 软件工程 > 分析与建模 > 设计,由你掌握

设计,由你掌握

时间:2009-12-01 20:27 点击:422次 字体:[ ]




  前言:XP中有个准则,就是只做目前你需要做的。例如,我需要加法运算,你就没有必要实现乘法运算,因为这不是客户需要的。因此,在开发中,我们可以不去考虑程序对于未来的扩展性。“简单最好!”那么,是否就不需要设计了呢?至于设计模式,是否也可以不去了解了呢?答案至少是否定的。因为客户的需求是“与时俱进”的,现在不实现,并不等于今后不实现。在实现中,不管是重构,还是重新设计,通过应用设计模式,能令你如虎添翼。关键不在于设计模式是否重要,而在于你怎么应用它,以及选择什么样的时机。总而言之,设计,由你掌握!

  一、从需求开始

  在我们的项目中,作费用结算的时候,客户要求将该过程与结果写入到日志文件中。不过他们的要求很善良,只需要知道日志记录结算开始与结束的时间而已。是的,按照XP的理念,我们只需要做好客户需要的事情就OK了。既然是这样,事情就好办,代码轻易而举就实现了:

  public class Fee
  {
   //结算程序将调用数据层的相关方法,访问数据库;
   //为简单起见,我用累加数取代;
   public double SettleFee(double money,int records)
   {
    double result = 0.0;
    //我用控制台输出来表示写日志;
    Console.WriteLine(”Start settling fee at {0}”,DateTime.Now);
    for (int i=0;i   {
     result+=money;
    }
    Console.WriteLine(”Settling fee finished at {0}”,DateTime.Now);
    return result;
   }
  }
  写好这个,还差点什么?不错,我们还需要为Fee类撰写相应的测试代码,做好单元测试。可能对于传统的程序员来说,更喜欢的是在编码完成后,再根据测试计划编写测试样例,最后测试。但敏捷开发的要求却是测试先行,单元测试是必不可少的环节。不过,我认为单元测试毕竟只是一种手段。我们在实际的项目开发中,对于单元测试不可拘泥教科书的要求,按部就班地一步一步进行;而应该根据实际情况,比如开发者对语言的掌握程度,对设计的理解等等,来决定你单元测试的步骤,乃至于重构的步伐。
  [TestFixture]
  public class TestFee
  {
   [Test]
   pubic void Settle()
   {
    Fee fee = new Fee();
    Assert.IsNotNull(fee);
    Assert.AreEqual(6,fee.SettleFee(2.0,3));
   }
  }
  在NUnit中打开这个测试类,并运行。毫无疑义,你会看到测试的绿灯全部都亮了。你可以在NUnit中看看控制台输出的结果。自然你也可以在AreEqual()方法中,故意将预期的值设置错误,来看看运行NUnit是什么情况,以及出现的错误提示信息。不过,这些都不是本文关注的重点。

  二、当需求改变了

  在XP中,客户的重要是举足轻重的。在客户提出需求的时候,你需要和他尽可能地沟通,并保证意见最后要达成一致。然而客户对产品的理解可能有时候会出现偏差,也许有时候对方的要求也会随着产品的应用而逐渐发生改变。所幸的是,这一次需求的改变,发生在项目开发过程中,且是在你和他结对交流的时候,最终发现的缺陷。因为客户认为,这个日志过于简单了,并不利于今后对产品的维护。我得承认这是一个好的要求。

  事实上,日志记录得越详细,对于开发人员自己也是有好处的。最后,我们决定,日志不仅仅要记载结算的起止时间,还应该记载可能会出现的错误信息,最好还能记载这个结算的过程代码,比如我们执行的是哪一个存储过程,读取了哪些表的数据,包括这些表的字段。

  然而有个不利的因素是,这个费用结算的过程可能会很频繁的使用。如果写入的日志太复杂了,会否影响产品的性能?而且频繁写日志的话,日志文件会不会越来越大?如果我们这个产品已经非常健壮,还有必要去记载这些信息吗?毕竟有很多信息,对于普通用户而言,并没有实际用处,反而干扰了他有效获取日志的有用信息。

  所以后来我们想到一个方法,就是将日志进行分级,从最简单到最详尽。用户在进行费用结算的时候,可以根据自身需要,选择日志的级别。无疑,这是一个令人满意的策略。

  三、如果不熟悉设计模式

  假设我们的开发人员对于设计模式一概不知,经过分析客户的需求,他会直接了当的做出如此的解决方案。首先定义三种级别的日志:SimplestLog,NormalLog,DetailedLog。SimplestLog只记录结算的起止时间和耗费的时间,同时还要记录结算后的结果。NormalLog则除此之外,还要记录可能会出现的错误信息。而DetailedLog最详尽,它不仅包含了NormalLog记录的信息,还包括记录结算的实现方法,如用到的存储过程,数据表和相应的字段。

  我们最初设想为这三种级别建立三个不同的私有方法,然后在SettleFee()方法中,引入一个日志级别参数,然后根据日志级别的值,决定调用哪一个私有方法,例如:
  private void WriteSimplestLog();
 private void WriteNormalLog();
  private void writeDetailedLog();

  public enum LogLevelEnum{Simple=0,Normal,Detail};

  public double SettleFee(double money,int records,LogLevelEnum logLevel)
  {
   switch (logLevel)
   {
    case LogLevel.Simple:
     WriteSimplestLog();
     break;
    case LogLevel.Normal:
     WriteNormalLog();
     break;
    case LogLevel.Detail:
     writeDetailedLog();
    break;   
   }
   for (int i=0;i  {
    result+=money;
   }
   }

  不用说,我们的程序员遇到麻烦了。因为在记录日志信息的时候,可能会在结算的前后来进行。也就是说,结算的那一段代码必须放到记录日志的方法中,才可以实现。幸运的是,我们的程序员应该还具备重构的知识,他决定把结算的那一段代码专门抽取出来,形成一个单独的方法,再放到日志方法中调用。“Extract Method”,不是吗?很聪明的做法。

  好吧,我们来实现它吧,看看会是怎样?
  首先,实现专门的结算方法:
  private double Settle(double money,int records)
  {
   double result = 0.0;
   for (int i=0;i  {
    result+=money;
   }
   return result;
  }
  再来实现日志方法:
  private void WriteSimplestLog()
  {
   DateTime startTime,endTime;
   startTime = DateTime.Now;
   Console.WriteLine(”Start settling fee at {0}”,startTime);
   result = Settle(money,records);
   endTime = DateTime.Now;
   Console.WriteLine(”Settling fee finished at {0}”,endTime);
   TimeSpan wasted = endTime - startTime;
   Console.WriteLine(”It wasted time {0}”,wasted);
   Console.WriteLine(”The result is {0}”,result);
  }
  在这个方法中,result是Fee类中的一个私有变量,用来保存结算后的结果。假  设不使用这个变量,而是在方法中引入局部变量,那么WritesimplestLog()方法就必须返回double类型了,这个设计可够糟糕的!同样的,money和records也应该是通过私有变量传递值,否则这个日志方法就必须带上这两个参数了。

  接着实现下面两个方法。我们已经注意到根据日志级别的不同,最详尽的日志内容总是包含了其低一级日志的内容。并且,后两级日志没有包括性能的记录,因此记录的日志并未要求必须出现在结算方法的前后。
  private void WriteNormalLog()
  {
   try
   {
    WriteSimplestLog();
    Console.WriteLine(”Settling operation succeed!”);
   }
   catch (Exception ex)
   {
    Console.WriteLine(”The error occured while settling the fee.”);
    Console.WriteLine(”The error is ” + ex.Message);
   }
  }

  private void WriteDetailedLog()
  {
   WriteNormalLog();
   Console.WriteLine(”The StoreProcedure whick was invoked is SpSettleFee.”); 
   Console.WriteLine(”Data table is: UserFee, OnLineRecord.”);
  }
  剩下的代码就简单了:
  private double result = 0.0;
  private doulbe money = 0.0;
  private int record = 0;

  public double SettleFee(double money,int records,LogLevelEnum logLevel)
  {
   this.money = money;
   this.record = record;
   switch (logLevel)
   {
    case LogLevel.Simple:
     WriteSimplestLog();
     break;
    case LogLevel.Normal:
     WriteNormalLog();
     break;
    case LogLevel.Detail:
     writeDetailedLog();
     break;   
   }
   return result;
  }

  嘿嘿,看起来还不错!
  当然,与之相应的测试代码也要发生改变:
  [Test]
  pubic void Settle()
  {
   Fee fee = new Fee();
   Assert.IsNotNull(fee);
   Assert.AreEqual(6,fee.SettleFee(2.0,3,LogLevelEnum.Simple));
   Assert.AreEqual(6,fee.SettleFee(2.0,3,LogLevelEnum.Normal));
  Assert.AreEqual(6,fee.SettleFee(2.0,3,LogLevelEnum.Detail));
  }



本文地址 : http://www.fengfly.com/plus/view-158709-1.html
标签: 日志 方法 设计 结算 掌握
------分隔线----------------------------
最新评论 查看所有评论
发表评论 查看所有评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
验证码: