2010-11-01

Ruby Cheat Sheet#1 反射



irb(main):001:0> a = Object.new
=> #<Object:0x7ff7a6ff6388>
irb(main):002:0> a.object_id #获取对象ID
=> 70350817702340
irb(main):003:0> a.class #获取对象类型
=> Object
irb(main):004:0> Integer.superclass #获取父类
=> Numeric
irb(main):006:0> Numeric.superclass #...
=> Object
irb(main):007:0> Object.superclass #Object父类为 nil
=> nil
irb(main):011:0> Integer.ancestors #获取类的父类及 include 的类
=> [Integer, Precision, Numeric, Comparable, Object, Kernel]
irb(main):013:0> Integer.class #类的类型是 Class
=> Class
irb(main):019:0> 1.methods #查找对象的方法
=> ["%", ...]
irb(main):020:0> 1.public_methods #查找对象的公开方法
irb(main):022:0> 1.protected_methods #查找对象的保护方法
irb(main):023:0> 1.private_methods #查找对象的私有方法

irb(main):029:0> class << a
irb(main):030:1> def foo; end
irb(main):031:1> end
=> nil
irb(main):032:0> a.singleton_methods #查找对象的单例方法
=> ["foo"]

irb(main):033:0> a.methods.grep /methods/ #快速查询方法
=> ["public_methods", "methods", "singleton_methods", "protected_methods", "private_methods"]

irb(main):034:0> Integer.methods #类也是对象,也有 :methods
=> ["private_class_method", ...]
irb(main):036:0> Integer.instance_methods #获取实例方法
irb(main):037:0> Integer.public_instance_methods #...
irb(main):038:0> Integer.protected_instance_methods #...
irb(main):039:0> Integer.private_instance_methods #...

irb(main):041:0> 1.method :times #获取方法对象
=> #<Method: Fixnum(Integer)#times>
irb(main):042:0> 1.method(:times).call #执行方法对象
=> #<Enumerable::Enumerator:0x7ff7a703fb78>
irb(main):044:0> 1.send :times #执行方法对象
=> #<Enumerable::Enumerator:0x7ff7a7019090>
irb(main):045:0> 1.__send__ :times #执行方法对象
=> #<Enumerable::Enumerator:0x7ff7a6ffd1b0>

irb(main):047:0> m = 1.method :times
=> #<Method: Fixnum(Integer)#times>
irb(main):048:0> m.name #方法名称
=> "times"
irb(main):051:0> m.owner #方法所在的类
=> Integer
irb(main):054:0> m.to_proc #转成 Proc 对象
=> #<Proc:0x00007ff7a6f955d8@(irb):54>
irb(main):055:0> m.unbind #解除绑定
=> #<UnboundMethod: Fixnum(Integer)#times>
irb(main):056:0> m.arity #最少参数个数
=> 0
irb(main):057:0> m.receiver #绑定的对象
=> 1

irb(main):058:0> m = Integer.instance_method :times #获取未绑定的对象
=> #<UnboundMethod: Integer#times>
irb(main):060:0> m.bind(1) #绑定
=> #<Method: Fixnum(Integer)#times>
irb(main):061:0> m.owner #方法所在的类
=> Integer
irb(main):062:0> m.arity #最少参数个数
=> 0
irb(main):063:0> m.bind(Object.new) #绑定时会做类型检查
TypeError: bind argument must be an instance of Integer
from (irb):63:in `bind'
from (irb):63
from :0
irb(main):075:0> Integer.method_defined? :times #查询类的实例方法是否被定义
=> true
irb(main):076:0> Integer.public_method_defined? :times
=> true
irb(main):077:0> Integer.protected_method_defined? :times
=> false
irb(main):078:0> Integer.private_method_defined? :times
=> false

irb(main):089:0> 1.respond_to? :times #检查对象是否有方法
=> true
irb(main):092:0> 1.respond_to? :raise
=> false
irb(main):093:0> 1.respond_to? :raise, true #同时检查私有方法
=> true
irb(main):106:0> [1.class, 1.class.superclass]
=> [Fixnum, Integer]
irb(main):098:0> 1.is_a? Fixnum #检查1是否是 Fixnum 或其子类的对象
=> true
irb(main):099:0> 1.is_a? Integer #检查1是否是 Integer 或其子类的对象
=> true
irb(main):103:0> 1.instance_of? Fixnum #检查1是否是 Fixnum 的对象
=> true
irb(main):104:0> 1.instance_of? Integer #检查1是否是 Integer 的对象
=> false
irb(main):108:0> Fixnum.instance_methods - Integer.instance_methods #查看 Fixnum 类新添加的实例方法
=> ["%", "<<", "&", ">>", "to_sym", "*", "+", "-", "/", "|", "size", "~", "^", "**", "to_f", "id2name", "[]"]

补1: 刚才忘了变量部分了

irb(main):001:0> class A
irb(main):002:1> @@b = 1
irb(main):003:1> @c = 2
irb(main):004:1> def initialize
irb(main):005:2> @d = 3
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0> A.class_variables
=> ["@@b"]
irb(main):010:0> A.class_variable_defined? "@@b"
=> true
irb(main):011:0> A.instance_variables
=> ["@c"]
irb(main):012:0> A.instance_variable_defined? "@c"
=> true
irb(main):014:0> A.instance_variable_set "@c", 15
=> 15
irb(main):015:0> A.instance_variable_get "@c"
=> 15
irb(main):016:0> a = A.new
=> #<A:0x7f55a5717c88 @d=3>
irb(main):018:0> a.instance_variables
=> ["@d"]
irb(main):019:0> a.instance_variable_defined? "@d"
=> true
irb(main):020:0> a.instance_variable_set "@d", 16
=> 16
irb(main):021:0> a.instance_variable_get "@d"
=> 16

[随笔] 只会一种语言带来的风险, 我的观点

今天 shanghai on rails 三周年聚会时提到一个问题,一个程序员能否只会一种语言,只会一种语言会带来什么样的技术风险?

我的观点是只会一种语言确实会带来风险,风险主要来自于程序员会变得偏执以及语言本身会跟不上需求的演化。前一点比较主观,所以主要说说语言本身跟不上需求演化的问题。

在 C 和 Fortran 时代, 编程的一个主要指导思想是如何充分利用 CPU 和内存,所以我们会推崇 blas, lapack, atlas 之类的数学库, 会去研究如何微调编译器选项, 会说程序就是算法+数据结构,会去提醒朋友使用 minmax 算法因为他只需比较 ~1.5N 次而不是 2N 次。

逐渐地,需求变更了, 我们觉得面向对象的设计很重要, 他能让我们能处理一些更复杂的业务,为此我们愿意牺牲一点性能, 所以 C++ 出现了。C++ 尽管声称性能不妥协,但在虚函数,异常,RTTI 上毕竟对性能做了一些妥协,而且由于 C++ 工程上难度的提高, 导致在实际实施中会损失了更多的运行效率。这时期我们会推崇 MFC, QT, WX 这类的 UI 库, 以及 ACE 之类的网络库。为了降低 C++ 工程上的风险,我们会去读 Effective C++, Exceptional C++ 这类书,会去尽量找有较长工作经验的工程师。

需求再次变更了,我们无法容忍 C++ 的低鲁棒性. 变量未初始化, 提前释放,缓冲区溢出, 复杂的多线程实践, 没有底层安全机制, 这些问题导致了一个又一个的项目失败。所以 Java 出现了, Java 抛弃了性能不妥协,引入了 GC, 引入了更方便的同步机制, 更堵住了一些让 C++ 程序崩溃的漏洞。这时期我们会推崇 J2EE,几乎霸占了整个企业应用市场。我们会教导我们的同事要使用 TDD, 接口要和实现分离, 要使用检查异常。

接着需求就出现分支了, 一支说 Web 开发很重要, 我们需要开发更有效率, 语言需要更灵活, 所以 Ruby, Python 这类的语言开始流行,诞生了 Rails, Django 这类优秀的 Web 框架。特别是 Ruby, 在引入 MixIn, Block, DSL, BDD, Bundler 后,极大程度地提高了开发效率。

Java 也做了一个很大规模的演化: Java 5, 再加上 SSH 框架, 引入 Annotation, 大规模使用非检查异常, 引入 Aspect, 引入 cglib, ORM, 尽管仍然叫 Java, 但是跟我们之前的 Java 已经有了很大的不同, 设计目标不再是安全,鲁棒,而是高效开发,甚至产生了 Spring Roo, Play framework 之类相当背离 Java 原始开发风格的框架。尽管很成功, 但是作为一种静态语言,他背了太多的包袱,开发效率上始终比不上动态语言。同时由于当初为了讨好 C++, 保留了原始类型和数组, 导致反射的使用异常痛苦,普通开发者很少使用反射来简化自己的设计。所以 Web 开发方面,尽管有 Structs 2, Spring 3 之类优秀的 MVC 框架,还是有更多的程序员选择逃离 Java。

另一支则说高并发很重要, 所以 Scala, Erlang, Concurrent Python 之类语言出现了.....(太晚了,不写了)。

所以只会一种语言的风险在哪儿?在于需求的变化很快, 语言的演化很慢(比如C++ 1x), 很痛苦(比如 Java 7), 失败率也很高(比如 Perl 6)。只会一门语言就意味着你把自己局限在了这种语言所擅长的问题圈子,你无法解决这个圈子之外的问题。

至于偏执, 这是我个人观察后得出的结论,你可以看看你周围的程序员,是否有这个问题

thanks.

PS. 为了避免不必要的纠纷,先说一句, lisp rocks.

2010-10-21

[读书笔记] LVM (更新)

原始版本: http://hi.baidu.com/lidaobing/blog/item/9509a344ed416a87b3b7dc2a.html


updated: ext2resize 不再可用,换到 resize2fs


它是什么?


LVM(逻辑卷管理)能将多个储存设备(包括分区,RAID设备等)合并成为一个新的储存设备。在这设备上可以建立多个分区。他支持动态调整分区大小,动态添加或删除底层设备。


安装



sudo apt-get install lvm2
sudo apt-get install e2fsprogs



创建


LVM 可以在磁盘的分区或者RAID上的分区(如/dev/md0)上创建



# pvcreate /dev/sdb /dev/sdc # 格式化分区为LVM格式
# vgcreate vg0 /dev/sdb /dev/sdc # 创建volume group
# vgdisplay vg0 # 显示volume group 信息
# apt-get install dmraid # 安装raid device map
# lvcreate -L 500G -n vol1 vg0 # 创建逻辑卷
# mke2fs -j /dev/vg0/vol1 # 在逻辑卷上创建 ext3 分区
# mount /dev/vg0/vol1 /mount/path


扩容 (无须停机)



# pvcreate /dev/sdd
# vgextend vg0 /dev/sdd
# lvextend -l +100%FREE /dev/vg0/vol1
# resize2fs -p /dev/vg0/vol1


磁盘损坏


还有额外的地方可以连接新的硬盘 (无须停机)


比如 /dev/sdb 损坏,接入的新硬盘叫 /dev/sde



# pvcreate /dev/sde # 格式化新硬盘
# vgextend vg0 /dev/sde # 添加新硬盘到卷组
# pvmove /dev/sda /dev/sde # 移动数据
# vgreduce vg0 /dev/sda # 卸载硬盘


没有额外的地方可以连接新硬盘


需要先缩小分区至可容纳在剩余的硬盘上



# pvmove /dev/sda # 移动数据至同组的其他硬盘
# vgreduce vg0 /dev/sda # 卸载硬盘
# 更换硬盘
# pvcreate /dev/sda
# vgextend vg0 /dev/sda
# 接上面的扩容步骤



参考文献


2010-10-19

[读书笔记] Rails 中的 Rack 中间件

0. 背景知识


0.1 Rack 协议


Rack 起源于 Python 的 WSGI 协议, 是一个语言相关的 HTTP 服务端接口 (CGI, FCGI, SCGI 是语言无关的接口, WSGI, Rack, Servlet 则属于语言相关的接口), 该协议下一个简单的应用形式如下


# config.ru
class Helloworld
def call(env)
[200, {'Content-Type' => 'text/plain'}, ['hello world!']]
end
end
run Helloworld.new

将该文件存为 config.ru, 然后在该目录下运行 rackup 即可启动该应用, 详细规范可以参考 http://rack.rubyforge.org/doc/files/SPEC.html

Rails 3.0, Sinatra 都使用了 Rack 作为自己的底层, 也就是说这两个 web 框架可以共用 Rack 中间件

0.2 Rack 中间件(middleware)接口



Rack 的协议本身很简单, 需要通过中间件来支持常见的 HTTP 功能,比如 cookie, session, flash, log, cache, ...

中间件本身也是一个 Rack 应用程序, 与简单 app 略有不同的是中间件通常以下一个 app 作为初始化参数

如下的代码就是 Rack::Runtime 中间件,用于记录应用程序的执行时间:

module Rack
class Runtime
def initialize(app)
@app = app
@header_name = "X-Runtime"
end

def call(env)
start_time = Time.now
status, headers, body = @app.call(env)
request_time = Time.now - start_time
if !headers.has_key?(@header_name)
headers[@header_name] = "%0.6f" % request_time
end
[status, headers, body]
end
end
end

中间件的使用也很方便, 只需加一句 "use Rack::Runtime" 即可, 如下所示

# config.ru
class Helloworld
def call(env)
[200, {'Content-Type' => 'text/plain'}, ['hello world!']]
end
end
use Rack::Runtime
run Helloworld.new


1. 如何在 Rails 程序中查看使用了哪些中间件




$ rake middleware
(in /path/to/project)
use ActionDispatch::Static
use Rack::Lock
use Rack::Runtime
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::RemoteIp
use Rack::Sendfile
use ActionDispatch::Callbacks
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::MethodOverride
use ActionDispatch::Head
use ActionDispatch::BestStandardsSupport
use OpenIdAuthentication
run ProjectName::Application.routes

这些中间件大部分在 $GEM_PATH/railities-3.0.0/lib/rails/application.rb 的 default_middleware_stack 函数中定义。

2. 这些中间件有什么用



  • ActionDispatch::Static, 静态文件(即 public/ 下的文件)支持, 一般在生产环境下会禁用此功能
    Rack::Lock, 线程锁, 保证 rails 代码单线程运行, 同时设置 env['rack.multithread'] = false, 你可以通过设置 config.allow_concurrency = true 来去掉该中间件

  • Rack::Runtime, 统计运行时间, 放在 response 的 "X-Runtime" header 中

  • Rails::Rack::Logger, 比如 log/development.log 中的这一行 "Started GET "/" for 127.0.0.1 at Wed Sep 15 21:46:51 +0800 2010"

  • ActionDispatch::ShowExceptions, 截获异常,把异常转换为 HTTP 错误号 (一般转为 500, 但一些特殊异常转到相应的错误号,比如 "ActionController::MethodNotAllowed" 会被转为 405, 同时显示对应的错误页面,对应开发环境,会显示异常的 backtrace, 对于生产环境,则会显示 public/500.html, 对于测试环境,该中间件会被禁用,直接把异常抛出

  • ActionDispatch::RemoteIp, 解决服务器转发, 代理导致客户端真实 IP 丢失的问题,用户的真实IP放在 env["action_dispatch.remote_ip"]

  • Rack::Sendfile, 如果返回数据已经放在一个文件里边了(比如生成的 PDF), 则可以让 nginx 服务器直接从该文件读取,降低系统消耗

  • ActionDispatch::Callbacks, 测试环境下用于检测源文件是否改变, 产品环境下作用不明 (TODO)
  • ActionDispatch::Cookies, cookie 支持
  • ActionDispatch::Session::CookieStore, session 支持,此处使用 cookie store
  • ActionDispatch::Flash, flash 支持, 参见 http://guides.rubyonrails.org/action_controller_overview.html#the-flash
  • ActionDispatch::ParamsParser, 分析XML, JSON参数,放到 env["action_dispatch.request.request_parameters"]
  • Rack::MethodOverride, 支持用 POST 来模拟 PUT, DELETE, ..., 可以在 POST 使用 _method 参数,也可以使用 HTTP 头 "HTTP_X_HTTP_METHOD_OVERRIDE"
  • ActionDispatch::Head, 把 HEAD 请求转为 GET 请求, 同时设置 env["rack.methodoverride.original_method"] = "HEAD"
  • ActionDispatch::BestStandardsSupport, 设置 HTTP 头: X-UA-Compatible
  • ProjectName::Application.routes, 终于进入你的 rails 程序了, 开始路由, 同时开始使用 rails 的协议栈

3. 如何配置中间件


3.1 通过 config 来配置


可以参考 $GEM_PATH/railities-3.0.0/lib/rails/application.rb 的 default_middleware_stack 函数, 源码显示了如何使用 config 来控制中间件

3.2 直接修改中间件


你可通过 config.middleware (如果在配置外则需使用 Rails::Application.middleware) 来访问 middleware, 这是一个 ActionDispatch::MiddlewareStack 对象, 你可以通过他的方法来操纵 middleware, 比如你要删除 Rack::Runtime, 就可以新添 config/initializers/stack.rb 文件,内容如下
Rails::Application.middleware.delete Rack::Runtime

3.3 覆盖 default_middleware_stack 函数


修改 config/application.rb, 把 Application 类改为如下所示

module MyApp
class Application < Rails::Application
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |x|
x.use Rack::Runtime
end
end
end
end

再运行 rake middleware, 可以看到大部分 middleware 都不见了

3.4 通过 rails 的插件体系


比如 vendor/plugins/open_id_authentication/init.rb 中间就有一句:
config.middleware.use OpenIdAuthentication

这句话就把 OpenIdAuthentication 这个中间件放到了 MiddlewareStack 的栈顶

2010-10-08

approx

打包时经常需要反复下载一些包, 如果你的网速不够快,建议安装 approx 透明代理, 可以缓存大部分包。

1. 如何安装 approx
安装方法: "sudo apt-get install approx", 然后修改配置文件 /etc/approx/approx.conf 为
ubuntu http://cn.archive.ubuntu.com/ubuntu
debian http://mirrors.163.com/debian
修改 /etc/apt/sources.list, 修改为如下的内容
deb http://127.0.0.1:9999/ubuntu lucid main restricted universe multiverse
deb http://127.0.0.1:9999/ubuntu lucid-security main restricted universe multiverse
deb http://127.0.0.1:9999/ubuntu lucid-updates main restricted universe multiverse

2. approx 的优点
2.1 可以在局域网内共享, approx 缺省绑定在 0.0.0.0, 所以一个局域网内用户可以共享这个源,互相加速, 如果使用安装法部署大量机器时也非常快速
2.2 i386 和 amd64 系统可以互相加速(因为可以互相共享架构无关的包),karmic 和 lucid 也可以互相共享包 (某些包在这两个版本是一致的), 但 debian 和 ubuntu 之间不能共享包
2.3 即使单人使用, approx 也很有帮助,比如可以快速重建 linux chroot, pbuilder, 或者在虚拟机内重装一份debian/ubunu
2.4 可以模拟多线程下载,比如你要同时安装 vim 和 emacs, 可以先运行 "sudo apt-get install vim", 然后在下载时按 Ctrl+C 终端,然后运行 "sudo apt-get install emacs23", 这时候 approx 会在后台帮你下载 vim 所需的包 (一般会帮你下载前面5个)
2.5 智能cache, debian/ubuntu 仓库有逐级签名, approx 能快速判定一个文件,是不是完整的,是不是最新的, 也可以定时清理掉不再被使用的文件。

2010-10-02

在 Ubuntu 构建 Debian 打包环境

0. 原因
尽管你是在 Ubuntu 下工作,但由于种种原因[1],你还是需要给 Debian 做打包工作,如果你不想你做的包因为一些简单的错误被退回,比如无法在 Debian 下编译,没有处理好 lintian 警告,那么最好在 Ubuntu 下给 Debian 打包的环境,步骤如下所示:

1. 安装 approx
打包时经常需要反复下载一些包, 如果你的网速不够快,建议安装 approx 透明代理, 可以缓存大部分包。安装方法: "sudo apt-get install approx", 然后修改配置文件 /etc/approx/approx.conf 为
ubuntu http://cn.archive.ubuntu.com/ubuntu
debian http://mirrors.163.com/debian

2. 修改 /etc/apt/sources.list, 修改为如下的内容
deb http://127.0.0.1:9999/ubuntu lucid main restricted universe multiverse
deb http://127.0.0.1:9999/ubuntu lucid-security main restricted universe multiverse
deb http://127.0.0.1:9999/ubuntu lucid-updates main restricted universe multiverse

3. 安装工具包, 运行 "sudo apt-get install ubuntu-dev-tools cowbuilder"

4. 准备 cowbuilder-sid
$ sudo cp /usr/bin/pbuilder-dist /usr/bin/cowbuilder-sid
$ sudo sed -ie 's,ftp://ftp.debian.org/debian,http://127.0.0.1:9999/debian,' /usr/bin/cowbuilder-sid

5. 准备开发环境
$ sudo mkdir /var/cache/pbuilder-dist/sid_result
$ cowbuilder-sid create

6. 开始开发
$ dget http://ftp.debian.org/debian/pool/main/h/hello/hello_2.6-1.dsc
$ dpkg-source -x hello_2.6-1.dsc
$ cd hello-2.6
$ # 修改 debian 包,版本改为 2.6-2
$ debuild -S -sa
$ cd ..
$ cowbuilder-sid hello_2.6-2.dsc # 使用 sid 环境进行编译

7. 检查
7.1 安装最新版的 lintian
Ubuntu 自带的 lintian 版本不够新,需要从 Debian 安装最新版本
$ # 到 http://ftp.us.debian.org/debian/pool/main/l/lintian/ 下载最新版本的 lintian
$ wget http://ftp.us.debian.org/debian/pool/main/l/lintian/lintian_2.4.3_all.deb
$ sudo dpkg -i lintian_2.4.3_all.deb
$ sudo apt-get -f install

7.2 对包做最后的检查
$ cd /var/cache/pbuilder-dist/sid_result
$ lintian -i hello_2.6-2_amd64.changes
$ #如果上一步出现错误,则继续修正
$ debsign hello_2.6-2_amd64.changes #签名

8. 上传
根据 http://mentors.debian.net/cgi-bin/maintainer-intro 的介绍准备好 mentors.debian.net 的上传的环境, 然后用如下的命令上传
$ dput mentors hello_2.6-2_amd64.changes

9. 召唤 Debian Developer (DD)
到 mentors.debian.net, 登录后可以找到一份模板邮件, 把模板邮件中的内容填好,然后发到 debian-mentors@lists.debian.org
如果你有相熟的 DD, 也可以直接发信给他让他帮忙检查。

2010-08-28

关于 Debian 的一些事实

昨天在 SHLUG 的每月聚会上, 和朋友聊起 Debian, 是 Debian Developer (简称为 DD), 当时另外一位 developer zigo 也在 (法国人,定居中国), 就跟大家介绍了 Debian 的一些工作方式, 发现大家对 Debian 还是有误解和不理解。现整理如下

  • Debian 是纯社区支持的发行版,没有一个公司主导, 这个与 Ubuntu, Fedora 不一致
  • Debian 没有需要付薪水员工, 一个也没有
  • Debian 后面与钱有关的组织叫 SPI, 一般是有事的时候才开始募钱, 所以收支少的时候一个月就 1000 美金左右,多的时候大概是几万美金(比如每年的 DebConf), DD 可以加入他们的列表,每月有一份账单, openoffice, postgresql 的钱似乎也归他们管,所以也能看到他们的账单,他们的账单比 Debian 还要小。
  • Debian 的领导人每年选举一次, 每个DD把候选人进行排序, 如果其中一人严格优于其他候选人,那么他就当选,否则重选, 比如 2010 年的选举: http://www.debian.org/vote/2010/vote_001
  • 尽管领导人每年一选,但具体执行事务的人不需要进行选举,也很少更换
  • Debian 的大部分事务都是动议,反对制, 也就是说你有一个想法, 就把想法发到邮件列表,没人反对你就可以做了,有人有意见,就大家讨论,达成一致你就可以实施了。
  • 对于一些有重大分歧的, 可以使用 General Resolution, 提出几个方案,大家再来投票, 比如以前的 GFDL 是否满足 DFSG, 是否引入 DM 等。
  • Debian 的沟通方式以 email 为主, IRC 为辅, 甚至报告 bug 也是通过 email, 对 email 方式不熟的话还是通过 reportbug 或者 reportbug-ng
  • 一个软件包需要进入 Debian, 只需要通过一个 DD 的同意即可, 另有一个小组(叫 ftp-master) 负责检查软件的 license 是否干净。
  • 申请 DD 的流程很长, 中位值时间大概是 10 个月, 我花了一年多。
  • 中国的 DD 总共 7 个(包括 zigo), 但其中一个帐号被锁, 可以通过 http://db.debian.org/ 查询
  • 现在有八百多 DD, 但上次Debian Leader 选举只有不到 400 人参加

2010-08-15

我们赶上了自由软件运动的尾巴

个人观点,仅供参考。

自由软件运动: 其实目的已经达成

自由软件运动最初的目标是为了建立一个 UNIX 复制品(clone)。这个复制品不能有版权问题, 同时也能让用户自由地使用,修改和分发。现在仔细看看, 这个目标其实已经达成, 甚至比当初设想的更多, 现在不仅有了 Linux 内核和GNU 工具, 甚至还有 GNOME, KDE 这样的桌面环境,openoffice 这样的办公套件, 可以用来吸引更多的用户。

GPL: 目的还是手段?

GPL协议是自由软件运动的一个重要组成部分。但相比于GPL协议, BSD, Apache 这类的协议给了用户更大的权利, 那么当初为什么自由软件运动会选择这种不太自由的协议呢?

个人觉得采用 GPL 是为了更快地实现自由软件运动这个目标, 也就是说GPL本身只是手段而不是目的。比如你用 GPL 协议写了一些漂亮的库(比如 libreadline), 如果开发者基于这个库写了一些软件,那么他的软件也会以 GPL 发布, 丰富了自由软件仓库。如果你用 GPL 写了一个软件, 其他开发者修改这个软件后, 再发布时也只能用 GPL 发布, 这保证了任何对软件的改进都可以回馈给原始项目, 这样可以加快改善软件质量。

但 GPL 限制了软件的使用, 特别是对于库来讲, 你无法使用 GPL 的库来开发私有软件, 所以又有了 LGPL 协议, 放松了对链接的限制, 但对软件的改进仍然保证能被回馈给原始项目。

诚然, GPL 协议对加快实现自由软件运动的目标起了很大的作用,但在这个目标已经达成的情况下, GPL 的劣势就逐渐显露出来了。


宽松的协议更易推广

现在GNU/Linux最大的用途还是在企业应用领域, 而在GPL在这个领域实在是很难推广。在这个领域里,尽管大家的协议都尽量做到与GPL兼容, 但采用GPL的少之又少, 我看到的只有 openjdk, mysql 这两个采用的是 GPL with linking exception, 其实加上 linking exception 后,这个协议就跟 LGPL 类似了(甚至更宽松)。 Ruby 是双 license, 其中一个是 GPL, 另一个 Ruby License 则非常宽松。我用过的一些其他技术,比如 ROR (MIT license), Django(BSD), Spring(Apache), Lucene(Apache), Scala(BSD), Erlang (MPL), Hadoop(Apache), ... 都没有采用 GPL, 应当都有推广会受限的考虑。

非企业应用领域, 对于普通程序, GPL 仍然有一定优势(比如保护开发者的心血不被掠夺), 但用在库上面, GPL 已经被视为一个不友好的协议。志愿者也会开发一些更宽松协议的替代品, 比如 libreadline, 现在已经有了 BSD 协议的 editline 库。opencc项目创建时,跟开发者聊起重造轮子的动机时,也提到 cconv 是 GPL 协议分发, 会给其他开发者造成困扰。

GPL 协议的另一个问题是没有回头路可走, 你如果在一个开源项目开始时使用 GPL 协议, 后来发现部分代码可以独立出来,作为一个库使用。这个时候你会发现对库重新设定授权协议(比如迁移到更宽松的LGPL)也是一个很麻烦的工作, 因为你需要取得所有贡献者的许可, 而某些贡献者可能已经联系不上了。


画饼充饥

以前一直有一个讨论, 自由软件作者如何养活自己这个问题。也出现过一些建议,包括技术支持, 捐赠, ...。技术支持对于已经流行起来了的框架软件是合适的,甚至也能挣不少钱(比如 spring), 但对于中小软件来说,这就是个笑话。对于捐赠, 也许 vim 是唯一的一个成功案例。从现实来看找一份稳定的工作, 然后在业余时间搞自由软件仍然是最方便也最大众的解决方案。这个也造成,如果一个自由软件没有公司支持, 那么就会出现推进能力不足,无法把控进度的问题。Debian 每次发行都会跳票,也很这个有很大关系,这也成就了 Ubuntu 现在的地位。


FSF: 其实作用不大

自由软件基金会(Free Software Foundation, FSF) 最早的作用是在 GPL 协议被侵犯时, 帮忙打官司的。但从现在来看, 敢于公然侵犯 GPL 的案例少之又少(中国不少, 但他又不来打官司)。FSF 要求旗下软件(比如gcc, emacs, ...)的开发者把版权转让给 FSF, 这也冒犯了部分开发者。最近推行的一些活动, 比如反对 DRM, 反对 Windows 7, 反对 Microsoft Office 格式标准化, 这类活动不仅没有得到社区的一致支持, 也没有取得成效, 大家对 FSF 难免有些失望。其他方面的推广工作就更少了, 比如 GNU Guile 其实也不算成功。


RMS: 光环渐消

RMS 对自由软件的定义有一套个人的标准, 因为这个标准, 跟很多社区吵过架,比如 BSD, Mono, ... 也跟 Linus 在 GPLv3 的问题上吵过。这些都降低了 RMS 的号召力。而对 SaaS 这类运动的攻击, 则把他自己推到了一个保守主义的位置。RMS 的观点似乎停留在了互联网之前的时代, 他的演讲, 演讲后的拜神活动都有那个时代的影子。 RMS 不再是那个引领潮流的人。


浪潮已经转移

现在其实也是一个软件业大转型的时期, 不仅有 智能手机, 平板, Web 这种新型的终端,也有 Android OS, iOS, Chromium OS 这种新型操作系统的设计理念, Scala, erlang 这种面对新挑战的语言,还有 Django, ROR 这类的 web 快速开发框架。大量的 Geek 们正在这些领域展现自己的聪明和才智,而这些似乎都不再与 RMS, FSF, 自由软件运动相关了。

自由软件软件运动目标已经达成, GPL 已经完成他的历史使命。即使没有 GPL 的保护, 现有的 GNU/Linux 也不会被摧毁。如果自由软件运动不能提出更有号召力的方向, 那么只会慢慢消失, 其实消失也不是坏事。