Клиника плохого кода
В чем причины возникновения ошибок в программном коде? Почему нечто, описанное словами или же в математической нотации, может быть реализовано неверно? И самое интересное - почему это случается так часто? Программирование как игра в шахматы. дальше...
Комментарии:
Хорошая статья )
Я бы еще добавил про опастность наличия leaked abstractions.
И не хватает примеров того как многие другие современные языки облегчают анализ вариантов. Haskell например...
Ах если бы современные сиподобные языки не заставляли меня играть в шахматы 80% времени =)
Типизация и дерево наследования только стимулируют эту игру
если мы вычисляем среднее, то возвращать должны тип с плавающей точкой.
Уважаемый автор забыл еще один класс ошибок - когда решается совсем не та проблема которая озаботила пользователя.
Чудо сервис о котором говорилось в начале статьи - наглядное тому подтверждение.
> если мы вычисляем среднее, то возвращать должны тип с плавающей точкой
Компиляторы Си производят целочисленное деление если результат должен быть записан в целочисленную переменную, причем даже при выключенной оптимизации.
Нахождение целочисленного среднего используется во многих алгоритмах, например в двоичном поиске. Большинство из встречавшихся мне реализаций переполнение не учитывается. Массив с двумя миллиардами элементов - вещь, конечно, экзотическая, но не такая уж фантастическая, например в Гугле такие массивы вроде существуют.
Во всех случаях это только очень простой пример ошибки, на котором удобно было рассмотреть как их отлавливать в общем случае.
Неплохая статья.
Впрочем, по поводу венгерской нотации: ее создатель имел в виду совсем другое. В качестве префиксов он использовал не информацию о типе переменной (за чем и так следит компилятор), но информацию о ее семантике. Т.е. если переменная хранит длину (чего-либо) в сантиметрах, то ее префикс - нечто вроде sl_. А если переменная хранит размер в пикселях - то pl_. Это позволяет находить семантические ошибки, когда переменные одного типа, но несущие несоизмеримые величины, использовались в одном и том же выражении.
В дальнейшем Микрософт творчески переработал эту идею и получился тот маразм, который есть сейчас.
С функцией получения среднего вы меня подкузьмили - у самого эта классическая ошибка. Правда, в моем приложении переполнение практически невозможно из-за специфики задачи, но пример очень хороший. Буду использовать в качестве тестовой задачи при собеседовании.
а так среднее можно вычислять?
avg = a/2+b/2+(a%2+b%2)/2;
или
avg = a >> 1 + b >> 1 + (a & b & 1);
avg = a/2+b/2+(a%2+b%2)/2;
avg = a >> 1 + b >> 1 + (a & b & 1);
Ага. А если а или б - отрицательное? Рассмотрели все граничные варианты?
В первом случае, может быть, что-то и получится - хотя остаток от деления отрицательного числа неочевиден. Во втором случае явно полезут глюки при наличии отрицательных чисел.
Хочу заступиться за ООП. Не хочу утверждать, что это панацея, но сравнение QuadraticEquation и num_roots просто некорректно. В классе есть методы setA(), setB() и setC(), соответственно и переменные, которые хранят эти значения. А зачем? Этот класс решает много задач, помимо той которая требуется. Нам нужен дверной глазок, а не телескоп. И ни при чем тут ООП. Уберите переменные a, b, c останутся x1, x2 numRoots... На что он станет похож? А нельзя ли его заменить std::vector<double>?
void solveQuadraticEquation (double a, double b, double c, std::vector<double>&);
Вполне в духе STL. Если не нравится глобальная функция можно ее сделать статическим методом класса.
Вообще, это типичная ошибка начинающих: добавить лишних переменных в класс, а потом мужественно отслеживать их статус в спагеттиобразном коде.
С удовольствием прочитал, спасибо :-)
Великолепная статья! Прочитал с большим удовольствием.
B писал:
Хочу заступиться за ООП. Не хочу утверждать, что это панацея, но сравнение QuadraticEquation и num_roots просто некорректно. В классе есть методы setA(), setB() и setC(), соответственно и переменные, которые хранят эти значения. А зачем? Этот класс решает много задач, помимо той которая требуется.
А зачем решать задачи помимо той, которая требуется? Может быть тогда и функциональность и читаемость кода повысятся?
"Создать, записать, прочесть, стереть" Если пустые файлы система будет считать несуществующими (или наоборот, несуществующие считать пустыми), можно обойтись и двумя командами - записать и прочесть. А если использовать "прологоподобную" "унификацию термов", то и вообще только одной!
(Осталось теперь понять, как сократить количество команд до нуля. :-)
А я заступлюсь за венгерскую нотацию.
Как всегда мелкософт перегнул палку - длинные префиксы действительно только мешают.
Лично я пользую ограниченый набор префиксов состоящих из одной и редко из двух/трех букв:
k - constant
t - user defined type
m - class member
gm - module global variable
c - class (variable)
in - input parameter for proc
out - output parameter for proc
io - input/output parameter for proc
Такая кодировка очень хорошо работает с Паскалевским стилем записи идентификаторов (капитализированые слова):
kMyGreatConstant
mThisIsMyClassMemberVar
Unix стиль записи с подчеркиваниями на дух не переношу из-за необходимости выкручивать мизинец правой руки при слепом методе набора на клавиатуре. :)
По долгу службы пишу на Модуле 2.
Дома на С++.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Следовательно, окончательная версия нашей функции будет выглядеть так:
int avg(int a, int b)
{
if (sign(a) != sign(b))
return (a + b) / 2;
else
return a + (b - a) / 2;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
А как насчёт:
…..
avg( MaxInt, MaxInt );
avg( 0-MaxInt, 0-MaxInt );
….
?
На всякого мудреца довольно простоты.
BALR 12,0
USING *,12
*
X EQU 11 * регистр в котором передано первое число
Y EQU 10 * регистр в котором передано второе число
RС EQU 9 * регистр в котором возвращается результат
*
TEMP EQU 8 * регистр для промежуточных результатов
*
ST TEMP,saTEMP
*
LR RC,X
SRA RC, =L’1’
*
LR TEMP,Y
SRA TEMP,=L’1’
*
AR RC,TEMP
*
LR TEMP,saTEMP
BR 14
saTemp DS F
END
>>> Alex | Август 30, 2007
>>> а так среднее можно вычислять?
>>> avg = a >> 1 + b >> 1 + (a & b & 1);
Не 'сто-ит. Можете сдвинуть значащий знаковый разряд.
К сожалению, этот замшелый С не имеет операции арифметического сдвига.
На всякого мудреца довольно простоты II.
BALR 12,0
USING *,12
SRA 11, =L’1’ * регистр в котором передано первое число и возвращается результат.
SRA 10,=L’1’ * регистр в котором передано второе число.
AR 11,10
BR 14
END
Итого: три команды Ассемблера и 4 (прописью: ЧЕТЫРЕ) машинные команды.
Эти ЧЕТЫРЕ машинные команды дорогого стоят. По меньшей мере – больше, чем все предыдущие посты в эту ветку.
THAT’S COOL, GUYS! THOSE CRAZY RUSSIAN PROGRAMMERS ARE FUNNY.
Интересная статья. Спасибо
Автор хотел бы язык с модульностью, l-исчислением и объектами. Такой язык есть - ocaml (функционально-императивный язык с агрессивным компилятором).
Ну а для тех кто привык к С подобным языкам можно обратить внимание на D www.digitalmars.com/d/2.0/index.html
<< В начало