Development Dairy
开发日记
项目管理
- 确定需求时直接判断是否大量复用,拆分成公用组件。
- App版本号规范,MAJOR.MINOR.PATCH。
版本迭代更新MINOR,服务器也至少通过MINOR判断对应服务器。PATCH更新不需要对应服务器。ios app每次提审后都需要更新PATCH版本号,因此如果使用PATCH对应服务器版本则提审后再次更新都需要更新对应服务器。
目录结构
定义类型声明
- 通用类型声明最好放一块, 便于查找, 避免定义过多重复类型。
- 单独类型放入各自目录下, 避免于通用类型声明混淆。
1 | scripts/ |
代码大全
软件构建中的设计
代码设计基本特性
- 最小复杂度
- 易于维护
- 松散耦合
- 可扩展性
- 可重用性
- 高扇入 (大量的类使用给定的类, 例如工具类)
- 低扇出 (一个类适中地使用其他类)
- 可移植性
- 精简性
- 层次性
- 标准技术
规范子系统通信规则
设计原则
- 信息隐藏 “我该隐藏什么?”;
- 为测试而设计。如何便于测试, 各个系统能独立检查。
配置 Eslint
文件目录规范
避免语义耦合
Module2 在 Module1 修改全局数据后使用这个数据。需要确保数据在 M2 中是否可用, 并且这个参数在 M1 中被正常调用过。
使用事件驱动或者状态管理。
M1 在调用 func()时会先调用 init(); M2 在使用 M1.func()时知道会提前调用 M1.init();
- M1 初始化时直接调用。
- func()中判断 initProperty 参数状态自动调用 init();
- func()中添加断言, 判断 init 是否被调用。
M1 传对象 obj 给 M2, 知道 M2 只需要部分 obj 的参数, 所以只初始化了 M2 需要的几个参数;
对象接口抽象
M1 传类型 BaseObject 对象给 M2, M2 知道实际传的是 DerivedObject 类型, 所以把对象强制转换成了 DerivedObject 类型;
直接传 DerivedObject 类型;
熟练掌握常用设计模式
经常比对需求判断其中的逻辑是否有合适的设计模式。
常用设计模式:
- Abstract Factory 抽象工厂
- Adapter 适配器
- Bridge 桥接
- Composite 组合
- Decrorator 装饰器
- Facade 外观
- Factory Metho
- Iterator 选代器
- Observer 观察者
- Singleton 单件
- Strategy 策略
- Template Method 模板方法
记录设计结果:
- 设计文档插入到代码中
代码开头写明关键设计决策。类似 JavaDoc 的工具, 直接提取注释生成 api 文档。
- wiki 记录
更全面, 更多文字格式, 可以贴图, 链接等。
类
开发程序任意一部分代码时, 能够安全地忽视程序中尽可能多的其余部分。类是实现这一目标的首要工具。
ADT (abstract data type)
抽象数据类型, 指一些数据以及对这些数据所进行的操作的集合。
抽象
如果阅读注释(文档)后仍然不知道接口的作用, 那说明接口抽象不够或者内聚性不强。
- 目标一致, 功能与类紧密结合。
- 抽象层次一致。一个类中实现单一 ADT(感觉不是很绝对)
- 同时考虑抽象性和内聚性。
封装
类的接口与类的实现隔离 PImpl(Pointer to Implementation)
- 减少编译依赖, 只用重新编译 MyClass.cpp。否则所有引用 MyClass.h 的 cpp 都要重新编译。
- 高封装, 高可维护。
- ABI 兼容性(暂时接触不到)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// MyClass.h
class MyClassImpl; // 只声明, 不引入具体实现
class MyClass {
private:
MyClassImpl* pImpl; // 指向实现的指针
public:
MyClass();
~MyClass();
void doSomething();
};
// MyClass.cpp
void MyClass::doSomething() {
pImpl->doSomething(); // 委托实现
}
类的设计
太抽象的也想不明白, 只能说要先多想。想了之后画画之类的再实现。实现后觉得有问题多重构。实现太多后不好重构了再去想就晚了, 想不明白了。晚了之后也要总结点经验。
- 接口是规范, 规定有什么。类是模版, 有什么, 可以做什么。
- 7±2 法则。一个类超过约 7 个数据成员时考虑是否需要拆分。
- Liskov 替换原则(LSP)。子类必须能够完全替换父类, 而不会影响程序正确性。必须语义完全相同才可以继承。派生类“是一个”特殊的“基类”。
- 继承不得超过 2 层
推荐是 2-3 层, 按理说超过两层复杂度已经很高了, 考虑通过不同抽象的方式减少层数。
- 组合。继承是“是一个”, 组合是“有一个”。通过包含其他类的实例构建复杂功能。
- 策略模式。是“有很多个”, 可以动态切换的时候使用策略模式。例如不同登录方法。
方法
命名
- 描述所做的所有事情, 如果太长则可能需要拆分方法。
- 含义清晰, 不要用 Handle, deal 等词。
- 长度最多 15 个字符。
- 命名时对返回值有所描述, 能看出返回结果。
- 准确使用对仗词, 一组名词命名规范要相同。
- 确定命名规范。
规范
- 一个方法尽量不超过 200 行。
- 参数 7 个以内
防御编程
- 垃圾进, 什么都不出。
- 垃圾进, 错误提示出。
- 不许垃圾进。
断言
处理不应该发生的情况, 例如某参数为空。
错误
处理有可能出现错误的情况, 抛出异常并处理保证程序正常进行, 定位问题输出日志帮助解决问题。
debug 开发
- 使用辅助调试代码, 方便开发。使用测试代码, 测试数据, 方便调用快速测试逻辑。需要判断环境避免在生产环境中使用。使用测试用例, 确保逻辑没有问题。
- 进攻式编程, 考虑极端情况。
变量
Typescript
- 变量直接初始化, 声明变量类型。
基础类型直接使用默认值, 0、”” 等;对象使用 null;数组使用[]; 判断时使用强等, 避免隐式转换。
命名
- 规范命名规则。
- 别拼错。
- 类名大写开头, 对象小写开头。(Unity 都是大写, 局部变量是小写)
- 接口: IInterface 与类做区分。
- 枚举: XXXType, XXXStatus。不单独使用, 直接调用即可, 不需要加前缀。
- 全局变量全部大写 GLOBAL_XXXX。
规范
减少无意义数字
1
2const duration = 0.2;
cc.tween().to(duration,{});bool 值不要再判断
1
2let a = true;
a === true ???使用枚举
1
2
3
4
5
6
7
8enum Type{
a = 1,
b = 2,
}
if(n === Type.a){
//
}过度声明
bool 值可以考虑使用数字类型, 后续开发有可能状态会变为多个的情况。
禁止使用 var, 使用 const 和 let。
eslint 配置自动将未重新赋值 let 改为 const。还能确保变量一定初始化。就是后续要再修改的时候还得回去改成 let 确实有点烦了。
软件质量
代码特性之间的影响

代码缺陷检出
代码检查比测试成本低效果好,要养成阅读代码的习惯,自检设计,自检代码。
- 非正式设计复查 (小团队)
- 正式设计检查 (强制流程审查)
- 非正式代码复查 (code review)
- 正式代码检查 (关键模块深度检查)
- 建立模型或原型 (验证技术方案可行性)
- 个人桌面代码检查 (自查)
- 单元测试
- 集成测试
- 回归测试
开发者测试
- 测试不会改善软件质量,改善软件质量需要的是更高质量的开发。
- 测试目的是找到错误而不是完成流程。
测试方法
- 测试用例
- 设计关注点测试
- 基础测试+数据流测试
- 检查表,记录所有犯错的类型
测试注意事项
- 尽可能多的满足测试覆盖率。(尽量保证每行代码都被执行了)
- dirty tests 应该是 clean tests 的 25 倍。(只能说尽可能的多吧,测试用例都不一定能写 25 个)
- clean tests: 预期的输入输出。 功能稳定后实现,自动化测试用例确保功能稳定运行。
- dirty tests: 极端,错误数据验证系统处理能力。能够快速试错
- 边界值分析
结构化基础测试
测试用例最少数量计算方法,遇到 if while for 等关键字计数+1. 感觉没必要这么多,视代码重要程度而定。算上个系数,一般游戏开发 N _ 0.1, 关键逻辑 N _ 0.5, 代码质量要求极高再直接 N 吧。
Debugger
科学调试
- 确定错误状态,触发错误的具体条件,尽量稳定复现,锁定错误范围。
- 收集错误相关数据
- 根据相关数据构造导致错误问题的假说
- 分析如何证明假说
- 实现假说证明是否能够修补,不能则重回 *3.
- 测试
- 判断是否还有类似错误
比较关键,改 bug 谁都会改,能多想多思考发现更多问题才是更好的开发。反思问题出现的原因,优化代码逻辑代码结构。
- 确定错误后可以添加单元测试避免再次出现相同或者类似的错误。
- 实在找不到可以休息一下,说不定啥时候来灵感了。
修改缺陷
- 修改代码前要确保理解问题和相关程序。
- 修改后一定要测试,不管多小的改动。
- 治本,理解错误最根本的,不要治标不治本
重构
需要代码重构的情况
- 代码重复
编写过程中就要避免,如果有重复代码就要抽象出来避免重复,哪怕只有两段重复)
- 冗长的子程序
编写过程中就要避免
- 循环过长或嵌套过深