<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>igaozp&apos;s cyberspace</title><description>Hi, I&apos;m igaozp, a full-stack engineer. I share my thoughts and experiences about technology and digital life in this place.</description><link>https://igaozp.me/</link><item><title>美化 Astro 项目的 RSS 订阅源</title><link>https://igaozp.me/blog/enhance-astro-project-rss-feed-style/</link><guid isPermaLink="true">https://igaozp.me/blog/enhance-astro-project-rss-feed-style/</guid><description>这篇教程展示了在 Astro 构建的项目中如何改进 RSS 的订阅源样式</description><pubDate>Thu, 02 Jan 2025 00:00:00 GMT</pubDate><content:encoded>众所周知网站中的 RSS 订阅源链接对应的是 XML 格式内容，一般情况下直接访问订阅源链接就直接展示对应的 XML 结构内容。

当然，我们可以对这个页面的样式进行优化。这篇教程展示了在 Astro 构建的项目中如何改进 RSS 的订阅源样式。

## 准备

我们借助 [RSS.Beauty](https://rss.beauty/) 这个工具来帮助我们优化样式

&gt; RSS.Beauty 是一个基于 XSLT 技术的 RSS 美化工具, 可以将普通的 RSS/Atom 订阅源转换成美观的阅读界面。
&gt;

我们选择 File 选项后可以下载到一个 XSL 文件，这个文件就是用来实现美化的样式文件，可以把它类比成 CSS。

![RSS.Beauty official site screenshot](/images/enhance-astro-project-rss-feed-style/01.png)

## 在 Astro 中集成

1. 首先将下载的 XSL 文件放入到 Astro 项目中的 `public` 目录中
2. 修改 Astro 项目的 rss.xml.js 文件
    1. 在 `RSSOptions` 配置中增加 `stylesheet` 配置，该配置可以指定我们的美化样式文件
    2. 修改响应参数，需要将 **HTTP Response** 的 **Content-Type** 调整为 `application/xml`，否则自定义样式可能不会生效

完整的 rss.xml.js 代码参考：

```jsx
import rss from &quot;@astrojs/rss&quot;;
import { getCollection } from &quot;astro:content&quot;;
import { SITE_TITLE, SITE_DESCRIPTION } from &quot;@const&quot;;

export async function GET(context) {
  const posts = (await getCollection(&quot;blog&quot;)).sort(
    (a, b) =&gt; b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
  );
  const resp = rss({
    title: SITE_TITLE,
    description: SITE_DESCRIPTION,
    site: context.site,
    items: posts.map((post) =&gt; ({
      ...post.data,
      link: `/blog/${post.slug}/`,
      content: post.body,
    })),
    // 👇 Please specify the directory for the XSL stylesheet file
    stylesheet: &quot;/rss.xsl&quot;,
  });

  return new Response((await resp).body, {
    headers: {
      // 👇 Modify the Content-Type of the HTTP Response
      &quot;Content-Type&quot;: &quot;application/xml&quot;,
      &quot;x-content-type-options&quot;: &quot;nosniff&quot;,
    },
  });
}
```

修改完后访问 RSS 订阅源链接，不出意外的话样式就生效了。也可以访问 https://igaozp.me/rss.xml 参考一下生效后的样式。

## 附录

- RSS.Beauty 项目官网 https://rss.beauty/
- RSS.Beauty 项目 GitHub https://github.com/ccbikai/RSS.Beauty</content:encoded></item><item><title>Follow RSS App Verify</title><link>https://igaozp.me/blog/follow-rss-verify/</link><guid isPermaLink="true">https://igaozp.me/blog/follow-rss-verify/</guid><description>This message is used to verify that this feed</description><pubDate>Fri, 18 Oct 2024 00:00:00 GMT</pubDate><content:encoded>This message is used to verify that this feed (feedId:63197430230262784) belongs to me (userId:61602685434006528). Join me in enjoying the next generation information browser https://follow.is.</content:encoded></item><item><title>《纳瓦尔宝典》摘要 The Almanack of Naval Ravikant: A Guide to Wealth and Happiness</title><link>https://igaozp.me/blog/the-almanack-of-naval-ravikant/</link><guid isPermaLink="true">https://igaozp.me/blog/the-almanack-of-naval-ravikant/</guid><description>致富不是靠运气，幸福也不是从天而降的。积累财富和幸福生活是我们可以学习的技能🥰</description><pubDate>Tue, 11 Apr 2023 00:00:00 GMT</pubDate><content:encoded>该文章为笔者读《纳瓦尔宝典》（The Almanack of Naval Ravikant: A Guide to Wealth and Happiness）时所记摘要，录以备考假以时日待作感悟。

## 认识财富创造的原理

如何致富（不靠运气）：

- 追求财富，而不是金钱或地位。**财富是指在你睡觉时仍能为你赚钱的资产**。金钱是我们转换时间和财富的方式。地位是你在社会等级体系中所处的位置。
- 创造财富和坚持道德标准是可以兼得的。如果你内心鄙视财富，财富就会对你避而远之。
- **无视一味追求社会地位的人。他们获得地位的手段就是攻击创造财富的人。**
- **依靠出租时间是不可能致富的。你必须拥有股权（企业的部分所有权），才能实现财务自由。**
- **获得财富的一个途径，就是为社会提供其有需求但无从获得的东西，并实现规模化。**
- 选择一个有长期发展前景的行业，找到可以长期合作的人。
- 互联网极大地拓展了职业空间，但大多数人还没有清晰地认识到这一点。
- 培养迭代思维。生活中所有的回报，无论是财富、人际关系，还是知识，都来自复利。
- 选择聪明过人、精力充沛的商业伙伴，但更重要的是，他们要正直诚信。
- 不要跟愤世嫉俗和消极悲观的人合作。他们的预言会自我实现。
- 学会销售，学会构建，两技傍身，势不可当。
- 用专长、责任感和杠杆效应武装自己。
- 专长指的是无法通过培训获得的知识。如果社会可以培训你，那么社会也可以培训他人来取代你。
- 要想有所专长，就要追求真正的兴趣和热爱，而不是盲目追逐热点。
- 累积专长的过程，对你而言就像玩耍，对他人来说则很吃力。
- 专长的传授需要通过师傅带徒弟的方式完成，而无法通过学校教育完成。
- **专长往往具有高度的技术性或创造性，不能被外包或自动化。**
- 培养责任感，勇于以个人名义承担商业风险。社会将根据责任大小、股权多少和杠杆效应回报你。
- 「给我一根足够长的杠杆和一个支点，我就能撬动地球。」——阿基米德
- **要想获得财富，就必须充分利用杠杆效应。商业杠杆来自资本、劳动力和复制边际成本为零的产品（代码和媒体）。**
- 资本是指金钱。要想获得融资，需要运用自己的专长和责任感，并表现出良好的判断力。
- 劳动力杠杆就是让别人为你工作。这是最古老、争夺最激烈的一种杠杆。拥有劳动力杠杆会让你的父母觉得你很了不起，但不要过度追逐劳动力杠杆。
- 资本和劳动力是需要获得许可才能使用的杠杆。人人都在追逐资本，但得有人愿意出资。人人都想领导他人，但得有人愿意追随。
- **代码和媒体是不需要许可就能使用的杠杆。这两个杠杆是新富阶层背后的杠杆。你可以创建软件和媒体，让它们在你睡觉时为你工作。**
- 有一大批机器人可供我们免费使用。为了提高热效率、节约空间，这些机器人就集中放在数据中心。用起来吧。
- 如果不会写代码，那就出书、写博客、做视频、录播客。
- 杠杆是判断力的倍增器。
- 判断力从经验中来，但可以通过学习基本技能快速建立起来。
- 没有所谓的「商业」技能。不要把时间浪费在商业杂志和商业课程上。
- **学习微观经济学、博弈论、心理学、说服术、伦理学、数学和计算机。**
- 读比听快，做比看快。
- 你应该忙得没时间社交，但依然把日程安排得井然有序。
- **设定一个大胆的个人时薪，并严格执行。如果解决一个问题节省的成本低于时薪，那就忽略问题；如果外包一项任务的成本低于时薪，那就选择外包。**
- 工作时要拼尽全力，毫无保留。不过，共事的人和工作的内容比努力程度更重要。
- 在自己选择的职业领域里做到全球顶尖。不断重新定义自己的事业，直到理想成为现实。
- 世界上没有快速致富的教程。即使有，那也只是提供教程的人想从你身上赚钱。
- 运用专长，发挥杠杆效应，最终你会得到自己应得的。
- 当你终于变得富有时，你会意识到，这并不是你最初的追求。但这是后话，此处暂且不提。

**总结：把自己产品化。一个是「自己」，一个是「产品化」。「自己」具有独特性，「产品化」是发挥杠杆效应；「自己」具有责任感，「产品化」需要专长。「自己」其实也具有专长。**

如果想要实现致富的长期目标，你就应该问问自己：**「这是我真正想要的东西吗？我的规划目标是我真正想要的吗？」** 得到肯定的答案后，再问问自己：**「我实现产品化了吗？我实现规模化了吗？我选择规模化的方式是劳动力、资本，还是代码或媒体？」** 由此可见，「把自己产品化」这个阐述方便简单，便于记忆。

金钱是我们转移财富的方式。金钱是社会的信用符号，具有调用别人时间的能力。

你真正想要的其实是财富。财富就是在你睡觉时也可以帮你赚钱的资产。财富是可以进行生产的工厂和机器人。财富是不分昼夜为客户服务的计算机程序。财富也可以是银行里被投资于其他资产或业务的钱。

技术让消费变得更大众化，也让生产变得更集中。在某个领域做到全球顶尖的人，现在可以为世界上任何人提供自己的产品或服务。

**某种东西一旦得到广泛应用，它就不再是科技了。**

**社会总是需要新事物。如果想变得富有，你就要弄清楚你能为社会提供哪些其有需求但无从获得的东西，而提供这些东西对你来说又是轻松自然的事情，在你的技术和能力范围内。**

## 找到天赋所在，积累专长

专长无法被教授，但可以被学习。

在「成为自己」这件事情上，没有人能比得过你。其实，**人生大部分时间都是在寻找，寻找那些最需要你的人，寻找那些最需要你的事情。**

**通常情况下，专长属于知识领域的边缘地带。有些领域尚处于发轫或发展阶段，有些领域研究难度很大，这些领域更容易产生专长。** 如果你在研究的时候不是百分之百投入，其他百分之百投入的人就会超过你。他们的表现会比你好不是一点儿，而是很多，因为我们讨论的是创意领域的竞争。**在创意领域，复利效应非常明显，杠杆效应也非常明显。**

**有了互联网，只要你在自己的领域做到最好，只要你能规模化你所提供的特别内容，那么就算你的兴趣很小众，你也能有所发展。**

「只有独辟蹊径，才能避开竞争。」**从本质上看，竞争就是模仿，与他人竞争，是因为你跟别人在做一样的事情**。但是，每个人都是独一无二的，不要模仿他人。

最好的工作与委任或学位无关。**最好的工作是终身学习者在自由市场中的创造性表达**。

**致富最重要的技能是成为终身学习者，无论想学什么，你都得找到途径和方法。以前的赚钱模式是读 4 年大学，拿到学位，在某个专业领域干上 30 年。现在不一样了，时代的发展日新月异，必须在 9 个月内掌握一门新专业，而这门专业在 4 年后就过时了。但在专业存在的这 3 年里，你可以变得非常富有。**

比起成为一名数字营销专家或点击优化专员，知道如何增强沟通说服力更重要。打好基础非常关键。在基础知识层面得 90 分或 100 分远胜于盲目地深入钻研。当然，有些东西你需要深入研究，否则你只能做到「样样都通，样样稀松」，无法实现人生目标。**一个人只能在一两件事上做到精通，而这一两件事通常是让你痴迷的事情。**

## 投资交友，着眼长远

个人声誉方面也存在复利效应。如果一个人声誉良好，数十年如一日，不断打造和积累自己的声誉，这个人就一定会得到关注和重视。而如果一个人才华横溢，却没有利用声誉的复利效应，相比之下，注重声誉的人的价值就会高出其成千上万倍。

你应该在经过深思熟虑后，清醒地认识到自己需要从大多数事情（人际关系、工作、学习）中找到可以尽全力去付出的那部分，以充分获取复利效应。我不是说那 99% 的事情都不要做，因为我们很难确定剩下的 1% 到底是什么。我的意思是：努力找到不会被浪费的 1%。这1%对你是有意义的，值得你用余生去追求。一旦找到，你就要心无旁骛，全力以赴。

## 承担责任

获得财富需要杠杆。杠杆可以来自劳动力、资本、代码或媒体。劳动力和资本等大多数杠杆需要有给予方。要获取劳动力，必须有人追随你。要获得资本，需要有人为你提供资金、管理资产或机械设备。

要得到劳动力或资本，必须建立信誉，尽可能以个人信誉做担保，而这种操作存在风险。因此，责任是把双刃剑：进展顺利，责任人会得到褒奖；一旦失败，责任人就会首当其冲。

明确的责任分配非常重要。没有责任，就没有动力。没有责任，就无法建立可信度。但责任也意味着风险：失败的风险，被羞辱的风险，以个人名誉承担失败的风险。

那些有能力以个人名义在公众面前承担失败风险的人，会获得很大的原动力。

## 创立企业或买入股权

如果你是通过出租时间来换取报酬的，即使从事律师和医生等高薪职业，你能够获得的报酬也非常有限，不足以让你实现财务自由。而如果拥有企业股权，你就可以获得被动收入——即使你在度假，企业也在帮你赚钱。

如果不持有企业股权，个人投入和收益的关联性就会非常强。几乎所有领取月薪的工作，即便是律师或医生这样的高薪职业，也需要投入时间才能获得相应的收入。

如果不持有企业股权，睡觉的时候没有收入，退休以后没有收入，度假期间也没有收入。总之，收入是相对固定的，根本无法实现实质性突破。

**从本质上看，上班就是给人打工。而企业所有者在承担风险和责任的同时也拥有知识产权和品牌效应。所以，他们支付给你的报酬一定低于你创造的价值。为了让你工作，他们会给你提供最低限度的报酬。这个最低限度的报酬可能很高，但依然不是真正的财富，因为你退休后，这份收入将难以为继。**

拥有企业股权意味着可以获得企业发展的收益。

但通常情况下，真正的财富是通过创建公司或者通过投资创造出来的。投资公司也是买入企业的股权。这些都是获得财富的途径。总之，**真正的财务自由都不是靠单纯地投入大量时间来实现的**。

## 找到杠杆

想要打造良好的职业基础，就要追随自己真正的求知欲上下求索，而不是盲目跟风眼下赚钱的热门行业。

正是在独特的热爱和爱好的驱动下，我们才得以掌握只有自己或一小部分人才知道的知识。如果能够把求知欲和兴趣爱好结合、相融，我们就更有可能找到自己的所爱。

如果一件事物一开始让你兴致盎然，后来又让你觉得索然无味，那么它只是暂时分散了你的注意力，并不是你心智上真正的好奇所在。请继续寻找。

如果在追随好奇心和求知欲的过程中又满足了社会需求，你就能得到优厚的经济回报。

**你更有可能获得这个社会还不能通过培训提供的技能。如果社会可以培训他人，这些人就可以取代你。如果你可以被轻易取代，社会就不需要为你支付重酬。要时刻牢记，掌握社会所需的独门绝技才是占据职业制高点的不二法门。**

认真想一想，社会上还有哪些尚未得到满足的需求，而你怎样才能成为第一个提供相关产品或服务的人，并将其规模化。这才是赚钱真正的挑战。

第一种是**劳动力杠杆**，也就是让别人给你打工。劳动力杠杆是一种最古老的杠杆，但在现代社会，这种杠杆的效果并不是最好的。我甚至认为这是一种最落后的杠杆。因为管理他人是一件非常复杂、极具挑战的工作，需要高超的领导技巧，弄不好管理者会落个众叛亲离、被手下生吞活剥的下场。

资本是第二种相对较好的杠杆形式。**资本杠杆就是用钱来扩大决策的影响力**。资本是一种更现代的杠杆形式，利用资本杠杆有一定的难度，需要一定的技能。在 20 世纪，人们曾经利用资本杠杆获得了惊人的财富。资本杠杆是 20 世纪杠杆的主要形式。

最后一种杠杆是最新出现的，也是普通人最触手可及的。这种杠杆就是 **「复制边际成本为零的产品」**。

其中包括**书籍、媒体、电影、代码**。在所有不需要他人许可就能使用的杠杆中，代码可以说是最强大的一种——只需要一台计算机就够了。

复制边际成本为零的产品是最值得研究的杠杆，也是最重要的杠杆。这是一种全新的杠杆形式，问世仅几百年。这种杠杆始于印刷机。广播媒体加速了其发展，而互联网和编程的出现更是使其产生了爆发式增长。不需要他人为你打工，也不需要他人给你投资，你就可以把劳动成果放大成百上千倍。

**新杠杆最重要的特点之一就是，使用它们或获得成功都无须经过他人的许可。要使用劳动力杠杆，就得有人决定追随你。要使用资本杠杆，就得有人给你提供资金，你再去进行投资或开发产品。**

无论处于人生的哪个阶段，努力的目标都是不断提高自己的独立性，而不是升职加薪。拥有独立性，为自己独特的产出成果负责（而不是像打工一样为投入的时间负责），这才是最理想的状态。

**每周工作 40 小时是工业时代的产物。知识劳动者的时间安排与运动员如出一辙，有训练和冲刺的时间，也有休息和重新评估的时间。**

投入和产出之所以脱节，是因为工具和杠杆的存在。一个职业的创造性越高，其投入与产出的不匹配性越高。而投入和产出高度相关的职业很难创造财富，从事这种职业也很难给自己带来财富。

**想加入一家伟大的科技公司，销售技能和构建技能必备其一。如果二者都不具备，那就去学习。**

销售和构建是两个非常宽泛的概念。构建产品很难，因为涉及多个变量，涵盖设计、开发、制造、物流、采购等环节，甚至还包括服务的设计和运营。因此，「构建」的定义非常多元。每个行业都定义了各自的「构建者」。在我们科技行业，「构建者」是首席技术官，是程序员，是软件工程师和硬件工程师。即便是洗衣行业，也存在「构建者」。洗衣行业的「构建者」就是设计洗衣服务的人，是确保流程正常运转的人，是确保所有衣服在正确的时间被运送至正确地点的人，等等。

另一个概念是销售。销售的定义同样很宽泛。销售不一定是针对个体顾客销售产品，也可以指市场营销、媒体传播、人才招聘、资金募集、员工激励、公共关系等，涉及面非常广。

不要做可能会让你全盘皆输的事情。不要孤注一掷、铤而走险。相反，要把赌注押在那些胜算较大、能带来巨大利益的事情上。

## 用判断力赚钱

我认为每个人都应该立志掌握某些领域的专业知识，并以此赚取经济回报。如果能够在实际工作中最大化地利用杠杆，无论是机器人、计算机，还是其他任何人或技术，那么我们都可以成为自己时间的主人，因为没有人会考核我们在工作中投入了多少时间，我们只需要对自己的产出负责。

**首席执行官的报酬之所以很高，就是因为杠杆效应。在判断力和能力上，毫厘之差都会有天壤之别。**

只要在优秀的基础上稍微提高一点儿，做到卓越（比如把完成 1/4 英里的短跑成绩提高零点秒），经济回报就会呈数量级增加。而杠杆效应进一步放大了经济回报的差异。在杠杆时代，在自己的领域做到极致非常重要。

## 分清主次，聚焦重点

**给自己的时间设定价格，用时薪计算时间价值。如果用花钱的方式节省的时间价值更高，那就花钱，不要犹豫。要想真的赚到钱，先要相信你自己很值钱。**

要从时间成本的角度做决策，如果做某件事外包的成本低于时薪，那就外包；如果不做的损失低于时薪，那就不做；如果花钱请人的成本低于时薪，那就花钱请人。甚至做饭也是同样的道理，你可能想吃健康的家常菜，但是如果可以外包，那就外包吧。

**在商业世界里，大多数人在玩零和游戏，少数人在玩正和游戏，他们在人群中寻找志同道合者。**

**这世上的游戏不外乎两种。第一种是金钱游戏。金钱不能解决所有问题，但可以解决所有和金钱有关的问题。大家都明白这一点，所以每个人都想赚钱。但与此同时，许多人内心深处觉得自己是赚不到钱的，所以，他们不喜欢看到创造财富的故事。他们会攻击整个商业体系，宣称「赚钱是邪恶的，不应该赚钱」。但其实他们在玩第二种游戏，这种游戏叫地位游戏。他们之所以说「我不需要钱，我不想要钱」，是因为他们想占领高地，让别人高看一眼。地位就是一个人在社会等级中的位置。**

在人类进化史上，财富创造是近代才出现的活动，是一个正和游戏。而地位之争自古有之，是一个零和游戏。那些攻击财富创造的人往往只是为了追求地位。

玩愚蠢的游戏，就只能赢得愚蠢的奖品。

**最重要的是，要在重大决定上花更多时间。人生早期有三个重大决定：在哪里生活，和谁在一起，从事什么职业。**

找到自己擅长的领域，然后用自己的技能去帮助他人：提供免费的产品或服务，主动向世界传递善意。好人终有好报。只要始终如一，假以时日，付出就一定能获得相应的回报。但不要计较自己付出了多少——一旦开始计较，耐心就会被消耗殆尽。

## 找到如玩耍般的工作

在人类社会形成之初，每个人都扮演着狩猎者或采集者的角色，自己劳动养活自己。农业社会伊始，社会等级开始形成。工业革命和工厂的出现极大地强化了社会的等级划分，因为没有人可以凭一己之力拥有或建立一家工厂。**但由于互联网的诞生和发展，现代社会越来越回归到每个人为自己劳动的时代。**

退休就是不再为了想象中的明天而牺牲今天。当你能活在当下，内心充盈地度过每一天时，你就达到了退休的状态。

第一种方法是存钱。只要存款够多，被动收入（不用动一根手指）就能满足开销。第二种方法是把开销降为零——出家修行。第三种方法是做自己热爱的事情，完全乐在其中，有钱没钱无所谓。所以，实现退休状态有很多种方法。

避开竞争陷阱的方法就是做独一无二的自己，找到自己能做到独步天下的事情。做到最好，只因热爱。如果你真的热爱一个事物，那就追随本心，努力找到利用它满足社会真实需求的切入点，利用杠杆效应扩大规模，以个人名义担起责任。这样，在承担风险的同时，你也将获得相应的回报，拥有自己所提供产品或服务的所有权或股权，然后持续精进。

毫无保留地爱，随心所欲地创造，无忧无虑地玩耍。

我永远都在「工作」。但是，别人眼里的工作于我而言就像是玩耍。正因如此，我才确信没有人可以在我的领域与我匹敌。因为我觉得工作就是玩耍，所以我可以一天玩 16 个小时。如果有人想跟我竞争，那么他们肯定会输，因为他们不可能一周工作 7 天，每天工作 16 个小时。

钱不是万恶之源。钱本身是无罪的，真正有害的是对钱的贪欲。从道德层面看，追求金钱并不是坏事，它跟人品没什么关系，但是，贪欲对人是有害的。

人之所以永远不会感到满足，是因为欲望这个开关一旦被打开，就不会在某个具体数字面前自动关停，正所谓欲壑难填。所以，不要以为赚到某个数额的钱，人自然就会满足了、停手了。

赚钱的目的是解决财务问题，满足物质需求。**我认为，摆脱金钱贪念最好的办法就是，赚了钱之后不要升级自己的生活方式。赚到钱的人往往会自然而然地提高生活水准。假设你一次性赚到一大笔钱，而不是靠日积月累。此时你依然保持着原有的生活方式，还没来得及升级，你的金钱就会远远超出你的实际需求和欲望，这反而让你达到一种财务自由的状态。**

我把自由看得高于一切。我说的自由是多种多样的：想做什么就做什么的自由，不想做什么就不去做的自由，不受自我情绪或外界影响的自由，等等。自由是我最珍视的价值。

硅谷最成功的人分为两类。第一类是做风险投资的人，因为他们非常多元化，控制着曾经是稀缺资源的东西。第二类是极为擅长识别公司发展阶段和前景的人。这些人会精准地找到下一个多宝箱或爱彼迎，在公司的产品和服务刚刚与市场需求相衔接时加入它们。这些人拥有公司扩大规模急需的背景和专业知识，也能找到合适的引荐人。

## 如何获得运气

在获得财富的过程中，你要排除「运气」这个不可控因素。

第一种运气是不期而遇的运气，一个人的好运完全源于他控制范围之外、意料之外的事情，比如获得意外之财、遇到贵人等等。

第二种运气源于坚持不懈、孜孜不倦、屡败屡战、不断尝试，是靠个人主动创造机会获得的。你释放了大量能量，使出浑身解数，移山倒海，一往无前。就好像在做科学实验，把不同的试剂混合在一起，看看能产生什么结果。因为你不懈地努力，不停地奋进，不断地释放能量、积蓄力量，所以好运找到了你。

第三种获得好运的方式就是善于发现好运。如果你在某个领域技艺娴熟、经验丰富，那么当这个领域实现了意外突破时，你就会在第一时间洞悉，这时，其他不熟悉这个领域的人会无动于衷。这就是增加对好运的敏感性，幸运会眷顾有准备的大脑。

第四种运气是最奇妙、最难得的一种，那就是打造独特的个性、独特的品牌、独特的心态，让运气找到你。

获得好运的方法：希望好运不期而至。·不停地折腾，直到撞上大运。·做好心理准备，对别人错过的机会保持敏感。·把你所做的事情做到极致。精益求精，直到名副其实。让机会自动找到你，让运气成为必然。

第四种运气总结一下就是，以某种方式塑造自己的性格，之后就由性格决定命运。

**「在一个长线游戏中，似乎每个人都在让彼此变得富有。而在一个短线游戏中，似乎每个人都在让自己变得富有。」**

## 保持耐心

江山易改，本性难移。所谓「性格决定命运」，就是一个人不断重复自己的行为模式，好的坏的、优点缺点，最终会得到与自己的行为相对应的结果。始终主动付出、不断奉献，不要斤斤计较、患得患失。

在物质世界里，金钱不能给人带来快乐，不能解决人的健康问题，不能让所有家庭变得美满和睦，不能让人免受情绪波动的困扰。但金钱可以买到自由，可以解决许多外在的问题。所以，赚钱是一个合情合理的奋斗目标。

## 判断力

把时间花在省钱上是不会致富的。省出时间来赚钱才是正确的思路。

我对智慧的定义是**「知道个人行为的长期后果」**，用于解决外部问题的智慧其实就是判断力。或者说，智慧和判断力是高度关联的：一个智慧而富有判断力的人，首先要知道个人行为的长期后果，然后做出正确的决策并付诸行动。

在杠杆时代，一个正确的决策可以帮你赢得一切。不付出努力，就无法培养判断力，也不会获得任何杠杆。

在前进的过程中，方向比速度更重要，特别是运用了杠杆以后。在每个岔路口选对方向，其重要程度要远远超过前进的努力程度。

## 如何清晰地思考？

真正的知识具有内在的关联性，就像一根链条，从基础层面到应用层面环环相扣。

如果在需要用到一些概念时却无法通过基础知识推导出来，你就会迷失在现有知识的迷宫中，你就成了简单的背诵机器。

阻碍我们看清现实的最大因素就是我们对现实「应有的样子」有先入为主的印象。

痛苦时刻的一个定义是：当你看到事物的真面目不是你本来想要的样子时，你是痛苦的。

难点在于看清真相。要看清真相，就必须摆脱自我，因为自我不想面对真相。自我越弱小，对自己反应的限制越少，对自己想要的结果的执念越低，就越容易看清现实。

悠闲的大脑才能产生伟大的创意。一个倍感压力、案牍劳形、四处奔波、焦头烂额的人，是没有办法思考的。所以，一定要为思考挤出时间。

## 摆脱自我束缚，认清世界真相

要持续成长，很重要的一点是学会打破现有的条件反射，改掉不良习惯。

要善于剖析自我，梳理每个习惯是怎样形成的。

## 学习决策技巧

要选择从长期来看让我们受益最大的做法，而不是只顾眼前得失。

当把成功归于自己时，你要更加谨慎，因为难免出现认知偏差。

自由的表现之一就是可以心口如一，表达自己的真实想法。诚实和自由高度统一，相辅相成。

具体地表扬，泛泛地批评。

如果要提出批评意见，不要批评某个人，可以批评工作方法，或者批评某一类行为。如果要表扬，那就找到一个榜样，表扬这个特定的人。这样有助于维护你身边人的自尊心和身份感，获得他们的支持，让他们为你所用，而不是与你作对。

人们好像很难从本质上理解决策的杠杆效应。我可以举例说明，如果我管理着 10 亿美元的资产，而且我的决策正确率比其他人高出 10%，我就能通过一个判断、一个决策创造出 1 亿美元的价值。这就是决策的杠杆效应。随着现代科技的发展、劳动力规模的扩大和资本的不断积累，决策也将发挥越来越大的杠杆效应。

越觉得自己无所不知，规避和处置风险的方法越少。

## 发现好的心智模型

所谓的心智模型，其实就是有助于调取你所学知识的简单方法。

有一种理论认为，文明的存在是为了解决交配权的分配问题。从纯粹的性选择角度看，人类社会精子充足，卵子稀少，所以存在分配问题。究其根本，人类的所有发明和成就都是为了解决交配权的分配问题。人生的很多问题都可以从进化论、热力学、信息论和复杂性理论中找到解释和预测。

成功的关键并不在于做出正确判断，而在于避免做出错误判断。

如果不能深刻理解供求关系、劳资关系、博弈论等问题，你就不可能在商业上取得成功，甚至也无法很好地适应现代社会。

委托和代理问题非常容易理解。恺撒大帝有句名言：「如果你想完成一件事，那就亲自去做。如果不想完成，那就派人去做。」他的意思是，如果想把事情做好，你就必须自己去做。如果你是委托人，你就会有主人翁的责任感，因为在意结果，所以你会做得很好。而如果你是代理人，你就是在为别人做事，你可能会做得很糟糕，因为你不在乎。你追求的是自身利益最大化，而不是委托人资产最优化。

公司规模越小，每个人越会觉得自己是委托人、是主人。越不觉得自己是代理人，工作就会做得越出色。增加所获报酬与创造价值之间的相关性，可以改变员工的认知，让他们越发觉得自己是委托人，而不仅仅是代理人。

**要吃透基础数学，真正掌握加减乘除、复利计算、概率论和统计学。**

对那些声称「科学」站在自己一边的人来说，最重要的原则，同时也是他们理解得最不透彻的原则，就是可证伪性。如果不能做出可证伪的预测，那就不是科学。要让人们相信某个理论是真理，这个理论就应该具有预测能力，而且必须是可证伪的。

如果你难以抉择，答案就是否定的。原因是，现代社会充满了选择，有成千上万个选择。我们生活在一个有 70 亿人口的星球上，我们和互联网上的每个人都相互连接，世界上有成千上万的职业供我们选择。大千世界，芸芸众生，选择永远不缺。

一个重大决策可能会影响未来十几年，甚至几十年的人生轨迹。创业可能需要 10 年时间。一段恋情可能会持续 5 年甚至更久。搬到一个城市可能会住上 10 年、20 年。这些决定都将产生深远的影响。人做不到绝对确定，但是我们一定要在非常确定的情况下再做出决定。

**一条简单的人生经验：如果在一个艰难的决定上意见不统一，你就应该选择短期内更痛苦的道路。**

**如果面对两个选择，利弊各占 50%，你就应该选择短期内更艰难、更痛苦的道路。**

从本质上看，两条道路中的一条会带来短期痛苦，而另一条会在未来引发更长久的痛苦。为了回避矛盾，大脑会本能地选择摆脱短期痛苦。

前提条件是，两个选择利弊相当，但如果一条道路会带来短期痛苦，那么它也会带来长期收益。而根据复利效应，长期收益才是你想要的。

## 学会热爱阅读

不要比谁读书更快。书越好，你越要慢慢阅读、慢慢吸收。

大部分图书都有一个论点（我讲的是非虚构类作品，不是小说），作者提出一个论点，引用海量实例证明自己的观点，然后用自己的观点去论证世界上各种各样的现象。一旦觉得自己已经明白了这个论点，我就不再读下去了。这样的书太多了，我称其为伪科学畅销书。别人会问我：「这本书你读了吗？」我总是说读过了，其实我可能只读了两章，但书的主旨我已经了解了。

阅读数学、科学和哲学领域的经典作品。不要读畅销书，不要看新闻。避免加入任何所谓的「读书俱乐部」，避免追求任何的社群认同。把真理置于社群认同之上。

**在解决问题时，问题越古老，解决方案存在的时间越长。**

任何流传了两千年的图书都经过了许多代人的甄别和筛选，其中的一般性原则更有可能是正确的。我想重新开始阅读这类书。

## 幸福是一种技能

有钱确实能提高幸福感，但金钱其实只发挥了很小的作用。幸福感的提升主要是因为，随着时间的推移，我逐渐认识到，让自己幸福对我来说是最重要的事，我也运用了大量技巧来培养自己的幸福感。

现在，我认为幸福就是一种不需要主动作为的状态。当把「缺憾感」从生活中剔除时，幸福感就会油然而生。

当感到生命中并不缺少什么时，大脑就会处于休眠状态，不再追忆昨天，也不再畅想明天，不会悔不当初，也不会谋求未来。

人们往往误将积极乐观的想法和行为等同于幸福。

幸福就是消除缺憾感之后的感受。

真正的幸福只是内心平和的副产品，主要还是源于接受现实，而不是改变外部环境。

理性很强的人可以通过训练自己的无感反应获得平和，即学会漠视自己无法控制的事物。

## 幸福是一种选择

我们无条件地听从自己脑海中的声音，以为这个声音就是一切真理的来源。殊不知，大脑也是可塑的，每一天都是崭新的。记忆和身份只是来自过去的负担，让我们无法自由自在、心无旁骛地活在当下。

## 幸福需要心境平和

你要意识到，是焦虑感让你感到不快乐。而这种焦虑感源于一连串不断涌现的想法。

## 欲望是主动选择的不开心

从根本层面上看，从自身以外的事物中寻求幸福，本身就是缘木求鱼。当然，我并不是说物质世界不重要。作为一种社会性动物，人需要去履行一些社会职责。人的一生就是在力所能及的范围内减少无序状态，即所谓**「局部熵减」**，这是你的人生责任。

欲望就是你跟自己的约定，约定的内容是：不得到我想要的东西，我是不会快乐的。我觉得大部分人都没有意识到，这就是欲望的本质。我们每天都生活在欲望中，又奇怪为什么自己不快乐。我已然认识到，欲望是我痛苦的来源。

## 成功不一定带来幸福

幸福就是满足现状。而成功源于对现状的不满，是对现状的改造。两者只能选一个。

生存和繁衍的本能驱使人们劳作，而享乐适应让我们步履不停。幸福的秘诀在于，知道何时停下劳作的脚步，开始随心去玩耍。

对现在的我来说，真正的赢家是那些已经完全退出游戏的人，甚至根本不玩游戏的人，是那些已经超越了游戏的人。这些人的内心无比强大，有极强的自控力和清醒的自我意识，他们不需要从任何人那里获得任何东西。

在现实生活中，内心的平和状态不是一劳永逸的，也不会是一成不变的。心理状态总在不断变化。在大多数情况下，接受并顺应现实，是获得幸福的核心技能。

平和是静态的幸福，幸福是动态的平和。只要愿意，你就随时可以把平和激活，使之变成幸福。但是大部分时间你想要的其实是被封印的幸福，即平和。如果你是一个内心平和的人，那么无论做什么事你都可以获得幸福的体验。

获得内心平静的唯一方法是摒弃「问题」这个概念。

## 嫉妒是幸福的敌人

我在生活中努力摆脱「应该」这个词。当「应该」在脑海中出现时，其背后隐藏的是负罪感或社会规训。如果做一件事是因为「应该」，那就表示你内心是不想这么做的，而违背自己的心意会让你变得痛苦不堪。因此，我努力在生活中减少「应该」做的事。

而现实是，生活就是一场单人游戏。人独自出生，独自死亡，独自解读人世间的一切。你的记忆只属于你一个人。你出生前无人在意，你离开人世后也无人在意，你存在于人世间只是短短几十年，人生就是一场单人游戏。

嫉妒是一种有害的情绪，因为归根到底，它并不能改善你的生活，只会让你不快乐，而你嫉妒的那个人仍然是成功的、美丽的，仍然拥有你所嫉妒的一切。

## 幸福源于好习惯

获取平和与幸福其实是一种技能，这种技能不是与生俱来的。

工作时，和比自己更成功的人在一起。玩耍时，和比自己更快乐的人在一起。

可以通过养成好习惯来获取。不喝酒、不吃糖可以提高情绪的稳定性，远离社交媒体（脸书，Snapchat 和推特）也可以提高情绪的稳定性。玩电子游戏会带来短暂的快乐（我一度痴迷于游戏），但会摧毁长期的幸福。喝咖啡也属于用长期健康换取短期兴奋。这些不受你控制的外界因素会刺激你体内多巴胺的分泌，刺激因素一旦消失，多巴胺水平就会下降。

从本质上讲，生活的过程就是用精心培养的好习惯替换那些在不经意间养成的坏习惯，努力成为一个更幸福的人。你的幸福指数最终取决于你的习惯和你花最多时间与之相处的人。

最幸福和最乐观的人会选择五只正确的黑猩猩做朋友。

如果我意识到自己正在对别人评头论足，我就会停下来自问：「我能不能正面解读这件事？」我过去常常因为一些事情感到恼火，但现在总会尝试看到积极的一面。刚开始，我可能需要花几秒钟，动用理性分析才能想出一种积极的解读方式。

新闻的目的就是让人感到焦虑和愤怒。但新闻背后的科学、经济学、教育和冲突趋势是有积极意义的。

政治、学术和社会地位都是零和游戏。正和游戏才能造就积极向上的人。

阳光、运动、正向思考和色氨酸，这些不是药物，但都可以增加大脑中的血清素，使人始终保持健康、清醒、积极、乐观。

这就是改变习惯的步骤：首先，知道自己要做什么；其次，知道该怎么做；再次，把规划告诉朋友，让一致性偏见发挥作用；最后，严格自律，知行合一，进行自我重塑，直至蜕变成全新的自己。

## 于接受中寻找幸福

试图改变现状是一种欲望。在成功改变现状之前，欲望会让人感到痛苦。所以不要总想着去改变外部环境。在任何特定的时间段，只选择一个最有价值的欲望，作为自己的奋斗目标和动力之源。

无论面对何种状况，你都有三种选择：改变现状，接受现状，逃避现状。很多人在遇到问题时会踟蹰不前，陷入空想：希望改变现状却没有横下一条心去改变，希望转身离开却没有毅然决然地离去，同时又不能心平气和地接受现状。

这种纠结和回避的态度正是人生中大部分痛苦的来源。我在脑海里对自己说得最多的就是两个字：接受。

所谓接受，就是无论结果如何，都可以泰然处之；就是保持心态平衡，大脑专注；就是退一步海阔天空；就是观大势，顾全局，谋长远。

一个方法是退后一步，回顾经历过的痛苦。我还会把这些痛苦写下来：上次分手，上次生意失败，上次健康出问题，后来都发生了什么？这样我就可以看到在随后的几年里我的成长和进步。

另一个应对小挫折的方法就是换个角度看问题。当我遭遇挫折时，部分自我会立即做出消极反应。但现在我已经学会自问：「这种情况有什么积极的意义吗？」

如何学会接受无法改变的事情？从根本上说，就是学会坦然面对死亡。

死亡是人的一生中最重大的事情。人终有一死，选择正视、承认死亡而不是逃避死亡，将赋予人生巨大的意义。我们在一生中会花费大量时间试图逃避死亡。我们的很多奋斗目标都可以归结为对永生的追求。

相对于宇宙，你就像一只在夜空中闪烁的萤火虫，你的生命转瞬即逝。如果能彻底认识到你所做的一切不过是徒劳，你就能获得巨大的幸福感和平和感，因为你会意识到，生命不过是一场游戏。但生命是一场有趣的游戏。在这场游戏中，唯一重要的事情就是，随着生命的展开，你要不断地体验现实。

既然如此，你为什么不以最积极的方式去诠释自己经历的一切呢？

宇宙的幸福值并不是恒定的，别人不会因为你不幸福而变得更加幸福。

你在地球上所拥有的时间稍纵即逝，无比宝贵，你一定要好好珍惜。你要把直面和正视死亡放在第一位。人的一生，不否认、不回避死亡是极其重要的，因为这是你学会坦然接受无法改变的事情、活在当下的本原。

## 选择做自己

你唯一应该做的事，是你自己想做的事。别人总希望你以特定的方式做事，但如果不再费心去揣摩别人的期待，你就能听到自己脑海中那个微弱的声音。那个声音代表了你真实的想法。倾听它，你就可以做自己了。

在「做自己」这件事情上，没有人能与你竞争。你永远不会像我一样擅长做我自己，我也永远不会像你一样擅长做你自己。当然，做人要虚心聆听，博采众长，但不要盲目模仿。模仿他人纯属徒劳。

相反，每个人都有独一无二的资质技能、专业知识、能力才干、个人欲望，这些是世界上其他人所没有的，是先天基因和后天经验共同作用的结果。

不要基于别人正在做的事为你自己列清单、做决策。你永远不会成为他们，你永远都不擅长做另一个人。

## 选择关爱自己

情绪上的大起大落让我们无比焦虑。生物进化的目标本来是适应物质的匮乏。为了应对匮乏，基因决定了我们看到糖分、酒精、毒品就难以抗拒，遇到亲密关系也无法拒绝。

但是，现代人生活在一个富足的世界里，面对琳琅满目、应有尽有的一切，当我们的基因一直在说「要要要」的时候，我们本该拒绝，但我们的身体已经不知道如何说不了。

所以，在现代社会，我们逐渐背离了符合自然规律的生活习性，形成了病态的生活方式。

人的大脑和身体存在备用机制，大自然的这种设计是有意义的。例如，在冰期，人类在没有很多植物可供食用的环境中进化。

后来，在备用机制的作用下，人们选择多种植物作为自己的食物。到现在，人类食用植物已有几千年的历史。我虽然并不认为植物对身体有害，但我感觉接近原始的饮食结构应该更好

让人产生饥饿感的食物是糖。糖会给你的身体发出这样的信号：「在我们进化的环境中竟然有这么好的食物资源。」于是你会迫不及待地摄取糖分。问题在于，糖效应凌驾于脂肪效应之上。

在高脂高糖的饮食中，糖会让人产生饥饿感，脂肪会提供卡路里，从而导致暴饮暴食。这也是为什么所有的甜点都含有大量的脂肪和碳水化合物。

糖和脂肪的结合是致命的，在饮食中我们必须注意避免摄入此类食物。

膳食中的脂肪会带来饱腹感，糖会导致饥饿感。但糖效应占据主导地位，所以我们需要相应地控制自己的食欲。

最常见的借口就是「我没时间」。表面上说的是「我没时间」，但其实说的是，「这对我来说不是优先事项」。

生活中的所有事情都一样，如果愿意做出短期的牺牲，你就会得到长期的好处。我的体能教练耶日·格雷戈雷克是个非常聪明的人。他总是说：「选择简单模式，人生会越来越困难；选择困难模式，人生会越来越简单。」

## 冥想+精神力量

情绪是生物进化的产物，能预测当前事件对未来的影响。在现代社会环境中，情绪反应通常是夸张的，甚至是错误的。

我发现，对我最有效的一种冥想叫「无选择觉知」，或叫「无评判觉知」。这种冥想方式就是在处理日常事务时（最好有跟大自然接触的机会），不跟任何人交谈，练习接受自己所处的当下时刻，不做任何评判。既不去想，「那边有个流浪汉，我最好到马路对面去」，也不要看着跑步的人暗想，「这人身材走样了，我的身材比他好」。

第二种方法是超觉冥想，就是通过反复唱诵，在头脑中制造一种白噪声来掩盖自己的想法。第三种方法就是对自己的想法保持高度敏锐和警觉。通过观察自己的想法，你会意识到很多想法都源于恐惧。

在你识别出恐惧的那一刻，恐惧感自然就消失了。一段时间过后，你的思绪就会平静下来。

我学到的第四种冥想方法是，坐在那里闭上眼睛，每天至少一个小时。此时，你以臣服的姿态接受发生的一切，不争取，不对抗。如果有想法在头脑中涌现，那就任其涌现。

洞察冥想是在「调试模式」下运行大脑，最终你会意识到自己只是一个大程序的子程序。

## 选择自我塑造

说「打算」，就是在拖延，就是在给自己找借口。不过，即使没有把想法付诸行动，至少也要对自己真实的心理活动保持觉知：「虽然我说我想做这件事，但其实并不是真的想。因为如果真的想，我早就去做了。」

有想法就直接采取行动，就是这么简单。

## 选择自我成长

「要建立系统，而不是设定目标。」运用你的判断力确定什么样的环境有助于你茁壮成长，然后在周围创造一个这样的环境，由此增加成功的概率。

第一条原则是阅读，广泛地阅读。阅读面要广，不要局限于社会认可的书，更不要局限于我推荐的书。要为了阅读而阅读，培养对阅读的热爱。

第二条原则是，掌握与阅读技巧相关的数学和说服技能。这两种技能有助于你在现实世界里活得游刃有余、畅行无阻。

## 选择解放自己

勇气不是在枪林弹雨中冲锋陷阵，而是不在乎别人怎么想。

不要花自己的时间去取悦别人。别人快不快乐是他们的问题，不是你的问题。你快乐了，别人也会快乐。你快乐了，别人会问你是如何快乐起来的，他们会从中学到点儿什么，但是你没有责任让别人快乐。

观察自己愤怒时的样子。愤怒就是对情况失去控制时的表现。愤怒是跟自己的契约，你同意让自己陷入身体、精神和情感的混乱，直到现实发生改变。

愤怒本身就是一种惩罚。一个愤怒的人试图把你的头摁到水下，但同时他也在溺水。

自我意识和自我发现没有终点，是毕生的功课，我们在这条路上不断精进。人生没有一个有意义的答案，也没有人可以完全解决人生的所有问题，除非你成为一个大彻大悟的人。也许有人能最终做到，但我知道我不太可能做到，因为我已经被卷入这场无休止的「老鼠竞赛」。在最好的情况下，我能做到偶尔抬头看看天上的云。

孤独的个体召唤出非人的意志力，进行断食、冥想、锻炼……对抗大批科学家和统计学家以充足的食物、药物和电子屏幕为武器制造出的垃圾食品、标题党新闻、无限的色情内容、无穷无尽的游戏、令人上瘾的毒品。

## 生命的意义

你做的任何事情都将烟消云散，你存在的一切痕迹都将无处可寻。人类终将灭亡，地球也会荡然无存。即使是移民火星的群体也会消失。无论你是艺术家、诗人、征服者、贫民，还是其他任何人，几代人过后，都不会有人记得你。总之，生命没有任何意义。

作为生命系统，我们所做的一切都是在推动宇宙加速达到热寂。创作艺术、研究数学、组建家庭、发明计算机、创建文明等等——所有这些更复杂的系统都在使宇宙加速达到热寂。你正在把我们推向「万物一体」的终极境界。

## 按照自己的价值观生活

在对别人撒谎之前，你必须先对自己撒谎。

## 我们唯一拥有的是当下

除了当下，一切都是不存在的。没有人能够回到过去，也没有人能够以任何有意义的方式成功地预测未来。唯一存在的就是此时此地，就是你恰好存在的这个时刻和在宇宙空间里的这个位置。

**灵感本易逝，行动应当时。**

## 纳瓦尔推荐的图书

《无穷的开始：世界进步的本源》

《理性乐观派：一部人类经济进步史》

《基因组：生命之书23章》

《红色皇后：性与人性的进化》

《自下而上：万物进化简史》

《万物解释者：复杂事物的极简说明书》

《趣味物理寻答集》

《主权个体：把握向信息时代的过渡》

《现实不似你所见：量子引力之旅》

《七堂极简物理课》

《竞争策略：策略博弈论入门》

《万物理论（启蒙的视角）》

《杰德·麦肯纳笔记本》

《杰德演讲#1》

《杰德演讲#2》

《全然的自由：克里希那穆提要义》

《塞涅卡之道：斯多葛派大师的实用书信》

《改变你的心智》

## 纳瓦尔的写作

纳瓦尔的写作生命公式一（2008年）这些是我写给自己的笔记。如果参照系不同，计算方法就会有所不同。这些不是定义，而是成功的算法。欢迎读者提供自己的见解。

幸福 = 健康 + 财富 + 良好的人际关系

健康 = 锻炼 + 饮食 + 睡眠

锻炼 = 高强度耐力训练 + 体育运动 + 休息

饮食 = 天然食物 + 间歇性禁食 + 植物

睡眠 = 不要闹钟 + 8～9小时 + 昼夜节律</content:encoded></item><item><title>如何给基于 Turborepo 的项目添加新应用？</title><link>https://igaozp.me/blog/how-to-add-a-new-application-to-my-turborepo-project/</link><guid isPermaLink="true">https://igaozp.me/blog/how-to-add-a-new-application-to-my-turborepo-project/</guid><description>来看看怎么给 Turborepo 项目添加新应用😎</description><pubDate>Mon, 30 Jan 2023 00:00:00 GMT</pubDate><content:encoded>## 前言

阅读本文章，需要你：

1. 已经了解 **Turborepo** 是什么？
2. 已经使用 **Turborepo** 完成模板项目的初始化
3. 不知道怎么再已有的项目中添加新的应用

## 基础概念

想要在项目中添加新的应用，我们需要先看一下当前的项目目录结构。我们使用 **Turborepo** 初始化的项目中，会包含 `apps` 和 `packages` 目录。根据官方文档描述，这是一个约定的目录结构，简单来说：

- `apps` 目录应该包含我们通常开发的可启动的应用程序，例如 **Next.js** 或 **Svelte** 应用程序。
- `packages` 目录应该包含被其他应用依赖的包，一般是公共的模块。

所以，如果我们要添加新的应用，最终的效果是要体现在 **apps** 目录中的。经过相关的查询和摸索，添加新应用需要如下的操作：

1. 在 `apps` 目录下，新建新项目的目录，目录名称不能与其他应用重复
2. 在新的应用目录下，完成新项目的初始化
3. 在整个项目的根目录下，完成整个项目的更新

## 示例

下面是以添加 **Vue** 应用为示例，展示整个操作流程：

1. 在 `apps` 目录下新建文件夹，目录名称以 `vue-learn` 作为示例。
2. 在 `vue-learn` 目录下，执行 **Vue** 应用的初始化脚本，这里使用 `pnpm create vue@latest` 来初始化应用，根据相关提示完成应用的初始化。初始化完成后，目录中应该包含 **Vue** 项目的正常目录结构和模板代码、配置。
3. 在整个 **Turborepo** 项目的根目录中（apps 目录的上一层），执行 `pnpm install`，完成整体项目的更新。
4. 使用 **Turborepo** 项目中 `package.json` 自带的 `dev` 脚本，或者使用 `turbo run dev --parallel` 命令，校验新增的应用能否正常启动，如果新增应用的前端页面可以正常展示则完成整个新建流程。

## 总结

这是只是针对笔者在 Turborepo 使用过程中遇到问题的一点简单方案。其中一些概念解释和操作并不能保证是合理和正确的，只是经过自身简单的理解和实践总结的，有条件的尽量先查阅最新的官方文档和搜索信息。</content:encoded></item><item><title>《Software Engineering at Google》 笔记 02：如何融入团队？</title><link>https://igaozp.me/blog/software-engineering-at-google-note-02/</link><guid isPermaLink="true">https://igaozp.me/blog/software-engineering-at-google-note-02/</guid><description>软件开发是团队的努力。要在工程团队或任何其他创造性合作中取得成功，你需要围绕谦逊、尊重和信任的核心原则重新定义你的行为🫡</description><pubDate>Mon, 19 Sep 2022 00:00:00 GMT</pubDate><content:encoded>[Software Engineering at Google](https://qiangmzsx.github.io/Software-Engineering-at-Google/#/zh-cn/Chapter-2_How_to_Work_Well_on_Teams/Chapter-2_How_to_Work_Well_on_Teams)

软件开发是团队的努力。要在工程团队或任何其他创造性合作中取得成功，你需要围绕谦逊、尊重和信任的核心原则重新定义你的行为。

人们害怕别人看到和评价他们正在进行的工作。从某种意义上说，缺乏安全感是人性的一部分——没有人喜欢被批评，尤其是那些没有完成的事情。认识到这个主题让我们看到了软件开发中一个更普遍的趋势：缺乏安全实际上是一个更大问题的征兆。

在内心深处，许多工程师暗中希望被视为天才。这种幻想是这样的：你会被一个了不起的新概念所震撼。你消失数周或数月躲在洞穴中，努力实现你的理想。然后世界上「发布」你的软件，用你的天才震撼每个人。你的同龄人对你的聪明感到惊讶。人们排队使用你的软件。名利自然随之而来。

人类有寻找领导者和榜样的本能，崇拜他们，并试图模仿他们。我们都需要英雄来激发灵感，编程世界也有自己的英雄。「科技名人」已经几乎被神化，我们都想写一些改变世界的东西，比如 Linux 或者设计下一种优秀的编程语言。

事实证明，这种天才神话只是我们缺乏安全感的另一种表现。许多程序员害怕分享他们刚刚开始的工作，因为这意味着同行会看到他们的错误，知道代码的作者不是天才。

如果你把所有的时间都花在独自工作上，增加了不必要失败的风险，耽误了你的成长潜力。尽管软件开发是一项需要高度集中精力和独处时间的深度智力工作，但你必须权衡协作和审查的价值（以及需求！）。

如果你对世界隐瞒你的牛逼想法，并在未完美之前拒绝向任何人展示，那么你就是在进行一场下注巨大的赌博。早期很容易犯基本的设计错误。你冒着重新发明轮子的风险。而且你也失去了协作的好处：注意到你的邻居通过与他人合作而效率有多高？这就是人们在跳入深水区之前将脚趾浸入水中的原因：你需要确保你在做正确的事情，你在做正确的事情，而且以前从未做过。早期失误的可能性很高。你越早征求反馈，这种风险就越低。记住**「早失败、快失败、经常失败」**这句经得起考验的至理名言。

**巴士因子：团队里因巴士撞倒的多少人，会导致项目失败。**

你的项目中的知识和技能分散程度如何？如果你是唯一了解原型代码工作原理的人，你需要会受到良好的工作保障，但如果你被公交车撞倒，项目就完蛋了。但是，如果你与同事合作，你的巴士因子就翻了一番。如果你有一个小团队一起进行设计和制作原型，情况会更好——当团队某个成员消失时，项目不会被孤立。记住：团队成员可能不会被公交车撞到，但其他不可预知的事件仍然会发生。有人可能会结婚、搬走、离开公司或请假照顾生病的亲属。确保每个责任领域除了一个主要和一个次要所有者之外，至少还有可用的文档，这有助于确保项目的成功，提高项目的成功率。希望大多数工程师认识到，**成为成功项目的一部分比成为失败项目的关键部分要好**。

当前 DevOps 对技术生产力的理念明确了这些目标：尽早获得反馈，尽早进行测试，尽早考虑安全和生产环境。这一切都与开发人员工作流程中的“左移”思想捆绑在一起；我们越早发现问题，修复它的成本就越低。

「隐藏」归结起来就是：独自工作比与他人一起工作具有更高的内在风险。即使你可能害怕有人窃取你的想法或认为你不聪明，你更应该担心浪费大量时间在错误的事情上。

在编程领域，孤独的工匠极其罕见，即使他们确实存在，他们也不会在真空中完成超人的成就；他们改变世界的成就几乎总是灵感迸发、团队英勇努力的结果。一个伟大的团队能够出色地利用它的超级明星，但整体总是大于各部分的总和。但打造一支集合多个超级明星球队是极其困难的。让我们把这个想法用更简单的话来说：**软件工程是一个团队的努力**。

你不能通过隐藏和准备你的秘密发明来改变世界或取悦数百万计的用户。你需要和其他人一起工作。分享你的愿景，分工，向别人学习，创建一个出色的团队。

- 支柱1：**谦逊** 你不是宇宙的中心（你的代码也不是！）。你既不是全方位的，也不是绝对正确的。你愿意不断提升自我。
- 支柱2：**尊重** 你真诚地关心与你一起工作的人。你善待他们，欣赏他们的能力和成就。
- 支柱3：**信任** 你相信其他人有能力并且会做正确的事情，你可以让他们在适当的时候牵头。

不要低估社交游戏的力量。这不是欺骗或操纵人们；这是关于建立关系来完成事情。关系总是比项目更长久。

我们普遍认为，如果你没有遭遇过失败，你就没有足够的创新或承担足够的风险的能力。失败被视为一个黄金机会，可以在下一次尝试中学习和改进。事实上，人们经常引用托马斯·爱迪生的话说：「如果我发现有一万种方法不能成功，我就没有失败。我并不气馁，因为每一个被抛弃的错误尝试都是向前迈出的另一步」。

从错误中学习的关键是通过进行根因分析和撰写「事后总结」来记录你的失败，在谷歌（和许多其他公司）成为事后总结（国内成为复盘）。要格外小心，确保 「事后总结」文件不只是一份无用的道歉、借口或指责的清单，这不是它的目的。正确事后总结应该总是包含对所学到的内容的解释，以及作为学习经验作为后续的改进落地。然后，确保事后总结可以随时查阅，并确保团队真正贯彻执行所建议的改变。好的故障复盘要让其他人（现在和将来）知道发生了什么，避免重蹈覆辙。不要抹去你的足迹——让它们在道路上照亮给那些追随你的人!

一个好的事后总结应该包括以下内容：事件的简要概述事件的时间线，从发现、调查到解决的过程事件的主要原因影响和损害评估一套立即解决该问题的行动项目（包括执行人）。一套防止事件再次发生的行动项目经验教训

要想让别人正确地听取你的意见，你首先需要倾听别人的意见。最好在下定决心或坚定地宣布决定之前进行倾听——如果你不断地改变主意，人们会认为你不坚定。

从长远来看，承认自己犯了错误，或者根本不在你的能力范围，都会提高你的地位。事实上，表达脆弱性的意愿是一种谦逊的外在表现，它表明了责任感和承担责任的意愿，也是你信任他人意见的信号。作为回报，人们最终会尊重你的诚实和力量。有时，你能做的最好的事情就是说，「我不知道」。

在模棱两可中茁壮成长即使在环境不断变化的情况下，也能处理相互冲突的信息或方向，建立共识，并对问题做出改进。重视反馈谦虚优雅地接受和给出反馈，理解反馈对个人（和团队）发展的价值。走出舒适区 能够设定宏伟的目标并去追求，即使有来自他人的抵制或惰性。

**客户第一** 对谷歌产品的用户抱有同情和尊重，并追求符合其最佳利益的行动。

**关心团队** 对同事抱有同情心和尊重，并积极主动地帮助他们，提高团队凝聚力。

**做正确的事** 对自己所做的一切有强烈的主人感；愿意做出困难或不易的决定以保护团队和产品的完整。

几乎任何规模的软件工作的基础都是一个运作良好的团队。尽管软件开发者单打独斗的「天才神话 」仍然存在，但事实是，没有人能够真正地单干。一个软件组织要想经受住时间的考验，就必须有一种健康的文化，植根于谦逊、信任和尊重，围绕着团队而不是个人。此外，软件开发的创造性要求人们承担风险并偶尔失败；为了让人们接受这种失败，必须有一个健康的团队环境。</content:encoded></item><item><title>《程序员修炼之道：从小工到专家》笔记</title><link>https://igaozp.me/blog/the-pragmatic-programmer-note/</link><guid isPermaLink="true">https://igaozp.me/blog/the-pragmatic-programmer-note/</guid><pubDate>Sun, 18 Sep 2022 13:18:10 GMT</pubDate><content:encoded>## 我的源码让猫给吃了

依据你的职业发展、你的项目和你每天的工作，为你自己和你的行为负责这样一种观念，是注重实效的哲学的一块基石。注重实效的程序员对他或她自己的职业生涯负责，并且不害怕承认无知或错误。

**Provide Options, Don&apos;t Make Lame Excuses 提供各种选择，不要找蹩脚的借口**

## 软件的熵

尽管软件开发几乎不受任何物理定律的约束，熵（entropy）对我们的影响却很大。熵是一个来自物理学的概念，指的是某个系统中的「无序」的总量。遗憾的是，热力学定律保证了宇宙中的熵倾向于最大化。当软件中的无序增长时，程序员们称之为「软件腐烂」（software rot）。

**Don&apos;t Live with Broken Windows 不要容忍破窗户**

置之不理都会更快地加速腐烂的进程

## 石头汤与煮青蛙

**Be a Catalyst for Change 做变化的催化剂**

**Remember the Big Picture 记住大图景**

## 足够好的软件

**Make Quality a Requirements Issue 使质量成为需求问题**

不要因为过度修饰和过于求精而毁损完好的程序。继续前进，让你的代码凭着自己的质量站立一会儿。它也许不完美，但不用担心：它不可能完美。

## 你的知识资产

管理知识资产与管理金融资产非常相似：

1. 严肃的投资者定期投资——作为习惯。
2. 多元化是长期成功的关键。
3. 聪明的投资者在保守的投资和高风险、高回报的投资之间平衡他们的资产。
4. 投资者设法低买高卖，以获取最大回报。
5. 应周期性地重新评估和平衡资产。

**Invest Regularly in Your Knowledge Portfolio 定期为你的知识资产投资**

学习的过程将扩展你的思维，使你向着新的可能性和新的做事方式拓展。思想的「异花授粉」（cross-pollination）十分重要；设法把你学到的东西应用到你当前的项目中。即使你的项目没有使用该技术，你或许也能借鉴一些想法。

批判地思考你读到的和听到的。你需要确保你的资产中的知识是准确的，并且没有受到供应商或媒体炒作的影响。警惕声称他们的信条提供了唯一答案的狂热者——那或许适用、或许不适用于你和你的项目。

**Critically Analyze What You Read and Hear 批判地分析你读到的和听到的**

- 确切地知道你想要问什么，并尽量明确具体。
- 小心而得体地组织你的问题。记住你是在请求帮助；不要显得好像是在要求对方回答。
- 组织好问题之后，停下来，再找找答案。选出一些关键字，搜索 Web。查找适当的 FAQ （常见问题的解答列表）。
- 决定你是想公开提问还是私下提问。
- 坐回椅子上，耐心等候。人们很忙，也许需要几天才能得到明确的答案。

## 交流

没有有效的交流，一个好想法就只是一个无人关心的孤儿。

![poem](/images/the-pragmatic-programmer-note/poem.jpeg)

**It&apos;s Both What You Say and the Way You Say It 你说什么和你怎么说同样重要**

## 重复的危害

系统中的每一项知识都必须具有单一、无歧义、权威的表示。

**DRY-Don&apos;t Repeat Yourself 不要重复你自己**

糟糕的代码才需要许多注释。DRY 法则告诉我们，要把低级的知识放在代码中，它属于那里；把注释保留给其他的高级说明。否则，我们就是在重复知识，而每一次改变都意味着既要改变代码，也要改变注释。注释将不可避免地变得过时，而不可信任的注释比完全没有注释更糟。

**Make It Easy to Reuse 让复用变得容易**

## 正交性

「正交性」是从几何学中借来的术语。如果两条直线相交成直角，它们就是正交的，比如图中的坐标轴。用向量术语说，这两条直线互不依赖。沿着某一条直线移动，你投影到另一条直线上的位置不变。

在计算技术中，该术语用于表示某种不相依赖性或是解耦性。如果两个或更多事物中的一个发生变化，不会影响其他事物，这些事物就是正交的。在设计良好的系统中，数据库代码与用户界面是正交的：你可以改动界面，而不影响数据库；更换数据库，而不用改动界面。

非正交系统的改变与控制更复杂是其固有的性质。当任何系统的各组件互相高度依赖时，就不再有局部修正（local fix）这样的事情。

**Eliminate Effects Between Unrelated Things 消除无关事物之间的影响**

## 曳光弹

曳光代码并非用过就扔的代码：你编写它，是为了保留它。它含有任何一段产品代码都拥有的完整的错误检查、结构、文档、以及自查。它只不过功能不全而已。但是，一旦你在系统的各组件间实现了端到端（end-to-end）的连接，你就可以检查你离目标还有多远，并在必要的情况下进行调整。一旦你完全瞄准，增加功能将是一件容易的事情。

曳光代码也是如此。你在不能100%确定该去往何处的情形下使用这项技术。如果最初的几次尝试错过了目标——用户说：「那不是我的意思」，你需要的数据在你需要它时不可用，或是性能好像有问题——你不应感到惊奇。找出怎样改变已有的东西、让其更接近目标的办法，并且为你使用了一种简约的开发方法而感到高兴。小段代码的惯性也小——要改变它更容易、更迅速。你能够搜集关于你的应用的反馈，而且与其他任何方法相比，你能够花费较少代价、更为迅速地生成新的、更为准确的版本。同时，因为每个主要的应用组件都已表现在你的曳光代码中，用户可以确信，他们所看到的东西具有现实基础，不仅仅是纸上的规范。

原型制作生成用过就扔的代码。曳光代码虽然简约，但却是完整的，并且构成了最终系统的骨架的一部分。你可以把原型制作视为在第一发曳光弹发射之前进行的侦察和情报搜集工作。

## 领域语言

**Program Close to the Problem domain 靠近问题领域编程**

通过在更高的抽象层面上编码，你获得了专心解决领域问题的自由，并且可以忽略琐碎的实现细节。

## 估算

**Estimate to Avoid Surprises 估算，以避免发生意外**

## 纯文本的威力

**Keep Knowledge in Plain Text 用纯文本保存知识**

使用纯文本有两个主要缺点：

1. 与压缩的二进制格式相比，存储纯文本所需空间更多。
2. 要解释及处理纯文本文件，计算上的代价可能更昂贵。

人能够阅读的数据形式，以及自描述的数据，将比所有其他的数据形式和创建它们的应用都活得更长久。

对于大多数二进制文件，要成功地进行解析，你必须了解整个格式的所有细节。

## shell 游戏

GUI 的好处是 WYSIWYG——所见即所得（what you see is what you get）。缺点是 WYSIAYG——所见即全部所得（what you see is all you get）。

**Use the Power of Command Shells 利用命令 shell 的力量**

## 强力编辑

**Use a Single Editor Well 用好一种编辑器**

## 源码控制

_进步远非由变化组成，而是取决于好记性。不能记住过去的人，被判重复过去。——George Santayana, Life of Reason_

**Always Use Source Code Control 总是使用源码控制**

## 调试

**Fix the Problem, Not the Blame 要修正问题，而不是发出指责**

如果你目睹 bug 或见到 bug 报告时的第一反应是「那不可能」，你就完全错了。一个脑细胞都不要浪费在以「但那不可能发生」起头的思路上，因为很明显，那不仅可能，而且已经发生了。

在调试时小心「近视」。要抵制只修正你看到的症状的急迫愿望：更有可能的情况是，实际的故障离你正在观察的地方可能还有几步远，并且可能涉及许多其他的相关事物。要总是设法找出问题的根源，而不只是问题的特定表现。

找到问题的原因的一种非常简单、却又特别有用的技术是向别人解释它。他应该越过你的肩膀看着屏幕，不断点头（像澡盆里上下晃动的橡皮鸭）。他们一个字也不需要说；你只是一步步解释代码要做什么，常常就能让问题从屏幕上跳出来，宣布自己的存在。

记住，如果你看到马蹄印，要想到马，而不是斑马。OS 很可能没有问题。数据库也很可能情况良好。

某样东西出错时，你感到吃惊的程度与你对正在运行的代码的信任及信心成正比。这就是为什么，在面对「让人吃惊」的故障时，你必须意识到你的一个或更多的假设是错的。不要因为你「知道」它能工作而轻易放过与 bug 有牵连的例程或代码。证明它。用这些数据、这些边界条件、在这个语境中证明它。

**Don&apos;t Assume it Prove It 不要假定，要证明**

## 文本操纵

**Learn a Text Manipulation Language 学习一种文本操纵语言**

## 代码生成器

**Write Code That Writes Code 编写能编写代码的代码**

## 注重实效的偏执

**You Can&apos;t Write Perfect Software 你不可能写出完美的软件**

## 按合约设计

**Design with Contracts 通过合约进行设计**

## 死程序不说谎

**Crash Early 早崩溃要崩溃，不要破坏（trash）**

## 断言式编程

**If It Can&apos;t Happen, Use Assertions to Ensure That It Won&apos;t 如果它不可能发生，用断言确保它不会发生**

## 何时使用异常

「如果我移走所有的异常处理器，这些代码是否仍然能运行？」如果答案是「否」，那么异常也许就正在被用在非异常的情形中。

**Use Exceptions for Exceptional Problems 将异常用于异常的问题**

异常表示即时的、非局部的控制转移——这是一种级联的（cascading）goto。

## 怎样配平资源

只要在编程，我们都要管理资源：内存、事务、线程、文件、定时器——所有数量有限的事物。大多数时候，资源使用遵循一种可预测的模式：你分配资源、使用它，然后解除其分配。

对于一次需要不只一个资源的例程，可以对资源分配的基本模式进行扩展。有两个另外的建议：

1. 以与资源分配的次序相反的次序解除资源的分配。这样，如果一个资源含有对另一个资源的引用，你就不会造成资源被遗弃。
2. 在代码的不同地方分配同一组资源时，总是以相同的次序分配它们。这将降低发生死锁的可能性。（如果进程 A 申请了 resource1，并正要申请 resource2，而进程 B 申请了 resource2，并试图获得 resource1，这两个进程就会永远等待下去。）

## 解耦与得墨忒耳法则

**Minimize Coupling Between Modules 使模块之间的耦合减至最少**

## 元程序设计

**Configure, Don&apos;t Integrate 要配置，不要集成**

要用元数据（metadata）描述应用的配置选项：调谐参数、用户偏好（user preference）、安装目录，等等。

**Put Abstractions in Code, Details in Metadata 将抽象放进代码，细节放进元数据**

## 时间耦合

时间是软件架构的一个常常被忽视的方面。吸引我们的时间只是进度表上的时间，发布之前我们剩余的时间——但这不是我们这里在谈论的时间。相反，我们谈论的是，作为软件自身的一种设计要素的时间的角色。时间有两个方面对我们很重要：**并发**（事情在同一时间发生）和**次序**（事情在时间中的相对位置）。

我们需要容许并发，并考虑解除任何时间或次序上的依赖。这样做，我们可以获得灵活性，并减少许多开发领域中的任何基于时间的依赖：工作流分析、架构、设计、还有部署。

**Analyze Workflow to Improve Concurrency 分析工作流，以改善并发性**

Design Using Services 用服务进行设计实际上我们创建的不是组件，而是服务——位于定义良好的、一致的接口之后的独立、并发的对象。

**Always Design for Concurrency 总是为并发进行设计**

## 靠巧合编程

**Don&apos;t Program by Coincidence 不要靠巧合编程**

不要做历史的奴隶。不要让已有的代码支配将来的代码。如果不再适用，所有的代码都可被替换。即使是在一个程序中，也不要让你已经做完的事情约束你下一步要做的事情——准备好进行重构。

## 算法速率

把 **_O_** 视为「阶为……」（on the order of）的意思。**_O( )_** 表示法对我们在度量的事物（时间、内存，等等）的值设置了上限。

![Order Chart](/images/the-pragmatic-programmer-note/order-chart.jpeg)

嵌套循环。如果你需要在循环中嵌套另外的循环，那么你的算法就变成了**_O(m x n)_** ，这里的 m 和 n 是两个循环的界限。

这通常发生在简单的排序算法中，比如冒泡排序：外循环依次扫描数组中的每个元素，内循环确定在排序结果的何处放置该元素。这样的排序算法往往是 **_O(n2)_** 。

简单循环。如果某个简单循环从 1 运行到 n，那么算法很可能就是 **_O(n)_** ——时间随 n 线性增加。其例子有穷举查找、找到数组中的最大值、以及生成校验和。

二分法。如果你的算法在每次循环时把事物集合一分为二，那么它很可能是对数型 **_O(lg(n))_** 算法

对有序列表的二分查找、遍历二杈树、以及查找机器字中的第一个置位了的位，都可能是 **_O(ln(n))_** 算法。

分而治之。划分其输入，并独立地在两个部分上进行处理，然后再把结果组合起来的算法可能是 **_O(nln(n))_** 。经典例子是快速排序，其工作方式是：把数据划分为两半，并递归地对每一半进行排序。尽管在技术上是 **_O(n2)_** ，但因为其行为在馈入的是排过序的输入时会退化，快速排序的平均运行时间是 **_O(nln(n))_** 。

组合。只要算法考虑事物的排列，其运行时间就有可能失去控制。这是因为排列涉及到阶乘（从数字1 到 5 有 5！=5×4×3×2×1=120 种排列）。得出 5 个元素的组合算法所需的时间：6 个元素需要 6 倍的时间，7 个元素则需要 42 倍的时间。其例子包括许多公认的难题的算法——旅行商问题、把东西最优地包装进容器中、划分一组数、使每一组都有相同的总和，等等。在特定问题领域中，常常用启发式方法（heuristics）减少这些类型的算法的运行时间。

## 重构

与建筑相比，软件更像是园艺——它比混凝土更有机。你根据最初的计划和各种条件在花园里种植许多花木。有些花木茁壮成长，另一些注定要成为堆肥。你可能会改变植株的相对位置，以有效利用光影、风雨的交互作用。过度生长的植株会被分栽或修剪，颜色不协调的会被移栽到从美学上看更怡人的地方。你拔除野草，并给需要额外照料的植株施肥。你不断关注花园的兴旺，并按照需要（对土壤、植株、布局）做出调整。

把需要重构的代码当作是一种「肿瘤」。切除它需要进行「侵入性」的外科手术。你可以现在手术，趁它还小把它取出来。你也可以等它增大并扩散——但那时再切除它就会更昂贵、更危险。等得再久一点，「病人」就有可能会丧命。

1. 不要试图在重构的同时增加功能。
2. 在开始重构之前，确保你拥有良好的测试。尽可能经常运行这些测试。这样，如果你的改动破坏了任何东西，你就能很快知道。

## 易于测试的代码

软件的单元测试是对模块进行演练的代码。在典型情况下，单元测试将建立某种人工环境，然后调用被测试模块中的例程。然后，它根据已知的值，或是同一测试先前返回的结果（回归测试），对返回的结果进行检查。

## 需求之坑

_完美，不是在没有什么需要增加、而是在没有什么需要去掉时达到的。——Antoine de St.Exupery, Wind, Sand, and Stars,1939_

**Don&apos;t Gather Requirements-Dig for Them 不要搜集需求——挖掘它们**

当你在尘土里四处挖掘时，你怎样才能识别出真实的需求？答案既简单又复杂。简单的回答是，需求是对需要完成的某件事情的陈述。

找出用户为何要做特定事情的原因、而不只是他们目前做这件事情的方式，这很重要。到最后，你的开发必须解决他们的商业问题，而不只是满足他们陈述的需求。

**Work with a User to Think Like a User 与用户一同工作，以像用户一样思考**

制作需求文档时的一大危险是太过具体。好的需求文档会保持抽象。在涉及需求的地方，最简单的、能够准确地反映商业需要的陈述是最好的。这并非意味着你可以含糊不清——你必须把底层的语义不变项当作需求进行捕捉，并把具体的或当前的工作实践当作政策记入文档。需求不是架构。需求不是设计，也不是用户界面。需求是需要。

**Abstractions Live Longer than Details 抽象比细节活得更长久**

许多项目的失败都被归咎于项目范围的增大——也称为特性膨胀（feature bloat）、蔓延特性论（creeping featurism）、或是需求蔓延（requirement creep）。

要创建并维护项目词汇表（project glossary）——这是定义项目中使用的专用术语和词汇的地方。项目的所有参与者，从最终用户到支持人员，都应该使用这个词汇表，以确保一致性。这就意味着，可以访问词汇表的人员范围应该很广泛——这是采用基于 Web 的文档的一个有效论据。

## 解开不可能解开的谜题

流行的俗话「在盒子外面思考」鼓励我们找出可能不适用的约束，并忽略它们。但这个俗话并不完全准确。如果「盒子」是各种约束和条件的边界，那么诀窍就在于找到盒子——它可能比你以为的要大得多。

**Don&apos;t Think Outside the Box-Find the Box 不要在盒子外面思考——要找到盒子**

在面对棘手的问题时，列出所有在你面前的可能途径。不要排除任何东西，不管它听起来有多无用或愚蠢。现在，逐一检查列表中的每一项，并解释为何不能采用某个特定的途径。你确定吗？你能否证明？

有时你会发现，自己在处理的问题似乎比你以为的要难得多。感觉上好像是你走错了路——一定有比这更更容易的方法！或许现在你已落在了进度表后面，甚或失去了让系统工作起来的信心，因为这个特定的问题是「不可能解决的」。这正是你退回一步，问问自己以下问题的时候：有更容易的方法吗？你是在设法解决真正的问题，还是被外围的技术问题转移了注意力？这件事情为什么是一个问题？是什么使它如此难以解决？它必须以这种方式完成吗？它真的必须完成吗？很多时候，当你设法回答这些问题时，你会有让自己吃惊的发现。很多时候，对需求的重新诠释能让整个问题全都消失——就像是戈尔迪斯结。

## 等你准备好

**Listen to Nagging Doubts-Start When You&apos;re Ready 倾听反复出现的疑虑——等你准备好再开始**

## 规范陷阱

**Some Things Are Better Done than Described 对有些事情「做」胜于「描述」**

## 圆圈与箭头

还有一些开发者，在有许多已沉没项目的大海里漂流，不断抓住最新的时尚，就像是遇到海难的人紧紧抓住漂来的木头一样。每当有新的木头漂过时，他们都会费力地游过去，希望这一块会更好。但到最后，不管漂浮物有多好，这些开发者仍然漫无目的地漂流着。

**Don&apos;t Be a Slave to Formal Methods 不要做形式方法的奴隶**

## 注重实效的团队

**Organize Around Functionality, Not Job Functions 围绕功能、而不是工作职务进行组织**

## 无情的测试

**Coding Ain&apos;t Done&apos;Til All the Tests Run 要到通过全部测试，编码才算完成**

**Use Saboteurs to Test Your Testing 通过「蓄意破坏」测试你的测试**

**Test State Coverage, Not Code Coverage 测试状态覆盖，而不是代码覆盖**

## 极大的期望

在抽象的意义上，应用如果能正确实现其规范，就是成功的。遗憾的是，这只能付抽象的账。在现实中，项目的成功是由它在多大程度上满足了用户的期望来衡量的。不符合用户预期的项目注定是失败的，不管交付的产品在绝对的意义上有多好。

**Gently Exceed Your Users&apos; Expectations 温和地超出用户的期望**

## 傲慢与偏见

我们想要看到对所有权的自豪。「这是我编写的，我对自己的工作负责。」你的签名应该被视为质量的保证。当人们在一段代码上看到你的名字时，应该期望它是可靠的、用心编写的、测试过的和有文档的，一个真正的专业作品，由真正的专业人员编写。

## 建设藏书库

### 分析与设计

- 《Object-Oriented Software Construction，2nd Edition》 Bertrand Meyer 的史诗般的著作，论述面向对象开发的基本原理。
- 《Design Patterns》设计模式在比编程语言惯用手法更高的层面上描述解决特定类型的问题的途径。这本由 Gang of Four 撰写、现已成为经典的书籍描述了23种基本的设计模式，包括 Proxy、Visitor 和 Singleton，等等。
- 《Analysis Patterns》一个高级架构型模式的宝藏，取自广泛的真实项目，「蒸馏」成书籍的形式。相对快速地深入了解多年建模经验的途径。

### 团队与项目

- 《The Mythical Man Month》 Fred Brooks 的经典著作，论述项目团队组织的各种危险，最近作了修订。
- 《Dynamics of Software Development》一系列论述大型团队软件构建的短文，着重讨论团队成员之间、团队与外界之间的动力机制。
- 《Surviving Object-Oriented Projects: A Manager’s Guide》 Alistair Cockburn 的「战地报导」，阐释OO项目管理的许多危险和陷阱——特别是你的第一个项目。Cockburn 先生提供了能让你解决最常见问题的提示与技术。

## 注重实效的程序员之快速参考指南 The Pragmatic Programmer Quick Reference Guide

1. 关心你的技艺 Care About Your Craft。如果你不在乎能否漂亮地开发出软件，你又为何要耗费生命去开发软件呢？
2. 思考！你的工作 Think! About Your Work。关掉自动驾驶仪，接管操作。不断地批评和评估你的工作。
3. 提供各种选择，不要找蹩脚的借口 Provide Options, Don&apos;t Make Lame Excuses 要提供各种选择，而不是找借口。不要说事情做不到；说明能够做什么。
4. 不要容忍破窗户 Don&apos;t Live with Broken Windows 当你看到糟糕的设计、错误的决策和糟糕的代码时，修正它们。
5. 做变化的催化剂 Be a Catalyst for Change 你不能强迫人们改变。相反，要向他们展示未来可能会怎样，并帮助他们参与对未来的创造。
6. 记住大图景 Remember the Big Picture 不要太过专注于细节，以致忘了查看你周围正在发生什么。
7. 使质量成为需求问题 Make Quality a Requirements Issue 让你的用户参与确定项目真正的质量需求。
8. 定期为你的知识资产投资 Invest Regularly in Your Knowledge Portfolio 让学习成为习惯。
9. 批判地分析你读到的和听到的 Critically Analyze What You Read and Hear 不要被供应商、媒体炒作、或教条左右。要依照你自己的看法和你的项目的情况去对信息进行分析。
10. 你说什么和你怎么说同样重要 It&apos;s Both What You Say and the Way You Say It 如果你不能有效地向他人传达你的了不起的想法，这些想法就毫无用处。
11. 不要重复你自己 DRY-Don&apos;t Repeat Yourself 系统中的每一项知识都必须具有单一、无歧义、权威的表示。
12. 让复用变得容易 Make It Easy to Reuse 如果复用很容易，人们就会去复用。创造一个支持复用的环境。
13. 消除无关事物之间的影响 Eliminate Effects Between Unrelated Things 设计自足、独立、并具有单一、良好定义的目的的组件。
14. 不存在最终决策 There Are No Final Decisions 没有决策是浇铸在石头上的。相反，要把每项决策都视为是写在沙滩上的，并为变化做好计划。
15. 用曳光弹找到目标 Use Tracer Bullets to Find the Target 曳光弹能通过试验各种事物并检查它们离目标有多远来让你追踪目标。
16. 为了学习而制作原型 Prototype to Learn 原型制作是一种学习经验。其价值并不在于所产生的代码，而在于所学到的经验教训。
17. 靠近问题领域编程 Program Close to the Problem domain 用你的用户的语言进行设计和编码。
18. 估算，以避免发生意外 Estimate to Avoid Surprises 在着手之前先进行估算。你将提前发现潜在的问题。
19. 通过代码对进度表进行迭代 Iterate the Schedule with the Code 用你在进行实现时获得的经验提炼项目的时间标度。
20. 用纯文本保存知识 Keep Knowledge in Plain Text 纯文本不会过时。它能够帮助你有效利用你的工作，并简化调试和测试。
21. 利用命令shell的力量 Use the Power of Command Shells 当图形用户界面无能为力时使用 shell。
22. 用好一种编辑器 Use a Single Editor Well 编辑器应该是你的手的延伸；确保你的编辑器是可配置、可扩展和可编程的。
23. 总是使用源码控制 Always Use Source Code Control 源码控制是你的工作的时间机器——你能够回到过去。
24. 要修正问题，而不是发出指责 Fix the Problem, Not the Blame bug 是你的过错还是别人的过错，并不是真的很有关系——它仍然是你的问题，它仍然需要修正。
25. 不要恐慌 Don&apos;t Panic When Debuging 做一次深呼吸，思考什么可能是bug的原因。
26. ”Select” 没有问题 &quot;Select&quot; Isn&apos;t Broken 在 OS 或编译器、甚或是第三方产品或库中很少发现 bug。bug 很可能在应用中。
27. 不要假定，要证明 Don&apos;t Assume It-Prove It 在实际环境中——使用真正的数据和边界条件——证明你的假定。
28. 学习一种文本操纵语言 Learn a Text Manipulation Language 你用每天的很大一部分时间处理文本，为什么不让计算机替你完成部分工作呢？
29. 编写能编写代码的代码 Write Code That Writes Code 代码生成器能提高你的生产率，并有助于避免重复。
30. 你不可能写出完美的软件 You Can&apos;t Write Perfect Software 软件不可能完美。保护你的代码和用户，使它（他）们免于能够预见的错误。
31. 通过合约进行设计 Design with Contracts 使用合约建立文档，并检验代码所做的事情正好是它声明要做的。
32. 早崩溃 Crash Early 死程序造成的危害通常比有问题的程序要小得多。
33. 用断言避免不可能发生的事情 Use Assertions to Prevent the Impossible 断言验证你的各种假定。在一个不确定的世界里，用断言保护你的代码。
34. 将异常用于异常的问题 Use Exceptions for Exceptional Problems 异常可能会遭受经典的意大利面条式代码的所有可读性和可维护性问题的折磨。将异常保留给异常的事物。
35. 要有始有终 Finish What You Start 只要可能，分配某资源的例程或对象也应该负责解除其分配。
36. 使模块之间的耦合减至最少 Minimize Coupling Between Modules 通过编写「羞怯的」代码并应用得墨忒耳法则来避免耦合。
37. 要配置，不要集成 Configure, Don&apos;t Integrate 要将应用的各种技术选择实现为配置选项，而不是通过集成或工程方法实现。
38. 将抽象放进代码，细节放进元数据 Put Abstractions in Code, Details in Metadata 为一般情况编程，将细节放在被编译的代码库之外。
39. 分析工作流，以改善并发性 Analyze Workflow to Improve Concurrency 利用你的用户的工作流中的并发性。
40. 用服务进行设计 Design Using Services 根据服务——独立的、在良好定义、一致的接口之后的并发对象——进行设计。
41. 总是为并发进行设计 Always Design for Concurrency 容许并发，你将会设计出更整洁、具有更少假定的接口。
42. 使视图与模型分离 Separate Views from Models 要根据模型和视图设计你的应用，从而以低廉的代码获取灵活性。
43. 用黑板协调工作流 Use Blackboards to Coordinate Workflow 用黑板协调完全不同的事实和因素，同时又使各参与方保持独立和隔
44. 不要靠巧合编程 Don&apos;t Program by Coincidence 只依靠可靠的事物。注意偶发的复杂性，不要把幸运的巧合与有目的的计划混为一谈。
45. 估算你的算法的阶 Estimate the Order of Your Algorithms 在你编写代码之前，先大致估算事情需要多长时间。
46. 测试你的估算 Test Your Estimates 对算法的数学分析并不会告诉你每一件事情。在你的代码的目标环境中测定它的速度。
47. 早重构，常重构 Refactor Early, Refactor Often 就和你会在花园里除草、并重新布置一样，在需要时对代码进行重写、重做和重新架构。要铲除问题的根源。
48. 为测试而设计 Design to Test 在你还没有编写代码时就开始思考测试问题。
49. 测试你的软件，否则你的用户就得测试 Test Your Software, or Your Users Will 无情地测试。不要让你的用户为你查找 bug。
50. 不要使用你不理解的向导代码 Don&apos;t Use Wizard Code You Don&apos;t Understand 向导可以生成大量代码。在你把它们合并进你的项目之前，确保你理解全部这些代码。
51. 不要搜集需求——挖掘它们 Don&apos;t Gather Requirements-Dig for Them 需求很少存在于表面上。它们深深地埋藏在层层假定、误解和政治手段的下面。
52. 与用户一同工作，以像用户一样思考 Work with a User to Think Like a User 要了解系统实际上将如何被使用，这是最好的方法。
53. 抽象比细节活得更长久 Abstractions Live Longer than Details 「投资」于抽象，而不是实现。抽象能在来自不同的实现和新技术的变化的「攻击」之下存活下去。
54. 使用项目词汇表 Use a Project Glossary 创建并维护项目中使用的专用术语和词汇的单一信息源。
55. 不要在盒子外面思考——要找到盒子 Don&apos;t Think Outside the Box-Find the Box 在遇到不可能解决的问题时，要确定真正的约束。问问你自己：「它必须以这种方式完成吗？它真的必须完成吗？」
56. 等你准备好再开始 Start When You&apos;re Ready 你的一生都在积累经验。不要忽视反复出现的疑虑。
57. 对有些事情「做」胜于「描述」Some Things Are Better Done than Described 不要掉进规范的螺旋——在某个时刻，你需要开始编码。
58. 不要做形式方法的奴隶 Don&apos;t Be a Slave to Formal Methods 如果你没有把某项技术放进你的开发实践和能力的语境中，不要盲目地采用它。
59. 昂贵的工具不一定能制作出更好的设计 Costly Tools Don&apos;t Produce Better Designs 小心供应商的炒作，行业教条、以及价格标签的诱惑。要根据工具的价值判断它们。
60. 围绕功能组织团队 Organize Teams Around Functionality 不要把设计师与编码员分开，也不要把测试员与数据建模员分开。按照你构建代码的方式构建团队。
61. 不要使用手工流程 Don&apos;t Use Manual Procedures shell 脚本或批文件会一次次地以同一顺序执行同样的指令。
62. 早测试，常测试，自动测试。Test Early. Test Often. Test Automatically 与呆在书架上的测试计划相比，每次构建时运行的测试要有效得多。
63. 要到通过全部测试，编码才算完成 Coding Ain’t Done‘Til All the Tests Run 就是这样。
64. 通过「蓄意破坏」测试你的测试 Use Saboteurs to Test Your Testing 在单独的软件副本上故意引入bug，以检验测试能够抓住它们。
65. 测试状态覆盖，而不是代码覆盖 Test State Coverage, Not Code Coverage 确定并测试重要的程序状态。只是测试代码行是不够的。
66. 一个 bug 只抓一次 Find Bugs Once 一旦测试员找到一个 bug，这应该是测试员最后一次找到它。此后自动测试应该对其进行检查。
67. 英语就是一种编程语言 English is Just a Programming Language 像你编写代码一样编写文档：遵守DRY原则、使用元数据、MVC、自动生成，等等。
68. 把文档建在里面，不要拴在外面 Build Documentation In, Don&apos;t Bolt It On 与代码分离的文档不太可能被修正和更新。
69. 温和地超出用户的期望 Gently Exceed Your Users&apos; Expectations 要理解你的用户的期望，然后给他们的东西要多那么一点。
70. 在你的作品上签名 Sign Your Work 过去时代的手艺人为能在他们的作品上签名而自豪。你也应该如此。

## 测试的各个方面

- 单元测试
- 集成测试
- 验证和校验
- 资源耗尽、错误及恢复
- 性能测试
- 可用性测试
- 对测试自身进行测试

## 劈开戈尔迪斯结，在解决不可能解决的问题时，问问你自己：

- 有更容易的方法吗？
- 我是在解决正确的问题吗？
- 这件事情为什么是一个问题？
- 是什么使它如此难以解决？
- 它必须以这种方式完成吗？
- 它真的必须完成吗？

## 何时进行重构？

- 你发现了对 DRY 原则的违反。
- 你发现事物可以更为正交。
- 你的知识扩展了。
- 需求演变了。
- 你需要改善性能。

## 怎样深思熟虑地编程？

- 总是意识到你在做什么。
- 不要盲目地编程。
- 按照计划行事
- 依靠可靠的事物。
- 为你的假定建立文档
- 不要只是测试你的代码，还要测试你的假定。
- 为你的工作划分优先级。
- 不要做历史的奴隶。

## 函数的得墨忒耳法则，某个对象的方法应该只调用属于以下情形的方法：

- 它自身
- 传入的任何参数
- 它创建的对象
- 组件对象

## 调试检查清单

- 正在报告的问题是底层 bug 的直接结果，还是只是症状？
- bug 真的在编译器里？在 OS 里？或者是在你的代码里？
- 如果你向同事详细解释这个问题，你会说什么？
- 如果可疑代码通过了单元测试，测试是否足够完整？如果你用该数据运行单元测试，会发生什么？
- 造成这个 bug 的条件是否存在于系统中的其他任何地方？

## 应制作原型的事物

- 架构
- 已有系统中的新功能
- 外部数据的结构或内容
- 第三方工具或组件
- 性能问题
- 用户界面设计

## 架构问题

- 责任是否得到了良好定义？
- 协作是否得到了良好定义？
- 耦合是否得以最小化？
- 你能否确定潜在的重复？
- 接口定义和各项约束是否可接受？
- 模块能否在需要时访问所需数据？

## 怎样维持正交性？

- 设计独立、良好定义的组件。
- 使你的代码保持解藕。
- 避免使用全局数据。
- 重构相似的函数。</content:encoded></item><item><title>《Software Engineering at Google》 笔记 01：软件工程是什么？</title><link>https://igaozp.me/blog/software-engineering-at-google-note-01/</link><guid isPermaLink="true">https://igaozp.me/blog/software-engineering-at-google-note-01/</guid><pubDate>Thu, 08 Sep 2022 00:00:00 GMT</pubDate><content:encoded>[Software Engineering at Google](https://qiangmzsx.github.io/Software-Engineering-at-Google/#/zh-cn/Chapter-1_What_Is_Software_Engineering/Chapter-1_What_Is_Software_Engineering)

软件工程可以被认为是 **「随着时间推移而整合的编程」**。

本书强调了三个基本原则，我们认为软件组织在设计、架构和编写代码时应该牢记这些原则：

- **时间和变化** ​代码如何展期生命周期内进行适配。
- **规模和增长** ​一个组织如何适应它的发展过程。
- **权衡和成本** ​一个组织如何根据时间和变化以及规模和增长的经验教训做出决策。
  编程和软件工程之间有三个关键的区别：时间、规模和权衡取舍。

在一个软件工程项目中，工程师需要更多关注时间成本和需求变更。在软件工程中，我们需要更加关注规模和效率，无论是对我们生产的软件，还是对生产软件的组织。最后，作为软件工程师，我们被要求做出更复杂的决策，其结果风险更大，而且往往是基于对时间和规模增长的不确定性的预估。

了解时间对程序的影响的一种方法是思考「代码的预期生命周期是多少？」

- 通常，周期短的代码不受时间的影响。对于一个只需要存活一个小时的程序，你不太可能考虑其适应基础库、操作系统（OS）、硬件或语言版本的新版本。这些短期系统实际上“只是”一个编程问题，就像在一个维度中压缩得足够扁的立方体是正方形一样。随着我们扩大时间维度，允许更长的生命周期，改变显得更加重要。在十年或更长的时间里，大多数程序依赖关系，无论是隐式的还是显式的，都可能发生变化。这一认识是我们区分软件工程和编程的根本原因。
- 如果在软件的预期生命周期内，你能够对任何有价值的变化做出反应，无论是技术还是商业原因，那么你的项目是可持续的。重要的是，我们只关注能力——你可能因为缺乏价值或其他优先事项而选择不进行特定的升级。当你基本上无法对基础技术或产品方向的变化做出反应时，你就把高风险赌注押在希望这种变化永远不会变得至关重要。对于短期项目，这可能是一个安全的赌注。几十年后，情况可能并非如此。

另一种看待软件工程的方法是考虑规模。有多少人参与？随着时间的推移，他们在开发和维护中扮演什么角色？

团队协作带来了新的问题，但也提供了比任何单个程序员更多的潜力来产生有价值的系统。

软件工程与编程的不同之处在于需要做出的决策的复杂性及其风险。在软件工程中，我们经常被迫在几个路径之间做评估和权衡，有时风险很高，而且价值指标不完善。软件工程师或软件工程负责人的工作目标是实现组织、产品和开发工作流程的可持续性和管理扩展成本为目标。考虑到这些投入，评估你的权衡并做出理性的决定。有时，我们可能会推迟维护更改，甚至接受扩展性不好的策略，因为我们知道需要重新审视这些决策。这些决策应该是明确的和清晰的递延成本。

**Hyrum&apos;s Law 海勒姆定律**：「当一个 API 有足够多的用户的时候，在约定中你承诺的什么都无所谓，所有在你系统里面被观察到的行为都会被一些用户直接依赖。」

对随时间变化和维护的讨论必须了解海勒姆定律，正如对效率或热力学的讨论必须注意熵一样。仅仅因为熵从不减少并不意味着我们不应该努力提高效率。在维护软件时，海勒姆定律会适用，但这并不意味着我们不能对它进行规划或试图更好地了解它。我们可以减轻它，但我们知道，它永远不可能被根除。
海勒姆定律代表了一种实践知识，即使有最好的规划、最好的工程师和可靠的代码评审实践，我们也不能假设完全遵守已发布的契约或最佳实践。作为 API 所有者，通过明确地接口约定，你将获得一定的灵活性和自由度，但在实践中，给定更改的复杂性和难度还取决于用户对你的 API 的一些可观察行为的有用程度。如果用户不能依赖这些东西，那么你的 API 将很容易更改。如果有足够的时间和足够的用户，即使是最无害的变更也会破坏某些东西；你对变更价值的分析必须包含调查、识别和解决这些缺陷的难度。

依赖其依赖性的脆弱和未发布特性的代码可能被描述为「黑客」或「聪明」遵循最佳实践并为未来规划的代码更可能被描述为「干净」和「可维护」。两者都有其目的，但你选择哪一个关键取决于所讨论代码的预期生命周期。我们常说，「如果『聪明』是一种恭维，那就是程序，如果『聪明』是一种指责，那就是软件工程。」

最初的设计可能完全符合逻辑，并遵循合理的最佳实践。只有在向后兼容的变化演变之后，新的、更有效的选择才变得重要。虽然没有犯错误，但随着时间的推移，变化仍然是有价值的。

我们的工程师的工作量是否随着组织的规模而增长？工作是否随着代码库的大小而变多？如果这两种情况都是真实的，我们是否有机制来自动化或优化这项工作？如果没有，我们就有扩展问题。
自动化（这样一个人就可以做到更多）、整合/一致性（这样低级别的更改影响有限的问题范围）和专业知识（以便少数人就可以做得更多）。

如果安全问题是在产品投入生产后才发现的，修复的成本就非常高。如果在部署到生产之前就发现了安全问题，那也需要花费大量的工作来检测和修复问题，但成本更低些。如果你能够在最初的开发之前发现安全问题，将缺陷提交到版本控制就被发现，修复的成本更低：他们已经了解该功能；根据新的安全约束规范进行开发，要比提交代码后再让其他人分类标识并修复它更简单。

我们看到的一个普遍真理是，在开发人员的工作流程中发现的问题，通常可以降低成本。考虑开发人员工作流程的时间表，从左到右，从概念和设计开始，通过实施、评审、测试、提交、金丝雀和最终的生产部署来进行。在此时间线之前，将问题发现转移到“左侧”会使修问题解决成本更低

“成本”大致可以转化为努力的方向，可以包括以下任何或所有因素：

- 财务成本（如金钱）
- 资源成本（如CPU时间）
- 人员成本（例如，工作量）
- 交易成本（例如，采取行动的成本是多少？）
- 机会成本（例如，不采取行动的成本是多少？）
- 社会成本（例如，这个选择将对整个社会产生什么影响？）

工程团队的决策应该归结为几件事：我们这样做是因为我们必须这么做（法律要求、客户要求）。我们之所以这样做，是因为根据当前证据，这是我们当时能看到的最佳选择（由一些适当的决策者决策）。

**杰文斯悖论（Jevons Paradox）**：「一种资源的消耗可能会随着使用效率的提高而增加。」

时间不仅会触发技术依赖和软件系统的变化，还会触发用于驱动决策的数据的变化。

本质上，在相关系统的生命周期内，需要不时地重新审视决策。对于长期项目而言，在做出初始决策后，有能力改变方向通常是至关重要的。更重要的是，这意味着决策者需要勇气承认错误。与人的本能相反，勇于承认错误的领导人受更多的尊重。

我们认为，区分相关但不同的术语「编程」和「软件工程」是很重要的。这种差异很大程度上源于随着时间的推移对代码的管理、时间对规模的影响以及面对这些想法的决策。编程是产生代码的直接行为。软件工程是一组策略、实践和工具，这些策略、实践和工具是使代码在需要使用的时间内发挥作用，并允许整个团队的协作。</content:encoded></item><item><title>关于控制论的一些资料</title><link>https://igaozp.me/blog/cybernetics-information/</link><guid isPermaLink="true">https://igaozp.me/blog/cybernetics-information/</guid><description>整理收藏的一部分关于控制论的介绍及历史的资料</description><pubDate>Thu, 02 Jun 2022 00:00:00 GMT</pubDate><content:encoded>整理收藏的一部分关于控制论的介绍及历史的资料

&lt;!--more--&gt;

## 视频

- [【计算机博物志·人物篇】被遗忘的科学天才：沃尔特·皮茨](https://www.bilibili.com/video/BV1y5411D7a6)
- [【游戏社会学】赛博朋克是资本主义的终极形态吗？控制论革命的残酷真相](https://www.bilibili.com/video/BV1NK4y1L7gw?p=1&amp;totalPage=2)
- [万字长文！解析阿连德政府与赛博工程的兴亡史。【合集】](https://www.bilibili.com/video/BV1pP4y1Y78V?p=1&amp;totalPage=3)

## 文章

- [控制论恐慌和互联网的起源](https://matters.news/@conanxin/51118-%E6%8E%A7%E5%88%B6%E8%AE%BA%E6%81%90%E6%85%8C%E5%92%8C%E4%BA%92%E8%81%94%E7%BD%91%E7%9A%84%E8%B5%B7%E6%BA%90-bafyreih4pfwgtvl4kif6psr56yd4yted47lvwuwwmybm7s7kw3d53cbfve)
- [苏联的控制论](https://upwikizh.top/wiki/Cybernetics_in_the_Soviet_Union)
- [论社会主义控制论、加速主义（Accelerationist）梦想和堤昆（Tiqqun）噩梦](https://conanxin.medium.com/%E8%AE%BA%E7%A4%BE%E4%BC%9A%E4%B8%BB%E4%B9%89%E6%8E%A7%E5%88%B6%E8%AE%BA-%E5%8A%A0%E9%80%9F%E4%B8%BB%E4%B9%89-accelerationist-%E6%A2%A6%E6%83%B3%E5%92%8C%E5%A0%A4%E6%98%86-tiqqun-%E5%99%A9%E6%A2%A6-75841bab70f6)
- [机器政治——互联网的兴起和威权主义的新时代](https://matters.news/@conanxin/5214-%E6%9C%BA%E5%99%A8%E6%94%BF%E6%B2%BB-%E4%BA%92%E8%81%94%E7%BD%91%E7%9A%84%E5%85%B4%E8%B5%B7%E5%92%8C%E5%A8%81%E6%9D%83%E4%B8%BB%E4%B9%89%E7%9A%84%E6%96%B0%E6%97%B6%E4%BB%A3-zdpuAxcS62nxQPuZfwKZ3nqvZf53NjfnqgF3rXgNrc4JgRu4i)
- [“那深沉而奇异的巨壑”：自由意志主义、新自由主义和计算机文化](https://matters.news/@conanxin/13224-%E9%82%A3%E6%B7%B1%E6%B2%89%E8%80%8C%E5%A5%87%E5%BC%82%E7%9A%84%E5%B7%A8%E5%A3%91-%E8%87%AA%E7%94%B1%E6%84%8F%E5%BF%97%E4%B8%BB%E4%B9%89-%E6%96%B0%E8%87%AA%E7%94%B1%E4%B8%BB%E4%B9%89%E5%92%8C%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%96%87%E5%8C%96-zdpuAxKYGcLC8TtMFkHFnpAz2GqY33FafrhLEJCNJKE4RNVY8)
- [控制论如何将计算、反主流文化和设计联系起来](https://matters.news/@conanxin/21075-%E6%8E%A7%E5%88%B6%E8%AE%BA%E5%A6%82%E4%BD%95%E5%B0%86%E8%AE%A1%E7%AE%97-%E5%8F%8D%E4%B8%BB%E6%B5%81%E6%96%87%E5%8C%96%E5%92%8C%E8%AE%BE%E8%AE%A1%E8%81%94%E7%B3%BB%E8%B5%B7%E6%9D%A5-zdpuB1Bw6NpBB1UH8vTjuUHS9K1DsUy6FwwmnUVj5CzNs8TJN)
- [控制论之父今天会如何看待人工智能?](https://matters.news/@conanxin/26241-%E6%8E%A7%E5%88%B6%E8%AE%BA%E4%B9%8B%E7%88%B6%E4%BB%8A%E5%A4%A9%E4%BC%9A%E5%A6%82%E4%BD%95%E7%9C%8B%E5%BE%85%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-bafyreicbtrf6fr3hjli2jyjs4ejgf5ddorsoe3fjaohxtpdxchslcv6t7m)
- [苏联计算机科学的复杂历史](https://matters.news/@conanxin/50456-%E8%8B%8F%E8%81%94%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E7%9A%84%E5%A4%8D%E6%9D%82%E5%8E%86%E5%8F%B2-bafyreidmudj5hztmtaw7wqnabr6xc4z4xa4evnwquntpl74mk5nd5s4zre)
- [智利的社会主义控制论——赛博协同控制工程（Cybersyn Project）的历史](https://matters.news/@conanxin/55277-%E6%99%BA%E5%88%A9%E7%9A%84%E7%A4%BE%E4%BC%9A%E4%B8%BB%E4%B9%89%E6%8E%A7%E5%88%B6%E8%AE%BA-%E8%B5%9B%E5%8D%9A%E5%8D%8F%E5%90%8C%E6%8E%A7%E5%88%B6%E5%B7%A5%E7%A8%8B-cybersyn-project-%E7%9A%84%E5%8E%86%E5%8F%B2-bafyreiby3fpz3wqyghbonnuhticzitdbxc5etahqk4w6yefzmbyzro7tfa)

## 书籍

- [《人有人的用处》](https://book.douban.com/subject/4928961/)
- [《控制论》](https://book.douban.com/subject/2356843/)
- [《工程控制论》](https://book.douban.com/subject/1967264/)
- [《控制论革命者-阿连德时代智利的技术与政治》](https://book.douban.com/subject/34998217/)
- [《失控》](https://book.douban.com/subject/5375620/)</content:encoded></item><item><title>Mirror 上的本站内容备份</title><link>https://igaozp.me/blog/backup-in-mirror-xyz/</link><guid isPermaLink="true">https://igaozp.me/blog/backup-in-mirror-xyz/</guid><description>站点备份地址</description><pubDate>Wed, 05 Jan 2022 00:00:00 GMT</pubDate><content:encoded>本站点内容备份地址 https://mirror.xyz/igaozp.eth</content:encoded></item><item><title>IdeaVim 自用配置分享</title><link>https://igaozp.me/blog/ideavim-personal-config/</link><guid isPermaLink="true">https://igaozp.me/blog/ideavim-personal-config/</guid><pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;!--more--&gt;

```
let mapleader = &quot; &quot;

set clipboard=unnamedplus,unnamed
set hlsearch
set incsearch
set ignorecase
set smartcase
set showmode
set number
set scrolloff=3
set history=100000
set multiple-cursors
set commentary
set easymotion
set exchange
set surround
set ideajoin
set clipboard+=ideaput
set ideamarks
set keep-english-in-normal-and-restore-in-insert
set relativenumber
set highlightedyank
set NERDTree
set showmode
set so=5
set incsearch
set nu
set idearefactormode=keep
set ideavimsupport

let g:EasyMotion_override_acejump = 0
let g:argtextobj_pairs=&quot;[:],(:),&lt;:&gt;&quot;

map &lt;leader&gt;a :action $SelectAll&lt;CR&gt;
map &lt;leader&gt;x :action $Cut&lt;CR&gt;
map &lt;leader&gt;c :action $Copy&lt;CR&gt;
map &lt;leader&gt;z :action $Undo&lt;CR&gt;
map &lt;leader&gt;v :action $Paste&lt;CR&gt;

nnoremap &lt;esc&gt;&lt;esc&gt; :noh&lt;return&gt;
nnoremap gd :action GotoDeclaration
nnoremap ,e :action SearchEverywhere&lt;CR&gt;
nnoremap ,g :action FindInPath&lt;CR&gt;
nnoremap ,s :action FileStructurePopup&lt;CR&gt;

nnoremap gd :action GotoDeclaration&lt;CR&gt;
nnoremap gs :action GotoSuperMethod&lt;CR&gt;
nnoremap gi :action GotoImplementation&lt;CR&gt;
nnoremap gb :action JumpToLastChange&lt;CR&gt;

nnoremap U :action FindUsages&lt;CR&gt;
nnoremap R :action RenameElement&lt;CR&gt;

nnoremap == :action ReformatCode&lt;CR&gt;
vnoremap == :action ReformatCode&lt;CR&gt;

nnoremap [] :action OptimizeImports&lt;CR&gt;
vnoremap [] :action OptimizeImports&lt;CR&gt;

nnoremap cc :action CommentByLineComment&lt;CR&gt;
vnoremap cc :action CommentByLineComment&lt;CR&gt;

nnoremap &lt;C-CR&gt; :action ShowIntentionActions&lt;CR&gt;

nnoremap ,a :action GotoAction&lt;CR&gt;
vnoremap ,a :action GotoAction&lt;CR&gt;

xnoremap p &quot;0p
xnoremap p &quot;_dP

&quot; 搜索文件相关
nnoremap &lt;leader&gt;zc :action GotoClass&lt;CR&gt;
vnoremap &lt;leader&gt;zc :action GotoClass&lt;CR&gt;
nnoremap &lt;leader&gt;za :action GotoAction&lt;CR&gt;
nnoremap &lt;leader&gt;zh :action RecentChangedFiles&lt;CR&gt;
nnoremap &lt;leader&gt;zf :action GotoFile&lt;CR&gt;
nnoremap &lt;leader&gt;zd :action ActivateDebugToolWindow&lt;CR&gt;
nnoremap &lt;leader&gt;zr :action ActivateRunToolWindow&lt;CR&gt;
nnoremap &lt;leader&gt;zs :action ShelvedChangesToolbar&lt;CR&gt;
nnoremap &lt;leader&gt;zt :action ActivateTODOToolWindow&lt;CR&gt;
nnoremap &lt;leader&gt;zv :action ActivateVersionControlToolWindow&lt;CR&gt;
nnoremap &lt;leader&gt;zb :action ShowBookmarks&lt;CR&gt;
nnoremap &lt;leader&gt;zp :action ActivateProjectToolWindow&lt;CR&gt;

nnoremap &lt;Leader&gt;o :&lt;C-u&gt;action RecentProjectListGroup&lt;CR&gt;
noremap &lt;Space&gt;p :action SelectInProjectView&lt;CR&gt;
```</content:encoded></item><item><title>可组合性是 VIM 最强大的理念</title><link>https://igaozp.me/blog/composability-in-vim/</link><guid isPermaLink="true">https://igaozp.me/blog/composability-in-vim/</guid><description>个人认为 VIM 中各种指令的可组合性，是 VIM 保持经久不衰的最深层次的源泉，是实现各种高效操作的基石。</description><pubDate>Fri, 31 Dec 2021 00:00:00 GMT</pubDate><content:encoded>个人认为 **VIM** 中各种指令的可组合性，是 **VIM** 保持经久不衰的最深层次的源泉，是实现各种高效操作的基石。

&lt;!--more--&gt;

使用过 **VIM** 的人都知道，可以使用简短的 **VIM** 快捷键完成一些相对复杂的操作。以个人经常使用的快捷键 `ciw` 为例，`ciw` 并不是一个完整独立的命令，而是由 _c_、_i_、_w_ 这 3 个命令组合起来，其中 _c_ 为 Change 即修改，_i_ 为 Inner 即内部，_w_ 为 Word 即单词，整个命令组合并直译过来，即为“修改内部的单词”，实际上的效果是修改当前光标选中的单词。正是因为这些原子命令的可组合性，允许这些命令可以有机地结合，实现 1+1 &gt; 2 的效果。一旦这种可组合性的特性让使用者产生印象，便会给人一种这样的隐喻：「组合中的指令是可以替换的」，使用者可能会通过自己的想法，在可组合的规则内自由的尝试其他的组合。例如将 `ciw` 中的 _c_ 替换为 _d_，`diw` 这个指令即代表删除当前光标所在的单词，如果替换为 _y_，`yiw` 即代表复制当前光标所在的单词。**VIM** 在这样用户的自由地实践中，通常会诞生一些连作者都未曾设想的操作，这使得 **VIM** 在用户的使用「迭代」中迸发出惊人的生产力。

可组合性不仅仅是 **VIM** 中最强大的理念，同时也是软件领域最为强大的武器。

1. 编程语言只提供少量的关键词及对应的约束规则，使用有限的元件「组合」出可运行的程序。
2. UNIX 中通过管道（pipe）将功能不同的程序组合成一个整体实现复杂的数据操作。
3. 互联网中使用不同的通信协议或者 API 将不同的软件和服务组合起来，实现更加复杂的互动结构。</content:encoded></item><item><title>Spring Boot 中实现服务端主动数据推送</title><link>https://igaozp.me/blog/spring-boot-json-stream/</link><guid isPermaLink="true">https://igaozp.me/blog/spring-boot-json-stream/</guid><pubDate>Wed, 29 Dec 2021 00:00:00 GMT</pubDate><content:encoded>在日常开发中通常使用 `WebSocket` 实现服务端的主动地数据推送，这里简单介绍两种其他的数据推送方案，`ndjson` 和 `SSE`。

&lt;!--more--&gt;

## ndjson

**ndjson** 全称 **Newline Delimited JSON**，即使用换行符分割的 JSON，同时能够保证每一行的内容都是一个完整的 JSON。这里使用 ndjson 并不是因为它可以实现服务端数据推送，而是凭借 **ndjson** 的数据格式的特性，作为服务端的 JSON 数据流的数据输出，来更好地帮助我们实现数据推送的效果。

这里使用 **Spring Boot** 作为基础的服务端框架，简单演示一下相关代码及功能。首先要确保项目依赖中包含 `spring-boot-starter-webflux` 依赖包。

代码示例

```java
@GetMapping(value = &quot;ndjson&quot;, produces = MediaType.APPLICATION_NDJSON_VALUE)
Flux&lt;Data&gt; ndjson() {
    return dataStream();
}

private Flux&lt;Data&gt; dataStream() {
    return Flux.interval(Duration.ofSeconds(1))
            .take(5)
            .map(i -&gt; new Data(i, Instant.now()));
}
```

代码中定义了一个私有方法用来生成数据流，效果是每隔 1 秒生成一条数据。在 `@GetMapping`中指定了 `MediaType.APPLICATION_NDJSON_VALUE` 作为数据的响应格式。

## SSE

**SSE** 全称 **Server-sent events**，即服务器发送事件，服务端可使用 **SSE** 生成相应的数据流。

代码示例，同样需要 **WebFlux** 支持。

```java
@GetMapping(value = &quot;sse&quot;, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux&lt;Data&gt; sse() {
    return dataStream();
}

private Flux&lt;Data&gt; dataStream() {
   return Flux.interval(Duration.ofSeconds(1))
            .take(5)
            .map(i -&gt; new Data(i, Instant.now()));
}
```

与 **ndjson** 方案唯一的不同是，数据响应的格式替换为了`MediaType.TEXT_EVENT_STREAM_VALUE`。

## 总结

虽然 **ndjson** 和 **SSE** 方案都可以实现服务端的数据推送，但这两种的数据通信都是单向通信，只能从服务端到客户端，无法实现双向通信，需要根据自身的场景需要酌情考虑使用。

项目的完整代码可参考 [StreamJsonController](https://github.com/igaozp/surabaya/blob/f8cc3573759ac22f63801e54edde563f6706a1ec/spring/src/main/java/xyz/andornot/controller/StreamJsonController.java)。

## 参考内容

[https://nurkiewicz.com/2021/08/json-streaming-in-webflux.html](https://nurkiewicz.com/2021/08/json-streaming-in-webflux.html)

[https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html](https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html)</content:encoded></item><item><title>【Java 回炉计划】01：基础知识流水账</title><link>https://igaozp.me/blog/java-base-knowledge-01/</link><guid isPermaLink="true">https://igaozp.me/blog/java-base-knowledge-01/</guid><pubDate>Sat, 25 Dec 2021 00:00:00 GMT</pubDate><content:encoded>详细内容可参考大佬整理的资料 [Java 拾遗卷1：语言特性 · 语雀](https://www.yuque.com/inuter/nghb49)，以下的内容是从个人角度觉得需要额外记录的内容。

&lt;!--more--&gt;

## 面向对象

**里氏代换原则**（Liskov Substitution Principle, LSP）：凡是父类能够出现的地方，都可以用子类替换。

多态是一种允许子类或接口可以有多种实现的特性，这种特性使得代码在运行时，同一个行为可能在不同的情况下，具有不同的表现形式。

子类 `Override` 父类方法时需要满足以下 4 个条件：

1. 方法的访问权限不能变小（private &lt;  package &lt; protected &lt; public）。
2. 返回类型能够向上转型成为父类的返回类型。
3. 异常也要能向上转型成为父类的异常。
4. 方法名、参数类型及个数必须严格一致。

把这个案例看明白：

```java
class A {
    public void m(A a) {
        System.out.println(&quot;AA&quot;);
    }

    public void m(D d) {
        System.out.println(&quot;AD&quot;);
    }
}

class B extends A {
    @Override
    public void m(A a) {
        System.out.println(&quot;BA&quot;);
    }

    public void m(B b) {
        System.out.println(&quot;BD&quot;);
    }
}

class C extends B {
}

class D extends B {
}

public class Test {

    public static void main(String[] args) {
        A a = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        a.m(a); // BA
        a.m(b); // BA
        a.m(c); // BA
        a.m(d); // AD
    }
}
```

## 类

普通的内部类可以与外部类互相访问私有属性。

构造方法调用的途径：

- 通过 `new` 关键字
- 在子类的构造方法通过 `super` 关键字调用父类的构造方法
- 通过反射的方式获取并调用

在创建类对象时，会先执行父类和子类的静态代码块，然后再执行父类和子类的构造方法。另外，静态代码块只会执行一次，而构造方法在每次实例化对象时都会被执行。

## 接口&amp;注解

注解是一种接口，但使用 `@interface` 修饰符修饰。

- `@Target` 注解用于限定注解的适用范围
- `@Retention` 注解用于表示一个注解保存到哪个阶段
- `@Inherited` 注解仅作用于类，默认情况下，父类的普通注解不能被子类继承，但如果父类的注解被 `@Inherited` 注解修饰，则该注解可以被子类自动继承。

Java 8 新增了一个 `@Repeatable` 元注解，使得某些注解可在同一元素上修饰多次。

## 常用数据类型及问题

Java 语言规范中并没有规定 `boolean` 类型数据的大小，其大小与 JVM 有关。

子类对象允许转换为父类对象，但父类对象不允许强制转换为子类对象。

由于计算机无法准确表示浮点数，`BigDemical` 不要使用入参为 `double` 类型的构造方法，而优先使用入参为 `String` 类型的构造方法。

`RoundingMode` 类

[https://www.yuque.com/inuter/nghb49/gakkv5?inner=g5LDq](https://www.yuque.com/inuter/nghb49/gakkv5?inner=g5LDq)

`BigDecimal` 类重写了 `equals` 方法，当且仅当两个 `BigDecimal` 对象表示的数值和 `scale` 都相同时，返回为 `true` ，如果两个对象表示的数值相同，但 `scale` 不同，返回 `false`。

如果希望只比较两个对象表示的数值，可以使用 `compareTo()` 方法

所有包装类都是 **immutable** 的（不可修改，不可继承），自动装箱都是通过包装类的 `valueOf()` 方法实现的，自动拆箱都是通过包装类的 `xxxValue()` 方法实现的。只有在自动装箱时（即调用 `valueOf()` 方法），相应的缓存机制才会生效。

对于 `表达式 1 ？ 表达式 2 ： 表达式 3` ，如果 `表达式 2` 和 `表达式 3` 有一项是基本数据类型，而另一项是对应的包转类型，那么包装类型的结果应该自动拆箱。

## String

先看如下代码，判断变量 `str1` 和 `str2` 的值分别为什么？

```java
String str1 = 1 + 2 + &quot;3&quot;;	// 33
String str2 = &quot;1&quot; + 2 + 3;	// 123
```

如果用 `+` 操作符进行拼接时，拼接对象包含其它类型的常量。则左边第一个字符串常量前的所有 `+` 操作符都会视为加运算。

在循环体内，字符串的链接方法应该使用 `StringBuilder` 的 `append` 方法，而不是使用 `+` 操作符。

字符串的长度等于字符串所包含的 Unicode 代码点总数，即 length() 方法返回的是 Unicode 代码点总数。

**代码点**（_code point_）是 Unicode 独有的定义，最简单的理解是：Unicode 中的每个字符都是一个代码点。说白了就是，“字符”在 Unicode 中被称为“代码点”。

**代码单元**（_code unit_）是代码点的最小组成单位，可以理解为“一个代码点是由多个代码单元组成的”。根据一个代码单元所占内存的大小，可以分为 UTF-8、UTF-16、UTF-32。比如 UTF-8 就表示一个代码单元占用 8 个 bit，即 1 个字节。

那对于 UTF-8 来说，一个字符（或代码点）包含几个代码单元呢，答案是 1 - 4 个代码单元。这类似于计算机组成原理中说的“变长编码”或“赫夫曼编码”，中心思想是“使用频率较高的字符的编码长度尽可能的小”。

- UTF-8：一个代码点可能由 1- 4 个代码单元组成，一个代码单元占用 1 个字节；
- UTF-16：一个代码点可能由 1- 2 个代码单元组成，一个代码单元占用 2 个字节；
- UTF-32：一个代码点由 1 个代码单元组成，一个代码单元占用 4 个字节。

## JVM

### **静态常量池**

静态常量池也称为 **class 文件常量池**，它是指 class 文件中的 **Contant Pool** 结构，可以通过 `javap -verbose` 工具反编译 class 文件查看。

静态常量池只是 class 文件的一个部分，仅存在于 class 文件中，与 JVM 无关。静态常量池用于存放编译阶段生成的各种**字面量**和**符号引用**。

**字面量**包括字符串（如 String str = &quot;a&quot;）、基本类型的常量（即 `final` 修饰的变量）。**符号引用**包括类和方法的全限定名（如 `String` 类的全限定名为 `java/lang/String`）、字段的名称和描述符、方法的名称和描述符。

### **运行时常量池**

运行时常量池是方法区的一部分，全局共享。

当类被加载到内存中时，JVM 会将 class 文件中的静态常量池内容加载到运行时常量池中。在解析阶段，JVM 会把符号引用替换为直接引用（对象的索引值）。

### **字符串常量池**

字符串常量池也是方法区的一部分，全局共享。可以认为字符串常量池是一个对 `String` 进行缓存的结构。

### **方法区&amp;永久代**

**方法区（Method Area）**是 Java 虚拟机规范中的一个概念，主要用来存放已被虚拟机加载的类的相关信息，包括类的信息、运行时常量池、字符串常量池等。

但 Java 虚拟机规范并未规定如何实现方法区，而常用的 **HotSpot** 虚拟机使用永久代（**PermGen**）来实现方法区，所以习惯将方法区称为永久代，但其实这两者并不等价。

## Object 类

**`wait()` 方法和 `Thread.sleep()` 方法有什么区别** ？

- `sleep()` 是 `Thread` 类中的静态方法，而 `wait()` 是 `Object` 类中本地方法；
- `sleep()` 不会释放锁，而 `wait()` 会释放，并自动加入到等待队列中；
- `sleep()` 不依赖于 synchronized 关键字，但 `wait()` 依赖，即 `wait()` 只能在 synchronized 块内使用；
- `sleep()` 不需要被主动唤醒（休眠之后退出阻塞），但 `wait()` 需要使用 `notify()` / `notifyAll()` 唤醒。

Java 语言规范规定 `equals()` 方法必须具备以下几个特性：

- **自反性**：如果 `x` 不为 `null` ， `x.equals(x)` 返回 `true` （ `x.equals(null)` 返回 `false` ）
- **对称性**：如果 `x.equals(y)` 为 `true` ，则 `y.equals(x)` 为 `true`
- **传递性**：如果 `x.equals(y)` 为 `true` 且 `y.equals(z)` 为 `true` ，则 `x.equals(z)` 为 `true`
- **一致性**：如果 `x` 和 `y` 未修改，则 `x.equals(y)` 的结果应该不变

**equals 与 hashCode 默认原则**

- 只要重写 `equals` ，就必须重写 `hashCode`
- 如果 `equals()` 返回 `true`，则 `hashCode()` 必须也返回 `true`
- 如果 `equals()` 返回 `false`，那 `hashCode()` 可以返回 `true` 或 `false`
- 因为 `Set` 存储的是不重复的对象，依据 `hashCode` 和 `equals` 进行判断，所以 `Set` 存储的对象必须重写这两个方法
- 如果自定义对象作为 `Map` 的 key，那么必须重写这两个方法

**equals 方法重写流程**

1. 可以先进行指针判断，如果是同一个对象，则直接返回 `true` ；
2. 判断传入的参数是否为 `null` ，如果是，直接返回 `false` ；
3. 判断两个对象的类型是否相同，如果不同，直接返回 `false` ；
4. 在确保类型相同的前提下进行强制类型转换，再对逐一对字段进行比较。

## 泛型

泛型本质是类型参数化，其与 JVM 无关，仅在编译时做语法检查，编译后被替换，这就是所谓的“类型擦除”。对于类型擦除，总结为一句话就是：**无边界，替换为 Object ；有边界，替换为边界**。

在编译一个泛型子类或泛型接口实现类时，编译器可能会生成一个 synthetic method，这种方法就叫桥接方法（bridge method）。桥接方法的出现是为了解决类型擦除对多态的破坏。

**正确理解 &lt;? extends T&gt; 与 &lt;? super T&gt;**

- `&lt;? extends T&gt;` 表示对象要么是 T 类型，要么是 T 的子类；
- `&lt;? super T&gt;` 表示对象要么是 T 类型，要么是 T 的父类。

**如何选择 &lt;? extends T&gt; 和 &lt;? super T&gt;**

- **不论是 `&lt;? extends T&gt;` 还是 `&lt;? super T&gt;` ，都是以类型 `T` 为中心的；**
- **如果你想使用 `T` 类型对象，则用 `&lt;? extends T&gt;` ；如果你想添加 `T` 类型对象，则用 `&lt;? super T&gt;` 。**

`&lt;?&gt;` 称为无界通配符（unbounded wildcard），可以匹配任何类型，但赋值之后就不能再添加元素了（ null 可以），只能进行获取或删除操作。

## 异常

`Error` 类及其子类和 `RuntimeException` 类及其子类被称为 **Unchecked Exception**；其它所有继承自 `Throwable` 接口的子类（包括 `Exception` 类自身）则称为 **Checked Exception。**Checked Exception 需要在代码中显式处理，否则会编译出错。

`NoClassDefFoundError` 和 `ClassNotFoundException` 有什么不同 ？

- `ClassNotFoundException` 表示在运行时找不到指定的类，如调用 `Class.forName()` 方法时，如果找不到目标类，则抛出该异常。
- `NoClassDefFoundError` 表示未找到目标类的定义，如在编译成功后，故意删除一个类的 class 文件，在执行程序时，就会抛出该错误

当存在 `try` 时，可以只有 `catch` 代码块，也可以只有 `fianlly` 代码块，但就是不能只有 `try` 部分。

`finally` 是在 `return` 表达式运行后执行的，此时要 `return` 的结果已经被先暂存起来，等 `finally` 代码块执行结束后再返回之前暂存的返回值。

**不要在 `finally` 代码块中执行 `return` 语句。**

### **为什么 finally 代码块总能被执行？**

因为编译器在编译 Java 代码时，会复制 `finally` 代码块，并分别放在 `try-catch` 所有正常执行路径及异常执行路径的出口中，这样不管发生什么情况， `finally` 代码块都会执行。

`Throwable` 类的（直接或间接）子类不能是泛型类

## 反射

[https://www.yuque.com/inuter/nghb49/op33g0?inner=IimI7](https://www.yuque.com/inuter/nghb49/op33g0?inner=IimI7)

其中 `getFields()` 、 `getConstructors()` 、 `getMethods()` 三个方法返回所有 public 修饰的属性和方法（包括从父类继承的）；而 `getDeclaredFields()` 、 `getDeclaredConstructors()` 、 `getDeclaredMethods()` 则仅返回本类定义的属性和方法，但不限于 `public`。</content:encoded></item><item><title>IDEA 编译大项目报错问题解决</title><link>https://igaozp.me/blog/idea-java-compile-error-fix/</link><guid isPermaLink="true">https://igaozp.me/blog/idea-java-compile-error-fix/</guid><pubDate>Wed, 07 Jul 2021 00:00:00 GMT</pubDate><content:encoded>最近使用 IDEA 启动 Java 项目时发现在编译阶段出现如下的错误：

- `java.lang.OutOfMemoryError: GC overhead limit exceeded`
- `java.lang.InternalError`
- `java.lang.OutOfMemoryError: Java heap space`

出现上述的错误，通常在项目工程比较大的时候才会出现。此时只需要调整 IDEA 的设置，
将 **File | Settings | Build, Execution, Deployment | Compiler** 中的
**Shared build process heap size(Mbytes)** 配置的阈值适当提高，问题通常便会解决。</content:encoded></item><item><title>使用 JConsole 简单排查死锁线程</title><link>https://igaozp.me/blog/jconsole-sample-usage/</link><guid isPermaLink="true">https://igaozp.me/blog/jconsole-sample-usage/</guid><pubDate>Sun, 27 Jun 2021 00:00:00 GMT</pubDate><content:encoded>最近在工作中遇到了 HTTP 请求长时间未响应的情况，初步判断是代码问题导致产生死锁，遂简单学习了下 JDK 中自带的 JConsole 工具。
以 Windows 为例，工具存放在 JDK 目录下的 bin/jconsole.exe，双击运行即可。

使用步骤：

1. 新建连接，选择本地或远程的应用进程，并连接。
2. 切换至线程选项卡。
3. 点击左下方的「检测死锁」按钮。如果存在死锁进程，会展示对应的线程信息，同时展示线程死锁相关的堆栈信息，排查堆栈信息即可找到问题根源。</content:encoded></item><item><title>《关于使用 create-react-app 初始化 React 项目屡遭失败愤而投入 Vite 怀抱的那件事》</title><link>https://igaozp.me/blog/vite-init-react-project/</link><guid isPermaLink="true">https://igaozp.me/blog/vite-init-react-project/</guid><pubDate>Sun, 14 Mar 2021 00:00:00 GMT</pubDate><content:encoded>最近在学习 React 时，最开始使用官方的 create-react-app 工具进行项目的初始化，完成工具的安装后进行项目初始化时遇到了一些问题。

&lt;!--more--&gt;

使用 crp 创建项目时遇到了关于 node-gyp 的错误，node-gyp 是用来构建跟平台相关的原生模块，在 Windows 下需要额外安装 windows-build-tools 之类
的工具，安装这些工具需要耗费大量的磁盘空间，遂放弃在 Windows 下使用 crp，进而在 WSL 环境中使用。

在 Linux 环境中使用 crp 也需要使用 node-gyp 来进行构建原生模块，只不过是使用的 make 工具，所以安装 make 相关的依赖。本以为能够继续顺利地使用 crp 完成项目地初始化，
结果遇到了 `gyp ERR! stack Error: `make` failed with exit code: 2` 及 `gyp: Call to &apos;pkg-config pixman-1 --libs&apos; returned exit status 127 while in binding.gyp. while trying to load binding.gyp` 之类的
问题，在查找了几种解决方案并尝试之后均无果而终。

决定放弃使用 crp 初始化项目寻找其他的替代方案，突然想到 Vite 最近已经更新 2.0 了，看了下相关文档发现除支持生成 Vue 项目外还支持 React 项目。

全局安装 Vite 工具

```
npm install -g vite
```

使用相关的模板初始化项目，这里使用 React 项目的模板，basic 可替换为任意的项目名称

```
npm init vite basic --template react
```

使用 Vite 顺利完成 React 项目的初始化，但运行项目时遇到了 esbuild 执行文件丢失的情况导致无法启动项目，可能是使用 npm 镜像下载依赖导致的，
可以使用 node 手动执行 `\node_modules\esbuild` 中的 install.js 来下载 esbuild 的可执行文件，随后项目可正常启动。

完。</content:encoded></item><item><title>npm 修改 registry 配置提高下载速度</title><link>https://igaozp.me/blog/npm-config-registry/</link><guid isPermaLink="true">https://igaozp.me/blog/npm-config-registry/</guid><pubDate>Sat, 13 Mar 2021 00:00:00 GMT</pubDate><content:encoded>使用如下命令修改全局配置，使用淘宝镜像加速 npm 的下载速度

```bash
npm config set registry https://registry.npm.taobao.org
```</content:encoded></item><item><title>[译] 分布式系统中的模式：High-Water Mark</title><link>https://igaozp.me/blog/high-water-mark/</link><guid isPermaLink="true">https://igaozp.me/blog/high-water-mark/</guid><pubDate>Sat, 06 Mar 2021 03:01:10 GMT</pubDate><content:encoded>该文章翻译自 Martin Fowler 官方网站上的系列文章，原文链接 [Patterns of Distributed Systems](https://martinfowler.com/articles/patterns-of-distributed-systems/)，
此系列文章以宏观的视角系统地讲述了分布式系统中会遇到的一些问题及其解决方案，并将其归纳总结出相关的通用「模式」，这些「模式」对我们普通开发者了解学习分布式系统有很好的指导意义。

本篇文章原作者介绍了分布式系统中的 High-Water Mark 相关的概念及一些场景，
原文链接 [High-Water Mark](https://martinfowler.com/articles/patterns-of-distributed-systems/high-watermark.html) 。

注：高水位线（High-Water Mark）通常是指投资基金或账户达到的最高价值峰值。
这个词通常用于基金经理的报酬，而基金经理的报酬是基于业绩的。高水位线确保基金经理不会因为业绩不佳而获得大笔报酬。

&lt;!--more--&gt;

## 正文

&gt; 在预写式日志（Write Ahead Log）中，索引展现最新的成功复制的数据。

预写式日志（WAL）模式用于恢复服务器崩溃和重启后的服务状态。但在服务器故障时，预写日志还不足以提供可用性。
如果单个服务器发生故障，那么在服务器重启之前，客户端将无法运转。为了获得一个更可用的系统，我们可以将日志复制到多台服务器上。
使用 Leader and Followers 模式，Leader 将其所有的日志条目复制到 Followers 上。
现在如果 Leader 崩溃，可以选出一个新的 Leader，客户端大多可以继续像以前一样使用集群服务。但还是有几个场景会出现问题：

- Leader 在给任意一个 Follower 发送日志条目之前崩溃。
- Leader 在向小部分 Follower 发送日志条目之后崩溃，但无法继续将日志发送给大部分 Follower。

在这些出现错误的场景下，有些 Follower 的日志中可能缺少条目，有些 Follower 的日志条目可能比其他 Follower 多。
所以，对于每个 Follower 来说，知道哪部分的日志是安全的，并可以提供给客户端服务就变得非常重要。

### 解决方案

高水位标记（high-water mark）是日志文件中的一个索引，它记录了已知已成功复制到一定数量（符合 Quorum 机制的数量）的 Follower 的最后一个日志条目。
在复制过程中，Leader 也会将高水位标记传递给它的 Followers。群集中的所有服务器应仅传输或更新低于高水位线标记的数据给客户端。

![highwatermark 序列图](/images/high-water-mark/highwatermark-sequence.png)

对于每个日志条目，Leader 将其附加到其本地的预写日志中，然后将其发送给所有的 Follower。

```java
  private Long appendAndReplicate(byte[] data) {
      Long lastLogEntryIndex = appendToLocalLog(data);
      logger.info(&quot;Replicating log entries from index &quot; + lastLogEntryIndex);
      replicateOnFollowers(lastLogEntryIndex);
      return lastLogEntryIndex;
  }

  private void replicateOnFollowers(Long entryAtIndex) {
      for (final FollowerHandler follower : followers) {
          replicateOn(follower, entryAtIndex); //send replication requests to followers
      }
  }
```

Follower 处理复制请求，并将日志条目追加到其本地日志中。在成功追加日志条目后，它们会将最新日志条目的索引响应给 Leader。
响应中还包括服务器当前的 [Generation Clock](https://martinfowler.com/articles/patterns-of-distributed-systems/generation.html)。

```java
  private ReplicationResponse handleReplicationRequest(ReplicationRequest replicationRequest) {
      List&lt;WALEntry&gt; entries = replicationRequest.getEntries();
      for (WALEntry entry : entries) {
          logger.info(&quot;Applying log entry &quot; + entry.getEntryId() + &quot; in &quot; + serverId());
          wal.writeEntry(entry);
      }
      return new ReplicationResponse(SUCCEEDED, serverId(), replicationState.getGeneration(), wal.getLastLogEntryId());
  }
```

Leader 在收到响应时，会跟踪复制在每个服务器上的日志索引。

```java
  recordReplicationConfirmedFor(response.getServerId(), response.getReplicatedLogIndex());
  long logIndexAtQuorum = computeHighwaterMark(logIndexesAtAllServers(), config.numberOfServers());
  if (logIndexAtQuorum &gt; replicationState.getHighWaterMark()) {
      var previousHighWaterMark = replicationState.getHighWaterMark();
      applyLogAt(previousHighWaterMark, logIndexAtQuorum);
      replicationState.setHighWaterMark(logIndexAtQuorum);
  }
```

高水位线可以通过查看 Follower 的日志索引及 Leader 自身的日志来计算，同时选出在多数服务器中都有的日志索引。

```java
  Long computeHighwaterMark(List&lt;Long&gt; serverLogIndexes, int noOfServers) {
      serverLogIndexes.sort(Long::compareTo);
      return serverLogIndexes.get(noOfServers / 2);
  }
```

在 Leader 选举中会出现一个微妙的问题。在任何服务器向客户端发送数据之前，我们必须确保集群中的所有服务器都有一个最新的日志记录。
这个微妙的问题就是，在向所有的 Follower 传播高水位线之前现有的 Leader 失效。RAFT 的做法是在 Leader 选举成功后，在 Leader 的日志中附加一个 no-op 条目，并且只有在其 Follower 确认这一点后才能为客户端提供服务。在 ZAB 中，新的 Leader 在尝试开始为客户提供服务之前，将其所有条目推送给所有的 Follower。

Leader 将高水位线作为常规心跳检测的一部分或作为单独的请求传播给 Followers。然后，Follower 据此设定他们的高水位线。

任何客户端都只能读取高水位线前日志条目。超过高水位线的日志条目对客户端来说是不可见的，因为没有确认这些条目是否被复制，
所以如果 Leader 失效，而其他服务器被选为 Leader，这些日志条目可能无法使用。

```java
  public WALEntry readEntry(long index) {
      if (index &gt; replicationState.getHighWaterMark()) {
          throw new IllegalArgumentException(&quot;Log entry not available&quot;);
      }
      return wal.readAt(index);
  }
```

### 日志截断

当一台服务器在崩溃/重启后加入集群时，其日志中总有可能出现一些冲突的条目。因此，每当一台服务器加入集群时，
它都会与集群的 Leader 进行检查，以了解日志中哪些条目可能是冲突的。然后，它将日志截断到与 Leader 相匹配的条目，
然后用随后的条目更新日志，以确保它的日志与集群的其他条目相匹配。

考虑下面的例子。客户端发送请求在日志中添加四个条目。Leader 成功复制了三个条目，但在向自己的日志中添加 Entry4 之后失效。
其中一个 Follower 被选为新的 Leader，并从客户端接收到了更多的条目。当失效的 Leader 再次加入集群时，它的 Entry4 是冲突的。所以它需要截断自己的日志，直到 Entry3，然后添加 Entry5，使日志与集群的其他部分相匹配。

Leader 失效：
![Leader 失效](/images/high-water-mark/leaderfailure.png)

新 Leader：
![新 Leader](/images/high-water-mark/newleader.png)

日志截断：
![截断](/images/high-water-mark/truncation.png)

任何在暂停后重新启动或重新加入集群的服务器都会找到新的 Leader。然后，它明确询问当前的高水位线，将自己的日志截断到高水位线，
然后从 Leader 那里得到高水位线以外的所有条目。RAFT 等复制算法有办法通过检查自己日志中的日志条目与请求中的日志条目，
找出冲突的条目，将日志索引相同但生成时钟较低的条目删除。

```java
  private void maybeTruncate(ReplicationRequest replicationRequest) throws IOException {
      if (replicationRequest.hasNoEntries() || wal.isEmpty()) {
          return;
      }

      List&lt;WALEntry&gt; entries = replicationRequest.getEntries();
      for (WALEntry entry : entries) {
          if (wal.getLastLogEntryId() &gt;= entry.getEntryId()) {
              if (entry.getGeneration() == wal.readAt(entry.getEntryId()).getGeneration()) {
                  continue;
              }
              wal.truncate(entry.getEntryId());
          }
      }
  }
```

支持日志截断的一个简单的实现是保存一个日志索引和文件位置的 MAP。然后可以在给定的索引处对日志进行截断，如下所示：

```java
  public void truncate(Long logIndex) throws IOException {
      var filePosition = entryOffsets.get(logIndex);
      if (filePosition == null) throw new IllegalArgumentException(&quot;No file position available for logIndex=&quot; + logIndex);

      fileChannel.truncate(filePosition);
  }
```

### 实际的应用示例

- 所有的共识算法都使用高水位标记的概念来知道何时应用所提出的状态突变，例如在 RAFT 共识算法中，高水位标记被称为 &quot;CommitIndex&quot;。
- 在 Kafka 复制协议中，维护了一个单独的索引，称为 &quot;high-water mark&quot;。消费者只能看到高水位标记之前的条目。
- Apache BookKeeper 有一个「[最后一次添加确认](https://bookkeeper.apache.org/archives/docs/r4.4.0/bookkeeperProtocol.html)」的概念，即在 Quorum 的法定人数上成功复制的条目。</content:encoded></item><item><title>寻找「隐藏」RSS 订阅地址的方法</title><link>https://igaozp.me/blog/found-rss-feed/</link><guid isPermaLink="true">https://igaozp.me/blog/found-rss-feed/</guid><description>寻找「隐藏」RSS 订阅地址的方法</description><pubDate>Sat, 09 Jan 2021 00:00:00 GMT</pubDate><content:encoded>很多博客站点提供了 RSS 订阅的方式，但博客作者并没有显著地标明，或者作者自身并不知道有 RSS 订阅。
一些静态网站的生成引擎或者主题会自动生成 RSS 订阅，所以我们只需要知道这些订阅源的路由位置即可。

&lt;!--more--&gt;

下面这几个路由是我操作总结的几个 RSS 订阅源常用位置

```
/index.xml
/feed
/feed.xml
/rss.xml
/atom.xml
```

使用上面提供的路由地址，可找到大部分站点的 RSS 订阅源，前提是站点提供了 RSS 订阅。

当然更简单的方式是安装 [RSSHub Radar](https://chromewebstore.google.com/detail/rsshub-radar/kefjpfngnndepjbopdmoebkipbgkggaa) 插件，就会自动检测当前页面的订阅源啦。</content:encoded></item><item><title>《编程之道》 (Tao of Programming) 十三则</title><link>https://igaozp.me/blog/tao-of-programming/</link><guid isPermaLink="true">https://igaozp.me/blog/tao-of-programming/</guid><pubDate>Mon, 30 Nov 2020 00:00:00 GMT</pubDate><content:encoded>《编程之道》 (Tao of Programming) 十三则「精选」

## 其一

The Grand Master Turing once dreamed that he was a machine. When he awoke, he exclaimed:
&quot;I don&apos;t know whether I am Turing dreaming that I am a machine, or a machine dreaming that I am Turing!&quot;

昔日，图灵大师梦见自己变成了一台机器，他醒来后惊呼：
「我不确定我是图灵梦见自己变成了机器，还是我是机器梦见自己变成了图灵！」

## 其二

Thus spake the Master Programmer:
&quot;When a program is being tested, it is too late to make design changes.&quot;

编程大师如是说：「待程序进入测试阶段，任何设计上的修改都太晚了。」

## 其三

There once was a Master Programmer who wrote unstructured programs. A novice programmer, seeking to imitate him, also began to write unstructured programs. When the novice asked the Master to evaluate his progress, the Master criticized him for writing unstructured programs, saying, &quot;What is appropriate for the Master is not appropriate for the novice. You must understand Tao before transcending structure.&quot;

从前，有位编程大师擅长编写没有结构的程序。他的弟子模仿他，也开始编写没有结构的程序。当弟子请大师评价他的程序，大师批评他：「适合大师的不一定适合初学者。在超越结构之前，你必须先领悟道。」

## 其四

Thus spake the Master Programmer:
&quot;A well-written program is its own Heaven; a poorly-written program is its own Hell.&quot;

编程大师如是说：「优秀的程仿佛置身天堂，差劲的程序如同自取灭亡。」

## 其五

A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little nor too much. Neither needless loops nor useless variables; neither lack of structure nor overwhelming rigidity.

程序应当轻巧灵活，内部的子过程彼此连接，串若珍珠。程序的精神和目的，当一以贯之。代码量要恰到好处，减一行则少，增一行则多。不存在多余的循环或变量。既不失整体结构，也不至僵化死板。

A program should follow the &quot;Law of Least Astonishment&quot;. What is this law? It is simply that the program should always respond to the users in the way that least astonishes them.

程序应当遵循「最少惊讶原则」。这个原则很简单：程序返回的结果永远符合用户的预期。

A program, no matter how complex, should act as a single unit. The program should be directed by the logic within rather than by outward appearances.

无论程序多么复杂，都可当作一个独立单元来运行。程序应靠内在逻辑指导，而不为前端形式左右。

If the program fails in these requirements, it will be in a state of disorder and confusion. The only way to correct this is to rewrite the program.

如果程序不满足以上要求，它将陷入无序与混乱。唯一的修复办法就是重写程序。

## 其六

Only a fool expects rational behavior from his fellow humans. Why do you expect it from a machine that humans have constructed? Computers simulate determinism; only Tao is perfect.The rules of programming are transitory; only Tao is eternal. Therefore, you must contemplate Tao before you receive Enlightenment.

唯有傻子才会期望世人举止理性。你又如何期望世人建造的机器能有理性的行为呢？计算机只能模拟决定论；世间唯有道是完美的。编程无常法，唯道恒久远。在获得启示之前，你必须深思道。

## 其七

What I follow is Tao -- beyond all techniques! When I first began to program, I would see before me the whole problem in one mass. After three years, I no longer saw this mass. Instead, I used subroutines. But now I see nothing. My whole being exists in a formless void. My senses are idle. My spirit, free to work without a plan, follows its own instinct. In short, my program writes itself. True, sometimes there are difficult problems. I see them coming, I slow down, I watch silently. Then I change a single line of code and the difficulties vanish like puffs of idle smoke. I then compile the program. I sit still and let the joy of the work fill my being. I close my eyes for a moment and then log off.

我追求的是道——超越所有技术的道！我刚开始编程时，眼前的问题堆积成团、杂乱如麻。三年后，我再也没见过这样的问题，因为我学会了子程序。如今，我什么也看不见了。我的存在变得虚无，我感受不到任何事物。我的精神不受约束，全凭本能自由工作。换句话说，我写程序时，代码是一行行自己显现的。当然，有时我会碰到一些棘手的问题。我看着这些问题出现，我慢下来，静静地看着。然后我改动一行代码，问题像轻烟般自己消失了。我开始编译程序。我静静地坐着，感受着快乐盈满身心。我闭目冥想片刻，然后退出系统。

## 其八

Software rots if not used.

软件没人用，神奇也会化为腐朽。

## 其九

Does a good farmer neglect a crop he has planted?

好的农民会随意对待自己种的庄稼吗？

Does a good teacher overlook even the most humble student?

好的老师会忽视自己最谦虚的学生吗？

Does a good father allow a single child to starve?

好的父亲会舍得让任何一个孩子挨饿吗？

Does a good programmer refuse to maintain his code?

好的程序员会拒绝维护自己的代码吗？

## 其十

Why are programmers non-productive? Because their time is wasted in meetings.

为什么程序员没有工作效率？因为要开的会议没完没了。

Why are programmers rebellious? Because the management interferes too much.

为什么程序员不听指挥？因为管理层管得太多太深。

Why are the programmers resigning one by one? Because they are burnt out.

为什么程序员一个接一个辞职？因为他们累得心力交瘁。

Having worked for poor management, they no longer value their jobs.

在管理不善的部门工作，他们再也看不到自己工作的价值。

## 其十一

In the East there is a shark which is larger than all other fish. It changes into a bird whose wings are like clouds filling the sky.

东方有鱼，其名为鲲。鲲之大，绵绵若千里之远；化而为鸟，其名为鹏。鹏之翼，皇皇若垂天之云。

When this bird moves across the land, it brings a message from Corporate Headquarters. This message drops into the midst of the programmers, like a seagull making its mark upon the beach. Then the bird mounts on the wind and, with the blue sky at its back, returns home.

这只大鸟从东边飞来，带来了企业总部的消息。如海鸥掠过沙滩，大鸟将消息撒播在了程序员之间。随后它背负青天，扶摇直上，缓缓归去。

The novice programmer stares in wonder at the bird, for he understands it not. The average programmer dreads the coming of the bird, for he fears its message. The Master Programmer continues to work at his terminal, unaware that the bird has come and gone.

初级程序员惊奇地盯着大鸟，因为他不明白这意味着什么。中级程序员害怕大鸟的到来，因为它带来了总部的消息。而编程大师一直在电脑前工作，完全没注意到大鸟来过。

## 其十二

Thus spake the Master Programmer:
&quot;Without the wind, the grass does not move. Without software hardware is useless.&quot;

编程大师如是说：
「没有风，草就不会动；没有软件，硬件就没用。」

## 其十三

Hardware met Software on the road to Changtse. Software said: &quot;You are Yin and I am Yang. If we travel together, we will become famous and earn vast sums of money.&quot; And so they set forth together, thinking to conquer the world.

在通往长江的路上，硬件和软件相遇了。软件说：「你是阴，我是阳。我们结伴而行，日后一定会出名、赚大钱。」于是他们一同出发，想要征服世界。

Presently, they met Firmware, who was dressed in tattered rags and hobbled along propped on a thorny stick. Firmware said to them: &quot;The Tao lies beyond Yin and Yang. It is silent and still as a pool of water. It does not seek fame; therefore, nobody knows its presence. It does not seek fortune, for it is complete within itself. It exists beyond space and time.&quot;

不久，他们碰见了固件。固件衣衫褴褛，拄着一根拐杖，步履蹒跚地走着。固件对他们说：「道在阴阳之外。它似一潭止水，寂静无声。它不求闻达，所以没人知道它在哪儿。它不求财富，因为它自身已完整无缺。它独立于时空存在。」</content:encoded></item><item><title>[译] 分布式系统中的模式</title><link>https://igaozp.me/blog/patterns-of-distributed-systems/</link><guid isPermaLink="true">https://igaozp.me/blog/patterns-of-distributed-systems/</guid><pubDate>Mon, 21 Sep 2020 02:12:10 GMT</pubDate><content:encoded>该文章翻译自 Martin Fowler 官方网站上的系列文章，原文链接 [Patterns of Distributed Systems](https://martinfowler.com/articles/patterns-of-distributed-systems/)，此系列文章以宏观的视角系统地讲述了分布式系统中会遇到的一些问题及其解决方案，并将其归纳总结出相关的通用「模式」，这些「模式」对我们普通开发者了解学习分布式系统有很好的指导意义。

## 正文

分布式系统为编程提出了一系列特殊的挑战。分布式系统通常要求我们持有多个数据副本，同时需要确保这些数据的同步。然而我们并不能依赖于这些服务节点能够可靠地工作，网络延迟很容易导致节点数据出现不一致。尽管如此，许多公司组织还是依靠一系列核心分布式软件来解决数据存储、消息传递、系统管理和（数据）计算能力。这些系统面临着共同的问题，它们可以用类似的方案来解决这些问题。本文将归纳这些解决方案并将其发展为**模式**，有了这些模式，我们就可以建立起如何更好地理解、交流和讲分布式系统设计的理解。

&lt;!--more--&gt;

## 这是关于什么？

在过去的几个月里，作者一直在 ThoughtWorks 举办分布式系统的研讨会。在举办研讨会时，面临的一个关键挑战是如何将分布式系统的理论关联映射到 **Kafka** 或 **Cassandra** 等开源的代码中，同时保持讨论的通用性，以涵盖广泛的解决方案。「模式」的概念提供了一个很好的思路。

「模式」结构就其本质而言，允许我们专注于一个特定的问题，并清楚知道为什么需要一个特定的解决方案来解决它。然后，我们可以给出一个代码结构来描述解决方案，这个结构要足够具体，可以描述实际的解决方案，但同时又要足够通用，可以适应足够广的应用场景。模式技术还可以让我们将各种模式连接在一起，构建一个完整的系统。这就为讨论分布式系统的实现提供了一个很好的术语。

下面是第一批在主流开源分布式系统中观察到的模式。希望这套模式能对所有的开发者有所帮助。

### 分布式系统 - 实施的角度

今天的企业架构中充满了各种平台和框架，这些平台和框架都是分布式的。如果我们概览一下今天典型的企业架构中使用的框架和平台，它看起来像以下内容：

| 框架及平台类型      | 示例                                       |
| ------------------- | ------------------------------------------ |
| 数据库              | Cassandra, HBase, Riak                     |
| 消息代理            | Kafka, Pulsar                              |
| 基础设施            | Kubernetes, Mesos, Zookeeper, etcd, Consul |
| 内存数据库/计算网格 | Hazelcast, Pivotal Gemfire                 |
| 状态化微服务        | Akka Actors, Axon                          |
| 文件系统            | HDFS, Ceph                                 |

本质上讲这些系统都是「分布式」的。什么叫系统的分布式？有两个方面：

1. 它们运行在多台服务器上。一个集群中服务器的数量可以从三台服务器到几千台服务器不等。
2. 它们管理数据。所以这些系统本质上是「有状态」的系统。

当多个服务器参与存储数据时，有几种场景会出问题。上述系统都需要解决这些问题。解决这些问题时会有一些反复出现的解决方案。当了解了这些解决方案的一般形式，会更有助于理解这些系统的更广泛的实现，当需要构建新的系统时，就可以起到很好的指导作用。

#### 模式

模式是由 Christopher Alexander 提出的概念，在软件界被广泛接受，是用来记录用于构建软件系统的设计结构。模式提供了一种结构化的方式来看待一个问题空间，并且这种方式被广泛认可。使用模式的一个有趣的方式是以模式序列或模式语言的形式将几个模式连接在一起，这为实现一个 &quot;整体 &quot;或完整的系统提供了一些指导。把分布式系统看成一系列模式，是一个深入了解其实现的有用方法。

## 问题及通用的解决方案

当数据存储在多个服务器上时，有这么几种场景可能会导致出错。

### 进程崩溃

进程随时可能崩溃。无论是由于硬件故障还是软件故障。进程崩溃的方式有很多：

1. 它可以被系统管理员关闭进行常规维护。
2. 它可以在做一些文件 IO 操作时被杀死，因为磁盘已满或者异常没有得到妥善处理。
3. 在云环境中它可能会更加棘手，因为一些其他的随机事件可能会使服务器宕机。

我们的最低要求是，如果进程负责存储数据，那么它们从设计上必须保证存储在服务器上数据的持久性。即使一个进程突然崩溃，也应当保存它已经通知用户成功存储的所有数据。根据访问模式的不同，不同的存储引擎有不同的存储结构，从简单的 **Hash Map** 到复杂的图存储。由于将数据刷新到磁盘上是最耗时的操作之一，不可能每一次对数据的插入或更新都刷新到磁盘上。所以大多数数据库的内存存储结构只定期向磁盘刷新。这就带来了一个风险，如果进程突然崩溃，未向磁盘刷新的数据都会丢失。

一种叫做 **Write-Ahead Log** 的技术被用来解决这种问题。服务器将每个状态变化作为一个命令存储在硬盘上的一个 append-only 的文件中。文件附加操作一般是一个非常快的操作，所以可以在不影响性能的情况下进行。单一的 log 文件、按顺序追加的特性，使之用来存储每次更新。在服务器启动时，可以重新回放日志，再次建立起服务崩溃前的内存状态。

这就提供了一个持久化的保证。即使服务器突然崩溃，然后重新启动，数据也不会丢失。但是在服务器恢复之前，客户端将无法从服务器获取或存储任何数据。所以在服务器故障的情况下，我们缺乏可用性。

一个显而易见的解决方案是将数据存储在多个服务器上。因此，我们可以在多个服务器上复制 **Write-Ahead Log** 日志。

当涉及到多个服务器时，需要考虑的故障场景就更多了。

### 网络延迟

在 TCP/IP 协议栈中，在网络上传输消息时造成的延迟并没有上限。它可以根据网络上的负载而变化。例如，一条 1Gbps 的网络链路可能会被一个触发的大数据量的任务所淹没，填满网络缓冲区，会导致一些消息到达服务器的延迟不可预估。

这里有两个问题需要解决：

1. 某台服务器不能无限期地等待，需要知道另一台服务器是否崩溃了。
2. 不应该有两组服务器（提供服务），每组服务器都认为另一组服务器已经失效，因此继续为不同的客户端服务。这就是所谓的**脑裂**问题。

为了解决第一个问题，每台服务器都会定期向其他服务器发送一个心跳（HeartBeat）消息。如果未收到心跳，就认为发送心跳的服务器是崩溃了。心跳的间隔时间要足够小，以保证不需要花费很多时间来检测服务器的故障。正如我们将在下面看到的那样，在最坏的情况下，服务器可能会启动并运行，考虑到即使服务器出现故障，集群作为一个组仍可以继续提供服务。这样可以确保提供给客户端的服务不会中断。

第二个问题是脑裂。在脑裂的情况下，如果两组服务器独立接受（数据）更新，不同的客户端获取并设置了不同的数据，就算脑裂问题解决，也无法自动解决（不同客户端之间数据的）冲突。

要想解决脑裂问题，我们必须保证两组服务器，即使相互之间是断开的，也不能独立进行（数据）处理。为了保证这一点，服务器所做的每一个动作，只有在大多数服务器能够确认该动作时，才算操作成功。如果不能获得多数服务器的确认，就不能提供所需的服务，可能有部分客户端无法接收服务（响应），但集群中的服务器（数据）将始终处于一致的状态。达到多数服务器确认的数量称为 Quorum 机制。如何决定 Quorum 机制生效的数量？那是根据集群所能容忍的故障数来决定的。所以，如果我们有一个5个节点的集群，我们需要的机制生效数量是3个。一般来说，如果我们要容忍f次故障，我们需要的集群大小为2f+1。

Quorum 机制可以确保我们有足够的数据副本在一部分服务器故障中保存下来。但这还不足以给客户端提供强大的一致性保证。假设一个客户端发起一个写操作，但写操作只在一台服务器上成功。其他服务器仍然有旧的值。当客户端读取值时，如果有最新值的服务器可用，它可能会得到最新的值。但是，如果当客户端开始读取值时，拥有最新值的服务器不可用，它很可能得到一个旧值。为了避免这种情况，需要跟踪 Quorum 是否同意某项操作，并且只向客户端发送保证在所有服务器上可用的值。在这种情况下使用了 Leader 和 Followers 机制。其中一台服务器被选为 Leader，其他服务器作为 Follower。Leader 控制和协调在 Followers 上的数据复制。Leader 现在需要决定，哪些变化应该对客户端可见。High-Water Mark 用于跟踪 write ahead log 中已知已成功复制到 Quorum 的跟随者的条目。所有到达 high-water mark 标记条目都会对客户端可见。Leader 也会将 high-water mark 传播给 Followers。因此，如果 Leader 失败了，而其中一个 Follower 成为新的 Leader，那么客户端看到的内容就不会有任何不一致。

### 进程暂停

但这还不是全部，即使有 Quorum 和 Leader Followers 机制，也有一个棘手的问题需要解决。Leader 进程可以任意暂停。一个进程可以暂停的原因有很多。对于支持垃圾回收的语言来说，可能会有一个很长的垃圾回收暂停。一个有长时间垃圾回收暂停的 Leader，可能与 Followers 断开连接，在暂停结束后会继续向跟随者发送消息。同时，由于追随者没有收到 Leader 的心跳，他们可能已经选出了新的 Leader，并接受了客户端的（数据）更新。如果照原样处理旧 Leader 的请求，可能会覆盖一些（数据）更新。所以我们需要一个机制来检测来自过时的 Leader 的请求。Generation Clock用于标记和检测来自过时 Leader 的请求。通常是（维护）一个单调递增的数字。

### 时钟不同步及事件排序

从新的 Leader 消息中检测出老的 Leader 消息的问题，是维持消息排序的问题。看起来我们可以使用系统时间戳来对一组消息进行排序，但是我们不能这样做。不能使用系统时钟的主要原因是，跨服务器的系统时钟不能保证同步。计算机中的时钟是由石英晶体管理的，根据晶体的振荡来测量时间。

这种机制很容易出错，因为晶体的振荡速度可能快一些，也可能慢一些，所以不同的服务器的时间可能大相径庭。一组服务器上的时钟是由一个叫做 NTP 的服务来同步的。这个服务会定期检查一组服务器的全局时间，并据此调整计算机时钟。

因为这发生在网络上的通信，而网络延迟可能会像上面的章节所讨论的那样有所不同，时钟同步可能会因为网络问题而产生延迟。这可能会导致服务器时钟相互漂移，在 NTP 同步发生后，甚至会在时间上后移。由于计算机时钟的这些问题，一般不使用一天中的具体时间来排序事件。取而代之的是一种叫做 Lamport 时间戳的简单技术。Generation Clock 就是一个例子。

## 整合起来 - 分布式系统示例

我们可以看到，理解这些模式，有助于我们从基础上建立一个完整的系统。我们以共识的实现为例。分布式共识是分布式系统实现的一个特例，它能提供最强的一致性保证。在流行的企业系统中常见的例子有，Zookeeper，etcd 和 Consul。它们实现了 zab 和 Raft 等共识算法，提供了复制和强一致性。还有其他流行的算法来实现共识，Google 的 Chubby 锁定服务中使用的 Paxos，Viewstamped Replication 和 Virtual Synchrony。简单来说，共识是指一组服务器就存储的数据、数据的存储顺序以及什么时候让这些数据对客户端可见达成一致。

### 实现共识的模式序列

共识实现采用状态机复制来实现容错。在状态机复制中，存储服务，如键值存储，被复制在所有服务器上，用户的输入在每个服务器上以相同的顺序执行。实现这一目标的关键实现技术是在所有服务器上复制 Write-Ahead Log，以拥有一个 &quot;Replicated Wal&quot;。

我们可以把实现 Replicated Wal 的模式组合如下。

为了提供耐久性保证，使用 Write-Ahead Log。使用 Segmented Log 将 Write Ahead Log 分成多个片段。这有助于日志清理，而日志清理由 Low-Water Mark 处理。通过在多个服务器上复制 Write-Ahead Log 来提供容错。服务器之间的复制是通过使用 Leader 和 Followers 来管理的。Quorum 用于更新 High-Water Mark，以决定哪些值对客户端可见。通过使用 [Singular Update Queue](https://martinfowler.com/articles/patterns-of-distributed-systems/singular-update-queue.html)，所有的请求都以严格的顺序进行处理。在使用 [Single Socket Channel](https://martinfowler.com/articles/patterns-of-distributed-systems/single-socket-channel.html) 从 Leader 向 Followers 发送请求时，顺序是保持的。为了优化 [Single Socket Channel](https://martinfowler.com/articles/patterns-of-distributed-systems/single-socket-channel.html) 的吞吐量和延迟，使用了 [Request Pipeline](https://martinfowler.com/articles/patterns-of-distributed-systems/request-pipeline.html)。Followers 通过从 Leader 收到的心跳检测了解 Leader 的可用性。如果 Leader 因为网络分区而暂时与集群断开连接，则会通过使用 [Generation Clock](https://martinfowler.com/articles/patterns-of-distributed-systems/generation.html) 来检测。

![Replicated WAL](/images/patterns-of-distributed-systems/Replicated-WAL.png)

这样，从一般形式上理解问题以及其通用的解决方案，有助于理解构建一个完整的系统。

## 下一步

分布式系统是一个庞大的话题。这里涉及的一些模式只是一小部分，涵盖了不同的类别，以展示模式方法如何帮助理解和设计分布式系统。我将不断增加模式的内容，大致包括以下几类在任何分布式系统中解决的问题。

1. 集群成员和故障检测
2. 分区
3. 复制和一致性
4. 存储
5. 处理</content:encoded></item><item><title>GitHub Star 仓库管理利器 - stargazed</title><link>https://igaozp.me/blog/github-starred-repositories-management-tools-stargazed/</link><guid isPermaLink="true">https://igaozp.me/blog/github-starred-repositories-management-tools-stargazed/</guid><pubDate>Wed, 09 Sep 2020 00:00:00 GMT</pubDate><content:encoded>个人在 GitHub 收藏了非常多的开源项目仓库，到目前为止收藏的项目大概 3000+，这些项目检索起来非常繁琐，导致一些收藏的项目由于检索效率的问题没有利用起来。

所幸无意间发现了一个名为 [stargazed](https://github.com/abhijithvijayan/stargazed) 工具，可以将 GitHub 用户收藏过的项目，进行分类并输出到 Markdown 文件中。
stargazed 会将项目按语言分类并生成目录，方便跳转查看不同编程语言的项目，并且生成的项目条目包括项目名称、项目地址、项目描述、作者等信息，可以非常方便地检索。

### 如何使用

该工具需要用户提供提供一个 GitHub 仓库，用来存储生成的 Markdown 文件。支持通过 GitHub 的 Personal access tokens 来自动创建仓库，同时支持 GitHub Actions 来实现定时更新文件信息。

使用该工具需要安装 Node.js 环境，安装命令：

```bash
npm install -g stargazed
```

使用命令如下，需要将 GITHUB_USERNAME 和 GITHUB-TOKEN 替换为自己的信息，运行成功后会在自动在 awesome-stars 仓库中生成文件。

```bash
stargazed --username GITHUB_USERNAME --token &quot;GITHUB-TOKEN&quot; --repository &quot;awesome-stars&quot;  --sort --workflow
```

上述命令会自动创建 GitHub Actions 配置文件，会定时更新生成的文件，即第一次执行后后续可以无需再次介入。

关于项目的其他信息可以访问 https://github.com/abhijithvijayan/stargazed 进行查看。</content:encoded></item><item><title>使用 GitHub Actions 自动构建部署 Hugo 静态页面</title><link>https://igaozp.me/blog/github-action-deploy-hugo/</link><guid isPermaLink="true">https://igaozp.me/blog/github-action-deploy-hugo/</guid><pubDate>Sun, 30 Aug 2020 00:00:00 GMT</pubDate><content:encoded>`GitHub Actions` 可以实现应用的持续集成、持续部署，可以与 `GitHub Pages` 整合实现静态网页的自动构建和部署。

本博客使用的主题需要使用 `Node.js` 环境完成前端资源的构建，同时需要 `Hugo` 环境完成静态页面的生成，因此需要多语言环境分步完成应用的构建。

在 `GitHub Actions` 配置使用多个构建步骤，前几个步骤用来实现多个语言框架环境的准备，后面的步骤完成应用的构建，最后完成应用的发布。

&lt;!--more--&gt;

下面 `GitHub Actions` 工作流程实现 `Hugo` 页面构建的详细配置文件：

```yml
# This is a basic workflow to help you get started with Actions

name: GitHub Pages

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
    branches:
      - master

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called &quot;build&quot;
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Checkout submodules
        uses: textbook/git-checkout-submodule-action@master
        with:
          remote: true

      - name: Setup Node.js environment
        uses: actions/setup-node@v1.4.4

      - name: Setup Go environment
        uses: actions/setup-go@v2.1.2

      - name: Hugo setup
        uses: peaceiris/actions-hugo@v2.4.13

      - name: Install Dependencies
        run: npm install --dev @babel/cli @babel/core @babel/preset-env browserslist clipboard cssnano postcss-cli postcss-import postcss-mixins postcss-nested postcss-preset-env postcss-url

      - name: Build
        run: |
          hugo -v --gc --minify
          echo &apos;andornot.xyz&apos; &gt; public/CNAME
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_branch: gh-pages
          publish_dir: ./public
          force_orphan: true
```

按照配置，只有当 `master` 分支产生提交时才会触发构建操作，构建完成后将静态网页资源发布到 `gh-pages` 分支。

最初尝试将 `GitHub Actions` 配置文件放在非 `master` 分支中，例如 `blog` 分支，预想当 `blog` 分支提交新内容时触发构建，
并将构建完成的资源发布到 `master` 分支，但尝试了一下似乎配置放在非 `master` 分支无法触发构建操作。

因此最后将构建静态页面需要的文章、主题等资源与 `GitHub Actions` 配置一同放在 `master` 分支，
单独使用并设置 `gh-pages` 分支作为构建完成后资源发布和 `GitHub Pages` 页面访问的分支。

这样便完成了整个 `Hugo` 应用的自动构建和部署，无需在本地环境进行配置环境生成静态页面，只需将新写的内容提交到 `master` 分支即可。</content:encoded></item><item><title>高性能 MySQL 读书笔记 - 架构和历史</title><link>https://igaozp.me/blog/high-performance-mysql-note-01/</link><guid isPermaLink="true">https://igaozp.me/blog/high-performance-mysql-note-01/</guid><pubDate>Wed, 19 Aug 2020 00:00:00 GMT</pubDate><content:encoded>## 并发控制

MySQL 中只要存在多个查询同时修改数据的场景，就会产生并发控制的问题。MySQL 通常在两个层面进行并发控制，一是**服务器**层面，二是**存储引擎**层面。

### 读写锁

并发控制中一类经典的问题就是：**如何避免对同一条数据进行并发地修改和查询操作，以避免产生不可预测的结果。**

这里可以通过构建一个锁系统来解决，这个系统有两种锁构成：共享锁（`shared lock`）和排他锁（`exclusive lock`），也被称为读锁（`read lock`）和写锁（`write lock`）。

通常读锁是共享的，意味着多个客户端可以同时读取同一数据资源，因为读操作不会对数据产生副作用，数据不会产生变化。
而写锁是排他的、非共享的，一个写锁会阻塞其他的写锁和读锁，只有这样才能保证其他的写操作和当前的写操作不会产生冲突，读操作不会读取到正在写入的内容。

这些锁定操作在 MySQL 内部频繁地发生着，MySQL 会自动管理这些锁的锁定和释放，对我们开发者来说是透明的，无需去干预。

&lt;!--more--&gt;

### 锁粒度

提高共享资源并发性的一种方式是让锁定的对象更具有选择性，尽可能对数据精确地锁定，锁定的数据越少，系统的整体并发程度越高。
但是锁的管理也需要消耗系统资源，为了精确地锁定资源系统可能会额外消耗资源去检索定位数据，系统的整体性能也会收到影响。

所以需要一种策略去**平衡锁的开销和数据的安全性**。

#### 表锁

表锁是 MySQL 中最基本的锁策略，且性能开销最小。
用户对表进行写操作时（插入、删除、更新）需要获得写锁锁定整张表，阻塞其他用户对该表的所有读写操作。
当没有写锁时才能获取到读锁，且读锁之间不会相互阻塞。另外，写锁比读锁有着更高的优先级。

#### 行级锁

行级锁能够最大程度地支持并发处理，同时也意味着在锁处理上会带来最大程度的性能开销。与表锁不同行级锁只能由存储引擎来提供实现。

## 事务

说到数据库就不能不提事务，简单来说事务内的语句要么全部执行成功，要么全部失败。

事务必须具备原子性（`atomicity`）、一致性（`consistency`）、隔离性（`isolation`）、持久性（`durability`），即**ACID**。

MySQL 默认采用自动提交事务，即每个查询都被当作一个事务执行并提交。

### 隔离级别

| 隔离级别                       | 脏读可能性 | 不可重复读可能性 | 幻读可能性 |
|----------------------------| ----------- | ----------- | ----------- |
| READ UNCOMMITTED           | YES        | YES            | YES        | NO  |
| READ COMMITTED             | NO         | YES            | YES        | NO  |
| REPEATABLE READ (DEFAULT)  | NO         | NO             | YES        | NO  |
| SERIALIZABLE               | NO         | NO             | NO         | YES |

何为脏读，简单来说指一个事务中访问到了另外一个事务未提交的数据。

又何为幻读，即一个事务读取两次，但得到的记录条数不一致，如果记录的数据不一致，那则称为不可重复读。

### 死锁

死锁是指多个事务在同一资源上相互占用，并且请求锁定对方占用的资源，从而导致恶性循环的现象。

`InnoDB` 目前解决死锁的方法为：**将持有最少行级排他锁的事务进行回滚**。

## 多版本并发控制

多版本并发控制（`MVCC`）是行级锁的一个变种，在很多情况下避免了加锁操作，且写操作只锁定必要的行，`MVCC` 通过保存数据在某个时间点的快照来实现。

`MVCC` 一般由乐观锁（`optimistic lock`）和悲观锁（`pessimistic lock`）来控制。

乐观锁假设不会发生并发冲突，只在提交操作时检查数据是否被其他人修改过，适用于读多写少的应用场景。

悲观锁假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作，每次在读取数据的时候都会上锁。乐观锁大多使用版本号进行控制。</content:encoded></item><item><title>使用 Multimap 对集合按属性进行分组</title><link>https://igaozp.me/blog/using-mulitmap-group-collections/</link><guid isPermaLink="true">https://igaozp.me/blog/using-mulitmap-group-collections/</guid><pubDate>Sat, 25 Jul 2020 00:00:00 GMT</pubDate><content:encoded>## 问题描述

存在一种场景，给定一个数据集合，根据集合元素中的某个字段属性对整个集合数据进行分组。

例如有如下的数据列表（省略非关键信息）：

```json
[
  {
    &quot;id&quot;: 1,
    &quot;createTime&quot;: &quot;2020-07-23 21:01:23&quot;
  },
  {
    &quot;id&quot;: 2,
    &quot;createTime&quot;: &quot;2020-06-23 21:01:23&quot;
  },
  {
    &quot;id&quot;: 3,
    &quot;createTime&quot;: &quot;2020-05-23 21:01:23&quot;
  }
]
```

&lt;!--more--&gt;

想要根据`createTime`创建时间的月份，对数据进行分组，得到类似这样的结构：

```json
{
  &quot;JULY&quot;: [
    {
      &quot;id&quot;: 1,
      &quot;createTime&quot;: &quot;2020-07-23 21:01:23&quot;
    }
  ],
  &quot;JUNE&quot;: [
    {
      &quot;id&quot;: 2,
      &quot;createTime&quot;: &quot;2020-06-23 21:01:23&quot;
    }
  ],
  &quot;MAY&quot;: [
    {
      &quot;id&quot;: 3,
      &quot;createTime&quot;: &quot;2020-05-23 21:01:23&quot;
    }
  ]
}
```

## 解决方案

可以使用 `Guava` 提供的 `Multimap` 方便的进行处理，`Multimap` 可以支持同一个 `key` 放入多个 `value`。 因此针对上述场景，将月份数据作为 `Map` 的 `key`，每个月份对应的数据元素作为 `value`，便可得到对应的分组结构。参考代码如下：

```java
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.time.LocalDateTime;
import java.time.Month;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class MultimapExample {

    static class Data {
        private Integer id;

        private LocalDateTime createTime;

        public Data(Integer id, LocalDateTime createTime) {
            this.id = id;
            this.createTime = createTime;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public void setCreateTime(LocalDateTime createTime) {
            this.createTime = createTime;
        }

        public Integer getId() {
            return id;
        }

        public LocalDateTime getCreateTime() {
            return createTime;
        }

        @Override
        public String toString() {
            return &quot;id: &quot; + id + &quot;, createTime: &quot; + createTime + &quot;\n&quot; ;
        }
    }

    // test with Java 14
    public static void main(String[] args) {
        // generate random data
        Collection&lt;Data&gt; collection = new ArrayList&lt;&gt;(Collections.emptyList());
        for (int i = 0; i &lt; 10; i++) {
            collection.add(new Data(i, LocalDateTime.now().plusMonths(i)));
        }
        System.out.println(collection);

        // Data convert to Month function
        java.util.function.Function&lt;Data, @Nullable Month&gt; function = new Function&lt;&gt;() {
            @Nullable
            @Override
            public Month apply(@Nullable Data data) {
                if (data == null) {
                    return null;
                }
                return data.getCreateTime().getMonth();
            }
        };

        Multimap&lt;Month, Data&gt; result = Multimaps.index(collection, function::apply);
        System.out.println(result.asMap());
    }
}
```

代码中核心的部分，创建了一个 `Function` 用于获取数据元素对应的分组的元素（输入 `Data` 数据获取对应的分组月份），使用 `Multimaps.index` 方法生成分组数据。

如果想要对生成的分组数据进行展示、输出等操作，需要调用 `asMap` 方法将 `Multimap` 转换成朴素的 `Java Map` 进行展示输出。</content:encoded></item><item><title>Java 10 局部变量类型推断</title><link>https://igaozp.me/blog/java-reserved-word-var/</link><guid isPermaLink="true">https://igaozp.me/blog/java-reserved-word-var/</guid><pubDate>Sun, 25 Nov 2018 00:00:00 GMT</pubDate><content:encoded>Java 10 引入了 `var` 保留类型来实现局部变量推断。为了兼容旧版本，`var` 不是关键字，而是一个保留类型，仍然可以使用 `var` 作为为变量和函数名。

&lt;!--more--&gt;

与 JavaScript 不同，使用 var 修饰的变量仍然是静态类型，并不是与 JavaScript 类似的动态类型，变量的类型在编译期已经确定，不能像动态类型语言一样在运行时随意改变变量的类型，所以 `var` 的引入使代码更加简洁易读。

我们可以写出这样的代码：

```java
var i = 0;
var s = &quot;123&quot;;
var list = new ArrayList&lt;Integer&gt;();
var map = Map.of(1, &quot;a&quot;, 2, &quot;b&quot;);
for (var entry: map.entrySet()) {
    System.out.println(entry);
}
for (var j = 0; j &lt; 10; j++) {
    System.out.println(j);
}
```

使用 `var` 声明的变量时必须要在声明的同时初始化：

```java
var a; // error: &apos;var&apos; on variable without initializer
a = 0;
```

同时 `var` 不能用于局部变量声明以外的地方，例如：

```java
public class LocalVariableTypeInference {
    var num = 0; // error: &apos;var&apos; is not allowed here

    public LocalVariableTypeInference(var i) { // error: &apos;var&apos; is not allowed here
        try {

        } catch (var e) { // error: &apos;var&apos; is not allowed here

        }
    }

    var toString() { // error: &apos;var&apos; is not allowed here

    }
}
```

也不能使用 `var` 将变量初始化为 `null`</content:encoded></item><item><title>Linux 开启 TCP BBR 拥塞控制算法</title><link>https://igaozp.me/blog/linux-enable-tcp-bbr/</link><guid isPermaLink="true">https://igaozp.me/blog/linux-enable-tcp-bbr/</guid><pubDate>Mon, 22 Oct 2018 00:00:00 GMT</pubDate><content:encoded>## 前提

要想开启 TCP BBR，需要 Linux kernel 4.9+ ，如果内核版本过低的话需要升级内核，本教程以 Ubuntu 系统为例。

### 开启 TCP BBR

执行 `lsmod | grep bbr`，如果结果中没有 `tcp_bbr` 的话就先执行

```bash
modprobe tcp_bbr
echo &quot;tcp_bbr&quot; &gt;&gt; /etc/modules-load.d/modules.conf
```

执行

```bash
echo &quot;net.core.default_qdisc=fq&quot; &gt;&gt; /etc/sysctl.conf
echo &quot;net.ipv4.tcp_congestion_control=bbr&quot; &gt;&gt; /etc/sysctl.conf
```

保存生效

```bash
sysctl -p
```

执行

```bash
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control
```

如果结果都有 `bbr`， 则证明你的内核已开启 `bbr`
执行 `lsmod | grep bbr`， 看到有 `tcp_bbr` 模块即说明 `bbr` 已启动</content:encoded></item><item><title>Linux 常用命令简记</title><link>https://igaozp.me/blog/linux-command/</link><guid isPermaLink="true">https://igaozp.me/blog/linux-command/</guid><pubDate>Thu, 13 Sep 2018 00:00:00 GMT</pubDate><content:encoded>系统信息显示

```bash
date                      # 当前日期时间
cal                       # 日历
uptime                    # 当前更新的时间
w                         # 在线用户信息
whoami                    # 当前登录用户的用户名
finger user               # 关于user用户的信息
uname -a                  # 内核信息
cat /proc/cpuinfo         # CPU信息
cat /proc/meminfo         # 内存信息
man command               # 查找关于command命令的手册
df                        # 磁盘使用情况
du                        # 当前目录下文件的空间占用情况
free                      # 内存和交换空间的使用情况
whereis app               # app可能出现的位置
which app                 # app运行时默认的运行位置
```

文件解压缩

```bash
tar cf file.tar files      # 将files文件夹或文件, 打包为file.tar
tar xf file.tar            # 解压file.tar
tar czf file.tar.gz files  # 使用gzip创建压缩文件file.tar.gz
tar xzf file.tar.gz        # 使用gzip解压file.tar.gz
tar cjf file.tar.bz2       # 使用bzip2创建压缩文件file.tar.bz2
tar xjf file.tar.bz2       # 使用bzip2解压file.tar.bz2
gzip file                  # 将file文件压缩并重命名为file.gz
gzip -d file.gz            # 解压缩file.gz
```

&lt;!--more--&gt;

进程管理

```bash
ps                         # 显示当前活动进程
top                        # 显示所有运行中的进程
kill pid                   # 终止ID为pid的进程
killall proc               # 终止所有名为proc*的进程
bg                         # 列出已终止或后台运行的进程
fg                         # 将最近的后台任务调至前台
fg n                       # 将任务n调至前台
```

网络相关

```bash
ping host                  # ping主机地址并显示结果
whois domain               # 获取domain域名的whois信息
dig domain                 # 获取domain域名的DNS信息
dig -x host                # 反向查询host主机
wget file                  # 下载文件
wget -c file               # 继续已停止的下载
```

安装软件

```bash
# 从源码安装软件
./configure
make
make install

dpkg -i pkg.deb            # 安装deb格式的软件包
rpm -Uvh pkg.rpm           # 安装rpm格式的软件包
```

搜索相关

```bash
grep pattern files         # 查找匹配pattern模式的文件
grep -r pattern dir        # 在目录下递归查找匹配pattern模式
command | grep pattern     # 在command命令的输出中查找匹配pattern的信息
locate file                # 查找file文件的所有实例
```

文件权限

```bash
chmod octal file           # 为文件设置octal数字所代表的权限
chmod 777 file             # 所有人可以完全控制file文件
chmod 755                  # 文件创建者可以完全控制file文件, 组内成员和其他人只能读取和执行
4:读取(r)
2:写入(w)
1:可执行(x)
```

文件相关

```bash
ls                        # 列出目录
ls -al                    # 列出所有文件目录的信息
cd dir                    # 切换到dir目录下
cd                        # 切换到home目录下
pwd                       # 显示当前目录
mkdir dir                 # 创建目录
rm file                   # 删除file文件
rm -r dir                 # 删除dir目录下的内容
rm -f file                # 强制删除file文件
rm -rf dir                # 强制删除dir目录下的内容
cp file1 file2            # 复制文件
cp -r dir1 dir2           # 复制文件夹
mv file1 file2            # 重命名文件或移动文件
ln -s file link           # 创建符号连接
touch file                # 创建或更新文件
more file                 # 输出文件内容
head file                 # 输出文件前10行的内容
tail file                 # 输出文件后10行的内容
```</content:encoded></item><item><title>Linux 字体极简安装教程</title><link>https://igaozp.me/blog/linux-install-fonts/</link><guid isPermaLink="true">https://igaozp.me/blog/linux-install-fonts/</guid><pubDate>Tue, 25 Jul 2017 00:00:00 GMT</pubDate><content:encoded>创建字体目录

```bash
sudo mkdir /usr/share/fonts/oh-my-fonts`
```

将下载的字体复制到相应的目录

```bash
sudo cp 存放字体的目录/*  /usr/share/fonts/oh-my-fonts
```

修改字体目录权限

```bash
sudo chmod 644 /usr/share/fonts/oh-my-fonts/*
cd /usr/share/fonts/oh-my-fonts
```

建立字体缓存

```bash
sudo mkfontscale
sudo mkfontdir
sudo fc-cache -fv
```</content:encoded></item><item><title>Electron 中加载 Chrome 扩展</title><link>https://igaozp.me/blog/electron-load-chrome-plugin/</link><guid isPermaLink="true">https://igaozp.me/blog/electron-load-chrome-plugin/</guid><description>在 Electron 的开发过程中可以使用专门为 Chrome 开发的扩展插件，这样可以极大的提高页面 Debug 的效率。接下来看一下如何在 Electron 中启用这些扩展插件。</description><pubDate>Thu, 05 May 2016 00:00:00 GMT</pubDate><content:encoded>在 Electron 的开发过程中可以使用专门为 Chrome 开发的扩展插件，这样可以极大的提高页面 Debug 的效率。接下来看一下如何在 Electron 中启用这些扩展插件。

&lt;!--more--&gt;

## 下载扩展插件

要想使用这些插件，首先要在自己的 Chrome 浏览器中安装这些插件，然后在 Chrome 的扩展程序页面 `chrome://extensions` 下找到刚下载的扩展，每一个扩展都以一个专属的 ID ，记住这个扩展的 ID。

## 查找扩展路径

接下来需要找到扩展的在本地磁盘的路径:

- 在 Windows 下 Chrome 扩展的路径一般为 `C:\\Users\\username\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Extensions\\` 或者 `%LOCALAPPDATA%\\Google\\Chrome\\User Data\\Default\\Extensions`
- 在 Linux 下为 `~/.config/google-chrome/Default/Extensions/`
- 在 macOS 下为 `~/Library/Application Support/Google/Chrome/Default/Extensions`

具体的路径可能在不同的系统和软件版本之间有差异，可以自行搜索。然后在目录下找到刚才的 ID 值一样的文件夹，这个文件夹就是扩展的安装路径。

## 加载扩展

在 Electron 中使用 `BrowserWindow.addDevToolsExtension(devToolsUrl)` 来加载扩展，`devToolsUrl`为扩展的安装路径 。例如加载 Windows 中的 Chrome 扩展 :

```jsx
const devToolsUrl =
  &quot;C:\\\\Users\\\\username\\\\AppData\\\\Local\\\\CentBrowser\\\\User Data\\\\Default\\\\Extensions\\\\nhdogjmejiglipccpnnnanhbledajbpd\\\\3.1.2_0&quot;;

function createWindow() {
  win = new BrowserWindow({
    width: 800,
    height: 600,
  });

  win.loadURL(
    url.format({
      pathname: path.join(__dirname, &quot;index.html&quot;),
      protocol: &quot;file:&quot;,
      slashes: true,
    }),
  );

  win.webContents.openDevTools();
  // 加载扩展
  BrowserWindow.addDevToolsExtension(devToolsUrl);

  win.on(&quot;closed&quot;, () =&gt; {
    win = null;
  });
}
```

这样就可以在 Electron 的开发工具中使用 Chrome 的扩展。

## 其他

现在 Electron 官方只支持部分 Chrome 扩展，具体信息可以查看此[链接](https://electron.atom.io/docs/tutorial/devtools-extension/#supported-devtools-extensions)。</content:encoded></item></channel></rss>