Различные статьи по Qt и c++

Частые ошибки новичков при работе с файлами, JSON и потоками в Qt

Введение

В заключительной статье цикла собрана подборка самых распространённых ошибок, с которыми сталкиваются начинающие Qt-разработчики при работе с темами, разобранными в этом цикле — файлами, JSON и многопоточностью. Это не новый материал, а концентрированный список того, на что стоит обратить особое внимание, опираясь на материал предыдущих девяти статей.

Концепция

Большинство ошибок новичков попадают в одну из нескольких категорий: забытая проверка результата операции (файл не открылся, JSON не распарсился — и код продолжает работу, как если бы всё прошло успешно), путаница между похожими, но различающимися по смыслу режимами или методами (WriteOnly vs Append, run() vs start()), и попытка обратиться к интерфейсу из другого потока напрямую, без сигналов. Зная эти категории заранее, проще узнавать соответствующие симптомы при отладке собственного кода.

Пример кода: Частые ошибки новичков

// ОШИБКА 1: отсутствие проверки результата open()
void mistakeNoOpenCheck()
{
    QFile file("data.txt");
    file.open(QIODevice::ReadOnly); // результат не проверяется!
    QTextStream in(&file);
    qDebug() << in.readAll(); // если файл не открылся — выведет просто пустую строку
}

// ИСПРАВЛЕНО
void fixedOpenCheck()
{
    QFile file("data.txt");
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Ошибка открытия файла:" << file.errorString();
        return;
    }
    QTextStream in(&file);
    qDebug() << in.readAll();
}

// ОШИБКА 2: WriteOnly вместо Append стирает существующее содержимое
void mistakeWriteInsteadOfAppend()
{
    QFile file("log.txt");
    file.open(QIODevice::WriteOnly | QIODevice::Text); // стирает весь файл!
    QTextStream(&file) << "Новая запись в лог\n";
}

// ИСПРАВЛЕНО
void fixedAppend()
{
    QFile file("log.txt");
    file.open(QIODevice::Append | QIODevice::Text); // добавляет в конец
    QTextStream(&file) << "Новая запись в лог\n";
}

// ОШИБКА 3: отсутствие проверки ошибки разбора JSON
void mistakeNoJsonErrorCheck(const QByteArray &data)
{
    const QJsonDocument doc = QJsonDocument::fromJson(data); // ошибка не проверяется
    const QJsonObject obj = doc.object(); // может быть пустым объектом без предупреждения
    qDebug() << obj.value("name").toString(); // тихо выведет пустую строку
}

// ИСПРАВЛЕНО
void fixedJsonErrorCheck(const QByteArray &data)
{
    QJsonParseError error;
    const QJsonDocument doc = QJsonDocument::fromJson(data, &error);
    if (error.error != QJsonParseError::NoError) {
        qDebug() << "Некорректный JSON:" << error.errorString();
        return;
    }
    qDebug() << doc.object().value("name").toString();
}

// ОШИБКА 4: вызов run() вместо start() — поток не создаётся, код выполняется синхронно
class Worker : public QThread
{
public:
    void run() override { qDebug() << "Долгая операция выполняется"; }
};

void mistakeCallingRunDirectly()
{
    Worker worker;
    worker.run(); // выполнится в текущем потоке, без какого-либо параллелизма!
}

void fixedCallingStart()
{
    auto *worker = new Worker();
    worker->start(); // правильно: запускает run() в новом потоке
    QObject::connect(worker, &QThread::finished, worker, &QObject::deleteLater);
}

Пояснения к коду

Все четыре пары примеров построены по одной схеме: сначала показан проблемный, но «тихо работающий» (без явного краша) код, а затем — исправленная версия. Это намеренно: большинство ошибок новичков в Qt не приводят к немедленному краху программы, а тихо дают неверный результат (пустая строка, нестёртый или, наоборот, стёртый файл, отсутствие параллелизма), что значительно усложняет их обнаружение без целенаправленной проверки кода возврата на каждом шаге.

Объединяющий принцип всех исправлений — явная проверка результата каждой потенциально проблемной операции (open(), fromJson()) и точное понимание разницы между похожими по названию, но различающимися по поведению вызовами (WriteOnly/Append, run()/start()) — это именно те детали, которые легко упустить при первом знакомстве с API, но которые быстро становятся второй натурой с опытом.

Подводные камни

  • Привычка считать, что «если программа не крашится — значит, всё работает правильно». Большинство описанных в этой статье ошибок именно такие: программа продолжает выполняться без исключений и крашей, но даёт неверный, неполный или просто отсутствующий результат — привычка проверять не только «не упало ли», но и «действительно ли получен ожидаемый результат», экономит много времени на отладке в будущем.
  • Копирование кода из непроверенных источников (форумы, старые статьи) без понимания, что именно делает каждая строка. Особенно это касается флагов открытия файлов и режимов доступа — если код скопирован «как есть» и в нём используется WriteOnly там, где автор статьи имел в виду другую ситуацию, отличную от вашей конкретной задачи, результат будет неожиданным именно для вашего случая использования.
  • Игнорирование документации Qt в пользу догадок о поведении метода по его названию. Например, можно интуитивно предположить, что QFile::exists() — метод экземпляра, тогда как это статический метод класса (QFile::exists("path"), а не file.exists() для уже созданного, но возможно не открытого объекта) — такие тонкости быстрее проверяются прямо в документации, чем угадываются.
  • Тестирование кода только на «счастливом пути» (happy path) без проверки поведения при ошибках. Код, который корректно читает существующий, правильно сформированный файл, может вести себя совершенно непредсказуемо при отсутствующем файле, повреждённом JSON или недоступной сети — целенаправленное тестирование именно ошибочных сценариев (что произойдёт, если файла нет? если JSON битый? если сервер недоступен?) на раннем этапе разработки экономит много времени по сравнению с обнаружением этих проблем уже на стороне пользователей.