Code Refactoring Javaeye Rails1.X 2.X
Click here to load reader
-
Upload
wear -
Category
Technology
-
view
1.271 -
download
2
Transcript of Code Refactoring Javaeye Rails1.X 2.X
从 rails 1.x 到 2.x
重构你的应用
Agenda 重构前后的变化
开发模式的改变
Database Migration RESTful Resource
重构实例 数据校验
acts_as_xxx Fat Model / Rich Domain Object 避免 monkey patch javascript / css 优化
Q & A
重构前后的变化
++++++++| 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
200712
200807
重构前后的变化
代码行数 7986 => 5606
每个方法的平均代码行数 8 => 4
测试代码 0 => 1166
功能比原先更多 增加了知识库,问答频道,博客频道,垂直频道 完善了个人博客,新闻频道,全文检索,后台管理
这些改变是如何发生的? 从 Rails1.x ~ 2.x 的新特性驱动
从开源软件中不断学习 Rails 开发最佳实践
什么是重构
http://en.wikipedia.org/wiki/Refactoring
在保持功能一致的前提下,让代码变得 更容易阅读 减少重复的代码 减少代码复杂度 更适应将来的扩展
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
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}
重构实例一 数据校验
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
重构实例一 数据校验
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
重构实例二 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
重构实例二 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
重构实例三 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
重构实例三 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
重构实例三 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")
重构实例四 避免 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
重构实例四 避免 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
重构实例五 javascript/css 优化
Yslow http://developer.yahoo.com/yslow/
避免使用 javascript_include_tag :defaults
ActionController::Base.asset_host = "http://www.javaeye.com"
合并常用 javascript 文件,对首页做特别优化
减少 CSS 请求
开发时使用多个 CSS 文件
发布时候用脚本合并
重构实例五 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
发布时候使用合并脚本 =>
Q & A