Tốt hơn để sử dụng đơn nguyên lỗi với xác nhận trong các chức năng đơn âm của bạn, hoặc thực hiện đơn nguyên của riêng bạn với xác thực trực tiếp trong liên kết của bạn?


9

Tôi đang tự hỏi những gì thiết kế tốt hơn khôn ngoan cho khả năng sử dụng / bảo trì, và những gì tốt hơn khi phù hợp với cộng đồng.

Cho mô hình dữ liệu:

type Name = String

data Amount = Out | Some | Enough | Plenty deriving (Show, Eq)
data Container = Container Name deriving (Show, Eq)
data Category = Category Name deriving (Show, Eq)
data Store = Store Name [Category] deriving (Show, Eq)
data Item = Item Name Container Category Amount Store deriving Show
instance Eq (Item) where
  (==) i1 i2 = (getItemName i1) == (getItemName i2)

data User = User Name [Container] [Category] [Store] [Item] deriving Show
instance Eq (User) where
  (==) u1 u2 = (getName u1) == (getName u2)

Tôi có thể triển khai các chức năng đơn âm để chuyển đổi Người dùng chẳng hạn bằng cách thêm các mục hoặc cửa hàng, v.v., nhưng tôi có thể kết thúc với một người dùng không hợp lệ để các chức năng đơn nguyên đó cần xác thực người dùng họ nhận và tạo.

Vì vậy, tôi chỉ nên:

  • bọc nó trong một đơn nguyên lỗi và làm cho các hàm đơn nguyên thực hiện xác nhận
  • bọc nó trong một đơn vị lỗi và làm cho người tiêu dùng liên kết một chức năng xác thực đơn nguyên theo trình tự đưa ra phản hồi lỗi thích hợp (để họ có thể chọn không xác thực và mang theo một đối tượng người dùng không hợp lệ)
  • thực sự xây dựng nó thành một cá thể liên kết trên Người dùng một cách hiệu quả tạo ra loại đơn lỗi của riêng tôi thực hiện xác thực với mọi liên kết tự động

Tôi có thể thấy những mặt tích cực và tiêu cực đối với mỗi trong số 3 cách tiếp cận nhưng muốn biết những gì thường được thực hiện cho kịch bản này bởi cộng đồng.

Vì vậy, trong thuật ngữ mã giống như, tùy chọn 1:

addStore s (User n1 c1 c2 s1 i1) = validate $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

Lựa chọn 2:

addStore s (User n1 c1 c2 s1 i1) = Right $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ Right someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"] >>= validate
-- in this choice, the validation could be pushed off to last possible moment (like inside updateUsersTable before db gets updated)

tùy chọn 3:

data ValidUser u = ValidUser u | InvalidUser u
instance Monad ValidUser where
    (>>=) (ValidUser u) f = case return u of (ValidUser x) -> return f x; (InvalidUser y) -> return y
    (>>=) (InvalidUser u) f = InvalidUser u
    return u = validate u

addStore (Store s, User u, ValidUser vu) => s -> u -> vu
addStore s (User n1 c1 c2 s1 i1) = return $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someValidUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

Câu trả lời:


5

Nắm tay tôi tự hỏi: Có Usermột lỗi mã không hợp lệ hoặc một tình huống thường có thể xảy ra (ví dụ như ai đó nhập sai dữ liệu vào ứng dụng của bạn). Nếu đó là một lỗi, tôi sẽ cố gắng đảm bảo rằng nó không bao giờ có thể xảy ra (như sử dụng các hàm tạo thông minh hoặc tạo các loại tinh vi hơn).

Nếu đó là một kịch bản hợp lệ thì một số xử lý lỗi trong thời gian chạy là phù hợp. Sau đó, tôi muốn hỏi: nó thực sự có ý nghĩa gì đối với tôi rằng một Userkhông hợp lệ ?

  1. Có nghĩa là một không hợp lệ Usercó thể làm cho một số mã thất bại? Các phần của mã của bạn dựa trên thực tế là a Userluôn hợp lệ?
  2. Hay nó chỉ có nghĩa là sự không nhất quán của nó cần được sửa chữa sau đó, nhưng không phá vỡ bất cứ điều gì trong quá trình tính toán?

Nếu là 1., tôi chắc chắn sẽ sử dụng một số loại lỗi (theo tiêu chuẩn hoặc của riêng bạn), nếu không, bạn sẽ mất đảm bảo rằng mã của bạn hoạt động đúng.

Tạo đơn nguyên của riêng bạn hoặc sử dụng một đống máy biến áp đơn nguyên là một vấn đề khác, có lẽ điều này sẽ hữu ích: Đã có ai từng gặp phải Monad Transformer trong tự nhiên chưa? .


Cập nhật: Nhìn vào các tùy chọn mở rộng của bạn:

  1. Trông như là cách tốt nhất để đi. Có lẽ, để thực sự an toàn, tôi muốn ẩn hàm tạo Uservà thay vào đó chỉ xuất một vài hàm không cho phép tạo một cá thể không hợp lệ. Bằng cách này, bạn sẽ chắc chắn rằng bất cứ khi nào nó xảy ra, nó sẽ được xử lý đúng cách. Ví dụ, một hàm chung để tạo một Usercó thể giống như

    user :: ... -> Either YourErrorType User
    -- more generic:
    user :: (MonadError YourErrorType m) ... -> m User
    -- Or if you actually don't need to differentiate errors:
    user :: ... -> Maybe User
    -- or more generic:
    user :: (MonadPlus m) ... -> m User
    -- etc.
    

    Ví dụ Map, nhiều thư viện có một cách tương tự Sethoặc Seqẩn việc triển khai cơ bản để không thể tạo ra một cấu trúc không tuân theo các bất biến của chúng.

  2. Nếu bạn trì hoãn việc xác thực đến cùng và sử dụng Right ...ở mọi nơi, bạn sẽ không cần một đơn vị nữa. Bạn chỉ có thể thực hiện các tính toán thuần túy và giải quyết bất kỳ lỗi nào có thể xảy ra ở cuối. IMHO phương pháp này rất rủi ro, vì giá trị người dùng không hợp lệ có thể dẫn đến việc có dữ liệu không hợp lệ ở nơi khác, vì bạn đã không dừng tính toán sớm. Và, nếu có một số phương pháp khác cập nhật người dùng để nó hợp lệ trở lại, bạn sẽ có dữ liệu không hợp lệ ở đâu đó và thậm chí không biết về nó.

  3. Có một số vấn đề ở đây.

    • Điều quan trọng nhất là một đơn nguyên phải chấp nhận bất kỳ tham số loại nào, không chỉ User. Vì vậy, bạn validatesẽ phải có loại u -> ValidUser umà không có bất kỳ hạn chế trên u. Vì vậy, không thể viết một đơn nguyên như vậy để xác nhận đầu vào return, bởi vì returnphải hoàn toàn đa hình.
    • Tiếp theo, điều tôi không hiểu là bạn khớp với case return u ofđịnh nghĩa của >>=. Điểm chính của ValidUserviệc phân biệt các giá trị hợp lệ và không hợp lệ, và do đó, đơn nguyên phải đảm bảo rằng điều này luôn đúng. Vì vậy, nó có thể chỉ đơn giản là

      (>>=) (ValidUser u) f = f u
      (>>=) (InvalidUser u) f = InvalidUser u
      

    Và điều này đã trông rất giống Either.

Nói chung, tôi chỉ sử dụng một đơn nguyên tùy chỉnh nếu

  • Không có đơn nguyên hiện có cung cấp cho bạn chức năng bạn cần. Các đơn nguyên hiện có thường có nhiều chức năng hỗ trợ, và quan trọng hơn, chúng có các máy biến áp đơn nguyên để bạn có thể kết hợp chúng thành các ngăn xếp đơn nguyên.
  • Hoặc nếu bạn cần một đơn nguyên quá phức tạp để mô tả như một ngăn xếp đơn nguyên.

Hai điểm cuối cùng của bạn là vô giá và tôi đã không nghĩ về chúng! Chắc chắn là sự khôn ngoan mà tôi đang tìm kiếm, cảm ơn vì đã chia sẻ suy nghĩ của bạn, tôi chắc chắn sẽ đi với # 1!
Jimmy Hoffa

Chỉ cần buộc toàn bộ mô-đun lên đêm qua và bạn đã chết ngay. Tôi đã đưa phương thức xác thực của mình vào một số lượng nhỏ các tổ hợp chính mà tôi đã thực hiện tất cả các cập nhật mô hình và nó thực sự có ý nghĩa hơn như thế này. Tôi thực sự sẽ đi sau # 3 và bây giờ tôi thấy cách tiếp cận đó không linh hoạt, vì vậy cảm ơn rất nhiều vì đã giúp tôi thẳng tiến!
Jimmy Hoffa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.