内疚的程序员

我发现,当程序员开发了一个项目,然后要把它移交给其他程序员时,他们会对开发这个项目时做出的一些决策感到内疚。我问他们当时为什么选择这样做, 他们会羞愧的说,“唉,我知道这不是最好的实现方法,如果现在再去做,肯定不会采用那样的方式。”有些人可能会辩护,或强调一下外部因素,比如工期压力。 但我的观点是,程序员不需要为老的项目感到太多的内疚。

经验

我承认,我曾经有一次重新发球的经验。那是一个作为内部工具使用的Ruby on Rails项目。我之前对这种技术架构了解不多。基本上就是把东西按照需求拼凑起来,它运行很正常。没有多少测试,设计上必然是没有体现出最好的设计原则。但它能用。

接着,我做了一个6个月长的Rails项目,过程完全是TDD的。在此之后,出现了一个机会,需要调整那个内部工具,增加一些功能。

我很高兴有这次机会。我感觉对这种技术有了更好的了解,能够看出代码中存在的问题,知道如何用更好的Rails或Ruby技术来解决这些问题。这让 人很兴奋。不止一次,我惊奇于那些老的代码竟然能正常的运行。我想,绝大多数程序员都很少能有这样的机会,除非他们是在维护一个老项目,我想这是一次很有 价值的经历,让我在事后看清了我自己写的程序。

综合分析

但后来,我开始意识到,程序员不必要为自己开发出的产品感到内疚。新的技术和实践方法不断的出现,等待着你去学习,每一次你都要权衡取舍,总会有事 后诸葛亮的情况出现。我应该现在重构这个类,还是放到以后再说?我是需要把设计的容易扩展,或者根本不需要这样?做这个项目时我们是否应该首先尽量的减少 技术上的风险?

在针对某一问题我遍历群书后,对解决这类问题我学会了新的技术,新的方法。但这并不能妨碍我们当前的工作。我们不可能百分百的知道我们所需要的知识,我们能想到的方案只是能满足解决当前问题需求。

我相信,程序员都已经尽了他们最大的努力。但这并不能免除程序员犯错误,并从错误中学到经验,也不能保证他们能够进行先知先觉的学习。

我想说的是,程序员如果没有足够的知识以最佳的方式来解决所有的问题或在困境中做出最正确的抉择,他不必为此不安。在之后的岁月里认识到了自己的错误,这是自己进步的标识。每一次都把事情做的正确无误,这暗示一种技术的停滞,或完美主义。哪一种更有可能?

你是否也有过这样的一种愿望,希望能够重新来一次,改变某个软件项目中的某些东西?有过看着自己写过的代码感到恶心的时候?把事情做对,还是把事情做完?平衡点在哪里?在评论里留下你的想法吧!

[本文英文原文链接:Guilty Developer Syndrome ]

纽约时报:《你画我猜》一夜成名 开发者上演“麻雀变凤凰”

《纽约时报》网络版称,《你画我猜》(Draw Something)的一夜成名,一下子扭转了此前处于挣扎、濒临破产的游戏开发商Omgpop的命运。
在被Zynga收购的前一天,《你画我猜》游戏的创始人银行户口里只剩1700美元了。一天之后,他的资产翻了10万倍。

周四晚上,曼哈顿一家豪华酒店二楼举行的鸡尾酒会上,满眼尽是一盘盘的酿鲜菇、薄薄的菲列牛排等美味菜肴,而查尔斯·福尔曼(Charles Forman)此时还在惊讶于这一切的瞬间改变。

福尔曼表示,“昨天我的银行账户只剩下1700美元,如今我多了很多钱了。”

该派对是为了庆祝Zynga成功收购福尔曼创建的纽约游戏厂商Omgpop,对于Omgpop的员工来说,那个派对来得有些奢侈,因为那是他们此前简直难以想象到的事情。

命运扭转

在推出仅7个星期的智能手机游戏《你画我猜》一夜成名之前,Omgpop一直磕磕绊绊。该游戏吸引了社交游戏巨头Zynga的注意,Zynga也于上周斥资1.8亿美元收购将Omgpop。

《你画我猜》将Omgpop从一家默默无名、濒临破产的初创公司一跃变成行业巨头的必需品。Zynga的收购表明公司是多么地需要迅速跟上互联网的发展速度,从而走在在线趋势的前沿,也表明公司的命运可在瞬间扭转。

市场研究公司IDC游戏行业分析师刘易斯·沃德(Lewis Ward)表示,“他们收购了一项在4秒之内从0瞬间加速至60英里的资产。”

《你画我猜》与《猜猜画画》(Pictionary)有些不同,它要求用户迅速画出表示诸如“游泳池”、“海星”等意思的画,然后供朋友去猜。据Zynga透露,自2月6日推出以来,它的累积下载量已经超过3500万次,玩家已经共计画出超过10亿幅画。

纽约时报:《你画我猜》一夜间扭转Omgpop命运

纽约时报:《你画我猜》一夜间扭转Omgpop命运

现年32岁的福尔曼并没直接参与《你画我猜》的开发。他一年前离开了该公司,但他保留了它的股权。他一直在致力于发展另一家初创公司。在接受采访 时,他拒绝透露该笔交易给他带来了多少钱,但他称该数字“远远超过”2200万美元。“那笔资金让我在出席别人的婚礼时能够穿上任何我想要穿的衣服。”

成立之初

Omgpop刚创建时并不是一家游戏公司。6年前,福尔曼还是一名狂热的健身爱好者,他的Facebook主页含有他的腹肌的特写照片。当时他成立了一家名为I’m in Like With You的约会网站。

“老实说,刚开始整个公司就像个笑话,”福尔曼说。他指出他只是将该网站视作娱乐的方式,后来他才意识到原来有很多人会在上面呆上很长的时间。为了 利用该用户群,他将网站改造成一个游戏网站,并更名为Omgpop。“它将载入史册,”谈到该公司的成功时他说。“我没有想过事情的发展会那样演变。”

2008年12月,福尔曼找来非盈利组织Teach for America前总裁、曾在其它创业公司供职的丹·波特(Dan Porter)担任Omgpop CEO。

Omgpop获得了1700万美元的投资,也打造了大约35款游戏,但这并未带来可观的收入。两名曾在Omgpop供职、熟悉Omgpop的财务状况的匿名消息人士透露,事实上该公司在去年5月濒临破产,要不是《你画我猜》的迅速流行,它恐怕已经倒闭了。

将出任Zynga纽约业务总经理的波特拒绝对此进行确认,但他表示,“在该游戏问世之前,我们募集了大量的资金,不过我们没有研发出任何热门游戏。那种情况不能够持续下去。”

该公司的首款绘画游戏是名为Draw My Thing的网页游戏,为福尔曼和Omgpop首席技术官E. J. Mablekos所研发。该游戏颇具竞技性,它要求玩家在规定时间内完成猜字,玩家在聊天室得争先输入答案。

《你画我猜》迅猛发展

福尔曼在去年秋季因个人原因离开Omgpop之后,波特率领一支5人团队研发《你画我猜》。

他称,该游戏的部分创意来源于他儿子在布鲁克林的展望公园与其朋友玩传球游戏的时候。如果孩子们能够将足球连续倒脚100次,保持不失误,波特就会给他们买冰淇淋。

“那与该款游戏异曲同工”他说。“该游戏就像是传球游戏,因为大家都在通力合作,力求实现目标。”《你画我猜》并没有时间限制,也没有真正的赢家和输家。

上个月推出该游戏时,波特一直留意它的下载量。上市当天它的下载次数达3万次,表现不是很惊艳。但大概10天之后,其下载量呈现爆发式增长,很快就突破了100万次的大关。

人们开始在Twitter和Facebook上发布他们的绘图,帮助该游戏实现迅猛的推广。登上苹果App Store应用商店下载量榜单前列,让它能够确保覆盖更多的人,获得更多的玩家。《你画我猜》拥有两个版本,一个需要支付1美元进行下载,另一个则是含有 广告的免费版本,两个版本都支持付费购买额外的功能,这让它每天能够创造数十万美元的收入。

“它丝毫没有放缓脚步的迹象,”波特表示。“玩家规模越大,它就变得越强大。”

《你画我猜》的成功吸引力一些追求者,Zynga高管不久将从旧金山飞往纽约洽谈收购事宜。IDC分析师沃德称,Zynga追求Omgpop,表明当前它重视移动游戏领域,而非帮助它起家的基于Facebook平台的网页游戏。

Zynga也没有浪费任何的时间:它在上周三宣布了该收购消息,到周四Omgpop所有的40名员工都已经到达Zynga供职,已经了解该公司的政 策和医疗保健计划。那天晚上,大约有60人参加了庆祝派对,其中很多都是Omgpop的员工,他们相互击掌祝贺,拥抱并拍摄相片。

波特称,在到他的新职位就任之前,他计划与家人到哥斯达黎加旅行。福尔曼目前正在图片存储服务商Picturelife供职,他说该交易完成之后,他一直处于惊愕且茫然的状态。

“我穿过大街,听到的都是‘汽车的喇叭声’,”他说。“感觉像发梦一样。”(网易科技编译)

神奇的 CSS 形状

在StackOverflow上有这么一个问题,有位同学在http://css-tricks.com/examples/ShapesOfCSS/  找到一些使用CSS做的形状,其中一位同学对下面的这个形状充满了疑问。

形状是:

代码是:

1 2 3 4 5 6 7 #triangle-up { width : 0 ; height : 0 ; border-left : 50px solid transparent ; border-right : 50px solid transparent ; border-bottom : 100px solid red ; }

这位同学就提问啦,为啥这么这么几句就能画出一个三角形呢?
于是呢,有高人出现,这个高人图文并茂的解释了这个三角的成因

首先呢,我们需要了解HTML标记的Box Model(盒模型),这个例子中呢我们将content,padding都看作content。忽略掉margin。那么一个盒模型就是下图

中间是内容,然后是4条边。每一条边都有宽度。
根据上面CSS的定义,没有border-top(顶边)的情形下 ,我们的图形如下:

width设置为0后 ,内容没有了就成为下图:

height也设置为0,只有底边了。

然后两条边都是设置为透明,最后我们就得到了

这个属于奇技淫巧,但是也说明CSS的强大,没有做不到只有想不到。另外http://css-tricks.com/examples/ShapesOfCSS/ 还能找到很多其他的形状,感兴趣的同学可以自己去看。

C++编译器无法捕捉到的8种错误

C++是一种复杂的编程语言,其中充满了各种微妙的陷阱。在 C++ 中几乎有数不清的方式能把事情搞砸。幸运的是,如今的编译器已经足够智能化了,能够检测出相当多的这类编程陷阱并通过编译错误或编译警告来通知程序员。最 终,如果处理得当的话,任何编译器能检查到的错误都不会是什么大问题,因为它们在编译时会被捕捉到,并在程序真正运行前得到解决。最坏的情况下,一个编译 器能够捕获到的错误只会造成程序员一些时间上的损失,因为他们会寻找解决编译错误的方法并修正。

那些编译器无法捕获到的错误才是最危险的。这类错误不太容易察觉到,但可能会导致严重的后果,比如不正确的输出、数据被破坏以及程序崩溃。随着 项目的膨胀,代码逻辑的复杂度以及众多的执行路径会掩盖住这些 bug,导致这些 bug 只是间歇性的出现,因此使得这类 bug 难以跟踪和调试。尽管本文的这份列表对于有经验的程序员来说大部分都只是回顾,但这类 bug 产生的后果往往根据项目的规模和商业性质有不同程度的增强效果。

这些示例全部都在 Visual Studio 2005 Express 上测试过,使用的是默认告警级别。根据你选择的编译器,你得到的结果可能会有所不同。我强烈建议所有的程序员朋友都采用最高等级的告警级别!有一些编译提示在默认告警级别下可能不会被标注为一个潜在的问题,而在最高等级的告警级别下就会被捕捉到!(注:本文是这个系列文章的第 1 部分)

1)变量未初始化

变量未初始化是 C++ 编程中最为常见和易犯的错误之一。在 C++ 中,为变量所分配的内存空间并不是完全“干净的”,也不会在分配空间时自动做清零处理。其结果就是,一个未初始化的变量将包含某个值,但没办法准确地知道 这个值是多少。此外,每次执行这个程序的时候,该变量的值可能都会发生改变。这就有可能产生间歇性发作的问题,是特别难以追踪的。看看如下的代码片段:

  if (bValue)

       // do A    else        // do B

如果 bValue 是未经初始化的变量,那么 if 语句的判断结果就无法确定,两个分支都可能会执行。在一般情况下,编译器会对未初始化的变量给予提示。下面的代码片段在大多数编译器上都会引发一个警告信息。

  int foo ()
  {
      int nX;
      return nX;
  }

但是,还有一些简单的例子则不会产生警告:

  void increment (int &nValue)
  {
      ++nValue;
  }
  int foo ()
  {
      int nX;
      increment (nX);
      return nX;
  }

以上的代码片段可能不会产生一个警告,因为编译器一般不会去跟踪查看函数 increment ()到底有没有对 nValue 赋值。

未初始化变量更常出现于类中,成员的初始化一般是通过构造函数的实现来完成的。

  class Foo
  {
  private:
      int m_nValue;

  public:
      Foo ();
      int GetValue () { return m_bValue; }
  };

  Foo::Foo ()
  {
      // Oops, 我们忘记初始化m_nValue 了    }
  int main ()
  {
      Foo cFoo;
      if (cFoo.GetValue () > 0)
          // do something        else           // do something else    }

注意,m_nValue 从未初始化过。结果就是,GetValue ()返回的是一个垃圾值,if 语句的两个分支都有可能会执行。

新手程序员通常在定义多个变量时会犯下面这种错误:

int nValue1, nValue2 = 5;

这里的本意是 nValue1 和 nValue2 都被初始化为5,但实际上只有 nValue2 被初始化了,nValue1从未被初始化过。

由于未初始化的变量可能是任何值,因此会导致程序每次执行时呈现出不同的行为,由未初始化变量而引发的问题是很难找到问题根源的。某次执行时, 程序可能工作正常,下一次再执行时,它可能会崩溃,而再下一次则可能产生错误的输出。当你在调试器下运行程序时,定义的变量通常都被清零处理过了。这意味 着你的程序在调试器下可能每次都是工作正常的,但在发布版中可能会间歇性的崩掉!如果你碰上了这种怪事,罪魁祸首常常都是未初始化的变量。

2)整数除法

C++中的大多数二元操作都要求两个操作数是同一类型。如果操作数的不同类型,其中一个操作数会提升到和另一个操作数相匹配的类型。在 C++ 中,除法操作符可以被看做是 2 个不同的操作:其中一个操作于整数之上,另一个是操作于浮点数之上。如果操作数是浮点数类型,除法操作将返回一个浮点数的值:

  float fX = 7;
  float fY = 2;
  float fValue = fX / fY; // fValue = 3.5

如果操作数是整数类型,除法操作将丢弃任何小数部分,并只返回整数部分。

  int  nX = 7;
  int nY = 2;
  int nValue = nX / nY;   //  nValue = 3

如果一个操作数是整型,另一个操作数是浮点型,则整型会提升为浮点型:

  float fX = 7. 0;
  int nY = 2;
  float fValue = fX / nY;
  // nY 提升为浮点型,除法操作将返回浮点型值
  // fValue = 3.5

有很多新手程序员会尝试写下如下的代码:

  int nX = 7;
  int nY = 2;
  float fValue = nX / nY;  // fValue = 3(不是3.5哦!)

这里的本意是 nX/nY 将产生一个浮点型的除法操作,因为结果是赋给一个浮点型变量的。但实际上并非如此。nX/nY 首先被计算,结果是一个整型值,然后才会提升为浮点型并赋值给 fValue。但在赋值之前,小数部分就已经丢弃了。

要强制两个整数采用浮点型除法,其中一个操作数需要类型转换为浮点数:

  int nX = 7;
  int nY = 2;
  float fValue = static_cast<float>(nX) / nY;  // fValue = 3.5

因为 nX 显式的转换为 float 型,nY 将隐式地提升为 float 型,因此除法操作符将执行浮点型除法,得到的结果就是3.5。

通常一眼看去很难说一个除法操作符究竟是执行整数除法还是浮点型除法:

 z = x / y;  // 这是整数除法还是浮点型除法?

但采用匈牙利命名法可以帮助我们消除这种疑惑,并阻止错误的发生:

  int nZ = nX / nY;     // 整数除法    double dZ = dX / dY; // 浮点型除法

有关整数除法的另一个有趣的事情是,当一个操作数是负数时 C++ 标准并未规定如何截断结果。造成的结果就是,编译器可以自由地选择向上截断或者向下截断!比如,-5/2可以既可以计算为-3也可以计算为-2,这和编译 器是向下取整还是向 0 取整有关。大多数现代的编译器是向 0 取整的。

3)=  vs  ==

这是个老问题,但很有价值。许多 C++ 新手会弄混赋值操作符(=)和相等操作符(==)的意义。但即使是知道这两种操作符差别的程序员也会犯下键盘敲击错误,这可能会导致结果是非预期的。

 // 如果 nValue 是0,返回1,否则返回 nValue    int foo (int nValue)
  {
      if (nValue = 0)  // 这是个键盘敲击错误 !            return 1;
      else           return nValue;
  }

  int main ()
  {
      std::cout << foo (0) << std::endl;
      std::cout << foo (1) << std::endl;
      std::cout << foo (2) << std::endl;
      return 0;
  }

函数 foo ()的本意是如果 nValue 是0,就返回1,否则就返回 nValue 的值。但由于无意中使用赋值操作符代替了相等操作符,程序将产生非预期性的结果:

  0   0   0

当 foo ()中的 if 语句执行时,nValue 被赋值为0。if (nValue = 0)实际上就成了 if (nValue)。结果就是 if 条件为假,导致执行 else 下的代码,返回 nValue 的值,而这个值刚好就是赋值给 nValue 的0!因此这个函数将永远返回0。

在编译器中将告警级别设置为最高,当发现条件语句中使用了赋值操作符时会给出一个警告信息,或者在条件判断之外,应该使用赋值操作符的地方误用 成了相等性测试,此时会提示该语句没有做任何事情。只要你使用了较高的告警级别,这个问题本质上都是可修复的。也有一些程序员喜欢采用一种技巧来避免= 和==的混淆。即,在条件判断中将常量写在左边,此时如果误把==写成=的话,将引发一个编译错误,因为常量不能被赋值。

4)混用有符号和无符号数

如同我们在整数除法那一节中提到的,C++中大多数的二元操作符需要两端的操作数是同一种类型。如果操作数是不同的类型,其中一个操作数将提升自己的类型以匹配另一个操作数。当混用有符号和无符号数时这会导致出现一些非预期性的结果!考虑如下的例子:

  cout << 10 – 15u;  // 15u 是无符号整数

有人会说结果是-5。由于 10 是一个有符号整数,而 15 是无符号整数,类型提升规则在这里就需要起作用了。C++中的类型提升层次结构看起来是这样的:

  long double (最高)
  double   float   unsigned long int   long int   unsigned int   int               (最低)

因为 int 类型比 unsigned int 要低,因此 int 要提升为 unsigned int。幸运的是,10已经是个正整数了,因此类型提升并没有使解释这个值的方式发生改变。因此,上面的代码相当于:

  cout << 10u – 15u;

好,现在是该看看这个小把戏的时候了。因为都是无符号整型,因此操作的结果也应该是一个无符号整型的变量!10u-15u = -5u。但是无符号变量不包括负数,因此-5这里将被解释为4,294,967,291(假设是 32 位整数)。因此,上面的代码将打印出4,294,967,291而不是-5。

这种情况可以有更令人迷惑的形式:

  int nX;
  unsigned int nY;
  if (nX – nY < 0)
      // do something

由于类型转换,这个 if 语句将永远判断为假,这显然不是程序员的原始意图!

C++编译器无法捕捉到的 8 种错误

5)  delete  vs  delete []

许多 C++ 程序员忘记了关于 new 和 delete 操作符实际上有两种形式:针对单个对象的版本,以及针对对象数组的版本。new 操作符用来在堆上分配单个对象的内存空间。如果对象是某个类类型,该对象的构造函数将被调用。

Foo *pScalar = new Foo;

delete 操作符用来回收由 new 操作符分配的内存空间。如果被销毁的对象是类类型,则该对象的析构函数将被调用。

delete pScalar;

现在考虑如下的代码片段:

 Foo *pArray = new Foo[10];

这行代码为 10 个 Foo 对象的数组分配了内存空间,因为下标[10]放在了类型名之后,许多 C++ 程序员没有意识到实际上是操作符 new[]被调用来完成分配空间的任务而不是 new。new[]操作符确保每一个创建的对象都会调用该类的构造函数一次。相反的,要删除一个数组,需要使用 delete[]操作符:

  delete[] pArray;

这将确保数组中的每个对象都会调用该类的析构函数。如果 delete 操作符作用于一个数组会发生什么?数组中仅仅只有第一个对象会被析构,因此会导致堆空间被破坏!

6)  复合表达式或函数调用的副作用

副作用是指一个操作符、表达式、语句或函数在该操作符、表达式、语句或函数完成规定的操作后仍然继续做了某些事情。副作用有时候是有用的:

x=5

赋值操作符的副作用是可以永久地改变x的值。其他有副作用的 C++ 操作符包括*=、/=、%=、+=、-=、<<=、>&gt;=、&=、=、^=以及声名狼藉的++和—操作符。但 是,在 C++ 中有好几个地方操作的顺序是未定义的,那么这就会造成不一致的行为。比如:

  void multiply (int x, int y)
  {
      using namespace std;
      cout << x * y << endl;
  }

  int main ()
  {
      int x = 5;
      std::cout << multiply (x, ++x);
  }

因为对于函数 multiply ()的参数的计算顺序是未定义的,因此上面的程序可能打印出 30 或 36,这完全取决于x和++x谁先计算,谁后计算。

另一个稍显奇怪的有关操作符的例子:

  int foo (int x)
  {
      return x;
  }

  int main ()
  {
      int x = 5;
      std::cout << foo (x) * foo (++x);
  }

因为 C++ 的操作符中,其操作数的计算顺序是未定义的(对于大多数操作符来说是这样的,当然有一些例外),上面的例子也可能会打印出 30 或 36,这取决于究竟是左操作数先计算还是右操作数先计算。

另外,考虑如下的复合表达式:

  if (x == 1 && ++y == 2)
      // do something

程序员的本意可能是说:“如果x是1,且y的前自增值是 2 的话,完成某些处理”。但是,如果x不等于1,C++将采取短路求值法则,这意味着++y将永远不会计算!因此,只有当x等于 1 时,y才会自增。这很可能不是程序员的本意!一个好的经验法则是把任何可能造成副作用的操作符都放到它们自己独立的语句中去。

7)不带breakswitch语句

另一个新手程序员常犯的经典错误是忘记在 switch 语句块中加上 break:

  switch (nValue)
  {
      case 1: eColor = Color::BLUE;
      case 2: eColor = Color::PURPLE;
      case 3: eColor = Color::GREEN;
      default: eColor = Color::RED;
  }

当 switch 表达式计算出的结果同 case 的标签值相同时,执行序列将从满足的第一个 case 语句处执行。执行序列将继续下去,直到要么到达 switch 语句块的末尾,或者遇到 return、goto 或 break 语句。其他的标签都将忽略掉!

考虑下如上的代码,如果 nValue 为 1 时会发生什么。case 1 满足,所以 eColor 被设为 Color::BLUE。继续处理下一个语句,这又将 eColor 设为 Color::PURPLE。下一个语句又将它设为了 Color::GREEN。最终,在 default 中将其设为了 Color::RED。实际上,不管 nValue 的值是多少,上述代码片段都将把 eColor 设为 Color::RED!

正确的方法是按照如下方式书写:

  switch (nValue)
  {
      case 1: eColor = Color::BLUE; break;
      case 2: eColor = Color::PURPLE; break;
      case 3: eColor = Color::GREEN; break;
      default: eColor = Color::RED; break;
  }

break 语句终止了 case 语句的执行,因此 eColor 的值将保持为程序员所期望的那样。尽管这是非常基础的 switch/case 逻辑,但很容易因为漏掉一个 break 语句而造成不可避免的“瀑布式”执行流。

8)在构造函数中调用虚函数

考虑如下的程序:

  class Base
  {
  private:
      int m_nID;

  public:
      Base ()
      {
          m_nID = ClassID ();
      }

      // ClassID 返回一个 class 相关的 ID 号        virtual int ClassID () { return 1;}
      int GetID () { return m_nID; }
  };

  class Derived: public Base
  {

  public:
      Derived ()
      {
      }
      virtual int ClassID () { return 2;}
  };

  int main ()
  {
      Derived cDerived;
      cout << cDerived.GetID (); // 打印出1,不是2!        return 0;
  }

在这个程序中,程序员在基类的构造函数中调用了虚函数,期望它能被决议为派生类的 Derived::ClassID ()。但实际上不会这样——程序的结果是打印出 1 而不是2。当从基类继承的派生类被实例化时,基类对象先于派生类对象被构造出来。这么做是因为派生类的成员可能会对已经初始化过的基类成员有依赖关系。结 果就是当基类的构造函数被执行时,此时派生类对象根本就还没有构造出来!所以,此时任何对虚函数的调用都只会决议为基类的成员函数,而不是派生类。

根据这个例子,当 cDerived 的基类部分被构造时,其派生类的那一部分还不存在。因此,对函数 ClassID 的调用将决议为 Base::ClassID ()(不是 Derived::ClassID ()),这个函数将m_nID 设为1。一旦 cDerived 的派生类部分也构造好时,在 cDerived 这个对象上,任何对 ClassID ()的调用都将如预期的那样决议为 Derived::ClassID ()。

注意到其他的编程语言如C#和Java会将虚函数调用决议为继承层次最深的那个 class 上,就算派生类还没有被初始化也是这样!C++的做法与这不同,这是为了程序员的安全而考虑的。这并不是说一种方式就一定好过另一种,这里仅仅是为了表示 不同的编程语言在同一问题上可能有不同的表现行为。

结论

因为这只是这个系列文章的第一篇,我认为以新手程序员可能遇到的基础问题入手会比较合适。今后这个系列的文章将致力于解决更加复杂的编程错误。 无论一个程序员的经验水平如何,错误都是不可避免的,不管是因为知识上的匮乏、输入错误或者只是一般的粗心大意。意识到其中最有可能造成麻烦的问题,这可 以帮助减少它们出来捣乱的可能性。虽然对于经验和知识并没有什么替代品,良好的单元测试可以帮我们在将这些 bug 深埋于我们的代码中之前将它们捕获。

英文原文:ALEX  编译:伯乐在线 — 陈舸

云平台:诸神之战

编者注:Michael Driscoll是Metamarkets的联合创始人兼CTO。他最近发表了一篇文章, 文章介绍了目前最有竞争力的几个云平台:Amazon Web Services、微软的Azure、Google App Engine以及EMC Virtual Private Cloud,并分析了各自的优势。作者认为云平台的实质就是计算、存储以及网络的虚拟化(抽象),谁赢下了云平台之争,就会像操作系统之争一样成为下一个 时代的新霸主。而一旦这个霸主确立,技术就会向着更高层面的抽象继续演进,那时候应用即服务型的初创企业将会百花齐放。

很多年以前情况就已经很显然——必须找到某种方式让程序员与硬件的复杂性无缘……这一层东西就是操作系统

—安德鲁·谭宁邦《操作系统(英文)》

云 就是企业新的操作系统,服务就是新的应用。云为下一代的服务提供庇护,从Pinterest到Instagram,从foursquare到AirBnB 都在腾云驾雾。这厢,为上一代的桌面应用提供接口的微软Windows和苹果MacOS X已如日落西山(注:这个结论也许下得太早,这些老牌操作系统本身也在蜕变),那厢,为上述服务提供必需的计算、存储和网络的Amazon之类供应商正冉 冉升起。

相对于上一辈,云这个操作系统更灵活,容错能力更高。云的这两个优势源自它的两个招牌特性:虚拟化和分 布式。因为虚拟化,失效的硬件可以升级或被换出,虚拟流程可移植到新机器,几乎不会对最终用户产生任何影响。因为分布式,云可以散布在成千上万个商品化的 设备中,服务的计算和带宽需求可通过灵活伸缩来满足,磁盘存储限制几乎已成时代逆流。

云在其他方面也带来了新挑 战,更确切地说又把我们带回了前PC时代的客户/服务器关系(注:又一次印证了螺旋式上升的哲学原理)。如果说云是更强大的主机的话,那么今天的客户端则 比昔日的哑终端更加智能。新的客户机是智能手机、平板电脑,以及更加现代的web浏览器,其本地缓存与计算能力乃富交互应用之精髓。不过,管理客户机与服 务器之间以及跨越不同类别客户机之间的状态会给开发人员带来复杂性。

这个计算新世纪打开了新的市场与机遇之门。这场决定战役的主题是:谁会成为云计算王国的标准平台?

这个赌注很大,不仅仅因为效用计算的开支增长迅速。跟以往操作系统之争一样,这也是一场生死攸关的战争,争夺的焦点是运行于云平台之上的应用之控制权。

Amazon Web服务:王座无可争议

Amazon的Web Services(AWS)初步取得领先。AWS在其核心的弹性计算云(Elastic Compute Cloud)之上提供了一组不断扩展的周边设备,如块存储、负载均衡以及内容交付网络。2011年,AWS的增长率为80%,收入估计将近10亿美元,云 服务的市场前景究竟有多大由此可略见一斑。在最近跟22位CEO的会面中我问他们有多少位在用AWS:每个人都举手。

Microsoft Azure:微软的双刃剑

伟大的桌面颠覆正在发生。桌面应用正一个接一个地被同等的云服务取代,办公、财会,甚至照片编辑均如此。这些服务吸收了诸如跨设备无缝同步(Evernote)、社交、实时协作(Zendesk、Google App)等云的服务器中心架构天生就支持的特性。

对 于桌面的传统霸主微软来说,云平台当然是个威胁。但机遇并存。从目前占统治地位的桌面过渡到新的云前沿,微软也拥有其战略杠杆。微软可帮助早已经熟悉其编 程API和运行环境的桌面开发者扩展其应用。与此类似,让微软的客户群平滑地演进到云服务当中相对也比较容易。所以说,微软携着Azure进入云平台领域虽然有些晚但它有居高临下的优势。微软的问题在于如何挥舞好这把双刃剑才能既可杀敌又能免于自残。

Google的App Engine:失速的平台

在为消费者开发应用即服务方面,Google是早期的领导者,Google的App套件最为人所熟知的。但是Google在为开发者开发自己的云平台App Engine方面的成就则略微逊色。跟操作系统一样,如果能够为应用开发者提供合适的工具,云平台就可以成功。

与微软的Azure和Amazon的Web Service相比,App Engine的开发者工具更高级,更面向Google,约束更多。只支持选定的编程语言,后端存储也存在限制。结果是App Engine在严肃的开发者那里的被接受程度相对较低。

尽管Google在创建自己的云服务方面的能力很强,借助着Android,它在客户端生态系统的存在也不断增强,但如果Google希望在云平台方面与别人一决高下的话,它需要换挡才行。

EMC的优势:数据信任的力量

EMC 是最后一个有竞争力的大玩家。跟Amazon、微软和Google不一样的是,EMC跟消费端IT的距离很遥远,主要面对的是企业。EMC的根本优势是数 据。它的服务器已经为一些全球最大型的企业的宝贵数据提供托管。这是一项很强大的战略优势,因为数据既是云价值的核心,也是其漏洞的来源。

云从本质上来讲属于数据操作系统(如Tim O’Reilly对互联网的评价一样)。云服务的价值在于连接—通过电子邮件、社交媒体、业务仪表盘等与整个星球颤动的数字脉搏相连。

不过数据不断增加的连接性也带来了漏洞风险;消费者隐私被侵犯的事件一定会越来越多。对于企业来说,很难评估数据安全性方面的敏感性。数据的价值独一无二,是不可替代的企业资产,其损失不可以金钱来进行衡量。

EMC已经赢得了要求严苛的企业的信任感,让他们放心将自己的数据交给EMC。EMC可以凭借这种信任发挥优势。相对于Amazon的公有云,EMC提供虚拟私有云(VPC),EMC很聪明,它意识到“公有云”这个词对于企业高管来说跟“公共厕所”和“公共泳池”差不多。

风云变幻,定局待时

云平台的王者之争至关重要,因为这跟其他的平台之争一样,这场决斗的结果也是胜者为王,赢家通吃。一旦某一初创企业将自己的数据放在了Amazon的S3存储服务上,那么它再将自己的Hadoop集群部署在Amazon上也将是一个划算的选择(带宽成本更低),同样,跟其他已经在Amazon上面的服务提供商的协作也会更好。

被锁定在一个平台之中的风险促发了某些云计算方面开源标准的发展,最值得注意的是OpenStack,这个标准已经得到了IBM、Rackspace以及HP的支持。

谁能够成为市场霸主取决于其发展势头、迁移成本、安全以及对开发者的友好程度(API设计、标准、文档)的综合较量。胜出者将会成为下一代原生云应用及服务基础性的操作系统,并将获得丰厚无比的物质回报。

无论谁将胜出,有远见的公司都能够继续享用云操作系统所赋予的存储、计算及网络虚拟化的好处,同时能够将自己的创造力集中在这个技术栈之上的地方。其结果将是应用即服务型初创企业的百花齐放,会对已有的技术公司和整个经济产生颠覆性的影响,好戏才刚刚开始。