Стоило мне написать про ночи без звонков. Как это сразу и случилось. Заказчик прислал такую вот картинку:
В общем, я конечно же не учел деления на ноль.
На мой удивленный вопрос - "А Вы знаете что на 0 делить нельзя ?"
Клиент заявил что - "Необходимо что бы калькулятор писал "Деление на 0" в ответе".
Быстрым ночным решением я просто сделал проверку знаменателя, и в ответ давал необходимое клиенту - "Деление на 0".
Однако с утра необходимо было привести в порядок наши тесты. А также сделать учет ошибок деления на 0. Почему так получилось ? Мы детально разберём в этой статье.
Александр предложил идти по определенному плану, что мы собственно и будем делать:
В общем, я конечно же не учел деления на ноль.
На мой удивленный вопрос - "А Вы знаете что на 0 делить нельзя ?"
Клиент заявил что - "Необходимо что бы калькулятор писал "Деление на 0" в ответе".
Быстрым ночным решением я просто сделал проверку знаменателя, и в ответ давал необходимое клиенту - "Деление на 0".
Однако с утра необходимо было привести в порядок наши тесты. А также сделать учет ошибок деления на 0. Почему так получилось ? Мы детально разберём в этой статье.
Александр предложил идти по определенному плану, что мы собственно и будем делать:
1. Залатали ошибку. ОДНУ.
class function TCalculator.Divide(const A, B: string): string; var x1, x2, x3 : single; begin x1 := StrToFloat(A); x2 := StrToFloat(B); if x2=0 then begin Result := 'Деление на 0'; exit; end; x3 := x1 / x2; Result := FloatToStr(x3); end;
2. Написали тест логики, который её проверяет.
3. Сделали тест с эталонами(здесь и далее всегда когда мы пишем ЭТАЛОНЫ подразумеваем ЭТАЛОНЫ на основе псевдослучайных данных), в котором участвуют ошибочные входные данные. Для начала я хотел бы привести код процедуры которая формирует "псевдослучайную" последовательность, которая работает сейчас:
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq(
Как видим второй аргумент который мы передаем в качестве знаменателя, однозначно никогда не будет нулем. Сделано это былопотому-что клиент ничего об этом не написал в ТЗ специально, дабы исключить ошибки на ранних стадиях тестирования. Сегодня пришло время это изменить. Убираем +1.
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq(
Запускаем наши тесты, конечно же все эталоны провалились. Но интересно, не только это. У нас ещё и выпал exception с делением на 0. Удивительно кстати, вероятность не такая уж и большая была. А почему ситуация возникла я объясню чуть позже.
4. Убедились, что он делает что надо.
Удаляем наши эталоны, так как у нас изменился, второй оператор для тестов.
5. Нашли ВТОРУЮ ошибку в DivInt. Предыдущим тестом.
Как видим наши тесты нормально прошли(то есть ЭТАЛОНЫ создались по "новой"), однако осталось исключение деления на 0 при запуске DivInt.
6. Обернули вызов логики из теста в блок try..except. В блоке try..except записали Exception.ClassName в эталон вместо результата. Для ошибочных данных естественно.
Изменяем наш код:
На:
После чего наши тесты все "зелёные", хотя при "запуске в отладке" и появляется Exception о котором я уже упоминал.
Когда я "полез" в эталоны разобраться в чем-же дело, я вспомнил о ТЗ.
Помните в прошлой главе я писал, о том что заказчик не рассказал что нам делать с вещественными числами при операции DivInt ? И вот что из этого получилось. Напомню код, которым мы решаем проблемы с вещественными числами:
Так как мы округляем числа до целых. У нас появилось исключение при делении на 0. Меняем наш код на этот:
После запуска тестов придется удалить эталон для DivInt, так как изменилась бизнес-логика программы. При всём этом хотел бы особо подчеркнуть разницу в обработке исключений в бизнес-логике и тестировании. В бизнес-логике мы проверяем "деление на 0" и выдаем соответствующий результат, то есть выполняем пожелание клиента. А в тестировании мы проверяем все варианты, и записываем соответствующий exception в файл "эталона". Зачем мы делаем именно так будет рассказано в будущих главах, сейчас скажу лишь что представьте как поведет себя программа когда мы дадим числа больше extended...
7. Написали тест логики DivInt.
По поводу этого пункта у меня с Александром возник спор на тему что является первичным.
Комментарий от Александра -
" Как у нас говорят - "два ЮРИСТА - три мнения", с программистами - веселее - "два ПРОГРАММИСТА - пять с половиной мнений :-)")
Итак пишем тест логики для деления на 0 для DivInt:
Запускаем наши тесты, и видим что всё ок, так как мы поправили логику ещё на прошлом шаге.
Как видим остальные шаги нам не понадобились потому, что мы их предприняли ещё в предыдущем пункте.
8. ПОПРАВИЛИ ВТОРУЮ ошибку. В DivInt.
9. Прогнали тесты с эталонами. Они не сойдутся для DivInt.
10. Пересоздали эталоны.
Нам остался последний шаг, это явно проверить нашу операцию для деления при занменателе 0. Одним из простых решений сделать это. Явно задавать 0 каждое сотое число:
Запускаем:
Как видим опять упали все наши эталоны. Удаляем старые и опять прогоняем тесты, результаты фиксируем в git. Заодно поправим наше ночное решение, на проверку исключений:
На этом я хотел заканчивать этот пост. Однако диалоги с Александром, вывели дополнение, которое я бы пока не заметил.
Так как мы дважды проверяем исключения. А в бизнес-логике мы определяем только то исключение, о котором "мы в курсе". То было бы не плохо, по концовке теста(Эталонно-Рандомного) проверять что у нас ни одно исключение не возникло.
Благодаря архитектуре тестов, кода будет не много. Меняем процедуру запуска "последовательности":
Предвкушая "все зеленые лампочки", очень был удивлен, когда увидел и фиолетовую(тест провалился) и красную(возникновение exception).
И вот тут я не полез в debug. Я сел и подумал, почему так вышло. Мы ведь по сути всё закончили. Сначала я думал что "виновато" мое последнее изменение(Отдельная благодарность людям которые придумали GIT и любые другие системы контроля версий). Откатившись назад на коммит... И запустив тесты, я увидел тоже самое.
Именно этот момент и является очень показательным. После изменения сравнения знаменателя с 0(ночное решение) на проверку исключений, и выдаче результата который я предвкушал, я решил что "дело в шляпе". Закомител код. И планировал заканчивать статью.
Первый тест "логики проверки деления на 0":
Здесь первая ошибка. Нужная нам константа c_ZeroDivideMessageError появится только тогда когда будет Exception для целочисленного деления на 0, или EDivByZero. Вместо него мы ловим EZeroDivide.
Delphi различает эти типы исключений, поэтому для вещественных чисел введён свой класс. Почему это сделано, я в принципе понимаю. Но мои тесты не поняли. Будем менять.
Второй тест провалился потому что, он не сошелся с эталоном. В котором уже было записано строковое значение нашей константы c_ZeroDivideMessageError.
В следствии того что в операцию Div попадают только вещественные числа, меняем проверку исключения на необходимый нам тип:
Всё отлично:
Но!!!
Наша проверка количества исключений, на данный момент находится в процедуре CheckOperation. То есть запуская каждый раз операцию на тестирование, мы проверяем что бы в результате не было не одного исключения. В данный момент нас это в принципе устраивает. Но суть проверки теряется. Изменим это:
Запустим тесты и УБЕДИМСЯ что всё ок.
Подведем итоги:
Первым делом, ТЗ ТЗ и ещё раз ТЗ. Уточнять и думать это именно наша работа, а не клиента.
Каждый раз меняя логику или условия тестирования мы обновляем эталоны.
Каждый раз МЕНЯЯ ЧТО УГОДНО - МЫ ПРОВЕРЯЕМ СЕБЯ ТЕСТАМИ.
Иначе на кой они вообще ?
Ноль не всегда равен 0, так уж вышло в программировании однако об этом будет другая статья.
Проверка на кол-во исключений позволяет нам быть более уверенными в "крепости обороны" от ошибок. Однако главное это человек сидящий за клавиатурой. Он всегда может допустить ошибку, как минимум он может не запустить тесты как я.
Программирование сложный и отчасти не предсказуемый процесс. А в придачу, мы все люди. Мы все допускаем ошибки. Пусть создание дополнительных "укреплений обороны" и несет в себе затраты в виде человеко-часов, зато взамен мы получаем КАЧЕСТВО и ПРЕДУСМОТРИТЕЛЬНОСТЬ. Однако есть ещё противоречия в ТЗ, и много других тем которые мы ещё обсудим.
p.s. В процессе подготовки статьи случайно натолкнулся на замечательную статью GunSmoker'a о расширении класса Exception после Delphi 2009.
Репозиторий проекта.
Диаграмма классов UML.
procedure TCalculatorOperationViaLogicTest.TestZeroDivide; var x1, x2: string; begin x1:= cA; x2:= '0'; CheckTrue(c_ZeroDivideMessageError = TCalculator.Divide(x1, x2)); end;
3. Сделали тест с эталонами(здесь и далее всегда когда мы пишем ЭТАЛОНЫ подразумеваем ЭТАЛОНЫ на основе псевдослучайных данных), в котором участвуют ошибочные входные данные. Для начала я хотел бы привести код процедуры которая формирует "псевдослучайную" последовательность, которая работает сейчас:
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq(
aLogger: TLogger; anOperation: TCalcOperation); var l_Index : Integer; begin RandSeed := 40000; aLogger.OpenTest(Self); for l_Index := 0 to 10000 do CheckOperation(aLogger, 1000 * Random, 2000 * Random + 1, anOperation); CheckTrue(aLogger.CheckWithEtalon); end;
Как видим второй аргумент который мы передаем в качестве знаменателя, однозначно никогда не будет нулем. Сделано это было
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq(
aLogger: TLogger; anOperation: TCalcOperation); var l_Index : Integer; begin RandSeed := 40000; aLogger.OpenTest(Self); for l_Index := 0 to 10000 do CheckOperation(aLogger, 1000 * Random, 2000 * Random, anOperation); CheckTrue(aLogger.CheckWithEtalon); end;
Запускаем наши тесты, конечно же все эталоны провалились. Но интересно, не только это. У нас ещё и выпал exception с делением на 0. Удивительно кстати, вероятность не такая уж и большая была. А почему ситуация возникла я объясню чуть позже.
Удаляем наши эталоны, так как у нас изменился, второй оператор для тестов.
5. Нашли ВТОРУЮ ошибку в DivInt. Предыдущим тестом.
Как видим наши тесты нормально прошли(то есть ЭТАЛОНЫ создались по "новой"), однако осталось исключение деления на 0 при запуске DivInt.
6. Обернули вызов логики из теста в блок try..except. В блоке try..except записали Exception.ClassName в эталон вместо результата. Для ошибочных данных естественно.
Изменяем наш код:
procedure TCalculatorOperationRandomSequenceTest.CheckOperation( aLogger: TLogger; aX1, aX2: Double; anOperation : TCalcOperation); begin aLogger.ToLog(aX1); aLogger.ToLog(aX2); aLogger.ToLog(anOperation(FloatToStr(aX1),FloatToStr(aX2))); end;
На:
procedure TCalculatorOperationRandomSequenceTest.CheckOperation( aLogger: TLogger; aX1, aX2: Double; anOperation : TCalcOperation); begin aLogger.ToLog(aX1); aLogger.ToLog(aX2); try aLogger.ToLog(anOperation(FloatToStr(aX1),FloatToStr(aX2))); except on E : Exception do aLogger.ToLog(E.ClassName); end; end;
После чего наши тесты все "зелёные", хотя при "запуске в отладке" и появляется Exception о котором я уже упоминал.
Когда я "полез" в эталоны разобраться в чем-же дело, я вспомнил о ТЗ.
TCalculatorOperationRandomSequenceTestTestDivInt.etalon ... 277.833182131872 0.400131568312645 EDivByZero ...
Помните в прошлой главе я писал, о том что заказчик не рассказал что нам делать с вещественными числами при операции DivInt ? И вот что из этого получилось. Напомню код, которым мы решаем проблемы с вещественными числами:
class function TCalculator.DivInt(const A, B: string): string; var x1, x2, x3 : Integer; begin x1 := round(StrToFloat(A)); x2 := round(StrToFloat(B)); x3 := x1 div x2; Result := FloatToStr(x3); end;
Так как мы округляем числа до целых. У нас появилось исключение при делении на 0. Меняем наш код на этот:
class function TCalculator.DivInt(const A, B: string): string; var x1, x2, x3 : Integer; begin x1 := round(StrToFloat(A)); x2 := round(StrToFloat(B)); try x3 := x1 div x2; except on EDivByZero do begin Result:= c_ZeroDivideMessageError; Exit; end; end; Result := FloatToStr(x3); end;
После запуска тестов придется удалить эталон для DivInt, так как изменилась бизнес-логика программы. При всём этом хотел бы особо подчеркнуть разницу в обработке исключений в бизнес-логике и тестировании. В бизнес-логике мы проверяем "деление на 0" и выдаем соответствующий результат, то есть выполняем пожелание клиента. А в тестировании мы проверяем все варианты, и записываем соответствующий exception в файл "эталона". Зачем мы делаем именно так будет рассказано в будущих главах, сейчас скажу лишь что представьте как поведет себя программа когда мы дадим числа больше extended...
7. Написали тест логики DivInt.
По поводу этого пункта у меня с Александром возник спор на тему что является первичным.
Тестирование логики или запуск Random эталонов, с вероятностью 1 к 10000 что ошибка проявит себя. однако эта темя для другого поста :).
" Как у нас говорят - "два ЮРИСТА - три мнения", с программистами - веселее - "два ПРОГРАММИСТА - пять с половиной мнений :-)")
Итак пишем тест логики для деления на 0 для DivInt:
procedure TCalculatorOperationViaLogicTest.TestZeroDivInt; var x1, x2: string; begin x1:= cA; x2:= '0'; CheckTrue(c_ZeroDivideMessageError = TCalculator.DivInt(x1, x2)); end;
Запускаем наши тесты, и видим что всё ок, так как мы поправили логику ещё на прошлом шаге.
Как видим остальные шаги нам не понадобились потому, что мы их предприняли ещё в предыдущем пункте.
8. ПОПРАВИЛИ ВТОРУЮ ошибку. В DivInt.
9. Прогнали тесты с эталонами. Они не сойдутся для DivInt.
10. Пересоздали эталоны.
Нам остался последний шаг, это явно проверить нашу операцию для деления при занменателе 0. Одним из простых решений сделать это. Явно задавать 0 каждое сотое число:
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq( aLogger: TLogger; anOperation: TCalcOperation); var l_Index : Integer; x1, x2 : single; begin RandSeed := 40000; aLogger.OpenTest(Self); for l_Index := 0 to 10000 do begin x1 := 1000 * Random; x2 := 2000 * Random; if (l_Index mod 100) = 0 then x2 := 0; CheckOperation(aLogger, x1, x2, anOperation); end; CheckTrue(aLogger.CheckWithEtalon); end;
Запускаем:
Как видим опять упали все наши эталоны. Удаляем старые и опять прогоняем тесты, результаты фиксируем в git. Заодно поправим наше ночное решение, на проверку исключений:
class function TCalculator.Divide(const A, B: string): string; var x1, x2, x3 : single; begin x1 := StrToFloat(A); x2 := StrToFloat(B); try x3 := x1 / x2; except on EDivByZero do begin Result:= c_ZeroDivideMessageError; Exit; end; end; x3 := x1 / x2; Result := FloatToStr(x3); end;
На этом я хотел заканчивать этот пост. Однако диалоги с Александром, вывели дополнение, которое я бы пока не заметил.
Так как мы дважды проверяем исключения. А в бизнес-логике мы определяем только то исключение, о котором "мы в курсе". То было бы не плохо, по концовке теста(Эталонно-Рандомного) проверять что у нас ни одно исключение не возникло.
Благодаря архитектуре тестов, кода будет не много. Меняем процедуру запуска "последовательности":
procedure TCalculatorOperationRandomSequenceTest.CheckOperation( aLogger: TLogger; aX1, aX2: Double; anOperation : TCalcOperation); var l_ExceptionCount: integer; begin aLogger.ToLog(aX1); aLogger.ToLog(aX2); l_ExceptionCount:= 0; try aLogger.ToLog(anOperation(FloatToStr(aX1),FloatToStr(aX2))); except on E : Exception do begin aLogger.ToLog(E.ClassName); inc(l_ExceptionCount); end; end; Check(l_ExceptionCount = 0); end;
Предвкушая "все зеленые лампочки", очень был удивлен, когда увидел и фиолетовую(тест провалился) и красную(возникновение exception).
И вот тут я не полез в debug. Я сел и подумал, почему так вышло. Мы ведь по сути всё закончили. Сначала я думал что "виновато" мое последнее изменение(Отдельная благодарность людям которые придумали GIT и любые другие системы контроля версий). Откатившись назад на коммит... И запустив тесты, я увидел тоже самое.
Именно этот момент и является очень показательным. После изменения сравнения знаменателя с 0(ночное решение) на проверку исключений, и выдаче результата который я предвкушал, я решил что "дело в шляпе". Закомител код. И планировал заканчивать статью.
Где моя главная ошибка ?
Я не воспользовался преимуществами подхода, о котором пишу. А именно. ТЕСТЫ ПРОВЕРЯЮТ РАБОТУ программиста. Я не запустил тесты, так как решил, что "уж тут-то точно всё ок".
У нас провалилось 2 теста:
- Тест логики проверки деления на 0.
- Тест эталонов с 10к вариантами.
- Тест логики проверки деления на 0.
- Тест эталонов с 10к вариантами.
Первый тест "логики проверки деления на 0":
procedure TCalculatorOperationViaLogicTest.TestZeroDiv; var x1, x2: string; begin x1:= cA; x2:= '0'; CheckTrue(c_ZeroDivideMessageError = TCalculator.Divide(x1, x2)); end;
Здесь первая ошибка. Нужная нам константа c_ZeroDivideMessageError появится только тогда когда будет Exception для целочисленного деления на 0, или EDivByZero. Вместо него мы ловим EZeroDivide.
Delphi различает эти типы исключений, поэтому для вещественных чисел введён свой класс. Почему это сделано, я в принципе понимаю. Но мои тесты не поняли. Будем менять.
Второй тест провалился потому что, он не сошелся с эталоном. В котором уже было записано строковое значение нашей константы c_ZeroDivideMessageError.
В следствии того что в операцию Div попадают только вещественные числа, меняем проверку исключения на необходимый нам тип:
class function TCalculator.Divide(const A, B: string): string; var x1, x2, x3 : single; begin x1 := StrToFloat(A); x2 := StrToFloat(B); try x3 := x1 / x2; except on EZeroDivide do begin Result:= c_ZeroDivideMessageError; Exit; end; end; x3 := x1 / x2; Result := FloatToStr(x3); end;
Всё отлично:
Но!!!
Наша проверка количества исключений, на данный момент находится в процедуре CheckOperation. То есть запуская каждый раз операцию на тестирование, мы проверяем что бы в результате не было не одного исключения. В данный момент нас это в принципе устраивает. Но суть проверки теряется. Изменим это:
procedure TCalculatorOperationRandomSequenceTest.CheckOperationSeq( aLogger: TLogger; anOperation: TCalcOperation); var l_Index, l_ExceptionCount : Integer; x1, x2 : single; begin RandSeed := 40000; aLogger.OpenTest(Self); l_ExceptionCount:= 0; for l_Index := 0 to 10000 do begin try x1 := 1000 * Random; x2 := 2000 * Random; if (l_Index mod 100) = 0 then x2 := 0; CheckOperation(aLogger, x1, x2, anOperation); except on E : Exception do begin aLogger.ToLog(E.ClassName); inc(l_ExceptionCount); end; end; end; CheckTrue(aLogger.CheckWithEtalon); Check(l_ExceptionCount = 0); end;
Запустим тесты и УБЕДИМСЯ что всё ок.
Подведем итоги:
Первым делом, ТЗ ТЗ и ещё раз ТЗ. Уточнять и думать это именно наша работа, а не клиента.
Каждый раз меняя логику или условия тестирования мы обновляем эталоны.
Каждый раз МЕНЯЯ ЧТО УГОДНО - МЫ ПРОВЕРЯЕМ СЕБЯ ТЕСТАМИ.
Иначе на кой они вообще ?
Ноль не всегда равен 0, так уж вышло в программировании однако об этом будет другая статья.
Проверка на кол-во исключений позволяет нам быть более уверенными в "крепости обороны" от ошибок. Однако главное это человек сидящий за клавиатурой. Он всегда может допустить ошибку, как минимум он может не запустить тесты как я.
Программирование сложный и отчасти не предсказуемый процесс. А в придачу, мы все люди. Мы все допускаем ошибки. Пусть создание дополнительных "укреплений обороны" и несет в себе затраты в виде человеко-часов, зато взамен мы получаем КАЧЕСТВО и ПРЕДУСМОТРИТЕЛЬНОСТЬ. Однако есть ещё противоречия в ТЗ, и много других тем которые мы ещё обсудим.
p.s. В процессе подготовки статьи случайно натолкнулся на замечательную статью GunSmoker'a о расширении класса Exception после Delphi 2009.
Репозиторий проекта.
Диаграмма классов UML.
Добрый день,
ОтветитьУдалитьСпасибо за статью. Наконец-то на этом блоге стали появляться детальные статьи. Приятно читать, автору спасибо.
Если я правильно понимаю, то условие в коде первого пункта:
if x2=0 then
не совсем корректное. Потому что x2 вещественного типа и равенства нулю собственно при такой проверки может и не достигаться. За счет погрешности типа. Поэтому нужно использовать сравнение с точностью SameValue.
Спасибо
"Спасибо за статью. Наконец-то на этом блоге стали появляться детальные статьи. Приятно читать, автору спасибо."
УдалитьПОЖАЛУЙСТА.
И от автора - ТОЖЕ (поскольку я не автор, а "научный руководитель").
"Наконец-то на этом блоге стали появляться детальные статьи"
Понимаете в чём дело! :-)
Это же не "просто пост в блоге", типа "бла-бла-бла я вас научу".
Это - ПОЛНОЦЕННАЯ статья.
Плод двухнедельного труда ДВОИХ людей. Со спорами, бессонными ночами и "вариантами как лучше". И уже ПРОРАБОТАННОЙ (как нам кажется) методикой.
Поэтому - если ТЕМА ИНТЕРЕСНА - МЫ РАДЫ. Пишите нам.
У нас есть "много тем" и БОЛЬШОЕ количество материала. И есть ПЛАН СТАТЕЙ.
И есть "список задач". Открыть его кстати в ПУБЛИЧНЫЙ ДОСТУП?
Т.е. мы пишем - "не потому что пишется", а "потому что это может быть интересно".
Мы себе "наметили вехи" и "написали план статей и завели список задач".
И пока - "у нас драйва хватает".
Но если отклика нету, то "энтузиазм угасает".
Посему - ЕСЛИ ИНТЕРЕСНО - пишите, СПРАШИВАЙТЕ и КРИТИКУЙТЕ.
Ну и СПАСИБО вам за ваш отклик.
И ещё - мы во-первых ищем человека, который мог бы эту "серию про калькулятор" перевести на английский.
А во-вторых - мы ищем соавторов.
Если есть что посоветовать - БУДЕМ ОЧЕНЬ рады.
И ещё... Уже НЕСКОЛЬКО людей написали что-то вроде - "пример не показательный, а усилия чрезмерны"...
УдалитьНа это я попытался ответить тут - http://programmingmindstream.blogspot.ru/2014/06/blog-post_21.html
Но! Попытайтесь "абстрагироваться от примера"... Подумайте о том, что это "не калькулятор", а "большой проект"... ПОВЕРЬТЕ - мало что изменится.
Если есть КОНКРЕТНЫЕ вопросы - ОБЯЗАТЕЛЬНО задавайте их.
Если есть КОНКРЕТНЫЕ примеры - приводите их.
МЫ БУДЕМ вам очень ПРИЗНАТЕЛЬНЫ.
"Наконец-то на этом блоге стали появляться детальные статьи."
УдалитьКстати ДЕТАЛЬНЫЕ статьи были и раньше.
Например "GUI-тестирование по-русски", но они НЕ ЗАИНТЕРЕСОВАЛИ аудиторию.
Например вот:
http://18delphi.blogspot.ru/2013/11/5.html
http://18delphi.blogspot.ru/2013/11/4.html
http://18delphi.blogspot.ru/2013/11/gui-25-tscriptcontext.html
http://18delphi.blogspot.ru/2013/11/gui-2.html
http://18delphi.blogspot.ru/2013/11/gui_5.html
"И ещё - мы во-первых ищем человека, который мог бы эту "серию про калькулятор" перевести на английский."
УдалитьДа! Забыл добавить - КОНЕЧНО ЖЕ - НЕ БЕСПЛАТНО.
"Мы себе "наметили вехи" и "написали план статей и завели список задач"."
УдалитьПример одного из тасков кстати:
"Тесты и "клиентороиентированность".
Про "ту самую запятую" в FloatToStr.
И как делать тесты и ЭТАЛОНЫ зависимыми от клиентского ОКРУЖЕНИЯ.
Когда в TLogger.Open мы подаём ещё и "предикат" клиентозависимости.
Например:
TLogger.Open(Self, [FormatSettings.DecimalSeparator]);
-- и тогда В ЗАВИСИМОСТИ от ЗНАЧЕНИЯ FormatSettings.DecimalSeparator - будет сделаны ЭТАЛОНЫ с РАЗНЫМИ именами."
Пожалуйста.
УдалитьПо поводу корректности. Согласен. В следующей главе описано применение SomeValue. В данном случае можно и IsZero использовать.
"не совсем корректное"
ОтветитьУдалитьКОНЕЧНО! Так это же из серии "залатали" :-)
Про "сравнение float'ов" у нас будет отдельная глава. Один из авторов - трудится над ней.
УдалитьИ ещё будет статья про то как "завернуть обработку ошибок" в класс TLogger. И тогда класс TLogger - становится РЕАЛЬНОЙ ИНФРАСТРУКТУРОЙ.
Удалить