在本机库上的托管包装器中,我必须完成某些操作,这些操作对于高级对象的用户应该被视为原子和一致的。但是,本机代码中的基础操作是原子的并且是单独一致的,但不是整体的。
// Simplistic look at the AddRange operation
void AddRange(IEnumerable<ChildType> range)
{
foreach (var value in range)
{
this.NativeAdd(value);
}
}
// Simplistic look at the Delete operation
void Delete(ParentType value)
{
foreach (var child in value.Children)
{
this.NativeDelete(child);
}
this.NativeDelete(value);
}
如果Native代码存在错误,则所有Native操作都会失败并显示常见的异常类型:
void NativeDelete(ChildType child)
{
StatusCode status = StatusCode.NoError;
NativeMethods.DeleteChild(this.id, child.Id, out status);
if (status != StatusCode.NoError)
{
throw new LibraryException(this, child, status);
}
}
在高级AddRange和Delete例程中,我遇到了一些原生的Add或Delete调用已经完成的情况,但是其中一个调用出现了错误,其余的都没有完成。
更新:如果用户添加了7个项目并且在第7个项目上失败了,或者如果他们成功添加了6个项目,那么对于用户来说,磁盘上的实际文件看起来不会有任何不同。记录错误的唯一时间是在运行时。因此,如果用户未意识到状态不一致的可能性,他们可能无法确定基础文件是否有效。
我应该:
答案 0 :(得分:2)
3就不可能实现。如果在那里发生错误,手动回滚只会导致更多的不一致状态。
我认为2是多余的。如果在父类型上调用方法(这是这些方法公开的方法),并且在抛出该异常时发生异常,则假设对象上的状态已损坏(除少数例外) 。通常,当发生这种情况时,除了扔掉它并使用具有一致状态的新对象之外,你不能对该对象做很多事情。
留下1,记录它。虽然您可能在2中提供了大量结构化信息,但如果对象处于不一致状态,您确实不想尝试修复该状态,因为您冒更大的风险更多。
答案 1 :(得分:1)
我喜欢你的第二个选择 -
这将提供一个更有意义的异常,您可以控制它。没有理由强迫你的最终用户理解一些奇怪的状态代码等。至少这样,很明显发生了什么以及为什么。
答案 2 :(得分:1)
如果我正确理解您的更新,则调用者可以做的就是将信息传递给最终用户,最终用户必须决定是接受状态还是回滚。
在这种情况下,(2):提供所有信息,以便调用者可以将信息传递给用户,或日志文件,或一些统计数据,而无需大量工作。
(1)如果对(2)没有信息差异,则可以,例如,知道哪个项目失败是无关紧要的。
(3)如果你不能期望在大部分时间里修复它是没有意义的,如果你有一点机会让它变得更糟,那就很危险了。
根据文件大小,您可以通过处理副本来进行准原子更改。至少,用户可能期望一种简单的方法来回滚到上一次保存。