有这么一个需求,一个在线音乐商店系统,我们暂且叫它’online-store’,需要记录消费者对每首歌的评论,一个有经验的rails developer会很快地写出下面的代码:

class Music
  has_many :comments
end

class Comment
  belongs_to :music
end

对应的表结构如下:

#musics:   
  |  id  | integer|   
  | name | varchar|   
  | ...  |  ...   |   
#comments:   
  | id      | integer|   
  | text    | varchar|   
  | music_id| integer|   

如果需要给music添加,查看评论,可以通过如下代码实现:

  #添加评论
  music.comments.create {:text => 'this is realy nice'}
  #查看评论
  music.coments

风云变幻,斗转星移,apple的app store创造了软件销售的新模式,我们的vendor也坐不住了,决定在现有的音乐商店系统上出售应用程序,电影,游戏等内容,同样,这些内容也需要支持评论,有了前面成功的经验,你信心满满增加了下面几个model:

  class Application
    has_many :comments
  end

  class Movie
    has_many :comments
  end

  class Game
    has_many :comments
  end

再来看看我们之前写的model Comment:

class Comment
  belongs_to :music
end

现在需要支持多种内容,而且这些类内容之间出了都可以被评论外,再无其他关联,那这个belongs_to该怎么写呢?一个最直接的思路是,扩展Comment,让其支持对以上四类内容的评论,代码如下:

class Comment
  belongs_to :music, though => "music_id"
  belongs_to :game, though => "game_id"
  belongs_to :application, though => "application_id"
  belongs_to :movie, though => "movie_id"
end

表结构如下:

#comments:   
  | id             | integer|   
  | text           | varchar|   
  | music_id       | integer|   
  | game_id        | integer|
  | application_id | integer|
  | movie_id       | integer|

有了以上的model,你就可以给应用程序,电影,游戏增加评论了:

  #创建评论
  application.comments.create {:text => "this is a nice app"}
  movie.comments.create {:text => "this is a nice movie"}
  game.comments.create {:text => "this is a nice game"}
  #查看评论
  application.comments
  movie.comments
  game.comments

目前看来,这些代码工作得很好,然而,做为一个有着良好直觉的程序员,你敏锐地觉察到,将来可能有更多的内容出现在这个”onlne-store”中,也会有更多的内容需要支持评论——你成功地识别出一个”易变点“,每当新增一种内容的时候,你就需要打开这个Comment类,新增一个association,同时,还需要增加migration,这个设计明显违背了开闭原则”。那么,这个问题该怎么解决呢? 再来分析下这个问题,上面我们提到,这些model唯一的共性是 “可以被评论”,于是我们可以抽象出一个概念——“commentable”。如果我们让comment对象知道它所对应的”commentable”对象的id以及类型(game/application/movie),我们就可以获得一个“commentable”对象的所有comments,参考下面的代码:

  #查看id为1的music的评论
  Comment.find(:commentable_id => 1, :commentable_type => 'music')

  #查看id为1的application的评论
  Comment.find(:commentable_id => 1, :commentable_type => 'application')

如此,comments的表结构就可以简化为:

#comments:   
  | id              | integer|   
  | text            | varchar|   
  | commentable_id  | integer|   
  | commentable_type| varchar|

model代码简化为:

 #添加评论
 Comment.create({:text => "good staff", :commentable_id => "1", :commentable_type => "music"})
 #查看评论
 Comment.find(:commentable_id => 1, :commentable_type => 'music')
  class Comment
    belongs_to :commentable, :polymorphic => true
  end
  class Application
    has_many :comments, :as => :commentable
  end
  class Movie
    has_many :comments, :as => :commentable
  end
  class Game
    has_many :comments, :as => :commentable
  end
  #添加评论
  movie.comments.create {:text => 'this is realy nice'} 
  #查看评论
  movie.coments

更多关于“polymorphic association”的信息,请参考这里 怎么样,这样一来,再新增多少种内容类型,处理起来都非常容易,扩展下commentable_type的值就可以了。这个思路的根本出发点在于为识别、分离变化点。这类问题可以抽象为这样一个问题:”如何把一个概念应用到一组除了这个概念,没有其他任何关联的对象上?” ,此类问题可以采用上述思路解决。

声明:本文翻译自这里

###递归101
我们都知道什么是递归吧?一个自己调用自己的函数,或者函数A调用函数B,函数B又调用函数A,或者是A调用B,B调用C, C调用A。但是我们大多数情况下所说的递归函数,是指一个调用自身的函数。
在Java世界中,递归函数的曝光率很低,说起来有不少原因。
第一,递归不直观,难以理解。对于一段循环代码(for, while), 你可以很直白地看到这段逻辑的全景,即便你是一个初学者,而对于一段递归代码,就不是那么容易了,你只能看到递归逻辑的一次调用,而不得不想象当递归调用发生时,这些多次调用是如何组合在一起的。
第二,比起递归,循环在java中更加容易实现,比如for ,for-each,while, do-while, 数组, iterator, ResultSet,这些结构都是用来实现循环的。
第三,Java中的递归有自己的Achille’s heel: call stack。

总的来说,当调用一个函数时,一个新的call stack frame会被放到call stack的顶部,用以保存局部变量,当前函数的caller,等等。但是,call stack的大小是有限的,当递归的深度不是很深时,调用递归函数是没有什么问题的,但是如果递归调用的深度无法预计,那么很有可能会导致stack overflow. 而循环却不会有这种问题(因为循环不会产生新的call stack frame),因此,使用循环更加安全。

###Scala中的递归
Scala,作为一新兴的functional language,更偏爱递归胜过循环,那么在scala中,是如何解决call stack大小限制的问题的呢?我们来看一个例子:

  def listLength1(list: List[_]): Int = {
    if (list == Nil) 0
    else 1 + listLength1(list.tail)
  }

  var list1 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  var list2 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  1 to 15 foreach( x => list2 = list2 ++ list2 )

  println( listLength1( list1 ) )
  println( listLength1( list2 ) )

函数listLenght1递归地计算list中item的数量,这个函数在计算item数量较少的list时工作地很好,然而在item数量很大时,就会得到stack overflow错误. 递归是函数式语言的谋生手段,但是在Scala中,递归仍然倒在了stack overflow的脚下。

先别急着放弃,Scala有一个很重要的优化递归的方案,只要你用了正确的递归类型。

###首递归和尾递归 根据递归调用的方式,递归可以分为首递归和尾递归。在首递归函数中,函数调用自身后,再进行其他运算(可能会把自用自身后的结果做为这些运算的输入)。在尾递归函数中,所有的计算都在函数调用自身前完成,调用自身是尾递归函数中做的最后一件事情。

这两种递归的区别的重要性目前看起来还不是那么明显,然而,它确实很重要!想象一下一个尾递归函数的执行过程,首先完成所有的计算,在最后一步,马上进行对自身递归的调用,一般情况下,这个时候就该使用call stack frame记录方法调用状态了,然而,这里却不需要:我们不需要记录局部变量,因为所有的计算都已经完成。我们也不需要知道目前在哪个函数中,因为我们始终在同一个函数中。基于以上前提,scala不会创建一个新的call stack frame,而是重用当前的call stack frame ,无论调用次数有多少,call stack也不会增长。这就是scala中尾递归函数的特殊性。在其他语言中,语言设计者通过把尾递归转换成循环的方式进行了优化。

而在首递归函数中,递归调用是不一样的,这是为什么呢?想象一下一个首递归函数的执行过程:先执行一些计算,在递归调用自身,然后在执行另一些计算。在调用自身前,需要记住当前的局部变量,以便在从递归调用返回后继续进行后面的运算,这样,就必须创建一个新的call stack frame来记录当前状态。因此首递归函数还是会有stack overflow的风险。并且无法被优化。

在这里问你一个问题,上面的listLenght1是一个尾递归还是首递归?让我们来看着这个函数做了哪些事情。
A) 检查参数是否为Nil。
B) 如果为空,返回零,因为Nil的长度是零。
C) 如果不为空,则返回1加上递归调用的结果。
递归调用逻辑在这个函数的最后一行,应该是尾递归函数吧?错!在尾递归调用结束后,然后对递归调用结果加一,然后返回最终结果。这实际上首递归(或者可以叫做中递归)因为递归调用并不是所有运算的最后一步。

###尾递归例子
当你用scala写一个递归函数时,你的目标是写成尾递归以便编译器对尾递归进行优化。现在让我们把上面的那个函数重写为尾递归函数。

def listLength2(list: List[_]): Int = {
  def listLength2Helper(list: List[_], len: Int): Int = {
    if (list == Nil) len
    else listLength2Helper(list.tail, len + 1)
  }
  listLength2Helper(list, 0)
}

println( listLength2( list1 ) )
println( listLength2( list2 ) )

我写成两个函数(listLength2 和一个内部的helper函数)以便和上面的例子中的函数接口保持一致。如果你能给listLength2Helper的参数给个默认值,我们就能只提供一个函数,但是我不知道怎么做。长话短说:listLength2只调用了做了实际工作的listLength2Helper,而且listLength2Helper也是个递归函数。

listLength2Helper是个尾递归函数吗?递归调用是所有运算的最后一步,允许scala进行优化?就像listLenght1一样,listLength2首先检查参数是否为Nil,如果不是,就进行递归调用,但是仍然会有一个加一的操作 —— len + 1。难道这个就不是尾递归吗?不,len + 1 运算会在递归调用前运算。只有所有的参数运算完了以后,才会进行递归调用,这个函数确实是个尾递归函数。

##什么是vagrant?
看<a =href=”http://vagrantup.com/docs/getting-started/index.html”>这里</a> ##为什么要用vagrant? 看<a =href=”http://vagrantup.com/docs/getting-started/why.html”>这里</a>

##怎样开始使用vagrant?

  1. 安装VirtualBox
  2. 安装基础虚拟机
$ vagrant box add base http://files.vagrantup.com/lucid32.box

这个基础虚拟机是一个运行着ubuntu,没有安装额外软件的虚拟机,以后的所有更改都是在这个基础虚拟机上进行的。

由于国内网络环境不好,推荐先把这个基础虚拟机镜像下载到本地,然后通过如下脚本安装:

$ vagrant box add base ./lucid32.box

然后运行如下命令初始化,启动虚拟机:

$ vagrant init base
$ vagrant up

至此,一个vagrant的基础虚拟机环境就创建成功了,想知道如何访问这个虚拟机?通过如下命令就可以登陆上虚拟机了。

$ vagrant ssh

##怎么配置虚拟机?

#创建一个目录用于存放配置虚拟机的文件
$ mkdir mybox 
#初始化这个新虚拟机
$ cd mybox 
$ vagrant box add mybox http://files.vagrantup.com/lucid32.box
$ vagrant init

经过以上步骤,在mybox目录下会多出一个文件Vagrantfile,这个就是配置虚拟机的文件, 在这个文件里,vagrant支持通过chefpuppet对虚拟机进行配置。

##如何使用这个虚拟机?

# 启动虚拟机
$ vagrant up
# 停止虚拟机
$ vagrant halt
# 登陆到虚拟机
$ vagrant ssh
# 删除虚拟机
$ vagrant destroy
# 把当前虚拟机打包成一个本地镜像,便于再次分发
$ vagrant package

####OOCamp Day1

  • Tasking 做tasking的标准是,这个task具备可测试性
  • 封装 对一个类的属性的读取,修改都在该类的上下文,那么这个类就没有封装泄露。 tell, don’t ask

####OOCamp Day2 使用自定义对象而不是java原生对象,更加容易适应需求变化

####OOCamp Day3

  • 什么是重构? 不改变功能的情况下改变代码的架构,重构的时候可以随时停止
  • 如何识别bad smell? 如果有一个子类,没有比父类更多的状态,那么这其实一个strategy模式
  • 如何证明代码被改进了? 把一个badsmell经过重构变成了一个pattern,或者bad smell消失了,那么代码就是被改进了。
  • 什么是Pattern? Pattern是一类问题及该类问题的解决方案。使用Pattern很容易,关键是正确地识别出问题, 而问题一般都表现为一个bad smell。例如下面几个problem以及解决这些bad smell所使用pattern:
    class explosion Decorator
    object creation is dependent on outside Factory

####OOCamp Day4
重构

  • 重构技巧:copy & paste => extract method & inline method
  • 代码中,什么是其组成的最小单元? 功能
  • 要有足够的bad smell支撑你来做重构

####OOCamp Day5 一个很难发现的bad smell: 上下文分散, Pattern:Strategy,把业务逻辑放到更加集中的类里

####OOCamp Day6 架构:

  • 什么是架构?
    软件架构回答了以下两个问题:
    1. 系统中有哪些组件
    2. 组件之间如何交互
  • 架构只有有了应用场景,才是有意义的

  • 五种常用的架构:
    单体: Singleton,最简单的架构
    黑板:模块之间的地位平等, 广播消息
    分层:上层把下层当做抽象机器, 拒绝跨层. 大多数问题都可以通过引入一个分层来解决, 但这通常都会带来新的问题.
    数据流:过滤器
    微核:分成平台和核. 带来的问题是隐性知识增多,一个典型的例子就是spring.

DRY:
DRY的代价是耦合, 绝大部分是值得的, 有些时候,需要故意引入重复来消除耦合.
DRY隐含了知识管理成本
note: 没有任何好的软件实践是不需要代价的.

Mock:
当我们提起Mock时,实际上在不同的上下文中,有着不同的含义。
如果你想测试的代码依赖一个尚未实现的外部接口,你需要“mock”这个外部接口,让其按照约定的行为工作, 然后测试你的程序,这时候,“mock”是一种技术。如果你写了了一个实现了这个外部接口的‘mock’ class,在这个class中返回假的数据以支撑你的测试,这时候,“mock”是一种“mock framework”。

当然,在实际开发过程中,你一般不需要自行实现一个“mock framework”,有太多的mock framework供你选择, easymock,jmock, mockito等等。

几种常见的mock手段:
fake, stub, dummy, mock

Q:什么时候决定了当前在用mock?
A:call verify, 在此之前,都是在stub.

State based testing:
内容实现不稳定时,采用基于状态的测试, 用stub即可。
Interaction based testing:
在明确系统的行为边界时,需采用基于交互的测试,需要使用mock来验证代码的行为。

Git save snapshot in each commit rather than store the delta

##Local operations:## Three states:
###modified modified but not adding to git local database Working directory

###staged modified and ready to add to git local database Staging area

###commited data safely stored in local database Git repository

##Git config you can put git config file in the following places:

  • /etc/gitconfig
  • ~/.gitconfig
  • /.git/config

you can show your git configs by

  • git config –list