我的主要问题是如何处理我抛出的异常?
例如:
Contract.Requires(foo != null);
使用此抛出异常在函数调用的uper级别中怎么做?
我是否应该忽略它,当我看到它知道我的设计有问题并修复它时?
但是如果我在没有Contracts的情况下进行部署会发生什么呢?我会得到一个foo == null的参数,我的逻辑不知道如何处理这样的参数。然后一切都会崩溃。
有人可以解释如何处理所有这些情景吗?
谢谢
答案 0 :(得分:18)
我的主要问题是如何处理我抛出的异常?
首先,您在技术上无法捕获这些异常。除Contract.Requires<TExc>()
之外的所有方法都会抛出一个嵌入您的程序集中的System.Diagnostics.Contracts.__ContractsRuntime.ContractException
并且是私有的。为了捕获它,你必须捕获所有异常,这是你能做的最糟糕的事情。
合同和断言是必须始终为真的条件。如果不是,则程序处于尚未设计的状态,并且您无法确定它是否可以安全地继续。您可以将合同视为语言的扩展。您不希望.NET程序让您在“特殊情况下”违反类型安全,对吗?合同也是如此。
使用此抛出异常在函数调用的uper级别中怎么做?
合同的整个想法是在调用带有合同的方法之前强制执行调用者检查。如果调用者没有检查并做错了什么 - 必须修复它。我的意思是:如果您的方法有Contract.Requires(arg != null)
,那么,如果您的null
值为{{1}},请不要调用它。
另一个问题是“你是否应该将所有合同留在已发布的位中?”从安全的位置来看,你最好保留它们。
如果你的代码不期望某些值,但它得到了它们,唯一绝对安全的决定就是暂停当前操作并出现错误。您无法确定如果忽略合同,则不会损坏数据或执行其他不良操作。当然,您需要一定程度的粒度才能让程序继续处于安全状态,而不是以大爆炸终止,尽管在某些情况下需要终止。
我是否应该忽略它,当我看到它知道我的设计有问题并修复它时?
如果您发布软件并发现由于合同失败而导致用例不起作用,即使没有合同也可能无效 - 您只是没有考虑过它并且已经考虑过做一些额外的工作来支持它。您应该担心仔细设计所有用例并执行彻底的QA以避免这种情况。合同与这些问题无关。
但如果我在没有Contracts的情况下进行部署,那么在部署中会发生什么?我将得到一个foo == null的参数,而且我的逻辑不知道如何处理这样的参数。然后一切都会崩溃。
这是签订合同的另一个原因。最好是在预先设计好的地方发生碰撞,而不是发生在你预期不会发生的地方。
或许,删除某些合同的唯一重要原因是性能:在每种方法之后检查不变量可能非常昂贵。
答案 1 :(得分:3)
代码契约允许您更精确地声明您的方法接受的参数以及它返回的内容(前置条件和后置条件)。您可以声明字符串应为非null,长度大于10,完全由大写字符等组成,而不是具有接受字符串(任何字符串)的函数。
如果调用者不遵守合同,那么这是一个错误,应该如此报告(例如应该抛出异常)。但是,在源中放置Contract.Requires()
语句不会生成任何实际的IL代码。您必须运行Code Contracts重写器来对代码进行后处理。这会将合同检查插入到最终的IL中,如果不遵守合同,这些检查将抛出异常。
您还可以使用代码合同静态检查程序来证明在整个代码中强制执行合同。如果确实如此,您可以指示重写者不要插入支票,因为您已经证明始终遵守合同。使用公共API,您无法做到这一点,因为静态检查器不知道如何调用您的代码。但是,如果您在公共API上声明了代码合同,则调用者可以使用静态检查器来验证他的代码是否正确。
因此,要回答您的问题,您应该希望您的来电者遵守您的合同。如果实际上调用者不遵守您的合同,您应该使用重写器插入检查并以受控制的方式失败。
MSDN杂志有一篇关于Code Contracts的文章,这是了解这些概念的一个很好的起点。答案 2 :(得分:2)
除了Pavel Gatilov的答案之外,请记住,你总是可以抛出前提条件的特定例外
Contract.Requires<ArgumentException>(foo != null, "foo");
这些可以由用户捕获和处理,并且可以为他们提供处理无效输入的方法,例如:通过向用户显示输入一些无效数据的警告。
答案 3 :(得分:0)
举一个具体的例子,合同传达的是包含方法的参数不应该为空(理想情况下,方法的文档也会告诉你)。在编写消费代码时,您应该认识到这一事实,如果您没有可用的非空值作为参数传递给方法,那么您不应该调用该方法。然后由消费代码决定在这种情况下该做什么。它可以采用替代执行路径或抛出异常。正如@sll所说,在这种情况下,行动的选择完全取决于应用程序需求所需的逻辑。
答案 4 :(得分:0)
我一直在寻找关于如何捕获合同条件引发的异常的解决方案。明确抛出可能发生的异常总是一个好主意。你想要捕获它们,以便你的代码不会因为大爆炸而停止;取决于验证的数据。我还使用合同进行用户输入验证。使用契约前置条件,您可以强制用户输入以符合某些要求(例如,不为空或空字符串)。第二,您可以使用契约来验证您自己的内部代码(尤其是计算),并强制执行参数输入无效以及结果计算有效。
可以捕获合同条件引发的异常;只需将调用代码置于try-catch块中,并显式捕获您的条件将抛出的异常类型。我只会通过用户输入验证来做到这一点。因为当合同条件设置为不仅验证参数而且基本代码逻辑也会抛出错误;您的代码逻辑可能有问题,而不是参数值。在这种情况下最好完全停止程序。但是如果你希望你的程序以更加可控的方式终止,你可以抓住它们。然后由验证是否可以安全地让程序继续。
我发现也可以检查事件的空引用(至少由你自己创建)。我在我自己的示例代码中使用它,它也捕获了合同抛出的错误。您需要将事件或调用事件的对象作为附加参数传递以访问事件。以下代码是我在其中一个课程中的一部分:
public delegate void Transactie(Rekening rekening);//signature for events
public Transactie RekeningUittreksel;
public Transactie NegatiefSaldo;
public void Storten(decimal bedrag,Transactie actie)
{
Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
VorigSaldo = Saldo;
Saldo += bedrag;
RekeningUittreksel(this);
}
public void Afhalen(decimal bedrag,Transactie actie,Transactie actie2)
{
Contract.Requires<NullReferenceException>(actie!=null,"\n\nNo event listeners have been added yet!\n\n");
Contract.Requires<NullReferenceException>(actie2 != null, "\n\nNo event listeners have been added yet!\n\n");
VorigSaldo = Saldo;
if (bedrag <= Saldo)
{
Saldo -= bedrag;
RekeningUittreksel(this);
}
else
{
NegatiefSaldo(this);
}
}
接下来是程序主要方法的一部分。我注释掉了添加事件侦听器的行,因此上面定义的契约规则将抛出 nullreference异常。这是如何抓住它们而不用大爆炸终止:
//mijnZichtrekening.RekeningUittreksel += pietjePek.ToonUittreksel;
//mijnZichtrekening.NegatiefSaldo += pietjePek.ToonNegatief;
try
{
mijnZichtrekening.Storten(50m, mijnZichtrekening.RekeningUittreksel);
}
catch (NullReferenceException ex)
{
Console.WriteLine(ex);
}
try
{
mijnZichtrekening.Afhalen(100m, mijnZichtrekening.RekeningUittreksel, mijnZichtrekening.NegatiefSaldo);
}
catch(NullReferenceException ex)
{
Console.WriteLine(ex);
}
我重写了一些代码,通过使用新的.NET 4.5合同缩写来对事件执行空引用检查:
public void Afhalen(decimal bedrag)
{
NegatiefSaldoHasListeners(this.RekeningUittreksel, this.NegatiefSaldo);//calls the contract abbreviator with delegate type parameters to check for Nullreference
VorigSaldo = Saldo;
if (bedrag <= Saldo)
{
Saldo -= bedrag;
RekeningUittreksel(this);
}
else
{
NegatiefSaldo(this);
}
}
public void Storten(decimal bedrag)
{
UittrekselHasListeners(this.RekeningUittreksel);//calls the contract abbreviator with a delegate type (event) parameter to check for Nullreference
VorigSaldo = Saldo;
Saldo += bedrag;
RekeningUittreksel(this);
}
public virtual void Afbeelden()
{
Console.WriteLine("Rekeningnr: {0:0000 0000 0000 0000}",Nummer);
Console.WriteLine("Saldo: {0}",Saldo);
Console.WriteLine("Creatiedatum: {0:dd-MM-yyyy}",CreatieDatum);
}
[ContractAbbreviator]
public void CheckArgs(string nummer, Klant eigenaar)
{
Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(nummer), "Geen nummer ingevuld!");
Contract.Requires<FormatException>(nummer.Trim().Length == 16,"Ongeldig aantal tekens ingevoerd!");
Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(eigenaar.ToString()), "Eigenaar niet opgegeven!");
}
[ContractAbbreviator]
public void UittrekselHasListeners(Transactie actie)
{
Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
}
[ContractAbbreviator]
public void NegatiefSaldoHasListeners(Transactie actie,Transactie actie2)
{
Contract.Requires<NullReferenceException>(actie != null, "\n\nGeen event listener toegewezen!\n\n");
Contract.Requires<NullReferenceException>(actie2 != null, "\n\nGeen event listener toegewezen!\n\n");
}