向后兼容性与老化

只想记个链接而已,标题党了(苦笑

Bruce Eckel发了篇帖子, Will Open-Sourcing Java Remove Competetive Corporate-Think?。其中的观点并不新了,他自己最近的几篇帖子里其实都有类似的内容。不过今天读到的这篇让我想随便记几句。

跟Bruce一样,Sun让我感到很困惑的一点就是"no one has been able to figure out Sun's business plan"。技术层面上Sun一直不缺人才,但在奇怪的商业策略的引领下这些技术总让人觉得没能发挥出它们最大的潜力。有一个挺有趣的观点(也不新了):Sun是想学习Microsoft的商业策略,并标榜自己为anti-Microsoft。某种程度上来说这有点道理。至少Bruce提到的这点我觉得很符合我接触到的状况:"Rather than fixing the problem, respond with rhetoric (another lesson learned from Microsoft)"。

Enough rants on Sun. I don't really care about what happens to Sun anyway. 我是联想到最近关注的C# 4.0的一些设计,也是严重受限于向后兼容性。

假如把一门语言或者一个库的API比做一件衣服,把这个语言或者库所要解决的问题领域比做一个人,那么一开始这个人还是个小婴孩,总不能把衣服做得太大。慢慢的这个小孩长大了,在一定范围内衣服可以修补、扩张来满足新的需要,但这样的修补会让衣服变得越来越重。超出这个范围后,如果不把衣服拆解回到零件的状态再重新组装的话,衣服就很难再继续改进来满足需要了。
一个问题领域也总是有从简单到复杂的发展过程的。一开始如果把语言或者API设计得太复杂,那会:1、提高学习曲线,使它难以学习;2、难以适应后续的变更。那么假如一开始设计得很简单,后面要继续满足新需求多半需要添加新的元素;对语言来说这意味着新的语法结构,对API来说这意味着新的类、新的方法或函数。为了保持 向后兼容性,添加新元素的同时就无法去掉旧的过时的元素,使语言或API变得越来越重,甚至影响到新设计的可能性——也就是所谓的 老化。持续下去的话,最终人们将不得不使用新的语言或者库来解决新出现的问题, 让原来的语言处理它老化之前就能处理的问题(注意:这并不是说原来的语言就“死”了。只是在它的问题领域已经“成熟”了而已)。

程序员真的需要向后兼容性么?当然,如果能保持向后兼容性同时又能提供新的服务,如果这两者不矛盾的话,自然是好的。没人会心甘情愿的只为了升级运行时环境而修改一大堆代码。但程序员本来就应该习惯“为了解决问题”而写程序。如果因为向后兼容性绑住了手脚而无法解决问题,那这个向后兼容性的代价也太大了。很多时候,真正的进化都意味着要放弃绝对的向后兼容性,像是Python和Ruby的发展过程中许多东西都为了适应新的需要而在改变着。

C# 4.0里有可选参数(optional parameter)的语法结构。这种语法结构在C++里已经存在多年,即便在.NET平台上也有VB.NET一直支持,看似微不足道的。但就是因为保持向后兼容性,它不得不做了个尴尬的设计: 如果将虚方法与可选参数一起使用,会带来十分不直观的行为。例子的话请参考这里: Optional parameters - Conclusion: Treat like "unsafe"。下面把例子的代码引用过来:
public class Derived : Base {
    public override string Test(int test = 2) {
        return "Derived " + test.ToString();
    }
}
 
public class Base : IBase {
    public virtual string Test(int test = 1) {
        return "Base " + test.ToString();
    }
}
 
interface IBase {
    string Test(int test = 3);
}
 
sealed class Program {
    static void Main(string[] args) {
        Base b = new Base();
        Console.WriteLine(b.Test());

        IBase bi = b;
        Console.WriteLine(bi.Test());

        Derived d = new Derived();
        Console.WriteLine(d.Test());

        Base d2 = d;
        Console.WriteLine(d2.Test());

        IBase d2i = d2;
        Console.WriteLine(d2i.Test());
    }
}

而输出是:
引用
Base 1
Base 3
Derived 2
Derived 1
Derived 3

也就是说同一个虚方法从不同的基类调用时很可能会出现行为的差异。这与一般对虚方法的认识有相当的出入,很不直观。虽说C++也是这样,但C#也没必要全盘照搬C++的行为嘛。
VB.NET在这方面的处理就不一样——它将可选参数的默认值考虑为signature的一部分,如果派生类在覆盖基类的虚方法时对同一个可选参数提供了不同的默认值,编译器会报错。这样就阻止了发生上述问题的可能性。
那为什么C# 4.0不学VB.NET呢?C# 4.0小组的成员告诉我是因为 向后兼容性。因为当前在C#里用户可以使用OptionalAttribute和DefaultParameterValueAttribute来修饰方法的参数,而C# 4.0也是使用这两个属性来修饰可选参数的。如果在C# 4.0里用户写出这样的方法:
void Foo([DefaultParameterValue(10), Optional]int x, int y = 10)

则x的默认值不会被编译器检查(因为老的C#编译器是不检查的),而y的默认值会被编译器检查。既然无法保证C# 4.0里所有的可选参数都能够被检查,那就只好整体都不检查,从而带来上述的问题了。
据说开发小组内部为这个问题争论过很多次,但最终还是决定采用这样的设计;全是让向后兼容性害的……

诶。C#和.NET Framework显然都在老化。Anders也说每次给C#添加新特性都意味着带来了老化,所以要非常小心。即便他和他的设计组很小心也还是带来了不可忽视的老化。而Microsoft一直有过不久就发布新平台来代替旧平台的传统,不知道.NET Framework能撑多久呢?

也有明明老化得很严重却还一直撑着的例子。例如说Win32 API。那里面许多设计都带着早期Windows的影子,有些API早该去掉了,却还一直存在。
又例如x86指令集。这算不上API,应该算是ABI(Application Binary Interface)的一部分吧。为了保持向后兼容性,新的x86兼容芯片还是得支持8086的一些过时依旧的运行模式。但也正是因为一直向后兼容,x86平台一直保持着强大的生命力。这个例子又说明了什么呢?

======================================================================

另外也想记点关于编程语言与API的关系。许多人认为核心语言应该非常小,非常简洁但足够强大,保持设计的稳定,让上面的库去承担语言发展的变化部分。对研究编程语言的人来说这自然是最好不过的,像LISP那样以不变应万变就是最好的例证。(不是说LISP的语言没变化,只是最核心的部分一直都没怎么变)。
对应用程序员来说,这种思路其实没多少意义。当你需要解决问题的时候,你会发现手边的工具都有所谓的“表面区域”;编程语言和上面的API都是表面区域的一部分。从某种意义上说,所有的库都可以看成是以某种编程语言为元语言的DSL(特定领域语言)。无论是编程语言发生了变化还是库发生了变化,对应用程序员而言都意味着表面区域变了,意味着自己写的程序可能需要修改。
十年前如果有人问是学习Java语言难还是学习它上面的库难,答案或许是两者都差不多;但到现在各种框架层出不穷,学习Java语言本身显然比不上学习框架的难度。而那些框架的API发生的变化都直接牵动着程序员的筋。不是框架不好,而是问题领域本身变复杂了。
语言和库都会遇到向后兼容性和老化。即便把许多常用的语法结构用库来实现,也改变不了它们的变化会影响到表面区域的问题。

你可能感兴趣的:(C++,c,C#,vb,VB.NET)