64

假设有一个创建用户的操作。如果指定的电子邮件或用户名存在,此操作可能会失败。如果失败,则需要确切知道原因。在我看来,有三种方法可以做到这一点,我想知道是否有明显的赢家。

所以,这是一个类用户:

class User
{
    public string Email { get; set; }
    public string UserName { get; set; }
}

并且有 3 种方式来完成创建操作:

测试创建

if (UserExists(user)) act on user exists error;
if (UsernameExists(user)) act on username exists error;
CreateUser(user);

UserExists 和 UsernameExists 向数据库服务器发出请求以进行验证。这些调用在 CreateUser 中再次重复,以确保正确使用 API。如果验证失败,我在这两种情况下都会抛出 ArgumentOutOfRangeException。因此,性能受到了影响。

尝试创建

enum CreateUserResultCode
{
    Success,
    UserAlreadyExists,
    UsernameAlreadyExists
}

if (!TryCreate(user, out resultCode))
{
    switch(resultCode)
    {
        case UserAlreadyExists: act on user exists error;
        case UsernameAlreadyExists: act on username exists error;
    }
}

这种模式只进行一次验证,但我们求助于使用所谓的错误代码,这不被认为是一种好的做法。

创造捕获

try
{
    CreateUser(user);
}
catch(UserExistsException)
{
    act on user exists error;
}
catch(UsernameExistsException)
{
    act on username exists error;
}

我在这里不使用错误代码,但我现在必须为每种情况创建一个单独的异常类。或多或少应该如何使用异常,但我想知道创建一个单独的异常而不是枚举条目是否值得。

那么,我们有明确的赢家还是更多的是品味问题?

4

4 回答 4

80

那么,我们有明确的赢家还是更多的是品味问题?

第一个选项有一个基本缺陷——如果CreateUser依赖外部资源,它永远不会是线程安全的或安全的,并且其他实现可能会在您的测试之间创建。一般来说,我倾向于避免这种“模式”。

至于其他两个选项 - 这真的归结为失败是否发生。如果CreateUser预计会在正常情况下失败,Try* 模式是我的首选,因为使用异常本质上变成了使用异常进行控制流。

如果失败真的是一个例外情况,那么例外情况会更容易理解。

于 2013-04-26T17:19:08.953 回答
37

Test-Create 会导致竞争条件,所以这不是一个好主意。它也可能做额外的工作。

如果您希望错误成为正常代码流的一部分(例如在用户输入的情况下),Try-Create 是很好的选择。

如果错误确实异常(因此您不必担心性能),则 Create-Catch 很好。

于 2013-04-26T17:19:04.140 回答
9

这有点主观,但有一些具体的利弊值得指出。

测试创建方法的一个缺点是竞争条件。如果两个客户端几乎同时尝试创建同一个用户,则它们可能都通过测试,然后尝试创建同一个用户。

在 Try-Create 和 Create-Catch 之间,我更喜欢 Create-Catch,但这是个人品味。有人可能会争辩说 Create-Catch 使用异常来进行流控制,这通常是不受欢迎的。另一方面,Try-Create 需要一个有点尴尬的output参数,这可能更容易被忽略。

所以,我更喜欢 Create-Catch,但这里肯定有争论的余地。

于 2013-04-26T17:20:52.190 回答
4

您指定了这一点,UserExists并且UsernameExists都进行了数据库调用。我假设这CreateUser也会进行数据库调用。

为什么不让数据库处理您的线程问题?进一步假设在您的表上设置了适当的约束,您可以让它在存储的过程调用中出错。

因此,我将投票给Create-Catch

于 2013-04-26T17:32:54.410 回答