開發新手最容易犯的50個 Ruby on Rails 錯誤(1)

OneAPM藍海訊通發表於2019-02-16

【編者按】本文最早釋出與 JETRuby 部落格,主要介紹了開發新手最容易犯的 Ruby 錯誤。文章系國內 ITOM 管理平臺 OneAPM 編譯呈現。

一年前,我們創立了以 “Rubyboost” 為名的 Ruby on Rails 課程。簡而言之,本課程的目標是使對程式設計瞭解不多的新手也能在兩個月內,提升技能、成為初級開發者。在成功完成課程之後,學生會收到為其兩個月的實習邀請,實習地點就在我們公司。如果一切順利,就會得到聘用。不得不說,這是一種相對公平且簡單的成為職業開發者的道路,你覺得呢?

順帶說一句,你根本想不到,有多少人願意來參加並學習 Rails 程式設計!

在分析了所有受訓者編寫的程式碼之後,我們總結了50個最常見的錯誤!更糟糕的是,每個小組所犯的錯誤與前一組的錯誤幾乎一模一樣。

以下是 Rails 新手常常忽略或做錯的地方。我們還包含了“對“,”錯”兩個版本的程式碼樣本,使得教程更為清楚。

1、他們不使用自動生成的方法

############
## WRONG ##
############  

if course.visible    
  # do something
  end
  
##############
##  RIGHT   ##
##############  

if course.visible?    
  # do something
  end

通常,Rails 和許多 gems 會為它們使用的物件新增一些有用的幫助方法。例如,Rails 會自動為布林欄位新增宣告。通常,這些方法的名字是以問號結尾的。請牢記這一點!

2、他們不知道“N+1”查詢來自何處

#############
##  WRONG  ##
#############  

 @homeworks = lesson.homeworks
  • @homeworks.each do |homework|
    %p homework.user.email

    #############
    ##  RIGHT  ##
    #############  
    

    @homeworks = lesson.homeworks.includes(:user)

  • @homeworks.each do |homework|
    %p homework.user.email

瞭解 ORM 如何與資料庫互動是非常重要的。但是,新手往往沒有這種瞭解。因此,他們很少使用 “includes”、“preload” 與 “eager_load” 這類方法,並且對 “bullet” gem 一無所知。

在第一個例子中,N+1 查詢會傳遞至資料庫。”N” 是已經完成的家庭作業數量。查詢數量可能是10、20甚至100。而在第二個例子中,只有2個查詢!

3、他們不用 scopes(域)

############
## WRONG  ##
############  

def index    
  @lessons = Сourse.lessons.order(position: :asc)  
end

############
##  RIGHT ##
############  

class Lesson < ActiveRecord::Base
    belongs_to :course

    scope :by_position, -> { order(position: :asc) }  
 end  
 
 def index    
   @lessons = course.lessons.by_position  
 end

Scopes 允許你隱藏資料庫的實現,並將程式碼唯一化(uniqualize)。而且,程式碼的可讀性也會大幅提升,因為他們透露了開發者的意圖,而非資料庫的結構。

4、他們不瞭解 “after_create” 與 “after_commit” 間的差別

模型的資料,包括其在 “after_create” 中的新 ID,可以從內部,而非外部進行讀取,原因是交易尚未完成。

如果我在資料庫中建立了一條記錄,之後打算將其 ID 放入 redis 或任意的儲存中,會得到以下結果:

  • 如果 ID 在交易完成之前使用,“after_create” 可能會導致無效資料。

  • 藉助 “Sidekiq” 或其他任意後臺工作,我總是可以使用 “after_commit” 確保資料的完整性。

5、他們總是使用 ORM

#############
##  WRONG  ##
#############

  Article.all.each { |article| article.delete }

  Article.all.map { |article| article.title }

  Course.all.select { |course| course.created_at < 5.years.ago }.each { |course| course.articles.delete_all }
  
  #############
  ##  RIGHT  ##
  #############

  Article.delete_all

  Article.pluck(:title)

  old_courses_ids = Course.where(‘created_at < ?’, 5.years.ago’).pluck(:id)
  Article.where(course_id: old_courses_ids).delete_all

儘管使用物件無疑非常方便,但整個過程卻非常緩慢,而且需要很多記憶體。新手們可能並不理解程式碼的工作原理,以及如何提高其效率。

6、他們不瞭解 “dependent destroy” 與 “delete_all” 的區別

在被移除之前,“dependent destroy” 會選擇所有受限記錄,建立其物件,並呼叫各自的毀滅方法。此方法允許你移除所有受限資料。但是,當涉及大量資料時,這種方法就不管用了。

至於 “dependent delete_all”,它會通過一條 SQL 查詢移除自己。它效率很高,但是,在這種情況下,你得自己考慮資料庫的完整性。

7、他們不用帶 bang 的方法

#############
##  WRONG  ##
#############

  class Article
    validates :body, length: { minimum: 200 }  
  end

  articles_data.each do |article_data|
    Article.create(article_data)  
  end
    
#############
##  RIGHT  ##
#############  

# There are 2 possible solutions

  articles_data.each do |article_data|
    Article.create!(article_data)  
  end  
  
  # In this case a developer will be able to see that data he was not expencting to receive will get on the input

  articles_data.each do |article_data|
    article = Article.new(article_data)

    unless article.save
      puts ‘Can not save article’      
      #process this situation    
     end   
   end  
# Give a user a choice.

根據協議,將 bang(!) 新增至方法名的情況有如下兩種:

  • 如果某個方法修改了其訪問的物件

  • 如果某個方法在執行失敗後丟擲了異常

新手們常常忽略第二種情況。如果程式碼出了問題,你必須儘快找到問題根源。例如,如果完全不處理將記錄儲存至資料庫的結果,最好還是丟擲異常以找到哪段程式碼處理了無效資料。

在上例中,如果一個無效的物品傳給輸入,就會被忽視。

8、他們不在遷移中設定預設欄位

#############
##  WRONG  ##
#############  

class Article
    after_initialize :set_default_status    
    
    def set_default_status      
      self.status = ‘pending’    
        end  
      end

      
#############
##  RIGHT  ##
#############  

class MyMigration    
   def up
      change_column :articles, status, :string, default: ‘pending’    
    end    
    
  def down
      change_column :articles, status, :string    
    end  
  end

如果欄位中的某個模型必須要有一個預設值,應該通過資料庫進行安裝。

9、他們不在遷移中設定限制條件

#############
##  WRONG  ##
#############  

class MyMigration    
  def change
      add_column :profiles, user_id, :integer    
    end  
  end
  
  
#############
##  RIGHT  ##
#############  

class MyMigration    
def change
      add_column :profiles, user_id, :integer, null: false   
   end  
 end

對於基礎架構的限制條件越多,我們的應用就會越可靠。此外,別忘記 “null:false”,使用者不可以沒有簡介。

10、他們不在遷移中寫反向遷移

如果不能回滾,遷移的意義在哪兒?

以上是新手們最常犯的 Ruby on Rails 錯誤的第一部分,如果喜歡本文,請記得分享哦。

未完待續……

本文系 OneAPM 工程師編譯整理。OneAPM 能為您提供端到端的 Ruby 應用效能解決方案,我們支援所有常見的 Ruby 框架及應用伺服器,助您快速發現系統瓶頸,定位異常根本原因。分鐘級部署,即刻體驗,Ruby 監控從來沒有如此簡單。想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格

本文轉自 OneAPM 官方部落格

原文地址:http://jetruby.com/expertise/common-ruby-rails-mistakes-beginners-make-model-database/

相關文章