<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>纯真年代</title>
    <link>https://pureage.info/</link>
    <description>Recent content on 纯真年代</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-cn</language>
    <lastBuildDate>Mon, 07 Feb 2022 17:07:27 +0800</lastBuildDate>
    
        <atom:link href="https://pureage.info/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>说说戊戟</title>
      <link>https://pureage.info/post/a-deceased-gongfu-story-writer-wuji/</link>
      <pubDate>Mon, 07 Feb 2022 17:07:27 +0800</pubDate>
      
      <guid>https://pureage.info/post/a-deceased-gongfu-story-writer-wuji/</guid>
      
        <description>&lt;p&gt;2021 年，我用了好几个月的空余时间，陆陆续续将戊戟的传奇系列全部看完，了却一个深藏多年的心愿。&lt;/p&gt;
&lt;p&gt;那么，戊戟是谁？&lt;/p&gt;
&lt;p&gt;百度百科里有他的词条：&lt;a href=&#34;https://baike.baidu.com/item/%E6%88%8A%E6%88%9F/403861&#34;&gt;戊戟&lt;/a&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;hr&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;hr&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;hr&gt;
&lt;p&gt;附上几幅从孔夫子旧书网上找到的几张照片，人的记忆很奇特，这些插画的样子，我都记得。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pureage.info/img/heiyingchuanqi-1.jpeg&#34; alt=&#34;heiyingchuanqi-1&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pureage.info/img/heiyingchuanqi-2.jpeg&#34; alt=&#34;heiyingchuanqi-2&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pureage.info/img/jianghuchuanqi-1.jpeg&#34; alt=&#34;jianghuchuanqi-1&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pureage.info/img/jianghuchuanqi-2.jpeg&#34; alt=&#34;jianghuchuanqi-2&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pureage.info/img/jianghuchuanqi-3.jpeg&#34; alt=&#34;jianghuchuanqi-3&#34;&gt;&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>Nginx 中的 X-Accel-Redirect 响应头</title>
      <link>https://pureage.info/post/x-accel-redirect-header-in-nginx/</link>
      <pubDate>Fri, 28 May 2021 15:32:35 +0800</pubDate>
      
      <guid>https://pureage.info/post/x-accel-redirect-header-in-nginx/</guid>
      
        <description>&lt;p&gt;在 Nginx 的 ngx_http_proxy_module 的&lt;a href=&#34;http://nginx.org/en/docs/http/ngx_http_proxy_module.html&#34;&gt;使用说明&lt;/a&gt;中，有对 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 的描述：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“X-Accel-Redirect” performs an &lt;a href=&#34;http://nginx.org/en/docs/http/ngx_http_core_module.html#internal&#34;&gt;internal redirect&lt;/a&gt; to the specified URI;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文就简单的描述一下在某种特殊场景下 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 的使用。&lt;/p&gt;
&lt;h2 id=&#34;需求&#34;&gt;需求&lt;/h2&gt;
&lt;p&gt;在实际业务中，需求是在一堆&lt;strong&gt;限制条件&lt;/strong&gt;中，达到某个目的。我这里将复杂的业务特性都去掉，最简化的描述一下。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;有个前端 OpenResty 服务，称为 A，一个后段 OpenResty 服务，称为 B。请求从 A 代理到 B。&lt;/li&gt;
&lt;li&gt;一般情况下，请求在服务 A 中的访问日志，记录在正常日志文件 &lt;code&gt;logs/access.log&lt;/code&gt; 里。&lt;/li&gt;
&lt;li&gt;如果后端服务 B 响应为 403，且包含了一个值为 1 的特定的响应头 &lt;code&gt;Invalid-Domain&lt;/code&gt;，则该请求在服务 A 中的访问日志，记录在特定日志文件 &lt;code&gt;logs/invalid_domain.log&lt;/code&gt; 里。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里面有一个需要注意的地方：&lt;/p&gt;
&lt;p&gt;后端服务 B，其响应 403 的时候，有两种情况，一种是根据业务逻辑，包含值为 1 的特定的响应头 &lt;code&gt;Invalid-Domain&lt;/code&gt;，而其他情况的 403 响应中会添加一些其他的正常的响应头，在通过 A 访问时，这些正常的头部不能有缺失。&lt;/p&gt;
&lt;h2 id=&#34;方案一&#34;&gt;方案一&lt;/h2&gt;
&lt;p&gt;最简单的实现方式，是在服务 A 中使用 error_page，最简化的完整配置如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;events {
}

http {
    # 模拟 后端服务 Server B, 这里为了简化, 让其永远返回 403.
    # 在请求带了参数 invalid_domain 为 1 时, 添加额外的响应头 Invalid-Domain.
    # 其他情况下, 添加响应头 Valid-Domain.
    server {
        listen 8081;
        location / {
            access_log logs/server_b.log;
            header_filter_by_lua_block {
                if ngx.var.arg_invalid_domain == &amp;#34;1&amp;#34; then
                    ngx.header[&amp;#34;Invalid-Domain&amp;#34;] = &amp;#34;1&amp;#34;
                else
                    ngx.header[&amp;#34;Valid-Domain&amp;#34;] = &amp;#34;1&amp;#34;
                end
                ngx.exit(403)
            }
        }
    }

    # 模拟前端服务 Server A.
    server {
        listen 8082;
        access_log logs/access.log;
        location / {
            proxy_pass http://127.0.0.1:8081;
            proxy_intercept_errors on;
            error_page 403 = @check_invalid_domain;
        }
        location @check_invalid_domain {
            internal;
            if ($upstream_http_invalid_domain = &amp;#34;1&amp;#34;) {
                access_log logs/invalid_domain.log;
            }
            return 403;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;分别使用参数 &lt;code&gt;invalid_domain=1&lt;/code&gt; 和不带参数去访问服务 A，会发现访问日志的生效是满足需求的。但有一个容易忽略的地方：当不带参数访问时，后端服务 B 的响应头 &lt;code&gt;Valid-Domain&lt;/code&gt;  由于 error_page 带来的内部跳转被去掉了。&lt;/p&gt;
&lt;p&gt;这看起来不是什么大事，但我这份配置只是为了演示而极端简化过的，在实际业务中，&lt;code&gt;Valid-Domain&lt;/code&gt; 头代表着复杂各异的定制化需求，被去掉是肯定不行的。&lt;/p&gt;
&lt;p&gt;问题的焦点，在于我们要同时判断后端响应头和响应状态码，但 error_page 指令中无法对响应头进行组合逻辑判断，所以需要到 error_page 目标 location 中做进一步判断，于是就导致了我们上面说的正常 403 时响应头 &lt;code&gt;Valid-Domain&lt;/code&gt; 头的丢失。&lt;/p&gt;
&lt;h2 id=&#34;方案二&#34;&gt;方案二&lt;/h2&gt;
&lt;p&gt;我们再来梳理一下逻辑，看看解决问题的思路。&lt;/p&gt;
&lt;p&gt;首先，响应 403 且带有 &lt;code&gt;Invalid-Domain=1&lt;/code&gt; 响应头的请求，因为要定制化 log 路径，是需要一个内部跳转到一个新的 location 中去的，即 &lt;code&gt;location @check_invalid_domain&lt;/code&gt; 是必须要引入的。可能有人会说，直接用 if 条件组合来定制 access_log 是不是也可以。第一，我个人非常不喜欢 Nginx 的 if 指令，甚至整个 rewrite 模块我都不喜欢使用；第二，由于要判断后端响应码和响应头，这个时候 if 是不管用的，因为它的阶段太靠前。&lt;/p&gt;
&lt;p&gt;引入 &lt;code&gt;location @check_invalid_domain&lt;/code&gt; 后，所有的 403 响应都会跳转到该 location，而如前所述，业务中的 403 分为两类，有一类的后端响应头是不能丢失的。所以，如果一定要使用这种方案，我们要在 &lt;code&gt;location @check_invalid_domain&lt;/code&gt; 中，将后端的响应头再添加进去，在实际业务中，这块会涉及到整个业务框架的部分，可能会很复杂。&lt;/p&gt;
&lt;p&gt;那我们再换个思路，能不能在进入 &lt;code&gt;location @check_invalid_domain&lt;/code&gt; 之前，就做好响应头的判断，在 &lt;code&gt;location @check_invalid_domain&lt;/code&gt; 中，只配置访问日志。但同样如前所属，error_page 无法做组合逻辑，所以似乎路又堵死了。&lt;/p&gt;
&lt;p&gt;这时，我们再来看一下 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 头，就会惊喜的发现，它恰好是干这个的。本质上只有 Server B 才真正知道 403 的各种情况，那么只要在需要 Server A 修改访问日志时，加一个 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 头来通知 Server A 做内部跳转就好了，起到一种&lt;strong&gt;精确制导&lt;/strong&gt;的效果，而非将这些逻辑判断交给前端 Server A。&lt;/p&gt;
&lt;p&gt;配置如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;events {
}

http {
    # 模拟 后端服务 Server B, 这里为了简化, 让其永远返回 403.
    # 在请求带了参数 invalid_domain 为 1 时, 添加额外的响应头 Invalid-Domain.
    # 其他情况下, 添加响应头 Valid-Domain.
    server {
        listen 8081;
        location / {
            access_log logs/server_b.log;
            header_filter_by_lua_block {
                if ngx.var.arg_invalid_domain == &amp;#34;1&amp;#34; then
                    ngx.header[&amp;#34;X-Accel-Redirect&amp;#34;] = &amp;#34;@check_invalid_domain&amp;#34;
                    ngx.header[&amp;#34;Invalid-Domain&amp;#34;] = &amp;#34;1&amp;#34;
                else
                    ngx.header[&amp;#34;Valid-Domain&amp;#34;] = &amp;#34;1&amp;#34;
                end
                ngx.exit(403)
            }
        }
    }

    # 模拟前端服务 Server A.
    server {
        listen 8082;
        access_log logs/access.log;
        location / {
            proxy_pass http://127.0.0.1:8081;
        }
        location @check_invalid_domain {
            internal;
            access_log logs/invalid_domain.log;
            return 403;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，与前一版的区别，只有 3 个地方：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;后端 Server B 在适当的时候增加 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 响应头。&lt;/li&gt;
&lt;li&gt;前端 Server A 的配置，去掉 error_page，由响应头 &lt;code&gt;X-Accel-Redirect&lt;/code&gt; 在 nginx 的内部处理逻辑来自行做内部跳转。&lt;/li&gt;
&lt;li&gt;内部跳转的目标地址只需要修改访问日志路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;方案三&#34;&gt;方案三&lt;/h2&gt;
&lt;p&gt;真正最灵活的方案，是修改一下 Nginx 的代码，将 access_log 的配置由静态配置改成每请求动态配置，并提供 lua 接口来给业务层调用，这样压根就不需要做内部跳转，代码改起来也不复杂，但实际操作中，能用已有的功能较好的满足需求就已经足够了。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>从一段代码到人们产生分歧的必然性</title>
      <link>https://pureage.info/post/some-thoughts-getting-from-a-simple-code/</link>
      <pubDate>Thu, 20 Feb 2020 18:10:05 +0800</pubDate>
      
      <guid>https://pureage.info/post/some-thoughts-getting-from-a-simple-code/</guid>
      
        <description>&lt;p&gt;这个题目取的有点大，严格意义上并不是我能驾驭得了的。本文并不一定能经得起推敲，只是记录一下我这段时间从一些事情上得到的想法。&lt;/p&gt;
&lt;p&gt;众所周知，文艺作品，比如电影、小说，虽然它们给每个人的输入材料都是相同的，但每个人都会有自己不同的理解，所谓“一千个读者，就有一千个哈姆莱特”。&lt;/p&gt;
&lt;p&gt;在&lt;strong&gt;非虚拟类&lt;/strong&gt;作品中，这种情况要好得多。比如，我们在看一本技术书籍时，重点就不再是得到&lt;strong&gt;每个人自己的理解&lt;/strong&gt;，而是尽量去理解书中所表达的确定的东西，这也是&lt;a href=&#34;https://pureage.info/post/howto-read-a-book/&#34;&gt;《如何阅读一本书》&lt;/a&gt;的主要内容。&lt;/p&gt;
&lt;p&gt;但即使对这类作品，我们也不能够完全消除人与人之间理解上的分歧。例如，我曾经写过一篇博客 &lt;a href=&#34;https://pureage.info/post/a-misunderstanding-of-tao-of-rust/&#34;&gt;在阅读《Rust 编程之道》时的一次抬杠经历&lt;/a&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;第一种，在图形上，保持原链表各节点顺序不变，我们逐个遍历原链表，并用一个额外的 pre 指针来记录当前节点的前一个节点，将当前节点的箭头改为指向前一个节点，依次进行。这个方法在视觉效果上，重点是&lt;strong&gt;反转指针&lt;/strong&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;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; ListNode&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;reverseList&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; ListNode&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; head) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; ListNode &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; NULL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; ListNode &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;cur &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; head;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; ListNode &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; NULL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (cur) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cur&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cur&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cur;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cur &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; next;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; t;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这份代码里有一个临时指针 t，持第一种理解的人，通常会把 t 命名为 pre 或其他相似意义的词，持第二种理解的人，通常会把 t 命名为 new_head 或其他相似意义的词。但我们当然可以认为，这就是同一份代码。&lt;/p&gt;
&lt;p&gt;这只是一段相当简单的代码片段，不同的人读起来，在脑海里呈现出来的视觉动作，就可能完全不同，那么对于更复杂的代码应该就更是如此了。如果逻辑如此确定的代码都有这个问题，那么在生活中，人与人之间的各种分歧是不是从生理上就根本是不可避免的。&lt;/p&gt;
&lt;p&gt;再做一下引申，放到到人类社会，如果每个人对相同事物理解的分歧不可避免，那么理想的社会环境，就必然是一个支持不同想法的人都发声、讨论的社会。讨论可能会带来争论、争吵，但只要是在规范之下，利于提升我们对世界的认知，则都应该是被鼓励的。&lt;/p&gt;
&lt;p&gt;然而，现实生活离这个理想环境，差的太远。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>记一次针对 srs 源站的类 syn flood 攻击</title>
      <link>https://pureage.info/post/an-attack-similar-to-syn-flood-in-srs/</link>
      <pubDate>Thu, 02 Jan 2020 11:02:01 +0800</pubDate>
      
      <guid>https://pureage.info/post/an-attack-similar-to-syn-flood-in-srs/</guid>
      
        <description>&lt;p&gt;近日翻看以前随手做的一些工作笔记时，发现了一年多以前处理过的这个 case，觉得可以发出来给有需要的朋友参考一下，于是做了个简短的整理。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://zh.wikipedia.org/wiki/SYN_flood&#34;&gt;syn flood&lt;/a&gt; 是一种经典的、广为人知的网络攻击。简言之，攻击者不停的建立连接，但是收到 server 响应的 syn+ack 后故意不继续回复 ack，导致 server 端的半连接队列满，其他正常请求无法进入。因此，这是一个针对&lt;strong&gt;半连接队列&lt;/strong&gt;的攻击。&lt;/p&gt;
&lt;p&gt;再来说说 srs。&lt;/p&gt;
&lt;p&gt;srs 是一个流媒体服务器，支持很多功能和协议，但其最重要最根本的，是一个 rtmp 服务器，工作方式为某个发布者发布一条流，多个订阅者可以播放这条流。&lt;/p&gt;
&lt;p&gt;srs 的角色分为两种，edge 和 origin，即边缘和源站。当播放器连接到 edge 时，edge 会从 origin 拿取指定的流并发布，这样播放器就可以播放指定的流了。&lt;/p&gt;
&lt;p&gt;这里面有一些细节，对流媒体行业来说是常识，但其他背景的同学可能需要大概的了解一下。对于一个具体的流，edge server 上播放器和回源的连接数是 N:1，即无论有多少个播放器，对该指定的流，edge server 只会向它的 origin server 建立一条连接。在 srs 的实现里，一个叫 edge ingester 的协程负责该动作。如果所有的播放器都断开连接了，edge ingester 才会退出工作。&lt;/p&gt;
&lt;p&gt;再来说这个 case。&lt;/p&gt;
&lt;p&gt;我们的节点探测程序有一天突然报告某一台 origin server 端口不通了，无法提供服务。在设备上做简单的验证后，发现确实如此。srs 错误日志里除了大量针对不存在的流的播放请求外，没有什么异常。&lt;/p&gt;
&lt;p&gt;使用 ss 查看 srs 的端口，发现其 recv-q 已满，即全连接队列满，于是我们知道为什么服务会不可用了。但还需要进一步排查为什么 recv-q 会满。&lt;/p&gt;
&lt;p&gt;我们知道，对一个监听端口来说，其 recv-q 代表了它当前的全连接队列使用了多少，send-q 代表它当前全连接队列的大小，该大小取决于创建监听套接字时传入的 backlog 参数和系统的 somaxconn，具体来说是 min(backlog, somaxconn)。&lt;/p&gt;
&lt;p&gt;全连接队列满，说明有大量的有效请求持续的进来，而应用程序 accept 不过来，正常情况下，这种情况是绝不会发生的。这个时候我们再回过头看 srs 日志里那些大量的不存在的流的播放请求，就拨云见日了。&lt;/p&gt;
&lt;p&gt;srs 对播放器有一个行为是，如果播放器请求的指定流当前并不存在，srs 并不会有一个超时机制去断开播放器的连接。&lt;/p&gt;
&lt;p&gt;对 edge server 来说，不主动断开播放器的连接，也就是说，对一个具体的流，只要有一个播放器没有主动断开连接，srs 的 edge ingester 协程就认为自己应该继续工作，它会孜孜不倦的尝试从 origin server 上拿去数据，但如果没有数据，edge ingester 就会超时主动断开与上游的连接，并重新向上游建立连接再次尝试获取数据。超时时间为 3 秒，这个过程是一个死循环，打破死循环的唯一条件是，当前已经没有播放器的连接了，但如前所述，如果播放器自身没有超时断开的机制，那 edge ingester 会永远任劳任怨的工作下去。&lt;/p&gt;
&lt;p&gt;在正常的场景下，这并不会有什么问题。因为一般我们认为，直播服务器的边缘是一个天然的具有合并回源功能的代理，源站的连接压力不会很大。现实中，某条流过期了但播放器不知情的情况也很常见。但如果是大量特意构造的这种请求，就有问题了。因为每一条不存在的流，都会对应一个向 origin server 的连接。&lt;/p&gt;
&lt;p&gt;为了方便说明，我把网络简化，假设有一台 origin server，下面接了 4 台 edge server。攻击方使用不会主动超时断开连接的 rtmp 播放器，同时向 edge server 拉取大量&lt;strong&gt;不存在且不同名&lt;/strong&gt;的流，假设是 1000 路流，4 台 edge server 都有这 1000 路流的播放连接，那么会有 4000 个 edge ingester 的连接从 edge server 去往 origin，这就存在了第一个放大的行为。edge server 从上游拉不到数据，就会无限制的断开和重连，这是第二个放大行为。4000 个客户端持续且迅速的建立和断开连接，origin server 的监听端口上 recv-q 迅速塞满，导致服务不可用。当 edge server 更多，恶意的流更多时，情况会更剧烈。&lt;/p&gt;
&lt;p&gt;这个场景是不是跟经典的 syn flood 攻击很像，只不过 syn flood 针对的是&lt;strong&gt;半连接队列&lt;/strong&gt;，而这里的攻击是针对的&lt;strong&gt;全连接队列&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;至此问题的根本原因是找到了，解决办法是通过一定的机制让 srs 断开这种类型的连接。在我们这个 case 中，刚好这个服务的客户已经不再服务了，我们将其从配置中去掉即可。更通用的情况下，我们给所有的播放请求都加上一定的鉴权手段即可。&lt;/p&gt;
&lt;p&gt;这里又有另一个有意思的事情。对传统 cdn 厂商来说，如果客户不主动要求加鉴权，cdn 是不会主动干这件事的，毕竟跑出来的带宽直接跟自己的利益挂钩。但在本文描述的情况下，cdn 从自己的利益出发，却应该主动添加鉴权。&lt;/p&gt;
&lt;p&gt;作为跟 srs 的对比，nginx-rtmp 就不会有这个问题，因为它默认的 idle_streams 配置，在流不存在时，会主动断开与播放器的连接。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>咬文嚼字：关于随机存取</title>
      <link>https://pureage.info/post/about-random-access/</link>
      <pubDate>Mon, 02 Dec 2019 18:29:36 +0800</pubDate>
      
      <guid>https://pureage.info/post/about-random-access/</guid>
      
        <description>&lt;p&gt;今天我来咬文嚼字一番。如果说在我学习计算机相关知识的过程中，有哪些词语最让人感到迷惑，随机存取（Random Access）一定身在其中。&lt;/p&gt;
&lt;p&gt;我大学时的本专业是电子信息，有一门专业基础课叫《随机过程》。整本书花了很大力气来讲“随机”这个概念，当然，在数学的概率论课里面也早有涉及。我们引用维基百科的描述：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;随机性&lt;/strong&gt;（英语：Randomness）这个词是用来表达目的、动机、规则或一些非科学用法的可预测性的缺失。
一个随机的过程是一个不定因子不断产生的重复过程，但它可能遵循某个概率分布。
术语&lt;strong&gt;随机&lt;/strong&gt;经常用于统计学中，表示一些定义清晰的、彻底的统计学属性，例如缺失偏差或者相关。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总而言之，&lt;strong&gt;随机&lt;/strong&gt;是一个跟统计、概率相关的概念。无论是在数学、还是在物理相关领域，&lt;strong&gt;随机&lt;/strong&gt;都有着举足轻重的地位，是一个专业的词汇。&lt;/p&gt;
&lt;p&gt;而在计算机领域，有一个词叫 RAM (Random Access)，中文书本里全部翻译为&lt;strong&gt;随机存取&lt;/strong&gt;或者&lt;strong&gt;随机访问&lt;/strong&gt;。记得当时在看到这个翻译的时候，我是很迷惑的，虽然对这个词表达的意思已经形成了条件反射般的肌肉记忆，但我一直没有深究为什么它叫 Random Access，也没有深究为什么它的中文翻译是&lt;strong&gt;随机存取&lt;/strong&gt;。比如，数据结构里一提到数组，我们就会说它的&lt;strong&gt;随机访问&lt;/strong&gt;的时间复杂度是 O(1)​ 的，我大脑里早已把这个情景下的随机访问自动替换成了按下标、地址访问。但，这个跟术语&lt;strong&gt;随机&lt;/strong&gt;可有半点关系？&lt;/p&gt;
&lt;p&gt;直到有一天无意中看到了&lt;a href=&#34;https://en.wikipedia.org/wiki/Random_access&#34;&gt;维基百科上对 RAM 的描述&lt;/a&gt;，我才豁然开朗，不怪我当时理解能力差，而是这个词本身就是不准确的，然后我们的中文翻译版本更是错上加错。&lt;/p&gt;
&lt;p&gt;参看维基百科上的描述：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In computer science, random access (more precisely and more generally called direct access) is the ability to access an arbitrary element of a sequence in equal time or any datum from a population of addressable elements roughly as easily and efficiently as any other, no matter how many elements may be in the set. It is typically contrasted to sequential access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先，如果是意译的话，RAM 应该翻译为&lt;strong&gt;直接存取&lt;/strong&gt;；其次，如果非要按字面意思，其实这里的 random，应该理解成 arbitrary，任意的。甚至翻译成&lt;strong&gt;随意存取&lt;/strong&gt;都比&lt;strong&gt;随机存取&lt;/strong&gt;要更符合原意。&lt;/p&gt;
&lt;p&gt;然而，这么多年，我从来没有看到一本书里有提到&lt;strong&gt;随机存取&lt;/strong&gt;里的&lt;strong&gt;随机&lt;/strong&gt;是什么意思，真是一件很奇怪的事情。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>掀桌子的 MinIO</title>
      <link>https://pureage.info/post/scalability-of-minio/</link>
      <pubDate>Fri, 15 Nov 2019 16:46:29 +0800</pubDate>
      
      <guid>https://pureage.info/post/scalability-of-minio/</guid>
      
        <description>&lt;p&gt;MinIO 是一款用 Go 语言编写的兼容 S3 的对象存储。一年多以前我曾经花了短暂的时间对其做了一个浅尝辄止的了解。当时我对朋友们说了下面的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个不支持扩容的存储也能叫存储？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个戾气比较重的评价产生的背景是，MinIO 不支持传统分布式存储的一个标配功能：横向扩容。&lt;/p&gt;
&lt;p&gt;但是一年以后，我的看法有一个 180 度的大改变，并对 MinIO 团队产生了非常高的敬意。这个敬意来自于，他们敢于打破桎梏，敢于“掀桌子”。&lt;/p&gt;
&lt;p&gt;前面说到，一个普遍的认识是分布式存储必须要能扩容。但是，扩容做起来像说起来这么容易吗？我相信绝大多数在一定量级以上做过扩容操作的人都会认同这是一个痛苦的事情。&lt;/p&gt;
&lt;p&gt;痛苦是正常的，因为这个事情的复杂性是逃不掉的，在存储容量到达一定量级之后，无论你选择的存储是中心结构的，还是非中心结构的，是基于数据库，还是使用各种改进型的一致性哈希，都会涉及到大量的数据迁移。而在迁移的过程中，当然旧文件的读，新文件的写不能停。理论上大多数分布式存储都能做到热迁移，实际执行起来需要注意的事情太多，服务多少都会受到影响。&lt;/p&gt;
&lt;p&gt;面对一个棘手的场景，该怎么继续走下去？比较明显的分成了两派意见，一派是大多数人的做法，坚持把迁移过程做到稳定可靠，小心驶得万年船。另一派就是 MinIO 团队的做法，“我不玩了”，干脆不支持这种传统意义上的横向扩容，数据不用迁移。&lt;/p&gt;
&lt;p&gt;看到这里，你可能会觉得，这是一种弱者的玩法。但事实并非如此，MinIO 团队就是原 GlusterFS 的主要开发者。在存储这个领域摸爬滚打这么多年，选择这样一个设计，这中间必然融入了他们深入的思考和血泪的教训。&lt;/p&gt;
&lt;p&gt;MinIO 的典型使用场景是在私有云存储，一个集群部署好之后，基本上就不用再费劲去维护它。底层使用纠删码保证数据的安全，坏盘只要不超过一半数量（默认情况，可调），数据就是可用的。由于没有内部的数据迁移，架构和功能变得极简单，稳定性也因而增强了很多。&lt;/p&gt;
&lt;p&gt;但是横向扩展仍然让人如鲠在喉，是不是他们就真的完全放弃扩容这一场景呢？&lt;/p&gt;
&lt;p&gt;其实并非如此，MinIO 同样具有横向扩展能力，只是它走了另一条路，
他们巧妙的将一个已经被公认为分布式存储自身的复杂问题，变成了一个部署问题。简单的说，就是如果要扩容，就再增加新的独立的集群，业务方自行决定使用具体哪一个集群。&lt;/p&gt;
&lt;p&gt;具体实施起来，我随便说几个方案。&lt;/p&gt;
&lt;p&gt;第一种方案，把集群信息编码到 url 中， 从 url 中定位到具体的集群。其实 MinIO 官方推荐的联邦模式也是这一种方案，只不过它具体针对的是 bucket 名做处理，这一过程是借助 etcd 和 coredns 来完成。&lt;/p&gt;
&lt;p&gt;第二种方案，调用方在 header 里指定集群，负载均衡服务做转发。&lt;/p&gt;
&lt;p&gt;这两种方案针对的场景比较有限，那就是私有云下，资源由业务方自己控制，集群信息也因而可以自己指定。&lt;/p&gt;
&lt;p&gt;那么，如果是在公有云的场景下，是不是就完全不能做呢？还是有一些方法的。&lt;/p&gt;
&lt;p&gt;第三种方案，部署双集群，每次写入都做双写，其中一个是备份集群，平时只写不读。当需要扩容的时候，启动新的大容量的集群，原只写不读的备份集群变成只读不写，写入则落到新的集群。读的时候，新集群里如果没有，再从备份集群中读，并慢慢将旧数据迁到新集群中去。&lt;/p&gt;
&lt;p&gt;第四种方案，使用一个中心的 kv，将每个资源与集群的关系对应起来。这种方案看起来这个中心的 kv 会变成瓶颈或者单点，可以使用一些高性能的分布式 kv 来做，比如 tikv。退一步说，即使 kv 完全挂掉甚至数据完全丢失，我们仍然可以通过对集群做轮询来重建映射关系，当然要付出在重建映射期间轮训带来的性能损失。&lt;/p&gt;
&lt;p&gt;第五种方案，使用一致性哈希或者各种改进型的一致性哈希方案，计算 url 对应的集群。扩容的时候，写入操作查询新哈希，读取操作先查新哈希，再查旧哈希。同时，集群中各设备慢慢将自己硬盘上需要迁移的数据迁到新的哈希关系决定的目标设备中去。&lt;/p&gt;
&lt;p&gt;这最后两种方案，大家可能会禁不住暗笑。这不就是完全借用了传统的分布式存储中资源到节点映射关系的两种典型方案吗？&lt;/p&gt;
&lt;p&gt;当然，这些方案都是我随意冒出来的想法，实际落地肯定需要根据自己的业务做大量严格的测试。你肯定也有自己的想法。&lt;/p&gt;
&lt;p&gt;写到这里，我们对 MinIO 的横向扩展做一个总结。如果你是用来做私有云存储，那 MinIO 非常适合你。横向扩展简单，且完全不会有数据的迁移。如果你是用来做公有的云存储，那 MinIO 不能直接使用，你需要在外围自行设计一个扩容的方案。&lt;/p&gt;
&lt;p&gt;为什么我现在会对 MinIO 团队有很高的敬意呢？&lt;/p&gt;
&lt;p&gt;因为 GlusterFS 项目证明了他们有能力做出复杂的系统，而 MinIO 项目又证明他们不仅有能力做出复杂的系统，还有能力从泥潭中&lt;strong&gt;跳脱&lt;/strong&gt;出来，放弃一些东西，做出一个简单的系统，开创一片新的天地。这种思维，值得我们去学习。&lt;/p&gt;
&lt;p&gt;附一些 issue 作为参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/minio/minio/issues/7986&#34;&gt;https://github.com/minio/minio/issues/7986&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/minio/minio/issues/4364&#34;&gt;https://github.com/minio/minio/issues/4364&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      
    </item>
    
    <item>
      <title>为什么这两个月没更新博客</title>
      <link>https://pureage.info/post/why-this-blog-not-update-so-long/</link>
      <pubDate>Sun, 03 Nov 2019 00:01:46 +0800</pubDate>
      
      <guid>https://pureage.info/post/why-this-blog-not-update-so-long/</guid>
      
        <description>&lt;p&gt;最近这两个月，我没有在本博客上更新一篇文章。这也不是什么罕见的事情，因为各种借口不愿意写文章在过去也发生过很多次。&lt;/p&gt;
&lt;p&gt;但这次还是有一些不一样。我其实还是在写文章，尤其是技术相关类的文章，写了不少。但如在&lt;a href=&#34;https://pureage.info/post/rebuild-my-notes/&#34;&gt;重新打造我的笔记系统&lt;/a&gt;中提到的，我现在在用 github 的 issues 来构建我的笔记系统，在写那篇文章时，只是刚刚将一种想法付诸行动，而这两个月，我开始迷上了这种记录的方式，自然就在博客上花的时间少了。&lt;/p&gt;
&lt;p&gt;博客我也是不愿意荒废的，以后我会尽量平衡好这两者的关系，一些适合公开的技术笔记也会放出来。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>再见，知乎</title>
      <link>https://pureage.info/post/leave-zhihu/</link>
      <pubDate>Fri, 30 Aug 2019 17:36:06 +0800</pubDate>
      
      <guid>https://pureage.info/post/leave-zhihu/</guid>
      
        <description>&lt;p&gt;印象中大概从 2012 年就开始玩知乎，这些年在上面花了不少时间，也生产了一些质量不高的内容，但其实很早就开始有离开知乎的想法了，今天做出了实际的行动。&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;</description>
      
    </item>
    
    <item>
      <title>Rust 中 struct 与其 method 定义中的范型标识符</title>
      <link>https://pureage.info/post/generic-type-identifier-in-struct-and-method-definition/</link>
      <pubDate>Sat, 24 Aug 2019 11:10:10 +0800</pubDate>
      
      <guid>https://pureage.info/post/generic-type-identifier-in-struct-and-method-definition/</guid>
      
        <description>&lt;p&gt;Rust 中，在 struct 定义中使用范型的方法，是在 struct 名称后跟&amp;lt;范型标识符&amp;gt;，例如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而在 struct 的方法的定义中使用范型，则是形如：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;impl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Int&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意这里有两个范型标识符&lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;impl 后面的&lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt;并不是可有可无的，如果其后有&lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt;，则后面 struct 名字后面的&lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt;才会被编译器认为是范型，否则编译器它是一个具体类型。&lt;/p&gt;
&lt;p&gt;Rust 使用这种方式可以来实现对一个范型的不同具体类型定义不同的方法的功能。&lt;/p&gt;
&lt;p&gt;如下面的代码所示，x 方法为所有 Int 实例所共有，但 x32 方法和 x64 方法则分别仅为具体类型为 i32 和 i64 的 Int 实例所有：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Int&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x: &lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;impl&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; Int&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;T&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;impl&lt;/span&gt; Int&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i32&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x32&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i32&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;impl&lt;/span&gt; Int&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i64&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x64&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i64&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;self.x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Int { x: &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    println!(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;p.x = &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, p.x());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    println!(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;p.x32 = &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, p.x32());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//下面的注释掉的是错误语句
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//println!(&amp;#34;p.x64={}&amp;#34;, p.x64());
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; p2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Int {x: &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;i64&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    println!(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;p2.x = &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, p2.x());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//下面的注释掉的是错误语句
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//println!(&amp;#34;p2.x32 = {}&amp;#34;, p2.x32());
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    println!(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;p2.x64 = &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, p2.x64());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      
    </item>
    
    <item>
      <title>Rust 已经让我爱不释手了</title>
      <link>https://pureage.info/post/refine-my-first-rust-project/</link>
      <pubDate>Wed, 21 Aug 2019 21:26:11 +0800</pubDate>
      
      <guid>https://pureage.info/post/refine-my-first-rust-project/</guid>
      
        <description>&lt;p&gt;在昨天写了自己的第一个 Rust 程序后，今天抽了一点时间把代码结构优化了一下，另外引入了 clap 这个第三方 crate 来做参数解析。&lt;/p&gt;
&lt;p&gt;在 github 上找到了 pingcap 的 &lt;a href=&#34;https://github.com/pingcap/talent-plan&#34;&gt;Rust培训计划&lt;/a&gt;，下一步会把它这个教程完整的走一遍，在夯实语言基础的同时，看看业界在实践中是怎么做的。&lt;/p&gt;
&lt;p&gt;就这短短的几天时间，Rust 已经让我爱不释手了，好像重新找到了大学时刚学编程的那个兴奋劲，有好多东西等着自己去深挖。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>用 Rust 写了一个博客的客户端发布工具</title>
      <link>https://pureage.info/post/test-for-blog-writer-by-rust/</link>
      <pubDate>Tue, 20 Aug 2019 23:25:18 +0800</pubDate>
      
      <guid>https://pureage.info/post/test-for-blog-writer-by-rust/</guid>
      
        <description>&lt;p&gt;这个事情本身没有什么好说的，算是刚刷完 Rust Book 之后自己额外写的一个 hello world 级的程序，只是这个程序对我自己还有点实际用处而已，用来简化了我写博客的一些流程。&lt;/p&gt;
&lt;p&gt;本来这种事情我是最擅长用 shell 来解决，强迫自己用 Rust 来写实在很别扭，但是这恐怕是不可避免的阵痛了，希望自己能尽快渡过这个不适期。&lt;/p&gt;
&lt;p&gt;这篇文章本身就是由这个工具来管理整个书写、发布流程的，算是一个测试文章。使用起来的感觉还挺好。&lt;/p&gt;
&lt;p&gt;初步印象，Rust 真是个好东西，继续用下去吧。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>在阅读《Rust 编程之道》时的一次抬杠经历</title>
      <link>https://pureage.info/post/a-misunderstanding-of-tao-of-rust/</link>
      <pubDate>Fri, 16 Aug 2019 23:59:34 +0800</pubDate>
      
      <guid>https://pureage.info/post/a-misunderstanding-of-tao-of-rust/</guid>
      
        <description>&lt;p&gt;最近学习Rust,在看完了官网上的 Rust Book 之后，我买了一本张汉东老师著的《Rust 编程之道》，来两本书对照着再看一遍。&lt;/p&gt;
&lt;p&gt;今天要说的，就是发生在阅读《Rust 编程之道》时发生的一个我认为很有意思的小事。&lt;/p&gt;
&lt;p&gt;本文标题中提到“抬杠”，实际上我并不是就书的某些内容跟作者或其他读者抬杠，而是与自己抬杠。&lt;/p&gt;
&lt;p&gt;起因是我读到的该书中的一段话，原文如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;条件表达式一定会有值，所以 if 表达式的分支必须返回同一个类型的值才可以。这也是 Rust 没有三元操作符 ?: 的原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于我之前已经完整的读过了一本 Rust 教材，而且也有其他语言的编程基础，所以这句话里传达的知识点本身我都已经是明白的。但读完后第一感觉就是不顺。不知道其他人看到这一段话第一反应会怎么理解，我的心理过程是这样的：&lt;/p&gt;
&lt;p&gt;第一句，“条件表达式一定会有值，所以 if 表达式的分支必须返回同一个类型的值才可以”，没问题，这一句很简单，跳过。&lt;/p&gt;
&lt;p&gt;第二句，“这也是 Rust 没有三元操作符 ?: 的原因”，看上去有点意思，关于来龙去脉的东西要好好看一下，回顾一下上一句，上一句是”所以 if 表达式的分支必须返回同一个类型的值才可以”。有一个&lt;strong&gt;必须&lt;/strong&gt;哦，所以说 Rust 没有三元操作符是因为三元操作符不满足这个&lt;strong&gt;必须&lt;/strong&gt;的要求？也就说，因为 if 表达式必须返回同一个类型的值，但三元操作符不满足这个要求，所以 Rust 中没有三元操作符？&lt;/p&gt;
&lt;p&gt;我心里马上咯噔一下，三元操作符还有这毛病？印象中不是这样啊。于是随手写了几行 c 代码用 gcc 验证了一下，更坚定我的信心了，至少在 c 里面，三元操作符是没有这个问题的。于是我在微信里向张汉东老师本人求证：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;汉东老师，在 2.5.1 条件表达式这一节，书里说 “表达式一定会有值，所以 if 表达式的分支必须返回同一个类型的值才可以。这也是 Rust 没有三元操作符 ?: 的原因。”&lt;/p&gt;
&lt;p&gt;读起来感觉这一句话有点问题，感觉您想表达因为三元操作符可能会返回不同类型的值，所以才没有支持三元操作符。&lt;/p&gt;
&lt;p&gt;一方面，在 c 里面，虽然 int a = 1 == 2 ? 2 : “aa”; 这样的语句编译器不会报错,但是会报类型不匹配和一个从指针到整数的类型转换的警告，而且这个其实跟三目运算本身没关系，直接用 int a = “aa”,也是会涉及到相同的警告，即类型不匹配和一个从指针到整数的类型转换。&lt;/p&gt;
&lt;p&gt;另一方面，我查了一下 rust 以前的 issues，比如 &lt;a href=&#34;https://github.com/rust-lang/rfcs/issues/1362&#34;&gt;https://github.com/rust-lang/rfcs/issues/1362&lt;/a&gt; （不止这一个），里面的讨论也大部分都是基于有没有必要在 if else 满足条件的情况下引入一个三目操作，而且 rust 之前的版本里本来是有三目，后来有觉得没必要所以去掉了。&lt;/p&gt;
&lt;p&gt;总之我想表达的是可能 rust 没有三元操作符不是因为这个原因，不知道我理解有没有问题。谢谢：）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;张汉东老师很快就回复我了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你想太多了。我想表达的就是两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;if 是个表达式。表达式自然要返回同一个类型的值。&lt;/li&gt;
&lt;li&gt;有了这个表达式就没必要引入三元操作符了（所以，没有三元操作符），你看看 Rust 的语法，基本上是避免同一件事引入多种方法。再加上三元操作符中的两个符号「:」和「?」，在 Rust 里会破坏一致性。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个回复非常清楚，没有任何问题和歧义。本身这几个知识点我是理解的，所以我非常好奇，是什么原因导致我把他的原句理解成了我上面所说的样子，这个原因必须找出来。&lt;/p&gt;
&lt;p&gt;再次回顾一下原文：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;条件表达式一定会有值，所以if表达式的分支必须返回同一个类型的值才可以。这也是 Rust 没有三元操作符 ?: 的原因。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;顿时恍然大悟。&lt;/p&gt;
&lt;p&gt;这两句话的最重要的地方是第一句话的前半部分，即“条件表达式一定会有值”，基于此，会产生两个结果，其一是 if 表达式的分支必须返回同一个类型的值，其二是 Rust 中不必有三元操作符。这两个结果的关系是&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;读一本书，能完全读懂作者在写作时的心理状态和想表达的内容，真不是一件容易的事情。逻辑性很强的一本编程书都是如此，其他领域，尤其文学作品中就更难了。我想也正因为如此，才导致了一千个读者就有一千个哈姆雷特吧。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>重新打造我的笔记系统</title>
      <link>https://pureage.info/post/rebuild-my-notes/</link>
      <pubDate>Wed, 29 May 2019 13:25:01 +0800</pubDate>
      
      <guid>https://pureage.info/post/rebuild-my-notes/</guid>
      
        <description>&lt;blockquote&gt;
&lt;p&gt;笔记系统，不就是个云笔记吗？说的这么高大上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从 2013 年起，我就开始将自己的各种笔记和收集到的零散知识放在有道云笔记上。到现在为止，客观的说，有道云笔记的表现是很让我满意的。网友反映它会丢笔记等种种严重的毛病，我基本都没遇到过，有印象的几次问题表现在同步出了冲突，但并没有导致笔记丢失，而是同一份笔记生成了几份。&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;想想家里的卫生状况就知道了，彻底来了一次大扫除之后，如果我们比较懒，卫生状况就会越来越糟，这个变糟的过程可能会持续很久，直到有一天它忽然就到达了令我们反感的“阈值”，不重新打扫是不行了。&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;在了解和适用了一大堆开源 wiki 应用比如 gollum、tiddlywiki 等之后，我决定不使用这些工具。我计划将笔记写在 github 的一个私有仓库里，笔记本身写在这个仓库的 issues 里，因为 issues 的在线编辑非常友好，比如图片拖拽上传等，最重要的原因当然还是 issues 的 tag 了。这样一个笔记从最底层的组织结构上看，是带了标签的。笔记的较高层次的组织，比如分类，放在仓库本身的文件里描述，比如 readme 里，链接到 issues 里的具体笔记。由于需要自己组织这些知识，就会比只有分类来的更加灵活。比如可以就某个专题来组织笔记，而同一个笔记可以属于不同的专题。而传统云笔记由于上层只有分类，则没那么灵活。&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;</description>
      
    </item>
    
    <item>
      <title>一些个人常用的工具</title>
      <link>https://pureage.info/tools/</link>
      <pubDate>Mon, 27 May 2019 18:27:06 +0800</pubDate>
      
      <guid>https://pureage.info/tools/</guid>
      
        <description>&lt;p&gt;这个页面仅仅用来记录我个人常用的一些网页版的工具，对您来说，多半无意义。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://passwd.pureage.info&#34;&gt;密码生成器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tune.pureage.info&#34;&gt;简谱转调器&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      
    </item>
    
    <item>
      <title>《非暴力沟通》读书笔记</title>
      <link>https://pureage.info/post/nonviolent-communication/</link>
      <pubDate>Thu, 09 May 2019 10:43:17 +0800</pubDate>
      
      <guid>https://pureage.info/post/nonviolent-communication/</guid>
      
        <description>&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;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;p&gt;非暴力沟通的使用，是单边可行的，即并不需要沟通的对象也熟悉非暴力沟通。上述四个要素及包括对自己、也包括对他人。对自己很好理解，对他人就是倾听，了解他人的观察、感受、需要和请求。&lt;/p&gt;
&lt;p&gt;在我看来，非暴力沟通的实质其实是聚焦。聚焦自己和对方的真实需求，一切以真实需求为中心，为其服务，从自己的主观情绪中跳出来。&lt;/p&gt;
&lt;p&gt;道理和方法这本书是讲清楚了，执行的事就只能是交给读者自己了。至少我相信，正确的执行非暴力沟通，夫妻间的不愉快将会大大减少。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>混迹技术聊天群容易产生的几个错觉</title>
      <link>https://pureage.info/post/illusions-produced-in-im-groups-of-software-technology/</link>
      <pubDate>Sun, 07 Apr 2019 17:51:22 +0800</pubDate>
      
      <guid>https://pureage.info/post/illusions-produced-in-im-groups-of-software-technology/</guid>
      
        <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;这种错觉的表现，就是某些群友们从来不会好好说话，言必称 A 语言垃圾，B 框架狗屎，C 项目巨烂无比。&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;比如，有人会因为使用的一些开源项目在某些情况下会 coredump，就怒而逢此项目必喷。你要问他们是怎么解决这些问题的，有没有给官方提 issues，有没有自己提交 pull request 解决这些问题，答案多半是没有。&lt;/p&gt;
&lt;p&gt;事实上，任何一个开源项目都必然会有 bug 存在，在使用开源项目时，遇到问题就尝试去反馈问题，解决问题，是使用开源项目的基本素养。&lt;/p&gt;
&lt;p&gt;几十年前，就有一位美国伟人说过：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My fellow programmers ask not what open source can do for you, ask what you can do for open source.&lt;/p&gt;
&lt;/blockquote&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;</description>
      
    </item>
    
    <item>
      <title>将域名转到 Namesilo</title>
      <link>https://pureage.info/post/transfer-domain-from-godaddy-to-namesilo/</link>
      <pubDate>Mon, 25 Mar 2019 10:45:26 +0800</pubDate>
      
      <guid>https://pureage.info/post/transfer-domain-from-godaddy-to-namesilo/</guid>
      
        <description>&lt;p&gt;本网站所使用的域名 pureage.info 自 2011 年注册开始，一直托管在godaddy 上。当年国外的域名注册商家中，只有 godaddy 支持支付宝。我是一个怕麻烦的人，所以虽然这么多年续费费用越来越高，我也一直没有想过将域名转出到其它商家托管。&lt;/p&gt;
&lt;p&gt;直到最近，域名又要续费了，而这个 info 的域名现在居然到了 150 元人民币一年，干脆趁这个机会转出到 namesilo 了。整个过程用了不到五分钟，namesilo 一年的费用是80多块，还借机体验了一下域名转出的流程，真是何乐而不为呢。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>Go 中令人不那么愉悦的 import</title>
      <link>https://pureage.info/post/annoying-golang-import/</link>
      <pubDate>Mon, 18 Mar 2019 14:49:45 +0800</pubDate>
      
      <guid>https://pureage.info/post/annoying-golang-import/</guid>
      
        <description>&lt;p&gt;众所周知，在 github 上参与开源项目的一般流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将开源项目 fork 到自己的名下。&lt;/li&gt;
&lt;li&gt;在本地开发环境中 clone 自己在上一步中 fork 的项目。&lt;/li&gt;
&lt;li&gt;本地完成开发测试和代码提交，再 push 到自己名下的仓库中。&lt;/li&gt;
&lt;li&gt;从自己名下的这个项目中，对原始项目发起一个 pull request。&lt;/li&gt;
&lt;li&gt;发起的 pull request 被上游 merge 后，自己的代码就进入开源项目中了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;虽然具体 workflow 的细节上可能有些差异，但总体流程大概就是这样。&lt;/p&gt;
&lt;p&gt;但是在参与 Go 语言的开源项目中，由于 gopath 的限制，这个流程就会有问题。举个例子，有一个项目为 github.com/userA/project，fork 之后，有一个新的仓库，路径为 github.com/userB/project。而这个 project 中大概率会有内部 package 引用，比如在 main.go中，import 了 github.com/userA/project/moduleA，这样在 go get 自己的仓库时，仍然会引用到原始的仓库路径。将会导致诸如构建失败等一些问题。&lt;/p&gt;
&lt;p&gt;为什么基于 c/c++ 的开源项目就没有这个问题呢，因为 c/c++ 的构建更原始更简单。include 只有两种形式，要么是在标准路径中或项目子目录中去查找，要么在非标准路径下，但能通过编译器的选项来告诉编译器去哪里找，而代码本身则是非常干净的，不会有硬编码的特定路径在源码里面。而 Go 的 import，是一个完整的 url，这样可以让 go get 之类的工具自动化的进行一些操作。&lt;/p&gt;
&lt;p&gt;上面可以解释 Go 项目在引用外部包的时候使用完整 url 的做法，但是在引用项目内部包时，仍然使用完整 url 的做法就多少让人觉得有点不能理解。如果 import 能支持相对路径，就不会有前面提到的问题了。Go 的开发者们当然对这个问题有自己的考虑，参考这个 issue：https://github.com/golang/go/issues/3515 。&lt;/p&gt;
&lt;p&gt;不能简单的说 Go 的这种构建方式不对，因为它的出现必然是有自己的优点和解决了一些问题的，所以从实用的角度出发，我们只能将其称之为“特点”，并去适应它。&lt;/p&gt;
&lt;p&gt;解决办法有几个，但都看上去有些丑陋。比如：&lt;/p&gt;
&lt;p&gt;在本地开发的时候，使用的路径仍然是原始项目的路径，将自己名下的仓库作为一个 remote upstream，push 的时候将修改 push 到自己名下的仓库中，然后再向原始仓库提交 pull request。&lt;/p&gt;
&lt;p&gt;对于一般的开源项目，这种“临时”的方法就够用了。但在下面的几个场景种，这种方法显然会很令人不适：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原始项目已经不维护了，你想自己长期维护一个 fork。&lt;/li&gt;
&lt;li&gt;原始项目仍然在积极维护，但是你仍然想自己长期维护一个 fork。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在上述情况下，其实我们是把原始项目和自己的 fork 当作两个相对独立的项目，我们希望在构建的时候能直接 go build 就行了，不希望有其他额外的操作。如果有第三个人，他想 fork 我们这个 fork 并进行开发，按上面的方法将会非常麻烦。&lt;/p&gt;
&lt;p&gt;对于这种情况，我选择将项目内所有源码中的 import 路径，修改为我自己名下的路径。虽然这种做法很丑陋，也是不得已而为之了。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>徒手攀岩</title>
      <link>https://pureage.info/post/free-solo/</link>
      <pubDate>Tue, 12 Mar 2019 14:09:22 +0800</pubDate>
      
      <guid>https://pureage.info/post/free-solo/</guid>
      
        <description>&lt;p&gt;从小到大做过不少不少恶梦，一个反复出现的场景是我被困在一面绝壁之上，进退维谷。最后通常有两个结局，一是始终在高压的状态下度过这个梦境，有可能会被吓醒；另一个是突然意识到自己正在做梦，然后抛开一切恐惧，纵身一跃，飞翔于天地间。&lt;/p&gt;
&lt;p&gt;以后我才知道，原来有一项叫徒手攀岩的极限运动，我在那些恶梦中所处的场景，是这项运动的爱好者们的日常。不同的是，这不是梦。一个闪失，他们就会粉身碎骨，成为不断增长的死亡列表里的一个新的名字。&lt;/p&gt;
&lt;p&gt;纪录片《徒手攀岩》，就讲述了 Alex Honnold 在无保护措施下成功攀登酋长岩的经过。&lt;/p&gt;
&lt;p&gt;当我们在看一部极限运动的纪录片时，我们看的是什么？对于我自己，是在试图了解这个人，他的动机，他的情感。我觉得当一个人醉心于一种一不留神就会让自己丧命的运动中时，他一定是接近甚至超越一个哲学家的存在。Alex Honnold 确实也是这样，对生死、亲情、友情、爱情都有着自己深刻的理解。&lt;/p&gt;
&lt;p&gt;英国探险家乔治·马洛里在被问及为何想要攀登珠穆朗玛峰时回答，“因为它就在那儿“，成为人们至今仍经常引用的名言。这句话可以用来回答许多极限运动领域中类似的问题。但是现在，对于极限运动的评论往往变成了”这就是为什么外国人这么少“之类的调侃。&lt;/p&gt;
&lt;p&gt;我当然不会死板到因为这些评论的盛行就感慨世风不古，在跟朋友们一起看这些视频时，我也会做出此类调侃。但是静下心来细想，一定会是满满的敬意。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>洛奇 6 中的一段台词</title>
      <link>https://pureage.info/post/a-line-in-rocky6/</link>
      <pubDate>Wed, 09 Jan 2019 18:27:11 +0800</pubDate>
      
      <guid>https://pureage.info/post/a-line-in-rocky6/</guid>
      
        <description>&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;You ain&amp;rsquo;t gonna believe this, but you used to fit right here. I&amp;rsquo;d hold you up to say to your mother, &amp;ldquo;this kid&amp;rsquo;s gonna be the best kid in the world. This kid&amp;rsquo;s gonna be somebody better than anybody I ever knew.&amp;rdquo; And you grew up good and wonderful. It was great just watching you, every day was like a privilige.&lt;br&gt;
Then the time come for you to be your own man and take on the world, and you did. But somewhere along the line, you changed. You stopped being you. You let people stick a finger in your face and tell you you&amp;rsquo;re no good. And when things got hard, you started looking for something to blame, like a big shadow.&lt;br&gt;
Let me tell you something you already know. The world ain&amp;rsquo;t all sunshine and rainbows. It&amp;rsquo;s a very mean and nasty place and I don&amp;rsquo;t care how tough you are it will beat you to your knees and keep you there permanently if you let it. You, me, or nobody is gonna hit as hard as life. But it ain&amp;rsquo;t about how hard ya hit. It&amp;rsquo;s about how hard you can get it and keep moving forward. How much you can take and keep moving forward. That&amp;rsquo;s how winning is done!&lt;br&gt;
Now if you know what you&amp;rsquo;re worth then go out and get what you&amp;rsquo;re worth. But ya gotta be willing to take the hits, and not pointing fingers saying you ain&amp;rsquo;t where you wanna be because of him, or her, or anybody! Cowards do that and that ain&amp;rsquo;t you! You&amp;rsquo;re better than that!&lt;br&gt;
I&amp;rsquo;m always gonna love you no matter what. No matter what happens. You&amp;rsquo;re my son and you&amp;rsquo;re my blood. You&amp;rsquo;re the best thing in my life. But until you start believing in yourself, ya ain&amp;rsquo;t gonna have a life. Don&amp;rsquo;t forget to visit your mother.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      
    </item>
    
  </channel>
</rss>
