计算机扫盲贴|第五章_编程与编程语言 |
上一章(
)我们讨论了算法。算法是忽略具体实例而对过程进行的一种抽象或理想化的描述,是分毫不差且没有歧义的“菜谱”。算法通过一组确定的基本操作来表达,这些操作的含义是完全已知且明确的。算法描述了应用这些基本操作的一系列步骤,涵盖所有可能的情况,而且保证最终能够停止。
另一方面,程序则不是抽象的,它陈述了一台真正的计算机要完成某个任务所必须执行的具体步骤。程序之于算法,犹如建筑之于图纸,一个是实际存在的,一个是理想化的。 换一个角度看,程序又是以计算机能够直接处理的某种形式表达出的一个或多个算法。程序必须考虑实际的问题,比如内存不足、处理器速度不快、无效或恶意的输人、网络连接中断,以及(看不见摸不着,但却经常会导致其他问题恶化的)人性弱点。因此,如果说算法是理想化的菜谱,那程序就是让烹任机器人冒着敌人的炮火为军队准备一个月的给养所需的操作说明书。 👂🔥🍟🅱🪰 好啦,打比方只能到此为止了。本章我想给大家好好地讲一讲什么是编程,让你理解编程是怎么回事儿。当然,光凭这些要想成为专业的程序员还是远远不够的。编程不是件容易的事儿,很多细节都必须准确无误,而稍有差池就可能铸成大错。 ![]() 我们需要或者希望计算机无所不能,如此一来就需要巨大的编程工作量,而世界上却没有那么多程序员。因此,让计算机代替人处理更多的编程细节就成为这个领域永恒的话题。这自然也就引出了编程语言,即让我们能够表达完成某个任务所需计算步骤的语言。 👂🛑🧊‼🪰 同样,管理一台计算机的资源也十分困难,而现代计算机的复杂性更是让这种困难有增无减。因此,我们也需要让计算机来控制自己的操作,而由此就有了所谓的操作系统。编程和编程语言是本章的主题,而软件系统,特别是操作系统将会在下一章讨论。 小伙伴们可以不去关注本章代码示例的语法细节,但比较一下它们在表达计算步骤时的异同还是很有意思的。 5.1 汇 编 语 言 🖕🦼🍖♂🦕 1949年,EDSAC的诞生标志着第一批真正可编程的电子计算机登上了历史舞台。那时候,给这些计算机编程要把表示指令和数据的数值打在穿孔卡片或纸上,然后把这些数值加载到存储器中执行。用这种编程方式哪怕写一个非常小的程序也十分艰难。首先是不可能一次就做对,其次是发现错误以后修改、增删指令或数据也不容易。20世纪50年代,出现了能代替人处理某些琐事的程序,因而程序员可以使用有意义的单词来表示指令(如用ADD代替数字5),使用名字来指代特定的内存位置(如用Sum代替数字14)。这其中蕴含的用程序操作程序的思想,一直都是软件领域各种重大进步的核心驱动力。 这种代替人执行具体操作的程序被称为汇编器(assembler),因为它最初也用来组装(assemble)程序中由其他程序员事先写好的部分。相应的语言叫做汇编语言,而这个层次上的编程叫做汇编语言编程。第3章( )给玩具计算机写程序所用的语言就是一种汇编语言。有了汇编器,给程序添加或删除指令就方便多了,因为汇编器会负责跟踪数据和指令在存储器中的位置,程序员就不必管这些琐碎的事儿了。🧒🧢📠😷🤙 不同处理器的汇编语言只能用于为该处理器编写程序。汇编语言通常都与CPU的指令一一对应,能够以特定方式将指令编码为二进制格式,也知道信息在存储器中如何存放。这也就意味着,用某种CPU(如Mac或PC中的Intel处理器)的汇编语言编写的程序,与在不同CPU(如手机中的ARM处理器)上完成相同任务的其他汇编程序差别会很大。把为一种处理器编写的汇编程序移植到其他处理器,实际上接近于重写一遍执行相同任务的程序。 举个例子,我们的玩具计算机使用了三条指令把两个数加起来,并将结果保存到存储器中的一个位置上: 👊🚤🍖®🐢
这个过程在当前各种处理器上都是类似的。但是,在带有另一种不同指令表的CPU上,上述计算可能只需要一条指令:👦🥼💳🥰👈 ADD X,Y,Z 为了让这个玩具程序能在其他计算机上运行,程序员必须熟悉两种处理器,并小心翼翼地把它们从一个指令集转换到另一个指令集。这活儿可不好干。 5.2 高 级 语 言 20世纪50年代末到60年代初,计算机在代替程序员做更多事方面又前进了一大步,而这无疑也是人类编程史上最重要的一步。那就是独立于任何CPU体系结构的“高级”编程语言问世了。高级语言让人类得以用接近自然语言的方式来表达计算过程。🙌🚠🍚🉑🦦 用高级语言编写的代码经过一个翻译程序,可被翻译为目标处理器的汇编指令。这些汇编指令则会进一步被转换为比特,从而能够加载到存储器中并执行。这个翻译程序通常被称作编译器一同样是一个信息量有限的老术语。 上面计算X、Y两个数之和并把结果保存在Z中的例子,在大多数高级语言中都可以写成这样: 🦷🎢🍓☣🐋 Z = X + Y这行代码的意思是:从存储器中的位置X和Y中取得值,把它们加起来,然后把结果保存到存储器中的位置Z。 一个编译器可能把这个玩具程序转换成三条指令,另一个编译器则可能把它转换为一条指令。而相应的汇编器将负责把各自的汇编语言指令转换为实际的位模式,同时为X、Y、Z这几个量在存储器中留出位置。当然啦,针对这两台计算机的位模式也不一样。 🧑🎤👑🧻💀🙌 上述过程如下图所示,它显示了相同的输人表达式,通过两个不同的编译器和它们各自的汇编器,产生了不同的指令序列。在实际当中,编译器在内部可能会被分成一个“前端”和多个“后端”。“前端”负责把高级语言的程序转换为中间形式,而“后端”则负责把中间表现形式转换成不同体系结构的汇编指令。这种做法要比使用多个完全不同的编译器更简单。 相比汇编语言,高级语言拥有很多优势。首先,它让更多的人得以学会编程,而且编程效率也大大提高。用高级语言编程接近人类的思维方式,因此学习和使用的难度都降低了。人们不需要熟悉CPU指令表,就可以使用高级语言高效地编程。其次,高级语言程序独立于各种体系结构,通常无需任何修改即可在不同的体系结构上运行,只要像上图所示换个编译器编译一下就行。 👩✈️🩳🩺☠✊ 于是,程序可以只写一次,随处运行了。这也大幅降低了为多种计算机开发程序的成本。而编译环节也为发现各种拼写错误、语法错误(如少写括号或操作未定义的量)等疏漏提供了机会,在生成可执行程序之前必须纠正这些错误。这些错误在汇编语言程序中很难发现,因为必须假设汇编指令的任何序列都合法。(当然,语法正确的程序仍有可能充斥着各种编译器检测不出来的语义错误。)高级语言的重要意义无论怎么强调都不过分。 ![]() 接下来我想用五种最重要的高级语言来编写同样的程序,好让大家一窥它们之间的异同。每个程序完成的操作都与第3章中那个玩具程序一样,即把一系列输人值加起来,如果读到的输人值为0,则打印累计和并停止运行。它们的结构也都相同:命名程序要使用的量,把保存累计和的量初始化为0,读取数值,如果遇到了0则打印累计和。不要太过关注语法,这几个例子主要是为了让你对不同语言写的程序有个大致的印象。 🧓🧦📀🙄👏 第一批高级语言专注于特定的领域。其中一门最早的语言叫做FORTRAN,这个名字源自“Formula Translation”(公式转换)。FORTRAN由约翰·巴库斯(John Backus)在IBM领导的一个小组开发,在表达科学和工程计算方面非常成功。许多科学家和工程师学习的第一门编程语言就是FORTRAN。FORTRAN到今天仍然有很多用户。自1958年以来,FORTRAN经历了几次大的变革,但其核心没有变过。巴库斯1977年获得图灵奖,其中部分原因就是他领导开发了FORTRAN。 下面就是加总一系列数值的FORTRAN程序:
这是用FORTRAN77写的,如果用它较早的方言或者最新版本的FORTRAN2008来写,多少会有些不同。看着这段程序,恐怕你能想象出来怎么把它转换为玩具汇编语言。 🖕🚐🍚✔🦟 20世纪50年代末的第二个主要的高级语言是COBOL(Common Business Oriented Language,面向商业的通用语言),格蕾斯·霍普(Grace Hopper)对汇编语言高级替代品的研究对它产生了重大影响。霍普与霍华德·艾肯(Howard Aiken)当时使用的是哈佛Mark I和II(当时的机械计算机),后来又使用过Univac(Universal Automatic Computer,通用自动计算机)。她是认识到高级语言和编译器具有巨大潜力的先驱之一。 COBOL是专门针对商业数据处理的语言,其功能非常适合表达库存管理、开发票、做工资等方面的计算。COBOL现在也有人在用,虽然变化较大但仍然有它自己的特点。 🧠🌧🍍🆎🦜 BASIC(Beginner's All-purpose Symbolic Instruction Code,初学者通用符号指令代码)也是当时问世的一门语言,是约翰·凯梅尼(John Kemeny)和汤姆·库尔茨(Tom Kurtz)于1964年在达特茅斯开发出来的。BASIC当初的设计目标是要成为学习编程的人门语言。 它特别简单,只需要非常有限的计算资源,因此也成为了第一批个人计算机中的第一个高级语言。事实上,微软公司的创始人比尔·盖茨和保罗·艾伦发迹,也是始于为1975年的Altair微型计算机编写BASIC编译器,这个编译器是微软公司的第一个产品。今天,Microsoft Visual Basic作为BASIC的一个主要分支,仍然被微软公司积极地维护着。 在计算机价格昂贵、速度又慢而且资源有限的时期,人们都担心用高级语言写出来的程序效率太低,因为编译器生成的汇编代码远不如一个熟练的汇编程序员写得好。编译器作者付出了很大努力,希望生成的代码能够达到手写代码一样的高质量,而这为高级语言的流行奠定了基础。👳👑🔋😋✌ 今天,计算机速度提升了上百万倍,而且有了充足的内存,程序员很少需要担心指令级的效率问题,尽管编译器和编译器作者仍然很关心。 FORTRAN、COBOL和BASIC获得成功的部分原因,是它们都专注于某个特定的领域,而且有意避免大而全的定位。20世纪70年代,出现了专门为“系统编程”开发的语言。所谓系统编程,就是编写汇编器、编译器、编程工具乃至操作系统等程序员使用的工具。 🧠🗺🍌☪🐖 迄今为止,这些语言中最成功的是C,由丹尼斯·里奇(Dennis Ritchie)于1973年在贝尔实验室开发,至今仍然有着非常广泛的应用。从那时到现在,C的变化很小,今天的一段C程序与30年前的相比,几乎没有多大差别。 为了便于比较,下面就来看看同样的“加总一系列数值”的C程序:
🥷👞🧲💩🖕 20世纪80年代又出现了C++,是比雅尼·斯特劳斯特鲁普(Bjarne Stroustrup)同样在贝尔实验室开发的,定位是应对大型程序开发过程中的复杂性。C++由C发展而来,而且看起来也跟C相似。多数情况下,C程序也是有效的C++程序(上面的程序就是),但反之却绝对不行。下面这个加总一系列数值的例子是用C++写的(多种写法中的一种):
今天,我们在计算机中使用的大部分软件都是用C或C++编写的,例如我上网使用的是Firefox和Chrome(都是用C++写的)。👩💍🎷😘🤌 20世纪90年代,随着因特网和万维网的发展,更多语言被开发出来。计算机处理器的速度继续加快,内存容量继续增大,而编程是否高效、是否便捷变得比机器效率更重要,此时诞生的Java和JavaScript在这方面考虑得比较多。 20世纪90年代初,詹姆斯·高斯林(James Gosling)在Sum Microsystems公司开发了Java。Java最初的目标是开发小型嵌人式系统,例如家用电器和电子设备中的系统,因此对速度要求不高,但对灵活性的要求很高。 🥷🥼💶🙄🖕 Java的目标后来变成了在网页中运行,虽然没有成功,但它在Web服务中的应用却非常广泛:打开eBay之类的网站,虽然你的计算机在运行C++和JavaScript程序,但eBay可能正在用Java来生成网页,然后发给你的浏览器。 Java比C++简单(但复杂度有越来越接近的趋势),但比C复杂。另外,由于去掉了一些危险的特性,并且内建内存管理等避免出错的机制,因此Java也比C安全。出于这个原因,Java普遍成为编程课上要学习的第一门语言。 以下是用Java写的加总一系列数值的程序: 🖕🏫🎂♀🦉
👃⛵🍖‼🐮 这段程序比其他语言写的稍微长一点,这对Java而言不算不正常。不过,通过组合几个计算,我还可以减少两三行代码。 这也引出了程序和编程方面一个非常重要的共识:针对某个特定的任务,总会有多种写程序的方式。从这个意义上说,编程就像是文学创作。没错,风格以及恰如其分地运用语言对写作至关重要,对写程序同样至关重要,而且还是区分真正伟大的程序员与普通程序员的标志。 👨🎨👙💉🤤🖕 程序员对特定的计算任务可以有如此丰富的表达方式,也意味着不难识别从他人程序中复制的非原创代码。我每次上编程课的时候都会着重强调这个观点,但还是有学生认为改改名字或者挪挪代码,就可以掩盖剽窃的事实。很抱歉,这是行不通的。 JavaScript同样是C衍生语言大家族的一员,但它与C的差别也非常大。它是布兰登·艾奇(Brendan Eich)1995年在网景公司开发的。最初,设计JavaScript的意图是在浏览器中实现网页的动态效果,而今天,几乎每个网页里或多或少都会包含一些JavaScript代码。关于JavaScript,我们在第7章还会详细讨论。但为了便于大家比较,下面给出JavaScript版的加总一系列数值的程序:
🖐🛩🍞🅾🦠 从某些方面看,JavaScript是所有语言中最容易实验的。这门语言本身也简单。你不需要为JavaScript程序找编译器,每个浏览器都内置了一个。于是乎,计算结果可以迅速出现在你眼前。后面我们还会介绍,给这个程序添上几行代码,然后把它放到网页中,全世界的任何人就都可以使用它了。 以后的语言将何去何从?我猜想,人们将继续使用更多的计算机资源让编程变得更容易。而且我们还会继续发展那些对程序员来说更安全的语言。 💅🏠🌰🔞🐅 比如,C语言就像一柄双刃剑,用它写出的程序很容易遗留错误,而等到发现的时候却已经为时已晚(或许已经被用到了不法用途上)。使用较新的语言更容易防止或至少能检测到某些错误,但有时代价是运行速度慢或占用内存多。大多数情况下,这种取舍的方向是没错的,然而肯定还是有很多应用(比如汽车、飞机、航天器和武器的控制系统)对代码的效率和速度要求相当高,因此像C这样的高效语言仍然会有人用。 虽然所有语言在形式上都是等价的(都可以模拟图灵机),但这绝不是说它们都适用于所有的编程任务。写一个控制复杂网页的JavaScript程序,与写一个实现JavaScript编译器的C++程序仍有天壤之别。同时擅长这两种编程任务的程序员并不多见,经验丰富的专业程序员也可能熟悉或粗通十几种语言,但他们不会对多种语言都同样熟练。 现在的编程语言多达几千种甚至上万种,但真正广泛使用的恐怕连100种都到不了。为什么会这么多?前面也提到过,每种语言都代表了对效率、表达力、安全性和复杂性的取舍。许多语言显然是为了弥补之前语言的不足才被发明的,它们不仅吸取了之前语言的教训,还能利用更多的计算资源,通常也会受到设计者个人偏好的强烈影响。新的应用领域也会催生专门面向该领域的新语言。🧑🎤👔💿😆🤙 不管怎么样,编程语言都是计算机科学的一个重要而迷人的部分。正如美国语言学家本杰明·沃尔夫(Benjamin Whorf)所说:“语言塑造我们的思维方式,决定我们可以思考什么。”这个论断是否适用于自然语言还有争议,但对于我们发明的告诉计算机去做什么的人造语言来说,好像还是挺靠谱的。 5.3 软 件 开 发 现实中的编程往往是大规模的。大规模编程的方法与任何人想写一本书或承担任何大项目时一样:先搞清楚要做什么,然后从大概的规程着手,将其一级一级分解为较小的任务,再分别完成这些小任务,同时保证它们能够组合在一起。在编程中,每个小任务意味着一个人用某种编程语言可以写出来的精确的计算步骤。确保不同的程序员编写的代码能够在一起工作很有挑战性,而做不到这一点则是错误的主要来源。 👎🚠🍭🈚🐥 例如,1999年美国航空航天局发射的火星气象卫星坠毁,就是因为飞行系统软件在计算推动力时使用的是公制单位,但输人的路线校正数据使用的则是英制单位,使得该卫星太过接近火星表面所致。 前面演示不同语言的示例都没有超过10行代码(Java除外)。类似这种学过编程人门课程就可以写出来的小程序,也可能包含几十甚至几百行代码。我曾经写过的第一个“真正的”程序(所谓真正,指的是供很多人用过),大约有1000行FORTRAN代码。那是一个简单的智能文字处理器,用于排版和打印我的论文,我毕业后这个程序就由一个学生组织接管,随后又被人使用了差不多五年之久。真是忆往昔峥嵘岁月稠啊! 👩✈️🎒📏💀👃 今天,稍有价值的程序可能都会包含几千甚至几万行代码。参加我的编程实践课的学生,分为几个小组,写两三千行代码通常需要8到10周时间,包括系统设计和学习一两门新语言的时间。他们的作品通常都是一些Web服务,涉及学生间的二手书交易或者为访问某些大学数据库提供便利。 编译器或Web浏览器可能有几十万到一百万行代码。大型系统则可能有几百万甚至上千万行代码,由数百或数千人共同开发,开发时间也长达几年乃至几十年。这种规模的软件需要程序员、测试人员、文档编写人员协同工作,还要有开发计划、最终期限,层层管理,历经无穷无尽的会议一步步走下去。(据我的一位同事说,他曾参与过一个重要系统的开发,针对该系统的每一行代码都要开一次会。那个系统有几百万行代码,所以他说得可能太夸张了,但老资格的程序员可能还是会说:“这话说得不过分。”) 5.3.1 库、接 口 和 开 发 工 具 包 👳👞🖥😅🖕今天如果要盖一间房子,你不必自己伐木取材,烧土做砖,你可以去买各种预制件,比如门、窗、卫浴器具、火炉和热水器。盖房起屋仍然是一个艰巨的任务,但却是你完全能够做到的,因为你可以使用其他人的工作成果,还有各种基础设施做保障。实际上是有一个完整的产业链条,从各个方面为你提供帮助。 编程未尝不是如此。所有重要的程序几乎没有从零开始写的,有许许多多别人已经写出来的东西可以拿来就用。举个例子,如果你在为Windows或Mac写程序,那么有很多库都能提供预制的菜单、按钮、文本编辑器、图形、网络连接、数据库访问功能。 实际上,你的大部分时间要用来理解这些组件,然后再以自己的方式把它们“粘”在一块儿。当然,这些组件有很多也依赖其他更简单、更基础的库,经常要分好几层。而在最下层,就是支持所有程序运行的操作系统,它是负责管理硬件并确保一切井然有序的程序。下一章我们会讨论操作系统。👩🩴🛒😶👃 在最基本的层次上,编程语言提供了一种机制,叫做函数。这样,程序员就可以写出一段执行某个任务的代码,然后以某种形式把它包装起来,提供给别的程序员在其他程序里使用,而这些程序员不必知道那些代码具体如何完成该任务。比如,前几页中的C程序包含以下几行代码:
这里的代码“调用”(也就是使用)了两个C语言内置的函数:scanf和printf。其中,scanf用于从输人源读取数据(类似我们玩具程序中的GET),而printf打印输出(类似PUT)。 函数有函数名,接收完成任务所需的输人数据值,完成计算后把结果返回给调用它的程序。这里的语法及其他细节都是C语言特有的,可能会与其他语言不一样,但内在思想是一致的。函数使我们可以基于组件搭建程序,而这些组件则是独立创建、可以由任何程序员按需使用的。把一组相关的函数集合起来,就叫做库。例如,C有一个标准函数库,用于读写磁盘和其他地方的数据,scanf和printf就这个库里的函数。 🧓🦺🗑😂👆 函数库提供的服务是通过API(Application Programming lnterface,应用编程接口)的形式描述给程序员的。API会罗列出所有函数,说明每个函数的用途、用法、需要的输人数据,以及生成什么值。API也会描述数据结构,也就是传进来传出去的数据的组织形式,以及为请求服务必须遵守哪些条条框框和计算将返回什么结果。这种说明书必须面面俱到、严谨准确,因为基于它编写的程序最终会由一台不会说话的计算机而不是一个随和友善的人去解读。 API文档中不仅包含对语法的要求,也包括大量辅助说明,用以帮程序员更有效地使用函数库。今天的大型系统开发通常都会用到SDK(Software Development Kit,软件开发工具包),以便程序员在极其复杂的软件库里找到有用的函数。 🖕🔥🍟🈷🐤 例如,苹果公司为开发iPhone和iPad程序提供了编程环境和相应的支持工具,谷歌也为Android手机开发提供了类似的SDK,微软则为使用不同的语言、针对不同的设备编写Windows代码提供了各种开发环境。5.3.2 b u g 可惜的是,没有多少程序第一次就能正常运行。我们周围的这个世界太复杂了,而程序也反映了其复杂性。编程要求对细节极端关注,而能做到这一点的人却不多。正因为如此,任何规模的程序都会包含错误,也就是说,它们在某些情况下会做错一些事或者得出错误的答案。这些缺陷被称为bug. 🧑⚕️👑🪟😉✊ 这个词是因我们前面提到的格蕾斯·霍普而流行起来的。那还是1947年,霍普的同事在哈佛MarkII(他们当时使用的一种机械计算机)中发现了一只虫子(死了的蛾子),结果她就说她们是在给这台机器“除虫”(debug)。那只死虫子后来被保存下来,还做成了标本供后人瞻仰。如果你去华盛顿,可以在史密森尼美国历史博物馆里看到它,下面就是它的照片。 ![]() 然而,用“bug”代指错误并不是霍普的发明,这个用法可以追溯到1889年。以下是摘自《牛津英语词典》(第2版)的解释:👨🦱🎒🪜🤩👀 bug.机器、图纸等中的缺陷或错误。起源:美国1889年《蓓尔美街报》,3月11曰,1/1我听说啊,爱迪生先生连续两个晚上都在找他留声机里的“bug”——其实就是在排除故障,但听起来好像所有问题都是因为有个虫子钻进去引起的。 导致bug的原因多种多样,甚至可以写出一本书来专门论述(确实有这类书)。其中比较常见的原因包括忘记处理可能发生的情况、在测试某个条件时写错了逻辑或算术表达式、用错了公式、访问了没有分配给程序的存储器、错误地操作了某种数据、没有验证用户输人等等。 🖕🏫🍟🅱🐂 举个例子吧,下面是两个JavaScript函数,用于实现摄氏度与华氏度的相互转换:
其中一个函数有错误。你看出来了吗?一会儿我再告诉你。 🧠⛴🫖♏🦬 在实际的编程中,很大一部分工作是在编写代码的同时测试代码。很多软件公司的测试人员比程序员还要多,目的就是尽可能在把产品交给用户之前发现更多的bug。查找bug不容易,但你至少可以发现那些会经常出现的。怎么测试上面的温度转换函数呢?你肯定会想,用几个知道结果的简单测试用例试一试,比如摄氏0度和100度,结果应该是华氏32度和212度。这两个测试都没有问题。 但相反方向的测试,即从华氏温度转换为摄氏温度则没有那么顺利。函数转换的结果是32华氏度等于-14.2摄氏度,而212华氏度等于85.8摄氏度,错得离谱了。问题在于,在乘5/9之前,必须把华氏温度值减32用括号括起来。ftoc中正确的表达式应该是: return 5/9 * (f-32); 👊💈🥛✡🪰 还好,测试这个函数并不难。不过由此可以想见,要调试一个上百万行的大程序,从中找出并不那么显明的问题,将会是多大的工作量啊。 软件中的错误如果暴露到网络上,就会成为可能遭受攻击的漏洞。攻击者通常会利用这些漏洞以自己的恶意代码重写内存。正是由于bug广泛存在,所以各种重要的软件才会频繁升级,比如浏览器,它已经成为很多网络黑客关注的焦点。就拿我使用的Firefox来说吧,短短18个月就经历了19次修订,共修复近100个安全漏洞。这可不是什么特例,但也并不意味着Firefox程序员都不够格。这说明编写一个耐用的程序非常难,而坏人始终都在寻找你的弱点。 🙏🦼🆘🐥 现实中软件面临的另一个复杂性在于外界环境瞬息万变,因此程序必须不断适应新情况。新的硬件问世后,它所需要的软件可能得进行系统级的改动。新的法律法规出台,程序的逻辑可能就必须调整——众所周知,税法每次有什么变化,相关软件都要升一次级。 计算机、工具和语言过了时,就需要有新的替代品问世。数据格式过时的情况更加常见——今天的Word软件打不开20世纪90年代初写就的Word文档。自然,存储和处理这些数据的物理设备也一样在代代更替。而随着人的退休、死亡或被公司解雇,专业知识也会消逝。学生在校期间开发的系统随着他们的毕业,也面临相同的遭遇。 必须持续不断地小幅更新,这是软件开发和维护的一大问题,但是不做还不行。如果不那么做,程序就会遭遇“比特腐烂”,一段时间之后,也许就不能用了,或者想更新都更新不了了,因为重新编译无法通过,或者它所依赖的库已经变得太多了。与此同时,无论更新还是修复问题,通常都会产生新的bug,或者改变用户熟悉的行为。 💅⛪🍼🈳🐖 5.4 软 件 资 产 软件所有权引发了很多棘手的法律问题,我认为比硬件的问题还要多,但作为一个程序员,这也可能是我的偏见。与硬件相比,软件是一个比较新的领域,1950年之前,还没有软件呢。软件独立成为经济发展的一支力量,还只是近二三十年的事。 因此,相关的法律、商业惯例和社会规范等机制还来不及完善。软件是有价值的,但又是无形的。开发和维护相当规模的代码需要投人持续的艰苦劳动。与此同时,又可以没有限制地复制软件或者在全世界范围内分发它,却不发生任何成本。你可以很容易地把它改来改去,而不管怎么改,它都是看不见的。👩👑🪟😶👃 凡此种种,导致软件的所有权问题特别棘手。本节将讨论一些这方面的问题,但没有给出任何解决方案。我只希望在这里介绍足够多的技术背景,以便你能够从多个角度来看待问题。当然,这些背景都依托于美国的法律,其他国家的情形虽然也类似,但肯定会有很多差异。 5.4.1 知 识 产 权 知识产权指的是由个人经过发明或创作等创造性劳动得到的各种无形资产。有一些保护知识产权的法律适用于软件,但适用程度和范围各不相同。这些法律涉及商业秘密、版权、专利和许可。 💅🏠🍞❌🦜 商业秘密是最明显适用的。产权所有人要对财产保密,只有在签订了有法律约束力的合同(如保密协议)之后才能对他人公开。签保密协议比较简单,有时候效果也不错,但果真发生了泄密事件,帮助也不大。说到商业秘密,最典型的一个例子是可口可乐的配方一当然这不是软件行业的例子。 理论上讲,就算它的秘密配方变得尽人皆知,任何人都可以生产相同的产品,但却不能把这些产品命名为“可口可乐”或“可乐”,因为这些都是注册商标,是另一种形式的知识产权。 🖕🔥🍧🅰🐕 版权保护创造性的表达。在文学、艺术、音乐和影视领域,版权是深人人心的。版权保护创意产品不被他人抄袭一至少在理论上,保护作者在一段时期内通过自己的产品获得收益的权利。 过去,版权的保护期相当短,而今天在美国这个时间是作者生前加死后70年,其他国家就算短也短不了多少。(2003年,美国最高法院裁定作者死后70年是一个“有限的”概念。严格来讲,这么说没错,而实际上呢,跟“永久”没有太大区别。) 保护数字产品的版权是很困难的,不同国家的版权法都不太一样。复制有形的CD和DVD很容易,而且复制得到的电子副本在互联网上无论再复制、分发多少份都几乎不费吹灰之力。通过加密或DRM(Digital Rights Management,数字版权管理)等手段保护版权的尝试无一例外都失败了。有加密就有破解,即便破解不了,也可以在播放的同时重新录制(所谓“模拟方式的漏洞”)。针对盗版的法律追索对个体而言是很困难的,就算是大型组织要有效地打击盗版也并非易事。这个话题我们在第9章还会再谈。 🪐🍚™🦦 版权也适用于程序。如果我写了一个程序,我就拥有它,就像我写了一本小说一样。其他人未经我的许可都不能使用我这个有版权的程序。说起来很简单,但别忘了那句话:魔鬼在细节里。 假如你研究了我这个程序的行为,自己又写了一个功能相同的,那么该如何把握两者的相似程度才能不致侵犯版权呢?如果你只改了改代码格式,重新命名了每个变量,那还是算侵权。但除此之外,就不好说了,要解决问题恐怕只能破财去打一场官司。 👩✈️👙📠😍✋ 而如果你研究了我的程序,完全理解了它,然后名副其实地重新实现了一次,那应该没什么问题。事实上,在技术圈子里这叫“净室开发”(clean room development),也就是说,程序员完全没有接触或者不了解自己正在仿制软件的代码。虽然他们自己写的新代码与原始程序有相同的行为,但是可以证明没有抄袭。于是,法律问题就变成了一个证明,要证明净室的确是净室,没有谁因看过原始代码而受到污染。 专利为发明提供法律保护。专利与版权不同,版权只保护表达,即代码是怎么写的,而不保护代码中包含的原创思想。硬件专利很多,有轧棉机、电话、晶体管、激光,当然还有各种各样的加工方法、设备,以及对它们的改进。 最初,软件是不能申请专利的,因为它被认为是“数学”,故而不在专利法管辖范围内。自20世纪60年代以来,这种观点逐步发展,今天已变得混乱不堪。很多观察者(包括我)都悲观地认为软件及相关领域(如商业方法)的专利机制已经破产了。算法或程序是否可以取得专利没有明确的说法,非常随意。 💅🚠🍓✔🦟 反对这种专利的一种论调认为,算法纯粹是数学知识,法律有明文规定说不能取得专利(即依法不能取得)。作为一个还算有数学背景的程序员,我觉得这种说法似乎不妥一尽管算法要用到数学,但算法并不是数学。(就说快速排序算法吧,放到今天很可能已经申请专利了。)另一种观点认为,很多软件专利都是显而易见的,只不过是使用计算机来完成一个既定或明确的过程而已,这些软件不应该取得专利,因为它们缺乏原创性。我不是专业人士,当然更不是律师,可我倒十分赞同这种观点。 亚马逊的“一键购买”(1-click)专利或许能作为软件专利的典型代表。1999年9月,美国第5960411号专利被授予Amazon.com的四位发明人,包括创始人和CEO杰夫·贝索斯(Jeff Bezos)在内。 👀🛑🫑📶🐢 这项专利涵盖“通过因特网下订单完成购买的方法和系统”,载明的创新之处是允许注册用户单击一次鼠标即可下订单购买。自此以后,这个专利就成为了讨论会或法庭辩论的主题。坦白地讲,大多数程序员都会认为这个想法很明显,但法律规定的却是一项发明在发明当时应该对“具有一般专业技能的人”是“不明显的”。当时还是1997年,电子商务才刚刚萌芽。美国专利局已经拒绝了这项专利的一些主张,另外一些主张仍在持续申请中。 在此期间,该项专利已授权给其他公司,包括苹果的iTunes在线商店。亚马逊已取得强制令,禁止其他公司未经允许使用“一键购买”。 许可是批准使用某种产品的法律协议。在安装软件时,大家都知道有一个环节:“最终用户许可协议”(End User License Agreement)或EULA。一个对话框中显示着一个小窗口,里面密密麻麻全都是小字儿,在进行下一步之前必须先同意这个法律文本。多数人看到这个对话框,为了快点安装都会直接点过去,而这样一来,从原则上来说,或许从实践来讲也是如此,你就受到了协议条款的限制。 👏🚈🍇❓🐺 ![]() 如果看一看这些条款,不难发现整个协议都是单方面的。供应商不承担任何保证和责任,事实上,甚至不承诺该软件会做任何事情。看看摘自iTunes App Store最终用户许可协议中的这一段: 许可商并未作如下保证:您使用许可应用程序时不会被干扰;许可应用程序中包含的功能或由许可应用程序履行或提供的有关服务会满足您的要求;许可应用程序或有关服务的运行不会受到干扰也不会出现错误;许可应用程序或有关服务中的缺陷会得到纠正。🧓🩲🖲😈👃 如果软件伤害了你,你不能要求赔偿。要使用这个软件,必须满足一些条件,而且你同意不会对它进行反向工程或者反汇编。你不能把它带到某些国家。我的律师朋友说,只要相应的条款不是特别不合理,这种许可一般都是有效的,而且是可以强制执行的,听起来好像什么也没说。 另外还有一条规定可能会让你感到惊异,特别是如果你是在实体店或在线商店买的软件:“本软件仅许可使用,并不销售。”大多数购买行为在法律上都被称为“首次销售”,即一旦你买了一样东西,你就拥有它。如果你买了一本书,那这本书就是你的,你可以把它扔掉或者再卖给别人。 👍🎠🍒☣🐢 当然,不能复制、传播,不能侵犯作者的版权。然而,数字产品供应商几乎都是以许可的形式“销售”他们的产品,从而保留权利并限制你对“你的”副本能做什么。 2009年7月发生了一件事儿,可以说是这方面一个典型的例子。亚马逊向其Kindle电子书小伙伴们“出售”了大量书籍,而事实上这些书都是许可阅读,并非卖出去的。后来,亚马逊知道自己分发了一些未经许可的书,于是就远程删除了所有Kindle中的这些书以“取消出售”。巧合的是,被他们召回的书中竟然有一本是乔治·奥威尔的反乌托邦小说《1984》,这真是绝妙的讽刺。我敢肯定,乔治·奥威尔一定会对这个关于Kindle的故事感兴趣。 API也会引发一些法律问题,主要都集中在版权方面。假设我是一个可编程游戏系统的制造商,类似于Xbox或PlayStation的制造商。我希望人们能买我的游戏机,而如果有很多为它开发的好游戏,那它一定会更好卖。我不可能自己来写所有程序,所以就精心设计了一套API(应用编程接口),以便其他程序员可以为我的游戏机写游戏。我可能也会发布一个软件开发包,类似于微软为Xbox发布的XDK,以方便程序员开发。运气好的话,我能卖出一大批机器,赚上一大笔钱,然后就可以高高兴兴地退休了。 👁⛪🍏🆒🦄 API实际上是服务用户与服务提供者之间的一个契约。它规定了接口两端都该做些什么——不是它的实现细节,而是明确规定每个函数在程序里可以做什么。这意味着其他人也可以扮演提供者的角色,只要制造一个与我竞争的游戏机,并提供跟我一样的API即可。 做得好的话,所有游戏都可以在两个平台上运行。而如果竞争机型在某方面有优势,比如卖价低、外观设计吸引人,那我恐怕就要破产了。这对一心想致富的人来说可不是个好消息。 👂🎂🈳🦬 我有什么合法权益?我定义了API,所以我应该能够通过版权保护它,其他人只有在获得我许可的前提下才能使用它;同样,对于我发布的SDK也应该如此。这样就有足够的保障了吗?法律问题,以及其他各种类似的问题,实际上并非固定不变的。根据立场不同,我可以为任何一方辩护。 有很多真实的公司身陷真实的纷争(雇用了真实的律师,并且投人巨额的真金白银)以图解决这个问题。我偶尔也会以顾问的身份试图引导律师倾向于其中某一方,这个经历让我觉得很有意思,而且受益匪浅。本书有些内容就诞生于我向别人解释计算机领域的某些技术细节的过程中。但争吵双方的当事人一定会对此感到懊恼和愤怒,毕竟他们才是直接利害相关人。 5.4.2 标 准 👨⚕️🩰📏🙂👂 标准是对某些产品如何制造或者应该具有什么用途的准确、详细的说明。软件标准的例子涉及编程语言(即语法和语义的定义)、数据格式(如何表示信息)、算法处理(完成某个计算的特定步骤),等等。 某些标准是事实标准,比如Word软件的.doc格式。事实标准指的是没有正式的名义,但每个人都在用。“标准”这个词最好只用于正式的说明书,通常由政府或协会等中立的团体制定和维护,规定某物如何制造和运作。标准的定义是足够完整和准确的,独立的实体可以反馈意见或提供中立的实现。我们每时每刻都受益于硬件标准,尽管我们根本想象不到有多少个硬件标准。 如果我买了一台新电视,我之所以可以把它的电源插头插到我家的插座上,就是因为有标准规定插头的大小和形状以及电视和插座的电压。电视可以接收信号并显示画面,是因为广播和有线电视也有标准。而使用标准的HDMI、USB、S-Video数据线和连接器,我还可以把其他设备连接到电视上。然而,每台电视都有它自己的遥控器,每一部手机也都有各自的充电器,则是因为它们都还未实现标准化。 🤟⛄🍌™🐟 计算机领域也有各种各样的标准,字符集有ASCII和Unicode,编程语言有C和C++,算法有加密的和压缩的,还有通过网络交换信息的各种协议。有时候甚至还有相互竞争的标准,让人觉得有点浪费。(正如计算机科学家安迪·特南鲍姆(Andy Tanenbaum)所说:“多个标准的好处在于让人有多个选择。。)过去的例子有录像带的Betamax和VHS标准,而最近的例子是高清视频盘的HD-DVD和Blu-rayDisc标准。对于这两种情况,前者以一种标准最终胜出告终,后者则很可能两种标准共存,就像美国存在两种不兼容的手机技术一样。 标准很重要。有了标准,大家各自制造的东西才能集成到一起,多个供应商才能同台竞技,而专有系统则会把每个人限制死。(专有系统的所有者自然愿意把人们都限制在它的平台上。)标准也有缺点一如果标准本身质量不高或者已经过时,但所有人又都被迫使用它,那它就会阻碍进步。不过,与它的优点相比,这些缺点还是能够接受的。 👳👞🪟😷🖕 5.4.3 开 放 源 代 码 程序员编写的代码,无论使用的是汇编语言还是(更可能的)某种高级语言,都被称为源代码。而编译源代码得到的适合某种处理器执行的编码,叫做目标码。 正如已经介绍的它们之间的其他区别一样,区别源代码和目标码看起来有点迂腐,但却非常重要。源代码是程序员可以读懂的,尽管可能得费点时间和精力。因此源代码是可以仔细研究并加以改编的,它所包含的任何创新和思想也是可见的。 👩🧢✒🤪✋ 相对而言,目标码则经过了很大程度的转换,一般不太可能再恢复为类似源代码的形式,也无法从中提取出什么结构再加以改造,甚至连理解它都是不可能的。正因为如此,大多数商业软件只以目标码的形式分发,而源代码是重要的机密,因此说比喻也好,事实也罢,反正它会被锁得严严实实的。 开放源代码则是指另一种做法,即源代码可以被任何人自由阅读、研究和改进。 早期,大多数软件由公司开发,源代码是一般人看不到的,那是属于开发者所有的商业秘密。在麻省理工学院工作的理查德·斯托曼(Richard Stallman),曾经希望能够修改和加强自己使用的某些程序,但这些程序的源代码是别人私有的,自己根本看不到。 👎🗼🍍💲🦋 为此,斯托曼感到很懊恼。1983年,他发起了一个叫GNU(即“GNU's Not Unix”,gnu.org)的项目,致力于开发一些重要软件(比如操作系统和编程语言的编译器)的自由和开放版本。 他还创办了一个非营利组织,叫自由软件基金会(Free Software Foundation)。这个组织的目标是开发那些永远“自由”的软件,也就是说这些软件不是私有的、不会受到所有权的限制。为此,自由软件的实现在分发时都必须遵守一个独创的版权许可,叫做GNU通用公共许可(GNU General Public License)或简称GPL。 🤙🗼🍧®🦬 GPL的序言如是说:“大多数软件及其他实用作品的许可,目的都是剥夺你分享和修改作品的自由。相比而言,GNU通用公共许可则意在保证你分享和修改程序各个版本的自由,也就是确保该程序对所有用户来说仍然是自由软件。”GPL规定,基于该许可的软件可以被自由使用,而如果再把它分发给其他人,则必须公开源代码,并同样遵守“所有用户都可以自由使用”的许可。GPL是一种强有力的许可,一些违反其条款的公司已经被禁止使用其代码,或者公开了以许可约束的代码为基础的源代码。 GNU项目由很多公司、组织和个人支持,发布了大量软件开发工具和应用程序,这些软件全部采用GPL许可。其他类似的开源软件也采用类似的许可方式。很多时候,开源软件都为专有商业软件设立了标杆。比如,Firefox和Chrome浏览器是开源的,大多数Web服务器上运行的Apache Web服务器软件也是开源的,Android手机操作系统也是开源的。所有主要的编程语言都有开源的编译器,大多数程序员工具也都有开源版本。 Linux操作系统或许是最广为人知的开源系统了(虽然它并不属于GNU项目),它被个人和大型商业企业广泛使用,比如谷歌的全部基础设施都运行在Linux之上。如果你想得到Linux内核源代码,访问网站kemel.org即可免费下载。👩👠🩺😅🤛 下载后既可以自己使用,也可以对它进行任意修改。不过,要是你想以任何形式再次发布(比如,把它作为操作系统放到一个小工具里发布),那必须遵守相同的GPL协议开放源代码。 开放源代码是很值得研究的。把源代码送人还怎么赚钱呢?为什么程序员愿意为开源项目做贡献呢?志愿者编写的开源软件比大型专业团队协作开发的专有软件更好吗?源代码可以随便下载会不会威胁到国家安全? 👄🔥🍪🅾🐉 这些问题持续吸引着经济学家和社会学家,也有一些答案慢慢浮出了水面。例如,红帽子(RedHat)是一家在纽约证券交易所公开上市的公司,该公司2011年的年收人近10亿美元,市值超过80亿美元。他们发布的Linux源代码可以在网上免费下载,但公司通过支持、集成和其他收费服务可以获得收人。一些开源程序员本身就在那些使用并支持开源软件的企业工作,IBM是一个明显的例子,但绝非特例。这些公司通过影响开源软件的发展,通过让其他人修复bug和改进功能而获得收益。 并不是所有开源软件都能独领风骚,开源版本不如它所模仿的商业版本的情况也比比皆是。但是,对于一些核心的程序员工具和系统来说,开源软件生生不息,的确很难被比下去。 ✊🏝🍍♑🦠
帖子热度 2.7万 ℃
|
|
不知道回家还能不能继续更新......如果4G信号好的话,就可以#j343:
|