Ознакомьтесь с нашей политикой обработки персональных данных
  • ↓
  • ↑
  • ⇑
 
Записи с темой: программирование (список заголовков)
22:32 

Untitled [492]

Коллега, которому передали один из проектов, с которым раньше работал я, поставил себе PVS Studio. Статический анализатор кода. И он стал выявлять разные шняги, которые написал, как выяснилось, я (и это подтверждает SVN Blame).

Вот один из таких кусков, высранных мной в 2013 году.

int nx, ny, nresx, nresy;
char file_list[80][261];
unsigned stx[80], sty[80];
fscanf(f, "%d %d %d %d\n", &nx, &ny, &nresx, &nresy);
int nitems, nfiles;
for(nfiles = 0, nitems = 0; !feof(f); nitems++, (strcmp(file_list[nfiles], "-") && (file_list[nfiles][0] != '\0')) ? nfiles++ : 0/*иначе ничего*/)
{
fgets(file_list[nfiles], 261, f), file_list[nfiles][strlen(file_list[nfiles]) - 1] = '\0';
if(strcmp(file_list[nfiles], "-"))
{
fscanf(f, "%d %d\n", &stx[nfiles], &sty[nfiles]);
}
}

//nitems--;// |---+----\_______. КОСТЫЛЬ
//nfiles--;// |---+----/ КОСТЫЛЬ


Что тут происходит конкретно, я уже и сам толком не помню. Предлагаю попробовать догадаться самостоятельно. f -- текстовый файл, откуда что-то считывается.

Интересно тут следующее:
1. Заголовок цикла for содержит не только операцию "запятая" в инициализаторе, но и в повторяемом выражении (третья часть).
2. Первый оператор цикла также содержит в себе операцию "запятая".
3. Комментарий с костылём смешной. Но почему-то закомментирован.
4. PVS Studio ругался не на стиль, а на то, что я условное выражение в операции ?: не обернул в скобки (хотя функционал от этого не меняется, т.к. у && приоритет выше, чем у ?:).

Зачем же были нужны операции запятая в таком количестве? Она тут только ухудшает читаемость. У меня только одна гипотеза. Изначально в теле цикла был только первый оператор. Когда ситуация усложнилась, я не захотел вводить новый блок, поэтому использовал всевозможные извращения, ТОЛЬКО БЫ обойтись одним оператором в теле цикла. Но в итоге сдался. А то, что уже запихнул в заголовок и в операцию "запятая", доставать обратно не стал. Эх...

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

23:58 

Как я позвонил себе на жёсткий диск

Не буду вдаваться в технические детали. Скажу коротко -- прежде, чем записать во флеш-память внутри микроконтроллера, её надо стереть.

В контроллерах STM32 для этого есть специальная операция. Надо в специальный регистр записать не менее специальное значение. И ещё -- номер сектора для стирания. Секторов штук 10-15 (в разных моделях по разному).

Я стирал два сектора -- номер 10 и номер 11. По очереди.

Управляющий регистр 32-битный, там ещё куча других настроек. Трогать я их не хотел. Поэтому я воспользовался побитовым "или":

FLASH->CR|=sector<<3;//установить номер сектора: биты 3-6.

И тут я заметил, что когда я стираю сектор 10, всё хорошо. Сектор 11 -- всё хорошо. Но когда я после этого опять стираю 10 -- происходит ошибка стирания. Нет, все статусные регистры хороши, но когда я де-факто смотрю в содержимое памяти, оказывается, что она не стирается! Долго прохожу с отладчиком, грешу на недостаточное напряжение питания, на закончившиеся циклы перезаписи (хотя 10000 израсходовать не так просто).

Потом начинаю уже параноидально проверять содержимое всех управляющих и статусных регистров флеш-модуля после каждой строчки. И обнаруживаю, что стирание происходит у сектора 11, а не 10! То есть, я всё время стирал не тот сектор?!

Т.е. я передаю в регистр сектор 10, а в регистре указано:

ХХХХХХХХ ХХХХХХХ ХХХХХХХХ Х0011ХХХ

(как вы догадались, номера секторов выше были в двоичной системе счисления)

Тут я понял, что виной всему было побитовое логическое или. 1|0 будет 1. То есть после записи сектора 11 (3) в регистр, запись 10 (2) поверх него давала опять 11 (3).

Т.е. мне надо было не просто НАБИТЬ ЕДИНИЦАМИ нужные поля, а набить конкретными значениями, включая нули. Побитовое "или" тут уже не подходит. В итоге я решил проблему так:

FLASH->CR=(FLASH->CR&0xFFFFFF00)|(sector<<3);//ценные биты были только в старших трёх байтах

А ведь в ассемблере для этого есть специальная команда -- BFI.

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

23:56 

Докажи, что ты не верблюд

Есть два измерительных модуля в одном из приборов, которые мы делаем -- Module1 и Module2. Меряют разные характеристики. Один давление, другой температуру.

Разные экземпляры прибора могут быть укомплектованы разным набором модулей. И вот данный конкретный экземпляр имел только Module2. И его эксплуатировали 4 месяца не включая этот модуль. Но понадобилось включить. И оказалось, что модуль не работает.

Начинаю отлаживать ПО. И вижу картину маслом:

void InitModule2()
{
if(Settings.bModule1Installed==false)
return;
//далее инициализация модуля2
}


То есть: если в приборе НЕ установлен модуль 1 (а он и не был установлен), прекратить инициализацию модуля 2! Модули более-менее независимы. Это меня удивило. Я сделал svn blame. Эта команда позволяет установить, кто является автором каждой строчки кода (и у неё есть синоним -- svn praise, лол). В результате этого я выяснил, что данную строчку написал начальник. Как раз 4 месяца назад. И тогда же обновляли ПО на данном приборе.

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

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

23:59 

Адресная книга

Обнаружил у себя в закромах родины собственноручно написанный эмулятор калькулятора МК-52 под калькулятор же МК-85. В виде листинга на пяти листах. Написанного от руки.

И вспомнил, как я его писал. Ещё в школе.

МК-52 -- программируемый советский микрокалькулятор. Он программируется в шестнадцатеричных кодах. Для их ввода можно воспользоваться клавиатурой, на которой написаны разные действия. Это помогает вводить программу и записывать на бумагу. На бумаге пишут, к примеру:

1 2 В↑ + С/П

Это значит, сложить 1+2 и закончить программу.

А в памяти (и встроенном просмотрщике программы) это отображается так:

50 10 0E 02 01 (читать надо справа налево)

МК-85 более крутой, он поддерживает Бейсик. Писать эмулятор на Бейсике извращение ещё то. Нет, место, где хранить программу в кодах -- есть. Вопрос, как её выполнять?

Допустим, в переменной D хранится код очередной операции. И её надо выполнить. Что же делать?

Логика подсказывает простой дедовский способ:

100 IF D=0 THEN ...
101 IF D=1 THEN ...

И тут меня ждала засада. В двух частях:
1. В МК-85М всего 5317 байт (!) памяти. Да, бейсик представляется в байт-коде, т.е. каждое ключевое слово идёт как 1 байт. Но всё равно памяти мало.
2. Скорость выполнения программ будет просто умопомрачительной, т.к. чтобы выбрать правильное действие, надо пробежать в среднем по половине проверок. А работает калькулятор так, что добавка даже 2-3 строчек, выполняющих простые операции -- заметна.

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

Потом я прочитал интересную вещь. Оказывается в Бейсике МК-85 команды GOTO и GOSUB (подпрограмма) могли принимать не только число. Но и переменную. И даже выражение.

Это и был ключ к тому, как всё уместить в 5317 байт.

100 GOSUB D+1000
//...
1000 (действие1):RETURN
1001 (действие2):RETURN
//и так далее для всех ~200 кодов операций

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

23:58 

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

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

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

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

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

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

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

23:58 

Парадокс близнецов

Отлаживал программу при помощи отладочных сообщений. То есть, сообщения по ходу выполнения программы пишутся в лог-файл.

Время на часах -- 14:00. Выполняю в программе операцию, которую хотел проверить. Захожу в лог -- а там последняя запись -- 13:52. Что такое?! Как так?! Проделал ещё раз операцию -- то же.

Потом меня стали терзать смутные сомнения. Я отодвинул окошко, загораживающее часы в трее.

13:53.

Работал на целевом компьютере я по удалёнке. И смотрел время по часам основного компа. Часы целевого компа отставали на 8 минут.

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

23:59 

То лапы ломит, то хвост отваливается

Перевели один из проектов из-под MSVS 2008 под MSVS 2015. Экзешник вырос в два раза и перестал работать под Windows XP! (наше ПО эксплуатируется в т.ч. на машинах с XP)

Потом, правда, оказалось, что есть специальный набор legacy-библиотек.

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

23:58 

Лакуна

Смотрю, пакеты от устройства приходят не целиком (это как раз то, из-за чего я делал вчерашние дампы). Конкретно -- отсутствует начало. А иногда вообще ответ не приходит. Причём "недостача" возникает только в начале процесса обмена. Если первый пакет пришёл нормально, все остальные тоже приходят нормально.

Оказалось, я делал вот что: сначала я посылал запрос к устройству, а потом запускал отдельный поток для считывания данных.

Однако поток при старте очищал входной буфер виртуального COM-порта! На всякий случай -- вдруг там мусор какой от предыдущих передач остался?

Если мне везло, то устройство не успевало ответить до очистки буфера, и ошибка не возникала. Поскольку очистка производилась только при старте потока, дальнейшие запросы и ответы воспринимались нормально. Если же мне не везло, то часть ответа (или даже весь ответ) оказывалась стёрта.

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

23:59 

signed char и шестнадцатеричное представление

Допустим, мы хотим увидеть char в шестнадцатеричном виде при помощи printf или аналогичной функции. Это бывает нужно для организации логирования дампов двоичных массивов при приёме/передаче данных, скажем.

Нас ждёт разочарование. char по умолчанию signed. Спецификатор для шестнадцатеричных чисел -- %x. Но он работает с числами размером с int. signed char будет преобразован в unsigned int, и все старшие биты будут забиты единичками из-за дополнительного кода. То есть, вместо 0x2F мы увидим 0xFFFFFF2F (при форматное строке "0x%02x").

Что же делать?
1. Забыть про массивы char. Использовать либо unsigned char, либо новомодный (лет пять-десять как) uint8_t.
2. Использовать двойное преобразование типов. Это лол. Я не ожидал, что это сработает:

printf( "0x%02x ",(unsigned int)(unsigned char)buffer[offset]);

@темы: Программирование

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 

Вавилонская башня

Микроконтроллер выдает HardFault (аппаратная ошибка). После какой конкретно строчки программы он туда вываливается по техническим причинам выследить невозможно.

Смотрю -- происходит это по адресу 0x1800 0000. Там как раз заканчивается настоящая оперативная память и начинает пустое адресное пространство. Ставлю точку останова по адресу 0x17FF FFFA -- недалеко от конца. Но она не срабатывает. Вывод -- в этот адрес упирается не программа, а данные.

Нахожу в коде следующий текст:

char out_buf[200];
char temp_buf[20];
int x;
int a,b,r[10];
// ---

//sprintf(out_buf,"%d\n",a);
sprintf(temp_buf,"%d\n",b);
strcat(out_buf,temp_buf);
for(x=0;x<10;x++)
{
sprintf(temp_buf,"%d\n",r[x]);//напечатать очередной кусок текста в строку
strcat(out_buf,temp_buf);//добавить к основной результирующей строке
}
//...


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

sprintf печатает в строку, strcat сшивает (конкатенирует) две строки.

Данный кусок выполняется в основном бесконечном цикле прошивки.
Первый вызов sprintf закомментирован. Он попал под раздачу, т.к. отвечал за одну из функций прошивки, которая была больше не нужна. Вместо её удаления, я её закомментировал. И правильно сделал. Благодаря этому я легко смог опознать, в чём же было дело.

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

Решением в данном случае было дописать после закомментированной строки:

out_buf[0]='\0';

Тогда заполнение стало происходить с нуля каждый раз.

P.S. Только что подумал, что можно оптимизировать, сделав:

sprintf(out_buf+strlen(out_buf),"%d\n",r[x]);

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

23:59 

Вас здесь не стояло

При входе в main() микроконтроллерной программы:
а) МК вылетает в исключение HardFault, если запустить программу на автоматическое выполнение до брейкпоинта.
б) если выполнять пошагово до него же, то всё работает.
в) после каждой строчки начиная с первой выдаётся предупреждение, что указатель стека вне допустимого диапазона.

Я изучил в дизассемблере пролог функции main(), т.е. в промежуток между её началом и первой командой:

int main()
{
// <- тут пролог... его не видно, потому что его делает компилятор.
func1();
//...
return 0;
}


Обнаружил там странную команду:

SUB.W SP, #7306

То есть, вычесть из указателя стека семь тысяч с гаком. А стека было всего две тысячи. То есть, программа не работала потому, что новый указатель стека выходил за пределы стека, о чём и выдавалось предупреждение. Но это ещё не всё -- указатель оказывался В ОБЛАСТИ КОДА и переписывал его данными на ходу. Это МК, тут нету DEP. Этим и был вызван HardFault.

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

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

23:59 

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

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

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

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

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

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

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

23:59 

Кратчайшее руководство по отладке при помощи дампов памяти

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

Сегодня я научился использовать дампы памяти в целях отладки. In a nutshell:

0. Отладка с помощью дампа используется, когда возможностей отладочной печати и удалённой отладки недостаточно (у меня удалённая отладка вообще не работала).
1. Не используйте команду "создать дамп" в диспетчере задач (или как там он сейчас называется). Дамп будет только частичный.
2. Качайте программу ProcDump.exe от Sysinternals и печально известного Марка Руссиновича.
3. В командной строке пишите ProcDump.exe -pa image.exe, где image.exe -- имя программы, дамп которой создаёте (желательно запускать отладочную версию). Есть ещё много параметров -- можно делать дампы при зависании, при превышении использования памяти/процессора, при падении программы и пр. Данная выше команда сделает дамп при её (команды) исполнении.
4. Перекачайте дамп на комп, где стоит вижуал студия. Дамп может занимать больше 100 мегабайт.
5. Откройте его. Для полноценной работы надо подгрузить символы (symbol, PDB-file). Для этого есть пункт в меню либо гиперссылка (зависит от версии).
6. Жмёте "запуск" (гиперссылка или пункт меню с зелёной стрелкой).
7. ???
8. ВУАЛЯ! Вы видите текущую точку работы на время создания дампа. Можно посмотреть все переменные. В окошечке "потоки" можно посмотреть точки выполнения всех потоков. Также доступны все стеки вызовов и все переменные в функциях более высоких уровней. Исполнять программу дальше, к сожалению, нельзя.

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

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 

Босохождение

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

@темы: Викторика, Программирование, Случай из жизни

23:59 

Стой там, иди сюда

Посылаю данные через последовательный интерфейс (SPI) из контроллера. Для тестовых целей посылаю по очереди 0xFFFFFF и 0xAAAAAA. По системе:

for(;;)
{
SendData(0xFFFFFF);
Pause(1);//us
SendData(0xAAAAAA);
Pause(1);//us
}


Но на осциллографе вижу какую-то хрень: вроде данные видны, но через несколько тактовых импульсов (а там есть вторая линия, где вместо данных -- тактовые импульсы) происходит сбивка длительности импульса (т.е. они все должны быть одинаковой длины, а очередной импульс оказывается другой длины). И данные совсем не те. А число импульсов... ОДИННАДЦАТЬ (а должно быть 24, я так задал). Вообще ни в какие ворота не лезет.

ОКАЗАЛОСЬ, что блок контроллера, отвечающий за SPI занимается посылкой данных независимо, т.е. как только я данные ему отправил, он сразу возвращает управление. А функция SendData не ждёт, пока завершится передача. А 11 импульсов проходит за время паузы как раз. И блок SPI бросает все дела и начинает посылать следующие данные, недопослав предыдущие.

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

23:59 

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

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

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

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


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

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

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

23:53 

Добровольно-принудительно


Необязательная галочка, которую нельзя сбросить! И это не вижал студио экспресс.

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

Untitled

главная