Trong F #, khi bạn muốn xử lý có thể khôi phục lỗi, bạn hầu như muốn sử dụng loại option
hoặc loại Choice<_,_>
. Trong thực tế, sự khác biệt duy nhất giữa chúng là Choice
cho phép bạn trả lại một số thông tin về lỗi trong khi option
thì không. Nói cách khác, option
là tốt nhất khi nó không quan trọng cách hoặc lý do tại sao một cái gì đó không thành công (chỉ vì nó không thành công); Choice<_,_>
được sử dụng khi có thông tin về cách hoặc lý do tại sao điều gì đó không thành công là điều quan trọng. Ví dụ, bạn có thể muốn ghi thông tin lỗi vào nhật ký; hoặc có thể bạn muốn xử lý tình huống lỗi khác nhau dựa trên lý do tại sao điều gì đó không thành công - trường hợp sử dụng tuyệt vời cho việc này là cung cấp thông báo lỗi chính xác để giúp người dùng chẩn đoán sự cố.
Với ý nghĩ đó, dưới đây là cách tôi muốn Refactor mã của bạn để xử lý thất bại trong sạch, phong cách chức năng:
open System
open System.Net
/// Retrieves the content at the given URI.
let retrievePage (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."
try
// If the data is retrieved successfully, return it.
client.DownloadData uri
|> Choice1Of2
with
| :? System.Net.WebException as webExn ->
// Return the URI and WebException so they can be used to diagnose the problem.
Choice2Of2 (uri, webExn)
| _ ->
// Reraise any other exceptions -- we don't want to handle them here.
reraise()
/// Retrieves the content at the given URI.
/// If a WebException is raised when retrieving the content, the request
/// will be retried up to a specified number of times.
let rec retrievePageRetry (retryWaitTime : TimeSpan) remainingRetries (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."
elif remainingRetries = 0u then
invalidArg "remainingRetries" "The number of retries must be greater than zero (0)."
// Try to retrieve the page.
match retrievePage client uri with
| Choice1Of2 _ as result ->
// Successfully retrieved the page. Return the result.
result
| Choice2Of2 _ as error ->
// Decrement the number of retries.
let retries = remainingRetries - 1u
// If there are no retries left, return the error along with the URI
// for diagnostic purposes; otherwise, wait a bit and try again.
if retries = 0u then error
else
// NOTE : If this is modified to use 'async', you MUST
// change this to use 'Async.Sleep' here instead!
System.Threading.Thread.Sleep retryWaitTime
// Try retrieving the page again.
retrievePageRetry retryWaitTime retries client uri
[<EntryPoint>]
let main argv =
/// WebClient used for retrieving content.
use wc = new WebClient()
/// The amount of time to wait before re-attempting to fetch a page.
let retryWaitTime = TimeSpan.FromSeconds 2.0
/// The maximum number of times we'll try to fetch each page.
let maxPageRetries = 3u
/// The URI to fetch.
let fullURI = Uri ("http://www.badaddress.xyz", UriKind.Absolute)
// Fetch the page data.
match retrievePageRetry retryWaitTime maxPageRetries wc fullURI with
| Choice1Of2 pageData ->
printfn "Retrieved %u bytes from: %O" (Array.length pageData) fullURI
0 // Success
| Choice2Of2 (uri, error) ->
printfn "Unable to retrieve the content from: %O" uri
printfn "HTTP Status: (%i) %O" (int error.Status) error.Status
printfn "Message: %s" error.Message
1 // Failure
Về cơ bản, tôi chia mã của bạn ra thành hai chức năng, cộng với bản gốc main
:
- Một chức năng cố truy xuất nội dung từ URI được chỉ định.
- Một hàm chứa logic để thử lại các lần thử; điều này 'kết thúc tốt đẹp' hàm đầu tiên thực hiện các yêu cầu thực tế.
- Chức năng chính ban đầu hiện chỉ xử lý 'cài đặt' (bạn có thể dễ dàng lấy từ
app.config
hoặc web.config
) và in kết quả cuối cùng. Nói cách khác, nó không biết gì về logic thử lại - bạn có thể sửa đổi một dòng mã với câu lệnh match
và sử dụng hàm yêu cầu không thử lại thay vào đó nếu bạn muốn.
Nếu bạn muốn kéo nội dung từ nhiều URI VÀ chờ một số lượng đáng kể thời gian (ví dụ, 5 phút) giữa retries, bạn nên sửa đổi logic thử lại để sử dụng một hàng đợi ưu tiên hoặc một cái gì đó thay vì sử dụng Thread.Sleep
hay Async.Sleep
.
Plug Shameless: thư viện ExtCore chứa một số điều giúp cuộc sống của bạn dễ dàng hơn khi xây dựng một cái gì đó như thế này, đặc biệt là nếu bạn muốn làm cho nó không đồng bộ. Quan trọng nhất, nó cung cấp một luồng công việc asyncChoice
và collections functions designed to work with it.
Đối với câu hỏi của bạn về việc truyền tham số (như thời gian chờ thử lại và số lần thử lại) - Tôi không nghĩ rằng có quy tắc cứng và nhanh để quyết định có chuyển chúng hay mã hóa chúng trong chức năng. Trong hầu hết các trường hợp, tôi thích chuyển chúng vào, mặc dù nếu bạn có nhiều hơn một vài thông số để truyền vào, bạn nên tạo một bản ghi để giữ tất cả chúng và thay vào đó. Một cách tiếp cận khác mà tôi đã sử dụng là tạo các tham số option
giá trị, trong đó mặc định được lấy từ tệp cấu hình (mặc dù bạn sẽ muốn kéo chúng từ tệp một lần và gán chúng cho một số trường riêng tư để tránh phân tích cú pháp lại tệp cấu hình mỗi khi hàm của bạn được gọi); điều này giúp bạn dễ dàng sửa đổi các giá trị mặc định mà bạn đã sử dụng trong mã của mình, nhưng cũng cung cấp cho bạn tính linh hoạt khi ghi đè chúng khi cần thiết.