О тонкостях, точках и запятых
March 26th, 2010 Begemot Posted in Использование
Есть у меня модуль для проверки наличия обновлений. Скачивает с сайта тхт файлик, парсит и радует юзверя наличием информации о новой версии.
Периодически народ жаловался что у него не работает, у меня все работало, я конечно бил себя в грудь и грешил на фаерволы, в принципе основания были. Пока однажды вдруг не сломалось у меня.
Начал ковыряться, оказалось фаерволы не причем, вернее не всегда причем. Оказалось что “2.8” это не всегда 2.8, банально правда?:) Вроде бы известно, банально, но пока гром не грянет…
В общем решение вот:
bool wxString::ToCDouble(double * val) const
Variant of ToDouble() always working in "C" locale.
Works like ToDouble() but unlike it this function expects the floating point number to be formatted always with the rules dictated by the "C" locale (in particular, the decimal point must be a dot), independently from the current application-wide locale (see wxLocale).
March 26th, 2010 at 11:05 am
…плюс человеческий фактор!
У меня юзеры, бывало, писали “рупь двадцать” как “1-20”. Тут никакая локаль не поможет 😉
March 26th, 2010 at 12:28 pm
ну ты даёшь! сравнивать версии с помощью double-ов – это ещё тот изврат…
ведь код такого типа:
double x = 2.8;
if (x == 2.8) {…}
не всегда будет работать (зависит от округления).
Поэтому я бы тебе советовал бы использовать разбиение версии на целочисленные токены при помощи wxStringTokenizer.
Будет и более точно и всегда так, как ты ожидаешь.
Ну и даст возможность проверять версии, там где больше двух чисел, например, 2.8.10.1
March 26th, 2010 at 2:24 pm
Полностью поддерживаю Norfolc! Бегемот, ты извращенец!!! 🙂
March 26th, 2010 at 8:29 pm
че-то меня сильно много критиковать начали в последнее время:)
А по делу, как только у меня хоть раз случится что 2.8 не равно 2.8 или появятся версии типа 2.8.10.2 – так сразу и усовершенствую систему:)
March 28th, 2010 at 12:59 am
не-а, мы тебе даем дельные советы 🙂 потому как сами тоже наступали на такие же грабли, и прекрасно помним время, проведенное в отладчике в поисках мистических ошибок.
А что касается double, то для них строго не рекомендуют использовать конструкции вида
if ( x == 2.8 )
а только сравнение больше или меньше. Ну ежели очень хочется, то вышеуказанное можно попытаться заменить на
if ( abs( x – 2.8 ) < 0.001 )
с желательной точностью
March 28th, 2010 at 8:56 am
хм, честно говоря не знал что с Double все таки плохо. То есть она получается путем деления\умножения – то еще ладно, тут я знаю что может быть не совсем .00, но что код типа
double x1 = 2.6;
double x2 = 2.7;
if (x1 != x2)
может не всегда работать для меня сюрприз:)
Посмотрел в код у меня там – if (version > curVersion), так что на подсознательном уровне получается я пишу правильно:)
March 28th, 2010 at 9:31 pm
эй, не передергивай 🙂 я говорил про
double x1 = 2.6;
if (x1 == 2.6)
ну а в твоем гипотетическом примере
double x1 = 2.6;
if (x1 != 2.6)
вполне может оказаться, что х1 не равен 2.6
March 28th, 2010 at 9:48 pm
эээ видимо я чего-то не понял 🙂
March 29th, 2010 at 10:12 am
да и вообще, даешь целочисленную арифметику!
March 31st, 2010 at 10:25 am
Тогда уж – даешь ООП, как учат отцы и старшие:
[code]
#include <wx/tokenzr.h>
#include <vector>
class Version
{
public:
Version(wxString versionString, wxString delimiter = wxT(".")) {
wxArrayString AS = ::wxStringTokenize(versionString, delimiter);
long L;
for(size_t n = 0; n < AS.size(); ++n) {
AS[n].ToLong(&L);
ver.push_back(L);
}
}
bool operator < (const Version& v) const {
for(size_t n = 0; (n < ver.size()) || (n < v.size()); ++n) {
if(getPart(n) != v.getPart(n)) {
return getPart(n) < v.getPart(n);
}
}
return false;
}
inline long getPart(size_t n) const {
return (n < ver.size())? ver[n] : 0;
}
inline size_t size() const {
return ver.size();
}
protected:
std::vector< long > ver;
};
[/code]
March 31st, 2010 at 10:39 am
Суровые русские программисты?:)
А меня и флоат устраивает выше крышы, к чему такие сложности?:)
March 31st, 2010 at 10:45 am
Ну, ты же сомневался, читать “Рефакторинг” Фаулера или погодить?
Я прочитал – и вот… 😉
March 31st, 2010 at 4:51 pm
оо, круто, ты прочитал реракторинг и зарефакторил мою 1 строчку, в целый класс? при этом не добавив функциональности 🙂
Хорошо наверное что я все-таки не купил Фаулера:)
March 31st, 2010 at 9:40 pm
Теперь вы добились своей цели: наконец то нихрена непонятно что весь этот код делает 🙂
March 31st, 2010 at 11:20 pm
Это-то как раз понятно: весь этот код лежит в .h-файле в личной библиотечке и ждет, когда вам в шестой раз понадобится работать с версиями программ в виде строки. (Написан он должен быть после того, как это понадобилось в пятый раз. Раньше смысла нет).
Между прочим, у Фаулера была бы единственная претензия к моему классу: “код должен быть самотестирующимся”.
То есть туда еще надо добавить функцию, которую можно будет запустить в серии тестов после каждой компиляции и убедиться, что класс не сломался…
Рефакторинг же – наука о том, как перелопатить жуткий унаследованный код так, чтобы в нем можно было разобраться, поддерживать и добавлять функционал. Исследование таких вещей оставляет мало шансов сохранить здоровую обывательскую психику 😉
April 1st, 2010 at 12:15 am
У меня есть еще одна притензия, где проверки на ошибки ? где вообще хоть какие-то проверки? 🙂
April 1st, 2010 at 8:30 am
Да, по этому поводу Фаулер бы тоже высказался 😉
Но я, к сожалению, не параноик и не смог найти здесь ни одного места для assert’a. Опыта, видимо, не хватает… 🙁
April 1st, 2010 at 1:32 pm
не ассертами едиными 🙂
что будет если я передам пустую строку, или “хрень” ? как вызывающему коду узнать об ошибке ?
April 1st, 2010 at 2:18 pm
Нечисловая хрень или пустая строка превратится в версию 0.0…
Как предусмотреть, что в этом случае нужно вызывающему коду?
Можно создать еще один метод:
[code]bool isOK() { return (size() > 1) || (getPart(0) != 0); }[/code]
, но это можно сделать тогда, когда он понадобится 😉
April 1st, 2010 at 8:13 pm
А что будет в случае 10.хрень.2.213 – такая строка возникнет в случае ошибока парсинга\форматирования исходных данных (у меня такая ситуация была), мой код из 1 строчеки сразу кричит про ошибку, а твой?:)
April 1st, 2010 at 8:14 pm
Вот кстати вчера ссылка проскакивала в тему – http://p.umputun.com/14926638, для всех кто увлекается Фаулером:)))
April 1st, 2010 at 8:26 pm
надо цикл переделать так:
long L = 0;
AS[n].ToLong(&L);
ver.push_back(L);
}
иначе для неверных значений исходной строки значение L будет неопределённым.
April 1st, 2010 at 8:29 pm
это не отвечает на мой вопрос…
April 1st, 2010 at 8:31 pm
будет ноль на месте хрени… а если нужн тебе ассерты, то добавь их:
April 1st, 2010 at 8:35 pm
Мне не нужны ассерты, мне нужно что-то типа того как у меня в вызывающем коде
if (!str2.ToCDouble(&version))
{
HaveError = true;
return -1;
}
Ошибка? отлично, говорим пользователю про ошибку – и выходим из процедуры проверки обновлений – на самый вверх.\
p.s. Вот под маком хорошо – там походу проверка на обновления, делается парой кликов мышки, а функциональность уже в системе встроенная (если я правильно понял)
April 1st, 2010 at 8:41 pm
#include
class Version
{
bool isOK;
public:
Version(wxString versionString, wxString delimiter = wxT(".")): isOK(true)
{
wxArrayString AS = ::wxStringTokenize(versionString, delimiter);
for(size_t n = 0; n < AS.size(); ++n) {
long L = 0;
isOK = isOK && AS[n].ToLong(&L);
ver.push_back(L);
}
}
bool IsOK() { return isOK; }
bool operator < (const Version& v) const {
for(size_t n = 0; (n < ver.size()) || (n < v.size()); ++n) {
if(getPart(n) != v.getPart(n)) {
return getPart(n) < v.getPart(n);
}
}
return false;
}
inline long getPart(size_t n) const {
return (n < ver.size())? ver[n] : 0;
}
inline size_t size() const {
return ver.size();
}
protected:
std::vector ver;
};
вот после создания объекта вызывай метод IsOK и проверяй всё ли в порядке…
April 1st, 2010 at 8:50 pm
мда, так действительно лучше, но согласитесь менять 1 строчку на целый класс, это изврат… ссылку выше читали?:)
April 1st, 2010 at 11:21 pm
Ссылка выше немного о другом…
Я согласен, что менять одну строчку на любой самый прекрасный класс неразумно. Собственно, и говорил, что такой класс может появиться, когда ты хочешь одну часто используемую фигню вылизать один раз, а потом использовать как библиотечную.
Кстати, о фанатизме: второй день обсуждается класс, написанный за 10 минут. Как-то это по-фаулеровски 😉