Dot-Net

.NET 流功能 - CanXXX 測試安全嗎?

  • May 18, 2013

.NET 中使用了一種相當常見的模式來測試類的功能。這裡我將使用 Stream 類作為範例,但該問題適用於使用此模式的所有類。

該模式是提供一個名為 CanXXX 的布爾屬性來指示該類上的能力 XXX 可用。例如,Stream 類具有 CanRead、CanWrite 和 CanSeek 屬性,以指示可以呼叫 Read、Write 和 Seek 方法。如果屬性值為 false,則呼叫相應的方法將導致拋出 NotSupportedException。

來自流類的 MSDN 文件:

根據底層數據源或儲存庫,流可能僅支持其中一些功能。應用程序可以使用 CanRead、CanWrite 和 CanSeek 屬性查詢流的功能。

CanRead 屬性的文件:

在派生類中重寫時,獲取指示目前流是否支持讀取的值。

如果從 Stream 派生的類不支持讀取,則對 Read、ReadByte 和 BeginRead 方法的呼叫將引發 NotSupportedException。

我看到很多程式碼都是這樣寫的:

if (stream.CanRead)
{
   stream.Read(…)
}

請注意,沒有同步程式碼以任何方式鎖定流對象——其他執行緒可能正在訪問它或它引用的對象。也沒有程式碼可以擷取 NotSupportedException。

MSDN 文件沒有說明屬性值不能隨時間改變。事實上,當流關閉時,CanSeek 屬性變為 false,展示了這些屬性的動態特性。因此,沒有契約保證上述程式碼片段中對 Read() 的呼叫不會引發 NotSupportedException。

我希望有很多程式碼會遇到這個潛在的問題。我想知道那些發現這個問題的人是如何解決這個問題的。什麼設計模式適合這裡?

我也很欣賞有關此模式有效性的評論(CanXXX、XXX() 對)。對我來說,至少在 Stream 類的情況下,這代表了一個試圖做太多事情的類/介面,應該分成更基本的部分。缺乏嚴格的文件化契約使得測試變得不可能,實施變得更加困難!

在不了解對象內部結構的情況下,您必須假設“標誌”屬性太不穩定,無法在多個執行緒中修改對象時依賴。

我看到這個問題更常見於只讀集合而不是流,但我覺得這是相同設計模式的另一個範例,並且適用相同的論點。

澄清一下,.NET 中的 ICollection 介面具有屬性 IsReadOnly,它旨在用作集合是否支持修改其內容的方法的指示器。就像流一樣,這個屬性可以隨時更改,並會導致 InvalidOperationException 或 NotSupportedException 被拋出。

圍繞這個的討論通常歸結為:

  • 為什麼沒有 IReadOnlyCollection 介面呢?
  • NotSupportedException 是否是個好主意。
  • 具有“模式”與不同的具體功能的優缺點。

模式很少是一件好事,因為您被迫處理不止一個“組”行為;擁有可以隨時切換模式的東西要糟糕得多,因為您的應用程序現在也必須處理多個“組”行為。然而,僅僅因為可以將某些東西分解為更謹慎的功能並不一定意味著您總是應該這樣做,尤其是在將其分解時,不會降低手頭任務的複雜性。

我個人的觀點是,你必須選擇最接近你認為你所在班級的消費者會理解的心智模型的模式。如果您是唯一的消費者,請選擇您最喜歡的型號。在 Stream 和 ICollection 的情況下,我認為對它們進行單一定義更接近於在類似系統中多年開發所建立的心智模型。當您談論流時,您談論的是文件流和記憶體流,而不是它們是否可讀或可寫。同樣,當您談論集合時,您很少以“可寫性”來提及它們。

我對此的經驗法則:總是尋找一種方法將行為分解為更具體的界面,而不是擁有操作“模式”,只要它與簡單的心理模型相得益彰。如果很難將單獨的行為視為單獨的事物,請使用基於模式的模式並非常清楚地記錄它。

好的,這是另一個嘗試,希望比我的其他答案更有用……

不幸的是,MSDN 沒有給出任何關於CanRead/ CanWrite/CanSeek可能隨時間變化的具體保證。我認為可以合理地假設,如果一個流是可讀的,它將繼續可讀,直到它關閉 - 其他屬性也是如此

在某些情況下,我認為流在以後變得可搜尋是合理的- 例如,它可能會緩衝它讀取的所有內容,直到它到達底層數據的末尾,然後允許在其中搜尋以讓客戶端重新讀取數據。但是,我認為適配器忽略這種可能性是合理的。

這應該照顧除最病態的病例之外的所有病例。(流幾乎旨在造成破壞!)將這些要求添加到現有文件中在理論上是一個突破性的變化,儘管我懷疑 99.9% 的實現已經遵守它。不過,在Connect上可能值得建議。

現在,關於是否使用“基於能力”的 API(如Stream)和基於介面的 API 之間的討論……我看到的基本問題是 .NET 不提供指定變數具有的能力作為對多個介面的實現的引用。例如,我不能寫:

public static Foo ReadFoo(IReadable & ISeekable stream)
{
}

如果它確實允許這樣做,它可能是合理的 - 但如果沒有它,你最終會出現潛在介面的爆炸式增長:

IReadable
IWritable
ISeekable
IReadWritable
IReadSeekable
IWriteSeekable
IReadWriteSeekable

我認為這比目前的情況更混亂——儘管我認為我支持在現有班級之外進行公正IReadable和補充的想法。這將使客戶更容易以聲明方式表達他們的需求。IWritable``Stream

誠然,使用Code Contracts,API可以聲明它們提供的內容和需要的內容:

public Stream OpenForReading(string name)
{
   Contract.Ensures(Contract.Result<Stream>().CanRead);

   ...
}

public void ReadFrom(Stream stream)
{
   Contract.Requires(stream.CanRead);

   ...
}

我不知道靜態檢查器對此有多大幫助 - 或者它如何應對流在關閉時確實變得不可讀/不可寫的事實。

引用自:https://stackoverflow.com/questions/1210875