+5

10 lỗi mà các lập trình viên Ruby on Rails hay mắc phải

Mở đầu

Rails được xây dựng trên nguyên tắc convention over configuration nghĩa là gần như lập trình viên đã được giảm thiểu tối ta việc tuân thủ convention khi phát triển, thay vào đó bản thân Framework đã làm thay việc đó. Ví du: nếu database của ta có bảng user thì mặc định model sẽ là User và controller sẽ là users_controller. Tuy nhiên càng đơn giản bao nhiêu thì lại càng gây chủ quan cho lập trình viên bấy nhiêu và đôi khi là có những sai lầm mà họ ko lường trước được. Đôi khi xảy ra những lỗi mà bản thân người lập trình không thể hiểu nổi cái gì đang xảy ra và vô cùng bực tức 😄. Những vấn đề đó có thể là vè bảo mật hay hiệu năng, tất nhiên tất cả những điều này ta đều phải tìm cách khắc phục nó. Bài viết này sẽ liệt kê 10 sai lầm mà lập trình viên hay mắc phải kèm theo cả cách hạn chế chúng.

1. Xử lý nhiều logic trong controller

Rails làm việc dựa trên mô hingf MVC. Chúng ta cũng hay nghe nói đến thuật ngữ fat model, skinny controller. Nghĩa là mọi business logic sẽ nằm ở Model, Controller chỉ nhận nhiệm vụ điều phối và xử lý những logic đơn giản giữa M và V là chính. Nếu đặt quá nhiều logic grong controller thì việc thay đổi sau này sẽ rất khó khăn nếu như ta cần thêm bất cứ logic nào. Thậm chí việc này còn vi phạm nguyên tắc single responsibility principle - Một class chỉ nên giữ 1 trách nhiệm duy nhất (Chỉ có thể sửa đổi class với 1 lý do duy nhất) Chúng ta chỉ nên đặt logic ở controller trong các trường hợp sau:

  • Xử lý Session và cookie
  • CHỉ định Dùng model nào
  • quản lý các params
  • Điều hướng (redirect, rende ...)

2. Xử lý nhiều logic trong view

erb hỗ trợ việc khai báo biến à xử lý logic rất tốt trong view. Tuy nhiên, nếu không cẩn thận thì bạn sẽ không thể quản lý nổi source code với những view có nhiều xử lý phức tạp. Và chắc chắn nếu đặt nhiều logic trong view thì việc tuân thủ quy tắc DRY sẽ bị phá vỡ. Ví dụ thay vì luôn luôn phải check xem có tồn tại current_user hay không để hiện thị name, nếu ko có thì hiển thị Guest:

<h3>
  Welcome,
  <% if current_user %>
    <%= current_user.name %>
  <% else %>
    Guest
  <% end %>
</h3>

thì ta hoàn toàn có thể luôn luôn set mặc định cho thuộc tính name = Guest nếu như không tồn tại current_user trong application_controller.

require 'ostruct'

helper_method :current_user

def current_user
  @current_user ||= User.find session[:user_id] if session[:user_id]
  if @current_user
    @current_user
  else
    OpenStruct.new(name: 'Guest')
  end
end

Từ đó ta có thể dụng ở bất cứ đâu trong project

<h3>Welcome, <%= current_user.name -%></h3>

2 chú ý quan trọng khi viết view trong rails:

  • Đảm bảo tính đóng gói (encapsulate ), ta cố gắng sử dụng layout và partial.
  • Sử dụng presenters/decorators để đóng gói các logic trong view thành 1 đối tượng, từ đó ta sử dụng các đối tượng này mà ko cần viét logic trong view. (Tham khảo Tại đây)

3. Xử lý nhiều logic trong model

Bên cạnh việc đặt lgic quá nhiều trong view và controller, thì nhiều lập trình viên xử lý quá nhiều logic trong ActiveRecord model dẫn đến vieejc maintain thật là kinh khủng.

  • Những function kiểu như send email, tương tác với các service bên ngoài, convert data hay những nghiêp vụ kiểu như vậy thì ko nên để trong model.

Từ nãy đến giờ, bạn ko nên code nhiều logic trong view, trong controller và cả model. Vậy ta sẽ đặt chúng ở đâu ? Câu trả lời là ta có thể sử dụng **POROs ** ( plain old Ruby objects) . Theo tiếng Việt, bạn có thể dịch sát nghĩa nó là “Đối tượng thuần Ruby”. Giải thích ngắn gọn: POROs là lớp chỉ sử dụng các kiểu dữ liệu tiêu chuẩn của Ruby. Bạn không được sử dụng các kiểu dữ liệu “ngoại lai” từ bất kì thư viện, framework nào nằm ngoài phạm vi chứa các PORO (có thể xác định là project). Như vậy, một PORO có thể sử dụng các PORO khác miễn là nó cùng phạm vi. Nói đơn giản, ta có thể khai báo các module để hạn chế logic trong model. Vậy Những logic nào ta có thể dùng trong model ?

  • ActiveRecord configuration. Ví dụ: relation, vlidate ...
  • Những logic nghiệp vụ thao tác với database như thêm sửa xoá ...
  • Querry, bạn chỉ nên sử dụng những query như where trong model mà ko nên viết trong controller hay view

4. Sử dụng helper không gọn gàng

Rails có generate 1 thư mục helper và 1 class helper với mỗi resource mà ta khai báo ( Ví dụ: model user, controller là users_controller thì helper sẽ là user_helper ). Ta sẽ viêt các function với logic vaf nghiệp vụ ko nên đặt trong view model và controller.

  • Bạn có thẻ tạo các thư mục riêng và kahi báo code trong đó để sử dụng. Tuy nhiên chú ý cân nhắc kỹ code đó sẽ xử lý những gì để đặt tên cho phù hợp

5. Sử dụng quá nhiều Gem

Ruby on rails được hỗ trợ rất nhiều từ 1 lượng vô cùng phong phú các Gem, nó giúp cho các lập trình viên xử lý các vấn đề phức tạp 1 cách rất dễ dàng thông qua việc cài gem chứ không phải thêm những logic phức tạp vào dự án. Tuy nhiên không phải lúc nào ta cũng được lạm dụng. Nhiều khi việc làm dụng dùng Gem làm cho số lượng Gem file quá lớn so với những function mà ta sử dụng từ chúng. Điều này sẽ gây ra một số vấn đề:

  • Sử dụng nhiều gem làm ứng dụng của ta nặng hơn mức cần thiết và tất nhiên ảnh hướng đến performance, ngốn ram và tất nhiên là tăng chi phí của hệ thống. Khởi đônhgj chậm, load chậm và khi chạy automation test cũng chậm.
  • Mỗi gem bạn cài thì thường sẽ cài thêm một số gem dependency. Ví dụ nếu cài gem rails_admin thì bạn đã cài thêm 11 gem đi kèm với nó.

6. Bỏ qua file log

Có thể hầu hết lập trình viên đều biết các file log được lưu trong thư mục logs, đối với từng môi trường development hay production nhưng nhiều khi họ lại chẳng mấy khi xem chúng. Rails hỗ trợ mọi thứ thật tuyệt vời. Chỉ cần khai báo relation trong model là ta có thể truy xuất dữ liệu một cách dễ dàng từ bất cứ đâu. Tuy nhiên ta lại ko biết thực sự về bản chất nó đã chạy câu lệnh SQL như thế nào. Ví dụ khi gặp phải vấn đề n+1 query thì nếu không đọc log thì ta sẽ không biết dò thế nào mà xử lý.

Ví dụ bạn cần query để hiển thị tất cả các comment của post:

def comments_for_top_three_posts
  posts = Post.limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

Và khi gọi hàm trên, log sẽ như sau

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (5.6ms)  ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]
  Comment Load (1.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 3]]
  Rendered posts/some_comments.html.erb within layouts/application (12.5ms)
Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)

nhìn vào log ta sẽ thấy có vấn đề n+1 query. Vậy từ đó ta có thể tìm đc cách khắc phục tình trạng này để tăng tốc độ app và xem lại log

def comments_for_top_three_posts
  posts = Post.includes(:comments).limit(3)
  posts.flat_map do |post|
    post.comments.to_a
  end
end

và log

Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700
Processing by PostsController#some_comments as HTML
  Post Load (0.5ms)  SELECT "posts".* FROM "posts" LIMIT 3
  Comment Load (4.4ms)  SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3)
  Rendered posts/some_comments.html.erb within layouts/application (12.2ms)
Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)

Đó chỉ là một ví dụ cho vấn đề n+1 query. Rõ ràng việc đọc log giúp ta có thể xem lại và kiểm tra xem hệ thống hoạt động có trơn tru hay ko.

7. Bỏ qua automated tests

Để đảm bảo chất lượng của dự án thì rails cung cấp khá nhiều công cụ hỗ trợ TDD và BDD test như Rspec hay Cucumber. Chúng đều rát dễ sử dụng tuy nhiên nhiều người lại bỏ qua bước quan trọng này

8. Sử dụng trực tiếp các function của bên thứ 3

Các dịch vụ bên thứ 3 thường rất dễ sử dụng và tích hợp vào ứng dụng rails tuy nhiên không phải lúc nào nó cũng chạy trơn tru và nhanh chóng hay thậm chí gặp lỗi tù phía họ. Điều này nếu không cẩn thận sẽ gây chậm ứng dụng của chúng ta hay có khi còn bị delay hoặc chết server. Để giải quyết điều này, tốt nhất ta hãy giảm sự phụ thuộc vào tốc độ của bên thứ 3. bằng cách sử dụng chúng thông qua background job. Có thể sử dụng job bằng một số cách

  • Delayed job
  • Resque
  • Sidekiq Nếu không muốn sử dụng background job thì bạn phải đảm bảo trong code đã xử lỹ error handling , và thử tắt mạng chạy local xem khi không connect được bên thứ 3 thì ứng dụng của bạn có gặp vấn đê gì không.

9. Lầm tưởng trong việc check cấu trúc dữ liệu

Rails ghi lại cấu trúc database hiện tại thông qua file db/schema.rb và nó luôn luôn đươc update mỗi khi bạn update migrate. Thậm chí nếu như không chạy migrate thì vẫn có thể update schema thông qua rake db:schema:dump. Vấn đề là đôi khi bạn chỉ check file migrate trong khi các file migrate thì thường không đầy đủ cấu trúc của bảng dữ liệu. Thay vào đó ta phải xem schema để biết đầy đủ và chính xác cấu trúc dữ liệu.

10. Đưa những thông tin quan trọng cần bảo mật lên các trình quản lý source code public

Rails cung cấp những công cụ hỗ trợ việc bảo mật rất tốt nhằm tránh việc bị tấn công bằng nhiều cách. Một trong số chúng là sử dụng app_secret để bảo vệ session của trình duyệt. Mặc dù token này được lưu trong config/secrets.yml và với môi trường production thì file này đọc thông tin từ các biến môi trường. Với phiên bản rails cũ thì nó được ghi trong config/initializers/secret_token.rb và file này cũng thường bị nhầm lẫn mà đưa lên các công cụ quản lý code như các file khác để mọi người ai cũng xem dược và điều này thì vô cùng nguy hiểm. Vì vậy cần phải để file này vào .gitignore để hạn chế người ngoài xem được và lấy thông tin.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí