лучше ли возвращать сложный объект или использовать параметры ссылки / выхода? - PullRequest
17 голосов
/ 28 июля 2010

Я собираю метод, который должен оценивать входные данные и возвращать true, если выполняются все условия, или false, если какой-то тест не пройден. Я также хотел бы, чтобы в случае сбоя вызывающему абоненту было доступно какое-то сообщение о состоянии.

Проекты, с которыми я сталкивался, включают возвращение bool и использование параметра out (или ref) для сообщения, возвращение экземпляра (специально разработанного) класса со свойствами bool и string или даже возвращение перечисления, указывающего проход или конкретная ошибка. Каков наилучший способ получить всю информацию из метода? Есть ли что-нибудь из этого "хорошего"? У кого-нибудь есть другие рекомендации?

Ответы [ 14 ]

13 голосов
/ 29 июля 2010

Я обычно пытаюсь вернуть сложный объект и возвращаюсь к использованию параметра out, когда это необходимо.

Но вы смотрите на TryParse методы в преобразованиях .NET, и они следуют шаблону возврата bool и параметра out преобразованного значения.Поэтому я не думаю, что плохо иметь параметры - это действительно зависит от того, что вы пытаетесь сделать.

4 голосов
/ 29 июля 2010

Я предпочитаю возвращать тип напрямую, так как некоторые языки .NET могут не поддерживать параметры ref и out.

Вот хорошее объяснение от MS о том, почему.

3 голосов
/ 29 июля 2010

Возвращаемые объекты более читабельны и требуют меньше кода.Никаких различий в производительности нет, за исключением вашего собственного, когда вы перепрыгиваете через пялец, которые требуют параметры out.

2 голосов
/ 29 июля 2010

РЕДАКТИРОВАТЬ: я добавляю резюме моей точки в верхней части, для удобства чтения:

  • Если ваш метод возвращает несколько частей данных, которые логически являются частью одной и той же «вещи», то определенно объедините их в сложный объект независимо от того, возвращаете ли вы этот объект в качестве возвращаемого значения или результата параметр.

  • Если вашему методу необходимо вернуть какой-либо статус (успех / неудача / количество обновленных записей / и т. Д.) В дополнение к данным, рассмотрите возможность возврата данных в качестве выходного параметра и использования возвращаемого значения для возврата статус

Существует два варианта ситуаций, к которым относится этот вопрос:

  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)
    {
        // ...
    }
2 голосов
/ 29 июля 2010

Я бы настоятельно рекомендовал вернуть объект (сложный или иной), потому что, несомненно, в будущем вы можете узнать, что вам нужно возвращать дополнительную информацию, и если вы использовали комбинацию простого типа возврата с одной или двумя ссылками out,вы будете застревать в добавлении дополнительных параметров, которые будут ненужно загромождать сигнатуру вашего метода.

Если вы используете объект, вы можете легко изменить его в будущем, чтобы получить дополнительную информацию, которая вам нужна.

Я бы держался подальше от перечислений, если это не ОЧЕНЬ упрощенно и вряд ли изменится в будущем.В долгосрочной перспективе у вас будет меньше головной боли, и все будет проще, если вы вернете объект.Аргумент, что он будет загромождать пространство имен, является слабым, если вы даете возвращаемым «объектам» свое собственное пространство имен, но также каждому свое собственное.

(Обратите внимание, если вы хотите использовать перечисление, просто введитечто внутри вашего возвращаемого объекта это даст вам простоту перечислителей в сочетании с гибкостью объекта, способного на все, что вам нужно.)

2 голосов
/ 29 июля 2010

Этот тип вещей легко представлен Int32.Parse() и Int32.TryParse().Чтобы вернуть состояние в случае сбоя или значение, если оно не требуется, вы должны иметь возможность возвращать два разных типа, следовательно, параметр out в TryParse()Возврат специализированного объекта (на мой взгляд) просто загромождает пространство имен ненужными типами.Конечно, вы всегда можете выдать исключение, добавив в него свое сообщение.Лично я предпочитаю метод TryParse (), потому что вы можете проверить состояние без необходимости генерировать исключение, которое довольно тяжело.

2 голосов
/ 29 июля 2010

Полагаю, это зависит от вашего определения good.

Я бы предпочел вернуть bool с параметром out.Я думаю, что это более читабельно.Причиной тому (и лучше в некоторых случаях) является Enum.Как личный выбор, мне не нравится возвращать класс только для данных сообщения;он абстрагирует то, что я делаю, только на один уровень слишком далеко для моих вкусов.

1 голос
/ 29 июля 2010

Я использую перечисления, но использую их как флаг / битовый шаблон. Таким образом, я могу указать несколько неудачных условий.

[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;
    }
}

Я скопировал этот код из другого ответа здесь на SO, но я понятия не имею, где этот ответ находится в данный момент ...

Это позволяет мне просто сказать

if (fail.IsFlagSet(FailState.Ok))
{
    //we're ok
}
1 голос
/ 29 июля 2010

Это может быть субъективно.

Но, как всегда, я бы сказал, что это в значительной степени зависит и что не существует одного "правильного" способа сделать это.Например, шаблон TryGet(), используемый словарями, возвращает bool (который часто используется в if сразу) и эффективный тип возврата как out.Это имеет смысл в этом сценарии.

Однако, если вы перечислите элементы, которые вы получите KeyValuePair<,> - это также имеет смысл, поскольку вам может понадобиться ключ и значение как один «пакет».

В вашем конкретном случае у меня может возникнуть соблазн на самом деле ожидать bool в качестве результата и передать необязательный (null разрешенный) ICollection<ErrorMessage> экземпляр, который получает ошибки (ErrorMessage может быть просто Stringа также если этого достаточно).Преимущество этого состоит в том, что можно сообщать о нескольких ошибках.

1 голос
/ 29 июля 2010

Я предпочитаю, чтобы объект выходил за пределы, главным образом потому, что вы можете больше проверять действительные значения в «Set» для свойств.

Это часто упускается из виду в веб-приложениях и является основной мерой безопасности.

В рекомендациях OWASP говорится, что значения должны проверяться (с использованием проверки в белом списке) каждый раз, когда они присваиваются.Если вы собираетесь использовать эти значения более чем в одном месте, гораздо проще создать объект или структуру для хранения значений, и проверять их, а не проверять каждый раз, когда у вас есть выходной параметр в коде.

...