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()
方法可以显示文本。您如何看待这种技术? />
#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
。现在,当我从透视图,我认为这种方法非常复杂,需要我们同时关注异常和状态代码-在我们的主要业务模块中还不错,它应该将多个不同的子系统整合到一个流中-但在使用时非常不方便简单的场景。自己;-)
评论
您有理由比例外更喜欢它吗?我们在以前的应用程序中使用了错误代码,但很快就会一团糟!
异常是指不可预测的错误,这种情况我很容易想到。 try-catch语句会使代码混乱,使其可读性降低。异常处理在处理流控制方面往往很昂贵。
@ThunderDev,在收到答案后您无法做某些事情,请查看这些元信息以获取更多信息,关于使答案无效的编辑
out几乎总是表明您的功能正在执行过多操作。返回码也是如此。如果您在创建新商家时遇到问题,则应该抛出异常。如果商人已经存在,则您应该基于预先定义的行为(具有相同名称的商人)检查商人是否存在-或如果该功能已经存在,则应允许该功能静默失败。异常不用于流控制,它用于特殊情况-已创建的商人是程序员应注意的特殊情况。