The project is made with ❤ by:
- 2016010981 陈晟祺 (代码中作者标识为 Harry Chen)
- 2016010912 王安冬 (代码中作者标识为 Ice Coffee)
- 2016010997 牛辰昊 (代码中作者标识为 牛辰昊)
- 2015010467 钮泽平 (代码中作者标识为 Zeping Niu)
除了本页以外,整个项目的文档使用 Doxygen 生成,文中提到的各个实现细节均配置了相应的跳转链接,点击可直接导向相应的类/文件进行查看。我们基本为每个类、每个源文件都加上了可读的注释(包括版权头和简要说明),这些均可以直接在本 HTML 文档中进行查看(需要进入相应的 .h/.cpp 文件,而非类本身,才能看到版权文件头)。文档均使用英语撰写。
由于我们使用的演示平台比较特殊,只有付费用户才能进行离线导出,而采用逐一截图的方法让我无法接受。故我在本文档中基本包含了展示的 slides 的所有内容,未将其一同打包。
如果尝试编译项目时遇到问题,请参见本文最后的“编译说明”部分。如有任何问题,可联系我:harry-chen at outlook.com
日常生活中,我们经常会面临几个重要的问题:我的钱去哪了?为什么我又没钱了?隔壁老王还欠我多少钱?
对于这些问题,养成记账的习惯可以避免很多不必要的麻烦。目前移动端,各色记账软件层出不穷;但在 PC 端,专门软件并不多,而且有着种种问题:太复杂(如 GNU Cash)、收费(如金蝶随手记)、杀鸡用牛刀(如 Excel)等等。
因此 ,我们开发了 Expensé,一款很 naïve 的记账软件,用来满足这个需求。
- 基本记账功能:简单记账法,含收入、支出、转账、借还款
- 多账户支持:钱包、在线支付、银行卡等
- 多用户支持:注册与登陆系统、用户数据隔离
- 多货币支持:支持不同汇率的货币
- 收支分类支持:饮食、交通、学习、在马路边捡到的……
- 多条件组合查询:金额范围、日期范围、账户、时间、备注、状态等参数,其中的一个或多个
- 统计与可视化:选定时间段的统计(年月日周)、条件查询结果的统计、统计图表(折线图、柱状图)的绘制
- 数据持久化与备份:数据库连接与存储、数据导入导出(JSON)
- 多前端实现:GUI、CLI(最终未实现)
- 日志与审计:调试模式下完整的日志记录,生产模式则只记录错误
注:任务量与列出的具体条数并非成正比
-
陈晟祺(组长)
- 项目整体架构、模型抽象
- 需(xiang)求(mu)控(jing)制(li)
- 后端数据综合处理、数据库连接
- 账单组合查询
- 模块整合、代码规范、文档统筹
- 程序 i18n 框架与多语言翻译
- 程序员催促师兼 Bug Hunter
-
王安冬
- 美工、GUI 设计
- 主程序框架
- 登陆窗口
- 用户管理、设置
- 数据导入导出
-
钮泽平
- 账单、增、删、改
- 分类、账户、货币管理
- 性能调优
-
牛辰昊
- 综合查询模块
- 数据可视化(绘图)
- 账单查询与详情呈现
项目整体架构大致如下图:
本项目采用了比较经典的存储——持久——业务——展现四层逻辑,每层通过对应的接口与上下层沟通,用户请求通过每层的处理得到实现。
项目整体的 UML 图略去。各个类的 UML 图,以及各类之间的调用、依赖关系,均在文档中给出。
各个核心模块在上述的需求分析以及项目架构中已有提及,解释说明与详细实现可见各个模块对应的类对应的文档(包括且不限于类页面、头文件页面、实现页面)。本节只补充代码文档中未涉及的部分。
- 模板方法(Template Method),见 [ItemManager](@ref ItemManager)
- 生成器模式(Builder Pattern),见 [Query](@ref Query)
- 单例模式(Singleton Pattern),见 [DatabaseHelper](@ref DatabaseHelper) 以及 [ItemSearcher](@ref ItemSearcher)
- 策略模式(Strategy Pattern),见 [ApplyChangeStrategy](@ref ApplyChangeStrategy)
- 抽象工厂模式(Abstract Factory Pattern),见 [PlotSystem](@ref PlotSystem)
- 借鉴与分享
- 多使用现成的开源方案,不重复发明轮子
- 项目以 Apache 2.0 协议开源
- 前后端分离与解耦合
- 后端:底层基础接口(增删改查)
- 前端:增强功能(绘图、统计等)
- 多线程并行
- 异步进行耗时操作,渐进式加载
- 分离UI线程与IO线程
- Qt 5.8.0 & C++ STL & boost:跨平台、全功能的开发库
- QCustomPlot (http://www.qcustomplot.com/):成熟的 Qt 绘图控件
- SQLite (https://www.sqlite.org):轻巧的小型 SQL 数据库
- sqlpp11 (https://github.com/rbock/sqlpp11):基于 C++ 11 的方便易用的 ORM (对象关系模型)
- Log11 (https://github.com/meox/Log11):基于 C++ 11 的小巧、线程安全的日志库
- date (https://github.com/HowardHinnant/date):基于 C++ 11 的时间日期处理库
- Doxygen (http://doxygen.org):强大的自动文档生成工具
- Git & GitHub:最清真的版本控制与源码管理系统
本项目在以下环境编译通过且能运行:
- Qt 5.8.0 (windows-x86-msvc2015_64) with MSVC 14.0 (shipped with VS 2015 Update 3)
- Qt 5.8.0 (windows-x86-msvc2015_64) with MSVC 14.1 (shipped with VS 2017 RC)
- Qt 5.8.0 (mac-x64-clang) with Clang 8.1.0 (shipped with XCode 8.3.2)
在下列环境编译通过但未测试运行:
- Arch Linux with Qt 5.9.0
- Ubuntu 17.04 with Qt 5.8.0
在下列环境确认无法编译通过:
- Qt <= 5.5.0
本项目的编译还需要 qmake, CMake 以及 GNU Make 的支持。一般的编译步骤为:
- 单独编译
sqlpp11-connector-sqlite3
库:
git clone https://github.com/rbock/sqlpp11-connector-sqlite3.git
mkdir build && cd build
cmake ../sqlpp11-connector-sqlite3/
make
- 将得到的静态库文件(.a或者.lib)复制到本项目的
libs
目录下 - 在
OOP-Cash.pro
文件中添加对该库的引用,如LIBS += -Llibs -lyourlib
- 执行
qmake
- 执行
make release
- 如要得到英语版本,可将源码目录中
lang/en_US.qm
复制到编译得到程序的工作目录
注:上述步骤只可用于编译 Release 版本,编译 Debug 版本需要将上述的库也进行 Debug 编译。我在项目中已经包含了对某些平台预编译的该库(for Windows 10 x64 & macOS 10.10)并在 OOP-Cash.pro
中进行了配置,但依旧不保证可以立刻正确编译运行。
跟随本程序提供一个样例数据库 sample.sqlite
,可在程序第一次启动时加载。登录用的用户名为 Test,密码为 1234。
- 在项目根目录执行
lupdate lang/en_US.ts
- 使用
Qt Linguist
等工具更新翻译 - 执行
lrelease lang/en_US.ts
- 用新得到的
lang/en_US.qm
覆盖老的文件
虽然之前我有过一些开发经验,但这是我第一次用小组合作的形式与他人完整地开发一个程序。尽管代码量不大,也略显简陋,但是不失为一次很好的锻炼。在开发过程中,我们吸取了不少经验教训,在此列举一部分。
让程序完全解耦合、每一个人各自为政,独立撰写各自的模块其实是很难的一件事情,这对整个系统的架构与抽象提出了非常高的要求——自然,我没有这样的水平。所以虽然我们的模块划分比较清晰,但在分工上依旧有不小的交叉。比如一个后端 API 设计的缺陷,只有在上层模块进行一些复杂的调用时才会被完整暴露出来;或者前端对接口的不合理使用和错误理解,也能从后端的日志中反映出来。于是每个人其实都对几乎所有的模块有过意见和自己的修改,这其实并非坏事。
另外,程序员们一直黑的产品经理,其实是十分重要的。对需求的控制与解读,是整个设计环节中最重要的部分。我们之前就经历了需求过多,坑填不完的情况。后来仔细思考,其实有一些部分是伪需求,是花架子;如一开始设想的 CLI 前端,其实并没有必要实现。做好每一个真正对用户有用的功能,才是应该追求的。
重要的话说三遍: 规范很重要,规范很重要,规范很重要!
虽然向来容易引战,但是不管大括号换不换行、Tab 还是空格缩进、大驼峰还是小驼峰还是下划线,一个可读性良好并且在整个项目中保持一致的代码风格是关键。一方面,它完美地满足了我这类强迫症患者的需要;另一方面,它传递出我们对待这个项目认真的态度。在赶进度那几天,大家的代码都是糙快猛,留下了一堆隐患;其实最后的这几天,我们的一个重点就是规范代码中各种符号的命名。就算完整修改来不及,所有对外暴露的接口都需要得到规范。
另外,文档也是不可或缺的,这一点不言而喻。感谢 Doxygen 这么优秀的工具,能从我们的代码注释中生成美观、方便使用的程序文档。以前我写项目从没有这样的习惯,常常过了一整子回头就看不懂自己究竟干了什么、用了什么黑魔法。这种糟糕的工程习惯,终于在这次得到了改观。
作为第一个超过两人合作的大作业,我在这方面可以说颇为费心。一方面,我坚持让队员使用 Git 管理源码,并严格遵循不得在主分支直接 commit 的约定,采用分支——合并的开发流程。事实证明这给我们带来了很大的便利:这样的工作流允许多个人同时并行开发,即使产生冲突也是很少一部分内容;另外,得益于 Git 完善的 blame 机制,写出 bug 的人能被第一时间找到,责任明确。
合作开发时难免会产生一些想法的冲突,作为队长如何妥善处理也很重要。虽然我的确经验比较丰富一些,但很多时候组员的设想与建议的确要比我想到的更好。这时候应该听取大家的建议。另外,作为程序员催促师,适当的提醒和督促有助于项目的稳定推进。
限于时间、我们的技术水平等一系列因素,项目留下了一些缺憾,现列于于此。诚实地说,这个项目大概从 deadline 的第二天起就不会再进行任何开发了 。但我希望,在今后的其他项目中,我们可以补上这些遗憾。
- 保留未解决的bug:
QListWidget
选择某些项时崩溃,产生不明报错。在 macOS 上稳定复现, Windows 上无法复现。怀疑是QListWidget
本身内部实现的问题,后期可能继续跟进。 - GUI 美观度、布局灵活度不够
- 在处理大量数据时仍然有卡顿现象,性能优化不到位
- 未尝试现代 C++(如 C++ 14)的一些新特性
- 程序功能依旧较少,too simple
如果问我们在这个项目中学到了什么,或许不是某些特定的设计模式和语法;从构思到设计到开发的整个过程都给了我们很多启示。希望我们能保持热情,积累经验,继续努力。