Должен ли я выбросить исключение в случае значимого значения вне диапазона или обработать его самостоятельно?

42

Я написал структуру, которая представляет координаты широты / долготы. Их значения варьируются от -180 до 180 для долгот и от 90 до -90 для широт.

Если пользователь этой структуры дает мне значение вне этого диапазона, у меня есть 2 варианта:

  1. Бросить исключение (arg вне диапазона)
  2. Преобразовать значение в ограничение

Поскольку координата -185 имеет значение (ее очень легко преобразовать в +175, поскольку это полярные координаты), я мог бы принять ее и преобразовать.

Лучше ли генерировать исключение, чтобы сообщить пользователю, что его код дал мне значение, которого не должно быть?

Редактировать: Также я знаю разницу между широтой и долготой и координатами, но я хотел упростить это для облегчения обсуждения - это была не самая яркая идея

К. Гкинис
источник
13
Разрешено ли пользователю вставлять значение вне диапазона? Если ответ отрицательный, выведите исключение. Если правила не такие строгие, выполните преобразование, но прямо укажите в документации, что преобразование может произойти. Также имейте в виду, что в некоторых языках обработка исключений довольно затратна.
Энди
C #, это имеет значение? Он изначально не поддерживает ограничения, если вы это имеете в виду.
К. Гкинис
2
Поэтому мне кажется довольно ясным, что правильный подход заключается в том, чтобы пользователь набирал что-то вне диапазона и выдает исключение, когда они это делают (если стандарт говорит, что значение abs не должно быть выше 180, то есть большее значение, чем это явное нарушение). Кстати, C # на самом деле является одним из языков, где исключения являются довольно дорогостоящими, поэтому используйте их на самом деле только в ситуациях, которые являются исключительными, а это означает, что не перехватывая его, вы сломаете свое приложение, например, такую.
Энди
2
Я стараюсь избегать предположений о том, что «имел в виду» пользователь, передавая определенные значения параметров, особенно те, которые мой код не обслуживает. Похоже, похожий случай.
JᴀʏM16
2
Координаты Web Mercator не от -180 до 180 и от -90 до 90. Это широта / долгота (и для этого есть даже несколько систем координат). Проекции Меркатора, как правило, исчисляются сотнями тысяч или миллионами и имеют единицы измерения «метров» (даже не строго, поскольку длина каждой единицы охватывает увеличение реального расстояния от земли при приближении к полюсам), а не градусов. Даже в градусах она ограничена до ± 85,051129 градусов, потому что проекция становится бесконечно широкой на полюсах. (Я отправил правку, исправляющую это, поскольку это не является суть вопроса.)
jpmc26

Ответы:

51

Если суть вашего вопроса заключается в следующем ...

Если какой-то клиентский код передает аргумент, значение которого недопустимо для вещи, которую моделирует моя структура данных, должен ли я отклонить это значение или преобразовать его во что-то разумное?

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

Вопрос в том, сталкиваешься ли ты с этим делом .

  • Если ваша структура данных предназначена для моделирования полярных координат в целом, то примите это значение, потому что углы вне диапазона -180 и +180 на самом деле не являются недопустимыми. Они совершенно верны, и у них просто всегда есть эквивалент в диапазоне от -180 до +180 (и если вы хотите преобразовать их в целевой диапазон, не стесняйтесь - клиентскому коду обычно не нужно заботиться) ,

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

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

Теодорос Чатзигианнакис
источник
12
Долготу вне диапазона от -180 до +180, вероятно, следует считать приемлемой, но широта вне диапазона от -90 до +90 может показаться бессмысленной. Как только человек достигает Северного или Южного полюса, дальнейшее путешествие на север или юг не определено.
суперкат
4
@supercat Я склонен с вами согласиться, но, поскольку я не знаю, какие значения на самом деле недопустимы в спецификации Web Mercator, я стараюсь не делать никаких твердых выводов. ОП знает проблемную область лучше, чем я.
Теодорос Чатзигианнакис
1
@supercat: начало, где меридиан достигает экватора. путешествовать по меридиану по широте. Если широта> 90, тогда продолжайте идти по тому же большому кругу. Нет проблем. Запишите это и оставьте остальное клиенту.
Кевин Клайн
4
@supercat Это хуже чем это. В проекции Web Mercator ± 90 фактически проецируется на бесконечную ширину. Таким образом, стандарт фактически обрезает его на уровне ± 85,051129. Также, lat / long! = Координаты веб-меркатора. Координаты Меркатора используют вариацию метров, а не градусов. (По мере приближения к полюсам каждый «метр» на самом деле соответствует большему и большему участку земли.) Координаты ОП чисто лат / долг. Web Mercator не имеет к ним никакого отношения, за исключением того, что они могут отображаться поверх базовой карты Web Mercator, а некоторая пространственная библиотека выступает за них.
jpmc26
1
Я должен сказать «эквивалент ± 85.051129», так как это не фактическая координата.
jpmc26
10

Это зависит от многих. Но вы должны решить что-то сделать и задокументировать .

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

В этом случае я могу видеть аргументы в любом случае. Если кто-то путешествует +10 градусов от 175 градусов, он должен в конечном итоге на -175. Если вы всегда нормализуете пользовательский ввод и, таким образом, рассматриваете 185 как эквивалентное -175, тогда клиентский код не может поступить неправильно, когда он добавляет 10 градусов; это всегда имеет правильный эффект. Если вы воспринимаете 185 как ошибку, вы заставляете каждый случай, когда клиентский код добавляет относительные градусы, вставлять в логику нормализации (или, по крайней мере, не забывать вызывать процедуру нормализации), вы на самом деле вызываетеошибки (хотя, надеюсь, легко поймать те, которые будут быстро устранены). Но если значение долготы вводится пользователем, записывается буквально в программе или вычисляется с помощью какой-либо процедуры, которая всегда должна быть в [-180, 180), то значение вне этого диапазона, скорее всего, будет указывать на ошибку, поэтому «с любовью» «Преобразование это может скрыть проблемы.

Мой идеал в этом случае, вероятно, будет определять тип, который представляет правильный домен. Используйте абстрактный тип (не позволяйте клиентскому коду просто обращаться к необработанным числам внутри него) и предоставьте как нормализующую, так и проверочную фабрику (чтобы клиент мог сделать компромисс). Но независимо от значения этого типа, значение 185 должно быть неотличимо от -175 при просмотре через ваш общедоступный API (не имеет значения, преобразованы ли они в конструкцию или вы предоставляете равенство, методы доступа и другие операции, которые каким-то образом игнорируют разницу) ,

Бен
источник
3

Если вам не важно выбрать одно решение, вы можете просто позволить пользователю принять решение.

Учитывая, что ваша структура является объектом значения только для чтения и создана методом / конструктором, вы можете предоставить две перегрузки, основанные на опциях, которые есть у пользователя:

  • Бросить исключение (arg вне диапазона)
  • Преобразовать значение в ограничение

Также никогда не позволяйте пользователю иметь недопустимую структуру для передачи другим вашим методам, сделайте это прямо при создании.

Редактировать: основываясь на комментариях, я предполагаю, что вы используете c #.

Филипе Борхес
источник
Спасибо за вклад! Я подумаю об этом, хотя боюсь, что это подорвет цель конструктора броска исключений, если можно будет просто избежать этого. Это интересно, хотя!
К. Гкинис
Если вы собираетесь использовать эту идею, было бы лучше определить интерфейс и иметь две реализации, которые генерируют или конвертируют. Было бы трудно перегрузить конструктор разумным способом, как вы описали.
2
@ Снеговик: Да, было бы трудно перегрузить конструктор с одинаковыми типами аргументов, но не было бы трудно иметь два статических метода и закрытый конструктор.
wchargin
1
@ K.Gkinis: Цель конструктора создания исключений не в том, чтобы «убедиться, что приложение умирает» - в конце концов, клиент всегда может использовать catchваши исключения. Как уже говорили другие, это позволяет клиенту ограничивать себя, если он этого хочет. Вы на самом деле ничего не обходите.
wchargin
Это предложение усложняет код без какой-либо выгоды. Либо метод должен преобразовывать неверные данные, либо нет. Наличие двух перегрузок делает код более сложным без решения дилеммы.
JacquesB
0

Это зависит от того, вводится ли пользователь напрямую через какой-либо пользовательский интерфейс или из системы.

Ввод через пользовательский интерфейс

Это вопрос пользовательского опыта, как обращаться с неверным вводом. Я не знаю о вашем конкретном случае, но в целом есть несколько вариантов:

  • Предупредить пользователя об ошибке и попросить пользователя исправить ее, прежде чем продолжить (наиболее часто встречающийся)
  • Автоматически преобразуйте в допустимый диапазон (если это возможно), но предупредите пользователя об изменении и разрешите пользователю проверить перед продолжением.
  • Бесшумно преобразовать в допустимый диапазон и продолжить.

Выбор зависит от ожиданий ваших пользователей и того, насколько важны данные. Например, Google автоматически исправляет орфографию в запросах, но это низкий риск, потому что бесполезные изменения не являются проблемой и их легко исправить (и даже на странице результатов ясно, что запрос был изменен). С другой стороны, если вы вводите координаты для ядерной ракеты, вам может потребоваться более жесткая проверка ввода и отсутствие скрытых исправлений неверных данных. Так что универсального ответа нет.

Самое главное, вы должны рассмотреть, если исправление ввода даже приносит пользу пользователю. Зачем пользователю вводить неверные данные? Легко увидеть, как кто-то может сделать орфографическую ошибку, но зачем кому-то вводить долготу -185? Если бы пользователь действительно имел в виду +175, он, вероятно, набрал бы +175. Я думаю, что, скорее всего , неверная долгота просто является ошибкой при наборе, а пользователь имел в виду -85 или что-то еще. В этом случае тихое преобразование плохо и бесполезно . Наиболее удобный подход для вашего приложения, вероятно, состоит в том, чтобы предупредить пользователя о недопустимом значении и заставить пользователя исправить его самостоятельно.

Ввод через API

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

JacquesB
источник
0

Вы должны бросить исключение.

В приведенном вами примере, отправив 185 и преобразовав его в -175, в некоторых случаях это может быть полезно для обеспечения этой функциональности. Но что, если звонящий отправит 1 миллион? Они действительно хотят преобразовать это? Кажется более вероятным, что это ошибка. Поэтому, если вам нужно сгенерировать исключение для 1 000 000, а не для 185, вам нужно принять решение о произвольном пороговом значении для исключения. Этот порог может сбить вас с толку, так как некоторые вызывающие приложения отправляют значения около порога.

Лучше выбросить исключение для значений вне диапазона.

Brendan
источник
-1

Наиболее удобный вариант для разработчика - поддержка ошибок времени компиляции в платформе для значений вне диапазона. В этом случае диапазон также должен быть частью сигнатуры метода, как и тип параметров. Точно так же, как ваш пользователь API не может передать строку, если ваша сигнатура метода определена как целочисленная , пользователь не должен был бы передавать значение без проверки, находится ли значение в диапазоне, указанном в сигнатуре метода. Если не проверено, он должен получить ошибку времени компиляции и, таким образом, можно избежать ошибки времени выполнения.

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

Кстати, мне очень нравится модель ошибки, предложенная здесь Джо Даффи .

Gulshan
источник
Как бы вы предоставили ошибки времени компиляции для пользовательского ввода?
JacquesB
@JacquesB Компилятор выдаст ошибку, если пользовательский ввод не будет проверен, чтобы быть в пределах диапазона после вставки, независимо от фактического значения, находящегося в пределах диапазона.
Гюльшан
Но тогда вам все равно придется решить, хотите ли вы отклонить ввод или преобразовать в допустимый диапазон, так что это не отвечает на исходный вопрос.
JacquesB
@JacquesB Сначала я должен сказать, что я всегда думал, что OP разрабатывает API, а пользовательский интерфейс разрабатывает кто-то другой, потребляя его API. Я был неправ по этому поводу. Я просто прочитал вопрос еще раз и понял это. Все, что я пытаюсь сказать, это то, что проверка должна проводиться у потребителей API, а если нет, компилятор должен выдавать ошибку.
Гюльшан
Итак ... вам нужно создать новый класс, который содержит входные данные и проверяет. Что произойдет, когда вы создадите объект класса из необработанных входных данных? Бросить исключение? Молча пройти? Вы просто перемещаете проблему.
Джечлин,
-1

По умолчанию должно быть выброшено исключение. Вы также можете разрешить такую ​​опцию, как strict=falseи делать принуждение на основе флага, где, конечно, strict=trueпо умолчанию. Это довольно распространено:

djechlin
источник
Это усложняет код без пользы.
JacquesB
@JacquesB выбрасывает исключение по умолчанию или разрешает strict=false?
Джечлин
@JacquesB Я добавил несколько примеров API, которые поддерживают строгие и мягкие режимы, пожалуйста, посмотрите.
Джечлин
-2

Для меня лучшая практика - никогда не менять пользовательский ввод. Подход, который я обычно использую, заключается в том, чтобы отделить валидацию от исполнения.

  • Есть простой класс, который просто использует заданные параметры.
  • Используйте декоратор, чтобы обеспечить уровень проверки, который может быть изменен по желанию, не затрагивая класс выполнения (или внедрить валидатор, если этот подход слишком сложен).
Дэвид А
источник
«Пользователь», о котором идет речь, - это разработчик, использующий API, а не конечный пользователь ...
Джей Элстон,