Август 20, 2007

Клиника плохого кода

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

Комментарии:

kmmbvnr | Август 21, 2007

Хорошая статья )

Я бы еще добавил про опастность наличия leaked abstractions.

И не хватает примеров того как многие другие современные языки облегчают анализ вариантов. Haskell например...

miramax | Август 22, 2007

Ах если бы современные сиподобные языки не заставляли меня играть в шахматы 80% времени =)
Типизация и дерево наследования только стимулируют эту игру

sinji | Август 23, 2007

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

Hovik | Август 23, 2007

> если мы вычисляем среднее, то возвращать должны тип с плавающей точкой

Компиляторы Си производят целочисленное деление если результат должен быть записан в целочисленную переменную, причем даже при выключенной оптимизации.

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

Во всех случаях это только очень простой пример ошибки, на котором удобно было рассмотреть как их отлавливать в общем случае.

Александр | Август 29, 2007

Неплохая статья.
Впрочем, по поводу венгерской нотации: ее создатель имел в виду совсем другое. В качестве префиксов он использовал не информацию о типе переменной (за чем и так следит компилятор), но информацию о ее семантике. Т.е. если переменная хранит длину (чего-либо) в сантиметрах, то ее префикс - нечто вроде sl_. А если переменная хранит размер в пикселях - то pl_. Это позволяет находить семантические ошибки, когда переменные одного типа, но несущие несоизмеримые величины, использовались в одном и том же выражении.

В дальнейшем Микрософт творчески переработал эту идею и получился тот маразм, который есть сейчас.

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

Alex | Август 30, 2007

а так среднее можно вычислять?

avg = a/2+b/2+(a%2+b%2)/2;

или

avg = a >> 1 + b >> 1 + (a & b & 1);

Александр | Август 30, 2007

avg = a/2+b/2+(a%2+b%2)/2;
avg = a >> 1 + b >> 1 + (a & b & 1);

Ага. А если а или б - отрицательное? Рассмотрели все граничные варианты?
В первом случае, может быть, что-то и получится - хотя остаток от деления отрицательного числа неочевиден. Во втором случае явно полезут глюки при наличии отрицательных чисел.

В | Август 30, 2007

Хочу заступиться за ООП. Не хочу утверждать, что это панацея, но сравнение 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. Если не нравится глобальная функция можно ее сделать статическим методом класса.

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

Илья Бирман | Сентябрь 9, 2007

С удовольствием прочитал, спасибо :-)

MicroBell | Сентябрь 11, 2007

Великолепная статья! Прочитал с большим удовольствием.

MicroBell | Сентябрь 11, 2007

B писал:
Хочу заступиться за ООП. Не хочу утверждать, что это панацея, но сравнение QuadraticEquation и num_roots просто некорректно. В классе есть методы setA(), setB() и setC(), соответственно и переменные, которые хранят эти значения. А зачем? Этот класс решает много задач, помимо той которая требуется.
А зачем решать задачи помимо той, которая требуется? Может быть тогда и функциональность и читаемость кода повысятся?

Вовка | Сентябрь 13, 2007

"Создать, записать, прочесть, стереть" Если пустые файлы система будет считать несуществующими (или наоборот, несуществующие считать пустыми), можно обойтись и двумя командами - записать и прочесть. А если использовать "прологоподобную" "унификацию термов", то и вообще только одной!
(Осталось теперь понять, как сократить количество команд до нуля. :-)

Corban | Октябрь 24, 2007

А я заступлюсь за венгерскую нотацию.

Как всегда мелкософт перегнул палку - длинные префиксы действительно только мешают.

Лично я пользую ограниченый набор префиксов состоящих из одной и редко из двух/трех букв:
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.
Дома на С++.

Assembler | Декабрь 9, 2007

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Следовательно, окончательная версия нашей функции будет выглядеть так:

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 );
….

?

На всякого мудреца довольно простоты. | Декабрь 13, 2007

На всякого мудреца довольно простоты.

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

~С | Декабрь 15, 2007

>>> Alex | Август 30, 2007
>>> а так среднее можно вычислять?
>>> avg = a >> 1 + b >> 1 + (a & b & 1);

Не 'сто-ит. Можете сдвинуть значащий знаковый разряд.

К сожалению, этот замшелый С не имеет операции арифметического сдвига.

На всякого мудреца довольно простоты II. | Декабрь 20, 2007

На всякого мудреца довольно простоты 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.

alex | Январь 29, 2008

Интересная статья. Спасибо

guest | Март 16, 2008

Автор хотел бы язык с модульностью, l-исчислением и объектами. Такой язык есть - ocaml (функционально-императивный язык с агрессивным компилятором).

Eugene Pelekhay | Март 16, 2008

Ну а для тех кто привык к С подобным языкам можно обратить внимание на D www.digitalmars.com/d/2.0/index.html

<< В начало

Ваше имя:

Ваш email (будет защищен от сканирования):

Запомнить имя и email

Комментарии: