论坛首页 编程语言技术论坛

朴实的C++设计

浏览 54615 次
该帖已经被评为精华帖
作者 正文
   发表时间:2010-08-13  
我是一个JAVA程序员,我习惯具体问题具体分析,不喜欢把简单的问题过度的抽象,也不喜欢把复杂的问题过程化。实用为主。需求变化时做必要的重构。最重要是平衡,中国人常说的阴阳平衡嘛。
0 请登录后投票
   发表时间:2010-08-13  
引用

非常深刻的见解!

关于第 2 点,我提供两个注脚:
1. Linus 在 2007 年炮轰 C++ 时说“——低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。”
http://thread.gmane.org/gmane.comp.version-control.git/57643/focus=57918

其实什么代码都有重写的可能。Linus说这话是不大地道的。毕竟,Linux是不提供Windows平台的二进制兼容的,代码改变了就要重编译,原因就是他写的“不漂亮”的代码有更多从头写过的可能。

引用

2. Google 的 Go 语言在设计时有意禁止了类型继承:
http://golang.org/doc/go_lang_faq.html#inheritance
这么做的原因是,如果有一棵类型继承树,人们在一开始设计时就得考虑各个 class 在树上的位置。
随着时间的推衍,原来正确的决定有可能变成错误的。但是更正这个错误的代价可能很高。要想把这个
class 在继承树上从一个节点挪到另一个节点,可能要触及所有用到这个 class 的客户代码,所有
用到其各层基类的客户代码,以及从这个 class 派生出来的classes 的代码。
简直牵一发而动全身,在 C++ 缺乏良好重构工具的语言下,有时候只好保留错误,用些 wrapper 或
者 adapter 来掩盖之。久而久之,设计越来越烂,最后只好推倒重来。
解决办法之一就是不采用基于继承的设计,而是写一些容易使用也容易修改的具体类。

其实这段话包含了关于遗留代码(Legacy code)的两方面的内容。第一,重用。第二,重写。“能不改就不改”的原则早就被Refactoring那本书给批判过了,所以“设计越来越烂,最后只好推倒重来”并不是OO的问题,而是开发模式的问题。其次,如果一旦囿于时间的金钱需要重用一段年久失修的代码,依我的经验,OO的风格至少要比c要好处理的多。
“牵一发而动全身”往往是源于设计失误。这样的问题,c程序里简直要多得多的多。
Go语言目前是很风光,但是我们要清楚,它离成功的路还太远。即使是那么不得意C++的Linus在评价Go的时候也说:Go很好。但是想推出一门新语言实在是太难了。

引用

总之,继承和虚函数是万恶之源,这条贼船上去就不容易下来。不过还好,在 C++ 里我们有别的办法:
http://blog.csdn.net/Solstice/archive/2008/10/13/3066268.aspx[/quote

C++最大的优势是在于提供了多种语言风格的选择。如果你实在讨厌OO,不用就可以了,至少还有C风格;如楼主的纯对象风格;模板/标准库风格;以及Generic Programming风格可以选择。
0 请登录后投票
   发表时间:2010-08-13  
ccyingfu 写道
我是一个JAVA程序员,我习惯具体问题具体分析,不喜欢把简单的问题过度的抽象,也不喜欢把复杂的问题过程化。实用为主。需求变化时做必要的重构。最重要是平衡,中国人常说的阴阳平衡嘛。

我觉得不应该说“平衡”,而是“权衡”。
敏捷开发那本书里讲的Open-Close原则,我觉得就是“权衡”。什么是不变的,什么是变化的,源于开发者对于需求和技术的理解,权衡之后达到的结果。什么该Open,什么该Close,玄之又玄。其实上面的帖子对于C++和OO风格的批判,都应该归结到这个原则上,是设计的问题,和具体语言无关。
0 请登录后投票
   发表时间:2010-08-13  
为什么这个时候把这篇文章又翻出来?两年过去,你的想法有没有什么发展和变化?这倒是我更感兴趣的。

不过其实我同意你在2008年底写的一系列文章中的观点,C++中OO的大部分内容,现在只有面试的时候可能有点用,其他时候最好绕道而行。

现在 mem_fn, reference_wrapper, function 和 bind 都被直接放到了 std 里面,C++编程的风格可以发生根本变化了。我其实很希望能够看到你有一个更系统的论述,毕竟在这方面,你可能是经验最丰富的人之一。

BTW,给我发个短信,我手机crash,把你的联系方式丢了。
0 请登录后投票
   发表时间:2010-08-13   最后修改:2010-08-13
我记得在 InfoQ 上看到一篇文章,几个OO先驱在反思,说OO当初的本意其实就是对象之间的消息交互——这是一个简单有力的世界观,也是对现实世界的一个有效的刻画。但是不知道为什么,到了80年代中后期,OO的重点变成了“封装、继承、多态性”,其中对于继承的强调完全是基于想象而不是软件开发实践,结果诱导一代人尝试去建立完整复杂的继承树——这需要对于现实世界进行静态的、层级化的概念分类,这样一来,就对OO来了一个釜底抽薪,偷换了其根本的世界观。而这个新的世界观,其实并不太经得起推敲,不但不同的人有不同的看法,而且过于繁复易错,而且会使开发者偏离软件开发的根本目标,转而去构建应用领域中的概念体系。

我想这个是OO实践十多年来未进入人意的根本原因。
0 请登录后投票
   发表时间:2010-08-13  
1 OO的性能肯定比PO低。因为OO的机制比较复杂。比如继承和多态,导致随便调用一个函数都有可能要找遍所有的父类有没有对应的函数。所以说像linux或者网络服务器这种讲究性能的程序,用c肯定是没错的。
2 OO首先是一种设计思想,然后才有OO编程语言。OO语言是一种工具让我们方便的用它来写出我们的OO设计。所以,用OO思想设计的系统就应该用OO语言来写代码,用PO设计的系统就最好用PO语言来写。

所以,像LZ这样的情况,是属于用PO思想设计的系统,最后用错了语言,选用了CPP这样一种支持OO编程的语言。但是仍然不要紧,因为CPP和java以及其他大多数支持OO编程的语言,都是同时支持OO和PO编程的。

关于语言和范式的知识,可参考http://en.wikipedia.org/wiki/Programming_paradigm
0 请登录后投票
   发表时间:2010-08-13   最后修改:2010-08-13
myan 写道
...而这个新的世界观,其实并不太经得起推敲,不但不同的人有不同的看法,而且过于繁复易错,而且会使开发者偏离软件开发的根本目标,转而去构建应用领域中的概念体系...

这两句话是精髓。不过我有两个问题:
1. 封装/继承/多态只是一种选择而不是全部。我觉得方法本身不存在对错,而错误在于开发者建立这种繁复的类体系的选择。
对于C++,我更倾向于一种混合式、渐进式的编程风格。只有时间检验过,用户检验过的类体系才是某种程度可信任的,纯概念性类体系,除了算法库,是很难有通用性的。
还是XP的概念,代码是变化的。变化是唯一不变的东西。
2. 什么是“好的”世界观呢?语言、编程风格、范式都不是永远正确的。不同开发者总会有不同的选择。一个Web开发人员,和OS开发人员,图形图像开发人员,以及骇客/安全开发人员,他们对于开发模式,和编程技术的体验会有很大的区别,这决定了他们对于开发技术的喜好。我不认为这点上Linus就是对的,或者楼主是对的,又或者我是对的。每个人有不同的开发体验,也决定了他们采取的开发技术。
从楼主的例子说,我的结论是,他成功的地方是在于对于代码和测试用例的仔细推敲,而不是采用了哪一种语言风格。采用纯对象式风格的好处,只不过是对一个初级水平的团队更容易理解和把握而已。
归根到底,是开发者的技术经验,对客户的理解,以及对于产品的付出,决定了他能否开发出“好的”产品。其实语言本身是相对次要得多的东西。
0 请登录后投票
   发表时间:2010-08-13  
hatedance 写道
1 OO的性能肯定比PO低。因为OO的机制比较复杂。比如继承和多态,导致随便调用一个函数都有可能要找遍所有的父类有没有对应的函数。

谁说的?你说的是script不是C++吧?
0 请登录后投票
   发表时间:2010-08-13   最后修改:2010-08-13
myan 写道
我记得在 InfoQ 上看到一篇文章,几个OO先驱在反思,说OO当初的本意其实就是对象之间的消息交互——这是一个简单有力的世界观,也是对现实世界的一个有效的刻画。但是不知道为什么,到了80年代中后期,OO的重点变成了“封装、继承、多态性”,其中对于继承的强调完全是基于想象而不是软件开发实践,结果诱导一代人尝试去建立完整复杂的继承树——这需要对于现实世界进行静态的、层级化的概念分类,这样一来,就对OO来了一个釜底抽薪,偷换了其根本的世界观。而这个新的世界观,其实并不太经得起推敲,不但不同的人有不同的看法,而且过于繁复易错,而且会使开发者偏离软件开发的根本目标,转而去构建应用领域中的概念体系。

我想这个是OO实践十多年来未进入人意的根本原因。


用“继承树”这种方式来建模,确实是基于概念分类的思想。“分类”似乎是西方哲学一早就有的思想,影响深远,这种思想估计可以上溯到古希腊时期。
比如电影,可以分为科幻片、爱情片、伦理片、战争片、灾难片、恐怖片等等。
比如生物,可以分为动物和植物,动物又可以分为有脊椎和无脊椎,有脊椎动物又分为鱼类、两栖类、爬行类、鸟类和哺乳类。
又比如技术书籍分为电子类、通信类、计算机类等等,计算机书籍又可分为编程语言、操作系统、数据结构、数据库、网络技术等等。
这种分类法或许是早期面向对象发展的一个模仿对象。这种思考方式的本质困难在于,某些物体很难准确分类,似乎有不止一个分类适合它。而且不同的人看法可能不同,比如一部动作科幻片到底科幻的成份重还是动作的成份重,到底该归入哪一类。
在编程方面,情况更糟,因为这个“物体x”是变化的,一开始分入A类可能是合理的(x "is-a" A),随着功能演化,分入B类或许更合适(x is more like a B),但是这样改动对现有代码代价已经太高了(特别对于 C++)。
这根 web 1.0 和 2.0 有点类似,在 web 1.0 时代,给网站分类造就了了 yahoo 的早期成功;到了 web 2.0 时代,tag 代替了分类,一个网址(或者帖子)可以有多个 tag 以便于查找。
在传统面向对象语言,可以用继承多个 interfaces 来缓解分错类的代价,使得一物多用。
现代编程语言这一步走得更远,ruby 的 duck typing 和 go 的无继承都可以看作以 tag 取代分类(层次化的类型)的代表。一个 object 只要提供了相应的 operations,就能当做某种东西来用,不需要显示地继承或实现某个接口。这确实是一种进步。
对于 C++ 的四种范式,我现在基本只把它当 better C 和 data abstraction 来用。OO 和 GP 可以在非常小的范围内使用,只要暴露的接口是 object based (甚至 global function)就行。
0 请登录后投票
   发表时间:2010-08-13  
jimmy_c 写道
hatedance 写道
1 OO的性能肯定比PO低。因为OO的机制比较复杂。比如继承和多态,导致随便调用一个函数都有可能要找遍所有的父类有没有对应的函数。

谁说的?你说的是script不是C++吧?

我说的是运行时才能确定的多态。
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics