主题:重构、三层中的一些“灾难性错误”的产生原因 DATE 2004-09-29

主题:重构、三层中的一些“灾难性错误”的产生原因
时间:9月29日下午3点
主持:A1、B1
地点:会议室

2004-09-29 14:44:44 A1.Aleyn.wu(45198124)
今天下午的主题是:(1),重构手法和技巧,
(2)三层中的一些“灾难性错误”的产生原因。

2004-09-29 14:51:32 B1.斜阳(249208513)
那先对重构下个定义吧,看看大家对重构的理解是否一致

2004-09-29 14:52:26 TCP/IP INFO(31329846)
重新架构

2004-09-29 14:52:37 C4.llyygg(13029886)
重新写代码.

2004-09-29 14:52:43 B1.斜阳(249208513)
还有别的理解吗?

2004-09-29 14:52:51 D10.天地弦(185511468)
重构的目的是什么?

2004-09-29 14:53:00 D10.天地弦(185511468)
重新写代码应该不是了

2004-09-29 14:53:23 B1.斜阳(249208513)
要明白重构的目的,首先要了解 何谓重构

2004-09-29 14:53:45 B1.斜阳(249208513)
重构(名词):对软件内部结构的一种调整,目的是在不改变「软件之可察行为」前提下,提高其可理解性,降低其修改成本。

2004-09-29 14:53:47 D10.天地弦(185511468)
重构是不是要减少代码的编写工作呢

2004-09-29 14:54:29 B1.斜阳(249208513)
这是一个描述性的定义

2004-09-29 14:54:29 TCP/IP INFO(31329846)
使代码更容易 重用,扩展

2004-09-29 14:55:18 B1.斜阳(249208513)
重构的目的不是为了提高代码的效率(当然附带产品可能会带来效率的提高),而是要使代码更容易管理

2004-09-29 14:55:54 D10.天地弦(185511468)
主要是更容易管理?

2004-09-29 14:56:20 B1.斜阳(249208513)
因此,重构的过程,不一定是提高效率的过程,有时候反而会损耗效率

2004-09-29 14:56:47 D10.天地弦(185511468)
提高效率是指哪方面?

2004-09-29 14:56:54 D10.天地弦(185511468)
运行效率?

2004-09-29 14:57:01 B1.斜阳(249208513)
差不多

2004-09-29 14:57:35 D10.天地弦(185511468)
继续

2004-09-29 14:57:43 B1.斜阳(249208513)
比如,好的代码自身就带着注释信息,看代码就知道作者的意图

2004-09-29 14:57:59  人生如路(26965829)
《重构:改善既有代码的设计(中文版)》
========是否此书??

2004-09-29 14:58:13 B1.斜阳(249208513)
是的

2004-09-29 14:58:30 B1.斜阳(249208513)
好的代码在发生需求变化的时候,可以以最小的变动来适应变化

2004-09-29 14:59:03 D10.天地弦(185511468)
和设计模式相同

2004-09-29 14:59:31 B1.斜阳(249208513)
重构有时候和设计模式是相辅相成的

2004-09-29 14:59:42 B1.斜阳(249208513)
还是从 重构的第一章说起吧,因为第一章的例子比较典型

2004-09-29 15:00:24 B1.斜阳(249208513)
我正好用 Delphi 重写了 重构书中的第一章的Java语言的那个例子,就拿到这里献丑了

2004-09-29 15:00:54 B1.斜阳(249208513)
首先了解一下这个例子的需求
实例非常简单。这是一个影片出租店用的程序,计算每一位顾客的消费金额并打印报表(statement)。操作者告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片类型算出费用。影片分为三类:普通片、儿童片和新片。除了计算费用,还要为常客计算点数;点数会随着「租片种类是否为新片」而有不同。

2004-09-29 15:01:47 B1.斜阳(249208513)
这是他的UML类图

 


2004-09-29 15:02:57 B1.斜阳(249208513)
简单解释一下:Movie是影片类,Rental是租赁类,Customer(顾客)

2004-09-29 15:05:51 B1.斜阳(249208513)
在上面的图中一个Customer(顾客)进行多次Rental(租赁)一次Rental可以租多个Movie(影片)

2004-09-29 15:06:49 B1.斜阳(249208513)
换句话说一个Customer类中包含1个或这多个Rental,一个Rental中包含1个或者多个Movie

2004-09-29 15:07:08 A1.Aleyn.wu(45198124)
http://www.e-midas.cn/eds/refactoring-ch1-ch6.pdf

2004-09-29 15:07:24 A1.Aleyn.wu(45198124)
不要开多线程,一个就可以了.

2004-09-29 15:07:32 B1.斜阳(249208513)
没有学过UML类图的应该可以清楚了吧

2004-09-29 15:08:23 ★hotdog★(278136300)
*是不是在movie那边

2004-09-29 15:08:25 B1.斜阳(249208513)
类似的图我就不再详细解释了

2004-09-29 15:08:58 B1.斜阳(249208513)
先不要管这些了,实际上,在他给的例子中租赁和影片是一对一的关系

2004-09-29 15:10:11 B1.斜阳(249208513)
需要明了的是,就是这三个类之间的关系,现在我把第一个版本的代码帖上,大家可以把我贴的代码复制出去,然后单独打开,这样清楚些

2004-09-29 15:10:34 B1.斜阳(249208513)
代码中有注释[:D]

2004-09-29 15:10:40 B1.斜阳(249208513)
[Code在例子中U_00.pas]

2004-09-29 15:15:20 A1.Aleyn.wu(45198124)
http://www.e-midas.cn/eds/01.rar

2004-09-29 15:17:55 B1.斜阳(249208513)
大家下载一下,一个单元就是一次重构的过程,经过6次重构,这个代码已经是很灵活的了,当然,在从Java到Delphi语言,在实现上有一些小的变动

2004-09-29 15:18:40 B1.斜阳(249208513)
其实,看我写的这些单元,基本就不用我讲课了,每个单元的注释写的很清楚[:D]

2004-09-29 15:18:56 A1.Aleyn.wu(45198124)
B1,得把单元名说一下  ;


2004-09-29 15:18:57 B1.斜阳(249208513)
是从U_00到U_06

2004-09-29 15:19:41 HUI云中鹤(20695124)
是一级级的重构吧?

2004-09-29 15:19:43 B1.斜阳(249208513)
U_00是最初的单元,U_06是经过最后一次重构的单元

2004-09-29 15:19:47 B1.斜阳(249208513)
是的

2004-09-29 15:20:06 B1.斜阳(249208513)
打开U_00

2004-09-29 15:20:55 B1.斜阳(249208513)
先看TMovie类

2004-09-29 15:21:36 B1.斜阳(249208513)
根据Create(const ATitle: String; const APriceCode: TMovieType)传入的参数创建一个影片

2004-09-29 15:22:03 B1.斜阳(249208513)
对外的接口主要是
    property PriceCode: TMovieType
    property Title: String
 

2004-09-29 15:22:26 B1.斜阳(249208513)
是一个纯数据类

2004-09-29 15:22:34 B1.斜阳(249208513)
再看TRental

2004-09-29 15:25:30 B1.斜阳(249208513)
在TRental类中,有
    FMovie: TMovie;       //影片
    FDaysRented: Integer; //租期
 
2004-09-29 15:25:59 B1.斜阳(249208513)
标识一个TRental类中包含一个影片和一个租期

2004-09-29 15:26:42 B1.斜阳(249208513)
通过TRental类Create(const AMovie: TMovie; const ADaysRented: Integer)创建一个租赁

2004-09-29 15:28:12 B1.斜阳(249208513)
顺便说一下,因为在TRental类的Create的实现部分只是将AMovie简单赋给了FMovie,这样的操作是否要在TRental类中释放FMovie是个问题,在这里,我直接释放了

2004-09-29 15:28:33 B1.斜阳(249208513)
原则上是不应该的

2004-09-29 15:28:46 B1.斜阳(249208513)
这个问题有兴趣的以后在讨论

2004-09-29 15:29:23 B1.斜阳(249208513)
最后看看TCustomer类,这个类中有
    FCustomerName: String; //租赁人姓名
    FRentals: TObjectList;    //租赁列表
 
2004-09-29 15:29:40 B1.斜阳(249208513)
意味着一个顾客可以进行多次租赁

2004-09-29 15:29:44 /:>D10.天地弦(185511468)
如果是类自己建立就应该翻译放

2004-09-29 15:30:15 B1.斜阳(249208513)
这里我使用的是TObjectList来保存租赁类实例的列表

2004-09-29 15:30:50 B1.斜阳(249208513)
第一次重构,主要是针对TCustomer的Statement

2004-09-29 15:31:44 B1.斜阳(249208513)
现在的Statement只是输出一个文本式的打印结果,如果用户想输出到网页上怎么办呢

2004-09-29 15:33:06 B1.斜阳(249208513)
最先想到的是重新定义一个HtmlStatement,然后实现同Statement一样的流程

2004-09-29 15:33:14 A1.Aleyn.wu(45198124)
所以,得想办法。

2004-09-29 15:33:59 B1.斜阳(249208513)
但是U_00中的Statement是不可被重用的,只能是把整段代码复制到HtmlStatement中,然后修改

2004-09-29 15:34:43 A1.Aleyn.wu(45198124)
= = U_1没找到HtmlStatement

2004-09-29 15:34:53 B1.斜阳(249208513)
这就会带来:当租金发生变化的时候,我们必须同时调整两段代码,忘了哪一个都会使程序输出有错误

2004-09-29 15:35:04 B6(43225570)
目的是重构,马上就可以知道了

2004-09-29 15:35:38 B1.斜阳(249208513)
要避免这样的情况,首先要对这个长长的Statement过程进行拆分

2004-09-29 15:36:06 B1.斜阳(249208513)
拆分的越细,重用的机会就越大,因此,第一步先对Statement进行重构

2004-09-29 15:36:10 TCP/IP INFO(31329846)
当租金发生变化的时候,我们必须同时调整两段代码:哪两段呀

2004-09-29 15:36:36 B1.斜阳(249208513)
看看前面你就会知道

2004-09-29 15:36:56 B3.Locet(2212967)
.........HtmlStatement和Statement啊

2004-09-29 15:37:26 B1.斜阳(249208513)
(如果没有进行重构而是直接加入了一个一样是很长的HtmlStatement,那就必须同时维护这两个过程)

2004-09-29 15:37:53 B1.斜阳(249208513)
因此,先对Statement进行手术

2004-09-29 15:37:55 A1.Aleyn.wu(45198124)
[:L]俺没听清楚就问。糗了。

2004-09-29 15:38:04 B3.Locet(2212967)
我期待下面的~[:X]

2004-09-29 15:39:01 B1.斜阳(249208513)
在重构的时候,时刻要清楚自己是在添加功能还是在重构,如果是在添加功能,就不要重构,如果是在重构,就不要添加新的功能

2004-09-29 15:40:04 /:>D10.天地弦(185511468)
B1添加HtmlStatement是添加功能吧

2004-09-29 15:40:18 B1.斜阳(249208513)
大家别着急,U_01,U_03,U_03都是在对Statement进行重构

2004-09-29 15:40:27 B1.斜阳(249208513)
是的

2004-09-29 15:41:03 TCP/IP INFO(31329846)
此次从构的目的是要加入 htmlstatement,是吧

2004-09-29 15:41:09 B1.斜阳(249208513)
当进行到u_03的时候,添加已经是轻而易举了

2004-09-29 15:41:37 B1.斜阳(249208513)
好了,继续刚才的

2004-09-29 15:42:21 /:>D10.天地弦(185511468)
U_00 -- U_03是重构Statement
U_04 添加功能(HtmlStatement

2004-09-29 15:42:21 A1.Aleyn.wu(45198124)
对,刚才是目的没讲清楚,所以导致大家都范同样错误了。

2004-09-29 15:42:47 B1.斜阳(249208513)
是的是的,怪我了[:D]

2004-09-29 15:43:36 B1.斜阳(249208513)
其实,重构的进行通常是在需求发生变化的时候进行的

2004-09-29 15:43:45 大羽(393921295)
继续第一次重构,你的第一次重构,好像
只判断了新片

2004-09-29 15:44:10 RedFox(8564093)
是不是应该把 TMovieType 写成一个类?用户好加新的类型?

2004-09-29 15:44:35 B1.斜阳(249208513)
在进行重构的时候要记住:是在保证当前需求不变的情况下修改代码,使之更完美

2004-09-29 15:45:00 B1.斜阳(249208513)
重构的结果不会带来新的功能,切记切记

2004-09-29 15:45:01 ★hotdog★(278136300)
记住了
需求不变

2004-09-29 15:45:07 HUI云中鹤(20695124)
这样有用过这个类的程序都不用改动

2004-09-29 15:45:20 B1.斜阳(249208513)
首先看Statement中的那一大断case语句,这部分比较完整,并且比较独立,那就先把他独立出去

2004-09-29 15:46:48 /:>D10.天地弦(185511468)
重构是将一段可以公用的代码提练出来。

2004-09-29 15:46:52 /:>D10.天地弦(185511468)
是吧

2004-09-29 15:47:21 /:>D10.天地弦(185511468)
U_01就是提练出计算金额那段代码

2004-09-29 15:47:32 B1.斜阳(249208513)

2004-09-29 15:47:46 B1.斜阳(249208513)
看看case语句中的代码

2004-09-29 15:48:08 大羽(393921295)
但为啥只计算 新片

2004-09-29 15:49:01 A1.Aleyn.wu(45198124)
我们只关心如何去重构,不要关心此程序的实际作业。

2004-09-29 15:49:06 阿乖(9631158)
那只是个规则

2004-09-29 15:49:21 B1.斜阳(249208513)
用到了ThisAmount和EachRental

2004-09-29 15:49:41 大羽(393921295)
要了解每次重构的规则

2004-09-29 15:50:04 B1.斜阳(249208513)
还有,重构的另一个规则是:一次只走一小步

2004-09-29 15:50:18 B1.斜阳(249208513)
这样会降低错误的发生

2004-09-29 15:50:21 /:>D10.天地弦(185511468)
把大问题化小

2004-09-29 15:50:32 A1.Aleyn.wu(45198124)
对。

2004-09-29 15:50:35 /:>D10.天地弦(185511468)
像大事化小,小事化了

2004-09-29 15:50:47 B1.斜阳(249208513)
我们先把这段case语句搬移出Statement

2004-09-29 15:51:11 B1.斜阳(249208513)
第一步暂时放到TCustomer类中

2004-09-29 15:51:56 A1.Aleyn.wu(45198124)
B1应该讲一讲,第一步暂时放到TCustomer类中的目的。

2004-09-29 15:52:04 B1.斜阳(249208513)
因为在case语句中,没有改变EachRental的值,但是改变了ThisAmount的值

2004-09-29 15:52:47 B1.斜阳(249208513)
因此,把ThisAmount作为返回值,把EachRental作为传入参数,函数原型是:
    function AmountFor(ARental: TRental): Double;  //计算总租金
 
2004-09-29 15:54:15 B1.斜阳(249208513)
把Statement中的Case部分整块搬移到AmountFor中

2004-09-29 15:54:59 B1.斜阳(249208513)
然后用AmountFor的传入参数ARental替换EachRental

2004-09-29 15:55:55 B1.斜阳(249208513)
用Result替换ThisAmount

2004-09-29 15:56:36 B1.斜阳(249208513)
这样修改后,AmountFor的完整代码如下:
{
 计算总租金
  将计算每次租赁的租金的过程从 Statement 过程中分离出来,有利于复用
}
function TCustomer.AmountFor(ARental: TRental): Double;
begin
  with ARental do
  begin
    Result := 0;
    //根据影片类型和租期计算租金
    case Movie.PriceCode of
      mtRegular:    //普通片
      begin
        Result := Result + 2;
        if DaysRented > 2 then
          Result := Result + (DaysRented - 2) * 1.5;
      end;
      mtNewRelease: //新片
      begin
        Result := Result + DaysRented  * 3;
      end;
      mtChildrens:  //儿童片
      begin
        Result := Result + 1.5;
        if DaysRented > 3 then
          Result := Result + (DaysRented - 3) * 1.5;
      end;
    end;
  end;
end;
 
2004-09-29 15:57:41 B1.斜阳(249208513)
这时候,可以用AmountFor(EachRental)替代原来的case了

2004-09-29 15:58:37 B1.斜阳(249208513)
用同样的方法将个人累积点数也从Statement中抽离出来,就得到了u_01中的样子

2004-09-29 15:59:56 B1.斜阳(249208513)
大家打开U_01单元,看看现在的Statement,你会发现,经过这次重构后,执行效率会有所下降

2004-09-29 16:00:15 A1.Aleyn.wu(45198124)
此次的目的小结:有大Case的地方,可考虑用ExtraMethod的重构手法。

2004-09-29 16:00:19 /:>D10.天地弦(185511468)
B1重构的主要精髓是不是
249208513(B1.斜阳) 15:50:04
还有,重构的另一个规则是:一次只走一小步 

2004-09-29 16:00:56 B1.斜阳(249208513)
是的,这个是初学者不喜欢的规则,但是很重要

2004-09-29 16:01:37 B1.斜阳(249208513)
每一小步的重构完成后,都要停下来进行完整的测试

2004-09-29 16:01:43 ★hotdog★(278136300)
这个需求没有变

2004-09-29 16:01:51 ★hotdog★(278136300)
变的是代码

2004-09-29 16:01:52 B1.斜阳(249208513)
这样才能保证重构的准确性

2004-09-29 16:02:15 TCP/IP INFO(31329846)
U_01->U_02 原因何在

2004-09-29 16:02:23 B3.Locet(2212967)
相对独立,但有关连

2004-09-29 16:02:50 B1.斜阳(249208513)
再将case语句搬移出来后,需要进行测试,测试无误后再将个人累积点数搬移出来,然后再测试

2004-09-29 16:02:58 /:>D10.天地弦(185511468)
不要相互关联,关联最好是单向的

2004-09-29 16:03:54 TCP/IP INFO(31329846)
U_01->U_02 原因何在 

2004-09-29 16:04:05 B1.斜阳(249208513)
要是向提前知道各个单元进化的原因,就看看单元的第一行了

2004-09-29 16:05:37 B1.斜阳(249208513)
好了,经过第一次的重构,我们可以复用AmountFor和GetFrequentRenterPoints来添加HtmlStatement功能了

2004-09-29 16:05:45 B1.斜阳(249208513)
但是先别着急

2004-09-29 16:06:50 B1.斜阳(249208513)
看看被我们抽离出来的这两个函数,你会发现这两个函数没有用到TCustomer中的东西,这表示TCustomer干了不该它干的工作

2004-09-29 16:07:27 B1.斜阳(249208513)
因此应该把这两个过程从TCustomer中移走

2004-09-29 16:07:52 B1.斜阳(249208513)
移到哪里呢?还是以AmountFor为例吧

2004-09-29 16:08:29 B1.斜阳(249208513)
在AmountFor中,我们看到主要操作的是ARental

2004-09-29 16:08:45 B1.斜阳(249208513)
那好,先把这个过程放到ARental中吧

2004-09-29 16:09:42 A1.Aleyn.wu(45198124)
目的是什么:因为在计算的过程中只涉及到 TRental 类中的内容而不涉及到 TCustomer 中的内容,因此应该将这个过程从 TCustomer 中移到 TRental 中。

2004-09-29 16:10:23 B1.斜阳(249208513)
搬移的过程是:先在TRental中添加一个计算租金的方法getCharge,然后将整段代码剪切过去,然后在原来的位置上加入对这个方法的调用

2004-09-29 16:10:33 A1.Aleyn.wu(45198124)
目的是什么,大家明白了就好办事了。

2004-09-29 16:11:13 TCP/IP INFO(31329846)
看完,头一行我就明白多了

2004-09-29 16:11:26 B1.斜阳(249208513)
目的只有一个,优化代码

2004-09-29 16:12:05 A1.Aleyn.wu(45198124)
你说的是总目的,而不是你讲课内容中的小目的。

2004-09-29 16:12:31 B1.斜阳(249208513)
上面我已经说了,在审查第一次重构的代码的时候,发现TCustomer干了不该他负责的工作,那么,谁该负责就放到谁那里去

2004-09-29 16:12:50 A1.Aleyn.wu(45198124)
对,这就是目的。

2004-09-29 16:13:43 B1.斜阳(249208513)
第二步重构的目的就是:该谁干的工作谁干,别总把责任往别人身上推[:D]

2004-09-29 16:14:45 B3.Locet(2212967)
嗯```现在分模块干活,也是基于这个

2004-09-29 16:14:55 TCP/IP INFO(31329846)
单一职责原则

2004-09-29 16:14:56 B1.斜阳(249208513)
在审查第一次重构后的AmountFor函数的时候,发现所有的工作都是围绕这ARental干的,那就应该把这个工作放到他的责任区中

2004-09-29 16:15:24 B1.斜阳(249208513)
(第一次重构后的代码是U_01)

2004-09-29 16:16:10 B1.斜阳(249208513)
至于TRental再转包给谁,那TCustomer就管不着了[:D]

2004-09-29 16:17:06 A1.Aleyn.wu(45198124)
好,这个可以过了,下一个。

2004-09-29 16:17:15 B1.斜阳(249208513)
总之,TCustomer对TRental说:给我你这次租赁的租金,那TRental就得乖乖地呈上来

2004-09-29 16:18:43 B1.斜阳(249208513)
因为AmountFor转移到了TRental中,变成了我自己的事情了,那么AmountFor(ARental: TRental)中的参数也就没有意义了

2004-09-29 16:18:49 B1.斜阳(249208513)
因此,去掉!

2004-09-29 16:19:19 B1.斜阳(249208513)
重构后的函数就是
    function getCharge: Double; //计算本次租赁的租金
 

2004-09-29 16:20:10 B1.斜阳(249208513)
同样的目的去审查U_01中的计算常客的总点数GetFrequentRenterPoints

2004-09-29 16:20:38 B1.斜阳(249208513)
也被搬移到了TRental中,变成了    function GetFrequentRenterPoints: Integer;  //计算本次租赁产生的点数
 

2004-09-29 16:21:16 B1.斜阳(249208513)
需要说的是:搬移的过程中,要一步一步地来

2004-09-29 16:22:18 ★hotdog★(278136300)
first

2004-09-29 16:22:42 B1.斜阳(249208513)
先把U_01中TCustomer.AmountFor的那段代码剪切到TRental中,然后,在这个函数中放入
ARental.getCharge

2004-09-29 16:23:09 ★hotdog★(278136300)
second

2004-09-29 16:23:34 B1.斜阳(249208513)
然后编译,调试,没有错误后在去掉TCustomer.AmountFor这个函数,并把所有引用这个函数的地方换成Rental.getCharge

2004-09-29 16:23:41 B1.斜阳(249208513)
然后再编译调试

2004-09-29 16:24:03 ★hotdog★(278136300)
third

2004-09-29 16:24:18 B1.斜阳(249208513)
这是重构中需要注意的,一次只走一小步(迈着四方步向前走)

2004-09-29 16:24:34 B1.斜阳(249208513)
这才稳当

2004-09-29 16:25:33 B1.斜阳(249208513)
在U_01的第一行写了为什么要优化U_00,在U_02中写了为什么要优化U_01

2004-09-29 16:28:54 B1.斜阳(249208513)
好吧,有人理解就算没白写

2004-09-29 16:31:46 B1.斜阳(249208513)
先看总租金这个变量TotalAmount(在Statement中)
to ★hotdog★ 正在说

2004-09-29 16:29:13 B1.斜阳(249208513)
现在回到U_02中的Statement

2004-09-29 16:29:17 A1.Aleyn.wu(45198124)
从1到2,比从0到1好多了。

2004-09-29 16:29:19 B3.Locet(2212967)
斜阳你辛苦了~[strong]

2004-09-29 16:30:02 飘尘(43745429)
说了半天重构就是各尽其责

2004-09-29 16:30:10 B1.斜阳(249208513)
在从U_02到U_03的迈进中,是在消除Statement中的一些临时变量,这可能有些人不理解,不过没关系,我告诉你为啥子

2004-09-29 16:30:27 B1.斜阳(249208513)
不尽然

2004-09-29 16:31:02 B1.斜阳(249208513)
不要望一叶而言秋,呵呵,好戏有的是

2004-09-29 16:31:15 ★hotdog★(278136300)
说说你的不尽然

2004-09-29 16:32:59 B1.斜阳(249208513)
为了得到 总租金,必须用循环反复调用EachRental.getCharge()并累加结果

2004-09-29 16:34:18 B1.斜阳(249208513)
试想这样一种情况:有好几个函数中要获得总租金(比如新需求中的HtmlStatement也要获得总租金),那是不是每一个函数中都要嵌入这么一个循环累加的过程呢?

2004-09-29 16:34:33 B1.斜阳(249208513)
结论是肯定的

2004-09-29 16:35:00 /:>D10.天地弦(185511468)
不是肯定的吧

2004-09-29 16:35:02 B1.斜阳(249208513)
这就是代码的重复,重复就会增加维护的成本

2004-09-29 16:35:29 B1.斜阳(249208513)
当然不是肯定的,要不然还重构个啥[:D]

2004-09-29 16:35:34 /:>D10.天地弦(185511468)
结论是否定的,不需要都嵌入这么一个循环累加的过程呢

2004-09-29 16:35:54 /:>D10.天地弦(185511468)
那你刚刚说
249208513(B1.斜阳) 16:34:33
结论是肯定的 

2004-09-29 16:36:03 B3.Locet(2212967)
重复会增加维护的成本?

2004-09-29 16:36:13 RedFox(8564093)
讲到三层的那个问题没有?

2004-09-29 16:36:22 /:>D10.天地弦(185511468)
2212967(B3.Locet) 16:36:03
重复会增加维护的成本? =>当然

2004-09-29 16:36:24 B1.斜阳(249208513)
那是说,在U_02的环境下

2004-09-29 16:36:50 B3.Locet(2212967)
不明白呢

2004-09-29 16:37:14 B3.Locet(2212967)
该重构时就重构啊!(该出手时就出手啊)

2004-09-29 16:37:27 B1.斜阳(249208513)
好了,现在把代表总租金的那个变量用一个函数来替换

2004-09-29 16:37:49 /:>D10.天地弦(185511468)
你想想每个地方都嵌入这么一个循环累加的过程,如果要更改一下算法,不是要在每个地方都去修改,这样就是增加维护的成本
 
2004-09-29 16:37:49 B1.斜阳(249208513)
也就是说把计算总租金的算法流程抽离出来

2004-09-29 16:39:21 B1.斜阳(249208513)
我们在TCustomer类中增加一个GetTotalCharge方法,用来计算总租金,方法的实现很简单,循环累加,然后将累加的结果返回

2004-09-29 16:40:21 ★hotdog★(278136300)
这个为什么不设成全局变量

2004-09-29 16:40:43 B1.斜阳(249208513)
好了,这个函数出来后,在U_02中的TCustomer.Statement函数中寻找引用到TotalAmount的地方(注意,是引用的地方,不是赋值的地方)

2004-09-29 16:40:51 B3.Locet(2212967)
全局变更尽理不要用

2004-09-29 16:40:56 /:>D10.天地弦(185511468)
OO不推荐用全局的变量

2004-09-29 16:41:14 ★hotdog★(278136300)
这个不用来回循环了

2004-09-29 16:41:39 B1.斜阳(249208513)
然后用GetTotalCharge()函数替换之,然后去掉TotalAmount变量,然后编译运行调试

2004-09-29 16:42:22 B1.斜阳(249208513)
使用同样的方法去消除  FrequentRenterPoints: Integer; //总累积点数

2004-09-29 16:44:18 B1.斜阳(249208513)
之所以看中了这两个变量,是因为这两个变量的计算都涉及到了TCustomer类同其它类的交互的过程,将这个交互的过程隐藏的越深,类和类之间的变动所波及的影响就会越少

2004-09-29 16:44:47 阿乖(9631158)
是这样

2004-09-29 16:44:58 /:>D10.天地弦(185511468)
这样影响运行效率比较大

2004-09-29 16:45:18 B1.斜阳(249208513)
试想如果TCustomer最后就在一个地方用到了TRental,那么如果TRental有变动,那么TCustomer只要变动一个地方就可以了

2004-09-29 16:46:58 B1.斜阳(249208513)
经过这次重构,得到了U_03的效果。
是的,这次重构增加了运行速度的负担,但是重构就是重构,重构的目的不是提高效率

2004-09-29 16:47:26 B1.斜阳(249208513)
要想提高效率,可以在重构结束的时候再统一考虑效率的问题

2004-09-29 16:48:02 B1.斜阳(249208513)
好了,到了U_03的时候Statement已经苗条多了

2004-09-29 16:48:05 /:>D10.天地弦(185511468)
function TCustomer.Statement: string;
const
  conEnter = #13#10;
  conTab = #9;
var
  EachRental: TRental;
  i: Integer;
begin
  Result := 'Rental Record for ' + CustomerName + conEnter;


  //循环取出租赁列表中的所有租赁信息,然后设置输出的信息(电影名称和每次的租金)
  for i := 0 to FRentals.Count - 1 do
  begin
    EachRental := TRental(FRentals[i]);

    Result := Result + conTab + EachRental.Movie.Title + conTab +
      Format('%.2f', [EachRental.GetCharge()]) + conEnter;
  end;

  Result := Result + 'Amount owed is ' + Format('%.2f', [GetTotalCharge()]) + conEnter;
  Result := Result + 'You earned ' + Format('%d', [GetTotalFrequentRenterPoints()]) + conEnter;
end;

2004-09-29 16:48:27 /:>D10.天地弦(185511468)
像这里得到费用要计算两次

2004-09-29 16:49:10 B1.斜阳(249208513)
是的,但是对TRental的引用却被封装了

2004-09-29 16:50:27 B1.斜阳(249208513)
重构进行到现在,为Statement打造的瘦身计划-重构,已经告一段落了

2004-09-29 16:51:11 B1.斜阳(249208513)
现在该是考虑考虑在那里等的直跺脚的要添加功能的客户了

2004-09-29 16:53:22 B1.斜阳(249208513)
好了,现在从U_03中的Statement衍生出个HtmlStatement已经不是什么麻烦事了,复制一份Statement,改改开头结尾就是了

2004-09-29 16:54:05 B1.斜阳(249208513)
如果还觉得不够精练,可以把格式信息的设置再进行重构,不过这就不是接下来讨论的重点了

2004-09-29 16:54:58 B1.斜阳(249208513)
看看U_04,添加功能是不是很简单的事情

2004-09-29 16:55:10 B1.斜阳(249208513)
现在要不要休息一下噢

2004-09-29 16:57:34 B1.斜阳(249208513)
从U_04到U_05到U_06是在对TMovie和TRental进行进化

2004-09-29 17:00:25 阿乖(9631158)
因为我没有看明白如何增加新的影片类型

2004-09-29 17:00:56 B1.斜阳(249208513)
好像都同意噢,那我省劲了

2004-09-29 17:01:31 /:>D10.天地弦(185511468)
好啊

2004-09-29 17:05:52 A1.Aleyn.wu(45198124)
如果CodeRush能加一个Refactoring就好了。

2004-09-29 17:06:16 RedFox(8564093)
CodeRush 是个什么样的工具?

2004-09-29 17:06:45 A1.Aleyn.wu(45198124)
Delphi中的好工具。

2004-09-29 17:07:00 RedFox(8564093)
自动语法?比如打了 if 就有提示?

2004-09-29 17:07:17 A1.Aleyn.wu(45198124)
有限时间,无限知识(感概ing)


2004-09-29 17:07:58 A1.Aleyn.wu(45198124)
B1歇一歇,我来主持好了。

2004-09-29 17:08:22 A1.Aleyn.wu(45198124)
(3)三层中的一些“灾难性错误”的产生原因。 

2004-09-29 17:09:41 A1.Aleyn.wu(45198124)
多数“灾难性错误”的产生原因与DLL或接口有关。

2004-09-29 17:10:01   た少林足球(59552649)
非模式窗体怎样不要在主程序退出才释放窗体

2004-09-29 17:10:03 /:>D10.天地弦(185511468)
你是说中间层?

2004-09-29 17:10:32 A1.Aleyn.wu(45198124)
(1)种情况:
中间层使用了无效的内存地址,产生Exception,而这个Exception无法直接返回到Client调用中间层的接口中,所以出错。

2004-09-29 17:12:20 A1.Aleyn.wu(45198124)
也就是,使用了无效的变量。


2004-09-29 17:13:17 A1.Aleyn.wu(45198124)
(2)种情况:
  中间层调用了其它进程的接口,而其它进程的接口产生了Exception没有  被自身截止,漫延到中间层,所以出错。

2004-09-29 17:16:47 A1.Aleyn.wu(45198124)
(3)种情况:  中间层调用了DLL,而DLL产生了Exception没有被自身截止,漫延到中间层,所以出错。
 

2004-09-29 17:17:03 A1.Aleyn.wu(45198124)
(4)种情况:
OleVariant或Variant在中间层中产生不匹配的情况,漫延到中间层,所以出错。
 
2004-09-29 17:18:32 /:>D10.天地弦(185511468)
Variant可以存对象变量吗?

2004-09-29 17:19:29 B1.斜阳(249208513)
可以,但是保存的应该是对象引用

2004-09-29 17:18:50 A1.Aleyn.wu(45198124)
(5)种情况:
内存申请和释放无效,引起锁定的Lock计数没有解开,所以出错。

2004-09-29 17:20:34 A1.Aleyn.wu(45198124)
(6)种情况:
强制使两个application(EXE和DLL)使用同一个MemoryManger,释放时所以出错.

2004-09-29 17:23:43 A1.Aleyn.wu(45198124)
我碰到了就这些了。

2004-09-29 17:24:01 B1.斜阳(249208513)
呵呵,好!保存之!!

2004-09-29 17:24:04 D1.jackey(409485288)
接口计数错.

2004-09-29 17:24:18 ★hotdog★(278136300)
解释一下

2004-09-29 17:24:54 A1.Aleyn.wu(45198124)
所有的情况的根本目的就是:

2004-09-29 17:25:24 A1.Aleyn.wu(45198124)
无论是Delphi的Exception,还是Win32的,还是User Create的,

2004-09-29 17:25:44 /:>D10.天地弦(185511468)
User Create?

2004-09-29 17:25:57 化神奇为腐朽(120254038)
自定义的

2004-09-29 17:25:57 A1.Aleyn.wu(45198124)
都是因为漫延到中间层,而没有劫止而产生的。

2004-09-29 17:26:04 A1.Aleyn.wu(45198124)
对。

2004-09-29 17:26:12 B1.斜阳(249208513)
总之是 没踹(try)明白

2004-09-29 17:26:55 化神奇为腐朽(120254038)
是不是try用好了就能全部解决?

2004-09-29 17:27:10 A1.Aleyn.wu(45198124)
不一定。

2004-09-29 17:27:17 化神奇为腐朽(120254038)
我想也是

2004-09-29 17:27:28 B1.斜阳(249208513)
那也不一定,有些你 踹不到[:D]

2004-09-29 17:28:02 B1.斜阳(249208513)
比如断电你就踹不到


2004-09-29 17:28:33 A1.Aleyn.wu(45198124)
其它进程的和WIN32的踹不到。

2004-09-29 17:30:03 B1.斜阳(249208513)
老大,我剩下的问题看我写的代码中的注释基本就可以了,是不是就不用我再在这里罗嗦了?

2004-09-29 17:30:18 A1.Aleyn.wu(45198124)
善用try except ,和善用 raise,就好办事了。

2004-09-29 17:30:34 A1.Aleyn.wu(45198124)
to B1,看大伙的意见吧。

2004-09-29 17:31:13 B1.斜阳(249208513)
翻译成普通话,老大的意思就是:该踹的踹,不该踹的别瞎踹[:D]

2004-09-29 17:31:53 B1.斜阳(249208513)
小心踹扭了脚

2004-09-29 17:33:00 A1.Aleyn.wu(45198124)
End.

posted @ 2004-09-29 19:18  D10.天地弦  阅读(959)  评论(0编辑  收藏  举报