没有你的城市 的个人资料古月文武的资料库照片日志列表更多 工具 帮助

日志


4月25日

代码的分支管理策略

    关于代码管理的分支和发布策略,目前我知道的主要有两种模式。
    一种是主干作为新功能开发主线,分支用作发布。
    另一种是分支用作新功能开发,主干作为稳定版的发布。
 
    前一种分支管理策略被广泛的应用于开源项目。比如freebsd的发布就是一个典型的例子。freebsd的主干永远是current,也就是包括所有最新特性的不稳定版本。然后随着新特性的逐步稳定,达到一个发布的里程碑以后,从主干分出来一个stable分支。freebsd是每个大版本一个分支。也就是说4.x,5.x,6,x各一个分支。每个发布分支上只有bug修改和现有功能的完善,而不会再增加新特性。新特性会继续在主干上开发。当稳定分支上发生的修改积累到一定程度以后,就会有一次发布。发布的时候会在稳定分支上再分出来一个
release分支。以6.x为例,就会有6.0,6.1,6.2…等发布分支。
    这种发布方法非常适用于产品线的发布管理。产品是要卖的,以前卖给客户的版本仍需要继续维护,而为了以后的市场,新功能也不断地在增加。这种管理方法对已发布产品的维护工作和下一代产品的开发工作进行了隔离。对于已经发布的产品,只有维护的补丁发布。而新发行的产品不仅包括了所有的bug修改,还包括了新功能。
    这种方法也不是没有缺点的。首先,必须对主干上的新功能增加进行控制。只能增加下一个发布里面计划集成进去的新特性。而且,已经在主干上集成的新特性中的任何一个,如果达不到里程碑的要求,稳定分支就不能创建,这很有可能影响下一个发布的计划。开源项目可能这方面的压力小一些,但是商业产品开发如果碰到这种情况就危险了。还有一个缺点就是bug修改必须在各个分支之间合并。从分支和合并的一些实践经验上看,各个长期存在的分支之间必须要周期性的进行合并,否则很容易引发合并冲突。可是各个stable分支以及release分支之间恰好是不能进行合并而且还要长期存在的。因此,采用这种分支策略可能碰到的最大问题就是某个分支上的bug修改内容往其它分支merge的时候出现的冲突。而且一旦发现一个bug,调查这个bug影响哪些分支的工作会随着维护的发布分支的数量而增加。
    在非产品开发的外包软件项目里面,这种发布方法的好处体现不出来,而缺点仍然存在。外包项目的特点是客户永远需要“最新”的代码,因此对已经发布的某个分支进行维护的情况很少出现(在测试的时候会出现)。而且发布的方法和产品的发布也不一样。产品的发布,只要把发布分支上的代码编译成安装盘就可以了,而外包的发布往往是把上一次发布和这一次发布之间发生变化的代码送给客户。如果每次发布都是一个分支的话,将会出现两个分支上的比较。强大的版本控制工具当然支持这种比较,但是很多版本工具不支持分支之间的比较,而只支持分支内的不同版本之间的比较。因此为了避免发布方法受工具的限制,就要避免出现分支间比较的情况。针对外包开发的特殊情况,只有采用另外一种分支管理策略。
 
    与第一种分支策略正好相反,主干上永远是稳定版本,可以随时发布。bug的修改和新功能的增加,全部在分支上进行。而且每个bug和新功能都有不同的开发分支,完全分离。而对主干上的每一次发布都做一个标签而不是分支。分支上的开发和测试完毕以后才合并到主干。
这种发布方法的好处是每次发布的内容调整起来比较容易。如果某个新功能或者bug在下一次发布之前无法完成,就不可能合并到主干,也就不会影响其他变更的发布。另外,每个分支的生命期比较短,唯一长期存在的就是主干,这样每次合并的风险很小。每次发布之前,只要比较主干上的最新版本和上一次发布的版本就能够知道这次发布的文件范围了。
    这种发布模式也有缺点。如果某个开发分支因为功能比较复杂,或者应发布计划的要求而长期没有合并到主干上,很可能在最后合并的时候出现冲突。因此必须时刻注意分支离开主干的时间。如果有的分支确实因为特殊的需要必须长期存在,那就必须定期把主干的更新往这个分支上合并。为了减少这种合并发生的次数,并且限定合并的范围,要为每次发布预先建立一个发布分支,然后所有的开发分支根据自己的发布计划向各个发布分支合并。当下一次发布的分支上已经集成了所有的变更并且测试完毕以后,把这个发布分支内容合并到主干,发布主干,然后锁定或者删除这个分支。然后把主干上的所有更新合并到后面几个发布分支里面去。外包项目的发布周期一般都比较短,往往客户验收测试的周期就是发布周期。所以这种方法就够用了。如果发布周期很长,各个发布分支之间还要定期的从前向后合并。这种发布方法还有一个缺点就是测试。不像第一种分支策略,发布的分支就是测试的分支。这种发布模式的测试分支往往是各个发布分支,在正式发布之前才把下一个发布分支上的更新合并到主干,这就引入了合并出错的风险,而主干上的程序是没有经过测试的。幸好从这个发布模式上看,下一个发布分支的合并基础应该和主干上一次发布内容相同,所以引入合并错误的风险很低。还有一种建议就是不设置主干,下一个发布分支就是主干,直接发布下一个发布分支的变更内容,然后把变更合并到再下一个发布分支上去。以此类推。有机会尝试一下。
    最后,说说分支合并管理的一些注意点:
1.分支离开主干的时间要尽可能短。长期离开主干的分支需要定期合并。
2.辅助文档是必需的。为了观察分支的创建和合并的过程,至少需要一份类似泳道图的文档标记每一次分支创建和合并的过程。
3.开发分支往主干或者发布分支合并的次数应该尽可能少。一般来讲应该在单体测试结束合并到主干或者发布分支,然后进行结合测试。如果结合测试里发现bug不应该在原来的开发分支上继续修改,而应该创建新的分支进行修改。
4.分支创建和合并的log必须规范。便于以后查找。基本的log信息应该包括从哪个个分支的哪个版本创建分支;把哪个分支的从哪版本到哪个版本范围内的变更合并到了哪个分支的哪个版本,合并后的版本号。这些信息有一些是版本控制工具本身可以很方便查找到的,就可以省略。
4月21日

请谨慎实现operator——操作符函数

c++中,==操作符是很有用的,但是它的实现也并非想象中的那样容易。本文将围绕一个简单的c++例子程序展开讨论,以便寻求一个简单的解决方法。

在开始讲述等于操作符之前,我们先了解一下涉及的类定义。第一个类是一个一维点定义,很简单。一个构造器和析构器,一个operator==操作符。

Definition of Class Point1D

class Point1D

{

protected:

    int xPos;

   

public:

    Point1D( int x = 0 )

        : xPos( x )

    {

    }

 

    virtual ~Point1D()

    {

    }

   

    inline bool operator==( const Point1D &rhs )

    {

        return xPos == rhs.xPos;

    }

};

第二个类是Piont2D,它是一个二维点,y轴被使用来确认相应位置。

class Point2D : public Point1D

{

protected:

    int yPos;

   

public:

    Point2D( int x = 0, int y = 0 )

        : Point1D( x ), yPos( y )

    {

    }

   

    virtual ~Point2D()

    {

    }

   

    inline bool operator==( const Point2D &rhs )

    {

        return (this == &rhs) || (xPos == rhs.xPos && yPos == rhs.yPos);

    }

};

从上面来看,两个类好像都定义的很好,真的是这样吗?让我运行一下看看结果吧。

#include <iostream>

using namespace std;


int main( void )

{

    Point1D p1d( 10 );

    Point2D p2d( 10, 20 );

    Point2D p2d2( 10, 30 );

    Point1D *pp2d1 = &p2d;

    Point1D *pp2d2 = &p2d2;

 

    if ( p1d == p2d )

        cout << "P1D is equal to P2D" << endl;

    else

        cout << "P1D is unequal to P2D" << endl;

 

    if ( *pp2d1 == *pp2d2 )

        cout << "P2D is equal to P2D2" << endl;

    else

        cout << "P2D is unequal to P2D2" << endl;

    return 0;

}

Result:

P1D is equal to P2D

P2D is equal to P2D2

WOW,居然P1DP2D是一样,P2DP2D2也是一样。显然是一个错误!错误的原因在于我们错误的实现了Point1Doperator==操作符。它没有对它的子类进行有效的检查。此外,即使Point2D也重载了==操作符,也没有使P2DP2D2能够正确的比较。其实,在多态的环境中,对于Point2D的操作符的重载往往是没有用处的,上面就是一个很好的例子。

我们现在遇到的问题是:

1)如何在Point1D中有效的对它的子类进行检查呢?

2)如何实现子类中的操作符比较函数?

对于1),我们没有办法判断,因为父类的实现代码中是无法预测子类的。有人可能会说为什么不把Point1D中的operator==声明为virtual类型,然后在子类中重置呢?它的实现代码可能类似于这样:

    virtual bool operator==( const Point1D &rhs )

    {

        const Point2D *pp2d = dynamic_cast<const Point2D *>( &rhs );

        if ( pp2d == NULL )

            return false;

        if ( this == pp2d )

            return true;

        return xPos == pp2d->xPos && yPos == pp2d->yPos;

    }  

 

到现在为止看上去好像是一个很好的主意,这样可以在使用==的时候时候清楚的识别子父类。真的是这样吗?请看下面测试结果J.

#include <iostream>

using namespace std;


int main( void )

{

    Point1D p1d( 10 );

    Point2D p2d( 10, 20 );

    Point2D p2d2( 10, 30 );

    Point1D *pp2d1 = &p2d;

    Point1D *pp2d2 = &p2d2;

 

    if ( p1d == p2d )

        cout << "P1D is equal to P2D" << endl;

    else

        cout << "P1D is unequal to P2D" << endl;

 

    if ( p2d == p1d )

        cout << "P2D is equal to P1D" << endl;

    else

        cout << "P2D is unequal to P1D" << endl;

 

    if ( *pp2d1 == *pp2d2 )

        cout << "P2D is equal to P2D2" << endl;

    else

        cout << "P2D is unequal to P2D2" << endl;

   

    return 0;

}

Result:

P1D is equal to P2D

P2D is unequal to P1D

P2D is unequal to P2D2

对于Point2D之间的比较我们很好的解决了,但是对于P1DP2D之间的比较居然出现了戏剧性的自相矛盾!仔细研究代码后,我们发现问题还是出在Point1D==操作符上,我们依然没有能够合适的识别子父类。如何解决呢?对于A=B成立的话,我们一定可以获得B=A也成立,所以我们在做Point1D==操作符的时候,一定要做两次判断等于判断,即A == B && B == A. 请看下列代码.

    virtual bool operator==( const Point1D &rhs )

    {

        return xPos == rhs.xPos && *const_cast<Point1D *>( &rhs ) == *this;

    }

Ok,让我们再次运行测试代码把。

Result:

P1D is unequal to P2D

P2D is unequal to P1D

P2D is unequal to P2D2

非常好,好象我们把问题解决了,是吗?Point1D的问题解决了,但是Point2D呢?它也存在这样的问题,我的天,这样蹩脚的代码还要在Point2D中重写一次!我简直要发疯了!难道日后所有的子类都要这样吗?!!!有没有解决方法?不要太着急,我们可以很简单的处理它。请看下面完整代码.

#include <iostream>

using namespace std;


class Point1D

{

protected:

    int xPos;

 

    virtual bool equalTo( const Point1D &rhs ) const

    {

        return xPos == rhs.xPos;

    }

   

public:

    Point1D( int x = 0 )

        : xPos( x )

    {

    }

 

    virtual ~Point1D()

    {

    }

   

    inline bool operator==( const Point1D &rhs )

    {

        return equalTo( rhs ) && rhs.equalTo( *this );

    }

   

};

 

class Point2D : public Point1D

{

protected:

    int yPos;

   

    virtual bool equalTo( const Point1D &rhs ) const

    {

        const Point2D *pp2d = dynamic_cast<const Point2D *>( &rhs );

        if ( pp2d == NULL )

            return false;

        if ( this == pp2d )

            return true;

        return yPos == pp2d->yPos && Point1D::equalTo( rhs );

    }  

 

public:

    Point2D( int x = 0, int y = 0 )

        : Point1D( x ), yPos( y )

    {

    }

   

    virtual ~Point2D()

    {

    }  

};

 

 

int main( void )

{

    Point1D p1d( 10 );

    Point2D p2d( 10, 20 );

    Point2D p2d2( 10, 30 );

    Point1D *pp2d1 = &p2d;

    Point1D *pp2d2 = &p2d2;

 

    if ( p1d == p2d )

        cout << "P1D is equal to P2D" << endl;

    else

        cout << "P1D is unequal to P2D" << endl;

 

    if ( p2d == p1d )

        cout << "P2D is equal to P1D" << endl;

    else

        cout << "P2D is unequal to P1D" << endl;

 

    if ( *pp2d1 == *pp2d2 )

        cout << "P2D is equal to P2D2" << endl;

    else

        cout << "P2D is unequal to P2D2" << endl;

   

    return 0;

}

Result:

P1D is unequal to P2D

P2D is unequal to P1D

P2D is unequal to P2D2

我们使用了equalTo函数来完成了A==B&&B==A的双向比较,在equalTo中,实现的方法和以前的完全一致,不关心比较的对象是否是父子关系。对于子类,我们仅仅重置equalTo方法就能够正确地实现了operator==方法,相当的出色,不是吗?!J

这样做的好处主要体现在以下几点:

1)能够在子类中正确地实现operator==方法,只要重置对应的equalTo就可以了。

2operator==没有使用virtual修饰,WOW,长出了一口气,对于它的很多注意点终于可以解脱了。

3)也无需为蹩脚的virtual operator==感到伤心了,因为毕竟那样做并非是真正意义上的子类==操作符。Point2D中的操作符参数应该是Point2D,而非Point1D,难道不是吗J

4月20日

软件管理的十大误区

曾经有一位常年混迹于中关村的朋友跟我说,现在但凡是计算机公司,都搞软件开发,几个大学生都能搞软件公司,tnnd世道变了.其实看看当今这个信息时代,计算机软件的规模和复杂度随着时代的进步也在增加。计算机软件开发从“个人英雄”时代向团队时代迈进,计算机软件项目的管理也从“作坊式”管理向“软件工厂式”管理迈进。这就要求软件开发人员特别是软件项目管理人员更深一步地理解和掌握现代软件工程的理论方法,完成思想观念上的转变。老V在此分析了10个在现代项目管理中思想观念上容易陷入的误区,希望能够抛砖引玉,引发大家更多的思索和讨论。

误区1:在项目的需求分析阶段,开发方与客户方在各种的问题的基本轮廓上达成一致可,具体细节可以在以后填充。因为无论开始时有多么细致,以后对需求的修改几乎是必然的。
 
分析:这是一种非常危险的思想。实际上许多软件项目失败的最主要的原因就是需求阶段对问题的描述不够细致,导致后来预算超出或者时间进度达不到要求。正确的做法是:在项目需求分析阶段,双方必须全面地尽可能细致地讨论项目的应用背景、功能要求、性能要求、操作界面要求、与其他软件的接口要求,以及对项目进行评估的各种评价标准。并且,在需求分析结束以后,双方还要建立可以直接联系的渠道,以尽早地对需求变动问题进行沟通。
 
误区2:软件项目的需求可以持续不断的改变,而且这些改变可很容易地被实现。
 
分析:的确,在具体实际中由于种种原因客户方很难在需求分析阶段全面而准确地描述所有问题。随着开发进度的推进,往往会有一些需求的改变。而现代软件工程理论也利用软件的灵活性特点通过各种方式来适应这种情况。不过,这并不表明“软件项目的需求可以持续不断的改变,而且这些改变可很容易地被实现”。实践表明:随着开发进度的推进,实现软件需求更改所需要的代价呈指数形式增长。假定在需求分析阶段实现需求更改需要花费1倍的代价;那么,在系统设计和编码阶段,需要花费1.5-6倍的代价;在系统测试阶段需要花费10-20倍的代价;在软件版本发布以后,甚至可能要花费60-100倍的代价。由此可见,在项目开展过程中,软件需求的改变应当尽量早地提出。这样才可能花费少,容易被实现。
 
误区3:软件程序主要由代码组成,因此编码阶段是整个软件项目的最重要的阶段,应该给与大量的时间,并且集中主要的资源。
 
分析:与以前相比,由于软件的规模和复杂度的增加,以及半自动化软件代码开发平台的出现,现代软件项目管理的中心发生了转移--不是着重编码阶段,而是着重系统总体/详细设计阶段。一般说来,在现代软件项目管理中各种资源的合理分配比例是:项目论证、风险评估阶段3% ,项目需求分析阶段8%,系统总体/详细设计阶段45%,编码阶段10%,系统测试阶段34%。
 
误区4:为了便于代码的维护修改,在系统的详细设计阶段文档工作应该做到写出所有程序的伪码。
 
分析:通常伪码的最大作用是对程序的算法流程进行描述,便于人们深入了解程序的功能和实现过程。可见,在一定程度上伪码的确有利于对程序代码的维护和修改。但是,我们知道为了保证项目文档和程序代码的一一对应关系,维护程序代码的时候同时需要对项目文档进行维护。伪码和程序代码是非常接近的,对伪码进行维护的话,相当于进行了2倍的程序代码维护。工作量是很大的。所以切合实际的方式应该是对一般的程序文档做到程序流程图即可,对于涉及了较复杂算法的才需要伪码。
 
误区5:既然在项目人员配置中设置了专门的测试人员,那么软件所有的内部测试工作全部应该由测试人员完成。
 
分析:软件程序测试可以分为“白盒法”和“黑盒法”两种方式。由于使用“白盒法”对测试人员各方面素质的种种要求,在进行程序测试时测试人员总是最优先使用“黑盒法”。他们的工作方式往往是先对程序进行“黑盒法”测试;如果测试没有通过,不得已这才考虑对程序代码进行“白盒法”测试。显然,这种对“白盒法”有意无意的“逃避”,对软件的可靠性和稳定性构成了威胁。如何解决这个问题?一方面需要提高对测试人员的要求,另一方面也需要程序员完成部分的“白盒法”测试(实际上,程序员往往也是进行“白盒法”测试的最佳人选)。
 
误区6:软件项目管理只是相关技术部门的事情,与公司其他部门无关。
 
分析:在竞争日益激烈的今天,软件项目规模大、复杂度高而且时间要求紧迫。要想提高公司的软件项目管理水平,这就需要提高公司的整体参与意识,需要公司各个部门协同作战。例如需要会计部门协助进行项目预算,财务管理和费用控制;需要研究部门(技术委员会)指派专家协助进行各种风险评估,提供技术指导;需要后勤部门提供各种保障。
 
误区7:在开发进度滞后的情况下,可以聘请更多的程序员加入到开发团队中,通过增加人力资源来赶上进度。
 
分析:在注重团队开发的时代,开发方应该根据目前的软件项目管理水平慎重考虑这个做法。如果新加入的程序员对目前软件项目的应用行业有一定了解,并且可以很快适应了开发方的项目管理方式、软件开发风格、团队协作氛围;那么"新人"的加入是有益的。否则,可能会"好心好意做坏事"。因为尽管其个人能力很高,但是为了使其与大家一起协同工作,开发团队不得不分出人手对其进行与项目有关的技术/业务培训,更重要的(也是难度最大的)是还要引导其融入团队。这可能需要花费开发团队许多时间和精力,很有可能使项目进度更慢。
 
误区8:技术骨干应该成为项目的项目经理,项目经理一定是所有项目成员中薪水最高的。
 
分析:在"软件作坊"时代,这是一种普遍使用而且效果不错的方法;而在"软件工厂"时代,这种方法却带来各种问题,有时甚至直接导致项目失败。究其原因这主要是因为随着现代软件开发分工的细化,对项目经理的要求也发生了根本的改变--最注重的不是其对某项专业技术的掌握程度,而是其组织、领导、协调开发团队的能力(当然,可以两者均突出最好)。至于项目经理的薪水问题,这和定薪制度有很大关系。通常,项目经理执行的是管理人员的薪酬体系,而其他人员执行的是技术人员的薪酬体系。项目经理的薪水在项目成员中是比较高的,但不一定是最高的。有时候,为了激励技术人员,项目中的技术骨干得到的酬劳比项目经理要高。
 
误区9:只有项目经理以及部门主管才会关心项目整体进度,程序员只关心自己的开发进度。
 
分析:这是一种"官僚"的想法。实际上程序员作为团队中的一员,他不仅仅是在打一份工,更重要的是在参与一件"作品"的创作。在体味工作的辛苦的同时,程序员更重要的是要享受创作的快感。项目经理不应该漠视程序员对"成就感"的追求,应该向每一个人详细描述最终" 作品"将会如何美妙和令人兴奋,并且在到达最终目标的路上设立一系列的里程碑。每当项目整体推进到一个里程碑的时候,项目经理应该把这个消息告诉每一位项目成员。实际上,这不仅仅可以让所有的项目成员享受到阶段胜利的喜悦,还可以激发大家更大的工作热情,提高工作效率。
 
误区10:为了保证项目继续,为了留住核心程序员,加薪吧。
 
分析:加薪可以说是很多企业在挽留程序员时所使用的常用方法。这一招可能暂时奏效,不过往往是人留下来了,但副作用也来了--加薪的人未必见得多干活,没有加薪的人却开始消极怠工了。其实,项目的进行过多地依赖程序员的个人技术是“作坊”时代沿袭下来的“陋习”。既然IT行业人员的流动是无法控制的,现在项目的执行应该更加注重团体的力量,应该更多的考虑公司整体技术水平和核心技术能力。例如形成公司自己的专家知识库,类/函数库,第三方控件库,拥有自主版权的开发平台等。另外,实际上程序员萌生去意的原因很大程度上不是薪水,而是缺少激励和尊重。这需要项目经理使用“老土”一点的办法,找适当的时机对程序员做一做思想工作,向其描述项目的美好未来,让其感受关心和尊重。总之,要从多方面着手保证项目的顺利开展,而不是简单地加薪。
4月14日

Coding在西元前——写给所有热爱C++的朋友

#include <iostream>
using namespace std;

int main(){
        Bjarne Stroustrup颁布了C++圣典
        厚重的黑色封面内一共有一千七百多页
        你在书店前
        凝视封底的价钱
        我却在旁静静欣赏你那张痛苦的脸

        * 重载多重继承模板是谁的发现
          喜欢在coding中你那独一无二的一面

          经过AT&T门前
          我与大师面对面
          灵感在沉默之河中闪现

          当软件工程只剩下难解的预言
          coding就成了永垂不朽的诗篇

        # 我给你的code写在西元前
          深埋在无尽的文件碎片间
          几十个世纪又翻开发现
          代码中的逻辑依然清晰可见

          我给你的code写在西元前
          闪烁在庞大的代码框架间
          用C++语言刻下了永远
          那已风化千年的组件
          又轰然运转

        rep *

          我回到了从前
          C就象征着永远
          害怕再也不能回到C++身边

        rep #

Coding在西元前……
Coding在西元前……

}

4月12日

为什么ClearCase & ClearQuest UCM 不适用于小公司

1. 配置管理是管理的一部分
    UCM的前提是公司管理规范,井井有条,可是天底下去那里找这么好的小公司呢?哪个小公司的老板不是唯利是图?那个小公司的员工不是被公司压榨的毫无尊严?这种情况下不会有人考虑去规范公司的管理的,出力不讨好不说,还减少了浑水摸鱼的机会。
    没有规范的管理,最直接的表现就是责权不清晰、计划混乱、计划无人跟踪细化、不知道向谁汇报、不知道谁在做什么、做错了也不会被追究责任、做得多也不会得到任何多余的好处……UCM是基于活动的,活动来源于计划,计划做不好,职责也不清楚,自然不可能用UCM了。
2. 培训成本太高
    CC & CQ的操作稍显复杂,开发人员会对很多操作不理解。这一方面是因为配置管理的观念没有深入人心,另一方面CC & CQ的界面也不是特别友好,开发人员也不会花心思去研究这个东东,所以很多对熟手来说非常简单的操作到了别人那里却很难实施。这种情况下,必然要求对员工进行很多配置管理的培训。可是实际情况是,小公司总是事比人多,哪个都耽误不起,怎么培训?
3. 员工素质一般不高
    尤其是与人合作的素质、软件工程方面的素质
4. CC & CQ很多功能对一般企业来讲,根本用不上
    一般的企业只要能够做到基本的版本管理也就够了,而CC&CQ的卖点并不在这最基本的功能上。比如配置审计、个人空间、多服务器……对小公司来说就是杀鸡用牛刀,不仅是成本上的浪费,而且也不可能充分发挥其效率。
    倒是VSS和CVS以其小巧,更容易成为开发人员手中的配置管理利器。
5.性能不佳
    实际使用的时候经常出现莫名其妙的慢,导致开发人员产生厌烦心理
6.管理和维护的成本
    一个20人的开发团队,至少得有一个全职的CC & CQ配置管理员和若干的配合人员,不然根本别想用。
7.总结
    看来选择任何工具之前,对工具的定位是很重要的,但也是很难确定的,尤其是对于一个没有经验的团队来说,首先要做的应该是优先选用便宜简便的工具,而不应当被一些产品的花里胡哨的功能和指标所迷惑,往往这些指标都是在一定规模下才会产生效益的。
    工具永远不能代替管理方式,人的观念转变是第一位的,这个时候关键要看团队和领导的决心。如果没有决心,就不可能完成转变,那样的话啥啥都是空话……
4月10日

RUP和RMC的区别

最近听到有人说RMC是新一代的RUP,在我的概念中RUP(Rational Unified Process)是一种软件开发方法论,RMC(Rational Method Composer)是一种流程方法的著作工具。RMC既可以用来生成不同配置的RUP方法论,如专门指导SOA开发的RUP for SOA,专门用于小型软件项目的RUP for Small Project等;也可以用来创作其它的方法流程,如IBM Tivoli专门根据ITIL标准开发了一个产品ITUP(IBM Tivoli Unified Process),ITUP是由ITUP Composer生成的,ITUP Composer实际上就是RMC。

看了一下IBM官方网站上的说明:"IBM Rational Method Composer is the next generation of Rational Unified Process." ,这句话的正确理解应该是“RMC是RUP产品的新一代版本”。RUP可以有两种理解,一种就是通常大家所理解的方法论,另一种就是指RUP产品。在RMC出现之前,Rational产品家族中有RUP产品,目前已经被RMC所取代。RUP作为一种产品,主要包括一个RUP方法论生成工具RUP Builder;以及利用RUP Builder所生成的RUP方法论,体现为一组HTML页面,它是RUP这一软件开发最佳实践经验组合的载体。就象我们平时所说的“圣经”是一种精神产品,《圣经》这本书是它的载体一样。

所以我们在说“RMC是新一代的RUP(产品)”时要加上产品两字,以免引起误解。RMC本身只相当于过去的RUP Builder (和Rational Process Workbench,用于开发RUP插件及其内容),作为一个产品,RMC中也包含了一些预定义的RUP配置发布,如“用于大项目的RUP”和“用于小项目的RUP”。现在已经不存在RUP产品,我们通常所说的RUP就是指Rational统一流程这一方法论。

4月7日

微软过桥问题与测试人员素养

      微软面试题过桥问题在IT业内几乎已变成一个众所周知的问题,问题如下:
      4个人在晚上过一座小桥,过桥时必须要用到手电筒,只有一枚手电筒,每次最多只可以有两人通过(人多了桥支撑不住就塌了), 4个人的过桥速度分别为1分钟、2分钟、5分钟、10分钟,试问最少需要多长时间4人才可以全部通过小桥?
      一般人碰到这道题目也许马上就在想该如何安排这4个人的过桥顺序使得过桥时间最少,稍微聪明一些的人也许马上就想到了答案:“先让1、2过去,1回来,5、10再过去,2回来,1、2再过去”,总共需要17分钟就可以让4个人都过去。
      当然如果数学知识足够好的话,可能会用图论来分析这个问题,最后发现这是一个图论的最短路径算法问题,只要根据过桥的状态建立一张有向图,然后求出最短路径就可以得到最少时间的过桥方案。(如果读者对如何用图论问题解这个问题感兴趣的话,可以看我的博客中的另外一篇讲微软过桥问题图论解法的文章)。
       
      现在如果让一个测试人员来回答这个问题的话,是不是也象上述一样回答就可以了呢?如果能在很短时间内象上面一样回答问题,当然说明你人比较聪明,但是如果作为测试人员的话,需要的不是简单的结果,而是要全面分析问题,仅仅回答出最短时间为17分钟的答案是达不到测试人员的要求的。
      作为一个测试人员,首先得对问题中的许多未知因素提出疑问,下面一些问题也许是测试人员所想知道的:
n        这4个人为什么要在晚上过这座桥,他们是在正常回家的路上还是野外探险迷路还是被人追杀?如果是被人追杀会不会有2人过去后独自逃走不回来接剩下的2人?
n        这四个人过桥的时间为什么差距这么大,最大最小时间差了10倍?进而可以提出疑问,这4个人到底是什么样的人?他们的年龄分别多大?他们是否有人是残疾人或是小孩?如果有小孩或残疾人的话,那么是不是残疾人和小孩不能同时过桥?(也就是说5和10不能同时过桥)
n        既然最多只有2人可以同时过桥,那么桥的最大承重能力是多少?是不是两个最重的人也可以同时过桥?
n        手电筒是不是好的,手电筒里的电是不是用完了,或者手电筒的电还能用多久?或者过桥的过程中手电筒会不会掉到桥下去或摔坏?
n        当时的天气是什么样的?有没有刮风下雨、打雷或下暴雪,会不会有人在过桥过程中被风刮下桥去,或者被打雷声吓得掉下桥去。或者过桥时天气变坏使得他们过桥需要的时间增加了。
n        当时的气温是多少度?如果温度低于0度的话,桥上是不是结冰了,过桥时会不会滑下去。
n        过桥的过程中对面是不是有救援人员来了,比如1、2过去后就碰到救援人员等在对岸,那么1也许可以拿救援人员的电筒过去接剩下的两个人,这样最少只要14分钟就可以过桥了。
n        过桥的过程中是否有山洪爆发或水突然涨高将桥淹没?
n        会不会在过桥过程中有野兽嚎叫等因素影响他们的过桥速度?
n        桥下面是什么?是水溪还是无水的沟谷还是很深的悬崖?人掉下去后能不能爬上来?
n        桥旁边有没有其他的捷径可以过去。
n        1分钟的人是否可以背上10分钟的人过桥?如果可以的话需要多长时间?
n        桥有多长?手电筒能照多远?是否过去的人在桥的另外一头就可以用手电筒照亮桥让未过去的人过桥?
      这些问题的确定对过桥需要的最短时间都会有影响,在不同的情况下过桥所需要的最短时间是不同的。以上只是写出了一些问题,还有更多的问题读者可以自己去发掘,如果能在上面的基础上再发掘出2个以上的新问题的话,那么相信你已经具备了测试人员全面分析问题的素养了。
      不过即使你不能发现新的问题,并且上面的那些问题你很多都没有想到,也没有任何关系,当学完后面第3章的测试用例设计方法后再来重新分析这个问题,也许你会惊喜地发现你已经有能力发现很多新的问题了。
当然初学者也许会觉得有些问题好像是在钻牛角尖,但是作为测试人员来讲,要的就是这种钻牛角尖的精神。
4月1日

何以证明你的设计优秀

我想很多人都饱受过糟糕软件设计的痛苦,因此心里总有一股气,自己一定要做一个非常优秀的设计。至少我是这样的人。

近日公司的一个项目进行TR1评审的时候,在技术方案的评估文档中,我发现其第一项列出的便是上一个版本存在的设计缺陷:

  1. 产品之间复用只是停留在代码级别
  2. 模块代码不够独立,后期不能复用
  3. 模块代码不易扩展现在的新功能

由于是公司产权产品,其具体的内容不便详说。幸好我们只需要关注一下其中的重点。对,就是复用!

当然了,这份技术方案中,剩下的就是自己如何解决所有问题,如何提取可供复用组件,如何架构一个好的系统。姑且不谈对本系统的设计如何,我们跳出来看看我们在做一件什么样的事。

否定 --> 肯定 --> 否定

第一个否定,是我们在“否定”上一个版本,“肯定”是我们在肯定自己的版本,最后一个“否定”,是别人来否定我们的版本。不管我们意愿如何,这几乎是很多公司中必然的境况。

再来看看我们所希望的:我们希望我的设计足够优秀,所以在这个产品的下一个版本的时候(或者维护阶段),他们不会抨击设计的缺陷,并且能发现,

  1. 其架构的扩展性非常好,
  2. 可重用的模块也非常多。
  3. 原有系统的稳定性值得肯定。

可是,事实总不如我们想象的那样。为什么呢?

古人有一句非常有道理的话:己所不欲,勿施于人。什么意思呢?你希望别人来称赞你的设计,将来来复用你的模块。那么你这么做了吗?你愿意这么做吗?你自己都不做,何以希望别人来做呢?

是的,作为设计师的你来说,设计的根本目的是解决问题。解决业务上的问题,解决原有系统的问题。那么你的设计才会被认为是有用的。至于设计是否是优秀的,事实上,谁也没把握。未来的事,谁知道呢?

如果只是来证明你有足够能力设计整个系统,我相信你一定早就拥有了。可是说到优秀,却又一个标准问题。这和编写代码不一样。我能编100000行代码,你一定会认为我很强。我把整个系统的大部分全都实现了,你一定会将我捧到非常高的地步。

可惜,你要考虑的是,你不再是在编码,你是在设计。那么到底设计的衡量标准是什么呢?设计的标准很多,可是我相信有一个非常重要:

用最少的代码完成最多的事!

这和程序员的宗旨看上去完全是反其道而行之的。为什么呢?

  1. 代码越少,复杂度越低,所隐含的BUG也会越少。
  2. 代码越少,编写成本也越少。生产率提高了,你的待遇也就提高了。
  3. 代码越少,才真正证明了设计的重要性。如果设计就是让系统复杂话的话,要设计做什么?
  4. 代码越少,维护越简单

代码的大量减少,其实是意味着稳定性的模块在持续增加。这些稳定性的模块,才是设计的真正精髓所在。那么,这些稳定性的模块如何来呢?

一种选择是你来完成这样的模块,让别人调用。

另外一个选择就是你直接用别人的模块。

当然了,这里有一个非常有意思的地方。你或许会说,原来那么差的设计,我怎么使用?只能等我来设计了。可是我要说,如果你眼里只有第一种选择,那么你对别人来说,也不会有第二种选择。或者几乎可以肯定的是,你的设计,没有人会愿意复用。他们总可以发现你的设计中有足够多的缺陷来让他们放弃!就像你现在一样。

这个不是简单的所谓态度决定一切的阐述。这之间几乎有着必然的联系:

  1. 你发现不了别人系统中可以复用的,那么你在复用方面的意识就没有提升到足够的高度
  2. 没有足够的高度的复用意思,编写不出足够可以复用的模块
  3. 如此循环。抛弃式的设计只是会证明一点,又有一个存在众多问题的产品出生了。

如果从企业角度来看,那么复用的重要性更是不可言语。不过,公司的高层一般在技术细节方面不会太多涉及,所以反而容易忽略这点最可以提升生产效率的过程。不过好的公司,会规划平台性质的产品模块,来降低系统设计的范围。也可以减少设计的错误。这和前面的想法从基本上是一致的。

总之一句话,如果在解决问题之外,我们还想设计成优秀的产品,那么先从复用别人开始!