你好。我正在编写一个用于处理笔记的小程序。现在已经实现了最低限度的功能:可以创建、编辑和删除注释。对于 GUI,我有 Qt,基础是 SQLite。一般来说,一切正常,做我想做的事。但是我的编程经验趋于零,所以我怀疑有很多缺点我无法修复,因为我不知道去哪里找。我希望您指出所有明显的错误/问题/缺点。对从划分到类的逻辑到可能的内存泄漏的一切都感兴趣。
现在基本上有 12 个类:
- 4 个窗口:
MattyNotesMainWindow,MattySettingsDialog,addNoteDialog,MattyMessageBox - 使用数据库的类
DbManager - 用于编写 SQL 查询的类
QueryConstructor - 注解类本身
MattyNote - 用于可视化显示注释的构造函数类
MattyGroupBox - 排序和显示笔记的类
NoteHolder - css控制类
MattyStyleSheetEditor - 用于存储字符串和字符的类
Constants - 手表
MattyClocks
我知道不太可能有人会校对所有代码,因此也许您可以查看类及其方法并说出哪些是多余的,或者查看任何一个类并指出其中的缺陷。
例如连接数据库、编辑已有笔记、从数据库中删除所有笔记等功能:
bool DbManager::connect(const QString & path)
{
MattyNotesDb = QSqlDatabase::addDatabase("QSQLITE");
MattyNotesDb.setDatabaseName(path);
if(QFile::exists(path))
{
if (!MattyNotesDb.open())
{
showIsNotOpenedError();
MattyNotesDb.close();
return false;
}
else
{
PathToDb = MattyNotesDb.databaseName();
}
return true;
}
else
{
return false;
}
}
bool DbManager::editNote(MattyNote & Note, int NoteId)
{
if (connected())
{
QueryConstructor Edit;
Edit.setTableName(QStringLiteral("Notes"));
Edit.addWhereFieldValue(QStringLiteral("NoteId"), QString::number(NoteId));
QMap<QString, QString> NoteTemp;
NoteTemp["NoteTitle"] = "\'" + Note.getTitle() + "\'";
NoteTemp["NoteType"] = "\'" + Note.getType() + "\'";
NoteTemp["NoteText"] = "\'" + Note.getText() + "\'";
NoteTemp["EventTime"] = "\'" + Note.getEventTime() + "\'";
NoteTemp["EventDate"] = "\'" + Note.getEventDate() + "\'";
NoteTemp["CrTime"] = "\'" + Note.getCrTime() + "\'";
NoteTemp["CrDate"] = "\'" + Note.getCrDate() + "\'";
NoteTemp["TypeId"] = QString::number(Note.getTypeId());
Edit.setWhatToSetFieldValue(NoteTemp); //
QSqlQuery editNoteQuery;
return editNoteQuery.exec(Edit.constructUpdateQuery());
}
else
{
return false;
}
}
QVector<MattyNoteRow> DbManager::showNotes()
{
if (connected())
{
QVector<MattyNoteRow> VectorOfNoteRows;
QueryConstructor SelectAllNotes;
SelectAllNotes.setTableName(QStringLiteral("Notes"));
SelectAllNotes.setOrderByClause("NoteId", Descending);
QSqlQuery getAllNotesQuery(MattyNotesDb);
if( getAllNotesQuery.exec(SelectAllNotes.constructSelectQuery()))
{
while (getAllNotesQuery.next())
{
MattyNoteRow Row;
Row.NoteId=getAllNotesQuery.value("NoteId").toInt();
Row.NoteTitle=getAllNotesQuery.value("NoteTitle").toString();
Row.NoteType=getAllNotesQuery.value("NoteType").toString();
Row.NoteText=getAllNotesQuery.value("NoteText").toString();
Row.EventTime=getAllNotesQuery.value("EventTime").toString();
Row.EventDate=getAllNotesQuery.value("EventDate").toString();
Row.CrTime=getAllNotesQuery.value("CrTime").toString();
Row.CrDate=getAllNotesQuery.value("CrDate").toString();
Row.TypeId=getAllNotesQuery.value("TypeId").toInt();
VectorOfNoteRows.push_back(Row);
}
}
else
{
QMessageBox::critical(NULL, QObject::tr("Error"), getAllNotesQuery.lastError().text());
}
return VectorOfNoteRows;
}
else
{
return QVector<MattyNoteRow>();
}
}
将注释发送到表单:
void NoteHolder::publishNotes(QWidget* ParentWidget)
{
erasePublishedNotes(ParentWidget);
getAllNotes();
QVector<class MattyNote>::iterator NoteNumber;
int i;
for (NoteNumber = ListOfAllNotes.begin(), i=0; NoteNumber < ListOfAllNotes.end();NoteNumber++, i++)
{
MattyGroupBox* MyGroupBox = new MattyGroupBox(*NoteNumber, ParentWidget);
ParentWidget->layout()->addWidget(MyGroupBox);
}
}
void NoteHolder::erasePublishedNotes(QWidget* ParentWidget)
{
MattyGroupBox* MgbTemp;
while ((MgbTemp = ParentWidget->findChild<MattyGroupBox*>()) != 0)
{
delete MgbTemp;
}
QGroupBox* GbTemp;
while ((GbTemp = ParentWidget->findChild<QGroupBox*>()) != 0)
{
delete GbTemp;
}
}
void NoteHolder::getAllNotes()
{
TotalNoteCount = 0;
if (!ListOfAllNotes.isEmpty())
ListOfAllNotes.clear();
QVector<struct MattyNoteRow> ListOfRows = DbManager::showNotes();
for (int i = 0; i < ListOfRows.length();i++)
{
MattyNote TempNote(ListOfRows[i]);
ListOfAllNotes.append(TempNote);
TotalNoteCount++;
}
}
整个源代码可以在这里看到:GitHub
分离成类、它们的方法、调用、依赖关系等。这里:doxygen
让我试着回答一下,因为我们已经见过马蒂一次了……
升序排序称为
Ascending。继承类中的析构函数必须是虚函数,否则会破坏调用析构函数的顺序。
MattyNotesMainWindow许多其他类也是如此。Type- 一个太容易发生冲突的名字。您正在使用 Doxygen 和一堆没有任何文本注释的图表,而这些图表很少有用(恕我直言)。Qt 项目本身有很好的文档记录,看看那里的一切是如何安排的。
我也同意我的同事的意见,即您不应该自己生成查询。即使在我们寻找丢失的桌子时,我也注意到了这一点。这种解决方案不仅难以维护,而且还容易出现 SQL 注入。使用预先准备好的参数查询完全消除了注入。将来,当您使用网络数据库时,考虑这一点很重要。在实践中,很少有请求结构必须动态更改的情况,对于其他情况
prepare来说,这已经足够了。不要忘记调用该方法时必须打开数据库prepare,否则行为未定义。我曾经偶然发现这个耙子。我建议我所有的类都继承自 QObject。否则,您将无法在其中使用信号和槽。例如,最有可能添加信号的候选者是 class
DbManager,信号可能是“数据库状态更改”(连接建立/丢失)。将来,您需要考虑如何安装该程序。据我了解,它是跨平台的。在 Linux 上,Qt 可执行库是先决条件,因此通过将适当的信息添加到包配置文件,您将解决与计算机上库可用性相关的所有问题。如果是 Windows,您将需要手动收集所有的 dll 文件并将它们添加到安装包中。这些文件大致位于以下路径:“c:\Qt\5.6.1\5.6\Src\MinGW”。您需要确定您的程序需要哪些文件,并在安装期间将它们放在程序旁边。曾经有一次我使用 qmake 选择所有必要的文件,将它们放在一个单独的目录中并为 Inno Setup 生成一个脚本。如果你想做类似的事情,使用变量
INSTALLS将所有文件放在一个公共目录中,以及编写 iss 脚本的 write_file 函数。决定包含文件的存储
所有包含的文件都必须在头文件
*.h中,但你的一半文件在*.cpp. 使应用程序看起来单一,删除标头中所有不必要的连接。通用路径
现在你已经将大部分路径和设置硬连接到应用程序中,这非常糟糕,因为每次你都必须重新编译并且程序失去了它的通用性。用户以配置文件的形式使用更通用的工具要容易得多。
配置.h
配置.cpp
现在你可以从中清理掉一堆不需要的东西
DbManager,也可以阅读文档,可以从中学到很多东西。现在您不需要传递行,它们将从文件本身读取,config.ini或者路径将默认设置为包含可执行文件的目录。此外,您不会关闭数据库连接,所以我认为您可以使用一个DbManager::MattyNotesDb.isOpen()简单的DbManager::connected().现在您可以将应用程序放在 USB 闪存驱动器上,它应该可以在任何地方运行。
单一职责原则
每个类都应该为自己的目的负责,它不应该承载多个功能。不要忘记在它们内部移动实体的形成,而不会将它们涂抹在其他类的代码上。
现在查询所有笔记看起来会更好:
关于数据库的更多信息
一般来说,
DbManager::MattyNotesDb对所有事情都使用一个连接是很好的,静态方法不是最好的出路。当应用程序突然获得多线程或一些其他实体时,问题就会开始。因此,这种做法最好不要使用。不要害怕在模型中编写不灵活的方法。设计模型与设计应用程序有很大不同。动态整形是最后的手段。这是因为以这种形式重新创建查询并在数据库上执行它或捕获错误是相当困难的。此外,几个人经常在一个项目上工作,明确的请求使沟通更容易。有时查看请求并找到错误或需要更改的地方就足够了。动态编队剥夺了你这一点,你将不得不铲除所有代码并通过代码重新创建请求。因此,更经常提出苛刻的要求,但效率更高。
为实体创建一个单独的类也是值得的。最后它应该是这样的。
数据库管理器.cpp
MattyNoteProvider.cpp