全文摘录于《上帝掷骰子吗?》,看完回味之余,觉得有必要整理下量子物理发展过程中比较重要的瞬间和那些颠覆认知的概念。

黄金时代

光是什么?自古以来科学家众说纷纭,但大体分为微粒说和波动说两派。最初微粒说以直线传播反射作为依据;波动说借衍射发展起来,但是人们认为光是种纵波,所以必须借助光以太这种虚无缥缈的物质而存在。

17世纪,格里马第认为颜色是因为光波频率不同引起,并得到胡克支持;牛顿在1672年完成光的色散试验后,认为光的复合和分解是不同颜色微粒的混合和分开,光是一种粒子,受到胡克和波义耳的批评后与胡克交恶。而后惠更斯引入波前的概念,以波动学的角度成功推导出光的反射和折射定律。而后牛顿环被发现,可以很轻松地被惠更斯的理论证明,自此波动说占据上风。

1704年,胡克死后次年,牛顿出版《光学》一书,以粒子说的角度分析了光的种种实验现象,并将之与自己的力学体系结合在一起。那时的牛顿出版了《数学原理》,且是微积分的发明人(尽管和莱布尼兹有争议),同时在政府担任要职,风头无二。从此,第一次波粒之争中微粒说以压倒性优势盖过波动说,大获全胜。

1804年,托马斯·杨在他的《自然哲学讲义》中描述他所做的光的双缝干涉实验,干涉形成的明暗条纹可以很好地被波动说解释。在发现无法更好解释时,微粒说以马吕斯在1809年发现的光偏振反驳。1819年,菲涅尔采用波动说很好地解释了光的衍射,泊松将之应用到圆盘衍射时,发现推导出阴影中心会出现一个亮斑,即泊松亮斑,并被阿拉果实验验证。同时,为了解释光的偏振,菲涅尔认为光是一种横波,而非纵波,并在1821年成功从理论中推导出偏振现象。最后1850年,傅科实验测量出光在水中的速度小于真空,从而奠定了波动说在第二次波粒之争中的胜局。

波动说并非没有弱点,因为光这种“横波”的传播速度如此之快,它的介质“以太”一定坚硬无比,但是一粒小小的灰尘就可以阻挡光的传播,这似乎又是匪夷所思的。波动说的解释是以太是稀薄且静止的。虽然有些牵强,但随着1856、1861、1865年麦克斯韦三篇电磁理论的论文发表,麦克斯韦预言光是电磁波的一种。1887年被赫兹的实验证实。自此波动说一统天下,此时的物理,经典力学、经典电动力学和经典热力学构成了经典物理的三大支柱,在当时看来,一切物理现象都在人们的控制之中,这个世界的所有基本原理都已经被发现。

乌云

黄金时代的喜悦没有维持太久,而后一些科学发现让科学家感受到不详的预兆:

  • 1895年,伦琴发现X射线
  • 1896年,贝克勒尔发现铀元素的放射性
  • 1897年,居里夫人和她的丈夫研究了放射性,并发现了其它放射性元素
  • 同年,J.J. 汤姆逊在研究阴极射线时发现电子
  • 1899年,卢瑟福发现元素嬗变

The beauty and clearness of the dynamical theory, which asserts heat and light to be modes of motion, is at persent obscured by two clouds

1900年,在开尔文的一次演讲中第一次提到“乌云”一词,其中提到的两朵乌云分别是:

  • 1881、1886年,迈克尔逊-莫雷实验否定了以太的存在
  • 经典物理在黑体辐射研究中的困境

前者导致了相对论的诞生,后者催生了量子论。

在黑体辐射中的困境是,针对能量分布和温度以及波长的关系,维恩得出的分布公式和瑞利金斯公式分别在长短波方面贴合实验结论。1900年,普朗克凑出了普朗克黑体公式,能完美符合各个波长上的实验结果。

在尝试使用经典物理学推出这个公式失败后,普朗克不得不做出这样的假定:能量在发射和吸收时,不是连续不断的,而是分成一份一份的。对于能量的最小单位,普朗克称为“能量子”,而后被称为量子(quantum)。量子的能量等于普朗克常数乘以辐射频率。

1
E=hν

那一年,普朗克42岁。

玻尔的原子模型

在光电效应中,人们发现对于特定金属,能不能打出电子之和光的频率有关,而打出电子的多少才和光强有关。特定频率的光打出的电子能量是有一个上限的,这个上限和光强并没有关系。

普朗克1900年的论文发表之初,并没有得到学术界甚至他自己的重视。1905年,爱因斯坦发展了普朗克量子化的思想。提出组成光的能量存在最小单位,称为“光量子”(light quanta),而后被简称为光子(photon)。光是以量子形式吸收能量,是一个瞬时作用,没有累积概念,光子的能量大小只与光频率有关。

1905年爱因斯坦的这篇论文同样并没有受到认同,因为光量子的概念和当时已尘埃落定波动说并不贴合。而后密立根的实验和康普顿研究X射线被自由电子散射时,很好地证明了爱因斯坦的观点。在1910年,索尔维赞助的第一次索尔维会议中,大家对骚动的量子理论莫衷一是。

1912年,考入剑桥的玻尔投入了卢瑟福的门下,这时卢瑟福正为它提出的原子行星模型苦恼。行星模型相对J.J.汤姆逊的枣糕模型更符合α射线轰击金箔的实验,但仍然说明不了为何电子可以稳定地围绕原子核运转。

玻尔受到巴尔末在1885年发现的氢原子谱线公式启发,提出了电子的能级和跃迁的概念。电子只能在特定能级上维持,在能级间跃迁时吸收或释放能量。玻尔的理论可以很好地推导出巴尔末公式,这系列论文发表于1913年。

玻尔模型提出伊始,得到了广泛地实验支持,同时预言了一些新的谱线存在,并很快得到证实。玻尔模型的能级假设十分具有量子化色彩。引入相对论和假定电子具有更多量子数后玻尔-索末菲模型也能成功解释塞曼效应和斯塔克效应(氢原子谱线受电磁场的影响)。通过这个模型,可以推导出一个原子的化学性质和它的最外层电子数相关,从而表现出一定的规律性,这也为周期表的存在提供了理论基础。为了解释电子为何能自发分层,泡利提出“泡利不相容原理”:同一层能容纳的电子数是有限的。

玻尔的量子化模型相对传统的麦克斯韦电磁体系走得实在太远,模型假设电子的能级、轨道是量子化的,但并没有解释为什么,总让人感受到不安稳。同时玻尔体系只能解释只有一个核外电子的原子模型。电子的跃迁时机,模型也无法解释,看似是完全随机的。

20世纪初,德布罗意在他的博士论文中提出,伴随电子运动的有一个超光速的相波,即德布罗意波。凭此论文德布罗意获得了1929年的诺贝尔物理学奖。德布罗意预言的德布罗意波而后被戴维逊和G.P.汤姆逊实验证实。这似乎又掀起了波动说和微粒说的战争。同时,玻色-爱因斯坦统计方法把光子视为不可分割的一种粒子,成功推导出普朗克的黑体辐射公式。波动说和微粒说似乎哪一种都不是、哪一种又都是。

1924年,玻尔、克喇默斯和斯雷特联名发表BKS理论,放弃了光量子假设,试图在波和粒子间建立一种对应,收效并不明显。

决定论?

在玻尔提出玻尔模型的时候,海森堡受邀前去哥本哈根工作,此时正是1924年,尝试统一微粒说和波动说的BKS理论提出不久,就被实验否定,光量子是实实在在的东西,而非只有统计意义。1925年4月,海森堡结束哥本哈根访问回到哥廷根后,使用矩阵的思路分析原子的运动模型。

海森堡从实验中观测到的,能够实实在在感受到的能级差出发,采用矩阵作为数学工具,将之运用在经典动力学公式中去,把玻尔-索末菲模型旧的量子条件改造成由矩阵推出的公式,进而可以很容易推导出原子能级和辐射频率。在和波恩和约尔当的合作下,海森堡发布《论量子力学》和《论量子力学II》。新体系马上获得巨大成功,矩阵力学在此基础上得到发展。受到海森堡矩阵启发,狄拉克借助泊松括号用更简单的方式相同的理论。乌伦贝克和古兹密特提出自旋模型,并得到矩阵力学支持。海森堡的模型将玻尔模型的范围推广至更广的范围(氦原子)。

1925年,薛定谔受到德布罗意启发,将电子看做德布罗意波,用一个波动方程去表示,从经典的哈密顿-雅克比方程出发,利用变分法和德布罗意公式求出了一个非相对论波动方程,即薛定谔波动方程。通过求解这个方程,可以很轻松地理解电子为何只能在特定能级运行。1926年1到6月,薛定谔一连发表多篇论文,并证明了经典力学知识波动力学的一种特殊表现。

尽管两派互不服气,但是薛定谔、泡利、玻尔等人证明这两种理论在数学上是等价的。在狄拉克的努力下,矩阵力学和波动力学作为一种理论的不同形式体现出来。前者更像微粒说,后者更像波动说。

我们观察下双缝干涉实验。对于波动说,在双缝时,电子可以很好地由薛定谔波动方程去描述,但是若此时关闭了一条缝,实验结果也就变成了一条缝而不是一片区域,电子所在的波到达缝时发生了什么呢。对于微粒说,电子穿过哪条缝似乎是完全随机,不可预期的,同时每个电子穿过狭缝时,是不可能知道狭缝间有多宽,甚至无法知道还存在其他狭缝,那么狭缝背后的干涉条纹的规律性怎么解释呢?更有意思的现象是,如果我们在每条狭缝上安装测量仪器,我们可以发现,每次只能在一条狭缝时测量到电子,但同时干涉条纹会随着测量行为而消失。

哥本哈根解释

上面提到的现象,似乎隐隐地体现出测量行为对理论表现的影响。海森堡在1927年反复思考矩阵运算中p × q ≠ q × p的特性,如果我们把p和q分别看做观测得到的结果,似乎说明,我们不可能同时观察得到p和q。原因是,观察本身就会影响我们对另一个物理量的测量。经过一阵计算,海森堡得出

1
△p × △q > h/4π

△p和△q分别代表对p和对q的测量误差。对其中一种的精确都会影响对另一种的粗略。这个理论被称为不确定性原理,抑或测不准原理。这个误差并不来自于测量仪器,而是在理论上就不可实现,就像永动机。p和q叫做共轭量,动量和位置就是一对共轭量。能量和时间也是一种共轭量,这意味着,对时间的精确会意味能量的模糊,在确定的某一刻,能量甚至可以凭借不确定性凭空出现,,由于质能本质相同,所以每时每刻我们的真空都在“沸腾着”,“幽灵”物质凭空涨落。

在看到海森堡的论文后,玻尔意识到,不确定性原理是具有普遍意义的,它“以一种极为漂亮的手法”显示了不确定性如何应用在量子论中。不确定性建立在波和粒子的双重基础上,对于粒子属性的了解越多,对于波属性就了解的越少

现在回头想一下那个问题:电子究竟是粒子还是波。玻尔认为:

任何时候我们观察电子,它只能表现出一种属性,要么粒子要么是波。但是作为整体来看,它表现出波粒二象性。如果你一定要问,电子本来是什么?我只能说我一点也不关心它本来是什么,我只关心我们能“观测”到它是什么。举个例子,我们对大自然的理解实际上都建立在我们观察到它是什么,它本质上是什么对我们没有意义(类似“白马非马”诡辩)。对于电子的观察方式不同,它的表现方式就不同,一旦观察方式确定了,电子就只能以一种特定的形式存在。

这就是玻尔的互补原理。它和波恩的概率解释、海森堡的不确定性三者构成了量子论“哥本哈根解释”的核心。 影响着人们对宇宙的根本认识。

回头再看对电子干涉实验的讨论,观察干涉条纹也好,在狭缝安装仪器也好都是不同的观察方式,得到的结果自然不同。谈论任何物理量都是没有意义的,除非你首先描述测量这个物理量的方式。再说的明确一点,一个物理量如果无法测量,那它就是没有意义的。换言之,不存在一个客观绝对的世界,唯一存在的就是我们能够观察到的世界。

这种解释把观测者和外部宇宙结合在了一起,即不存在一个观测者能独立之外的宇宙。这听上去似乎有点形而上学了。

这个解释当然并不完善,比如在你不观测时,电子以概率波的形式存在于空间,而只要你观察,它的波函数就会随机坍缩到某个具体位置。对此,哥本哈根的解释是,不观测时,对电子的讨论没有意义,只有数学可以描述——波函数,而在观测时才具有实在的意义。

薛定谔的猫

对于哥本哈根解释,许多科学家是并不能信服的,其中就包括笃信确定论和因果论的爱因斯坦。爱因斯坦认为,观察导致坍缩这一点暗示了某种超距作用,而这是和相对论相违背的。在1927年第五届索尔维会议上,爱因斯坦和玻尔展开了激烈的论战,以玻尔获胜告终。

三年后的第六届索尔维会议,爱因斯坦提出光箱实验反驳海森堡的不确定性原理,被玻尔通过广义相对论的红移效应完美解释。人们似乎感到,上帝真的掷骰子。

1933年,爱因斯坦和他的两个同事波多尔斯基、罗森共同提出“ERP佯谬”(名字来自三个首字母),试图说明量子论是不完备的。下面是一种简化了的版本:

我们想象一个自旋为0的大粒子衰变成两个小粒子,向相反两个方向飞去,两者的自旋方向必定相反。只要我们部去观察,每个例子自旋便都处在上/下两种概率的叠加态中,但我们只有观察其中一个例子,如A是上旋,那么B必定是下旋,可问题是B如何在A坍缩至上旋时,一定坍缩为下旋呢?

量子论的解释里面,观察后,例子A坍缩至上旋完全是概率现象,是在被观测一刻才有的意义,粒子B在此刻也会根据A的决定作出相应的坍缩。这个信号是怎么传递的。

玻尔解释说,在观察前,并不存在两个分裂的粒子,它们是一个整体,只能用波函数去描述。所以不存在超光速的信息传递。有意思的是,ERP佯谬是可以被实验证明的。后面也会提到。

相对爱因斯坦的思维实验,薛定谔的实验要更易理解和更出名。在他1935年《量子力学的现状》的第5节。薛定谔描述了著名的“薛定谔的猫”的实验。既然量子效应的匪夷所思还能让人容忍,那就把它放大到宏观世界里。

它设想了一个不透明的箱子,有一个处于衰变和不衰变叠加态的原子,只要原子衰变,就会激发一系列的连锁反应,打破箱子里的毒气瓶,从而杀死箱子里的猫。在量子论的解释里,只要我们不打开箱子,原子处于只能用波函数描述的叠加态中,此时猫也只能处于死和活的概率波叠加态中。这恐怕很难被接受。

那么我们把猫换成人呢?如果他能在纸上写下在箱子内的感受,他会这么认为吗?当然不会,他会坚定的认为自己从头到尾活得好好地,抑或是感觉不太妙。这是因为他作为观察者在箱子内不断观察自己的状态,不停地出发自己的波函数坍缩。

难道人和猫有区别吗?换句话说,难道因为人有能力“测量”自己存活与否(换句话说就是人有“意识”),而猫不能,所以人能让自己的概率波坍缩,而猫只能任由自己停留在死或生的概率波函数中?

真是匪夷所思。

坍缩和平行宇宙

冯·诺依曼指出使用仪器观测和人来观察并无区别,仪器本身也是由不确定的粒子组成,使用仪器观察只不过把粒子的叠加态转移到了仪器上,无限复归,直到人意识的参与。为了避免过于唯心的讨论,恐怕要探究下“意识”究竟是什么。

首先,意识并不是实际的物质存在,它基于我们的大脑而存在,有种观点认为:意识是组成原子群的一种“组合模式”,照这么推论一个人的意识理论上是完全可以复制和粘贴的。同时,由于模式才是本质,那么意识的载体并不重要,我们人类的肉体作为意识载体存在完全只是一种选项。意识也可以依附在机器上存在,它不过是输入输出间极为复杂的一种算法加上复杂的数据结构而已。关于意识的更多探讨,可以阅读罗杰·彭罗斯的《皇帝新脑》(原文推荐)

1979年,约翰·惠勒进行了一次延迟选择实验,他通过巧妙地设置半透镜的角度使得,在终点处观察者能否观测到光子直接影响光子在起点半透镜的选择。换句话说,观察者决定了光子的历史。推广而言,我们的观测行为本身参与了宇宙的创造过程。这实际上是加强版的人择原理。人择原理说,我们存在这个事实本身,决定了宇宙的某些性质是这样而不是那样的。参与性宇宙则表明,我们的存在不仅影响了宇宙的性质,甚至参与并创造了宇宙和它的历史。我们选择了宇宙,宇宙又创造了我们。

坍缩这个概念玄之又玄,于是有科学家提出了其他解释。比如艾弗莱特1954年提出了MWI(多世界解释)。按照艾弗莱特的说法,电子穿过双缝后,整个世界本身成为两个独立的叠加,我们观察到电子穿过左缝或右缝,是因为我们碰巧处在对应的分裂出来的宇宙中。量子过程产生的一切可能都会对应一个实际的宇宙,只不过大多数宇宙中,没有智能生物来提出问题。多世界解释又被称为平行宇宙。艾弗莱特假定任何孤立系统都必须严格按薛定谔方程演化。虽然宇宙只有一个波函数,但是这个宇宙态矢量十分复杂,它存在于高维甚至无限维的希尔伯特空间中。我们处在的每个世界不过是“真实”矢量在某个方向上的投影。

既然世界是叠加态的,电子也是叠加态的。那为何处在宏观世界的我们从未感受到量子尺度的叠加态呢?

退相干理论的提出尝试回答这个问题。理论提出,我们只谈论微观物体时,牵涉的粒子数很少,模拟它的希尔伯特空间维度自然也较低。但一旦牵扯到仪器或我们本人观察时,我们就引入了一个极为复杂的态矢量和维度极高的希尔伯特空间。这样,在低维空间相干的两个世界在此时被退相干,变得毫无联系,从而让我们感受不到彼此。量子叠加态的在宏观层面的瓦解正是退相干的直接后果。

退相干的提出让我们摆脱了对“观测”,“意识”的哲学讨论。让我们再次可以抽身于环境之外世界。宇宙重新成为唯一的主宰,人和观测者只是它的一部分。但是,为了解释电子行为而把整个宇宙拖下水的做法也受到一些质疑。

“量子自杀”实验在20世纪80年代提出,它实际上是薛定谔的猫的真人版,实验假设把猫换成人。对于哥本哈根解释,伴随原子衰变概率,有一半可能人活着,一半可能人死掉。MWI则认为,一个世界里的你活着,另一个世界的你死掉。然而,多宇宙预言:永远都会有一个“你”活着。其中活着的宇宙对你来说才是有意义的。那么为了验证MWI假说是否正确,那么只要做实验的人一直活着就可以了。如果假设成立,不论概率多么低,活着的你必定存在。对一直选择活着分支的你来说,你是“永生”的。这也就是说,如果MWI成立,那么对于某人来说,他无论如何自杀都不会死!因为按照MWI,在每个量子过程中都有两个世界分裂出来,这么多世界中必定包含你活着的那个。那么从该人的视角看,他怎么死都死不了。这就是从“量子自杀”中推出的“量子永生”。推广来看,总存在一些量子效应,使人不会衰老,那么,从主观意义上看,只要一个人有意识,那么他就必定永生。

量子计算机和其他解释

在量子理论中,一个量子不仅有0或1的可能,还可以表示0和1的叠加态。原来n个bit可以表示的信息,使用量子后可以表示2 ^ nbit。1985年,德义奇证明,量子计算机无法实现超越算法的任务,意味着它只能做普通图灵机能做的。但是计算同样的任务时,它能做的更快。量子计算机进行的是并行计算。当10bits的信息在处理时,量子计算机实际上在操作2 ^ 10 = 1024种状态。由于量子态纠缠很容易退相干,目前量子计算机进展有限。

有意思的是,哥本哈根解释和MWI对于量子计算机的算力有着不同的解释。MWI解释里,在n bits的信息处理时,存在着2 ^ n个宇宙的计算机在计算并最后汇总。哥本哈根解释认为,在观测(输出结果)前,宇宙中存在着2 ^ n个叠加的计算机在干活。两种听起来都挺不可思议的。

无论哥本哈根还是MWI在叠加态上的解释都离常识较远。按照德布罗意的思路,有一个导波造成了量子看似的随机性。量子论中一些随机的表现实际上我们不够了解的原因,有一些不可见的变量没有考虑进去才导致了系统的不可预知。一旦把这些额外的变量考虑进去,整个系统又变成完备、确定、可预测的。这种“隐变量理论”在1932年被冯·诺依曼的论文证明不存在。

1952年,大卫·玻姆发现冯·诺依曼推导中的假设存在漏洞,并重新使用“量子势”建立起新的隐变量理论。量子势称,粒子周围散发着一种势场,这种势弥漫在整个宇宙中,每时每刻对环境了如指掌。在电子向双缝进发时,势场会提前发现双缝存在并指导电子的行为。在观测时,测量仪器首先与势场相互作用,这会使电子发生一些微妙的变化。玻姆的理论虽然可以解释量子力学中的现象。恢复了世界实在性(世界可以独立在之外观察)、决定性(现象可预测)的同时但是破坏了定域性(不能存在超距的因果关系)。

1963年,贝尔发展了ERP佯谬,提出和定域性、实在性等价的贝尔不等式。而后在1982年,被阿斯派克特实验证明,粒子间存在违背贝尔不等式,即违背定域性、实在性的行为。而后又被一系列实验验证。说明我们宇宙并不存在定域实在性,即要么选择“观察者”,要么选择“超距信号作用”。

这次爱因斯坦输了,“上帝真得掷骰子”。

除了上面提到的哥本哈根、MWI和隐变量,还有许多别的解释:

  • 系综解释:我们的世界不存在单个的事件,每一个预测都只能是平均的,针对整个集合的。系综说,不要深究具体某个事件的意义,而且我们也不解释单个事件,不确定性在这里只是统计极限。这种解释并没有分析“坍缩”这个难题,只是简单将之踢出了体系之外
  • GRW理论(命名取自三个提出者的姓名首字母),理论认为不论宏观还是微观系统,都不可能严格意义上孤立,即与外界毫不相干。它们总是与外界发生交流,受一些随机过程影响,比如位置从弥漫的叠加态变成比较精确的准确位置。但是这种情况出现的概率太低,几乎要等很久很久才会出现一次(按照他们的估计约为)。我们称这种过程为“自发定域”。尽管微观粒子自发定域如此之难,但是组成宏观物体的粒子数的量级很高,所以宏观物体内每秒发生着成千上万次自发定域。同时,由于系统内的粒子间相互纠缠,少数几个粒子的自发定域会引起多米诺效应,从而让宏观物体一直处于定域状态。这种定域时间很短,所以我们没法感觉到叠加态(近日耶鲁大学有一篇类似的论文疑似发现了定域、抑或坍缩的发生过程确实是有时间的)。但是GRW解释中抛弃了能量守恒定律,另一方面,如果自发定域的时间和参与的粒子数有关,我们完全有能力安排只用小数量级的粒子去观测,如银离子,这时叠加态维持时间在GRW解释中应该会很久。
  • 退相干历史,根据这种解释,现实是唯一的,历史是叠加的,“精细历史”间是相关的,但我们并不关心精细历史,我们只关心能观测到的粗略历史。随着历史树向上粗略化,不同历史间退相干。

大统一与超弦

宇宙中存在4种力:

  • 引力
  • 电磁力
  • 强相互作用力
  • 弱相互作用力

20世纪60年代,弱作用力与电磁力统一成交换“中间玻色子”的力,而后量子色动力学(QCD)尝试统一强作用力。1968年,意大利物理学家韦尼基亚诺通过欧拉β函数提出韦尼基亚诺模型,并被南部阳一郎、萨斯金、尼尔森用来描述粒子,并发展出弦论。但根据弦论推导,我们的时空需要是26维的,因此并不受科学界欢迎。1971年,施瓦兹和雷蒙引入超对称思想,把26维简化为10维,之后施瓦兹和格林完成弦论和超对称结合,得到“超弦”。1982年,通过计算发现,超信理论可以很好地解释引力。1984年,两人发现,在理论自洽下的一些有限情况里,不仅可以包容规范场理论,还能包容粒子的标准模型。从而让超弦理论一炮而红。理论认为,我们生活在一个10维空间里,但是其中6个维度是紧紧蜷缩起来的。当我们把观察尺度降低到普朗克空间尺度上,会发现时空中的一个“点”,实际上是个6维空间,其中6个维度不停扰动,造成了全部的量子不确定性。

1995年,爱德华·威顿在超弦年会上统一了不同耦合常数下的5种弦论,这种统一理论成为“M理论”。目前,超弦理论仍在持续探索之中。

参考自Effective Dart,截至2019/06/12

通用原则

类似其他编程语言,有下面两点注意事项:

  • Be consistent, 统一风格
  • Be brief, 保持精简,DRY

最佳实践

指南以下面的关键词开头:

  • ,一定遵守,下面没有前缀的就是以此开头
  • 不要,这么做不是个好主意
  • 推荐,应该遵守,当不遵守时确保有合理理由
  • 避免,和上面相反,除非有足够好的理由,否则不应该这么做
  • 考虑,根据实际情况而定

同时会提到下面这些客体:

  • 库成员,顶级变量、getter、setter、函数
  • 类成员,类变量、getter、setter、函数
  • 成员,库成员或类成员
  • 变量
  • 属性,类中的成员变量、getter、setter,顶级变量、getter、setter

样式

标识符

  • 类名用UpperCamelCase风格
  • 库和文件名用lowercase_with_underscores风格
  • 导入前缀用lowercase_with_underscores风格
    1
    import 'package:javascript_utils/javascript_utils.dart' as js_utils;
  • 其他标识符使用lowerCamelCase风格
  • 推荐使用lowerCamelCase风格命名常量
    • 原因:CAPS_STYLE可读性差/可能会用于final变量/和枚举不搭
  • 把超过2个字母的缩略词当做一般单词来做首字母大写
    • 原因:提高可读性
  • 不要在标识符前加前缀
    • 举例:kTimes

顺序

  • 把”dart:”导入语句放在最前
  • 把”package:”放在相对导入前
  • 推荐把第三方”package:”导入放在其他语句前
  • export语句放在最后
  • 按字母序排序

格式化

  • 使用dartfmt帮你美化
  • 考虑让你的代码更容易美化
  • 避免每行超过80字符
  • 所有控制结构都使用大括号
    • 只有if语句写成1行时可以省略

文档

注释

  • 使用句子的形式表达注释
  • 用单行注释符表达注释

文档注释

  • ///表达文档注释
  • 推荐为公开API书写注释
  • 考虑为私有API书写注释
  • 用一句话为文档注释开头
  • 类似git commit message,第一行后空出一行独立成段
  • 去掉能从上下文直接读出的冗余信息
  • 推荐使用第三人称动词开头表示函数、方法注释
  • 推荐使用名词短语开头表示变量、成员、getter、setter注释
  • 推荐使用名词短语开头表示库、类型注释
  • 考虑在注释中添加示例代码
  • 在注释中用[]方括号引用作用域里的标识符
  • 使用简短平实的语言描述参数、返回值和异常
  • 在注解(annotation)前添加注释

Markdown

Dart允许在comment中使用Markdown格式。

  • 避免滥用markdown
  • 避免使用html格式化文本
  • 推荐使用反引号(```)格式化代码

行文

  • 推荐简洁清晰
  • 避免使用缩写和首字母缩略词
  • 推荐使用“this”而不是“the”来引用实例成员

实践

下面的规则是书写Dart代码时需要知道的指导原则,尤其是维护你类库的人。

  • 出于历史原因,Dart允许通过part of的方式使用库的一部分文件,使用时通过路径而不是变量名引用

    1
    2
    3
    4
    5
    6
    library my_library;
    // good case
    part of "../../my_library.dart";

    // bad case
    part of my_library
  • 不要从库的src文件夹下引用代码

  • 推荐使用相对路径应用库,但是不要跨src文件夹引用

字符串

  • 在长字符串场景下,使用邻接字符串而不是“+”链接
    1
    2
    3
    4
    // good case
    raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');
  • 推荐使用插值构造字符串
  • 避免在插值中使用多余的大括号(对于简单的变量)

集合

  • 尽可能使用字面量形式定义集合,必要时提供泛型类型即可
    1
    2
    3
    4
    5
    6
    7
    // good case
    var points = [];
    var userMap = {};

    // bad case
    var points = new List();
    var userMap = new Map();
  • 不使用length属性判断集合是否为空,Dart提供了isEmptyisNotEmpty
  • 考虑使用高阶函数来明确表达你的意图
    1
    2
    3
    var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);
  • 避免Iterable.forEach()中使用函数声明,Dart里的for-in循环可以很好完成该工作,当然函数本身已经定义好除外。
    1
    2
    3
    4
    5
    // good case
    for (var person in people) {
    ...
    }
    people.forEach(print);
  • 使用iterable.toList替代List.from,只在改变list类型时使用List.from
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Creates a List<int>:
    var iterable = [1, 2, 3];

    // Prints "List<int>":
    print(iterable.toList().runtimeType);

    // Prints "List<dynamic>":
    print(List.from(iterable).runtimeType);

    // Use it with a type
    var numbers = [1, 2.3, 4]; // List<num>.
    numbers.removeAt(1); // Now it only contains integers.
    var ints = List<int>.from(numbers);
  • 使用高级的whereType方法从collection中过滤出特定类型元素
    1
    2
    var objects = [1, "a", 2, "b", 3];
    var ints = objects.whereType<int>();
  • 有类似用法时,不使用cast()方法
    1
    2
    3
    4
    5
    6
    var stuff = <dynamic>[1, 2];

    // Good case
    var ints = List<int>.from(stuff);
    // Bad case
    var ints = stuff.toList().cast<int>();
  • 避免使用cast()方法,用该方法可能更慢且更有风险,通常情况下有下面一些备选方案
    • 创建有正确类型的list
    • 使用每个集合元素时进行casting操作
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // Good case
      void printEvens(List<Object> objects) {
      // We happen to know the list only contains ints.
      for (var n in objects) {
      if ((n as int).isEven) print(n);
      }
      }

      // Bad case
      void printEvens(List<Object> objects) {
      // We happen to know the list only contains ints.
      for (var n in objects.cast<int>()) {
      if (n.isEven) print(n);
      }
      }
    • 真正想要强制类型转换时,使用附加类型的List.from

函数

  • 使用函数声明形式命名有名函数(不要使用lambda表达式)
  • 当有有名函数可以完成任务时,不要创建lambda表达式
    1
    2
    3
    4
    5
    6
    7
    // Good case
    names.forEach(print);

    // Bad case
    names.forEach((name) {
    print(name);
    });

参数

  • 使用=分隔入参和它的默认值
  • 不要显式地使用null作为默认值(直接不指定即可)
    1
    2
    3
    void error([String message]) {
    stderr.write(message ?? '\n');
    }

变量

  • 不要显式地使用null初始化变量(语言保证了行为可靠性,不需要再显式设置成null)
  • 不要存储computed value(即可以推算出的值) ,减少冗余信息,保证数据唯一可信源,使用getter和setter去动态推导出它们
  • 考虑忽略局部变量的类型,Dart有强大的静态分析工具帮你推断类型。

成员

  • 不要创建没必要的getter和setter
  • 推荐使用final限定只读属性
  • 考虑使用=>实现只有单一返回语句的函数,对于多行语句建议还是老老实实使用花括号
    1
    2
    3
    get width => right - left;
    bool ready(num time) => minTime == null || minTime <= time;
    containsValue(String value) => getValues().contains(value);
  • 不要使用this.访问成员,除非遇到变量冲突
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // Good case
    class Box {
    var value;

    void clear() {
    update(null);
    }

    void update(value) {
    this.value = value;
    }
    }

    // Bad case
    class Box {
    var value;

    void clear() {
    this.update(null);
    }

    void update(value) {
    this.value = value;
    }
    }
  • 尽可能地在定义变量时初始化该值

构造函数

  • 尽可能使用更简洁的初始化形式
    1
    2
    3
    4
    class Point {
    num x, y;
    Point(this.x, this.y);
    }
  • 不要在初始化形式中定义类型
  • 使用;代替{}表示空方法
    1
    2
    3
    4
    class Point {
    int x, y;
    Point(this.x, this.y);
    }
  • 不要使用可选的new来返回一个对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Widget build(BuildContext context) {
    return Row(
    children: [
    RaisedButton(
    child: Text('Increment'),
    ),
    Text('Click!'),
    ],
    );
    }
  • 不要无谓地使用const(基本上const可能出现在所有你能使用new的地方),因为有些语境已经隐式包含了const语义
    • 字面量集合
    • const构造函数调用
    • metadata注解
    • switch的每一个case

异常处理

  • 不要在on以外的语句中丢弃错误,因为在没有on限定时,catch会捕获所有异常
  • 要只在编程错误时抛出Error的异常
  • 不要显式地捕获Error及其子类
  • 使用rethrow重新抛出异常

异步

  • 推荐使用asyncawait提升你的异步代码可读性
  • 只在必要的时候使用async
    • 代码块中使用了await
    • 希望返回一个Future
    • 希望更方便地处理异步中出现的Error
    • 异步事件发生具有先后顺序
  • 考虑使用高阶函数处理stream
  • 避免直接使用Completer
  • Future<T>而不是T判断FutureOr<T>的具体类型

API设计

命名

  • 使用一致的术语
  • 避免缩写,只使用广为人知的缩写
  • 推荐把描述中心词放在最后
  • 考虑尽量让代码看起来像普通的句子
  • 推荐使用名词短语命名非布尔类型的成员或变量
  • 推荐使用非命令式短语命名布尔类型成员或变量
    • 比如配合be动词的不同时态,isEnabled, hasShown
    • 配合助动词,比如hasChildren, canSave
  • 有可能的情况下,考虑省去上一种情况里的动词
  • 推荐使用正向含义的布尔类型变量/方法名
  • 推荐使用命令式动词命名带有副作用的函数和方法
  • 考虑使用名词短语或非命令式动词命名返回数据为主要功能的方法或函数
    1
    2
    list.elementAt(3)
    string.codeUnitAt(4)
  • 考虑使用命令式动词表示你需要对方法所做工作有所关心
  • 避免使用get开头的命名,它通常能用getter代替
  • 推荐使用to___()来命名类型转换
  • 推荐使用as___()来命名类型快照
  • 避免在命名中使用方法、函数的入参
  • 使用助记符命名类型参数
    • E代表集合元素
    • KV代表key和value
    • R代表return type
    • T, SU命名单一通用且上下文表意清晰的泛型
    • 除上面情况外,可以使用完整词汇作为泛型类型名

下划线开头的成员表示成员是私有的,这个特性是内置在Dart语言中的。

  • 推荐使用私有声明,未用_开头的库中的公开声明、顶级定义表示其他库可以访问这些成员,同时也会受到库实现契约的约束。
  • 考虑在同一个库内定义多个类,这样便于在类之间共享私有变量

Dart是纯OOP的语言,它的所有对象都是类实例。当然不像Java,Dart也允许你定义顶级的变量、函数…

  • 避免定义一个函数就可以实现的只有一个实现方法的抽象类
    1
    2
    3
    4
    5
    typedef Predicate<E> = bool Function(E element);

    abstract class Predicate<E> {
    bool test(E element);
    }
  • 避免定义只有静态成员的类,可以使用顶级变量、函数更方便地实现等价效果。当然,如果变量属于一个组,可以这么实现
  • 避免不必要地定义子类
  • 避免实现一个不作为接口的类
  • 避免mixin不设计用作mixin的类
  • 在你的类支持拓展时,定义好文档
  • 在你的类作为接口存在时,定义好文档
  • 在你的类作为mixin存在时,定义好文档

构造函数

  • 考虑在类支持的情况下,让构造函数成为const

成员

  • 考虑尽可能地把成员变量和顶级变量定义为final类型
  • 使用setter和getter定义computed value
  • 不要使用没有getter的setter
  • 避免在返回bool,double,int,num的方法里返回null
  • 避免在方法中返回this,只为了串联调用函数

类型

Dart中的类型可以帮助使用者理解你API中的静态类型设计,它分两种:类型注解和类型参数。前一种放在变量名前注解变量类型,后一种作为泛型参数传入。

1
2
3
4
5
6
bool isEmpty(String parameter) {
bool result = parameter.length == 0;
return result;
}

List<int> ints = [1, 2];

在未指定类型时,Dart会从上下文自动推断或者使用缺省的dynamic类型。

简言之,Dart提供了强大的类型推导简化了你声明类型的负担,但同时不声明类型会降低API的可读性,下面一些guideline帮你在两点间找到一个平衡。

  • 推荐对于类型表意不清晰的public属性和顶级变量使用类型注解
    1
    2
    3
    Future<bool> install(PackageId id, String destination) => ...

    const screenWidth = 640; // Inferred as int.
  • 考虑对于类型表意不清晰的private属性添加类型注解
  • 避免为局部变量添加类型注解,如果你需要静态类型提供的便利,可以借助is限制变量类型
  • 避免在方法表达式上使用类型,考虑到方法表达式通常作为方法入参,类型可以自动推断,不需要类型注解
  • 避免冗余的泛型和类型注解
    1
    2
    3
    4
    5
    // Good case
    Set<String> things = Set();

    // Bad case
    Set<String> things = Set<String>();
  • 在不希望使用Dart推断的类型时,使用类型注解
  • 推荐使用显示的dynamic代替Dart推断失败回退的dynamic
  • 推荐在Function类型注解中添加函数类型签名
  • 不要为setter指定返回值
  • 使用新式的typeof判断类型
    1
    typedef Comparison<T> = int Function(T, T);
  • 使用Object代替dynamic表示可以接受任何对象
  • 使用Future<void>作为无返回值的异步函数返回类型
  • 不使用FutureOr<T>作为返回值

参数

  • 避免位置参数作为可选布尔参数,这样可读性比较差
    1
    2
    3
    4
    5
    6
    7
    // Bad case
    new Task(true);
    new Task(false);
    new ListBox(false, true, true);
    new Button(false);

    // Good case
  • 避免将用户想忽略的参数放在位置可选参数的前列
  • 避免使用强制的无意义的参数
    1
    2
    // Bad case
    string.substring(start, null)
  • 使用左闭右开区间表示两个参数代表的范围

相同判断

  • 覆写==的同时覆写hashCode,默认的哈希函数实现了恒等式哈希。任何两个相等的两个对象必须具有相同的哈希值
  • ==需要遵循数学的相等规则
    • 自反,a == a
    • 对称,a == b => b == a
    • 传递,a == b && b == c => a == c
  • 避免为可变对象自定义相等函数,hashCode函数会增加你的工作量
  • 不要在自定义==中判断null,Dart也已经替你做了这部分工作

入门

1
2
3
4
5
6
7
8
9
10
// 定义个方法。
printNumber(num aNumber) {
print('The number is $aNumber.'); // 在控制台打印内容。
}

// 这是程序执行的入口。
main() {
var number = 42; // 定义并初始化一个变量。
printNumber(number); // 调用一个方法。
}
  • 注释:///* ... */,同其他主流语言
  • 类型:num、String、int、bool等
  • 字面量:42,’Hello world!’
  • 函数:类似print()的形式
  • 字符串插值
  • 入口方法:main

基本理念

  • 所有可以用变量引用的都是对象,每个对象都是一个类的实例,例如数字、方法、null,所有对象都继承Object类
  • Dart是强类型语言。但是不强制使用类型标注,因为它可以通过推导得到变量类型。在你明确不希望有类型时,使用dynamic关键字表示动态类型
  • Dart支持泛型,比如List<int>
  • Dart支持顶级方法main(),支持类的静态方法、实例方法,也可以在函数内使用函数
  • 类似地,Dart支持全局变量、局部变量和在类中定义的成员变量
  • Dart没有public、protected、private的区分,如果标识符以_开头,那么该标识符就是私有的
  • Dart的变量名只能以下划线和字母开头,后跟字符或数字
  • Dart区分语句块和表达式,只有表达式有值。

关键字

分为三类:

  • 对于只在特定上下文环境下生效的上下文关键字,可以用作标识符
  • 对于内置标识符,为了便于移植JavaScript代码到Dart,这些关键字不可用作类或类型名或import的前缀
  • 其他关键字为保留字

变量

1
2
3
4
var name = 'Dart';

String name = 'Dart';
Dynamic name = 'Dart';

根据基本理念,变量都是存储值的引用。使用var修饰时,变量类型会自动推导;也可以显示声明变量类型,或者使用dynamic关键字表示变量可能有多种类型。

任何没有初始化的变量默认值都为null。

常量使用finalconst(实例变量只能用final)。

1
2
3
4
5
6
7
8
final name = 'foo';
final String title = 'FE';

const foo = 10000;
const double percent = 0.314;

final bar = const[];
const baz = []; // 和上面一个效果
  • final变量只能赋值一次,const变量是编译时常量。
  • const除了用来定义不变量,还可以用来创建不变的值,以及定义创建常量的构造函数。在这么用时可以省略构造过程,像上面的baz变量一样

内置类型

  • numbers
  • strings
  • booleans
  • lists (也被称之为 arrays)
  • maps
  • runes (用于在字符串中表示 Unicode 字符)
  • symbols

再次重申,Dart中变量都是一个对象,所以你都可以使用构造函数来初始化。

Number

intdouble两种类型。提供了原生操作符和abs()等常用函数,整数和浮点数的字面量初始化类似js。

字符串和数字互转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// String -> int
var one = int.parse('1');
assert(one == 1);

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');

String

Dart的字符串是UTF-16编码的字符序列。可以使用单引号或双引号创建。字符串中用${expr}的语法使用表达式,**如果表达式是一个标识符,可以省去{}**,对{}内的表达式,Dart使用toString()方法转成字符串使用。

使用'''"""表示多行字符串。使用r''表示纯字符串。

Boolean

布尔类型有两个字面量值,truefalse。和JavaScript不同的是,在if语句等使用bool类型的地方,只有true被认为是true,其余所有值都是false。这也是为了避免JavaScript中判断true、false时坑爹的地方。

1
2
3
4
5
6
7
if (1) {
print('JS prints this line.');
} else {
print('Dart in production mode prints this line.');
// However, in checked mode, if (1) throws an
// exception because 1 is not boolean.
}

List

List的字面量写法和JavaScript一样。Dart会做类型推导,在元素类型不一致时报错。你可以使用const语句定义一个不变的List对象。

1
2
const list = [1, 2, 3];
var list = const [1, 2, 3];

2.3后,Dart支持...解构操作符,以及对空列表兼容的...?。同时支持collection if和collection for语法。

1
2
3
4
5
6
7
8
9
10
11
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];

Set

2.2版本后支持

一组元素唯一的无序列表。字面量写法类似数学中集合的定义方法。

1
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

也可以使用构造函数的方式创建。

1
2
3
var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);

类似List,2.3之后有......?的语法支持。

Map

表达键值对数据,每个键只出现一次,且可以是任意类型。类似Set,可以使用字面量和构造函数两种方式构造。使用字面量时,Dart会做类型推导。

Map的设置和JavaScript类似,另外类似List,2.3之后有......?的语法支持。

Rune

Dart用Rune类型表示UTF-32的字符,如emoji等。

Symbol

用来代表Dart中声明的操作符或标识符,可以在标识符前添加#获取标识符的Symbol对象。

方法

类似JavaScript,Dart中的Function也是对象并具有Function类型。推荐使用显式类型声明方法。

1
2
3
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}

Dart支持箭头函数。

可选参数

可选参数分两种:命名参数、位置参数。

命名参数使用param: value指定,在调用时使用{param1, param2}的形式传递参数。支持在参数前添加@required表示参数必选。

位置参数使用[]包裹方法参数,使用时不传参数即可。

1
2
3
4
5
6
7
8
9
enableFlags(bold: true, hidden: false);
enableFlags({bool bold, bool hidden}) {
// ...
}

String say(String from, String msg, [String device]) {
// ...
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');

定义方法时,可以使用=定义可选参数的默认值。否则默认值为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String say(String from, String msg,
[String device = 'carrier pigeon', String mood]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device';
}
if (mood != null) {
result = '$result (in a $mood mood)';
}
return result;
}

assert(say('Bob', 'Howdy') ==
'Bob says Howdy with a carrier pigeon');

main函数

每个应用都需要有顶级的main()函数作为入口,返回值void类型,并且有可选的List<String>参数(用于解析命令行输入的参数数据)。如

1
2
3
4
5
void main() {
querySelector("#sample_text_id")
..text = "Click me!"
..onClick.listen(reverseText);
}

上面的..语法为级联调用,表示在一个对象上执行多个操作。

第一公民

类似JavaScript,Dart中Function可以作为参数、返回值、变量、对象使用。同样也有匿名函数可以使用,区别是箭头后是语句块时,不使用箭头,只在之后是表达式时使用箭头。

作用域与闭包

Dart是静态作用域,即变量的作用域在写代码时就确定了,作用域层级即大括号的层级。

类似JavaScript,Dart的闭包意味着方法可以封闭其作用域内的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// Returns a function that adds [addBy] to the
/// function's argument.
Function makeAdder(num addBy) {
return (num i) => addBy + i;
}

void main() {
// Create a function that adds 2.
var add2 = makeAdder(2);

// Create a function that adds 4.
var add4 = makeAdder(4);

assert(add2(3) == 5);
assert(add4(3) == 7);
}

返回值

所有函数必须返回一个值,否则默认return null

操作符

  • ~/返回取整截断的商
  • 使用==判断相等性
    • 会调用左侧对象的==方法,和后面的对象对比
  • 类型转换:
    • as,类型转换,类似typescript中的as
    • is 判断对象是否是指定类型
    • is! 判断对象是否不是指定类型
  • ??=在value不是null时赋值给变量
  • expr1 ?? expr2表示如果expr1是非null则返回其值,否则执行expr2并返回
  • .. 级联操作符,表示在一个对象上连续调用多个函数以及访问成员变量,可以嵌套
  • ?..类似,但是在左侧操作对象为null时返回null
    1
    2
    3
    4
    5
    6
    7
    8
    final addressBook = (new AddressBookBuilder()
    ..name = 'jenny'
    ..email = 'jenny@example.com'
    ..phone = (new PhoneNumberBuilder()
    ..number = '415-555-0100'
    ..label = 'home')
    .build())
    .build();

流程控制

  • for循环中,Dart会自动捕获当时的index索引值,避免JavaScript中问题。对interable的对象可以使用forEach()方法遍历,对List、Set还支持for-in形式的遍历
  • switch中的每个case(除了空case)都必须有break
  • assert在检查模式下会被跳过

异常

和JavaScript中的异常类似。

不一样的是,可以使用oncatch捕获异常,可以通过rethrow在其中重新抛出异常。

  • 构造方式类似ES6中引入JavaScript Class。
  • 用成员方式声明的类变量在定义时初始化,也就是在构造函数前
  • 可以使用Object的runtimeType属性来判断实例的类型
  • 使用const关键字结合构造函数可以构造出不可变的对象实例

构造函数

使用和类名同名的方法作为构造函数(或者使用命名构造函数)。因为把构造函数参数赋值给实例变量的场景太常见了,Dart提供了下面的语法糖。

1
2
3
4
5
6
7
8
class Point {
num x;
num y;

// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}

可以使用命名构造函数实现多个构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
num x;
num y;

Point(this.x, this.y);

// Named constructor
Point.fromJson(Map json) {
x = json['x'];
y = json['y'];
}
}

子类不会从父类继承构造函数,在未定义构造函数时,会有一个默认构造函数,这个函数没有参数,且会调起父类的没有参数的构造函数。

在有初始化参数列表(initializer list)的情况下,初始化参数列表在父类构造函数前执行。

  1. 初始化参数列表
  2. 父类无参构造函数
  3. 子类无参构造函数

父类没有无参构造函数时,需要手动调用父类的其他构造函数。

初始化列表

在执行父类构造函数前,可以初始化实例参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
num x;
num y;

Point(this.x, this.y);

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map jsonMap)
: x = jsonMap['x'],
y = jsonMap['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}

在冒号右边用逗号隔开初始化表达式。注意:等号右边无法访问this

重定向构造函数

重定向构造函数没有代码,在构造函数声明后,用冒号调用其他构造函数

1
2
3
4
5
6
7
8
9
10
class Point {
num x;
num y;

// The main constructor for this class.
Point(this.x, this.y);

// Delegates to the main constructor.
Point.alongXAxis(num x) : this(x, 0);
}

常量构造函数

如果类支持提供状态不变的对象,需要定义一个const构造函数,且所有类变量都要是final

1
2
3
4
5
6
7
class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y);
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
}

工厂构造函数

当你的构造函数不需要返回新对象,而从其他地方获取时(如缓存),使用工厂构造函数。**工厂构造函数内无法访问this**。调用时方式和普通构造函数等同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Logger {
final String name;
bool mute = false;

// _cache is library-private, thanks to the _ in front
// of its name.
static final Map<String, Logger> _cache =
<String, Logger>{};

factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name];
} else {
final logger = new Logger._internal(name);
_cache[name] = logger;
return logger;
}
}

Logger._internal(this.name);
}

var logger = new Logger('UI');

方法

类方法可以访问this,另外对于类对象的每个属性都有隐含的getter和setter(final除外)。也可以显式使用getset定义getter和setter的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rectangle {
num left;
num top;
num width;
num height;

Rectangle(this.left, this.top, this.width, this.height);

// Define two calculated properties: right and bottom.
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
  • Dart使用extends继承,用super指代父类,用@overide注解重载操作。
  • Dart中有抽象类/抽象方法,设计和使用类似Java的抽象类/抽象方法。如果你希望抽象类可实例化,可以定义一个工厂工造函数。
  • 每个类都隐式的定义了一个包含所有实例成员的接口,通过使用implement实现若干其他类的API(不包括构造函数)
  • 可以重载一些操作符,如+, -, [], >>等,实现在特定类上的特定表现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector {
final int x;
final int y;
const Vector(this.x, this.y);

/// Overrides + (a + b).
Vector operator +(Vector v) {
return new Vector(x + v.x, y + v.y);
}

/// Overrides - (a - b).
Vector operator -(Vector v) {
return new Vector(x - v.x, y - v.y);
}
}

有意思的是,Dart提供noSuchMethod()方法,在访问不存在的类实例或方法时被调用。如果没有填写,默认使用Object的同名方法。

1
2
3
4
5
6
7
@proxy
class A {
void noSuchMethod(Invocation inv) {
print('You tried to use a non-existent member: ' +
'${inv.memberName}');
}
}

枚举

枚举是特殊的类,使用enum关键字定义。每个枚举值都有index属性的getter函数,枚举的values常量可以返回所有枚举值。

1
2
3
4
enum Color { red, green, blue }
assert(Color.red.index == 0);
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

mixin

Dart中提供了多类继承中重用类代码的mixin,用with结合mixin类实现,这种类没有构造函数。除非你想像正常类一样使用mixin,否则使用mixin关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;

void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}

当限制mixin只在特定类中使用时,结合on让mixin也能调用父类方法。

1
2
3
mixin MusicalPerformer on Musician {
// ···
}

类变量、函数

使用static前缀修饰,表示类级别的变量、函数。类变量只在第一次使用时初始化。静态方法无法访问this。

泛型

使用泛型的两个动机:

  • 有助于IDE、环境、同事帮你定位问题和代码自动生成
  • 减少重复代码

List和Map的泛型定义类似C++风格。

1
2
3
4
5
6
7
8
9
10
var names = <String>['Seth', 'Kathy', 'Lars'];
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};

var names = new List<String>();
var views = new Map<int, View>();
print(names is List<String>); // true

在泛型中使用extends可以限制泛型的具体类型。在1.21之后,Dart支持泛型函数。

1
2
3
4
5
6
T first<T>(List<T> ts) {
// ...Do some initial work or error checking, then...
T tmp ?= ts[0];
// ...Do some additional checking or processing...
return tmp;
}

包管理

使用importlibrary引入和导出模块。_开头的标识符只在库内部可见。

1
2
3
4
5
6
7
import 'dart:html';
import 'dart:io';
import 'package:mylib/mylib.dart';
import 'package:utils/utils.dart';

import 'package:lib2/lib2.dart' as lib2; // 指定库前缀,避免重名
import 'package:lib1/lib1.dart' show foo; // 部分导入

dart:开头代表内置库,package:开头代表外部库。外部库使用pub包管理器管理。

懒加载库

懒加载即在使用时再加载库,如优化app启动时间,加载很可能用不到的功能。

加载时使用deferred as导入,使用loadLibrary()方法加载。

1
2
3
4
5
6
import 'package:deferred/hello.dart' deferred as hello;

greet() async {
await hello.loadLibrary();
hello.printGreeting();
}

异步支持

Dart中返回FutureStream的方法都是异步的,意味着设置好耗时操作(I/O)后就返回。类似ES7中的awaitasync,你也可以像组织同步代码一样组织你的异步代码。

Dart中声明异步方法是在函数名后加入async,这类方法返回一个Future对象,了解JS中Promise的同学可以很快理解Future是做什么的。

1
2
3
4
5
6
7
8
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}

在返回值是Stream时,使用await for的形式接收Stream中的数据。另外别忘了用async修饰外界函数。

1
2
3
4
5
6
7
Future main() async {
// ...
await for (var request in requestServer) {
handleRequest(request);
}
// ...
}

生成器函数

惰性生产数据,类似ES6中的function*。Dart提供两种类型:

  • 同步:返回Iterator
  • 异步:返回Stream
1
2
3
4
5
6
7
8
9
10
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}

可调用的类

类中实现了call()方法时,类实例可以当做方法调用。

1
2
3
4
5
class WannabeFunction {
call(int a, int b) => a + b;
}
var wf = new WannabeFunction();
wf(3, 4); // 7

类型别名

类似typescript中的interface定义,Dart可以借助typedef进行一些更复杂的类型判断。typedef只是类型别名的一种说法。

1
2
3
4
5
6
7
typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
assert(sort is Compare); // True!
}

元数据

使用元数据给代码添加额外信息,也能便于文档自动生成。

  • @deprecated
  • @override
  • @proxy

你还可以自定义自己的元数据注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library todo;

class todo {
final String who;
final String what;

const todo(this.who, this.what);
}

// another file
import 'todo.dart';

@todo('seth', 'make this do something')
void doSomething() {
print('do something');
}

注释

  • 单行,//
  • 多行,/**/
  • 文档注释, ///开头,或/**开头,*/结束

遵守规范的注释风格会有助于文档自动生成。

《程序员修炼之道》这个书名实际上不如原版《The Pragmatic Programmer: From Journeyman to Master》来得更清晰明了。此书第一版写于1999年,我看的是11年的版本,但还是透流露着不少世纪初的观念和视野。除开一部分观点认识的过时,书中绝大多数观点都四溢着国外互联网行业的工作风格和流程,和国内凶猛生长、一把梭就是干的风格各有不同,甚至我感觉国内IT,尤其是互联网企业里的工作流更加讲求实效。尽管和读之前的预期不大一样,里面许多经验还是很有借鉴意义,比如正交性、不要依赖巧合等等。

书中内容在项目管理、编程哲学、以及编程过程的各个方面都有涉及,在这些方面上更加深入的探讨,书的最后也推荐了继续阅读的材料。下面就各章节的关键观点加以整理。

关于本书的读法:

  • 能不能让正确原则指导正确的行动本身,其实就是区分是否是高手的一个显著标志
  • 要能内化书中提到的各个小提示,不实践的话,是不会有太大收获的
  • 思考,你的工作,多思考

实效哲学

  • 对你的源码负责
  • 不要容忍破窗,它们会增大你软件的熵(这个也是要结合实际情况看的)
  • 记住大图景,注意方向是否有误,不要光低头做事
  • 知道在何时打住,你不可能做到完美
  • 定期为你的知识资产投资,就像经济投资一样
    • 定期投资
    • 多元化
    • 管理风险,不要把所有技术放在一个篮子里
    • 低买高卖,能看清形势
  • 为此,你需要
    • 每年至少学习一种新语言
    • 每季度阅读一本技术书籍
    • 也要阅读非技术书籍
  • 批判地思考你读到和听到的
  • 如何表达自己很重要,这会增加你的影响力
    • 你想让谁知道
    • 你想让他们知道什么
    • 他们是否感兴趣
    • 他们需要知道细节么
    • 如何促使他们与你交流

实效途径

  • DRY,不要重复自己
    • 强加的重复 => 使用自动生成,减少是信息冗知识
    • 无意的重复 => 优良的设计
    • 无耐心的重复 => 考虑长远
    • 开发者间的重复 => 加强组内交流,制定代码规范,制造更容易复用的环境
  • 减少无关事物的影响,非正交 => 次级效应 => 补偿行为 => 经验依赖
    • 好处:提高生产率(促进复用)、降低风险(风险隔离,易测试)
    • 分层设计、抽象和接口约定
    • 避免使用全局变量
    • 考虑使用库的代码侵入性
    • 文档和认同正交性
  • 不存在最终决策,当需求变动频繁的时候,不仅要思考程序架构如何适应这种变动,也要反思是否是设计者没想清楚到底要做什么
  • 使用曳光弹找到目标,即MVP + 快速迭代 + 即时的反馈
    • 逐步逼近,摸着石头过河
    • 曳光弹模式永远包含着一个可用的软件版本
  • 原型和便笺,使用原型去表达和快速验证项目的可行性
  • 靠近问题领域编程
    • 使用DSL(Domain Specified Language)
  • 估算,以避免意外,主要用来估计工期、分析风险
    • 适当地降低估算速度,慎重思考隐藏的风险

基本工具

工具成为双手和大脑的延伸,优秀的工具可以放大你的才干。

  • 纯文本以其自解释能力几乎可以永久保存,XML、JSON就是利用此成为通用的数据表现形式
  • 利用shell的力量,它是自动化任务避不开的工具
  • 要能烂熟地使用你的编辑器要使用高级的编辑器,由于你的所有开发工作都建立在它上面,做好这两点可以节省你大量时间
    • 反思一下,在用你的编辑器时,你有遇到过到比较繁琐的操作吗?
    • 是因为你不会高级使用方式,还是编辑器本身不支持?
  • 总是使用源码控制(这一点早已成为共识)
  • 调试,debug
    • 通常认为匪夷所思的bug,都来自健忘、自大和愚蠢
    • QA角色的重要性之一:帮助复现、找到规律性
    • 橡皮鸭调试法
    • 二分查找法
    • 如果bug来自某人的错误假设,那么需要清除团队其他人的相同误解
  • 学习一种文本操纵语言或工具,例如awk,sed,处理数据和结果时一定能用上
  • 编写代码生成器
    • 开发者手动触发,如模板代码
    • 程序自动出发,如scheme to idl,idl to code

偏执编程

  • 通过合约设计,约定好接口,合作方基于接口开发
    • 强类型语言更容易实现这一点
  • 早崩溃,这在需要编译的软件开发上比较科学,对于web应用来说却不是这样
  • 使用断言确保某事不会发生,减少预设条件代理的隐藏bug
    • 不要滥用断言
  • 只在异常处用异常
  • 一定记住释放请求的资源,如内存、句柄等,可以通过封装统一的资源类实现自动的资源释放

时间的魔力

  • 德墨忒尔法则,使模块间的依赖减少到最小
    • 物理解耦
  • 要配置不要hardcode,使用元数据动态描述你的程序
    • 抽象放进代码,细节放进元数据
  • 时间耦合:考虑并发和事件的发生顺序
    • 在异步代码中,总考虑并发
  • 一些GUI的设计模式
    • 发布订阅
    • MVC
  • 基于规则/规则集的黑板系统
    • 黑板给出统一接口
    • 耦合方通过调用黑板接口避免耦合
    • 黑板通过规则给出输出

编码时

  • 不要靠巧合编程
    • 改动要有文档沉淀
    • 只依靠文档中记录的行为
    • 把你的假设记入文档
  • 在大数据量时,考虑算法数量级
    • 兼顾效率和可读性
  • 早重构,常重构,代码是业务设计的近似同构体,常重构才能保证代码完美贴合需求设计
    • 重构和功能开发分开进行
    • 重构一定要有测试
  • 优秀的代码不是看新增了多少行,而是看删除了多少行
  • 编写易于测试的代码
    • 测试你的软件,否则你的用户会代你做测试

项目开始前

完美,不是在没有什么需要增加,而是在没有什么需要去掉的时候达到的。

  • 去挖掘需求,思考用户做特定事情的原因,和如何去做的方式,让需求成为一种一般性的陈述
    • 制作需求文档时的一大危险是太过具体,好的需求文档会保持抽象
    • 经常性复盘
    • 鼓励文档分享和交流
  • 巧妙解决看似不能解决的难题,关键要找到真正的约束,去思考
    • 有更容易的方法么
    • 你是在解决真正的问题,还是被外围的技术问题转移了注意力
    • 这件事为什么是一个问题
    • 是什么让它难以解决
    • 它必须这么做么
    • 它必须完成么
  • 准备好再开始,但不要让它成为你懈怠的借口
  • 不要成为方法学的奴隶

实效项目

  • 团队建设
    • 不留破窗户(考验leader的管理能力)
    • 经常性的复盘和例会
    • 减少团队成员分工的冗余
    • 自动化项目流程 => 效率工程团队开发内部工具
    • 制造context,给成员足够空间
  • 不要使用手动流程,它不可控且难以复制
    • shell、crontab
    • CI和自动化持续集成
    • 代码生成
    • 自动化测试
    • 代码review和源码版本控制流程
  • 常测试,早测试,自动化测试
    • 单元测试/集成测试/压力测试/回归测试
    • 测试状态覆盖,而不是代码覆盖,代码覆盖率提供的意义有限
  • 关于如何生产文本
    • 所有文档都是代码的反映
    • 源码注释应该去把项目里那些难以描述、容易忘记、不能记录在其他地方的东西记载下来
    • 比无意义的名称更糟糕的是有误导性的名称
    • 除非有程序或人工维护,否则任何形式的文档都只是快照
  • 温和地超出用户期望,如
    • 友好的新手指引
    • 快捷键
    • 自动化安装
  • 自豪地为你的作品签名

更多资源

  • 《人月神话》
  • 《Unix编程艺术》
  • 《Effective C++》
  • 《集市与大教堂》

–END–

正文:《经济学原理》 - 微观经济学原理笔记

导言

经济学十大原理

  • 稀缺性:社会资源的有限性
  • 经济学:研究社会如何管理自己的稀缺资源
  • 机会成本:为得到某种东西必须放弃的东西
  • 理性人:系统有目的地尽最大努力实现其目标的人
  • 边际变动:对行动计划的增量调整
  • 激励:引起一个人做出某种行为的某种东西
  • 市场经济:在许多企业和家庭在物品与服务市场上交互交易时。通过他们的分散决策配置资源的经济
  • 产权:个人拥有并控制稀缺资源的能力认证
  • 生产率:单位劳动投入所生产的物品与服务数量
  • 通货膨胀:经济中物价总水平的上升
  • 经济周期:就业和生产等禁忌活动的波动

像经济学家一样思考

  • 循环流量图:一个说明货币如何通过市场在家庭和企业间流动的经济模型
  • 生产可能性边界:生产要素和生产技术固定时,一个经济所能生产的数目组合的图形
  • 微观经济学:研究家庭和企业如何做出决策,以及它们如何在市场上相互交易
  • 宏观经济学:研究整体经济现象
  • 实证表述:描述世界时什么样
  • 规范表述:描述世界应该是什么样

相互依存性和贸易的好处

  • 绝对优势:一个生产者用比另一个生产者更少的投入生产某种物品的能力
  • 比较优势:一个生产者以低于另一个生产者的机会成本生产某种物品的能力

市场如何运行

供给和需求的市场力量

  • 市场:由某种物品或服务的买者与卖者组成的群体
  • 竞争市场:买者和卖者都很多,以至于每个人对市场价格的影响都微乎其微
  • 需求量:买者愿意并且能够购买的一种物品的数量
  • 需求定理:其他条件不变时,价格和物品的需求量成反比
  • 正常物品:随着收入增加需求量增加的物品
  • 低档物品:随着收入增加需求量减少的物品
  • 替代品:一个物品价格的上升引起另一种物品需求量上升的物品
  • 互补品:一个物品价格的上升引起另一种物品需求量下降的物品
  • 供给量:卖者愿意并且能够出售该物品的数量
  • 供给定理:其他条件不变时,价格和物品的供给量成正比
  • 均衡:市场价格达到是供给量和需求量相等的状态
  • 均衡价格(市场出清价格):使供给和需求平衡的价格
  • 供求定理:认为任何一种物品的价格都会自发调整并达到平衡

弹性及其应用

  • 弹性:更亮需求量或供给量对某种决定因素的变动的反应程度指标
  • 总收益:对于一种物品,买者支付而卖者得到的量
  • 需求收入弹性:消费者收入变动程度对一种物品需求量变动程度的影响程度
  • 需求的交叉价格弹性:衡量一种物品需求量对另一种物品价格变动的反应程度
  • 供给价格弹性

供给、需求和政府政策

  • 价格上限:出售商品的法定最高价格
  • 价格下限:出售商品的法定最低价格
  • 税收归宿:税收负担在市场参与者中分配的方式

市场和福利

消费者、生产者、市场效率

  • 福利经济学:研究资源配置如何影响经济福利的学问
  • 支付意愿:买者愿意为某种物品支付的最高价
  • 消费者剩余:买者愿意为某种物品支付的量减去实际支付的量
  • 成本:卖者为了生产一种物品必须放弃的所有东西的价值
  • 生产者剩余:卖者出售一种物品得到的量减去其生产成本
  • 效率:资源配置使得社会所有成员总剩余最大化的性质
  • 平等:社会成员平均分配经济成果的性质

赋税和国际贸易

  • 无谓损失:市场扭曲(如税收)引起的总剩余下降
  • 世界价格:一种物品在世界市场上通行的价格
  • 关税:对国外生产、国内销售的物品征税

公共部门经济学

外部性

  • 外部性:一个人的行为对旁观者福利的无补偿影响
  • 外部性内在化:改变激励,使人们考虑到自己行为的外部效应
  • 矫正税:引导私人决策者考虑外部性引起的社会成本的税收
  • 科斯定理:私人各方可以无成本地协商资源配置时,他们就可以解决外部性问题
  • 交易成本:各方在达成协议与遵守协议过程中发生的成本
  • 排他性:物品使用可以阻止另一个人使用的特性
  • 消费中的竞争性:使用一种物品将减少其他人使用的特性
  • 私人物品/公共物品/公共资源/俱乐部物品

税制

  • 预算赤字:政府支出大于收入
  • 预算盈余:政府收入大于支出
  • 边际税率:增加收入引起的额外税收
  • 平均税率:支付税收占收入的比例
  • 定额税:对每个人征收的等量的税收
  • 受益原则、支付能力原则
  • 纵向平等、横向平等
  • 比例税、累进税、累退税
  • 粘蝇纸理论

企业行为和行业组织

生产成本

  • 总收益:企业出售产品得到的货币量
  • 总成本:企业用于生产的投入品的市场价值
  • 利润:总收益 - 总成本
  • 显性成本:需要企业支出货币的投入成本
  • 隐形成本:不需要企业支出货币的投入成本
  • 经济利润:总收益 - 显性成本 - 隐性成本
  • 会计利润:总收益 - 显性成本
  • 生产函数:投入量和产量的函数
  • 边际产量:增加一单位投入引起的产量增加
  • 边际产量递减:投入边际产量随投入增加而递减的特征
  • 固定成本:不随产量变动的成本
  • 可变成本:随产量变动的成本
  • 平均总成本、平均固定成本、平均可变成本、边际成本
  • 有效规模:使总成本最小的产量
  • 规模经济、规模不经济、规模收益不变

竞争市场上的企业

  • 竞争市场:有许多交易相同产品的买者和卖者,以至于买卖双方都是价格的接受者
  • 平均收益、边际收益
  • 沉没成本:已经发生而且无法收回的成本

垄断

  • 垄断企业:一种没有相近替代品的产品的唯一卖者
  • 自然垄断:一个企业能够以低于绝大多数企业成本的方式向市场供给物品或服务产生的垄断
  • 价格歧视:对不同顾客以不同价格出售同一物品

垄断竞争

  • 寡头:只有少数几个提供相似或相同产品的卖家的市场
  • 垄断竞争:存在许多出售相似但不相同的企业的市场结构

寡头

  • 勾结:市场上的企业就生产产量或收取价格达成的协议
  • 卡特尔:联合行事的企业集团
  • 纳什均衡:相互作用而经济主体再讲其他所有主体所选策略为既定时,选择他们自己最优策略的状态
  • 囚徒困境、占优策略

生产要素市场

  • 生产要素:生产物品和服务的投入
  • 劳动的边际产量:增加一单位劳动所引起的产量增加量
  • 边际产量值:一种投入的边际产量乘以产品价格
  • 资本:用于生产物品与服务的设备和建筑物
  • 补偿性工资差别:抵消不同工作非货币特性而产生的工资差别
  • 人力资本:对人的投资的积累,如教育、在职培训
  • 歧视:对仅仅是种族、民族、性别、年龄或其他个人特征不同的相似个人提供不同机会
  • 贫困率、贫困线
  • 功利主义:主张政府应该选择使社会上所有人总效用最大化
  • 自由主义、自由至上主义
  • 负所得税:向高收入人群征税给低收入家庭补助

深入探讨的论题

  • 预算约束线:对消费者可以支付得起的消费组合的限制
  • 无差异曲线:表示给消费者同等满足程度的消费组合的曲线
  • 边际替代率:消费者愿意以一种物品交换另一种物品的比率
  • 吉芬物品:价格上升引起需求增加的物品
  • 道德风险:一个没有受到完全监督的人从事不诚实行为的倾向
  • 逆向选择:从无信息一方的角度看,无法观察到的特征组合变为不合意的倾向
  • 经济政治学:用经济学的方法研究政府
  • 康多塞悖论、阿罗不可能定理、中值选民定理
0%