Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Statement of our opinions on class fields and its related package of proposals #329

Open
zeldajay opened this issue Apr 19, 2021 · 2 comments

Comments

@zeldajay
Copy link
Member

对于class fields及其相关一揽子提案的意见陈述

Statement of our opinions on class fields and its related package of proposals

说明:本文原以中文撰写,我尽量确保翻译准确,但限于条件,可能有错漏。

Note: This article is formally written in Chinese, I try my best to translate it, but may include translation mistakes.

首先我们很支持委员会致力于为JS提供面向对象编程的更完整的解决方案,这一直以来都是JS缺失的部分。但是根据近几年对于class field和相关提案的讨论、争议和推进情况来看,我们认为这一系列提案并没有达到预期的质量,反而增加了广大普通开发者的心智负担,增加了工程中风险和成本的不确定性,很可能把JS中的面向对象编程的实践带向一个错误的方向。因此,我们很遗憾地声明,我们不同意此提案进入阶段4。具体理由如下:

First of all, we appreciate the committee's commitment to providing a more complete solution for object-oriented programming, which has been the missing piece of JS. However, based on the discussion, controversy, and advancement of class fields and related proposals in recent years, we believe that this package of proposals has not achieved the desired quality, but added the mental burden of the average developers, increased uncertainty about the risks and costs of engineering, and likely taken the practice of object-oriented programming in JS towards a wrong direction. Therefore, we regret to state that we do not agree with this proposal going to stage 4 for the following reasons.

一、public fields部分中的[[Define]][[Set]]语义的选择一直存在很大的分歧。既有代码里几乎无人在类构造器中主动使用Object.defineProperty()来定义属性,因此我们认为,选择[[Define]]在工程实践角度是很可疑的。对绝大多数普通开发者来说,[[Set]]显然是风险更小的方案。但委员会最终选择了[[Define]],并且也无法为主张[[Set]]的开发者提供合理的解决方案。我们认为,普通开发者要理解[[Define]][[Set]]的微妙差异及其后果,相当困难。即使理解了,要事先做出合理选择,也具有很高的心智负担,而且此种心智负担只是为了规避此人为的footgun,而缺乏实际的业务收益。而且[[Define]]的效果是跨越类的边界的,故此风险和成本会涉及工程中的所有参与方,任何一方的小失误都很可能在不经意中将隐患引入代码中。最终的错误仅当同时使用override accessor和[[Define]]时才触发。一些代表可能认为这类错误是小概率事件,因此可以忽略。我们却认为恰恰相反,正因为其是小概率事件,反而使得这类错误的诱因很容易潜伏于代码中,直到某个时刻爆发,这在工程风险管控上是非常糟糕的。反过来说,也因为是小概率事件,投入大量成本去防止这类错误是得不偿失的。更重要的是,由于是多个条件共同触发,单独看其中每个部分代码,甚至都很难界定错误的原因和责任。此外,我们提请委员会注意,本质上说,define own property语义与ES class的继承机制所基于的prototype机制是矛盾的,上述种种具体问题实为将两者硬性缝合在一起所必然导致之结果。

  1. The choice of [[Define]] and [[Set]] semantics in the public fields section has been highly controversial. It is rare in the existing code proactively uses Object.defineProperty() to define properties in the class constructor, so we believe that the choice of [[Define]] is very problematic from an engineering practice perspective. For the vast majority of average developers, [[Set]] is clearly the less risky option. Yet the committee ultimately chose [[Define]], and was unable to provide a reasonable solution for developers who want [[Set]]. We believe that it is quite difficult for average developers to understand the subtle differences between [[Define]] and [[Set]] and their consequences. Even if understood, it has a high mental burden to make a reasonable choice in advance, and such mental burden is only to circumvent the footguns relate to it, and provide no real business benefits. Moreover, the effect of [[Define]] affect the whole class inheritence hierachy, so the risks and costs could also spread to all parties in the ecosystem, and a small mistake by any party is likely to inadvertently introduce a potential problem into the code. The final bug is only triggered when both override accessor and [[Define]] are used. Some delegates argue that such bugs are a small probability and can therefore be ignored. We argue that, on the contrary, because they are small probabilities, they make it easy for the cause of such bugs to lurk in the code until they explode at some point, which is very bad for engineering risk management. Conversely, because it is a small probability event, it is not worthwhile to invest a lot of cost to prevent such bugs. More importantly, because the bugs only be triggered by multiple conditions together, it is difficult to look through each part of the code individually to recognize the potential issue, and it's even hard to differentiate the cause and responsibility for the bug. We also want to draw the committee's attention to the fact that, in essence, the define own property semantics contradicts the prototype mechanism on which the ES class inheritance mechanism is based, and that the problems described above are the inevitable consequence of sewing the two together.

二、hard private语义在社区中也一直受到质疑。我们完全赞同class fields提案的前champion之一kevin smith的意见,绝大多数普通开发者并不需要hard private那样完全禁止reflection的封装,而只是需要「足够」的封装。我们认可少数场景(比如模拟和实现平台API)需要hard private语义,我们也理解某些大框架和库的作者希望以hard private来阻止任何人触及内部状态和API,但我们认为这在所有JS开发者中只占很少数,并且这些平台API开发者、大框架和库的维护者,具有比普通开发者高得多的工程能力,完全可以自行使用WeakMap或其他手段来达成目标。相反的,诱使普通开发者过于轻易地使用hard private,会对工程和生态造成严重的后果。因为hard private在大量场景下会限制对象的使用,比如与基于Proxy的库和框架(如vue3、mobx等)、依赖reflection的场景(如序列化、白盒测试等)不能很好的协作。讽刺的是,简单地在现有代码中将一小部分代码抽取重构成一个private method,就破坏了这些场景的使用,从实践上说这当然就是一个breaking change,这与private能避免不必要的breaking change的承诺正好相反。

  1. The hard private semantics have been questioned in the community as well. We fully agree with Kevin Smith's opinion, who is one of the former champions of the class fields proposal, the vast majority of developers do not need hard private which prohibit all reflections, but just "enough" private. We recognize that a few scenarios (such as implementing/polyfilling platform APIs) require hard private semantics, and we understand that the authors of some large frameworks and libraries want to keep anyone from touching internal state and APIs with hard private, but we think this is a small minority of all JS developers, and these platform API developers, maintainers of large frameworks and libraries normally have powerful engineering ability than average developers, could achieve the hard private by using WeakMap or other tools easily. On the contrary, inducing ordinary developers to use hard private too easily can have serious engineering and ecosystem consequences. This is because hard private can limit the use of objects in a large number of scenarios, such as not working well with Proxy-based libraries and frameworks (e.g., vue3, mobx, etc.), cases which rely on reflection (e.g., serialization, white-box testing, etc.). Ironically, simply refactoring a small part of the existing code to a private method breaks the use of these scenarios, which is of course a breaking change in practice, contrary to the promise of private to avoid unnecessary breaking change.

三、特别的,我们要指出,public fields、private fields的语法与传统的属性的语法具有高度对应性,但语义大相径庭,public fields是基于define own property语义,private fields是基于WeakMap语义。值得注意的是,在绝大部分面向对象语言中,public/private成员除了可见性之外是没有任何语义差别的。如前所述,define own property与允许accessors的传统属性是不相容的,而private fields则根本不是属性。我们认为,语法的对应性在这种情况下非但不是优点,反而成了缺点。可以说,无论是普通JS开发者,还是具有其他面向对象语言经验的开发者,这样语法高度一致,但语义却不一致的情况,都是前所未遇。尤其令人担忧的是,这些后果在简单的场景中并不会立即暴露,使得开发者很容易忽视或低估其潜在影响。今天,JS生态已经相当复杂,我们预期这样的隐蔽问题很容易通过复杂的生态而扩散和放大。

  1. In particular, we would like to point out that the syntax of public fields, private fields and traditional properties have a high degree of correspondence, but the semantics are quite different, public fields being based on define own property semantics and private fields on weakmap semantics. It is worth noting that in most object-oriented languages, there is no semantic difference between public/private members other than visibility. As mentioned earlier, define own property is incompatible with the traditional properties that allow accessors, while private fields are not properties at all. We believe that syntactic correspondence becomes a disadvantage in this case instead of an advantage. It is fair to say that such a high degree of syntactic consistency but semantic inconsistency is unprecedented for both JS developers and developers with experience in other object-oriented languages. What is particularly worrisome is that these consequences are not immediately exposed in simple scenarios, making it easy for developers to overlook or underestimate their potential problems. Today, the JS ecosystem is already quite complex, and we expect that such hidden problems can easily spread and amplify through the complexity of the ecosystem.

四、此外,我们认为目前的提案对于面向对象编程的支持依然是不完整的,例如缺少等价或类似于包或模块私有、保护、友元的能力,缺少协议或接口、扩展方法等相关功能。部分功能虽然有提案,但仍处于非常早期的阶段,整体上缺乏明晰的roadmap。面向对象编程作为JS的最重要范式,我们有理由要求委员会通盘考虑,提供更坚实可靠的完整解决方案,以覆盖面向对象编程的典型需求,避免提案内容不同步、依赖不稳定、重复性工作等问题。从长期来看,也可以避免将来可能出现的提案冲突、约束限制、修复性工作等问题。

  1. In addition, the current proposals still has incomplete support for object-oriented programming, such as lack of equivalence or similar capabilities of package/module scope private, protected, friend, lack of protocols or interfaces, extensions, etc. Some of the features have proposals but are still at a very early stage and overall lack roadmap. Object-oriented programming as the most important paradigm for JS, it is reasonable to ask the committee to provide a complete solution and roadmap for JS all at once to cover the typical needs of object-oriented programming and avoid problems such as unsynchronized proposal content, unstable dependencies, and repetitive work. In the long run, it will also avoid possible future problems such as proposal conflicts, constraint limitations, and restorative work.

五、一些其他问题。如语法二义性,当前的class field提案,会导致出现相同语法产生不同语义的情况。例如x=1; y=x这么简单的代码,在class body和非class body,语义截然不同。这对code review造成严重影响。另外,class fields也引入了新的ASI hazard,且其中某些hazard比历史上过往的ASI hazard都更严重地削弱semicolon-less的代码风格的可行性。此外还有private static的this.#static会在子类中失败的问题,诸如此类,不一而足。尽管单独看每一个问题似乎也没有到致命的地步,因此在过往讨论中均作为提案必须之代价而予以容忍。但若从整体上观察,这三个提案的每一个都包含不尽人意之处,如同一艘木船上的小洞,虽然每一个洞并不很大,但却遍布船体的各个部分,令船上水手防不胜防。

  1. Some other issues. For example, syntactic ambiguity, the current class field proposal lead to situations where the same syntax produces different semantics. For example, the semantics of a simple code like x=1; y=x are very different inside and outside the class body. This has a serious impact on code review. The other one is the introduction of new ASI hazards, some of which are more severe than previous ASI hazards in history and undermine the viability of the semicolon-less coding style. There is also the issue of private static this.#static failing in subclasses. The list goes on and on. While each of these problems may not seem fatal when viewed individually, they have been tolerated in past discussions as the necessary cost of the proposal. However, when viewed as a whole, each of the three proposals contains unsatisfactory points, like small holes in a ship, each of which is not very large, but is spread throughout the hull, making it impossible for the sailors on board to prevent them.

综上所述,我们将坚持在2019年9月在TC39内部repo中所表明的立场,不同意该提案进入stage 4。

In summary, we will maintain our position as stated in the TC39 internal repo in September 2019 and do not agree with the proposal going to stage 4.

最后我们也想简单谈一下流程。根据TC39的流程,上述问题本该在进入stage 3之前解决,但是结果却没有。这些提案早在我们加入Ecma的两年之前就进入了stage 3。仅仅从这一点说,我们并不对这一令人遗憾的状况负有任何责任,反过来说,这也使得我们的意见无法对提案的走向产生任何实质性作用。事实上,在2017年这些提案进入stage 3之后,上述问题反复在相关repo的issue中被提出,许多TC39代表不断提出各种替代或补救方案,但最终没有取得任何进展。我们意识到,在现有的TC39流程下,上述问题不可能得到承认和解决。

Finally we would also like to talk briefly about the process. According to the TC39 process, the above-mentioned issues should had been solved before entering stage 3, but it turned out that they were not. These proposals entered stage 3 two years before we joined Ecma, and from that point of view alone, we are not responsible for this regrettable situation, which in turn makes it impossible for our comments to have any substantive effect on the direction of the proposals. In fact, after these proposals went to stage 3 in 2017, the above issues were repeatedly raised in issue list of the proposal repos, and many TC39 representatives continued to propose various alternatives or remedies, but ultimately no progress was made. We realize that the above issues are unlikely to be solved under the existing TC39 process.

而根据2020年7月流程文档的更新,block一个处于stage 4的提案需要「the block is related to implementation experience or the block identifies an issue or information which has not previously been discussed by the committee」,这使得在流程字面意义上我们的反对是无效的。对此,我们提出以下意见:

And according to the July 2020 process document update, block a proposal that is at stage 4 requires "the block is related to implementation experience or the block identifies an issue or information which has not previously been discussed by the committee", which makes our objection invalid as the process literally. However, we also note that:

一、我们在2019年9月已经在TC39的内部repo声明过我们反对class fields及其相关提案,从那个时候开始我们的立场并未发生改变。故我们认为2020年7月的流程更新不应具有追溯力。

  1. We have already stated our objection to class fields and its related proposals in the internal repo of TC39 in September 2019, and our position has not changed since that time. Therefore, we think that the July 2020 process update should not have retroactive effect.

二、我们所提出的前述问题中,包括一些本质性的设计问题(public fields与继承机制的冲突、hard private并不适合大部分JS开发者、复用语法但语义却截然不同)。因此我们认为这些提案在进入stage 3时并没有达到stage 3所应有的质量。

  1. The mentioned issues we have raised include some essential design problems (conflict between public fields and inheritance mechanisms, hard private not suitable for most JS developers, have very similar or identical syntax but very different semantic, etc.). We believe that these proposals did not enter stage 3 with the quality that stage 3 deserves.

三、在进入stage 3之前,class fields提案的private部分没有任何可让开发者进行尝试的实现,public部分虽有babel和TS实现,但关键语义是相反的。现有的流程要求在stage 3之后只考虑实现方面的反馈,这是可以理解的,但其前提是确保在进入stage 3之前,社区和开发者能进行有效的实验和提供反馈。而事实表明,这一前提并没有得到满足。

  1. Before entering stage 3, the private part of the class fields proposal did not have any implementation for developers to experiment with, and the public part had babel and TS implementations, but the key semantics were reversed. The existing process, which understandably requires that only implementation feedback be considered after stage 3, is predicated on ensuring that the community and developers can experiment and provide feedback effectively before moving to stage 3. And the facts show that this premise has not been met.

四、虽然stage 3意味着委员会达成一致,但过程上其实只是当时那次TC39会议上在讨论该话题时与会者没有反对。从当时的会议纪要看,进入stage 3的表决会议似乎只做了一些简要的讨论,丝毫未涉及到我们所提到的那些问题,一些对某些问题持不同看法的人似乎也并没有出席该次会议。在进入stage 3之后,有多位TC39代表不断提出各种class fields的替代提案或补救方案,这表明这些代表对该提案并不完全赞同甚至完全不赞同,故而我们认为虽有流程意义上的stage 3 consensus,但其基础是非常不牢靠的。无奈的是,撤销这一不牢靠的stage 3 consensus也需要新的consenus,而任何一个现有提案的支持者都可以阻止新的consensus。之前Kevin已经很好地阐述了这一困局,很遗憾TC39至今还未找到解决之道。

  1. While stage 3 meant that the committee reached consensus, the process was really just that the participants at that TC39 meeting did not object when the topic was discussed at that time. From the minutes of that meeting, it appears that the session that went into stage 3 was only a brief discussion that did not in any way address the issues we mentioned, and some of the people who disagreed on certain issues did not appear to be present at that meeting. After entering stage 3, a number of TC39 delegates kept proposing various alternative proposals for class fields or remedial solutions, which showed that these delegates did not fully agree or even completely disagree with the proposal, so we think that although there is a stage 3 consensus in the sense of process, its basis is very weak. Unfortunatly, withdraw such a weak stage 3 consensus requires new consensus which is very impossible, this issue has been well articulated by Kevin before, but we feel TC39 has not found a solution for such problem yet.

五、最后,我们认为流程的目的是为了产出高质量的标准,如果结果是有问题的,那么第一应该正视问题,第二应该检讨流程,如此才能解决问题。

  1. Finally, we believe that the purpose of the process is to produce high quality standards, and if the result is problematic, then firstly, the problem should be faced and secondly, the process should be reviewed so that we could have a basis of solving the problems.

由于时区和工作安排原因,我们无法派代表参加本次会议。因此我们将我们的意见发表于此处,谢谢。

Due to time zones and work schedules, we are unable to send a representative to this meeting. So we post our opinions here. Thank you.

三六零科技集团TC39代表
TC39 delegates of 360 Technology Group Co., Ltd

@TechQuery
Copy link

  1. [[Set]] is natural semantic of = (assign) a property, [[Define]] makes it hard to upgrade to libraries like MobX v6, why would I need a confused Syntax suger?
class A {
    set test() {
        // ...
    }
}
class B extends A {
    test = 1;

    constructor() {
        this.test = 1;    // It's so confused that these two ways aren't equal...
    }
}
  1. Private needs to be something in runtime for a pure JavaScripter:
#private_key === Symbol.for('private_key')

If a standard may become ES 4, we should stop and rebuild a ES 6, or JSer like me will use TypeScript private keyword & "useDefineForClassFields": false as a fork ecosystem forever.

I don't want JavaScript become confused language for beginners again...

Community developers first! Make the JavaScript great again!

@Pokute
Copy link

Pokute commented Apr 19, 2021

Object-oriented programming as the most important paradigm for JS, it is reasonable to ask the committee to provide a complete solution and roadmap for JS all at once to cover the typical needs of object-oriented programming and avoid problems such as unsynchronized proposal content, unstable dependencies, and repetitive work.

That doesn't sound like what the TC39 is. I think every proposal is ran by a volunteer and the committee can't just assign anyone to a proposal or work. The volunteers champion and work on proposals when they so feel.

Roadmaps and "complete solutions" are not the domain of TC39 and must be done outside it. Even then, all proposals must follow the correct committee process and thus the roadmaps would still be under the scrutiny of other delegates.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants