高阶特性与低阶语言的尴尬

174 views
Skip to first unread message

pongba

unread,
Sep 16, 2007, 1:15:20 AM9/16/07
to pon...@googlegroups.com
主要是说OO这个高阶特性。C++/D都支持OO,但同时C++和D的模块又都是二进制复用的(总不能要求每个机器都安装编译器然后发布源代码吧),这就构成了矛盾,什么矛盾呢?矛盾在于二进制复用的本质导致了OO技术在二进制边界上必须让步。如果跨二进制边界即使传输一个接口指针的话,那么后面就再也不能动这个该死的接口了,就连加个成员函数都不成。跟别说减,或者分裂接口了。最违反直觉的就是加成员函数也不成。

另一方面,真正高阶的语言,像ruby这种脚本语言,抛弃二进制复用,那么这个矛盾立即解决了。再加上强大的火星人类型系统,就算一个函数所依赖的对象整个变掉了加了成员数据了加了成员函数了又减了成员函数了或者改变成员数据排列顺序了加了基类了减了基类了,管它的,只要这个函数所依赖的那些个成员函数都还在,就没问题,就能把那个对象所在的模块给换掉。云风一语道破天机,这就叫通过字符串来解耦合。NND这才是真正自然的解耦合。二进制复用是不人道的,影响了OO表达和跨边界复用。

但又不得不用二进制复用,因为二进制复用是"最小公分母",硬件平台的厂商大战已经将天下三分,益州疲痹~ 但VM大战还没有到这个程度,哪一天真的CLR或者什么VM占去了像intel占领硬件平台这么大的份额,那把复用层面往上提一层还是可以的,但目前,也只能将就~

--
刘未鹏(pongba)|C++的罗浮宫
http://blog.csdn.net/pongba
TopLanguage
http://groups.google.com/group/pongba

Atry

unread,
Sep 16, 2007, 1:42:14 AM9/16/07
to pon...@googlegroups.com
那么像 LLVM 这样的技术呢?反正 LLVM 的 backend 也支持转换成特定平台的机器码。 http://llvm.org/
其实 Java 也是用字符串解耦合。 用二进制编辑器打开 .class 文件看就发现它引用其他类的方法都是用字符串来定位的。

在07-9-16, pongba <pon...@gmail.com> 写道:

oldrev

unread,
Sep 16, 2007, 1:45:45 AM9/16/07
to pon...@googlegroups.com
"字符串来解耦合"这句话说得非常好,C动态库为什么能被众多语言调用,不就是
因为函数在库里是人类可读的字符串形式的吗?Ruby 的复用正是"每个机器都安装
编译器然后发布源代码",在目前看来静态和动态两种语言暂时不能互相取代。像
template 这样 source-only 的东西是很邪恶的,这就是为什么 M$ 不能大刀阔斧
的为C#添加成堆的编译时特性的原因,因为要照顾 VB,两兄弟都是 .net 的顶梁
柱,总不能厚此薄彼吧。

话又说回来,二进制接口的变化也不是什么大问题,比如你用 C++ 写了一个 OS,
提供了 template/OO 的 C++-only 的 API,那么每当推出新版本 OS 的时候,只
需在说明书里添加一句"请联系软件供应商确保支持新版本 OS"即可,甚至还能搞
个 "Designed for MyOS" 认证,捞上一笔,何乐而不为呢。

在这方面,无数的 Linux/*BSD 发行版已经实践了多年:操作系统本身和应用程序
都是一起发布的,使用同一版本的编译器,也就不存在二进制兼容性问题。如果你
想用不被发行商支持的软件,那么唯一的途径就是自己编译,如果是闭源软件,那
么只有去找开发商抱怨了。

在 2007-09-16日的 13:15 +0800,pongba写道:
> 主要是说OO这个高阶特性。C++/D都支持OO,但同时C++和D的模块又都是二进制
> 复用的(总不能要求每个机器都安装编译器然后发布源代码吧),这就构成了矛
> 盾,什么矛盾呢?矛盾在于二进制复用的本质导致了OO技术在二进制边界上必须
> 让步。如果跨二进制边界即使传输一个接口指针的话,那么后面就再也不能动这
> 个该死的接口了,就连加个成员函数都不成。跟别说减,或者分裂接口了。最违
> 反直觉的就是加成员函数也不成。
>
> 另一方面,真正高阶的语言,像ruby这种脚本语言,抛弃二进制复用,那么这个
> 矛盾立即解决了。再加上强大的火星人类型系统,就算一个函数所依赖的对象整
> 个变掉了加了成员数据了加了成员函数了又减了成员函数了或者改变成员数据排
> 列顺序了加了基类了减了基类了,管它的,只要这个函数所依赖的那些个成员函

red...@gmail.com

unread,
Sep 16, 2007, 1:54:39 AM9/16/07
to pon...@googlegroups.com
OO 真是这么重要吗 ?

我最近用 D 写的一个服务器框架, 用得多的是 GP (反正编译很快), mixin; 继承
用得很少, 只有一个类有两次继承, 其中一层是看到代码量比较大, 将一些逻辑上
相对独立的东西抽了出去, 另外一层是为了可以 copy tango 的 各种 poll
selector代码来用(poll, select, epoll, 以后可能有 kqueue), 否则用 GP 更合适.

这个框架完毕之后, 对外也是不会暴露这些内部关系的, 写一个 struct 出来, 里
面放上delegate, 给外面做接口, 这就和 com 差不多了, 似乎也很简单. C++ 的
member function 不容易完成 delegate 的任务, 不好用这个方法, D 倒没有问题.

现在的我的代码更倾向于所谓的 object based, 而不是 object oriented 了.

搞得太 OO, 之前讨论过的开销就不说了, 碰到线程同步这个问题, 就更多罗嗦,
如果对象和类的关系复杂, 程序的当前状态需要多个对象才能共同表达出来, 函数
调用链复杂, 再加上临界区跟随 OO 而细化, 那人肉分析会不会死锁, 简直就成为
不可能的任务了.

我就写过一个这样的程序, 到最后, 我也无法搞清楚我的程序有没有同步问题, 大
量测试的时候, 有很低的几率会出现一些问题, 但是不知道具体缘故, 因为调用链
太复杂, 程序的状态分布到太多对象中去了.

反倒是, 如果用过程式语言写, 数据就是在那里了, 要访问什么, 加哪个锁, 很清
晰, 要调试应该也会简单不少.

OO 是用增加数据之间的复杂性来换取操作之间的抽象和内聚, OO 过了问题很大.

云风的看法是过度设计比代码冗余危害更大一些. 我的看法在云风的基础上还有一
点, 代码冗余问题, 靠人力堆上去是容易解决的, 但是设计太复杂带来的问题, 人
力堆上去都没有用处.

如果程序中临界区种类多, OO 还更不要太复杂了, 否则重犯我翻船错误的几率估
计不小.

pongba 写道:


> 主要是说OO这个高阶特性。C++/D都支持OO,但同时C++和D的模块又都是二进制
> 复用的(总不能要求每个机器都安装编译器然后发布源代码吧),这就构成了矛
> 盾,什么矛盾呢?矛盾在于二进制复用的本质导致了OO技术在二进制边界上必须
> 让步。如果跨二进制边界即使传输一个接口指针的话,那么后面就再也不能动这
> 个该死的接口了,就连加个成员函数都不成。跟别说减,或者分裂接口了。最违
> 反直觉的就是加成员函数也不成。
>
> 另一方面,真正高阶的语言,像ruby这种脚本语言,抛弃二进制复用,那么这个
> 矛盾立即解决了。再加上强大的火星人类型系统,就算一个函数所依赖的对象整
> 个变掉了加了成员数据了加了成员函数了又减了成员函数了或者改变成员数据排
> 列顺序了加了基类了减了基类了,管它的,只要这个函数所依赖的那些个成员函

> 数都还在,就没问题,就能把那个对象所在的模块给换掉。云风一语道破天机,

pongba

unread,
Sep 16, 2007, 2:13:48 AM9/16/07
to pon...@googlegroups.com


On 9/16/07, red...@gmail.com <red...@gmail.com> wrote:
OO 真是这么重要吗 ?


啊哈:) 偶可没说OO有那么重要啊:) 其实只要OO有用,这个问题都可以讨论的。
 

我最近用 D 写的一个服务器框架, 用得多的是 GP (反正编译很快), mixin; 继承
用得很少, 只有一个类有两次继承, 其中一层是看到代码量比较大, 将一些逻辑上
相对独立的东西抽了出去, 另外一层是为了可以 copy tango 的 各种 poll
selector代码来用(poll, select, epoll, 以后可能有 kqueue), 否则用 GP 更合适.

redsea认为GP和OO的职责有交集吗?如果有的话,GP跟OO这两种抽象机制哪个更好呢?(取决于哪种对"好"的定义)。
 

这个框架完毕之后, 对外也是不会暴露这些内部关系的, 写一个 struct 出来, 里
面放上delegate, 给外面做接口, 这就和 com 差不多了, 似乎也很简单. C++ 的
member function 不容易完成 delegate 的任务, 不好用这个方法, D 倒没有问题.

现在的我的代码更倾向于所谓的 object based, 而不是 object oriented 了.

OB,就是OB。本来人的思维模式也是先认识实体再抽出共性的。每写一个concrete class就要考虑抽象,烦不胜烦,还不如直接写具体类,然后辅以GP解耦合以避开过度依赖的问题,以后,如果实在必要的话,再考虑用interface和继承
 

搞得太 OO, 之前讨论过的开销就不说了, 碰到线程同步这个问题, 就更多罗嗦,
如果对象和类的关系复杂, 程序的当前状态需要多个对象才能共同表达出来, 函数
调用链复杂, 再加上临界区跟随 OO 而细化, 那人肉分析会不会死锁, 简直就成为
不可能的任务了.

上次一个朋友问我一个问题。dynamic_cast<D*>(pb)出错了,而且并不是返回0,而是直接异常,说no_rtti_info之类的。D和B都有虚函数,而且编译的时候还开了RTTI选项。

我就写过一个这样的程序, 到最后, 我也无法搞清楚我的程序有没有同步问题, 大
量测试的时候, 有很低的几率会出现一些问题, 但是不知道具体缘故, 因为调用链
太复杂, 程序的状态分布到太多对象中去了.

反倒是, 如果用过程式语言写, 数据就是在那里了, 要访问什么, 加哪个锁, 很清
晰, 要调试应该也会简单不少.

说到调试,最近有一本好书《Why Programs Fail》!

OO 是用增加数据之间的复杂性来换取操作之间的抽象和内聚, OO 过了问题很大.

云风的看法是过度设计比代码冗余危害更大一些. 我的看法在云风的基础上还有一
点, 代码冗余问题, 靠人力堆上去是容易解决的, 但是设计太复杂带来的问题, 人
力堆上去都没有用处.

如果程序中临界区种类多, OO 还更不要太复杂了, 否则重犯我翻船错误的几率估
计不小.

 
尤其是,OO一旦抽象抽错了,依赖已经造成,大错就很难挽回了。我觉得这个风险才是最重要的弊端。而过程式的抽象逼着你去只依赖必须依赖的东西,接口也很紧凑,耦合更小,不像interface,一依赖就依赖一个整体。另外,我还没做过non-trivial的OO设计,也就是看看一些Bob的《敏捷软件开发:原则模式与实践》和Meyer的OOSC等之类的书,再加上一些思想实验而已,所以可能在方家看来纯属扯淡 :P

red...@gmail.com

unread,
Sep 16, 2007, 3:10:58 AM9/16/07
to pon...@googlegroups.com
pongba 写道:


On 9/16/07, red...@gmail.com <red...@gmail.com> wrote:
OO 真是这么重要吗 ?


啊哈:) 偶可没说OO有那么重要啊:) 其实只要OO有用,这个问题都可以讨论的。
我的意思实际是, OO 不那么重要, API 中暴露的类没有几个的话, 你说的问题就不严重了, 可以勉为其难弄成 C 的接口方式来用 :)

如果 OO 还真的很重要, 那只好通过 xpcom 之类的东西进行二进制重用好了.


 


redsea认为GP和OO的职责有交集吗?如果有的话,GP跟OO这两种抽象机制哪个更好呢?(取决于哪种对"好"的定义)。
说老实话, 最近这些年我进行理论思考已经不多了, 更多的是靠感觉行事.

我感觉用 gp 来分离东西(分离功能, 分离对象, 分离复杂度等等吧), 更加自然, 更加顺手.

如果没有顾虑的话, 我觉得GP 这个机制更好,
1. 性能不必说了;

2. 对于降低耦合度很有好处

3. 还不必要如同先知一般事先将全部东西都分拆成合适的 OO 对象(这个你也说过 :)

4. 程序也容易组织: 各个独立的功能部件, 用独立的具体类或者模板类来实现, 然后粘合层用gp, 只要被粘合的东西实现了指定的功能, 就可以粘合起来, 不必管有没有实现哪个 interface, 公共基类之类的事情.  说老实话, 粘合层, 框架之类的东西, 用 OO 搞不好就引入大量的耦合, 很讨厌的.
    这个和 2 的关系很大, 只是2从程序结构出发, 4从开发过程出发.

只是以前 C++ 的 template, 调试器的 watch 难受, 错误信息也难看, 在一定程度上让我有所顾虑而已.

改用D 之后, 错误信息输出的问题没有了, 另外, 反正也没有好用的调试器, 是用 unittest 加 log/console output, 我会很愿意多

用 gp.  OO 可能留到确实需要运行时期的多态才去用, 例如被举过很多次的 选择一个形状, 从 factory new 一个对象, 让它自己绘制这种, gp 不行的.   实际上, gp 不行而 oo 可以的, 可能只剩这种真正允许运行时期多态, 而不能是编译时期选择的东西了.

 

这 个框架完毕之后, 对外也是不会暴露这些内部关系的, 写一个 struct 出来, 里
面放上delegate, 给外面做接口, 这就和 com 差不多了, 似乎也很简单. C++ 的
member function 不容易完成 delegate 的任务, 不好用这个方法, D 倒没有问题.

现在的我的代码更倾向于所谓的 object based, 而不是 object oriented 了.

OB,就是OB。本来人的思维模式也是先认识实体再抽出共性的。每写一个concrete class就要考虑抽象,烦不胜烦,还不如直接写具体类,然后辅以GP解耦合以避开过度依赖的问题,以后,如果实在必要的话,再考虑用interface 和继承
恩, 我也是这样, 然后会发现, 可以很少动用到 OO.

 
尤其是,OO一旦抽象抽错了,依赖已经造成,大错就很难挽回了。我觉得这个风险才是最重要的弊端。而过程式的抽象逼着你去只依赖必须依赖的东西,接口也很 紧凑,耦合更小,不像interface,一依赖就依赖一个整体。另外,我还没做过non-trivial的OO设计,也就是看看一些Bob的《敏捷软件 开发:原则模式与实践》和Meyer的OOSC等之类的书,再加上一些思想实验而已,所以可能在方家看来纯属扯淡 :P


你说的也很有道理.
另一方面, 彻底的抽象错了也不容易, 可能是抽象得不太好, 然后就不太好地继续用下去, 和实际有距离, 代码会比较bad smell.

不过据说 java 的重构工具很厉害, 可以在类拆分, 类合并方面给予很大的帮助? 如果真有这点, 碰到这种情况, 比起 C++ 的抢救方案就更多了 ?

yq chen

unread,
Sep 16, 2007, 4:37:06 AM9/16/07
to pon...@googlegroups.com
"OB,就是OB。本来人的思维模式也是先认识实体再抽出共性的。每写一个concrete class就要考虑抽象,烦不胜烦,还不如直接写具体类,然后辅以GP解耦合以避开过度依赖的问题,以后,如果实在必要的话,再考虑用interface和继承。"
 
严重同意pongba的这个观点,在C++中这种方式开发系统成功的可能性最大,我个人也是这样做的。
 
我不会因为我家的插座可以开关然后我家的门也可以开关我就定一个IOpenCloseable接口,然后让插座和门都从这个接口派生,或许只有一些半吊子的java程序员才这么做。
 
我喜欢的编程方式是:以OP作为程序开发的主体流程,用OB定一个概念(concrete)类,用少量(必要)的OO来做辅助手段,然后可能是重要的也可能是不重要的(看程序开发的目标是什么)使用GP来实现duck typing式的复用(亦即基于字符串的解耦)。
Reply all
Reply to author
Forward
0 new messages