到目前为止,这是我在业务层中所做的将错误消息返回给用户的操作。

public void CreateMerchant(MerchantCreationModel model, out MerchantCreationStatus status)
{
    //check if merchantId exists
    MerchantDTO merchant = _merchantRepo.GetMerchant(model.MerchantId);

    if (merchant != null)
    {
        status = MerchantCreationStatus.MerchantAlreadyExists;
        return;
    }

    // Rest of merchant creation logic has been removed for clarity

    status = MerchantCreationStatus.Success;
}


我的控制器中有一个ErrorCodeToString()方法可以显示文本。

您如何看待这种技术? />

评论

您有理由比例外更喜欢它吗?

我们在以前的应用程序中使用了错误代码,但很快就会一团糟!

异常是指不可预测的错误,这种情况我很容易想到。 try-catch语句会使代码混乱,使其可读性降低。异常处理在处理流控制方面往往很昂贵。

@ThunderDev,在收到答案后您无法做某些事情,请查看这些元信息以获取更多信息,关于使答案无效的编辑

out几乎总是表明您的功能正在执行过多操作。返回码也是如此。如果您在创建新商家时遇到问题,则应该抛出异常。如果商人已经存在,则您应该基于预先定义的行为(具有相同名称的商人)检查商人是否存在-或如果该功能已经存在,则应允许该功能静默失败。异常不用于流控制,它用于特殊情况-已创建的商人是程序员应注意的特殊情况。

#1 楼

实际上,状态码已经沿Dodo的方式走了---由于异常而被淘汰了。例外不能。考虑这两个示例。

状态码作为返回值

首先,为商家实现CRUD操作的“商家存储库”:

public enum MerchantStatusCode
{
    OK = 0,
    Created = 1,
    Updated = 2,
    Deleted = 3,
    MerchantAlreadyExists = 4,
    MerchantDoesNotExist = 5
}

public class MerchantRepository
{

    public MerchantStatusCode Create(Merchant model)
    {

        if (MerchantExists(model))
            return MerchantStatusCode.MerchantAlreadyExists;

        // Save to DB

        return MerchantStatusCode.Created;
    }
}


现在,使用C#代码创建商家并忽略错误处理:通过查看这些代码行,没有任何迹象表明您应该在执行后续代码行之前进行任何错误处理。

将状态代码作为输出参数

假设使用了输出参数:

var merchantRepository = new MerchantRepository();

// Create a merchant. I'm not really sure what could go wrong, and if
// something should go wrong, I'm not really sure if it's a problem.
merchantRepository.Create(merchant);


忽略状态代码的代码:

public class MerchantRepository
{
    public void Create(Merchant model, out MerchantStatusCode statusCode)
    {

        if (MerchantExists(model))
        {
            statusCode = MerchantStatusCode.MerchantAlreadyExists;
            return;
        }

        // Save to DB

        statusCode = MerchantStatusCode.Created;
    }
}


虽然这更像是代码味道,但错误代码不会停止程序执行。

现在,让我们对其进行调整以引发异常: >
var merchantRepository = new MerchantRepository();
MerchantStatusCode code;

// Create a merchant. Sure I could check the "status" but, meh. If
// something fails it must not be THAT important. After all, good
// programmers are lazy, right?
merchantRepository.Create(merchant, out code);


和C#代码以与上一个示例相同的方式使用此代码,程序员非常高兴地忽略了问题: br />执行代码和创建重复的商人时,两个示例的工作原理相同,但是这里有一个明显的区别:空的MerchantRepository.Create块。空的try-catch块超出了“代码气味”的范围,而变成了“整个下午都在阳光下烘烤的腐烂肉堆”。

现在,如果我们将示例2的代码调整为与示例1完全相同的代码:
以后的代码行都不会执行,除非有必要,否则程序员不必防备这种情况。

这里的教训:为了上帝的爱,不要盲目地捕获异常,什么也不要做,除非您在注释中记录这种行为,并提供这样做的理由。 >
关于您的特定方法,您应该做几件事:

关于数据库操作的错误处理最佳实践


要创建商家,请检查数据库以确保商家不存在。这是基本的数据验证,应在其自己的层中进行处理。
创建商人时,如果某个商人恰好存在,则抛出一个异常,该异常将迫使您(作为程序员)处理这种“异常情况” (因此称为异常)。

调用数据库操作时,它需要假定所有数据均有效,然后如果出现任何错误,则引发异常。首先,永远都不会出错,数据访问层也无法解决此问题,因此引发异常将停止任何进一步的操作。无法创建商家是一个灾难性的,无法解决的问题,需要立即停止逻辑。 。

有关错误代码和异常的有趣博客文章:

错误代码或异常?为什么可靠的软件这么难?

评论


\ $ \ begingroup \ $
“状态码的问题是可以忽略它。异常不能。”您能对此进行扩展吗?我不明白为什么用try-empty-catch来“忽略”异常比丢弃out参数值更难。消费者以任何方式了解它们
\ $ \ endgroup \ $
–本·亚伦森
2014年9月16日在17:09

\ $ \ begingroup \ $
必须创建try-catch块的原因恰恰是告诉其他程序员“这里发生了灾难性的事情,但我对此一无所知”。空的try-catch是代码的味道。
\ $ \ endgroup \ $
– Greg Burghardt
2014年9月16日17:14在

\ $ \ begingroup \ $
当然,我想这显然是一种嗅觉/不良习惯,而不是忽略一个过时的参数
\ $ \ endgroup \ $
–本·亚伦森
2014年9月16日17:17

\ $ \ begingroup \ $
+1空捕获块的怒吼-每当我看到这个时,我也会在里面死一点! :)
\ $ \ endgroup \ $
– Mathieu Guindon♦
2014-09-16 18:02

\ $ \ begingroup \ $
这不是方法的执行引发异常或返回需要停止的状态代码。需要停止调用方的执行。这就是异常工作良好的原因。您有一种例外情况可以告诉全世界,发生了无法解决的问题,必须有人处理。不处理它不是一种选择。在任何科学领域,失败总是一个选择。
\ $ \ endgroup \ $
– Greg Burghardt
2014年9月17日下午12:34

#2 楼

这里有问题。

public void CreateMerchant(MerchantCreationModel model, out MerchantCreationStatus status)
{
    //check if merchantId exists
    MerchantDTO merchant = _merchantRepo.GetMerchant(model.MerchantId);

    if (merchant != null)
    {
        status = MerchantCreationStatus.MerchantAlreadyExists;
        return;
    }

    // rest of merchant creation logic

    status = MerchantCreationStatus.Success;
}
方法

这不是CreateMerchant方法。

这是一个写得不好的MerchantExists方法

我会将其设为布尔值方法并执行类似的操作

public boolean MerchantExists(MerchantCreationModel model)
{
    if (_merchantRepo.GetMerchant(model.MerchantId) != null)
    {
        return true;
    }
    return false;
}


或者如果您喜欢像我这样的三元语句,则可以这样编写

public boolean MerchantExists(MerchantCreationModel model)
{
    return _merchantRepo.GetMerchant(model.MerchantId) != null ? true : false;
}


所以这不是我第一次使三元组显然不需要成为一个三元组,这是这样编写的代码更简单了

public boolean MerchantExists(MerchantCreationModel model)
{
    return _merchantRepo.GetMerchant(model.MerchantId) != null;
}


这将返回true或false值,具体取决于表达式是true还是false ...

Hammer说,


“嘿,看起来很有趣的指甲。我可以修复它。”方法,这样您就可以说

if (_merchantRepo.Exist(MerchantID))
{
    //Perform Tasks on Merchant... etc.
}
else
{
    //Create Merchant
}


这有助于检查数据层中的内容,例如@Greg Burghardt提示他的答案。

我只是不认为您实际上需要错误处理,您只需要定义如果_merchantRepo不存在该怎么办,所以请设法让MerchantExists可以通知其他人merchant还不存在,然后创建一个新的。

评论


\ $ \ begingroup \ $
这只是我的代码的一小部分,它还有许多其他步骤,不仅限于MerchantExists。我的错误代码正好在那里定义了商人存在或其他情况下的处理方式。
\ $ \ endgroup \ $
– ThunderDev
2014年9月17日8:00

\ $ \ begingroup \ $
我不知道为什么有人要使用? true:假三元运算符:)
\ $ \ endgroup \ $
– Nikita B
2014-09-17 9:28

\ $ \ begingroup \ $
只返回_merchantRepo.GetMerchant(model.MerchantId)!= null;足够的。
\ $ \ endgroup \ $
–user11153
2014-09-17 11:59

\ $ \ begingroup \ $
这是我第二次这样做。锤子说:“嘿,看另外一个钉子”
\ $ \ endgroup \ $
–马拉奇♦
2014年9月17日下午13:25

\ $ \ begingroup \ $
@ThunderDev,用代码讲起来很困难,因为您只给了一个方法,它承担的责任比应有的要多,而您忽略了它的实质。据我了解,是的,控制器应该说“嘿,已经有一个商人,所以我们跳过创建并使用现有的商人”或“嘿,这个{插入对象}不存在商人,让我们为它创建一个新商人。它”
\ $ \ endgroup \ $
–马拉奇♦
2014年9月17日下午15:27

#3 楼

我个人同意“状态码异常”原则。

但是,一旦有了真正深入而分散的业务逻辑(带有可插入策略等),我们决定将它们混合在一起。我们创建了一些特殊的BusinessResult类(和BusinessResult<T>),该类可以包含所有内容:结果本身,异常(如果发生),状态代码等。能够在各种执行深度上捕获并转换为通用状态代码。
如果尝试使用处于Value状态的BusinessResult<T>中的Failed,那么一个人将收到一个InvalidOperationException-因此,我们不能轻易忽略StatusCode

然后-根据收到的状态的严重性-我们能够:决定再次尝试一些操作或回滚整个事务,然后再试一次,甚至关闭整个服务或仅将其设置为空闲模式。
我们有一些重载的构造函数,隐式和显式运算符,使我们能够通过轻松简洁的代码将常见错误情况转换为特定的Result

现在,当我从透视图,我认为这种方法非常复杂,需要我们同时关注异常和状态代码-在我们的主要业务模块中还不错,它应该将多个不同的子系统整合到一个流中-但在使用时非常不方便简单的场景。自己;-)