Sau quãng thời gian dài làm việc với Ruby on Rails ở nhiều dự án khác nhau, mình đã đúc kết được một số kinh nghiệm coding mà nếu áp dụng theo, nó có thể giúp cho code của bạn dễ đọc và dễ dàng bảo trì hơn. ## Naming Convension ### Readable and meaningfull Việc rút gọn tên biến có thể làm cho đoạn code trở nên ngắn gọn nhứng bộ não của chúng ta lại dễ dàng ghi nhớ những từ có nghĩa hơn. Vì vậy, nếu bạn đặt tên biến là một từ vô nghĩa thì khi đọc code, bạn sẽ có cảm giác không thoải mái và khó hiểu: **Bad:** ```ruby posts.each_with_object([]) do |p, r| next if p.draft? r << p.as_json.merge(user_name: p.user.name) end ``` **Good** ```ruby posts.each_with_object([]) do |post, results| next if post.draft? results << post.as_json.merge(user_name: post.user.name) end ``` ### Ignore current context Không nên thêm current context khi đặt tên cho variables, methods vì nó không cần thiết. **Bad:** ```ruby class User def initialize user_name @user_name = user_name end def user_valid? user_name.present? end private attr_reader :user_name end user = User.new("Lam") user.user_valid? ``` **Good:** ```ruby class User def initialize name @name = name end def valid? name.present? end private attr_reader :name end user = User.new("Lam") user.valid? ``` ### Not be too detailed Cái gì quá cũng không tốt và việc đặt tên methods hay class cũng vậy: **Bad:** ```ruby class UpdateUserSalaryAtEndOfMonth # Logic update salary at end of month end ``` Chỉ cần nhìn vào tên class chúng ta đã có thể biết được nội dung bên trong class đó là gì: * Action update user salary * Thời điểm update là ngày cuối tháng Nhưng có một vấn đề đó là action update user salary có thể sẽ không thay đổi nhưng thời gian update thì hoàn toàn có thể. Đến lúc đó, tên class sẽ phải thay đổi theo để phù hợp với yêu cầu và như thế thì không tốt chút nào. Vì vậy, tên của methods hay class chỉ nên chứa đựng những thông tin ít thay đổi của biến hay class đó và không cần quá chi tiết: **Good:** ```ruby class UpdateUserSalary # Logic update salary at specific time end ``` ### Not contains values Mục đích của việc sử dụng constant là để khi thay đổi, ta không phải tìm và sửa ở nhiều nơi, chứ không phải là để dùng constant đó ở nhiều nơi. **Bad:** ```ruby PER_PAGE_20 = 20 MINUTES_PER_HOUR = 60 ``` Hãy tưởng tượng, sẽ thế nào nếu một ngày bạn muốn thay đổi giá trị per page lên thành 30 ở những nơi đang dùng `PER_PAGE_20`? **Good:** ```ruby DEFAULT_PER_PAGE = 20 COOKIE_EXPIRATION_IN_MINUTES = 60 ``` ### Use by context not by value Một trường hợp khác mà chúng ta thường mắc phải: **Bad:** ```ruby class User < ApplicationRecord PERMITTED_ATTRS = %i(name email status_id) end ``` ```ruby class UsersController < ApplicationController def index render json: @users.as_json(User::PERMITTED_ATTRS) end def create @user = User.create(user_params) end private def user_params params.require(:user).permit(User::PERMITTED_ATTRS) end end ``` Mọi thứ sẽ hoạt động bình thường nếu dữ liệu trả về ở `index` giống với các thông tin `permit` khi tạo user. Nhưng rõ ràng, khi chỉ muốn thay đổi logic tạo user, việc sửa `User::PERMITTED_ATTRS` tự nhiên sẽ làm ảnh hưởng đến cả `index` mặc dù chúng không hề liên quan đến nhau. **Good:** ```ruby class User < ApplicationRecord PERMITTED_ATTRS = %i(name email status_id) JSON_ATTRS = %i(name email status_id) end ``` ```ruby class UsersController < ApplicationController def index render json: @users.as_json(User::JSON_ATTRS) end def create @user = User.create(user_params) end private def user_params params.require(:user).permit(User::PERMITTED_ATTRS) end end ``` ## Methods ### Use keyword arguments Method chứa càng ít tham số đầu vào thì càng tốt. Tuy nhiên điều này không phải lúc nào cũng thực hiện được. Vì thế, trong trường hợp method có chứa nhiều tham số, hoặc có những tham số là optional, hãy sử dụng keyword arguments. **Bad:** ```ruby def send_mail email, cc = [], reply_to = [] mail_to(email, cc, reply_to) end send_mail("example@mail.com", [], ["reply@mail.com"]) ``` **Good:** ```ruby def send_mail email, cc: [], reply_to: [] mail_to(email, cc, reply_to) end send_mail("example@mail.com", reply_to: ["reply@mail.com"]) ``` ### Raise error if needed Nếu method nhận tham số được truyền từ client và nó có thể là bất cứ giá trị nào, thì việc kiểm tra để tránh các ngoại lệ không mong muốn là cần thiết: **Good:** ```ruby def role_name_for role_id return "" if [:normal, :member].exclude?(role_id) User.send("role_#{role_id}_text") end role_name_for(params[:role_id]) ``` Tuy nhiên, với những tham số được truyền vào bởi chính lập trình viên thì việc kiểm tra là không cần thiết. Nếu có lỗi thì đó có thể được xem là lỗi typo và method cũng nên raise lỗi trong trường hợp này. **Bad:** ```ruby def role_name_for role_id return "" if [:normal, :member].exclude?(role_id) User.send("role_#{role_id}_text") end role_name_for(:normal) ``` **Good:** ```ruby def role_name_for role_id User.send("role_#{role_id}_text") end role_name_for(:normal) ``` ### Define method in order Thông thường, chúng ta sẽ đọc code từ trên xuống dưới, vì vậy methods cũng nên được đặt theo thứ tự này để dễ dàng folow hơn. **Bad:** ```ruby def perform save_user send_mail_confirm end def send_mail_confirm end def build_user end def save_user build_user validate_user! end def validate_user! end ``` **Good:** ```ruby def perform save_user send_mail_confirm end def save_user build_user validate_user! end def send_mail_confirm end def build_user end def validate_user! end ``` ### Explicit instance variables initialization Instance variables có thể được khởi tạo và sử dụng ở bất cứ đâu trong class. Vì vậy, nếu không được khai báo rõ ràng, sẽ rất khó để có thể theo dõi chúng khi chạy code: **Bad:** ```ruby def save validate! @user.save end def validate! @user = User.new(user_params) raise("User invalid") if @user.invalid? end def user_params end ``` Khi đọc method `save` chúng ta hoàn toàn không biết `@user` được tạo ra ở đâu và khi nào điều này làm cho đoạn code trở nên không rõ ràng. **Good:** ```ruby def initialize @user = User.new(user_params) end def perform validate! save end def validate! raise("User invalid!") if @user.invalid? end def save @user.save end def user_params end ``` ## Conclusion Vừa rồi là một số tips viết code Ruby tốt hơn. Hi vọng bài viết sẽ hữu ích với bạn.