文斯雜談凸^-^凸

何かの犠牲なしに何も得ることができない、
何かを得るためには同等の代価が必要になる。
            --「鋼の錬金術士」

{今天: {Ruby: ‘24岁’, 我: ‘36岁’}}

低头猛赶稿,已经不知道是过了多少个凌晨3点了,只知道令人惭愧的进度。
Ruby已经迎来2.4版本了,而手上的《Ruby基础教程》第五版(对应Ruby2.3)的稿件还有三分之一没完成。
凑巧某处提到1993年2月24日是Ruby的“诞生日”,才发现原来今天就是Ruby24岁生日!
回想07年知道Ruby,11年开始用Ruby至今,感慨万千。

“你以前搞Java的,为什么会想到转Ruby呢?”
“Ruby很小众,不好招人吧!?”
“带过团队吗?”
“Ruby性能不好啊!”
“Ruby怎么那么慢?”
“Ruby怎么会这样?”
“Ruby怎么会那样?”
“Ruby……和PHP差不多?”
“Ruby……”
“哦,那还是不要用Ruby吧!”

已经过了血气方刚的年龄,已经“不敢”和人争辩。
听到,心里也就是咯噔一下,眉头皱一皱,然后……就过去了。

“因为Ruby会令你感到快乐,而且喜欢Ruby社区的氛围……”
“不好招,都是找PHP转的,或者……”
“带过,小团队,因为……”
“但开发效率高啊,而且现在硬件性能也好……”
“也还好吧,因为Ruby比较灵活,还有……”
“其实是那样的……”
“其实是这样的……”
“也……是啦,主要都是用来做网页的……”
“嗯……明白……说的也是……”
“也……不是……特别地……反对用NodeJS,……都行……”

认识Ruby以前,转职时讲的是“……想换个环境、平台……”。
认识Ruby以后,讲的是“……因为公司不准备用Ruby了……”。

曾经背叛过。
现在也在想是否应该放弃。

为什么要坚持。
坚持的到底是什么。
……
还是用Ruby的时候才是最开心的。

感恩!
感谢!

Ruby!
Happy Birthday!

Ruby程序员!
Happy Hacking!

浅谈ActiveRecord的N + 1查询问题

ORM框架的性能小坑

在使用ActiveRecord这样的ORM工具时,常会嵌套遍历model。
例如,有两个model,Post、Comment,关系是一对多。

1
2
3
4
5
6
7
class Post < ApplicationRecord
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :post
end

总共有4个post。

1
2
3
> Post.count
(0.1ms)  SELECT COUNT(*) FROM "posts"
=> 4

获取每个post的所有comment,我们可以:

1
2
3
4
5
6
> Post.all.map{|post| post.comments}
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]
  Comment Load (0.6ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 3]]
  Comment Load (0.6ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 4]]

可以看到为了得到4条数据,我们执行了5(4 + 1)次的查询,这就是所谓N + 1查询问题。

发现问题

除了凭经验外,有一些gem也可以帮助我们提早发现N + 1查询问题。
例如收费的New Relic,免费的Bullet

解决问题

预加载

简单来说,就是提前加载model关系,让ActiveRecord预先加载所需要的数据。
ActiveRecord提供了以下三个方法预加载。

  • includes
  • preload
  • eager_load

他们的区别可以参考这里这里
以最常用的includes方法为例。

1
2
3
> Post.includes(:comments).map{|post| post.comments}
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3, 4)

得到的结果一样,但执行的查询只有两次。

傻瓜式预加载(Goldiloader)

传统预加载的“问题”

includes方法的确很惊艳,但……

第一,代码不够优雅。
例如,假设我们现在想找的是id在1到3之间的post的comment。
一般的我们的逻辑是,查找id在1到3之间的post,获取各post的comment然后合并。
而预加载后的逻辑是,查找id在1到3之间的post,关联comment,再获取各post的comment然后合并。
总觉得有点冗余。

1
2
3
> Post.where(id: 1..3).includes(:comments).map{|post| post.comments}
  Post Load (0.5ms)  SELECT "posts".* FROM "posts" WHERE ("posts"."id" BETWEEN ? AND ?)  [["id", 1], ["id", 3]]
  Comment Load (0.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3)

第二,不符合DRY。
既然我们都不喜欢N + 1,那就应该从源头上杜绝,而不是每次查询时都要主动includes一次。

Goldiloader

懒癌程序员的救星Goldiloader——几乎完美的解决了以上两个问题。

1
gem 'goldiloader'

bundle install以后,就可以用最直接(傻瓜)的方式点点点……

1
2
3
> Post.where(id: 1..3).map{|post| post.comments}
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" WHERE ("posts"."id" BETWEEN ? AND ?)  [["id", 1], ["id", 3]]
  Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3)
auto_include

Goldiloader默认自动加载所有关联数据,用auto_include: false可以方便地关闭自动加载。

1
2
3
class Post < ApplicationRecord
  has_many :comments, auto_include: false
end
fully_load

以下的方法比较特殊,如果关系已经加载了,则会直接返回已缓存的值,如果没被加载,则会通过SQL查询。

  • first
  • second
  • third
  • fourth
  • fifth
  • forty_two
  • last
  • size
  • ids_reader
  • empty?
  • exists?

假设现在我们需要获取每个post的最新的comment。
但这不是我们想要的。

1
2
3
4
5
6
> Post.all.sum{|post| [post.id, post.comments.last&.content]}
  Post Load (0.1ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."id" DESC LIMIT ?  [["post_id", 1], ["LIMIT", 1]]
  Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."id" DESC LIMIT ?  [["post_id", 2], ["LIMIT", 1]]
  Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."id" DESC LIMIT ?  [["post_id", 3], ["LIMIT", 1]]
  Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."id" DESC LIMIT ?  [["post_id", 4], ["LIMIT", 1]]

添加选项full_load: true后,当调用上述方法时,Goldiloader会强制自动加载所需的关系。

1
2
3
class Post < ApplicationRecord
  has_many :comments, fully_load: true
end

这才是我们想要的。

1
2
3
> Post.all.sum{|post| [post.id, post.comments.last&.content]}
  Post Load (0.3ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3, 4)

Goldiloader也不是万能的

has_one使用SQL limit时的隐患

Goldiloader是ActiveRecord的衍生工具,所以ActiveRecord预加载的副作用也一并继承了。
现在我们自定义一个has_one关系,用以获取最新的一条comment。

1
2
3
4
class Post < ApplicationRecord
  has_many :comments, fully_load: true
  has_one :latest_comment, -> { order(created_at: :desc) }, class_name: 'Comment'
end

遍历post获取最新的comment。

1
> Post.all.map{|post| post.latest_comment}

不使用Goldiloader或者预加载时,每条SQL自动回加上limit 1

1
2
3
4
5
Post Load (0.3ms)  SELECT "posts".* FROM "posts"
Comment Load (0.2ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."created_at" DESC LIMIT ?  [["post_id", 1], ["LIMIT", 1]]
Comment Load (0.2ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."created_at" DESC LIMIT ?  [["post_id", 2], ["LIMIT", 1]]
Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."created_at" DESC LIMIT ?  [["post_id", 3], ["LIMIT", 1]]
Comment Load (0.1ms)  SELECT  "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."created_at" DESC LIMIT ?  [["post_id", 4], ["LIMIT", 1]]

使用Goldiloader或者预加载时,世界变清净了,但同时会有性能隐患,因为post的数据量可能非常大。

1
2
  Post Load (0.5ms)  SELECT "posts".* FROM "posts"
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3, 4) ORDER BY "comments"."created_at" DESC
其他限制

遇到以下的关系(方法),Goldiloader会自动关闭自动预加载。

  • limit
  • offset
  • finder_sql
  • group (due to a Rails bug)
  • from (due to a Rails bug)
  • joins (only Rails 4.0/4.1 – due to a Rails bug)
  • uniq (only Rails 3.2 – due to a Rails bug)

本文结束之前

N + 1查询问题是一个容易被忽略的问题。
发现解决它也不难,includes已经够用,Goldiloader更是锦上添花,对新手足够友好。
不过对于我这种被Rails“坑”习惯的斯德哥尔摩症候群患者来说,没有includes反而没安全感了>_<|||

参考文档

非动态,也非静态,Ruby3 Typing 的第三条路

Matz在2016年的Rubykaigi里做了一个关于Ruby3 Typing的分享。
首先简单介绍一下什么是RubyKaigi
Kaigi就是日语【会議】的罗马字母写法,顾名思义也就是在日本举行的RubyConf。
RubyKaigi在2006年首次举行时的名字就是【日本Rubyカンファレンス】(Japan Ruby Conference),由于容易与Ruby Central混淆,因此在2007年改名为【日本Ruby会議】,直到2011年停办。
2013年大会重开,改名为【RubyKaigi】,并统一使用英语作为大会的官方名称。

今年的RubyKaigi2016在9月8号到10号京都举行。由于近水流台,有不少像Matz这样的Ruby committer参与分享,所以大会含金量非常高。
官网中以及发布了所有视频,希望以后有机会可以亲身去感受下。

大会的首个Topic是Matz的 Ruby3 Typing油管直通车

02.ruby3_typing

演讲者Matz就不多做介绍了。

01.matz

官方没有提供在线版的PPT,我把这个日语演讲“翻译”成以下的文字,为了保持文字通顺,稍微加工了一下。


Ruby3 Typing(Matz)

在2010年以来的新语言很多都是静态类型语言(Static Typed Languages),例如TypeScript、Flow、Go、Swift等等。
与之相比,Ruby没有静态类型,又是上世纪90年代语言,所以有些人会说“Ruby is Dead”、“Rails is Dead”。

03.no_static_typing

但技术有时候就像钟摆,有时候偏向一种技术,有时候又偏向另外一种技术,这是常有的事情。例如静态类型与动态类型“之争”。

  • 1970s到1980s左右,从Smalltalk、lisp摆到Java、C++

  • 接下来又从Java摆到Ruby、JavaScript

  • 到最近又从Ruby、JavaScript摆到Swift、Go

那么未来Swift、Go又会摆到哪里去呢?未来Ruby3的类型又会有什么改变呢?

首先我们需要知道什么是Ruby的type?对于动态类型语言来说,Class不是类型
另外在Ruby其中一个重要的原则就是Duck typing,也就是说对于一个Ruby对象来说,我们不关心她继承关系(inheritance),也不关心她的内部结构(structure),我们只关心她的行为(behaves)。

请看下面String IO的例子。

Ruby版本的日志输出代码片段:

log(STDERR, "error!")  

静态类型版本:

log(dst IO, mesg String)

Ruby StringIO版本:

sio = StringIO.new()
log(sio, "error!")
sio.string # => retrieve string

上面的StringIO例子在静态类型的世界行不通,因为StringIO没有与IO有共同的superclassinterface,所以无法通过编译。

Duck typing使我们在开发的时候不需花时间研究各个Class间的关系,大大降低开发者的开发成本,而且扩展起来也更加灵活。
所以我们可以认为,在Ruby的type就是“Duck”。“Duck”不是Java那样的Nominal type,更不是Class,它是一种被期待的行为。

04.duck_is_behavior

“期待”只是存在于我们的脑子里面,很暧昧的想法,也正因此Ruby的Type可以很灵活,对比用Class来定义Type的方式会有很多限制。

04.typing_by_class

对比Nominal typing,我(Matz)更喜欢Go的Interface,也就是Structural Subtyping这种方式。

04.go_interface

在上面的例子中,上面三行定义一个包含Write方法的interface LogDst,下面三行的log函数接受两个参数LogDst和字符串mesg。在这个函数里我们只需要LogDstWrite的行为(方法)就可以了, 它可能是输出到standard IO、String,或者其他什么地方,我们并不需要关心它的内部逻辑。
Structural Subtyping和Duck typing同样保持很好的灵活性,当然我(Matz)还是更喜欢Ruby的Duck Typing :–)。

05.duck_typing_is_awesome

DRY(Don’t repeat yourself)是Ruby另外一个重要原则。

06.do_not_repeat_yourself

为了避免不必要的重复,我们不会在程序写实际上不需要的东西,也就是说Ruby程序的运行不依赖于type annotations,因此我们就不需要它们,甚至要去除它们。

但是Dynamic Typing也是存在不少不足的地方。

  • 在程序运行时能发现错误
    07.errors_only_found_in_runtime
  • error message不友好,信息量少,下面可能是我们最熟悉但又“莫名其妙”的错误信息
    08.undefined_method
  • 如果测试覆盖不够,可能会有预想不到的typing error
  • 缺少文档,像Ruby这样没有类型的语言写程序的时候非常爽,但读程序的时候就可能有困难了,所以有些人会写下面这样的注释
    09.document

可能会有人吐槽,最终不还是要把类型写出来吗。。。
但。。。无论如何还是不想指定类型,绝对不想。。。(Matz特别强调两次,全场都笑了)

10.do_not_want_to_specify_types

因为这样会降低程序的灵活性,但为了以后的维护,我们又希望有可读性好的文档。
除了把类型信息像刚才那样写在注释外,还有另外一个做法是把他写在文档中。把类型写在文档里,但实际程序又不会做类型检查,到头来实际两边都没有讨好。
至少对Ruby来说Type Annotation,Mixed/Gradual都不是好主意。

正是因为还有以上种种问题,Ruby还有很多改进的空间,

11.room_for_improvement

并且我们作为一个工程师应该要主动去解决这些问题。

12.solve_problems

有些人提出了Static Typing with Type Inference的解决方案,但这个方案还是没能解决静态类型不够灵活的缺点。
又有人提出Gradual Typing 或者 Optinal Typing的解决方案,但这两种类型实际还是静态类型,因此灵活性这个问题还是没能得到解决。
Ruby需要除上面以外其他的什么东西,一种像Static Typing这样进行类型检查,但又像Duck Typing这样灵活的类型。

13.static_type_with_duck_typing

暂且就把她叫做Soft typing。

14.soft_typing

Soft typing是一套用行为来定义的Type System。

15.soft_typing_system

所谓行为就是一组的方法和参数数量、类型等。

16.behavior_is_a_set_of_method

回到刚才日志输出的例子,Go版本的interface其实可以让程序自动生成,并且我们写程序的时候也不需要关心 interface(Type)的名字(取名字对有些人来说是件麻烦事),

17.not_to_worry_about_type_names

因此我们只可以忽略这些细节,专注于程序开发。(Happy Programming的真谛)

18.vague_ideas

例如,我们可以把Type信息搜集起来,就像放到数据库中一样。然后,我看可以从这个数据库中获取Type的定义和Type行为(方法)。
我们也这些Type信息看做是一种表达式(expression),

19.retrieve_a_type_as_a_expression

例如,我们可以检查当把A表达式赋值给B表达式时是否兼容。

20.check_compatiblity

我们也可以检查某个类型有没有对应的方法。

21.check_method

这样的做法也许并不能做到100%的类型检查,但还是比之前一点都没用要好。

22.better_than_nothing

如果找不到对应的类型信息,由于本来就是dynamic typing,那么我们就退回到dynamic typing就可以了。

23.fallback

有两种方式实现Soft typing。

一个是利用ad-hoc type的信息。
例如,有a表达式(也有可能是变量),我们期望她有gsub,slice,map三个方法,如果找不到有对应的class满足这个条件的,那就抛出错误信息。

24.ad_doc_example

但对于在运行时不断动态添加或修改的方法,这种检查方式就无能为力了。

另外一个是在运行时搜集类型信息,

25.collect_from_runtime

特别是在测试的时候,

26.collect_from_test

一般Libray或者Gem都会进行测试,那么我们可以在测试的同时,建立类型数据库。
这样我们就可以在发布Gem的同时,以某种方式一起创建和发布与之对应的类型数据库。

27.build_type_database_from_gem

IDE也可以利用这些Type Database的信息,让我们可以构造更加有效率、聪明的开发环境。
可惜的是在先阶段以上这些暂时都还只是构想,我们还不能用到。

28.still_mere_concept

所以让我们一起期待Ruby3吧!

29.part_of_ruby3

最后,我们(Ruby committees)有一个很重要的信息传递给大家,我们是非常重视开发者的。

30.we_care_about_you

我们不会对Dynamic typing的“缺点“视而不见,或者叫开发者多做测试就了事,而是希望努力的改善Ruby,让开发者有更好的开发体验。

31.willing_improve_development_experience

关于Ruby3什么时候发布,目前还是不知道。。。

32.i_dont_know

从committee management的角度来看,开源软件一般没有所谓的dead line,也没有很明确的road map,至少对于Ruby这个项目来说没有。但如果什么都没有又很难开展工作,因此我们对Ruby3的开发指定了3个目标。

33_three_goals_of_ruby3

就像当年美国登月一样,也是先定了一个困难、远大的目标,然后大家一起为之努力,最后成功。
那Ruby3的三个目标什么时候才能实现呢?我(Matz)希望在下一次的日本奥运会的时候。。。

34.wait_for_years

虽然Ruby3还”遥遥无期“,但Ruby前进的脚步是不会停止的。

35.keep_moving_forward

36.i_promise

我们会一直不遗余力的帮助广大开发者在编程中找到乐趣—Happy Hacking!

37.happy_hacking

OPEN SUSE下的RVM小坑

OPEN SUSE13.1默認就安裝了Ruby2,不過還是習慣用RVM這樣的工具管理Ruby。沒想到把系統默認的Ruby更改後,竟然給我發現了YaST的“小祕密”。
P.S.YaST做得很優秀,没Ubuntu的花哨,非常實用。
yast 用RVM安裝Ruby後,上圖的所有項目的子窗口都無法再彈出了。試着在命令行用root身份登陸運行yast2 --qt,這樣是運行是沒問題的。 command-line 後來差了一下yast的日誌/var/log/YaST2/y2log,發現以下的錯誤:

2013-12-16 12:59:28 <3> linux-en59.site(4285) [Y2Ruby] binary/YRuby.cc(callClient):238 cannot require yast:cannot load such file -- fast_gettext at /usr/lib64/ruby/2.0.0/rubygems/core_ext/kernel_

很明顯是Ruby的問題,fast_gettext非常可疑。
gem search fast_gettext果然發現的確有fast_gettext這個gem,果斷install。

gem install fast_gettext

問題解決! command-line2 原來YaST也用到Ruby!

關於gettext可以參考維基,fast_gettext可參考這裏

ReText——Linux下的MarkDown利器

尋覓Linux下的MarkDown編輯器

最近由於工作的關係,經常會使用MarkDown寫東西。用MarkDown那就一定離不了支持MarkDown的編輯器。我對MarkDown編輯器的要求只有兩點:

  • 可以自定義CSS,因爲我喜歡用黑色等暗色系的顏色作爲寫作和預覽時的底色
  • 有同步的Live View功能,不是另外啓動瀏覽器預覽html

在Mac和Windows下非常容易就找到了符合以上兩點要求的編輯器——Mac的Mou(完全免費)和Windows下的MarkdownPad(標準版,免費)。它們都是非常優秀的編輯器,關於它們兩就不多說,是該平臺下MarkDown編輯器的不二之選。

由於平時工作至少50%時間還是在Linux下的,如果在Linux也能找到那麼方便的MarkDown編輯器就方便多了。試過很多Linux下的MarkDown編輯器,愣是沒找到像Mou或者MarkdownPad安裝即用的好編輯器。也曾經試過ReText,安裝的確方便,Ubuntu下apt-install retext就可以了,雖然也有Live View(這在Linux下已經非常難得了),但是默認安裝後界面的簡陋,讓我這樣已經被Mac界面寵壞的人難以接受。而且最重要的是在圖形界面配置沒有找到使編輯器底色設爲暗色系的選項,這點直接導致我“放棄”了ReText。

“放棄”ReText後,我試過很多其他解決方案。

在虛擬機中的Windows使用MarkdownPad,現在Linux下的VirtualBox的體驗也還不錯,問題是解決了,但是這樣做感覺非常笨,太過小題大作,果斷放棄。

用在線的雲MarkDown編輯器,寫作的體驗很好,但是還是不太方便,寫點東西就得佔有一個瀏覽器窗口,而我習慣一般寫東西的同時有得開瀏覽器和一些離線的資源(pdf)查資料,用Alt+Tab不斷在瀏覽器、編輯器、pdf中不斷切換。使用在線編輯器後這樣爽快的切換就很難再進行了,最後還是放棄。

使用其他一些誇平臺編輯器的MarkDown插件。我也曾經試過Sublime的sublimetext-markdown-preview之類的插件,第一個問題可以說是完美解決了,但是同步的Live View的需求還是不能滿足,雖然Sublime寫代碼是一流,但是最終還是忍痛放棄了。

重拾ReText

試過了多種方法後,還是覺得原生的MarkDown編輯器靠譜。剛好最近SUSE13.1發佈,安裝過後感覺KDE的用戶體驗上比Ubuntu自己的Unity有過之而無不及。而且本來玩Linux就是爲了折騰,沒有安裝即用的MarkDown編輯器,自己配一個總可以吧。所以最後決定在SUSE上再次挑戰ReText!

安裝

SUSE有自己的一套軟件管理工具,類似Ubuntu的apt-get,這裏就不多做介紹了,直接在終端運行下面命令即可。ReText還是比較流行的Linux軟件,他管理工具一般改一下命令也都應該可以安裝。當然,也可以參考ReText的Page,折騰一下源碼安裝。

sudo zypper install retext

SUSE13.1安裝後的版本爲:
版本
基於Python2.x(ReText最新版本基於Python3.x的)。

配置

默認安裝後的界面還是比較簡陋,所以接下來的重點就是如何給ReText做手術。
在手術前,首先需要弄清楚兩部分東西,一個是界面,一個Live View。 Retext的界面是基於Qt的,而決定Live View外觀的是CSS。
所以如果想要改造ReText就需要從這兩部分着手。
另外,雖然SUSE或者Ubuntu的ReText都有圖形界面的配置畫面,但是配置項目都很少(Ubuntu的稍微多點),但還是不能符合我的期望,因此以下的配置都是按照官方文檔直接修改配置文件的。

配置文件

默認的配置文件在~/.config/ReText project/ReText.conf,其他的具體配置細節可以參考官方文檔
在這裏,我只關心兩個問題——界面與Live View如何配置。

界面

appStyleSheet是關於ReText程序界面的配置。ReText的界面是使用Qt的,因此這個配置實際就是Qt Style Sheets的配置。
像我這樣的懶人,實在不想去折騰Qt Style Sheets語法了,有沒有更簡便的配置方法呢?懷着試一試的心情,去github逛了一下,發現還真有人和我的想法是一樣的——QDarkStyleSheet
這裏我稍微取巧了一下,由於我只需要程序界面的配置,因此我只直接下載Qt的配置qss,然後把ReText的appStyleSheet直接指向該Style Sheet。 ReText.conf配置:

appStyleSheet=/home/kamiiyu/.config/ReText project/style.qss

Live View

與Qt配置同樣的思路,看看github上有沒有現成的好東西。對比Qt配置,MarkDown的css配置是容易找多了。最後我選擇的是markdown-css-themes中的screen.css

ReText.conf配置:

styleSheet=/home/kamiiyu/.config/ReText project/screen.css

大功告成,華麗變身後的ReText,帥多了!
手術前:
原始圖 手術後:
完成圖

以下是我的ReText.conf,僅供參考。

[General]
recentFileList=/home/kamiiyu/kamiiyu.github.com/source/_posts/2013-12-06-retext.markdown
font=\x6587\x6cc9\x9a7f\x7b49\x5bbd\x6b63\x9ed1
fontSize=9
styleSheet=/home/kamiiyu/.config/ReText project/screen.css
appStyleSheet=/home/kamiiyu/.config/ReText project/appstyle.css
restorePreviewState=true
tabWidth=4
saveWindowGeometry=true
windowGeometry=@ByteArray(\x1\xd9\xd0\xcb\0\x1\0\0\0\0\a\x80\0\0\0T\0\0\n\xa3\0\0\x2\xc6\0\0\a\x82\0\0\0k\0\0\n\xa1\0\0\x2\xc2\0\0\0\0\0\0)
previewState=true
useWebKit=true

小遺憾

OPEN SUSE默認安裝的ReText還是有點不盡人意的地方。
例如,滾動左邊的工作區的時候,右邊的Live View並不會相應的自動滾動。ReText的開發Tickets也有人彙報過這個問題,但似乎還是沒有很好的解決。可能是和環境有關係,因爲最新版本的ReText其實已經建議運行在Python3的環境下,但OPEN SUSE默認還是Python2。有機會在Ubuntu在驗證一下這個問題。

Jack是不是管理員?

  要問Jack是不是管理員,代碼一般有三種寫法。

php
1
if is_admin(Jack){ echo "Hi, Jack!"; }
java
1
if (Jack.is_admin()){ System.out.println("Hi, Jack!"); }
ruby
1
puts "Hi, Jack!" if Jack.admin?

  以上三種方法只有ruby的最一目瞭然,不會看花眼,最接近人的語言習慣。
  去除了繁雜的括號、花括號,行文直截了當,寫代碼就像寫文章一樣,甚至連寫註釋的功夫的省去了。
  更神奇的admin?這個方法根據ruby的動態特性可以在程序運行的時候才生成,而不需要預先寫好代碼。
  以下是我rails項目中的代碼片段:

實際代碼片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def method_missing(method_id, *arguments, &block)
  if method_id.match(/^can_.*\?$/)
    something = method_id[4..-2]
    if something.blank?
      super(method_id, *arguments, &block)
    else
      self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
        def #{method_id}
          self.can_do?(:sub_action => %{#{something}})
        end
      METHOD
      send(method_id, *arguments)
    end
  elsif method_id.match(/^dpmt_.*\?$/)
     ()
end

  下面解釋一下我的思路:

  • 約定查找用戶的權限的方法名為can_do?。假設想確認Jack有沒有游泳的權限,則如下那麼寫:
1
Jack.can_swim?
  • Jack發現他本身並沒有can_swim?方法,在一系列的處理後他最終調用用了method_missing方法。
  • method_missing簡單來說只做一件事,判斷Jack是否有swim的權限。
  • 如果想看看Jack有沒有跑步的權限,則直接使用can_run方法,重複以上三個步驟即可。

  對比java,如果需要判斷swin、run、jump三個動作,我們怎麼做?

  • 寫三個can_xx方法。(代碼重複,擴展性差)
  • 寫一個can(xx)方法,每次都把動作當作參數傳進去。(純主觀看法,括號很難看,不美觀)
     

  ruby的can_do?是不是很簡單易懂?特別方法尾部畫龍點睛的”?“,比java的is_xx()簡直好看百倍!

被遺棄的聖經

  我的宗教體驗是由一本被遺棄的聖經開始的。
  在日本的公共團地(小區)大家都有會把看完的雜誌、漫畫、小說等各種書籍放在樓梯間的固定的地方等待回收。我偶爾也去翻翻,尋尋寶。
  一天,我找到一本發黃的聖經。在中學的時候就對聖經有興趣,來日本後也想參加一些宗教活動,體驗一下,可惜一直工作沒時間。沒想到在這種環境下我“收到”人生中第一本聖經。
  如獲至寶似的,把聖經拿回宿舍。一來是日語的,而來詞彙好多都不是現代常用的,讀起來非常費勁,翻了一會就放一邊了。
  第二天,突然有一男一女來敲門了,男的大概40多歲瘦瘦的說話很有禮貌很客氣,女的應該20多吧,話比較很少,都是日本人。他們第一句就問我知不知道聖經,是否對聖經有興趣,我心抽了一下,就把昨天撿了本聖經並對聖經有興趣的事情告訴他們。在門口和他們聊了好久,後來男的日本人說能不能看看那本聖經,我就拿出來給他看了看。他看了一下,皺了一下眉還給我後,說如果我想學,他們每個星期都可以來宿舍教我,並送一本新的聖經給我,我馬上就答應了。
  他們走後,我和室友聊了一下,他也有興趣,就決定一起學習了。
  接著的星期天下午,那個日本男人果然來了,不過旁邊的不是上次那個女孩,而是一個帶眼鏡稍微成熟點的女生。
  日本男人叫TK,旁邊是他的妻子。他們送了一本手掌大的聖經給我,由於來了才知道室友也想學習,他們說下星期來的時候再拿本聖經和資料給我們。他們對有新成員加入似乎都挺高興。我們四個人聊了好久,主要聊着我們倆的工作生活,剛來日本習慣不習慣啊,對神有什麼感覺之類的。期間,TK又問能不能再看一下那本撿回來的聖經,他看了看,又給他妻子看了看,口中嘀咕了一下“やっぱりね“(果然如此),然後和我說這本聖經寫的不好,以後就看他們送來的就可以了,下星期他會再另外送一本日語的聖經,到時候再和我們說哪裡不好。
  很快又到了下個星期天了,他們很準時的來了。帶了三本聖經和好幾本精美的硬皮包裝的圖文版的聖經故事,說送給我們,弄得我們有點不好意思了。
  進屋坐下後就和我們解釋上星期留下的謎題了。親密朋友之間會直呼對方的名字,而神就是我們最親密的朋友,所以我們要說出神的名字而不需要忌諱,而我撿的聖經(應該是天主教版本的)把神的名字換成其他稱呼,這是對神的不尊敬,神也不會喜歡這樣,所以不好。
  關於神的名字能不能直呼,不同教派有不同的理解,主流的像天主教和新教都是主張不直呼的,所以英文一般用lord,中文用上帝來代替,而一些像TK所在非主流教派卻有自己的一套解釋,也正因此成為了非主流。這種分歧無處不在,大者如猶太教、伊斯蘭教、基督教雖然信奉的用一位真神,但卻由於不同的理解而拼個你死我活;小者如在基督教世界也是派別林立,各不相讓,各稱異端⋯⋯
  聖經的學習挺有趣的,TK所在的教派會自己在編寫本教材,然後結合自己翻譯的聖經來教我們。第一課意外的非常“科學”——宇宙起源。教這些無非就是一個目的,讓我們知道人類多渺小,自己為什麼都知道,但是事實上還是很多科學不能解釋的東西,但這些其實都是全知全能的神賜於我們的。
  聖經的學習更加激發了我對宗教的興趣,主要的參考來源就是維基百科,英語、日語、繁體中文的幾個版本對比着看,有時候一看就是一整天⋯⋯
  所謂的宗教也好、科學也好,其實都是一種信仰而已,歸根結底就是一個“信”字,只能信不能問。若是刨根問底,哪怕是科學,到最後總會發現無法解釋的地方,反過來會讓你更敬畏這個自然界,也許這就是為什麼很多有名的物理學家例如牛頓同時又是虔誠的基督徒的原因。自大的以為自己能解釋一切,其實是無知的表現。
  和有信仰的人交往很放心,很快就成為好朋友了。我們基本固定沒星期學習一次,或者來我們宿舍或者去他們家,去他們家的時候他們還會親自下廚。偶爾還會參加他們的大聚會,在那裡認識了不少中國人甚至韓國人。有時候還會組織活動,去賞櫻花、吃烤肉⋯⋯宗教對於他們來說就是一個大家庭,在裡面的都是兄弟姐妹,守望相助,十分融洽。
  後來,我才知道原來TK那天是看錯地址才找到我們宿舍,我又一次被神的力量所折服:–)
  但可能由於自己性格的問題,太執著、太喜歡問為什麼了,直到現在,我還是對很多事情有保留,不過有一點我敢確定的是,讓以前教科書上學的東西見鬼去吧⋯⋯

文斯雜談 醞釀已久

  關於自己的博客醞釀好多年了,學生時代在csdn寫過一段時間博客,也正是csdn的博客被當時的老板看到後,才有機會到日本發展。
  但是工作以後一直也沒閒下來,而且後熱衷旅遊和照相,所以一耽誤就6、7年沒寫東西了。
  曾經有想過在iteye寫寫自己的博客,但總不太希望受制於人⋯⋯
  直到半年多前知道Octopress這個博客系統,非常喜歡他的口號--blogging like a hacker,而且由於系統是基於ruby的編寫,只要有時間我可以輕鬆自由的為我的博客添磚加瓦,總之這個就是我夢寐以求的自由的平台。
  btw,用markdownMou寫博客真的很有黑客的感覺:–)
  所以離開瑞卡後,我寧願先兼職也希望把這個博客搞起來後再找專職的工作,我怕一忙起來又什麼都丟掉了-_–#
  懶惰是最難治的病⋯⋯
  總之,這裡是流淌着技術血液的大雜燴:–)   

華南商業網 臨危受命

  2011年9月,朋友找到了我,朋友的老東家做的網站--華南商業網的網站改版一年多了沒弄好,問我有沒有時間幫忙看看。
  由於這家公司離家也近,剛開始大概一星期抽空前一次,做做技術指導;離開瑞卡後就以兼職形式加入這個項目。
  這是一個關於連鎖市場、專業市場、地產市場等的商業門戶網站。2011年底,由於業務需要,希望做一次改版。網站早在2003年就開始運營,但一直沒做過技術更新,用的是現在已經幾乎沒人用的asp,網站只有一個常規的維護人員,並沒有開發人員。找了家外包公司負責網站的改版,各種原因拖了大半年也沒完成,最後扔下一堆半成品的代碼就拍拍屁股走人了。
  新版的網站已經改為用php,數據庫也是我熟悉的mysql,但是服務器竟然還是用windows2003,不過也沒辦法,舊網站是asp+sqlserver,肯定一套都是m$的東西。
  百廢待興,排版交給原來的維護人員負責,我專注于新版網站基礎結構做調整。半成品代碼可謂是烏煙瘴氣,連最基本的字符編碼都弄的亂七八糟,各種亂碼橫飛。不少功能還沒實現或者有bug,因此我在調整結構的同時還需要把完善功能。代碼半天就看完,php現學現做,不過相比ruby的開發效率,php落後100年-_–#。接這是服務器的調整優化,為了保證穩定性所以選擇iis運行php,另外由於需要保留舊網站的資源以及部分新功能是我用ruby做的,所以選擇了nginx做反向代理,一個小小的網站用到三種技術兩個數據庫實現,的確夠折騰的。
  9月份接觸,11月正式加入,終於趕在老板挑的1月的吉時上線。從我的角度來看問題還是一大堆,服務器硬件老化,大部份元件已經停產;網站結構過於混亂,需要進一步的整合、優化等等。雖然還有這樣那樣的問題,但是折騰了一年多的改版終於完成,公司上下的一塊心頭大石終於放了下來。
  臨危受命,這是我做過的技術上最輕鬆的項目,php、windows雖沒有太多的實戰經驗,但一理通百理明,很快的就搞通了;但是項目的壓力卻是最大的,老板曾經說過,如果這次還不行就不改版了,整個項目的成敗都壓在我一個人身上。最終我還是扛過來了,不過也慨嘆這樣的爛尾項目為什麼在中國似乎是常態呢⋯

瑞卡租車 如願以償

  2011年,回廣州後正式加入的一個項目。一個我從2007年就想加入的項目,ruby的項目。早在2006年我就知道ruby,2007年在日本自學ruby過一個月左右,就一直想做ruby的項目,但是在那之後由於工作太忙,公司也沒有ruby項目,也就不了了之了,但是我的ruby夢一直沒有停歇過。三言兩語很難說清楚為什麼我會對ruby情有獨鍾,但是如果沒瑞卡租車這個ruby項目,我可能會選擇回日本發展了。
  7月25號正式入職,就立刻告知新的ruby系統三個月內就要完成,做好準備替換現有的php系統,因此上班時間是早9點晚9點,沒有雙休,沒有節假日。對於早已經適應日本工作強度的我來說,這個也不算什麼,真正有挑戰性的是ruby和rails。團隊總共只有五個人,其中一個是team leader K,ruby達人,比我早入職幾個星期;剩下三人都是公司原來php團隊調過來的元老;還有一個業務、技術都不懂的我⋯⋯
  記得以前高中英語老師說過,別看有些人寫著精通20多國家語言有多麼了不起,只要學好英語,什麼法語、德語等等就是詞彙量的問題
  我覺得編程語言也同樣的道理,到你真開竅的時候,不會拘泥與某種技術或語言,關心只是怎麼去解決問題。
  現學現做,印象最深的是晚上報銷的都城鴨腿飯,還有每天回家繼續工作到一兩點後發郵件匯報的時發現K竟然馬上回了,五人開發團隊的激情可見一斑。
  三個月後,立下的軍令狀完成了,再接下來是一個月的測試,到12月底系統正式切換,雖然也有磕磕碰碰,但是新系統總算在一個月後穩定下來。
  沒有一切編程規範的繁文縟節,沒有都麼牛x的工具,rails就是我們的規範,ruby就是我們的工具。
  五個人五個月不到的時間成功改寫了一個運行了兩年多的業務系統,我們辦到了!
  也就是這個時候我第一次知道所謂的創業型公司是什麼樣子的。公司年輕,員工有朝氣,氣氛活躍,來公司不到半年,我有幸的經歷了公司的一次融資,全公司為之而振奮。
  伴隨着2011年的年會的結束,我迎來了更有挑戰性的2012。
  和原來的php團隊匯合,整個技術團隊一下子飆升為12個人。新系統從測試開始我就是主要的業務對接人,上線後也參與到公司各種運營理會中,而此时带领团队tl却开始退居二线……
  3月,半年來幾次小地震後,公司迎來了第一次大地震,至少對於我來說。一個初創時就在公司管理團隊骨幹(也是技術出身)和一直帶領我們的K相繼離開公司,整個技術部頓時群龍無首⋯⋯
  4月,公司聘請的新技術部負責人到位⋯⋯
  5月,在其他部門的“外科手術“過後,技術部也接到裁員通知,最終只剩下7人⋯⋯
  6月,剛來3個月的技術部負責人完成了他的歷史使命後,也揚長而去⋯⋯
  在這樣的“亂世”中,卻給我提供了一個展示自己的好機會。從系統上線後到K離去,我已經慢慢的從系統開發工程師轉變為業務負責人,K離去時把技術部所有資料都交接給我,事實上是整個技術部交接給我了。業務系統的需求、網站、郵箱系統、網絡、服務器等無論大小事情都順理成章的變成我的任務,同時由於裁員後人手不足,我還不得不兼顧部分開發的任務。
  8月,公司再聘請了一名技術部負責人。
  9月,我第一次被叫到ceo辦公室,和ceo暢談後,拿到了也許是創業型公司的“最高獎勵”--股票期权。
  10月,經過一番掙扎後,我向公司提交了辭呈。
  如願以償,不僅是技術上的,還是更多是來自努力後被肯定的那份滿足感。加入公司後一個月轉正,三個月後掌管業務系統、半年後時技術部,這一切都都多虧了有K對我的賞識。
  開始考慮是否離開,是在6、7月間,當時整個技術部已經趨於穩定,已經從之前的地震恢復過來,而由於公司策略慢慢改為線下,對系統的依賴慢慢減少。人員的變動加上公司策略的改變,那種不眠不休的那種工作激情已經一去不復返,可以選擇留下慢慢混日子,但我還是希望能找一個能發揮自己潛能的新舞台,嘗試更多新事物。