Computer Science 中最難的兩件事情
- cache invalidation
- naming things
不過 Ruby / Rails 本身就有一些基本的慣例及約定在,只要熟悉這個規則通常都能夠寫出易懂易維護的程式碼。
就像我們呼叫 created_at
欄位時,腦袋會默認他就是 datetime
,而不會是 string
以下是一些常用的慣例,分別是
- Database column name
- Method name
- Library name
Column 欄位命名
使用過去分詞 + “at”
時間欄位請使用 Date / Datetime,嚴禁使用 String
被驗證的時間不應該使用 activated_time 而應該使用 activated_at。 因為 time 是表示時間(時間是指什麼時候還是花多久時間?),不是「什麼時候被驗證」。
example:
- submitted_at
- paid_at
- done_at
- created_at
- updated_at
使用 “is” + 形容詞
欄位請使用 Boolean
,這樣一來 Rails 可以直接使用帶有 ?
的 method
假設欄位名稱為 is_admin
屬性為 Boolean
時,可以直接呼叫 user.is_admin?
The #{attribute}? method is a method defined automatically by Active Record
example:
- is_admin
- is_activated
- is_hidden
- is_locked
- is_enterprise
Method 方法命名
使用動詞 + 名詞表示要做的事情
把該做的事情放到對的地方,不要都在 controller 做
# Bad
class UsersController < ApplicationController
def action
user.token = SecureRandom.hex(5)
user.save
end
end
應該放在 model 裡面用 method 包起來,並敘述是做什麼事
# Grate
class User < ApplicationRecord
def generate_token
update_attribute(:token, SecureRandom.hex(5))
end
end
請注意,名詞 method 不要有小動作,嚴禁偷動手腳,要操作資料務必另外寫
# Bad
class User < ApplicationRecord
def full_name
name = "#{first_name} #{last_name}"
update_column(:fullname, name)
name
end
end
method 是名詞時,就讓他回傳單純的東西就好,例如 Array, String, Integer, Object
# Great
class User < ApplicationRecord
def full_name
"#{first_name} #{last_name}"
end
end
使用 ! 結尾的 method 表示會做一些改變
- Ruby method 會預期改變原有的 object 狀態
- String 的 gsub! 與 gsub 是不同的結果與作用。
- Rails 中的 ActiveRecord method (create!, save!) 失敗會 throw exception
class Cart < ApplicationRecord
def clean!
item.delete_all
end
end
Class 命名
一律採用中性命名
Library
Library 是提供一個介面讓其他人可以呼叫
例如常用的第三方服務 OneSignal 推播通知
用 OneSignal(one_signal.rb) 是比較好的
不需要用
- OneSignalSender(one_signal_sender.rb)
- OneSignalWrapper(one_signal_wrapper.rb)
或是常用的 Telegram 機器人
用 TelegramBot(telegram_bot.rb) 是比較好的
不需要用
- TelegramBotSender(telegram_bot_sender.rb)
- TelegramBotWrapper(telegram_bot_wrapper.rb)
Wrapper
wrapper 應該是包裝了一些方法讓人呼叫
class OMDBWrapper
include Singleton
class << self
delegate :get_movie_by_title, to: :instance
end
def initialize
@omdb_client = OMDBClient.new
end
def get_movie_by_title(title)
movie_attributes = omdb_client.
get_movie(t: title).
deep_transform_keys(&:underscore)
Movie.new(movie_attributes)
end
private
attr_reader :omdb_client
end
# OMDBWrapper.get_movie_by_title('The Rock') # =>
# <OMDBWrapper::Movie:0x00007fc6f5b7b290 @runtime="136 min", @title="The Rock", @year=1996>
Sender
sender 的職責是呼叫其他的 services wrapper,像是 Rails mailer 一樣,透過 mailer 去呼叫其他外部程式
例如
module NotificationSender
def send_to_user
# code
end
def send_to_vendor
# code
end
end