互联网的未来属于实时交互

作为一个写代码的,我做互联网产品也有十个年头了,从来没有想过要在设计上指点江山,只是谈谈自己的一些感触。

我所谓的实时交互,指的是服务器端的状态会实时同步到本地并展示。比如即时通讯软件,你可以直接看到其他用户发来的消息,甚至能看到对方正在输入。比如 MMORPG 多人在线游戏中,在本地就能进入游戏世界并看到一切更新。而很多软件中使用『推送』、『通知』,还算不上实时交互。那种需要刷新页面的,就更不实时了。

我认为未来的产品的形态,交互模式会朝着这个方向去发展,过去的客户端发起请求,服务器产生响应的模式会被革新掉。以往的互联网是内容为准,以后的互联网便是以交互为主。为何呢?看我分析理由。

一、人喜欢更快的响应速度

我06年毕业,毕业论文就写的是关于 AJAX 方面的东西。从那时候开始,我就发现互联网应用是追求越来越快的响应速度的。当时Amazon有个说法是页面的请求每增加多少毫秒,销售额就会减少多少。可见响应速度是一个非常重要的『用户体验』。

从那时候开始,各种技术、组件、框架、语言,都在尽可能的提高应用的流畅度和响应速度。巨头们发明各种新的浏览器,以及 JavaScript 引擎,展开了速度上的竞赛。

用户界面也从服务器端搬到了客户端,原生 app 的兴起,为了尽可能提供能酷炫和流畅的界面。

这些都是在提高响应速度。然而,在过去的『请求-响应』的模式下,再快也是在我请求之后的,只有服务器端能直接把状态推送下来,才是真正最快的响应

二、人喜欢和活的人交互

即时通讯软件可以说是经久不衰的产品类型之一了,而且现在也是占据人类使用手机主要时间的 App。为何会这样?因为人的本质就是喜欢跟人交互的。

所以后来出现了一些特殊的产品形态,目的是为了『模拟』这种好像有活人在跟你交互的感觉。『弹幕』就是其中之一,大家知道其实弹幕最早是出现在点播的视频上的。但我们在使用的过程中,主要的体验就是好像这个时候也有其他人在吐槽视频。

twitter、微博则定时提醒用户有多少信息更新,告诉用户,我们有新鲜的信息,你赶紧来交互吧。

直播也是非常重要的产品形式,而直播上面的弹幕,毫无疑问就是最新的用户的交互了。

三、人喜欢更多的信息细节

前面说到即时通讯软件一直很火,但是我们也发现伴随即时通讯的,往往有语音对话、视频对话。当然,这些功能都是在技术的发展后,门槛越来越低。

图片、语音所包含的信息比普通的文字多,视频就更多了,所以所需要的计算时间和带宽也更多。但是也挡不住大家更喜欢用语音通讯和视频直播。

我认为,人到底都是感情动物,而感情是非常细腻的,比如人可以从细微的表情变化,语气语调中,看出很多东西,也更容易建立情感联系。这也是为何在现在的聊天当中,表情包占据了越来越多的份额。

我相信,从维持感情的角度来看,视频通话>语音通话>文本对话。而直接的对话交互又好于发一份异步的邮件。

实时交互能带来更多的信息细节,能让用户感受到真的是活生生的人,这就是大家对此爱不释手的原因。

四、现在是承上启下的时代

实时交互的技术基础基本上都已经完善了,像服务器端推送,高并发等等基础问题其实已经不是问题。在 Web 上,Socket.io,Primus 等基础通信框架帮助大家解决了实时通信的问题,而一些框架像 Sails.js,Feather.js 也号称是实时框架。最近兴起的『响应式编程』Reactive Programming 也为这种实时的响应和交互提供了支持,出现了相应的框架如 Rx 系列。

同时虚拟现实(VR)已经进入了人们的视野,难道人们会希望在一个只有自己一个人的虚拟世界里面玩吗?显然,我们需要的是一个互联的虚拟世界,在这样的世界里,不可能存在像过去那样的『请求-响应』的操作模式,一切信息都肯定是实时呈现到本地的。

这就要求我们能够打破传统的思维,能颠覆现有的交互方式,把实时交互考虑进来,去创新出一些新的产品形态。

本人也正在研究实时交互方向的产品,并希望提出一套实时交互的应用框架,最近正在写 Coronajs,有感兴趣的朋友可以一起交流。

Node.js 应该拥抱 Actor 模型

Node.js 是近年来服务器端开发工具中可以说最为成功的一个工具了,不仅仅利用 JavaScript 和 Reactor 模型来达到快速开发高并发应用的目的,也顺利入侵前端生态圈,前端开发各种必备的工具链基本都使用 Nodejs 开发。我也用 Nodejs 做过一些针对并发的项目如聊天室等等,但是始终觉得 Nodejs 的基础理念和各种框架还不足够抽象,以帮助我快速对此类项目进行清晰有条理的建模。当项目尺度越来越大之后, 为了达到更好的 Scalability 和高可用性,容错性,因此我越来越希望 Nodejs 能有类似于 Erlang 中的 Actor 模型的框架来实现这些目标。

以往解决并发,往往是一个进程服务一个链接,但是进程占用资源过多,无法很好的伸展。后来变成了一个线程服务一个链接,虽然可以开很多线程,但是频繁的上下文切换消耗过多的 CPU 时间,线程之间的状态共享又容易出问题,所以也无法很好扩展。

Reactor 模型利用事件机制,在 IO 密集的场景中,解决了两者的问题。由于 IO 密集的场景下,大部分链接都是在等待传输等,只有少数链接处于活跃状态,所以可以通过事件的方式,仅仅处理需要处理的那部分,来减少资源的开销。

但是Reactor 模型也不是没有缺点,在 Nodejs 中:

  1. 单线程,难以利用多核优势,一旦阻塞了事件循环,会导致所有任务都会延迟。
  2. 过多的回调导致难以组织代码
  3. 回调函数打乱了控制流程,让处理异常变得麻烦

Actor 模型的基本概念

同样是由于线程和进程模型的一些缺陷,Erlang 之父Joe Armstrong在解决电信交换机上的高并发问题的时候,提出了 Actor 模型。简单的说,每个 Actor 封装了自己的逻辑和状态,互相之间不共享任何状态,只能通过消息(Message)来互相通信。每个 Actor 有一个自己的邮箱(Mailbox),是一个存放消息的队列,Actor 会一个一个处理每一个消息。

_JC@ABJ3(7{)]Y1VF@3PH58

Actor 之间不共享状态,就减少了很多多线程中的锁的问题。顺序处理消息,也保证了 Actor 内部不容易出现竞争条件(Race condition)。

当然,Reactor 模型和 Actor 模型并不完全排斥,Actor 模型底层也是可以使用 Reactor 的。

Actor 具有天生的分布式特性,我整理了一张表:

Actor Process Machine Service
标识 唯一 ID 进程 ID,端口 IP 地址,Mac 地址 IP,域名,服务发现
状态 不共享 通常不共享 不共享 不共享
交互 消息 信号,管道,网络 网络 网络
容错 Supervisor 机制 supervisord, monit,等 heartbeatkeepalived Cluster 内建,DNS,服务发现

其实 Actor, Process,Machine,Service,在很多方面都是一脉相承,正因如此,内置了 Actor 模型的 Erlang 才在云时代大放光彩。

虽然 Nodejs 号称是云时代的编程工具,但实际上在这方面做得并不特别好。

那为何 Nodejs 应该加入对 Actor 的支持?

清晰的单元

首先,Actor 本身只是将一个逻辑处理单元给具体出来,即便我们的 Nodejs 程序没有使用 Actor 模型,实际上在处理并发的时候,依然会隐含着这种逻辑处理单元。Nodejs 中是通过闭包来隔离内部状态,通过事件/回调函数来处理消息。随着代码量的增加,为了让代码更加结构化清晰化,我们通常依然会封装出一些自己的接近 Actor 模型的对象。虽然我们现在有 Promise,未来有 async/await,但是这主要是封装一次『任务』,而非整个逻辑。

Actor模型理念跟面向对象一致

Actor 模型其实本质上也是符合面向对象编程的理念的,面向对象编程中

  1. 对象也是同时封装了状态和行为的单元。
  2. 对象和对象之间通过『消息』进行互相操作

其实在面向对象语言的鼻祖 Smalltalk 中,方法并不叫方法,而是叫『消息选择器』(Message Selector),故名思议就跟函数式语言中,对消息结构进行模式匹配一样。

JavaScript 同样是一门面向对象的语言,是完全可以和 Actor 模型结合起来的。

把前两点结合起来举例,在 Nodejs 中,一个 HTTP 请求往往会对应到自己的一个上下文 context,里面包含了请求request和相应response的对象。这种封装的计算单元就有了 Actor 模型的一些基本理念,像 Koa 中更是使用了 generator/yield(将来可能是 async/await)来简化控制流。

我们可以得到一个理想的单进程内的 Actor 模型

KQRGE}1IRHRI9_M8P{V}W9C

其实上图用一般的面向对象和类来理解也是非常简单的,对吧?

可以优化错误处理以及容错性

在 Node 中,由于控制流的混乱,很难确定出错的位置,有时候出错也会导致整个进程的 crash。而有了 Actor 之后,错误的范围就可以聚焦到 Actor 上,而不会像原来那样找不到出错的位置。同时,也可以引入类似于 Erlang OTP 的 Supervisor 机制。Erlang 的理念是 Failfast,但是Erlang 中的代码出错之后,会有监控者进行善后。

分布式

Actor 模型的理念具有天然的分布式潜力。如果在 Nodejs 中能有良好框架的支持,让跨进程的消息传递变得透明的话,那么在各种应用上的 Scalability 都会有极大的提升。一般 Web 服务的 nodejs 层那样是无状态的,横向扩展比较容易,加服务器就够了,而写长连接的 nodejs 应用比如聊天室的时候,为了实现 nodejs 层的横向扩展,不得不通过更后端的诸如 redis 来实现跨进程通信,更后端的压力更大,也由于中转导致延迟更大。甚至在某种程度上,我们可以实现在不同服务器之间的动态调度。

有了透明的分布特性之后,同时高可用性也更容易达成,我们可以将 Actor 的状态,跨进程/机器去进行 Replication,当某个节点 crash 之后,可以在 replication 的节点上重启相应的 Actor。

L$7%A7B]C8VKFN2BRJP8STJ

(图片来自网络)

解决方案

在众多 Actor 模型的解决方案中,使用 Fiber 是跟 Reactor 最为自然的搭配,因为 Fiber 就是封装了一个计算过程,并且可以根据情况将 Fiber 暂停和恢复,这就可以通过 Reactor 中的非阻塞的特性,利用事件机制将活跃的 Fiber 唤醒,将需要等待的 Fiber 暂停。我们可以很容易的通过对 Fiber 进行封装,在 Nodejs 中实现 Actor 模型。像 fibjs 就能很好的完成这一任务。

但我认为 Actor 模型并不一定非等需要使用 Fiber,只要内心包含相应的理念,即使只使用 Promise,async/await,也是完全没有问题的。

Virtual Actor

微软提出了一套新的理念称之为 Virtual Actor,非常接近于我所追求的框架和平台。

Virtual Actor在基础的 Actor 模型上增加了几点:

  1. 实例化后始终存在,自动 replicate 到其他节点。
  2. 不活跃的 Actor 可以进行垃圾回收或者被持久化。
  3. Actor 位置透明,任何节点之间都可以互相访问
  4. 自动 Scale Out

RMV7Y~QEWMZ_[}K93``VQ(Q

目前实现了 Virtual Actor 的框架有,微软.net 平台的 Orleans, Java 上也有开源的 Orbit。并且 Orleans 的平台在微软内部有很多的实际应用,包括 Halo 的服务器也应用了,并能达到上百万的在线。

相对于 Erlang,Akka 之类的基础 Actor 平台,Virtual Actor 更为简单易用,而 Erlang 由于更加底层,能够对自己的应用进行更好的调整。

如果 Nodejs 也能出一套类似于 Orleans 的框架,我觉得对于 Nodejs 实现更大系统有极大的帮助。

如何在 Windows 下配置 Ruby 开发环境

一般开发 Ruby/Rails 应用我们都会推荐使用 Linux、MacOS X。因为 Ruby 相关的很多工具链在这两个系统上都有成熟的生态。然而 Windows 毕竟是最普及的操作系统,所以我觉得有必要跟大家介绍一下如何在 Windows 下搭建Ruby/Rails 的开发环境。

大家可以考虑安装 RailsInstaller,或者 Bitnami RubyStack,这两个安装包集成了大部分常用的相关组件,有点类似于 PHP 生态中的 XAMPP 这样的软件。

RailsInstaller 包含以下内容:

Git 是用于管理源代码的工具,由于现在很多组件的安装可以直接用 Git 进行操作,所以一般需要安装。DevKit 其实是一个 MinGW 的环境,他是在 Windows 环境上用了一些相关的库来兼容一些*nix 的源代码,可以编译一些 *nix 下开发的源代码,Ruby 就是利用了 MinGW 编译出了 Windows 版本。在 Windows 系统上安装一些 Ruby 的 Gem 需要 DevKit 才能编译再安装。

而 Bitnami RubyStack 则包含更多的工具,如: ImageMagick(图像处理工具),MySQL 数据库,Apache,Nginx 等等。

当然,RailsInstaller 和 Bitnami RubyStack 都比较庞大,如果仅仅只是学习 Ruby,也可以只安装 Ruby 解释器。RubyInstaller 是专门为 Windows 编译的 Ruby 分发包——在 Ruby 官网上是没有直接的 windows 安装包下载的。

推荐安装 Ruby 2.2或者2.1版本的,本人比较偏好32位版(非64位,出于某些兼容性考虑)。

安装一个编辑器也是必须的,现在比较推荐使用 Atom

接下来很多操作需要在『控制台』中完成,在 Windows 的开始菜单选择『运行』,输入『cmd』回车,就可以启动一个控制台。

如果在控制台中输入『ruby -v』,回车后能看到诸如以下内容的文本,就表示 Ruby 已经安装成功了:

$ ruby -v
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin13.0]

由于国内的一些特殊的网络状况,在安装 Ruby gems 的时候经常会无法下载,这时候我们需要利用国内淘宝的 Ruby gems 镜像来替代官方镜像,安装它的网站上指南进行替换:

这样就能基本保证以后 Gems 的安装不会因为网络问题而失败。

最后,还需要再安装 Node.js,由于 Rails 自带了对 CoffeeScript 的支持,而 CoffeeScript 的官方编译器是由 JavaScript 写成的,所以在 Rails 把 CoffeeScript 编译成 JavaScript 的过程中,需要调用某个 Javascript 引擎。在这里我们选择使用 node.js,效率较高。

欢迎观看我的直播 http://www.douyutv.com/413281

教孩子们什么编程语言好?

如今计算机已经非常普及了,智能手机也基本上人手一部了。美国总统也在吹嘘人人都要会编程。我有两个小孩,作为一个码农,总得把这门手艺传递下去。那么我就寻思究竟教他们什么好呢?

回想我个人的编程经历,最早是初中开始接触编程的。因为参加信息学奥赛,第一门学习的语言是 Pascal,然后又自学了诸如 Basic,C,Logo 等等。

我的印象中,我最喜欢的便是 Basic,原因就是他简单。Logo 也很好玩,但是功能过于简单,没法实现复杂的功能。Pascal 则过于严格。C 最让人难以入门的便是指针,而且非常容易出错。了解指针往往还需要有一些操作系统常识,否则也不能理解其中的原理。

中学时代之后还学习了 C++和 Java,但实际上都没有掌握其精髓,以当时的心智还不能理解面向对象,对于 C++的模板也是不能理解具体开发中如何使用。

其实对于一个初学者来说,最重要的是引导他建立编程的基本概念,培养他的兴趣。这个过程中应该尽量减少他们的心智负担,像什么 C 的指针,Java 中面向对象的什么 abstract,interface, C++中的 virtual、引用等等,这些深奥的概念,对初学者并不友好,如果没有实际工作经验,会让人深陷不自信的状态,无法自拔,最后往往放弃。在这方面,我觉得脚本语言在这方面则比较有优势,既没有特别严格的格式,有些也不需要强调类型系统,把关注点集中在编码本身。

另一个很重要的就是可视化,或者说所见即所得。对于孩子们来说,他们的抽象思维还不足以达到成人这样,可以直接对着一些抽象的数据进行开发。对于他们来说,像真实世界一样的东西是最好理解的,因为人的心智就是从这样的世界中进化出来的。

同时如果能立刻得到反馈,则非常有助于孩子们在编程中的探索,像一些 reactive programming 的环境,就非常好,Logo 就是这样的。

基于以上的经验,我把孩子的编程学习分成两个阶段。

第一阶段是启蒙阶段,大约就是小学阶段。根据我前面所说的三点,1. 减轻心智负担,集中于编程本身 2. 可视化  3. 实时反馈。目前首选的是 Scratch 语言。

Scratch 由 MIT 发明,就像搭积木一样,他把各种要素以积木的方式呈现给学习者,通过积木的组合来形成指令,不得不说是一种创新。然而这种概念在某些程度上是继承了 Squeak Smalltalk 的 Etoy,但是依托于互联网时代,得到了发扬光大。

第二阶段是转型阶段,有两种选择:

A 方案,让孩子们在这个阶段跟上逐渐接受工程化的开发思想。尝试教授诸如 Ruby,Python,JavaScript 这类脚本语言。逐渐引导让他发现现实生活中的需求,让他能把需求转化成设计和代码,并通过测试再去修改调试代码。通过迭代的方式不断完善代码。期间会让他熟悉敏捷开发的方式,了解单元测试,GIT 等等。

采用这个方案的孩子,在高中就基本上可以拥有非常强的实际开发能力,能独立完成小的产品。即便不上大学,也完全可以胜任一般的工作。

B 方案,让孩子们钻研算法,参加信息学竞赛。这样的好处是如果能得奖,保送进大学也是非常不错的方案,在大学里继续深造,最后进入大公司做专精的方向也是非常好的。但是毕竟竞赛是残酷的,能得奖的也就那么几个人。

以上毕竟只是设想,同时除了编程之外,还有很多精彩的东西。

重启博聆网

在本人加入暴走漫画之后,原来的创业项目博聆网就被搁置了。去年底因为文化部查处黑暗童话类信息导致博聆网被关停。到今年6月份,网站服务器到期,续费也是一笔不小的开支。但毕竟作为我曾经自己的项目,总有割舍不掉的感情。

不管怎样,失败的项目还是要总结一下的。

博聆网最初的定位是想做一个兴趣社区的平台,里面每个小组围绕一个兴趣点,网友在其中进行互动。做这个平台的本质是让用户能看到优质的内容,有足够的用户在这里可以进行社交。而初期来说没有足够的内容,也没有足够的用户,以我个人和两个同伴又不能原创,即便种子用户也没有能找到一个专精的突破口,却创建了过多的小组分散了内容和用户。随后,我给小组的帖子加入了过多的功能,希望帖子能设置为图片、视频、外部网页,投票,flash等等,却没有直接使用富文本排版,这种做法类似于做一个复杂的对象数据库,因为不同的格式会有不同的字段,这令技术实现难度变得很大,然后缺忽视了不同类型的帖子之间在用户体验上的差别,用户又因为提交内容要选择不同的格式,并没有提升用户的效率和体验。最后,在移动互联网刚开始的时候,因为自己对移动应用开发的不熟悉,没有迅速切入,痛失良机。

当然,目前我的状况也不可能对博聆网做什么大的功能升级。只是为了几个骨灰级网站用户继续维护这个网站。所以我对博聆网做了大量的功能清理,比如去除了过去的个人 timeline,完全转型成一个论坛的形式,不同的帖子格式也都统一起来,成为最简单的文本帖子。而小组的数量也会被清理,先从最基本的灌水小组开始,如果人多了,再增加不同的版块。同时,尽量优化用户体验,如果有可能,则做一个手机 app。

 

RabbitMQ简介

RabbitMQ是一个很受欢迎的消息中间件,通过它可以很方便地实现异构子系统之间的通讯,还可以将不同子系统之间进行解耦。它用erlang开发,基本上是实现了AMQP 1.0标准的消息协议。

了解RabbitMQ首先要了解以下一些概念:Message,Producer、Exchange、Queue、Consumer

Message是一些简单的字符串, Producer(Publisher)是实际发布消息的角色

Queue,是实际存放消息的地方。顾名思义,消息从Queue一端放入,另一段由Consumer(Subscriber)取出,如果有多Consumer,每个consumer各自取出不同的消息进行处理。

当Producer发布消息的时候,首先是发布到Exchange,然后RabbitMQ根据Exchange的类型和逻辑来判断应该发送到哪个Queue中。所以Queue必须bind到特定的Exchange上才能获取到消息,绑定的时候可以提供一个routing_key来判断选择什么消息,Publisher在发出信息的时候就可以指定不同的routing_key来选择如何分发消息。

当使用最基本的队列模式的时候,可以不指定exchange,这时候会使用默认exchange来进行消息的发送。

Exchange和Queue都有自己的名字,多个Publisher可以发布到同一个Exchange,多个Consumer也可以订阅到同一个Queue。

RabbitMQ支持的Exchange方式有:

  • direct 直接投递
  • fanout 广播投递
  • topic 可以按照一个topic名字的模式进行匹配routing_key,例如topic.*可以匹配topic.paragraph和topic.paragraph.word,而topic.#
  • rpc Producer可以等待Consumer处理消息结束并把结果返回给Producer

Ruby下可以使用基于EventMachine的异步客户端amqp,或者是同步模式的bunny和carrot。

用bunny,以topic订阅为例:

publisher部分:

consumer部分

RabbitMQ集群和High Scalability

由于RabbitMQ实现的是AMQP,它非常强调一致性,而AMQP本身就是一种适用于金融行业的消息协议。根据CAP原理,一致性、高可用性和分区容忍性只能选两项,RabbitMQ提供了三种配置选项:

  • ignore:默认配置,发生网络分区时不作处理,当认为网络是可靠时选用该配置
  • autoheal:各分区协商后重启客户端连接最少的分区节点,恢复集群(CAP 中保证 AP,有状态丢失)
  • pause_minority:分区发生后判断自己所在分区内节点是否超过集群总节点数一半,如果没有超过则暂停这些节点(保证 CP,总节点数为奇数个)

由于使用Erlang开发,RabbitMQ可以非常方便的搭建集群,可以随时加入节点,这些节点之间是互相等同的,可以在客户端随机选择节点,或者使用诸如haproxy等进行负载均衡反向代理,以达到水平扩展的目的。

如何解决翻页条目重复问题?

之前做糗百和暴漫的时候,由于帖子更新速度很快,所以当用户看完一页帖子之后,翻到下一页,则会发现有一些帖子是跟前一页是重复的。

如果短时间内翻页就出现这种情况,或是每次翻页都会出现这种情况,就非常恼人了,那么如何能解决这个问题?

原理分析

首先我们分析一下问题出现的原理(请原谅我其实不懂数学):

  1. 用户访问的是按id顺序排列的页面:
    这种情况下是不会出现翻页重复的
    当用户在t0时间访问服务器请求第P(P>=1)页时,服务器的帖子列表是a0, a1, a2 … an的一个序列A,服务器根据每页e进行分页,得到了一个A'[p]a[(p-1)*e] .. a[p*e-1]
    对于服务器帖子A来说,下标是单调增加的,然后t1时间(t1>t0),A增加了一个a[n+1]的帖子,对于用户来说,即使t2时间进入p+1页,也可以得到A'[p+1]a[p*e] .. a[(p+1)*e-1]帖子,与之前是没有任何重复的,因为帖子的增加都是在最后的

  2. 用户访问的是按id倒序排列的页面:
    这种排序方式,是最新的排序在最前,目前大部分网站的帖子排序都是这样的

当用户在t0时间访问服务器请求第P页时,服务器的帖子列表是a1, a2 ... an的一个序列A,服务器根据每页e进行分页
由于是id倒叙,则得到了一个A'[p]a[n-(p-1)*e] .. a[n-p*e+1]的序列
下一页p+1页d的记录A'[p+1]则为a[n-p*e] .. a[n-(p+1)*e+1]的序列
假如t1(t1>t0)时间,A增加了一个a[n+1]的帖子,则P和P+1页的序列都会变了
P+1页则会变成A''[p+1]a[n+1-p*e] .. a[n+1-(p+1)*e+1]的序列
如果增加了m条记录,则有可能变成 Am[p] = a[n+m-p*e] .. a[n+m-(p+1)*e+1]

我增加一个起始点s的概念,在t0时刻,s=n,而在t1之后,s=n+m

所以,如果要顺序不变,则需要保证该用户会话中,起始点s始终为n

这时候,可以为每个用户存一个自定义的s,用起作为偏移的参考。(怎么存下面会讲到)

  1. 用户访问的是按某个字段(如热度)排序的页面:
    对于暴漫和糗百首页而言,实际并不是前两种排序,而是按其文章的打分来排序,很多其他网站有按点击率进行排序,分数的计算公式也会很不相同。

这种情况相当复杂,因为这个字段并非单调增长,而且经常会变化,我们先来看一下:

当用户在t0时间访问服务器请求第P页时,服务器的帖子列表是a1, a2 ... an的一个序列A,排序算法为f,f(A)得到此时其排序为S,其第P页的内容为S(a,p)
t1之后,f让A的排序变为了S’,其第P页的内容为S'(a, p)

那么对于用户而言,根据A的对应的热度的变化频率,则翻页是内容变化的概率会很大,并不一定会重复
换句话说,如果用户想得到一个较为稳定的视图,那么我们必须假设用户始终在t0时间的角度来看内容
假设t1对应A的初始状态,t2的变更操作C将A的排序属性变成A’,令S也变成S’
那么我们必须保留t1和t2时候A状态的快照,同时当用户访问网站的时候,记录其所绑定的快照。
这样才能令用户看到一致的结果。

这种情况也可以完全涵盖第二种情况,第二种情况其实是本例的特例

实现方法

说完了原理之后,必须阐述一下实现方法。

首先是比较简单的第2种情况,需要为用户会话绑定一个起始id,这个id可以保存在每个用户的cookie中,或者是保存在服务器端session中,也可以是在翻页的链接上加上一个起始id,比如/articles/latest/page/1?s=100

由于用户在同一时间看到的内容都是相同的,所以其实我们并不需要针对每个不同的用户保存单独的起点信息。

而对于上节描述第三种情况来说,其实早在很久之前数据库就有一种称之为“游标”cursor,可以实现类似的功能。但如今大部分网站都不会使用数据库游标,因为游标会大量消耗数据库服务器的资源。

另一种方式是使用业务层的游标方式。我们可以观察到,对于每一个不同的排列顺序S,产生一个特定的快照,将记录的顺序存下来。
有些网站比如hacker news,便利用了基于continuation的方式,保存了针对每一个排列顺序产生的快照,它的下一页的链接是这种形式的 https://news.ycombinator.com/x?fnid=Y7HN9XBN2kRzQlD5LklRh4 如果用户在页面长时间没有动作,再次点击这个链接的时候,便会显示为“expired link”,表示这个排序方式的快照已经失效了,业务层服务会在每次排名变更之后丢弃之前的排序方式。
但是对于国内大部分网站而言,使用这种简单粗暴的方式是不可行的,可以根据业务需求,保存不同版本的快照,每个快照内的翻页链接都加上快照的版本号。当快照过期之后,把旧的链接重新定向到最新的链接。

如何保存快照?当需要排序的元素数量较小,比如几千几万条,就可以使用单独的数据库表,或者是Redis的Sorted Set,甚至使用Google的leveldb单独做一个Service都可以。

第三种方式是直接进行客户端排序,每次用户新访问的时候,便将排序的顺序都下载到客户端,当服务器端排序方式发生变化的时候通知客户端是否要重新加载最新的排序方式。
由于现在的浏览器都支持JavaScript,还支持localStorage,所以网站已经做成了纯的Web Application,那么如果当记录数量不是很大,一次性下载几千个id,也是可以考虑的方式。但这并不适合直接从服务器端取页面的方式。

综合考虑的解决方案

由于大部分网站的大部分用户,在同一时刻看到的页面是一样的,所以很多网站都会有缓存。同时仔细考虑,对于缓存来说,每个快照S,都会直接对应一组页面缓存C,而客户端则会有另一组缓存C’。

过去由于缓存的页面并没有绑定到快照,排序更新后,缓存的更新也并非同步,有的页面可能根据新的排序更新了,有的可能还没有。
所以,只需要增加一个缓存绑定到排序快照的机制,可以直接将页面缓存视为排序的快照,而无需保存实际的排序方式。
于是我们便可以使用类似于id逆序的方式,为分页的页面缓存增加一个快照id,形如/articles/latest/page/1?s=100,同时当排序发生变化时,把快照id增加1
对于页面缓存的失效时间,则可以根据可容忍的程度进行设置。当请求的快照id 不存在时,则可以跳转至最新的快照id。

这样不用额外保存排序快照,对于网站而言,大部分用户同一时间能复用同一套缓存,提高了性能和效率。

当然,其实对于用户而言,是否真的需要非常准确、无重复的翻页?首先也要从产品设计的层面来考虑。

程序员,你调试过的最难的 Bug 是?

伯乐在线导读:调试 Bug?每个程序员工作中必须品。在 Quora 上有一个和 Bug 相关的热门问答帖:《What’s the hardest bug you’ve debugged? | 你调试过的最难 Bug 是?》。在众多回复中,Dave Baggett 的经历最让人惊叹,得到了 2400 多个顶。感谢@cugbabyebar 的热心翻译。

回想起这个bug,仍然让我有些痛苦。作为一个程序员,在发现bug时,你学会了首先在自己代码中找问题,或许在测试一万次之后,你会把问题归咎于编译器。只有在这所有的都不起作用之后,你才会把问题归咎于硬件。

这是我遭遇一个硬件bug的故事。

抛开别的不说,我曾为《Crash Bandicoot》写存储卡(读写)代码。对于一个自大的游戏程序员,这就像是在公园里散步一样轻松愉快,我认为只要几天就写完了。我中止调试六个礼拜。在此期间我做一些其他的事情,但我一直回来处理这个bug——几天内每天几个小时。这个bug实在烦人。

这个bug的症状是,当你需要保存你的进度时,代码会访问存储卡,而大部分情况下没有什么问题…但是偶尔读写会超时…没有任何明显的原因。一个短小的写入经常毁掉存储卡。玩家要保存进度,我们不仅不保存,还擦除他们存储卡上的全部东西。天哪。

过了一段时间,我们在Sony的制作人Connie Booth慌了。我们显然不能带着这个bug发布游戏,而六个星期之后我对于问题出在哪一点线索都没有。通过Connie我们向其他 PS1 开发者求助:有没有人出现过像我们这样的情况?没有。绝对没有任何人在存储卡系统上出现任何问题。

在你绞尽脑汁之后,你能做的唯一一个调试方法就是分而治之:一点点去除程序中的代码,直到留下的代码很少但你仍然出问题。像木雕一样去除没有问题的代码,留下的就是你的bug所在。

在这样的背景下挑战在于,视频游戏是很难去除某一部分的。在你删除模拟重力或者显示字符的代码后,如何运行游戏?

你必须做的是用一个假装做真正的事情,但实际上只是做很简单的不会出现bug事情的东西来替换掉整个模块。你必须写新的支撑代码来让这些玩意正常工作。这是一个缓慢而痛苦的过程。

长话短说:我做完了。我移除了大片大片的代码,相当多,只留下了初始化代码——就是准备游戏运行系统,初始化底层硬件等等。当然,我不能显示加载/保存菜单,因为我截除了所有的图像代码。但是我能够假装用户使用(不可见的)加载/保存屏幕并且请求保存,然后写入卡中。

我最终以一个带有这个bug的很少量的代码结束——但问题仍然随机出现!在大多数情况下没啥问题,但是偶尔会失效。基本上所有的Crash的实际代码都被移除了,但还是这样。这实在是莫名其妙:留下来的代码基本上都没做什么事。

在那时——估计是凌晨3点——一个想法蹦了出来。读写(I/O)涉及精确定时。无论是硬盘、存储卡、蓝牙发送器——随便啥——做读写的底层代码都是根据时钟来的。

时钟让不直接连接到CPU的硬件设备和cpu运行的代码同步。时钟决定了波特率——数据从一头传到另一头的速率。如果计时有什么问题,硬件或者软件或者两者都会乱七八糟的。这真的,真的很糟糕,并且通常导致数据损坏。

如果我们的初始化代码以某种方式弄乱了计时会怎么样?我又看了一遍测试程序中和计时有关的代码,并注意到我们将PS1上的可编程计时器设置到了1kHz(1000跳每秒)。这是比较快了,当PS1启动的时候,默认状态大概是100Hz。因此,大多数游戏将他们的计时器设置为100Hz。

这个游戏的带头(和除我外的唯一)开发者Andy,将计时器设置为1kHz,使得Crash的动作计算更加准确。Andy喜欢矫枉过正,如果我们要模拟重力,我们应该尽可能的提高精度!

然而如果提高计时器频率莫名其妙的干扰了整个程序的计时,故而将这个计时器设置到存储卡的波特率上会怎样呢?

我将计时器代码注释掉。然后我就无法复原这个bug了。但是这并不表示bug被修复了,这个问题是随机发生的。万一我只是运气好呢?

几天过去了,我还是在玩我的测试程序。Bug没有再出现。我回到全部的Crash代码中,修改了加载/保存代码,在访问存储卡之前将可编程计时器重置为默认设置(100Hz),之后设置回1kHz。从此之后没有发现问题再次出现。

但是…为什么?

我重新回到测试程序上,试着检测当计时器设置为1kHz时出现的那些错误的模式。终于,我注意到这些错误出现在使用PS1手柄的人身上。因为我自己很少这样做,所以我没有注意到(为啥我要在测试加载/保存代码的时候用手柄)。但是有一天我们的美工等我去完成测试(我确定那时候我在爆粗口),而他紧张的摆弄着手柄。卡损坏了。“等下,怎么回事?喂,再来一次!”

一旦我发现了这两件事是联系着的,就很容易重现bug:开始写入存储卡,动一下手柄,存储卡损坏。在我看来完全是硬件bug。

我去找Connie告诉他我的发现。她转述给设计过PS1的硬件工程师。她被告知:“不可能,这不可能是硬件问题。”我跟她说问一下我能不能直接和他说。

那个工程师给我打电话了,他用着他的烂英语,我用着我更烂的日语,我们争论一会。我最后说:“我给你一个30行的测试程序,让你在动手柄的时候能够出现这问题。”他答应了。他向我保证,这是浪费时间,而他正在一个新项目上很忙,但因为我们是Sony很重要的开发者,他会试的。

第二天晚上(我们在洛杉矶,而他在东京,所以对于我来说是晚上而他是到了第二天),他给我打电话,不好意思的向我道歉。这是个硬件问题。

我还是没有完全搞清楚问题到底在哪,但是我的印象中,从Sony总部的反馈听到的是,如果将可编程计时器设置到足够高的时钟频率,会影响到主板上时钟晶振附近的一些东西。这些东西之一就是存储卡的波特率控制器,同时也设置手柄的波特率。我不是搞硬件的,所以对于细节我相当模糊。

但是主旨是主板上两个独立部分的串扰,以及手柄接口和存储卡接口数据发送的结合在1kHz的时钟频率下会导致丢位,从而数据丢失,以致卡损坏。

这是我全部编程生涯中,唯一一次因为量子力学debug的问题。

原文链接: Dave Baggett 翻译: 伯乐在线 – CuGBabyBeaR
译文链接: http://blog.jobbole.com/50995/
[ 转载必须在正文中标注并保留原文链接、译文链接和译者等信息。]

程序员幽默

1、程序猿最烦两件事,第一件事是别人要他给自己的代码写文档,第二件呢?是别人的程序没有留下文档。

2、程序猿的读书历程:x 语言入门 —> x 语言应用实践 —> x 语言高阶编程 —> x 语言的科学与艺术 —> 编程之美 —> 编程之道 —> 编程之禅—> 颈椎病康复指南。

3、还没上大学的时候,高三暑假,跑到家那边的图书城想买传说中的C++的书,然后看到一本C#,我一看,嘿,这个++还写得挺艺术的,重叠起来了,于是把C#买了回来……

4、问:程序猿最讨厌康熙的哪个儿子。答:胤禩。因为他是八阿哥(bug)

5、有一天,程序猿们突然发现他们要涨的工资掉到井里啦!大家都很害怕,连忙一个吊着一个,从树上伸到井里去捞工资。正好他们摸到工资的时候,一个老程序员忽然兴奋的大叫:别蠢了,要涨的工资还好好的挂在天上呢!

6、诸葛亮是一个优秀的程序猿,每一个锦囊都是应对不同的case而编写的!但是优秀的程序猿也敌不过更优秀的bug!六出祈山,七进中原,鞠躬尽瘁,死而后已的诸葛亮只因为有一个错误的case-马谡,整个结构就被break了!

7、生活中程序猿的真实写照、一款游戏一包烟,一台电脑一下午。一盒泡面一壶水,一顿能管一整天。

8、程序猿要了3个孩子,分别取名叫Ctrl、Alt 和Delete,如果他们不听话,程序猿就只要同时敲他们一下就会好的…

9、宪法顶个球!中国的法律都是.txt文件,不是.exe文件。

10、同事说,他在写i++的时候总觉的自己写的是 我艹………有木有同感????

11、程序员,年二十有二,始从文,连考而不中。 遂习武,练武场上发一矢,中鼓吏,逐之出。 改学IT,自撰一函数,用之,堆栈溢出。

12、《桃花庵–程序员版》写字楼里写字间,写字间中程序员; 程序人员写程序,又将程序换酒钱; 酒醒只在屏前坐,酒醉还来屏下眠; 酒醉酒醒日复日,屏前屏下年复年; 但愿老死电脑间,不愿鞠躬老板前; 奔驰宝马贵者趣,公交自行程序员; 别人笑我太疯癫,我笑自己命太贱; 但见满街漂亮妹,哪个归得程序员;

13、有一天某程序员去买肉,要了一公斤, 拿到公平电子秤上一称:”额。。怎么少了24克。。”

14、检验代码质量的唯一标准 = 代码review时骂的次数 / 代码review时间 。

15、杀一个程序员不需要用枪,改三次需求就可以了。

16、C++程序员看不起C 程序员, C 程序员看不起java程序员, java程序员看不起C#程序员,C#程序员看不起美工。周末了,美工带着妹子出去约会了,一群SX程序员还在加班。。。

17、问:如何生成一个随机的字符串?答:让新手退出VIM 。

18、“我给你出个脑筋急转弯,你说达芬奇密码的上面是什么?” “这。。太难了吧。。不知道。。。” “笨!达芬奇密码的上面就是达芬奇帐号啊,那达芬奇密码的下面是什么?”“我。。。这。。。还是不知道。。。”“是达芬奇验证码”。

19、随机函数可以帮你实现家庭和谐: Talk(){:top word(1)=”恩!”; word(2)=”好的!”;word(3)=”然后呢?”;word(4)=”有道理”;i=random(4); say word(i) goto top;}

20、程序员爱情观:爱情就是死循环,一旦执行就陷进去了;爱上一个人,就是内存泄漏–你永远释放不了;真正爱上一个人的时候,那就是常量限定,永远不会改变;女朋友就是私有变量,只有我这个类才能调用;情人就是指针用的时候一定要注意,要不然就带来巨大的灾难。

21、女同学们纷纷表示,这年头不找个程序员老公,还真是连节日低价购物权都没了。

22、Delphi象吉普车,什么路上都能开,却在啥路上也开不好;PB就象卡丁车,只能在固定线路上开,到室外就有些不稳;VC象跑车,你开得起却买不起,而且一旦发生故障,想修都找不到毛病在哪;Java象敞棚车,不管刮风下雨还是艳阳高照,都能照开不误;VB就是摩托车,骑的时间越长,你越痛恨它!

23、上联MYSQL明月三千里 下联: XHTML.信号他妈烂!

24、程序员的四大理想:南极有套房,澳大利亚有群羊,全世界电脑死光光,孩儿有个娘。

25、有一种崩溃叫密码输入有误;有一种惊慌叫做账号异地登陆;有一种感情叫隐身对其可见;有一种误会叫人机离线;有一种失落叫没有访问权限;有一种感情叫站点访问失败;有一种无奈叫bug无法复现。。。

26、黑体的锯齿,宋体的沧桑,崩溃的避头尾集。美工永远纠结于网站程序员的粗犷,就像MAC永远不懂PC的忧伤。。。。

27、程序猿追求MM不成,含泪追问:我在你眼里算什么?!MM答曰:真人版的windows优化大师……极客哥们莫伤心,小戴安慰递纸巾。

28、 据说有一位软件工程师,一位硬件工程师和一位项目经理同坐车参加研讨会。不幸在从盘山公路下山时坏在半路上了。于是两位工程师和一位经理就如何修车的问题展开了讨论。硬件工程师说:“我可以用随身携带的瑞士军刀把车坏的部分拆下来,找出原因,排除故障。” 项目经理说:“根据经营管理学,应该召开会议,根据问题现状写出需求报告,制订计划,编写日程安排,逐步逼近,alpha测试,beta1测试和beta2测试解决问题。” 软件工程说:“咱们还是应该把车推回山顶再开下来,看看问题是否重复发生。”

29、【高效的程序员】当世界末日还有5分钟就要到来的时候。程序员: 让我们在这最后的时刻作些什么吧!女友: 那好,让我们在做最后一次吧!程序员: 那剩下的4分50秒做什么啊?

30、【开发时间】项目经理: 如果我再给你一个人,那可以什么时候可以完工?程序员: 3个月吧!项目经理: 那给两个呢?程序员: 1个月吧!项目经理: 那100呢?程序员: 1年吧!项目经理: 那10000呢?程序员: 那我将永远无法完成任务。

31、一个程序员对自己的未来很迷茫,于是去问上帝。“万能的上帝呀,请你告诉我,我的未来会怎样?”上帝说:“我的孩子,你去问Lippman,他现在领导的程序员的队伍可能是地球上最大的”。于是他去问Lippman。Lippman说:“程序员的未来就是驾驭程序员”。这个程序员对这个未来不满意,于是他又去问上帝。“万能的上帝呀,请你告诉我,我的未来会怎样?”。上帝说:“我的孩子,你去问Gates,他现在所拥有的财产可能是地球上最多的”。于是他去问Gates。Gates说:“程序员的未来就是榨取程序员”。这个程序员对这个未来不满意,于是他又去问上帝。“万能的上帝呀,请你告诉我,我的未来会怎样?”。上帝说:“我的孩子,你去问侯捷,他写的计算机书的读者可能是地球上最多的”。于是他去问侯捷。侯捷说:“程序员的未来就是诱惑程序员”。这个程序员对这个未来不满意,于是他又去问上帝。“万能的上帝呀,请你告诉我,我的未来会怎样?”。上帝摇摇头:“唉,我的孩子,你还是别当程序员了”。

32、面试官:“熟悉哪种语言”。应聘者:“JAVA”。面试官:“知道什么叫类么”。应聘者:“我这人实在,工作努力,不知道什么叫累”。面试官:“知道什么是包?”。应聘者:“我这人实在 平常不带包 也不用公司准备了”。面试官:“知道什么是接口吗?”。应聘者:“我这个人工作认真。从来不找借口偷懒”。面试官:“知道什么是继承么”。应聘者:“我是孤儿没什么可以继承的”。面试官:“知道什么叫对象么?”。应聘者:“知道,不过我工作努力,上进心强,暂时还没有打算找对象。”。面试官:“知道多态么?”。应聘者:“知道,我很保守的。我认为让心爱的女人为了自已一时的快乐去堕胎是不道德的行为!请问这和C#有什么关系?”。

33、IT工程师=加班狂+程序员+测试工程师+实施工程师+网络工程师+电工+装卸工+搬运工+超人,有同感的转走。

34、 用一句话总结了HTML,CSS,JS的关系。HTML是名词,JS是动词,CSS是形容词和副词。

35、我是个程序猿,一天我坐在路边一边喝水一边苦苦检查bug。这时一个乞丐在我边上坐下了,开始要饭,我觉得可怜,就给了他1块钱,然后接着调试程序。他可能生意不好,就无聊的看看我在干什么,然后过了一会,他幽幽的说,这里少了个分号。。。分号。。。分号。。。

36、女友对程序员说:“紫禁城占得地方好大呀!”程序员:“杀死那个子进程……”

37、从前有个全国管理系统,是孙中山做的设计,老蒋做的实现,结果老毛写了个病毒,趁着日本黑客对系统做攻击的当口,拿到了管理员权限,把原来那批程序员给隔离了。老邓接手以后,重构代码,出了个2.0版,为了开发速度,遗留了一堆BUG没处理。人们纷纷质疑:是不是核心构架太单一,双核会不会好点?

38、一程序员家的水管坏了,他打电话叫来一个水管工修理。 水管工鼓捣了一个小时,终于把管子修好了,他递给程序员一张600元的帐单。 “600元!”程序员愤怒地说:“我当程序员一天都赚不了这么多钱!” “是啊。”水管工平静地说,“我当程序员的时候也是。”

39、十年前,女:“对不起,我不会喜欢你的,你不要再坚持了,就好比让 Linux 和 Windows 同时运行在一台PC机上,可能吗?”男生听后默默走开,十年后,在一次虚拟技术大会上,我听到一名虚拟技术开发程序员给我讲述了这个故事。

40、程序猿问程序媛:”为什么要离开我,我做得还不够好吗?” 媛说:”别傻了,我们根本就是两个世界里的人,就像在JS里永远都无法调用JAVA类一样,我们之间也是不可能的。” 猿沉默了很久,转身离开了。一个月之后,他在开源社区公布了dwr的完整代码。

41、【程序员被提bug之后的反应】1.怎么可能; 2.在我这是好的,不信你来看看; 3.真是奇怪,刚刚还好好的; 4.肯定是数据问题; 5.你清下缓存试试; 6.重启下电脑试试; 7.你装的什么版本的类库(jdk) 8.这谁写的代码; 9.尼玛怎么还在用360安全 浏览器 ; 10.用户不会像你这么操作的。

42、敲一夜代码,流两行老泪;用三种语言,唯四肢受罪 ; 待五更鸡鸣,遇骤雨初歇;遂登门而去,伫十里长亭;欲望穿泪眼,无如意郎君;借微薄助力,愿寻得佳偶;成比翼双鸟,乃畅想云端;卷情网之内,做爬虫抓取;为连理桂枝,容数据分析;思千里子规,助框广天地; 念茫茫人海,该如何寻觅?

43、早晨一女生抱着一堆书进了阅览室,结果警报响了,大妈让女生看看是哪本书把警报弄响了,那女生把书倒出来,准备一本一本的测。大妈见状急了,把书分成两份,第一份过了一下,响了。又把这一份分成两份接着测,三回就找到了,大妈用鄙视的眼神看着女生,仿佛在说O(n)和O(log2n)都分不清。

44、发现程序员经常熬夜有三个弊端:第一,记忆力越来越差;第二,数数经常会数错;第四,记忆力越来越差。

45、医院回来的程序猿一脸的苦逼样。程序媛:怎么了?程序猿:得了类风湿性关节炎了,我怕会遗传给下一代啊。程序媛:谁说类风湿性关节炎能遗传的?程序猿一脸诧异:类不是继承的吗?

46、知道JAVA程序员和C程序员的差别吗?食堂里,吃完饭就走的是JAVA程序员,吃完饭还要自己 收拾的那就是是C程序员。至于为什么会这样、大家都明白(因为JAVA自带垃圾回收机制、、、C需要手动释放内存)←这就是原因

47、计算机系的男同学追班里一女同学,结果此女总是躲躲闪闪。 男的看没戏,就另找了一个去追,结果这女的不满意了,质问这男的为啥抛弃她。 男的问:“请教一个电脑问题,如果你点击一个程序,总是提示‘没有响应’,怎么办?” 女的说:“马上结束任务。” 男的:“对,我也是这样想的。”

48、一个程序员的吐槽:即要被当做修电脑的,也要被当作做网站的;即要被当作杀毒的,也要被当作盗号的。我要告诉大家,其实我们只是写代码的。

49、如果一个足球界的人“猝死”了,会被怀疑和赌球有关;如果一个官员“猝死”了,会被怀疑和贪腐有关;如果一个农民”猝死”了,会被怀疑和拆迁有关;而如果一个程序员猝死了,那他真的猝死了。

50、老婆是操作系统,一但安装卸载十分麻烦;小秘是桌面,只要你有兴趣可以天天更换;情人是互联网,风光无限花钱不断;小姐是盗版软件,用时记着先杀毒。

51、前台美女三宝:你好,找谁,倒饮料。产品经理三宝:山寨,改版,再推倒。项目经理三宝:进度,流程,做报表。团队经理三宝:团建,开会,评绩效。程序员三宝:闷骚,加班,修电脑。

52、对于程序员来说、没老婆不悲催。悲催的是、没老婆、控制台还不停的提示你Error:could not find the object

53、假如生活欺骗了你,不要悲伤不要心急。《代码大全》会一直陪伴着你……

54、有时候真觉得有些事情如同char*一般,从开始就注定,无法改变。

55、洛阳亲友如相问,就说我在敲代码。

56、”如果你ctrl+alt+del,蹦出任务管理器,你从上到下扫一眼,所有的进程你都认识,知道他们是干什么的,并且知道关掉有什么后果,而且你还能从CPU和内存占用的数字跳动上清楚的知道电脑现在什么状态,那么你应该没有女朋友”………..你妹啊

57、用IE6的吃方便面都没有调料包,你知道不知道……

58、普通青年用IDE(Visual Studio, Eclipse, XCode);文艺青年用VIM, Emacs;二逼青年将IDE设置成VIM模式。

59、程序员换IDE相当于搬家,换主力语言相当于改嫁,换操作系统相当于参加FBI证人保护计划…

60、有两个程序员钓鱼,其中一个钓到一条美人鱼,这个美人鱼上半身是美女,下半身是鱼,于是这个程序员 就吧她放了,另一个问他:Why,他回答说:没有API

61、阿里小米皆自主,百度排名最公平;京东全网最低价,当当爱国很理性;用户体验看新浪,网易从来少愤青;豆瓣从来不约炮,人人分享高水平;从不抄袭数腾讯, 开放安全三六零。

62、编程夜当午,手握小滑鼠。谁知编程辛,行行皆“心”苦;头昏不觉晓,使劲揉眼角。夜夜太辛苦,睡眠知多少;

63、热火朝天的办公室,一精壮青年一边啃着馒头,一边看着眼前产品,愁眉紧锁的他陷入了沉思:产品下一步应该怎么走?如何保证代码质量?如何缩短项目时间?如何控制项目成本?一个个难题需要他思索,抉择。此时,传来项目经理的吆喝:“程旭元,先别敲代码了!给我修下电脑……”

64、原来《人月神话》不是本奇幻小说! 原来《代码大全》不是一堆开源代码! 原来《鸟哥的Linux私房菜》不是教你做菜! 原来《边城》不是教你写代码的! 原来《深入浅出HTML》不是教你How to Make Love

65、文艺程序员写代码追求让别人看懂,普通程序员追求让自己看懂,2B程序员则追求让编译器能看懂;半年后看自己当初写的代码,文艺程序员不知道是自己写的但很容易看懂,普通程序员知道是自己写的但是不太容易看懂,2B程序员埋头看了半天后拍着桌子吼到:“这是哪个SB写的程序!”

66、我真的想让这个世界变得更好,但是他们不给我源代码……