Open finscn opened 4 months ago
个人认为 strictPropertyInitialization 没任何问题。只需要实现类似rust option类型即可。对于一个可空类型在任意地方unwraps 都是合理的。只需要程序本身可以认为此时是安全的就行。如果出现了空异常,那么说明程序执行了错误的逻辑。对于无法明确是否已经不会空的地方,也可自行通过 if let = Some(..) 判断。 也就是Nullable。或者从其他大型ts项目(babylon/vue)代码来看,大家都会默认为设置此类型为Nullable。也就是 T | undefined。这也更符合强类型语言的编程思维。cocos 的ts是基础设施。严格理当更好。对于这种变量,要么赋值要么可空。
个人认为 strictPropertyInitialization 没任何问题。只需要实现类似rust option类型即可。对于一个可空类型在任意地方unwraps 都是合理的。只需要程序本身可以认为此时是安全的就行。如果出现了空异常,那么说明程序执行了错误的逻辑。对于无法明确是否已经不会空的地方,也可自行通过 if let = Some(..) 判断。 也就是Nullable。或者从其他大型ts项目(babylon/vue)代码来看,大家都会默认为设置此类型为Nullable。也就是 T | undefined。这也更符合强类型语言的编程思维。cocos 的ts是基础设施。严格理当更好。对于这种变量,要么赋值要么可空。
你最后一句话 "要么赋值要么可空" 说到了点子上, 这个是我本来打算说的第二个话题(第二步) : 如果不想关闭 strictPropertyInitialization , 可以考虑关闭 strictNullChecks .
现在cocos 最尴尬的是 (其实不是 cocos的尴尬, 是tsconfig的尴尬) , strictPropertyInitialization = true 了 , 同时 strictNullChecks 也为 true 了.
这就导致你推崇的(其实也是我推崇的) "要么赋值要么可空" 无法实现, 现在的情况是既要赋值, 又不能为空. 于是cocos研发团队那些小机灵鬼们只能用"明明是null, 却要欺骗编译器这不是null" 的手段来解决这个尴尬.
毕竟在oop模式下, 绝大多数的对象是可以为null的, 所以 A 全面禁止为null, 对可以为null做特殊处理 B 全面允许为null, 对不可以的为null的做特殊处理 两者相比, 显然后者是更科学合理的.
其实我也一直希望 ts里能提供 Nullable
可惜并没有. (甚至那个 NonNullable<> 的实际作用也完全不像名字看起来那样)
说回 strictPropertyInitialization 的问题. strictPropertyInitialization 自然是有好的一方面, 但是对于我来说, 它这种把"成员变量声明" 和 "成员变量初始化" 两个过程强制绑定的做法我并不喜欢. 我个人是喜欢解耦这两个过程的.
个人认为 strictPropertyInitialization 没任何问题。只需要实现类似rust option类型即可。对于一个可空类型在任意地方unwraps 都是合理的。只需要程序本身可以认为此时是安全的就行。如果出现了空异常,那么说明程序执行了错误的逻辑。对于无法明确是否已经不会空的地方,也可自行通过 if let = Some(..) 判断。 也就是Nullable。或者从其他大型ts项目(babylon/vue)代码来看,大家都会默认为设置此类型为Nullable。也就是 T | undefined。这也更符合强类型语言的编程思维。cocos 的ts是基础设施。严格理当更好。对于这种变量,要么赋值要么可空。
你最后一句话 "要么赋值要么可空" 说到了点子上, 这个是我本来打算说的第二个话题(第二步) : 如果不想关闭 strictPropertyInitialization , 可以考虑关闭 strictNullChecks .
现在cocos 最尴尬的是 (其实不是 cocos的尴尬, 是tsconfig的尴尬) , strictPropertyInitialization = true 了 , 同时 strictNullChecks 也为 true 了.
这就导致你推崇的(其实也是我推崇的) "要么赋值要么可空" 无法实现, 现在的情况是既要赋值, 又不能为空. 于是cocos研发团队那些小机灵鬼们只能用"明明是null, 却要欺骗编译器这不是null" 的手段来解决这个尴尬.
毕竟在oop模式下, 绝大多数的对象是可以为null的, 所以 A 全面禁止为null, 对可以为null做特殊处理 B 全面允许为null, 对不可以的为null的做特殊处理 两者相比, 显然后者是更科学合理的.
其实我也一直希望 ts里能提供 Nullable 来有针对性的对个别变量和属性进行约束, 而不是通过 strictNullChecks 来一次性全局强制.
可惜并没有. (甚至那个 NonNullable<> 的实际作用也完全不像名字看起来那样)
说回 strictPropertyInitialization 的问题. strictPropertyInitialization 自然是有好的一方面, 但是对于我来说, 它这种把"成员变量声明" 和 "成员变量初始化" 两个过程强制绑定的做法我并不喜欢. 我个人是喜欢解耦这两个过程的.
我觉得 strictPropertyInitialization 其实还好说,只是编码习惯问题,但是必须开启 strictNullChecks,这是使用 TypeScript 的理由。
在规划这些的时候尽量不应该考虑团队成员的错误使用,而是通过员工手册等东西解决,如果不正确使用,那么任何安全措施都会被 !
解除,无论 Rust 还是 TypeScript。
我觉得 cocos 团队对于 TS 不太了解的话,还是直接参考顶级的,热门的开源项目是怎么制定的吧。
比如 VSCode:https://github.com/microsoft/vscode/blob/main/src/tsconfig.base.json
暂时没有见到什么热门项目开启严格模式竟然会取消 strictNullChecks。
个人认为 strictPropertyInitialization 没任何问题。只需要实现类似rust option类型即可。对于一个可空类型在任意地方unwraps 都是合理的。只需要程序本身可以认为此时是安全的就行。如果出现了空异常,那么说明程序执行了错误的逻辑。对于无法明确是否已经不会空的地方,也可自行通过 if let = Some(..) 判断。 也就是Nullable。或者从其他大型ts项目(babylon/vue)代码来看,大家都会默认为设置此类型为Nullable。也就是 T | undefined。这也更符合强类型语言的编程思维。cocos 的ts是基础设施。严格理当更好。对于这种变量,要么赋值要么可空。
你最后一句话 "要么赋值要么可空" 说到了点子上, 这个是我本来打算说的第二个话题(第二步) : 如果不想关闭 strictPropertyInitialization , 可以考虑关闭 strictNullChecks . 现在cocos 最尴尬的是 (其实不是 cocos的尴尬, 是tsconfig的尴尬) , strictPropertyInitialization = true 了 , 同时 strictNullChecks 也为 true 了. 这就导致你推崇的(其实也是我推崇的) "要么赋值要么可空" 无法实现, 现在的情况是既要赋值, 又不能为空. 于是cocos研发团队那些小机灵鬼们只能用"明明是null, 却要欺骗编译器这不是null" 的手段来解决这个尴尬. 毕竟在oop模式下, 绝大多数的对象是可以为null的, 所以 A 全面禁止为null, 对可以为null做特殊处理 B 全面允许为null, 对不可以的为null的做特殊处理 两者相比, 显然后者是更科学合理的. 其实我也一直希望 ts里能提供 Nullable 来有针对性的对个别变量和属性进行约束, 而不是通过 strictNullChecks 来一次性全局强制. 可惜并没有. (甚至那个 NonNullable<> 的实际作用也完全不像名字看起来那样) 说回 strictPropertyInitialization 的问题. strictPropertyInitialization 自然是有好的一方面, 但是对于我来说, 它这种把"成员变量声明" 和 "成员变量初始化" 两个过程强制绑定的做法我并不喜欢. 我个人是喜欢解耦这两个过程的.
我觉得 strictPropertyInitialization 其实还好说,只是编码习惯问题,但是必须开启 strictNullChecks,这是使用 TypeScript 的理由。
在规划这些的时候尽量不应该考虑团队成员的错误使用,而是通过员工手册等东西解决,如果不正确使用,那么任何安全措施都会被
!
解除,无论 Rust 还是 TypeScript。我觉得 cocos 团队对于 TS 不太了解的话,还是直接参考顶级的,热门的开源项目是怎么制定的吧。
比如 VSCode:https://github.com/microsoft/vscode/blob/main/src/tsconfig.base.json
暂时没有见到什么热门项目开启严格模式竟然会取消 strictNullChecks。
参考大项目没问题, 但是最不应该参考的就是 vs本身. 因为他几乎不考虑 运行时扩展 自定义 等等. 和游戏引擎的需求完全不同. (大多数不会在使用vs时, 在运行时去继承重写覆盖vs本身的代码, 有特殊需求通常是在开发一个插件)
我个人比较推崇 pixi 的配置: https://github.com/pixijs/pixijs/blob/dev/tsconfig.json 它开了 "strictPropertyInitialization" , 但是关了 "strictNullChecks"
但是游戏引擎是需要一些动态定制的.
另外, strictPropertyInitialization 和 strictNullChecks 单独看其实 都没什么大问题.
大问题就是 两者 同时为true, 绝对是反模式的.
"声明成员变量的同时, 必须初始化, 且不能为空, 如果要为空 就要| null
,或者 = null!
这种开发思路我实在无法接受.
因为大多数成员是允许为空的.
项目的默认配置 应该是尽量适合大多数情况, 针对少数特例去编写额外代码. 而不是反过来.
另外 strictNullChecks 本身的设计还有一个缺陷: "词不达意". 或者说 功能过耦合. strictNullChecks 用在做 null check 可以, 但是 他不应该兼具 "任何对象都不能为null" 这样一个副作用.
如果真需要这样一个功能应该再通过另一个属性来实现, 比如新增一个 strictNonNullable .
我更倾向于 关闭 strictNullChecks , 然后 使用 @typescript-eslint/strict-boolean-expressions
来辅助 null check.
再补充下 :
找 tsconfig 参考时, 有的人会习惯性的找 最大的ts项目 vue.js 做参考. vue.js 没有改变 strictPropertyInitialization 和 strictNullChecks 在严格模式下的默认值 (都为 true) 但是 vue.js 本身基于 函数式编程的思想, 几乎没有 类和成员变量的概念, 自然没有受到 strictPropertyInitialization = true 的影响. (对于vue来说, 相当于 strictPropertyInitialization = false, strictNullChecks = true )
所以找参考时, 参考项目的编程思想是否和cocos一致 也很重要. cocos本质还是oop思想. 它ecs也是基于oop开发的.
而我推崇的 pixijs 和cocos有很多类似的地方.
我从 pixijs 1.0 开始就关注pixi, 一路看着它的演变和进化, 可以说cocos遇到的很多问题 它也遇到过, 很多坑它其实已经替cocos踩过了. 它确实有很多地方值得cocos借鉴.
再补充下 :
找 tsconfig 参考时, 有的人会习惯性的找 最大的ts项目 vue.js 做参考. vue.js 没有改变 strictPropertyInitialization 和 strictNullChecks 在严格模式下的默认值 (都为 true) 但是 vue.js 本身基于 函数式编程的思想, 几乎没有 类和成员变量的概念, 自然没有受到 strictPropertyInitialization = true 的影响. (对于vue来说, 相当于 strictPropertyInitialization = false, strictNullChecks = true )
所以找参考时, 参考项目的编程思想是否和cocos一致 也很重要. cocos本质还是oop思想. 它ecs也是基于oop开发的.
而我推崇的 pixijs 和cocos有很多类似的地方.
- 都是游戏相关引擎
- 都是基于oop编程思想
- 早年都是 js 版本, 最近几年开始转的ts
我从 pixijs 1.0 开始就关注pixi, 一路看着它的演变和进化, 可以说cocos遇到的很多问题 它也遇到过, 很多坑它其实已经替cocos踩过了. 它确实有很多地方值得cocos借鉴.
我没 get 到 strictNullChecks 的开关为什么会受到 vs、游戏领域或者 Web 领域的差别所影响,也没 get 到会受到编程范式的影响,这个开关可以说是 TypeScript 的核心特性,也是最没有争议必须开的特性,不像 strictPropertyInitialization、noImplicitAny 这些有争议的特性。
其实这个我也不太明白,能否举个例子说明一下
"任何对象都不能为null" 这样一个副作用
是什么样的场景?
然后,
我查找带 game-engine
tag 的前几名的 TypeScript Github 项目,只有两种情况:
如果说 Pixijs 是故意将 strictNullChecks 去掉的话只能说令人震惊,但是我仔细查了一下 Issue,可以看到 Pixijs 也是因为迁移成本的问题没有开启 strictNullChecks,而不是故意去掉:https://github.com/pixijs/pixijs/issues/8852
@smallmain
我没 get 到 strictNullChecks 的开关为什么会受到 vs、游戏领域或者 Web 领域的差别所影响 我说vue 是函数式编程那段, 重点要说的是 strictPropertyInitialization , vue函数式编程, strictPropertyInitialization变得不那么重要, 相当于 strictPropertyInitialization = false , strictNullChecks = true.
我想强调的是 tsconfig整体如何配置 和 编程范式有关, 而不是单指 strictNullChecks .
对于cocos而言, 我的观点是 strictPropertyInitialization 和 strictNullChecks 两者只能有一个为 true. (vue 和 pixi一样. vue 相当于 strictPropertyInitialization = false , strictNullChecks = true. pixi中 开启了 strictPropertyInitialization = true , 但是 strictNullChecks = false. )
我说 strictNullChecks = true 不合适, 是站在 @whaqzhzd 说的 "个人认为 strictPropertyInitialization 没任何问题" 的角度去看的.
如果站在你的角度 "strictNullChecks 最没有争议必须开的特性,不像 strictPropertyInitialization、noImplicitAny 这些有争议的特性" , 那么我的观点就是 strictPropertyInitialization = true 不合适.
如果我只站在自己的角度的话, 我还是会选择: strictPropertyInitialization = false strictNullChecks = false
因为:
1) 我坚持认为 成员变量的声明 和 初始化 是两个独立的 解耦的过程.
2) OOP也有很多流派, 我是 "万事万物皆对象, null也是对象, 对象可以为null" 这个流派的 .
因为错误的使用null, 带来的bug, 应该用其他手段来解决, 比如 eslint, test-case 等, 而不是阉割语言特性和改变编程范式.
(let foo = null
是合法语句, 这个是语言特性; "null也是对象, 对象可以为null" 是编程范式)
如果不允许都为 false, 两者至少有一个为true的话, 我会选择 strictPropertyInitialization = false , strictNullChecks = true.
Use Case
关于cocos源码的 tsconfig 和 eslint 的问题, 我在很多渠道发表过观点了. 我也知道 这种改动如果要一步到位 一蹴而就 很难 . 但是可以分部进行. 先找可以平滑过渡的.
建议第一步可以先将 tsconfig 中的 "strictPropertyInitialization" 设置为 false.
当开启 "strict": true 时, ts默认也会把 strictPropertyInitialization 设置为 true, 其实这是非常不合理的. 说明如下:
实际上, 从cocos的源码也能看出来, strictPropertyInitialization = true 时, 给开发人员带来了巨大的麻烦. 因为实际上 99%类是不需要
在 声明成员变量时 直接初始化
的, 所以cocos里大量的.strictPropertyInitialization
这个绝对是 反模式(至少是反oop模式) 的设计, 事实证明也确实给 cocos的开发人员带来了困扰. 所以 改造的第一步就是:null! , undefined! , | undefined
一类的代码了.以上两步 不会破坏任何已有的逻辑, 只会让代码变得更干净.
cocos 越来越复杂 , 开发人员和网上愿意提交pr的人也越来越多, 大家的编码水平和编码习惯也有所差异,
越是如此 越需要一套科学合理的 tsconfig 以及 eslint (还有个叫 ESLint Stylistic 的 建议关注下) 来约束代码.
希望官方可以考虑下.
这个事情其实远比你们想象的要重要, 因为目前这种不合理的tsconfig和eslint 已经影响到cocos的开发了 . cocos官方开发人员为了 绕开这些不合理的 tsconfig和eslint , 自己写了很多hack代码 , 不断的用一个个错误的编程方式来弥补错误的tsconfig和eslint 造成的影响. 甚至有人在自己的开发机上暗改 tsconfig、eslint, 偷偷的关闭了 ts的严格模式。 到最后 tsconfig和eslint 只是用来限制社区提交PR的效率和热情 ,对你们内部正面价值甚微。
我们可以站在那些选择"用一个错误弥补另一个错误, 将错就错 错上加错"的开发人员的角度去思考下, 他们为什么这么做. 现在在堆积如山的todo list面前, 奋战在第一线的开发者肯定无暇顾忌太多,快点完成任务才是王道。 所以他为什么暗改tsconfig, 为什么关闭严格模式, 为什么无脑添加 null! ? 是不是因为不如果不这样做 影响他的开发效率和进度?如果是这样,那么是不是可以证明现在的 tsconfig 和 eslint 存在问题?
磨刀不误砍柴工的道理大家都懂, 但是什么时候磨刀 磨多久,每个人的看法不同,每个团队每个项目的标准肯定也不同, 这玩意也没有最佳实践 标准答案.
最后如何去做 , 肯定还是要你们团队自己去考虑. 我只是想善意的提醒一下: 3.8.5之后 该考虑磨磨刀了.
Problem Description
如上所述
Proposed Solution
No response
How it works
No response
Alternatives Considered
无
Additional Information
No response