10.19
本来不打算写日记了,但终究还是强迫自己来输出一点文字了。
今天早上六点钟睡觉八点钟起床,下午四点半 SEP 答辩,答辩完晚上爽爽吃了今天唯一的一顿饭。由于 qlink project 答辩完了,整个人一身轻松,回寝上楼的时候听 耳わほう 不禁随着旋律舞蹈起来,恰好被下楼的人看到了,这个人肯定被吓得不轻。
驾驭巨鲸
从 10.16 中午开始研究 SEP 的 QLink project 到 10.19 24:00 提交最终的项目文件,一共花了差不多四天。
看完 SEP 提供的 QLink 项目描述之后,可以发现
- 项目的评分标准很模糊。今天答辩实测下来雀石过于肤浅了,这答辩强度甚至不如我自己玩个上十把对正确性检验的强度。
- 没有测试样例。或许没有测试样例才是答辩存在的理由吧。这意味着 bug 无法尽早地被发现,这样累积下来的弊端是显著的。
- 没有基础文件,一切从零开始。当然有 Qt 也不完全是从零开始。
哈哈鉴定为是个低质量 lab。
当然虽然它的评分标准一言难尽,不过他的背景还是十分有意思的。毕竟自己手搓一个游戏大概是一个令所有男人难以拒绝的诱惑。
死灰复燃
看完描述之后不禁令我回想起先前尝试 Parallaria 中道崩殂的遗憾,于是产生了一个大胆的想法:用 Qt 搓一个具有一定 RPG / 动作游戏 通用性的游戏框架。10.16 瞎玩 Qt 玩了半天,进一步让我确信了这个大胆想法的合理性:
-
深度集成的 IDE Qt Creator,成熟的用户界面制作工具 Qt Designer
-
Qt 6 与 CMake 深度集成
- 提供了管理资源的文件系统(qt_add_resource,.qrc 文件,QFile)
- 提供了调试(qt_add_executable,qDebug(), qFatal(),类似 gdb 的调试机制)与测试(qt_add_test,QTest)机制
-
成熟的主循环控制 QApplication,只需要关注窗口、控件等的绘制逻辑
-
不需要从 Graphics Pipeline 等最基础的元素做起,可以直接利用 Qt 的绘制逻辑。
-
不需要从场景的基础逻辑堆起来,Qt 有一套 Scene/View/Item 逻辑。
-
QObject 提供了相当丰富的基础机制
- signal / slot 对象通信机制。一个 sender 的 signal 可以绑定多个 receiver 的多个 slot,发送的信号在 Qt 主循环中被处理。
- 利用对象通信实现的事件机制:每个对象都有丰富的事件触发,如窗口输入处理、QGraphicsItem 的 paint。
- 元对象系统(反射机制):提供了强大的运行时变量类型信息(如类名、父类名)、函数信息(如函数签名、返回类型)、属性信息等 Inspection,函数动态调用等 Dynamic Invocation。
-
Qt 自己实现了丰富的基础库,我用过的包括:
- 绘图:QColor、QPen、QBrush,以及 QPaint 提供的大量基础绘制函数
- 控制流:slot/signal 对象通信机制中 connect,时间控制 QTimer,QTimeLine 等
- 文件操作:提供了管理资源的虚拟的文件系统,QFile,QDataStream 序列化
- 基础类型:QString,qreal,QPoint,QRect,QList,QQueue 等
- 基础函数:qSwap 等
简要描述和评价一下这四天搓出来的版本:记录在了 Match Quest Record
淬苦成甘
这么多天过去,许多 coding 中的细节早就变得模糊,不过即使如此有一些感受依然是很深刻的,从中或许可以汲取一些思考的果实
-
提升码力的核心在于实现”心中有码“。提升基础工作流中元素的丰富性和熟练度在 LLM 盛行的今日学习成本大幅减小。重心从具体内容学习转变为对结构框架的了解和基于对结构的理解、分析提出的提升思路,即为技术强大的 LLM 提供一个构想的思路。所以其重要性逊于前者。心中无码的典型表现比如构建一个类的时候,常常是思考不充分想一点写一点,这里改一点那里改一点,十分琐碎。这样做潜在的风险在于
-
编码不连贯,在不同上下文中切换不仅浪费时间(强烈谴责 Qt Creator 只有一个 tab 的而且不支持分屏的傻逼设计)还会消耗更多大脑资源(类比 Cache)。
-
带来重构风险,满足一部分性质的一个实现不一定能拓展到全部性质上,如果没有完全考虑所有性质就开码,发现不对劲之后就不仅需要重新构建结构而且需要重新实现。
所以从理论分析上来看最佳的 coding 姿势应该是先完全构思好(架构 + 实现)然后再将它们一同连贯地全部转换为代码。从实际经验上来看,要点在于如何完全且深刻地考虑所有需求。
-
-
不可稳定复现的 Bug 十分可怕。其可怕的源泉来自于其几乎不可 debug 的性质。只能用瞪眼法从假想的程序逻辑上进行分析。
-
对于具有一定规模的构建而言,维护历史版本十分重要。今天答辩前 3h 就不小心把一个版本完全改崩了,debug 出来是一些十分奇怪的 Segmentation Fault,最终查出来真正导致错误的其实是一个简单的 delete,但是在 Qt 的架构下一通变换完全变成了令我陌生的模样,这个时候还想要保持稳定的心态是十分困难的。
-
日志(比如 qDebug())以及检测正确性的断言(类似 C++ assert)很重要。它们不是面向用户设计的,而是面向开发者设计的。日志可以用于快速定位程序崩溃的位置。检测正确性的断言可以让 bug 提早暴露,bug 经过转换之后的可以变得面目全非。
-
代码的世界里没有完美与统一,只有权衡与接受。很多细节上的处理方法不胜枚举,而且更重要的是他们之间有明显的多方面的优缺差异,不仅包括实际意义上的,也包括构建的虚拟设想意义上的,需要考虑的因素。这让一个强迫症 / 完美主义者生不如死。
-
比如 private / protected / public 构建的一个虚拟的权限系统设想与实际实现所需上的平衡,这种权衡某种意义上源自于 C++ 只提供了两层权限访问搭建的支持,不支持精细的控制:private / protected / public 是第一层,决定单个成员针对所有外部对象,friend 是第二层,决定单个外部对象对所有内部成员,而且友元还不能继承。没有一种中间的机制来决定某一组类的访问权限,或者某一个类的某组成员的访问权限,或者某一组类的某一组访问权限。
-
又比如成员函数的 const 和 non-const 的选择,C++ 支持的 const 是 bit-wise const,即指针本身不改变,而对指针成员的 const 大多时候是 logical const,即指针指向的对象不改变。
-
-
速度与良构程度的取舍。越漂亮的结构必然意味着更多的搭建时间。由于开工前期过于追求项目结构的完美,导致答辩之前若干小时还在冲进度。
-
面向对象设计哲学在面对未知时构建难度上升。面对对象是一种自顶向下的构建,这要求你在构建之前对项目的结构十分清晰。但是在面对未知时,很多时候并不能一开始就把整体抽象关系梳理的十分清晰(面对熟悉的场景如物品、水果、橘子、金桔而言,这是十分轻松的),这是与 “心中有码” 带来的挑战是不谋而合的。但是面向过程不一样,它是一种自底向上的构建,他的哲学是先把构建框架所需要的所有基本元素造出来,这样结构就会清晰,在此之上再做结构搭建。这个过程是 brain-friendly 的。