如何建立自己的PKMS(个人知识系统)

学习的本质就是记忆

纸笔模型

念书的时候很多人都有做笔记的习惯,我也不例外。上课的时候我习惯的会把课堂笔记写在书页的边角,课后再转抄到笔记本上。一方面重抄强化了记忆,另一方面我会在重抄的时候把散落的知识点整理一下,让它尽可能有条理。

课程在编写时有考虑到知识的连贯性,所以对于课程的知识图谱很容易被梳理出来。我把这种模型成为“纸笔模型”。它基于几个假设:

  • 每个知识节点在创建后基本趋于稳定,不会有过多修改。即便有修改,数字化后的纸笔模型也是“修改友好”的
  • 节点之间的联系稳定且有限
  • 完整的知识图谱内容有限,体积不会太大
  • 节点是随时间添加,检索起来也比较方便,比如按教科书章节检索
纸笔模型

纸笔模型的挑战

工作了之后,我也有尝试过很多次做工作笔记,却收效甚微。和学习笔记不太一样,工作中接触到的知识很散乱。更多的时候需要快速学到一个全新的领域知识,然后应用在工作中,然后再学习下一个领域知识,循环往复。学到的知识本身也会随着时间被更新,或者推翻。这些工具/知识之间并不甚相关,导致了知识的碎片化,并且很难被检索。

这时,纸笔模型的假设受到了挑战:知识点之间的联系微弱,需要大量的背景知识来建立,这些知识会导致没有一个完整的图谱,或者一个巨大的图谱;知识点本身会频繁的更新;检索困难,很多时候单纯的关键字检索很难找到所有相关的节点。这三个挑战推向同一个结论:传统的纸笔模型,甚至说数字化后(对更新节点更友好)的纸笔模型已经不适用于工作笔记了。

简言之,传统的纸笔模型更适合一个有主干,有直线,内容有限的叶脉结构——教科书等天然适合使用这种工具。

知识图模型

而如同之前所讨论的,工作之后接触到的很多领域知识是驳杂,正交的。这样的知识图谱就是很多细小碎屑的枝干,缺乏一个主干来统一。这种模型我称之为知识图模型。

知识图模型

知识图模型的一大挑战就是对节点的多维度,联想性检索。在检索节点时,目前绝大多数工具都只能做到关键字检索。很少,或几乎没有工具能够把“与XX”相关联作为条件来检索节点。

举个例子,假如我今天读到了一个知识,通胀与央行加息之间的关系是如何如何。除非我在这个知识节点内放了“通胀”“加息”“贷款”“利率”等等关键字,否则很难在未来的某天,我想了解”贷款利率”与“通胀”之间的关系。

Logseq

一个好的PKMS工具,需要有这种能力,将相关联的碎片信息进行整理,连接,并在我需要的时候,能够通过寥寥几个节点的检索,就能够将整个图谱的内容提取出来,比如能够帮助我从“贷款”联想到“利率”,到“加息”,一直到“通胀”。

这里介绍一个我自己在用的PKMS工具,logseq。logseq是一个markdown驱动的PKM软件。它通过节点间的双向连接生成知识图谱,并且可以通过关键字,标签,页面引用等来对节点进行多个维度的整理。比如上文的例子中,“通胀与央行加息之间的关系”节点中我可以通过#通胀,#加息,#经济 等不同标签使这个节点和不同的节点互通。当然在可视化时,它也能够从根据不同的维度,快速的建立不同的可视化结构。

除此之外,logseq 还可以通过不同的宏把pkms和各种不同的管理工具结合到一起,比如TODO,读书笔记,个人周报等。还可以在TODO中按优先级,ddl进行排序,永远把“最重要”的事项显示给你。也可以按需来添加不同的插件,进行强大的功能拓展。

它既是一个笔记本,也可以是一个日记本,一个知识图归纳器,一个PKMS,一个TODO,等等等等。更多与logseq相关用法,请参考这里

希望这篇文章能对同样挣扎在碎片化知识的小伙伴们提供一种思路,以期早日进入work hard, play hard的状态。

工作学习论

特别鸣谢宫师姐校对

——甚至这个鸣谢也校对过了

今天突然想写点关于方法论的东西。

起因是组里有个小哥跳槽了。我和他一起做过他离职前最后一个项目——接触之后,我对他的离职早有心理准备。和他导师聊了之后,我们都觉得他缺少工作中学习的能力:和他工作非但没法把工作分出去,反而会给自己增加很多答疑的工作量。

细想之下,小哥在工作中体现出了学习能力和写作能力的不足,导致合作双方平添了许多不必要的工作。如果他能够做到会提问题,善用搜索,勤记笔记,我想我们的合作会更加愉快。

提问的艺术

刚开始工作的时候,我经常会问组里高工(Senior)一些很蠢的问题,要么是太泛没法让人回答(比如“Docker 是啥啊?”),要么是对方不知道从哪个角度来回答你(比如“我该咋用Docker部署啊?”)。在我读了《提问的智慧》之后明白了一件事:一个问题,你问的越具体,事前做过越多的研究,被问的人越好回答你。而越含糊,无脑的问题,别人越难回答。

“在for循环里应该用i++还是++i”就比“这个循环应该咋写”好回答。前者把问题聚焦到一个很小的范围,提供了上下文(是for 循环不是while 循环)并且给出了两个可选项。后者完全让人无法推测这个循环是干什么的。

小哥就经常问我很多含糊、不过脑的问题,让我很多时候不知道该怎么回答。比如:“我这个服务跑不起来”——然后我吭哧吭哧翻了半天日志发现他的启动脚本缺了个标志值;“我这个bug不知道怎么回事”——我去看了一下bug然后找到了问题。

工作中有问题,问题多本身不是一个问题,遇到问题也不要胆怯不敢问。不会问问题,或者说不动脑子直接把问题推给别人,这个才是问题。

主动的搜索

小哥还有一类比较频繁的问题就是“我在哪能找到X?”“这个标志值Y是什么意思”——基本上把X和Y 放到Google里就能直接搜索到结果或者文档。我有时开玩笑说我们都在Google工作了,遇到这类问题更应该支持自家公司的产品,多搜一搜,实在搜不到再问嘛。

在工作中遇到的至少50%的问题完全可以靠搜索引擎找到答案。当然,和上一个话题一样,问题越具体,关键字越多,找到你想要的答案的概率越大。

笔记的习惯

在刚开始工作的时候,我自己是没有做笔记的习惯的。直到我遇到一个有做笔记的习惯同事:他的笔记整理的很好,把他做过的项目细节都整理下来,然后把一些有必要的资料转换成文档。和他交流项目的时候完全可以直接去读他写的文档,然后把文档中有疑问的地方记下来,再单独问这些有疑问的地方。这样极大的节约了彼此的时间。

等到我开始实践记笔记时,才发现笔记这个宝藏真的应该从day 0 就开始记。这样不会因为自己没记住而重复的问同一个人同样的问题。

同样,笔记不单单可以记录工作,也可以用来记录一些突如其来的灵感。这些灵感有的可以加以转化成为新的项目,有些经过论证无效的也可以用作“前车之鉴,后事之师”。

以上是我觉得工作中非常有用的三个方面,写下这块砖,希望能“抛砖引玉”。

CL描述信息的最佳实践

翻译腔依旧

Change List(CL) 是一个公开的,包含有“修改了什么”,以及“为什么修改“的记录。它会被永久的存储在版本控制系统里。并且,不仅仅是你的组员,其他部门甚至其他公司的工程师也常常会参考这些CL描述。

如果你的CL描述过于含糊,重点不明,或者只是简单的“修复bug”,“重构”,“重排格式”,那其他的工程师将很难通过仅仅阅读CL描述来理解你做了什么。同理,当你在阅读别人的CL时,你也不想每次都阅读长长的代码,而是希望有一个简短精准的描述告诉你他/她做了什么。因此,本文档将概述如何写易读易懂的CL描述。

第一行

  • 简短描述你在CL里做了什么。
  • 完整的一句话,尽量使用祈使句。
  • 后面加一句空行

CL描述的第一行应该是一句简短且具体的做了什么的总结,和一个空行。因为大多时候代码搜索工具在显示历史树的时候,它会显示每个CL的第一行。所以第一行应该具有足够多的信息量,这样其他工程师不必点开每一个CL,阅读它完整的描述才能知道这个CL做了什么。

按照传统,CL描述的第一行应是一个完整的祈使句。比如,说“Delete the FizzBuzz RPC and replace it with the new system.” 而不是“Deleting the FizzBuzz RPC and replacing it with the new system.” 不过,除第一句外,剩下的CL描述不必都是祈使句。

正文应当信息丰富

除第一行外,剩余的描述应当提供尽量多的信息。它可能包括对要解决的问题的简要概述,以及为什么这是最好的方法。如果该方法有任何缺点,均应在CL正文中提及。如果有与之相关的设计文档,bug 号,基准测试的结果等,均应提供相应的链接。就算CL只有一行代码修改,也应尽量包含上下文信息。

最差实践示范

“fix bug” 是一种最没用的CL描述。工程师们无法获知:修复了什么bug?如何修复的?和它一样糟糕的描述还有:

  • “Fix build.”
  • “Add patch.”
  • “Moving code from A to B.”
  • “Phase 1.”
  • “Add convenience functions.”
  • “kill weird URLs.”

上述示例中都是真实案例。这些CL的作者可能相信他们已经提供了有用的信息,但是作为CL,只写这些并不足以让其他工程师们理解上下文。就好比你去读书之前,总是希望书的名字会告诉你接下来你会读的内容,比如《C++算法》就是讲C++和算法的。而《书》,《这是一本书》这种名字无疑会让读者摸不着头脑。

较好的cl描述示范

功能变更

rpc: remove size limit on RPC server message freelist.

Servers like FizzBuzz have very large messages and would benefit from reuse. Make the freelist larger, and add a goroutine that frees the freelist entries slowly over time, so that idle servers eventually release all freelist entries.

第一行用了寥寥数语描述了这个cl做了什么。正文部分描述了它解决了什么问题,为什么这个cl的实现是一个可行的方案,并交待了一些实现的细节。

重构

Construct a Task with a TimeKeeper to use its TimeStr and Now methods.

Add a Now method to Task, so the borglet() getter method can be removed (which was only used by OOMCandidate to call borglet’s Now method). This replaces the methods on Borglet that delegate to a TimeKeeper.

Allowing Tasks to supply Now is a step toward eliminating the dependency on Borglet. Eventually, collaborators that depend on getting Now from the Task should be changed to use a TimeKeeper directly, but this has been an accommodation to refactoring in small steps.

Continuing the long-range goal of refactoring the Borglet Hierarchy.

第一行描述了这个CL做了什么以及此改动与现有方案的不同。正文部分则描述了具体实现的细节,提供了问题的上下文,以及重构后依旧可能会遇到的问题。并且解释了为什么要重构。

小改动也要提供上下文

Create a Python3 build rule for status.py.

This allows consumers who are already using this as in Python3 to depend on a rule that is next to the original status build rule instead of somewhere in their own tree. It encourages new consumers to use Python3 if they can, instead of Python2, and significantly simplifies some automated build file refactoring tools being worked on currently.

第一句话描述了改动是什么,正文则提供了此次改动的背景信息。 这样读者在阅读时即可了解背景而不用去搜索“为什么要把status.py的Build Rule由Python2 更改到Python3”。

Libra 币

蹭热度一时爽,一直蹭一直爽

昨天FB宣布了他们自己的数字货币Libra 币。各路新闻纷纷报导顺带着把FB的股价推高到了194。昨晚的新消息是各路机构开始呼吁要对Libra加强监管,审查等,又把股价拉了回来。

那么问题来了,要知道市面上各种币数以十万计,为啥偏偏Libra币就能突然爆火?就凭他爸是FB?

简单来比对一下Libra币与其他数字货币

储备金支持

数字货币被人诟病的问题之一就是背后缺少储备金支持,导致空气币满天飞。这家吹完自己的一个币能够在北上换一套房,那边就敢吹自己一个币就能让马一龙带你去火星转一圈。与之相反Libra背后有着储备金的支撑,换句话说就类似你把钱存进银行,你的账户上多出来的数字是有着真正的金钱对应的。所以风险相比其他数字货币低很多。

稳定币

Libra号称稳定币,会与当地法币依一定比例稳定兑换,因而相比比特币更具有交易属性。比特币的波动性使它的投资(机)属性远大于交易属性

挖矿

根据白皮书所说,Libra Coin 的唯一获取途径就是通过法币向Libra 机构购买(是不是很像Q币?),并且销毁途径是用Coin从机构回购法币。而非比特币通过奖励的coinbase来增发新币

交易速度

未见白皮书披露Libra具体交易速度,但是第三节开头有提到可以支持十亿级用户量交易。相比,比特币的每笔交易需要经过约一小时的时间来确认交易记录在主链上。因而Libra相比比特币更具有日常交易属性。

匿名

与比特币一样,所有账户均为匿名

去中心化

虽然Libra标榜自己去中心化,但是从其白皮书而言,我个人的感觉是它更类似于以寡头(机构)为中心的“伪去中心化”数字币。原因有二:
一,寡头完全掌握铸币权
二,白皮书并未披露如何决定所提到的Libra 币与当地法币兑换比例,但推测应有寡头(机构)决定。

从此两点来看,寡头(机构)在Libra币交易中充当一个“应被绝对信任”的角色——有权发币,有权定价。

无国界货币

Libra在发布初期,构想是做无国界货币。最终目标是一处换币,处处潇洒。主要受众大多是移动支付不发达,本地法币波动性大,甚至不存在银行的不发达地区。但是考虑到如果一个地区落后到不存在银行,它的配套设施(网络,带宽,线下购买渠道)很难支撑Libra交易。很可能会面临的一个局面是:发达国家用不着(有自己的联储,货币保值),落后国家用不起,发展中国家不敢用(想想97金融危机?)

监管

白皮书未披露如何监管交易/账户/外汇。这也是很多人对Libra不看好的原因——技术并没有重大突破,瓶颈在于如何通过监管许可。

总结

从Libra的描述上来看,FB对Libra的构想是它能够成为新一代的交易货币而非比特币之类的投资大于交易的投资商品。如果Libra能够解决监管上的问题,那么其未来不可限量。否则,可能只依靠FB的噱头赚一下眼球,然后泯然众币矣。

std::move

21天是肯定没法学会C++的。22天也不行。

C++11里引入了一个新的概念 :移动。 这个概念说简单其实很简单,因为它是介于拷贝和引用之间的一种概念。但还是有很多小伙伴表示不理解移动到底做了什么,如何理解移动

要想理解移动,先来介绍一个概念:左值与右值。

左值与右值

笼统来讲,左值就是能够对其取地址的值。反之就是右值(临时变量也是右值)。比如1, 2.0, true, nullptr这种纯值类型,就是妥妥的右值。再比如 int a = 100;a是左值,100是右值。在给a赋值的过程中,我们将右值拷贝到了左值。(其实并不是完全这样,编译器会做一定程度的优化,但为了方便理解,我们不考虑编译器的功劳)

两个栗子

在早期C++里,我们通常用指针或者引用来传递一个‌大变量,因为可以避免一次拷贝的开销。第一个栗子:如果我们有一个比较大的右值变量,我想将它传递给其他的函数,该如何避免/减少拷贝的开销呢?比较熟悉的朋友可能会说,我们可以传递 const & 来避免拷贝的开销。

没错,在C++11 之前,const & 可以对右值直接取引用。这样我们可以传递引用来避免拷贝的开销。那么下一个栗子就是,如果我们有一个比较大的右值变量,我想将它传递给其他的函数,‌又想要修改这个右值变量的值,该如何避免/减少拷贝的开销呢?

C++11 提出了一个概念:右值引用。语法是两个&。 它允许我们对右值进行直接引用。

移动

理解了右值之后,我们来思考一个问题。当我们进行赋值语句时,其实是构建了一个右值,然后复制给了左值。那么有没有花销更小的方法呢?答案就是移动

熟悉C++的都知道C++中有构造方法。构造方法又有拷贝构造赋值构造两种特殊的构造方法。C++11提出了新的构造类型:移动构造。也即方法接受的参数是一个右值,然后通过移动的方式,减少拷贝与销毁的开销,实现资源的转移。样式为: Type::Type(Type && rval)

另一个作用就是,std::move在移动的时候,可以 剥夺原变量的数据所有权。通常进行所有权转移时我们会显式调用std::move 方法。

vector来举例。

vector<int> a {1,2,3,4,5};
for (auto i : a){
    cout << i<< ", ";   // --> 1, 2, 3, 4, 5   
}
vector<int> b  = move(a);

for (auto i : a){
    cout << i<< endl;   // Nothing, moved to b
}
cout << "----" <<endl;
for (auto i :b){
    cout << i<< ", ";   // --> 1, 2, 3, 4, 5   
}

联通

C++ 11 中的移动语义使得我们在构造临时变量,减少构造与析构的消耗,并且可以显示表达所有权的转移。既增强了代码的运行效率,又增加了代码的可读性。

move不产生新变量,它只是c++的搬运工。

华为手机

科技博主来蹭政治热度了

昨天收到了一个朋友的微信消息:

XX,请教你下,这次Google禁华为,到底是哪些功能不能用了?我没大看明白:一个是以后的OS都不更新?然后所有Google 的apps 都禁止下载?那访问呢,比如已经下好的apps?

为了防止消息不准确造成的错误解读,我是等到了今天白天看了一圈新闻之后才打算写这个博文的。

首先回答下小伙伴的微信:

  • 哪些功能不能用:Google play,GMS 等Google原生的app
  • 以后的OS都不更新?:不,按照现在的Android 开源授权,依旧会有更新,只是没有办法预览新系统。
  • 所有的Google apps都禁止下载?:应该是
  • 访问已经下好的apps?这点没有任何确认的消息,有可能已经存在的app可以继续使用,也有可能无法访问服务器了。

接下来谈一谈短期与长期的影响。

短期影响:

对于国内来说,这个短期内几乎没有影响。毕竟国内用不了Google 全家桶这已是既定事实了,那官宣不支持也没有什么大影响。毕竟大部分服务在国内已经有了本土化的替代品。而从操作系统上来说,Android 是开源的,所以华为短期内依旧可以用Android,受益于Android生态。

对于国外来说,这个影响就比较大了。不能用Google 全家桶的Android 手机基本上就是一个只能打电话发短信的功能机了。而且在国外又缺少一套完整的可以替代Google 全家桶的生态,所以在欧洲等地,可以预见华为手机销量的下跌。

长期影响

长期来说,没有了官方合作关系,那么华为没法获得新系统的官方支持、预览等。适配等工作都需要正式版系统版本发布之后才能进行。换句话说,可能一加都已经用了半年的新Android,华为这边才适配好。另外Google也随时有可能更改开源授权来彻底禁止华为使用Android,所以长期来看对于华为影响还是很大的。

但是从好的方面来看,华为的技术独立准备的相当充分。方舟,海思芯片,自研OS等都表面了华为“高筑墙,广积粮,缓称王”的战略思想。也许经此事件,华为 OS会成为第三大移动操作系统。

科学没有国界,但科学家有祖国。同样,技术无国界,但公司有国界。祝愿华为手机继续大卖,技术独立。

最后不替东家洗个地:你们知道Apache也对华为停止合作了么?

所有权与智能指针

没错,技术博主开始写技术文了

从我开始学C/C++的时候,指针和内存管理就是最让我头疼的东西:要么是忘了释放内存造成内存泄露,要么是多重释放造成了访问失败。甚至有些时候一个方法有多个返回语句的时候还要在每一个返回语句前释放一下内存。当时就在想为什么不能像Java一样有一个智能的垃圾回收器,来管理所有的内存呢。

这个痛点一直困扰到我开始学STL。原来STL早就考虑到手动释放内存的繁琐,所以提供了多种智能指针:auto_ptr, unique_ptr, shared_ptr, weak_ptr,并引入了“所有权”这个概念。大大简化了工程项目中的内存管理。也让我意识到我学的是假C++,真C(菜)。

所有权

单一所有权

两句话:所有权的拥有者有义务释放内存或转移所有权。同一时刻只会有一个所有权拥有者。

共享所有权

和用裸指针一样,所有权的拥有者不必负责释放内存,引用计数器会负责释放内存。同一时刻可以有多个所有权拥有者。

unique_ptr

重要的事情说三遍:

unique_ptr 不能被拷贝

unique_ptr 不能被拷贝

unique_ptr 不能被拷贝

unique_ptr 的所有者即为单一所有权的拥有者,因此它无法被拷贝。如果需要转移所有权,需要显示调用 move(unique_ptr) 来转移 或者 unqie_ptr.release() 方法放弃所有权。在生命周期结束时,若 unique_ptr 依然是所有权的拥有者,它会立刻释放掉对应的资源。示例:

unique_ptr<A> a = make_unique<A>();
unique_ptr<A> b = a; // can not assign unique_ptr to other unique_ptr
unique_ptr<A> c(a);  // can not copy unique_ptr
unique_ptr<A> d(a.release()); // OK, a release the resource
unique_ptr<A> e = move(a); // OK, a transfer ownership to e

与裸指针不同,单一所有权明确了资源的所有者,确定了资源该于何时被释放,增强了代码的可读性。当然,传递 unique_ptr 引用这种合法不合理的事也别干,要么直接 move 过去,要么只提供 unique_ptr.get() 但不转移所有权。

小惊喜:试试 std::set<unique_ptr>,有惊喜。

shared_ptr

shared_ptr 在使用上更像裸指针,不同的地方就是它内部有一个引用计数器,记录了有多少共享所有权的拥有者。当计数归零,它会自动释放掉资源。它的优势是比 unique_ptr 更灵活,在很多需要共享资源的场景(比如多线程共享物件),它符合场景的需求。并且活用 shared_ptr最后走的人关门特性,在某些多线程应用的场景里可以实现会话结束通知,更新标记等功能。

彩蛋:

const unique_ptr<T> & make the world a better place