2010-04-09 47 views
24

Tôi đã tự hỏi liệu mọi người có chia sẻ các phương pháp/chiến lược hay nhất của họ về xử lý ngoại lệ & lỗi hay không. Bây giờ tôi không hỏi khi nào nên ném một ngoại lệ (nó đã được trả lời một cách thô bạo ở đây: SO: When to throw an Exception). Và tôi không sử dụng điều này cho luồng ứng dụng của mình - nhưng có những trường hợp ngoại lệ hợp pháp xảy ra mọi lúc. Ví dụ, cái phổ biến nhất là ActiveRecord :: RecordNotFound. Điều gì sẽ là cách tốt nhất để xử lý nó? Cách DRY?Chiến lược tốt nhất để xử lý các ngoại lệ và lỗi trong Rails là gì?

Ngay bây giờ tôi đang thực hiện rất nhiều kiểm tra trong bộ điều khiển của mình vì vậy nếu Post.find(5) trả về Nil - tôi kiểm tra điều đó và gửi một thông báo flash. Tuy nhiên trong khi điều này rất chi tiết - nó hơi cồng kềnh theo nghĩa tôi cần kiểm tra các ngoại lệ như trong mọi bộ điều khiển, trong khi hầu hết chúng đều giống nhau và phải làm với bản ghi không tìm thấy hoặc các bản ghi liên quan không tìm thấy - là một trong hai Post.find(5) không tìm thấy hoặc nếu bạn đang cố gắng để hiển thị bình luận liên quan đến bài viết mà không tồn tại, đó sẽ ném một ngoại lệ (giống như Post.find(5).comments[0].created_at)

tôi biết bạn có thể làm một cái gì đó như thế này trong ApplicationController và ghi đè lên nó sau này trong một bộ điều khiển/phương thức cụ thể để nhận hỗ trợ chi tiết hơn, tuy nhiên đó có phải là cách thích hợp để thực hiện điều đó không?

class ApplicationController < ActionController::Base 
    rescue_from ActiveRecord::RecordInvalid do |exception| 
     render :action => (exception.record.new_record? ? :new : :edit) 
    end 
end 

Ngoài ra điều này sẽ làm việc trong trường hợp Post.find(5) không tìm thấy, nhưng những gì về Post.find(5).comments[0].created_at - Tôi có nghĩa là tôi không thể ném một ngoại lệ thổi đầy đủ nếu bài tồn tại nhưng không có ý kiến, phải không?

Để tóm tắt cho đến nay tôi đã thực hiện rất nhiều kiểm tra thủ công bằng cách sử dụng if/else/trừ khi hoặc case/when (và thỉnh thoảng tôi thú nhận bắt đầu/cứu) và kiểm tra nil? hoặc trống rỗng ?, v.v., nhưng có vẻ tốt hơn.

Trả lời:

@Milan: Hi Milan Cám ơn bài trả lời - Tôi đồng ý với những gì bạn nói, và tôi nghĩ tôi lạm dụng ngoại trừ từ. Điều tôi ngụ ý là ngay bây giờ tôi làm rất nhiều việc như:

if Post.exists?(params[:post_id]) 
    @p = Post.find(params[:post_id]) 
else 
    flash[:error] = " Can't find Blog Post" 
end 

Và tôi thực hiện rất nhiều loại "xử lý ngoại lệ", tôi cố gắng tránh sử dụng bắt đầu/cứu hộ. Nhưng có vẻ như với tôi rằng đây là một kết quả/xác minh/tình huống đủ phổ biến mà cần phải có một cách DRYer để làm điều này, phải không? Bạn sẽ làm loại séc này như thế nào?

Còn cách xử lý trong trường hợp này? Giả sử bạn muốn hiển thị ngày tạo nhận xét trong chế độ xem của bạn:

Last comment for this post at : <%= @post.comments[0].created_at %> 

Và bài đăng này không có bất kỳ nhận xét nào. Bạn có thể làm

Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %> 

Bạn có thể làm bộ kiểm soát. Vv Có một số cách để làm điều đó. Nhưng cách "tốt nhất" để xử lý điều này là gì?

Trả lời

14

Thực tế là bạn làm rất nhiều kiểm tra thủ công cho trường hợp ngoại lệ cho thấy rằng bạn không sử dụng đúng cách. Trong thực tế, không có ví dụ nào của bạn là ngoại lệ.

Đối với bài đăng không tồn tại - bạn nên mong đợi người dùng API của bạn (ví dụ: người dùng sử dụng web của bạn qua trình duyệt) để yêu cầu các bài đăng không tồn tại.

Ví dụ thứ hai của bạn (Post.find (5) .comments [0] .created_at) cũng không phải là ngoại lệ. Một số bài đăng chỉ không có nhận xét và bạn biết nó ở phía trước. Vậy tại sao điều đó lại ném một ngoại lệ?

Trường hợp tương tự với ví dụ ActiveRecord :: RecordInvalid. Không có lý do gì để xử lý vụ này bằng ngoại lệ. Người dùng nhập một số dữ liệu không hợp lệ vào một biểu mẫu là một điều khá bình thường và không có gì đặc biệt về nó.

Sử dụng cơ chế ngoại lệ cho các loại tình huống này có thể rất thuận tiện trong một số trường hợp, nhưng không chính xác vì những lý do nêu trên.

Với điều đó đã nói, điều đó không có nghĩa là bạn không thể DRY mã đóng gói các tình huống này. Có một cơ hội khá lớn mà bạn có thể làm điều đó ít nhất ở một mức độ nào đó vì đây là những tình huống khá phổ biến.

Vì vậy, ngoại lệ thì sao? Vâng, quy tắc đầu tiên thực sự là: sử dụng chúng càng thưa thớt càng tốt.

Nếu bạn thực sự cần phải sử dụng chúng có hai loại ngoại lệ nói chung (như tôi nhìn thấy nó):

  1. trường hợp ngoại lệ mà không phá vỡ quy trình làm việc chung của người dùng bên trong ứng dụng của bạn (hãy tưởng tượng một ngoại lệ bên trong hình thu nhỏ tạo hình ảnh tiểu sử của bạn) và bạn có thể ẩn chúng khỏi người dùng hoặc bạn chỉ thông báo cho anh ta về sự cố và hậu quả của nó khi cần thiết. Đây là phương sách cuối cùng và nên được xử lý thông qua lỗi máy chủ nội bộ 500 trong các ứng dụng web.

tôi có xu hướng sử dụng các phương pháp rescue_from trong ApplicationController chỉ dành cho sau này, vì có những nơi thích hợp hơn cho các loại đầu tiên và ApplicationController như trên cùng của lớp điều khiển có vẻ là đúng nơi để rơi trở lại trong hoàn cảnh như vậy (mặc dù hiện nay một số loại middleware Rack có thể là nơi thích hợp hơn để đặt một thứ như vậy).

- EDIT -

Phần xây dựng:

Đối với điều đầu tiên, lời khuyên của tôi sẽ bắt đầu sử dụng find_by_id thay vì tìm, vì nó nó không ném một ngoại lệ nhưng trả về nil nếu không thành công. Mã của bạn sẽ trông giống như sau:

unless @p = Post.find_by_id(params[:id]) 
    flash[:error] = "Can't find Blog Post" 
end 

ít trò chuyện hơn.

Một thành ngữ phổ biến khác cho DRYing loại tình huống này là sử dụng bộ điều khiển before_filters để đặt các biến thường được sử dụng (như @p trong trường hợp này).Sau đó, điều khiển của bạn có thể trông như sau

controller PostsController 
    before_filter :set_post, :only => [:create, :show, :destroy, :update] 

    def show 
     flash[:error] = "Can't find Blog Post" unless @p 
    end 

private 

    def set_post 
    @p = Post.find_by_id(params[:id]) 
    end 

end 

Đối với các tình huống thứ hai (không tồn tại chú thích cuối cùng), một giải pháp rõ ràng cho vấn đề này là để di chuyển toàn bộ điều vào một helper:

# This is just your way of finding out the time of the last comment moved into a 
# helper. I'm not saying it's the best one ;) 
def last_comment_datetime(post) 
    comments = post.comments 
    if comments.empty? 
    "No comments, yet." 
    else 
    "Last comment for this post at: #{comments.last.created_at}" 
    end 
end 

sau đó, trong quan điểm của bạn, bạn muốn chỉ cần gọi

<%= last_comment_datetime(post) %> 

Bằng cách này, trường hợp cạnh (bài mà không cần bất kỳ ý kiến) sẽ được xử lý tại chỗ riêng của nó và nó sẽ không lộn xộn xem. Tôi biết, không ai trong số này cho thấy bất kỳ mô hình nào để xử lý lỗi trong Rails, nhưng có thể với một số phép tái cấu trúc như vậy, bạn sẽ thấy rằng cần một số loại chiến lược để xử lý ngoại lệ/lỗi chỉ biến mất .

+0

Hãy - xem trả lời của tôi ở trên trong câu trả lời - Tôi không thể gửi mã đúng thụt vào trong các ý kiến ​​:-) – konung

+0

Hey Nick, xin lỗi vì nghe có vẻ rất ranty. Tôi chỉ không có đủ thời gian để thêm bất kỳ phần xây dựng nào vào câu trả lời của tôi. Bây giờ, nó ở đó. Hy vọng toàn bộ câu trả lời đó là hữu ích hơn bây giờ. –

+0

Không vấn đề gì - không có hành vi phạm tội nào, câu hỏi của tôi cũng không rõ lắm - cho đến khi tôi dọn sạch nó. Bạn đã cho tôi một vài con trỏ tốt - Tôi sẽ chờ một chút, có lẽ ai đó có thêm vài gợi ý và sau đó tôi sẽ đánh dấu câu trả lời này. Cảm ơn bạn đã trả lời chi tiết. :-) – konung

1

Trường hợp ngoại lệ dành cho các trường hợp ngoại lệ. Đầu vào người dùng xấu thường không ngoại lệ; nếu có, nó khá phổ biến. Khi bạn có một hoàn cảnh đặc biệt, bạn muốn cung cấp cho mình càng nhiều thông tin càng tốt. Theo kinh nghiệm của tôi, cách tốt nhất để làm điều đó là cải thiện tôn giáo xử lý ngoại lệ của bạn dựa trên trải nghiệm gỡ lỗi. Khi bạn gặp phải một ngoại lệ, điều đầu tiên bạn nên làm là viết một bài kiểm tra đơn vị cho nó. Điều thứ hai bạn nên làm là xác định xem có thêm thông tin có thể được thêm vào ngoại lệ hay không. Thông tin thêm trong trường hợp này thường có dạng bắt ngoại lệ cao hơn ngăn xếp và xử lý nó hoặc ném một ngoại lệ mới, có nhiều thông tin hơn có lợi ích của ngữ cảnh bổ sung. Quy tắc cá nhân của tôi là tôi không thích bắt ngoại lệ từ nhiều hơn ba cấp lên ngăn xếp. Nếu một ngoại lệ phải đi xa hơn thế, bạn cần phải bắt nó sớm hơn.

Đối với việc phơi bày lỗi trong giao diện người dùng, các câu hỏi if/case hoàn toàn OK miễn là bạn không lồng sâu chúng. Đó là khi loại mã này khó duy trì. Bạn có thể tóm tắt điều này nếu nó trở thành một vấn đề.

Ví dụ:

def flash_assert(conditional, message) 
    return true if conditional 
    flash[:error] = message 
    return false 
end 

flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return 
Các vấn đề liên quan