免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
重構(gòu) Rails 項目之最佳實踐
開放課堂項目是由教育大發(fā)現(xiàn)社區(qū)發(fā)起,成都 ThoughtWorks,成都彩程設(shè)計公司,成都超有愛教育科技有限公司等一起合作開發(fā)和運營的教育公益網(wǎng)站,是一個提供給小學(xué)3-6年級師生設(shè)計和開展綜合實踐課的教育開放平臺。項目代碼放在 GitHub,采用 Ruby on Rails 作為開發(fā)框架。

很高興我們 Pragmatic.ly 團隊能參與到這個公益項目的開發(fā)中,我相信這是個對社會很有價值的事情。征得發(fā)起方的同意,我把這次重構(gòu)工作做成了一次在線秀,也正是因為這次這樣的形式,和很多朋友直接在http://sifuyou.com/zhuxian/ 上交流了很多 Rails 項目重構(gòu)方面的想法。通俗點說,重構(gòu)就是對內(nèi)要通過修改代碼結(jié)構(gòu)等方法讓代碼變得更美,提高可閱讀性和可維護性,而對外不改變原來的行為,不做任何功能的修改。所以我們做重構(gòu)要做好兩點: 1) 一次只做一件事情,不能修改了多個地方后再做驗證 2) 小步增量前進,路是一步一步走出來的。同時,為了保證重構(gòu)的正確性,必須要測試保護,每一次小步修改都必須要保證集成測試仍然通過。之所以要保護集成測試而非單元測試,正是因為重構(gòu)只改變內(nèi)部結(jié)構(gòu),而不改變外部行為,所以,單元測試是可能失敗的(其實概率也不高),而集成測試是不允許失敗的?;?Re-education 的代碼,這次重構(gòu)主要涉及了 Controllers 和 Models 兩個方面。有興趣的朋友可以去 RailsCasts China 觀看視頻。

Rails 做為一個 Web 開發(fā)框架,幾個哲學(xué)一直影響著它的發(fā)展,比如 CoC, DRY。而代碼組織方式,則是按照 MVC 模式,推崇 “Skinny Controller, Fat Model”,把應(yīng)用邏輯盡可能的放在 Models 中。

Skinny Controller, Fat Model

讓我們來看最實際的例子,來自 Re-education 的代碼。

class PublishersController < ApplicationController
  def create
    @publisher = Publisher.new params[:publisher]
 
    # trigger validation
    @publisher.valid?
 
    unless simple_captcha_valid? then
      @publisher.errors.add :validation_code, "驗證碼有誤"
    end
 
    if !(params[:password_copy].eql? @publisher.password) then
      @publisher.errors.add :password, "兩次密碼輸入不一致"
    end
 
    if @publisher.errors.empty? then
 
      @publisher.password = Digest::MD5.hexdigest @publisher.password
      @publisher.save!
 
      session[:user_id] = @publisher.id
      redirect_to publisher_path(@publisher)
    else
      p @publisher.errors
      render "new", :layout => true
    end
  end
end
按照 “Skinny Controller, Fat Model” 的標準,這段代碼有這么幾個問題:

action 代碼量過長
有很多 @publisher 相關(guān)的邏輯判斷。
從權(quán)責(zé)而言,Controller 負責(zé)的是接收 HTTP Request,并返回 HTTP Response。而具體如何處理和返回什么數(shù)據(jù),則應(yīng)該交由其他模塊比如 Model/View 去完成,Controller 只需要當(dāng)好控制器即可。所以,從這點上講,如果一個 action 行數(shù)超過 10 行,那絕對已經(jīng)構(gòu)成了重構(gòu)點。如果一個 action 對一個 model 變量引用了超過 3 次,也應(yīng)該構(gòu)成了重構(gòu)點。下面是我重構(gòu)后的代碼。
class PublishersController < ApplicationController
  def create
    @publisher = Publisher.new params[:publisher]
 
    if @publisher.save_with_captcha
      self.current_user = @publisher
      redirect_to publisher_path(@publisher)
    else
      render "new"
    end
  end
end
 
class Publisher < ActiveRecord::Base
  apply_simple_captcha :message => "驗證碼有誤"
 
  validates :password,
    :presence => {
      :message => "密碼為必填寫項"
    },
    :confirmation => {
      :message => "兩次密碼輸入不一致"
    }
 
  attr_reader :password
  attr_accessor :password_confirmation
 
  def password=(pass)
    @password = pass
    self.password_digest = encrypt_password(pass) unless pass.blank?
  end
 
  private
 
  def encrypt_password(pass)
    Digest::MD5.hexdigest(pass)
  end
end
在上面的重構(gòu)中,我主要遵循了兩個方法。

把應(yīng)該屬于 Model 的邏輯從 Controller 移除,放入了 Model。
利用虛擬屬性 password, password_confirmation 處理了本不屬于 Publisher Schema 的邏輯。
關(guān)于簡化 Controller,多利用 Model 方面的重構(gòu)方法,Rails Best Practices 有不少不錯的例子,也可以參考。

Move code into model
Add model virtual attribute
Move finder to scope
Beyond Fat Model

對于項目初期而言,做好這兩個基本就夠了。但是,隨著邏輯的增多,代碼量不斷增加,我們會發(fā)現(xiàn) Models 開始變得臃腫,整體維護性開始降低。如果一個 Model 對象有效代碼行超過了 100 行,我個人認為因為引起警覺了,要思考一下有沒有重構(gòu)點。一般而言,我們有下面幾種方法。

Concern

Concern 其實也就是我們通常說的 Shared Mixin Module,也就是把 Controllers/Models 里面一些通用的應(yīng)用邏輯抽象到一個 Module 里面做封裝,我們約定叫它 Concern。而 Rails 4 已經(jīng)內(nèi)建支持 Concern, 也就是在創(chuàng)建新 Rails 項目的同時,會創(chuàng)建 app/models/concerns 和 app/controllers/concerns。大家可以看看 DHH 寫的這篇博客 Put chubby models on a diet with concerns 和 Rails 4 的相關(guān) commit。具體使用可以參照上面的博客和下面我們在 Pragmatic.ly 里的實際例子。

module Membershipable
  extend ActiveSupport::Concern
 
  included do
    has_many :memberships, as: :membershipable, dependent: :destroy
    has_many :users, through: :memberships
    after_create :create_owner_membership
  end
 
  def add_user(user, admin = false)
    Membership.create(membershipable: self, user: user, admin: admin)
  end
 
  def remove_user(user)
    memberships.find_by_user_id(user.id).try(:destroy)
  end
 
  private
 
  def create_owner_membership
    self.add_user(owner, true)
    after_create_owner_membership
  end
 
  def after_create_owner_membership
  end
end
 
class Project < ActiveRecord::Base
  include Membershipable
end
 
class Account < ActiveRecord::Base
  include Membershipable
end
通過上面的例子,可以看到 Project 和 Account 都可以擁有很多個用戶,所以 Membershipable 是公共邏輯,可以抽象成 Concern 并在需要的類里面 include,達到了 DRY 的目的。

Delegation Pattern

Delegation Pattern 是另外一種重構(gòu) Models 的利器。所謂委托模式,也就是我們把一些本跟 Model 數(shù)據(jù)結(jié)構(gòu)淺耦合的東西抽象成一個對象,然后把相關(guān)方法委托給這個對象,同樣看看具體例子。

未重構(gòu)前:

class User < ActiveRecord::Base
  has_one :user_profile
 
  def birthday
    user_profile.try(:birthday)
  end
 
  def timezone
    user_profile.try(:timezone) || 0
  end
 
  def hometown
    user_profile.try(:hometown)
  end
end
當(dāng)我們需要調(diào)用的 user_profile 屬性越來越多的時候,會發(fā)現(xiàn)方法會不斷增加。這個時候,通過 delegate, 我們可以把代碼變得更加的簡單。

class User < ActiveRecord::Base
  has_one :user_profile
 
  delegate :birthday, :tomezone, :hometown, to: :profile
 
  def profile
    self.user_profile ||
      UserProfile.new(birthday: nil, timezone: 0, hometown: nil)
  end
end
關(guān)于更多的如何在 Rails 里使用 delegate 的方法,參考官方文檔 delegate module

Acts As XXX

相信大家對 acts-as-list,acts-as-tree 這些插件都不陌生,acts-as-xxx 系列其實跟 Concern 差不多,只是它有時不單單是一個 Module,而是一個擁有更多豐富功能的插件。這個方式在重構(gòu) Models 時也是非常的有用。還是舉個例子。

module ActiveRecord
  module Acts #:nodoc:
    module Cache #:nodoc:
      def self.included(base)
        base.extend(ClassMethods)
      end
 
      module ClassMethods
        def acts_as_cache(options = { })
          klass = options[:class_name] || "#{self.name}Cache".constantize
          options[:delegate] ||= []
 
          class_eval <<-EOV
            def acts_as_cache_class
              ::#{klass}
            end
 
            after_commit :create_cache, :if => :persisted?
            after_commit :destroy_cache, on: :destroy
 
            if #{options[:delegate]}.any?
              delegate *#{options[:delegate]}, to: :cache
            end
 
            include ::ActiveRecord::Acts::Cache::InstanceMethods
          EOV
        end
      end
 
      module InstanceMethods
        def create_cache
          acts_as_cache_class.create(self)
        end
 
        def destroy_cache
          acts_as_cache_class.destroy(self)
        end
 
        def cache
          acts_as_cache_class.find_or_create_cache(self.id)
        end
      end
    end
  end
end
 
class User < ActiveRecord::Base
  acts_as_cache
end
 
class Project < ActiveRecord::Base
  acts_as_cache
end
Beyond MVC

如果你在使用了這些方式重構(gòu)后還是不喜歡代碼結(jié)構(gòu),那么我覺得可能僅僅 MVC 三層就不能滿足你需求了,我們需要更多的抽象,比如 Java 世界廣而告之的 Service 層或者 Presenter 層。這個更多是個人習(xí)慣的問題,比如有些人認為應(yīng)用邏輯(業(yè)務(wù)邏輯)不應(yīng)該放在數(shù)據(jù)層(Model),或者一個 Model 只應(yīng)該管好他自己的事情,多個 Model 的融合需要另外的類來做代理。關(guān)于這些的爭論已經(jīng)屬于意識形態(tài)的范疇,個人的觀點是視需要而定,沒必要一上來就進入 Service 或者 Presenter,保持代碼的簡單性,畢竟減少項目 Bugs 的永恒不變法就是沒有代碼。但是,一旦達到可適用范圍,該引入時就引入。這里也給大家介紹一些我們在用的方法。

Service

之前已經(jīng)提到 Controller 層應(yīng)該只接受 HTTP Request,返回 HTTP Response,中間的處理部分應(yīng)該交由其他部分。我們可以優(yōu)先把這部分邏輯放在 Model 層處理。但是,Model 層本身從定義而言應(yīng)該是只和數(shù)據(jù)打交道,而不應(yīng)該過多涉及業(yè)務(wù)邏輯。這個時候我們就需要用到 Service 層。繼續(xù)例子!

class ProjectHookService
  attr_reader :project, :data
 
  def initialize(hook_params = {})
    @project = Project.from_param(hook_params)
    @data = JSON.parse(hook_params['payload'])
  end
 
  def parse
    Prly.hook_services.each do |service|
      parser = service.new(@project, @data)
      if parser.parseable?
        parser.parse
      end
    end
  end
 
  def parseable?
    @project.present? && @data.present?
  end
end
 
class HooksController < ApplicationController
  def create
    service = ProjectHookService.new(params)
    if service.parseable?
      service.parse
      render nothing: true, status: 200
    else
      render text: 'Faled to parse the payload', status: 403
    end
  end
end
如果大家仔細分析這段代碼的話,會發(fā)現(xiàn)用 Service 是最好的方案,既不應(yīng)該放在 Controller,又不適合放在 Model。如果你需要大量使用這種模式,可以考慮一下看看 Imperator 這個 Gem,算是 Rails 世界里對 Service Layer 實現(xiàn)比較好的庫了。

Presenter

關(guān)于 Presenter,不得不提的是一個 Gem ActivePresenter,基本跟 ActiveRecord 的使用方法一樣,如果項目到了一定規(guī)模比如有了非常多的 Models,那么可以關(guān)注一下 Presenter 模式,會是一個很不錯的補充。

class SignupPresenter < ActivePresenter::Base
  presents :user, :account
end
 
SignupPresenter.new(:user_login => 'dingding',
                    :user_password => '123456',
                    :user_password_confirmation => '123456',
                    :account_subdomain => 'pragmaticly')
We’re good now

基本上上面是我在一個 Rails 項目里重構(gòu) Controller 和 Model 時會使用的幾種方法,希望對你有用。Terry Tai 上周在他的博客里分享了他在重構(gòu)方面的一些想法,也很有價值,推薦閱讀。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
使用 ActiveScaffold 增強 Ruby on Rails 的功能
〈轉(zhuǎn)〉重構(gòu)臃腫 ActiveRecord 模型的 7 種方式 [翻譯] · Ruby China
Rails實現(xiàn)字段加密存儲
Rails 基礎(chǔ)指南
python3 manage.py遷移異常
JFinal BaseController 通用
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服