Meta 大裁员 他的元宇宙失败了吗?

Meta 探索元宇宙投入过高,持续亏损。投资人和员工都在对扎克伯格的元宇宙失去信心。前几天还宣布裁员1万6千人。动荡了整个 IT 行业,简直要跟 Elon 抢风头的意思。

在元宇宙成为热词的时候,扎克伯格紧跟热点将 Facebook 改名叫 Meta。这个动作可谓又快又狠。追热点当然没错,对于一个做社交网络的公司,追元宇宙的热点,这一步没错。
可是扎克伯格真的懂元宇宙吗?这个怀疑应该是从一开始就有的。

元宇宙热炒的初期,人们的第一印象的都是头戴 VR 进入虚拟世界,开启自己的赛博人生。于是扎克伯格真的就给你做 VR 去了。
于是……

这个图出来的时候,我相信业内的业外的,懂行的不懂行的都会从不同的角度惊呆。惊呆:他,真就是这么理解元宇宙?
那这样的元宇宙和 VR 游戏到底有啥区别?
哦,VR 的社交网络?
哦,就相当于我建一个 VR 版本的 qq 空间,这样理解没错吧?
问题是你扎克伯格也这样理解,就有点不对了。

一个极度中心化的平台想搞元宇宙,动机值得怀疑,行为让人不安,最后直接放出这样的原型,我认为是对元宇宙这个概念的一种打击:

  1. 让无限的想象空间缩小成了狭窄的 qq 空间。
  2. 让很多业外人认定了元宇宙是一场炒作,从而放弃关注。
  3. 让真正看好元宇宙未来的人,失去了发挥的机会。
    我还是坚持自己的理解,元宇宙的核心问题还是解决赛博资产的确权。
    网络世界的发展大概是从一开始:“打回来的野猪大家分着吃,谁也别管是谁的”,到后来大公司掌握一切:“全都是我的,连你也是我的”,再到未来,“我的就是我的,你的就是你的”。

要实现这种未来,目前最可行的就是区块链技术。元宇宙是需要区块链作为底层支持的。
我希望扎克伯格浪子回头,别着急做 VR,好好考虑资产的确权问题。或者你干脆放过元宇宙吧,把这个事情留给区块链来做。
放上去年(2021-11-10)的一篇文章,刚刚陈酿了一年,似乎还可以继续支持我今年的这套说辞:
元宇宙三问

记录 postgre 的小坑一枚

Source:
https://serverfault.com/questions/110154/whats-the-default-superuser-username-password-for-postgres-after-a-new-install

CAUTION The answer about changing the UNIX password for “postgres” through “$ sudo passwd postgres” is not preferred, and can even be DANGEROUS!

This is why: By default, the UNIX account “postgres” is locked, which means it cannot be logged in using a password. If you use “sudo passwd postgres”, the account is immediately unlocked. Worse, if you set the password to something weak, like “postgres”, then you are exposed to a great security danger. For example, there are a number of bots out there trying the username/password combo “postgres/postgres” to log into your UNIX system.

What you should do is follow Chris James‘s answer:

1
2
3
4
5
sudo -u postgres psql postgres

# \password postgres

Enter new password:

To explain it a little bit. There are usually two default ways to login to PostgreSQL server:

  1. By running the “psql” command as a UNIX user (so-called IDENT/PEER authentication), e.g.: sudo -u postgres psql. Note that sudo -u does NOT unlock the UNIX user.

  2. by TCP/IP connection using PostgreSQL’s own managed username/password (so-called TCP authentication) (i.e., NOT the UNIX password).

So you never want to set the password for UNIX account “postgres”. Leave it locked as it is by default.

Of course things can change if you configure it differently from the default setting. For example, one could sync the PostgreSQL password with UNIX password and only allow local logins. That would be beyond the scope of this question.

本地写 Hexo Markdown 发到 GitPress 和自己的 blog

  1. 解决 Hexo 不渲染 Markdown 的图片标签的问题:

安装插件:

1
npm install hexo-render-marked

如果有报错,可以再运行一下 npm audit。

config文件作如下修改:

1
2
3
4
5
post_asset_folder: true
#hexo-render-marked config:
marked:
prependRoot: true
postAsset: true

将图片放在文章同名文件夹,就可以渲染图像标签如:

1
2
![](img.jpg)
![](articalname/img.jpg)

至此解决了渲染图片标签的问题,于是把 ‘_posts’ 文件夹创建为 GitHub Repo 并同步到 GitPress 中,这里按照 GitPress 的说明来进行就好,不做记录了。

  1. 解决 GitPress 图片地址出错的问题

GitPress 中读取到图片标签会加上自己的域名,但没有经过 Hexo 自身渲染图片地址,于是和 deploy 到 blog 上的图片地址有冲突。
解决方法简单粗暴的把 Hexo 自身渲染的年月日标记去掉就好:

1
2
#permalink: :year/:month/:day/:title/
permalink: :title/
  1. 发布文章的方法:

3.1. Hexo 发布到 blog:
本地创建新文章:

1
hexo new articalname

通过 Obsidian 编辑文章。
用 deploy 命令发布到自己的 blog。

3.2. git 命令发布到 GitPress:
在本地通过 git 命令把 ‘_post’ 文件夹同步到 GitHub。
GitPress 自动读取该 Repo 并发布。

把kindle的书全部下载到电脑上!

亚马逊宣布 Kindle 将退出中国市场。
买了的书咋个办?亚马逊给的说法是书仍然可以下载并在阅读器中阅读。
但是我的泡面盖子已经好多年历史了,Pad 上的 APP 又给人不踏实的感觉——想起之前还经历过 Pad 版本太旧,Kindle APP 不支持的情况。

所以趁自己在恐慌期间有动力,把下载 kindle 电子书的方法研究出来了:
(有拖延症的朋友也不必太着急,官宣是在2023年6月30日才正式停止运营。)

第一步,工具下载:
首先要感谢这位大佬,这个工具是他创始的,还要感谢之后涌来的一群程序员大佬,做出了图形化界面,才有我们小白的操作空间:

下载地址在这里:
https://github.com/yihong0618/Kindle_download_helper/releases

如果某种不明原因打不开这个网站,可以公众号回复“kindle”获取下载链接。

找到自己操作系统所对应的文件进行下载。
第一个是 linux,第二个是 macOS,第三个是 windows。

第二步,打开下载工具:

第二.1步,点击登录(1 号红圈处),进入自己的 Kindle 图书管理页面:

在这个页面按 F12 按键。以我对小白的了解(我就是),我认为有必要讲一下 F12 在哪里:

第二.2步,在调试窗口中找两串神秘文本,粘贴到相应的位置:
出现了调试窗口,在这个窗口中搜索“csrf”,可以看到“csrfToken=”””这一串文本。

把双引号里面的文本复制下来,粘贴到下图 3 号圆圈处。注意不能复制双引号。

回到调试窗口,找到“network”这一栏,随便找一个带有 200 这个数字的数据,打开:

在小窗口中找到“cookie”,鼠标右键,复制下来:

粘贴到下图 2 号圆圈出:

第二.3步,点击 4 号圆圈处,获取下载列表,等一小会儿,就会出现自己的书了;
第二.4步,点击 5 号圆圈处“浏览”按钮,指定自己的书要下载到哪里(如果是苹果电脑,必须点一次浏览,这样才能给电脑授权):

第二.5步,点击 6 号圆圈处的下载全部按钮。
这个时候会出现进度条,和一些进度提示,耐心等待完成就好了。

第三步,转换成 epub 文件:
下载下来了后缀名是“.azw”的文件,用 Kindle APP 就可以打开了,但是我还是觉得 epub 文件更通用一点,这里提供一个转换工具:
https://www.neat-reader.cn/downloads/converter

画 - 思绪大乱炖

昨晚听了朱利安大王的世界历史课的第一讲。
脑子里是一团乱麻还没有理出头绪。
直到今天和团队的大佬聊起 AI 作画。

我们知道,在 AI 擅长的领域,人类早已一败涂地。而现在艺术领域遭到 AI 的大举进攻。
近一两年时间看到了很多很掉 san 值的 AI 作画,可以说看到 AI 画的这么烂,有点失望也有点安心。
可就在前不久,一个叫 Disco Diffusion 的 AI 作画引擎大火。看到这个 AI 的作品,心情从失望+安心这种奇怪的混合,变成激动+担忧。

这里引用西乔的文章来介绍一下这个 AI 作画工具:
[引用]

一想到这个 AI 也许在人类睡大觉的时候也不眠不休的在学习人类的绘画作品,有点渗人呢。

AI 不眠不休的学习人类,这种焦虑刺激着我认真听课,学习人类:

朱利安大王的人类历史课,一共有七讲。
刚刚听完第一讲,主要是两个部分:
一,人类作为生物这种存在本身
二,人类早期的文明

人类这种生物,可以说是进化树上开出的一朵奇葩了。

门-纲-目-科-属-种。
在属和种这两个分支,智人都是独一无二的存在。上溯到科,也仅有几种猩猩和人放在一起。相较别的物种,一个科下面上百个属上千个种,都是很正常的。
我们在进化树这个大树上伸出一根长长的光溜溜的树枝,都快伸到外星去了。

上亿年的生物进化史,智人的出现才十万年。而且这十万年间,考古查证的其他人属人种生物被排挤的一干二净。这个是 Wikipedia 上扒下来的人类种的时间线:

看看,几百万年漫长的直立人种历史,在最近2万年左右,尼安德特人消亡,只剩下智人,也就是我们。
凭什么智人剩下来了,尼安德特人和其他人种到哪里去了,目前是未解之谜。

作为一种动物,人类搞不明白人类自己。

而作为一种建立了文明的智慧生物,嗨,看看人类在地球上都干了些什么。

这里有个概念:世界上最长的步行路线(the Longest Walkable Distance on Earth)
有个网站介绍了这个路线:
https://www.popularmechanics.com/science/a30285283/longest-walkable-distance-earth/


这条路全程都可以步行通过。长度是14000英里。喜欢徒步的人应该知道,人一天大概可以走50公里,差不多是30英里。我们算一下:14000/30=466天。
一年多。
一个人在理想的情况下(有吃有喝有住)就这么走下去,一年多可以穿过整个亚非欧三个大陆。
再对比智人存在的9万年时间,是不是太长了点,足够我们走遍全世界了。
智人的迁徙路线:


祖先在几万年前走遍全世界,看看我们现在,有了汽车,飞机,火车,远洋巨轮。我们却被自己在地图上画的那一根根线,圈了起来,哪儿都去不了。

我在[元宇宙三问]这篇文章中就说,人类以后也许再也没有自由迁徙的日子了,统统到虚拟世界去当电池,赚取维持肉身存活的营养液吧。

朱利安大王讲到史前文明的壁画。
首先这是3万年前的史前壁画,发现于法国拉斯科洞穴。

一头充满细节的牛

整体观感

大王还提到了另一处史前壁画:
阿根廷平图拉斯河,手洞。
叫它手洞是因为真的有很多只手:

跟前面的法国拉斯科洞穴一样,也有动物:

甚至还有涂鸦:

我看了第一反应,和其他同学一样,觉得法国拉斯科洞穴的画更漂亮。
我们来对比一下两处壁画的年代:

  • 法国拉斯科洞穴,3万年前。
  • 阿根廷平图拉斯河手洞,复杂些:
    动物(其实是大羊驼),9000年前;
    涂鸦,7300多年前;
    手,公元前550年左右……

3万年前的壁画比两万年以后的涂鸦明显漂亮,说明3万年前的人类更聪明吗?

来看看身边有多少孩子到处乱涂乱画吧。
合理的解释是,后来的人类技术更先进,更容易获得颜料和作画工具,更多不专业的人都可以画壁画,甚至是熊孩子。

以前的画家,为了赚取生活费,要给王公贵族打工,给他们画肖像画。或者给神仙打工,给上帝画壁画。那时候颜料也贵,没人付钱的话,画家不太可能自己凭借兴趣爱好画个什么风景画。

周末去参观了印象派的绘画展。

激发印象派画家开创印象派的很重要的一个动因是,相机的发明。
对啊,都有照片,我没必要以写实为目的来画画了。或者,不是没必要而是没需求了。那个时候的画家面对相机的心情是怎样的呢?是否跟现在的艺术家们面对 AI 绘画的心情一样呢?

至少,我看到艺术家们从此开创了越来越多的艺术形式,开始奔放地表达着自己,无论是好坏美丑善恶或者表达不好不坏反正就什么都不是的或者表达凭什么要这样非要是什么不是什么的艺术。

现在我请 AI 帮我画了一幅画来具象化我们当前的一种状态:

感谢 AI 帮我这样一艺术白丁也能用艺术创作来表达自己。
总之,我们走着瞧吧。

敏捷学习方法论

浅尝则止是正义!逃避困难也可以!论敏捷学习方法在编程学习上的应用。

0. 浅尝则止未尝不可

我们讨论学习方法的时候,常有的一个观念就是深入学习,认真钻研总是好的;不深入,肤浅,皮毛,浅尝则止都是贬义词。这篇文章我想说的一个观点是:

浅尝则止未尝不可。

特别是应用在编程的学习上,好读书不求甚解其实是一个不错的学习模式。再来我还会用敏捷思维来完善这种学习模式,形成一个学习方法论。

1. 想学好编程,想学多好?其实是学习的项目管理问题

这里引入一个项目管理的概念:“项目管理三角形”;

如图,想要追求质量,就要投入相应的成本和时间。如果工程范围再不进行限制,整个项目的成本将会爆炸式增长,最后根本收不了工。

要学习一门技能,完全可以套用相同的模型。

不管我在前面抖机灵说什么“浅尝则止未尝不可”,但学一门技能,谁又不想学得炉火纯青,达到一定造诣呢?

在编程领域能达到什么样的学习成果,就要像项目管理那样考虑学习投入的平衡问题。我们把三角形的标注换成这样应该很好理解:

完全套用这个三角形来建立学习方法又有些问题,因为在学习上我们的成本投入基本就是自己的时间,特别如果是通过阅读来学习,不是报各种学习班的话,金钱成本几乎可以忽略。

我们在学习上的投入具体有哪些呢?

  • 首先一定是时间投入,花的时间越多自然会产生更多的学习成果,学界公认,无可辩驳。
  • 接下来要看我们投入时间的质量,其实就是专注度,如果熬更守夜,导致学习专注度不够,投入再多时间,质量一定是低下的。
  • 最后,是兴趣,兴趣可以影响我们的专注度;如果兴趣浓厚,在没有学习的时候我们的大脑都在思考和处理;如果兴趣浓厚,坐电梯都会思考电梯的控制算法,相当于一直在利用碎片时间进行学习。

这三个投入是互相影响的,所以这个公式只好把三个因素乘起来:学习成本投入 = 时间专注度兴趣。给学习成本投入取个热血的名字,叫学力值吧,最终公式:

1
学力值 = 时间*专注度*兴趣

于是三角形中的两边就合起来,画成一个正方形:

那么还剩“范围”这一边,看看编程这个技能有可能涵盖的范围吧:

首先从应用范围来看,

学习编程可以用于数据分析与统计、人工智能、知识管理、游戏开发、文书处理、机械自动化控制……

或者打开你的手机看看有多少 APP 就有多少不同的应用。

而这些不同领域的应用需要的编程知识其实也不尽相同。

再来看学习编程有可能涉及的知识范围:

  1. 编程基础知识,就是我们在各种“从入门到精通“的编程入门书里面学到的那些知识。举例如:

    • 基本语法
    • 数据类型
    • 条件判断
    • 循环、递归、嵌套等过程控制
    • 面向对象的思想
    • 链表、二叉树等各种数据结构
    • 数据库的操作

    等等等等

  2. 数理知识,就像刚刚提到的各种应用领域,在不同的应用领域可能会用到好多不同的数学知识:

    1. 图论
    2. 离散数学
    3. 工程数学
    4. 逻辑学
    5. 统计学

    等等等等

  3. 计算机系统的相关知识,因为编程是在计算机上操作,一些计算机的知识总要懂吧,至少都要学个打字和一些基础操作,在当今网络时代,还要学一些网络知识才能做出实用的软件应用。

  4. 英语!是的,英语技能对于学编程非常重要。会一点点英语才能看懂程序语法。而会很多英语,就可以看英文文档了。偷偷说:在文档里学习才是高效呢。要是读者大大们英语好,写编程教程的人会丢饭碗的(这句划掉)。

综上,如果我们不注意控制学习的范围,我们需要投入的学力值就会去填无底洞了。

那么投入编程学习前,请参考这个模型,根据自己的兴趣、自己能够投入的时间、自己想要应用的范围做一个规划吧。

2. 要深度还是要广度?其实是投入与回报的平衡问题。

前面我们讲了一个平面的学习模型,似乎只要投入学力值就可以很顺利获得学习成果。可事实仍然是编程学习者众,学成者寥寥。其实除了学习者自身的规划与投入,我的观察是,很多编程学习教程本身对范围没有一个好的把握,典型如:《某语言从入门到精通》、《N天掌握某语言》。

回顾我们的学习模型:

这种教程想到达的学习成果是精通、掌握(我理解这两个词是一个意思)。

投入的时间是N天?一本书就从入门到精通?写书的是有多瞧不起自己的专业?

这种书翻开会发现,大同小异:比如先是教你输出一个“Hello World”,然后就是基础知识比如语法、条件判断、循环、数据结构;再讲到面向对象编程;再强行切入网络、数据库这些劝退新人的环节,可能作者都知道后面没几个人在看了就更加不注意对新人的友好度。最后扔几个编程实例,也许就是所谓的精通或掌握了。

我们要意识到的是,范围是广度和深度两个维度构成的。

而这两个维度对学力值投入的需求是不同的。

比方说,我花五天就可能入门五种编程语言(广度),但是要精通一门语言(深度)需要花五年。

学习的广度和投入的程度正相关,呈线性增长。如图所示:

学习的深度和投入的程度也是正相关,但是呈对数增长。如图所示:

而广度本身又会影响深度,这句结论有个很好理解的俗话叫做:

触类旁通。就像我们经常观察到很多人学的东西多不说,学习新东西还能很快理解,抓住重点,找到学习方法,精进自己的技艺。

因此容我再点题:逃避困难也可以。

因为我们以产出的成果为导向来看,程序设计是一个开放性的课题。

比如我们需要实现一个数据存储的需求,数据库可以,看起来很蠢的文本文件也可以。

新手很头疼的递归啊排序啊这些算法是能解决很多性能问题,但在不需要性能的时候,写个很蠢的嵌套循环也可以。

也许会有人质疑,想要把编程学好,认真,努力,迎难而上,为什么到你这就不可以了?

我在本文表达的意思,主要还是针对学力的保护和高效利用,保护我们的兴趣,专注力,更高效地利用学习时间。如果我们提前遇到了高等级的怪兽,先走别的路就好,等我们学了更多,再回过来,常常能意外发现过去的困难并不是困难。

因为程序的这种开放性特点,成了我们学习编程才享有的特权。我们为何不利用呢?

3. 拥抱敏捷:敏捷学习方法

本文开头提到,用项目管理的思维来学习编程。而项目管理的方法论是在上个世纪60年代建立起来的,适用于人类对各种工程项目进行大规模合作。

软件开发行业的出现显然是晚于传统项目所在行业的。到了2001年,一群软件行业大佬相约去滑雪,互相吐槽抱怨了几句终于发现大家都不太认同旧时代的章法,于是滑雪之余顺便签署了一份“敏捷宣言“。

敏捷宣言的原文以及配套的十二条原则可以在官网上看到(链接附后),经过多年的发展,敏捷方法已经形成了一套成熟的管理体系。这套体系大概长这样:

不是开玩笑,这真就是传统工程项目和软件工程项目的最大区别。

在传统工程中,如果要建造一辆汽车,需要一个零件一个零件生产,组装,最后完工才能投入使用。

敏捷开发是这样:我先做滑板车,不需要考虑路况、安全、燃油、性能,满足代步的基本需求先。下一步升级可以把轮子放大,加上把手,就变成了踏板车。

把用户正踩脚下滑着的滑板车轮子改大?每次软件更新不就是做类似的事吗?就是说每一个小的项目进度都可以单独成为一个独立运行的产品。

说了这么多软件工程的项目管理方法,我跑题了吗?

别忘了,我们在讨论编程的学习方法,编程的产出就是软件啊!

是的,编程学习完全可以采用敏捷思维来进行!

回顾我们上一节讨论的学习范围示意图。我们确实知道,一味的浅尝辄止和逃避困难,导致在一定范围内深度不足,就无法达成我们想要的学习成就。但经过本节的讨论,我们是不是可以试着,敏捷地,达成学习成就呢?

即使刚入门的新手,抱着“想要做一辆跑车“的心态去学习,有错吗?为了实现这个远大的目标,我们知道要一步一步的来。

那同样的一步,我们学着做一个滑板车还是做一个跑车的轮子?(也许直到放弃都用不上这个轮子)

敏捷方法给软件行业带来了巨大改变,我认为敏捷也能够让我们的编程学习得到效率的提升。

也即是我们探寻好的编程学习方法的最终目的:

在一定学习范围内,用更少的学习投入,获得更多的学习成果。

4. 学习成果的积累

敏捷学习方法还会给编程学习者带来一个非常重要的好处。就是学习成果的积累!

如果是按照传统的编程学习模式,从入门直到放弃,我们都没有造出一辆跑车,最后硬盘里会散布着堆积着一辆永远不可能完成的跑车的零件。

如果按照敏捷方法来学习,我们一开始就能造出一辆滑板车,等我们最终造出跑车的时候,我们已经造了自行车、电瓶车、摩托车。

是的,编程学习特别适合积累学习成果:

  1. 软件不会占用物理空间。

放硬盘里,仓储成本不要太便宜。

要是做木工,学徒时期做的东西只能当柴烧了。

  1. 代码可以复制粘贴。

每一次产出学习成果都可以复用原来的代码做一个新的版本,并且保留上一个版本。

要是做木工,做好的板凳不光很难改,就算修改也只能在原来的板凳上改。

  1. 可以修改更大的成熟的项目,放大我们的学习成果。

举例如 Github,我们的学习成果甚至可以提交成为某个项目的贡献,我们也可以 fork 别人的项目作为自己的项目在上面修改。

要是做木工,师傅是不会把他做好的成品家具给你练手的。

  1. 更容易应用自己的成果。

我们可以把自己做的东西放到自己的网站上,打包放到手机上,就可以用了。就算是发布到应用商店也是我们可以承受的价格。

要是做木工,我们做好的板凳也是可以自己用的。做得好的挂淘宝上卖?好像也可以?(划掉划掉)

总之,应用敏捷学习方法,我们可以积累更多的学习成果,我们投入的宝贵学力可以取得更多的收获,更多的收获又可以激励我们的学习兴趣。

怎么实践我们的敏捷学习方法呢?我模仿滑雪场那群大佬拟了一份敏捷编程学习宣言,包含了一套价值观和一些原则性的方法,希望对各自努力前行的自学者们有所帮助:


敏捷编程学习宣言

作为自学者,我们身体力行地实践和探索更好的编程学习方法,提高了自己的学习效率。为帮助更多的人,我们建立这样一套价值观:

保持兴趣和专注地高效学习 高于 大量投入时间的学习

平衡学习范围的深度和广度 高于 大量投入学力追求深度

在小范围内及时产出成果 高于 对知识点的全面掌握

响应变化 高于 遵循计划

以上,尽管右项有其价值,我们更重视左项的价值。

敏捷编程学习方法

基于以上的学习宣言,我们总结了一些可以遵循的原则来实践适合自己的敏捷学习方法:

我们最重要的目标,是产出学习成果。

寻找容易产生学习成果的编程语言入手。而这样的编程语言容易产生学习成果:开发环境搭建简单;编译和打包简单;发布和运行简单。

寻找社区很活跃的编程语言入手。社区活跃意味着有很多方便的库可以用,很多热门项目可以参与,很多问题都能 Google 到答案。

不用局限于一本书,可以同时阅读多本书籍。因为不同的书籍目录结构不同,学习范围的广度和深度都不同,我们可以根据自己的需要来搭配书籍。

尽量选择电子书这类非实体的教材。便于我们用 Ctrl+F 来检索书籍,还可以 Ctrl+C Ctrl+V,大量提升学习成果的产生效率。

用好官方文档,因为教材覆盖的范围有限,官方文档可以帮我们兜底,补充我们实现学习成果所需的知识点。编程语言基本上都有自己的官方网站,而官方网站基本上有官方文档。找到它们,在不懂的时候先搜索一下官方文档。

用好 Google。在教材、官方文档都解决不了我们问题的时候,Google 是最后的兜底手段。希望你能尽量用 Google 来搜索自己需要的知识点,实在因为网络原因用不了,还可以试试 Bing。如果用某度,只会给我们带来学力的损失。

遇到上面的办法都解决不了的困难,先绕开困难。条条大路通罗马,甚至小路也可以。实现一个可运行的程序有非常多的方法。

最后,简洁为本,以最小可运行的产出为目标来学习,大胆略过书中用不上的知识点,减少不必要的学力投入。


附:

敏捷宣言 https://agilemanifesto.org/

用 Go 语言写焦虑发生器并发布到 Rum 上·最终篇

初学 Go 语言是在去年 11 月 20 日。到今年 2022 年的 2 月 3 日,我写了一个小 bot 连续运行成功,发布到了 github 上开源。花了两个多月时间。我感觉效率还是不错的。
于是我这就来记录这段学习经历:
目的一是让同样正在跨编程入门这道薛定谔之忽高忽低门槛的小白同学一些参考,更顺利的入门编程;
目的二是回顾并巩固自己过去的学习,为下一步继续学习打好基础;
目的三是让更多的人能够对 Rum 这个新的东西感兴趣。
希望能成功达成目的:

到服务器上去运行 bot

Rum 是一款建立在区块链上的去中心化的内容平台。

这个定义不一定准确,因为 Rum 现在还是非常初级的阶段,想象空间非常大,提前下定义只会限制想象。这也是我选择 Rum 来学习 Go的原因吧。
前面三篇我们做好了一个可以持续运行并给 Rum 发送内容的 bot。我们自己的电脑不一定会长期在线和联网。于是我们可以在服务器上去运行 Rum,同时再运行我们写好的 bot。

我自己搭建了一个运行 Rum 服务端(Rum 服务端我们称为 quorum)的服务器环境,再用我本地的电脑去连接,以便我后续在本地写 Go 程序能够更容易地在服务器环境上去测试。这个过程我也总结了一篇文章:
从零开始在 Ubuntu 20.04 上Build Quorum 并用本地 Rum App 进行连接

上面提到的文章也是写给我这样的新手小白看的,目的都是帮助新手避免踩坑,与帮助自己巩固基础并进行下一步的学习。搭建好环境之后,像我们自己在电脑上测试 bot 一样,把我们的代码放到服务器上运行就可以了。

变得更酷之立 flag(带参数运行)

既然到了服务器上去运行就不得不用命令行进行操作。命令行对我这个小白来说,用起来即使不方便,感觉上很酷,就够了。我们自己写的程序要是也需要输入一个带参数的命令才能运行,岂不是酷毙了。

也是一番搜索,查到我们要带参数运行命令,需要一个包叫做 flag。是的,在命令行里带参数,就是立 flag。
我们 import 这个包:

1
2
3
import (
"flag"
)

然后在 main 函数里立起一个新的 flag

1
2
flagGroupID := flag.String("gid", "fe2842cb-db6b-4e8a-b007-e83e5603131c", "group ID, default ID is for testing")
flag.Parse()

调用 flag.String 方法,需要三个参数,第一个是 flag 名,这里我写的 gid,group ID 的简写;第二个是默认值,我用了“Go语言学习小组”的 ID;第三个是一个说明文档,用户用了 -h 的 flag,可以告诉用户怎么使用。
填完了该填的参数,需要用 flag.Parse() 来解析用户传递的 flag。
最后需要注意的一点是, flag.String 返回的是一个地址,要用上 flag 的值需要加上 * 这个符号,于是 postToRum 这个函数的第三个参数现在写成这样:

1
postToRum("2022 进度条", progressBar, *flagGroupID, url)

变得更酷之通过外部 config 文件配置参数

要正式运行这个代码了,会有好多测试,每次都输入一长串的 ID 感觉很麻烦而且一点都不酷。
看了大佬们的代码,经常都会有一个配置文件,把很多东西写好在配置文件上,再来运行。
酷!我们也来搞这个。

先设计一下我们配置文件的结构,之前学过一点 Json 的数据结构,所以也没多想,就用 Json 来作为配置文件的格式吧。然后画图进行一个简单的框架设计:

这里面计划的功能冗余了一些,很多都没用上,比如可以选择不同的发送方法什么的,可以留给读者来加工(嗯,并不是我偷懒了),按照设计写出来的 Json 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"url":"https://127.0.0.1/api/v1/group/content",
"groups":[
{
"name":"YearProgress 2022",
"ID":"[quorum seednet ID]",
"testGroup":false,
"cron":{
"method":"daily",
"schedule":"0 0 * * *"
},
"timeZone":"UTC"
},
{
"name":"Test Group",
"ID":"[quorum seednet ID]",
"testGroup":true,
"cron":{
"method":"percently",
"schedule":"default"
},
"timeZone":"UTC"
}
]
}

下一步是解析并读取配置文件,把刚刚的一段 Json 保存到 config.json 文件中,和代码放在一个根目录下。
读取 Json 文件:

1
rawData, _ := ioutil.ReadFile("config.json") 

这个时候变量 rawData 储存的是 config.json 里面的字符串,也就是 String 格式。我们还需要解析到一个 struct 中,按照我们自己设定的格式来写 struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Cron struct {
Method string `json:"method"`
Schedule string `json:"schedule"`
}
type Group struct {
Name string `json:"name"`
ID string `json:"ID"`
TestGroup bool `json:"testGroup"`
Cron Cron `json:"cron"`
TimeZone string `json:"timeZone"`
}
type Configs struct {
URL string `json:"url"`
Groups []Group `json:"groups"`
}

这样我们就可以把 rawData 里面的字符串存到 Configs 这个我们新建的 struct 里,这次我们直接写一个读取 config 文档的函数好了:

1
2
3
4
5
6
func ReadConfig(jsonFile string) *Configs { // to read config file
rawData, _ := ioutil.ReadFile(jsonFile) // filename is the JSON file
var configs Configs
json.Unmarshal(rawData, &configs)
return &configs
}

更多:自定义包

注意到我们的 ReadConfig 函数是用的大写字母开头,因为我把 ReadConfig 函数写进了一个自定义包里。所以 ReadConfig 函数是一个公共的函数,就是说我 import 了 ReadConfig 所在的包,就可以调用这个函数。
下面就讲怎么做一个自定义包,非常简单,包名就是文件夹名,把文件夹放到根目录里。比如,我的包名就叫 readconfig,于是文件夹也叫 readconfig,文件夹里面的代码源文件名可以随便写,我就写 readconfig.go 了。
readconfig.go 的第一行不能随便写,要写成:

1
package readconfig

于是我们就成功自定义了一个叫 readconfig 的包。
接下来把上一步写好的 ReadConfig 函数以及 Configs 的 struct,放置于 readconfig.go 里就好了。

回到根目录的 main.go,这个时候有个特别需要注意的是,因为是自定义包,我们需要在 readconfig 前面加上主包名,我这里自定义的是 yearprogress。如下:

1
2
3
import (
"yearprogress/readconfig"
)

主包名 yearprogress 又是在 go.mod 文件里定义的。要生成 go.mod 需要我们在命令行里输入这样的命令:

1
go mod init yearprogress

Go 语言会根据我们引用的各种包自动帮我们生成一个 go.mod 文件,用来进行包管理。

这样就算完结了,很快是不是?因为我就是这样学习的,会跳过很多知识点,直接去找能帮助我完成这个小项目的积木块。这是我自己总结的学习方法,我称之为敏捷学习法。
这里贴一个我之前写的文章叫做:
【浅尝则止是正义!逃避困难也可以!论敏捷学习方法在编程学习上的应用。】
是微信公众号链接,以后考虑逐步都转移到 github 吧:
https://mp.weixin.qq.com/s/ZmOaU_VPwSJICqGpefiwaQ
是去年5月份写的文章,年底就成功实践学了 Go 语言,说明这个学习方法还是有效果的。

按照惯例,我把代码的全貌展示在 github 上。作为小白本白,我经常复制粘贴了别人的代码再修修补补一下就没法成功运行了,所以我这里提供一个完整的可以执行的代码,修修补补出了问题可以回滚:

https://github.com/hawken-im/yearprogress/tree/main/Step3

以后的计划:

我通过这个小项目入门了 Go 语言,下一步打算做个稍微复杂一点的项目,具体还没有想好,当然还是会和 Rum 结合起来。
至于这个进度条小项目,我的计划是升级一个小功能,就是年末的时候可以按每 0.1% 来发布。还有就是研究 JWT,看看怎么才能稳定的远程接入 quorum(Rum 的服务器端)。
希望能帮助到读者!

用 Go 语言写焦虑发生器并发布到 Rum 上·第三篇

初学 Go 语言是在去年 11 月 20 日。到今年 2022 年的 2 月 3 日,我写了一个小 bot 连续运行成功,发布到了 github 上开源。花了两个多月时间。我感觉效率还是不错的。
于是我这就来记录这段学习经历:
目的一是让同样正在跨编程入门这道薛定谔之忽高忽低门槛的小白同学一些参考,更顺利的入门编程;
目的二是回顾并巩固自己过去的学习,为下一步继续学习打好基础;
目的三是让更多的人能够对 Rum 这个新的东西感兴趣。
希望能成功达成目的:

变得更酷之定时任务

到现在,我们已经可以给 Rum 发送进度条了。当时我本人,在写到这一步的时候已经偷偷自调闹钟,在整点向 Rum 的“去中心微博”那个种子网络发了两三次了。
但是自己调闹钟来发内容,这根本不 bot!根本都不酷!
这一步我们要加入定时任务这个 new feature。

经过多番研究

(踩了不少坑,关于 cron job 的繁琐本人在 Rum 上的朋友圈里吐槽了一番)

还是确定了引用这个包:

1
2
3
import{
cron "github.com/robfig/cron/v3"//前面的cron是自己取的包的别名
}

选定了这个包,就通过阅读官方文档,找到新建计划任务的方法:

1
c := cron.New(cron.WithLocation(time.UTC))

cron.New() 可以新建一个实例,里面的参数是指定时区,我们这里就用 UTC 时间。
接着应用实例 c 的方法 AddFunc 就好,如下:

1
c.AddFunc([计划任务时间], func() {[要执行的函数]})

[计划任务时间]采用的是 linux 著名的 crontab 计划任务常用的格式,这个具体怎么弄除了自己去查询,还有个神奇的网站帮我们去做计划任务的时间:
https://crontab.guru/

AddFunc 方法可以使用多次,也就是添加多个计划,之后再用:

1
c.Start()

才可以开始计划好的任务。

一开始我是每 24 小时发送一次,感觉有些打扰别人的时间线,经过一番折腾,最终把发送进度条的频率设置为每 1% 发送一次。

这个的算法是这样思考的:
在接近一个整数百分比的时间前,每分钟一个百分比,算出 15 分钟 15 个百分比,每个百分比作一次被减数,去减下一个整数百分比的时间。直到整数百分比减去算出来的百分比小于 0.00001。那么就设定一个计划任务,在算出来的那个时间发布进度条。然后,休眠 85 个小时,因为一年的 1% 差不多是 87个小时。
如果这 15 分钟内没有一个百分比达成这个条件,则休眠 15 分钟,在下一个 15 分钟唤醒程序,再算一次。
打太多字读者也许会有点难读,我们放代码吧,代码配合注释可能更容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
c := cron.New(cron.WithLocation(time.UTC))
url := "https://127.0.0.1:8002/api/v1/group/content" //Rum 定义的 api

for {
startTime := time.Date(2022, time.Now().UTC().Month(), time.Now().UTC().Day(), time.Now().UTC().Hour(), time.Now().Minute(), 0, 0, time.UTC) //开始时间
for x := 0; x <= 14; x++ { //循环15次,下一个15分钟每分钟一次
addMinutes, _ := time.ParseDuration(fmt.Sprintf("%dm", x))//每次循环,在开始时间前加x分钟
realTimePerc := timePerc(startTime.Add(addMinutes))
roundPerc := math.Ceil(realTimePerc*100) / 100 //计算下一个整数百分比
differVal := roundPerc - realTimePerc //计算差值,差值接近于零代表时间接近整数百分比了
if differVal < 0.00001 { //每分钟计算一次,每分钟是一年的0.000002,因此精确到小数点后5位
realTime := startTime.Add(addMinutes)
nextPostTime := fmt.Sprintf("%d %d %d %d *", realTime.Minute(), realTime.Hour(), realTime.Day(), realTime.Month())
progressBar := printBar(roundPerc)
c.AddFunc(nextPostTime, func() { postToRum("2022 进度条", progressBar, "fe2842cb-db6b-4e8a-b007-e83e5603131c", url) }) //设置定时任务
c.Start() //开始定时任务
fmt.Println("######## went to sleep for 85 hours ########") //休眠85个小时,因为一个百分比大概接近87个小时
time.Sleep(85 * time.Hour)
break
}
}
fmt.Println("######## went to sleep ########") //休眠15分钟
time.Sleep(15 * time.Minute)
c.Stop()
fmt.Println("############ awaken ###########") //唤醒
}
}

上面的 for 循环是放在 main 里,是一个死循环,我的理想情况是这个代码能够一年都不停地运行不会出错。写这篇文章的时候已经稳定运行了 3 个百分点。
这里的变量 startTime 是每一个 15 分钟判断开始的时间。设定的是整秒数。然后嵌套一个运行 15 次的 for 循环,每次循环都会在 startTime 的基础上增加一分钟并判断这个时间和整百分点时间差多少百分比。直到相差小于 0.00001。
再嵌套的一个 for 循环会遍历我的 config 文件里的 Rum 种子网络 ID。

变得更酷之记录日志

最终实现自己的目标前,会出不少问题,要定位这些问题,最好的方法就是每一步都输出一个结果。
特别是我们上一步那种循环套循环,还要长期运行的代码,没有日志很难定位问题出在哪里。
于是我引入了日志。
也是老办法,查了下有哪些好用的包。试水了两三个,最后选到了这个:

1
2
3
import (
log "github.com/sirupsen/logrus"
)

非常不好意思的告诉读者们,我作为小白一开始连前面的 log 是别名都不懂,有了别名,以后要引用这个包就不需要写全名,而只需要写 log 这个小短词了。

阅读文档得知,将 log 输出到文件的语句如下:

1
2
3
4
5
f, err := os.OpenFile("YP.log", os.O_WRONLY|os.O_CREATE, 0755) //log file
if err != nil {
panic(err)
}
log.SetOutput(f)

先用 os.OpenFile 新建或打开一个文档,我这里取名叫 “YP.log”,将文档赋值给变量 f,然后调用 log.SetOutput(f) 就可以将日志统统输出到文档 YP.log 里了。
在需要记录日志的地方,调用 log.Info([日志内容]) 就可以了。
于是我在主循环中加入了一些日志,好让我可以观察程序的运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
func main() {
f, err := os.OpenFile("YP.log", os.O_WRONLY|os.O_CREATE, 0755) //log file
if err != nil {
panic(err)
}
log.SetOutput(f)

c := cron.New(cron.WithLocation(time.UTC))
url := "https://127.0.0.1:8002/api/v1/group/content" //Rum 定义的 api

for {
startTime := time.Date(2022, time.Now().UTC().Month(), time.Now().UTC().Day(), time.Now().UTC().Hour(), time.Now().Minute(), 0, 0, time.UTC) //开始时间
log.Info("startTime:", startTime) //记录一下循环开始时间
for x := 0; x <= 14; x++ { //循环15次,下一个15分钟每分钟一次
addMinutes, _ := time.ParseDuration(fmt.Sprintf("%dm", x)) //每次循环,在开始时间前加x分钟
log.Info("addMinutes:", addMinutes) //记录一下每次加的时间对不对
realTimePerc := timePerc(startTime.Add(addMinutes))
log.Info("realTimePerc:", realTimePerc) //加了时间之后的百分比,记录一下这个增长过程
roundPerc := math.Ceil(realTimePerc*100) / 100 //计算下一个整数百分比
log.Info("roundPerc:", roundPerc) //虽然每次都是一样的值,但还是想看看
differVal := roundPerc - realTimePerc //计算差值,差值接近于零代表时间接近整数百分比了
log.Info("differVal:", differVal) //看看差值的变化过程,越来越接近于零
if differVal < 0.00001 { //每分钟计算一次,每分钟是一年的0.000002,因此精确到小数点后5位
realTime := startTime.Add(addMinutes)
log.Info("differVal less than 0:", differVal) //终于到整百分点了,记录一个
nextPostTime := fmt.Sprintf("%d %d %d %d *", realTime.Minute(), realTime.Hour(), realTime.Day(), realTime.Month())
log.Info("nextPostTime:", nextPostTime) //报告具体的整百分点发布时间
progressBar := printBar(roundPerc)
c.AddFunc(nextPostTime, func() { postToRum("2022 进度条", progressBar, "fe2842cb-db6b-4e8a-b007-e83e5603131c", url) }) //设置定时任务
c.Start()
log.Info("######## went to sleep for 85 hours ########") //日志里也记录一下 //开始定时任务
fmt.Println("######## went to sleep for 85 hours ########") //休眠85个小时,因为一个百分比大概接近87个小时
time.Sleep(85 * time.Hour)
break
}
}
log.Info("######## went to sleep ########")
fmt.Println("######## went to sleep ########") //休眠15分钟
time.Sleep(15 * time.Minute)
c.Stop()
log.Info("############ awaken ###########")
fmt.Println("############ awaken ###########") //唤醒
}
}

按照惯例,我把代码的全貌展示在 github 上。作为小白本白,我经常复制粘贴了别人的代码再修修补补一下就没法成功运行了,所以我这里提供一个完整的可以执行的代码,修修补补出了问题可以回滚:

https://github.com/hawken-im/yearprogress/tree/main/Step2

用 Go 语言写焦虑发生器并发布到 Rum 上·第二篇

初学 Go 语言是在去年 11 月 20 日。到今年 2022 年的 2 月 3 日,我写了一个小 bot 连续运行成功,发布到了 github 上开源。花了两个多月时间。我感觉效率还是不错的。
于是我这就来记录这段学习经历:
目的一是让同样正在跨编程入门这道薛定谔之忽高忽低门槛的小白同学一些参考,更顺利的入门编程;
目的二是回顾并巩固自己过去的学习,为下一步继续学习打好基础;
目的三是让更多的人能够对 Rum 这个新的东西感兴趣。
希望能成功达成目的:

生成进度条

上一篇我们写了函数,可以用 HTTP Post 方法向 Rum 发送内容,我们这一篇就来发送焦虑内容之年度进度条吧。
进度条采用文本形式,我在 Unicode 的列表里找到了一些方块:

这个找 Unicode 的地方我提供给读者:https://unicode-table.com/

进度条的算法我是这样规定的:
整个进度条长度是30个块,把百分比换算成三十分之 N。N 是一个整数。
那么把百分之几换算成三十分之几感觉掉了很多精度,这个丢掉的精度我用一些不完整的方块来表示。我找了 quarter block 表示四分之一块,找了 half block 表示一半,three quarter block 表示四分之三块,这样至少视觉上不会感觉太“不精确”了。
照着这样的算法思路我写了生成进度条的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func printBar(perc float64) (bar string) { //print progress bar by percentage
const fullB string = "\u2588" //0.9
const halfB string = "\u2584" //0.5
const quarterB string = "\u2582" //0.25
const threeQuartersB string = "\u2586" //0.75
const emptyB string = "\u2581" //0
const ttlBs float64 = 30 //total number of blocks

bar = ""

fBs := int(math.Floor(perc * ttlBs))

for i := 0; i < fBs; i++ {
bar += fullB
}

gB := perc*ttlBs - math.Floor(perc*ttlBs) //to decide which gab block to chose.
log.Info("the gap block indicator is:", gB)

if gB < 0.0001 && perc < 0.9999 {
bar += emptyB
} else if gB >= 0.0001 && gB < 0.35 {
bar += quarterB
} else if gB >= 0.35 && gB < 0.6 {
bar += halfB
} else if gB >= 0.6 && gB < 0.85 {
bar += threeQuartersB
} else if perc >= 0.9999 {
log.Info("quit earlier to prevent an extra empty block ", perc*ttlBs)
return
} else {
bar += fullB
}

eBs := int(ttlBs) - fBs - 1
for i := 0; i < eBs; i++ {
bar += emptyB
}


content := ""
content += "2022 进度条 / Year Progress 2022\n"
content += bar

now := time.Now().UTC()
displayPerC := fmt.Sprintf("%.1f", perc*100) + "%"
bar = content + displayPerC + "\nUTC时间: " + now.Format("2006, Jan 02, 15:04:05") + "\n"

return
}

变量 bar 是函数要返回的值,也是要发送给 Rum 的全部内容。经过前面的计算,生成进度条之后,干脆在这个函数里把标题和时间戳也加上,成了最后的内容。
这个函数的一个参数是 perc,也即percentage,百分比,顺理成章的,我们需要根据时间来计算百分比了。

计算时间的百分比很简单,输入一个时间,计算从 2022 年 1 月 1 日零点,到这个时间的时间长度,再比上整个 2022 年的时间长度就好了。
代码如下:

1
2
3
4
5
6
7
func timePerc(nextPost time.Time) (perc float64) { //calculate percentage
initialTime := time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)
duration := nextPost.Sub(initialTime)
log.Info("duration is:", duration)
perc = duration.Hours() / (365.0 * 24.0)
return
}

代码设置了变量 initialTime ,初始的时间,即是 2022 年 1 月 1 日零点。参数是 nextPost,类型是一个时间类型。变量 duration 就是用 nextPost 减去 initialTime 得出的时长了。精确到了小时,因为我最终输出的百分比只保留了 1 位小数点,精确到小时足够了。

整合三个函数

到这里我们基础的功能都已经有了,再做更进一步的优化调整前我们做一次整合。手上现在有三个函数:

1
2
//第一个函数:
func postToRum(title string, content string, group string, url string)//发送内容给 Rum

输入参数 content,也即要发布的内容,参数 group 是指定要发到 Rum 的哪一个种子网络(群组)。

1
2
//第二个函数:
func printBar(perc float64) (bar string)//生成要发送的进度条内容

参数 perc 是百分比,根据百分比生成内容 bar,并返回。

1
2
//第三个函数:
func timePerc(nextPost time.Time) (perc float64)//根据时间计算百分比的函数

参数 nextPost 是一个时间,输入时间,返回 perc 这个百分比。

把三个函数整合到 main 函数中:

1
2
3
4
5
6
7
func main() {	
url := "https://127.0.0.1:8002/api/v1/group/content" //Rum 定义的 api
progressBar := printBar(timePerc(time.Now().UTC())) //按照当前的UTC时间生成一个进度条
postToRum("进度条测试", progressBar, "fe2842cb-db6b-4e8a-b007-e83e5603131c", url) //发布到Go语言学习小组

}

这里采用了当前的 UTC 时间,传递给 timePerc 函数,生成了百分比,把百分比传递给 printBar 函数,生成要发送的内容,赋值给变量 progressBar。
接下来把内容传递给上一篇写的函数,postToRum,礼成!

按照惯例,我把代码的全貌展示在 github 上。作为小白本白,我经常复制粘贴了别人的代码再修修补补一下就没法成功运行了,所以我这里提供一个完整的可以执行的代码,修修补补出了问题可以回滚:

https://github.com/hawken-im/yearprogress/tree/main/Step1

另外,最终实现自己的目标前,会出不少问题,要定位这些问题,最好的方法就是每一步都输出一个结果。这里我用了 fmt.Println()。