代码之家  ›  专栏  ›  技术社区  ›  lincolnk

返回复杂对象或使用引用/输出参数是更好的做法吗?

  •  17
  • lincolnk  · 技术社区  · 14 年前

    我正在整理一个方法,它应该评估输入,如果满足所有条件,则返回true;如果某些测试失败,则返回false。如果有故障的话,我还想给来电者提供一些状态消息。

    我遇到的设计包括返回bool并为消息使用out(或ref)参数,返回具有bool和string属性的(特别设计的)类实例,甚至返回指示pass或特定错误的枚举。从方法中获取所有信息的最佳方法是什么?这些“好”吗?有人有其他建议吗?

    14 回复  |  直到 14 年前
        1
  •  13
  •   David Hoerster    14 年前

    我通常尝试返回一个复杂的对象,并在需要时返回到使用out参数。

    但是你看 TryParse 方法在.NET的转换中,它们遵循返回已转换值的bool和out参数的模式。所以,我认为没有参数是不坏的——这取决于你想做什么。

        2
  •  4
  •   ConsultUtah    14 年前

    我更喜欢直接返回类型,因为某些.NET语言可能不支持ref和out参数。

    这里是一个 good explanation 从女士那里得知原因。

        3
  •  3
  •   Byron Sommardahl    14 年前

    返回对象的可读性更强,代码占用更少。没有性能上的差异,除了你自己的,而你在跳篮球“出参数”的要求。

        4
  •  2
  •   AllenG    14 年前

    我想这取决于你对善的定义。

    对于我来说,我更喜欢用out参数返回bool。我觉得它更可读。seoncd到那个(在某些情况下更好)是枚举。作为一个个人选择,我不喜欢仅仅返回一个类来获取消息数据;它抽象了我正在做的事情,这对于我的口味来说太远了。

        5
  •  2
  •   Scott M.    14 年前

    这种类型的东西很容易用 Int32.Parse() Int32.TryParse() . 如果失败则返回状态,如果不需要返回值,则返回两种不同的类型,因此 TryParse() . 返回一个专门的对象(在我看来)只是用不必要的类型使您的名称空间变得混乱。当然,您也可以在消息中抛出异常。就我个人而言,我更喜欢typarse()方法,因为您可以检查状态,而不必生成异常,这很重。

        6
  •  2
  •   Capital G    14 年前

    我强烈建议您返回一个对象(复杂或其他),因为毫无疑问,您将来可能会发现需要返回附加信息,并且如果您使用简单返回类型和一个或两个out引用的组合,您将被困于添加附加out参数,这将不必要地扰乱您的me。签名。

    如果你使用一个对象,你可以很容易地修改它来支持你需要的额外信息。

    我会远离枚举,除非它非常简单,将来也不会很可能改变。从长远来看,如果你返回一个对象,你的头痛会减少,事情也会简单一些。如果返回“objects”它们自己的名称空间,但同时返回它们各自的名称空间,那么将使名称空间变得杂乱的论点是很弱的。

    (注意,如果要使用枚举,只需将其放在返回的对象中,这将使枚举器的简单性与对象的灵活性相结合,从而能够满足您的需要。)

        7
  •  2
  •   Dr. Wily's Apprentice    14 年前

    编辑:为了便于阅读,我在顶部添加了我的观点摘要:

    • 如果您的方法返回多个逻辑上都属于同一“事物”的数据块,那么一定要将它们组合成一个复杂的对象,而不管您是将该对象作为返回值还是输出参数返回。

    • 如果您的方法除了返回数据之外还需要返回某种状态(成功/失败/更新了多少记录等),那么考虑将数据作为输出参数返回,并使用返回值返回状态。

    此问题适用的情况有两种变化:

    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)
        {
            // ...
        }
    
        8
  •  1
  •   Lucero    14 年前

    这可能是主观的。

    但和往常一样,我会说,这在很大程度上取决于,没有一种“正确”的方法可以做到。例如, TryGet() 字典使用的模式返回bool(bool通常在 if 以及有效返回类型 out . 在这种情况下,这是完全合理的。

    但是,如果你列举你得到的项目 KeyValuePair<,> -这也很有意义,因为您可能同时需要密钥和值作为一个“包”。

    在你的具体案例中,我可能会想 bool 作为结果并传入一个可选( null 允许的) ICollection<ErrorMessage> 接收错误的实例( ErrorMessage 可能只是 String 如果足够的话)。这样做的好处是允许报告多个错误。

        9
  •  1
  •   Tim Coker    14 年前

    我使用枚举,但使用它们作为标志/位模式。这样我就可以指出多个失败的情况。

    [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
    }
    
        10
  •  1
  •   David    14 年前

    我更喜欢对象而不是参数,主要是因为您可以在属性的“set”中进行更多的有效值检查。

    这在Web应用程序中经常被忽视,是一种基本的安全措施。

    OWASP准则规定,每次分配值时,都应该对其进行验证(使用白名单验证)。如果要在多个地方使用这些值,那么创建一个对象或结构来保存这些值并在其中进行检查要容易得多,而不是每次代码中有out parm时都进行检查。

        11
  •  0
  •   oReally    14 年前

    在这种情况下,typarse(…)方法不是最好的例子。t仅当对数字进行分析或不进行分析时发出信号,但它没有指定分析失败的原因。它不需要这样做,因为它失败的唯一原因是“格式错误”。

    当你想到没有明显好的方法签名的情况下,你应该后退一步,问问自己这个方法是否做得太多。

    你在进行什么测试?它们有关系吗?是否有相同方法的测试会因完全不同的不相关原因而失败,您必须跟踪这些原因才能向用户/消费者提供有意义的反馈?

    重构代码会有帮助吗?您是否可以将测试逻辑地分组为只能由于一个原因(单用途原则)而失败的单独方法,并从原始调用网站中相应地调用它们,而不是您正在考虑的一个多用途方法?

    失败的测试是只需要记录到某个日志文件中的正常情况,还是必须以某种间接的方式通知用户可能会停止应用程序的正常流?如果是后者,是否可以使用自定义异常并相应地捕获它们?

    有很多可能,但我们需要更多的信息,关于你想做什么,以便给你更准确的建议。

        12
  •  0
  •   theburningmonk    14 年前

    我们使用请求和响应对象对我们的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
    {
       ...
    }
    

    属性中的异常处理逻辑可以使用异常消息来设置状态消息,如果您的应用程序中有一组自定义异常,您甚至可以在可能的情况下设置错误代码。

        13
  •  0
  •   Nathan Ernst    14 年前

    我认为这取决于复杂程度,以及操作是否会失败,从而导致……什么?

    在这种情况下 TryParse 例如,您通常不能返回无效值(例如,没有“invalid”之类的东西 int )并且绝对不能返回空值类型(对应的 Parse 方法不返回 Nullable<T> )另外,退货 可以为空<t> 需要引入 可以为空<t> 变量在调用站点,通过条件检查查看值是否为 null 然后是一个演员 T 类型,在返回该实际非空值之前,必须复制运行时检查以查看该值是否为空。如果将结果作为 out 参数并通过返回值指示成功或失败(显然,在这种情况下,您不关心 为什么? 它失败了,只是 )在以前的.NET运行时中,唯一的选择是 解析 方法,引发任何类型的失败。捕获.NET异常非常昂贵,特别是相对于返回 true / false 指示状态的标志。

    在您自己的接口设计方面,您需要考虑 为什么? 您需要一个out参数。可能需要从函数返回多个值,并且不能使用匿名类型。也许您需要返回值类型,但不能根据输入生成合适的默认值。

    外面的 ref 参数不是no,但是在使用它们之前,应该仔细考虑您的接口是否需要它们。在超过99%的情况下,引用的标准返回应该是足够的,但我会考虑使用 外面的 裁判 在不必要地定义新类型之前,参数只满足返回“单个”对象而不是多个值。

        14
  •  0
  •   Community Keith    7 年前

    “我正在整理一种方法, 应该评估输入和 如果满足所有条件,则返回true 如果某些测试失败,则返回false。我也会 喜欢拥有某种地位 如果 失败了。”

    如果您的方法有关于输入的条件,并且不满足这些条件,那么您可以非常考虑抛出适当的异常。通过抛出适当的异常,调用者将知道到底出了什么问题。这是一种更易于维护和扩展的方法。由于明显的原因,返回错误代码不是一个好的选择。

    我遇到的设计包括返回bool并为消息使用out(或ref)参数,返回具有bool和string属性的(专门设计的)类实例,甚至返回一个指示pass或特定错误的枚举。

    1. 当然,复杂对象是最明显的选择。(等级或结构)

    2. Out parameter :“考虑out参数的一种方法是,它们类似于方法的附加返回值。在本例中,当一个方法返回多个值firstname和lastname时,它们非常方便。但是,out参数可能会被滥用。作为一种良好的编程风格,如果您发现自己编写了一个带有许多out参数的方法,那么您应该考虑重构代码。一种可能的解决方案是将所有返回值打包成一个结构。” 过多的out或ref参数指向设计缺陷。

    3. Tuple :“在某些结构中,将一组不相关的数据组合在一起,这些数据比类更轻。”

    必须阅读: More on tuples :