簡體   English   中英

返回復雜對象或使用引用/輸出參數是更好的做法嗎?

[英]is it better practice to return a complex object or use reference/out parameters?

我正在組合一個應該評估輸入的方法,如果滿足所有條件則返回true,如果某些測試失敗則返回false。 如果出現故障,我還希望呼叫者可以獲得某種狀態消息。

我遇到的設計包括返回bool並使用out(或ref)參數作為消息,返回具有bool和string屬性的(特別設計的)類的實例,或者甚至返回指示pass或特定的enum的枚舉錯誤。 什么是從方法中獲取所有信息的最佳方法? 這些中的任何一個“好”嗎? 有沒有人有其他建議?

我通常會嘗試返回一個復雜的對象,並在必要時回退到使用out參數。

但是你看一下.NET轉換中的TryParse方法,它們遵循返回bool的模式和轉換后的值的out參數。 所以,我不認為有參數是不好的 - 這實際上取決於你想要做什么。

我更喜歡直接返回類型,因為某些.NET語言可能不支持ref和out參數。

以下是MS對原因的一個很好的解釋

返回對象更易讀,占用的代碼更少。 沒有性能差異,除了你自己跳過“輸出參數”所需的箍。

我想這取決於你對好的定義。

對我來說,我更喜歡用out參數返回bool。 我認為它更具可讀性。 對於那個(在某些情況下更好)是Enum。 作為個人選擇,我傾向於不喜歡僅為消息數據返回課程; 它根據我的口味抽象出我正在做的一個級別太遠了。

Int32.Parse()Int32.TryParse()很容易表示這種類型的東西。 要在失敗時返回狀態,要么返回一個值,如果它不需要你能夠返回兩個不同的類型,那么在TryParse()輸出out參數。 返回一個專門的對象(在我看來)只是用不必要的類型混淆你的命名空間。 當然,您也可以隨時在其中拋出異常。 就個人而言,我更喜歡TryParse()方法,因為您可以檢查狀態而不必生成異常,這很重。

我強烈建議您返回一個對象(復雜或其他),因為您將來可能會發現需要返回其他信息,如果您使用簡單返回類型和一個或兩個引用的組合,您將會堅持添加額外的參數,這將不必要弄亂您的方法簽名。

如果您使用對象,則可以輕松修改它,以支持您需要的其他信息。

我會遠離枚舉,除非它非常簡單,並且將來很可能不會改變。 從長遠來看,你會有更少的麻煩,如果你返回一個物體,事情就會變得更簡單。 如果你給返回'對象'他們自己的命名空間,那么它會使命名空間混亂的論點是一個弱點,但是對於他們自己的命名空間也是如此。

(注意,如果你想使用枚舉,只需將它放在你的返回對象中,這將為你提供簡單的枚舉器,以及一個能夠滿足任何需要的對象的靈活性。)

編輯:我在頂部添加了我的觀點摘要,以便於閱讀:

  • 如果您的方法返回邏輯上屬於同一“事物”的多個數據,那么無論您是將該對象作為返回值還是輸出參數返回,都必須將它們組成一個復雜對象。

  • 如果您的方法除了數據之外還需要返回某種狀態(成功/失敗/更新了多少條記錄/等),那么請考慮將數據作為輸出參數返回並使用返回值返回狀態

此問題適用的情況有兩種變體:

  1. 需要返回包含多個屬性的數據

  2. 需要返回數據以及用於獲取該數據的操作的狀態

對於#1,我的觀點是,如果你有由多個屬性組成的數據全部組合在一起,那么它們應該作為一個類或結構組成一個單獨的類型,並且一個對象應該作為一個返回值返回方法或作為輸出參數。

對於#2,我認為這是輸出參數真正有意義的情況。 從概念上講,方法通常執行一個動作; 在這種情況下,我更喜歡使用方法的返回值來指示操作的狀態。 這可能是一個簡單的布爾值來表示成功或失敗,或者它可能是更復雜的東西,如枚舉或字符串,如果有多個可能的狀態來描述該方法執行的操作。

如果使用輸出參數,我會鼓勵一個人只使用一個(參見第1點),除非有特定的理由使用多個。 不要僅使用多個輸出參數,因為您需要返回的數據包含多個屬性。 如果您的方法的語義具體指明它,則僅使用多個輸出參數。 下面是一個例子,我認為多個輸出參數是有意義的:

    // an acceptable use of multiple output parameters
    bool DetermineWinners(IEnumerable<Player> players, out Player first, out Player second, out Player third)
    {
        // ...
    }

相反,這里有一個例子,我認為多個輸出參數沒有意義。

    // Baaaaad
    bool FindPerson(string firstName, string lastName, out int personId, out string address, out string phoneNumber)
    {
        // ...
    }

數據的屬性(personId,address和phoneNumber)應該是方法返回的Person對象的一部分。 更好的版本如下:

    // better
    bool FindPerson(string firstName, string lastName, out Person person)
    {
        // ...
    }

我更喜歡一個對象而不是parms主要是因為你可以在屬性的“Set”中更多地檢查有效值。

這在Web應用程序中經常被忽略,並且是一種基本的安全措施。

OWASP指南規定,每次分配值時都應驗證值(使用白名單驗證)。 如果您要在多個位置使用這些值,則創建一個對象或結構來保存值並檢查那里更容易,而不是每次在代碼中都有一個out parm時檢查。

這可能是主觀的。

但與往常一樣,我會說它幾乎取決於並且沒有一種“正確”的方式來做到這一點。 例如, TryGet()由字典使用圖案返回一個布爾值(其通常消耗在if馬上)和有效返回類型out 這在這種情況下非常有意義。

但是,如果您枚舉項目,您將獲得KeyValuePair<,> - 這也是有意義的,因為您可能需要將鍵和值都作為一個“包”。

在您的特定情況下,我可能會試圖實際期望bool作為結果並傳入一個可選的(允許nullICollection<ErrorMessage>實例,該實例接收錯誤(如果這足夠,則ErrorMessage可能只是String )。 這具有允許報告多個錯誤的益處。

我使用枚舉,但使用它們作為標志/位模式。 這樣我可以指出多個失敗的條件。

[Flags]
enum FailState
{
    OK = 0x1; //I do this instead of 0 so that I can differentiate between
              // an enumeration that hasn't been set to anything 
              //and a true OK status.
    FailMode1 = 0x2;
    FailMode2 = 0x4;
}

那么在我的方法中我就是這樣做的

FailState fail;

if (failCondition1)
{
    fail |= FailState.FailMode1;
}
if (failCondition2)
{
    fail |= FailState.FailMode2;
}

if (fail == 0x0)
{
    fail = FailState.OK;
}

return fail;

關於這種方法的煩人的事情是確定枚舉中是否設置了一個位是這樣的

if (FailState.FailMode1 == (FailState.FailMode1 && fail))
{
    //we know that we failed with FailMode1;
}

我使用擴展方法在我的枚舉上給自己一個IsFlagSet()方法。

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }
}

我在這里從另一個答案復制了這個代碼,但是我不知道答案在哪里...

這讓我可以說

if (fail.IsFlagSet(FailState.Ok))
{
    //we're ok
}

在這種情況下,TryParse(...)方法不是最好的例子。 TryParse僅在解析數字時發出信號,但沒有說明解析失敗的原因。 它不需要因為它可能失敗的唯一原因是“格式錯誤”。

當你想出一些沒有明顯好的方法簽名的情況時,你應該退后一步,問自己這個方法是否可能做得太多。

你正在進行什么測試? 他們有關系嗎? 是否存在由於完全不同的無關原因導致失敗的同一方法中的測試,以便向用戶/消費者提供有意義的反饋?

會重構你的代碼幫助嗎? 您是否可以邏輯地將測試分組為單獨的方法,這些方法只能由於一個原因(單一目的原則)而失敗,並相應地從原始調用點調用它們而不是您正在考慮的一種多用途方法?

失敗的測試是否只是需要記錄到某個日志文件的正常情況,還是必須以某種方式通知用戶可能會停止正常的應用程序流? 如果是后者,是否可以使用自定義異常並相應地捕獲它們?

有很多可能性,但我們需要更多關於您要做什么的信息,以便為您提供更精確的建議。

我們使用Request和Response對象執行與WCF服務非常相似的操作,並且異常一直冒泡(不在方法本身中處理)並由自定義屬性(用PostSharp編寫)處理,該屬性捕獲異常並返回'失敗的回應,例如:

[HandleException]
public Response DoSomething(Request request)
{
   ...

   return new Response { Success = true };
}

public class Response
{
   ...
   public bool Success { get; set; }
   public string StatusMessage { get; set; }
   public int? ErrorCode { get; set; }
}

public class Request
{
   ...
}

屬性中的異常處理邏輯可以使用Exception消息來設置StatusMessage,如果您的應用程序中有一組自定義異常,您甚至可以在可能的情況下設置ErrorCode。

我認為這取決於復雜程度,操作是否會失敗,導致......什么?

TryParse示例的情況下,您通常不能返回無效值(例如,沒有“無效” int這樣的東西)並且肯定不能返回空值類型(相應的Parse方法不返回Nullable<T> )。 另外,返回Nullable<T>將需要在調用站點引入Nullable<T>變量,條件檢查以查看值是否為null ,然后轉換為T類型,必須將運行時檢查復制到在返回實際的非null值之前查看該值是否為null。 將結果作為out參數傳遞並指示通過返回值成功或失敗(顯然,在這種情況下,您不關心它失敗的原因 ,只是它確實如此 )效率較低。 在以前的.Net運行時中,唯一的選擇是Parse方法,它會引發任何類型的故障。 捕獲.Net異常是昂貴的,特別是相對於返回指示狀態的true / false標志。

就您自己的界面設計而言,您需要考慮為什么需要out參數。 也許您需要從函數返回多個值,並且不能使用匿名類型。 也許您需要返回值類型,但根據輸入無法生成合適的默認值。

outref參數不是no-nos,但在使用之前,應仔細考慮您的界面是否有必要。 在超過99%的情況下,標准的引用返回應該足夠了,但我會在不必要地定義新類型之前使用outref參數,以滿足返回“單個”對象而不是多個值。

“我正在組合一個應該評估輸入的方法,如果滿足所有條件則返回true;如果某些測試失敗,則返回false。如果出現故障,我還希望調用者可以獲得某種狀態消息“。

如果您的方法具有關於輸入的條件,並且如果它們不符合,則您可以考慮拋出適當的異常。 通過拋出適當的例外,調用者將知道究竟出了什么問題。 這是一種更加可維護和可擴展的方法。 由於顯而易見的原因,返回錯誤代碼不是一個好的選擇。

“我遇到的設計包括返回bool並為消息使用out(或ref)參數,返回具有bool和string屬性的(特別設計的)類的實例,或甚至返回指示pass或a的枚舉具體錯誤。“

  1. 復雜對象是最明顯的選擇。 (類或結構)

  2. Out參數 :“考慮out參數的一種方法是它們就像方法的附加返回值。當方法返回多個值時,它們非常方便,在本例中為firstName和lastName。但是可以濫用Out參數。如果您發現自己編寫了一個包含許多參數的方法,那么您應該考慮重構代碼。一種可能的解決方案是將所有返回值打包到一個結構中。“ 太多或參考參數指向設計缺陷太多。

  3. 元組 :“在一些比一類更輕量級的結構中將一堆其他不相關的數據組合在一起”

必讀: 有關元組的更多信息

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM