Russian Qt Forum
Ноябрь 22, 2024, 16:15 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Qt & C++ :: Обработка ошибок и отладка сложных проектов  (Прочитано 11443 раз)
Majestio
Гость
« : Июль 05, 2013, 13:17 »

Вобщем вопрос встал совсем уж конкретно. Суть в следующем:

В простейших программах написал проверку, вывел сообщение, если есть возможность обработать, обработал. Все понятно и прозрачно. Но когда программа вырастает в достаточно большой проект, когда стек вызова вырастает до 10-15 вложенных процедур (а при этом количество созданных экземпляров классов от 50), начинает съезжать крыша. Ну хорошо, программу худо бедно assert'ами и qDebug'ами отладил. Дальше начинается ввод в эксплуатацию, и там явно будут ошибки. Сообщение "Ошибка при работе с базой данных" не скажет ровным счетом ничего.

Далее опишу моменты, которые у меня сейчас встречаются, и которые я обрабатываю "как умею"

Вопрос 1. Поиск ошибок в коде

Использую __FILE__ и __LINE__ . Они указывают место в коде. Но есть "но". См "псевдо-код":

1) если (!процедура_удаления_старых_данных) вывести уведомление

2) процедура_удаления_старых_данных
{
   2.1) открыть транзакцию
   2.2) заблокировать на update нужные записи
   2.3) удалить записи
   2.4) закоммитить транзакцию
   2.5) записать изменения в служебную таблицу-лог
}

Допустим на шаге 2.3 рвется связь с базой. Естественно БД самостоятельно откатывает транзакцию. Хорошо, мы отработали проверку и вывели ошибку - "ошибка удаления записей". Вопрос - в каком объекте, удаление каких данных, и пр. В идеале бы хотелось мы видеть весть стек вызова со всеми параметрами.

Пока решил примитивным способом - вывел все  операции с базой в класс с глобальной видимостью. В классе определил QString Error. Если метод класса отрабатывает с ошибкой, он в эту переменную пишет подробности. В коде использую макросы:

Код
C++ (Qt)
#define GuiCheckContinue() if (Global.Error!="") { QMessageBox::warning(0, QObject::tr("Warning!"), Global.Error + QObject::tr("<hr>File: \"")+QFileInfo(__FILE__).fileName()+QObject::tr("\"<BR>Line: ")+QString::number(__LINE__)); qCritical() << Global.Error + QObject::tr(" File: \"")+QFileInfo(__FILE__).fileName()+QObject::tr("\" Line: ")+QString::number(__LINE__); Global.Error="";}
#define GuiCheckReturn() if (Global.Error!="") { QMessageBox::warning(0, QObject::tr("Warning!"), Global.Error + QObject::tr("<hr>File: \"")+QFileInfo(__FILE__).fileName()+QObject::tr("\"<BR>Line: ")+QString::number(__LINE__)); qCritical() << Global.Error + QObject::tr(" File: \"")+QFileInfo(__FILE__).fileName()+QObject::tr("\" Line: ")+QString::number(__LINE__); Global.Error=""; return; }
 

В коде это выглядит примерно так:

Код:
if (!Global.DataQuery.exec()) { 
  GuiSqlErrorData("Ошибка при работе с БД!");
  Global.RollbackTransaction();
  GuiCheckContinue();
  return;
}
Global.CommitTransaction();                     GuiCheckReturn();
Global.DelLocker("Rubricator",Num);             GuiCheckReturn();
Global.SetOperation("Rubricator",Num,3,"");     GuiCheckReturn();
Global.Notify("Rubricator","*");                GuiCheckReturn();

Выводится в случае ошибки нечто, типа:



Вобщем меня это как-то не устраивает. Какие есть мысли?

Вопрос 2. Обработка отката занятых ресурсов в случае ошибки

Не знаю почему, но в Qt почему-то не принято пользоваться исключениями.
Без них данный вопрос вообще выглядит каким-то монстром.
В приведенном выше коде, как вы заметите - с этим явно не чисто.
Я пока еще в раздумиях.

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

1) Открыли транзакцию
2) Заблокировали запись
3) Занесли информацию с служебную таблицу - кто заблокировал и на какую операцию
4) Отредактировали запись
5) Закоммитили транзакцию
6) Удалили инфу о вашей блокировке
7) Записали в журнал операций что вы сделали с записью

На шагах 1-2-4-5 используется первое соединение с базой, с 3-6-7 второе (назовем служебное). Чтобы "служебные" операции не попадали в транзакционный блок. Теперь представим случай, на шаге 5 связь с СУБД рвется, коммит не прошел. Что делать дальше?

Вощем буду рад любым идеям   Непонимающий
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #1 : Июль 05, 2013, 19:46 »


Не знаю почему, но в Qt почему-то не принято пользоваться исключениями.

Исключения в Qt не используются по историческим причинам - когда АПИ появлялось, поддержка исключений в компиляторах была не полной.
Но внутри какой-то отлов исключений присутствует - с большой вероятностью, выброшенное исключение ничего не поломает.
Так что никто не мешает их использовать в вашем коде.
Другое дело, сможете ли вы написать код, безопасно работающий, не оставляющий приложение в не пойми каком состоянии?Улыбающийся
Записан
navrocky
Гипер активный житель
*****
Offline Offline

Сообщений: 817


Погроммист


Просмотр профиля
« Ответ #2 : Июль 08, 2013, 14:27 »

Исключения можешь использовать в своем коде, но не пропускать прохождение исключения через qt-шные недра и код сторонних библиотек. К примеру, код внутри слота надо обернуть в try/catch, иначе программа может грохнуться.

На самом деле, qt может быть собран с поддержкой исключений, тогда можно пулять исключения и в слотах. Но тогда тебе надо таскать с собой свою сборку qt, т.к. в некоторых ОС qt собирают без поддержки исключений и там приложение тупо вылетает.

А для crash report существуют готовые решения, google breakpad, например.

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

А теперь, предположим, у клиента старая программа и он вам присылает лог программы, а в новом коде уже и таких файлов нет, а может строки уже хорошо сместились.

Я для себя сделал простейший генератор гуидов и растыкиваю по коду именно эти гуиды + сообщение (опционально). Гуиды позволяют почти со 100% точностью найти нужный кусок кода куда бы он не переместился.
Также гуиды позволяют построить словарь, с расшифровкой ошибки, в который можно будет вносить внятные сообщения пользователю для каких-то "популярных" ошибок.
Еще один плюс гуида - можно очень быстро найти это место в проекте (поиск по исходникам в IDE).

Я для себя сделал маленький скриптец генерации гуида вида "<91dd34f1>" на пистоне:
Код
Python
import uuid
import sys
sys.stdout.write('<%08x>' % (hash(uuid.uuid4()) & 0xffffffff))
 
В QtCreator добавил его в External Tools с вызовом по горячей кнопке. Очень удобно.

Код
C++ (Qt)
qWarning() << "<f03ba823> Telemetry parsing: Pts" << curPts << " not in range" << telemetryRange.start << telemetryRange.finish;

Надеюсь кому-то это пригодится. Спасибо за внимание.
Записан

Гугль в помощь
navrocky
Гипер активный житель
*****
Offline Offline

Сообщений: 817


Погроммист


Просмотр профиля
« Ответ #3 : Июль 08, 2013, 14:39 »

По поводу сбора стека вызовов есть идея оборачивать код в критических местах в try/catch, в котором накапливать строку стека вручную и отпускать исключение дальше. Для облегчения можно наваять макрос.
Код
C++ (Qt)
try{
   // function body
   throw std::runtime_error("<3c36be6c> Invalid password");
}
STACK_TRACE("<43c25203> MyFunc");

Если каждый уровень пометить гуидом  Улыбающийся, тогда можно для определенных "снимков" вида "<43c25203>,<3c36be6c>,<f43cbce8>,<0c3e5a79>,<1ac698c4>" сформировать внятные сообщения.

Грубо говоря, примерно так:
Код
C++ (Qt)
if (stack == "<43c25203>,<3c36be6c>,<f43cbce8>,<0c3e5a79>,<1ac698c4>")
   showMessage("Authorization failed, check your password");
Записан

Гугль в помощь
kamre
Частый гость
***
Offline Offline

Сообщений: 233


Просмотр профиля
« Ответ #4 : Июль 08, 2013, 16:42 »

А теперь, предположим, у клиента старая программа и он вам присылает лог программы, а в новом коде уже и таких файлов нет, а может строки уже хорошо сместились.
Разве сложно достать из контроля версий по тэгу или ревизии из лога нужную версию исходников, которая соответствует той сборке, что была у клиента?
Записан
panAlexey
Гипер активный житель
*****
Offline Offline

Сообщений: 864

Акцио ЗАРПЛАТА!!!!! :(


Просмотр профиля
« Ответ #5 : Июль 08, 2013, 17:58 »

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

А теперь, предположим, у клиента старая программа и он вам присылает лог программы, а в новом коде уже и таких файлов нет, а может строки уже хорошо сместились.

Я для себя сделал простейший генератор гуидов и растыкиваю по коду именно эти гуиды + сообщение (опционально). Гуиды позволяют почти со 100% точностью найти нужный кусок кода куда бы он не переместился.
Использую такую-же технику в 1С с 2008 года, но пользуюсь не гуидами, а текущей датой в формате ГГГГММДДММСС: "[201303141439]"
написал скриптик на VBS, который вызываю прямо в редакторе:

Код:
function glFrmDateTime( psDate )
str = CStr(Year(psDate))
str = str + glFrmNum ( Month(psDate), 2 )
str = str + glFrmNum ( Day(psDate), 2 )+"-"
str = str + glFrmNum ( Hour(psDate), 2 )
str = str + glFrmNum ( Minute(psDate), 2 )
glFrmDateTime = str
end function
' Скрипт, работающий в паре с плагином "Телепат"
' и обрабатывающий события от него
' разместите в bin\config\scripts

' Обработка события "Вставка шаблона".
' Возникает при вставке шаблона перед обработкой его 1С.
' Позволяет изменить текст замены, скорректировав его по ситуации.
' name - имя вставляемого шаблона.
' text - текст замены шаблона. Можно изменить его.
' cancel - флаг отмены. При установке а True вставка шаблона отменяется.
'
Sub Telepat_OnTemplate(Name, Text, Cancel)
    Select Case Name
        Case "дата_время_для_опознания_точки"
            Text = "["+glFrmDateTime( Now())+"]"
        Case "фы"
            Text = "as"
        Case "ФТВ"
            Text = "AND"
        Case "шт"
            Text = "IN"
        Case "сегодня"
            Text = "//Сегодня" & Trim(CStr(Now()))
            Cancel = False
    End Select
txt1 = "дата_время_для_опознания_точки"
if inStr(1,Text,txt1) > 0 Then
Text = Replace(Text,txt1,"["+glFrmDateTime2( Now())+"]")
End If
End Sub
Записан

Win Xp SP-2, Qt4.3.4/MinGW. http://trdm.1gb.ru/
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #6 : Июль 08, 2013, 22:13 »

Жесть какая, гуиды в коде...

Касательно стек трейса - я в свое время писал класс исключения, который умеет получать текущий стектрейс. В никсах работает даже в релизе (если апп не стрипнутый), а вот в винде - нет. Но, если поиграться с внешней отладочной инфой символами, может и можно что-то придумать.
Записан
serg_hd
Хакер
*****
Offline Offline

Сообщений: 668



Просмотр профиля
« Ответ #7 : Июль 17, 2013, 16:38 »

https://code.google.com/p/google-breakpad/wiki/GettingStartedWithBreakpad
при падении вашей проги выдаст всё что надо знать об ошибке
Записан

kubuntu/Win7/x64/NetBeans
kambala
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4747



Просмотр профиля WWW
« Ответ #8 : Июль 17, 2013, 17:47 »

интересная штука, спасибо
Записан

Изучением C++ вымощена дорога в Qt.

UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you. © Matt Gallagher
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.154 секунд. Запросов: 23.