<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Food for Thought Expected</title>
    <description>while [ &apos;alive&apos; ]; do think &amp;&amp; act | tee blog; done</description>
    <link>https://blog.yuzhi.run/</link>
    <atom:link href="https://blog.yuzhi.run/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 18 Jun 2026 17:34:53 +0800</pubDate>
    <lastBuildDate>Thu, 18 Jun 2026 17:34:53 +0800</lastBuildDate>
    <generator>Jekyll v3.8.7</generator>
    
      <item>
        <title>Token 的恩格尔系数</title>
        <description>&lt;p&gt;大家都在烧 token。但烧法确实不一样。&lt;/p&gt;

&lt;p&gt;最常见的用法，是让 AI 写代码、改文档、回邮件、做总结。这个当然好。原来要半天的事，现在十分钟有个像样的初稿，效率是看得见的。&lt;/p&gt;

&lt;p&gt;还有一类用法，看起来绕一点：磨 skill，改 workflow，设计 eval，复现 paper，做小实验，甚至专门花时间想自己该怎么用 AI。&lt;/p&gt;

&lt;p&gt;旁观者可能会觉得，这不是干活，是折腾。但这里有个区别很要紧：这批 token 烧完以后，会不会改变下一次？&lt;/p&gt;

&lt;h2 id=&quot;房租&quot;&gt;房租&lt;/h2&gt;

&lt;p&gt;经济学里有个指标，叫恩格尔系数。&lt;/p&gt;

&lt;p&gt;它看食品支出占总支出的比例。收入越低，越大比例的钱要花在吃饭这类基础需求上。生活水平上去以后，食品支出的占比反而下降，更多钱会流向教育、娱乐、投资、自我发展。&lt;/p&gt;

&lt;p&gt;不是钱变多这么简单。是钱的结构变了。token 也可以这么看。开个玩笑，可以定义一个：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Token 恩格尔系数 = 用来完成眼前任务的 token / 总 token&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;写周报、改文档、回消息、修 bug、做会议总结，都是 token 的硬支出。&lt;/p&gt;

&lt;p&gt;这类支出不是不重要。恰恰相反，它们是 AI 最直接的价值。任务来了，烧 token；任务完成，token 燃尽。下次同类任务再来，再烧一遍。&lt;/p&gt;

&lt;p&gt;很像交房租。房租当然要交。问题是，不能因为每个月都按时交房租，就以为自己在积累资产。&lt;/p&gt;

&lt;h2 id=&quot;留下结构&quot;&gt;留下结构&lt;/h2&gt;

&lt;p&gt;更值钱一点的用法，是让 token 在结果之外留下些什么。&lt;/p&gt;

&lt;p&gt;比如，不只是让 AI 帮你写一次周报，而是借这个机会想清楚：周报到底由哪些信息组成？哪些可以自动收集？哪些必须人工判断？什么样的周报才算有用？&lt;/p&gt;

&lt;p&gt;这时候，token 换来的就不只是一份周报，而是下次生成周报的方式。结果会过期。工具能复用。能力会迁移。&lt;/p&gt;

&lt;p&gt;留下来的东西不一定大。可能只是一个模板，一个检查清单，一个判断标准，一段自动抓取脚本，或者一个新的工作习惯。但它们都有一个共同点：会改变下一次。&lt;/p&gt;

&lt;h2 id=&quot;改流程&quot;&gt;改流程&lt;/h2&gt;

&lt;p&gt;大多数 AI 提效，是在旧轨道上加速。原来三小时写完，现在半小时写完。当然是进步。但更大的进步，是开始问另一类问题：&lt;/p&gt;

&lt;p&gt;这件事为什么每次都要这样做？它背后的信息能不能自动沉淀？它服务的决策到底是什么？现在这个流程是不是本身就有问题？到这一步，AI 就不只是铲子了，而是可以拿来改生产线。&lt;/p&gt;

&lt;p&gt;这也是为什么 meta thinking、skill 优化、paper 落地、探索实验，看起来不如“直接干活”务实，但长期可能更值钱。它们不是在生产一次结果。它们是在生产未来生产结果的方式。&lt;/p&gt;

&lt;h2 id=&quot;自由度&quot;&gt;自由度&lt;/h2&gt;

&lt;p&gt;再往上一层，我更在意的其实是自由度。提效，是同样的事做得更快。自由度，是以前做不了的事，现在能做了。&lt;/p&gt;

&lt;p&gt;以前你只能完成任务。后来你能沉淀方法。再后来你能改造流程。最后你会发现一些以前根本不会提出的问题。&lt;/p&gt;

&lt;p&gt;这才是 AI 真正有意思的地方。它不只是让人更快回答问题。它会改变一个人能提出什么问题。而很多时候，一个人的上限不是由答案决定的，而是由问题决定的。&lt;/p&gt;

&lt;h2 id=&quot;分水岭&quot;&gt;分水岭&lt;/h2&gt;

&lt;p&gt;所以，AI 使用水平的分水岭，不是每天烧多少 token，也不是会不会把活干快。真正的分水岭是：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;你的 token，是在维持当前系统，还是在改造当前系统？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;硬支出占比越高，人越容易被任务牵着走。硬支出占比降下来，才有机会反过来整理任务。最终要看的，不是 token 消耗量，而是 token 的去向。&lt;/p&gt;

&lt;p&gt;它是在交房租，还是在买机器？是在清掉一个任务，还是在改变下一次任务发生的方式？是让你更快回到原点，还是让你站到了一个新的位置？&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Jun 2026 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2026/06/17/token-engel-coefficient.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2026/06/17/token-engel-coefficient.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>monkey patch in go</title>
        <description>&lt;h2 id=&quot;什么是monkey-patch&quot;&gt;什么是monkey patch&lt;/h2&gt;

&lt;p&gt;早前的一个python项目遇到性能瓶颈，试图用对标准库做&lt;code class=&quot;highlighter-rouge&quot;&gt;monkey
patch&lt;/code&gt;，在不改源码的情况下，用&lt;a href=&quot;http://www.gevent.org/api/gevent.monkey.html&quot;&gt;gevent&lt;/a&gt;让标准库用上非阻塞IO。
留下的印象是以为在&lt;code class=&quot;highlighter-rouge&quot;&gt;python&lt;/code&gt;等动态语言里才有&lt;code class=&quot;highlighter-rouge&quot;&gt;monkey patch&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;偶然看到&lt;a href=&quot;https://github.com/bouk/monkey&quot;&gt;bouk/monkey&lt;/a&gt;，才发现，&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;语言也可以实现。好奇之下，研究了它的原理。作者有个&lt;a href=&quot;https://bou.ke/blog/monkey-patching-in-go/&quot;&gt;博客&lt;/a&gt;，讲得很好，但缺了很多细节和过程。于是，从问题源头出发，自己理一遍，收益良多：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;程序的编译、连接与执行&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;工具&lt;code class=&quot;highlighter-rouge&quot;&gt;compile&lt;/code&gt;与&lt;code class=&quot;highlighter-rouge&quot;&gt;objdump&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;函数值实现&lt;/li&gt;
  &lt;li&gt;plan9汇编&lt;/li&gt;
  &lt;li&gt;X86指令、寄存器&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;魔法&quot;&gt;魔法&lt;/h2&gt;

&lt;p&gt;简单来说，通过&lt;code class=&quot;highlighter-rouge&quot;&gt;monkey patch&lt;/code&gt;，以下代码将输出”2”而不是”1”：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不难看出，魔法都藏在&lt;code class=&quot;highlighter-rouge&quot;&gt;replace&lt;/code&gt;函数里：需要修改&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;函数，使其不执行自己的函数体，而是跳转到函数&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;为了讲清楚如何实现，得先铺垫几点背景知识。&lt;/p&gt;

&lt;h2 id=&quot;go与汇编&quot;&gt;go与汇编&lt;/h2&gt;

&lt;p&gt;在&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;项目里使用汇编是件很容易的事。&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;代码&lt;code class=&quot;highlighter-rouge&quot;&gt;func.go&lt;/code&gt;里只声明函数：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;汇编代码&lt;code class=&quot;highlighter-rouge&quot;&gt;func.s&lt;/code&gt;里，实现两函数，这里，我们让&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;函数跳转到&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;函数：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-assembly&quot;&gt;#include &quot;textflag.h&quot;

TEXT ·a(SB), NOSPLIT, $0-8
  JMP ·b(SB)

TEXT ·b(SB), NOSPLIT, $0-8
  MOVQ $2, ret1+0(FP)
  RET
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把&lt;code class=&quot;highlighter-rouge&quot;&gt;func.go&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;func.s&lt;/code&gt;放到同一目录，然后执行：
&lt;code class=&quot;highlighter-rouge&quot;&gt;GO111MODULE=&quot;off&quot;; go build -o func &amp;amp;&amp;amp;
./func&lt;/code&gt;。这段代码定义了两个函数，&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;函数直接跳转到&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;函数，&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;返回整数&lt;code class=&quot;highlighter-rouge&quot;&gt;2&lt;/code&gt;。至此，没什么大不了，手动实现跳转而已。&lt;/p&gt;

&lt;p&gt;关于汇编的语法，不是本文的重点，可以参考文后链接。&lt;/p&gt;

&lt;h2 id=&quot;go的函数值类型&quot;&gt;go的函数值类型&lt;/h2&gt;

&lt;p&gt;手动跳转显然不够，&lt;code class=&quot;highlighter-rouge&quot;&gt;replace(f, g)&lt;/code&gt;的职责就动态改变函数&lt;code class=&quot;highlighter-rouge&quot;&gt;f&lt;/code&gt;的代码，分两步：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;取得函数&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;的地址&lt;/li&gt;
  &lt;li&gt;重写&lt;code class=&quot;highlighter-rouge&quot;&gt;f&lt;/code&gt;，使其跳转到&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要指出的是，&lt;code class=&quot;highlighter-rouge&quot;&gt;replace&lt;/code&gt;的入参&lt;code class=&quot;highlighter-rouge&quot;&gt;f&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;很容易被误解为函数&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;的指针，但其实它们是指针的指针。这点可以通过反编译来验证，保存下面代码到&lt;code class=&quot;highlighter-rouge&quot;&gt;funcaddr.go&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;unsafe&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0x%x&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;go build funcaddr.go &amp;amp;&amp;amp; ./funcaddr&lt;/code&gt;得到：&lt;code class=&quot;highlighter-rouge&quot;&gt;0x109adc0&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;再执行&lt;code class=&quot;highlighter-rouge&quot;&gt;go tool objdump -S funcaddr&lt;/code&gt;，搜索这个地址，发现确实是&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;函数的地址：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-assembly&quot;&gt;TEXT main.a(SB) funcaddr.go
func a() int { return 1 }
  0x109adc0             48c744240801000000      MOVQ $0x1, 0x8(SP)
  0x109adc9             c3                      RET
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ok，通过函数值&lt;code class=&quot;highlighter-rouge&quot;&gt;f&lt;/code&gt;可以拿到函数&lt;code class=&quot;highlighter-rouge&quot;&gt;a&lt;/code&gt;的地址了。为什么要拿到地址呢？&lt;/p&gt;

&lt;h2 id=&quot;在运行时改写函数&quot;&gt;在运行时改写函数&lt;/h2&gt;

&lt;p&gt;函数体本质是一段字符串，知道开始地址后，从那里开始写入表示新逻辑的字符串即可实现覆盖。随之而来的问题是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;新逻辑的字符串是什么？&lt;/li&gt;
  &lt;li&gt;如何知道覆盖的范围？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为新的字符串要能直接被机器运行，所以它必须是机器码。把汇编翻译成机器码，并不是件容易的事，同一段汇编在不同平台得到的机器不尽相同。如果想手动翻译，可参考文后链接。&lt;/p&gt;

&lt;p&gt;我用了一个取巧的方式，反翻译上面的&lt;code class=&quot;highlighter-rouge&quot;&gt;func&lt;/code&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;go tool objdump -S
func&lt;/code&gt;，找到&lt;code class=&quot;highlighter-rouge&quot;&gt;main.a&lt;/code&gt;的定义：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;TEXT main.a(SB) func.s
  0x1054e70         e90b000000           JMP main.b(SB)
  //...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中，&lt;code class=&quot;highlighter-rouge&quot;&gt;e90b000000&lt;/code&gt;就是跳转到函数&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;的机器码。终于可以来实现&lt;code class=&quot;highlighter-rouge&quot;&gt;replace&lt;/code&gt;函数了：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawMemoryAccess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0xe9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x0b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;funcLocation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawMemoryAccess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;funcLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;逻辑实现了，但这段代码是无法运行的。因为加载的二进制文件&lt;a href=&quot;https://en.wikipedia.org/wiki/Segmentation_fault#Writing_to_read-only_memory&quot;&gt;默认是无法修改的&lt;/a&gt;，即&lt;code class=&quot;highlighter-rouge&quot;&gt;copy&lt;/code&gt;这行将报错。我们用系统调用&lt;code class=&quot;highlighter-rouge&quot;&gt;mprotect&lt;/code&gt;来关闭这一保护机制，得到可用的代码：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;//go:noinline&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;syscall&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;unsafe&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawMemoryAccess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0xFFFFFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Getpagesize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Getpagesize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assembleJump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0xe9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x0b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assembleJump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;functionLocation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rawMemoryAccess&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;functionLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;functionLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mprotect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PROT_READ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PROT_WRITE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;syscall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PROT_EXEC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;nb&quot;&gt;copy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;现在可以直接执行&lt;code class=&quot;highlighter-rouge&quot;&gt;go run
func.go&lt;/code&gt;，因为&lt;code class=&quot;highlighter-rouge&quot;&gt;func.s&lt;/code&gt;只是用于帮助理解，现在不再需要了。
注意&lt;code class=&quot;highlighter-rouge&quot;&gt;//go:noinine&lt;/code&gt;这行，用于关闭函数内联，这样才能支持改写。&lt;/p&gt;

&lt;p&gt;眼尖的读者可能发现了，这个&lt;code class=&quot;highlighter-rouge&quot;&gt;replace&lt;/code&gt;不够通用，还是写死了跳转到函数&lt;code class=&quot;highlighter-rouge&quot;&gt;b&lt;/code&gt;而不是指定的函数&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;，实际上&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;参数根本没用上！
为了通用，我们改造&lt;code class=&quot;highlighter-rouge&quot;&gt;assembleJump&lt;/code&gt;，让跳转的机器码使用&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;所指向的地址：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;assembleJump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;funcVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unsafe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;m&quot;&gt;0x48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0xC7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0xC2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;funcVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;funcVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;funcVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;funcVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// MOV rdx, funcVal&lt;/span&gt;
    &lt;span class=&quot;m&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0x22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;          &lt;span class=&quot;c&quot;&gt;// JMP rdx&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里不是直接&lt;code class=&quot;highlighter-rouge&quot;&gt;jmp&lt;/code&gt;到函数&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;，而是先把&lt;code class=&quot;highlighter-rouge&quot;&gt;g&lt;/code&gt;的地址存到寄存器&lt;code class=&quot;highlighter-rouge&quot;&gt;rdx&lt;/code&gt;，再&lt;code class=&quot;highlighter-rouge&quot;&gt;jmp&lt;/code&gt;到&lt;code class=&quot;highlighter-rouge&quot;&gt;rdx&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;用这个通用的&lt;code class=&quot;highlighter-rouge&quot;&gt;assembleJump&lt;/code&gt;替换上面返回固定值的&lt;code class=&quot;highlighter-rouge&quot;&gt;assembleJump&lt;/code&gt;，大功造成。&lt;/p&gt;

&lt;h2 id=&quot;monkey-patch的应用&quot;&gt;monkey patch的应用&lt;/h2&gt;

&lt;p&gt;显然，这是一种hack，不能用于生产环境。随着go版本的迭代，没准不久的将来就失效了。如果仔细观察，会发现我们手写的汇编或者机器码，比&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;编译得到的少了些含有&lt;code class=&quot;highlighter-rouge&quot;&gt;FUNCDATA&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;PCDATA&lt;/code&gt;字眼的内容：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  0x0000 00000 (func.go:9) FUNCDATA        $0,
gclocals·33cdeccccebe80329f1fdbee7f5874cb(
SB)
  0x0000 00000 (func.go:9) FUNCDATA        $1,
gclocals·33cdeccccebe80329f1fdbee7f5874cb(
SB)
  0x0000 00000 (func.go:9) FUNCDATA        $2,
gclocals·33cdeccccebe80329f1fdbee7f5874cb(
SB)
  0x0000 00000 (func.go:9) PCDATA  $0, $0
  0x0000 00000 (func.go:9) PCDATA  $1, $0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;PCDATA&lt;/code&gt;把程序计数器和代码行号对应起来，&lt;code class=&quot;highlighter-rouge&quot;&gt;FUNCDATA&lt;/code&gt;则是为垃圾回收服务的，详见&lt;a href=&quot;https://www.altoros.com/blog/golang-internals-part-4-object-files-and-function-metadata/&quot;&gt;Object
Files and Function
Metadata&lt;/a&gt;。缺少它们，相应功能就有缺陷。&lt;/p&gt;

&lt;p&gt;难道，只能用于装逼了？&lt;/p&gt;

&lt;p&gt;不，有一个场合正是用武之地：测试。将它用于打桩，让用户在单元测试中低成本的完成mock。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;go&lt;/code&gt;的各种mock工具都只能对&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;类型做mock。虽然我们一直提倡依赖倒置，现实中，还是有很多代码直接依赖了具体实现，给mock带来不必要的麻烦。&lt;/p&gt;

&lt;p&gt;正好，黑魔法般的&lt;code class=&quot;highlighter-rouge&quot;&gt;monkey
patch&lt;/code&gt;来搭救了。使用封装好的&lt;a href=&quot;https://github.com/bouk/monkey&quot;&gt;monkey&lt;/a&gt;，可以非常简单地实现对非&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;依赖的mock。&lt;/p&gt;

&lt;p&gt;举个例子，&lt;code class=&quot;highlighter-rouge&quot;&gt;RpcClient&lt;/code&gt;是个&lt;code class=&quot;highlighter-rouge&quot;&gt;struct&lt;/code&gt;，代表外部rpc调用：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rpc&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RpcClient&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SayHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;// call remote endpoint&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello world&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在测试时mock这个rpc调用，返回指定内容：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rpc&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;bou.ke/monkey&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;github.com/stretchr/testify/assert&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;reflect&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;testing&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestSayHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;fakeRpc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;monkey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PatchInstanceMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;SayHello&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rpcClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RpcClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hi five&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fakeRpc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unpatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SayHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hi five&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行测试时，需要关闭内联：&lt;code class=&quot;highlighter-rouge&quot;&gt;go test -gcflags=-l&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;参考链接&quot;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mit.edu/afs.new/sipb/project/golang/doc/asm.html&quot;&gt;A Quick Guide to Go’s
Assembler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/teh-cmc/go-internals/blob/master/chapter1_assembly_primer/README.md#a-word-about-goroutines-stacks-and-splits&quot;&gt;A Primer on Go
Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.systutorials.com/beginners-guide-x86-64-instruction-encoding/&quot;&gt;A Beginners’ Guide to x86-64 Instruction
Encoding&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.osdev.org/X86-64_Instruction_Encoding&quot;&gt;X86-64 Instruction
Encoding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 20 Jun 2020 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2020/06/20/go-monkey-patch.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2020/06/20/go-monkey-patch.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>Go性能诊断工具pprof入门</title>
        <description>&lt;p&gt;测试环境一个api应用突然cpu飙高，稳定在98%……&lt;/p&gt;

&lt;p&gt;第一反应是jstack看下是不是代码里有死循环了？然而这是go不是java，只能在go的生态里重新寻找类似工具。&lt;/p&gt;

&lt;p&gt;切换语言时，除了改变写代码的语言，有必要&lt;strong&gt;熟悉维护应用、定位问题的工具链&lt;/strong&gt;。这里简单介绍go pprof的使用，以求需要时不必临时google，能以最快速度开始定位问题。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;pprof is a tool for visualization and analysis of profiling data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;安装请执行：
&lt;code class=&quot;highlighter-rouge&quot;&gt;go get -u github.com/google/pprof&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;cpu-profile&quot;&gt;cpu profile&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;找到容器的IP和debug端口对应的访问端口。&lt;/li&gt;
  &lt;li&gt;执行&lt;code class=&quot;highlighter-rouge&quot;&gt;go tool pprof -http=:6061 http://$ip:$port/debug/pprof/profile&lt;/code&gt;
需要一段时间来采样，耐心等待下。采样结束后，将自动在浏览器中打开可视化分析页面。不加&lt;code class=&quot;highlighter-rouge&quot;&gt;-http=:6061&lt;/code&gt;时将在终端使用命令行方式交互。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;采样数据默认放到~/pprof/目录下，可通过以下命令直接指定文件进行分析。
&lt;code class=&quot;highlighter-rouge&quot;&gt;go tool pprof -http=:6061 ~/pprof/xxx.pb.gz&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&quot;top-view&quot;&gt;Top view&lt;/h4&gt;

&lt;p&gt;效果如下：
&lt;img src=&quot;/img/202005/ppro-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;重点是理解&lt;code class=&quot;highlighter-rouge&quot;&gt;Flat&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;Sum&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;Cum&lt;/code&gt;三个指标。对于函数&lt;code class=&quot;highlighter-rouge&quot;&gt;foo&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                             &lt;span class=&quot;c&quot;&gt;// step1 takes 1s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                             &lt;span class=&quot;c&quot;&gt;// step2 takes 1s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;something&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;directly&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;          &lt;span class=&quot;c&quot;&gt;// step3 takes 3s&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;                             &lt;span class=&quot;c&quot;&gt;// step4 takes 1s&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;flat&lt;/code&gt;是step3的耗时，即3s。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;cum&lt;/code&gt;是&lt;code class=&quot;highlighter-rouge&quot;&gt;foo&lt;/code&gt;的整体耗时，包括子函数和直接指令的耗时，即6s。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;sum&lt;/code&gt;是截至当前行的累计耗时。&lt;/p&gt;

&lt;p&gt;上图的例子是调用了一次api的效果（服务器没有其它请求），耗时27s，大部分时间花在&lt;code class=&quot;highlighter-rouge&quot;&gt;go-polaris&lt;/code&gt;这个库内，无论哪个指标出发，都很显然要重点看看&lt;code class=&quot;highlighter-rouge&quot;&gt;scanAttrEql&lt;/code&gt;函数(结论是该函数死循环了)。&lt;/p&gt;

&lt;h4 id=&quot;graph-view&quot;&gt;Graph view&lt;/h4&gt;

&lt;p&gt;按调用层级从上到下显示调用图，需要安装&lt;code class=&quot;highlighter-rouge&quot;&gt;graphviz&lt;/code&gt;：
&lt;code class=&quot;highlighter-rouge&quot;&gt;brew install graphviz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;括号内的数值同top view。框的大小和箭头的粗细会随着耗时和增加而变大，比较直观。&lt;/p&gt;

&lt;h4 id=&quot;flame-graph&quot;&gt;Flame graph&lt;/h4&gt;

&lt;p&gt;倒置的火焰图，更常见的形式，也很直观。不展开，可参考&lt;a href=&quot;https://www.ruanyifeng.com/blog/2017/09/flame-graph.html&quot;&gt;如何读懂火焰图？&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;memory-profile&quot;&gt;memory profile&lt;/h3&gt;

&lt;p&gt;执行:
&lt;code class=&quot;highlighter-rouge&quot;&gt;go tool pprof -http=:6061 http://$ip:$port/debug/pprof/heap&lt;/code&gt;
几个view的解读同cpu profile，不再重复。需要指出的是内存分析比较关心的两个指标，通过在命令中添加以下参数可以获得：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;-inuse_space&lt;/strong&gt;：分析程序&lt;strong&gt;常驻&lt;/strong&gt;内存的占用情况，数值大说明程序当前时刻内存占用较多。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;-alloc_objects&lt;/strong&gt;：分析内存的&lt;strong&gt;临时分配&lt;/strong&gt;情况，记录程序累计分配的对象，数值大说明有很多对象经常需要临时分配，然后在gc的时候又被释放，对gc的影响比较大。gcPause问题比较严重的时候可以关注这个指标。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;其它profile&quot;&gt;其它profile&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;cpu&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;memory&lt;/code&gt;是常用的的profile类型，值得事先花点时间掌握，其余的可在碰到需求时临时查阅文档。&lt;/p&gt;

&lt;p&gt;访问&lt;code class=&quot;highlighter-rouge&quot;&gt;http://$ip:$port/debug/pprof/&lt;/code&gt;得到整体可供profile的选项：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;cpu（CPU Profiling）: /debug/pprof/profile，默认进行 30s 的 CPU Profiling，得到一个分析用的 profile 文件&lt;/li&gt;
  &lt;li&gt;block（Block Profiling）：/debug/pprof/block，查看导致阻塞同步的堆栈跟踪&lt;/li&gt;
  &lt;li&gt;goroutine：/debug/pprof/goroutine，查看当前所有运行的 goroutines 堆栈跟踪&lt;/li&gt;
  &lt;li&gt;heap（Memory Profiling）: /debug/pprof/heap，查看活动对象的内存分配情况&lt;/li&gt;
  &lt;li&gt;mutex（Mutex Profiling）：/debug/pprof/mutex，查看导致互斥锁的竞争持有者的堆栈跟踪&lt;/li&gt;
  &lt;li&gt;threadcreate：/debug/pprof/threadcreate，查看创建新OS线程的堆栈跟踪&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;延伸讨论&quot;&gt;延伸讨论&lt;/h3&gt;

&lt;h4 id=&quot;启用方式&quot;&gt;启用方式&lt;/h4&gt;

&lt;p&gt;pprof主要有三种数据获取方式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;runtime/pprof: 手动调用&lt;code class=&quot;highlighter-rouge&quot;&gt;runtime.StartCPUProfile&lt;/code&gt;或者&lt;code class=&quot;highlighter-rouge&quot;&gt;runtime.StopCPUProfile&lt;/code&gt;等 API来生成和写入采样文件&lt;/li&gt;
  &lt;li&gt;net/http/pprof: 通过 http 服务获取Profile采样文件，简单易用，适用于对应用程序的整体监控。通过 runtime/pprof 实现。如果在自己的程序中需要支持pprof，推荐此方式，只需要引入这个依赖即可：&lt;code class=&quot;highlighter-rouge&quot;&gt;import _ &quot;net/http/pprof&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;go test: 通过&lt;code class=&quot;highlighter-rouge&quot;&gt;go test -bench . -cpuprofile prof.cpu&lt;/code&gt;生成采样文件 适用对函数进行针对性测试&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;原理&quot;&gt;原理&lt;/h4&gt;

&lt;p&gt;没有正式的文档，参考golang团队成员ian lance taylor的一段回答：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;blockquote&gt;
    &lt;p&gt;Is it true that pprof is a stop-the-world sampler? Does it periodically stop
the program being profiled to collect information?&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;p&gt;No.&lt;/p&gt;

  &lt;p&gt;Is pprof a statistical/stochastic profile?&lt;/p&gt;

  &lt;p&gt;Yes.&lt;/p&gt;

  &lt;blockquote&gt;
    &lt;p&gt;Is it also an event based profiler?&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;p&gt;Yes.&lt;/p&gt;

  &lt;blockquote&gt;
    &lt;p&gt;I know it doesn’t run at the kernal level, but where in the OS does pprof
profile?&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;p&gt;As you say, the net/http/pprof package is basically a wrapper around
the runtime/pprof package.&lt;/p&gt;

  &lt;p&gt;For CPU profiling, runtime/pprof works by periodically interrupting
the program. On Unix-like systems, this is done using setitimer to
send a periodic SIGPROF signal. When this signal arrive, the
goroutine that receives it stores a stack trace. This very briefly
stops the goroutine being profiled, but it doesn’t affect the rest of
the program.&lt;/p&gt;

  &lt;p&gt;For heap profiling, the memory allocator tracks the number of
allocations it has done, and periodically records a stack trace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;overhead&quot;&gt;overhead&lt;/h4&gt;

&lt;p&gt;凡事都有成本，采样时的成本(只有访问debug profile时才会引入该成本，只是引入依赖不会有成本)：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;pprof is safe to use in production. We target an additional 5% overhead for CPU and heap allocation profiling&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;另一google员工的描述：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;At Google, we continuously profile Go production services and it is safe to do so.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;单纯讨论这一成本是高低意义不大，这里涉及一个更大的话题：&lt;a href=&quot;https://medium.com/google-cloud/continuous-profiling-of-go-programs-96d4416af77b&quot;&gt;Continuous Profiling of Go programs&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;参考文档&quot;&gt;参考文档&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/google/pprof/blob/master/doc/README.md&quot;&gt;Google/pprof readme&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://golang.org/pkg/net/http/pprof/&quot;&gt;Package pprof&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/32571396/pprof-and-golang-how-to-interpret-a-results&quot;&gt;Pprof and golang - how to interpret a results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bytedance.feishu.cn/wiki/wikcnRO5EewsntuRMbB4DPzescf#&quot;&gt;Go pprof 业务中实战&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/forum/#!topic/golang-nuts/PiFa55aX7Ds&quot;&gt;How does pprof work under the hood&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 30 May 2020 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2020/05/30/pprof-tutorial.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2020/05/30/pprof-tutorial.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>Go项目单元测试实践</title>
        <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;

&lt;p&gt;单测本身不是目的，更根本地，要提升工程的可维护性。&lt;/p&gt;

&lt;p&gt;为什么随着时间的推移，工程越来越难维护？因为工程的复杂度的增速快于我们治理复杂度的能力的增速。治理复杂度的能力落地了就是工程的可维护性。&lt;/p&gt;

&lt;p&gt;用线性的手段去治理指数的问题，只在初期可行。长期必须要有一个比问题曲线更陡的能力曲线。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/202004/go-ut-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;影响工程复杂度的因素：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;业务的本质复杂性&lt;/li&gt;
  &lt;li&gt;互联网高人员流动性&lt;/li&gt;
  &lt;li&gt;文档永远缺失或滞后&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;治理复杂度的能力&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;设计能力&lt;/li&gt;
  &lt;li&gt;测试能力&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本文从测试角度出发做一点探讨。首先澄清概念，这里的“测试”专指研发人员自行开展的测试工作，不包括QA同学的工作。&lt;/p&gt;

&lt;p&gt;可能涉及单元、集成、功能测试，用下图说明：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/202004/go-ut-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;测试的意义&quot;&gt;测试的意义&lt;/h2&gt;

&lt;p&gt;常见的说法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;等项目提测后再补些单测。其心理：
    &lt;ul&gt;
      &lt;li&gt;不知有没有意义，tl要求，没办法&lt;/li&gt;
      &lt;li&gt;有意义，为了方便后续人员维护&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;事后补单测，好比把到女神了，依然热度不减当初，天天嘘寒问暖。是有这样的人，但你是吗？&lt;/p&gt;

&lt;p&gt;另一种认识：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;帮助本人开发现在的功能。&lt;strong&gt;本人&lt;/strong&gt;！&lt;strong&gt;现在&lt;/strong&gt;！
在没有护栏的高速路狂奔，开得越快，死得越快。
&lt;img src=&quot;/img/202004/go-ut-3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
  &lt;li&gt;帮助提高项目的长度维护性。顺便！
在&lt;strong&gt;高人员流动&lt;/strong&gt;的情境下实现工程的&lt;strong&gt;长期可维护性&lt;/strong&gt;。
    &lt;ul&gt;
      &lt;li&gt;靠员工传承 ✕&lt;/li&gt;
      &lt;li&gt;靠文档传承 ✕&lt;/li&gt;
      &lt;li&gt;自解释工程 √&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;test as a doc&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;可用的测试&quot;&gt;可用的测试&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;低成本&lt;/strong&gt;地实现：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可重复运行&lt;/li&gt;
  &lt;li&gt;可自动运行&lt;/li&gt;
  &lt;li&gt;不依赖外部环境&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;即，测试本身的scalability&lt;/p&gt;

&lt;p&gt;对比几种测试做法：&lt;/p&gt;

&lt;h4 id=&quot;流程1&quot;&gt;流程1:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;为case1在db造数据 (每次3m)&lt;/li&gt;
  &lt;li&gt;本地启用应用（改配置连本地服务) (每次2m)&lt;/li&gt;
  &lt;li&gt;在postman配置，为case1调api (若能长期保存psotman配置，则每次1m，否则每次5m）&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;流程2&quot;&gt;流程2:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;为case1在db造数据 (每次3m)&lt;/li&gt;
  &lt;li&gt;写测试代码 (首次10min，以后0)&lt;/li&gt;
  &lt;li&gt;运行测试代码 (每次0.1m)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;流程3&quot;&gt;流程3:&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;为case1在代码中靠数据 (首次6m，以后0)&lt;/li&gt;
  &lt;li&gt;写测试代码 (首次10min，以后0)&lt;/li&gt;
  &lt;li&gt;运行测试代码 (每次0.1m)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;长期耗时对比：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;流程1：(3+2)&lt;em&gt;n + 5 + 1&lt;/em&gt;(n-1) = 6n+4&lt;/li&gt;
  &lt;li&gt;流程2：3&lt;em&gt;n + 10 + 0.1&lt;/em&gt;n = 3.1n+10&lt;/li&gt;
  &lt;li&gt;流程3：6+10+0.1n = 0.1n+16&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/img/202004/go-ut-4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;流程1和2都是非scalable的做法，问题分析：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;流程1，依赖了外部环境，不可重复、无法自动化&lt;/li&gt;
  &lt;li&gt;流程2，依赖了外部环境，不可重复，可以自动化&lt;/li&gt;
  &lt;li&gt;流程3，不依赖外部环境，可以重复，可以自动化&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以为点点postman、连mysql造条数据是图省事，诸不知这是更费事的做法。一个短期、一个长期，本质上都是“偷懒”，省点时间多看看窗外的风景、少掉几根头发。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Less is exponentially more —— Rob Pike&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果认同上述观点，接下来的内容其实不看也没啥损失。因为你总会想出各种手段去“偷懒”的，具体的手段反而关系不大了。换言之，以下方式随时可能被更先进、更scalable的方式替代。&lt;/p&gt;

&lt;h2 id=&quot;工程可测性&quot;&gt;工程可测性&lt;/h2&gt;

&lt;h3 id=&quot;遵守控制反转原则&quot;&gt;遵守控制反转原则&lt;/h3&gt;

&lt;p&gt;并不是所有代码都是可测试的。谈具体测试做法前，得先保证代码的可测性。道理上是极其简单的，即&lt;code class=&quot;highlighter-rouge&quot;&gt;SOLID&lt;/code&gt;原则中的&lt;code class=&quot;highlighter-rouge&quot;&gt;D&lt;/code&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Any higher classes should always depend upon the abstraction of the class rather than the detail. –&lt;strong&gt;Dependency Inversion Principle&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但实践起来并不那么容易。
比如，业务代码中很常见的repo调用dal的写法：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RepositoryImpl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;userDO&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;convert&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserModel2DO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreateUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userDO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比如，调用rpc的写法：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// 调用rpc：&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;thirdcall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProduceServiceClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QueryTaskPackPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pageTaskPackRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// ProduceServiceClient定义：&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;kc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kitc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KitcClient&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;毫无违和感，却是违背&lt;code class=&quot;highlighter-rouge&quot;&gt;DI&lt;/code&gt;的，进而限制可测性。&lt;/p&gt;

&lt;p&gt;因为抽象是可变的，实现是固定的。依赖抽象使得测试过程中剥离无关部分（可能是其它系统、也可能是本系统的其它代码）成为可能。而测试，只应测试目标代码，既不应依赖另一个系统、模块的输入，也不应输出到另一个系统、模块，这是“不依赖外部环境”的双重含义。（从这个意义上说，测试的过程应践行函数式编程的理念：pure、immutable、no side effect。）&lt;/p&gt;

&lt;p&gt;就go语言而言，唯一的抽象工具就是&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;了。当依赖&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;时，可以在测试时用内存实现的db替换外部的mysql；用mock的rpc客户端替换真实的rpc调用。&lt;/p&gt;

&lt;h3 id=&quot;尽量避免全局变量&quot;&gt;尽量避免全局变量&lt;/h3&gt;

&lt;p&gt;一时全局一时爽，一直全局会很惨！&lt;/p&gt;

&lt;p&gt;散落在各处的全局变量引用，让人无法快速分析出外部依赖。本质上全局变量是固定的实现，绑定全局变量同样使得剥离依赖变量困难。&lt;/p&gt;

&lt;p&gt;建议：总是在&lt;code class=&quot;highlighter-rouge&quot;&gt;struct&lt;/code&gt;定义里声明清楚外部依赖，哪怕只是一个&lt;code class=&quot;highlighter-rouge&quot;&gt;config&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TaskPackServiceImpl&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;MaterialService&lt;/span&gt;         &lt;span class=&quot;n&quot;&gt;MaterialService&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;TaskService&lt;/span&gt;             &lt;span class=&quot;n&quot;&gt;TaskService&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt;             &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MatrixClientImpl&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MatrixConfig&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有个例外情况。构造器初始化传参的方式过于简单，复杂项目下，在我们没有依赖注入工具的情况下，会让单例生成变得很繁琐。如&lt;code class=&quot;highlighter-rouge&quot;&gt;TaskPackService&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;TaskService&lt;/code&gt;互相依赖，无法直接构造出来。如果严格执行上述建议，相当于人工实现依赖注入。所有单例都先使用无参数构造器&lt;code class=&quot;highlighter-rouge&quot;&gt;new&lt;/code&gt;出来，然后再遍历依赖图，一个个&lt;code class=&quot;highlighter-rouge&quot;&gt;set&lt;/code&gt;属性。&lt;/p&gt;

&lt;p&gt;在引入依赖注入工具之前，这种耦合严重的场景可以直接引用全局变量，其余场景（占多数，毕竟是微服务）仍坚持该建议。&lt;/p&gt;

&lt;h2 id=&quot;方法与实操&quot;&gt;方法与实操&lt;/h2&gt;

&lt;p&gt;权衡投入产出，推荐对服务的serivce层做测试。服务的handler层和api暂不推荐。以service的公有方法为单位编写若干测试用例。&lt;/p&gt;

&lt;p&gt;推荐两种实践：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对复杂的service方法做单元测试，即把该方法的外部依赖全部mock掉，包括其它service，和自己dal层。&lt;/li&gt;
  &lt;li&gt;复杂度一般的service方法，直接做集成测试，即不mock其它的其它service，不mock自己的dal层。但mock掉外部依赖：rpc、中间件的调用，等。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;总得来说：&lt;code class=&quot;highlighter-rouge&quot;&gt;Mock&lt;/code&gt;，只是结合具体场景的手段不尽相同。&lt;/p&gt;

&lt;h4 id=&quot;场景1数据库调用&quot;&gt;场景1，数据库调用&lt;/h4&gt;

&lt;p&gt;有两种路线：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1，直接把dal层mock掉&lt;/li&gt;
  &lt;li&gt;2，dal层真实，但db被mock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;建议走路线2，因为我们的业务往往&lt;code class=&quot;highlighter-rouge&quot;&gt;sql&lt;/code&gt;的正确性是非常关键的，有些功能甚至就是些&lt;code class=&quot;highlighter-rouge&quot;&gt;crud&lt;/code&gt;，路线1把dal层都mock掉了，发现问题的可能性大大降低了。&lt;/p&gt;

&lt;p&gt;用内存数据库替代真实数据库（这也是一种mock）。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;dao依赖抽象的&lt;code class=&quot;highlighter-rouge&quot;&gt;DBManager&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;提供&lt;code class=&quot;highlighter-rouge&quot;&gt;DBManager&lt;/code&gt;的两个实现&lt;/li&gt;
  &lt;li&gt;在init内提供选择（只在这里有区别，其余代码完全一样）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// 抽象的db协议&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DBManager&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;WithDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;GetDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gorm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DB&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;TransactionWithResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// 测试用的db实现&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DBManagerFake&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// 生产用的db实现&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DBManagerReal&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// dal包的Init方法提供两种Init:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;initRealDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 外部mysql&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;EMDBManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DBManagerReal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;initDAOs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InitTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;initFakeDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;// 内存sqlite&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;EMDBManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DBManagerFake&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;initDAOs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// XXX_test.go文件里使用InitTest：&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestAuditPassAction_Transfer_DoublePass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;testing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;dal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InitTest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;场景2外部调用&quot;&gt;场景2，外部调用&lt;/h4&gt;

&lt;p&gt;数据库场景里代码在我们掌握范围内，像redis、rpc之类的（统称外部调用）客户端代码都是提供好的，像我司的kitool生成的客户端代码就是一个&lt;code class=&quot;highlighter-rouge&quot;&gt;type Client struct&lt;/code&gt;，并没有提供&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;，怎么办？&lt;/p&gt;

&lt;p&gt;我们自己写个&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;，再引用预生成的代码实现该&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;。实际使用时，不直接用预生成的代码，而是通过依赖该&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;以&lt;code class=&quot;highlighter-rouge&quot;&gt;crowd&lt;/code&gt;项目和题库的交互为例，我们自己定义&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;表达题库提供的能力协议：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MatrixClient&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;AddUpdateBook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddUpdateBookRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MatrixResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;UpdateBookState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UpdateBookStateRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MatrixResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;AddUpdateItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requests&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddUpdateItemRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MatrixResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;UpdateItemState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;itemIds&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MatrixResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后有两份实现&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;真实的&lt;code class=&quot;highlighter-rouge&quot;&gt;MatrixClientImpl&lt;/code&gt;，生产使用&lt;/li&gt;
  &lt;li&gt;假的&lt;code class=&quot;highlighter-rouge&quot;&gt;MockMatrixClient&lt;/code&gt;，测试时使用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;类似地，其它形式的外部依赖，也可以这么解决。付出的额外成本是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一个&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;定义&lt;/li&gt;
  &lt;li&gt;一个调用真实接口的&lt;code class=&quot;highlighter-rouge&quot;&gt;implementation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个成本是非常小的，因为&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;的定义就是原方法签名的拷贝，而&lt;code class=&quot;highlighter-rouge&quot;&gt;implementation&lt;/code&gt;只是简单地返回真实调用。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;MockMatrixClient&lt;/code&gt;怎么搞后面再介绍。&lt;/p&gt;

&lt;p&gt;值得讨论的问题是，换位思考下，作为服务提供方时，我们是否应该提供&lt;code class=&quot;highlighter-rouge&quot;&gt;interface+implementation&lt;/code&gt;，而不是只提供&lt;code class=&quot;highlighter-rouge&quot;&gt;implementation&lt;/code&gt;？&lt;/p&gt;

&lt;p&gt;乍一看，前者更好。但更推荐后者，因为一个&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;往往有多个方法，但多数场景下，并不会用到全部方法。一个大而全的&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;反而让使用方背负过多负担。使用方根据需求定义自己的小&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;，成本更低。&lt;/p&gt;

&lt;h3 id=&quot;用例的编写&quot;&gt;用例的编写&lt;/h3&gt;

&lt;h4 id=&quot;收集用例&quot;&gt;收集用例&lt;/h4&gt;

&lt;p&gt;产品 &amp;lt; 研发 &amp;lt; QA：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;产品给规则(和典型case)&lt;/li&gt;
  &lt;li&gt;研发单测覆盖主干case&lt;/li&gt;
  &lt;li&gt;QA覆盖各种情形的case&lt;/li&gt;
  &lt;li&gt;bug反馈&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;建议：当修复qa反馈的bug后，应该考虑落地成代码内的测试用例，方便后续回归。（当因某个路段护栏坏了掉进沟里，把车吊上起之后，还想把护栏补一补，对吧？）&lt;/p&gt;

&lt;h4 id=&quot;保持独立&quot;&gt;保持独立&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;一个测试方法对应一个case&lt;/li&gt;
  &lt;li&gt;用例之间不共享数据、状态&lt;/li&gt;
  &lt;li&gt;线程安全，可并发跑测试&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;测试代码与业务代码分离&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;文件独立，测试代码写在XXX_test.go里&lt;/li&gt;
  &lt;li&gt;包独立, 业务为&lt;code class=&quot;highlighter-rouge&quot;&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;/code&gt;，对应测试应为&lt;code class=&quot;highlighter-rouge&quot;&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service_test&lt;/span&gt;&lt;/code&gt;。
    &lt;ul&gt;
      &lt;li&gt;独立包的好处是编译后成的生产用的可执行文件内不会包括&lt;code class=&quot;highlighter-rouge&quot;&gt;test&lt;/code&gt;相关代码。&lt;/li&gt;
      &lt;li&gt;减少包互相依赖的可能性。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;单测覆盖率&quot;&gt;单测覆盖率&lt;/h3&gt;

&lt;p&gt;命令：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;cd app/service&lt;/li&gt;
  &lt;li&gt;go test -coverprofile=c.out&lt;/li&gt;
  &lt;li&gt;go tool cover -html=c.out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意，覆盖率是statements，不是branches。&lt;/p&gt;

&lt;p&gt;多少合适？&lt;/p&gt;

&lt;p&gt;覆盖率不是追求的目标，作为研发，&lt;strong&gt;覆盖主干case&lt;/strong&gt;是目标。但这个目标不易量化和评价。因此暂且用覆盖率代替，个人想法：60%及格，80%良好。&lt;a href=&quot;https://github.com/avelino/awesome-go&quot;&gt;awesome-go&lt;/a&gt;要求项目测试覆盖率达到80% 以上才有资格入选。Go社区两个常用库的覆盖率情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;gin: 98%&lt;/li&gt;
  &lt;li&gt;gorm: 78%&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;mock生成工具&quot;&gt;mock生成工具&lt;/h3&gt;

&lt;p&gt;利用&lt;a href=&quot;https://github.com/golang/mock&quot;&gt;mockgen&lt;/a&gt;，只要有&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;，就能自动生成&lt;code class=&quot;highlighter-rouge&quot;&gt;implementation&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mockgen &lt;span class=&quot;nt&quot;&gt;-source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;search.go &lt;span class=&quot;nt&quot;&gt;-package&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;thirdcall &lt;span class=&quot;nt&quot;&gt;-destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;search_mock.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对&lt;code class=&quot;highlighter-rouge&quot;&gt;search.go&lt;/code&gt;内的&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;进行mock，生成实现&lt;code class=&quot;highlighter-rouge&quot;&gt;search_mock.go&lt;/code&gt;，其package为&lt;code class=&quot;highlighter-rouge&quot;&gt;thirdcall&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;注意，&lt;code class=&quot;highlighter-rouge&quot;&gt;search_mock.go&lt;/code&gt;为自动生成的代码，任何时候都不应人工修改它。当源&lt;code class=&quot;highlighter-rouge&quot;&gt;interface&lt;/code&gt;有变化时，应重新执行上述命令。&lt;/p&gt;

&lt;p&gt;mock生成的代码虽然是固定的，其行为表现却是高度可制定的。可以在测试代码里直接指定被Mock对象的行为，如：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// 执行SearchItem时传ctx和任意参数，都返回指定的resp和nil：&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;algoService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EXPECT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;SearchItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// 执行SearchItem时传ctx和任意参数，sleep两分钟，然后返回nil, nil。模拟服务超时。&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;algoService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;thirdcall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewMockAlgorithmService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;algoService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;EXPECT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;SearchItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gomock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;DoAndReturn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchpage0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SearchItemRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;searchpage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;KitcSearchItemResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Minute&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;AnyTimes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;测试的成本&quot;&gt;测试的成本&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;项目初期，更长的开发时间&lt;/li&gt;
  &lt;li&gt;更高的技能要求，对语言、对设计&lt;/li&gt;
  &lt;li&gt;更煎熬的心理：
    &lt;ul&gt;
      &lt;li&gt;长短期思维的博弈&lt;/li&gt;
      &lt;li&gt;个人vs团队,前人vs后人&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 26 Apr 2020 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2020/04/26/go-unit-test.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2020/04/26/go-unit-test.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>协程在手，并发不愁</title>
        <description>&lt;ul&gt;
  &lt;li&gt;概念与实现&lt;/li&gt;
  &lt;li&gt;协程的使用&lt;/li&gt;
  &lt;li&gt;vs线程进程&lt;/li&gt;
  &lt;li&gt;协程的优点&lt;/li&gt;
  &lt;li&gt;kotlin的协程&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(2019.09.20在团队的分享)&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;概念与实现&quot;&gt;概念与实现&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;运行在单线程中的&lt;strong&gt;并发&lt;/strong&gt;代码段，或：&lt;/li&gt;
  &lt;li&gt;一种&lt;strong&gt;用户态&lt;/strong&gt;的轻量级线程&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;bonus:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;并发 vs 并行&lt;/li&gt;
  &lt;li&gt;用户态 vs 内核态&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;概念与实现-1&quot;&gt;概念与实现&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;属性&lt;/strong&gt;: 拥有自己的&lt;strong&gt;寄存器上下文&lt;/strong&gt;和&lt;strong&gt;栈&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;行为&lt;/strong&gt;: 切换时，保存当前状态或恢复之前状态&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可不必与特定的线程绑定，可以在一个线程中暂停，并在另一个线程中恢复&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;bonus: 对比尾递归&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;概念与实现-2&quot;&gt;概念与实现&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;有栈协程(Stackful)：有自己的调用栈&lt;/li&gt;
  &lt;li&gt;如&lt;code class=&quot;highlighter-rouge&quot;&gt;Golang&lt;/code&gt;，栈内存可以根据需要进行扩容和缩容，最小一般为内存页长 4KB。&lt;/li&gt;
  &lt;li&gt;无栈协程(Stackless)：没有自己的调用栈&lt;/li&gt;
  &lt;li&gt;如&lt;code class=&quot;highlighter-rouge&quot;&gt;Python&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;Kotlin&lt;/code&gt;。上下文通过CPS(continuation-passing-style)保存，在&lt;code class=&quot;highlighter-rouge&quot;&gt;Kotlin&lt;/code&gt;中，就是一个&lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-continuation/index.html&quot;&gt;Continuation&lt;/a&gt;类，可想像成&lt;code class=&quot;highlighter-rouge&quot;&gt;Callback&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;bonus: &lt;code class=&quot;highlighter-rouge&quot;&gt;CPS&lt;/code&gt; vs &lt;code class=&quot;highlighter-rouge&quot;&gt;Direct Style&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的使用&quot;&gt;协程的使用&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;python&lt;/code&gt; 版&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;consumer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# 接收producer的传参，执行后面代码，直到再次碰到yield后返回producer
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[31;1m[consumer] %s&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m 消费 %s &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;producer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;obj1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# 发None启动消费者
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[32;1m[producer]&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[0m 生产 %s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;obj1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# 暂停producer，并切换到consumer
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;bonus: 对比子程序&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的使用-1&quot;&gt;协程的使用&lt;/h1&gt;

&lt;p&gt;单步调试，注意&lt;code class=&quot;highlighter-rouge&quot;&gt;send&lt;/code&gt;(1)之后跳到了&lt;code class=&quot;highlighter-rouge&quot;&gt;yield&lt;/code&gt;(2)：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201909/coroutine_trace.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的使用-2&quot;&gt;协程的使用&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;kotlin&lt;/code&gt;版：&lt;a href=&quot;https://pl.kotl.in/7s9CC8kSa&quot;&gt;playgroud&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;channel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Channel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;()&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;GlobalScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[producer] 生产 $x&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;GlobalScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[consumer] 消费 ${channel.receive()}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;bonus: 为何&lt;code class=&quot;highlighter-rouge&quot;&gt;python&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;kotlin&lt;/code&gt;版输出不同？&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的使用-3&quot;&gt;协程的使用&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/img/201909/pipe.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的使用-4&quot;&gt;协程的使用&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;子程序就是协程的一种特例。 —— Donald Knuth&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;或者说，协程是子程序的泛化。&lt;/p&gt;

&lt;h3 id=&quot;为什么&quot;&gt;&lt;em&gt;为什么?&lt;/em&gt;&lt;/h3&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;vs-线程进程&quot;&gt;vs 线程、进程&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;进程&lt;/strong&gt; 应用程序的启动实例，有代码和打开的文件资源、数据资源、独立的内存空间。最小的资源管理单元。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;线程&lt;/strong&gt; 从属于进程，有自己的栈空间。最小的执行单元。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;表面看它们是语言特性，本质却是操作系统能力，通过API暴露给用户使用。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;vs-线程进程-1&quot;&gt;vs 线程、进程&lt;/h1&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;vs-线程进程-2&quot;&gt;vs 线程、进程&lt;/h1&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;vs-线程进程-3&quot;&gt;vs 线程、进程&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;谁来调度&lt;/li&gt;
  &lt;li&gt;何时切换&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;线程/进程是os通过调度算法，保存当前的上下文实现暂停，重新开始的地方不可预期。每次CPU计算的指令数量和代码跑过的CPU时间有关，跑到os分配的cpu时间到达后就会被os强制挂起。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Coroutine&lt;/code&gt;是编译器的魔术，通过插入相关的代码使得代码段能够实现分段式的执行，重新开始的地方是&lt;code class=&quot;highlighter-rouge&quot;&gt;yield&lt;/code&gt;关键字指定的，一次一定会运行到&lt;code class=&quot;highlighter-rouge&quot;&gt;yield&lt;/code&gt;语句，所以本质是程序员决定何时挂起。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的优点&quot;&gt;协程的优点&lt;/h1&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;c1&quot;&gt;// 可以轻松执行以下代码：&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;GlobalScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;300000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;${Thread.currentThread().name} is busy calculating&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 视机器配置，可能无法运行下面代码：&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;300000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;${Thread.currentThread().name} is busy calculating&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的优点-1&quot;&gt;协程的优点&lt;/h1&gt;

&lt;h3 id=&quot;开销小&quot;&gt;开销小&lt;/h3&gt;

&lt;p&gt;线程的时间成本可以拆解为：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;切换本身&lt;/strong&gt;的开销，主要是&lt;strong&gt;寄存器保存和恢复&lt;/strong&gt;的成本，可腾挪的余地非常有限；&lt;/li&gt;
  &lt;li&gt;执行体的&lt;strong&gt;调度&lt;/strong&gt;开销，主要是如何在大量已&lt;strong&gt;准备好的执行体中选出谁获得&lt;/strong&gt;执行权；&lt;/li&gt;
  &lt;li&gt;执行体之间的&lt;strong&gt;同步与互斥&lt;/strong&gt;成本。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;线程的空间成本可以拆解为：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;执行体的&lt;strong&gt;执行状态&lt;/strong&gt;；&lt;/li&gt;
  &lt;li&gt;TLS（线程&lt;strong&gt;局部存储&lt;/strong&gt;）；&lt;/li&gt;
  &lt;li&gt;执行体的&lt;strong&gt;堆栈&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;显然，上述成本的比重各不相同。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;默认情况下Linux 线程在数MB 左右，其中最大的成本是堆栈。如果一个线程 1MB，那么有 1000 个线程就已经到 GB 级别了。&lt;/li&gt;
  &lt;li&gt;执行体的调度开销，以及执行体之间的同步与互斥成本，也是一个不可忽略的成本。单位成本看起来不大，但扛不住次数太多。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;http://web.eece.maine.edu/~vweaver/projects/perf_events/overhead/fastpath2013_perfevents.pdf#page=4&quot;&gt;for core2 and modern Linux context switch may cost 5-7 microseconds.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;bonus: 每秒多少cs是合理的？&lt;/p&gt;

&lt;h3 id=&quot;不易出错&quot;&gt;不易出错&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;共享变量的同步锁&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;线程的任务分配是&lt;strong&gt;抢占式&lt;/strong&gt;，存在共享变量时，需要使用锁来保证线程间数据安全。
协程间任务分配是&lt;strong&gt;分发式&lt;/strong&gt;，本身无此问题，但&lt;strong&gt;如果运行在多线程中，依然有问题&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;No silver bullet&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;协程的应用&quot;&gt;协程的应用&lt;/h1&gt;

&lt;p&gt;协程主要应用场景是高性能的&lt;strong&gt;网络服务&lt;/strong&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;来自客户端的请求包和服务器的返回包，都是网络IO；&lt;/li&gt;
  &lt;li&gt;过程中，需要访问存储来保存和读取自身的状态，也涉及本地或网络IO。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果用多线程来实现，如上所述，成本高，易出错。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;kotlin协程&quot;&gt;Kotlin协程&lt;/h1&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EmptyCoroutineContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineStart&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;DEFAULT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;suspend&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoroutineScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Unit&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Job&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;CoroutineContext&lt;/code&gt;: 可以理解为协程的上下文，其中一个实现&lt;code class=&quot;highlighter-rouge&quot;&gt;CoroutineDispatcher&lt;/code&gt;支持4种线程模式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dispatchers.Default, 默认线程池, &lt;code class=&quot;highlighter-rouge&quot;&gt;CPU-heavy&lt;/code&gt;任务&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html&quot;&gt;Dispatchers.IO&lt;/a&gt;, 适合&lt;code class=&quot;highlighter-rouge&quot;&gt;IO-heavy&lt;/code&gt;任务&lt;/li&gt;
  &lt;li&gt;Dispatchers.Main, 主线程&lt;/li&gt;
  &lt;li&gt;Dispatchers.Unconfined, 没指定，就是在当前线程&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-kotlin highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;kotlinx.coroutines.*&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;GlobalScope&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;launch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// launch a new coroutine in background and continue&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// non-blocking delay for 1 second (default time unit is ms)&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// print after delay&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// main thread continues while coroutine is delayed&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// block main thread for 2 seconds to keep JVM alive&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;参考资料&quot;&gt;参考资料&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md&quot;&gt;Kotlin Coroutines Design Proposal&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/reference/coroutines/basics.html&quot;&gt;Kotlin Coroutine Reference&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kotlinlang.org/docs/reference/coroutines/shared-mutable-state-and-concurrency.html&quot;&gt;Shared mutable state and concurrency&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do&quot;&gt;What does the “yield” keyword do&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=YrrUCSi72E8&quot;&gt;Deep Dive into Coroutines on JVM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/zingp/p/5911537.html&quot;&gt;协程及Python中的协程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jianshu.com/p/76d2f47b900d&quot;&gt;kotlin - Coroutine 协程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.itboth.com/d/J3uE7v/context-cpu-switch-context-switch-java&quot;&gt;从Java视角理解CPU上下文切换&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 20 Sep 2019 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2019/09/20/coroutine.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2019/09/20/coroutine.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>Queuing theory——排队的艺术</title>
        <description>&lt;p&gt;近期团队遇到相关场景，把2016年在堆糖做过的分享搬出来做了点完善，再分享一次：&lt;a href=&quot;/pdf/queuing-theory.pdf&quot;&gt;排队的艺术&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 29 Jan 2019 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2019/01/29/queuing-theory.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2019/01/29/queuing-theory.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>像代数推导一般</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;In math we trust——张首晟&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;纯函数已然为函数式编程赢得了巨大优势，但这还不够。纯函数还只是一块基石，它和另一块基础——数据不可变，一道构筑了函数式编程蔚为壮观的独特风景：代数推导。这是函数式编程最吸引我的地方，因为，数学终于从编程语言或者计算机科学的背后走向前台，让我们可以像代数推导一般，进行现实问题的建模和编码。根据张首晟教授的说法，区块链的信任机制本质上是对数学的信任。我想，这一样适用于函数式编程，数学的严谨性让模型和代码质量有了更多保障。而数学的抽象，让貌似不同的事物有了变成一样的可能，提高了代码的普适性。&lt;/p&gt;

&lt;p&gt;学习函数式编程，与其说是学习某门具体的语言，不如说是学习如何运用代数结构描述现实事物，运用代数式子表达现实逻辑，运用代数推导求解现实问题。&lt;/p&gt;

&lt;h5 id=&quot;面向表达式语言&quot;&gt;面向表达式语言&lt;/h5&gt;

&lt;p&gt;代数，简而言之，研究的是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对象的&lt;strong&gt;集合&lt;/strong&gt;，这里的对象不是编程语言里的对象，反倒是更像类型。&lt;/li&gt;
  &lt;li&gt;用对象创建新对象的&lt;strong&gt;操作&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如，数构成集合，加减乘除构成操作。&lt;/p&gt;

&lt;p&gt;介绍纯函数时，我们知道了数学上的运算可以写成纯函数，如&lt;code class=&quot;highlighter-rouge&quot;&gt;f(x) = 2 * x + 3&lt;/code&gt;可以写成&lt;code class=&quot;highlighter-rouge&quot;&gt;def f(x: Int) = x * x + 3&lt;/code&gt;，&lt;code class=&quot;highlighter-rouge&quot;&gt;f(x, y) = x + y&lt;/code&gt;写成&lt;code class=&quot;highlighter-rouge&quot;&gt;def f(x: Int, y: Int) = x + y&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;集合里的对象，也就是语言的类型，后面再辟专门的章节讲述。先看看看代数的推导过程，蕴含了哪些值得借鉴的做法。以下是一道简单代数题的求解过程：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;已知函数f(x + 2) = 2x^2 - 5x + 1，求f(x)。
解：
设 t = x + 2, 则x = t - 2
∴ f(t) = 2 * (t - 2)^2 - 5 * (t - 2) + 1
∴ f(t) = 2 * (t^2 - 4 * t + 4) - 5 * t + 10 + 1
∴ f(t) = 2 * t^2 - 13 * t + 19
即，f(x) = 2 * x^2 - 13 * x + 19
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以发现：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;首先，在整个推导过程中，保持变量含义的不变，从头到尾的一致性。不会突然令&lt;code class=&quot;highlighter-rouge&quot;&gt;x = 9&lt;/code&gt;或别的值，当需要表达其它量时，启用新的变量t，并表述清楚t的来源&lt;code class=&quot;highlighter-rouge&quot;&gt;t = x + 2&lt;/code&gt;，而不是复用原来的变量：&lt;code class=&quot;highlighter-rouge&quot;&gt;x = x + 2&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有人把变量的&lt;strong&gt;不可变性&lt;/strong&gt;视为函数式编程最重要的特性，因为，就像上述推导一样，它喻示着过程的可省视性。仅仅通过阅读代码，就可以理解整个逻辑，而不是指令式编程那样，必须在脑子里”运行”代码。当大脑无法跟踪太多变量的变化时，不得不借用纸笔或调试工具来debug，只消几层的if判断就足以把人绕晕。有了不可变性，可以放心地将代码的各个部分分离开，独立解读，脑子里要保留、跟踪的信息几乎不随代码规模的增长而增加。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;其次，在值相同的情况下，符号和表达式可以互相替代，如&lt;code class=&quot;highlighter-rouge&quot;&gt;t&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;x + 2&lt;/code&gt;在通篇任意位置可以互相替代，这种替代在数学上叫&lt;strong&gt;换元&lt;/strong&gt;，用于辅助推导，并不改变语义。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;若要在代码层面“换元”，则要求满足引用透明，引用透明辅助编译器对代码做优化，如果没有引用透明，编译器无法自由地替换“表达式“和“值”，因为前后两次的执行结果可能不同，哪怕结果完全一样，程序只能无奈地一次次对该表达式做求值操作。也不能改变表达式的顺序，因为前后两次的求值结果可能不一样，进而限制了程序的并发性。&lt;/p&gt;

&lt;p&gt;代数也是一门语言，它用数学模型描述现实世界，并解决逻辑问题。如果把它也看成一门编程语言，它应该归入什么类别呢？大概“面向表达式语言”是比较合理的分类。没错，表达式！看上面代数题的求解过程，本质是表达式的归约操作、合并同类项操作。所谓归约，就是将每个最内层可归约表达式用其值来替换，这样又形成了新的最内层可归约表达式，然后再对其进行归约，最后，整个程序全部被归约，仅留下最终结果。&lt;/p&gt;

&lt;p&gt;所以，用它解决问题的过程应该是，用数字或符号描述现实世界，如直角三角形的两和直角边长度分别为a和b，依据放之四海而皆准的公理，如勾股定理，构建它们的逻辑关系，如斜边&lt;code class=&quot;highlighter-rouge&quot;&gt;c^2 = a^2 + b^2&lt;/code&gt;
，再用加减乘除等运算操作，简化、规约表达式，求得斜边长度。这一过程中，我们并没有过问这些变量寄存在哪里（有人直接在脑子里算，也有人要在纸上演算），也不关心它们是否复用了同一块内存，会不会被其它人错误地读写了。我们关心的是想要什么，得到结果的逻辑是什么，而不是如何赋值、如何改写变量。前者是人类擅长的，后者是机器擅长的。我们应让机器服务人类，而不是反过来，人类迁就机器。为此，我们需要一个工具，把高阶的人类思维忠实地转换为低阶的机器语言。&lt;/p&gt;

&lt;p&gt;函数式语言就是这样的工具，它有一个少为人知的别名，正是“面向表达式语言”。&lt;strong&gt;表达式&lt;/strong&gt;是值和逻辑（函数）的组合，讲究如何编织逻辑以创建新的值，执行的过程即是规约化得到最终结果的过程，如：&lt;code class=&quot;highlighter-rouge&quot;&gt;val total = sum(list)&lt;/code&gt;，sum是一个求和的运算逻辑，通过这个逻辑得到值&lt;code class=&quot;highlighter-rouge&quot;&gt;sum&lt;/code&gt;。 与之相反的是&lt;strong&gt;指令式&lt;/strong&gt;，用一系列的执行单元，告诉计算机如何改变指令计数器、数据存储器、当前计数状态等，通过状态的改变得到最终结果，如：&lt;code class=&quot;highlighter-rouge&quot;&gt;list.sum()&lt;/code&gt;，sum是个运算指令，它不返回计算的值，而是改变了某个状态，通过读取这个状态的地址得到结果。&lt;/p&gt;

&lt;p&gt;指令式语言让我们从低级语言的内存、栈、寄存器等概念中解脱，却依然是指令运行过程的封装：循环、锁、线程、并发。函数式语言则进一步抽象，更多地让编译器、解析器处理这些概念，让人腾出更多精力关注数据模型、关注业务逻辑。&lt;/p&gt;

&lt;h5 id=&quot;函数的组合&quot;&gt;函数的组合&lt;/h5&gt;

&lt;p&gt;他山之石，可以攻玉。我们回头再挖掘下代数里可以借鉴的特质。&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;如果f(x)=3x-1，且g(x)=x^3+2，那么f(g(3))的值是多少？
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;间接的做法是，先求得&lt;code class=&quot;highlighter-rouge&quot;&gt;g(3) = 29&lt;/code&gt;，进而&lt;code class=&quot;highlighter-rouge&quot;&gt;f(g(3)) = f(29) = 86&lt;/code&gt;。那么，有没有直接的做法呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/compose_func.png&quot; alt=&quot;compose&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为了直接计算&lt;code class=&quot;highlighter-rouge&quot;&gt;f(g(x))&lt;/code&gt;，我们得&lt;strong&gt;组合&lt;/strong&gt;（Composition）f和g这两个函数。由于&lt;code class=&quot;highlighter-rouge&quot;&gt;g(x)&lt;/code&gt;是&lt;code class=&quot;highlighter-rouge&quot;&gt;f(x)&lt;/code&gt;的输入，可以把&lt;code class=&quot;highlighter-rouge&quot;&gt;f(x)&lt;/code&gt;的各个&lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;替换为&lt;code class=&quot;highlighter-rouge&quot;&gt;g(x)&lt;/code&gt;，得到&lt;code class=&quot;highlighter-rouge&quot;&gt;f(g(x)) = 3g(x)-1 = 3x^3+5&lt;/code&gt;，即是图中绿线所表示的函数。&lt;/p&gt;

&lt;p&gt;在Scala里，&lt;code class=&quot;highlighter-rouge&quot;&gt;Function1&lt;/code&gt;接口定义了&lt;code class=&quot;highlighter-rouge&quot;&gt;compose&lt;/code&gt;方法和&lt;code class=&quot;highlighter-rouge&quot;&gt;andThen&lt;/code&gt;方法，均可以用来组合单参数函数。&lt;code class=&quot;highlighter-rouge&quot;&gt;f(g(x))&lt;/code&gt;可以写成：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;f: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;g: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compose&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fg2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;andThen&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意，”_“表示Eta expansion。&lt;/p&gt;

&lt;p&gt;组合看上去再自然不过了，以至于我们不觉得有什么特别或厉害的。然而，稀松平常的组合却透露了编程的本质。&lt;/p&gt;

&lt;p&gt;我们是如何解决问题的？每当碰到复杂的问题，就把它分解为更小的问题，直到问题足够小，小到我们可以写代码解决它。然后，再组合这些小颗粒度的代码，形成原始问题的解决方案。问题规模的限制并不是计算机强加给我们的，而是人类大脑的限制。1956年发表的著名论文《神奇的数字 7，加减 2：人类信息加工容量的某些局限》指出，不管记忆的内容怎么变化，是数字，字母，数字 + 字母，还是从 1000 个单音节单词中随机选，人们在记忆这些材料的时候，大概只能记住 5-9 个。所以，我们必须把一次性可以处理的代码块规模控制在大脑可以接受的程度。所谓”优雅代码”，不是结构清晰、简洁所能概括的，更切中要害的说法是，可被有限的大脑带宽处理。&lt;/p&gt;

&lt;p&gt;分解本身不是目的，解决问题需要重组这些子块，如果无法重新组合，分解的意义就不存在。什么样的函数能组合呢？显然，一个函数的输出类型必须和另一个函数的输入类型保持一致。至少在函数式语言内，这是”类型”概念存在的根本原因。有关类型，我们后面再详谈。&lt;/p&gt;
</description>
        <pubDate>Mon, 29 Oct 2018 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2018/10/29/fp-algebra.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2018/10/29/fp-algebra.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>纯函数的好处</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;哦，雪白的纯朴具有何等大的威力！——济慈&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;纯函数的两个优良品质，为它赢得了很多优势。&lt;/p&gt;

&lt;h5 id=&quot;引用透明&quot;&gt;引用透明&lt;/h5&gt;

&lt;p&gt;以直接用函数运行的结果替代函数表达式本身而不改变程序的最终结果，称为引用透明（Referential Transparent）。如：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;length&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;length&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果把第二处”hello”.length换成x，或者反过来，把第二处y换成”world”
.length，并不改变z的值。这似乎是很显然的事，却是以length是纯函数为前提的，换成随机数生成函数、获取当前时间的函数，就不能替换。如果改变函数范围外的变量，也会破坏引用透明，如：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;t1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;t1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;//false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;记忆&quot;&gt;记忆&lt;/h5&gt;

&lt;p&gt;引用透明很好，可避免函数的反复执行，但毕竟还是需要人工替换，如果函数自身能智能地实现一次计算，多处使用就好了。可喜的是，对于纯函数，编译器可以实施名为”记忆”（Memoization)的优化技术，正是达到这个效果。特别是执行很费时的函数，把结果缓存起来，当下次用相同入参调用时，便可直接返回缓存的结果，不再重新计算，大大提高程序的效率。当然，通过把入参和结果作为键值对保存在Map中，我们可以自己实现缓存过程，方便控制缓存策略。用通常不太缺乏的内存空间换取宝贵的计算时间，这是计算机科学里常用的”伎俩”。&lt;/p&gt;

&lt;h5 id=&quot;缓求值&quot;&gt;缓求值&lt;/h5&gt;

&lt;p&gt;缓求值(Lazy Evaluation)，指尽可能地推迟求解表达式，是函数式编程语言常见的一种特性。Scala通过lazy关键字声明缓求值。缓求不会在遇到表达式时就触发运行，而是在用到的时候才真实计算。缓求在记忆之外，为昂贵的运算提供了另一种优化手段。如，以下判断质数的函数，虽然表达式&lt;code class=&quot;highlighter-rouge&quot;&gt;isPrimeSlow(num)&lt;/code&gt;在if判断前面，但只有当输入为奇数时，才会真正被执行。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isPrimeSlow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;until&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;forall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isPrime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Boolean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isPrimeSlow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;缓求值在集合中的应用更能体现其价值。熟悉Python的同学知道生成器（generator）的概念，应用了缓求了集合与之类似，对于很大的集合，不是一次性生成（或者说根本就没有生成），只是需要的时候才吐出所需的元素。对比intsEager和intsLazy执行效果，便会发现对于寻找大于5的第一个数6而言，intsEager一次性生成了10万个元素，但比6大的元素对于求解结果毫无帮助，白白浪费了内存，简直是暴殄天物。如果把这个形式推到极限，对于有无限个元素的集合，似乎只有缓求方式才能胜任了。这下我们发现了，相比于”记忆”，缓求倒过来了，它是以临时计算来免除需要事先准备好的内存。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;intsEager&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;intsEager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;intsLazy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;until&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100000&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;intsLazy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以上，是纯函数为语言提供的额外特性，可以认为是一些与身俱来的优势。接下来分析纯函数为编程实践带来的好处，如何帮助提高代码质量，提高开发效率。&lt;/p&gt;

&lt;h5 id=&quot;纯函数更易推断&quot;&gt;纯函数更易推断&lt;/h5&gt;

&lt;p&gt;纯函数的签名已经把它的目的和盘托出了，不需要细看内部实现，就可以相信它没有多干其它小动作，所以不会有超乎签名的副作用和意外效果。经验不足的同学甚至在java的getter类方法中做了写操作：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;visit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;哪怕老手也难免有被糊弄的时候。比如，出乎意料，下面这段代码会访问网络，因为URL的hashCode方法会尝试解析域名。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;notPure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;URL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://example.com&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Sets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;newHashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MalformedURLException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//ignored&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;纯函数就没有这些问题，这是多么大的心智减负。&lt;/p&gt;

&lt;h5 id=&quot;纯函数更易组合&quot;&gt;纯函数更易组合&lt;/h5&gt;

&lt;p&gt;函数的组合（compose)，简单而言，就是把若干函数按一定顺序拼接组成新函数。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compose&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;B&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;后续会介绍，组合在函数式编程中有着举足轻重的地位。且看组合的具体应用——优雅的链式调用：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doThis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;thenThis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;andThenThis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;doThisToo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;andFinallyThis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果不是纯函数，很难想象可以放心地写出这段代码。doThis(a)如果改变了a，会怎么影响doThisToo的行为？如果各步骤共享了状态，拋了异常，代码的整体行为表现是怎样的？正确评估这些问题极其消耗心力。而纯函数的确定性，可以让后续步骤放心地依赖前面步骤的输出，代码的作用如字面语义一样跃然纸上。&lt;/p&gt;

&lt;h5 id=&quot;纯函数更易测试和定位问题&quot;&gt;纯函数更易测试和定位问题&lt;/h5&gt;

&lt;p&gt;唯一能影响函数行为的只有输入参数，没有不为人知的魔法，没有数据库、磁盘读写，所以不用关心函数外的环境因素，进而在不同人的电脑上debug效果一样。同理，指令式编程过程中费时费力的单步调试，此时不再重要了，关心函数的输出即可。&lt;/p&gt;

&lt;h5 id=&quot;纯函数更易并发&quot;&gt;纯函数更易并发&lt;/h5&gt;

&lt;p&gt;纯函数保证值不变性，值一旦生成就不再改变，无论是同一线程还是多个线程，都不允许修改。所以连锁都免了，更没有死锁或数据竞态问题了。&lt;/p&gt;

&lt;p&gt;因为没有副作用，从出入参就可以看出函数之间的依赖关系，如果前后没有数据依赖，其先后顺序便可以交换，或者在多个线程中独立运行而不相互干涉。如下面代码中，f、g、h在字面上的先后顺序是固定的，但编译器完全可以根据需要改动实际执行顺序，或者优化为并发执行。如果它们有副作用，比如暗地里修改了共享的状态，就不能这么轻巧地优化了。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们从回顾众所周知的函数开始，再区分纯函数和非纯函数，进而专注于纯函数式的特点、好处。接下来，如何不是特别说明，提到函数时指的就是纯函数。函数式编程，更准确的说法应该是”纯函数”式编程。纯函数所带来的好处构成了函数式编程的核心优势，这点后续将有更多体现。&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Oct 2018 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2018/10/20/why-pure-fp.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2018/10/20/why-pure-fp.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>纯函数的特质</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;大人者，不失其赤子之心者也。——孟轲&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;数学家比较幸福的一点是，他们研究的函数是”纯净”的。没有异常，没有意外，同样的入参一定返回同样的结果。如果不是，那就是人出错了。&lt;/p&gt;

&lt;p&gt;编程里的函数就没这么纯粹，会遇到无法处理的输入，会在中途拋出异常，在返回值之外会留下别的痕迹。这些不确定性因素，使得人们不能放心大胆地使用函数。因此，需要一个标准，把有这类特点的问题函数识别出来，让”纯函数”发扬光大。&lt;/p&gt;

&lt;p&gt;“纯函数”具备以下性质：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt; 相同的入参，一定返回相同结果，不受内部隐藏状态、隐藏值的影响，也不受任何I/O的影响。多次调用的结果保持一致，说明纯函数具有幂等性。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;无副作用&lt;/strong&gt; 运行函数不能引发可辨别的副作用或输出，如修改可变对象，或写入I/O设备。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;展开来讲，纯函数应该避免：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;读取函数之外的任何值，如所在类的属性，或全局变量。&lt;/li&gt;
  &lt;li&gt;修改函数之外的任何对象，如所在类的属性或全局变量。&lt;/li&gt;
  &lt;li&gt;依赖任何I/O操作，如从本地文件、数据库、API、屏幕等途径读取或写入内容。&lt;/li&gt;
  &lt;li&gt;修改输入参数。&lt;/li&gt;
  &lt;li&gt;拋出异常。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;回顾之前的定义，对于数学意义上的函数，定义域的元素总能唯一对应值域的元素，加上数学是一种逻辑符号语言，自然没有副作用。那么不难理解，表达这些数学含义的代码函数（scala.math._里定义的函数）也应该是纯函数，如：square, min, max, abs等。或者你自定义的计算，如：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;squareSum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;很多常用类的方法，并不改变方法之外的类属性，也不进行I/O操作，也是纯函数。如String的charAt, isEmpty, length；集合类型的drop（没错，drop也是）, filter, map等。&lt;/p&gt;

&lt;p&gt;再来看看纯函数的反例。同样是数学上的运算，如果函数拋出异常，就不纯了，因为异常时没有和输入对应的结果。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;scala&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;x:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;y:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Int&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;scala&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;ArithmeticException&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;zero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也不是所有集合类的方法都是纯函数，foreach方法就是专门为副作用准备的。&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Unit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unit在scala中表示”没有东西”，如果一个函数接受了输入，但不返回任何结果，那么它做的工作就只能是”副作用”了。&lt;/p&gt;

&lt;p&gt;常见的非纯函数还有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;getDayOfWeek，getHour，getMinute等方法，因为不同时刻调用它们得到的结果不同。&lt;/li&gt;
  &lt;li&gt;scala.util.Random中用于生成随机数的nextInt，因为返回值依赖了输入之外的隐藏状态。&lt;/li&gt;
  &lt;li&gt;I/O操作，如&lt;code class=&quot;highlighter-rouge&quot;&gt;def println(x: Any): Unit&lt;/code&gt;和&lt;code class=&quot;highlighter-rouge&quot;&gt;def readLine(): String&lt;/code&gt;。所以，当发现签名的入参为空或出参为Unit时，就得擦亮眼睛了。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此外，面向对象语言里常见的setter方法，或改变了所在对象的状态的方法也不是纯函数，如：&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;pureFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addNum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Int&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至此，我们已经能辨别纯函数，可纯函数有什么好处呢？&lt;/p&gt;
</description>
        <pubDate>Fri, 05 Oct 2018 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2018/10/05/what-is-pure-function.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2018/10/05/what-is-pure-function.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>默克尔树（Merkle Tree）</title>
        <description>&lt;p&gt;默克尔树（Merkle Tree）的定义很简单，却是区块链技术领域的重要概念。它使得大规模的分布式区块网络成为可能，也让普通机器可以成为网络节点。&lt;/p&gt;

&lt;h3 id=&quot;什么是默克尔树&quot;&gt;什么是默克尔树&lt;/h3&gt;

&lt;p&gt;数据结构上，它是一种特殊的二叉树(也可以是多叉树)，设计如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;叶子节点的值是数据的哈希值。&lt;/li&gt;
  &lt;li&gt;非叶子节点的值是其所有子节点的哈希值。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/img/2018.3/merkle_tree.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图所示，&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;hash代表哈希算法，如MD5，SHA系列等，可把任意长度的数据转换为固定长度的哈希值。&lt;/li&gt;
  &lt;li&gt;L1, L2等是叶子节点对应的数据，直接作为哈希算法的输入。&lt;/li&gt;
  &lt;li&gt;非叶子节点的哈希输入为所有对应子节点哈希串的拼接。&lt;/li&gt;
  &lt;li&gt;最终形成唯一的根节点，称为默克尔根(Merkle Root).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上图叶子节点数为4，恰好每一层都有完整输入。如果叶子节点数量不是2的次方，即不能形成满二叉树，怎么处理呢？&lt;/p&gt;

&lt;p&gt;有多种处理“孤立”叶子节点的方式。一种方案是不断重复最后一个节点，直到整体数量达到2^n
个。这种方案的想法比较直接，缺点也很明显，多做了很多无用功。比较好的方法是，把“孤立”节点提升到更高层次中。具体提到哪一层，要看叶子节点的数量情况，下图展示了5-7个叶子节点的情况：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ┌───┴──┐          ┌────┴───┐            ┌─────┴─────┐
  ┌──┴──┐   │       ┌──┴──┐     │         ┌──┴──┐     ┌──┴──┐
┌─┴─┐ ┌─┴─┐ │     ┌─┴─┐ ┌─┴─┐ ┌─┴─┐     ┌─┴─┐ ┌─┴─┐ ┌─┴─┐   │
       (5)                 (6)                    (7)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;默克尔树的特性&quot;&gt;默克尔树的特性&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;对变动敏感&lt;/strong&gt;。任何细微的变动都会引发叶子节点哈希值的变化，依次向上传导，最终导致根节点的变动。因此，像普通哈希一样，可以用来验证数据拷贝的一致性。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;快速定位差异&lt;/strong&gt;。二叉树查询的时间复杂度为O(logN)，沿着根节点向下对比，可以非常高效地确定具体是哪个（些）叶子的数据有差异。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;默克尔证明&lt;/strong&gt;。如图，要证明数据d3在数据集中，只要知道节点c,i,n（白色节点）的值，即可通过重新计算节点d,j,m的值，进而计算根节点的值，再和给定的根节点对比，判断d3是否属于该数据集。不需要拿到所有数据，即可完成此验证。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/img/2018.3/merkle_proof.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;默克尔树的应用&quot;&gt;默克尔树的应用&lt;/h3&gt;

&lt;h4 id=&quot;p2p下载&quot;&gt;P2P下载&lt;/h4&gt;

&lt;p&gt;在p2p下载出现之前，整个文件的数据都从一个中心节点上获取。这个中心节点的资源和稳定性常常成为瓶颈点，一旦下载异常，整体文件都需要重新下载。p2p
网络出现后，一个大文件被分割成许多小块，编号后分布在不同的资源节点上，下载操作同时从多个节点上进行，每一块都有对应的哈希值，用于下载后的检验。就算一个块出错，只需要重新下载这小块就行，而不需要重新下载整个文件。&lt;/p&gt;

&lt;p&gt;问题是如何确保每一块的哈希值本身是正确的呢，在p2p
网络中，任何人都可以成为提供下载资源的节点，无法确保数据本身或其哈希值没被恶意修改。一种方式是由可靠的权威节点提供这些小块数据的哈希值，可以从做任意节点下载资源，但只从权威节点下载哈希值。这当然可行，但对权威节点的依赖还是太大了。&lt;/p&gt;

&lt;p&gt;默克尔树能解决这个问题呢？把小块数据作为叶子节点，构建默克尔树，只要根节点的哈希值从可信节点下载，剩下所有数据和节点哈希值都可以从任意节点下载。各个分支的值可以对其子树进行检测，根的值则可以对整个默克尔树进行检测。这样既可以各个分支并行独立处理，又确保了整个大文件的完整性。&lt;/p&gt;

&lt;h4 id=&quot;比特币交易验证&quot;&gt;比特币交易验证&lt;/h4&gt;

&lt;p&gt;比特币的设计里，区块头是不包含交易（Transaction）信息的，关于交易的数据只是交易构建的默克尔树的根节点。
&lt;img src=&quot;/img/2018.3/blockchain.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而比特币网络的轻量客户端（如钱包）又不存储完整的区块数据，仅下载区块头，就能验证某笔交易是否被打包到链上。这是如何做到的呢？客户端验证步骤如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;首先对交易数据进行哈希。&lt;/li&gt;
  &lt;li&gt;然后咨询完整节点：这个哈希值对应的交易是否在第index
个区块中？&lt;/li&gt;
  &lt;li&gt;完整节点的区块不会直接返回在或不在的结论，而是返回一个&lt;em&gt;默克尔证明&lt;/em&gt;（重新计算根节点所需的路径）。&lt;/li&gt;
  &lt;li&gt;客户端验证这个”默克尔证明”，即独立计算根节点的哈希值。&lt;/li&gt;
  &lt;li&gt;对比区块头中的哈希值与计算结果是否相同。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于n笔交易而言，路径节点数只要log2(n)，无论是查找还是数据传输都是一个巨大的效率提升。不妨试算一下，假设某个区块包含32768笔交易，每笔交易占256字节，则所有交易大小为8M
，而路径数为15，每个哈希占32字节，则路径大小仅为480字节，差距高达5个数量级。&lt;/p&gt;

&lt;p&gt;值得指出的是，比特币网络的任何节点都不会也没必要相信其它节点，节点只能依赖事先约定的协议独立地验证来自网络的数据。这个验证的最终基础正是数学。用张首晟的话说：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In math, we trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当然，默克尔树早在比特币之前就已经广泛使用了，分布式代码版本工具Git，以及开源数据库Apache Cassandra均有使用。&lt;/p&gt;

&lt;h3 id=&quot;默克尔树的实现&quot;&gt;默克尔树的实现&lt;/h3&gt;

&lt;p&gt;为了加深理解，用python实现了简单的默克尔树，包含构建、验证和查找功能。数据结构本身比较清楚明了，关键代码做了注释，这里不再讨论。可直接到&lt;a href=&quot;https://github.com/vitrun/pok/tree/master/merkletree&quot;&gt;github&lt;/a&gt;阅读源码。&lt;/p&gt;

&lt;h3 id=&quot;参考文档&quot;&gt;参考文档&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;https://github.com/adjoint-io/merkle-tree&lt;/li&gt;
  &lt;li&gt;https://en.wikipedia.org/wiki/Merkle_tree&lt;/li&gt;
  &lt;li&gt;https://brilliant.org/wiki/merkle-tree/&lt;/li&gt;
  &lt;li&gt;https://juejin.im/entry/5a2e1135f265da432f311168&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 08 Jul 2018 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2018/07/08/merkle-tree.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2018/07/08/merkle-tree.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>编程界的剑气之争</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/2017.4/kungfu.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;华山派曾有剑宗和气宗之分。双方你争我夺，势不两立。是权力之争，也是道统和武力之争。&lt;/p&gt;

&lt;p&gt;咱编程界也有指令式程序（如，面向对象）和函数式编程两派。他们的关系虽不至于剑拔弩张，却也相当紧张。表面是语言风格之争，其实是方法论之争。&lt;/p&gt;

&lt;p&gt;话说，当年高级语言诞生不久，场面就开始混乱了。面向对象派，又称bottom-up派，他们从底层硬件开始向上走，在不牺牲性能的前提下，逐步增加抽象层级，不断接近数学。Fortran, C/C++, Pascal, Java以及C#等等都属于这派。函数式编程派，又称top-down派，坚持从数学出发，逐渐减少抽象级别，以接近现实问题，并获得硬件支持，为了保持概念完整，牺牲了部分性能。其拥护者有Algo，Lisp，Smalltalk和Haskell等。&lt;/p&gt;

&lt;p&gt;双方坚持信仰，互相揭短。top-down说，哎呀，别这么快向硬件妥协，那会把选择局限在少数几个不可逆的设计中；而且，mutable太可怕了，尤其是在并发情况下，冷不防就来个意外。bottom-up派则说，别给我套这么多数学，我很难兼顾垃圾回收、函数调用……而且，性能、性能、性能，我不能牺牲任何性能；immutable太理想化了，毕竟现实世界在变，试想，如果date和random不变，调用它们有什么意义？&lt;/p&gt;

&lt;p&gt;计算机科学家Erik Meijer说，每当新语言出现时，他总会看看技术规范。这对他来说，也许就像喝着咖啡看报纸，轻松自如。但他在一次分享中说，有一门语言，他根本读不懂，一看就犯困，比什么安眠药都更有效。你可能会觉得那一定是门高深的语言，然而，这门语言就是很多人熟悉的Java8。他觉得lambda，method reference这些东西实在太复杂了。怎么办？每当碰到复杂的东西，他就听Leslie Lamport（图灵奖获得者）的话：“我们应该多用点数学。”&lt;/p&gt;

&lt;p&gt;他知道，大部分程序员都惧怕数学，就更别说如何用数学理解语言，如何在编码中应用数学。所以他上台演示，分享了《Category Theory, The essence of interface-based design》。老头子精力十足，对照着讲数学概念和函数式编程中的语言概念，很是精彩。时不时揶揄下Java：lambda演算早在1928年就提出了，Java作者Gosling一度声称没必要支持lambda，但最终还是在Java8中支持了，这是为什么？因为我们畏惧数学。但我们不应该畏惧，搞数学的人都是很聪明的，我们应该从他们身上偷师点东西。&lt;/p&gt;

&lt;p&gt;年迈的Java徘徊良久，终于接纳函数式编程；年轻的Scala和F#是与身俱来的多面手，两边讨好；新生的Kotlin长了一张面向对象的脸，却又兼俱一点函数式气质。争吵了近70年，两派终于开始弥合了。&lt;/p&gt;

&lt;p&gt;纵是阳春白雪， 也有下里巴人的时候。多核时代下并发需求的普及，以及日益复杂的软件架构，使得指令式编程力不从心。我们需要更有力的武器。Brian Beckman说，我们将无可避免地接触monad，因为函数式编程正迎面呼啸而来。&lt;/p&gt;

&lt;p&gt;这段历史似曾相识，关于剑气宗的高下，岳不群有段论述：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;剑宗功夫易于速成，见效极快。大家都练十年，定是剑宗占上风；各练二十年，那是各擅胜场，难分上下；要到二十年之后，练气宗功夫的才渐渐的越来越强；到得三十年时，练剑宗功夫的便再也不能望气宗之项背了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但我们是文明的现代人，何必争个你死我活。我们要融合，要扬长避短。总有一天，犹太教、基督教和伊斯兰教的朋友们会在他们共同的圣地——耶路撒冷，愉快地生活的，是不是？哦弥陀佛！&lt;/p&gt;

&lt;p&gt;有个问题摆在所有程序员面前：如何提高软件质量？这是个可以写好几本书的话题。我敢打包票的是，这些书都会讲code review、unit test、 QA……其中单元测试（或者包括集成测试）的争议最大。写测试确实费时费力，又不一定讨好。我认识的人中就有坚决肯定和坚决否定两种极端。我认为这是个性价比的问题，如果能自动生成单元测试就好了。于是，一度想用AI搞个这样的项目（事实是确实有人在研究这个，比如randoop，曾测出了jdk的bug）。但转念一想，又打住了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;People are always more willing to work harder than they have to than to work smarter than they’re able to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;质量的关键并不在测试之类的防御性措施上。测试是有意义的，但就像公路两边的防护栏，能防止你掉下悬崖，却并不能把你带向目的地。我们应该回到业务本身，琢磨如何更优雅地设计和实现它，这是科学、艺术，是编程之美，也是我一直提倡design review的原因。但关于设计以及架构，我发现的更多是其中的“艺术”成分，也就是依靠经验和模仿习得的那些。是否有完整的章法和可言传的套路呢？我们需要更多科学来指导代码的设计和组织。与其说我们需要函数式编程，不如说我们需要更高的视角看待编程。&lt;/p&gt;

&lt;p&gt;带着这个疑问，我撞见了category theory，中文叫范畴论，不知道这个鬼见了都犯愁的理论，能多大程度解决问题。但我愿意一试，没准某一天，我能以气御剑？&lt;/p&gt;

&lt;p&gt;注：上面都是废话，本文重点是以下链接：&lt;/p&gt;

&lt;p&gt;REF.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://www.infoq.com/presentations/Simple-Made-Easy&lt;/li&gt;
  &lt;li&gt;https://www.youtube.com/watch?v=ZhuHCtR3xq8&lt;/li&gt;
  &lt;li&gt;https://www.youtube.com/watch?v=JMP6gI5mLHc&lt;/li&gt;
  &lt;li&gt;https://ykode.id/making-sense-of-category-theory-6f901e39fa3c&lt;/li&gt;
  &lt;li&gt;https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 06 Dec 2017 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2017/12/06/fp-vs-op.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2017/12/06/fp-vs-op.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>程序员的核心能力</title>
        <description>&lt;p&gt;程序员要掌握的知识，要具备的能力实在太多，多得头发都不够掉。&lt;/p&gt;

&lt;p&gt;大体有两大方向。一是对工具的熟练掌握，如操作系统、网络、IO、编程语言等；另一个是用代码为现实问题生成解决方案的能力，这其中最重要的是抽象能力。&lt;/p&gt;

&lt;p&gt;前一个方向是很容易意识到的，很多现象可以说明这一点，比如，世面上介绍如何使用语言、框架的书汗牛充栋；比如，很多人眼里进这一行的门槛是上1个月的语言培训课。&lt;/p&gt;

&lt;p&gt;工具的意义不容否认，为此我还写过一篇&lt;a href=&quot;/computer/2015/12/20/tool-awareness.html&quot;&gt;《工具优先》&lt;/a&gt;。但工具的生命周期其实很短，从个人发展角度看，把过多时间投入到半衰期很短的事物上，并不划算。我入门时接触的是Pascal, BasicScript, ASP, IIS，不知道现在还有没有人用这些。很多程序员也赶时髦，本来写java的，golang流行了，python流行了，学！本来搞业务开发的，大数据火了，机器学习火了，学！打的旗号自然很鲜明：持续学习。几次之后，却怅然若失，貌似没一个是拿得出手的，不过是低水平重复，换个工具，继续做原来的事而已。充其量效率提高了，但效率型工作是可替代性最强的，被其它人替代，被机器替代。&lt;/p&gt;

&lt;p&gt;一颗上进爱学习的心，怎么就被辜负了呢？因为核心能力没有提升。人的能力好比电子围绕原子核旋转，大部分情况下处于巡航状态，在这过程中不断积蓄能量，始终向核心方向用力，就会跃迁到更高级轨道（这里指更靠近核心的轨道，实际电子是更远离核心的道）。“一万小时理论”和“10万行代码理论”只片面强调了量，如果没有聚焦核心，刻意练习，只能是低水平重复，甚至轨道降级。&lt;/p&gt;

&lt;p&gt;核心是抽象能力。这个世界的运行，有讲逻辑的，也有不讲逻辑的。程序员要处理的事是讲逻辑的那部分，因为你所依赖的计算机是讲逻辑的，要让它意气用事，感情用情，目前还很困难。通过抽象，我们识别并保留逻辑部分，抛弃其它内容，然后用计算机语言翻译、实现这个逻辑，进而解决问题。&lt;/p&gt;

&lt;p&gt;抽象这个词，本身就挺抽象的。到底什么是抽象？&lt;/p&gt;

&lt;p&gt;抽象是去除多余和细节。比如下面这个标志，一看就懂是座拱桥，但并没有显示拱桥的幅度、宽度和长度，因为这些数据对于你意识到这是一座拱桥并没有帮助。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2017.4/bridge.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;什么是多余信息，取决于目的。考虑地铁换乘图，其目的是告诉乘客该搭哪条线，在哪里换乘，所以保留了结构关系：站点的分布，以及线路的汇合点，但忽略了物理关系：站点的地理位置、相对距离，甚至扭曲了线路的方向。而如果是开车用的导航图，则必须保证比例尺和实际情况一致，方向也不能有差错，以免误导。&lt;/p&gt;

&lt;p&gt;抽象是建立模板或蓝图。不少公司里有邮件模板、文档模板、PPT模板、报销单模板，等等，它们规定好了结构、风格，并留出一个个空白，使用的时候填空就好。模板描述不变的内容，变量则延迟到使用场景中确定。Java编程时，经常要应用各种设计模式，其实质是通过抽象，固化不变的，封装变化的。比如，很常用的模板方法，流程和步骤无论什么场景都不变，已经在父类写好了，将具体场景的方法在父类里声明，但延迟到子类实现，封装的是方法实现。又比如，创建对象时，不常写new Tesla()，而是运用简单工厂模式，写成TeslaFactory.create()，因为对象的创建是易变的。与其在特斯拉多一个型号时，把所有new的地方都改一遍，不如在create方法里集中改。&lt;/p&gt;

&lt;p&gt;总之，抽象是应对变化，或者说寻找不变性的手段。既可以是不同事物之间的不变性，也可以是同一事物不同历史时间的不变性。虽然这里讲的是编程，但其应用远不止于此，看看贝索斯是怎么说的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我常被问一个问题：“在接下来的10年里，会有什么样的变化?”……但我很少被问到“在接下来的10年里，什么是不变的?”我认为第二个问题比第一个问题更加重要，因为你需要将你的战略建立在不变的事物上。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;有了上面的解释，便不难理解面向对象编程的原则：依赖接口而不是实现，依赖抽象类而不是具体类。它让代码的适应性更强，将来少改代码，少出错。同时，做一些参数设定时，更加有理有据，而不是trial and error。线程池大小怎么定？不用关心具体工作，分析阻塞和非阻塞的时间比例，应用Amdahl’s law搞定。队列大小怎么定？不管究竟放的是什么，确定你期望的排队时长，用Little’s law算下。&lt;/p&gt;

&lt;p&gt;世面上鲜有讲如何培养抽象思维的书，设计模式一类的，算搭一点边，但那是人家抽象的结果，而不是关于抽象的方法。也许我们在运用这些模式，或浏览一些工具和类库的代码过程中，偶有灵光一现，能从这些结果中反推作者的设计思想和精妙之处，毕竟它们也是抽象的结果。&lt;/p&gt;

&lt;p&gt;学习使用工具时，如果多个心眼，留意为什么有这个工具，做了什么取舍，工具于你将不仅是效率意义。做业务开发时，如果不是简单地翻译需求，多想一层，哪里易变，哪里不易变，如何隔离变化，再简单的开发，于你也有精进意义。&lt;/p&gt;

&lt;p&gt;抽象的层级可以有很多，能做多少层级的抽象是一种能力，而判断需要多少层级的抽象则是一种艺术。&lt;/p&gt;

&lt;p&gt;地上一个猴，树上七个猴，一共有几个猴？1+7=8，一共八个猴，用数字符号代替猴子，这是第一层。从对象到数字，大多数人对此熟悉到甚至没有意识到这是一种抽象。再进一层，则有些困难了，当初我理解“加速度”这个概念，就费功。不光是数量，还有结构的抽象，关系的抽象。当然，它们离我们都很远……&lt;/p&gt;

&lt;p&gt;可是，真的很远吗？当大部分人在关心如何写程序时，有人开始研究如何用程序写程序，当大部分人在关心如何看书、学习时，有人在教别人看书、学习。&lt;/p&gt;
</description>
        <pubDate>Sun, 26 Nov 2017 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2017/11/26/core-skill-programmer.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2017/11/26/core-skill-programmer.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>股价到底可不可预测</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/2017.2/stock-price.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;昨晚ali太屌了&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;微信上，老田没头没脑来这么一句。我的第一反应是去看股价，果然屌，一夜之间涨了超过13%。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;你买了吗？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;略不好意思地回答：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;20股…上市当天拿到现在…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我在股市大抵就是这样，赚钱的买得少，亏钱的买了一堆。辛酸血泪史，就不多提了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;为什么会一天之内涨这么多？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;昨天开了个投资者大会&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我没有继续问，但心里埋着第二问：“为什么开个会，效果这么好？”&lt;/p&gt;

&lt;p&gt;最近，撸了个“时间交易所”，可以理解为股市的翻版，所以对这类问题稍微敏感了些。其实，对这个问题，大家多少都能回答一些：会上披露的收入增长远高于市场预期，推动股价上涨。&lt;/p&gt;

&lt;p&gt;足够简洁，但意尤未尽，有没有更底层，更系统的解释？凑巧翻出了积灰近两年的《经济学通识》，释疑解惑。&lt;/p&gt;

&lt;p&gt;要在时间流逝中发挥功能的商品或资源，叫着易耗品。易耗品的价格完全建立在人们对未来的主观估计上。&lt;/p&gt;

&lt;p&gt;股票是一种耐用品。股票的价格，是股票未来全部预期收益的现值。股票价格的变动，是人们对股票未来全部收益预期的变动。&lt;/p&gt;

&lt;p&gt;看看真正的高手是如何把握这一特点的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我决定买苹果以前主要想的是他们是不是还有可能成长，有多大的空间可以成长，威胁都可能来自什么地方，等等。我不去想他现在的股价和过去的股价。——段永平&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;人们现在愿意花多少钱买一只股票，取决于他们预计将来能从中获得多少。认识一变，价格就变。阿里的投资者大会，披露了有关未来事件的信息，改变了人们的认识，进而反应在股价上。&lt;/p&gt;

&lt;p&gt;既然 “人们对事物发展变化的认识变化”决定了价格的变动，那么，认识是如何变化的？&lt;/p&gt;

&lt;p&gt;很遗憾，认识的变化没有规律。&lt;/p&gt;

&lt;p&gt;在现实世界中，一切都按自然规律，有条不紊地进行着，但在观念世界中，人们在不断形成、比较、交换和修正对未来的预期。哲学家波普尔说：&lt;/p&gt;

&lt;p&gt;只要知识是增长的，那就必定有部分知识是我们明天才知道的。&lt;/p&gt;

&lt;p&gt;尽管事物的变化是有规律可循的，但新信息的内容和披露时间，是不可预知的。今天的你不知道明天的你会知道什么。有点绕口，美国前国防部长拉姆斯菲尔德在一次记者招待会上有有段rap式的描述:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns – the ones we don’t know we don’t know.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;用过去的图表、曲线预测未来的趋势，是不靠谱的。于是，我默默地取关了github上预测比特币价格的repo。试图发现和掌握认识变化、信息批露的规律，也是徒劳的。怎么办？回归价值投资，老老实实研究公司的基本面，考察未来的盈利能力吧。&lt;/p&gt;

&lt;p&gt;如果还想动点“歪脑筋”呢？&lt;/p&gt;

&lt;p&gt;还是在人身上找机会吧，毕竟股市是人发明的，也是人在玩的，我们要战胜的不是客观规律，也不是股市规则，而是演对手戏的人。他人的弱点，正是你的机会。&lt;/p&gt;

&lt;p&gt;人常常是不理性的，但不理性的行为一旦被识别，就成了新的知识，就会被其他人理性地运用。对于信息披露，有人会“反应迟钝”，有人会“过度反应”，还有人会“惯性行动”。比如，大涨之后，常常出现回调，阿里这次也不例外，创新高之后，便稳步下跌。以及，大跌之后，常常过度下跌。这些是大概率重复出现的现象，于是就有人以此来牟利，而这种新认识马上又会反映到价格上。“在别人贪婪时恐惧，在别人恐惧时贪婪。”，说的大概就是这个理。只是贪婪和恐惧的程度，过度反应何时开始，惯性行动止于何处，都不是容易预测的。近年来，利用大数据技术分析 Twitter 用户的情绪预来测股价涨跌，就是一种尝试。&lt;/p&gt;

&lt;p&gt;alswl说，他新公司有同事在尝试追财报买股票，赌财报发布后股价会上涨。脑筋转得挺快，就不知道效果如何了。&lt;/p&gt;
</description>
        <pubDate>Sun, 18 Jun 2017 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2017/06/18/stock-price.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2017/06/18/stock-price.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>不为人知的阅读模型</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/201612/reading-model.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上周讲了读书的效率问题，这周再说说质量问题。&lt;/p&gt;

&lt;h4 id=&quot;价值衰减模型&quot;&gt;价值衰减模型&lt;/h4&gt;

&lt;p&gt;永泽有个原则：“对死后不足三十年的作家，原则上是不屑一顾的”，意思是没有经受住时间考验的作品，不值一看。这个原则可以拓展为，不仅看“过去”，也要看“未来”：书中所讲的内容，在未来多少年内依然成立？时间越短，越不值得去读。毕竟，精力有限，我希望回报能长久一点。&lt;/p&gt;

&lt;p&gt;任何信息，包括书，都有保值期。&lt;/p&gt;

&lt;p&gt;只是这个保值期，不像食品的保质期一样有明确的时间点。它甚至不是一个“二值”化的阶跃函数，而是一个连续变化的曲线。&lt;/p&gt;

&lt;p&gt;有些书，价值是个常量，不随时间的变化而变化。比如数学，即使地球消失了，它依然有价值。有些书，还没写完，价值就开始衰减。比如，讲林丹出轨之类的八卦杂志。&lt;/p&gt;

&lt;p&gt;基础模型确定了，接下来要细化。哪些书是常量型的，哪些是衰减型的，衰减的速度是多少。明确这些，就不难决择了。不知道你有没有注意到，这个模型不光是适用在选书这一个场景，判断一件事值不值得做，值得投入多少精力，都可以用这个“价值衰减模型”。从一个场景、案例、细节中发现规律，总结、归纳，抽象出模型，并把它应用到其它场景，这叫迁移。理解力，决定了你能吸收多少，而迁移力，决定了你能运用多少。&lt;/p&gt;

&lt;p&gt;上周，在公司的读书分享会上介绍了《Math Better Explained》一书，着重讲了自然底数e。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;e is the base amount of growth shared by all continually growing processes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所有持续增长（其实也包括减少）的过程，都有共同的基数e。类似复利、放射性元素的衰减……e像一根线把这些原本散落的，随时可能脱落的点串了起来。我的知识架构上又多了一个钩子，可以连接原本不通的节点，又可以为以后挂载新节点做铺垫。这是我十分珍视的东西，一看到钩子型知识，就两眼放光。&lt;/p&gt;

&lt;h4 id=&quot;收益函数模型&quot;&gt;收益函数模型&lt;/h4&gt;

&lt;p&gt;价值衰减慢的，不一定对你就是好书。涉及个人时，在“价值衰减模型”基础上还要看它能给你带来多少收益。&lt;/p&gt;

&lt;p&gt;给团队贴过一篇博客：《The Immutability of Math and How Almost Everything Else Will Pass》，很让人触动的博客，它让我真正意识到了知识是存在保质期的。而上面对e的理解又提醒我，知识在保质期内应该是持续衰减的过程。你在不同地方，不同时间看的书会不经意间碰撞，融合，产生新的见解。这种碰撞是不可预期的，只有事后回顾才能发现，就像乔布斯说的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You can’t connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;看书的收益正比于数量的平方。A看了5本，B 4本，C 3本。虽然绝对数量上A少于B和C之和，但A的收益等于二者之和。当然，前提是他们联想、撮合知识点的能力是一样的。&lt;/p&gt;

&lt;p&gt;为什么是平方？这是我臆想的，没法论证。你也可以说成是立方，阶乘。当然，不幸的话，开方也是有可能的。这不是重点，我重点强调的是，这是个非线性的关系，影响收益函数的因素有：读书量，以及所读内容之间产生化学反应的量级。我觉得大部分人的收益要好于线性函数，但又不至于好到夸张，所以用了平方。我称之为“收益函数模型”。&lt;/p&gt;

&lt;p&gt;那么，有什么启发？首先，要改变单纯追求数量的观念，多不等于好；其次，要选择那些跟你已有知识体系能产生反应的书籍。一本书，看完很长时间后，内心依然平静如水，甚至快过了保值期，还激不起半点涟漪，无疑就是一次失败的阅读。如果你也写作，看完后有没有写点什么的冲动，也是一个不错的验证方式。&lt;/p&gt;

&lt;h4 id=&quot;好莱坞大片模型&quot;&gt;好莱坞大片模型&lt;/h4&gt;

&lt;p&gt;现实生活中和人发生冲突的可能性很小，我把它转移到了书上：看书最好要能制造冲突。你带观点时，就和纸面、屏幕后面的作者来一场辩论；不带观点时，就当捣蛋鬼专门给老师难堪。不论书中内容如何，刻意制造冲突，看书的过程就是一部跌宕起伏的大戏。一会儿作者冲着你骂“傻X，还用这种过时的方式看待世界！”，一会儿你还道：“我早知道了，啰里吧嗦！”，扳回一局。这样看书确实挺累，有时候为了搞懂或者反驳对方一个论点，百度谷歌齐上阵，翻箱倒柜，扯出好多资料。但你是带情绪的，带感情的，印象深刻，以后发生化学反应的概率就大了。&lt;/p&gt;

&lt;p&gt;当然，你也可以用苏格拉底式辩论法——提问，心平气和地提问，直到对方意识到他错了。不过这有点难，毕竟苏格拉底不是想学就能学的。&lt;/p&gt;
</description>
        <pubDate>Wed, 07 Dec 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/reading/2016/12/07/on-reading.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/reading/2016/12/07/on-reading.html</guid>
        
          <category>reading</category>
        
      </item>
    
      <item>
        <title>思考，弯与直</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/201612/thinking.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;思考，不紧有快与慢，还有弯与直。&lt;/p&gt;

&lt;p&gt;我们常说，设目标提要求时，尽量稍微高出现在的能力水平，跳一跳能够着，这样对人的提升最有效。说得高大上一些，叫舒适区理论：在舒适区没有成长，恐慌区损害健康，伸展区最好。这一理论的基本假定是，人是不能突变的，即人不可能在短时间内有大变化。其背后的认知是，人是线性的。也许人确实是线性的，但重点不在于此，重点在于，它影响了我们的思维，使它表现出线性。加减乘除我们理解起来很自然，一旦到了次方，开方，阶乘，指数之后，没几个人能凭直觉理解。复利算是生活中常见的了，有几个人真切体会到它的威力了呢？又有多少人有资格笑话印度国王不假思索的豪气：“赏你这么多小麦，第1格1粒，2格2粒，3格4粒……直到64格”？&lt;/p&gt;

&lt;p&gt;我们习惯性地以线性思维来看待这世界，就像我们容易不自觉地戴上有色眼镜一样。只可惜，这个世界并不因为我们的线性思维而改变它非线性的主体本质。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;弱小和无知不是生存的障碍， 傲慢才是 ——《三体》&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;能认知到思维的线性特征的人，无疑是有自知之明的人。没有冒犯的意思，自知之明在我看来，确实是个非常高的要求。认知到了你就会在常规思考后，再多想一层：会不会是我的线性思维导致我得出这样的结论，而其实它压根就不是线性的呢？甚至是不连续的、跳跃的、混沌的？不连续性比非线性还要更有冲击力，我们一再强调总结、复盘，可对于不连续的事物，归纳过去真的能预测未来吗？&lt;/p&gt;

&lt;p&gt;我了解的算法不多，其中 SVM(支持向量机)，是我非常喜欢的一个。两年前在公司内部做过一个技术分享，当然，我估计大部分都忘得一干二净了，分享的最大受益人总是分享者本人。而那句对 SVM的描述我可能永远都不会忘记：“这个算法的本质是把平面上线性不可分的样本，映射到高维空间中，使其线性可分。”。这简直太妙了，人很容易理解平面，但那又如何，不解决问题呀。而人不能很好地驾驭高维空间，就有点遗憾了，因为我们需要构造核函数来实现这个映射，无法想象高维空间，你如何来调整参数呢？这就制约了这个算法的使用。思维的局限性使得我们无法完全发挥它的威力。&lt;/p&gt;

&lt;p&gt;生活中有没有例子？当然有。你的心电图是什么曲线？肯定不是直线。健康人的心率曲线是凹凸不平的不规则形状，呈现某种自相似性，貌似混沌。而帕金森患者的心率曲线反而呈现更多的规则性和周期性行为，表现得更有规律。&lt;/p&gt;

&lt;p&gt;再如，工程师群体经常讨论的，一个牛逼程序员顶几个普通程序员？可能是2个也可能是10个甚至100个。前段时间帮人面试新项目的后端技术，他准备前期就用一个程序员搞定。引发我的联想是，堆糖现在这个量级以极致的程度估计的话，多少后端能搞定呢？当然，这不是本文的重点了，按下不谈。&lt;/p&gt;

&lt;p&gt;汽车为什么能取代马匹？谷歌为什么能取代雅虎？因为后者的效率相对于前者有几何级别的增长，不是简单的涨百分几，翻番也不算。前面《如何判断创业项目靠不靠谱》里提到吴军的“黄赌毒”模型，其实他还讲了别的标准，其中一条就是，效率，或者其他的纬度，相比于上一代产品或者公司，有没有数量级的提升。下面这张图来自FirstRound，描述了苹果的产品迭代路线：在旧产品即将进入线性区之时，及时推出新的拳头产品，华丽丽地进入更抖的成长“弯线”。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201612/apple-grow.jpeg&quot; alt=&quot;苹果的阶跃&quot; /&gt;&lt;/p&gt;

&lt;p&gt;问个一般人不会考虑的问题：人为什么会感到惊讶，奇怪？就像现在的你……可能请心理学的同学回答比较好。我的理解是，因为认知和现实发生了冲突。冲突的原因可能有很多，其中一个便是现实的非线性和人的线性。我很乐于听说那些让人感叹“这也可以，屌！”的故事，既挠了痒痒，又照亮了盲点。&lt;/p&gt;

&lt;p&gt;讲到这里不得不提下马斯克的第一原理性理论。有光环耀眼的钢铁侠背书，总觉得很牛逼，但又找不出牛逼的原因。也是码这些字的时候突然发现，第一原理性正是克服线性思维的一大利器。不要凭感觉，也不要看这事是不是让大众咋舌了，就看它有没有违背物理学规律。没违背， OK，那就有实现的可能。你想得再夸张都没问题，越夸张越让别人艳羡。&lt;/p&gt;

&lt;p&gt;那么，你的思考，弯的还是直的？&lt;/p&gt;
</description>
        <pubDate>Fri, 02 Dec 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/12/02/thinking-method.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/12/02/thinking-method.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>建立高效的阅读体系</title>
        <description>&lt;p&gt;阅读，是我们汲取知识、涤荡思想的主要手段。如果学习是终身的，那么如何建立高效的阅读体系，也是一个伴随一生的话题。这里阶段性地总结下我的探索，略做分享。&lt;/p&gt;

&lt;h3 id=&quot;发现好内容&quot;&gt;发现好内容&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;永泽有个原则：“对死后不足三十年的作家，原则上是不屑一顾的。”，我们也许不必如此，但未经选择的阅读，确有随波逐流之嫌。发现好内容，可以放出眼光，自己来选，也可以众人拾柴，相信群众的智慧。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;豆瓣&lt;/strong&gt;
豆瓣读书频道上可以找到大部分书，有目录、简介、网友评分、笔记和书评，根据这些，不难判断一本书是否符合你的胃口。已经形成这样的习惯：不经意看到的、别人推荐的，都到豆瓣上看看，不错就标记下“想读”。统一收口到这里，不用临时想下一本读什么，“想读”的列表总是比“读过”的长。额外的好处是，发现读书达人，某天循着晓良的朋友圈来到了他的豆瓣主页，发现比你忙的人，读书也比你多，比你快，动力就来了。&lt;/p&gt;

&lt;p&gt;豆瓣上新的速度不快，对于国外较小众的书支持也不多。如果你也想猎奇，正好又是个科技题材的读者，MEAP（Manning Early Access Program）是个不错的选择，人家边写，你边看，有更新了，会及时推送。感谢苏土豪推荐。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reeder 3&lt;/strong&gt;
个人博客时代已经远去，但依然有不少大牛在坚守。特别是在技术领域，除了他们自己的网站，很难在其它地方及时发现他们的内容。大牛的自留地，世外桃源般的独特存在，拜访模式也稍有不同。彼时，我们有Google Reader，纵有每天几百条的更新，管理起来依然很轻松。 gr之后，能让人称心的不多， Reeder的快捷方式和简洁程度都很赞，Mac端和IPhone端都有，无缝切换。&lt;/p&gt;

&lt;p&gt;断断续续收集了40多个订阅源。因为更新频率普遍不高，内容的时效性也不高，每个月翻个两三次即可。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCI-HUB&lt;/strong&gt;
学术文献阅读，不比大众读物，对我而言还是个痛苦的过程。老轮子隔三差五甩给我一堆论文时，我总是很好奇，他是如何能快速消灭这些“天书”的呢？但我知道，当读多了通俗读物，心中的疑问依然没有解决时，好奇心会驱使你找到学术文献。如果你也需要的话，Sci-hub是找论文，下论文， 不二选择，之前有专门篇幅说它：&lt;a href=&quot;/thinking/2016/10/19/paper-reading.html&quot;&gt;如果你也穷得看不起论文&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;碎片时间简单读&quot;&gt;碎片时间简单读&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;对古人的“床上、厕上、马上”读书，一直有个误解，以为是说他们勤奋刻苦，其实可以有另一种理解，这“三上”不正是古人的碎片时间吗？碎片时间古已有之。看书不必刻意，空了拿出来翻翻，再自然不过。只是不同时段，不同环境，适合不同的题材，碎片时间适合消灭简单易懂的内容。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pocket&lt;/strong&gt;
同事跟我描述他的一个痛点，本来想找主题 a的资料，中途看到 b被吸引了，循迹而去，半路又杀出 c、d、 e，一直被带着走，a没看多少，一上午过去了。 类似的还有，别人给你贴的链接，当下没空细看，不看的话忙着忙着又忘记了，直接保收集保存吗，可能又还没达到你的保存标准。&lt;/p&gt;

&lt;p&gt;先丢进一个盒子，缓存下，空了再翻出来看。这个盒子有不少选择，试用下来还是Pocket好用，速度快。浏览器里装个插件，点一点就好，空了之后，电脑或手机打开，相当方便。&lt;/p&gt;

&lt;p&gt;不必害怕时间碎片化，如果这是一个既定事实，就拥抱它吧，刷微博、知乎、Twitter、 Hacker News，微信公众号……我使用这些应用的习惯甚至因 Pocket而改变了。在它们上面尽量快速浏览，淘汰掉大多数，少数不错的放进Pocket，很快就清理干净，专治“红点”强迫症。这时在Pocket内，你有了一个待读队列，而且是全局的，这很重要，确保了你始终可以看到所有信息源中最重要的文章。怕信息过载，多半是没有好的手段来管理信息。&lt;/p&gt;

&lt;h3 id=&quot;完整时间深度读&quot;&gt;完整时间深度读&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;碎片化阅读是游击战，注重轻便灵活，适合解决小冲突，打不了大战役。需要整块时间做深度阅读，打硬仗，打大仗。硬仗、大仗少不了重型武器，我的重型武器是“多看阅读”。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;多看阅读&lt;/strong&gt;
用多看的人不多了（似乎一直没多过），我一直这么忠实，是看重以下几个特性。在阅读这件事上，如果有一个工具能把事情做好，就都交给它好了，不必再搞第二个。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PDF重排功能。在手机上看 PDF的利器，数学公式多的除外……&lt;/li&gt;
  &lt;li&gt;全文检索。这是标配了，看书不太会是线性前进的，难免要搜索，来回跳转。&lt;/li&gt;
  &lt;li&gt;WIFI传书功能。至今没有任何一个平台可以直接买到所有书。 因此，工具的开放性就很重要了，它必须能加载其它书籍。WiFi传书就是一个简便的方式，把从不同渠道淘来的PDF，EPUB甚至是TXT，集中到多看，保证一致的阅读体验。&lt;/li&gt;
  &lt;li&gt;笔记同步到Evernote。笔记自动同步到自动同步到绑定的Evernote帐号，免去了整理笔记的麻烦。这是我最在意的功能。豆瓣阅读也有同步笔记的功能，早期我一直用它。但因为是同步到豆瓣网站，大大减弱了检索和复用笔记的能力，只好弃用。&lt;/li&gt;
  &lt;li&gt;统计。数量统计，时间统计，变化曲线等等，虽然使用频率低，但也很重要，无法评估就无法进步。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;没有提到纸质书，因为它大概已经从我的阅读体系内退出了，去年买的书还堆在角落积灰。也没提很多人喜欢的Kindle，因为我无法再在手机之外携带一个Kindle，以及，我看重的功能，Kindle得有一些辅助工具才能完全做到。凡此种种小问题，放在大时间尺度上就是不小的成本。&lt;/p&gt;

&lt;p&gt;比工具更重要的是完整的阅读时间，至少一小时吧。挤时间，这是工具之外的事了，威武如波姐，不论忙到几点，铁定要看一两小时才睡觉，第二天，依然生龙活虎。对于这种bug式的存在，我在自愧不如的同时，只想画个圈圈诅咒他。&lt;/p&gt;

&lt;h3 id=&quot;沉淀沉淀沉淀&quot;&gt;沉淀、沉淀、沉淀&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Evernote&lt;/strong&gt;
这是整个阅读体系的中心，因为阅读的目的不是阅读本身，而是沉淀和运用。直接沉淀到大脑当然是最好了，但估计没人能百分百做到。我们需要一个触手可及、离线可用、搜索强大、安全可靠的知识管理工具。上面提到的工具都支持把内容收集到知识库中，这是硬性要求，绝不能妥协。 Evernote满足了我对这个工具的所有想象。12年至今，不离不弃，越来越倚重它。&lt;/p&gt;

&lt;p&gt;Evernote本身的普及度已经很高了，不再赘述。想提一提的是Evernote Web Clipper，一个浏览器插件。这些功能都让人爱不释手：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;一键收集到Evernote，我们的精力很有限，一定要千方百计降低自己收集和保存的成本，不要浪费在无用的动作上：“复制-&amp;gt;打开软件-&amp;gt;新建文档-&amp;gt;粘贴”。&lt;/li&gt;
  &lt;li&gt;页面重排版，准确判断正文，去掉广告，去掉周边内容，花里胡哨瞬间变得清晰简洁。真的让阅读成为一种享受。&lt;/li&gt;
  &lt;li&gt;高亮功能，收集前先高亮下重点，轻巧的小插件，也有重型武器的功能。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;写在最后&quot;&gt;写在最后&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;上面提到的工具有各自的作用和分工，配合起来，解决了各个环节的问题：发现、筛选、计划、阅读、笔记、整理和沉淀。上下游建立畅通的管道，协同作战，才能成为体系。
&lt;img src=&quot;/img/201611/reading.jpeg&quot; alt=&quot;&quot; /&gt;
祝大家都能有个合适自己的高效阅读体系。&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Nov 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/reading/2016/11/28/reading-system.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/reading/2016/11/28/reading-system.html</guid>
        
          <category>reading</category>
        
      </item>
    
      <item>
        <title>如何判断创业项目是否靠谱</title>
        <description>&lt;p&gt;可穿戴设备有机会吗？&lt;/p&gt;

&lt;p&gt;VR到底能不能火，什么时候火？&lt;/p&gt;

&lt;p&gt;O2O行业有未来吗？&lt;/p&gt;

&lt;p&gt;生鲜电商行不行？&lt;/p&gt;

&lt;p&gt;不光是创业项目，还可以是想法、产品，甚至是行业。孟庆祥和吴军的课里分别提出了他们的模型，我觉得还蛮受用，这里做个简单的整理。&lt;/p&gt;

&lt;h4 id=&quot;信息电路图&quot;&gt;信息电路图&lt;/h4&gt;

&lt;p&gt;商场和门店的本质作用是展示信息。这可能是整节课下来我印象最为深刻的观点。商场和门店都太常见了，很少有人去思考“卖东西”之外的更深层次的作用。另外，这个描述具有一定抽象程度，能够把很多看似没有关系的东西联系在一起。原本需要case by case分析的案例，可以投射到信息维度，进而用这个模型分析。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201611/circuit.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;要判断是不是信息，首先问，有变化吗？一成不变就没有信息可言；其次，顾客本来就知道吗？人皆尽知构不成信息；最后，是否易于理解？不易理解的也很难成为信息。&lt;/p&gt;

&lt;p&gt;基于这个问题的回答，孟老师判断现在市面上的可穿戴设备大部分是没有价值的。血糖、血压、脉搏本身的变化范围非常小，现在看和一小时后看，一天后看，一个月后看，数据基本上是一样的。即使是病人，测过几次后，自己心里就有底了。随身穿戴一个不提供信息的设备，意义何在？&lt;/p&gt;

&lt;p&gt;确认有信息存在后，要看信息的成本。实体店披露、展示信息，实体店的租金便是信息的成本。网上卖的衣服普遍比线下便宜，很重要一点是因为网上展示信息的成本要低很多。这个成本既是商家的成本，也是消费者的成本，拿起手机和出门逛商场，哪个方便不言自明。除了更方便，网上能提供更丰富、更多样的选择，进一步降低了成本。&lt;/p&gt;

&lt;p&gt;为了取得比较好的传播效果，要注意挖掘人的心理。科技永远取代不了人的原始需求。比如，人们喜欢八卦，虽然从没有人把它定义为原始需求，但八卦伴随人类的历史可不短。《人类简史》说：“等到认知革命之后，智人有了八卦的能力，于是部落规模变得更大，也更稳定”。新闻、八卦类的东西，总能轻易虏获大众眼球。&lt;/p&gt;

&lt;p&gt;电路的最后一环是信息的深度。&lt;/p&gt;

&lt;p&gt;线上信息的流动之迅速和便捷极大地减少了信息的成本。很多传统行业的公司都在琢磨互联网会不会颠覆它们。以房地产为例，既然线上的信息成本更低，为什么房地产线上交易还是不如线下交易？因为，网上提供的轻度信息根本无法支撑起决策。&lt;/p&gt;

&lt;p&gt;链家曾做过一次调查，全国的门店租金平均下来相当于每个业务员每个月多支出500块钱。显然和线上模式对比没有任何优势，但这个额外的信息成本给它们带来了线上无法实现的收益：它可以带客户看房，现场检验，当面交流，提供专业咨询服务甚至是金融服务。越是重大的决策，对信息深度的要求越高，也就越难被线上模式颠覆。&lt;/p&gt;

&lt;h4 id=&quot;黄赌毒先行&quot;&gt;黄赌毒先行&lt;/h4&gt;

&lt;p&gt;这是吴军老师的模型。黄赌毒是人类社会极力避免但可能永远无法消灭的东西，因为它们根植于人性的底层，反映了人类动物性的一面。一来需求强烈，二来见不得光，使得这些领域的从业者非常愿意去尝试新事物，寻求更大的利益或更隐蔽的方式。&lt;/p&gt;

&lt;p&gt;吴军举了HTTPS的例子。没人愿意被发现浏览了黄色网站，因此在线黄色产业有很强的保密需求，还要能安全地交易和支付。互联网早期，人们根本无法想象在网站传输信用卡号。不能很好地保护用户隐私，这对于黄色网站而言是个痛点，所以加密技术甫一出现，他们便如获至宝，积极使用。如果说支付宝培养了人们网上支付的习惯，那么可以说是黄色网站种下了人们在网上交易的种子。&lt;/p&gt;

&lt;p&gt;色情产业扶持的不光是HTTPS。google一下，发现有太多技术的流行与之有关。VCR、DVD、流传输、压缩，甚至是浏览器内的脚本JavaScript，VBScript和DHTML。&lt;/p&gt;

&lt;p&gt;码这段字的时候收到一条短信提醒，比特币大涨到5500，创三年新高。投资比特币靠谱吗？套用吴军的模型，一个也许有用的表征是，比特币在国际洗钱链条中正发挥着越来越不可替代的作用。&lt;/p&gt;

&lt;p&gt;似乎明白了点什么。&lt;/p&gt;

&lt;p&gt;Facebook把宝押在了VR上，那VR什么时候什么能真正点燃大众消费市场呢？如果哪天你发现新闻报道VR在岛国的特色服务里，极大地提高了用户检验，获得了大家的一致好评，那它离流行就不远了。也许，你可以看准时机，买入Facebook的股票。&lt;/p&gt;

&lt;p&gt;判断项目靠不靠谱绝非易事，以上提供两个可供参考的模型，也可以说是套路。积累几个套路，碰到实际问题时，若能灵活应用，或许能少走一些弯路。&lt;/p&gt;
</description>
        <pubDate>Mon, 21 Nov 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/11/21/check-idea.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/11/21/check-idea.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>从美国大选看不确定性</title>
        <description>&lt;p&gt;公众号开张的第一篇写了不确定性，过去的一年，时不时会再冒出这个话题来，因为它实在是太常见，又太重要了。比如这周全球热议的美国大选，就是个典型的案例。戏剧般变化的选情，以及特朗普政策本身的不确定性，无不牵动着全球股市的变化。两位候选人自己也是一脸蒙逼，希拉里不得不取消早早预备的庆功宴，而她其实有更好的选择，就是转让给特朗普，因为后者压根没有准备庆功宴。&lt;/p&gt;

&lt;p&gt;今天从另一个角度再聊聊不确定性，算是对一年前的呼应。&lt;/p&gt;

&lt;p&gt;Weinberg在《系统化思维导论》一书中用随机程度和复杂程度两个维度界定问题。这是一个全局的视角，让我一下子从细分的学科中跳出来，看清全貌。有了更加泛化和统一的框架来规整散落的学科认识，这是让我觉得受用的地方。“有序的简单”，是整个学生生涯的主要学习对象，如牛顿三大定律，质能方程等等。“无序的复杂”，问题本身可简单可复杂，但都表现出足够的随机性，因此可以进行统计研究，一个简单的例子：掷骰子，我们无法知道单次掷出的点数，但大量重复后又表现出一定规律。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201611/random-complex.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“有序的复杂”，是大自然中的普遍现象。这些现象，既不能用解析法准确分析，也不能用统计法预测概率，是不确定性的最大来源。&lt;/p&gt;

&lt;p&gt;“一只南美洲亚马逊河流域热带雨林中的蝴蝶，偶尔扇动几下翅膀，可以在两周以后引起美国得克萨斯州的一场龙卷风”，这是大家熟知的蝴蝶效应。像这样演变过程对初态非常敏感的系统，称为混沌系统。&lt;/p&gt;

&lt;p&gt;高晓松在最新一期《晓松奇谈》中反思美国大选，他提到在一个剧本里的假设：“每个人脑门上都直接显示内心的真实想法”，他预计世界人口会因此减少一半，然后重新稳定。因为，当一对男女互相说我爱你时脑门上却显示另一个人的名字，会起杀人的心思，政客说希望和贵国世代友好时脑门上却显示他只是来骗钱，也会引发战争的念头。&lt;/p&gt;

&lt;p&gt;这是他的剧本，怎么构思他说了算。但他也许不知道，这个假设背后涉及了不确定性。&lt;/p&gt;

&lt;p&gt;可以用马尔萨斯的“人口论”来研究这个假设，“人口论”背后的公式非常简单：
&lt;img src=&quot;/img/201611/population-equation.png&quot; alt=&quot;&quot; /&gt;
Xn表示第n代的人口相对于地球能承受的最大人口数N的比例 。这个公式综合考虑了人口增长率和诸如食物、疾病、战争，以及“脑门上装显示器”等因素对人口的影响。简单背后的复杂可能超出大部分人的估计。
&lt;img src=&quot;/img/201611/population-chart.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图绿色曲线表示不同k值下繁衍到无穷后的情况，不同的初值k，最终的归宿截然不同，图中从左到右分别经历了灭绝、平衡、双态平衡和混沌。混沌状态时，人口不会收敛到稳定状态，而是在无穷多个不同的数值中无规则地跳来跳去。&lt;/p&gt;

&lt;p&gt;这么看，高晓松大概把k值设定在1.2-2之间了。&lt;/p&gt;

&lt;p&gt;知道了混沌现在后，我们还想进一步描述混沌的程度，于是就出现了“熵”的概念：一个系统混乱程度、或称无组织程度的度量。系统越混乱，熵就越大；系统越有序，熵就越小。不同熵值对人们做决策有什么影响呢？这在一年前的文章中有过讨论，可以翻出来看看。&lt;/p&gt;
</description>
        <pubDate>Mon, 14 Nov 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/11/14/election-determine.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/11/14/election-determine.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>如果你也穷得看不起论文</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/201610/scihub.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;不太追论文，但偶尔也有拿来瞄几眼的需求。比起搜网页，下论文一直是个frustrating的过程，直到碰到了一个良心工具。如果你也有此需求，比如看看新鲜出炉的“深度学习”论文，这篇文章也许对你有帮助。&lt;/p&gt;

&lt;p&gt;何恺明，两次获得CVPR（Computer Vision and Pattern Recognition）最佳论文奖，建议做研究时应该：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;多读论文。研究初期，需要海量阅读论文，并且不需要太专注于别人的具体算法和如何实现等细节，更多地去关注论文简介及作者所做的与研究相关的工作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这话固然不错，但实践起来却不是容易的事。光从经济角度看就不容易，看论文其实是件奢侈的事。大部分论文，特别是经过校审的学术论文，都是付费下载，而且价格不菲。&lt;/p&gt;

&lt;p&gt;也许正是这个原因，有个学长毕业后经常找还在学校的我下论文，因为学校以教育的名义能用较低的价格下载论文。但即使是这样，也是一笔不小的开支，就连哈佛大学这样的知名学府也在报怨付不起看论文的费用。&lt;/p&gt;

&lt;p&gt;同样不爽的还有刚毕业时的俄罗斯姑娘Alexandra Elbakyan：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;看一篇论文要付30多美元，实在是一件疯狂的事。要知道，做一个研究，常常要浏览几十几百篇论文。所以，我通过pirating获得论文！不论收入和身份，每个人都有权获得知识。这完全合法。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;战斗民族的彪悍之处在于，不爽了就得反抗，于是，&lt;a href=&quot;http://sci-hub.cc/&quot;&gt;Sci-Hub&lt;/a&gt;诞生了。&lt;/p&gt;

&lt;p&gt;搜索论文时，Sci-Hub会直接从兄弟网站LibGen中下载，如果还没有收录，就从传统论文期刊网站下载，再保存到LibGen中。借助于人们匿名贡献的访问凭证，Sci-Hub可以越过这些期刊的收费环节。这意味着上传到Springer，Elsevier上的论文很快就会在Sci-Hub上出现。&lt;strong&gt;迄今为止，它已经收录了约5000万篇论文，差不多是所有已发表的审阅论文&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;网站不仅没有广告，连操作都简洁得出奇，如果是精确命中，甚至没有搜索结果列表，点击搜索就直接下载。如果你也穷得看不起论文，还等什么，赶紧收藏吧。&lt;/p&gt;

&lt;p&gt;Sci-Hub的做法引发了激烈的讨论，利益受损的Elsevier更是将其告上法庭。好在美国人的法律，管不到老毛子的土地上。不讨论法律，我们从普通民众角度朴实地发表看法。&lt;/p&gt;

&lt;p&gt;论文有著作权，和同样有著作权的书籍、音乐类似，这样似乎可以得出Sci-Hub不对的结论。但必须注意到一个重要区别：通过音乐发行商和书籍出版社，著作权所有者都能从中获得经济利益，而论文的作者并没有从期刊的收录中获得利益，更没有所谓的点击付费。不仅没有，还要付出一些代价。因为，研究人员的晋升一定程度上取决于发表多少论文，以及论文在什么样的刊物上发表。为了让论文能被大众看到，他们常常要付费给期刊。&lt;/p&gt;

&lt;p&gt;期刊其实是个两面收费的中介。这个中介没有保护、激励作者，也没有促进知识的流通。而互联网的一大功能是促进信息的流动和透明，让更多人能免费、高效地获得信息。可以断言，即使这个Sci-Hub倒下了，也还会有更多Sci-Hub站起来。&lt;/p&gt;

&lt;p&gt;正如&lt;a href=&quot;http://www.un.org/en/universal-declaration-human-rights/index.html&quot;&gt;联合国人权宣言&lt;/a&gt;里讲的：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Everyone has the right freely to participate in the cultural life of the community, to enjoy the arts and to share in scientific advancement and its benefits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当然，你可能会说，哪还有功夫去争论谁对谁错，如果穷得看不起论文，赶紧让自己先富起来！诚哉，斯言。&lt;/p&gt;
</description>
        <pubDate>Wed, 19 Oct 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/10/19/paper-reading.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/10/19/paper-reading.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>蚂蚁与互联网</title>
        <description>&lt;p&gt;&lt;img src=&quot;/img/201610/ant.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;要不要出去找吃的，出去多少只，对蚂蚁而言，这是必须面对的问题。&lt;/p&gt;

&lt;p&gt;出去多了，有收获还好，没收获的话，浪费“蚁力”不说，江湖险恶，还有丧命的风险。&lt;/p&gt;

&lt;p&gt;出去少了，虽不至于“不知有汉，无论魏晋”，但错过食物，减少收成，却是很有可能的。&lt;/p&gt;

&lt;p&gt;虽简单，却是生死攸关的问题。生存是第一需求，但食物越多并不完全等价于存活得越久。后者要求的是性价比，是消耗较少资源，收集较多食物。&lt;/p&gt;

&lt;p&gt;蚂蚁们给出了自己的答案。&lt;/p&gt;

&lt;p&gt;这个答案不是“蚁后坐阵指挥，决胜千里”。蚁后虽有王后的名头，却不过是一只专门负责下蛋的蚂蚁，并没有什么“政治”权力。况且，也没有蚂蚁坐在门口清点和汇报布阵情况供它决策。&lt;/p&gt;

&lt;p&gt;每当有蚂蚁带着食物载誉而归时，便会在洞口放下食物，和守候在那里的蚂蚁碰一碰那两根“小天线”。等待的蚂蚁是否出发，踏上冒险之旅，取决于这种交流的次数和间隔的时长。&lt;/p&gt;

&lt;p&gt;一旦出发，不找到点什么，蚂蚁是不会轻易回家的。因此，周围的食物越多，回来得越频繁，就有越多的蚂蚁集结出发；食物越少，回来的次数也少，家里的蚂蚁就自觉省点力，不出去白忙活了。当然，回来的少，也可能是壮烈牺牲了，说明外面危险，还是按兵不动的好。&lt;/p&gt;

&lt;p&gt;没有统筹兼顾，没有上传下达，个体的机械和无知，却构成了整体的灵活与英明。&lt;/p&gt;

&lt;p&gt;这种方式，在蚂蚁界已经运行了几百万年，恐龙在的时候，它们在；恐龙不在的时候，它们依然在。人类在的时候，它们在；人类不在的时候，诅咒它们也不在……&lt;/p&gt;

&lt;p&gt;你以为这是在讲蚂蚁觅食，但其实是在讲 TCP通信。TCP协议能够流行、成为整个互联网的基石，很重要的一点是它解决了之前一直存在的“拥塞崩溃”问题。解决此问题的一个措施是“拥塞控制”：&lt;/p&gt;

&lt;p&gt;拥塞控制是一种用来调整传输控制协议（TCP）连接单次发送的分组数量（单次发送量，在英文文献和程序代码中常叫做cwnd）的算法。它通过增减单次发送量逐步调整，使之逼近当前网络的承载量。&lt;/p&gt;

&lt;p&gt;我们要在事先不知道带宽，或带宽一直变化的情况下，完成通信又避免拥塞。&lt;/p&gt;

&lt;p&gt;TCP发送数据包，好比蚂蚁外出觅食；返回确认符，好比蚂蚁带回食物。很快返回大量确认符，意味着带宽充足，允许发送更多数据包，好比大量蚂蚁回巢，表明食物充足，要多派人手出去抢运。反之亦然。如果根本不返回，发送和外出也将停止，对应TCP的超时。&lt;/p&gt;

&lt;p&gt;第一只外出的蚂蚁是如何决定出去而不是留下呢？这其实不难，一开始总得有一批侦查员，先出去打探打探，为后续行动收集情报。因为只是侦查，所以数量不用也不宜太多。对应的是TCP控制拥塞的另一招——慢启动，服务器通过 TCP 连接初始化一个新的单次发送量 (cwnd)，将其值设置为一个系统设定的保守值。每次往返都令其翻倍(指数式增长)，进而迅速向有效带宽靠拢。&lt;/p&gt;

&lt;p&gt;一个优秀的分布式系统是没有中央节点的分布式系统。神奇的自然选择造就了蚁群，这个优秀的分布式系统。&lt;/p&gt;

&lt;p&gt;争气的是，我们并没有“抄袭”蚂蚁，而是独立发现这一算法，并应用在TCP协议中。&lt;/p&gt;

&lt;p&gt;1988 年，Van Jacobson 和 Michael J. Karels 撰文描述了解决这个问题的几种算法：慢启动、拥塞预防、快速重发和快速恢复。这四种算法很快被写进了 TCP 规范。事实上，正是由于这几种算法加入TCP，才让因特网在 20世纪80年代末到 90年代初流量暴增时免于大崩溃。&lt;/p&gt;

&lt;p&gt;2005年，Vint Cerf和Bob Kahn获得小布什颁发的“总统自由勋章”。向两位大神致敬，也向发明TCP/IP的Vint Cerf（被誉为“互联网之父”）和Bob Kahn致敬。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201610/award.webp&quot; alt=&quot;2005年，Vint Cerf和Bob Kahn获得小布什颁发的“总统自由勋章”&quot; /&gt;&lt;/p&gt;

&lt;p&gt;对了，我们知道蚂蚁懂这个算法，这事本身也是很牛逼的。这是斯坦福大学教授Deborah Gordon长达30年的研究成果。没错，我们是独立提出了蚂蚁觅食的算法，但我们有什么信心能独立提出它们的更多算法？&lt;/p&gt;

&lt;p&gt;搜“Harvester ants use interactions to regulate forager activation and availability”，看教授的研究论文。&lt;/p&gt;

&lt;h4 id=&quot;参考资料&quot;&gt;参考资料&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;http://news.stanford.edu/news/2013/may/ants-forage-evolve-051413.html&lt;/li&gt;
  &lt;li&gt;http://news.stanford.edu/news/2012/august/ants-mimic-internet-082312.html&lt;/li&gt;
  &lt;li&gt;https://priceonomics.com/the-independent-discovery-of-tcpip-by-ants/&lt;/li&gt;
  &lt;li&gt;https://www.wired.com/2013/07/what-ants-yes-know-that-we-dont-the-future-of-networking/&lt;/li&gt;
  &lt;li&gt;https://zh.wikipedia.org/wiki/%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6&lt;/li&gt;
  &lt;li&gt;https://www.ted.com/talks/deborah_gordon_digs_ants#t-965306&lt;/li&gt;
  &lt;li&gt;High Performance Browser Networking&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 15 Oct 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/10/15/ant-www.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/10/15/ant-www.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>活得牛逼，死得励志</title>
        <description>&lt;p&gt;在澳门刷Hacker News时，看到“I’m choosing euthanasia etd 1pm. I have no last words”，并没有点进去的意思，毕竟不想让这种消息冲淡假期的气氛。然而，紧接着就被春雨医生创始人突然去世的消息刷屏了，再加上纪念乔老爷子逝世五周年的文章，这个国庆似乎离不开“死亡”这个话题了。&lt;/p&gt;

&lt;p&gt;特意google了下没点进去的新闻，认识了下当事人Pieter Hintjens——程序员、写作者和思考者。著名分布式消息系统ZeroMQ的作者，写第一个软件时，机器的内存只有5120字节，35年沉浸在编程中，如其所言，是个软件的“通灵者”。&lt;/p&gt;

&lt;p&gt;设计了什么架构，创造了什么语言，提出了什么算法，我想，在这些维度之外，Pieter再次丰富了我描述技术大牛的词汇——协议，他提出了超过30个协议。在我看来，架构是业务场景的抽象，语言是人机交互的抽象，算法是计算逻辑的抽象，而协议则是沟通方式的抽象，上一篇提到TCP协议的两位作者获得“总统自由勋章”，足见其重要性。&lt;/p&gt;

&lt;p&gt;令人唏嘘的是他的最后一个“协议”——临终协议，解释了他从患上胆管癌，到选择安乐死的过程，以及临终者如何处理，亲友如何应对。协议不长，透出平静、理智和勇敢，全文在他的博客中：http://hintjens.com/blog:115，&lt;/p&gt;

&lt;p&gt;facebook上有人翻译成中文了，读起来更加感人：https://goo.gl/zRn6nq。&lt;/p&gt;

&lt;p&gt;Pieter让我想起了Randy Pausch，另一个生得牛逼，死得励志的大拿。以下是五年前写Randy的一篇旧作，权且用来缅怀两位前辈。&lt;/p&gt;

&lt;p&gt;以下，有鸡汤的味道，干了好上班。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;以现在这个年龄来回答“谁是我的偶像”这种小儿科问题，似乎有点幼稚，谁叫我当年迟迟无法回答这个问题呢？不经意看了段视频，认识了个人，如果再有人问我偶像问题，我想答案就他了:Randy Pausch。&lt;/p&gt;

&lt;p&gt;离别是件痛苦的事，世间最痛苦的事莫过于生离死别，世间最最痛苦的事莫过于把短暂的生离死别拉长，慢镜头把瞬间或几分钟的痛苦放大到几个月中，细细熬着。Randy Pausch在生命的最后时光里经历的便是这种煎熬，他被诊断胰腺癌晚期，只有3到6个月的生命。视频Last Lecture便是他在此期间的一个演讲。非诚勿扰2中李香山的告别会令人深刻印象，尤其是那首仓央嘉措的《见与不见》。但一想到那是电影，震撼程度难免打个折扣。Last Lecture是现实版的告别会，在生命里的最后一个演讲，该讲些什么？事实上，我甚至没有勇气去考虑这个问题。&lt;/p&gt;

&lt;p&gt;这也许这是史上笑声最响、笑容最多的一次告别会了，少了非2中的煽情，但它毕竟是一场告别性质的演讲，欢笑难掩悲伤，请注意第85到87分长时间的全场起立鼓掌，此后上台的Steve Seabolt那抑制不住的哭腔，还有他请Jai上台吹蜡烛的那一幕。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ji5_MqicxSo&quot;&gt;Last Lecture&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;看了两遍视频，我决定更加深入地了解Pausch。于是，果断买了他的那本《The Last Lecture》，我买的第一本正版英文书。书很轻、16开，篇幅不多，价格倒不低，如果从这个角度讲，这绝对是我买过的性价比最低的一本书了，哈。事实上，就是否要进行这次演讲，Pausch自己以及他妻子都进行了激烈的思想斗争，毕竟这是最后的日子，他们有各种重要得多的事需要做。在决定演讲后，Pausch又如何决定该讲些什么。。。除了对演讲的内容的记录和补充，书中还披露了这些心路历程。面对医生的宣判，他没有放弃，仍坚持每天骑自行车锻炼身体。也许读者不知道，书并不是他写的，而是他边锻炼边通过手机口述，由别人记录的。就是这样一本书，在纽约时报的畅销书列表中保持了长达85星期，被译成了46种语言，单在美国就卖出了450万册。&lt;/p&gt;

&lt;p&gt;他有着简单朴素的人生理想。没有“为资本主义的继续伟大而奋斗”式的豪言壮语，大而空不是他的风格。他的理想是那么的具体，具体得以至于让人觉得有点傻，如“体验零重力”、“成为世界百科全书的作者”、“成为迪斯尼的想象力工程师”（想象力工程师是神马？）。“先天下之忧而忧，后天下之乐而乐”太沉重。如果，每个人都有着基于自己兴趣的朴实的理想，并在能力范围内将它做到极致，范仲淹们是不是可以轻松些呢？&lt;/p&gt;

&lt;p&gt;他是个变态的乐观主义者，除了积极向上，还是积极向上。Pausch说，他快死了，但他仍然不知道如何才能不快乐地过每一天。&lt;/p&gt;

&lt;p&gt;他是个工作狂，热爱自己的事业。新来的老师问他为什么能提前一年获得终身教授荣誉，他回答说：“周五晚上10点打电话到我办公室，我告诉你。”也许，更深层次的原因在于他的工作基于他的兴趣、他的理想。金钱、家庭压力、世俗的目光。。。有太多的障碍阻止一个人做自己喜欢的事。能将理想和工作高度统一的人无疑是幸福的。&lt;/p&gt;

&lt;p&gt;他是个好丈夫、好父亲。在自己的最后一个演讲中，仍为不能给妻子过生日感到歉疚，然后出人意料地推出一个大蛋糕，并请全场500人一起为她唱“Happy birthday”，那一刻真的很感人，这个男人，极品。而这个演讲，按他的话说， 其实是个“假动作”，是为他的三个尚未形成记忆的孩子准备的。（我们赚了）&lt;/p&gt;

&lt;p&gt;他还是个真诚的人，懂得感恩的人，为别人着想的人。（我发现，我不太擅长描写人物，还是不折磨各位了，偷懒下，有兴趣的人自己去发现吧）&lt;/p&gt;

&lt;p&gt;做一个有理想的人不难，做一个积极向上的人不难，做一个事业上有所成就的人不难，做一个好丈夫、好父亲也不难。以上各项都做到了，而且一生如此，难不？&lt;/p&gt;

&lt;p&gt;摘抄几句他的不怎么出名的名言。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The brick walls are not there to keep us out. The brick walls are there to give us a chance to show how badly we want something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;砖墙并不是为了挡住我们。它在那里，只是为了测试，我们的决心到底有多迫切。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because the brick walls are there to stop the people who don’t want it badly enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;它在那里挡住了那些没有强烈决心的人。它不让那些人通过。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never lose the childlike wonder. It’s what drives us.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;永远不要失去好奇心，它是我们前进的动力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loyalty is a two way street.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;诚以待人，这样别人也会忠实地对待你。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can’t get there alone. People have to help you. You get people to help you by telling the truth.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;你不能单打独斗，必须有人来帮你。只要你讲真话，就会有人来帮你。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apologize when you screw up and focus on other people, not on yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当你把事情搞砸，首先要向别人道歉，首先关心他们的损失，而不是你自己的损失。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get a feedback loop and listen to it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;注意倾听反馈。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Show gratitude.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;感恩。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don’t complain. Just work harder.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;不要抱怨，而要加倍努力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be good at something, it makes you valuable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;要有一技之长，它使你有价值。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find the best in everybody.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;注意发现他人的优点。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be prepared. Luck is truly where preparation meets opportunity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;做好准备。所谓幸运，真的是机会和准备的结合。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it comes to a man who is romantically interested in you, ignore everything he said, just pay attention to what he did.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当一人男人爱慕你时，不要管他说了什么，只要关注他为你做了什么就行了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experience is what you get when you didn’t get what you wanted.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;失败了，但你至少能吃一堑，长一智。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on other people, not on yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;多替别人着想，而不是自己。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I could only give three words of advice, they would be, “tell the truth.” If I got three more words, I’d add: “All the time.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果要我给一个三个字的建议，我会说“讲真话”，如果再加三个字，我会说“无论何时”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not the things we do in life that we regret on our death bed. It is the things we do not. Find your passion and follow it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;让我们在临终前懊悔的往往不是我们做了什么，而是我们没有做什么。追寻你内心的冲动吧。&lt;/p&gt;

&lt;p&gt;最后一句让我想起了Steve Jobs。他们还真有几分相似，两人都得了生存率最低的”癌中之王”，都被告知只有3到6个月的剩余时间。Jobs比较走运，后来发现他的癌症可以通过手术治疗。两人都是技术极客，事实上，在Pausch的最后时光里，他还买了一部苹果电脑。Jobs有句经典语录，“每天早晨，我都会在镜子中间看自己，并且问自己：‘如果今天是我生命中的最后一天，你会不会完成你今天想做的事情呢 ？’当答案连续很多次是‘NO’的时候，我知道我需要改变某些事情了”。&lt;/p&gt;

&lt;p&gt;把“如果”去掉，就是Pausch一生的谢幕曲，平凡的一生。&lt;/p&gt;
</description>
        <pubDate>Sat, 08 Oct 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/10/08/randy-pausch.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/10/08/randy-pausch.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>“阿法狗”第二式：神经网络</title>
        <description>&lt;p&gt;写在前面：&lt;/p&gt;

&lt;p&gt;这是一篇CNN（卷积神经网络）的学习笔记，不同之处在于随处可见的数学公式不见了，真的！它的目的不在于把CNN讲清楚，讲清楚CNN不是一篇文章能做到的，更不是碎片时间浏览下就能做到的。目的在于提供一个勾子，一个抹去细节留下方法论的勾子，一个能和现有知识框架搭上的勾子。有了这个勾子，粗，可以窥探时髦的“深度学习”，细，可以为深挖细节做铺垫。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;第一式里，卷积核可以理解为滤镜，滤掉无用信息，留下有效特征。但是，构造卷积核却不是一件容易的事。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“这有何难，打开Photoshop，选择‘滤镜-其它-自定义’。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;乱涂乱画，当然不难，做个实用的滤镜看看，做一个不难，做一百个一千个不同类型的滤镜呢？&lt;/p&gt;

&lt;p&gt;脏活累活，又极其消耗脑细胞，祖师爷怎么甘心自己动手，于是，毅然踏上了另外一条……更加消耗脑细胞的路，寻找批量新建“滤镜”的方法，便悟出吸星大法般的bug功夫——神经网络。奏是这么牛！&lt;/p&gt;

&lt;p&gt;牛在结果，更牛在背后的想法。一般我们都把机器当成提升效率的方式，人工走通流程，然后交给机器大规模地运行这个流程，机器运行的每一步人都了如执掌。而这次不同，老师教会了学生做一件自己都没弄明白的事，学生不仅做到了，而且做得很高效。所以，直播人狗大战时，会听到解说员说：&lt;/p&gt;

&lt;p&gt;太聪明了！人是绝对想不到这步的。对，人想不到！！即使想到，需要的时间也远远超过阿法狗。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“吹吧就，BB了这么多，神经网络到底长什么样？”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;/img/201608/neuron.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;虽然不是长这样，但它真的是在生物神经元的启发下提出的，所以才叫人工神经网络。&lt;/p&gt;

&lt;p&gt;我们试着以识别人脸为例，说明人工神经网络的工作机制。拿出张三、李四、王五三人的头像各若干张，让机器去学习，然后再取几张没见过的照片，让机器判断是谁。&lt;/p&gt;

&lt;p&gt;假定每张照片的大小都是100x100像素，把照片直接输入到系统，一个像素都不能少，让神经网络自己抽取特征。这样便有了10k个输入点，分别代表照片上的各个像素，3个输出点，分别代表属于各个人的可能性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201608/net-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;维基百科上解释说，当神经元接收到相连神经元的电信号时，会变得兴奋。神经元之间的连接强度不同，一些足以激活其它神经元，而另一些则会抑制激活。大脑中数千亿神经元及其连接一起构成了人类智能。&lt;/p&gt;

&lt;p&gt;相应地，各个像素对于判断人脸的重要性不尽相同，输入层各节点的取值也各不相同，范围在0到1之间，这体现了神经元之间连接的强度。此外，在输入、输出中间有个隐含层，这是体现神经元互连的层级，也是见证神奇的地方。任意输入节点i和任意隐含节点j建立连接，连接的系数为Wij，任意隐含节点j和任意输出节点k也建立连接，连接的系数为Wjk。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201608/net-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“长得可真不咋滴，又是如何工作的呢？”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;很简单，不停地调整连接系数，让结果尽量逼近正确答案，这叫训练。&lt;/p&gt;

&lt;p&gt;过程是这样的，把输入层设置为照片的像素，然后以Wij的连接系数传导到隐含层，再以Wjk的连接系数汇总到输出层，输出层中值最大的节点即为这个网络判断的结果。一开始的时候，网络的连接系数是人工设置的，输出的结果肯定不尽如人意，很可能出现，明明给的是张三的照片，在输出层中却是李四对应的节点得分最高。&lt;/p&gt;

&lt;p&gt;训练有一个目标函数，可以看成是由所有待求系数Wij为自变量的复合函数，这个目标函数定义了什么样的系数才算一组“好系数”。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“相邻层的任意节点之间都有系数，这么多系数，牵一发而动全身，如何调整才能获得‘好系数’？”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;是时候让反向传播出场了。&lt;/p&gt;

&lt;p&gt;为了讲反向传播，先绕道说一说负反馈的概念。负反馈，是系统在条件发生变化时作出抵抗该变化的行为。作为自控理论的核心概念，负反馈随处可见。此刻刷着微信的你，一定也吹着空调，空调就是个很好的例子，不停检测实际温度和目标温度的差异，高了就降一降，低了就升一升。&lt;/p&gt;

&lt;p&gt;反向传播在想法上与负反馈类似，但具体算法不同，复杂度也不同。输出节点给出的计算值和期望值对比，得出错误，把这个错误，沿网络的相反方向传播，进而校正各个节点的系数，使得全局错误最低。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201608/net-3.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“这个描述也太模糊了，能否再细化下？”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;逼我写公式，就不！反向传播算法的核心是，“梯度下降”和“链式法则求偏导”，要通俗地讲可不简单，先挖个坑，哪天悟出了群众喜闻乐见的解释方法，再填上不迟。&lt;/p&gt;

&lt;p&gt;总之，如此这般，冬练三九，夏练三伏，错误降为零时，则神功初成。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“听说，有人反复练习了几百万次，错误依然无法降到零……”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这倒有可能，不仅可能，而且是很可能，错误收敛到一个很小的值，也算成功。练得差不多，便可以牛刀小试一番了，拿一张没见过的照片，看它能否辨认出。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“且慢，是不是少了点什么，前面说了神经网络能自动生成卷积核，体现在哪里？”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;嘿嘿，隐层那些训练得到的系数，就构成了这个层级定义的过滤函数，也就是卷积核。实际的卷积神经网络有很多个隐层！图片经过一个隐层上的若干过滤器后，就得到了对应数量的特征图谱（feature map），图谱自身其实也是图片，代表了原始图片的某些特征，如轮廓、颜色、亮度，质地等。通常，卷积神经网络的隐层越深，包含的特征图谱就越多，表达能力也就越强。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“原来如此，难怪叫深度学习。”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;没错。高深莫测，以至于数十载内，少有人能将其运用自如。&lt;/p&gt;

&lt;p&gt;一来对计算能力的要求极高，不是一般的硬件能驾驭，特别是训练一个大型的、高容量的神经网络，CPU也无能为力。近年GPU大行其道后，深度学习才得以流行开来，执其牛耳者，如谷歌，更是在打造机器学习专用的TPU。&lt;/p&gt;

&lt;p&gt;二来可解释性不强，像个“黑箱子”一样不知为什么能取得好的效果，不知其所以然，就很难针对性地改进，更别提再上一层楼了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“哦。阿法狗逼格太高，我还是继续当程序猿吧……”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不，你应该复习下第一式。&lt;/p&gt;
</description>
        <pubDate>Tue, 16 Aug 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/math/2016/08/16/alphago.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/math/2016/08/16/alphago.html</guid>
        
          <category>math</category>
        
      </item>
    
      <item>
        <title>小心假设，大胆质疑</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;A：这里用Integer.valueOf把字符串强制转化为整数，但没有判断是否能转，也没有捕捉异常，太粗暴了。
B：哎呀，和客户端做了约定，这个一定是可转为整数的。
A：……&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;类似上面的对话，程序员读者一定不陌生。B的行为背后有着坚强的道理支撑，充分相信别人的承诺，让事情变得简单，有何不可？一时竟无法反驳，倒显得A鸡蛋里挑骨头了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;开源项目不靠谱，必须自己写。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;这个就不必再开会沟通了，早说过了，大家肯定都知道的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;类似的对话不胜枚举，润物细无声地融入日常生活，以至于没人发觉它有丝毫的问题。&lt;/p&gt;

&lt;p&gt;真没问题吗？&lt;/p&gt;

&lt;p&gt;你把这段代码的work寄托在了诸多假设上。假设客户端同学正确理解了这个约定，假设他敲代码时没有忘记，假设他写了注释给后人看，假设后人get到且遵守了，假设不会有人恶意调用接口……呵呵。&lt;/p&gt;

&lt;p&gt;什么，你只关心它现在能work，才不care以后怎样？滚，别让我再看到你。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Assumption is the mother of all screw-ups.by Wethern&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;不信？问特斯拉老板去，相信马斯克会用他的“第一原理”呼你一脸，继续滚。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在每一系统的探索中，存在第一原理，是一个最基本的命题或假设，不能被省略或删除，也不能被违反。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;眼熟吧，没错，数学里的公理。“两点之间，直线最短”……&lt;/p&gt;

&lt;p&gt;基于颠不破的事实、真理，还是基于经验、猜测，决定了事物的生命力。&lt;/p&gt;

&lt;p&gt;马斯克牛逼，首先在于他的思考架构，剥开表象，洞察本质，然后从本质一层层推演。&lt;/p&gt;

&lt;p&gt;这很难，真的很难。但至少，你得努力。至少，你得开始提防“假设”，下次撞见的时候，祭之以“质疑”，尤其是对你自己的假设。&lt;/p&gt;

&lt;p&gt;害人之心不可有，承诺别人不返回null，就努力做到；防人之心不可无，虽然别人承诺了不返回null，但你得“质疑”一下，加个判断，善待生命，远离NPE。&lt;/p&gt;

&lt;p&gt;线上又出诡异问题了，你感觉是线程池的问题，决定换个库，发布看看？ 也许你是胡适的粉丝，对“大胆假设”深以为然，但请你也一并记住他的下半句，“小心求证”。没有经过验证的假设都是耍流氓，无法解释现象的措施都是投机。&lt;/p&gt;

&lt;p&gt;重构一词，现在越来越高大上了，三天两头听人说，我准备重构下XX模块，似乎没有做过重构的开发，都不好意思叫自己开发。如果真是需求改得面目全非那倒也罢了，如果是之前代码写得烂，呵呵。于我而言，重构虽有废旧立新的快感，更有悔不当初的痛苦。尤其是发现当初把一些不靠谱的假设当成前提，不禁要自问，尼玛，这真是我写的代码？take it for granted的假设残酷地坍塌了，前期考虑不足欠下的巨款，也该还了，然而，摇身一变，它们成了重构的光鲜理由。&lt;/p&gt;

&lt;p&gt;这是横亘在通往架构师之路上的一座高山。&lt;/p&gt;

&lt;p&gt;功夫在代码外，在于思维起点。是从实际出发，还是从主观出发？从数学出发，还是从规则出发？从需求出发，还是从功能出发？越原始的出发点，越牢靠，在此基础上理性推导，逻辑证实，构建的系统就稳固，重构的需求就低。&lt;/p&gt;

&lt;p&gt;因此，在制定方案时，要明确评判标准，记录决策过程的假设，以及假设的论证方式。你得让别人清楚明白地知道这一过程，不论是为了堵上后人质疑的嘴，还是为了供后续重新评估。&lt;/p&gt;

&lt;p&gt;已经不再想当然地假设了，可有些东西本身是不确定的，不假设就无法继续啊。没错，不得不承认，这个世界本来就有很多不确定性，这个时候，我们能做的是，遵守延迟决策法则。能推迟就推迟，直到有迹象可以观察，测试和验证，再决策不晚。在此之前，总是选择包容所有可能性的方案，不确定是Integer还是Float，那干脆用Number吧，顺带还有了更宽的适用面。这么看，不确定性不再是负担，反倒成了一种动力，促使代码更通用的动力。&lt;/p&gt;

&lt;p&gt;总是保留各种可能性，给自己留后路。鸡贼，但鸡贼得有理有据，这个理就是最大熵原理。算法的应用，不光是写代码的过程，工作乃至生活，无所不在。关于不确定性和最大熵原理，这里不展开了，想进一步了解的，请戳文末的“阅读原文”。&lt;/p&gt;

&lt;p&gt;小到一个接口参数，大到一个产品功能，把它当成一种未知，忽略“绝对不改”的鬼话，抛弃主观假设，包容更多可能，前期，多花点时间思考，后期，省出时间喝茶灌水，看路过的世界。&lt;/p&gt;
</description>
        <pubDate>Sat, 30 Jul 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/07/30/provision-doubt.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/07/30/provision-doubt.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>“阿法狗”的武功秘籍</title>
        <description>&lt;p&gt;5月，下围棋的阿法狗，用战胜李世石霸占了我的头条，这两天，会美图的“阿法狗”，开始入侵我的相册，名字不土了，叫Prisma，挺秀气。&lt;/p&gt;

&lt;p&gt;先来感受下她的画风。呃，准确来说，是凡高风，毕加索风，列维坦风……&lt;/p&gt;

&lt;p&gt;随手拍一张：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;选择“Candy”派，变酱紫，挂在画展里也毫无违和感。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;换个“wave”风看看：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-3.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;拿来装逼，不会有被雷劈的风险。&lt;/p&gt;

&lt;p&gt;“我们会像一名真正的艺术家一样重新创作”，创始人Alexey说。据说，和凡老，毕老比起来，他们的创作速度快得不只一点点，秒秒钟搞定一幅艺术品。激动地试了下，然并卵，来自网络错误和服务器繁忙的硬伤不下50点。一张图片跨越万水千山终于到达远在俄罗斯的服务器，却被礼貌地告知已经人满为患，请再跑一趟。&lt;/p&gt;

&lt;p&gt;想想创作总是艰难的，也就原谅他们了，尤其这等上乘功夫，一群内功深厚的服务器合力方能驾驭，远非势单力薄的手机所能修炼。&lt;/p&gt;

&lt;p&gt;可等待也是艰难的，排队的时候，做点什么呢？不如扒一扒这位俄罗斯大师的秘籍吧。她量产艺术品的过程是，从经典的画作中提炼画风，再分析用户上传的照片，两个结合，duang，一幅全新的作品诞生了。和阿法狗相似的地方在于，她们都修炼了一种深度学习算法，卷积神经网络。&lt;/p&gt;

&lt;p&gt;卷积神经网络，没有金庸”八荒六合唯我独尊功”式的朗朗上口，却不妨碍我们窥探其奥秘，下面就翻一翻这本秘籍。&lt;/p&gt;

&lt;p&gt;开篇第一式，卷积。&lt;/p&gt;

&lt;p&gt;要修炼卷积，必须酒足饭饱。每次修炼前，得先查下胃。饱餐一顿后，肚子里剩余的食物量在慢慢减少，这个过程可以表示为：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-4.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;差不多五个小时，肚子里的东西就消化完了。如果练功偷懒，体能消耗慢一些，也可能是六小时，七小时。&lt;/p&gt;

&lt;p&gt;不管怎样，为了早日修成此功，你三餐不落，早上7点，中午12点和晚上6点，各吃了一顿，只是量稍有不同，也用一张图表示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-5.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;肚子里剩余的食物就是前面没消化完的，和时间的关系可以表示为：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-6.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当然，练功偷懒的人，耗能少，上一顿没消化完就吃下一顿了，曲线就变成这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-7.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;师傅，我吃饱了，咱开始教招式吧。
我已经讲了。
可，明明只说了吃饭。
招式就在饭里。
……&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;要练成第一式，吃饭除了要准时，姿势也很重要。不论是狼吞虎咽，还是细嚼慢咽，只要是相同的吃法，都要有相同的消化法，要切换自如，还要能叠加，这叫“线性”。无论是中午吃，还是晚上吃，时间虽不同，只要吃法一致，消化方式也必须一致，这叫“时不变”。&lt;/p&gt;

&lt;p&gt;达到这一境界，便练成了第一式。对于这样的肠胃（线性时不变系统），消化快慢的规律，叫“冲激响应”，取决于修炼者的身体情况，三餐吃的饭量是“输入”，肚子里剩余的食物量是“输出”。输出和输入的关系就叫“卷积”，函数f和h的卷积写成：&lt;/p&gt;

&lt;p&gt;h (x) = f ⊗ g&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“可是，画画涉及的是图形图像处理，和“吃饭”如何能联系在一起呢？”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;吃饭的过程也可以理解为，根据特定的规律（消化），把若干份不同的信息（饭）混合的过程。图片是长宽固定的像素矩阵，它本身就是一份信息。和谁混合呢？我们人为制造的另外一个矩阵（卷积核），具体什么样的矩阵取决于我们需要达到什么样的目的。混合后得到的，就是机器学习里经常提到的“特征”。&lt;/p&gt;

&lt;p&gt;feature map = input ⊗ kernel&lt;/p&gt;

&lt;p&gt;比如，我们想保留图片内物体的轮廓，去除颜色，就可以这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-8.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当然，细节要比这个复杂一些，并不是直接用整个图片矩阵计算，而是逐一滑动窗口计算，注意看动图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/alphago1-9.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“咦，上面说的明明是卷积，这张图里怎么是乘积呢？”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;问得好（眼太尖），但是要回答好可不容易。肚子里剩余食物显然是随时间的变化而变化的，这种变化是发生在“时域”上的。图片在采集特征之前，做了傅里叶变换，从时域转到了频域，而时域上的卷积就是频域上的乘积。因此，处理图片时的乘积和吃饭时的卷积，不仅不矛盾，而且在本质上是一回事。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“傅里叶变换是个什么鬼？”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;傅里叶变换，卷积的姊妹心法，20世纪最重要的公式，之一，一旦掌握，卷积大法也就指日可待。傅的名声大噪，在于它是众多其它法门的基础，犹如小无相功，可催动少林七十二门绝技。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“这么牛逼，赶紧讲讲。”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;没问题，只是这里篇幅太小（能力有限），施展不开（讲不清楚），还请移步知乎，看韩昊对此所做的精妙阐述。&lt;/p&gt;

&lt;p&gt;咱们还是回来继续撸卷积。Kaggle——机器学习界的少林寺，流行这么个说法：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Feature engineering is the most important skill to score well in competitions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;“用人话讲就是，特征定得了，功夫才能好。”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;没错，异于其它事物的任何属性都是特征，轮廓，颜色，亮度，质地……凡老，毕老的画各有特点，他们的画风就是众多特征的集合。图片和卷积核运算后，去除了无关的信息，只留下这个核关心的信息，从这个角度看，卷积核就是筛子。构造足够多这样的筛子，就可以量化地取得很多特征。看似只可意会，不可言传的“画风”，卷积大法一出，便手到擒来。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“吹牛，若真这么简单，论资排辈，也轮不到看特征了。”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;嗬，变聪明了嘛。构造合适的卷积核，颇费脑筋，怎么办？让机器自动帮我们选择合适的卷积核。具体如何操练？且看下回，神功第二式——神经网络。&lt;/p&gt;
</description>
        <pubDate>Mon, 25 Jul 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/machine-learning/2016/07/25/alphago-kungfu-1.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/machine-learning/2016/07/25/alphago-kungfu-1.html</guid>
        
          <category>machine-learning</category>
        
      </item>
    
      <item>
        <title>魔都有多少喵星人？</title>
        <description>&lt;p&gt;据说曾经有位西方记者问周总理，中国有多少厕所，周总理回答有两个，男厕所和女厕所。很机智，但也回避了这个问题。今天我们不回避，来聊了聊一个类似的问题，魔都有多少只猫。&lt;/p&gt;

&lt;p&gt;问题很刁钻，看着又有几分眼熟，“全世界共有多少名钢琴调音师？”，“多少只高尔夫球才能填满一辆校车？”，没错，这是谷歌等公司在面试时喜欢问的问题。&lt;/p&gt;

&lt;p&gt;这么问，大概不是为了碾压智商的，即使最聪明的人，也无法准确说出这些数字。开放性问题很友善，谁都能给出答案，又绵里藏针，因为对思维品质的要求很高，要在短时间内给出有理有据的回答，相当困难。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;化整为零，各个击破&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;费米是回答这类问题的大师。1945年的7月16号，内华达的沙漠深处，美国人傲骄地试爆了第一颗原子弹，远离爆点的基地里，工程师们紧张地盯着繁忙的仪表，忙得不可开交。一旁的费米，作为“曼哈顿计划”的主要领导者，却闲庭信步，玩起了撕纸片游戏，把纸片撕碎，然后扔向天空，如是几次之后，沉吟片刻，轻描淡写的说道，爆炸当量大概相当于1万吨TNT。而精确计算后的爆炸当量是2万吨TNT，只差1倍。上一刻还在各种忙乱的大家，此刻怀着错愕的心情，转入计算内心的受伤面积，“how do you know by…”。费米淡淡一笑，并没有回答，引发了几十年后Quora和果壳上的种种猜测。&lt;/p&gt;

&lt;p&gt;没有直接答案，但费米在后来的执教中留下了回答这类问题的一般性思路，Fermi-ize。&lt;/p&gt;

&lt;p&gt;这类问题通常涉及我们不熟悉的领域，或在尺度上非日常生活所能感知。直接回答，困难重重，即使答得精确，也难以让人信服。因此，第一步是化整为零，各个击破（divide and conquer）。大问题分解成小问题，混沌状态分解成已知部分和未知部分。取决于对问题领域的了解程度，可能仍然摆脱不了猜测的成分。但不同的是，此时不再是一个黑盒子，而是把其中的一个量从盒子里拖了出来，有更大的概率取得更为准确的估计。当然，在这一过程中，仍然需要鼓起勇气，克服对陌生领域的恐惧，大胆地说出我们的假设和估计，并且承认结果依然可能是错的。快速发现错误，逐步迭代，比把它掩藏起来要好得多。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;用树形解构问题&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;用顺序表述的文字来描述分解和分析的过程，隐藏了子问题层级结构，面对复杂问题时便不那么直观。The Art of Insight in Science and Engineering（以下简称the art）一书中提出了树形分解法（tree presentation）。简单的问题，如“每天北京到上海的铁路客运量有多少”，可以表示为：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/cat-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;稍微复杂一点的，如“一张纸的体积有多少？”，可以表示为：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/cat-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其中ream（令）有500张纸，-1表示倒数。一张纸的体积很难估算，分解为相对容易估算的长度，宽度，一张纸的厚度不好估计，500张好估计多了，所以，可以把厚度再次做分解。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/cat-3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;分解到什么程度适合呢？到你对估算的准度有较大把握时即可。比如上面的纸张体积问题，也许对于有些人（如造纸厂的工人），一张纸的厚度，已经可以直接估算了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;依靠直觉估算&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;树形分解时，对叶子节点的估算有时不得不依靠直觉。直觉的合理性一定程度上决定了整个估算的准确性，因此有必要讨论下如何提高直觉的合理性。中学课堂上教的多次测量取平均值不失为一种方式。但考虑到平均值比较容易受极值的影响。&lt;/p&gt;

&lt;p&gt;the art中提出了几何平均的方式。并且，不是直接依赖直觉给出数值，而是考虑到右脑的特点，用问的方式来间接逼近。如，估算上海市的平均人口密度，从一个极限值开始，逐步向另一个权限靠近，如高于1000平米／人都是你直觉上判断不太可能的，那么记下1000这个数值。逐步向下，直到一个很小的数值，如低于50平米／人后，直觉上会说，局部地区也许会达到或低于50平米一个人，但整个市平均这么低，可能性很低，所以最终给出的平均值应该是sqrt(50*1000)，即223.6平米／人，或者说4472人／平方公里。这和2010年人口普查时的3631人／平方公里相差不远。&lt;/p&gt;

&lt;p&gt;要选择合适的单位进行估算。对我的直觉来说，每人占多少平米比每平方公里多少人更容易估计，因此我选择前者为单位，而不是后者。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;蒙特卡罗方法&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有些领域在生活中几乎不会涉及，毫无直觉可言。比如圆周率π，大概没有人能凭直觉说出它约为3.14吧。这时候，需要直觉之外的方法。同样是在“曼哈顿计划”，S.M.乌拉姆和冯·诺伊曼提出了蒙特卡罗方法。其原理是通过大量随机试验，统计样本，计算所需要的值。&lt;/p&gt;

&lt;p&gt;用蒙特卡罗方法估算π可以这么做。在正方形内部画一个相切的圆，那么，它们的面积之比是π/4（π*r^2/(2*r)^2）。在这个正方形内部，随机产生10000个点，计算它们与中心点的距离，从而判断是否落在圆的内部。如果这些点均匀分布，那么圆内的点应该占到所有点的 π/4，因此将这个比值乘以4，就是π的值。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/cat-4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;回到开头的问题，魔都有多少喵星人？&lt;/p&gt;

&lt;p&gt;我们可以利用上面的树形分解和直觉估算法进行估计。因为问题结构比较清晰，可以在脑子里迅速画出树形图。野生的和家养的在数量上相差不大，因此总数应该是后者的两倍。上海市2300万人（常识，差不离即可，如果缺乏这个常识，则利用上面的人口密度估算）, 由于上海市的生育水平较低，同时存在大量外来人口和单身年轻人，因此家庭的平均人口应该比较低，大概2.2人一个家庭，通过对身边朋友同事的不完全采样，以及直觉估算，得出全市家庭的养猫概率应该是5%左右，这样算下来，大概有2&lt;em&gt;23000000&lt;/em&gt;1/2.2*0.05，即104万只猫生活在魔都。&lt;/p&gt;
</description>
        <pubDate>Mon, 18 Jul 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/math/2016/07/18/number-of-cats-in-shanghai.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/math/2016/07/18/number-of-cats-in-shanghai.html</guid>
        
          <category>math</category>
        
      </item>
    
      <item>
        <title>扬长还是克短</title>
        <description>&lt;p&gt;有限的精力, 你想用来弥补不足, 还是用来强化优势? 去年还是前年的某个下班路上, 和3D, SM聊起这个话题. 三人略有争议, 当时的对话过程早已忘却, 留下这么根线索牵着, 牵出一些零碎的思考.&lt;/p&gt;

&lt;p&gt;上周又和3D提起这个问题, 他回答说, 如果缺点很致命, 应当去克服缺点, 如果不那么致命, 应该继续拔高优势. 显然这家伙比当年进(鸡)步(贼)了, 说话滴水不漏. 就这个问题, 我们就不关注描述的严密性了, 抓其主干, 或者说一般情况即可. 发现问题本身的存在, 并有意识地去思考, 从而有选择性地分配精力, 这是讨论这个问题的意义所在. 因为, 一旦没有意识到, 或放弃选择, 就有别的力量或人插手, 替我们选择. 其结果是, 听天由命, 撞大运.&lt;/p&gt;

&lt;p&gt;美国管理学家彼得提出木桶原理, 一只水桶能装多少水取决于它最短的那块木板. 在一定程度上回答了这个问题. 即, 每个人都应积极发现自己的短板，并努力补足它. 很有名的理论, 我们很早就被这样教育, 也因此为大众所接受. 但这个原理的成立, 其实有一个前提: 木桶是水平放置的. 也就是说, 每一块木板所承担的作用, 或对于整体的重要性是一致的. 貌似很容易满足的前提, 其实不然, 这个世界并不那么”平均”, 或者说, 木桶其实是斜放的.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201607/tilt-barrel.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;小到个人, 大部分人的左右手灵巧程度不同, 左右眼的近视程度不同, 因为我们对它们的依赖和使用程度不同; 大到公司, 对于市场导向的公司而言, 市场这块板的重要性显然大于运营这块板. 运营的短, 不太会影响公司的成败, 自然不太需要花精力去弥补它的短.&lt;/p&gt;

&lt;p&gt;后来的新木桶原理进行了修正. 进化点在于认清了现实情况, 现实就是斜的, 而不是正的. 经过恰当的旋转, 短板承担的作用小了, 长板起的作用大了, 进而容纳的水更多了. 在倾斜角度一定的情况下, 去弥补短板所带来的收益就不那么明显.反而是长板的强化, 以及围绕长板构建辅助板, 就显得更加重要.&lt;/p&gt;

&lt;p&gt;长期以来, 我一直秉持”问题导向”的思维方式, 甚至引以为傲, 不停地发现问题, 解决问题, 进而趋于完美, 多好. 回顾一季度工作时, 第一次明确地意识到这个思维方式的缺陷. 它其实就是传统木桶原理的思维方式, 专注在找不足, 改不足. 本质上, 它只是木桶原理换层皮, 伪装了下而已. 看, 一个原本非常容易识别的低级思维方式, 换个马甲, 就无法识破了, 不仅不能识破, 还奉如圭臬. 认知的艰难, 可见一般.&lt;/p&gt;

&lt;p&gt;为什么容易专注于弱点,短处，而不是优势呢？《管理成就生活》一书中, 有段话很好地解释了:&lt;/p&gt;

&lt;p&gt;弱点之所以会吸引人们的注意, 是因为它们会带来麻烦. 看出一个人干不好什么, 并不需要特别的智力或经验. 而且, 确定一个人的弱点, 也不需要跟他深交. 然而要想找出一个人的优势, 不仅需要智力, 经验和沟通, 甚至需要更多的东西. 必须对一个人感兴趣, 才能发现他的优势. 此外, 找出人们的优势非常花费时间.&lt;/p&gt;

&lt;p&gt;对别人如此, 对自己, 对组织机构, 何尝不是如此? 况且, 消除一个弱点并不意味着会自动形成一个优势, 它仅仅是减少了一个弱点而已. 如果有个外语很烂的同学跟你说, “我终于及格了, 我可以当翻译家了”, 你大概也只会回答”呵(S)呵(B)”. 成就乔布斯的, 是他这颗星球上无人出其右的产品能力, 不是他克服怪癖和不近人情的缺点, 事实上, 他也没去克服.&lt;/p&gt;

&lt;p&gt;“解决的问题不管多有必要性, 都不会创造成果, 而只能防止损失. 只有利用好机会, 才能产生好结果.”&lt;/p&gt;

&lt;p&gt;“机会导向”或者说”优势导向”, 我努力用它来取代”问题导向”的思维方式. 单点突破, 极致发挥. 从团队角度看, 发现各个的长处, 在影响力所及的范围内, 把它放大到最大, 让他站出来, 在这一领域带领整个团队前进. 缺点怎么办? 发现它, 了解它, 但不是为了消灭它, 而是出于另外一个完全不同的目的: 避免依赖它.&lt;/p&gt;

&lt;p&gt;哦, 对了. 上面说了这么多, 都是忽悠你的, 为什么我更愿意去扬长, 而不是克短?因为克短真的很难. 懒癌, 拖延症, 多年不治, 已弃疗…&lt;/p&gt;
</description>
        <pubDate>Sat, 09 Jul 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/07/09/advantage-disadvantage.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/07/09/advantage-disadvantage.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>谷歌范儿</title>
        <description>&lt;p&gt;《重新定义公司》一书， 概括起来， 讲了两个方面：创意时代对人的要求， 和创意时代对公司的要求。 本文大部分内容是关于第一个命题的摘要。&lt;/p&gt;

&lt;p&gt;在谷歌， “谷歌范儿”（Googleyness）与一般认知能力、职位相关知识以及领导经验并列为评判人才的四大板块。所谓“谷歌范儿”，包括上进心和抱负、团队精神、服务精神、倾听及沟通能力、行动力、效率、人际交往技巧、创造力以及品行等特质。&lt;/p&gt;

&lt;p&gt;这是“创意革命”时代对人的要求。&lt;/p&gt;

&lt;p&gt;他们认为，人类已经经历了四次大的革命，依次为工业革命，生产力革命和管理革命，并正在经历第四次革命，即“创意革命”（creative revolution）。从互联网到移动互联网，再到物联网，从云计算到大数据，基于机器学习的人工智能将成为未来商业的基础。第一，互联网让信息免费、源源不断、无处不在，也就是说，几乎所有信息都可以在网络上找到。第二，移动设备和网络让全球范围内的资讯共享及持续通信成为可能。第三，云计算让人人都能以低廉的价格现付现购地使用强大的计算功能、无限的内存空间、精密的工具和各种应用程序。三股强大的科技狂潮汇集在一起，让多数行业的大环境发生了乾坤挪移， 也让在第二， 三次革命的产生要素──知识， 在一定程度上被机器代劳了， 而人突出的价值则在机器无法胜任的“创意”上。&lt;/p&gt;

&lt;p&gt;因此， 这个时代的弄潮儿是“创意精英”。他们不仅拥有过硬的专业知识，懂得如何使用专业工具，具备充足的实践经验， 还有常人不具备的特质。&lt;/p&gt;

&lt;p&gt;2002年的一个周五， 拉里·佩奇在谷歌网站上闲逛时发现搜索结果的广告很烂， 但只是在公告板上贴了个字条， 并没有向任何人透露， 更没开紧急会议。第二周的周一清晨5点， 一位搜索工程师发邮件说他和几位同事利用周末时间拿出了一个解决方案， 通过定义“广告相关度数值”来评估广告与搜索请求的相关性，然后决定广告是否出现，以及出现在页面的什么位置， 而这就是后来价值几十亿美元的AdWords的核心理念。&lt;/p&gt;

&lt;p&gt;到底是什么驱动了并不直接负责此业务的员工， 自发组织去解决别人的问题呢? 书中描述了“创意精英”的很多典型特点， 在我看来， 其中最重要的几项包括:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;好奇心强&lt;/strong&gt;
很难想象光责任心强就可以解释上述行动， 一定是有更加原始的驱动力。 最原始的力量莫过于小孩子刚开始认识这个世界时的好奇心。 因此， 我把它列为首位。&lt;/p&gt;

&lt;p&gt;创意精英们总是在提问，绝不满足于守常不变。他们善于从各处发现问题，自信解决问题的人非自己莫属。他们喜欢做有挑战性的事，不惧怕失败，这要么是因为他们觉得自己总能从失败中挖掘出宝贵的财富，要么就是因为他们自信即便失败也能重整旗鼓、下次再战。&lt;/p&gt;

&lt;p&gt;绝大部分人都不喜欢不确定性。而创意精英与一般人不同，他们就爱自己动脑筋想对策。他们“适应性强，能够在这令人眩晕的环境中保持随机应变的灵敏”。实际上，一纸宣称能解决一切问题的计划得不到创意精英的信赖，他们宁愿为“不完美”的计划投入精力。充满激情地探索未知事物， 这正是好奇心的表现。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;自动自发&lt;/strong&gt;
他们不会坐等别人为他们指出方向，不拘泥于特定的任务，也不受公司信息和计算能力的约束。他们不被职位头衔或企业的组织结构羁绊住手脚，甚至还有人鼓励他们将自己的构想付诸实现。对于有悖于他们自己信念的指示，他们会选择充耳不闻。他们注重自己的理念，也会依据自己的理念主动行动。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;心态开放&lt;/strong&gt;
他们可以自由地与他人合作，在评判构思和结论时，他们看重的是优点和价值而非出处。他们用不同于你我的崭新视角看问题，如果出现不同意见，他们不会选择缄口不言。有的时候，甚至能还能跳出自己的视角，因为他们懂得如何在必要时充当变换视角的“变色龙”。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;擅长分析&lt;/strong&gt;
他们对数据运用自如，可以利用数据做出决策，同时也懂得数据的误导性，因此不会沉迷其中。他们认为，数据对做判断大有帮助，但绝不会被数据牵着鼻子走。他们具有多领域的能力，经常会将前沿技术、商业头脑以及奇思妙想结合在一起。&lt;/p&gt;

&lt;p&gt;以上，谷歌范儿。&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Feb 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/02/20/google-style.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/02/20/google-style.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>你想要的是你需要的吗</title>
        <description>&lt;p&gt;前几天Z同学若有所悟地说，原来产品和业务方跟你说的，不一定是他真的需要的，原原本本按他们说的做，很有可能最后并不解决他们的问题。&lt;/p&gt;

&lt;p&gt;诚哉此言！和需求方打交道久了，大部分程序员都会有的感慨。然而这并不是他们故意或不用心，因为你描述的，你想要的确实不一定是你需要的。&lt;/p&gt;

&lt;p&gt;乔老爷的遗训尤在耳边，“用户根本不懂他们想要什么，除非你秀给他看”。福特也说，“如果问人们想要什么，他们会说要一匹更快的马”。&lt;/p&gt;

&lt;p&gt;受限于认知水平和环境约束，当事人往往对于可能的选项没有概念，用美国前国防部长拉姆斯菲尔德的话说，叫着未知的未知。比如，对于福特汽车发明前的年代，大众的字典里根本没有“汽车”这个词，自然打死也想不到，除了骑马还可以开车。&lt;/p&gt;

&lt;p&gt;当然，最常见的要属对需求的描述不够本质。人的大脑更拿手具象思维，而不是抽象思维。这在大部分时候是没问题的，比如，口渴了，直接说想喝水，说我想解渴，反而会让人一脸疑惑。这里的关键点在于，是否具备准确描述解决方案的能力。如果很清楚这就是最优或唯一的对策，OK，直接把这个对策当成需求。如果不是很有把握，还是不要冒这个风险，至少可以在提供对策的同时，描述下原始需求或面对的问题，给对方一个考察更优对策的可能。在福特的例子里，可以这么说，我需要一匹快马，或者你有别的更快出行方式，也行。可惜的是，多数“上帝”都是甩手掌柜，并没有为“小二”考虑的意识。那么，小二们，尤其是有了这层认识的小二，比如Z同学，就得学会自己来挖掘，多提问，溯本求源。&lt;/p&gt;

&lt;p&gt;程序员的另一大抱怨是，无休止的需求变更。虽为程序员，我想说，这可能是我们所做事情的固有特点。很多时候需求方只能战战兢兢地说，我认为这样能解决问题，实现目标，但没试过之前，我不敢保证实际就是这样。当事人自己的需求尚且如此，更别提广大用户的需求了。需求不是一次性把握住的，需求是逐步接近的，而且很可能是进两步退一步的螺旋式接近的。这是一门实践学科，需要观察，探索和试错。根据反馈及时做出调整是它的固有特点，不应该是负担。当然，没收到反馈就发起的变更，没有验证任何假设，确是要极力避免的。&lt;/p&gt;

&lt;p&gt;防止变更成为沉重负担，首先要转变认知，拥抱变化。接着要切割，把大需求切割成小需求，降低每个需求失败的成本。对每个需求使用“PDCA”模型持续改善。用上个需求的检查结果调整下个需求的计划。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201601/adjust.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;目前的普遍毛病是，光有计划-执行，检查和调整做的少，做的不够科学。必须通过测试让数据说话，表明设计的有效性，而不是一直用主观判断来说服人。少了后两个环节，就无法建立闭环，迭代将退化为重复。这是个足够大的话题，可以另开一篇了。&lt;/p&gt;

&lt;p&gt;50年代，心理学家马斯洛提出了他的需求层次理论，把人的需求概括为，生理需求，安全需求，社交需求，尊重需求，和自我实现需求。前三者是基本需求，一旦被满足，人们不太会过多考虑，甚至不再意识到它们是种需求，进而当成是理所当然的。尊重需要，包括要求受到别人的尊重和自己内在的自尊心。自我实现需要，指通过努力，实现自己的理想和抱负。它们是近乎无止境的需求，永远无法被真正满足。&lt;/p&gt;

&lt;p&gt;如果把马斯洛模型和“想要的，需要的”结合起来看，会发现一个有意思的现象，你最急切想要的反而是你最不需要的。&lt;/p&gt;
</description>
        <pubDate>Sun, 17 Jan 2016 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/thinking/2016/01/17/need-want.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/thinking/2016/01/17/need-want.html</guid>
        
          <category>thinking</category>
        
      </item>
    
      <item>
        <title>由补码想到的</title>
        <description>&lt;p&gt;某君不无兴奋地跟我说他有个重大发现: java里最小负整数的绝对值等于它本身! 程序员朋友可以试试以下判断:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Math.abs(int.MIN_VALUE) == int.MIN_VALUE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不管怎样, 取绝对值后一定是个正数, 怎么可能等于一个负数呢.乍一看, 太二和尚摸不着头脑. 向来以精准著称计算机世界, 怎么会得出如此可笑的结论?&lt;/p&gt;

&lt;p&gt;其中一定有什么隐情! 凭直觉, 这应该和补码有关. 补码, 初中时代的知识, 不出意外地, 忘得差不多了, 先温故一下, 没准能知新.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;正数的补码等于它自己, 负数的补码等于其反码+1.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;计算机是以二进制方式表示数字的, 其中负数又以补码的方式表示. 当回忆起这点的时候, 有一个问题吸引了我:为什么负数要用补码表示呢?, 这似乎是一个和1+1为什么等于2一样傻的问题, 所以当年聪明的我没敢问老师为什么. 如今智商越发下降, 类似的问题也越来越多, 然后, 像小孩子发现更好玩的玩具一样, 开头那个问题就被晾在一边了…&lt;/p&gt;

&lt;p&gt;大概一开始人们也没设计出补码这玩意儿, 毕竟谁会上来就找麻烦搞这么复杂的东西. 应该是很自然地第一位是符号位, 0表示正数, 1表示负数, 剩下是表示值的多少. 当然, 对于人类轻而易举想到的答案, 上帝一般是要发笑的. 这个方案会有什么问题呢? 让我们简单地假定整数是用3个比特表示的, 其表示的所有数字, 排列组合如下:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;000: 0, 001: 1, 010: 2, 011: 3, 100: -0, 101: -1, 110: -2, 111: -3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样也能work. 但很显然的不足是, 0有两种表示法, 有点浪费空间了. 还有一个更隐蔽的缺点, 用一道题来说明. 按上述规则计算下1-1, 也就是1+(-1)的结果.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;001+101=110
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;110就是-2, 显然不对. 那么如何设计才能让同时弥补上述两个缺点呢? 这时候, 补码就隆重登场了. 按补码的规则重新穷举上述排列:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;000: 0, 001: 1, 010: 2, 011: 3, 100: -4, 101: -3, 110: -2, 111: -1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;0不再重复了, 表示的空间从[-3, 3], 扩大到[-4, 3]. 而且1-1的计算, 变得简单自然, 丝般顺滑(最高位溢出):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;001+111=000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因此, 不必为加法和减法设计两套计算电路! 两个看似不同, 甚至对立的逻辑, 得到了统一. 设计之巧妙, 令人叹服. 看似很傻的问题, 细细研究, 原来这般微妙, 优雅得简直像艺术品, 不得不佩服提出补码那位兄台.&lt;/p&gt;

&lt;p&gt;绕了个圈, 回到最开始的问题. 绝对值的计算大家都清楚: 正数, 直接返回它自己, 负数, 返回其相反数. 而java计算相反数, 就是取其补码. 仍然拿3比特的为例, 最小的负数是100(-4), 取反是011, 加1后是100, 还是-4. int类型一般是32比特, 但演算过程是一样的.&lt;/p&gt;

&lt;p&gt;写代码如何能少出bug? 做架构如何能更灵活适应需求的变化? 一个共同的答案是, 发现和归纳事物的规律, 通过巧妙的设计, 或更高的抽象, 减少差异性, 提高普适性.&lt;/p&gt;

&lt;p&gt;设计行业里流行少即是多的理念. 我想, 少不是刻意删除, 不是空洞无物. 少, 是在充分理解的前提下, 找到那个支点, 四两拨千斤, 用最小的付出, 撬动最大的收益. 九九归一, 少得下来, 一定是看透了规律, 抓住了本质.&lt;/p&gt;

&lt;p&gt;上面对补码的理解, 足够概括和普适了吗? 并没有.&lt;/p&gt;

&lt;p&gt;先试试在进制上做点延展. 小学生都会的减法, 能否统一成加法呢? 答案是肯定的. 事实上,&lt;/p&gt;

&lt;p&gt;两整数 A、B 用同一个正整数 M (M 称为模)去除而余数相等,则称 A、B 对 M同余,记作: A=B (MOD M). 具有同余关系的两个数为互补关系,其中一个称为另一个的补码.&lt;/p&gt;

&lt;p&gt;那么, 一个更加概括的理解是:减去一个数, 等于加上这个数的补码.&lt;/p&gt;

&lt;p&gt;数学源于生活, 让我们举个日常生活的例子. 时钟连3岁小孩都能看懂, 比11点早3小时是8点, 比11点晚9小时也是8点, 嘿嘿, 说的就是这个理, 只不过这里的模是12. 而计算机用的是二进制, 以2为模: 1-1 = 1+1 = 2+0 = 0 (2被抹掉了).&lt;/p&gt;

&lt;p&gt;所以下回教小孩子做减法可以这么说: 7-3 = 7+7 = 10+4 = 4. 当然, 要解释为什么10被任性地抹掉了, 也挺费劲.&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Dec 2015 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2015/12/28/complement.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2015/12/28/complement.html</guid>
        
          <category>computer</category>
        
      </item>
    
      <item>
        <title>工具意识</title>
        <description>&lt;p&gt;“为了上一天的新品, 我们得两个人花四个多小时, 都快奔溃了”. 最近常常听到业务方诸如此类的抱怨, 大量的人力浪费在重复的机械劳动中. 向来对体力劳动怀有嫉恶如仇之情的我, 听完后, 先是表示婶婶的同情, 即而又是一阵羞愧, 有我们技术坐镇, 落后的体力劳动为什么还能大行其道?&lt;/p&gt;

&lt;p&gt;这背后可能是业务流程问题, 可能是任务的优先级问题, 似乎没什么新内容可挖掘. 但有一个更深层次的原因却很容易被忽视: 使用工具的紧迫感. 若是现成的工具放在眼前, 断没有不用的道理. 所以, &lt;strong&gt;缺的不是使用工具的基本意识, 缺的是决不做牛做马干体力活的偏执, 是没有工具就要找工具找不到工具就要开发工具的紧迫感, 是不用最好的工具就会落后就会被淘汰的危机感&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;工具是第一优先级&lt;/strong&gt;
人类能制造和使用工具, 从而把自己和其它生物区分开来了. 发明并大规模使用蒸汽机后, 人类迎来了工业革命时代, 期间创造的财富超过了近代之前人类创造的一切财富总和. 近几十年, 计算机,互联网的普及, 让我们进入信息革命时代, 这时期创造的科学知识再次超过了此前人类积累的知识总和. 撬动这些成就的恰恰是我们手中的工具.&lt;/p&gt;

&lt;p&gt;人的价值不在于做重复工作, 尤其是在互联网行业, 任何一家有追求的公司, 任何一家想要有所成就的公司, 主动也好, 被动也罢, 都必须把人从简单的重复劳动中解脱出来, 让他们专注在真正产生价值的创造性活动上. 越是机械重复的工作, 就越适合用工具, 用技术来解决.&lt;/p&gt;

&lt;p&gt;黄易山是Facebook的前工程主管, 他有个鲜活的例子. 05年到06年中期, Facebook经历了一段大规模招聘客服人员的时期, 在1000万用户时有20个客服. 但他们立刻意识到不能这样线性发展下去. 便派了一个技术小组和客服人员密切合作, 分析他们的工作内容, 开发针对性的工具来接管苦力活, 让他们专注在人擅长的领域. 很快, 用户增长了30倍达3.2亿, 而客服只是增加了3倍, 差不多60-70人. 没有任何一个外部产品或管理咨询公司能够提供这种量级的效率提升, 更何况简单地用人力扛.&lt;/p&gt;

&lt;p&gt;现在创业的成本越来越低, 其中一个点就是因为有越来越先进的工具. 现成的开源产品节省了开发成本, 云的出现, 更是连自己买机器的运维成本都省去了, 几个人的小团队就能建起完整的网站. 而计算型工具比传统物理工具更高级的地方还在于, 它们能很方便地组合, 叠加使用, 从而获得几何级别的效率提升. 工具的使用, 直接影响了公司业务效率, 人员招聘, 运营成本等等.&lt;/p&gt;

&lt;p&gt;因此, 工具应是第一优先级. &lt;strong&gt;引入, 开发工具, 并持续更新, 孜孜不倦地使用更好的工具, 应优先于大部分产品特性开发&lt;/strong&gt;. 决大多数人并没有这个认识, 甚至不敢这样想, 因此, 让他们来评定优先级时, 工具的引入总是落后于业务开发. 长此以往, 再多人也累成狗. 此言一出, 难免是要引来口水的. 不必做过多的争论, 本来就没有绝对的事, 这里重点强调的是工具的重要性并没有得到足够的认知. 包括我自己, 身为技术人员, 很多时候以业务为借口, 拖延了技术和工具的升级, 有时候又偷懒不愿意去发掘和学习新的技术, 工具.&lt;/p&gt;

&lt;p&gt;有趣的是, 在被繁重的体力劳动压得透不过气的时候, 似乎更难想到要另辟蹊径, 仅有的注意力也被眼前的堆积如山的工作量吸光了, 连换个现成轮子的精力都没了, 何况造轮子? Twitter上刚好看到一张图, 形容这个再生动不过了.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201512/wheel-recommend.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;工具意识应根植在公司文化中&lt;/strong&gt;
工具优先的认识, 应在公司文化上体现, 而且越早越好。 让使用工具的意识根植在每一个人的脑海里, 不论是做技术的, 还是做运营的, 做市场的. 培养起对一切重复的体力劳动嫉恶如仇般的敏感度, 浪费生命的危机感会迫使每个人想方设法去使用工具. 没错, 这只是第一步, 但却是最重要的一步. 至于怎样找到工具, 哪个工具更好, 就各显神通了, 不行就交给工程师们吧!&lt;/p&gt;

&lt;p&gt;还要在资源部署上体现工具优先, 让最优秀的员工致力于提供更好的工具. 常听到有工程师报怨工作成就感不强, 原因可以分析很多, 大概又可以归纳为”他感受不到自己对别人的影响”. 给同事提供更好的工具, 让他们实实在在感觉到对别人的影响力, 偶尔收到来自身边的积极反馈和夸奖, 工程师们的虚荣心指数爆表, 这算是工具优先的附加收益吧. 看到这里, 开水同学一定笑了, 给凳子开发了无耻的点赞工具后, 好评如潮, 这小子就沉浸在幸福的海洋里, 意淫着开个淘宝店开卖…&lt;/p&gt;

&lt;p&gt;画面很美好, 现实很丑陋. 因为在前期, 使用工具没有直接铺人力来得快速, 而且性价比不如人力投入高! 这是个功利的年代, 功利的行业, “成本意识”和”效果导向”早已深入人心, 我们非常在乎性价比, 两相对比, 工具自然让人很失望.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/201512/linear-expo.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;顶住压力, 跳出局部最优的蜜罐, 需要远见, 需要勇气和毅力. 但用不了多久, 它们就将画出一条华丽的弧线. 展现在眼前的将是潮平两岸阔, 风正一帆悬, 望一眼身后苦苦挣扎却渐行渐远的同行们, 满意地笑了笑, 随即又举目远眺, 扬帆启航.&lt;/p&gt;
</description>
        <pubDate>Sun, 20 Dec 2015 00:00:00 +0800</pubDate>
        <link>https://blog.yuzhi.run/computer/2015/12/20/tool-awareness.html</link>
        <guid isPermaLink="true">https://blog.yuzhi.run/computer/2015/12/20/tool-awareness.html</guid>
        
          <category>computer</category>
        
      </item>
    
  </channel>
</rss>
