Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[esnext] 建议优先使用const而不是let,以及“不得为了编写的方便,将可以并行的IO过程串行化”的示例代码需要改进 #23

Closed
hax opened this issue Nov 10, 2015 · 14 comments

Comments

@hax
Copy link

hax commented Nov 10, 2015

这示例代码用await作为bad的例子可能让人对async/await产生误解。建议把good改写为:

async function requestData() {
    const [tags, articles] = await Promise.all([
        requestTags(), 
        requestArticles(),
    ])
    return {tags, articles}
}

BTW,注意我把 let 改写为了 const

@errorrik
Copy link
Contributor

赞,我说怎么一直也觉得这个有点别扭。

另外,求教这里用const和let的区别?const也挡不住对对象本身的修改诶

erik168 added a commit that referenced this issue Nov 11, 2015
@hax
Copy link
Author

hax commented Nov 11, 2015

const并不是“常量”(使用const作为关键字只是因为它过去就是是保留字),只是不变。对于对象,它确保了引用不变。对象自身不可变,是需要由对象自己保证,或者等待将来加入struct之后,用struct(值类型)。JS存在一些古老的缺陷,比如Date对象居然是可变的,也要等到那个时候来修复。

这并不是不用const的理由。相反,应该首先使用const,实在需要可变才用let。eslint有对应的rule:http://eslint.org/docs/rules/prefer-const

@otakustay
Copy link
Member

const这个问题在社区上也是比较有争议的

支持const的表示应该控制变量值不可变来提升程度的健壮度,而不支持的则认为const应该更在语义上去表达一个“变量值和结构均不变”的更为严格的限制

本质上依旧是大家对const所应该表达的逻辑意义并不统一,我认为这种不统一是合理的,也并不会在短时间内趋于一个统一的结论,正如分号和换行的问题

我个人支持的是后者,即你看到一个const时,你就不能再修改其中的内容,而不论是否有freeze或者preventExtension之类协助你控制

@hax
Copy link
Author

hax commented Nov 11, 2015

symbol对应的值是否可变,与对象本身是否可变,在写代码的时候是两回事情。混为一谈是不妥当的。
在C++里表达这个也是要分别用两个const关键字来表达的。see http://stackoverflow.com/questions/7526152/easy-rule-to-read-complicated-const-declarations

有些人可能以为const应该是C#或PHP中那样的,即必须是编译时可确定的值,但那是另一回事情。js目前的语义并没有很大的需求要引入编译时常量。现在js的const的语义的定义是非常清楚的,我们最多说这个关键字叫const有点confusing(比如改用final(java)或readonly(如C#),然而C++之类的也是这么用const的,所以……),所以我认为目前的eslint的 prefer-const 的coding style是完全合理且应该采纳的。我们需要的是教育团队,此const并非是编译时常量,也不是代表值不变,而只是symbol的binding是readonly的意思。

@otakustay
Copy link
Member

对这个话题,我认为只从程序功能的角度去理解并不合适,例如在C#中一个local variable是可以在编译期确定值,我们会推荐用const吗?事实上整体社区上并没有这么做,从此我的推断是我们不能因为“某个关键字有x的作用”而得出“在x的情况下均使用它”

当然这个话题本身确实是争议性话题,不同的人肯定有不同的看法,只是我个人在现阶段还是保持着“在native immutable支持出现以前,让const代表immutable”这个观点

提供一些esdiscuss上的资料,与任何结论均不相关,仅供参考

@hax
Copy link
Author

hax commented Nov 11, 2015

几个问题:

一,编译期const是一个不同的话题,通常的coding style中,是否要把某个值声明为const并不仅仅因为该值是编译时常量(此仅是必要条件),而是该值应该是“真常量”,比如PI。因为通常编译器会直接替换掉const为值,意味着很可能在运行时不再有引用的过程。如果是假常量,则在万一变更后,你可能需要重新编译所有引用它的模块/单元,这很可能是不行(比如已经部署出去了)。

二,C#的coding style没有local var用readonly(类似js的const)是因为readonly只能用于类的field。所以它和js的const只是语义上相近,但实际使用场景确实不同。(js里需要用@readonly的decorator。)

三,在满足前述条件的情况下,我见过的coding style都是要求明确标出const/readonly/final的。

所以你举的这个例子并不成立。

实际上,因为“某个关键字有x的作用”而得出“在x的情况下均使用它”——这恰恰是显然的,否则干嘛设计这个特性。如果社区不用,那么说明这个语言特性设计得有问题。(比如js的with。)目前看来js的const并没有什么问题。

——除了名字可能不是最好,比如你给的第二个链接里说的let比const更短,或者重新设计的话应该用不同的语法——我自己心仪的是:let直接就是现在的const语义,而要声明可变的用 mut x 或 let var x(考虑不增加关键字)。不过现在已经没法改了。这名字的毛病毕竟是小问题。

@otakustay
Copy link
Member

仅针对上面所说的,我还是有一些不同的观点的

通常的coding style中,是否要把某个值声明为const并不仅仅因为该值是编译时常量(此仅是必要条件),而是该值应该是“真常量”,比如PI

所以为何到了JavaScript我们就希望采用一个不通常的coding style,让const用于很多和PI这种真常量明显不大一样的地方

通常编译器会直接替换掉const为值,意味着很可能在运行时不再有引用的过程。如果是假常量,则在万一变更后,你可能需要重新编译所有引用它的模块/单元,这很可能是不行

这个并不成立,不通过readonlyconst声明的内容同样会被编译器做常量替代,主流的编译器都是“过于聪明”的。事实上const除了语义上说明外,开发上的主要作用在于对人的compile stage safety,而不是对编译器的hint

C#的coding style没有local var用readonly(类似js的const)是因为readonly只能用于类的field

但是C#的const可用于local variable,以下代码接自MSDN关于const的说明页

public class SealedTest
{
    static void Main()
    {
        const int c = 707;
        Console.WriteLine("My local constant = {0}", c);
    }
}

但在实践中几乎没有见过这样的使用,以至于大部分C#程度员都不知道const是可以用在局部变量上面的

Java的final也同样可用于局部变量,以下代码接自Wikipedia对于Java的final说明页

for (final SomeObject obj : someList) {
   // do something with obj
}

同样,我们几乎见不到这么玩Java的

如果看动态语言,考虑到Python并没有类似const的功能,而Ruby则是以命名作为约定,constUPPER_CASE_WITH_UNDERSCORE的方式可能太丑,大家也不会这么去命名一个局部变量

在各语言这样的前提下,我确实看不以JavaScript独树一帜把const的使用泛化的前景

这恰恰是显然的,否则干嘛设计这个特性

这一点我也不能完全同意,不恰当地说刀有切肉的作用(const有不能二次赋值的作用)但我们不会拿它往活人的脖子这种肉上切(拿const放到所有不需要二次赋值的变量上)

从实践中,如果我们坚持“有X作用的东西我们在X的场景下都用”,也不难得出不少奇怪样的情况:

  • JSONP可用于GET场景的request -> callback,且支持跨域,因此我们所有GET都使用JSONP而放弃XHR
  • 解构可以一次定义多个变量,所以我们一个作用域内的所有变量统一用一个解构定义
  • 当IndexedDB存在时,它可以用于存放各种类型的数据,因此我们抛弃localStorage所有数据都放这边

所以我们应该切实地在此基础上给予对应场景的引导,一味看一个功能适应的所有场景套上去并不合适


最后,其实我也同意let + mut是最舒服的,但既然现在是let + const,那么我依旧支持let语义不变,const作为和mut完全相反的表达这种转换模式

@hax
Copy link
Author

hax commented Nov 12, 2015

解释一下:

为何到了JavaScript我们就希望采用一个不通常的coding style,让const用于很多和PI这种真常量明显不大一样的地方

C#的const和js的const语义是不同的,你不能直接比较。比较合理的比较是java的final/c++的const和js的const。所以像我之前说的,这纯粹是一个关键字选择的问题。大多数人可能更熟悉php/C#的const语义,而不是C++的const语义。这个不是coding style问题。

这个并不成立,不通过readonly或const声明的内容同样会被编译器做常量替代,主流的编译器都是“过于聪明”的。事实上const除了语义上说明外,开发上的主要作用在于对人的compile stage safety,而不是对编译器的hint

我讲的意思是,在不同的部署单元引用const,则const所在单元的新版本是不能变更const值的,因为该常量在编译时已经被inline到其他单元代码中,其他单元并不会动态链接新版本的值,这是为什么必须是真常量。没有标记const的field是一定不会做这样的处理的(除非是local的)。

但在实践中几乎没有见过这样的使用,以至于大部分C#程度员都不知道const是可以用在局部变量上面的... 同样,我们几乎见不到这么玩Java的

确实这样用的人比较少。相对来说C++的可能比较多。
我认为有以下原因:

  • Java/C#作为典型的命令式语言,长期以来Java/C#程序员习惯了变量,更少受到函数式编程不变量的影响。
  • Java/C#并不流行针对coding style的lint工具,所以虽然有些代码规范建议尽量const/final,但是并没有工具帮助实施。
  • C#的readonly只能用于field而不能用于local var(我认为这是一个设计缺陷),C#的const必须是编译期常量,且惯例是真常量(命名为全大写),所以大部分immutable变量场景不适合用const。
  • Java的final在不同场合有太多不同的语义(我认为这是一个设计失误),以至于程序员不太确定final的用法。
  • Java/C#的变量声明只需要类型(如 int i),导致const/readonly/final都是作为额外的修饰符,从而让程序员觉得麻烦/多余/累赘。

而所有这些因素在JS这边是完全不同的:

  • 最近几年函数式编程思想得到了更多的重视,尤其是JS社区,本来就有函数式传统,最近又受到多种编译到js的函数式语言的影响(如clojure、elm等),还有最近非常流行的immutable.js,所以社区对不变量的理解是不错的。尤其JS是现在新加入const关键字,结合整个ES5到ES6+编程模式的转变,正好有机会建立新的习惯。
  • JS有非常好的lint工具,且使用lint是社区的主流方式。且ESLint已经有prefer-const的规则可用了。
  • JS没有C#的readonly/const和Java的final的设计问题。(虽然const这个命名可能有点confusing,但是也不能说错误,因为C++也是这样用的。)
  • JS你总要使用var/let/const关键字,它不是额外的修饰符号。尽管const比let长是一个小问题,但是跟Java/C#的问题比完全可以忽略。

如果看动态语言,考虑到Python并没有类似const的功能,而Ruby则是以命名作为约定,const的UPPER_CASE_WITH_UNDERSCORE的方式可能太丑,大家也不会这么去命名一个局部变量

全大写是真常量(c#的const,java的static final且编译期可确定)的命名规范。js的const / C#的readonly / Java的final local,并不应该这样命名。(比如此点在Google的Java代码规范里也是这么写的:https://google.github.io/styleguide/javaguide.html#s5.2.4-constant-names )。

而 python/ruby 跟 es6 之前的 js 一样,是没有 readonly/const 的概念的,只存在基于命名约定的常量(其语义没有明确规定,纯粹大家自己看着办)。

在各语言这样的前提下,我确实看不以JavaScript独树一帜把const的使用泛化的前景

新语言,如 Rust / Swift / Scala(这个其实不算新了)都有一样的构造。Rust/Swift是let,Scala是val。其中Rust的语法let vs let mut是最理想的。Swift是let vs var,Scala是val vs var

const/let 的分化显然是比较新的候选工业主流语言(除了奇葩语言Go之外)的共识。所以并不是JS独树一帜。JS只是做了正确的选择。

后面的例子我就不说了。比喻总是容易出现各种问题。

@hax hax changed the title [esnext] “不得为了编写的方便,将可以并行的IO过程串行化”的示例代码需要改进 [esnext] 建议优先使用const而不是let,以及“不得为了编写的方便,将可以并行的IO过程串行化”的示例代码需要改进 Nov 12, 2015
@hax
Copy link
Author

hax commented Nov 12, 2015

我修改了本issue的标题,以反映后面的主要讨论内容。

@otakustay
Copy link
Member

  1. JavaScript的主流依旧是命令式编程,非常多年之内也仍然会是。最多会利用一些函数式编辑的局部思想来对一些容易出现的case进行补足
  2. eslint有prefer-const规则这样的理由我想还是不要在讨论的范围之内。工具会因为部分人的需求而增加不同功能的支持,但是因此认为推而广之是理所当然并不合适

在这个主题上我们已经各自发表了很多的意见, @errorrik 我建议之后我们从几个方面入手:

  1. 去找一些主流的ES6编写的框架、库、项目,看看社区上是怎么在做的,如有必要,我可以再email这些作者做一些交流,看看他们的意见如何
  2. CMC范围内的投票是必须的,把这个决定权交给更多的人吧

我现在来看使用const为主有这么几个好处:

  1. 可以通过let / const加上命名风格来区分“不变常量”、“不再赋值的变量”、“多次赋值的变量”这三者,而主用let只能区分出2个
  2. 程序健壮性有略微的提升但几乎可以忽略

更多的实际可得的收益我暂时没有想出来

无论主用let还是const我都写过1-2个月的代码,从写的角度对我来说都是能接受的,3个字符还是5个字符不影响我。从读的角度是let在流畅度上拥有略微优势,脑子转得比较舒服

另外 @hax 我想再追加一个疑问,在for .. of循环中你使用let还是const

for (let i of array) {
    console.log(i);
}

fot (const i of array) {
    console.log(i);
}

@hax
Copy link
Author

hax commented Nov 13, 2015

@otakustay for...of 里我以前是用 let 的,但自从用了 prefer-const 后,它自动提示我应该用 const。我一想还真是啊!类似的还有 for...ineslint/eslint#2739 ),只不过我现在的coding style里是完全禁止 for...in 的(可用 for (const [key, value] of Object.entries(o))替代)。

可见工具的帮助是非常大的。

工具会因为部分人的需求而增加不同功能的支持,但是因此认为推而广之是理所当然并不合适

这个说得当然没错,我们仍然要分析这个需求本身是否合理。

然而以前很多时候的问题不都是出在“可执行性”或方便程度上嘛。现在工具解决了这些问题(eslint甚至支持自动修复某些style而不需要人介入),所以我们可以更aggresive地去应用好的coding style,而不是总被一些习气而“平衡”掉。

而const/let在我的判断,并非是没有需求!

比如C#社区的需求:

比如Java社区的需求:
http://programmers.stackexchange.com/questions/98691/excessive-use-final-keyword-in-java
注意,因为我不用java很久了,所以之前不知道现在Eclipse/Netbean其实都支持自动加上final的。
http://programmers.stackexchange.com/questions/115690/why-declare-final-variables-inside-methods
注意,这里的代码是Android源码,也就是说Android的代码风格是都加final的(相当于js都用const)。

关于主流的ES6+写的库是怎么做的,这个不太好找。不过JS社区主流coding style之一,Airbnb的JS coding style已经开启了prefer-const。见:https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/es6.js#L43

@errorrik
Copy link
Contributor

由于工作日时间碎片化和周末贪玩,一直没有看完 @hax@otakustay 的讨论,今天总算看完了。在这个问题上,这是我见到过最精彩的讨论了。 @hax 一开始吓得我还以为哪里理解出了问题

好吧,说说我的一些想法:

  1. 以语言支持为准。语言提供了什么样的能力,就在这个范围内做文章,要不你也没办法啊。我虽然倾向const代表的是变量不变,但JS显然表示引用不变。所以我觉得,当你确实需要 原始值不变 或者 引用不变 时,应该const;其余场景,随意吧。其实const是这样的也没问题,JS程序员应该能区分值类型和引用类型,要不还出来写啥代码。
  2. 写什么就要像什么,写c++时函数参数加上const、c#时不可变的属性加上readonly,都是这些语言程序员的习惯,应当遵循。我个人判断,在const然并卵的场景,未来写ES6的大部分人,还是更愿意使用let而非const,理由有时没那么复杂,短呗。而且从字母组成来说,敲得更顺手......
  3. 按照const的实际含义:常量,我还是愿意为一个 在整个运行过程应该不变的变量对象 使用const的。这确实明确表达了我的愿望,虽然引擎不会为我保证这点。但是下面这种代码,tags和articles一眼就能看到马上就要被return回去了,在肉眼可见范围内也没改它,我实在不会去纠结这里非要用const。
async function requestData() {
    const [tags, articles] = await Promise.all([
        requestTags(), 
        requestArticles(),
    ])
    return {tags, articles}
}

其实我更喜欢python的风格:根据命名约定。所以我的JS世界观里大多是let,const我一直视作是一种补充。


以上只是一些个人喜好, @otakustay 按你说的,prefer-const这条是否通过,以StyleGuide Work Group投票结果为准。

@errorrik
Copy link
Contributor

voting, just a note

@errorrik
Copy link
Contributor

errorrik commented Dec 2, 2015

内部投票结论已出,该issue关闭。 @otakustay 可以继续补充一些case。


A. 强制prefer-const (0)
B. 建议prefer-const (2)
C. 不做要求 (10) √

@errorrik errorrik closed this as completed Dec 2, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants