如何写一本书

我在过去的几年中,写了4本书。有传统意义上的两本实体书:《JavaScript核心概念及实践》和《轻量级Web应用开发》,还有两本电子书《3周3页面》和《函数式编程乐趣》。当然对我而言,主职工作是软件开发,写作是个副业。 在写作的过程中,有一些有趣的心得。 写作本身是一个很好的学习过程(至少是一个驱动你学习的动力) 写书非常枯燥,特别是校对的时候 写作不会让你变得富有,但是有时候会让你开心(不总是) 写文章 vs 写书 写博客/文章和写书还是有很大差别的,一个明显的差异是写文章会比较随意,而且应该尽量保持精简。一篇文章提供一些信息即可,应该尽量远离细节(如果写一篇教程,则另当别论)。而写书则应该尽可能的深入细节,尽可能可以让读者依书自修。 投入与回报 首先要明白的一点是,不要指望用写书来赚钱,至少前4本是这样的。粗略的算一下:我的第一本书卖了3000册,每卖一本我可以得到4元RMB,一共就是12,000元RMB。而这本书我断断续续写了三年。那是很多个周末,很多个假期,很多个夜晚的付出换来的,如果真正要计算投入产出比的话(纯粹金钱上),这显然是一个毫不合算的事情。 作为一个参考,IBM developerWorks的投稿,千字200元,一般写5,000字以内,也就是800元RMB左右。而要写一篇这样的文章,我只需要一天(当然需要数周/数月的积累)。12,000元RMB需要写15篇文章,如果每周写一篇,不到4个月就可以写完,而且写文章比写书容易多了,毕竟篇幅比较短小,易于校对。而且对于大部分开发者来说,固定在一个主题上的难度要比15个独立的主题简单的多,因为无需特别深入。 所以根据经验,要抱着公益的情怀来写书。也就是说为了让知识更好的分享,让你学习到的先进科学技术来帮助更多的开发者,提高他们的开发效率,让他们可以在周末多休息一天。而至于翻译技术书籍,那基本上就是免费的了,完全是一个公益活动(耗时数月,斟酌字句,推敲表达方式,但是价格极为低廉:千字60元RMB),所以下次见了技术书籍的译者,就多少给他捐点吧,他们才是在为人民服务。 知识的诅咒 “知识的诅咒”是指人们在获得了某种知识之后,就无法想象没有这种知识的情况了。这种现象随处可见,比如一个你到了一个从未去过的陌生城市,遇到以为当地人,然后向他问路。当地人觉得已经说的很清楚了,但是你还是不知道该怎么走。另一个例子是:假设你不认识泰文,然后你打开任何一本泰文写的小说,你只能依稀感觉到这是一种文字,除此之外你并不能从中获取任何的信息。但是当你学习了一段时间泰文之后,再来看这本小说,之前的那种感受就再也没有了。 写书的时候,你首先需要具备某种知识。但是写书的目的是将这些知识传递给那些不具备此知识的人,而根据“知识的诅咒”,你又无法确知那些初学者会遇到哪些问题!解决这个问题的方法就是找初学者来试读。而且为了保险起见,还应该找尽可能多的人来试读。 写作方式 一种方式是自下而上的,写一些独立的文章,最后发现可以串起来,然后形成一本书,另一种方式是自上而下,但是又会逐步调整。根据经验,不论是写一篇简单的博客,还是写一本书,都需要按照自上而下的方式。随心所欲的写下去,基本上都收不住,而且整个文章支离破碎,貌似有很多内容,但是不成章法,读者也无法轻松的获取知识。 先列出大的章节,然后逐步细化,但是未必是按照顺序来写。先编写自己最熟悉的部分,然后逐步完善。例子的选取需要精妙而恰当,最好有图例来说明。 配图制作 一般而言,我在书中会使用两种图:流程图和一些截屏。截屏通常使用Mac OSX自身的功能就已经足够,而流程图我会采用一些额外的工具如: graphviz keynote/sketch 用Graphviz画图的好处就是可以将图像代码一样放入版本库来管理。 除此之外,我还学习了一些设计软件的基本用法,事实上只需要用一些简单的元素就可以做出非常专业的配图: 字形/字体(大小,粗细的变化) 颜色(基本的配色理论就可以做出很舒服的配色) 层次(尺寸,位置,颜色的深浅) 阴影 代码格式 书中实例需要很多代码来说明,如果是制作电子书的话,可以使用Markdown预处理器自带的功能来高亮。另外如果需要RTF格式,可以使用这些工具: highlight工具 intelij中的插件copy on steriod 这里有一篇博客来说明如何将你的代码带着格式拷贝到剪贴板中,拷贝之后,就可以将这些内容粘贴到Word或者Keynote中了。 jest.dontMock('../components/headline.jsx'); describe('Headline', function() { var React = require('react/addons'); var Headline = require('../components/headline.jsx'); var TestUtils = React.addons.TestUtils; it('#render', function() { var text = "this is a title"; var headline = TestUtils....

August 4, 2015 1 min

如何将你的想法变为现实

说实话,这是一篇软文,为我的新书《轻量级Web应用开发》写的软文。如果你不想接着往下读,可以直接去这里买一本来看,:) 之过去的几年中,我参加过好多次Hackday活动。每次看到在为期两天的时间里,2-3个人将一个想法变成现实,都会有一种强烈的成就感。而且这个Hack的过程中,会重拾编程的乐趣,大家的积极性都非常高,用着各种有趣的技术(大数据,开源硬件,Node.js,GIS系统),逐步的将模糊的想法,变成现实,并最终为客户带来价值。最新的一次在这里 通过这些Hackday的经历,以及在众多项目中的经验,我总结了一些轻量级的方法/实践。这些方法/实践非常容易落地,并且久经验证。在很多项目中已经在不断的使用。它们可以帮你更好的将一个想法变成现实,并且在随后的开发中还可以继续发挥作用而不至失效(测试,构建脚本,自动化部署等等)。我希望你可以在自己的项目中尝试这些方法/实践,也希望这些方法/实践可以真正的帮助你和你的项目取得成功。 细化你的“点子” 根据一个已有的产品来参考,演绎,并形成自己的产品并非难事,而创新则是一件非常困难的事情,因为你需要“无中生有”。在ThoughtWorks,我们有这样一些步骤可以帮助客户来梳理信息,并最终交付产品,简而言之,可以归结为这样几个步骤: Discovery(用户研究,探索) Define(归纳洞见,发现) Design(原型设计,验证) Delivery(制定计划,实施) 上图是一个“点子”的原型(一个交换技能的应用,用户可以教别人自己擅长的技能,作为交换,也可以从别人那里学习心得技能),原型事实上是第三步的产物。我们通过一些调查(口头采访,或者问卷调查)得到一些基本的信息,然后归纳这些信息,并和真实用户再次确认,得到一个概念。有了概念,再来设计一个基本的原型,这个原型还可以迭代数次,然后进入下一个阶段。 前三个阶段更多的是用户体验设计师,以及客户的业务人员参与的。在前三步完成之后,进入实施的时候,软件工程师开始投入。这篇文章更关注**第四步(Delivery)**中的各种实践,通过这些实践,我们可以很好的完成交付计划,使得我们的好想法最终可以变成为用户提供服务的产品。 实现“点子”的方法 在软件领域,将一个想法变成实际的产品需要经历若干个阶段。按照传统的软件开发方式,会有前期的调研,需求分析,概要设计,详细设计,编码实现,测试,发布等一系列的流程。这种方式对每个阶段的定义都非常明确,而且每个阶段需要依赖前一个阶段的输出,因此往往被称为“瀑布模型”。后来慢慢发现,这个模型的反馈周期太长,一个软件从调研到发布往往需要数年,当发布之后,可能市场早已经沧海桑田。人们后来发明了更加符合现代市场需求的“敏捷开发”,在敏捷中,更加强调短平快的将需求变为产品。 简而言之,敏捷开发更强调: 快速发布 渐进增强 小步迭代 而在敏捷开发的继承者精益中,这几点理念也被更进一步的深化。由于没有办法预见未来,我们只能用一种边做边看的方式来验证想法。简而言之,就是先根据经验和调查,做出一个合理的推断,然后定义好范围,构想出一个最小可行产品(MVP),这个MVP的功能非常内聚,非常紧凑,我们需要尽可能快的让其上线,并被真是的用户使用,测试。根据这些用户的反馈,我们会做一些调整,比如去掉那些很少人使用的功能,聚焦在用户喜欢的功能上;从用户的实际使用中,调整界面元素的位置,子功能的入口等等。这个过程会持续多轮,最后的结果会是一个有真是用户使用,并且比较贴近真实需求的产品。当然这还不够,我们需要不断的打磨,渐进式的增强产品的功能,逐步完善功能等。 有一个非常形象的图,可以看出瀑布模型和敏捷开发两种方法的对比: 敏捷开发通过逐步细化,迭代前进的方式,分阶段的将需求实现,在整个过程中,更容易做到快速调整。 所有的这些过程,都非常依赖“快速”这个关键点。如果MVP花了3周就产生了,但是为了让其上线,你花费了1个月,那么很可能这个MVP已经过时了;如果你确实快速的将MVP发布了,在得到了用户的很多反馈之后,花费1个月来实现这些反馈,又会让你落在竞争对手之后;如果快速的发布了多次,并且幸运的是,你的用户量变多了,如果花费很长时间来调整架构,则可能失去当前的市场窗口。也就是说,你需要非常快速地对变化做出反应! 轻量级的开发方式 开发中的三个重点 在工程实践中,我认为有三个特别需要注意重要的点,这三点可以极大程度的改善项目现状,提高效率,并使得产品的高质量交付成为可能,它们分别是: 自动化(自动化一切) 质量内嵌(defect的数量,是否真正满足了需求) 代码本身的质量(可读性,可维护性,可扩展性) 自动化包括,自动provision,自动部署,自动化测试,自动打包等等。这是提高团队开发效率的必要工具。比如书中提到的grunt/gulp脚本,jasmine/rspec/capybara测试,部署脚本,vagrant/Chef等,都是关于如何将日常开发中的任务尽可能的自动化。 软件没有Bug当然是所有人都追求的,我们有很多中方式来保证代码质量。而在编写产品代码的同时,写大量的自动化测试,是投入产出比最高的一种了。通过单元测试,集成测试,以及一些有限但是关键的UI测试,我们可以覆盖很多的需求,而将这些测试自动化起来之后,可以节省大量的开发/测试成本,并减少回归测试的代价。 要支撑快速的发布,我们需要一系列的技术实践。这些技术包括环境的搭建,框架的使用,代码的编写,产品的发布;而且包括后台的数据库设计,业务代码,同样还有前端的展现等。 何为轻量级? 在《轻量级Web应用开发》中,我介绍了一系列的实践/工具,这些实践/工具贯穿整个软件开发的生命周期,使得敏捷开发/精益的开发方式变得可以“落地”。比如如何使用轻量级的开发框架来搭建API原型,如何将应用发布在免费的云平台上,如何通过虚拟化技术快速搭建开发环境,从而节省环境配置的投入,如何快速平滑的发布,如何使用测试先行的方式来保证代码质量,如何做高效的自动化UI测试等等。 轻量级Web框架 前端开发流程 构建工具 环境自动化(开发环境的搭建,CI服务器的搭建) 自动化部署 UI测试 实例驱动(书中有很多的实例,也有很多从项目中总结出来的实践) 这是一本主要关注开发实践的书,书中通过很多实际的例子来帮助读者建立一套完整,高效,轻量级的开发方式,这些方式可以直接在你的下一个项目中使用。甚至如果项目的技术栈变成了另外一种语言,你也可以迅速找到同类的替代品。比如rake之于gradle,sinatra至于spring-mvc等等。 每个组件都是可以替换掉的,比如ORM,如果你觉得DataMapper无法满足实际需要,那么可以换成ActiveRecord。如果Rails太重,使用Sinatra或者Grape或许是一个更好的选择。AngularJS包含了太多东西,Backbone.js或许适合你的场景,而也未尝不可以用Riot.js来替换掉Backbone中的view层。 在本地,可以将应用部署到一个vagrant+chef来provision的环境中,而通过部署自动化,这个动作可以很容易的在AWS的云上实现。轻量级的开发方式,帮助你用最小的代价来替换系统中的任意一个组件,因为每个组件在一开始都是按照可替换原则选用的。 另一方面,轻量级的另一个意思是:现发布静态的版本,然后再将内容替换为动态版本。发布一个静态的页面非常容易,具体细节可以参考这篇文章。当需要动态内容是,免费版的Heroku是一个触手可得的选择,AWS则是一个更加专业的选择(各种服务都配置完善,你只需要关注自己的应用部署即可)。

August 2, 2015 1 min

CGI是如何工作的

CGI的一些背景 Web在设计之初只是可以提供静态内容,用于诸如文档分享,论文引用这样的内容。但是很快人们就不满足于静态的内容了,根据UNIX系统的哲学,人们倾向于让不同的应用程序通过已有的机制(进程间通信如管道,UNIX域socket,以及TCP/IPsocket)连接起来。 在Web服务器,诸如Apache httpd中加入与外部应用程序的通信接口就显得非常自然了。CGI(Common Gateway Interface)即是在这种背景下被发明的。 基本来说,CGI可以是任何的可执行程序,可以是Shell脚本,二进制应用,或者其他的脚本(Python脚本,Ruby脚本等)。CGI的基本流程是这样: Apache接收到客户端的请求 通过传统的fork-exec机制启动外部应用程序(cgi程序) 将客户端的请求数据通过环境变量和重定向发送给外部应用(cgi程序) 将cgi程序产生的输出写回给客户端(浏览器) 停止cgi程序(kill) 配置Apache支持CGI 本文的所有示例都是在Mac OSX环境下编写和实验。 先创建一个cgi的运行目录/Users/jtqiu/Sites/cgi-bin/,然后创建一个空的文件echo.cgi: $ mkdir -p /Users/jtqiu/Sites/cgi-bin/ $ touch echo.cgi 在这个文件中,添加一小段python代码: #!/usr/bin/env python print("Content-Type: text/html\n\n") print("<b>Hello, World</b>\n") 修改文件的执行权限: $ chmod +x echo.cgi 这段python代码并无特别,如果在shell运行这个脚本,可以得到: Content-Type: text/html <b>Hello, World</b> 这个可执行文件将作为我们的第一个CGI脚本,完成了这一步,我们需要配置Apache来支持CGI,首先,在目录/etc/apache2/users/中创建一个文件,文件名就是你的用户名称,如jtqiu.conf。 $ sudo vi /etc/apache2/users/jtqiu.conf 添加以下配置,其中/Users/jtqiu/Sites/cgi-bin/目录就是所有cgi脚本所在的目录,在次配置中AddHandler cgi-script .cgi表示为所有后缀为cgi的添加cgi-script的Handler。 <Directory "/Users/jtqiu/Sites/cgi-bin/"> AddHandler cgi-script .cgi Options +ExecCGI </Directory> 然后重启apache: $ sudo apachectl restart 下面我们就通过curl来进行测试: curl -v http://localhost/~jtqiu/cgi-bin/echo.cgi 更进一步 传统的CGI脚本的生命周期很短,Web服务器在接收到一次请求之后,会fork出一个进程来执行CGI脚本,一旦请求完成,这个进程就会被终止。 我们可以设置一个超时来查看:...

April 20, 2014 1 min

依赖管理器Bower简介

Bower简介 Bower安装及简单配置 Bower是一个基于Node.js的依赖管理工具,它是一个npm的包,因此安装十分简单,由于我们需要在所有项目中都可以使用bower,因此将其安装在全局目录下: $ npm install -g bower 安装完成之后,可以通过bower search来搜索需要的包,比如: $ bower search underscore 典型的应用场景可能会是这样的,新建一个项目目录,然后运行bower init: $ mkdir -p listing $ cd listing $ bower init 和Grunt类似,bower会问你一些问题,比如项目名称,项目入口点,作者信息之类: { "name": "listing", "version": "0.0.0", "authors": [ "Qiu Juntao <juntao.qiu@gmail.com>" ], "main": "src/app.js", "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ] } 比如我们需要安装jQuery和underscore.js,则很简单的运行bower install命令即可: $ bower install jquery $ bower install underscore 如果需要团队中的其他成员可以在本地恢复我们的环境,需要在bower.json中指定dependencies小节: "dependencies": { "jquery": "~2.0.3", "underscore": "~1.5.2" } 所有的JavaScript包都被安装到了本地的bower_components目录下,如果有了bower....

October 9, 2013 1 min

Grunt常用插件

Grunt的几个常用插件 grunt-karma 简介 grunt-karma是一个karma的Grunt插件,上一篇文章中已经介绍了karma的基本用法。这里简单介绍如何在Grunt中使用karma。 首先需要安装grunt-karma插件: $ npm install grunt-karma --save-dev 然后在Gruntfile.js中加载该插件: grunt.loadNpmTasks('grunt-karma'); 在使用karma之前,需要生成一个karma的配置文件karma.conf.js: $ karma init karma.conf.js 然后在Gruntfile.js中,加入初始化karma的参数,并指定,karma需要使用karma.conf.js文件作为配置来运行: grunt.initConfig({ karma: { unit: { configFile: 'karma.conf.js' } } }); 大多数情况下,如果要把karma作为CI的一部分,应该启动单次运行模式: singleRun: true 这样karma会启动浏览器,运行所有的测试用例,然后退出。 grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-karma'); grunt.registerTask('default', ['jshint', 'karma']); 注意此处的default后边带了一个任务数组,其中每个任务会按照声明的顺序依次被执行。事实上此处的’default’是后边整个列表的一个别名(alias)。 grunt-jshint / grunt-uglify / grunt-concat grunt-contrib-jshint是一个用于JavaScript静态语法检查的工具,它会帮助开发者在进行较为严格的语法检查。 和其他的Grunt插件一样,它是以一个npm的包的形式发布的,因此安装非常容易: $ npm install grunt-contrib-jshint --save-dev 然后在Gruntfile.js中加载该插件: grunt.loadNpmTasks('grunt-contrib-jshint'); 即可,类似的还有:用以连接所有JavaScript源代码为一个独立文件的grunt-contrib-concat,以及用以最小化JavaScript源码的grunt-contrib-uglify。 自定义插件 grunt-init是一个帮助开发人员快速搭建基于Grunt项目的工具,比如开发jQuery插件,Gruntfile,或者Grunt插件本身。安装方式很简单,我们需要在其他项目也用到grunt-init,因此安装在全局路径下-g: $ npm install -g grunt-init 开发Grunt插件,我们需要一个基本的模板,将这个模板clone到home下的.grunt-init目录下: $ git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin 然后新建一个目录,并在该目录下运行: $ mkdir beautify $ cd beautify $ grunt-init gruntplugin grunt-init会让你回答一些问题,比如插件名称,版本号,github链接等。之后,grunt-init会生成一个基本的模板,开发者只需要完成自己插件的逻辑代码即可。逻辑实现在tasks/<plugin-name>....

October 8, 2013 1 min