Code Refactoring Javaeye Rails1.X 2.X

19

Click here to load reader

Transcript of Code Refactoring Javaeye Rails1.X 2.X

Page 1: Code Refactoring Javaeye Rails1.X 2.X

­­ 从 rails 1.x 到 2.x 

重构你的应用

Page 2: Code Refactoring Javaeye Rails1.X 2.X

Agenda 重构前后的变化

开发模式的改变

Database Migration RESTful Resource

重构实例 数据校验

acts_as_xxx Fat Model / Rich Domain Object 避免 monkey patch javascript / css  优化

Q & A

Page 3: Code Refactoring Javaeye Rails1.X 2.X

重构前后的变化

+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Controllers          |  3006 |  2556 |      49 |     407 |   8 |     4 || Helpers              |   537 |   492 |       0 |      47 |   0 |     8 || Models               |  2169 |  1805 |      54 |     262 |   4 |     4 || Libraries            |   932 |   753 |       8 |      80 |  10 |     7 || Model specs          |  1270 |   974 |       0 |       0 |   0 |     0 || View specs           |     0 |     0 |       0 |       0 |   0 |     0 || Controller specs     |   181 |   101 |       0 |       0 |   0 |     0 || Helper specs         |   111 |    91 |       0 |       0 |   0 |     0 |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Total                |  8206 |  6772 |     111 |     796 |   7 |     6 |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+  Code LOC: 5606     Test LOC: 1166     Code to Test Ratio: 1:0.2

+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Controllers          |  4508 |  3868 |      17 |     373 |  21 |     8 || Helpers              |  1311 |  1112 |       0 |      86 |   0 |    10 || Models               |  3153 |  2121 |      45 |     205 |   4 |     8 || Libraries            |  1379 |   885 |       7 |      80 |  11 |     9 |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+| Total                | 10351 |  7986 |      69 |     744 |  10 |     8 |+­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­+­­­­­­­+­­­­­­­­­+­­­­­­­­­+­­­­­+­­­­­­­+  Code LOC: 7986     Test LOC: 0     Code to Test Ratio: 1:0.0

2007­12

2008­07

Page 4: Code Refactoring Javaeye Rails1.X 2.X

重构前后的变化

代码行数 7986 => 5606

每个方法的平均代码行数 8 => 4

测试代码 0 => 1166

功能比原先更多 增加了知识库,问答频道,博客频道,垂直频道 完善了个人博客,新闻频道,全文检索,后台管理

这些改变是如何发生的? 从 Rails1.x ~ 2.x 的新特性驱动

从开源软件中不断学习 Rails 开发最佳实践

Page 5: Code Refactoring Javaeye Rails1.X 2.X

什么是重构

http://en.wikipedia.org/wiki/Refactoring

在保持功能一致的前提下,让代码变得 更容易阅读 减少重复的代码 减少代码复杂度 更适应将来的扩展

Page 6: Code Refactoring Javaeye Rails1.X 2.X

Database Migrations

0.x 手工维护

1.x database migration script

2.x sexy migration

DRY – Don't Repeat Yourself

class CreateUsers < ActiveRecord::Migration  def self.up    create_table :users do |t|      t.column :name, :string, :null => false

class CreateCompanies < ActiveRecord::Migration  def self.up    create_table :companies do |t|      t.string :name, :null => false

Page 7: Code Refactoring Javaeye Rails1.X 2.X

RESTful Resource

1.x routes.rb

2.x routes.rb

COC ­ Convention over Configuration

  map.connect 'members', :controller => 'group', :action => 'members'  map.connect ':controller/:action/:id'  map.connect ':controller/:action'

  map.resource :group_setting, :controller => 'group/admin/group_setting'

    map.resources :group_users, :controller => 'group/admin/group_users',

 :member => {:approve => :put}, :collection => {:search => :get}

Page 8: Code Refactoring Javaeye Rails1.X 2.X

重构实例一 数据校验

Before

After

  def create    #...    if @news.save      redirect_to :action => :show, :id => @news.id    else      render :action => 'new'    end  end

  def create    #...    @news.save!    redirect_to news_path(@news)  end

Page 9: Code Refactoring Javaeye Rails1.X 2.X

重构实例一 数据校验

application.rb

学自 Beast http://beast.caboo.se/

  def rescue_action(exception)    exception.is_a?(ActiveRecord::RecordInvalid) ? render_invalid_record(exception.record)   end

  def render_invalid_record(record)    @invalid_record = record    respond_to do |format|      format.html do        render :action => (record.new_record? ? 'new' : 'edit')      end      format.js do        render :update do |page|          page.alert @invalid_record.errors.full_messages.join("\n")        end      end    end  end

Page 10: Code Refactoring Javaeye Rails1.X 2.X

重构实例二 acts_as_xxx

Before

After

class Topic < ActiveRecord::Base  serialize :rate_data  def rateable_by?(user)    #...  end

  def new_rate(user, type)    #...  endend

module RateableModel  def self.included(model)    model.serialize :rate_data  end

    def rateable_by?(user)    #...  endend

class Post < ActiveRecord::Base  serialize :rate_data  def rateable_by?(user)    #...  end

  def new_rate(user, type)    #...  endend

class Topic < ActiveRecord::Base  include RateableModel

class Post < ActiveRecord::Base  include RateableModel

Page 11: Code Refactoring Javaeye Rails1.X 2.X

重构实例二 act_as_xxx

持续改进,更加符合 Rails 的 DSL 习惯: acts_as_xxx

学自 Rails  自带功能,如 acts_as_list

从 Plugin 中学习,如 acts_as_cached, acts_as_taggable

class Topic < ActiveRecord::Base  acts_as_rateable

class Post < ActiveRecord::Base  acts_as_rateable

Page 12: Code Refactoring Javaeye Rails1.X 2.X

重构实例三 Fat Model / Rich Domain Object

显示圈子共享文件 groups_controller.rb

Controller 放置了太多的逻辑

不符合 MVC 的理念

单元测试困难 代码重用度低

利用 Rails 提供的各种特性,将代码转移到 Model 上

def share  @latest_attachments = Attachment.find(:all,     :conditions => ["group_id = ?", @group.id], :limit => 5)  @total_size = Attachment.sum(:size, :conditions => ["group_id = ?", @group.id])end

Page 13: Code Refactoring Javaeye Rails1.X 2.X

重构实例三 Fat Model / Rich Domain Object

group.rb

groups_controller.rb

学自 <Agile Web Development with Rails>  和 Rails doc

  has_many :attachments, :order => 'id DESC' do    def latest(number = 5)      find(:all, :limit => number)    end

    def total_size      sum(:size) || 0    end  end

def share  @latest_attachments = @group.attachments.latest  @total_size = @group.attachments.total_sizeend

Page 14: Code Refactoring Javaeye Rails1.X 2.X

重构实例三 Fat Model / Rich Domain Object

named_scope

学自 Rails 2.1 新特性

ActiveRecord 的动态特性让实现 Rich Domain Object 变的轻松

associations' dynamic operation  named_scope, with_scope dirty_check method_missing (e.g will_paginate)

class News < ActiveRecord::Base  named_scope :categoried_by, lambda { |category| { :conditions => ['category = ?', category] }  named_scope :recent, lambda { |days| { :conditions => ['created_at >= ?', days.ago] }end

News.categoried_by("ruby")News.recent(3.days).categoried_by("ruby")

Page 15: Code Refactoring Javaeye Rails1.X 2.X

重构实例四 避免 monkey patch

什么是 monkey patch?http://en.wikipedia.org/wiki/Monkey_patch

方便,简单但是不利于 Rails/Plugin 升级

实例:压缩用户头像

module MiniMagick  class Image    def compressing(convert_gif)      format = self[:format]      if convert_gif && format == "GIF"        #convert 1st frame for animation gif        run_command("convert","#{@path}[0]", "#{@path}")      elsif format == "BMP"        format("PNG")      end    end  endend

Page 16: Code Refactoring Javaeye Rails1.X 2.X

重构实例四 避免 monkey patch

monkey patch

initializers/attachment_fu.rb

#直接修改 mini_magick_processor.rb的 resize_image方法def resize_image(img, size)  img.compressing(true)  #...end

#利于 Rails提供的 alias_method_chain做修改Technoweenie::AttachmentFu::Processors::MiniMagickProcessor.module_eval do  def resize_image_with_compressing(img, size)    img.compressing(true)    resize_image_without_compressing(img, size)  end

    alias_method_chain :resize_image, :compressingend

Page 17: Code Refactoring Javaeye Rails1.X 2.X

重构实例五 javascript/css  优化

Yslow http://developer.yahoo.com/yslow/

避免使用 javascript_include_tag :defaults

ActionController::Base.asset_host = "http://www.javaeye.com"

合并常用 javascript 文件,对首页做特别优化

减少 CSS 请求

开发时使用多个 CSS 文件

发布时候用脚本合并

Page 18: Code Refactoring Javaeye Rails1.X 2.X

重构实例五 javascript/css  优化

DIR_PATH = "public/stylesheets"IMPORT_EXP = /@import url\( (.*) \);/COMMENT_EXP = /^\/\*(.*)\*\/$/

def pack(file_name)  file_path = File.expand_path(file_name)  dir_name = File.dirname(file_path)  f = File.new(file_path, "r")  result = ""  while (line = f.gets)    if m = IMPORT_EXP.match(line)      result << pack("#{dir_name}/#{m[1]}")    else      result << line unless line =~ COMMENT_EXP    end  end    resultend

Dir.foreach(DIR_PATH) { |f|  if f =~ /.css$/    file_name = "#{DIR_PATH}/#{f}"    result = pack(file_name)    File.open(file_name, "w") do |file|      file.puts result    end  end}

@import url( themes/basic/tools.css );@import url( themes/basic/typo.css );@import url( themes/basic/forms.css );@import url( themes/basic/layout.css );@import url( themes/javaeye2.css );@import url( themes/news_default.css );

开发时多个 CSS

发布时候使用合并脚本 =>

Page 19: Code Refactoring Javaeye Rails1.X 2.X

Q & A