参考《社会心理学》 David G. Myers 第8版

社会心理学 Part 2 - 社会影响 社会心理学 Part 3 - 社会关系 社会心理学 Part 4 - 应用

导论

是什么

什么是社会心理学?社会心理学研究:

  • 人们如何看待他人(社会思维)
  • 人们如何影响他人(社会影响)
  • 人们如何相互关联(社会关系)

类似经济学等其他人文社科,社会心理学亦有些基于上面几方面的重要观点

  • 我们个人构建起社会现实
  • 我们天生具有的社会直觉是强大的,但有些时候是危险的
  • 社会影响塑造行为,比如我们渴望彼此之间建立关联、渴望归属感、渴望得到他人的良好评价
  • 个人态度和性格倾向塑造行为
  • 社会行为同样也是生物性行为,我们是社会、心理、生物的共同产物

社会心理学不同于社会学:

  • 社会学研究团体,社会心理学研究个人
  • 社会学较难控制研究因素来做实验,社会心理学则可以控制变量来模拟实验

社会心理学不同于人格心理学:

  • 人格心理学研究个体间差异,社会心理学研究个体的社会因素
  • 人格心理学历史悠久,有诸多大师;社会心理学则比较年轻

另外不论是心理学还是别的学科,都是对一个事实的不同表述,每个学科都需要一些公设作为基础,它们之间没有高下之分。

价值观对研究的影响

直接影响来自于:课题选择、投身人群、实验目标等等方面。间接影响来自于:

  • 科学具有主观性,心理学则更是如此
  • 研究者在做价值判断时会受到个人价值观的影响
  • 当讨论从是什么变成怎么样时,就会把价值观纳入讨论之中

另外,特别澄清一个误解:社会心理学不过是常识而已嘛。社会心理学的结论往往简单易懂且贴近生活,所以会给人一种事后聪明型偏见。主要因为下面两点:

  • 心理学的高级结论往往正着说、反着说都有道理;就像谚语,比如“魔高一尺道高一丈”和“道高一尺魔高一丈”听起来都对
  • 事先得知结论然后去理解和通过努力发现结论完全不是一回事;当你有上述偏见时,尝试把两个相反结论放在一起做个选择题就知道了困难程度了

如何研究

好的假设:1)为研究指出方向;2)探测新的研究领域;3)具有应用价值。

好的理论:1)能对大范围观察结果做总结;2)对我们证明修改理论、进行新探索、指出可能应用方向给出清晰的预测

研究方法主要分为两种:相关研究实验研究

  • 相关研究发生在真实情景中,易于进行,只能发现相关性,因为无法控制变量,因而无法证明因果性
    • 对实验对象要足够有代表性(随机抽样
    • 认真组织问题,因为问题的顺序选项编制措辞都会影响被试的反应
  • 实验研究通过在实验室控制自变量,从而探寻因果性
    • 同样需要随机分配 + 施加不同外在条件保证额外因素的干扰消除
    • 需要自设实验情景,有时会面临伦理道德问题

社会思维

自我

这一章主要讨论人们如何评价、认识自己,如何维持自尊,社会思维如何产生等问题。

焦点效应使我们高估自己的突出程度,从而产生透明度错觉(实际注意我们的人比我们认为的少)。

自我概念

自我的部分是自我概念的基础,自我图式是我们组织自己所在世界的心理模板。自我参照效应让我们更好地以自我为中心组织记忆。同时,自我概念还包括我们想象中的自我

社会的部分同样扮演了很重要的角色。包括以下几点:

  • 我们在社会中扮演的角色
  • 社会同一性,即你身边的社会群体,诸如民族、信仰、性别等
  • 社会比较
  • 成功和失败经验。用积极的信息提高自尊会激发个体做出更大成就,全力以赴并取得了成功会使人感到更加自信有力。自尊不仅来自告诉孩子他们有多棒,还要让他们通过辛苦努力获得成功
  • 其他人的评价。与我们自我概念有关的是我们觉得他们如何评价我们,而不是他们实际上如何评价我们。自尊,是我们对他人如何评价我们的监控并作出相应反应的心理学尺度

自我受文化影响也很深。西方更盛行个人主义,赞美那些依靠自己的人。在亚洲、非洲、南美洲地区的文化则更重视集体主义,这些文化中的人们更多自我批评,而较少自我肯定。个人主义更容易观察到事物本质,集体主义更容易观察到事物间的联系。一个具有相互依赖自我的人会有更强烈的归属感。对于偏重个人主义的个体,积极情绪来自于效能感、出众和骄傲;来自集体主义的个体,积极情绪伴随着社会交往——亲密感、友好和尊重。

自我认识上,我们存在着一些天然的缺陷。

  • 我们会错误地解释我们的行为,行为相关的思想先于行动时,人们会感觉是按意愿做出这样行动的。
  • 我们会错误地预测他人和自己的行为,实验中发现人们预测别人的行为比预测自己的更为准确
  • 我们会错误地预测自己的感受
    • 一方面,人们很难预测自己未来情绪的强度和持续时间,比如预测自己谈恋爱、收到礼物、赢得比赛等后的感觉,又比如饥饿的人会错估自己的食量,且具有更高的购物冲动。这一点上可以粗略地总结为错误地想要得到某些东西。研究显示,好消息的情绪消失比自己预期的要快。
    • 另一方面,人们会高估消极情绪事件的持久性影响,以及过高地预期自己的痛苦。实际上,生理上和心理上人都具有强大的恢复能力

最后一点,人们在自我分析时,对一些原因并不显著的行为往往很难分析出原因。换句话说,我们对思维的结果比对思维的过程了解的更多。这带来两个结论:

  • 心理学研究中,自我报告通常靠不住
  • 日常生活中,人们分析或解释其经验的真实性无法保证报告准确性

自我效能 & 自尊

自我效能对自己能力、效率的乐观信念会获得更大回报。这种态度让你积极面对外界变化。心态上,自我效能高的人会认为身边发生的事“是受自己努力和技巧支配的”,而自我效能低的人会认为“是受外界力量支配的”。

人在对所做的事或他人对自己做的事失去控制时,会产生不愉快的压力情绪。长此以往会产生习得性无助,降低个体的抗挫能力。反之促进个体控制可以真正增强个体健康和幸福感。但是“过度的自由”也会降低幸福感,比如拥有一些无法反悔的事会让人心理更好受。

自尊是我们全面的自我评价。高自尊的人在自尊面临挑战时,会非常敌对。自尊带来的摩擦发生在熟人之间,比如兄弟姐妹、朋友。把自尊建立在良好的自我感觉,而不是分数、外貌、金钱和别人评价基础上的人,会一直感到状态良好。

自我服务偏见

我们加工和自我相关的信息时,会出现一种潜在偏见,即自我服务偏见

  • 我们把成功归于内部原因,失败归于外部因素
  • 我们总将成功与自我联系在一起,以保持良好的自我形象。因此,大多数人认为“今天的我比昨天更完善”
  • 我们认为自己比平均水平好,尤其是主观行为维度,如品德。教育也无法消除这种自我服务偏见。
  • 我们认为自己擅长的事情是更重要的,以此维持我们的自我形象
  • 绝大多数人盲目乐观,而较少居安思危
  • 虚假普遍性虚假独特性。即过高估计他人对自我观点的认同程度和把我们的成功、才智、品德看成超乎寻常。比如人们自己的生活变化时,可能会认为整个世界也在发生变化

总结来看,自我服务归因自我恭维的比较盲目乐观以及虚假普遍性是自我服务偏见的根源。这一机制有助于我们抵制抑郁,却会引起对他人、自己的错误评价,包括对自己所在群体的评价,从而带来群体冲突。

自我展示

人们对外展示的自我和真实的自我是不同的。最常出现的是虚伪的谦虚,比如对自我成就自传式的解释,这在匿名自我评价上就消失了,这种表浅的谦虚只是为了表现谦虚而不是内心的真实感受,这也能避免“获胜后的危险”。

人们为了减少失败结果的影响,可能会故意地自我妨碍,以维持对自己能力的信任,比如考试前不好好复习。

作为一种社会性动物,人们总是在向周围的观众表演。在熟悉的环境下,不需要意识参与就能完成。而在陌生环境里,比如和陌生异性聊天或参加一场宴会,我们就能确切地意识到营造形象的过程。在自我展示的程度上,有两种极端——行骗专家式的高自我监控和我行我素的第自我监控。更多人处在两个极端之间。尽管在集体主义国家,自我展示被抑制。但是自我美化依旧存在。

社会信念和判断

归因

人类天生想让整个世界合乎情理,至少能被自己解释。因而带来自然而然地对他人行为的归因

归因即探索行为的因果关系,比如“你不回我电话是因为你不关心我了!”。根据海德(Heider)始创的归因理论,归因分为两种:

  • 内因:性格归因,如“你不爱我了!”
  • 外因:情境性归因,如“我在打游戏”

伴随我们的归因天性,我们内置一些归因天赋:

  • 特质推断:不寻常的行为让我们更了解这个人
  • 常识性归因:共同反应(个体经常表现这种行为吗),区别性(不同情境下行为不同吗),一致性(别人也这样吗)共同作用得出结论

基本归因错误

遗憾的是,我们的归因天赋不完美的。李·罗斯发现个体在归因他人行为时,会低估情境因素的作用影响,而高估个人特质的影响。这种倾向即基本归因错误。当归因涉及个人利益时,这种错误更为明显。即使我们事先知道有情境因素存在,依旧会有这种错误。比如,我们会认为电影中反派的饰演者就是本人的真实反应,我们会认为考官或出题者会更聪明。类似地,如果某个人自己所持的观点被其他人反复表达,他也会认为其他人确实也持有这种观点。

应用在日常生活中,解释他人行为时,我们会犯基本归因错误,而对于自己的行为,我们却通常用情境因素解释。我们通常用描述行为、反应的词句解释自己,涉及到他人时,更经常用“他怎么怎么样”来描述。

基本归因错误可以来自于许多原因:

  • 行动者和观察者身处位置不同。作为观察者时,解释他人行为会更多聚焦在突出的行为人身上,从而易于忽略情境影响;相反,作为行动者本身时,自我和环境融为一体,观察联想时就会对情境因素更加敏感。
  • 聚焦观点偏见,即人们会更多关注被聚焦的个体,支持其观点。如观看聚焦犯人录像的观众几乎百分百认为犯人有罪,而观看聚焦审讯员录像的观众则会认为犯人是被迫认罪的。
  • 观点是随时变化的,回顾自己记忆时,会分配情境更多权重,近期行为更多归因情境,远期行为更多归因个人性质。比如Bob下周邀请我参加生日和2年后邀请归因是不同的
  • 自我觉知,注意力集中在自己身上时,更多归因自我特质而非特性。对于他人,越缺乏在不同情境下观察行为的机会,就越容易将行为归因他人人格,尤其是陌生人,对于我们很熟悉且在多种环境相处过的人,我们会对环境更加敏感。
  • 文化差异,集体主义更多归因情境,如“体制有问题”;个人主义更多归因个人特质

基本归因错误的原理很可能是,评价情境对他人的影响比归因个人特质要更花脑力,即将行为归因个体内在而非环境是种有效率的行为,且有些场景下归因个人特质确实是有效的。

研究归因错误是很有价值的。因为对于他人行为归因的不同会直接影响我们对他们的印象和感受,正如上面的“你不爱我了!”和“我在打游戏”。了解了这些,在日常生活中解释行为的时候,就更能以批判性的思维看待自己和他人的评价。

社会知觉和社会记忆

先入为主 —— 我们的预期会引导我们对信息的知觉和解释。比如,球迷们总认为裁判偏袒另一方。人总是主观的,事实上告诉我你从哪里看到了偏见,我甚至能获得有关你所持态度的线索。类似地,对于观点模糊的混合型信息,对立观点双方都会吸收信息并同化为支持自己,从而更坚定自己的观点。书中给出了一个例子——一个面无表情的男人面孔,在告诉你他是个慈善家和告诉你他是个纳粹分子时,你会对脸做出不同的认识。

事实就在那里,但我们的思维却积极地解释它,并根据不同的解释做出不同的行为。

解释的过程也会左右他人对我们的知觉,当我们说某人好话或坏话时,人们也会试图将那些特质和我们联系在一起

信念固着

一旦人们为错误的信息建立了理论基础,就很难再让他们否定这条错误的信息。建立理论基础的解释是人自然做出的,在解释形成后,会独立于最初推论出它的信息而存在。因此即使在信息被推翻后,被试依旧坚持自己归纳出的解释。这给我们一个启示:我们越是极力证明我们的理论和解释是正确的,我们就对挑战自己信念的信息越闭塞。

从进化学的角度看,这种归纳可以减轻理解负担;但是这个收益的代价是:我们成为自己思维方式的囚徒。实验发现,解释相反的观点可以减轻信念固着对我们思维的固有影响。

记忆重构

记忆与读书不同,它更像根据不连贯的笔记片段写一本书 —— John F. Kihlstrom, 1994

我们的记忆不像一个储物箱,需要什么可以直接取出。我们会无意识地修正和重构我们的回忆。

  • 一些细小的令人愉快的事件会让回忆比实际所经历的美好得多。一些不愉快或无聊的事情会被最小化,似乎消失在记忆里。这在旅行回忆中最为常见。(很像是大脑记忆机制的趋利避害)
  • 我们会改变同其他人关系的回忆,当记忆模糊时,当下的感觉会主导我们的回忆
  • 我们会重构过去行为的回忆,从而体现出“事后聪明式的偏差”。比如,当初要是怎么怎么样就好了。同时,回忆会变得更符合我们现在的观点
  • 一些暗示会影响回忆的结果(误导信息效应

整体来看,记忆的提取更像一个复杂的相互关联的过程。极易受上下文(如暗示、引导)影响,context不同,记忆启动的画面就不同。

直觉和判断

实验表明,无意识控制我们大多数行为,对绝大多数人来说,其日常生活不是由有意识的目标和经过深思熟虑的选择决定的,而是受内部心理过程的控制。比如在食物中看到虫子会感到恶心,跳舞时肢体动作的选择等等。我们意识小部分是受控制的,大部分是自动化的(冲动的、无需努力的、无意识的)。

  • 情绪反应是即时的,简单的喜欢、不喜欢、恐惧通常不涉及分析的过程。这些是伴随基因而来的。
  • 人在领域内拥有足够多专业知识时,可以依赖直觉获得问题答案,如下棋、写代码。高手通常会察觉到新手看不到的pattern。
  • 我们对事实、名字、过去经验的记忆是外显的,而对技能、条件特征的记忆是内隐的

看起来,我们的控制意识处理着最重要、最新鲜是事件,而把日常事情分配给其他系统处理。然而,直觉抑或说是无意识过程并没有想象中那么敏锐。比如事后聪明式判断。这些即错觉思维。

过度自信

过度自信现象会影响我们对目前知识的评价和对未来行为的预测。讽刺的是,能力不足会促进过度自信倾向,对能力的认识也是需要能力的。这种现象不是个别的:

  • 计划者通常会低估工程所需时间和精力
  • 投资专家都认为他们能够取得超过股市平均回报率的业绩
  • 过度自信优势会带来浩劫,如越战

验证性偏见:人们往往会寻找支持自己信念的信息,而非尝试证明自己的直觉不成立。因为这么做有助于个人证实自己意象。

有两种降低过度自信的方法:即时反馈,比如天气预报;设想自己判断可能出错的原因。同时考虑到过度自信的普遍性,我们也需要对别人独断性的陈述保持谨慎。

心理捷径

我们的认知系统在进化中形成了专门的心理捷径,让我们很容易就能形成印象,作出决定和生成解释。但这也会带来一些错误。

  • 代表性直觉,对某个事物进行评价时,在直觉的引导下,将其与某一类典型的心理表征进行比较。比如,告诉你一个人朝九晚五,很可能认为他是公务员;告诉你一个人996修福报,你很可能认为他是程序员。这会让我们忽略其他重要信息。
  • 易得性直觉,我们会优先从记忆中取出现成易得的信息做判断。比如,那些鲜明容易形象化的事件,如容易形象化症状的疾病,与那些较难形象化的疾病相比,被认为更易发生。再比如,那些小说、电影、电视中的虚构情节会给人留下印象,甚至也会影响我们随后的判断。如,我们认为飞机不如汽车安全。这会让我们过度重视生动鲜明的例证。
  • 反事实思维,容易想象的事件会影响我们对负罪、遗憾、挫败和宽慰的体验。比如我们队以一分之差输掉(赢得)比赛,相比差距20分,我们会感到更大的遗憾(宽慰)。铜牌会比银牌获得者更高兴。事件本身越重要,反事实思维强度就越大,如在酒驾中失去亲人会带来莫大的遗憾感。尽管如此,绝大部分人对已做事情的悔恨比没有做的事情的会很要小。对于已做事情,人们会更多可能感到歉意。

关联错觉

当我们期待发生某种重要联系时,会知觉到错觉相关,如吃巧克力会让我这一天更幸运。我们相信事件存在相关时,会更可能注意并回忆某些支持性的证据(幸存者效应)。

将随机事件知觉为有联系的倾向会让人产生控制错觉,即认为各种随机事件受自己影响。赌博中很常见,掷骰子希望点数较小时,出手会相对轻柔,希望点数较大时,出手相对较重。同时,由于趋均数回归,即随机现象会在期望附近上下波动的存在,在上一次或上几次偏离期望较远时,下一次更可能回归到期望附近。所以,当我们处在一个最低水平时,任何尝试行为看起来似乎都是有效的。这会让老师误认为自己的辅导生效了,或者看心理治疗书籍拯救了偶然的低迷情绪。

情绪影响判断

个体在乐观和意抑郁的情绪下,对世界的知觉不同。因为,我们的情绪会将与其相关的经验带入头脑并给我们的所见所闻着色。情绪对简单、自动化的思维影响比复杂、有意控制的思维要小。即我们思考越多,思维就越受情绪侵染。

自我实现预言

我们的社会信念和判断会影响我们的感觉和行动,由此改变自己的现实,这种观念引导我们以证实自己的方式行动即自我实现预言。老师认为学生是否有天赋,就会带来自我实现预言的现象。有趣的是,学生认为老师出色和有趣也会带来自我实现。推广来看,恋人之间也有这种现象。相反,一旦形成错误的社会信念,就可能引发他人做出某些行为反应以支持这些信念,即行为确证

确认他人期望的倾向具有局限性,且个体预先告知他人其期望时,可能会引起他人做出行动去克服期望。在采访中,对记者态度不明确的受访者会更经常硬核采访者期望。

整体来看,对于高效判断的偏向是直觉难以避免误判,我们直觉思维的缺陷是我们对复杂信息简化加工的心理捷径的副产品。这种直觉有时会让我们进入歧途,但大多数时候确实可以帮助我们做出高效迅速的决定。在有些职业里,需要考虑到这点。比如记者就需要了解如何避免上面这些认知偏见。

行为和态度

人的态度和行为具有什么关系呢?

ABC:感觉(affect)、行为倾向(behavior tendency)、认知(cognition)三种相互影响

态度决定行为?

我们精通并擅长为自己的行为寻找原因,但却非常不善于做我们已找到原因的事 —— Abelson 1972

社会心理学家最初认为态度和行为并没有明显关联。

  • 学生对于作弊的态度和他们实际作弊行为几乎没有关联
  • 道德伪善:表现出有道德水准,但实际上拒绝付出任何代价

因此,寄希望于改变态度来改变行为通常会以失败告终。

而后,社会心理学家发现行为和我们的态度不同是因为有其他因素的影响:

  • 外在表现受制于外部因素的影响,比如记名投票和匿名投票结果通常不同。为了避免外部因素的影响,可以使用伪途径法,类似“测谎仪”或看起来像“测谎仪”的东西
  • 长期观察个体行为、个体的通常行为,态度对于行为的预测会更明显
  • 测量的态度和行为直接相关时,态度确实可以预测行为

在行为是自发做出时,态度会潜在地起作用。当我们思考自己态度时,态度才会影响我们的行为。让人自我觉知或者本身自我意识感强的人,言行的一致性会更高。

总结来看,在限制条件的场景下,态度可以决定行为:

  • 其他因素最小化
  • 态度针对具体行为
  • 我们能清除意识到态度

行为如何影响态度

我们会逐步相信我们坚持的东西

下面一些现象都说明着,行为能影响态度,形成信念,被人所接受

  • 角色扮演,扮演一种新的社会角色时,起初我们可能会觉得虚假,但很快就能适应(融入角色、融入环境)。比如演员们入戏。
  • 语言变成信念,当一个人的话没有可以信服的外在解释时,语言就会变成信念。我们倾向于根据听众调整说话内容,并在说过之后相信歪曲的信息
  • 登门槛现象,即想让人帮大忙第一个有效策略是先请他们帮个小忙。当人们承诺公众行为并且认为这些行为是自觉作出时,他们会坚信自己的所作所为。商家的低价法策略就是利用了这一点。已经进行的行为会说服自己。
  • 邪恶的行为会侵蚀人的良心,通常导致攻击者贬抑受害者,以此为行为正当性辩护
  • 对他人的积极行为会增强对那个人的好感度,下次希望别人对自己有好感时,可以尝试引导别人对自己表露出积极行为
  • 我们会相信自己所坚持的所作所为,即使行为是受外在推动。政治仪式就是一个例子:唱国歌,参加演习。

行为为何会影响态度

有三个理论:自我展示:印象管理自我辩解:认知不协调自我知觉

自我展示认为人们行为改变的态度实际上是人们想给别人留下好印象,毕竟没有人愿意看起来自我矛盾。

自我辩解

认知不协调理论由费斯汀格(Leon Festinger)提出。它认为两种想法或认知在心理上不一致时,我们就会感到失调(即紧张)。因此我们要保持行为和认知间的一致性。理论主要用来解释态度和行为间的矛盾关系。

在此基础上,有个有趣的发现是,在行为的理由不足时,人们会更可能感到不舒服,因此要更相信自己的所作所为。这带来的应用有:

  • 温和地告诫不能为他们的所作所为(比如打扫房间)提供充分理由,孩子更可能将行为内化为合理且自发的。
  • 如果我们觉得要对自己行为负责时(而不是为了应付命令),我们的态度就会依从行为。鼓励和诱导好的行为可以激发孩子内化正确的态度

另外,当做出重要决策时,我们经常会过高评价自己的选择,而贬低放弃的选择,以此减少不协调程度。从而很轻易地带来决定——变成——信念。比如,当我们决定机票在1000元以下就坐飞机时,我们已经在想象坐飞机时的情景,机票涨到1000元以上时,我们很可能仍然会选择坐飞机。

自我知觉

自我知觉理论由贝姆(Daryl Bem)提出,它假设我们在态度还摇摆不定时,会处在局外人的态度观察自己,从而通过行动揭露自己的态度。比如,第二天要考试,前一天翻来覆去睡不着,我发现我失眠了,我意识到我很焦虑。

我们的表情和情态可以放大自我知觉,比如在害怕的时候哼一首愉快的小曲也许真有帮助。模仿别人表情和情态时,我们更能体验到别人的感受。实际上,我们在和他人交流时,通常通过保持行动、姿势等一致而让彼此感到和蔼可亲,从而造成情绪传染的效果。面部表情甚至也会影响我们的态度。比如在听取观点时,被要求保持点头姿势的人更容易同意此观点。

由于我们通过观察周围情境来解释自己的行为,自我知觉理论推导出一个结论:不必要的报酬会带来隐性的代价。给人们报酬让他们做自己喜欢的事会让他们将行为归因于报酬,从而削弱他们的自我知觉,即过度合理化效应。更准确地说:

  • 如果报酬和赞赏是针对人们的成就,则会增加个体的内部动机
  • 如果报酬是为了控制人们,而且人们也相信了是报酬导致了努力,那么会降低个体对工作的内在兴趣

因此,要适当地给予报酬:提供充分的理由,基于报酬和赞赏。抑或,反其道而行之,削减一些行为,比如寓言故事里说的,花钱让小孩制造噪音,再逐渐减少报酬,从而让小孩不再制造麻烦。

理论对比

认知不协调主要用来处理言行不一致的情况。言行不一致,不协调激活时,还会伴随排汗量增加、心率加快等生理表现,因为它会威胁到我们对自我价值的积极体验。自我知觉较弱的人,不协调感也会较弱。

自我知觉理论主要用在没有自我矛盾,态度并未完全形成时,它可以很好解释态度如何伴随行动形成和明确。

总结

要想养成习惯,那就去付诸行动。 —— 埃皮克提图

没有反应就没有接受,没有相关表达就不会产生印象

行为可以改变态度。我们用语言去解释某事时,会记得更牢。当你想形成某种态度时,不妨用行动去塑造。

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

黄金时代

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

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%