Какой стиль возврата мне следует использовать?

StackOverflow https://stackoverflow.com/questions/957065

  •  12-09-2019
  •  | 
  •  

Вопрос

Это связано с соглашениями, используемыми в C#.

У меня есть метод с двумя параметрами (координаты X и Y).Эти координаты представляют позицию, в которой может находиться «плитка».Если плитка находится по этим координатам, метод возвращает ее номер.Если в этих координатах нет плитки, мне интересно, как должен вести себя метод.

Я вижу три варианта:

  1. Используйте исключения.Я могу вызывать исключение каждый раз, когда метод не находит плитку.Однако, поскольку такая ситуация не является редкостью, этот вариант является худшим.
  2. Сделайте это по старинке в C++ и верните -1, если плитки нет.
  3. Сделайте номер плитки ссылочным параметром и измените тип возвращаемого значения метода на логический, чтобы показать, есть плитка или нет.Но мне это кажется немного сложным.

И что же мне делать?

Это было полезно?

Решение

Возврат -1.

Это не просто соглашение C++, оно также распространено в .NET Framework, например.такие методы, как String.IndexOf, или свойства, такие как SelectedIndex, для элементов управления, представляющих списки.

РЕДАКТИРОВАТЬ

Чтобы уточнить, из трех вариантов в вашем вопросе (Исключение, возврат -1, параметр out) возврат -1 - это путь.Исключения предназначены для исключительных ситуаций, и рекомендации Microsoft по программированию рекомендуют по возможности избегать использования out-параметров.

На мой взгляд, возврат -1 (при условии, что оно всегда будет недопустимым значением), возврат целого числа, допускающего значение NULL, или возврат объекта Tile — все это приемлемые решения, и вам следует выбрать то, что наиболее соответствует остальной части вашего приложения.Я не могу себе представить, что у какого-либо разработчика возникнут хоть малейшие трудности с чем-либо из следующего:

int tileNumber = GetTile(x,y);
if  (tileNumber != -1)
{
   ... use tileNumber ...
}


int? result = GetTile(x,y);
if (result.HasValue)
{
    int tileNumber = result.Value; 
   ... use tileNumber ...
}


Tile tile = GetTile(x,y);
if (tile != null)
{
   ... use tile ...
}

Я не уверен, что понимаю комментарий Питера Рудермана о том, что использование int «гораздо более эффективно, чем возврат типа, допускающего значение NULL».Я думал, что любая разница будет незначительной.

Другие советы

Вы можете вернуть значение null и проверить это в вызывающем коде.

Конечно, вам придется использовать тип, допускающий значение NULL:

int? i = YourMethodHere(x, y);

Исключения предназначены для исключительных случаев, поэтому использование исключений в известен и ожидал ситуация с ошибкой «плохая».Кроме того, теперь у вас больше шансов везде использовать try-catch для обработки этой ошибки, потому что вы ожидаете, что такая ошибка произойдет.

Превращение возвращаемого значения в параметр приемлемо, если единственное условие ошибки (скажем, -1) можно спутать с реальным значением.Если у вас может быть отрицательное число плиток, то это лучший способ.

Обнуляемое int является возможной альтернативой ссылочному параметру, но вы создаете объекты с его помощью, поэтому, если «ошибка» является рутинной, вы можете выполнять больше работы таким образом, чем ссылочный параметр.Как отметил Роман в комментарии в другом месте, у вас будет C# vs.Проблемы с VB тип, допускающий значение NULL был представлен слишком поздно, чтобы VB мог предоставить хороший синтаксический сахар, подобный C#.

Если ваши плитки могут быть только неотрицательными, то возврат -1 является приемлемым и традиционным способом указать на ошибку.Это также будет наименее затратно с точки зрения производительности и памяти.


Еще следует учитывать самодокументацию.Использование -1 и исключения является соглашением:вам придется написать документацию, чтобы убедиться, что разработчик знает о них.Используя int? return или ссылочный параметр лучше описывают себя и не будут требовать документация, позволяющая разработчику знать, как действовать в случае возникновения ошибки.Конечно :) вы всегда должны писать документацию, точно так же, как вы должны ежедневно чистить зубы ниткой.

Используйте возвращаемое значение, допускающее значение NULL.

int? GetTile(int x, int y) {
   if (...)
      return SomeValue;
   else
      return null;
}

Это самое ясное решение.

Если ваш метод имеет доступ к базовым объектам плитки, другой возможностью будет возврат самого объекта плитки или значение null, если такой плитки нет.

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

Также подумайте о string.IndexOf() метод.Если ничего не найдено, возвращается -1.Я бы последовал этому примеру.

Вы можете вернуть -1, поскольку это довольно распространенный подход в C#.Однако, возможно, было бы лучше вернуть плитку, по которой щелкнули, а в случае, если ни одна плитка не была нажата, вернуть ссылку на одноэлементный экземпляр NullTile.Преимущество такого подхода заключается в том, что вы придаете конкретное значение каждому возвращаемому значению, а не просто числу, которое не имеет внутреннего значения, кроме своего числового значения.Тип NullTile имеет очень конкретное значение, не оставляя сомнений другим читателям вашего кода.

Лучшие варианты — также вернуть логическое значение или вернуть ноль.

например

bool TryGetTile(int x, int y, out int tile);

или,

int? GetTile(int x, int y);

Есть несколько причин отдать предпочтение шаблону TryGetValue.Во-первых, он возвращает логическое значение, поэтому клиентский код невероятно прост, например:if (TryGetValue(out someVal)) { /* некоторый код */ }.Сравните это с клиентским кодом, который требует жестко запрограммированных сравнений контрольных значений (с -1, 0, нулем, перехватом определенного набора исключений и т. д.). В таких конструкциях быстро возникают «магические числа», и вынесение на учет тесной связи становится рутинная работа.

Если ожидаются контрольные значения, значения NULL или исключения, крайне важно проверить документацию, в которой используется механизм.Если документация не существует или недоступна (это распространенный сценарий), вам придется сделать вывод на основе других доказательств: если вы сделаете неправильный выбор, вы просто настраиваете себя на исключение с нулевой ссылкой или другие серьезные дефекты.Принимая во внимание, что шаблон TryGetValue() довольно близок к самодокументированию только по его имени и сигнатуре метода.

У меня есть свое мнение по заданному вами вопросу, но оно изложено выше, и я проголосовал соответственно.

Что касается вопроса, который вы не задали, или, по крайней мере, как дополнение ко всем ответам выше:Я бы позаботился о том, чтобы решение подобных ситуаций было единообразным во всем приложении.Другими словами, какой бы ответ вы ни выбрали, сохраните его в приложении.

Если метод является частью библиотеки низкого уровня, то ваш стандартный дизайн .NET, вероятно, требует, чтобы вы создавали исключения из вашего метода.

Именно так обычно работает платформа .NET.Вызывающие абоненты более высокого уровня должны перехватывать ваши исключения.

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

Я бы разбил его на два метода.Имейте что-то вроде CheckTileExists(x,y) и GetTile(x,y).Первый возвращает логическое значение, указывающее, есть ли плитка по заданным координатам.Второй метод, по сути, тот, о котором вы говорите в исходном сообщении, за исключением того, что он должен выдавать исключение при задании неверных координат (поскольку это указывает на то, что вызывающий абонент не вызывал сначала CheckTileExists(), так что это действительно исключительная ситуация.Ради скорости вы, вероятно, захотите, чтобы эти два метода использовали общий кеш, чтобы в случае их вызова один за другим накладные расходы на GetTile() функция будет незначительной.Я не знаю, есть ли у вас уже подходящий объект для размещения этих методов или, возможно, вам следует сделать их двумя методами в новом классе.ИМХО, снижение производительности при таком подходе незначительно, а увеличение ясности кода намного перевешивает его.

Возможно ли, что вы создали (или могли бы создать) Tile объект, на который ссылаются координаты?Если да, вы можете вернуть ссылку на эту плитку или null если по заданным координатам нет тайла:

public Tile GetTile(int x, int y) {
    if (!TileExists(x, y)) 
        return null;
    // ... tile lookup here...
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top