selective focus photography of monk at corridor

大部分中国人没有信仰

大部分中国人是没有信仰的。

你可能会问,中国人不是也去寺庙去拜佛,也到教堂去做礼拜。为什么说中国人没有信仰呢?

d2b5ca33bd970f64a6301fa75ae2eb22 5

维基百科中,对于信仰的定义是“人事物或概念的坚定信念或信任。”,但对于那些去寺庙拜佛、去教堂礼拜的人来说,他们可能并不坚定的认为这些“神”的存在的,他们关注的也不是自己对于“神”的信仰,而是关注神到底能为自己解决多少问题?

这也是为什么我们发现,财神庙总是香火最旺。

text

到底多少个错误码才是合理的?

在对外开放 OpenAPI 的时候,错误的设计也是一个极为影响开发者开发体验的设计点。今天我们简单聊聊关于错误码和错误处理

为什么需要错误码?

由于我们不能保证系统可以完全处理用户的请求,因此我们需要通过错误码来告诉开发者发生了不符合预期的情况。不符合预期的情况可能由用户输入错误导致,也可能由内部微服务故障导致。为了建设一个健壮(Robust)的系统,我们需要通过对外暴露一批错误码,帮助开发者更好地处理各种异常情况。

避免错误码数量过少

既然我们的目的是帮助开发者解决异常情况,那么一个合理的答案是:和异常情况匹配的错误码数量是一个好的错误码数量。

如果在某个 API 接口上提供了一个错误码,则意味着我们认为这个接口只会出现一种情况导致的错误。而实际上,很可能会有多种情况导致错误发生。这种情况经常在 OpenAPI 评审过程中被提出来,也是用户在使用 OpenAPI 时常遇到的问题:为什么我找不到这个错误码?对于开发者来说,无论输入何种错误,都会得到相同的错误码,难以定位和解决问题。同一个错误码也意味着你无法提供足够的错误信息来进行排查。例如,常见的 “400 Bad Request” 错误,如果参数很多,排查错误可能是一个极其痛苦的过程。

避免错误码数量过多

另一方面,错误码数量过多也会导致问题。有些 OpenAPI 接口提供了几十个不同的错误码,看起来感觉很不错。但是仔细一看,就会发现这些错误码实际上只是针对不同的字段错误而已,导致错误码数量快速增长。而实际上,可以将参数错误放在同一个错误码中,并通过动态的参数和原因来解决,而不是返回一堆类似的错误码。大量的错误码对于开发者来说,存在记忆困难的问题,在实际编写代码的过程中,也需要编写大量的错误处理逻辑,来兼容我们对外抛出的错误处理逻辑。

换一种思路来组织错误码

如果你无法很好的掌握拆分和组织错误码的粒度,那我可以给你一个建议:按照用户处理错误的手段来拆分错误码。过去从内部视角来组织和错误码,很容易出现错误码过多或过少的情况,而从外部视角来梳理错误码,则可以帮助我们更好的厘清错误码的分类和组织。

用户并不关注我们的系统到底因为什么出现了错误,他们只关心出现了什么样的错误?我应该如何处理这些错误?那错误码可以非常快速的收敛为以下几类:

  • 本资源参数校验错误,对应的处理策略往往是开发者需要查看文档,了解资源参数的限制, 修改参数重新调用。
  • 跨资源参数校验错误,对应的处理策略往往是开发者需要通过其他 API 获取关连资源的 ID 等信息,以便于重新调用。
  • 请求频率太高,对应的处理策略是优化调用的接口和方法,调低并发量。
  • 服务端错误,对应的处理策略是重试,并在重试无效时联系官方人员。

通过上述的分类方式,我们可以快速的将错误码归类到几个大的分类,从而实现合并同类项,收敛错误码但不至于让开发者不知道下一步 Action 的情况。

错误码不重要,错误处理才重要

看到这里,相信你对于文章中提出的什么才是好的错误码设计已经有了答案。但我想说的是,错误码从来都不是核心。实际上,如果我们回看各种编程语言的范式,大多没有错误码这种设计,而是选择将更多的信息通过 exception 这样的形式暴露给开发者。错误码的设计虽然给到开发者一个可以用来做唯一判定的数据,但可以做唯一判定的不一定非要是数字。数字错误码的设计严格来说,并不是一个好的设计,因为他使得你的代码中必然会存在某些特定的 Magic Number,你需要小心的维护这些 Magic Number 来确保向开发者返回错误时不至于返回错误的错误类型和错误码。

对于一个已经存在的 OpenAPI 系统来说,错误码已经成为既定事实,则要做的是让这套系统可以更好的运转下来。但如果你要设计一套全新的错误系统,那么类似 Slack 这样的返回形式,可能是一个更好的选择,既可以规避掉 Magic Number 的问题,又可以确保每一个错误有其对应唯一的枚举值。

p3nta3

总结

好的错误码设计是适度的,你需要学会平衡错误码和对应错误信息的数量,不要太多干扰开发者,但也不可太少,不足以支撑实际的接口调用。好的错误码可以帮助我们和开发者建设更加健壮的系统,减少不必要的沟通成本,也可以让我们每一个使用这个 API 的人,都更加的幸福。

9ac4e9081f7d880526f74a5c114d3369

使用 DSL 管理你的正则表达式

最近在写 LCTT 译文的解析工具,写正则、调试正则的过程非常的痛苦。

比如,下面的这个正则表达式就是我用来提取文章的英文标题的正则。由于 LCTT 的文章元信息存储历史上出现过多次,所以我不得不写一个比较复杂的正则来匹配出想要的结果。

比如,下面的这段代码是我用来从文件路径当中提取出不同内容的正则,看起来十分复杂,对吧?

/(?<collectDate>(?<type>(?<path>\.\/)(?:published))\/(?<yearOrSeries>(?:.+)?)\/(?<month>(?<=\d+)\.(?=\d+))?)(?:⭐️|⭐️⭐️)?(?<title>.+)\.md/

相比于看起来复杂,更加麻烦的是可维护性。正则表达式的可维护性相比于正常我们熟悉的编程语言来说,是差了一大截。基本上如果要对上述这段正则表达式进行修改或者二次开发,难度极高。但借助一些工具,我们可以使用类似 DSL 的方式来管理正则表达式,则可以以一个更友好的方式来维护你的正则表达式。

d2b5ca33bd970f64a6301fa75ae2eb22

比如,以我为例,上面这张图片中的定义,便是上方正则的等价替换。虽然写了更多的代码,但确实可读性、易于理解性和易于维护性,有了显著的提升。

想要使用,也不复杂,只需要使用对应的 DSL 语法来完成定义,即可完成正则表达式的构建。以我上面的这段正则为例,我使用的是 javascript 的 magic-regexp 包,实际使用时,大概是这样的,定义出对应的正则,直接基于其进行匹配即可。

const magicRegxp = require('magic-regexp');
const { createRegExp, exactly, oneOrMore, char, anyOf, carriageReturn, global, multiline, linefeed, maybe, digit } = magicRegxp;

const regExp = createRegExp(
            exactly("./").groupedAs("path")
            .and(anyOf("published")).groupedAs("type")
            .and(exactly("/"))
            .and(maybe(oneOrMore(char)).groupedAs("yearOrSeries"))
            .and(exactly("/"))
            .and(maybe(
           exactly(".").after(oneOrMore(digit)).before(oneOrMore(digit)).groupedAs("month")
            )).as("collectDate")
            .and(maybe(
                exactly("⭐️").or(exactly("⭐️⭐️"))
            ))
            .and(exactly(oneOrMore(char)).groupedAs("title"))
            .and(exactly(".md")
        ));
let result = regExp.exec(file_content);
console.log(result.groups.title);
Code language: JavaScript (javascript)

当然,这样的维护方式,在不同的语言下有着类似的实现,如果你使用 Golang ,那么 Rex,也是一个不错的选择。

d2b5ca33bd970f64a6301fa75ae2eb22 2

推荐一个最近发现的好东西 — Save To Notion

这个工具是我在研究别人的的 Notion 工作流时发现的,一个支持从网页中提取核心数据的 Notion Saver 插件 —— Save To Notion官网

d2b5ca33bd970f64a6301fa75ae2eb22 2
d2b5ca33bd970f64a6301fa75ae2eb22 1

和绝大多数 Notion Saver 类似,Save To Notion 也支持直接存储一个书签到 Notion 当中,对于平时需要大量阅读的人来说,这是一个和其他产品无法形成差异化的 Feature。

但 Save To Notion 一个非常厉害的功能,是你可以在你的保存表单中,设置从网页中提取数据,它应该是采用了 XPath 来实现这个 Feature,你可以根据自己的需求,从页面中选择数据来提取,从而可以基于 Notion 完全实现一套自己的数据库。相比于其他 Saver,多了一步解析数据的能力,而这个能力,可以帮助你节约许多的时间。

d2b5ca33bd970f64a6301fa75ae2eb22 3

Just Use IT!

red and blue light streaks

复盘:买好资产和好价格

我的美股持仓里有两支股票,MSFT 和 AAPL,应该说是互联网人常买的了。而正是这两支股票,让我真正理解了买好资产和好价格

从资产的视角来看,这两家企业都是值得购买的,至少 5 年内,微软不会崩盘(Windows/Office 的实力你一无所知),苹果得益于其订阅生态,也不是那么容易被干掉的公司。因此,至少在我的认知内,他们都还算是好公司。

而好价格,在我购买的这两家公司的过程中,则体现出了不同的风格。这两支股票我都几乎购买在了顶点。苹果的平均持仓价格为 $156,而微软的平均持仓价格则是 295 美元。

4yydfo
4uc7pu

从 K 线图上可以看到,我几乎是都买在了山顶,但因为实际购买的时机来看,距离山顶的距离又有所差异,苹果是左侧购买,而 MSFT 则是右侧购买。

我购买的价格平心而论,都不算是特别好的价格,MSFT 的52周最高价格是 312 美元,距离我的价格只有 10 美元左右的空间,可以预测到,这一笔投资我大概率没有什么收益。AAPL 类似,52 周最高价格为 177 美元,稍微好点有大概 20 美元的空间,盈利的机会相比于 MSFT 都要更高一点,但相对也有限。

好在是美股当中投的钱本来对我来说,也属于闲钱,不太依赖他求生,所以这笔钱大概率我可以等到它回本以后卖出,甚至是重新涨回到高点卖出,我的实际损失可能不多。

但这两笔投资还是值得复盘:

  1. 这两家公司是好公司
  2. 但我购买的价格不一定是个好价格

如何选择一个好的价格?这个就需要我们心中对其有个定价。之前我关注的主要是愿景,但确实没有仔细算过这个到底是不是一个值得购买的价格,从而导致这两笔不成功的投资。

如果基于现在我的认知,再买股票我应该做的:

  1. 计算估值:一个企业的估值的具体数值是比较难算的,但其长期的量级是相对明确的。所以我需要关注的是他到他应该有的量级的空间还有多大,这个空间便是留给我的盈利空间。
  2. 关注 52 周最高价格和最低价格:关注最近一年的价格未必是一个好的参考,但至少是一个参考。如果当时关注了 52 周最高价格和最低价格,可以看到,我其实离最高点没有多远,留给我的盈利空间非常的少,后续在实际购买时,还是应当注意我自己的盈利空间。

《无人知晓》《知行小酒馆》给我带来的反思还是挺多的。至于关于「好资产和好价格」,我推荐你看看有知有行的投资第一课,在第 9 课中的:好公司等于好股票,就提出过类似的话题。

d2b5ca33bd970f64a6301fa75ae2eb22 4
Investment

复盘:别急着上牌桌

我的美股里常年放着一些闲钱,在上一轮大的暴涨、暴跌的时候,我做了一把韭菜,把这些钱全部都投入到了股市当中。

现在回看起来,我觉得最大的问题还是当时犯了 FOMO(Fear of Missing Out) 的毛病,导致自己盲目出手,买了一些不是好价格的好资产。

但实际上,现金是最重要的,我不应该因为担心没有及时将钱投入到股市当中而带来的货币贬值,而选择盲目的购买资产。虽然通过购买资产,让我短暂的心安,但实际上可能带来更加长期的亏损。

作为持有现金的人,一方面要警惕现金的贬值(但贬值其实没有那么快),另一方面,也是更加重要的,便是不要将现金投入到错误的资产当中,带给自己更加长期的贬值的可能。

a person stacking coins on top of a table

Flomo 收购幕布 – Win-Win Game

Flomo 收购了幕布,这是个我难以相信的事情。但仔细想想,其实很合理,也很有价值。

作为幕布的原持有方,字节跳动面临着业务需要收缩,战略需要聚焦的现状,养着一个幕布团队没有太大的意义,只不过是因为之前和用户的协议所迫,不得不继续维护。能过将幕布卖出去,对于字节跳动来说,是利大于弊的。且字节跳动收购产品一般是来收购团队的,而在字节跳动的产品飞书当中,已经实现了类似幕布的能力,对于字节跳动来说,幕布的历史使命已经完成,继续留着只不过是鸡肋,刚好 Flomo 要收购幕布,就可以顺利成章的将其出售出去。

而作为幕布的收购方 Flomo,则更是一个好的选择,Flomo 本身的调性和幕布十分匹配,对于 Flomo 的用户来说是利好,对于幕布的用户来说,也不算差。而对于 Flomo 团队来说,Flomo + 幕布的组合,可以让其在知识管理上进一步拓展,挺好。

一个难得的 Win-Win 的收购。当然,对于我来说还是难以想象的,毕竟,都是字节收购别人家的产品,第一次碰到从字节收购产品的。

少楠厉害!👍。

photo of silhouette photo of man standing on rock

2023, 松弛

2022 年,为了保持对自己的压力,我保持了为期一年的高密度更新。回过头去看,我觉得这些更新有价值,将我思维中的碎片都展现出来了。但同样的,这些碎片过于简单和不集中,可能对于绝大多数人来说,其实很难有比较大的帮助。对于我自己来说,也只是将我的思维碎片提前拿出来,而不是在我自己的脑海里发酵一下。

在 2023 年,我对自己的定位是松弛,不再逼自己去做一些事情(即使这些事情的确很好),而是更加随性,不强求,看命,看运。

MacBook Pro on brown wooden table

ChatGPT-FeiShu 为什么能火?

复盘 – ChatGPT-Feishu 项目 之后,我的项目也经历了一些迭代,OpenAI 也将最新的 ChatGPT 所使用的 API 版本进行了开放,对于我们开发者来说,无疑是一场狂欢。

而在这个过程中,ChatGPT-Feishu 也收获到了不少的关注,用户越来越多,在这个时候,我开始思考 Why?

一方面,是 ChatGPT 本来就很热,毕竟是一个 AI 新物种,看起来比过去的各种 NLP 产品都更有意思。再加上各种媒体的渲染, ChatGPT 成功的出圈了。

另一方面,是 ChatGPT 在注册和使用上存在限制,大部分的普通人是无法直接使用的,所以使得 ChatGPT 又有了一些神秘感。这种限制使得我们这些能够将 ChatGPT 的能力提供出来的项目得到了更多的关注。

而最后,则是 ChatGPT 和企业协作场景的契合,如果你将 ChatGPT 接入到企业的聊天工具中(如飞书、企业微信),就可以让一个企业的人用起来 ChatGPT,且可以在不损失企业上下文的场景下使用,可以达到非常好的日常使用的效果。

说起企业协作的场景契合,让我想起了多年前的 ChatOps。我前几天去看,Hubot 项目已经被 Archive 掉了,真的是时代的眼泪💧。

MacBook Pro on brown wooden table

OpenAI 的 API 使用的三种层次

随着 ChatGPT 的逐步推广,我看到了大量基于 OpenAI 的产品出现,如果要将其分层,我认为可以分为三层:

Connect 层:只是在使用 OpenAI 的 API ,并没有做太多的功能提升

比如我自己做的 ChatGPT-Feishu,其实就是在这个层次,更多是将 ChatGPT 和一些现成的应用进行连接,所以差异性不大,大家大多是在技术上卷一些新的 Feature。

在我看来,这个层次的卷动是非常有限的,因为现成的应用和场景就这些,大部分时候我们能做到的也就是将 ChatGPT 和现有的生态更好的结合,但没有什么本质上的变化。

Prompt Engineering 层:预制 Prompt,帮助用户问出好问题

当我们仔细去看社交网络上的那些 ChatGPT 的用法之后,其实你会发现, 大多数人对于 ChatGPT 的用法是非常简单粗暴的 —— 问一些过去问搜索引擎的问题。ChatGPT 会给你一个看起来还不错的答案。

这个问题背后其实是大部分人是问不出一个精确、明晰、易于理解的好问题的。

而 Prompt Engineering 层的产品则可以实现对于问题的解构,将一个复杂问题拆解为一套模板 + 一些用户可以理解和输入的内容,从而降低提问的难度。

这一层的应用更多是在卷不同的场景以及对于 Prompt 的优化,以实现更加精准和优质的返回内容,从而帮助用户解决问题。

Finetune 层:对模型进行微调,以符合应用和业务的场景

OpenAI 对于模型提供了 Finetune 的能力,开发者可以准备自己的数据集,将其上传至 OpenAI,由 OpenAI 对模型进行微调,后续开发者可以使用经过微调的模型来进行自动的补全。

这个层面大家卷的就是行业领域认知和干净的数据了,就回到了 AI 经典的行业落地场景了:收集行业数据 – 清晰数据 – 训练模型 – 实际应用。只是 OpenAI 将这件事的难度给降低了。对于开发者来说,可以更加低成本完成整个流程。

如果你只是玩票,那我觉得第一层和第二层都是不错的。但如果你打算正经做个事情,那么第二层可能是必备的基础。