HTML5 设计原理

李松峰老师翻译,Jeremy Keith 在 Fronteers 2010 上的主题演讲

今天我想跟大家谈一谈HTML5的设计。主要分两个方面:一方面,当然了,就是HTML5。我可以站在这儿只讲HTML5,但我并不打算这样做,因为如果你想了解HTML5的话,你可以Google,可以看书,甚至可以看规范。

实际上,确实有人会谈到规范的内容。史蒂夫·福克纳(Steve Faulkner)会讲HTML5与可访问性。而保罗·艾里什(Paul Irish)则会讲HTML5提供的各种API。因此,我今天站在这里,不会光讲一讲HTML5就算完事了。

说老实话,在正式开始之前,我想先交待清楚我所说的HTML5到底是什么意思。这话听起来有点搞笑:这会子你一直在说HTML5,难道我们还不知道什么是HTML5吗?大家知道,有一个规范,它的名字叫HTML5。我所说的HTML5,指的就是这个规范。但问题是,有些人所说的HTML5,指的不仅仅是这个规范,还有别的意思。比如说,用HTML5来代指CSS3就是一种常见的叫法。我可不是这样的。我所说的HTML5,不包含CSS3,就是HTML5。

类似的术语问题以前也有过。Ajax本来是一种含义明确的技术,但过了不久,它的含义就变成了“用JavaScript来做一切好玩的东西”。这就是Ajax,对不对?今天,HTML5也面临同样的问题,它本来指的是一个特定的规范,但如今含义却成了“在Web上做一切好玩的事。”我说的不是这种HTML5,不是这种涵盖了最近刚刚出现的各种新东东的HTML5。我说的仅仅是规范本身:HTML5。
刚才已经说了,我今天想要讲的内容不多,也没有打算介绍HTML5都包含什么。今天我要讲的是它的另一方面,即HTML5的设计。换句话说,我要讲的不是规范里都包含什么,而是规范里为什么会包含它们,以及在设计这个规范的时候,设计者们是怎么看待这些东西的。

设计原理

设计原理本质上是一种信念、一种想法、一个概念,是你行动的支柱。不管你是制定规范,还是制造一种有形的物品,或者编写软件,甚至发明编程语言。你都能找到背后的一个或者多个设计原理,多人协作的任何成果都是例证。不仅仅Web开发领域是这样。纵观人类历史,像国家和社会这样大规模的构建活动背后,同样也有设计原理。

就拿美国为例吧,美国的设计原理都写在了《独立宣言》中了。

我们认为这些真理是不言而喻的,人人生而平等,造物主赋予了每个人不可剥夺的权利,包括生存、自由和追求幸福。

这里有一句口号:生存、自由和追求幸福。这是被写进宪法中的核心理念,它关系到我们所有人的一切,也就是我们构建自己社会的原则。

还有一个例子,就是卡尔·马克思(Karl Marx),他的著作在20世纪曾被奉为建设社会主义的圭臬。其基本思想大致可以归结为下面这条设计原理:

各尽所能,各取所需。

这其实就是一种经济体系背后的设计原理。
还有一个例子,比前面两个的历史更久远一些,不过大同小异:

人人为我,我为人人。

这个极为简单的设计原理,是两千年前的拿撒勒犹太人耶稣基督提出来的。而这条原则成为了后来许多宗教的核心教义。原理与实践有时候并不是同步的。

下面是小说中的一个例子。英国小说家乔治·奥威尔(George Orwell)笔下的《动物庄园》,就是在一条设计原理的基础上构建起来的虚拟社会。这条设计原理是:

四条腿的都是好人,两条腿的都是坏蛋!

《动物庄园》中有意思的是,随着社会的变迁——变得越来越坏,这条设计原理也跟着发生了改变,变成了“四条腿的都是好人,两条腿的就更好了。”最关键的是,即使是在虚构的作品里,设计原理都是存在的。

还有一套虚构的作品是以三条设计原理为基础构建起来的,那就是美国著名小说家艾萨克·阿西莫夫(Issac Asimov)的机器人经典系列。阿西莫夫发明了机器人学这个术语,并提出了机器人学三大法则,然后在这三个简单的设计原理基础上创作了一系列经典作品——大约有50本书。无论作品的情节如何变化,实际上都是从不同的角度来阐释这三大设计原理。我想,在座各位对机器人三大法则都不应该陌生。

机器人不得伤害人类,或袖手旁观人类受伤害。
机器人必须服从人类命令,除非命令违反第一法则。
机器人必须自卫,只要不违背第一和第二法则。

这些恐怕是第一次出现在小说中的针对软件的设计原理了。虽然基于这三个设计原理的软件运行在虚构的机器人的“正电子脑”中,但我想这应该是软件设计原理的事实开端。从此以后,我们才看到大量优秀软件背后的设计原理。

蒂姆·伯纳斯-李(Tim Berners-Lee),Web的发明者,在W3C的网站上发表过一份文档,其中有一个URL给出了他自己的一套设计原理。这些设计原理并不那么容易理解,不仅多,而且随着时时间推移,他还会不断补充、修改和删除。不过我还是觉得把自己认同的设计原理写出来放在某个地方真是个不错的主意。

实际上,CSS的发明人之一伯特·波斯(Bert Bos),也在W3C的网站上放着一份文档,其中讲的都是基本的设计原理,比如怎样设计并构建一种格式,无论是CSS还是其他格式。推荐大家看一看。

只要你在W3C的站点中随便找一找,就可以发现非常多的这种设计原理,包括蒂姆·伯纳斯-李个人的。当然,你还会看到他从软件工程学校里借用的一些口号:分权(decentalisation)、容忍(tolerance)、简易(simplicity)、模块化(modularity)。这些都是在他发明新格式的时候,头脑中无时无刻不在想的那些关键词。

在座各位对蒂姆·伯纳斯-李的贡献都是非常熟悉的,因为大家每天都在用。他发明了Web,与罗伯特·卡里奥(Robert Cailliau)共同发明了Web,而且在发明Web的同时,也发明了我们每天都在Web上使用的语言。当然,这门语言就是HTML:超文本标记语言。

HTML

HTML最早是从2.0版开始的。从来就没有1.0版。如果有人告诉你说,他最早是从HTML 1.0开始使用HTML的,那他绝对是在忽悠你。从前确实有一个名叫HTML Tags的文档,其中的部分标签一直用到现在,但那个文档并非官方的规范。

使用标签、尖括号、p或h1,等等,并不是蒂姆·伯纳斯-李首创的想法。当时的SGML里就有了这些概念,而且当时的CERN(Conseil Europeen pour la Recherche Nucleaire,欧洲核子研究委员会)也在使用SGML的一个特定的版本。也就是说,即便在那个时代,他也没有白手起家;这一点在HTML后来的发展过程中也体现了出来:继往开来、承前启后,而不是另立门户、从头开始。

换句话说,这篇名为HTML Tags的文档可以算作HTML的第一个版本,但它却不是一个正式的版本。第一个正式版本,HTML 2.0,也不是出自W3C之手。HTML 2.0是由IETF,因特网工程任务组(Internet Engineering Task Force)制定的。在W3C成立之前,IETF已经发布了不少标准。但从第三个版本开始往后,W3C,万维网联盟(World Wide Web Consortium)开始接手,并负责后续版本的制定工作。

20世纪九十年代HTML有过几次快速的发展。众所周知,在那个时代要想构建网站,可是一项十分复杂的工程。浏览器大战曾令人头疼不已。市场竞争的结果就是各家浏览器里都塞满了各种专有的特性,都试图在专有特性上胜人一筹。当时的混乱程度不堪回首,HTML到底还重不重要,或者它作为Web格式的前景如何,谁都说不清楚。

从1997年到1999年,HTML的版本从3.2到4.0到4.01,经历了非常快的发展。问题是到了4.01的时候,W3C的认识发生了倒退,他们说“好了,这个版本就这样了,HTML也就这样了;HTML 4.01是HTML的最后一个版本了,我们用不着HTML工作组了。”

W3C并没有停止开发这门语言,只不过他们对HTML不再感兴趣了。在HTML 4.01之后,他们提出了XHTML 1.0。虽然听起来完全不同,但XHTML 1.0与HTML 4.01其实是一样的。我的意思是说,从字面上看这两个规范的内容是一样的,词汇表是一样的,所有的元素是一样,所有的属性也都是一样的。唯一一点不同之处,就是XHTML 1.0要求使用XML语法。也就是说,所有属性都必须使用小写字母,所有元素也必须使用小写字母,所有属性值都必须加引号,你还得记着使用结束标签,记着对img和br要使用自结束标签。

从规范本身的内容来看,实际上是相同的,没有什么不同。不同之处就是编码风格,因为对浏览器来说,读取符合HTML 4.01、HTML 3.2,或者XHTML 1.0规范的网页都没有问题,对浏览器来说这些网页都是一样的,都会生成相同的DOM树。只不过人们会比较喜欢XHTML 1.0,因为不少人认同它比较严格的编码风格。

到了2000年,Web标准项目(Web Standards Project)的活动开展得如火如荼,开发人员对浏览器里包含的那些乱七八糟的专有特性已经忍无可忍了。大家都很生气,就骂那些浏览器厂商“遵守个规范就他妈的真有那么难吗?”当时CSS有了长足的发展,而且与XHTML 1.0结合得也很紧密,CSS加XHTML 1.0基本上就可以算是“最佳实践”了。虽然在我看来HTML 4.01与XHTML 1.0没有本质上的不同,但大家都接受了。专业的开发人员能做到元素全部小写,属性全部小写,属性值也全部加引号:由于专业人员起到了模范带头作用,越来越多的人也都开始支持这种语法。

我就是一个例子!过去的10年,我一直都使用XHTML 1.0文档类型,原因是这样一来验证器就能给我帮上很大的忙,对不对?只要我写的是XHTML 1.0,然后用验证器测试,它就能告诉我是不是忘了给属性值加引号,是不是没有结束某个标签,等等等等。而如果我写的是HTML 4.01,同样的问题就变成了有效的了,验证器就不一定会提醒我了。

这就是我一直使用XHTML 1.0的原因。我估计很多人都……使用XHTML 1.0的朋友,请把手举起来。好的。HTML 4.01呢?人少多了。一直没有举手的呢,大声点,你们用什么?HTML5,也很好!更早的呢,还有人使用更早的文档类型吗?没有了?

10年来我一直使用XHTML 1.0,就是因为验证器能够真正帮到我。有人用XHTML 1.1吗?你知道有人用吗?请举手,别放下。有人把网页标记为XML文档吗?有吗?那你们使用的就不是XHTML 1.1。

这就是个大问题。XHTML 1.0之后是XHTML 1.1,只是小数点后面的数字加了一个1,而且从词汇表的角度看,规范本身没有什么新东西,元素也都相同,属性也都相同。但对XHTML 1.1来说,唯一的变化是你必须把自己的文档标记为XML文档。在使用XHTML 1.0的时候,还可以把文档标记为HTML,而我们也正是这样做的,否则把文档标记为XML没准真会把人逼疯的。

为什么这么说呢?首先,把文档标记为XML后,Internet Explorer不能处理。当然,IE9是可以处理了。恐怕有人会讲“真是太可爱了”,他们到现在居然都没有忘了这件事。这艘船终于靠岸了!不过那时候,作为全球领先的浏览器,IE无法处理接收到的XML文档类型的文档,而规范又要求你以XML文档类型来发送文档,这不把人逼疯才怪呢。

所以说XHTML 1.1有点脱离现实,而你不想把文档以XML格式发送给那些能够理解XML的浏览器,则是因为XML的错误处理模型。XML的语法,无论是属性小写,元素小写,还是始终要给属性值加引号,这些都没有问题,都很好,事实上我也喜欢这样做,但XML的错误处理模型却是这样的:解析器如果遇到错误,停止解析。规范里就是这么写的。如果你把XHTML 1.1标记为XML文档类型,假设你用Firefox打开这个文档,而文档中有一个和号(&)没有正确编码,就算整个页面中就这一处错误,你看到的也将是黄屏,浏览器死掉了。Firefox会说:“没戏了,页面中有一个错误,你看不到这个网页了。”根据XML规范,这样处理是正确的,对Firefox而言,遇到错误就停止解析,并且不呈现其他任何内容是严格按照XML规范做的。因为它不是HTML,HTML根本就没有错误处理模型,但根据XML规范,这样做没错。

这就是为什么你不会把文档标记为XML的另一个原因。接下来,新的版本是XHTML 2,大家注意后面没有日期,因为这个规范并没有完成。

现在就说说XHTML 2,我很愿意把问题说清楚,XHTML 2实际上真是一个非常非常好的规范,确实非常好……从理论的角度来说。我的意思是说,制定这个规范的人都是非常非常有头脑的。直说吧,领导制定这个规范的家伙是斯蒂芬·彭伯顿(Stephen Pemberton),他应该是本地人,是一个聪明过人的家伙。规范本身也很了不起,如果所有人都同意使用的话,也一定是一个非常好的格式。只不过,还不够实际。

首先,XHTML 2仍然使用XML错误处理模型,你必须保证以XML文档类型发送文档;这一点不言自明:没人愿意这样做。其次,XHTML 2有意不再向后兼容已有的HTML的各个版本。他们甚至曾经讨论过废除img元素,这对每天都在做Web开发的人来说确实有点疯了的味道。但我们知道,他们之所以这样做,理论上确实有充足的理由——使用object元素可能会更好。

因此,无论XHTML 2在理论上是多么完美的一种格式,但却从未有机会付诸实践。而之所以难以将其付诸实践,就是因为像你我这样的开发人员永远不会支持它,它不向后兼容。同样,浏览器厂商也不会,浏览器厂商必须要保证向后兼容。

为什么XHTML 1.1没有像XML那样得到真正广泛地应用,为什么XHTML 2从未落到实处?因为它违反了一条设计原理,这条设计原理就是著名的伯斯塔尔法则(Postel’s Law)。大家都知道:

发送时要保守;接收时要开放。

没错,接收的时候要开放,而这也正是Web得以构建的基础。开发浏览器的人必须敞开胸怀,接收所有发送给浏览器的东西,因为它们过去一直都在接收那些不够标准的东西,对不对?Web上的很多文档都不规范,但那正是Web发展的动力。从某种角度讲,Web走的正是一条混沌发展之路,虽然混沌,但却非常美丽诱人。在Web上,格式不规范的文档随处可见,但那又怎样呢?如果所有人都能够写出精准的XML,所有文档的格式都十分正确,那当然好了。可是,那不现实。现实是伯斯塔尔法则。

作为专业人士,在发送文档的时候,我们会尽量保守一些,尽量采用最佳实践,尽量确保文档格式良好。但从浏览器的角度说,它们必须以开放的姿态去接收任何文档。

有人可能会说XML有错误处理模型,XHTML 1.1和XHTML 2都使用该模型,但那个错误处理模型太苛刻了。它绝对不符合接收时开放这个法则,遇到一个错误就停止解析怎么能叫开放呢?我们只能说它与健壮性法则(也就是伯斯塔尔法则)是对立的。

HTML5

之后,就到了HTML5,但HTML5并不是由W3C直接制定的。故事的经过是这样的,到20世纪末的时候,还没有HTML工作组,W3C内部的一些人就开始琢磨了,“HTML也许还可以更长寿一点,只要我们对它稍加扩展就行了。只要把我们放在XHTML上的时间和精力拿出一部分来,就可以提升一下HTML中的表单,可以让HTML更接近编程语言,就可以让它更上一层楼。”

于是,在2004年W3C成员内部的一次研讨会上,当时Opera公司的代表伊恩·希克森(Ian Hickson)提出了一个扩展和改进HTML的建议。他建议新任务组可以跟XHTML 2并行,但是在已有HTML的基础上开展工作,目标是对HTML进行扩展。W3C投票表决的结果是——“反对”,因为HTML已经死了,XHTML 2才是未来的方向。然后,Opera、Apple等浏览器厂商,以及其他一些成员说:“那好吧,不指望他们了,我们自已一样可以做这件事,我们脱离W3C。”他们成立了Web Hypertext Applications Technology Working Group(Web超文本应用技术工作组,WHATWG)——可巧的是,他们自称工作组,而不是特别小组(task force),这就为HTML5将来的命运埋下了伏笔。

WHATWG决定完全脱离W3C,在HTML的基础上开展工作,向其中添加一些新东西。这个工作组的成员里有浏览器厂商,因此他们不仅可以说加就加,而且还能够一一实现。结果,大家不断提出一些好点子,并且逐一做到了浏览器中。

WHATWG的工作效率很高,不久就初见成效。在此期间,W3C的XHTML 2没有什么实质性的进展。特别是,如果从实现的角度来说,用原地踏步形容似乎也不为过。

结果,一件有意思的事情发生了。那是在2006年,蒂姆·伯纳斯-李写了一篇博客,说:“你们知道吗?我们错了。我们错在企图一夜之间就让Web跨入XML时代,我们的想法太不切实际了,是的,也许我们应该重新组建HTML工作组了。”善哉斯言,后来的故事情节果真就是这样发展的。W3C在2007年组建了HTML5工作组。这个工作组面临的第一个问题,毫无疑问就是“我们是从头开始做起呢,还是在2004年成立的那个叫WHATWG的工作组既有成果的基础上开始工作呢?”答案是显而易见的,他们当然希望从已经取得的成果着手,以之为基础展开工作。于是他们又投了一次票,同意“在WHATWG工作成果的基础上继续开展工作”。好了,这下他们要跟WHATWG并肩战斗了。

第二个问题就是如何理顺两个工作组之间的关系。W3C这个工作组的编辑应该由谁担任?是不是还让WHATWG的编辑,也就是现在Google的伊恩·希克森来兼任?于是他们又投了一次票,赞成“让伊恩·希克森担任W3C HTML5规范的编辑,同时兼任WHATWG的编辑,更有助于新工作组开展工作。”

这就是他们投票的结果,也就是我们今天看到的局面:一种格式,两个版本。WHATWG的网站上有这个规范,而W3C的站点上同样也有一份。

如果你不了解内情,很可能会产生这样的疑问:“哪个版本才是真正的规范?”当然,这两个版本内容是一样的……基本上相同。实际上,这两个版本将来还会分道扬镳。现在已经有了分道扬镳的迹象了。我的意思是说,W3C最终要制定一个具体的规范,这个规范会成为一个工作草案,定格在某个历史时刻。

而WHATWG呢,他们还在不断地迭代。即使目前我们说的HTML5,也不能完全涵盖WHATWG正在从事的工作。最准确的理解是他们正在开发一项简单的HTML或Web技术,因为这才是他们工作的核心目标。然而,同时存在两个这样的工作组,这两个工作组同时开发一个基本相同的规范,这无论如何也容易让人产生误解。误解就可能造成麻烦。

其实这两个工作组背后各自有各自的流程,因为它们的理念完全不同。在WHATWG,可以说是一种独裁的工作机制。我刚才说了,伊恩·希克森是编辑。他会听取各方意见,在所有成员各抒己见,充分陈述自己的观点之后,他批准自己认为正确的意见。

W3C则截然相反,可以说是一种民主的工作机制。所有成员都可以发表意见,而且每个人都有投票表决的权利。这个流程的关键在于投票表决。从表面上看,WHATWG的工作机制让人不好接受。岂止是不好接受,简直是历史的倒退。相信谁都会认为“运作任何项目都不能采取这种方式!”

W3C的工作机制听起来让人很舒服。至少体现了人人平等嘛。但在实践中,WHATWG的工作机制运行得非常非常好。我认为之所以会这样,主要归功于伊恩·希克森。他的的确确是一个非常称职的编辑。他在听取各方意见时,始终可以做到丝毫不带个人感情色彩。

从原理上讲,W3C的工作机制很公平,而实际上却非常容易在某些流程或环节上卡壳,造成工作停滞不前,一件事情要达成决议往往需要花费很长时间。那到底哪种工作机制最好呢?我认为,最好的工作机制是将二者结合起来。而事实也是两个规范制定主体在共同制定一份相同的规范,我想,这倒是非常有利于两种工作机制相互取长补短。

两个工作组之所以能够同心同德,主要原因是HTML5的设计思想。因为他们从一开始就确定了设计HTML5所要坚持的原则。结果,我们不仅看到了一份规范,也就是W3C站点上公布的那份文档,即HTML5语言规范,还在W3C站点上看到了另一份文档,也就是HTML设计原理。而这份文档的一位编辑今天也来到了我们大会的现场,他就是安妮·奇泰丝(Anne Van Kesteren)。如果大家对这份文档有问题,可以请教安妮。

这份文档非常好,真的非常出色。这份文档,可以说见证了W3C与WHATWG同心协力共谋发展的历程。难道你们不觉得他们像是一对欢喜冤家吗?那他们还怎么同心同德呢?这份文档忠实地记录了他们一道做了什么,他们共同拥护什么。

接下来,我想要讲的就是这份文档。因为,既然他们能就这份文档达成共识,那么我相信,HTML5必将是一个伟大的规范,而他们已经认可这就是他们的共同行动纲领。为此,你才会看到诸如兼容性、实用性、互用性之类的概念。即便W3C与WHATWG之间再有多大的分歧——确实相当多——至少他们还有这份文档中记录的共识。这一点才是至关重要的。正因为他们有了共识,才有了这份基于共识描述设计原理的文档。

避免不必要的复杂性

下面我就给大家介绍一些这份文档中记载的设计原理。第一个,非常简单:避免不必要的复杂性。好像很简单吧。我用一个例子来说明。

假设我使用HTML 4.01规范,我打开文档,输入doctype。这里有人记得HTML 4.01的doctype吗?好,没有,我猜没有。除非……我的意思是说,你是傻冒。现场恐怕真有人背过,这就是HTML 4.01的doctype:

<!DOCTYPE html PUBLIC "-//W3C/DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

我不记这个两行代码,不然还要记事本、要Google、要模板有什么用呢?

要是我使用XHTML 1.0呢,这个规范我都已经用了10年了。有谁记得住这个doctype吗?没错,它的长度跟HTML 4.01的差不太多:

<!DOCTYPE html PUBLIC "-//W3C/DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

是不是,基本上相同。它要告诉浏览器的是:这个文档是XHTML 1.0的文档。那么在HTML 5中,省掉不必要的复杂性,doctype就简化成了:

<!DOCTYPE html>

仅此而已。好了,就连我也能过目不忘了。我用不着把这几个字符记在记事本里了。我得说,在我第一次看到这个doctype的时候——我当然以为这是一个HTML文档的doctype——被它吓了一跳:“是不是还少一个数字5啊?”我心里想:“这个doctype想告诉浏览器什么呢?就说这个文档是HTML吗?难道这是有史以来唯一一个HTML版本吗,这件事我得首先搞清楚,HTML今后永远不会再有新版本了吗?”好一副唯我独尊的架式!我错了,因为这个doctype并没有这个意思。为此,必须先搞清楚为什么文档一开头就要写doctype。它不是写给浏览器看的。Doctype是写给验证器看的。也就是说,我之所以要在文档一开头写那行XHTML 1.0的doctype,是为了告诉验证器,让验证器按照该doctype来验证我的文档。

浏览器反倒无所谓了。假设我写的是HTML 3.2文档,文档开头写的是HTML 3.2的doctype。而在文档中某个地方,我使用了HTML 4.01中才出现的一个元素。浏览器会怎么处理这种情况?它会因为这个元素出现在比doctype声明的HTML版本更晚的规范中,就不解释呈现该元素吗?不会,当然不会!它照样会解释呈现该元素,别忘了伯斯塔尔法则,别忘了健壮性。浏览器在接收的时候必须要开放。因此,它不会检查任何格式类型,而验证器会,验证器才关心格式类型。这才是存在doctype的真正原因。

而按照HTML5的另一个设计原理,它必须向前向后兼容,兼容未来的HTML版本——不管是HTML6、HTML7,还是其他什么——都要与当前的HTML版本,HTML5,兼容。因此,把一个版本号放在doctype里面没有多大的意义,即使对验器证也一样。

刚才,我说doctype不是为浏览器写的,这样说大多数情况下没有问题。在有一种情况下,你使用的doctype会影响到浏览器,相信在座诸位也都知道。但在这种情况下,Doctype并非真正用得其所,而只是为了达到某种特殊的目的才使用doctype。当初微软在引入CSS的时候,走在了标准的前头,他们率先在浏览器中支持CSS,也推出了自己的盒模型——后来标准发布了,但标准中使用了不一样的盒模型。他们怎么办?他们想支持标准,但也想向后兼容自己过去推出的编码方式。他们怎么知道网页作者想使用标准,还是想使用他们过去的方式?

于是,他们想出了一个非常巧妙的主意。那就是利用doctype,利用有效的doctype来触发标准模式,而不是兼容模型(quiks mode)。这个主意非常巧妙。我们今天也都是这样在做,在我们向文档中加入doctype时,就相当于声明了“我想使用标准模式”,但这并不是发明doctype的本意。这只是为了达到特殊的目的在利用doctype。

下面我出一道有奖抢答题,听好:“一分钟后开始,如果你手快的话,第一个在文档前面写完doctype html,然后我用Internet Explorer打开你的文档,会触发它的标准模式,还是会触发它的兼容模式?”

答案是,这是在Internet Explorer中触发标准模式的最少字符数目。我认为这也说明了HTML5规范的本质:它不追求理论上的完美。HTML5所体现的不是“噢,给作者一个简短好记的doctype不好吗?”,没错,简短好记是很好,但如果这个好记的doctype无法适应现有的浏览器,还不如把它忘了更好。因此,这个平衡把握得非常好,不仅理论上看是个好主意——简短好记的doctype,而且实践中同样也是个好主意——仍然可以触发标准模式。应该说,Doctype是一个非常典型的例子。

还有一个例子,同样可以说明规范是如何省略不必要的复杂性,避免不必要的复杂性的。如果前面的文档使用的是HTML 4.01,假设我要指定文档的字符编码。理想的方式,是通过服务器在头部信息中发送字符编码,不过也可以在文档这个级别上指定:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

同样,我也不会把这行代码背下来。我还想省下自己的脑细胞去记点别的更有价值的东西呢。不过,如果我想指定文档使用UTF-8编码,只能添加这行代码。这是在HTML 4.01中需要这样做。要是你在XHTML 1.0指定同样的编码,就得多敲一下键盘,因为你还得声明meta元素位于一个开始的XML标签中。

<?xml version="1.0" encoding="UTF-8" ?>    
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

在HTML5中,你要敲的字符只有:

<meta charset="utf-8">

简短好记。我能背下来。

同样,这样写也是有效的。它不仅适用于最新版本的浏览器,只要是今天还有人在用的浏览器都同样有效。为什么?因为在我们把这些meta元素输入浏览器时,浏览器会这样解释它:“元数据(meta)点点点点点,字符集(charset)utf-8。”这就是浏览器在解释那行字符串时真正看到的内容。它必须看到这些内容,根据就是伯斯塔尔法则,对不对?

我多次提到健壮性原理,但总有人不理解。我们换一种说法,浏览器会想“好,我觉得作者是想要指定一个字符集……看,没错,utf-8。”这些都是规范里明文规定的。如今,不仅那个斜杠可以省了,而且总共只要写meta charset=”utf-8″就行了。

关于省略不必要的复杂性,或者说避免不必要的复杂性的例子还有不少。但关键是既能避免不必要的复杂性,还不会妨碍在现有浏览器中使用。比如说,在HTML5中,如果我使用link元素链接到一个样式表,我说了rel=”stylesheet”,然后再说type=”text/css”,那就是重复自己了。对浏览器而言,我就是在重复自己。浏览器用不着同时看到这两个属性。浏览器只要看到rel=”stylesheet”就够了,因为它可以猜出来你要链接的是一个CSS样式表。所以就不用再指定type属性了。你不是已经说了这是一个样式表了嘛;不用再说第二次了。当然,愿意的话,你可以再说;如果你想包含type属性,请便。

同样地,如果你使用了script元素,你说type=”text/javascript”,浏览器差不多就知道是怎么回事了。对Web开发而言,你还使用其他的脚本语言吗?如果你真想用其他脚本语言,没人会阻拦你。但我要奉劝你一句,任何浏览器都不会支持你。

愿意的话,你可以添加一个type属性。不过,也可以什么都不写,浏览器自然会假设你在使用JavaScript。避免-不必要的-复杂性。

支持已有的内容

支持已有的内容。这一点非常重要,因为很多人都认为HTML5很新,很闪亮;它应该代表着未来发展的方向,应该把Web推向一个新的发展阶段。这就是HTML5,对吗?显然,我们都会考虑让Web的未来发展得更好,但他们则必须考虑过去。别忘了W3C这个工作组中有很多人代表的是浏览器厂商,他们肯定是要考虑支持已有内容的。只要你想构建一款浏览器,就必须记住这个原则:必须支持已有的内容。

下面我们就来看一个HTML5支持已有内容的例子。

这个例子展示了编写同样内容的四种不同方式。上面是一个img元素,下面是带一个属性的段落元素。四种写法唯一的不同点就是语法。把其中任何一段代码交给浏览器,浏览器都会生成相同的DOM树,没有任何问题。从浏览器的角度看,这四种写法没有区别。因而在HTML5中,你可以随意使用下列任何语法。

<img src="foo" alt="bar" />
<p class="foo">Hello world</p>

<img src="foo" alt="bar">
<p class="foo">Hello world

<IMG SRC="foo" ALT="bar">
<P CLASS="foo">Hello world</P>

<img src=foo alt=bar><p class=foo>Hello world</p>

好了,看到这几段代码,恐怕有人会说“不对不对不对。其中只有一个是对的,另外三个——说不好。”不对,应该给属性值加引号!拜托,我们可是一直都给属性值加引号的!元素名大写对吗?这种做法10年不是就被抛弃了吗?

看到HTML5同时允许这些写法,我心里忍不住一阵阵想吐。我写了10年的XHTML 1.0,已经非常适应严格的语法了。但你必须明白,站在浏览器的角度上,这些写法实际上都是一样的。确实没有什么问题。

还有谁也感到不舒服了吗?有谁看到这些之后想“噢,这不是乱写嘛,这样做不对”?只有我这样想吗?还有别人吗?

但是,HTML5必须支持已经存在的内容,而已有的内容就是这个样子的。不是吗?根据伯斯塔尔法则,浏览器没有别的选择。

有人可能会说“这样不行。我觉得语言本身应该提供一种开关,让作者能够表明自己想做什么。”比如说,想使用某种特定的语法,像XHTML,而不是使用其他语法。我理解这些人的想法。但我不赞成在语言里设置开关。因为我们讨论的只是编码风格或者写作风格,跟哪种语法正确无关。对于像我们这样的专业人士,我认为可以使用lint工具(一种软件质量保证工具,或者说是一种更加严格的编译器。它不仅可以象普通编译器那样检查出一般的语法错误,还可以检查出那些虽然完全合乎语法要求,但很可能是潜在的、不易发现的错误),对其他技术我们不是也在使用lint工具嘛。

比如说对JavaScript使用lint工具。JavaScript同样也是比较混乱、不严谨的例子,但它非常强大,原因恰恰是它混乱、不严谨,而且有很多不同的编码方式。在JavaScript,你可以在每条语句末尾加上分号,但不是必需的,因为JavaScript会自动插入分号……是不是听起来有点不好接受?

正因为如此,才有了像JSlint这样的工具,在道格拉斯·克劳克福德(Douglas Crockford)的网站jslint.org上面。有个网页上写着“JSlint可能会伤害你的感情。”但这确实是个非常棒的工具,它可以把JavaScript代码变得完美无瑕。如果你通过JSlint运行JavaScript,它会告诉你“好,你的JavaScript代码有效,但写法不妥。你这种编码风格啊,我不喜欢。不赞成你这样写。这样写不好。”特别是对团队,对于要使用统一的编码风格的团队,JSlint是非常方便的工具。

我个人认为,不仅对团队来说,就算是你自己写代码,也要坚持一种语法风格。从浏览器解析的角度讲,不存在哪种语法比另一种更好的问题,但我认为,作为专业人士,我们必须能够自信地讲“这就是我的编码风格。”然而,我不认为语言里应该内置这种开关。你可以使用lint工具来统一编码风格。现在就来说说lint工具。大家可以登录htmllint.com,在其中运行你的HTML5文档,它会帮你检查属性值是否加了引号,元素是否小写,你还可以通过勾选复选框来设置其他检查项。

但这不意味着拒绝粗心大意的标记,做不做清理完全取决于你自己。我说过,因为浏览器必须支持已有的内容,HTML5自然也不能例外。归根结底还是伯斯塔尔法则。我们始终离不开伯斯塔尔法则。

解决现实的问题

HTML5的另一个设计原理是解决现实的问题。显而易见的是,解决各种问题的格式和规范已经比比皆是了,但是在我看来,那些格式和规范要解决的都是理论问题,而非现实问题。这条设计原理才是真正要解决今天的人们所面临的现实问题、令人头疼的问题。

下面我来举个例子。相信这个例子有不少人都遇到过。假设我使用HTML 4或XHTML 1,页面中已经有了一块内容,我想给整块内容加个链接,怎么办?问题是这块内容里包含一个标题,一个段落,也许还有一张图片。如果我想给它们全部都可以点击,必须使用3个链接元素。于是,我得先把光标放在标题(比如说h2元素)中,写一个链接标签,然后再选中所有要包含到链接里面来的文本。接着,再把光标放在段落里,写一个链接标签,然后把段落中的文本放在链接里……

<h2><a href="/path/to/resource">Headline text</a></h2>
<p><a href="/path/to/resource">Paragraph text.</a></p>

在HTML5中,我只要简单地把所有内容都包装在一个链接元素中就行了。

<a href="/path/to/resource">
    <h2>Headline text</h2>
    <p>Paragraph text.</p>
</a>

没错,链接包含的都是块级元素,但现在我可以用一个元素包含它们。这样太好了。因为我碰到过类似的情形,必须给几个块级元素加上相同的链接,所有能这样写就太好了。为此,我就非常欢迎HTML5这个新标准。

它解决了一个现实的问题。我敢说在座不少朋友都曾遇到过这个问题。

那这到底解决的是什么问题呢?浏览器不必因此重新写代码来支持这种写法。这种写法其实早就已经存在于浏览器中了,因为早就有人这样写了,当然以前这样写是不合乎规范的。所以,说HTML5解决现实的问题,其本质还是“你都这样写了很多年了吧?现在我们把标准改了,允许你这样写了。”

求真务实

在所有设计原理中,这一条恐怕是最响亮的了——求真务实。不知道大家有没有在公司里开会时听到过这种口号:“开拓进取,求真务实。”实际上,除了作为企业的口号,它还是一条非常重要的设计原理,因为求真务实对于HTML的含义是:在解决那些令人头痛的问题之前,先看看人们为应对这些问题都想出了哪些办法。集中精力去理解这些“民间的”解决方案才是当务之急。

HTML5中新的语义元素就是遵循求真务实原理的反映。新增的元素不算多,谈不上无限的扩展性,但却不失为一件好事。尽管数量屈指可数,但意义却非同一般。这些新元素涉及头部(header)、脚部(footer)、分区(section)、文章(article)……,相信大家都不会觉得陌生。我的意思是说,即便你不使用HTML5,也应该熟悉这些称呼,这些都是你曾经使用过的类名,比如class=”header”/“head”/“heading”,或class=”footer”/“foot”。当然,也可能是ID,id=”header”,id=”footer”。这些不都是我们已经司空见惯了的嘛。

好,举个例子吧,假设你今天写了下面这个文档。

<body>
    <div id="header">...</div>
    <div id="navigation">...</div>
    <div id="main">...</div>
    <div id="sidebar">...</div>
    <div id="footer">...</div>
</body>

这里有一个div使用了id=”header”,另一个div使用了id=”navigation”,……。怎么样,都轻车熟路了吧?在HTML5中,这些元素都可以换掉。说起新增的语义元素,它们价值的一方面可以这样来体现:“嘿,看啊,这样多好,用HTML5新增的元素可以把这些div都替换掉。”

<body>
    <header>...</header>
    <nav>...</nav>
    <div id="main">...</div>
    <aside>...</aside>
    <footer>...</footer>
</body>

当然了,你可以这样做。在文档级别上使用这些元素没有问题。但是,假如新增这些元素的目的仅仅是为了取代原来的div,那就真有点多此一举了。

虽然在这个文档中,我们用这些新元素来替换的是ID,但在我个人看来,将它们作为类的替代品更有价值。为什么这么说呢?因为这些元素在一个页面中不止可以使用一次,而是可以使用多次。没错,你可以为文档添加一个头部(header),再添加一个脚部(footer);但文档中的每个分区(section)照样也都可以有一个头部和一个脚部。而每个分区里还可以嵌套另一个分区,被嵌套的分区仍然可以有自己的头部和脚部,是这样吧?

这四个新元素:section、article、aside和nav,之所以说它们强大,原因在于它们代表了一种新的内容模型,一种HTML中前所未有的内容模型——给内容分区。迄今为止,我们一直都在用div来组织页面中的内容,但与其他类似的元素一样,div本身并没有语义。但section、article、aside和nav实际上是在明确地告诉你——这一块就像文档中的另一个文档一样。位于这些元素中的任何内容,都可以拥有自己的概要、标题,自己的脚部。

其中最为通用的section,可以说是与内容最相关的一个。而article则是一种特殊的section。Aside呢,是一种特殊的section。最后,Nav也是一种特殊的section。

好,即便是现在,你照样可以使用div和类来描述页面中不同的部分,就像下面这样:

<div class="item">
    <h2>...</h2>
    <div class="meta">...</div>
    <div class="content">...</div>
    <div class="links">...</div>
</div>

其中包含可能是有关内容作者的元数据,而下面会给出一些链接,差不多就这样。在HTML5中,我完全可以说这块内容就是一个文档,通过对内容分区,使用section或article或aside,我可以说“这一块完全是可以独立存在的。”因此,我当然可以使用header和footer。

<section class="item">
    <header><h1>...</h1></header>
    <footer class="meta">...</footer>
    <div class="content">...</div>
    <nav class="links">...</nav>
</section>

请注意,即便是footer,也不一定非要出现在下面,不是吗?这几个元素,header、footer、aside、nav,最重要的是它们的语义;跟位置没有关系。一想到footer这个词,我们总会不由自主地想,“噢,应该放在下面。”同样,我们把aside想象成一个侧边栏。可是,如果你看一看规范,就会发现这些元素只跟内容有关。因此,放在footer中的内容也可以是署名,文章作者之类的,它只是你使用的一个元素。这个元素并没有说“必须把我放在文档或者分区的下面。”

这里,请注意,最重要的还不是我用几个新元素替换了原来的div加类,而是我把原来的H2换成了H1——震撼吧,我看到有人发抖了。我碰到过不少职业的Web开发人员,多年来他们一直认为规范里说一个文档中只能有一个H1。还有一些自诩为万能的SEO秘诀同样说要这样。很多SEO的技巧其实是很教条的。所谓教条,意思就是不相信数据。过去,这种教条表现为“不行,页面中包含两个以上的H1,你就会死掉的。”在HTML5中,只要你建立一个新的内容块,不管用section、article、aside、nav,还是别的元素,都可以在其中使用H1,而不必担心这个块里的标题在整个页面中应该排在什么级别;H2、H3,都没有问题。

这个变化太厉害了。想一想吧,这个变化对内容管理是革命性的。因为现在,你可以把每个内容分区想象一个独立的、能够从页面中拿出来的部分。此时,根据上下文不同,这个独立部分中的H1,在整个页面中没准会扮演H2或H3的角色——取决于它在文档中出现的位置。面对这个突如其来的变化,也许有人的脑子会暂时转不过弯来。不要紧,但我可以告诉你,我认为这才是HTML5中这些新语义标记的真正价值所在。换句话说,我们现在有了独立的元素了,这些元素中的标题级别可以重新定义。

我的文档中可能会包含一个分区,这个分区中可能会嵌套另一个分区,或者一篇文章,然后文章再嵌套分区,分区再嵌套文章、嵌套分区,文章再嵌套文章。而且每个分区和文章都可以拥有自己的H1到H6。从这个意义上讲,H元素真可谓“子子孙孙,无穷匮也”了。但是,在你在编写内容或者内容管理系统的时候,它们又都是独立的,完全独立的内容块。这才是真正的价值所在。

实际上,这个点子并不HTML5工作组拍脑门想出来的,也不是W3C最近才提出来的。下面这几句话摘自蒂姆·伯纳斯-李1991年的一封邮件,邮件是发给丹·康纳利(Dan Connolly)的。他在邮件中解释了对HTML的理解,他说:“你知道……知道我的想法,我认为H1、H2这样单调地排下去不好,我希望它成为一种可以嵌套的元素,或者说一个通用的H元素,我们可以在其中嵌套不同的层次。”但后来,我们没有看到通用的H元素,而是一直在使用H1和H2——那是因为我们一直在支持已有的内容。20年后的今天,这个理想终于实现了。

平稳退化

下一条原理大家应该都很熟悉了,那就是平稳退化。毕竟,我们已经遵守这条规则好多年了。渐进增强的另一面就是平稳退化。

有关HTML5遵循这条原理的例子,就是使用type属性增强表单。下面列出了可以为type属性指定的新值,有number、search、range,等等。

    input type="number"
    input type="search"
    input type="range"
    input type="email"
    input type="date"
    input type="url"

最关键的问题在于浏览器在看到这些新type值时会如何处理。现有的浏览器,不是将来的浏览器,现有的浏览器是无法理解这些新type值的。但在它们看到自己不理解的type值时,会将type的值解释为text。

无论你写的是input type=”foo”还是input type=”bar”,现有的任何浏览器都会说:“嗯,也许作者的意思是text。”因而,你从现在开始就可以使用这些新值,而且你也可以放心,那些不理解它们的浏览器会把新值看成type=”text”,而这真是一个浏览器实践平稳退化原理的好例子。

比如说,你现在输入了type=”number”。假设你需要一个输入数值的文本框。那么你可以把这个input的type属性设置为number,然后理解它的浏览器就会呈现一个可爱的小控件,像带小箭头图标的微调控件之类的。对吧?而在不理解它的浏览器中,你会看到一个文本框,一个你再熟悉不过的文本框。既然如此,为什么不能说输入type=”number”就会得到一个带小箭头图标的微调控件呢?

当然,你还可以设置最小和最大值属性,它们同样可以平稳退化。这是问题的关键。

再看input type=”search”。你也可以考虑一下这种输入框,因为这种输入框在Safari中会被呈现为一个系统级的搜索控件,右边还有一个点击即可清除搜索关键词的X。而在其他浏览器中,你得到的则是一个文本框,就像你写的是input type=”text”一样,也就是你已经非常熟悉的文本框。那为什么还不使用input type=”search”呢?它不会有什么副作用,没有,对不对?

HTML5还为输入元素增加了新的属性,比如placeholder(占位符)。有人不知道这个属性的用处吗,没有吧?没错,就是用于在文本框中预先放一些文本。不对,不是标签(label)——占位符和标签完全不是一回事。占位符就是文本框可以接受的示例内容,一般颜色是灰色的。只要你一点击文本框,它就消失了。如果你把已经输入的内容全部删除,然后单击了文本框外部,它又会出现。

使用JavaScript编写一些代码当然也可以实现这个功能,但HTML5只用一个placeholder属性就帮我们解决了问题。

当然,对于不支持这个属性的浏览器,你还是可以使用JavaScript来实现占位符功能。通过JavaScript来测试浏览器支不支持该属性也非常简单。如果支持,后退一步,把路让开,乐享其成即可。如果不支持,可以再让你的JavaScript来模拟这个功能。

现在,我不得不提到另一个话题了:HTML5对Flash。也许你早听说过了,或者在哪里看到了这方面的讨论。说实话,我一点也不明白。我搞不懂人们怎么会仅仅凭自己的推测来展开争论。

首先,他们所说的HTML5对Flash,并不是指的HTML5,也不是指的Flash。而是指HTML5的一个子集和Flash的一个子集。具体来说,他们指的是视频。因此,不管你在哪里听到别人说“HTML5对Flash”,那很可能说的只是HTML5视频对Flash视频。

其次,一说HTML5对Flash,就好像你必须得作出选择一样:你站在哪一边?实际上不是这样的。HTML5规范的设计能够让你做到鱼和熊掌兼得。

好,下面就来看看这个新的video元素;真是非常贴心的一个元素,而且设计又简单,又实用。一个开始的video元素,加一个结束的video元素,中间可以放后备内容。注意,是后备内容,不是保证可访问性的内容,是后备内容。下面就是针对不支持video元素的浏览器写的代码:

<video src="movie.mp4">
    <!-- 后备内容 -->
</video>

那么,在后备内容里面放些什么东西呢?好,你可以放Flash影片。这样,HTML5的视频与Flash的视频就可以协同起来了。你不用作出选择。

<video src="movie.mp4">
    <object data="movie.swf">
     <!-- 后备内容 -->
    </object>
</video>

当然,你的代码实际上并没有这么简单。因为这里我使用了H264,部分浏览器支持这种视频格式。但有的浏览器不支持。

对不起,请不要跟我谈视频格式,我一听就心烦。不是因为技术。技术倒无所谓,关键是会牵扯到一大堆专利还有律师、知识产权等等,这些都是Web的天敌,对我建网站一点好处都没有。

可你实际上要做的,仅仅就是把后备内容放在那而已,后备内容可以包含多种视频格式。如果愿意的话,可以使用source元素而非src属性来指定不同的视频格式。

<video>
    <source src="movie.mp4">
    <source src="movie.ogv">
    <object data="movie.swf">
        <a href="movie.mp4">download</a>
    </object>
</video>

上面的代码中包含了4个不同的层次。

  1. 如果浏览器支持video元素,也支持H264,没什么好说的,用第一个视频。
  2. 如果浏览器支持video元素,支持Ogg,那么用第二个视频。
  3. 如果浏览器不支持video元素,那么就要试试Flash影片了。
  4. 如果浏览器不支持video元素,也不支持Flash,我还给出了下载链接。

不错,一开始就能考虑这么周到很难得啊。有了这几个层次,已经够完善了。

总之,我是建议你各种技术要兼顾,无论是HTML5,还是Flash,一个也不能少。如果只使用video元素提供视频,难免搬起石头砸自己的脚,我个人认为。而如果只提供Flash影片,情况也好不到哪去,性质是一样的。所以还是应该两者兼顾。

为什么要兼顾这两种技术呢?假设你需要面向某些不支持Flash的手持设备——只是举个例子——提供视频,你当然希望手持设备的用户能够看到视频了,不是吗?

至于为什么要使用不同的格式,为什么Flash视频和音频如此成功,我想可以归结为另一个设计原理,即梅特卡夫定律(Metcalfe’s Law):

网络价值同网络用户数量的平方成正比。

梅特卡夫的这个定律虽然是针对电话网提出来的,但在很多领域里也是适用的。使用网络的用户越多,网络的价值也就越大。人人都上Facebook,还不是因为人人都上Facebook嘛。虽然Facebook真正的价值不在于此,但只有人人都上才会让它的变得如此有价值。

梅特卡夫定律也适用于传真机。如果只有一个人购买了传真机,当然没有什么用处。但如果其他人也陆续购买了传真机,那么他的投资会就得到回报。

当然,面对竞争性的视频格式和不同的编码方式,你感觉不到梅特卡夫定律的作用,我也很讨厌以不同的方式来编码视频,但只向浏览器发送用一种方式编码的视频是行不通的。而这也正是Flash在视频/音频领域如此成功的原因。你只要把Flash影片发送给浏览器就好了,然后安装了插件的浏览器都能正常播放。本质上讲,Flash利用了梅特卡夫定律。

最终用户优先

今天我要讲的最后一个设计原理,也是我个人最推崇的一个,但没有要展示的代码示例。这个原理更有哲学的味道,即最终用户优先。

这个设计原理本质上是一种解决冲突的机制。换句话说,当你面临一个要解决的问题时,如果W3C给出了一种解决方案,而WHATWG给出了另一种解决方案,一个人这么想,另一个人那么想……这时候,有人站出来说:“对这个问题我们这样来解决。”

一旦遇到冲突,最终用户优先,其次是作者,其次是实现者,其次标准制定者,最后才是理论上的完满。

理论上的完满,大致是指尽可能创建出最完美的格式。标准制定者,指的是工作组、W3C,等等。实现者,指的是浏览器厂商。作者,就是我们这些开发人员,对吧?看看我们在这个链条里面的位置多靠上啊!我们的地位仅次于最终用户——事情本来就该这个样子。用户是第一位的。而我们的声音在标准制定过程中也同样非常非常重要。

Hixie(即Ian Hickson, Acid2、Acid3的作者及维护者,HTML5、CSS 2.1规范的制定者)经常说,在有人建议了某个特性,而HTML5工作组为此争论不下时,如果有浏览器厂商说“我们不会支持这个特性,不会在我们的浏览器中实现这个特性”,那么这个特性就不会写进规范。因为即使是把特性写进规范,如果没有厂商实现,规范不过是一纸空文,对不对?实现者可以拒绝实现规范。

而根据最终用户优先的原理,我们在链条中的位置高于实现者,假如我们发现了规范中的某些地方有问题,我们想“这样规定我们不能同意,我们不支持实现这个特性”,那么就等于把相应的特性给否定了,规范里就得删除,因为我们的声音具有更高的权重。我觉得这样挺好!本质上是我们拥有了更大的发言权,对吧?我认为开发人员就应该拥有更多的发言权。

我觉得这应该是最重要的一条设计原理了,因为它承认了你的权利,无论是设计一种格式,还是设计软件,这条原理保证了你的发言权。而这条原理也正道出了事物运行的本质。难道还不够明显吗?用户的权利大于作者,作者的权利大于实现者,实现者的权利大于标准制定者。然而,反观其他规范,比如XHTML2,你就会发现完全相反的做法。把追求理论的完满放在第一位,而把用户——需要忍受严格错误处理带来的各种麻烦的用户——放在了链条的最底端。我并没有说这种做法就是错误的,但我认为这是一种完全不同的思维方式。

因此,我认为无论你做什么,不管是构建像HTML5这样的格式,还是构建一个网站,亦或一个内容管理系统,明确你的设计原理都至关重要。

软件,就像所有技术一样,具有天然的政治性。代码必然会反映作者的选择、偏见和期望。

下面我们讲一个例子。Drupal社区曾联系马克·博尔顿(Mark Boulton)和丽莎·雷贺特(Leisa Reichilt)设计Drupal的界面。他们计划遵循一些设计原理。为此,他们并没有纸上谈兵,而是经过了一段时间的思考和酝酿,提出指导将来工作的4个设计原理:

简化最常见的任务,让不常见的任务不至于太麻烦。
只为80%设计。
给内容创建者最大的权利。
默认设置智能化。

实际上,我在跟马克谈到这个问题时,马克说主要还是那两个,即“只为80%设计。给内容创建者最大的权利。”这就很不错了,至少它表明了立场,“我们认为内容创建者比这个项目中的任何人都重要。”在制定设计原理时,很多人花了很多时间都抓不住重点,因为他们想取悦所有人。关键在于我们不是要取悦所有人,而是要明确哪些人最重要。他们认为内容创建者是最重要的。

另一条设计原理,只为80%设计,其实是一条常见的设计原理,也是一种通用模式,即帕累托原理(Pareto principle)。

帕累托是意大利经济学家,他提出这个比例,80/20,说的是世界上20%的人口拥有80%的财富。这个比例又暗合了自然界各个领域的幂律分布现象。总之,无论你是编写软件,还是制造什么东西,都是一样的,即20%的努力可以触及80%的用例。最后20%的用例则需要付出80%甚至更多的努力。因此,有时候据此确定只为80%设计是很合理的,因为我们知道为此只要付出20%的努力即可。

再比如,微格式同样也利用了帕累托原理,只处理常见用例,而没有考虑少数情形。他们知道自己不会让所有人都满意;而他们的目标也不是让所有人都满意。他们遵循的设计原理很多,也都非常有价值,但最吸引人的莫过于下面这条了:

首先为人类设计,其次为机器设计。

同样,你我都会觉得这是一条再明显不过的道理,但现实中仍然有不少例子违反了这条原理:容易让机器理解(解析)比容易让用户理解更重要。

所以,我认为平常多看一看别人推崇的设计原理,有助于做好自己手头的工作。你可以把自己认为有道理的设计原理贴在墙上。当然,你可以维护一个URL,把自己认为有价值的设计原理分享出来,就像Mozilla基金会那样,对不对,以下是Mozilla的设计原理:

Internet作为一种公共资源,其运作效率取决于互通性(协议、数据格式、内容)、变革及全球范围内的协作。
基于透明社区的流程有助于增进协作、义务和信任。

我觉得像这样的设计原理都非常好。而有了设计原理,我认为才更有希望设计出真正有价值的产品。设计原理是Web发展背后的驱动力,也是通过HTML5反映出来的某种思维方式。我想,下面这条原理你绝对不会陌生:

大多数人的意见和运行的代码。
对不对?这句话经常在我脑际回响,它囊括了Web的真谛,触及了HTML5的灵魂。

也许我该把这条原理打印出来贴到办公室的墙上,让它时刻提醒我,这就是Web的设计原理:大多数人的意见和运行的代码。

我想,今天的演讲就到这里了。如果大家有什么想法可以在twitter上通过@adactio找到我。有时候我也会在自己的博客,adactio.com上写写有关这个主题的文章。最后,可能还要顺便给我自己做个广告,我刚出了一本书,希望大家关注。

非常感谢大家。

[全文完]

程序员段子:等我敲完这行代码,就和你离婚!

工作是高端大气上档次,工资是低调奢华接地气!

我们叫做“程序猿”,也叫“攻城狮”!

但是往往城还没攻下来,我们的头发就先掉下来!

我们最喜欢听的一句话就是

图0:等我敲完这行代码,就和你离婚!

段子一

“等我敲完这行代码,就和你离婚!”

他头也不抬的说

听完之后,她心里暖暖的

她想,这可能是最长情的承诺

(因为深知永远敲不完代码)

–2017年度十大感动故事奖

段子二

“等我敲完这行代码,就陪你去吃饭”

听完之后,她的心拔凉拔凉的

她想,这可能是最婉转的分手了

(因为深知永远敲不完代码)

–2017年度十大感动故事提名奖

段子三

人生难得一知己,别人都不懂搞程序的我在想什么~终于有一天,我在山顶上与我的蓝颜知己相遇了。他光着头,穿着僧衣,莫名的,我两心心相惜。

我对他说:我放不下一些事,放不下一些人。

他说:这个世界上没有什么是放不下的。

我说:可我偏偏放不下。

他说:依我看,无非是你存储空间不足,要学会内部虚拟化,自然放得下。

我惊呼了,急忙问道:大师,你怎么这么懂。

他叹了一口气说:当初我就是不懂得内部虚拟化,费脑过多,头发掉光光,最后才来当和尚~

图1:等我敲完这行代码,就和你离婚!

段子四

一位搞程序的刚结婚不久,跟老板出去见客户,边喝酒边要说一些是人都听不懂的abcd代码,醉倒后不省人事。被抬回家后,老婆试着用各种方法给他醒酒,都无济于事,于是打电话询问他的同事。

同事说,我在现在做的系统发一个bug通知,突然老公手机短信微信邮箱钉钉同时震了,只见男人噌的一下从床上蹦起来,精神抖擞,大喊:“又TM的哪里有BUG”老公好惨!老公好辛苦!老公加油!

——致敬这一群拥有无与伦比的耐力、超越时代的智商、和横穿社会的苦逼

图2:等我敲完这行代码,就和你离婚!

段子五

男盆友是传说中的攻城狮,bug对他来说就是因为要修改,接着就是加班,然后就是没有夜生活~夜生活~夜生活~

图3:等我敲完这行代码,就和你离婚!

段子六

程序猿的世界你不懂~男朋友出差到上海做系统演讲,他说这是他长的这么大第一次走出广东省,屁溜溜地乘着火车滚了~途经长江,就想看到长江的宏伟壮大~而在程序猿的眼中,长江是这样滴~

图4:等我敲完这行代码,就和你离婚!

段子七

最烦男友说一句话就是:电脑在手,代码我有!

坐车就好好坐车,还发了图片证明自己坐车也不忘工作

一分钟不敲代码是不是就手痒,手痒我可以抽你

(我不就是想让你多休息休息)

图5:等我敲完这行代码,就和你离婚!

段子八

在IT这个行业里,程序猿都被称为“互联网精英”,其实他们是拿着“白菜”工资的“短命鬼”,正常上班时,他们在公司加班;正常休假时,他们在家加班,吃饭更没规律,只因他们要时时刻刻盯着,防着bug的骚扰。

连亲人和他见个面都要杀到机房才能见到他,不知道的人、不了解的人还以为你早出晚归,甚至夜不归宿是因为外面有“小五”

图6:等我敲完这行代码,就和你离婚!

做这行业的没有一个会脑呆,因为每天大脑都在高速运转,看不懂英语没关系,我看懂代码就知道上面那句翻译是什么,神思维~

段子九

记者问一个大爷:大爷,您保持亮丽的秘诀是什么?

大爷说:白天敲代码,晚上撸系统,姿势不要动,眼动手动就可以。

记者:啊?大爷您是做什么工作的?

大爷:敲代码的呀。

记者:那大爷您是本身就很喜欢光头的吗?

大爷:掉光的~

图7:等我敲完这行代码,就和你离婚!

段子十

如果你身边有搞程序的朋友,请多给他一点帮助。一天差不多有十几个小时坐在电脑前,保持一个姿势动也不动。有时间多带他出去见见溜溜,约他吃饭,喝酒吃肉各种消费时你来买单吧,不要跟他提钱了。

工作压力已经很大,请理解她、包容他、打牌也故意输给他。临走再塞个万儿八千的红包也行,让他感受到人间的温暖吧!请紧密陪伴他,生活是相互扶持的!不说了,前边有人扔鸡蛋!!!我要正面迎敌~

图8:等我敲完这行代码,就和你离婚!

看完以上的段子,你是不是感同身受,哭笑不得呢,虽然有一丢丢夸张搞笑的成分在,但是IT行业是真心不容易!每一个搞程序的人都是可亲可敬的超级英雄!

Oracle Trace文件过量生成问题解决

随着Oracle技术本身的不断发展,“自动化”和“智能化”的数据库时代已经来临。无论是运维管理、开发调试,传统DBA们的工作内容都已经发生了很大变化。一些诸如内存池划分调整、归档日志管理等功能,都已经被Oracle自动或者半自动的特性所解决。对新一代DBA而言,保持不断学习的精神,接受新问题,发现属于自己的一片新天地,才是当务之急。

今天,和一个朋友解决了一个运维环境问题。笔者感觉很有意思,记录下来,供需要的朋友不时之需。

 

1、问题说明

 

今天,一个朋友从QQ上提出问题,说在测试环境的一台10gR2库,近期突然生成很多Trace Dump文件。每个文件大小在4-5M,频率每分钟若干次。几天之内占用了上百G的空间,删除无用文件之后依然产生。

Oracle Trace文件从本质上是Oracle前后台进程在特殊的情景下,例如开启跟踪模式、运行异常,自动向文件系统中输出的诊断文件。Trace文件通常以trc结尾,包含进程环境信息、运行过程和故障点输出。对Oracle官方和管理人员而言,Trace文件是诊断的重要依据。

正常运行情况下,每台数据库都会生成一些trc文件,每个文件大小在几k左右,是不会产生朋友遇到的问题的。

之后,朋友将数据库alert log和一个trace文件样本发给笔者。Trace文件大小在4M左右,明显超过正常大小。之所以还要alert log,是因为Oracle在生成trace文件时候,通常对应alert log中有相应记录。

 

2、问题分析

 

朋友的数据库环境是Oracle 10gR2,具体版本是10.2.0.3.0.。从alert log中,可以发现是一个标准的Windows数据库环境。

 

 

System parameters with non-default values:

processes                = 150

sga_target               = 612368384

control_files            = D:\ORADATA\ORACLE10\CONTROL01.CTL, D:\ORADATA\ORACLE10\CONTROL02.CTL, D:\ORADATA\ORACLE10\CONTROL03.CTL

db_block_size            = 8192

compatible               = 10.2.0.3.0

db_file_multiblock_read_count= 16

db_recovery_file_dest    = D:\oracle\product\10.2.0\flash_recovery_area

db_recovery_file_dest_size= 2147483648

undo_management          = AUTO

undo_tablespace          = UNDOTBS1

remote_login_passwordfile= EXCLUSIVE

 

 

在alert log中,我们果然发现了很多异常点。从若干天之前,就不断报错异常。

 

 

Wed Jan 21 12:49:10 2015

db_recovery_file_dest_size of 2048 MB is 0.00% used. This is a

user-specified limit on the amount of space that will be used by this

database for recovery-related files, and does not reflect the amount of

space available in the underlying filesystem or ASM diskgroup.

Wed Jan 21 12:49:11 2015

Completed: alter database open

Wed Jan 21 12:49:22 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j002_2524.trc:

 

Wed Jan 21 12:49:28 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j000_2516.trc:

 

Wed Jan 21 12:49:34 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j003_3752.trc:

 

Wed Jan 21 12:49:35 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j002_2524.trc:

ORA-12012: 自动执行作业 26 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 1674 (2)

 

 

之后,每隔约5秒钟,就生成一个trace文件,对应内容相同。

 

 

Mon Mar 23 09:30:52 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j002_4348.trc:

ORA-12012: 自动执行作业 26 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 1674 (2)

 

Mon Mar 23 09:30:55 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j001_1376.trc:

ORA-12012: 自动执行作业 4 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 1674 (2)

 

Mon Mar 23 09:30:56 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j001_1376.trc:

ORA-12012: 自动执行作业 4 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 1674 (2)

 

 

再打开trace文件,文件名称为:oracle10_j000_5820。Jxxx通常为Oracle的自动作业Job后台进程,自动按照执行的规则后台执行。其中,包括了报错功能点:

 

 

*** 2015-03-23 09:30:38.468

ksedmp: internal or fatal error

Current SQL statement for this session:

update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2,  next_date = greatest(:3, sysdate),  total=total+(sysdate-nvl(this_date,sysdate)) where job=:4

 

 

执行job$修改操作时候,发出的错误信息。

诊断的依据中,“对象239,文件1,块1674(2)”是很好的入手点。从提示信息看,似乎这个对象遇到了坏块情况。文件1是system表空间,应该是Oracle内部对象。

由于笔者不能实际操作问题库,所以找到了类似的环境进行简单检查。

 

 

SQL> select * from v$version;

 

BANNER

—————————————————————-

Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 – Prod

PL/SQL Release 10.2.0.3.0 – Production

CORE  10.2.0.3.0    Production

 

TNS for 32-bit Windows: Version 10.2.0.3.0 – Production

NLSRTL Version 10.2.0.3.0 – Production

 

 

找到object_id=239对象。

 

 

SQL> select object_name, object_type, status from dba_objects where object_id=239;

 

OBJECT_NAME          OBJECT_TYPE         STATUS

——————– ——————- ——-

I_JOB_NEXT           INDEX               VALID

 

 

报错对象是一个索引对象,对应的段结构。

 

 

SQL> select SEGMENT_TYPE, TABLESPACE_NAME, HEADER_FILE, HEADER_BLOCK, BLOCKS, EXTENTS from dba_segments where segment_name=’I_JOB_NEXT’;

 

SEGMENT_TYPE       TABLESPACE_NAME      HEADER_FILE HEADER_BLOCK     BLOCKS    EXTENTS

—————— ——————– ———– ———— ———- ———-

INDEX              SYSTEM                         1         1673          8          1

 

 

对应的索引段结构很小,只有一个extents,合计八个数据块。注意:头块编号为1673。报错块号为1674,是段头块后面第一个数据块。

熟悉Oracle段结构的朋友一定知道,索引段后第一个数据块就是索引树的根节点块。报错信息很可能是出现了索引段损坏的情况。

 

3、问题修复

 

在Oracle中,坏块是很难处理的一种故障。从官方角度,坏块的来源有两个:一个是软硬件存储之间的不兼容,另一个是Oracle内部Bug。处理坏块的手段有很多,落实到这个案例中,索引段坏块是比较好处理的一种。

最简单的处理策略是尝试rebuild索引,让数据库重新整理结构。模拟操作:

 

 

SQL> alter index I_JOB_NEXT rebuild;

Index altered

 

 

让朋友在环境上进行测试,发现执行成功,索引状态也是valid。但是问题依然存在,trace文件还会生成。

索要最新的alert log之后,发现报错信息变化。

 

 

Mon Mar 23 10:15:35 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j002_6932.trc:

ORA-00604: 递归 SQL 级别 1 出现错误

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 65946 (2)

ORA-12012: 自动执行作业 26 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 65946 (2)

 

Mon Mar 23 10:15:37 2015

Errors in file d:\oracle\product\10.2.0\admin\oracle10\bdump\oracle10_j001_7056.trc:

ORA-00604: 递归 SQL 级别 1 出现错误

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 65946 (2)

ORA-12012: 自动执行作业 4 出错

ORA-08102: 未找到索引关键字, 对象号 239, 文件 1, 块 65946 (2)

 

 

注意,报错内容发生了变化。块号变化到了system表空间另一个位置。说明:rebuild操作生效,修改了index的结构,但是错误依然存在。

笔者的一种猜想是:也许rebuild操作并不能直观反映数据表的数据情况,而报错问题是在索引数据和表段数据的不一致。尝试解决方法:drop后重新create索引对象。

 

 

SQL> drop index i_job_next;

Index dropped

 

SQL> create index i_job_next on job$ (next_date);

Index created

 

 

之后,问题解决。

 

4、结论

 

相对于各种复杂问题,今天这个问题相对比较单纯简单。但是这个问题告诉我们教训。首先,问题处理要从核心线索入手,层层深入,最后定位问题。其次,对于一些想当然的概念,如rebuild,要更加深层次理解含义,更详细的分析。

不可复制的“去IOE”

“IOE”并不当代表IBM、Oracle和EMC三家国际品牌的IT厂商,而是特指:“I”是IBM的缩写,指的是IBM小型机;“O”是Oracle的缩写,指的是Oracle数据库;“E”是EMC的缩写,指的是EMC存储设备。这里的“IOE”架构为针对传统行业企业关键应用而设计,基于向上扩展(Scale-up)技术高端设备以及围绕着它们开发的大型数据库和商业中间件。

对于绝大多数企业来说,不仅要了解自身的技术需求是否合适采用“去IOE”技术,还需要拥有庞大的技术团队,并具有自我试验的精神和决心,但这只是效仿阿里巴巴“去IOE”的必要不充分条件。

一旦企业用户效仿阿里巴巴选择分布式+自行开发开源系统,就意味着它将从此迈入孤独之旅,软件的开发将没有可以借鉴的经验,也没有战略合作伙伴。此外,貌似通过开源社区讨论对技术可控,但软件的可控性实际上要低于硬件的可控性,一旦开发核心人员发生变故,整套系统的开发成果都将有付诸东流的风险。

去IOE”到底是节省成本的命题还是成本转移的命题,也是值得企业用户推敲的。

最近,“去IOE”风声正劲。

阿里巴巴集团高调宣布今年“去IOE”成功,引发互联网行业甚至传统行业企业的热议:一、现在已经采用的IOE系统是否要效仿阿里巴巴进行替换?二、未来采用的系统是否不再优先考虑“IOE”架构?

企业用户要想获得这两大问题的个性化答案,其实还需要对这背后隐藏的若干潜在问题进行思路梳理。问题无外乎集中在如下几点:“去IOE”到底指的是什么?阿里巴巴为什么要“去IOE”?“去IOE”对于企业用户尤其是具有一定规模的企业来讲是否普遍适用?未来系统的选择是集中式还是分布式,商用系统还是开源系统?IOE系统的成本是否就高于非IOE系统,可控性是否就劣于后者?IBM、Oracle和EMC企业的产品是否代表的就是专有昂贵的集中式系统?

什么是“去IOE

当业界热议“去IOE”时,首先需要给“IOE”一个相对明确的定义。实际上,“IOE”并不当代表IBM、Oracle和EMC三家国际品牌的IT厂商,而是特指:“I”是代表IBM的缩写,指的是IBM小型机;“O”是Oracle的缩写,指的是Oracle数据库;“E”是EMC的缩写,指的是EMC存储设备。这里的“IOE”架构是针对传统行业企业关键应用而设计的,基于向上扩展(Scale-up)技术高端设备以及围绕着它们开发的大型数据库和商业中间件。

因此,如果将“去IOE”简单地理解成去掉三家国际品牌IT 厂商无疑是误读。这三家企业作为商用产品提供商,在互联网普遍推崇分布式与向外扩展(Scale-out)技术、开源软件、云服务中也一直处于活跃的态势。比如EMC的VMware是x86架构服务器云计算的基础,其公有云存储服务也开展得风生水起;开源分布式数据库MySQL实际上隶属于Oracle;IBM一直是开源软件的重要支持者与贡献者,其Power服务器也不再仅仅是拥有强大Scale-up能力的专有小型机的代名词。PowerLinux开始强调Scale-out分布式能力和对开放的系统的支持,而近期成立的OpenPOWER联盟更是开放了POWER内核IP授权,Google的加盟也使得Power未来在互联网行业的迅速推进成为可能。明年POWER8芯片的问世或将使得业界对Power服务器的变身刮目相看。

去IOE”的试验精神

阿里巴巴集团从2010年开始的“去IOE”运动耗时3年,经过1.7万名内部技术人员的努力,在今年高调宣布“去IOE”成功。据悉,除了支付宝完成了“去IE”目前依旧采用Oracle数据平台,阿里巴巴最大的现金流结算系统也完成了“去O”的工作,基本实现了“去IOE”的既定目标。

这里的一组数字值得关注,即耗时3年和1.7万名人员,阿里巴巴无疑将自身作为风险极高的“去IOE”创新试验品,下定决心才有了现在的成果。众所周知,在国外,Google、亚马逊等代表性互联网企业根本就不存在“去IOE”问题,因为它们构建系统之初从小规模起步日渐发展到超大规模,采用Scale-out的分布式系统是其“路径依赖”的结果。而“IOE”的系统架构则依据传统企业对IT的需求,基于Scale-up技术的高端设备以及围绕着它们开发的大型数据库和商业中间件。

阿里巴巴后来总结“去IOE”是“技术门槛很高、技术风险很大、水很深”的技术改革,敢冒如此风险的首要原因就是,考虑成本可控、技术可控等因素,不愿继续增加成熟商用系统以满足阿里巴巴特别是淘宝爆炸式业务增长的架构需求。由于其中的特殊性和特定性,这一过程虽然具有示范效应,但却有着太多不可复制的底层技术细节。比如互联网交易系统对数据一致性要求低于传统银行,但任何交易都存在数据复杂性与一致性的协调问题。因而虽然阿里巴巴采用分布式架构处理部分交易系统,但也需要对分布式开源数据库进行大量定制化改造。

一些具有一定技术规模的大型企业也曾经尝试“去IOE”,但在实施过程中出现技术反复,这其中甚至包括技术实力雄厚的电信运营商。因此绝大多数企业不仅要了解自身的技术需求是否合适采用“去IOE”技术,还需要拥有庞大的技术团队,并具有自我试验的精神和决心,但这只是“去IOE”的必要不充分条件。单凭这几点,企业效仿阿里巴巴将现在已经采用的“IOE”系统进行替换,就是风险极高的事。换句话说,阿里巴巴的“去IOE”运动是不可复制的。为此,多数企业对阿里巴巴“去IOE”运动思考落脚点开始集中在,未来将要采用的新系统是否不再优先考虑“IOE”系统?

去IOE”的实质

阿里巴巴为什么要“去IOE”?因为集中式部署很难适应互联网大规模应用对扩展性的要求,与其说是“去IOE”,更不如说其实质是分布式架构+开源系统替代了集中式架构+商用系统。

众所周知,IOE架构有效地支撑着绝大多数非互联网企业的关键业务。但大型企业自身技术的逐渐成熟,尤其是技术团队自主开发能力的增强,使得部分企业认为对大型IT厂商依赖过多,成本偏高,技术上逐渐产生依赖感,如何在未来新系统中实现技术可控与成本可控成为“去IOE”思想产生的重要原因。

分布式架构+开源系统是否就意味着技术可控值得推敲。原来的软件设计使得早期的IT系统强调单机可靠性和单机性能,但随着云计算的崛起,软件层面的可靠性、可扩展性设计降低了业务对底层服务器单机的可靠性和性能的要求。为此,IBM Power服务器也在不断变化。在拥有强大Scale-up技术的基础上,Power开始逐步淡化小型机形象,强调其在Scale-out上的分布式能力。实际上,IBM引以为傲的“Wston”系统就是由90台Power 750服务器构建的处理平台。而在最新一期HPC500强排行榜,就有16套Power系统上榜,其集群能力与x86服务器相比并无伯仲之分。

在开源系统和商用系统的博弈之中,企业需要考虑方法论问题,即需要考虑一个系统的功能性需求和非功能性需求。在企业新业务新系统的创新中容易首先考虑功能性需求,即创新的系统是否能够解决当下的问题。但当其满足需求之后,企业将很快面临非功能性需求的压力,即如何构建一套稳定的系统。有多少人员能够维护好开源系统,不断进行开源中的Bug修改,按照系统的新要求加入新的功能并不断优化。

这恰恰就是商用系统存在的价值,毕竟系统从特定条件下的“可用”到能够向其他企业推广商用,这其中的门槛很高,商用软件用户可以通过与商用系统厂商的战略合作,了解其他类似用户的做法,获取经验防患未然。因此,一旦企业用户效仿阿里巴巴选择分布式+自行开发开源系统,就意味着它将从此迈入孤独之旅,软件的开发将没有可以借鉴的经验,也没有战略合作伙伴。而且,貌似通过开源社区讨论对技术可控,但软件的可控性实际上要低于硬件的可控性,一旦开发核心人员发生变故,整套系统的开发成果都将有付诸东流的风险。

成本可控是“去IOE”的另一重要原因,但实际发生的也许只是成本的迁移。企业投资购置成熟高端设备和商用中间件,可以只关注业务的创新,而功能的实现、扩展、优化、安全的保障等都由商用系统厂商交付。如果企业采用低端分布式设备和自行开发开源软件,确实降低了初始投资,但却转移了成本。

阿里巴巴在“去IOE”中就谈到,原来只需要几十台小型机,现在却要面临数千台x86服务器,必须重新架构全新的运维体系把这种复杂性对上层进行“封装”。 如果企业此时又选择了自行开发开源软件,固然再次节省了软件投资,但实际成本又将转移到自身技术人员队伍的建设上,比如阿里巴巴就拥有1.7万人的庞大技术团队。因此,“去IOE”到底是节省成本还是成本转移,也是值得企业用户推敲的。

【编后语】

企业发展对信息技术的依赖性,信息技术手段的丰富和增强,促使企业有更多的技术方案和硬件平台选择,故去IOE化是信息化技术发展的趋势,也能提高企业信息化技术能力,也能降低信息化建设的成本,增强企业的技术研发实力和话语权,去IOE事件成为近年来行业内的热门话题。

若是企业选择去IOE的信息化建设道路,须弄清楚:去IOE概念,为什么要去IOE去IOE对企业投资成本的影响,那些业务场景适合非IOE技术架构,用什么技术和产品替代IOE平台,企业去IOE进程与力度等,换句话说企业必须弄清楚去IOE的本质和核心。

对于非互联网企业绝大多数情况下,IOE平台的时候会采购相应的维保服务,若是推动去IOE化,也会采购去IOE服务,不过比商业软硬件产品的维保费和License费用至少低40%。我们公司拥有一批互联网行业内实践过“去IOE”的技术精英,同时熟悉各行业的业务和流程,去IOE服务商-上海热璞网络科技有限公司愿为您企业的信息化建设保驾护航,企业文化:诚信是立足之本,口碑是客户认可的标志,技术、服务、信誉是核心竞争力。

 

Node.js 抓取中文网页乱码的若干问题

 

使用 iconv-lite 解决 request 乱码问题

Node.js 抓取非 utf-8 的中文网页时会出现乱码问题,比如网易的首页编码是 gb2312,抓取时会出现乱码

var request = require('request')  
var url = 'http://www.163.com'

request(url, function (err, res, body) {  
    console.log(body)
})

可以使用 iconv-lite 来解决

安装

npm install iconv-lite  

同时我们顺带把 user-agent 修改一下,以防网站屏蔽:

var originRequest = require('request')  
var iconv = require('iconv-lite')  
var headers = {  
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}

function request (url, callback) {  
  var options = {
    url: url,
    encoding: null,
    //代理服务器
    //proxy: 'http://xxx.xxx.xxx.xxx:8888',
    headers: headers
  }
  originRequest(options, callback)
}

request(url, function (err, res, body) {  
    var html = iconv.decode(body, 'gb2312')
    console.log(html)
})

乱码问题解决

使用 cheerio 解析 HTML

cheerio 可以简单粗暴的理解为服务器端 jQuery 选择器,有了它,比正则要更加直观许多

安装

npm install cheerio  
request(url, function (err, res, body) {  
    var html = iconv.decode(body, 'gb2312')
    var $ = cheerio.load(html)
    console.log($('h1').text())
    console.log($('h1').html())
})

输出如下

网易
&#x7F51;&#x6613;

那么问题来了,$('h1').html() 输出的代码是经过 Unicode 编码的,网易变成了&#x7F51;&#x6613;,给我们的字符处理带来了一些麻烦

解决 cheerio .html() 「乱码」问题

查阅文档可知,可以关闭这个转换实体编码的功能

var $ = cheerio.load(html)  

改成

var $ = cheerio.load(html, {decodeEntities: false})  

即可,完整代码如下:

var originRequest = require('request')  
var cheerio = require('cheerio')  
var iconv = require('iconv-lite')  
var headers = {  
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}

function request (url, callback) {  
  var options = {
    url: url,
    encoding: null,
    //代理服务器
    //proxy: 'http://xxx.xxx.xxx.xxx:8888',
    headers: headers
  }
  originRequest(options, callback)
}

var url = 'http://www.163.com'

request(url, function (err, res, body) {  
    var html = iconv.decode(body, 'gb2312')
    var $ = cheerio.load(html, {decodeEntities: false})
    console.log($('h1').text())
    console.log($('h1').html())
})

上谷歌的方法

下面是我经常上谷歌的几个网址:

将 Sublime Text 3 添加到右键菜单上

当你想浏览到一个文件夹/文件时,想要编辑它,很自然的会右键看看有哪些可编辑的方式。当安装sublime text之后,一般的代码文件右键都有有“使用sublime text 2/3”打开的命令,文件夹(工程目录)就没有相应的命令。当然可以自定义添加,有如下2种方法。

方法一(推荐)

把以下代码,复制到SublimeText3的安装目录,然后重命名为:sublime_addright.inf,然后右击安装就可以了。

[Version]
Signature="$Windows NT$"

[DefaultInstall]
AddReg=SublimeText3

[SublimeText3]
hkcr,"*\\shell\\SublimeText3",,,"用 SublimeText3 打开"
hkcr,"*\\shell\\SublimeText3\\command",,,"""%1%\sublime_text.exe"" ""%%1"" %%*"
hkcr,"Directory\shell\SublimeText3",,,"用 SublimeText3 打开"
hkcr,"*\\shell\\SublimeText3","Icon",0x20000,"%1%\sublime_text.exe, 0"
hkcr,"Directory\shell\SublimeText3\command",,,"""%1%\sublime_text.exe"" ""%%1"""

ps:如果发现右键中的”用 SublimeText3 打开”中的中文有乱码,就进入regedit.exe搜索”SublimeText”然后将乱码中文修改正确。

方法二

把以下代码,复制到SublimeText3的安装目录,然后重命名为:sublime_addright.reg,然后双击就可以了。

PS:需要把里边的Sublime的安装目录,替换成实际的Sublime安装目录。

Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\SublimeText3]
@="用 SublimeText3 打开"
"Icon"="D:\\Program Files\\Sublime Text 3\\sublime_text.exe,0"

[HKEY_CLASSES_ROOT\*\shell\SublimeText3\command]
@="D:\\Program Files\\Sublime Text 3\\sublime_text.exe %1"


[HKEY_CLASSES_ROOT\Directory\shell\SublimeText3]
@="用 SublimeText3 打开"
"Icon"="D:\\Program Files\\Sublime Text 3\\sublime_text.exe,0"

[HKEY_CLASSES_ROOT\Directory\shell\SublimeText3\command]
@="D:\\Program Files\\Sublime Text 3\\sublime_text.exe %1"

附一个删除右键菜单的脚本

把以下代码,复制到SublimeText3的安装目录,然后重命名为:sublime_delright.reg,然后双击就可以了。

Windows Registry Editor Version 5.00
[-HKEY_CLASSES_ROOT\*\shell\SublimeText3]
[-HKEY_CLASSES_ROOT\Directory\shell\SublimeText3]

趣味题:重男轻女的村庄

题目

在一个世世代代都重男轻女的村庄里,村长决定颁布一条法律,村子里没有生育出儿子的夫妻可以一直生育直到生出儿子为止,假设现在村子上的男女比例是1:1,这条法律颁布之后的若干年后村子的男女比例将会()

  • A 男的多
  • B 女的多
  • C 一样多
  • D 不能确定

解答

大家都知道,生男孩生女孩的概率上是一样的,意思就是说,生1000个女孩大致也要生1000个男孩,结果是1:1。所以最终结果是接近一比一。
代码模拟也是1:1的结果,而且人口数量基本不变。

var girls = 0,boys=0;

function giveBirth(){
    if(Math.random()>0.5){
        boys+=1;
    }else{
        girls+=1;
        giveBirth();
    }
}

var i=50000000;
while(i--){
  giveBirth();
}

console.log(girls);
console.log(boys);

招大四实习生得出两结论

  1. 学生实习生不能免费 要么收费 要么给钱
  2. 三流大学计算机系学生 大部分都是垃圾

根本不能想象快大四的计算机系学生连二叉树都不明白,当场写个链表 全不会,Fibonacci数列的定义 全目瞪口呆 两眼朝天翻
期望实习期薪水。。。 3k+

滚!!!
题目
木有一个全答得上来的

考题

  1. 什么是递归,迭代
  2. Fibonacci数列的定义
  3. 二叉树的基本概念
  4. 什么是哈希表
  5. 冒泡排序定义及平均时间复杂度
  6. 了解Linux么,说出三个Linux发行版?
  7. SQL语言么?数据操作指令
  8. 看课程之外计算机书籍么?
  9. 重男轻女的村庄,村长决定颁布一条法律:没有生育出儿子的夫妻可以一直生育直到生出儿子,假设现在村子的男女比例是1:1,这条法律颁布之后的若干年后村子的男女比例将会?
  10. GFW是什么,了解如何翻墙么?
  11. 什么是TCP/IP协议,参考模型

什么是P问题、NP问题和NPC问题

这或许是众多OIer最大的误区之一。
你会经常看到网上出现“这怎么做,这不是NP问题吗”、“这个只有搜了,这已经被证明是NP问题了”之类的话。你要知道,大多数人此时所说的NP问题其实都是指的NPC问题。他们没有搞清楚NP问题和NPC问题的概念。NP问题并不是那种“只有搜才行”的问题,NPC问题才是。好,行了,基本上这个误解已经被澄清了。下面的内容都是在讲什么是P问题,什么是NP问题,什么是NPC问题,你如果不是很感兴趣就可以不看了。接下来你可以看到,把NP问题当成是 NPC问题是一个多大的错误。

还是先用几句话简单说明一下时间复杂度。时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快。也就是说,对于高速处理数据的计算机来说,处理某一个特定数据的效率不能衡量一个程序的好坏,而应该看当这个数据的规模变大到数百倍后,程序运行时间是否还是一样,或者也跟着慢了数百倍,或者变慢了数万倍。不管数据有多大,程序处理花的时间始终是那么多的,我们就说这个程序很好,具有O(1)的时间复杂度,也称常数级复杂度;数据规模变得有多大,花的时间也跟着变得有多长,这个程序的时间复杂度就是O(n),比如找n个数中的最大值;而像冒泡排序、插入排序等,数据扩大2倍,时间变慢4倍的,属于O(n^2)的复杂度。还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是O(a^n)的指数级复杂度,甚至O(n!)的阶乘级复杂度。不会存在O(2*n^2)的复杂度,因为前面的那个“2”是系数,根本不会影响到整个程序的时间增长。同样地,O (n^3+n^2)的复杂度也就是O(n^3)的复杂度。因此,我们会说,一个O(0.01*n^3)的程序的效率比O(100*n^2)的效率低,尽管在n很小的时候,前者优于后者,但后者时间随数据规模增长得慢,最终O(n^3)的复杂度将远远超过O(n^2)。我们也说,O(n^100)的复杂度小于O(1.01^n)的复杂度。
容易看出,前面的几类复杂度被分为两种级别,其中后者的复杂度无论如何都远远大于前者:一种是O(1),O(log(n)),O(n^a)等,我们把它叫做多项式级的复杂度,因为它的规模n出现在底数的位置;另一种是O(a^n)和O(n!)型复杂度,它是非多项式级的,其复杂度计算机往往不能承受。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。

自然地,人们会想到一个问题:会不会所有的问题都可以找到复杂度为多项式级的算法呢?很遗憾,答案是否定的。有些问题甚至根本不可能找到一个正确的算法来,这称之为“不可解问题”(Undecidable Decision Problem)。The Halting Problem就是一个著名的不可解问题,在我的Blog上有过专门的介绍和证明。再比如,输出从1到n这n个数的全排列。不管你用什么方法,你的复杂度都是阶乘级,因为你总得用阶乘级的时间打印出结果来。有人说,这样的“问题”不是一个“正规”的问题,正规的问题是让程序解决一个问题,输出一个“YES”或“NO”(这被称为判定性问题),或者一个什么什么的最优值(这被称为最优化问题)。那么,根据这个定义,我也能举出一个不大可能会有多项式级算法的问题来:Hamilton回路。问题是这样的:给你一个图,问你能否找到一条经过每个顶点一次且恰好一次(不遗漏也不重复)最后又走回来的路(满足这个条件的路径叫做Hamilton回路)。这个问题现在还没有找到多项式级的算法。事实上,这个问题就是我们后面要说的NPC问题。

下面引入P类问题的概念:如果一个问题可以找到一个能在多项式的时间里解决它的算法,那么这个问题就属于P问题。P是英文单词多项式的第一个字母。哪些问题是P类问题呢?通常NOI和NOIP不会出不属于P类问题的题目。我们常见到的一些信息奥赛的题目都是P问题。道理很简单,一个用穷举换来的非多项式级时间的超时程序不会涵盖任何有价值的算法。
接下来引入NP问题的概念。这个就有点难理解了,或者说容易理解错误。在这里强调(回到我竭力想澄清的误区上),NP问题不是非P类问题。NP问题是指可以在多项式的时间里验证一个解的问题。NP问题的另一个定义是,可以在多项式的时间里猜出一个解的问题。比方说,我RP很好,在程序中需要枚举时,我可以一猜一个准。现在某人拿到了一个求最短路径的问题,问从起点到终点是否有一条小于100个单位长度的路线。它根据数据画好了图,但怎么也算不出来,于是来问我:你看怎么选条路走得最少?我说,我RP很好,肯定能随便给你指条很短的路出来。然后我就胡乱画了几条线,说就这条吧。那人按我指的这条把权值加起来一看,嘿,神了,路径长度98,比100小。于是答案出来了,存在比100小的路径。别人会问他这题怎么做出来的,他就可以说,因为我找到了一个比100 小的解。在这个题中,找一个解很困难,但验证一个解很容易。验证一个解只需要O(n)的时间复杂度,也就是说我可以花O(n)的时间把我猜的路径的长度加出来。那么,只要我RP好,猜得准,我一定能在多项式的时间里解决这个问题。我猜到的方案总是最优的,不满足题意的方案也不会来骗我去选它。这就是NP问题。当然有不是NP问题的问题,即你猜到了解但是没用,因为你不能在多项式的时间里去验证它。下面我要举的例子是一个经典的例子,它指出了一个目前还没有办法在多项式的时间里验证一个解的问题。很显然,前面所说的Hamilton回路是NP问题,因为验证一条路是否恰好经过了每一个顶点非常容易。但我要把问题换成这样:试问一个图中是否不存在Hamilton回路。这样问题就没法在多项式的时间里进行验证了,因为除非你试过所有的路,否则你不敢断定它“没有Hamilton回路”。
之所以要定义NP问题,是因为通常只有NP问题才可能找到多项式的算法。我们不会指望一个连多项式地验证一个解都不行的问题存在一个解决它的多项式级的算法。相信读者很快明白,信息学中的号称最困难的问题——“NP问题”,实际上是在探讨NP问题与P类问题的关系。

很显然,所有的P类问题都是NP问题。也就是说,能多项式地解决一个问题,必然能多项式地验证一个问题的解——既然正解都出来了,验证任意给定的解也只需要比较一下就可以了。关键是,人们想知道,是否所有的NP问题都是P类问题。我们可以再用集合的观点来说明。如果把所有P类问题归为一个集合P中,把所有 NP问题划进另一个集合NP中,那么,显然有P属于NP。现在,所有对NP问题的研究都集中在一个问题上,即究竟是否有P=NP?通常所谓的“NP问题”,其实就一句话:证明或推翻P=NP。
NP问题一直都是信息学的巅峰。巅峰,意即很引人注目但难以解决。在信息学研究中,这是一个耗费了很多时间和精力也没有解决的终极问
题,好比物理学中的大统一和数学中的歌德巴赫猜想等。
目前为止这个问题还“啃不动”。但是,一个总的趋势、一个大方向是有的。人们普遍认为,P=NP不成立,也就是说,多数人相信,存在至少一个不可能有多项式级复杂度的算法的NP问题。人们如此坚信P≠NP是有原因的,就是在研究NP问题的过程中找出了一类非常特殊的NP问题叫做NP-完全问题,也即所谓的 NPC问题。C是英文单词“完全”的第一个字母。正是NPC问题的存在,使人们相信P≠NP。下文将花大量篇幅介绍NPC问题,你从中可以体会到NPC问题使P=NP变得多么不可思议。

为了说明NPC问题,我们先引入一个概念——约化(Reducibility,有的资料上叫“归约”)。
简单地说,一个问题A可以约化为问题B的含义即是,可以用问题B的解法解决问题A,或者说,问题A可以“变成”问题B。《算法导论》上举了这么一个例子。比如说,现在有两个问题:求解一个一元一次方程和求解一个一元二次方程。那么我们说,前者可以约化为后者,意即知道如何解一个一元二次方程那么一定能解出一元一次方程。我们可以写出两个程序分别对应两个问题,那么我们能找到一个“规则”,按照这个规则把解一元一次方程程序的输入数据变一下,用在解一元二次方程的程序上,两个程序总能得到一样的结果。这个规则即是:两个方程的对应项系数不变,一元二次方程的二次项系数为0。按照这个规则把前一个问题转换成后一个问题,两个问题就等价了。同样地,我们可以说,Hamilton回路可以约化为TSP问题(Travelling Salesman Problem,旅行商问题):在Hamilton回路问题中,两点相连即这两点距离为0,两点不直接相连则令其距离为1,于是问题转化为在TSP问题中,是否存在一条长为0的路径。Hamilton回路存在当且仅当TSP问题中存在长为0的回路。
“问题A可约化为问题B”有一个重要的直观意义:B的时间复杂度高于或者等于A的时间复杂度。也就是说,问题A不比问题B难。这很容易理解。既然问题A能用问题B来解决,倘若B的时间复杂度比A的时间复杂度还低了,那A的算法就可以改进为B的算法,两者的时间复杂度还是相同。正如解一元二次方程比解一元一次方程难,因为解决前者的方法可以用来解决后者。
很显然,约化具有一项重要的性质:约化具有传递性。如果问题A可约化为问题B,问题B可约化为问题C,则问题A一定可约化为问题C。这个道理非常简单,就不必阐述了。
现在再来说一下约化的标准概念就不难理解了:如果能找到这样一个变化法则,对任意一个程序A的输入,都能按这个法则变换成程序B的输入,使两程序的输出相同,那么我们说,问题A可约化为问题B。
当然,我们所说的“可约化”是指的可“多项式地”约化(Polynomial-time Reducible),即变换输入的方法是能在多项式的时间里完成的。约化的过程只有用多项式的时间完成才有意义。

好了,从约化的定义中我们看到,一个问题约化为另一个问题,时间复杂度增加了,问题的应用范围也增大了。通过对某些问题的不断约化,我们能够不断寻找复杂度更高,但应用范围更广的算法来代替复杂度虽然低,但只能用于很小的一类问题的算法。再回想前面讲的P和NP问题,联想起约化的传递性,自然地,我们会想问,如果不断地约化上去,不断找到能“通吃”若干小NP问题的一个稍复杂的大NP问题,那么最后是否有可能找到一个时间复杂度最高,并且能“通吃”所有的 NP问题的这样一个超级NP问题?答案居然是肯定的。也就是说,存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那么所有的NP问题都解决了。这种问题的存在难以置信,并且更加不可思议的是,这种问题不只一个,它有很多个,它是一类问题。这一类问题就是传说中的NPC 问题,也就是NP-完全问题。NPC问题的出现使整个NP问题的研究得到了飞跃式的发展。我们有理由相信,NPC问题是最复杂的问题。再次回到全文开头,我们可以看到,人们想表达一个问题不存在多项式的高效算法时应该说它“属于NPC问题”。此时,我的目的终于达到了,我已经把NP问题和NPC问题区别开了。到此为止,本文已经写了近5000字了,我佩服你还能看到这里来,同时也佩服一下自己能写到这里来。

NPC问题的定义非常简单。同时满足下面两个条件的问题就是NPC问题。首先,它得是一个NP问题;然后,所有的NP问题都可以约化到它。证明一个问题是 NPC问题也很简单。先证明它至少是一个NP问题,再证明其中一个已知的NPC问题能约化到它(由约化的传递性,则NPC问题定义的第二条也得以满足;至于第一个NPC问题是怎么来的,下文将介绍),这样就可以说它是NPC问题了。
既然所有的NP问题都能约化成NPC问题,那么只要任意一个NPC问题找到了一个多项式的算法,那么所有的NP问题都能用这个算法解决了,NP也就等于P 了。因此,给NPC找一个多项式算法太不可思议了。因此,前文才说,“正是NPC问题的存在,使人们相信P≠NP”。我们可以就此直观地理解,NPC问题目前没有多项式的有效算法,只能用指数级甚至阶乘级复杂度的搜索。

顺便讲一下NP-Hard问题。NP-Hard问题是这样一种问题,它满足NPC问题定义的第二条但不一定要满足第一条(就是说,NP-Hard问题要比 NPC问题的范围广)。NP-Hard问题同样难以找到多项式的算法,但它不列入我们的研究范围,因为它不一定是NP问题。即使NPC问题发现了多项式级的算法,NP-Hard问题有可能仍然无法得到多项式级的算法。事实上,由于NP-Hard放宽了限定条件,它将有可能比所有的NPC问题的时间复杂度更高从而更难以解决。

不要以为NPC问题是一纸空谈。NPC问题是存在的。确实有这么一个非常具体的问题属于NPC问题。下文即将介绍它。
下文即将介绍逻辑电路问题。这是第一个NPC问题。其它的NPC问题都是由这个问题约化而来的。因此,逻辑电路问题是NPC类问题的“鼻祖”。
逻辑电路问题是指的这样一个问题:给定一个逻辑电路,问是否存在一种输入使输出为True。
什么叫做逻辑电路呢?一个逻辑电路由若干个输入,一个输出,若干“逻辑门”和密密麻麻的线组成。看下面一例,不需要解释你马上就明白了。
logic-gate
这是个较简单的逻辑电路,当输入1、输入2、输入3分别为True、True、False或False、True、False时,输出为True。
有输出无论如何都不可能为True的逻辑电路吗?有。下面就是一个简单的例子。
logic-gate2
上面这个逻辑电路中,无论输入是什么,输出都是False。我们就说,这个逻辑电路不存在使输出为True的一组输入。
回到上文,给定一个逻辑电路,问是否存在一种输入使输出为True,这即逻辑电路问题。
逻辑电路问题属于NPC问题。这是有严格证明的。它显然属于NP问题,并且可以直接证明所有的NP问题都可以约化到它(不要以为NP问题有无穷多个将给证明造成不可逾越的困难)。证明过程相当复杂,其大概意思是说任意一个NP问题的输入和输出都可以转换成逻辑电路的输入和输出(想想计算机内部也不过是一些 0和1的运算),因此对于一个NP问题来说,问题转化为了求出满足结果为True的一个输入(即一个可行解)。

有了第一个NPC问题后,一大堆NPC问题就出现了,因为再证明一个新的NPC问题只需要将一个已知的NPC问题约化到它就行了。后来,Hamilton 回路成了NPC问题,TSP问题也成了NPC问题。现在被证明是NPC问题的有很多,任何一个找到了多项式算法的话所有的NP问题都可以完美解决了。因此说,正是因为NPC问题的存在,P=NP变得难以置信。P=NP问题还有许多有趣的东西,有待大家自己进一步的挖掘。攀登这个信息学的巅峰是我们这一代的终极目标。现在我们需要做的,至少是不要把概念弄混淆了。

原文地址:Matrix67: The Aha Moments