• ↓
  • ↑
  • ⇑
 
Записи с темой: говнокод (список заголовков)
23:58 

Коллекционер стеклотары

Появилась необходимость сохранять настройки в энергонезависимой памяти микроконтроллера. МК STM32. У большинства моделей отсутствует EEPROM, специально для этого предназначенный. Поэтому остаётся только одно -- хранить в основной флеш-памяти, где прошивка.

Я знал, что это делать можно -- и даже примерно представлял себе, как. Но решил почитать, что пишут умные люди.

Забиваю в гугл "работа с флеш-памятью stm32". Передо мной появляется 10+ статей (и я наверное напишу ещё одну). Во всех написано примерно одно и то же. Но интересовал меня изначально в статьях строго определённый момент: как определить, где заканчивается прошивка, чтобы использовать под свои данные свободное место?

Но во всех статьях этот вопрос решался одним и тем же образом: предлагалось писать в последние адреса памяти, т.к. там шанс наткнуться на прошивку наименьший! Самоуверенность необыкновенная.

Потом я нашёл одну (!) статью, где интересующий вопрос освещался не с позиции рандома.

@темы: Программирование, Говнокод

23:59 

Опасность enum

enum -- что может быть лучше?

(для тех кто не в теме)

Чем же это опасно? При любом изменении списка, исключая добавление новых констант в конец, меняется фактическая нумерация! И значение, которое было раньше 2, теперь становится 3. Это совершенно некритично, если доступ к переменным завязан ТОЛЬКО на применении символических значений. Как только происходит выход за пределы данного лягушатника -- начинается беда-печаль. Вот примеры:
1. Бинарное общение с другой программой, у которой список констант немного отличается.
2. Общение самого с собой через файлы. Сохраняем, к примеру, настройки в файл. Обновляем программу, файл считывается, а значение уже не то!
3. Использование символических констант в качестве индексов предопределённых массивов. Например:

enum TypeName {SYMBOLIC_CONSTANT1, SYMBOLIC_CONSTANT2, SYMBOLIC_CONSTANT3};
char SymbolicNames[][80]={"Name1", "Name2", "Name3"};

printf("Name: %s\n",SymbolicNames[SYMBOLIC_CONSTANT2]);

Если модифицировать первый список, по индексам будут выпадать чужие имена.

Что с этим делать:
1. Не модифицировать список в середине и в начале. Никогда. (иногда может показаться, что это можно сделать безболезненно... переупорядочить константы по группам, к примеру)
2. Присваивать принудительные номера. Всегда. (как это делать, см. под катом в начале)
3. Использовать #define .

@темы: Программирование, Говнокод

23:58 

Как провести рефакторинг кода и не облажаться?

За этим громким названием скрывается всего лишь один трюк. Он применим только к одной очень конкретной ситуации, которая, как мне кажется, не совсем подходит под определения "рефакторинга".

Но раз уж я написал заголовок, я, пожалуй, всё же отвечу на него.

Так как же провести рефакторинг кода и не облажаться? Ответ: никак.

Но кое-от-чего защититься можно.

Итак, пусть у нас в программе есть об'ект A. Нам надо добавить новый об'ект -- A2, того же класса. Из текущих ситуаций использования A половина должны остаться за ним, а половина -- перейти к A2 (правила определения, кто чем будет заниматься, известны заранее). Ситуации встречаются по всей программе. Об'ект практически глобальный. Как же технически провести эту работу?

Самое очевидное решение -- пролистать все употребления A и часть перевести на A2 (предварительно создав его). Но так есть риск пропустить что-нибудь, даже если использовать глобальный поиск.

Я предлагаю способ, благодаря которому пропустить ни одного употребления просто не выйдет. Об'явление об'екта A надо исключить из программы! Либо удалить A, а вместо него создать массив A0[2], либо переименовать A в определении (и только там!) в A1.

К чему это приведёт? Программа не будет компилироваться из-за употребления несуществующих переменных. Придётся исправлять ошибки...

Хотя и тут есть возможность сфейлиться. В какой-нибудь функции может быть одноимённый локальный об'ект того же класса. Он раньше перекрывался A, а теперь -- нет. В этом случае ошибки не будет.

@темы: Программирование, Лайфхак, Говнокод

23:59 

И прочие ингредиенты

Есть древняя-древняя функция puts. Она выводит на экран строку. И переводит каретку на новую.

У меня был список чисел, надо было записать в файл. При работе с текстовыми файлами я и в Си и в пхп предпочитаю олдскульный способ -- fopen/fcolse, fprintf/fscanf и прочее.

И вот я зачем-то решил воспользоваться fputs. Не знаю, зачем.

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

Это одна из плохих вещей в Си: что функции разных семейств, делающие по сути одно и то же, различаются в существенной особенности.

@темы: Программирование, Говнокод

23:55 

Критическая уязвимость CloudFlare: diary.ru тоже в опасности

CloudFlare -- это такая фирма, которая предоставляет миллионам сайтов услуги по перераспределению нагрузки к ним, защиты от ддос-атак и прочее. Многие, наверное, видели страницу "КлаудФлейр проверяет ваш браузер" или "КлаудФлейр не смог достучаться до сайта (ошибка 522)".

Так вот:
github.com/pirate/sites-using-cloudflare
Официальное сообщение:
blog.cloudflare.com/incident-report-on-memory-l...

Если коротко, то в течение последнего полугода данные с 4 миллионов сайтов, которые используют CloudFlare, потенциально могли утекать в неизвестном направлении, включая ваши пароли, сообщения и прочее. И через кэши поисковых систем часть данных ещё может быть доступна третьим лицам.

На практике это означает, что надо поменять пароли на всех сайтах, которые используют эту систему. Их список в запакованном виде занимает 22 мегабайта. Вот он, упорядоченный по алфавиту:
github.com/pirate/sites-using-cloudflare/archiv...

Обращаю внимание, что надо смотреть на точное совпадение доменов. То есть, gmai-l.com, yandex.info -- это всё левые домены, не имеющие отношения к жмейлу и яндексу.

Что важно -- так это что сайт diary.ru использует CloudFlare. С учётом написанного выше, мне кажется, очевидно, что надо сделать в связи с этим. Интересно, что администрация сайта про это ничего не сообщила.

Кроме того, это означает, что ВСЕ сведения, передаваемые вами через эти сайты в течение последнего полугода, могут оказаться в руках неопределённого круга лиц, помимо адресата, провайдера и ФСБ.

@темы: Фейлы, Программы, Говнокод

23:58 

Что посеешь, то и пожмёшь

Одним проектом я не занимался год. Потом открыл. Ох, как всё запущенно.

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

1. У переменных должны быть понятные имена. Это касается не только переменных типа _, asdbsd и Burrrrp (хотя игроку в НетХак последняя будет весьма понятна) -- тут всё ясно. Название может иметь отношение к реальной ситуации, но имеет шанс быть воспринятым неправильно. Так, у меня в программе была переменная по имени need_count. Я долго думал, что это число раз, которые надо (need) посчитать (count) что-то. Однако на самом деле эта переменна обозначало то, НАДО считать (1) или НЕ НАДО (0).

2. Отсюда вытекает второе правило -- тип переменной должен соответствовать её назначению. Переменная из п.1 имела тип int. Хотя гораздо понятнее всё было бы, если бы я сделал её типа _Bool (это было в той части проекта, которая работала на микроконтроллере, а она написана на голом Си-99, поэтому именно _Bool, а не bool). Для МК, это, правда, простительно, т.к. int будет быстрее обрабатываться в ряде случаев.
Примечание. Беззнаковые типы при совместном использовании со знаковыми таят в себе гремучую бомбу: zhz00.diary.ru/p211118163.htm

3. А третье правило -- не следует повторно использовать ту же переменную для других целей. Хотя для переменных типа x, y, i особой разницы нету, сколько раз их использовать. Но повторное использование более специальных переменных часто ведёт к нарушению п.1. То, на чём конкретно напоролся я -- WriteFile возвращает через указатель число записанных байтов. Для этого я использовал переменную с подходящим именем written. Но потом мне надо было читать данные при помощи ReadFile. Как не трудно догадаться, он тоже через указатель возвращает число байтов, но уже прочтённых. Видимо тогда я решил сэкономить четыре байта. Ну вот нафига? Да-да, именно так. Я написал --

ReadFile(hFile,size,&written,NULL);

Потом очень удивился -- зачем я после чтения проверяю число записанных байт?.. Это уже было в части программы для ПК, так что тут прощения мне нет -- под ПК экономить нечего.

Это я тогда был не в своём уме или так вырос за год, что считаю дикостью то, что сделал своими же руками?

Но кое-от-чего чего я пока избавиться не смог -- так это от применения операции ?: . Очень её люблю, хотя она сильно усложняет чтение текста.

@темы: Программирование, Очевидное-невероятное, Говнокод

23:59 

Как не надо обрабатывать ошибки

Если у вас обработка ошибок сделана через коды ошибок, то должны соблюдаться следующие правила:
1а. Код, возвращаемый из каждой ветки управления, должен быть уникальным.
ЛИБО
1б. При выводе сообщения об ошибке должен указываться не только код, но и место возникновения ошибки (файл с исходным текстом, строка; в си/пхп для этого есть __FILE__, __LINE__, а в пхп ещё и __FUNCTION__).

2а. Сообщения об ошибках должны быть уникальными.
ЛИБО
2б. В сообщения должен быть включён код ошибки.

Я же встретил следующую вещь:
switch(nError)
{
//...
case 100:
case 101:
case 102:
AfxMessageBox("Разгерметизация скафандра!");
break;
//...
}


А ошибки 100, 101 и 102 выпадали из десяти разных мест.

После этого разработчикам становится понятно, в чём дело, но становится непонятно, куда копать.

@темы: Говнокод, Программирование

22:49 

>>_<<

Коллеги из соседнего отдела прислали исходники своей проги на PHP. Начинаю их изучать.

Программа для Raspberry (Pi?). Она подаёт на 4 вывода GPIO число. То есть число разбивается на 4 бита, и каждый из битов управляет одним из проводов. Если бит равен нулю, на проводе 0 вольт, если 1 -- 3.3 вольта (или 5 вольт, не разбирался).

На, значит, разбить число на отдельные биты. Как же выделить определённый бит? Я вижу следующий текст:

$bit3=$number/8;
$bit2=($number-$bit3*8)/4;

И так далее. Это, конечно, работает, но меня сильно удивило. В PHP работают побитовые операции, включая обычные сишные >>, << и &.

@темы: Программирование, Говнокод

23:21 

На долгую память о старых друзьях

Я отлаживал программу. Программа работала на компе с подключённым прибором. Возможности поставить туда среду разработки или удалённый отладчик не было.

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

Спустя 4 дня отладки я обнаружил наконец, в чём заключалась ошибка в коде. Вот отрывок. Зависание иногда происходило где-то в нём, причём один раз на 10-20 выполнений.

bFlag=GetStatus();
fTime=0;
while(!bFlag&&fTime<0.5);
{
//do something
fTime=TimeElapsed();
bFlag=GetStatus();
}


Вписать в само условие GetStatus() по некоторым причинам было нельзя (я привожу тут текст упрощённо). Функция TimeElapsed() возвращает, сколько прошло времени с начала ожидания. Если прошло 0.5 секунды, дальше мы начинаем сушить вёсла, продолжение нормальной работы невозможно. Функция GetStatus() проверяет состояние другого потока в многопоточном приложении -- не возникло ли в нём определённое событие? Если статус равен фолс (не возникло), ждём дальше. Если тру (возникло) -- выходим. Специфика её работы такова, что вернувши один раз тру, дальше она начинает возвращать фолс -- до следующего возникновения события во втором потоке. Я долго грешил на ошибки синхронизации, тыкал палочкой в критические секции -- безрезультатно. Пока не обнаружил при помощи отладочной печати странную вещь. bFlag до цикла равно тру, а внутри цикла -- фолс. В тех случаях, когда программа не зависала. Это значит, что ГетСтатус вызывался два раза. Это невозможно, т.к. Если он равен тру, в цикл вход происходить был не должен -- условие сразу ложно! И тут я присмотрелся повнимательнее...

***


Когда я ещё не имел опыта программирования на Си, классе так в восьмом-девятом, я всё же написал одну довольно об'ёмную по тем меркам программу. Но она не работала. Это была компьютерная игра. Основной цикл был прост, как число 3. Прорисовать экран -- дать пользователю сделать ход -- если конец игры достигнут, сделать выход. И с начала. Проблема была в следующем месте:

1	while(1);
2 {
3 Draw();
4 GameMove();
5 if(GameOver())
6 break;
7 }


Это плохой стиль. Не рекомендуется делать бесконечный цикл в тех условиях, когда чётко понятно условие выхода. Это допустимо делать, когда этих условий много и они сложно комбинируются.

При отладке наблюдалась странная ситуация -- после строчки 1 программа зависала. Я нажимал "следующую строку", но она продолжала висеть на этой. Кроме всего прочего выдавался варнинг -- unreachable code на строках 3-6. Это было очень странно. Я же всё чётко написал! Строчка должна выполняться. Я искал ошибку полгода, с перерывами. Потом до меня допёрло -- в первой строке в конце стояла точка с запятой. Это и было тело цикла. Т.е. это был бесконечный пустой цикл. И он бесконечно выполнялся, не переходя к строчке 3.

Я каждый год предупреждаю об этом всех студентов. И сам, спустя много лет, попал в ту же ловушку. Но попадание туда было закономерным -- дальше по тексту был похожий цикл, но с постусловием. Очень похожим. Чтобы не писать два раза (по факту оно было несколько сложнее, чем я привожу в примере), я его просто скопировал. А в цикле с постусловием точка с запятой быть как раз должна.

Вообще первый отрывок написан очень плохо. Его надо было тоже делать с постусловием.

@темы: Программирование, Говнокод, Борьба с техникой

23:59 

Zero no Tsukaima

Студенты учат Си/Си++ по разным книжкам. Я их иногда разглядываю. Сегодня разглядывал книжу Костюкова, Калинина // Язык Си и особенности работы с ним. Разглядывал долго. Авторы -- две женщины глубоко пенсионного возраста. Само по себе это может быть и неплохо -- типа люди старой советской закалки, суровые женщины-программисты... В целом книжка мне не понравилась. Но одно место откровенно поразило:

/*Посчитаем среднее геометрическое*/
g=pow(a*b*c,(1/3));

pow стандартная функция из math.h и имеет прототип:
double pow(double a, double b);
Возвращает a в степени b.

Да я сам студентам постоянно даю задание на подсчёт того, чему будет равна 1/3. Я думаю -- может быть, это специально -- типа чтобы обратить внимание на приведение типов (а в Си 1/3 равно нулю, т.к. делится нацело -- частное 0 и 1 в остатке). Но нет -- никаких дополнительных комментариев не было.

@темы: Говнокод, Программирование, Студенты

23:57 

Десятичный сдвиг

Не подумайте, это не я писал.

double var1;
//...
// оставляем для вывода в поле только 2 знака после точки
var1 = double(int(var1*100.0))/100.0;

На самом деле это довольно остроумно -- кроме того, других методов понизить точность переменной я придумать не могу. И стандартной функции в Си не помню (кроме printf, но она даёт результат в виде текста).

Есть другие методы?

@темы: Говнокод, Программирование

23:56 

Одна стена -- огонь, другая -- лёд

В начале был цикл вайл:

n=n0;
while(cond)
{
//... что-то с участием array[n]
n--;
}

Потом планы изменились, и я переделал это в цикл фор:

for(n=0;n<n0;n++)
{
// то же самое
}

Потом думаю -- что-то цикл бесконечным стал.

ОКАЗАЛОСЬ

я забыл убрать n--;

Так он и принимал значения -- то 0, то -1.

@темы: Говнокод, Программирование

23:36 

Untitled [360]

О том, почему плохо применять одну и ту же переменную для двух разных задач.

Была одна процедура (процедура в смысле "логическое действие, которое должна выполнять программа", а не в смысле "функция в терминах языка программирования, которая не возвращает значения") и она имела, допустим, три режима:

#define MODE1 1
#define MODE2 2
#define MODE3 3

А у каждого из режимов было два подрежима:

#define MODE1A 1
#define MODE2A 2
#define MODE3A 3
#define MODE1B 4
#define MODE2B 5
#define MODE3B 6

И была переменная, которая хранила ОБА этих режима.

int nMode;

СНАЧАЛА там хранился общий режим и процедура работала с его использованием. Но, на определённом этапе (которой пришлось долго вычислять, т.к. процедура длинная) та же переменная начинала хранить уже ПОДРЕЖИМ. При этом диагностику затрудняло то, что числовые значения констант частично совпадали.

Не надо так.

P.S. Предвижу вопрос "Почему не сделали сразу просто хранение подрежимов, а на режимы бы просто не забили?". На самом деле режимов не три, а больше. А число подрежимов у каждого режима -- своё. На определённом этапе ветвление осуществляется по коду режима, а по коду подрежима -- потом. Поэтому разделение на "режим" и "подрежим" логично. Что нелогично -- так это одна переменная и под то и под другое. И что очень плохо -- что замена режима на подрежим происходила при вызове определённой функции ВНУТРИ неё, при этом замена производилась по ссылке:

void non_suspicious_name(int a, int b, int &mode)
{
mode=0;//...или что-нибудь ещё
}
//в другой функции
non_suspicious_name(val1,val2,nMode);
// теперь тут уже не режим, а подрежим

Это аргумент в пользу того, чтобы не использовать ссылки, а только указатели.

@темы: Говнокод, Очевидное-невероятное, Программирование

23:53 

Это Спарта

Дело было давно. Я просматривал код на Delphi, и мой взгляд упёрся в такую строку:

if(variable and 1=1) then // что дальше -- значения не имеет


У меня глаза по пять рублей. Боже, что это такое?! Один всегда равно один. Зачем это было писать?!

Оказалось, что у and приоритет выше, поэтому сначала выполняется variable and 1, а потом сравнивается с единицей. variable -- целая переменная, поэтому and -- побитовый. Это была проверка последнего бита.

@темы: Программирование, Говнокод

23:22 

Неизвестный источник

Я не помню, где и когда я прочитал (или услышал) следующее утверждение:
Если вы выделили память для массива или для одного элемента, лучше его всегда! удалять при помощи delete [], который сработает, даже если вы выделяли один элемент, например int *p=new int;delete []p;

Так вот, оказывается, это не так.

Выделил через new -- удаляй через delete.
Выделил через new[] -- удаляй через delete[].

Иначе поведение не определено.

@темы: Программирование, Говнокод

23:18 

wand of waiting (0:∞)


int t;
//...
while(!some_check())
{
cout << "\\|/-"[t=(t+1)%4]<<"\b";
wait();
//...
}


Вот это открытие. Оказывается работает индексация строковых констант! АААААААА!

@темы: Программирование, Говнокод

23:16 

Untitled [125]

Как нормальные люди пишут код, если им надо вызвать несколько функций подряд?

void a();
void b();
void c();
void d();

int main()
{
a();
b();
c();
d();
return 0;
}


Как пишут все остальные?

void a();
void b();
void c();
void d();

int main()
{
a();
return 0;
}

void a()
{
// тут какие-то действия
b();
}

void b()
{
// тут какие-то действия
c();
}

void c()
{
// тут какие-то действия
d();
}


@темы: Говнокод, Программирование

23:27 

Untitled [122]

class A
{
.
.
.
void type_A()
{
int t;
cout<< "Введите тип A:";
cin>>t;
switch(t)
{
case 1:
cout << "Тип абырвалг";
break;
case 2:
cout << "Тип трали-вали";
break;
default:
cout << "Ошибка";
break;
}
}
.
.
.
};


Вопрос на много: зачем был нужен этот метод в классе? Да, он спрашивает типа об'екта класса А (что правильно), но почему он никуда не девает то, что получил?

@темы: Программирование, Говнокод

22:31 

Деление без применения операции деления

22:44 

Чудо програмистской мысли

Это уже я сам придумал:

//double fE=0.0;//, fdAlfa=0.0; //, fVal=0.0;

Как это родилось? Сначала была нормальная строчка, но потом переменные оказывались не нужны и по очереди комментировались (не удалялись -- на всякий случай). В итоге что осталось... то осталось.

@темы: Говнокод

Untitled

главная