Visual Studio 2015高级编程(第6版)
上QQ阅读APP看书,第一时间看更新

16.1 使用正确的语言

我们需要成为灵活而多变的程序员,而编程领域需要优雅、高效和持久。选择一种语言和平台并拼命地练习以满足解决问题的需要,这种方式已经一去不复返了。有时不同的钉子需要不同的锤子。

在.NET平台上有上百种语言,是什么使它们互不相同?坦白地说,大多数语言的演变都很小,并且不适合于企业环境。但是,很容易把这些语言划分到编程范式中。

给编程语言分类有各种方式,但这里采用一种广义的方式,把语言分为4种类别:命令式、声明式、动态和函数式。下面简要讨论一下这些类别及属于这些类别的语言。

16.1.1 命令式语言

传统的命令式语言描述了执行的方式,而不是执行的内容。命令式语言从一开始就设计为提升机器码的抽象级别。据说Grace Hopper发明第一个编译器——A-0系统时,她的机器码编程同事抱怨这会使他们失业了。

命令式语言的语句主要是操作程序的状态。面向对象的语言就是经典的状态操作器,因为它们一直在创建和改变对象。C和C++语言属于命令式语言,VB和C#也是。

这些语言都非常擅长利用类型系统和对象描述现实世界中的情形。它们都非常严格——即编译器要进行许多安全检查。安全检查(或类型合理性)表示,不能轻易地把Cow类型变成Sheep类型,所以如果在方法的签名中声明需要一个Cow类型,编译器(和运行时)就要保证不会给方法传送Sheep类型。这些语言通常有奇异的重用机制——用多态性原则编写的代码很容易抽象出来,这样其他地方的代码,从同一个模块到完全不同的项目,就可以利用已编写好的代码。这些语言还非常流行,所以如果需要一个团队来解决问题,显然这些语言就是很好的选择。

16.1.2 声明式语言

声明式语言描述了执行的内容,而不是执行方式(这与命令式语言相反,命令式语言描述了如何通过程序语句操作状态)。为人所熟知的声明式语言是HTML,它描述了页面的布局:需要的字体、文本和修饰,以及在哪里显示图像。另一个经典的声明式语言是SQL,它描述了要从关系数据库中提取的内容。最近的一个声明式语言例子是XAML(eXtensible Application Markup Language),它引出了一大串基于XML的声明式语言。

声明式语言非常擅长描述和转换数据。多年来,我们从命令式语言中调用它们来检索和操作数据。

16.1.3 动态语言

动态语言包括具有“动态”特性的所有语言,如后期绑定和调用、REPL(Read Eval Print Loops)、鸭子类型化(不严格的类型化,即如果对象看起来像一只鸭子,行走起来也像一只鸭子,它就一定是一只鸭子)等。

动态语言一般会尽可能把编译的操作推迟到运行时执行。一般的C#方法调用Console.WriteLine()会进行静态检查,并在编译时链接,而动态语言会把这些操作都推迟到运行时。动态语言会在执行程序时查找Console类型上的WriteLine()方法,如果找到,就在运行时调用它。如果没有找到该方法或类型,动态语言就会让程序员关联一个失败方法,使程序员可以捕获这些失败,以编程方式尝试其他操作。

动态语言的其他特性包括在运行时扩展对象、类和接口(表示随时修改类型系统);动态的作用域(例如,在全局范围内定义的变量可以在私有方法或嵌套的方法中访问)等。

像这样的方法编译有一些有趣的副作用。如果类型不需要事先完全定义(因为类型系统非常灵活),就可以编写使用严格接口(例如,COM或其他.NET程序集)的代码,使这些代码在遇到该接口的失败或版本问题时有很大的灵活性。在C#中,如果在外部程序集中使用的接口变化了,一般需要重新编译(并修改内部的代码),才能再次启动和运行代码。在动态语言中,可以关联该语言的“方法缺失”机制,在某个接口变化时,只要在该接口上进行一些“反映性”的查找,就可以确定能否调用其他内容。这意味着可以编写易于粘合的代码,把不具备版本独立性的接口粘合起来。

动态语言非常适于快速建立原型。不必事先定义类型(在C#中必须先定义类型),就可以把注意力集中在解决问题的代码上,而不是集中在实现过程中的类型约束。REPL允许逐行编写原型代码,程序中的变化会立即反映出来,而不需要把时间浪费在编译-运行-调试循环上。

在.NET平台的动态语言中,Microsoft发布了IronPython(www.codeplex.com/IronPython),这是.NET Framework的Python实现。Python语言是动态语言的一个经典例子,广泛应用于科学计算、系统管理和一般编程领域。如果对Python没有什么兴趣,还可以下载并试用IronRuby(www.ironruby.net/),它是.NET Framework的Ruby语言实现。Ruby是Web领域非常流行的一种动态语言,虽然它仍相对年轻,但有非常大的用户群。

16.1.4 函数式语言

函数式语言把计算看成数学函数,它们努力避免状态操作,而主要考虑函数的结果,以此作为解决问题的基础。如果读者以前做过微积分,那么就会很熟悉函数式编程的理论。

因为函数式编程语言一般不操作状态,所以程序中生成的副作用就小很多。这意味着这类语言一直在执行并行算法。高度并行系统的圣杯是避免重叠“无意中”的状态操作。死锁、竞态条件以及被破坏的不变量都是没有同步状态操作代码的经典问题。通过线程、共享内存以及锁来并行编程和同步都是非常困难的,所以为什么不一起避免?因为函数式编程语言鼓励程序员编写无状态的算法,这样编译器就可以推断出代码的自动并行性。这意味着可以利用多核处理器的强大能力,而没有管理线程、锁和共享内存的巨大负担。

函数式程序是很简洁的,与命令式语言相比,函数式语言通常需要较少的代码就可以解决问题。代码较少一般意味着bug较少,要测试的区域也较少。

16.1.5 这些类别的含义

按照设计,这些类别是广义的:语言可能包含一个或多个类别共有的特性。类别应用作把已有的语言特性与它们擅长解决的问题关联起来的一种方式。

像C#和VB.NET这样的语言现在是利用了它们的动态性和函数性。LINQ(Language Integrated Query)是一个借用范式的好例子。考虑下面的C# 3.0 LINQ查询:

          var query =   from c in customers
                        where c.CompanyName == "Microsoft"
                        select new { c.ID, c.CompanyName };

这里有几个借用的特性。关键字var表示推断出指定查询的类型,它很像一种动态语言。而查询本身from c in . . .看起来又像是声明式语言SQL,select new { c.ID . . .创建了一个新的匿名类型,是相当动态的。这些语句的代码生成结果很有趣:它们并没有编译为经典的IL(Intermediate Language,中间语言),而是编译到所谓的表达式树中,再在运行时解释——又是动态语言的特性。

事实是,这些类别对于确定使用什么工具来解决问题并没有太大的作用。在语言中把每个类别的特性集混合起来是目前的一个趋势,这对程序员有好处,程序员喜欢使用的语言常会从每个类别中选择最佳特性。当前的趋势是应用程序开发人员使用命令式/动态语言,而函数式语言擅长解决特定领域的问题。

.NET程序员会得到更多的特性。通过CLS(Common Language Specification,通用语言规范)可以无缝地进行语言的交互操作,因此可以使用自己喜欢的命令式语言解决大多数要解决的问题,再使用函数式语言进行数据操作,或者利用某些核心数学来解决问题。