第五章 编程
学习心得:复杂的东西拆解之后就会变得简单
“如果调试程序是移除臭虫(bug,软件缺陷)的过程,那编写程序就是把臭虫放进来的过程。”
——迪杰斯特拉,著名计算机科学家
关于本章的补充图像、视频和评论,请访问 http://first20hours.com/programming.
从 2007 年开始,我就一直生活在网络上,当时我辞掉了宝洁公司营销经理一职,开了一家出版咨询公司。
我的门户网站 PersonalMBA.com 是我的谋生之道。从学识上讲,我自认为已经完全达到商学院教授级别了,只是我不在大学工作而已。每年我都会为一些想自学商业课程的读者更新最佳商业读物书单。[1]
2005 年,我在网站里推荐的书单深受读者朋友的喜爱,这是我第一次做推荐。后来,更新的清单更是吸引了来自世界各地浪潮般的读者。现在, PersonalMBA.com 门户网站的访问量已突破 200 万次。
我的第一本书《在家就能读 MBA:掌握经营的艺术(2010)》(The Personal MBA:Master the Art of Business)是网站 PersonalMBA.com 的自然延伸,该书一经出版立刻就登上国际畅销书排行榜。写书期间我一直在思考如何推广这本书,过去的几年我一直在尝试吸引更多的读者。
经过多年努力,《在家就能读 MBA》一书先后受到《纽约时报》《华尔街日报》《财富》《福布斯》和《快公司》等报纸、杂志以及一些热门网站和博客的推荐。一经推荐, PersonalMBA.com 网站的访问量在极短的时间内就会破万。
进步的代价
自己的网站一下子拥有成千上万的粉丝的确是一件美妙的事情,但这样的美妙需要条件,即每个访客都能顺利访问该网站。然而,我的网站一旦接收到大量的访问请求,系统一定会在“无上的荣光”中彻底崩溃,草草留给访客一条无奈且神秘的错误信息。这让我非常头疼。
举一个典型例子: Lifehacker.com 是美国著名新闻博客 Gawker Media 公司旗下的独立博客网站,旨在分享一些生活诀窍和 IT 类的信息,以便提高用户的办事效率。在过去的几年里,它连续为《在家就能读 MBA》做了三次推荐,每次推荐后,数以万计的访客便会蜂拥而至,不出意外地我的网络服务器(计算机对访客每一次访问要求均会发送一个网页)一定会以崩溃收场。这种情况下,访客再也无法找到我的网页,相反,服务器会返回一条“数据库连接错误”或“错误 503”的页面提示。瞧,我的服务器开始求饶了。
眼睁睁看着我的服务器因不堪重负一次次崩溃,我的灵魂似乎也随之崩塌。我苦心经营的网站屡遭劫难,一切努力付之东流。成千上万的读者对我的个人网站充满无数好奇,可我的服务器总是不争气,瘫痪了,他们只好两手空空,黯然离去。我的营销工作固然做得漂亮,然而我的系统却无力应对激增的访问请求。
问题检查
最初,我通过增强处理能力和扩大内存来改善服务器。那样做有用,但只起到一定的作用。超出了那点作用之后,我的网站仍然面临崩溃,尤其在我的营销取得重大胜利的时候。当时, PersonalMBA.com 在一个受欢迎的网站管理系统上运营,这个系统叫 WordPress。[2] WordPress 易于安装和使用,运行无负担。在 WordPress 的默认配置里面,每一个网页请求都会引起级联服务器的活动,它会产生数以百计的隐性请求,最终把网页发送给访客。
这样的话,每一个单独的网页请求就会变得麻烦。也就是说,每一个请求都对内存和处理能力有很高的要求。如果一个访客浏览了网站的 5 个页面,那么该用户就会产生 5 个请求。以此类推,如果 1000 个访客同时请求浏览同一个页面,那么服务器就会试图同时启动 1000 个相同的流程,这真的太麻烦。
系统瘫痪
遇到这样的情况,可怜的被围攻的服务器将会试图回复每个请求,但因为每个请求都涉及大量资源,所以服务器在完成所有请求之前就将耗尽内存。到那个时候,服务器就会“举白旗”投降,此时的访客倒霉透顶。
为了应对这个问题,我先后换了五家网络主机服务商。耗费了大量的时间学习如何让 WordPress 在重压下保持在线。每换一个新的服务器,其配置的复杂程度都会增加;而每一个新的装置,都要求提供更全面的长期维护。
最终,我建立起了我的自定义服务器,调用了一长串神秘的系统命令来安装、设定和修正复杂的服务器应用,而这些应用我之前几乎一无所知。在设置或维护的过程中,我遭遇的每一个小错误或大问题都会让我花很长的时间对它们所造成的故障进行研究和排除。
也许这样做还不够。WordPress 拥有大量的用户群,这使得其存在安全隐患,它常常成为黑客和垃圾邮件发送者的攻击目标。每隔一个星期左右,那些不怀好意的程序员就会利用某个新的薄弱点干点儿坏事,他们要么盗取用户账号,要么制造数以百万计的垃圾邮件去填满 WordPress 的博客档案。因此,你需要 24 小时不间断地对整个系统进行监控,以此保证 WordPress 装置的安全以及软件随时处于更新的状态。特别是在维护多个网站的时候,你必须得这样做。(我曾经同时维护了 12 个网站,但没这样去做,最终惨败收场。)
突然间,我意识到我在保持网站在线方面所花的时间比我在为读者搜索资料和写作方面所花的时间还要多。这简直毫无意义。我不仅浪费了生产力,而且还未真正学会如何编程。我只是在学习一大堆针对特定情况的技巧和方法,而且这些技巧和方法只能用于 WordPress 的运行。这一点也不酷![3]
我一定要找到别的维护网站的方法。好在不久之后,我真的找到了一个极具前景的替代方法。
一个潜在的解决方案
一天,我偶然发现一篇关于 Jekyll[4] 的文章,Jekyll 是一个由著名的开放源代码库 GitHub[5] 的创立者 Tom Preston-Werner 开发的网站管理工具。Jekyll 的设计是为了取代如 WordPress 这样的系统,它使网站运行变得更加容易,而且无须依赖那些麻烦的请求。
设想此时你正有成百上千的包含重要信息的文档要处理,你需要将它们按同一标准进行页面处理,即同样的字体、同样的标题样式等。在这种情况下,如果你开发了一个工具,这个工具可以把指定的页面设计自动应用到你选择的每一个文档中,这可比手动升级每个文档花的时间少多了。
这实质上就是 Jekyll 的网页功能。当运行一条指令,Jekyll 就会使用你计算机上的文档(包含你的网站信息和设计模板)创建一个完整的网站。如果你需要对页面设计或页面内容做出修改,那么只需要再次开启 Jekyll,整个网站就会进行自动更新。瞧,这太省工夫了吧。
Jekyll 提供了极具前景的机遇。从理论上讲,我用电脑里的一个简单的文本文件来就能替代 WordPress。我的网站运行速度会极快,而且超级稳定,这样下来,每年节省超过 100 小时的服务器维护时间。
然而,有个小问题:Jekyll 是使用 Ruby 编写的——一种我不懂的程序语言。我不知道怎样使用 Ruby 编写代码,不明白如何运行 Ruby 应用服务实际用户。所以,若要运行我的 PersonalMBA.com ,我要做的可不只格式化那么简单。
为了使用 Jekyll 运行我的网站,我不得不学习编程并部署 Ruby 网页应用。
似乎这个小问题变得相当让人“喜欢”。
学习编码
我想学习如何编程已经有好一段时间了,但是其他事情总是被我优先考虑。如果我学会了编码,我的出版业务将迎来更多的契机,因为我的业务都在网上进行。
其实到目前为止,我没做过一件和编程相关的事,还好我注意到这点。我用来创建网页的 HTML 和 CSS 语言叫作“标记”语言。HTML 和 CSS 语言根本不智能,它只是简单地以特定方式告诉计算机把文本文档展示给用户(例如,“使该文本变成粗体”或者“这部分标题的大小是 24 号的字体”)。
我那几近疯狂的服务器设置也是如此。即使我把程序放在了一起,我却从未真正地编程。事实上,我只是把事先写好的程序安装好,然后变更一些设定而已。在不需要任何编程知识的情况下,我只是使用它们而已,真正书写它们的是程序员。服务器设置和管理的确是一项有用的技能,但那毕竟不是编程。
什么是编程
说起编程,我的第一个念头是:编程就是告诉计算机“要做的事情”。这样的解释太不具体,对细致学习编程好像也没有多大作用。现在要搞清楚的是“要做的事情具体是什么”。
10 年前,我在大学里面学习过两门编程基础课程。此时的我却想不出一个适合的定义来,这着实令人有点尴尬。我只依稀记得一些基本术语,比如变量、循环、输入、输出、功能和面向对象等,还有程序设计里面的“冒泡分类法”,其他的就想不起来了。
该课程的作业是要求我们学习 C++编程语言。我记得当时花了好多时间才找到了一个让我的程序陷入瘫痪的分号,这让我备受煎熬。一个教授曾经说过:“冒泡分类法可能永远不会在实际应用程序中使用,但我们仍然要学习它。”
学习期间,我写了好多基本的程序,我必须得这样做。因为我的目标是以良好的成绩通过这门课程,最终我也确实做到了。然而,非常不幸的是,当时编写的程序在毕业后就再无实际用处,那些概念也长时间无用武之地。虽然我至今仍记得寥寥数语,但是为了把握核心理念,我必须从头开始。
虽然我想学习编程,但自己却无法明确具体要做的事情,这就意味着我无法设定我的预期目标。“创建一个计算机程序”听起来好像更加具体了点,但仍然不是非常有用。
不如我先把目前对编程的了解说一遍吧。
●我知道程序员“编写”程序是一件创造性活动,可以通过很多种方式来完成。
●程序经常被称为“应用”,它们之间可以互换。
●一旦计算机程序被“运行”或“执行”,不管接下来会发生什么,它们一定会按照编写的程序命令做事情。
●“输入”和“输出”很容易记住,因为它们是常见的用法。“输入”就是程序所使用的信息或数据,而“输出”则是当程序运行的时候,你得到的东西。
●“变量”基本上就是一些变化的占位符。你可以创建任意数量的占位符,然后让那些占位符代表你所想要的。
●“程序”本身实质上是一套详细的指令和规则,它们精确地告诉计算机使用“输入”做什么。当程序运行完成,它就会给你“输出”。
●当出错时或者计算机无法弄清楚接下来该做什么时,程序就会“瘫痪”或者会显示一条错误消息。
瞧,我们好像取得了一些进展,有点眉目了。除了“编程”,我们现在有三个子概念需要理解。我得做一个非常基本的分解。
●输入:用于执行一个流程的信息。
●流程:在给定输入的基础上,程序运行所采取的一系列步骤。
●输出:程序的最终结果。
这个分解明显有用多了。“编写一个计算机程序”意味着你要定义从什么信息开始,定义计算机在输入时所进行的精确步骤,以及定义程序完成运行之后计算机将返回的输出。
试着在大脑里想出一个流程图,显示程序每一个相连的步骤。该流程始于某些输入,在这个流程中,当遇到特定的条件,不管是真条件还是假条件,你会采取相应的措施。直到达到流程图的末端,整个程序运行结束。相应地,你也得到了输出,即流程完成之后的最终结果。
“创建一个计算机程序”看起来就像用不同的方式去做同样的一件事。你会问到同样的问题:
●从哪里开始输入?
●流程开始时会发生什么?
●然后呢,再然后呢?
●流程什么时候结束?
●流程结束时会获得什么输出?
流程图以可视化的形式描述这些问题的答案,而程序则使用文本描述它们,但它们的思维过程是一样的。
流程图模拟也同样有用,因为它提供了其他概念的重要线索。
条件语句就像这样书写:
●“如果 X 是真/假,然后做 Y”;
●“如果 X 是/不是 Y,然后做 Z”;
●“当 X 是真/假,然后做 Y”;
●“当 X 是 Y,然后做 Z”;
●“若 X 是真/假,然后做 Y”;
●“若 X 是 Y,然后做 Z”。
在这种情况下,X、Y 和 Z 是变量,它们可以代表任何东西。变量可以代表数字,像基本代数,或者它们也可以代表单词。有时候变量是单一的字母或符号,而有时候它们是单词。又或者说,它们代表所有我们正在做的东西。
条件语句(如果<IF>、然后<THEN>、当<WHEN>以及循环语句<WHILE>)就像流程图上的问题的箭头一样。想象一下我们驾驶汽车时的情景:红灯停,绿灯行,黄灯放慢速度或准备停止。
很有必要更加深入地了解一下这些条件语句,因为它们有共同的模式。“真/假”经常出现,而循环语句看起来就像我们一直在做某件事,而不是只采取单一的行动。
任何情况下,条件句包含了一些定义是否采取行动的语句。这个语句称为一个条件,有多种形式。有时候这个条件只是一种基本的真/假比较(红灯亮了吗),有时候它是一种数学比较(X 大于 100 吗),或者有时候它包含逻辑(交通信号灯不是红色的吗)。
该条件句的目的就是为了定义是否应该进行相关流程。如果该条件句是真或有效的,那么该程序就运行指令;如果不是,则跳过相关指令,然后该程序执行下一条指令。
真/假变量被称为布尔变量,可以用于那些只有两个选项的奇特语句。“是/否”和“开/关”也是布尔变量。布尔变量在计算机编程当中非常重要,因为它们是两个简单流程(如流程图中的真/假)的基本单位以及计算机核心当中的微型电子开关的上/下翻转。
在这种情况下,循环语句就是一种特殊的条件句类型。循环使存在问题的流程不断重复,直至条件符合。让我们再次模拟刚才的驾驶场景:当红灯亮的时候,不要前行。
非常简单,不是吗?如果我们把编写计算机程序想象成绘制一个流程图,基本流程更加容易想象。
如果计算机不知道该做什么,卡住了,或者不运行某一指令甚至失灵,怎么办呢?下面我告诉你一个小窍门。
程序“瘫痪”是指程序完全停止运行,并输出一条错误信息,此时我们无法得到所需要的结果。我想几乎所有人都遭遇过令人恐惧的“蓝屏死机”状况或者在浏览网页时看到过突然跳出的一条错误提示:“错误 404:未找到页面”。计算机行为失常,程序瘫痪,这样意想不到的事情时有发生。
计算机程序员的工作是预防瘫痪和错误的发生。确保程序总是拥有按计划完成流程所需要的信息,这当然是最理想的情况,可事实上并不可能每次都如愿。当计算机试图完成一个流程,但因遇到无法避免的不确定因素而失效时,有些恢复方法非常有用。
这些错误恢复编程语句被称为 Exceptions 函数,它们非常简单,容易上手。你可以把它们想象成针对错误的条件句:如果程序将要以 X 方式瘫痪,那么做 Y 可避免瘫痪。
Exceptions 函数有点像医院大楼里的备用发电机。大多数时候,发电机只是无所事事地待在那里。然而,如果停电,发电机就会上岗工作。医院使用了发电机产生的电力,不再陷入黑暗之中了,患者也能继续使用维持生命的电气设备,这是一件非常好的事情。由此可见,完全失效是相当危险的,此时备份就显得非常关键。
坦白讲,这只是基本的编程。定义输入、设定变量和创建流程,得到预期的输出。通过一份流程图去思考那些流程,使用必要的条件语句和 Exceptions 函数。如果一切顺利,你输入,运行程序后,就会得到预期的输出。
我以上所讲的编程内容已经大大降低了它本身的复杂程度。对于编程菜鸟而言,其实已经足够具体和有用了。通过这种方式解构编程,我们就更加容易知道该从哪里着手了。
编程语言
计算机不会像人类那样说话,这是一个棘手的问题。从本质上而言,计算机通过特殊的方式翻转微型电子开关来工作。除非计算机具有某种方法把我们人类语言的命令转换成为电子开关翻转,否则计算机无法接收我们的命令。
这就是编程语言的作用:它们赋予人类程序员一种特定的方式来告诉计算机何时开始、做什么以及何时停止。它们还允许程序员定义输入、流程以及输出的样式,以及程序的执行完成之后将会获得怎样的输出。
每种编程语言都有一种特定的编写命令的方式,这叫作语法。语言的语法包括计算机用来将程序命令转换为微型电子开关翻转的规则。
基本上,每种语言都有一种定义变量、条件句和 Exceptions 函数的方式。细节虽有差异,但核心却是相同的。
像程序员般思考
程序员经常通过所谓的伪代码来思考问题:看起来或听起来像代码的语言,但尚未具体到能够让一部电脑实际执行。仅仅把它作为打草稿的一种形式。伪代码可以帮助你通过流程来思考问题的解决方法。
之前提到的驾驶的例子就是一个伪代码事例。如果我在车里说:“当我转动钥匙,发动机就会启动了。”若只是这样“说”的话,接下来什么事情都不会发生。但并不代表我说的话是无用的。我的话只是通过所需的步骤来思考问题的解决方案,它是获得预期结果的一种方式。
你可以使用我们刚才讨论过的基本编程概念来为普通的任务勾勒出简单的程序。
这里介绍一个有趣方法:找一个朋友帮你完成一件简单的任务,比如“做一份三明治”。唯一的规则就是你的朋友只能做你告诉他的事情,不多不少。他得假装什么都不知道,只能按照你吩咐的意思执行每个要求。
几分钟之后,你会发现自己正重复下面的对话。
你:拿起面包。
朋友:我不明白什么叫作“拿起”。
你:把你的手移到我说的地方,然后拿起它。
朋友:我不明白什么叫作“手”。
你:(叹气)这个东西在这里。(你指向朋友的手)
朋友:明白。
你:把你的手移到我说的地方,然后抓起它。
朋友:我不明白什么叫作“抓”。
你:像这样弯曲你的手指。(你演示、弯曲以及摊开你的手指)
朋友:明白。
你:把你的手移到面包那里,然后抓住它。
朋友:我不明白什么叫作“面包”。
你:这个东西在这里。(你指向面包)
朋友:明白。
你:把你的手移到面包那里,然后抓住它。
朋友:(把手移到面包那里,弯曲手指,然后摊开。面包无法移动。)
你:这简直是一个愚蠢的游戏!
这的确够蠢的!但是它非常接近编程的样子和感觉,特别是在刚开始的时候。
计算机就像你的朋友那样,如果你的定义不够明确,它就无法理解。记住:任何复杂流程的定义都需要特别的完整和清晰。
这就是编程的困难所在:一个歧义或偏题命令可以造成整个程序的失效。所以,代码必须从细节上精确表述。从这一点上来说,编程是一门无情的技术。计算机从不会为魅力或智慧所打动。一旦代码不完整或格式有误,那么就有可能会出现两种情况:要么你的应用瘫痪,所有数据都会消失;要么臭虫(bug,软件缺陷)出现,它的出现将会导致意想不到的或者不可预见的后果。如同数学一样,逻辑要么可行,要么不可行。
尽管如此,在编程这个领域里,编程问题不像数学问题那样有单一且通用的解决方案。在指定输入的基础之上,预期输出可以通过成千上万种方式获得。程序员必须基于可以使用的工具选择合适的编程方法。
计算机没有读心术,知道了这一点,你就应该开始学习如何下达系统可以理解的命令,同时你还应该学会如何清楚地为术语下定义,就像在之前的“三明治游戏”当中,在下达复杂的命令之前向朋友演示基本动作。
是什么让网络应用程序与众不同
我们现在有了编程的工作定义。它虽然简单,但是它让我们对目前应该做什么有了足够的了解。
尽管我想在网站上运行我自己编的程序,但是我对各种编程都不感兴趣。如果你曾经使用基于网络的电子邮件程序,比如 Gmail、Hotmail、Yahoo Mail 或类似的电子邮件程序,你就会明白我到底在说什么。这些程序不需要你费脑筋学习编程知识,在你的因特网浏览器上就能运行。简单到你无须下载任何软件程序,只需点开浏览器,输入网址,登录,接下来你就可以愉快地浏览网页了。
本地运行的软件和在网络服务器上远程运行的软件之间存在差异,知晓这一点非常重要。为了开发一个网络应用,你必须首先编写该应用,然后测试它是否可以运行。用自己的电脑就能完成所有的开发和测试工作。
如果程序能够运行,你就将它发送到“生产服务器”,以便其他人也可以应用。因特网上的人无法从网络上登录你的个人电脑,所以,为了让其他人可以使用你的程序,必须将该软件上传到可公开访问的网络服务器。
也就是说,开发的流程有两个主要阶段:先进行本地编程和测试,然后将完成的程序推送到远程生产服务器上以获得实际应用。那么,现在让我们看看这两个阶段是如何运作的。
之前的 HTML 和 CSS 经验告诉我:它们是“愚蠢的”,因为你无法让基本网页储存任何信息。
举个例子,你有一个显示着“你好,世界!”[6] 的网页文档,你现在想把“世界”改为网页访客的名字。这个点子挺不错,但是基本网页没办法储存信息以备该访客下次访问。它们只会展示文档中的文本,而这个文档是不允许自我更新的。
如果要用一个术语来描述这种情况,我想应该是“状态”这个术语。使用 HTML 和 CSS 建立的基本网页是没有状态的,所以它们被称为“无状态”资源。你可以在网页上增加一个旁边有储存按钮的小方框并请访客填写姓名,但是该按钮实际上不会储存任何信息,除非你创建一个单独的空间来储存那些信息。
那就是为什么后续的网页应用都使用两种普遍的方法来储存数据:数据库和用户本地终端上的数据(cookies)。
数据库好似一叠索引卡片。例如,你想创建一个地址簿,那么你得获取每个朋友的姓名、电话号码、电子邮箱地址、性别以及年龄。
每个朋友都有他们各自的索引卡片,里面储存着他们的详细信息。例如,如果你的一个朋友改变了电子邮件地址,你可以把旧的邮件信息删除掉,然后更新该卡片,填入新的邮件地址。当打开某一个朋友的卡片时,你立即可以看到他的所有信息。
你可以把这一整叠索引卡片看成一个数据库,索引卡片可以称为“记录”。你可以在数据库中任意存放记录。但有一天你会发现这些记录变得难以管理。将这些记录进行细致分组,这不失为一个好办法。例如,把朋友和家人分在一组,把工作上的同事分为另一组。
这样不就清晰了吗?这就是它酷的地方:瞧,你的索引卡片具有了魔力!你可以和魔法卡片对话,并命令它根据不同的条件展示合适的卡片,比如:
●“向我展示 John Smith 的卡片”;
●“向我展示所有女性朋友的卡片”;
●“向我展示所有年龄超过 50 岁的人的卡片”。
这很有用吧!从本质上讲,这就是数据库的作用:它为你提供一种可以储存结构性信息的方式,以及一种检索那些你想要的信息的方式。
记录卡里的每一个数据都叫作“域”。你的数据库里面拥有的域越多,当你需要数据的时候,你拥有的检索方式就越多。
数据库是网络应用中储存数据最普遍的方式。当你想储存信息的时候,比如你要储存一个用户的名称、电子邮件地址以及其他信息,使用数据库是最合适的选择。一旦 John Smith 登录你的应用,你就可以命令应用从 John 的数据库记录当中检索他的姓名,然后显示“你好,John Smith!”
在网络应用当中储存信息的另一种普遍方式是通过用户本地终端上的数据:储存在用户计算机上的一个非常小的文本文件。数据非常适用于储存那些不需要长时间保存且量少的数据。
在我们的地址簿程序案例当中,当 John Smith 登录的时候储存一个数据是恰当的。John 的数据文档将包含类似这样的信息:“用户名=Johnsmith”和“登录=真”。如果 Smith 离开了该应用,下次重新访问的时候,应用就会识别出他的数据并欢迎他的访问(无须再次登录)。数据可以设定一个有效期,这就让这类程序变得非常有用。(如果你曾经在网站上看到“记住该密码”的功能,这就是它工作的原理。)
我们在这里所做的只是基本的解构。这不是解释网络程序因何独特的详细清单,它只是足以提供一个我需要学习什么的简单框架:变量、条件句、Exceptions 函数、本地/生产环境、数据库和用户本地终端上的数据。
那么,这一分解到底有多大用处?起初,我也不清楚怎么开头,到底要做些什么?然而现在,我已经拥有了一份具体的关键技能明细表。
尽管如此,我还没准备好开始。还记得我之前提到过计算机不懂读心术,无法理解人类的语言吗?我需要选择一种编程语言来编写程序指令,这还需要多做一点研究。
网络应用编程语言的选择
编程语言有成千上万种,而且每天都有新的语言被开发出来。每种语言的语法规则各不相同,而且深受语言设计目的性的影响。针对不同的任务,它们各具优势。
在开始之前,我花了 1 小时浏览一些主要的编程网站,看看网络应用开发人员推荐的语言。这一早期研究将会帮助我决定学习哪种语言,以及我需要练习哪些早期技巧。
Stack Overflow[7] 和 Hacker New[8] 是最受程序员欢迎的两个网站,为了学习最好的语言,我决定也去浏览一下。
Stack Overflow 是一个问答网站,在这里可以问“我如何做 X”之类的问题。针对这些问题,经验丰富的程序员会给出他们的建议、方法,或者帮你修改错误。如果遇到棘手的编程问题,请登录 Stack Overflow 获取帮助吧。
Hacker News 是一个社会新闻网站,在这里有很多相关讨论的链接。Hacker News 上的主题不断变化,但通常都涉及编程、技术和业务这些领域。如果想搜索关于新开发的编程的观点,或者至少对观点一知半解,请登录 Hacker News 看看吧。
全世界的程序员每天都在创建新的语言、程序库和技术。一些技术和方法的集合体对某些问题非常有用,但是对于其他问题不见得有用。通常的情况是,你试过之后才知道。
在编程术语当中的“最佳”只是相对于你试图解决的问题以及你的特别优先事项而言。一般来说,我们的建议是选择可以让你有效地解决问题的工具;如果可以的话,你最好选择自己喜欢使用的工具。没错,就是这样。
浏览 Stack Overflow 和 Hacker News 的文档给我一种信息过载的感觉:太多信息需要立即加工,特别是在你不熟悉那些术语的情况下。如果我想要找到更加具体的建议,那么我得摒除一些杂音。
这是一个大多数人都不知道的战术研究技巧:流行的搜索引擎可以让你把搜索限定在一个特定的网站,而不是整个网络。在谷歌上的搜索代码看起来是这样的:
“搜索短语”网站: example.com
将“搜索短语”替换成你想要搜索的术语,然后把“ example.com ”替换成你想要搜索的网站。该引号的意思是精确搜索匹配引号内的搜索短语内容。没有引号的话,谷歌会返回包含在该短语内的所有单词页面,但不一定是按照那个顺序。
我用这个技术试着搜索了几个短语变量:“网络应用编程”“编码学习”以及“编程初学者”。然后,我花了大概一个小时阅读搜索到的内容。
经验丰富的网络开发人员认为初学者可以先从两种常见语言中选择一种学习,比如 Ruby 或者 Python。这两种语言易于学习,使用广泛,口碑不错,是日后学习重要编程概念的基础。对于程序员而言,专注网络应用的 Ruby 更受欢迎一些,而 Python 则在科学、数学以及图形库领域更受科学家和数学家欢迎。
Ruby 和 Python 的优点是,它们已经了拥有大批活跃的开发者社区,学习者随时能够下载到大量的免费资源,也可以在市面上购买一些写得不错的参考书,此外它们有预先设置好的且更易于执行的程序和工具。所以,我认为选择合适的编程语言似乎是一个应该优先考虑的问题。
对我这个外行来说,我希望在一开始学习时进展顺利。我在看完大量不同语言的编程代码之后,我决定学习 Ruby。Ruby 代码更简洁、更具可读性、更易于理解,它涉及很多有用的知识和技术,这些知识和技术也有助于我将来学习别的编程语言。
此外,我打算学习的很多程序和工具都需要支持或要求 Ruby,尤其是 Jekyll,它是由 Ruby 编写的,所以学习 Ruby 可以帮我解决一个迫在眉睫的问题。其实,Python 也存在类似的工具,但它的操作方法看起来更加复杂。
框架选择
除了编程语言的选择之外,网络应用程序员对框架的选择也有自己独到的见解:代码程序库可以帮你更容易地完成大部分应用需要做的事。
程序库非常重要,因为计算机会完全按照你的程序要求去做。不多不少。
这还真棘手,因为你提供的编码对于一个计算机程序而言意味着一切。正如物理学家卡尔·萨根所言:“如果你想从头做苹果馅饼,你就必须首先创造宇宙。”
程序的“宇宙”由以下内容决定:(1)编码库的指令和命令;(2)程序输入的程式库;(3)程序运行系统。如果完成给定操作的必要编码不存在于系统中,那么此时的程序将崩溃或返回一条错误消息。
大部分编程语言都包含大部分程序所需的常见程序库,但里面的专业工具非常少。这时就需要选择框架。从头开始编码会花费很长时间,然而,如果有一套框架的话,输入和使用可以得到测试,在完成专业任务时拥有可靠程序库。这样,就能专注于应用的核心部分,完全不必重新创造“宇宙”。框架的容量可大可小。有的框架包含许多功能和命令,这样一来,程序员的工作就轻松了许多。而有的框架小一些,只具备一些基本功能。
Ruby 现在有几个主要的网络应用开发框架,其中 Ruby on Rails[9] 和 Sinatra[10] 是最受欢迎的两个。
Ruby on Rails(经常缩写为 Rails)是为 Ruby 开发的最早的主要网络应用框架之一。它由大卫·汉森于 2004 年创建,并很快成为最受欢迎的框架,曾经为 37signals[11] (一家私人控股的网络应用公司,汉森是合伙人)成功开发了几个口碑不错的应用。目前为止,数以千计的企业在使用由 Rails 开发出的大型关键业务的网络应用程序。
Rails 非常依赖“生成器”,它是用单一命令创建大量样板代码的内置程式。样板代码可以根据程序员的不同要求做相应修正。有了 Rails,程序员无须从头开始花大量时间创建一个应用。只要他们知道接下来怎么做,费不了多少工夫就可以创建出一个功能性应用来。
Sinatra 则是由布雷克设计和开发的最小化框架。它不依赖生成器,更专注于为程序员提供大部分网络应用都需要的简单且常见的功能。
Sinatra 应用似乎比 Rails 应用更简单。Rails 的一个命令可以生成 10 个或 20 个,甚至更多的文档;而 Sinatra 应用常常只生成一个单一的文档,这样可以避免一堆可能需要删掉的代码。总的来说,它保持了项目的简单性,只增加必要的代码完成任务。
同语言选择一样,框架选择也需要根据个人喜好选择自己最合适的工具。Rails 更受需要一定数量程序员的大型项目的青睐,而 Sinatra 则更适合于小型项目。然而,它们之间也存在许多重叠的特征,所以,来自 Rubysource.com 的一份分析报告认为它们最终还是见仁见智的选择。[12]
GibHub 是许多程序员用于发布和维护项目的开源代码库,使用 Rails 和 Sinatra 编写应用的很多例子非常容易在上面找到,所以我又花了一个小时去浏览公共项目,想好好了解一下这两种框架。
这里有一个特别提醒:要想在编程方面取得进步,就必须专注学习。如果选定了合适的语言和框架,那么接下来学习其他相关编程知识时才会更顺利。但是,如果你为了追求所谓的“完美”编程环境而迟迟不做选择,那么你将为此荒废数年的宝贵时间。
与其浪费数年时间做毫无进展的“研究”,不如选择感兴趣的语言和框架,专注研究一段时间,权衡利弊。整天浏览 Stack Overflow 和 Hacker News 毕竟不是真正的编程。
最后,我决定从 Sinatra 开始学习。Rails 生成器倒是可以帮我节省很多时间,但前提是我得清楚接下来具体要做什么,遗憾的是我并不知道。
我的直觉告诉我,Sinatra 是此刻最好的选择。它简单且易于理解,语法还很清晰。Sinatra 应用当中存在的唯一代码就是开发人员增加的代码。框架是有据可查的,在 GitHub 上很容易找到应用程式的例子,同时在 Stack Overflow 上很容易获得帮助。
或许在将来的某一天我会尝试 Rails,但现在,我要从 Sinatra 学起。
解构最终结果
经过五个小时的初步研究,我想我已经做好了所有准备工作。我选好了网络编程解构、语言、框架以及具体的项目。是时候开始了!
我的目标是编写一个可以服务于 Jekyll 网站的 Sinatra 应用,听上去有一定水准吧!但是我需要解构一下语句内容,以弄清楚下一步该做什么。我必须怎么做?
经过一个小时的研究,我列出了需要完成的任务:
1.运行 Jekyll,从本地静态文档中创建成品网站,并为该网站创建一个带特殊格式标记的 HTML 模板,再在 Personal MBA.com 上发布文档帖子(保罗·斯塔马蒂奥[13] 的教程中有相关方法描述)。
2.用 Sinatra 处理来自网站访客的请求,然后发送所请求的文档。(我需要从头开始编写该应用。)
3.把完成的 Jekyll 网站和 Sinatra 服务器应用上传到一个网站主机。
4.为了完成所有任务,我需要弄清楚如何在我的计算机上安装最新版本的 Ruby、Sinatra 以及其他我需要的程序。
第四个任务要求大家获取关键工具。如果我无法弄清楚如何在我的计算机上安装 Ruby,我将无法进行后续步骤。所以,安装 Ruby 应该是第一步。
需要注意的是:网络技术日新月异,当你读到本章节时,里面所提到的一系列特定命令符很可能已经过时。别担心:重要的是方法,而不是命令符。
本章节提到的这些代码符号的确复杂得让你无法立即识别出它的意思。你很可能有一种想跳过它们的冲动。
但是,我希望你打消这股念头。我和你一样不熟悉这些陌生的名字、命令和符号。在这一章里,我不但要帮助你弄清楚这东西,而且还会告诉你如何使用它们。请你试着阅读这些代码符号,你一定会从本章节中获益的。
加油吧!
Ruby 升级
有电脑才可以学习编程。我已经拥有一台电脑,这是个好的开始。
我目前使用的是苹果公司的 MacBook Air,它运行的是 Mac OS X 10.6 操作系统。查阅资料后,我发现该操作系统已经预先安装好了 Ruby 1.8.7 版本。这无疑是个好消息,因为从理论上讲,我可以立即在我的电脑上运行 Ruby 程序。
问题是,Ruby 1.8.7 不是 Ruby 的最新版本。当我试图安装 Jekyll 的时候,系统提示我该程序要求的最低版本是 1.9.1。也就是说,我必须升级,得上谷歌去搜搜看。
谷歌搜索后的结果显示有两个比较容易安装和管理 Ruby 的程序:rbenv 和 ruby-build。这两个程序均由萨姆·斯蒂芬森进行维护,他是 37signals 的一名 Ruby 开发人员。这些程序可以帮助我安装 Ruby 新版本,并告诉我的电脑应该使用哪个版本的 Ruby。
rbenv 的安装说明文档[14] 里展示了如何将该程序安装到计算机当中。安装命令符看起来是这样的:
$ cd ~ $ git clone git://github.com/sstephenson/ruby-build.git $ cd ruby-build $ sudo ./install.sh $ cd .. $ git clone git://github.com/sstephenson/rbenv.git .rbenv $ mkdir -p ~/.rbenv/plugins $ cd ~/.rbenv/plugins $ git clone git://github.com/sstephenson/ruby-build.git $ echo 'export PATH=$HOME/.rbenv/bin:$PATH 敀 >> ~/.bashprofile $ echo 'eval "$(rbenv init -)"'>> ~/.bashprofile $ exec $SHELL $ rbenv install 1.9.3-p125 $ rbenv rehash $ rbenv global 1.9.3-p125
这些符号看起来怪吓人的,它其实只是一个命令列表。让我们把它拆解一下。
这些命令被输入一个叫作终端的程序当中,这个终端在苹果电脑中已经预先安装。如果你曾经在电影当中看到过黑客疯狂地敲打键盘,电脑随后显示出一串又一串的文本信息,这时的电脑就是在运行一个终端程序。[15]
我打开终端并输入第一个命令:
$ cd ~
这个命令很容易理解。$就是终端显示,它已经准备好接收新的命令。cd 是“改变目录”的缩写,它是文件夹的另一个术语。查阅资料后,我知道“~”是“用户的主文件夹”的缩写,也就是我的电脑中用于储存用户配置文件的文件夹。
我输入命令,然后按下回车键。此时,终端显示如下:
joshkaufman $
太好了,我已进入我的主目录。目前来说非常不错。于是,我输入了第二个命令:
$ git clone git://github.com/sstephenson/ruby-build.git
屏幕显示如下:
git: command not found
似乎我的计算机没有安装程序 git。接下来,我得弄明白如何安装 git 这个程序。
什么是“Git”
在搜索如何把 git 安装到 Mac OS X 时,我发现了 Heroku Toolbelt。[16] Heroku[17] 是一个网络应用服务器主机公司,主要为创建网络应用的开发人员提供更方便的服务。
Heroku Toolbelt 是一个程序,该程序主要负责安装程序员需要的一些常见软件开发工具,以便在 Heroku 上开发应用。Git[18] 就是那些程序中的一种。
我下载了安装包运行,然后收到一条“所有设置均已完成”的确认信息。于是,我又试了下该命令:
$ git clone git://github.com/sstephenson/ruby-build.git
然后我获得以下输出:
Cloning into ruby-build... remote: Counting objects: 1004, done. remote: Compressing objects: 100% (453/453), done. remote: Total 1004 (delta 490), reused 937 (delta 431) Receiving objects: 100% (1004/1004), 108.77 KiB, done. Resolving deltas: 100% (490/490), done.
成功!“done”是一个积极的信号。我没有收到任何错误消息,继续前行!
我继续输入剩下的命令。根据安装说明,接下来我要做的是下载必需文档。使用 echo 命令自动增加一些文本到我的计算机的配置文档当中,然后重启计算机的 SHELL 程序进行更新储存。SHELL 重启之后,rbenv 和 ruby-build 完全安装完毕。耶!
现在该安装 Ruby 的最新版本了:
$ rbenv install 1.9.3-p125
该程序自动下载 Ruby 源代码,然后进行生成。在这个过程当中,终端程式吐出数量惊人的滚动信息。(现在我开始觉得自己像一个真正的好莱坞程序员。)[19]
$ rbenv rehash
来自文件的该命令帮助计算机识别所安装的 Ruby 新版本:
$ rbenv global 1.9.3-p125
该命令将这台计算机上安装的 Ruby 默认版本设置为 1.9.3-p125。安装说明提示我运行这个命令来确认我的计算机正在使用最新的版本:
$ ruby -v
以下是我获得的输出:
ruby 1.9.3p125 (2012-02-16 revision 34643)[x8664- darwin11.3.0]
成功了!这就是根据安装说明所应该得出的结果。
根据该文件,如果我想要在这台计算机上安装 Ruby 新版本,我只需要再次运行 rbenv install、rbenv rehash 和 rbenv global。真够容易的!
这些命令在刚开始时似乎很吓人,但它们实际上非常简单。看起来毫无意义的数据只是缩写而已。一旦知道了这些缩写的意思,命令本身就很容易理解了。
记住,没有人天生就会这些东西。大多数时候,你需要做的只是花几分钟时间阅读这些文件,然后按照它们所说的去做就行了。[20]
程序库安装(gems)
既然现在已经安装好了 Ruby 最新版本,那也就该弄明白如何安装包括 Sinatra 在内的我所需要的程序库了。
Ruby 程序库叫作 gems,非常容易安装。安装 Sinatra gem 的命令如下:
$ gem install sinatra
Sinatra 的升级命令如下:
$ gem update sinatra
没有比这更容易的了,不是吗?
当然,在安装程序库之前,我要确保 gem 程序是最新版本的。鉴于我的电脑之前安装的是旧版本的 Ruby,看来相关软件也需要再次进行更新。
经过一番搜索之后,我找到了更新 Ruby gem 的程序:
$ gem update--system
这看起来相当容易。
在运行 gem 安装命令的同时,我注意到该命令还安装了别的 gems,比如 rack,rack-protection 和 tilt。我们把这样的程序库叫作“依赖项”。Sinatra 依赖它们运行,所以 gem 的安装命令会自动安装它们。
埋头读书
我即将运行 Ruby 应用,在这之前我决定阅读一下 Stack Overflow 强烈推荐的两本大众参考书:David A.Black 的 The Well-Grounded Rubyist(2009)和 Russ Olsen 的 Eloquent Ruby(2011)。两本书都是初级读本,主要介绍 Ruby 的常见概念和技术以及基本参考文献。
我也购买了 Alan Harris 和 Konstantin Hasse 的 Sinatra:Up and Running(2011)。这本书主要是对 Sinatra 框架的基本介绍。虽然 Sinatra 在网络上已经有很多可供查阅的资料,但是该书包含了很多例子,这就让人更加容易弄明白如何将 Sinatra 应用到常见任务当中。
在浏览书本的同时,我也发现了几个 Ruby 语法的参考网站:
●The Official Beginner’s Guide to Ruby[21]
●The Ruby Refresher[22]
●Ruby Security Reviewer’s Guide[23]
这些参考资料非常丰富,于是我决定花 90 分钟的时间快速浏览一下。
翻开书,先快速扫描目录和索引,然后记下重要术语、观点、重复出现的概念以及说明顺序。标题和侧边栏也在我的阅读范围之内。之后,我对网站也以同样的方式进行了预览。
除了学到变量、条件句、exceptions 函数和其他编程基础外,我还了解到 Ruby 是围绕两个核心进行创建的:对象和方法。
对象是“编程世界”的名词,是我们做事的目标。假设我想要在 Ruby 上创建一个叫作 firstname(名字)的新变量,并且让它包含我的名字。在 Ruby 上的命令好像就应该是这样:
firstname =“Josh”(名=“Josh”)
做起来相当容易,只需把“Josh”放到引号里,我正在告诉 Ruby,firstname(名)是一个字符串(string):一个字母数字序列。那就让 firstname(名)成为“字符串(string)”类的对象。(一个类别只是一个特定类型的带有某些特征的对象。)
字符串不是对象的唯一类别。以下是整数类的一个对象:
Million=1000000(100 万=1000000)
如果对象是编程中的名词,那么方法就是动词:它们就是让我们可以完成对象的东西。
假设我有两个包含我的名和姓的字符串对象:
firstname =“Josh”(名=“Josh”) lastname =“Kaufman”(姓=“Kaufman”)
我可以使用一个加号(+)来连接这些字符串,这是“将它们放在一起”的一个特别方法:
fullname = firstname + lastname(全名=名+姓)
考考你:fullname(全名)包含什么?如果你猜是“Josh Kaufman”,那你就错了。
记住,计算机只会完全按照你告诉它的去做。我们没有告诉计算机要在“Josh”和“kaufman”之间添加一个空格,所以它不会添加。它只会认为 fullname(全名)是“Josh Kaufman”。
如果我们想要纠正这个小错误,我们得修改代码,添加一个空格:
fullname = firstname + “ ”+ lastname(全名=名+“ ”+姓)
这个“+”是一种方法,该方法如何运作取决于使用的对象。如果我们把它应用到整数而不是字符串中,那么它的作用是加上而不是连接:
sum = million + million(总和 = 一百万 + 一百万)
sum(总和)等于多少?“2000000”
Ruby 内置的方法可以立刻帮你完成很多很酷的事情。假设我想看看我的全名反过来会是什么样子。不需要用手去弄或者编写我自己的小程序来反转那些字母,我只需要使用每个字符串对象都具有的反转功能:
fullname.reverse(全名.反转)
输出如下:namfuaKhsoJ
我还可以同时使用不止一种方法。如果我想要反转我名字里的字母,并且同时把所有的字母转化为小写,我可以这样运行:
fullname.reverse.downcase(全名.反转.所有字母转换成小写)
输出:namfuakhsoj
太简单了!
Ruby 代码很大一部分都似乎涉及对象、类别和方法、创建和控制。该语言有许多内置程度,而且 Ruby 极大程度地允许你随意创建、修正或取消对象、类别和方法,这也就赋予了该语言强大的功能性和灵活性。[24]
Ruby 的官方文件[25] 包含所有可用对象和方法的明细表。看一眼就有种崩溃的感觉,但是你无须全部使用它们。绝大多数现在都是可以忽略的。你只需在必要的时候选择使用。
该文件还有另外一个优点:当你试图运行 Ruby 无法理解的事情,显示的错误消息会告诉你哪个程序出了问题。
假设我们要运行这个程序:
animal = “Zebra”(动物=“斑马”) number = 7(数字=7) puts animal + number(字符串输出 动物+数字)
命令 puts(字符串输出)是表示 print(打印输出)的另一种方式。我们只是想要程序显示它认为 animal(动物)+number(数字)的意思是什么。
当我运行这个程序的时候,输出结果如下:
TypeError: can’t convert Fixnum into String from program.
(错误类型:无法将固定数字从程序转化为字符串)
rb:3:in ‘+’
在非计算机语言中,你不可以使用算术来把一个数字加上一个单词从而使其在某种程度上存在合理性,所以计算机显示了一条错误消息。这就好像用某个数字去除以 0,你无法做到,故程序停止运行。
为了解决这一问题,我们需要把数字转化为字符串,以便“+”法连接两个变量,而不是通过算术或者修正这个程序去做其他事情。
修正后的程序如下:
animal = “Zebra”(动物=“斑马”) number = 7.to_s(数字=7.to_s) puts animal + number(字符串输出 动物+数字)
当我们运行这个程序时,得到输出“Zebra7”(“斑马 7”)。内置的方法.to_s 将数字 7 转换为一个字符串,所以 Ruby 可以使用连接。
我们也可以做完全不同的事情,比如:
animal = “Zebra”(动物=“斑马”) number = 7(数字=7) number.times { puts “#{animal}” }(数字.次数{字符串输出 “#{动物}”})
输出如下:
Zebra Zebra Zebra Zebra Zebra Zebra Zebra
我们只使用了一个内置于 Ruby 的基本条件循环:数字.次数的意思是“执行这个程序 X 次,这里的 X 等于数字变量值。”如果我们改变动物或数字的值,输出也将改变。(是的,你可以通过修改这个程序来输出“wombat<袋熊>”10 亿次,如果你真想那么干的话。)
注释和调试
我在阅读的过程中还发现了 Ruby 的另一个基本特征:注释。每次当你在一行程序的开头标上#(经常被称为“磅字符”,或者比较不常用的“散列字符”),Ruby 就会将那一行解读为注释并跳过它。
在一条程序当中加入注释可以让它变得更容易跟进,因为你可以使用简明的语言来描述你想做的事。以下是注释到我的“Animal Print”(“动物打印输出”)程序当中的样式:
# Assign variables(#变量赋值) animal = “Wombat”(动物=“袋熊”) number = 1000000000(数字=1000000000) # Print loop(#打印输出循环) number.times { puts “#{animal}” }(数字.次数{字符串输出“#{动物}”})
注释也是基本的故障排除方法:你可以一次性注释掉几行代码来隔离问题或错误。结合置于适当地点的 print(打印输出)或 puts(字符串输出)语句,你可以逐步跟进程序运行,确保一切都在预料之中。
经过总共 8 个小时的研究和安装,我现在运行着最新版本的 Ruby。我可以安装我需要的任何程序库,对 Ruby 程序的工作原理我也有了一个基本的了解。
值得注意的是,我实际上还没有进行过任何编程的操作。到目前为止,我一直做的只是研究,安装 Ruby 以及编写 Ruby 程序会是什么样子呢?
让我们探究更加复杂的程序。
交互式 Ruby(IRB)的检验
在研究 Stack Overflow 的过程当中,我发现了一个由 Rob Sobers 和 Zed Shaw 编写的一本叫《笨方法学 Ruby》[26] 的教程。该教程提供了简单的 Ruby 程序例子,要求你修正和运行,看看它们产生特定的结果,以此来学习 Ruby 是怎样运行的。如果你没有得到正确的结果,那么你的任务就是修正这个程序,直至你获得预期的结果。
这个“代码、测试、运行和调试”方法就是一种快速反馈循环的很好的例子。当你运行一个程序的时候,计算机会在几毫秒内告诉你它是否可以正常运行。如果你的代码存在缺陷,你可以修正它,接着再运行一次,在一分钟内测试几个变量。
《笨方法学 Ruby》的前几章涉及 Ruby 的设置、基础文本编辑程序的安装以及如何运用 IRB(在计算机上运行 Ruby 程序的一个程序)。
它是这样工作的:你把程序输入一个文本编辑器中,然后将其保存到文档(让我们把该文档的名称设定为 program.rb.)。当你准备运行该程序的时候,把以下命令输入终端:
$ irb program.rb
IRB 会运行这个程序并提供结果给你。它还会向你展示计算机达到那个结果的步骤,这对于调试非常有用。如果程序不正确,IRB 会显示详细的错误信息。
《笨方法学 Ruby》以变量赋值、基本算术、字符串操作以及基本条件语句作为开始,这些与我之前提到的例子类似。对于基础知识的学习,它是一种非常具有结构性和逻辑性的方法。
在尝试编写我的第一个“真正意义上”的程序之前,我原本的计划是先读完 Eloquent Ruby 和 The Well-Grounded Rubyist,然而完成《笨方法学 Ruby》里面的所有练习。虽然只有 10 课,然而,我注意到了一件重要的事情:我越来越焦躁不安,而且正在失去兴趣。
我好像正在复制另外一个人创建的程序,并且正在解决另外一个人所定义的问题。问题来了:编程开始变得像是一种学术活动,而不是一项有用的技能。我需要跳出研究模式,进入实施模式。
在开始编程之前,我不需要读完所有的书籍、课本、教程和其他我已经收集的资料。我需要立即开始编写真正的程序,如果遇到任何问题,再参考我准备的资源。
是时候行动了……
应用#1:Sinatra 的静态网站
对于我的第一个网络应用,我已经有了一个想法:服务于基本 HTML 网站的 Sinatra 应用。以下是这个应用的目标:
1.创建一个 Sinatra 基本工作应用,它可以把简单的网站传输给终端用户(读者)。
2.在计算机上测试该应用,以确保其可以正常运行。
3.将这个应用部署到 Heroku 的生产中,让它“活起来”,以便可以被真正的读者使用。
就这样,没有花哨的功能,只是一个可以在公共服务器上运行的一个非常简单的 Sinatra 程序。
那么我该从哪里开始呢?让我们回顾一下在第二章总结过的学习备忘录:
●选择方向
●集中精力
●制定目标
●分解技能
●获得工具
●扫除障碍
●腾出时间
●及时反馈
●计时训练
●数量速度
我已经有了一个明确定义的目标,也解构好了该项技能,并且我知道这个程序完成之后会是什么样子。接下来该说说关键工具,完成这个项目是否需要一些我还没有的工具?
是的,我还差一个 Heroku 账号。这很容易解决,我只需访问 Heroku.com ,注册并验证我的电子邮件地址,然后创建密码。
由于我下载了 Heroku Toolbelt(我用来安装 git 的程序),Heroku gem 已经在我的计算机里面,所以这方面也没问题了。
根据说明提示,我还需要做最后一件事情来让我的计算机能够与 Heroku 交谈,生成叫作“SSH 密匙”的东西,一份类似密码的特殊文档。一旦有了密匙,将它上传到 Heroku,以便系统可以识别我的计算机并授予访问权限。
幸运的是,Heroku 对于如何做这个有详细的讲解。[27] 我运行这个命令来生成密匙:
$ ssh-keygen -t rsa
……将这个命令输入 Heroku:
$ heroku login
……然后这个命令把密匙添加到我的 Heroku 账户:
$ heroku keys:add
太好了,我登录进去了。现在我该如何编写应用呢?
基本应用的创建
浏览一下 Heroku 的文件。很好,有两个提示看起来很有用。
●“在 Heroku 上开始 Ruby。”[28]
●“部署基于架构的应用。”[29]
看来我需要这样做:
1.在我的计算机上创建程序文档;
2.把它们增加到“git 仓库”(不管它是什么……);
3.使用命令 git push heroku master 把完成的应用发送到 Heroku。
幸运的是,Heroku 的讲解里有一个关于 Sinatra 应用的例子!这比我想象的还要容易……
我在计算机上创建了一个“根”文件夹。程序中的每个文档都会储存在这里。
接着我打开了我的文本编辑器(我使用的是 TextMate[30] ),并根据以下指引创建了三个文档:
application.rb config.ru Gemfile
这个程序的核心在 application.rb。Ruby 应用总是以.rb 结尾。
config.ru 用于设定架构的配置。记住,Sinatra 是在架构的基础上创建,所以拥有单独的一个配置文档也有其道理。“架构”档案以.ru 结尾。
Gemfile 是定义程序使用哪个 gems(Ruby 程序库)的地方。你的程序将会只有一个 Gemfile,所以它一直被称为“Gemfile”。似乎相当简单。
文档的创建完成之后,Heroku 文件建议编写一个基本的“你好,世界!”程序来测试安装程序。application.rb 的内容如下:
require ‘sinatra’(要求‘sinatra’) get ‘/’ do (到达‘/’执行) “Hello World!”(你好,世界!) end (结束)
配置.ru 的内容如下:
require ‘./application.rb’(要求‘./应用.rb’) run Sinatra::Application(运行 Sinatra::应用)
Gemfile 的内容如下:
source ‘http://rubygems.org’(资源‘http://rubygems.org’) gem ‘sinatra’(Ruby 程序库‘sinatra’)
代码不是非常多,很容易理解。
●Gemfile 命令服务器把用于运行程序的 Sinatra gem 包含在内。这个 gem(Ruby 程序库)可以从 RubyGems.org 中下载。
●config.ru 设置主应用,然后执行该程序。
●一旦该程序开始运行,无论何时有人访问“/”(代表网站主页的一种速记方法),它都会显示“你好,世界”。
它真有那么容易吗?
讲解告诉我还有一件事要做:将文档储存到 git 仓储(有时候简称为“repo”)。我不是很确定那代表什么意思,但是我知道我已经安装 git,而且它们提供了如下命令:
$ git init . $ git add -A $ git commit -m “Initial Commit”
第一个命令在现有根文件夹当中创建了一个新的 git 仓库。命令$git add–A 表示把文件夹里的所有文档添加到 git 仓库,随附一条来自程序员并详细描述正在提交的是什么东西的信息。(我不太清楚“add(添加)”和“commit(提交)”之间的差别,所以我做了注释,以便迟一些对它们进行研究。)
输入这些命令之后,计算机的输出如下:
[master (root-commit) 8ed1099] Initial commit 3 files changed, 9 insertions(+), 0 deletions(-) create mode 100644 Gemfile create mode 100644 application.rb create mode 100644 config.ru
似乎它可以正常运行。还有一件事:在 Heroku 创建一个新的服务器,然后把我的程序“推送”到那个服务器。
我运行以下命令来创建这个服务器:
$ heroku create
然后我得到以下响应:
Creating shielded-springs-2049....done, stack is stack is bamboo-ree-1.8.7 http://shielded-springs-2049.heroku.com/ | git@heroku. com:shielded-springs-2049.git Git remote heroku added
成功了!“Stack”对该服务器运行的软件进行了详细的描述,并且这条信息给了我这个服务器的公共 URL(统一资源定位器)。
这是最终命令:
$ git push heroku master
如果一切顺利,我将会正式地将我的第一个应用推送到 Heroku 的生产网站。
这是我得到的输出:
Heroku receiving push Ruby/Sinatra app detected Gemfile detected, running Bundler version 1.0.7 Unresolved dependencies detected; Installing..... Using--without development:test ! Gemfile.lock will soon be required ! Check Gemfile.lock into git with `git add Gemfile.lock` ! See http://devcenter.heroku.com/articles/bundler Fetching source index for http://rubygems.org/ Installing rack (1.4.1) Installing rack-protection (1.2.0) Installing tilt (1.3.3) Installing sinatra (1.3.3) Using bundler (1.0.7) Your bundle is complete! It was installed into ./.bundle/gems/ Compiled slug size: 500K Launching.....done, v4 http://shielded-springs-2049.heroku.com deployed to Heroku
现在,最激动人心的时刻到了……我打开一个网络浏览器,导航到 http://shielded-springs–2049.heroku.com ,此时我看到以下内容:
“Hello,World!”(“你好,世界!”)
胜利!
警告,警告
这个程序可以正常运行,但是同时我也收到了一条警告消息。什么是 Gemfile.lock?
Heroku 的 Ruby 文件显示系统使用了一种叫作 bundler[31] 的程序库来把 gems(Ruby 程序库)安装到 Heroku。它是一种 gem,所以我可以通过以下命令来实现本地安装:
$ gem install bundler
Bundler 是必需的,因为 Heroku 不会默认安装任何 gems(Ruby 程序库)到你的应用。Heroku 会给我一些访问权限,但是其所给的水平绝对跟我在自己计算机上的权限不同,所以我无法直接在我的账户当中运行 gem install sinatra。
不是给我(或任何其他用户)处于危险级别的系统访问权限,相反,Heroku 使用 bundler 来安装 Gemflie 里面所指定的 gems。如果你已经确定你的应用需要安装的 gems(Ruby 程序库),请在你的计算机上运行以下命令:
$ bundle install
这个命令在你的程序当中创建了一个名为 Gemfile.lock 的新文档。当你上传文档到 Heroku 的时候,系统会测试 Gemfile 和 Gemfile.lock,确认它们是否安全,然后下载 gems(Ruby 程序库),并安装。
当我推送程序到 Heroku 的时候,如果你看着输出,你就可以看到系统会自动安装 bundler。没有错误消息提示,相反,Heroku 的工程师在程序当中增加了一条 exception 函数,以便可以自动安装程序并且显示一个警告,而不是瘫痪掉。
系统这次可以正常运行,但是以后我在推送应用之前就得把 Gemfile.lock 添加到 git 程序库。幸好现在知道了!
Sinatra 走上舞台
既然我的简单应用已经启动并运行,我终于可以开始学习 Sinatra 是如何工作的了。Sinatra 的资料[32] 非常全面,而且有很多例子,所以我决定从它开始学习。
Sinatra 应用的核心叫作路径(route)。理解它的最好方式是多看例子。
基本的 Sinatra 应用具有一个单一路径,它包含小型网站的“root(根)”。因特网用户通常将网站的根视为一个网站的主页。
如果你访问 google.com 或 yahoo.com ,你的网络浏览器会发送一个请求到 Google(谷歌)或者 Yahoo(雅虎)的服务器。这个请求叫作 GET 请求,而且它会叫服务器向你展示其根目录下的全部内容。计算机用于发送该请求的协议或格式叫作 HTTP,它的意思是“超文本传输协议”,也就是你经常在网址开头看到的 http://的意思。
GET 是 HTTP 请求的最常见类型,但它不是唯一的类型。这里有三个额外的 HTTP“动词”:
- POST-发送资源到服务器
- PUT-更新服务器资源
- DELETE-删除服务器资源
如果你曾经在一个网站上推送过一条公开评论,你的俏皮话通过使用一个 PUT 命令来发送到服务器。如果你出错了并需要编辑评论,你的更新也是通过一个 PUT 命令来发送的。如果你认为这个评论非常愚蠢并且选择删除它,此时浏览器就会发送一个 DELETE 命令。
包含 GET、POST、PUT 和 DELETE 的 routes 是 Sinatra 应用工作的核心。你创建的每个路径都是条件语句:“如果在路径 Y 上接收到一个 GET/POST/PUT/DELETE 命令,则执行 X。”
Sinatra 路径还可以包含变量,它们被称为参数。Sinatra 应用通常使用参数作为每个路径所包含的流程的输入。
让我们修正一下我们简单的 Sinatra“Hello,World!(你好,世界!)”应用,以便用名字称呼我们的用户。以下就是一个可以对其进行修正的路径:
get ‘/hello/:name’ do “Hello, #{params[:name]}!” end
你可以通过链接 http://first20hours.com/hello/name 看到实际效果。随意将“name(姓名)”替换成你的“name(姓名)”。真有效果!
这个应用允许服务器获取路径上在“name(姓名)”部分的内容,然后将其使用到应用当中。路径上的这个命令就是将“name(姓名)”参数展示给用户的一条简单指令。
Sinatra 允许你命名参数(比如姓名),同时它也有一个可以包含任何东西的“wildcard(通配符)”参数(也称为“splat”)。在我们修正后的“Hello(你好)”应用当中,它的用法如下:
get ‘/hello/*’ do “Hello, #{params[:splat]}!” end
这很酷。在命名参数和通配符之间,你可以创建一些非常智能化的路径。路径的创建方式决定了你的 Sinatra 应用的工作方式。
搞清楚如何编写能够让我达到目标的程序,我想这些资料已经足够。由于 Jekyll 创建的是应用作为对用户的 GET 请求的响应而发送的实际文档,我需要做的就是编写好路径,这些路径可以接受请求并在系统中找到正确的文档发送给读者。
根据 Jekyll 的资料,该程序会把已完成的网页放到根目录当中一个叫作“site(站点)”的文件夹里面。通向那张页面的路径由 Jekyll 自动生成。如果想要我们的网站的 About 页面出现在 http://example.com/about ,我们需要在 Jekyll 文档当中设置一个/about 路径,然后这个程序会在我们网站的根文件夹当中创建一个文档_site/about/index.html。
也就是说,为了响应用户的 GET 请求,我必须使用 Sinatra 创建一个新的路径来读取文档。创建完成之后的路径如下:
# Index handler get ‘/?’ do ## File.read(“_site/index.html”) end # Post handler get ‘/*/?’ do ## File.read(“_site/#{params[:splat]}/index.html”) end
File.read(“”)是 Ruby 的内置命令。File 是一个对象,而.read 是一种方法。它的使用相当直接:相对于应用的根文件夹,放置到(“”)里面的内容就是你要程序读取的文档。非常简单。
如果万一这个文档不存在呢?那就会触发一个 exception 函数。Sinatra 具有两个内置的基本错误路径:not_found(未发现)和 error(错误)。让两条路径返回相同的错误页面:
not_found do ## File.read(“_site/error/index.html”) end error do File.read(“_site/error/index.html”) end
所有其他东西保持原样。我不打算对 config.ru 或者我们的 Gemfile 做出任何更改。我只是增加新的路径到程序的 Git 仓库,提交更改,然后将更新后的程序推送到 Heroku。完成!
是否想看我们更新后的程序执行起来的实际效果?访问 Personal MBA.com :这个站点现在运行 Jekyll,而不是使用这个程序的 WordPress。通过使用一个叫作 seige 的加载测试程序,我的网站现在可以毫无压力地同时服务超过 2000 名的读者。大多数页面请求的发送时间在 18~25 毫秒之间,所以我的站点现在不会再次陷入因重压而崩溃的局面。
我的第一个工作网络应用是完整的。它花了我大概一个小时的时间去弄明白如何将我的网站信息和设计从 WordPress 转移到 Jekyll。
总共完成时间大约为 10 小时,其中包括我花在研究和编程概念回顾上的时间。还不赖!
应用#2:Codex-个人笔记本数据库
我的第一个应用工具表现得非常好。它非常简单有用。较少的运动部件意味着较少的瘫痪程序。
让我们看一些更加复杂的东西。
还记得我们之前讨论的数据库吗?基础网页不能自我更新,因为它们无法储存信息。第一个应用之所以那么有用是因为文档是静态的,它们不会改变。文档的任何改变都是通过 Jekyll 实现的,它是一个独立的程序。这个应用快速而稳定,因为它不依赖于数据库。
那么使用数据库的应用呢?数据库一般是网络应用的一大部分,所以我需要了解它们是如何工作的。为了学习它们的工作方式,我必须从一个依赖于数据库的项目学起。
Backpack[33] 是我每天使用的应用之一,它由 37signals 开发。Backpack 的主要优点是可以创建包含很多东西的“页面”:文本、明细表、图像和文档等。当你在 Backpack 的一个页面储存信息的时候,你稍后就可以在任何计算机上访问到它,因为所有的信息都已经储存在应用的数据库当中。
我想知道,我自己可以创建一些类似的东西吗?值得一试……但从何开始呢?
在研究 Jekyll 的时候,我读到了一篇由 Tom Preston-Werner(创建 Jekyll 的程序员)发表的叫作《自述文件驱动开发》(Readme Driven Development)[34] 的文章。有别于每几年就会轰动软件业一次的项目管理技巧,该文章旨在说明创建一个应用的最好方式,即做其他任何事情之前,编写一个自述文件。
一份自述文件就是和代码一起被程序员放到应用的根文件夹里的文档。该文档包含如何安装、设置以及使用程序等信息。
自述文件是非常重要的,因为许多程序都无法自我解释。一般而言,如果没有一些参考资料的话,那么要想弄明白如何使用一个程序就显得非常困难。挖掘代码并自己弄懂它的效率,不如阅读附有详细讲解的由原程序员编写的文件。
Tom 认为,在开始编程之前,你最好先写程序的自述文件。大多数程序员都是先编程,然后再写自述文件,那将错失良机。先写自述文件可以帮你弄清楚这个程序将会如何运作。与其说自述文件是一个资料工具,不如说它更像一个设计工具。
我觉得有道理。在编写《在家就能读 MBA》(The Personal MBA)的过程中,我学到的其中一项产品开发技术就是先写销售文案,然后再进行营销,而不是先营销,然后再写销售文案。如果弄懂潜在买家的需求并将之整合到销售文案中,那么你会更加了解怎样的产品才可以吸引客户。市场研究能够反映营销的开发。
我在笔记本上记录好了我的应用应该完成的任务,以及这个应用所要达到的质量水平,具体内容如下。
- 这个程序是一个简单的参考和笔记应用。
- 该应用为独立的用户设计。
- 该应用使用 Sinatra 和一个数据库创建、保存、更新以及删除页面记录。
- 该应用允许用户创建具有花俏格式(如粗体、斜体和标题等)的页面。
- 该应用要求密码访问,并尽可能安全地把信息储存在数据库当中。
- 该应用程序看起来不错。
- 该应用可以很容易地部署到 Heroku 或者另一个类似的主机当中。
我打算把这个应用称为 Codex,术语“书本”的旧称,因为这个应用的主要功能就是储存参考资料和清单等。
这类应用的网络编程的专门术语是“CRUD”,它代表创建、读取、更新和删除。值得一提的是,这些功能和 GET、POST、PUT 以及 DELETE 基本是一致的,所以创建这类应用当然有可能使用 Sinatra 路径。最大的不同之处在于数据库的引进。
Heroku 有哪些可供选择的数据库类别?我不知道。这要回到资料文件当中去。
在默认模式下,Heroku 使用一种叫作 Postgres 的数据库。[35] 每一个新的应用都会默认得到一个小型的数据库。那对我很有用,但是该如何使用它呢?在计算机里又该使用什么工具来测试这些程序呢?
进入 DataMapper
为了回答这些问题,我决定搜索 Stack Overflow。现在的共识就是使用一个叫作 DataMapper[36] 的程序库让这类开发变得更加容易。
DataMapper 是一种叫作“对象关系映射”的程序,通常缩写为 ORM。[37] ORMs 能够为程序员解决一个迫在眉睫的问题:数据库经常使用它们自己的语言,这和程序员用来创建应用的语言有所不同。最常见的数据库语言叫 SQL,[38] 但同时也有数百种其他语言。
假设我们是 Amazon.com 的一名程序员,我们想展示哈利波特系列的作者 J.K.罗琳的书籍清单。这个 SQL 命令可能如下:
SELECT * FROM Book WHERE author = “J.K. Rowling” ORDER BY title;
这个命令会检索书籍数据库当中含有 J.K.罗琳的所有记录,然后按标题字母顺序将它们列出。
不幸的是,让 SQL 或任何其他数据库查询语言与像 Ruby 这样的语言很好的契合是非常不容易的。使用一种语言进行编程已经很困难了,更别提同时使用几种语言了。
这就是 ORMs 产生的原因:它们允许程序员使用一种语言编程,然后用 ORM 将它们转换为数据库的语言。这就简单多了。
DataMapper 是一种程序库,它让与使用 Ruby 的数据库的沟通变得更加容易。在默认情况下,DataMapper 提供了许多有用的功能用于创建、读取、更新和删除数据库记录。因为 DataMapper 的面世已经有一段时间,而且经过了全面测试,所以在大多数情况下,它比你自己编写的数据库代码更加可靠。
DataMapper 以一种程序的样式存在,它是这样安装的:
$ gem install data_mapper
虽然 DataMapper 是一个如此巨大的程序库,但是它也可以一块一块地进行安装。那就是一种叫作“模块化”的概念,而且它是好的编程的标志。以下就是安装所有个体程序库的命令:
$ gem install dm-core dm-aggregates dm-constraints dm-migrations dm-transactions dm-serializer dm-timestamps dm -validations dm-types
不用安装整个程序库,你只需要安装你的程序将会用到的部分,这样就更加高效了。
使用 DataMapper
既然已经安装 DataMapper,我必须得弄懂如何使用它,以便于:(1)与一个数据库交谈;(2)设置这个数据库,让它可以储存并检索我所需要的信息。
基于 Heroku 的 Postgres 文件,以下命令可以让我的 Sinatra 应用与数据库交谈:
DataMapper.setup(:default, ENV[‘DATABASE_URL’] || “sqlite3://#{Dir.pwd}/database.db”)
在这种情况下,||是表达“或者”的另一种方式。ENV[‘DATABASEURL’]是 Heroku 用来表示应用的数据库的变量。如果那个数据库不存在,它将会使用第二个选项,一个叫作 Sqlite 的数据库。[39]
Sqlite 默认安装在 Mac 计算机上,所以它可以随时使用。DataMapper 可以与 Postgres 和 Sqlite 交谈,如果我安装了这两个 Ruby 程序库的话:
$ gem install dm-sqlite-adapter dm-postgres-adapter
这就意味着,当我的应用在 Heroku 运行的时候,它使用的是 Postgres,而当它在计算机上运行的时候,它使用的是 Sqlite。无论使用哪种方式,我的代码都是一样的,虽然我的数据库说着不同的语言。这真是太酷了!
接下来,我该如何在我的计算机上运行这个应用呢?
Pow 函数
我在 Stack Overflow 和 Hacker News 上搜索如何在计算机上运行这种类型的应用的信息。幸运的是,有几个选项。当我输入一个命令到终端的时候,我似乎可以安装运行这个应用的程序库(比如 Foreman 或者 Shotgun),或者我可以安装一个让这个程序一直保持运行的程序。
程序采用的第二个方法是 Pow 函数,[40] Mac OS X 的一种零配置架构服务器。这个站点能够在一分钟内在我的计算机上设置本地开发主机。正合我意!
安装 Pow 函数需要 10 秒钟:它需要一个终端命令来下载和安装这个应用。一旦安装,你可以运行一个命令把你的程序连接到 Pow 函数,然后你就可以在计算机上运行 Pow 函数。
有一个叫作 Powder[41] 的 Ruby 程序可以让这个流程更加容易:
$ gem install powder
安装这个 Ruby 程序之后,你运行它来安装 Pow 函数:
$ powder install
然后你进入你的应用的根目录,并输入这个命令:
$ powder link
就是它了。我的根目录叫作“codex”,所以我的应用现在可以通过我的个人电脑在 http://codex.dev 上运行了,而且我还可以测试我的作品。
如果我进行了变更,这个命令可以重启程序:
$ powder restart
非常容易!我现在准备开始创建。每天晚上我都留出一个半小时,而且我还会继续,直至完成。
编码、测试和修改
在这一点上,我打算描述我在做什么以及我是怎么做的。如果你想跟进的话,你可以在 https://github.com/first20hours/codex 看到完整的代码。
当这个应用完成之后,我想要的效果如下:
你将会注意到,这个设计共有三个部分:顶部的导航栏、主要内容区域以及侧边栏。我使用由推特开发人员 Mark Otto 和 Jacob Thornton 创建的引导程序(Bootstrap)[42] 把这些基本设计放在一起。
无须从头开始创建一个网络设计,引导程序(Bootstrap)是一个预先编写的免费使用的 HTML 和 CSS 程序库。使用引导程序(Bootstrap)可以节省大量的时间:你可以几分钟内把你想要的应用程序的基本原型放在一起,无须花费数天时间。
这个应用的基本单位是“页面”,它们可以显示储存在数据库的记录。页面的信息显示在主内容区。有一个按钮可以引导到显示数据库所有页面明细的屏幕。它的底部有两个按钮,第一个按钮让你可以编辑当前的页面,然后第二个按钮让你可以删除它。
侧边栏主要包含三个部分。首先,它的顶部有一个让你可以创建一个新页面的表格,这个页面需要标题。其次,它的侧边栏有一个用户增加的页面列表,其功能是作为快速参考部分。再次,它有一个格式参考,可以帮助用户记住如何使用常见的格式功能。
顶部的导航栏非常简单。它包含到达主页的链接以及到达“页面列表”屏幕的次级链接。如果我想的话,我迟一些还可以在导航栏上添加项目,但是我现在真正需要的就那些。
每个网络应用都有一个主页,所以我需要决定哪个主页应该包含什么内容。在这种情况下,我只想要显示数据库的主要记录。
所以一个页面应该包含什么呢?由于每个页面都是一个数据库记录,而记录拥有包含实际信息的领域,所以我需要告诉 DataMapper 应该建立哪些领域。代码如下:
class Page include DataMapper::Resource property :id, Serial property :title, String property :content, Text property :lastupdated, DateTime end DataMapper.finalize
这个代码使用 DataMapper 创建一种叫作页面的新型对象。Ruby 现在可以使用一个像任何其他对象那样的页面,并且我可以创建和使用建立、修正和删除页面的方法。当我对一个页面作出变更的时候,那个变更就会通过 DataMapper 储存到数据库当中。
如果这些领域不存在的话,那么命令 Datamapper.finalize 可以告诉应用在实际数据库里面建立它们。
既然数据库已经建立领域,现在该弄明白 Sinatra 将服务于哪些路径了。基于直到目前为止我所知道的,我的列表如下:
# Show home page get ‘/’ # Creates new note from “new page” form post ‘/’ # Displays requested note get ‘/:url/’ # Edits requested note get ‘/:url/edit’ # Saves user edits to note post ‘/:url/edit’ # Deletes specified note delete ‘/:url/’ # List all pages in database get ‘/all/’ # Error handling not_found error
这是一份非常好的列表。我的应用将会围绕我为每条路径所创建的命令。
个性化需求,到处都是个性化需求
是否还记得我之前提到过数据库有点像一堆魔术索引卡片,你可以使用任何一种方式搜索到它们?我们需要一种搜索特定页面记录的方法,这就是为什么你在这些路径当中看到 url 的原因。该 url 参数的内容就是告诉数据库需要检索什么记录。
我们可以使用网页标题作为参数,但是这样做会有一个问题:我们不喜欢网址当中存在空格、字母和特殊符号($和%)之类的东西。网页标题可能会含有那些东西,所以你需要一种将它们摒除的方法。
网页的唯一标识符就是其中一种个性化需求(Slug)。[43] 我的网页个性化需求(Slug)将会基于网页标题,而且根据以下规则使网络地址变得友好:
- 所有字母都是小写;
- 没有特殊字符——都是字母;
- 没有空格——所有空格都必须替换成破折号。
达到以上效果的方式就是创建一个接受网页标题的方法,然后将其转换成个性化需求(Slug)格式。棘手的代用必须由一种叫作正规表示式(regular expressions)的编程进行处理,它可以基于既定的规则在字符串内转换或查找文本。[44]
正规表示式(regular expressions)可以非常神秘,但这是一种常见的用法,所以我能够找到一个标准的例子。我的方法如下:
# Converts page name into post slug def slugify(content)content.downcase.gsub(/ /, ‘-‘).gsub(/[^a-z0-9-]/, “).squeeze(‘-‘) end
现在我可以使用个性化方法转换一个字符串,比如说将“Page-Title”转换成“page-title”,让它适合用于网址。
另外,如果我们连同网页标题一起储存个性化需求(Slug),我们可以使用:url 参数来检索页面记录。
我把这个领域增加到 DataMapper 类别当中:
property :slug, String
现在,无论何时创建一个网页,我们都可以“个性化”这个网页的标题,将其储存到数据库当中,当想要检索它的时候,我们可以使用它来显示页面。这就是应用程序知道应该展示哪个页面的方法。
创建页面
我从确定必需路径的明细表开始。“home(主页)”的路径非常简单:我将它重新导向/home(主页)/个性化需求,因为我想要主页显示主记录。
“创建页面”路径连接侧边栏顶部的小表格。用户在表格当中输入网页标题,然后点击按钮。系统收到网页标题之后会将其个性化,然后在数据库当中保存标题、个性化需求以及创建时间。之后它会发送一个包含该个性化需求的 GET 请求以显示新的页面。
在“创建网页”的路径当中,有一个微小的细节相当重要:如果这个网页已经存在了呢?如果这个网页已经含有数据的话,我不想将其覆盖。意外的数据丢失是让人无法接受的。
幸运的是,DataMapper 使用内置的方法.first_or_create 解决了这一问题。在创建页面之前,DataMapper 会首先检查它是否已经存在。如果页面存在,DataMapper 不会覆盖它,而且 Sinatra 会将浏览器重新导向已经存在的页面。问题解决。
“显示页面”路径读取 URL(统一资源定位器)的个性化需求(Slug),从数据库当中检索记录,然后在主内容区域显示信息。迟一些我会增加一些花哨的格式,但现在我只想要它可以工作。
编辑页面
编辑一个页面涉及两条独立的路径。第一条路径导向用户想要编辑的页面,然后将页面记录的内容显示在用户可以编辑的表格内。
为了显示这些页面,应用依赖于一种叫作 ERB 的模板语法,它基本上是 HTML+Ruby 命令。ERB 让程序员可以编写包含可更改元素的 HTML。因为 ERB 是在页面显示给用户之前对其进行加工,所以每一次加载页面的时候它都可以基于模板里面的 Ruby 命令来更改页面的文本。
编辑屏幕的储存按钮发送一个 POST 请求到升级页面记录的应用当中。
删除页面
删除页面需要注意一些事项:记住,意外的数据丢失是让人无法接受的。如果你打算删除一个页面,你必须百分百确定用户实际上确实想删除那个页面。
删除页面的错误方式就是将删除按钮直接连接到应用的 DELETE 路径,因为这种做法会删除页面,即使是因为用户不小心点击到删除按钮了。
更好的策略是使用一种两相流程。按下一个页面上的删除按钮会将客户导向一个确认屏幕,这个屏幕显示了用户想要删除的网页标题。如果用户想要继续删除,他们可以点击确认按钮,然后这个按钮会发送 DELETE 请求。如果用户不小心点击到删除按钮,他们可以点击取消或浏览器上的返回按钮。
页面明细表
“页面明细表”将用户导向/all/个性化需求,这跟常规页面不同。
不是检索单一的记录,DataMapper 检索的是数据库中所有的页面记录。页面的 ERB 模板包含一种条件循环,这个循环可以为检索到的页面创建一份列表项。每个项目均包含页面标题,标题以链接的形式显示,并且包含那个页面的相关个性化需求。点击该链接可以将用户导向该个体页面记录。
第一次启动应用程序
我已经设置好基本功能,但是我又遇到一个问题:当我通过访问主页来测试这个应用程序的时候,我立即收到一条错误消息。该程序试图在数据库当中寻找主记录,但是它不存在,而我刚刚才开启了这个应用。
这个问题的解决方案就是使用一个叫作 Rake 的程序创建一个“一次性行政流程”。Rake 程序储存在 Rakefile,它位于应用的根目录当中。
Rakefile 就像常规 Ruby 应用一样工作,只有一个例外事项:它们存在于你的核心程序之外,而且你必须得手动运行命令。
在某些情况下,Rake 非常有用,比如说在正式运行实际程序之前增加默认信息到数据库当中。我将重要的文件 application.rb 复制到 Rakefile,然后输入一个命令,这个命令能够在数据库当中创建一个新的主页。之后我需要做的就是立即运行这个命令:
$ rake setup
Rake 创建主页记录之后,我的应用在启动的时候就不再提示发生错误了。当我把这个应用推送到 Heroku 之后,在我使用该应用之前,我将会远程运行 Rake 命令来设置数据库。
到这个时候,我已经设置好所有的主要功能。现在该加入一些有趣的东西了。
添加侧边栏支持工具
我刚刚意识到我没有依照原先的意图将增加页面到侧边栏的方法包含在内。这类功能需要一种叫作 Boolean 的数据类型,因为它只有两个值:页面在或不在侧边栏中显示。
我将这种数据增加到了 DataMapper 当中:
property :sidebar, Boolean, :default => false
我还在“是否添加侧边栏?”(连接到数据库的侧边栏领域)的旁边增加了一个复选框来“编辑”屏幕。然后我编写了一个简单的循环来搜索数据库的记录,如果:sidebar=true,那么以列表形式显示它们,跟“列出所有项目”页面相似。
我重启该应用,编辑一个记录,然后整个应用瘫痪了。哎呀!
我尝试了一遍又一遍去弄明白到底哪里出错了,但是我始终未能成功。在结合 DataMapper 的资料和搜索 Stack Overflow 之后,我发现以这种方式使用 Boolean 变量不能很好地配合 HTML 格式。所以需要另外一条命令:
property :sidebar, Enum[ :yes, :no ], :default => :no
这基本上是做同样一件事的另外一种方式。代表“enumerate(列举)”的 Enum 创建了一个选项列表,而且这个表格设定了哪些选项应该储存到数据库当中。
添加 Markdown 支持工具
我现在想确保我的网页可以包含有趣的格式,像斜体、粗体文本和标题。
我已经选择 Markdown 作为格式语法,它是一种由 John Gruber[45] 创建的受欢迎和非常有用的小标记语言。作为用户,我已经熟悉 Markdown 的工作方式,因为我的计算机里面的一些应用程序有使用到这种语言。
稍微进行搜索之后,我发现 Markdown 有几个 Ruby 程序库。我选择了 rdiscount 程序库,而且我还将它应用到 application.rb:
require ‘rdiscount’
Rdiscount 将使用 Markdown 格式编写的文本转换成 HTML:然后用户的网络浏览器使用适当的格式来显示那个文本。Markdown 文档本身没有任何特别之处:它们只是以某种方式编写的文本文档。
也就是说,在我将它添加入数据库之前,我不需要将页面信息转换成 Markdown,毕竟它只是一种语言。当我想要展示有趣的格式文本的时候,我需要做的只是调用 rdiscount。
这是做这项工作的命令,我已经将其添加入 ERB 模板当中以显示页面:
<% markdown(&page.content) %>
该方法将页面内容中的字段转换为 HTML,然后展示最终结果给用户。非常简单。
增加安全性
那关于登录信息呢?如果我将这个应用上传到 Heroku 而不要求用户名和密码,那么任何人都可以看到我储存在数据库的东西。
事实证明,现代网络浏览器支持一个叫作 HTTP 基本认证的安全协议,[46] 它是一种要求用户在操作之前输入用户名和密码的简单方式。如果用户不能提供访问权限,他们会被重新导向一个错误页面。
以下是在 Sinatra 上实现基本身份验证的代码:
use Rack::Auth::Basic, “Restricted Area” do |username, password| [username, password] == [ENV[‘ADMIN_USER’], ENV[‘ADMIN_PASS’]] End
在这种情况下,我将实际用户名和密码作为环境变量储存在 Heroku 当中,我可以使用终端命令设置这些环境变量。它让我可以在不同的应用当中使用相同的代码,以及向你展示这个代码而无须同时提供我的密码给你!
这也是为什么需要知道 Sinatra 是建立在框架结构上的语言的很好例子。有很多像 Rack::Auth::Basic 这样的程序库,我可以通过 Sinatra 来使用它们中的任何一种。最好不需要另起炉灶。
我还想增加多一道安全性:加密。我计划使用 rack-ssl-enforcer 添加 SSL 加密(跟银行用来确保它们的在线银行部分的私密性所使用的安全类型相同)到我的应用当中:
require ‘rack-ssl-enforcer’ use Rack::SslEnforcer
这个程序库迫使网络浏览器使用一种安全的 SSL 连接来访问站点。Heroku 允许托管在 Heroku 领域上的应用程序默认使用 SSL,所以无须再进行设置。[47]
添加“Flash”消息
我还想添加最后一个功能:在你做完某事之后,网站会向你展示诸如“你的页面已经创建/编辑/删除”之类的小消息。这是如何做到的?
我闲逛了一下,发现了一个处理这种效果的叫作 sinatra-flash 的程序库。[48] 这些消息叫作“flash”消息,而且在进入下一个页面之前,它们会在用户浏览器的数据里面储存一点文本。当加载下一个页面的时候,应用程序会读取数据并将该消息展示给用户。
我添加了该程序库到 Gemfile 和 application.rb 当中,设定了我想要在恰当的路径上显示的消息,然后增加一些代码以便在我的 ERB 模板上实际展示那些消息。我的应用程序现在算是完成了。
代码完成
这是 Codex 自述文件的开头部分:
Codex 是一个使用 Ruby 编写的简单的单用户参考网络应用程序。Codex 使用 Sinatra 和 DataMapper 来创建、保存、更新和删除来自一个简单的 Postgres 数据库的页面记录。该应用随时都可以在 Heroku 进行部署。
Markdown 格式可以应用到所有页面当中,这就让它可以使用简单的标记来编写复杂的页面。所有访问的 HTTP 认证和强制 SSL 使你的信息保持安全性。Bootstrap(引导程序)样式使你的页面看起来干净而具有吸引力。
自述文件包含如何在 Heroku 上设置应用程序的详细指引。《自述文件驱动开发》是一个非常好的方法。
我总共花了 10 个小时的时间来建立 Codex。这使我在学习编程方面投入的时间达到了 20 个小时。(编写本章所花的时间比我编写实际应用所花的时间还要长。)
创建 Codex 之后,我参加了当地的一个 Ruby 程序员交流会,并自愿解释了程序的工作方式,反响非常热烈。我的编程凭借其整洁、紧凑、易于理解受到赞扬。其中一名参与者还说,我编写的程序的质量比他曾经见过的由专业程序员所编写的项目还要好。
任务完成。
愤怒机器
我想要澄清一些事情:我所描述的流程听起来非常线性化和直截了当。那是因为直到目前为止我都在描述行得通的程序,而没有描述那些行不通的项目。
编写一个有用的以及可以运行的网络应用程序有点像把一个拼图放在一起,而同时又带有一些额外的挑战:你不知道哪些拼块存在,你必须得自己创建一些拼块,而如果你犯了一个错误,这个拼图就会爆炸。
这是我的编程流程实际看起来的样子:我首先会产生一个关于部分程序将如何运作的想法。我编写一些代码,测试它,然后它毁掉了这个程序。我会尝试修正它。有时候我的修改可以解决程序的问题,有时候却不能,而且有时它甚至会毁掉更多的东西。如果我被严重卡住,我会在 Stack Overflow 或谷歌上搜索错误消息或程序库。
当你仍然在学习各项技能的时候,你的应用程序瘫痪的次数比运行的次数还要多。你应该也已经了解到了一些事情的价值,比如说版本控制,它可以让你的代码返回之前可以运行的版本。
还记得我之前提到过我不是非常清楚 git 的作用吗?是这样的:如果你正在编辑文档,然后一些程序瘫痪了,你可能无法发现哪些程序出了问题。返回之前可以正常运行的版本是天赐之物和一种解脱。如果不能返回,你就只剩下恐慌了。
在我编写侧边栏的添加功能的时候,我把这个应用程序弄坏了。我试图找出哪里出了问题,但是我无法弄明白。如果我还有头发的话,我早就把它们拔光了。
就在那些时候,你意识到了 git branch 和 git merge:的价值:你可以创建程序的实验性版本,然后进行修正。如果你的代码行得通,你可以将它合并到原版当中。如果你把事情搞砸了,你可以删除你的实验性版本而又不会丢掉之前所有的工作。
编程是一件艰难的工作,而且有数以百万计的方式将它搞砸。计算机是无情的,并且不需要承受恶意命令的痛苦。同样地,我们非常容易忽略一些会产生意想不到的后果的小细节。我曾经有段时间被一个软件缺陷整惨了,它把大量的空白记录储存到数据库当中,而且显示在“列出所有项目”的页面当中。
每一次我看到那个网页都会发现更多的空白记录项出现在列表中,而我却无法弄明白它们是怎么来的。结果是“列出所有项目”的路径存在一个缺陷:我使用了一个不正确的命令来检索数据库中的记录。
每一次我弄坏一些东西,都会学到一些东西。那就是编程的其中一个潜在好处。在所有的反馈循环当中,计算机是最快的。如果你做错了某事,你立即就能够知道。如果你做对了,你立即就可以看到程序运行的结果。如果你能避免将计算机扔到房间另一边的冲动,那就把你的计算机放在房间,即时反馈能使编程非常容易上瘾。
在开始进行这个实验的时候,我根本无法编程,而现在我可以了。我需要做的就是花费一些必需的时间来击败一些代码,而无论何时被卡住,我都会推动或进行一点研究。
我从我的努力当中获得了什么呢?一大堆。我学到了什么是编程,它是什么样子的,以及为什么它是有用的。我学到了怎么使用 Ruby 从头开始创建真正可以运行的网络应用,然后推送它们到生产服务器。我学到了 Sinatra、Heroku、Jekyll、DataMapper 和 Rake 的基础知识,以及其他我可以用来创建有用的新应用程序的多用途工具。我学到了如何排除错误和找到缺陷,然后解决它们。
方法回顾
让我们回顾一下我用来学习编程的核心方法。
●我花费了一些时间去学习一般意义上的编程和网络应用是什么,然后将这些技能解构成更小的子技能,这些子技能更加容易理解和练习。
●通过选择我想要创建的两个特定项目,我设定了目标,然后定义了那些项目完成之后的样子。
●我把那些项目解构成更小的子步骤,然后识别哪些子步骤看起来最重要。
●我确保自己拥有工作所需的工具(像最新版本的 Ruby),并且,我可以找到和使用任何我需要的额外工具。
●我找到了一些编程信息的可靠来源,但是我跳过了录音的教程,而是更倾向于直接编写实际的程序。
●我首先完成最重要的子步骤,比如弄懂如何在我的计算机上测试程序,以及如何推送已完成的应有程序到生产服务器等。
●我从学习参考例子开始并建立信心,然后测试各种方法以弄懂如何编写我想要的功能的程序。
●当我犯错的时候,程序会瘫痪,然后给我一条错误消息,从而创建了一个快速的反馈循环。
●遭遇错误之后,我尝试了几种方法来解决它。如果我不能自己解决问题,我就搜索帮助。
●我保持使用建立/测试/解决的方法直至我的程序完成。
总共花费时间:大约 20 个小时。其中 10 个小时是研究,然后剩下的 10 个小时是编写两个应用的程序,现在它们已完成并在生产当中。
接下来做什么
自从完成这些基本的项目之后我就一直继续编写应用程序,专注于那些能够使我的业务运营更加容易的程序。
我可以非常骄傲地说,我现在的整个业务都是在我自己创建的软件上运营。我的应用可以进行信用卡收费、设置订阅、发送电子邮件以及为我的客户管理网站访问。通过学习编程,我现在拥有了我自己的小型编程机器人部队来完成我的命令。
我编写所有这些项目花费了多长时间呢?总共 90 个小时,包括本章之前提到的 20 个小时。
这是额外的好处:无论何时我发现自己的业务存在重复或令人沮丧的领域,我就会开始以代码的思维进行思考。解决这一问题的程序将会是如何工作的呢?然后往往有一种方式来系统化业务流程,以便让我的日常生活变得更加容易些。
我也正在学习一些新的技巧,比如定制化我的计算机以使程序运行更快。我在文本编辑器当中学习键盘快捷键以便节省时间,而且我还将终端(Terminal)升级到了 iTerm2 和 Z-Shell,从而使编程更快一些。
我仍然在练习当中,而以任何标准来看我都不是一名专家。我必须得研究所有东西,并且我需要一些时间来解决问题、错误以及软件缺陷。这经常让人感到沮丧。
然而,我仍然在创建可以直接和可靠地解决实际问题的程序。这才是真正重要的。
我与计算机战斗,最终赢得了胜利。
[1] http://personalmba.com/best-business-books/.
[2] http://wordpress.org.
[3] For the curious:my standard WordPress stack consisted of PHP5 with PHP-FastCGI,NGINX,APC,MSMTP,and WP-Supercache on a Slicehost.com VPSrunning Ubuntu 8.04 LTS,all with custom configuration files.
[4] http://jekyllrb.com.
[5] http://github.com.
[6] For some reason,almost every programming tutorial begins with showing youhow to display or print“Hello,World!”
[7] http://stackoverflow.com.
[8] http://news.ycombinator.com.
[9] http://rubyonrails.org/.
[10] http://www.sinatrarb.com.
[11] http://37signals.com/
[12] http://rubysource.com/rails-or-sinatra-the-best-of-both-worlds/.
[13] http://paulstamatiou.com/how-to-wordpress-to-jekyll.
[14] https://github.com/sstephenson/rbenv.
[15] In practice,“hacking”is nothing like how it’s portrayed in movies,which I find highly disappointing.
[16] https://toolbelt.heroku.com.
[17] http://www.heroku.com/.
[18] http://git-scm.com/.
[19] Versions of Ruby before 1.9.3-p125 required a program called GCC to completethe installation.GCC is available at https://github.com/kennethreitz/osx-gcc-installer.
[20] Programmers coined the acronym“RTFM,”which stands for“read the(freaking)manual,”as a standard response to questions about issues covered in a program’s officialdocumentation.
[21] http://www.ruby-lang.org/en/documentation/.
[22] http://0xfe.muthanna.com/rubyrefresher/.
[23] https://code.google.com/p/ruby-security/wiki/Guide.
[24] The term for advanced modification of Ruby’s core objects,classes,and methodsis called metaprogramming.I picked up a book called Metaprogramming Ruby:ProgramLike the Ruby Pros by Paolo Perrotta(Raleigh,NC:Pragmatic Bookshelf,2010),and it’sway over my head at the moment.First things first.
[25] http://www.ruby-doc.org/core-1.9.3/index.html.
[26] http://ruby.learncodethehardway.org/.
[27] https://devcenter.heroku.com/articles/keys.
[28] https://devcenter.heroku.com/articles/ruby.
[29] https://devcenter.heroku.com/articles/rack.
[30] http://macromates.com/.
[31] https://devcenter.heroku.com/articles/bundler.
[32] http://www.sinatrarb.com/intro.
[33] http://backpackit.com.
[34] http://tom.preston-werner.com/2010/08/23/readme-driven-development.html.
[35] http://www.postgresql.org/.
[36] http://datamapper.org/.
[37] http://stackoverflow.com/questions/1152299/what-is-an-object-relational-mapping-framework.
[38] http://en.wikipedia.org/wiki/SQL.
[39] http://www.sqlite.org/.
[40] http://pow.cx/.
[41] https://github.com/rodreegez/powder.
[42] http://twitter.github.com/bootstrap/.
[43] I have no idea why it’s called a slug,and I agree it’s weird.
[44] See http://www.regular-expressions.info/for examples of common regular expressions.
[45] http://daringfireball.net/projects/markdown/.
[46] http://www.httpwatch.com/httpgallery/authentication/.
[47] Using SSL on a custom domain is more complicated:you have to go through along process to verify your identity and obtain a“certificate”that secures each user’ssession.
[48] https://github.com/SFEley/sinatra-flash.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论