引擎开发组对引擎自身的脚本系统能有多不了解?

我就不该大半夜点开引擎的 issues,一点开就气得花半小时发贴。

事情的起因

是这个 issue:https://github.com/cocos/cocos-engine/issues/16937

在用引擎的 tween 缓动系统对节点做动画时,以下代码会报错:

tween(node).to(
    1,
    { ... },
    {
        // ts(2322) error here
        onUpdate(target: Node) { ... },
    },
);

大家如果直接看这段代码,可能看不出啥问题,target 确实是一个节点,但 ts 却会报错。

是的,这个报错确实如 finscn 所说,是引擎 dts 的问题。

来看引擎的类型声明:

onUpdate?: (target?: object, ratio?: number) => void;

target 被声明为了 object,其实最正确的声明 finscn 也说出来了,那就是将其声明为泛型 T

onUpdate?: (target?: T, ratio?: number) => void;

这样,ts 会根据你在 tween(node) 传入的类型,来自动推断出 target 是什么类型。

引擎组的态度

说到这,即使不深究,问题也已经清楚了,但为什么这个 issue 还有高达 24 条评论呢?

(如果继续阅读,建议先看看原 issue 的内容)

一个是因为 finscn 的 issue 是要求将 targetobject 类型改为 any 类型。

这显然不合理,因为使用 any,确实可以解决问题,但我们就没有必要去使用 ts 了,所以引擎组的应该也是这么认为的,所以发生了很多争论。

我个人猜测其实 finscn 是从一开始就知道应该改为泛型,而不是 any,之所以 issue 这么写,应该就是觉得改成泛型可能对引擎组比较麻烦,引擎组就又会一直拖,而改成 any 其实很快,秉承着又不是不能用的理念,就妥协了。

另一个,并且我认为是最重要的原因就是,
引擎开发组宁愿手打 50 行代码,
花几天的时间搁着掰扯来掰扯去想要证明现在的 dts 没有问题,
都不愿意自己亲手试试输入一下用户提出的那段在不格式化时只有几行的会报错的代码。

就是因为和引擎组反映问题有时候会如此费劲,所以我也深有同感。

争论的答案

其实直到我发帖时,最后的几条评论是:

image

也就是最后依然没有搞清楚为什么上面那段代码会报错的具体原因。

为什么这样声明时:

onUpdate?: (target?: object, ratio?: number) => void;

我们将这个函数:

onUpdate(target?: Node, ratio?: number) { ... }

传进去时却会报类型不兼容的问题呢?明明 Node 也是 object,他们应该是兼容的啊:

// 不会有任何问题
let obj: object = node;

评论里提到的严格模式其实并不是根本问题,而引擎组竟还提了嘴 eslint 真是把我气笑了。

这里的根本问题是类型的协逆变:https://zh.wikipedia.org/zh-cn/协变与逆变

简单来说以下代码能工作:

// obj 变量只能赋值为一个能兼容 object 类型的值,而 Node 确实兼容。
let obj: object = node;

而以下代码不能工作:

// fn 变量需要一个能处理 object 类型参数的函数,而赋值给它的函数只能够处理 Node 类型参数。
let fn: (obj: object) => void = (node: Node) => {};

如果还是有点想不通,那么先假设上面的代码能通过类型检查,以下这段代码就会出现问题:

fn(component);

你赋值给 fn 的函数以为传入的一定是 Node,但其实可以是其他 object 的字类型,比如是 Component,而这就会导致代码的运行出现错误。

这和是否严格模式没有太大关系,甚至和 ts 都没有什么关系,这是 c#、c++ 等任何编程语言都通用的知识。

普通的开发者不太了解,其实也正常,但引擎的开发者不了解,不好意思,我不太理解。

那么最后还有个问题,既然和严格模式没关系,为什么关闭严格模式会让错误消失?

因为这个选项:strictFunctionTypes

直接引用 ts 官方文档的话:

During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, the setting only applies to functions written in function syntax, not to those in method syntax:

意思大概就是需要兼容 js 有太多类型协逆变不安全的代码,所以只有当开启这个选项,才会严格检查参数和返回值的类型。

不开启时由于不会检查所以也就不会报错了。

而无论是否开启,ts 中,类方法永远也不会进行严格检查,因为 ts 还没有显式标记协逆变的方法。

说到这,其实说他们对引擎自己的脚本系统不了解,都算轻的了。
这只是关于类型的通用编程知识。
这只是引擎非常常用的模块。
他们表现出来的是甚至都没有怎么使用过。
而且我认为他们表现得非常傲慢。

请指出我上述的测试代码,为什么没有你说的报错

自己不去弄清楚原因,让普通用户指出为什么,why?

就不说引擎的其它更多的问题了,比如这个:

const data: Prefab | Node;
const node = instantiate(data);
// ts(2339) error here
node.getComponent(Label);

以上代码,node 会被推断为 Prefab | Node 而导致 ts 报错,但这个用法是正确并且并不算偏僻的,但是因为错误的引擎 d.ts 声明而报错。

以上种种的问题如果你懂的时候,确实你自己 as 一下就解决了。

然后这些问题就不会有人提出来,而是默默踩坑。

但当开发团队进来新人的时候呢?
刚学习 cocos 的学生们呢?
他们甚至可能还没法说清楚 js/ts 的关系,他们只能不断地重复地踩坑。
他们会觉得非常匪夷所思,why?我只想实例化一个节点出来而已,为什么就会产生报错?

你指望他们去了解这么多吗?刚学习的第一天就告诉他们什么是协逆变?告诉他们如何绕过引擎 dts 问题?
他们只会选择另一个引擎,然后说这个引擎写个 xx 都有问题!

引擎组傲慢、不重视的问题也不是一天两天了:

https://github.com/cocos/cocos-docs/issues/2733

误人子弟的文档到现在也不改。

没有新人哪有未来?

4赞

已经在沟通了 莫生气

实话说,可能我有些特性用得不多,没有感受到脚本系统有以上问题。

这份帖子是我在半夜编写,里面的情绪化内容太多了,我先为我的语气向引擎开发组道歉。

我总结下我想表达的观点:

  1. 我主要是不满引擎组解决问题的方式,明明自己试着写一下 tween 的代码就知道有协逆变问题,却消耗了这么多时间。

  2. 引擎的 dts 类型声明确实有上述问题,并且不止这些问题,这些细节问题其实是一个个坑会严重影响新人的使用。

4赞

话说论坛没通知的bug是没人修了吗 :sweat_smile:

看了下tween的ITweenOption这的target确实都是定义的object,没用泛型,确实有类型提示不正确的问题.

论坛的问题部分缺少复现,尽量发github 包括版本,系统,复现,如果不能复现成本太高了优先级就会很低
image

我的号100%收不到通知,论坛里好多人也这样

只有点赞会有通知,私信发出去对方也都收不到

我的也是一样的

image 看下设置 你的权限都是正常的 image

就这一个界面,你那权限我是普通用户没有对应设置的地方
image

所以尽量是在github提issue吗

抱歉,给你带来了这么不好的体验。

关于这个 github issue,我是有一些情绪的回复。原因是我觉得沟通交流,应该是建立在相互平等的角色。

首先,反馈问题,竟可能详细的描述问题,而不是一句话“这里应该是 any”。

Screenshot 2024-05-11 at 17.56.02

其次,引擎维护者并不是每个人对每个系统都非常了解透彻。当分配到某个模块某个任务,并不是当前处理这个 issue 的同学就对当前模块或者系统非常了解。当对当前系统并不完全了解的情况下,我们可能会多问一些情况,因为有时候复现环境问题会导致差异。而这个 issue 得到的回复却是「趾高气昂」「高高在上」:

Screenshot 2024-05-11 at 17.57.02

Screenshot 2024-05-11 at 17.57.27

我想我的姿态也算是摆得特别低了吧? 帖子中也贴了 issue 地址,每条回复也都没有编辑过,既然也都甩到论坛上了,那么也欢迎大家去围观。

当然,在这个世界上,一个人也不可能做到让每个人满意。如果你觉得我高傲,那么就是吧,我没啥好反驳的。

是的,我承认我对 ts 这门蹩脚的语言并没有了解透彻(我之前的工作主要在处理原生相关的模块)。
由于刚接管这个脚本相关模块,也不清楚新建出来的工程是 ‘strict’: false 的情况,因此,跟进此 issue 的时候,我也先尝试写了几行代码复现问题,这也是正常的复现问题的流程,但是并无法复现出开发者反馈的问题,ts compiler 并没有报错,eslint 也没有错误,因此就继续在 github 上与开发者交流。但是得到的却是开发者的各种冷嘲热讽不耐烦自以为是的言语。

另外,脚本系统相关的 issue 近 200 个,这是一个深不见底、牵一发动全身的底层模块,处理起来相对会比较谨慎,新人开始维护也需要一些时间了解透彻,和尝试还之前的(背起锅)。

接起这个锅,请给我一些时间。如果没有及时回复,让你觉得是傲慢的话。那么这里抱歉了。

其实,可以搜索我账号 dumganhar 关联的中文论坛英文论坛、github 的所有回复,如果有你觉得傲慢的任何回复,那么我先道歉、道歉、再道歉。

https://forum.cocos.org/u/dumganhar/summary
https://discuss.cocos2d-x.org/u/dumganhar/messages
https://github.com/cocos2d/cocos2d-x/pulls?q=is%3Apr+author%3Adumganhar+is%3Aclosed

说实话,回复这个帖,我知道我会被怼死,因为我并不是一个擅长与人争辩的人。

最后,关于 loose 模式文档的事情。我的想法是做更好,需要细化,因为大部分情况下开启 loose 是对 性能有好处的,而某些时候, 比如对 … 的处理,的确会导致行为上不符合预期,这里应该针对一些行为上容易导致异常的选项,开放出子 loose 选项来让用户选择,而这子 loose 选项 默认关闭。平时琐碎的事情的确比较多,没有及时更新文档。其实,作为开源引擎,我们也希望开发者能够尽可能参与进来,我们的文档也是开放的,也欢迎开发者贡献对应的文档改进:https://github.com/cocos/cocos-docs

1赞

提示保错 运行不报错吧?

其实不用太在意,因为可能仅仅是刚好他那天心情不太好

抱歉当时语气太激动,帖子里其实说的是一直以来反馈问题官方给人的感觉

ts 是蹩脚的脚本语言的话,你觉得如果情况允许,不考虑生态之类的问题,什么脚本语言最好?

1赞

专职跟轮岗,差异就在这里。想要一个模块能够有很高的专业度,必然就需要有强力的人专职在一件事情上。
问题是今天哪个公司不是一波一波换人,一波一波调整架构,一个坑还没捂热就得去填新的坑。
作为脚本系统的上级管理者(不是我),也很难取舍,团队里强力的人就那么几个,你是希望他们多去做新功能,不断换新人来填老坑?还是希望他们就不断折腾老坑,把新功能留给新人去挖出更大的坑?
互相理解一下吧。

1赞

层面 位置 角度 出发点不同 还是尽量多点理解吧
就算是一个项目组在做同一个游戏的一批人 程策美都要经常打架 从制作人主策到运营客服 更别说还有玩家 玩家反馈问题到运营客服再到研发 也是会觉得官方态度怎么傲慢 不能真正理解 那就多点包容吧

俺的也一样!