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 也不会被摧毁。如果自由软件运动不能提出更有号召力的方向, 那么只会慢慢消失, 其实消失也不是坏事。

2010-07-18

[读书笔记] 从 TDD 到 BDD

最近在学 ruby, 也用了 rspec, 传统的 xUnit 是 TDD 指导思想下的产物, 而 rspec 则算是 BDD (Behavior Driven Development) 影响下的产品。

TDD 和 BDD 区别究竟何在呢?

首先是思路上的区别, 传统的 TDD 关注的是接口是否被正确地实现了, 所以通常每个接口有一个对应的单元测试函数。而 BDD 通常以类为单位, 关注一个类是否实现了文档定义的行为

具体从 rspec 的实现上来看, 主要有如下的不同

1. rspec 可以成为设计的一部分 , 你在设计一个类的时候, 一般会先写这个类需要实现哪些功能, 然后在逐个实现需求,
比如 XmlParser, 你会先计划他有三个特性, 支持从字符串初始化, 支持"=="符号, 支持序列化为字符串,
这时候你就可以写下如下的测试代码



# xml_parser_spec.rb

describe "XmlParser" do

  it "should support init with string"

  it "should support =="

  it "should can convert to string"

end



此时运行如下的命令就可以看到你规划的类行为

$ spec -f n xml_parser_spec.rb

XmlParser

  should support init with string (PENDING: Not Yet Implemented)

  should support == (PENDING: Not Yet Implemented)

  should can convert to string (PENDING: Not Yet Implemented)

...



上面的输出可以清晰地看到 XmlParser 需要支持的功能。你不用把这些功能需求记在脑子里, 也不放在你的 TODO 列表里,直接放这儿就行了


2. 使用一个句子来描述你要测试的行为, 而不是简单的重复被测试的接口名, 比如一个典型的测试:
it "should return [Miniblog] with different id" do
其中的 "should return [Miniblog] with different id" 就是这个测试的描述

3. 多级的 context, 便于将类的行为分到多个组, 同时每个组可以有不同的上下文环境(setUp+tearDown), 比如一个 xml parser, 如下所示的测试代码可以把行为分为两组,在遇到合法数据时的行为以及遇到非法数据时的行为

describe XmlParser do
  context "with valid data" do
    before do
      @valid_data = "<a>123</a>"
    end
    ...
  end
  context "with invalid data" do
    before do

      @invalid_data = "<a>123</b>"

    end

    ...

  end  
end

4. 支持自动文档化, 在1里已经可以看到 spec 支持生成一个简略的文档来描述你的对象, 下面是一个比较长的例子用于描述 Douban::Authorize 的功能, 树状的结构把类的功能分到了多个小组


5. 表述更接近自然语言的习惯, 比如 xUnit 通常使用 assertEqual(a, 1), 而 rspec 经常使用 a.should == 1

相对于 TDD 来说, 这些都不是什么革命性的突破。但是思路的转变,能够让你的开发更流畅。

首先你在设计时就可以开始写测试代码, 在开发完成后可以通过 rspec 生成的文档来快速浏览你是否完成了所有特性。这些性质都能让测试驱动开发更容易推广。从我的工作经验来看, 由于种种原因 (比如没有迭代周期, 缺乏 code review 制度, 没有硬指标的覆盖率要求...), 大家的测试代码写得很少。而在 rspec 的帮助下, 直接看 rspec 生成的文档就可以督促大家补全测试。

参考资料

[1] The RSpec Book

2010-07-12

SCRIBEFIRETuDFJ9hOFzSCRIBEFIRE

SCRIBEFIREt3xl3sLqSCRIBEFIRE

2010-05-21

[有问有答] Debian 缩略语

Q: 经常在邮件列表中看见 RFS, 这是什么意思
A: RFS 代表 Request For Sponsor, 当你打好了一个包, 需要人上传时, 一般会发送一封邮件到 debian-mentors@lists.debian.org, 标题一般以 "RFS:" 开头, 这样大家就都知道你是想让人帮你上传包, 其他常用的缩写还有

角色:
  • DPL: Debian Project Leader, 就是 Debian 的总负责人, 每年选举一次, 现在的 DPL 是 Stefano Zacchiroli
  • DD: Debian Developer, Debian 开发者, 有投票权和上传包的权利, 我现在的角色就是 DD
  • DM: Debian Maintainer, 比 Debian 开发者权限低,简单来说只能上传自己维护的包,具体情况请看前面的链接
bug 相关:
  • RFP: Request For Packaging, 我觉得这个包应当加入 Debian, 但是我不想去打包, 所以请求谁来打包
  • ITP: Intent To Package, 我想打这个包, 已经在开始工作了, 大家不要抢, 抢也要先跟我联系
  • RFH: Request For Help, 我忙不过来了, 或者技术能力不足, 需要人帮忙一起维护
  • RFA: Request For Adoption, 这个包我不想继续维护了, 谁想接手
  • O: Orphan, 这个包已经被我放弃了, 谁想接收就接手吧
  • ITA: Intent To Adoption, 我要接手这个包
前面这六个缩写可以说是包的一个生命周期, 其中前 5 个你在运行 "reportbug wnpp" 时就可以看到(wnpp 的意思是 Work-Needing and Prospective Packages), 最后一个是修改别人的 "RFA:" 或 "ITA:" bug 后的新标题。这部分可以参考 wnpp 的首页

其他:
  • FTBFS: Failed To Build From Source, 其实就是编译失败, 经常在 bug 报告中出现
  • NMU: Non-Maintainer Upload, 经常在修改日志出现, 表名修改人员不是这个包的指定维护人员
  • NM Process:  New Maintainer Process, 新维护人员流程, 这个流程是针对申请 Debian 开发人员(DD)的, 但由于 DM 概念的引入, 这个名字现在带来了一些混淆, 估计会改一个名字吧.
  • QA: Quality Assurance, 质量保证, Debian 的 QA 小组主要负责每个包的管理首页, 比如 ibus 的, 还有开发工具来保证 Debian 的质量, 比如 lintian, DEHS, ..., 还有就是维护孤儿包(orphaned package)
  • i18n, l10n, m17n: 分别表示 internationalization (国际化), localization
    (本地化), multilingualization (多语言化), Debian 与这方面相关的内容在 http://www.debian.org/intl/l10n/
  • xxx@l.d.o: xxx@lists.debian.org, Debian 的 maillist, 懒得敲字的时候就变成了 @l.d.o
  • .d.o: .debian.org, 同上, 类似的原因
  • CVE: Common Vulnerabilities and Exposures, 一般在修改日志中出现, 一般表示一个安全漏洞
  • LP: launchpad, Ubuntu 的开发网站, 也是 Ubuntu 的 Bug 中心, 在关闭 Ubuntu 相关bug 时, 用这个来支持对应的 bug 号,比如 "LP: #317443", 就指 https://bugs.launchpad.net/bugs/317443
  • 当然还有很多日常使用的缩略语,比如 IMHO, LOL, 大家就自行维基百科吧

2010-05-20

[有问有答] 如何用 git 来管理你的打包工作

Q: 我看到很多包的 debian/control 里面有 Vcs-Git 和 Vcs-Browser 这样的字段, 这个是什么意思?
A: Vcs 的全称是 Version Control System, 即版本控制系统, 使用这两个字段表明打包工作使用了 Git 来协助管理的, 比如 chmsee 的 debian/control 就有如下的两行:
Vcs-Browser: http://git.debian.org/?p=chinese/chmsee.git
Vcs-Git: git://git.debian.org/git/chinese/chmsee.git
这个表示, 如果你要在线查看打包的情况, 那么就直接访问 http://git.debian.org/?p=chinese/chmsee.git
如果你要下载他的打包工作, 那么就运行 git clone git://git.debian.org/git/chinese/chmsee.git

使用版本管理来协助打包是有几个好处的
  1. 首先是常规版本管理带来的好处, 比如误删了文件可以马上恢复, 在多个机器上工作时可以很方便地同步, 一个复杂的工作切成多步后会更加清晰, 出现错误后能方便追踪
  2. 另外就是方便其他开发人员为你提交补丁, 他的补丁可以直接基于你最新的工作, 而不是基于你发布出去的那个版本, 这样能减少你合并补丁时的工作量
  3. 现在 debian 的包管理工作也在逐步演化为一组人管理一组包, 这样的好处是单个人由于某些原因离开 Debian 时,他的包不至于荒废(看着身边的朋友一个个转投 Apple 的怀抱, 我真得很伤心), 而一组人一起管理包时,版本管理系统就成为必须了
下面我就用 chmsee 来演示一下如何把一个包转为 git 管理
  1. 你要安装一些 git 工具包: sudo apt-get install devscripts git-buildpackage gitk git-gui
  2. 找一个空目录, 然后用如下的命令下载 chmsee 的源码包: dget http://ftp.debian.org/debian/pool/main/c/chmsee/chmsee_1.1.0-1.dsc (如果年代久远, 这个 URL 不再可用, 请到QA页面查找新链接)
  3. 运行 git import-dsc chmsee_1.1.0-1.dsc, 这时会创建出一个新目录: chmsee
  4. 进入 chmsee 目录, 运行 gitk --all, 可以看到已经有两个分支(master 和 upstream), 和两个 tag (upstream/1.1.0 和 debian/1.1.0-1)
  5. 运行 debuild -i.git 编译
日常操作: 发布新版本:
  1. 下载新版本, 比如 chmsee-1.1.1.tar.gz
  2. 在 chmsee 目录运行 git import-orig ../chmsee-1.1.1.tar.gz
  3. 运行 dch -v 1.1.1-1, 加入一行 new upstream release, 保存
  4. 运行 git add debian/changelog
  5. 运行 debcommit
  6. 运行 debuild -i.git 编译
如果你对 git 的操作本身已经非常熟悉, 那么 git-buildpackage 上手也没有什么难度。如果你对 svn 更拿手, 那么可以考虑使用 svn-buildpackage.


2010-05-02

[有问有答] 软件如何进入 Debian

Q: 我有一个软件,我觉得他可以进入 Debian, 那么如何让他进入 Debian 呢?
A: 整个流程分为如下几步:
  1. 首先你应该提交一份 ITP bug, ITP 的含义是"Intent To Packaging", 最简单的方式是运行 "reportbug wnpp", 接着选择 "ITP", 填完他要求的内容, 即可提交, 这个 bug 报告最主要的作用是为了防止重复劳动, 可以追踪谁在负责这个包, 也方便直接联系负责人询问进度, 所以也没有必要太详细, 范例可以参见 ibus 的 ITP bug
  2. 稍后你可以收到一封邮件, 里面应该有这个 bug 对应的号码, 比如 ibus 的 ITP bug 号就是 501106, 这个号码在第3步打包时会用到(放在 debian/changelog 文件中)
  3. 接着就是具体的打包工作了, 这个部分内容比较多,不在这儿说了, 详细情况你可以查看Debian 新维护人员手册(英文版, 简体中文版)。 打好的包首先要保证能用, 同时最好用 pbuilder 编译一遍,确定能编译成功, 用 lintian 检查一遍, 确认没有警告, 然后再复查一下 debian/copyright 里面的版权声明, 是否有遗漏和错误, 这些都检查完毕后, 就可以申请上传了.
  4. 接着呢,你需要把这个包上传到 mentors.debian.net, 方便其他人员检查你的包, 上传方法可以查看他的文档
  5. 接着呢你要发封信到 debian-mentors@lists.debian.org, 找一个Debian Developer 帮你上传, 这个人一般被称之为 sponsor, 这封信的标题一般含有"RFS", 含义为 "Request For Sponsor"。mentors.debian.net 一般已经为你准备了一个模板,  就在你的包详细页面下面会有一个链接, 你把模板拷贝一份, 补充一下里面缺少的内容, 就可以发到 debian-mentors@lists.debian.org , 值得注意的是, 如果你没有订阅这个邮件列表, 记得在邮件里面加上一句 "please cc me, thanks.".
  6. 接着呢就需要等待几天时间, 当然如果运气不好, 没人对你的包没有兴趣的话, 可能需要更长的时间, 一般两周后你可以重新发一遍你的邮件, 补充说明一下为什么你的包值得进入 Debian. 如果你想加快这个进程, 一个办法是到 IRC 问问, IRC 服务器是 irc.debian.org (不是 freenode), 频道是 #debian-mentors 。另外, 如果你有认识的 Debian Developer 的话, 可以直接给他发信, 问他是否愿意帮你上传这个包。
  7. 包上传后就进入 Debian 的 unstable了(即sid), 然后一般会在 10 天后进入 testing (需要满足如下的要求: a. 包在所有的平台上都成功编译, b.这10天内没有发现重大 bug, c. 你所依赖的包也已经都在 testing 存在了), 下一个 stable 版发布时就会包含你的包了。
  8. 恭喜你,你的包已经进入 Debian 了。



2010-05-01

[有问有答] 什么样的软件可以进入 Debian

Q: 什么样的软件可以进入 Debian?
A: 首先是授权协议, 所有进入 Debian 的软件都必须满足 DFSG, 值得注意的是, DFSG 与自由软件的定义有不一致的地方, 比如 cc-by-nc 协议就不是 DFSG 兼容的, 部分 GFDL 文档(含有不可变文本) 也不满足 DFSG, 如果你对软件授权是否满足 DFSG 拿不准, 那么可以到 debian-legal 咨询

剩下的是一些非强制的标准, 但 Debian 开发人员会通过这些来判定一个包是否值得上传:
  • 上游是否仍然在维护这个软件, 这个尽管不是强制标准, 但除非这个软件不可替代(比如 mutt?), 否则一般不会收纳这种软件.
  • 与现在仓库中同类软件相比, 这个软件的特点是什么?这条就比较容易达到, 毕竟哪个软件没有自己的特点呢?功能, 基础库(比如 GTK vs QT), 速度, 大小, 等等都可以是自己软件的特点。
  • 软件质量如何?是否经常崩溃?
  • 软件的用户数多少?几乎没人使用的软件很难保证质量。
  • 如果没有 Debian 打包流程, 用户直接使用是否方便, 比如不依赖于本地库的 firefox 插件就倾向于不要打包, 因为安装起来实在是太方便了
  • 如果你在打包一个库的话, 这个库是否有程序使用? 如果没有程序使用, 那么这是否是一个重要的开发工具包? 如果都不是话, 那么一般不会收纳这种库.
以上其实都不是强制标准, 其实最关键的问题是是否有人愿意贡献时间来打包和进行后续维护, 打包后能否说服 Debian 开发人员帮你上传。Technorati Tags:

2010-02-04

tomcat 部署 https

tomcat 部署 https


  1. 在 startssl 上申请认证, 最后你会得到两个文件: ssl.key 和 ssl.crt, 建立一个新目录, 把这两个文件拷贝到该目录
  2. 下载 https://www.startssl.com/certs/ca.pem 和 https://www.startssl.com/certs/sub.class1.server.ca.pem
  3. 运行
    cat ssl.crt ca.pem sub.class1.server.ca.pem > chain.crt
  4. 运行
    openssl pkcs12 -export -in chain.crt -inkey ssl.key -out keystore.pkcs12 -name tomcat
    使用密码 "PASSWORD"
  5. 运行 (可选, 该步为校验步骤)
    keytool -list -rfc -keystore keystore.pkcs12 -storetype pkcs12
    注意: 结果中要含有如下的一行 "Certificate chain length: 3"
  6. 运行
    keytool -importkeystore -srckeystore keystore.pkcs12 \
    -srcstoretype PKCS12 -destkeystore keystore \
    -srcalias tomcat -destkeypass PASSWORD
    (注意: 此处的 PASSWORD 需要与目标 store 的密码一致) 
  7. 运行
    cp keystore $TOMCAT_HOME/conf/
  8. 修改 $TOMCAT_HOME/conf/server.xml, 去掉 SSL Connector 附近的注释, 内容如下:
    <Connector URIEncoding="UTF-8" port="443" protocol="HTTP/1.1" SSLEnabled="true"
    maxThreads="150" scheme="https" secure="true"
    clientAuth="false" sslProtocol="TLS"
    keystoreFile="conf/keystore"
    keystorePass="********"
    keyAlias="tomcat"
    />
  9. 把其余的 redirectPort="8443" 改为 redirectPort="443"
  10. 重启 tomcat
  11. 调整防火墙,启用 443 端口
  12. 备份 ssl.key 和 ssl.crt 到安全位置, 删除整个目录, 设置 $TOMCAT/conf/keystore 的属主为 tomcat 的运行用户, 设置权限为 640 或更低权限

注意事项:
  1. ssl.crt ca.pem sub.class1.server.ca.pem 都必须是 PEM 格式 (看起来类似base64)
  2. 所有步骤中建议使用同一套密码
  3. 此处对密码的要求不是很高, 因为运营机器上的 root 用户能同时拿到 keystore 和 server.xml, 密码再复杂也无效, 非 root 用户拿不到 keystore, 所以是安全的
  4. 如果有更高的安全要求, 建议修改 tomcat 的启动脚本, 在启动时手动输入密码, 设置为环境变量, 然后在 server.xml 中使用环境变量来获得密码 (仍然有问题, root 不在自己手上, 安全经常是幻像)
  5. Firefox, Chrome, IE 下均不会报警, 但 Java 客户端仍然不能使用, 解决方法如下
    1. 运行: wget https://www.startssl.com/certs/ca.pem
    2. 运行: keytool -importcert -trustcacerts -file ca.pem -keystore foo -storepass changeit
    3. 把 foo 拷贝到你的程序中
    4. 把如下两行代码加到你的程序 main 函数中:
      System.setProperty("javax.net.ssl.trustStore", "/path/to/foo");
      System.setProperty("javax.net.ssl.trustStorePassword", "changeit");




2010-01-31

gtalk bot: toascii@lidaobing.appspotc...

gtalk bot: toascii@lidaobing.appspotchat.com


解决在 PTT.CC 上看不懂注音文的问题, 差不多就是一个 echo server, 不过会把注音符号, 平假名, 片假名, 转成对应的罗马符号.截图见下: