Pull to refresh

Comments 41

Разбирался как-то с реализацией parseDouble() в OpenJDK. Код на удивление запутанный и объемный. Зато корректный, наверное.
Я, кажется, из-за этой уязвимости туда и полез. Прочел эту самую заметку и заинтересовался.
Я согласен, что метод работает не совсем корректно. Однако, использовать parseDouble для проверки формата входящих данных и построение логики на основе Exceptions может быть не самой лучшей идеей.
В общем-то, я с вами согласен. В свою защиту могу сказать, что код был написан до того момента, как он перешел ко мне. К счастью, случай с данным багом заставил меня поменять реализацию.
И как вы теперь проверяете если это не число?
Я нашел баг под конец рабочего дня, поэтому временно заменил его таким решением, не знаю насколько оно производительно:

ParsePosition parsePosition = new ParsePosition(0);
NumberFormat.getInstance().parse(stringToCheck, parsePosition);
boolean isNumber = parsePosition.getIndex() == stringToCheck.length();


Другие варианты:
— написать regexp;
— использовать NumberUtils.isNumber() из Apache Commons Lang
А пройтись в for по каждой букве и вызвать Character.isDigit(буква); не пробывали?
Если хоть один false выходим и сразу возвращаем false.
Ну вообще-то, эту самую «e» никто не отменял. Причем она должна встретиться не более одного раза. А после нее возможен знак. И в начале числа еще возможен знак. А еще точка может быть. Причем только одна, причем до буквы «e». А еще, я, возможно, что-то упустил или забыл… Что-то, что учли разработчики NumberUtils.isNumber().

Я, кстати, сам большой ветеран велосипедостроения. ;)
Примерно так:

public class Validation {

    public static boolean isNumeric(String value) {

        for (Character c : value.toCharArray()){
            if (!Character.isDigit(c)) {
                return false;
            }
        }

        return true;
    }

}
Это не универсально. Может быть упомянутая в статье экспоненциальная форма записи — там есть буква e. Опять же, в даблах есть разделитель (точка) и он должен быть строго 1. Зачем создавать проблемы на пустом месте и городить велосипеды, если можно воспользоваться готовыми решениями?
UFO landed and left these words here
А действительно, почему это не хорошая идея? Если распарсилось — значит число, что нам и нужно (за исключением таких багов, конечно).
Это не очень эстетично:) Ну а если серьёзно, то от этого страдает производительность. Всё-таки обработка исключения.
Вот здесь, на stackoverflow, например, есть пример, где прогоняют тесты с исключениями и без и смотрят разницу
Логика за эксепшенах — медленно и сложнее читается.
Exception-ы изначально разрабатывались для того, чтобы дать приложению шанс не рухнуть и не потечь в случае каких-либо совсем неожиданностей — ну типа там аппаратный сбой, или там out of memory, или какой-нибудь database gone, которые могут приключиться в совершенно любом месте кода. Соответственно, реализуются они так, чтобы [почти] не добавлять накладных расходов в том случае, если эти самые исключения не происходят. Ну, т.е., никто не проверяет после вызова каждой функции, а не вылетело ли OutOfMemoryException вместо нормально результата. Вместо этого, если исключение таки выброшено, вызывается довольно мудрёный обработчик, который крутит стэк, прибивает недопроинициализированные объекты, которые ещё нельзя убивать штатным образом, и делает прочую чёрную магию. Но — за дорого.
Поэтому считается, что использовать исключения там, где вы ожидаете вероятные ошибки, нельзя — типа, слишком дорого по ресурсам.
С другой стороны, сегодня в большинстве софта основной источник «ожидаемых» ошибок — ввод пользователя, а там экономить на спичках смысла нет.
Поэтому концепция правильного использования исключений стала забываться.
не только питон, и не только GUI

blogs.msdn.com/b/oldnewthing/archive/2004/12/15/313250.aspx

… Intel representatives asked, «So if you could ask for only one thing to be made faster, what would it be?»
Without hesitation, one of the Microsoft lead kernel developers replied, «Speed up faulting on an invalid instruction.»

It so happens that on the 80386 chip of that era, the fastest way to get from V86-mode into kernel mode was to execute an invalid instruction! Consequently, Windows/386 used an invalid instruction as its syscall trap.

Я не знаю как там в Java, но exception это такое состояние программы, когда она просто не знает что делать дальше. Совершенно очевидно, что если парсеру double подсунули строку «преведмедвед», то возникает именно такая ситуация и совершенно правильно, что он выкидывает исключение. А какие ещё у него пути об этом сообщить? То что у Java тяжелые эксепшены, это не проблема исключений как инструмента. Выше правильно заметили, что на питоне писать try перед любым обработчиком это хорошо и правильно, не говоря об Erlang, например, где логика только на этом и строится.
Очевидно же — самый быстрый и во многих случаях самый грамотный способ сообщить об ошибке для double.parse — вернуть NaN (специальное аппаратное значение float/double, сообщающее 'not a number').
Вовсе не очевидно! NaN должен возвращаться в случае, когда передали строку «nan», а не когда произошла ошибка.
Нет, NaN надо вернуть когда на ноль поделили, например. То есть когда случилась математически неопределенная ситуация.
А мне кажется, что всё зависит от ситуации. Само слово exception указывает на то, что речь идет об исключительной ситуации. В Java бывают 2 типа исключительных ситуаций — ситуации «непреодолимой силы» (то есть unhandled) и ситуации, которые можно (и нужно) обработать. Мы сейчас обсуждаем вторые. И они как раз и предназначены для того, чтобы строить на них логику. Но, конечно, не в любом случае.

1. Если мы берем нечто и оно с вероятностью 50% либо окажется числом, либо нет, то, конечно, тут parseDouble и ловля исключения — не лучшая идея.
2. Но если мы, скажем, читаем колонку чисел, среди которых может случиться ошибка, то exception — лучшее решение.

В целом, думаю, правило должно выглядеть так: исключения целесообразны в том случае, если
1. некоторый класс ситуаций маловероятен и его можно назвать «нештатным»
2. если несколько сходных ситуаций обрабатываются в обход основного алгоритма и сходным образом
ИМХО, если уж и кидать этот NumberFormatException, то он должен быть checked. Я, например, ожидаю, что если не распарсилось — то возвращается 0, как в каком-нибудь JS — это вполне логично, но кидать целое исключение, которое с достаточно большой вероятностью может уронить всё приложение, всего лишь из-за того, что не удалось распарсить число — это реально перебор.
Да, пусть лучше приложение продолжит работу со значением из ниоткуда (а если это банковское приложение?)
Приложение, с которым непосредственно взаимодействует пользователь (а на андроиде это в 99% случаев так), должно продолжать работу в любом случае, что бы ни происходило, потому что пользователю очень неприятно, когда у него что-то вылетает. Если оно банковское — так ведь с моим вариантом проверить правильность введённых значений ещё проще. В конце концов, отсутствие unchecked-исключений, выкидываемых по мелочам там, где их ждёшь меньше всего, делает код проще.
Ага, а если число действительно было нулем? У Вас странные представления об эксепшенах. Если он выбросился, это не значит, что программа перестала работать и пользователю «очень неприятно».
Вы не обижайтесь, но у вас джава скрипт головного мозга. Возвращать 0 когда число не распарсилось это жесть — почему не 100, или 146?
Так получилось. На вход приходят различные объекты в виде строк, нужно понять число это или нет.
Была у меня как-то похожая надобность. Так вот, в процессе поиска информации, встречались «умельцы», разбрающие проходящую строку посимвольно, с целью определения — число это или нет…
Ужасно! Вежь куда быстрее и эффективнее преобразовать строку целиком в число, да ещё и словить по пути исключение!
Я думаю, что проблема с подходом выше — с запятыми, точками, знаками плюса/минуса, экспоненты и чего_там_еще_может_быть. А проверялись только циферки.
Конкретно этот баг они уже починили. Там в комментах написано, что в >= ICS баг не повторяется и StringIndexOutOfBoundsException не выбрасывается, а выбрасывается корректный NumberFormatException. Так что нужно делать новый.
Sign up to leave a comment.

Articles