Russian Qt Forum

Qt => Общие вопросы => Тема начата: Majestio от Июль 05, 2013, 13:17



Название: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: 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();

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

(http://i48.fastpic.ru/big/2013/0705/cf/0ca883d1fef68f7b3b1661da3d3d85cf.png)

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

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

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

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

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

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

Вощем буду рад любым идеям   ???


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: Авварон от Июль 05, 2013, 19:46

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

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


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: navrocky от Июль 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;

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


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: navrocky от Июль 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");


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: kamre от Июль 08, 2013, 16:42
А теперь, предположим, у клиента старая программа и он вам присылает лог программы, а в новом коде уже и таких файлов нет, а может строки уже хорошо сместились.
Разве сложно достать из контроля версий по тэгу или ревизии из лога нужную версию исходников, которая соответствует той сборке, что была у клиента?


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: panAlexey от Июль 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


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: Авварон от Июль 08, 2013, 22:13
Жесть какая, гуиды в коде...

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


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: serg_hd от Июль 17, 2013, 16:38
https://code.google.com/p/google-breakpad/wiki/GettingStartedWithBreakpad
при падении вашей проги выдаст всё что надо знать об ошибке


Название: Re: Qt & C++ :: Обработка ошибок и отладка сложных проектов
Отправлено: kambala от Июль 17, 2013, 17:47
интересная штука, спасибо