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

9ac4e9081f7d880526f74a5c114d3369

最近在写 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,也是一个不错的选择。

如何解决 Gem 安装 Rails 无法执行的问题

MacBook Pro on brown wooden table

如何解决 Gem 安装 Rails 无法执行的问题?

由于我并不在大型生产环境使用 Ruby on Rails,都是在一些自己的 Side Project 上使用 Ruby On Rails,所以一直以来,我都是使用 Homebrew 来安装最新版本的 Ruby & Gem。

最近升级了 M1 以后,重新在配置 Ruby ,突然发现之前的配置失效了。在配置 Rails 时,发现报了个错: Rails is not currently installed on this system. To get the latest version, simply type:

截图
截图

但我可以肯定的是,我已经执行过了 gem install rails,所以出问题的不可能是我没安装,唯一的可能便是我安装的 Rails 没有安装到 PATH 当中

870yd1

所以想要解决这个问题,只需要将我安装 gem 的路径添加到 path 中即可。

解决方案

1. 获取具体的 Path

执行 gem info rails 来获取到我的 rails 安装的路径,这里可以看到,被安装在了 /opt/homebrew/lib/ruby/gems/3.2.0 路径。

04qwth

2. 找到 Bin 目录

使用 cd /opt/homebrew/lib/ruby/gems/3.2.0 进入到 gem 目录。你可以看到这里有个 bin 目录,bin 目录就是我们具体要用的可执行文件。

re9kwr

你可以执行 ./rails -v 来确认版本正确。

rbj0dk

3. 修改 PATH 环境变量

接下来就是修改你的 PATH 环境变量了。在你用的 Shell 的配置文件当中,加入相应的 path 配置,并重启终端,即可完成配置。

4. 验证配置

重启终端后,随便找个目录,执行 rails -v,查看其结果,来验证我们的配置已经生效。如果你可以看到类似下面的结果,则说明你的配置已经生效了~

8c0vdo

PayJS 测试二维码生成工具

d2b5ca33bd970f64a6301fa75ae2eb22 12

简要描述

这个工具主要用于生成 PayJS 的测试支付订单,在 PayJS 官方不提供测试订单工具之后,可以使用这个工具来生成测试订单,简化操作。

下载地址

截图

d2b5ca33bd970f64a6301fa75ae2eb22 12
未生成二维码效果
d2b5ca33bd970f64a6301fa75ae2eb22 13
生成二维码效果

更新日志

0.0.3

  • 设置 key 为密码类型,保护信息安全。

0.0.2

  • 支持 payjs 生成 1 分钱订单,并展示二维码。

一个支持 ES3 环境的 querystring

text

相比于使用 Uniapp / Taro 之类的,我其实更喜欢使用小程序的原生来进行开发。主要是减少中间商赚差价,性能损耗更少一些。当然,也少了不少好用的体验 —— 比如随便引入 NPM 包,好在是现在的小程序开发者工具也提供了 NPM 构建的能力,所以一些基本的使用是没有问题的。

不过,小程序本身环境的特殊性,我在使用 NPM 包的时候还是会有一些谨慎的 —— 要选择尽可能小的、不受平台依赖的包,来缩小小程序的包。所以当我发现一个可以在小程序中使用的包的时候,我就会将其写下来, 以备不时之需。

在涉及到 Web 开发时,一个比较常见的场景是构建 HTTP 中的 QueryString,以便在发送 GET 请求时传递参数。但自己手拼参数还是比较痛苦的,所以用一些 package ,可以有效的提升开发的体验。

TL;DR

你可以在小程序环境中使用 <a href="https://www.npmjs.com/package/querystring-es3">querystring-es3</a> 来进行 querystring 的构建,包的体积不大,可以达到比较好的效果。

const { encode } = require('querystring-es3')

encode({
  page:1,
  pageSize: 10
})
// return 'page=1&pageSize=10'
Code language: JavaScript (javascript)

为什么不是 qs

querystring 的处理包当中,比较出名的除了 node 内置的 querystring 之外,应该就是 qs 了,但实际在使用过程中,小程序的静态分析依赖了 qs,导致开发者使用时要么关闭提醒,要么换包。考虑到我还是希望使用小程序的静态分析,所以就只能替换包了。

待解决问题

  • 实际上我使用 querystring-es3 主要是看到他写的 ES3 compat,但可能其实我可以直接用 query-string ? 需要验证一下。

几个 Swagger UI 的替代品

black and silver laptop computer

Swagger 算是 Code 2 Doc 的默认选项了,各种语言都提供了生成工具,此外官方还提供了一套方便使用的默认 GUI。

d2b5ca33bd970f64a6301fa75ae2eb22 36

不过,Swagger UI 本身其实并不够好用,所以社区就会提供一些 Swagger UI 的替代品,方便大家使用。

ReDoc

ReDoc 是一个前端的开源项目,在 Github 上拥有超过 1W + 的 Star。对我来说,我觉得他最好的点是左中右三栏模式,左侧切换 API,中间查看 API 详情,右侧查看接口调用的 Sample,比 Swagger 官方的 UI 瀑布式的要舒服。

d2b5ca33bd970f64a6301fa75ae2eb22 37

Angular Swagger UI

Angular Swagger UI 同样是一个开源的项目,界面简洁明了,虽然是瀑布式布局,但由于在界面上更加简洁,所以同样可以在同样的屏幕中,获取到更多的信息。

d2b5ca33bd970f64a6301fa75ae2eb22 38

使用 Taro 的一些小配置

text

我自己在使用 Taro 开发小程序的时候,一定会开启的两个配置:

1. 开启压缩

Taro 在预览模式生成的文档比较大,对于小程序的预览来说,不是很友好,所以开发的时候,我经常会打开压缩,以便于在小程序开发者工具进行预览。

修改也比较简单,只需要在你的 package.json 当中加入对应的环境变量 NODE_ENV=production 来支持即可。

diff 见下

d2b5ca33bd970f64a6301fa75ae2eb22 34

2. 开启 webpack 持久化缓存

Taro 可以开启 webpack 的持久化缓存,以便加速 webpack 的构建速度,因此我也会开启这个配置,以提升构建的速度。

d2b5ca33bd970f64a6301fa75ae2eb22 35

开发者文档的三种内容层次:字典、概览和教程

APILetter

不知道,你有没有关注过开发者文档的信息结构。下图是 Google Docs 的 API 文档。

89ug0i

在菜单中 Google Docs 提供了 Home、Guides、Reference、Samples、Support。你有没有想过,这些内容为什么如何设计?在我看来,这是开发者文档内容的三种层次。

第一层:解决有没有的问题

典型代表:API Reference

绝大多数开放平台也好,To Developer 产品也罢,首先要解决的问题是让开发者知道我有什么样的能力。而最简单、最直接最高效的方式,便是 API Reference。

各大产品在进行对外开放时,首先准备的便是一套 API Reference。在 API Reference 当中,你可以看到开发者应该如何调用一个 API;某个 API 的入参是什么、出参是什么;如果出现了失败,应该如何处理这些错误。

当一个 API Reference 为开发者提供了简洁、明确、易懂的文档内容之后,开发者基本就可以准备开始进入开发流程了。

Opinionated Notes:RESTFul API 的 API Reference 理论上不应该存在多层级的资源关系。

优秀的 API Reference 参考:

第二层:解决怎么用的问题

典型代表:API Overview、单个业务领域的 API Guides & API Tutorial

作为开放平台,当完成了 API Reference 之后,便算是及格了。但及格不应该是目标,目标应当是卓越。抵达卓越,则需要更多内容的帮助。

API Reference 回答了「能用什么」的问题,但没有解决「如何使用 API」的问题,特别是复杂的业务场景下,同一个业务内可能会需要多个不同的资源来进行 API 的操作,这个时候,如何使用这些资源,并将这些资源包装、关联起来,就成为极具业务属性的 Know How 知识。

举个例子来说,以服务端 OpenAPI 为例,日历的资源就涉及到用户、日历、日程、会议室、参会人、权限管控等多个不同的资源,如何串联这些 API 才能达成你的业务目标,就成为一个非常重要的说明。对于日历的能力熟悉的开发者可能很快就可以找到调通的方式,但对于不够熟悉的,这些 API 之间的串联,可能需要他花费一天,甚至一周的时间才能真正理解背后的含义。如果想要降低开发者调通过个 API 的时间,那么介绍这些不同 API 之间的关系,其在业务系统之中的含义就尤为重要。

如果你对于服务端的 API 感触不深,那么客户端的 API 可能会让你感受更加深刻,以微信小程序的中的蓝牙设备开发为例,如果你作为一个从未开发过蓝牙能力的开发者,看到了蓝牙提供的这一系列 API,只怕马上头皮发麻,需要每一个都看一遍才能理解其含义和价值;在蓝牙这件事上阻塞个半周一周也是常态。

d2b5ca33bd970f64a6301fa75ae2eb22 31

一个好的 API Guide / Tutorial 可以有效的降低开发者在调试这些 API 过程中所耗费的时间。当然,微信也意识到了这个问题,提供了一套非常好的 API 说明

优秀的 Tutorials 参考

第三层:解决场景化使用的问题

典型案例:Code Sample,Demo App,场景化的使用教程

API Tutorials 可以解决的是面向场景化的单个领域的内容。它帮助开发者很好的解决了一个领域内的产品能力的串联。但在实际的业务场景当中,开发者面对的不是已经拆解到 API 粒度的任务,而是面向场景设计的不同问题。对于开发者来说,把场景拆分成 X 个 API 并没那么容易(特别是如果经验不足的时候),因此,一套标准的 Code Sample ,甚至是 Demo App ,都可以帮助开发者快速解决面向场景化的问题。它可能涉及到了客户端、服务端,或者是面向了多个不同的领域。这些内容帮助开发者可以快速 landing。

以飞书开放平台为例,飞书开放平台在开发教程里提供了多个领域、不同方向的场景化教程(虽然还不够多,远远不够),帮助开发者快速完成某个特定领域的功能的开发。

d2b5ca33bd970f64a6301fa75ae2eb22 32

这些内容可能未必能完全满足开发者的需求,但确实可以给开发者一定的借鉴意义,帮助开发者理解整个业务流程和对应场景下的具体的实现,帮助开发者快速完成业务需求。

第三层之后,应该是什么?

前面三层解决了有什么、怎么用的问题,而在整个应用的生命周期当中,怎么用并不是终极问题。下一步需要解决的是如何用好的问题。不过这个问题不在本次阐述的内容当中,所以我们留一个悬念,下次再说。

 衡量 API 设计质量的指标 — TTFC/TTFHW

APILetter

任何一个开放平台类产品,要解决的问题都是如何在现有的平台能力之上,开放一些能力给到第三方开发者,帮助第三方开发者开发应用,拓展体验,如果我们可以降低开发者在完成开放能力接入所需的时间,我们就可以让开发者有更多的时间去构建属于自己的业务逻辑,拓展平台之上的应用生态和开发者体验。

API 的设计质量、文档质量和错误处理信息的质量可以非常明显的影响到 API 的使用体验。但这些体验却很难通过我们在软件工程领域所使用的各种性能指标来衡量。我们可以通过一些专业的、对于 API 有判断能力的人来完成对于 API 质量的评估和优化的引导,但这件事并不利于持续发展和长期迭代。我们需要一个靠谱的指标,来引导我们持续性的、规模化的进行 API 本身的优化和迭代。

上图为 readme.com 提供的 TTFC 介绍

在这个问题上,Slack 给出了自己的答案 —— Time to First Hello World(TTFHW), API 服务提供商 readme.com 则提出了一个类似的概念 Time to First Call(TTFC)。

TTFC/TTFHW 为什么是一个好的指标?

在 TTFC 之前,我相信你或多或少都试过对自己的 API 进行统计和分析。你可能会基于技术工程指标来判断 API 的好与坏;进一步则会通过一些反馈的能力来完成(比如划词反馈、点赞点踩反馈)数据的收集和进一步的分析,你可能会将这些数据进行加权分析,从而得出一个质量评分,用于判断一个 API 的好与坏。

表面上来看,他可以帮助你发现你的 API 当中的不好的点,你可以基于开发者的反馈来快速对 API 当中的一些问题来进行修复,从而提升开发者的开发体验。但这个指标的一个缺点是:它是一个负向反馈的指标。作为 API 的提供者,你拿到这些反馈的时候,它可能已经早已影响了你的大多数开发者,甚至早已令你的开发者十分不快而你却从不知道。不仅如此,如果你的开发者们并不喜欢反馈,你的 API 的问题可能永远都不会暴露出来。

TTFC/TTFHW 如其名所述,记录的是一个开发者第一次调用所需要时长,你可以根据你自己的需要,来设定开发者行为的起点。而终点,则是开发者成功发起一次调用。

相比于需要开发者主动反馈所进行的数据收集,TTFC/TTFHW 的指标不需要开发者做什么,我们可以通过一些技术手段来完成这些信息的收集,从而在第一天开始收集数据。你不需要等自己的 API 有很多开发者之后才能开始进行数据的收集和反馈,只要设计合适的埋点,从 Day 1 就可以获取数据,并基于数据来分析指标和下一步优化的方案。

如何建设 TTFC/TTFHW

TTFC 和 TTFHW 的指标在实际的建设过程中并不容易。其中最大的问题是如何将开发者的行为和实际的调用进行关联。如果你的 API 在前端就可以发生调用,且能够在前端直接调用,还算是简单,可以通过简单的前端埋点来完成数据的建设,并配合开发者在网站上行为的埋点,来分析出具体的 TTFC / TTFHW。

而对于一些服务端的 API,则需要花费更多的心力来建立这些数据,你可能需要将开发者在网站上的行为和其对应的在 API 上的行为进行串联,从而形成一个完整的开发者图景。在最理想的情况下,你的统计系统和你的 API 网关系统上是可以打通的,你可以非常简单的将开发者在网站上的行为与发生在 API 网关系统上的行为进行串联,从而形成一整个从开发者进入网站到开发者了解 API 设计再到开发者真实发生 API 调用当中来完成。

不过,这件事在某些支持以应用身份来调用 API 的开放平台当中来说,就是比较困难的了。因为发生调用的最小单位是应用,而不是某一个人。但也并非绝对无解,你同样可以基于某些指标来圈选一部分开发者,并将这些开发者和实际网关当中产生调用的接口进行串联分析,得出开发者的实际的调用时长。

如何使用 TTFC / TTFHW

当你通过艰辛的数据埋点、数据收集、数据入仓之后,便可以进行数据分析,使用这些数据了。这里分享两个使用这些数据的思路,供你参考。

TTFC/TTFHW 的数据可以从整体数据视角和局部数据视角查看。

当你作为整体数据来查看时,你所拿到的是一个开发者完成整体调用所需的时长。你可以基于开发者的行为,来计算出开发者的 TTFC 所需的时长,和他进入到转化漏斗当中下一步所需的时间,并分析不同时长人群在实际转化率上的差异值,对比这些差异值,并得出判断和下一步的 Action ,优化你的 API 产品的实际体验,提升产品转化率。

上图为 moesif 提供的 TTFC 转化分析图表

当你作为局部数据来查看时,你可以将 TTFC/TTFHW 拆分成若干个不同的阶段,比如网站访问、查找文档、了解鉴权信息、阅读 API 文档、实际发生调用等多个阶段,并对这几个部分的耗时进行分析,从而得出一些定向优化的 Action ,来优化整个网站和 API 的质量,帮助开发者降低调用所需耗费的时长。

建设 TTFC / TTFHW 数据指标的一些建议

在建设 TTFC / TTFHW 的过程中,如果你遇到了困难,不妨试试如下的方式:

  1. 为你的服务端 API 提供一套在线的调试工具,从而让开发者可以在看文档的时候直接调用接口,测试效果。
  2. 如果你的 API 支持以开发者的身份调用,可以试着使用 OAuth 协议,让开发者在看文档的时候直接授权发起调用,并在调用成功之后给出相应的代码的生成。

一个可互动(Interactive)的文档,可以帮助你有效的降低 TTFC/TTFHW。

当然,除了产品本身的优化,你还可以试着提供更多的开发者教程、示例代码,来降低开发者调用你所提供的 API 的难度,优化开发者的体验。

为什么开放平台大多选择了 RESTful 风格?

APILetter

大型互联网平台会提供一些开放平台,并提供一定的开放能力,帮助开发者基于自己的平台进行二次开发,拓展平台之上的应用生态。而在这些开放平台当中,最常见的便是采用 RESTful 风格的开放平台,比如 GitHubMicrosoftGoogleTwitterStackOverflow

如果说用一个偷懒的想法,那我们可以得出一个结论 —— RESTful 一定做对了什么,不然不会这么多平台都愿意选择它。

考虑到我们的 Newsletter 是一个面向开发者、To Dev 业务的产品经理的邮件,就不再详细介绍 RESTful 是什么,如果你感兴趣,可以去看看 Fielding R T. Architectural styles and the design of network-based software architectures[M]. University of California, Irvine, 2000 这篇论文。国内的技术劳模阮一峰老师也有一篇文章阐述 RESTful 架构:理解RESTful架构

如果用一句话来描述 RESTful 风格做对了什么?我想它应该是「RESTful 风格的 API 通过将方法抽象回资源,面向资源提供操作的方式,简化了 API 的设计,减少使用 API 所需要的上下文,用最简单的方法为我们提供了各种各样不同的操作的可能。」

举个🌰

个简单的对比的理解,以创建一篇博客文章为例,我们所熟悉的 Internal API (应用内部的 API,没有特定的风格)和 RESTful API 的区别如下:

Internal API 下的创建文章

POST /create-post

{
  "title":"xxx",
  "content":"xxx"
}

RESTful API 下的创建文章

POST /posts

{
  "title":"xxx",
  "content":"xxx"
}

看起来,好像也没什么不同,啊哈?那我们再看看根据 ID 获取文章的区别:

Internal API 下的根据 ID 获取文章

POST /get-post-by-id

{
  "id":123
}

RESTful API 下的根据 ID 获取文章

GET /posts/123

{
}

看获取文章,似乎有那么点意思了,显然,我们所习惯使用的 Internal API 比 RESTFul API 的风格的路径更长,也更喜欢用 POST。当然,这也完全可以接受。我们可以再举个例子,既然有根据 ID 获取文章,是不是还可以根据标题来获取文章:

Internal API 下的根据标题获取文章

POST /get-post-by-title

{
  "title":"xxx"
}

RESTful API 下的根据标题获取文章

GET /posts/?title=xxx

{}

你可以看到, 在 Internal API 当中,我们为了提供获取标题的方式,不得不又写了一个新的路径,一个新的方法,来实现类似的功能。而在 RESTful 当中,我们只是在创建文章的路径当中,提供了一个不同的 Query Param,来完成查询。

实际上,如果我们的文章提供了不同的参数,我们又要针对这个文章提供各种不同类型的搜索条件(相信你肯定在各种中后台管理系统当中看到各种各样的筛选器),在 RESTful API 当中,你只需要在同一个路径上叠加不同的 Query Param 即可,而在传统的 Internal API 当中,你可能需要设计不同的方法来实现。

为开发者省时间的 RESTful 风格

RESTful 风格的 API 不再面向具体的操作来设计,而是将具体的操作抽象为不同的资源和对应的资源的操作,来降低开发者实际在使用这些 API 的成本。

在 RESTful 风格下,一个开发者唯一知道的,便是资源的命名,剩下的便都是交给 RESTful 风格的规范来完成的。

  • 如果开发者知道对应的资源是叫 User,那么他就知道获取所有用户使用 GET /users,获取某个用户就是 GET /users/ID,创建用户就是 POST /users,删除一个用户使用DELETE /users/ID。
  • 类似的,如果他需要操作邮件的对象,则是把 URL 当中的 users 替换为emails,仅此而已。

开发者不需要每次打开文档去猜测 API 到底是 camelCase 、snake_case、PascalCase 还是 kebab-case,也无需猜测每个 API 应该使用什么 HTTP 方法发出请求,开发者只需要遵循 RESTful 规范去请求接口即可。

为平台方省设计的 RESTful 风格

对于服务提供来说,RESTFul 风格也提供了简化设计的可能。举个例子来说,假设我们有一个相对比较复杂的用户数据,如果开发者不能很好的抽象,带来的可能是一系列复杂的接口,让我们的维护成本疯狂提升。随着数据结构本身的复杂,要实现、维护的接口的数量也在不断的增多,需要投入的人力也不断的变多。

举个例子来说,假设我们的平台上有一个 User 对象,他的结构如下面的 JSON 所示:

{
  "id":1, // ID
  "name":"张三", // 姓名
  "nick_name":"小张", // 昵称
  "former_name":"张老三", // 曾用名
  "login":"zhangsan2022", // slug
  "i18n_names": {
     "en":"Sam Zhang" // 英文名
  },
  "blog":"http://example.com" //个人博客
}

由于使用开放平台的用户可能在开放平台上有各种各样不同的用法和操作,所以我们可能需要为用户提供一系列的方法,来帮助用户实现不同目的下的数据查询。你可能不得不提供一系列的 API:

  • GetUserById
  • GetUserByName
  • GetUserByNickName
  • GetUserByFormerName
  • GetUserByLogin
  • GetUserByBlog

而同样的问题,放在 RESTful API 当中,只需要一个 /users? 就可以完成了,剩下的便是在 Query Param 当中去叠加各种不同的判断规则,简单但直接。对于一个开放平台来说, RESTful 风格将过去可能需要用 N 个方法才能实现的方式收拢成一个方法,大大降低了持续维护的成本。

不严谨的的 RESTful 风格

说了那么多 RESTFul 风格的好处,也来说一说 RESTful 风格的坏处。

  1. 没有明确的规范,只有一个指导思想:RESTful 风格的提出者 Roy Thomas Fielding 在自己的博士论文Fielding R T. Architectural styles and the design of network-based software architectures[M]. University of California, Irvine, 2000.当中提出了 RESTful 风格的设计,但并未详细的指明具体的设计细节,所以对于实际落实到工业生产环境过程中,往往会衍生出不同的风格,比如 Google 的 RESTful API 规范和 Microsoft 的 RESTful API 规范就并不相同。他 2008 年写的一篇文章轻描淡写的说自己当时没写细则是因为没时间了。但论文发表了 8 年时间,也没见写第二篇论文解释细则。。。
  2. 对于资源的抽象要求非常高,很容易抽象错:RESTful 风格看起来是否简单,面向资源设计是个人就行。但落实到实际应用场景中,就没这么容易了。
    1. 一个最容易引发讨论的点便是:如何表现出数据结构之间的关系?常见的做法是在路径里展示出关系,比如用户的文章是 /users/1/posts ,但在 Roy 2008 年的文章《REST APIs Must be hypertext-drivern》当中给出的答案是,应该抽象出一个更高层的资源来完成这个筛选,路径中不应该包含关系。
    2. 另一个容易引发讨论的点是批量操作:你如何设定一个批量操作?比如批量创建用户,或者是批量发送消息?你可能会单独设计出一个 /message/batch_send 的方法,但在 Roy 2008 年的文章《REST APIs Must be hypertext-drivern》当中给出的答案是,你应该设计一个全新的资源抽象,来确保整个 API 架构是稳定、一致的。

选择 RESTful 风格,归根还是省钱

RESTFul 风格的好处和坏处都很明显 —— 好处是他提供了简化的实现,统一的表现,最小惊讶原则的落实,让开发者只需要知道资源的信息,便可以进行具体的操作,而无需担心众多的 API 存在各种不同的设计风格和设计实现。开发者需要自行编写 SDK 来完成各种不同的操作。毕竟,RESTful 本身就是基础的 HTTP 操作。

而作为一个已经提出了 20 余年的 API 设计风格,RESTful 已经拥有了丰富的开发者共识(大部分开发者可能不熟悉 RESTful API ,但看到了,大多能猜到是 RESTful 风格,也能用起来 RESTful 风格的 API )、抽象也更统一(RESTful 虽然没定义特别多细节,但一些基础的逻辑也是定义了的,比如 URL 里不能包含动词),对于开放平台这样需要大规模的和外部开发者交互的平台来说,无疑是成本更低的。

评语

RESTful 风格有着其明确的优势,但同样,他的劣势(定义不明确、抽象要求高)使得大部分我们所见的的 RESTful 风格都不那么 RESTful(也并没有人能定义谁更 RESTful),更重要的是,RESTful 的使用体验,并不一定真的就比 Internal API 更好。很多时候,它只是一种无奈的选择。

而未来,能够提供更加丰富的组合能力的 GraphQL ,可能是一个选择。不过,想要做好 GraphQL,你要先做好基础的 RESTful 资源抽象,才好在真正进入 GraphQL 的时代,快速发展。

在 macOS 上使用 Podman 来运行 Docker 环境

docker

Podman 是由 Redhat 开源的 Docker 替代品。在绝大多数场景下, Podman 可以直接替代 Docker来执行(比如 alias docker=podman)。

如果你的 mac 上安装了 homebrew ,你使用 podman 会非常简单。

安装 Podman

使用 homebrew 即可安装 Podman,执行 brew install podman ,即可完成 podman 的安装。

初始化 VM

由于 macOS 并不是一个标准的 Linux Kernel(底层其实是 Unix),因此,你需要单独跑一个 VM 来完成容器环境的创建。和 Docker 不同的是,Podman 使用的是 qemu 来创建一个 VM,而不是依赖 VirtualBox。

执行 podman machine init,即可初始化一个 VM 环境。

d2b5ca33bd970f64a6301fa75ae2eb22 25

初始化完成后,执行 podman machine start 启动 VM,即可使用 Podman 命令来操作容器环境了。

(支线任务)使用 Docker cli 来操作 Podman 环境

由于 Podman 提供了兼容的实现,因此,你甚至可以使用 docker 的cli 来控制 Podman 创建的 VM 。在你执行 podman machine start 之后,在输出的 Log 当中会提示你,你可以将环境变量配置好,就可以实现让 Docker 也能识别到你的 Podman VM。

d2b5ca33bd970f64a6301fa75ae2eb22 26

这样你就可以使用原生的 docker cli 来控制 podman 的 VM。

不过,我想绝大多数的人可能不会既安装 Podman,又安装 Docker,那你可以选择配置一下 alias,直接使用 docker 命令来操作 Podman

alias docker=podman
d2b5ca33bd970f64a6301fa75ae2eb22 27

执行 rpi-update 提示Make sure you have ca-certificates installed and that the time is set correctly 怎么处理?

9e6a931fa940bc97a9753cbfa3f2a954

我在尝试执行 rpi-update 给自己的树莓派做固件升级时,提示了如下报错。

Make sure you have ca-certificates installed and that the time is set correctly
Code language: JavaScript (javascript)

经过研究,发现是因为 rpi-update 的自更新域名被国内封禁了(挂载 github content 上),导致无法通过自检,只能跳过自检来更新。执行如下命令,即可跳过自检完成更新。

UPDATE_SELF=0 rpi-update

树莓派开启密钥登录

9e6a931fa940bc97a9753cbfa3f2a954

由于树莓派跑内网,再加上这个树莓派算得上是我的实验机器。因此便打算直接使用 root 来完成日常操作,懒得在 pi 和 root 用户之间切换了。而 root 登录,我又不希望使用密码登录(以我的懒散的性子,可能会设置一个比较短的密码),便为树莓派配置了密钥登录。

先使用 pi 登录到树莓派上,并切换至 root 用户。在 root 用户下,将我本地公钥复制,并放在 ~/.ssh/authorized_keys 当中,保存并退出。

接下来,就可以在本地以 ssh root@ip 的方式直接登录树莓派了。为了简化这个ssh命令,我还配置了 SSH Config 的别名,来简化我的登录命令(具体可参考 如何用 SSH Config 来优化你的 SSH 连接?

d2b5ca33bd970f64a6301fa75ae2eb22 2

树莓派切换清华源镜像

9e6a931fa940bc97a9753cbfa3f2a954

树莓派的官方源在海外,国内访问时速度难免不理想。再加上软件更新又是一个常态化的工作,所以我就希望将其切换成国内源,以加速软件更新的速度。

d2b5ca33bd970f64a6301fa75ae2eb22 10

玩 Linux 的,更换源简直再正常不过了。

修改 /etc/apt/sources.list 文件,将其中内容替换为如下内容,即可完成源的替换。

deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ bullseye main non-free contrib rpi
# deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ bullseye main non-free contrib rpi
Code language: PHP (php)

替换完成后,记得执行一次 apt update ,来更新掉本地的 APT 缓存。

使用 glean 插件优化你的 React 项目

9a1f326b911de6c1629837f3b57551e5

React 相比于 Vue 的好处是你可以相对轻松的将一个组件抽离出来,这使得开发者可以根据自己的需要进行抽象。

但手动抽象组件还是相对较麻烦,于是我便搜索有没有一些方便的插件,可以帮助我更好快的完成组件的抽象。于是乎便找到了 Wix 出的 glean 插件。

glean 插件可以将任意层级的组件抽出为一个全新的函数,并在之前的位置引用你的函数,因此,你可以先非常快的构建出一个完整的 UI 界面,再基于这个界面,进行不同层次的抽象。或者是在开发界面时,随时根据需要来完成界面的抽象。

tz4sh
官方提供的 Demo

在实际使用过程中,你可以将 glean 插件提供的命令设置为快捷方式,从而实现更快的抽象组件,或者是点击组件前的黄色小灯泡💡来完成组件的抽象。

d2b5ca33bd970f64a6301fa75ae2eb22 9
黄色小灯泡

树莓派镜像烧录后无法使用 SSH 的问题 Debug

9e6a931fa940bc97a9753cbfa3f2a954

最近开始玩树莓派,使用官方的树莓派镜像烧录器烧录了一个镜像到系统中。

但烧录成功后发现却无法通过 SSH 登录。但我记得自己明明是在配置时设置了「开启 SSH 服务」。

d2b5ca33bd970f64a6301fa75ae2eb22 1

经过网上搜索发现,原来这个配置并没有什么卵用。你配置完成后,需要在 TF 卡的根目录创建一个 ssh 文件夹,来开启 SSH 功能。

d2b5ca33bd970f64a6301fa75ae2eb22 4

bundle audit 命令失效怎么办?

MacBook Pro on brown wooden table

我最近在使用 Github 来做 Rails 的 CI 自动检查时,发现 CI 自动检查时使用的 bundle audit 命令失效了,搜索后发现,是 Bundle 将 audit 拆为了单独的 gem ,而 CI 的模板并未更新,导致在调用的时候出现了错误。

d2b5ca33bd970f64a6301fa75ae2eb22 1

既然明确了问题,解决就变得简单了。只需要在 CI 的脚本当中,加入对应的 gem 安装命令,即可完成修复。

  - name: Install bundle audit
        run: gem install bundler-audit

通过 Code Snippet 提升效率

black flat screen computer monitor

在开发应用的时候,我们常常会借助快速生成工具来帮助我们生成一些模板代码。

相比于 JS,Ruby 干脆让很多需要手动输入的地方可以不用输入,体验会更好。Rails 更是做到了极致,大量的约定。对于一些模板化的行为,你甚至可以不用写代码,框架帮你来解析。

常见的编辑器比如 VSCode、Sublime Text 都提供了大量基于插件的 Code Snippets,方便你可以快速生成一段模板代码。

不过在实际开发过程中,他们提供的模板代码可能并不太符合我们的需求,我们往往可能会有一些定制化的需求需要满足,这个时候,就比较依赖自定义模板了。

好在各种编辑器不止可以通过插件来定义 Code Snippet,你还可以自定义自己需要的 Code Snippet。

以 VSCode 为例,你可以参考其官方文档来定义自己需要的 Code Snippet,只需要简单的配置,就可以定义出你自己所需要的 Code Snippet。它可以是你自己写代码时的 Code Snippet,也可以是你在写博客时的 Code Snippet(比如写博客时自动生成前置的描述信息)。

不过,Code Snippet 的语法很多时候比较麻烦,需要一行一个字符串,对于较为复杂的 Code Snippet,构建这个 Code Snippet 本身就比较麻烦。好在有一些第三方工具,可以帮助简化这个过程。比如 Snippet Generator 就可以非常方便的帮助生成 VSCode 的语法,你只需要将需要生成模板的代码粘贴在左侧,并填写一些基本信息,就可以生成包括 VSCode、Sublime、Atom 的 Code snippet 代码,再将其粘贴到你的 VSCode 的配置中即可。

d2b5ca33bd970f64a6301fa75ae2eb22
Snippet Generator

你还可以将其中的一部分内容替换为对应编辑器所提供的占位符,就可以帮助你更加高效的利用这个 Snippet 来完成内容的快速撰写。

其他

如何用 SSH Config 来优化你的 SSH 连接?

text

在 Windows 的时候,我一度非常喜欢使用诸如 XShell 之类的软件,主要原因是我可以将不同的服务器信息保持在同一个软件当中,不用每次都手动保存(Putty往往就无法保存)。

而到了 macOS 之后,我不再使用 XShell 这样的软件来完成我的 SSH 工作流(事实上我也找不到类似 XShell 的软件,后来在 Setapp 软件包中发现了 Core Shell,但我已经有了下面的技巧了,就没再用)。

经过一番研究,我发现对于我的诉求来说,其实完全没必要使用一款第三方软件来完成,SSH 自带的功能即可完成。

再次 Review 一下我的诉求:

  1. 我希望不要输入 IP 地址,而是输入一个短语,或者是域名来连接我的服务器。
  2. 我希望能够支持自定义端口,因为我一般会把我的服务器端口从 22 改为一个随机的数字。
  3. 我希望可以指定密钥文件,因为我有多个密钥,不通的场景可能使用不同的密钥。

这些诉求如今我的新方法都可以实现。

SSH 支持自定义 Config 文件,而默认的 Config 文件位于 ~/.ssh/config 这个文件中,你可以在其中添加自己的配置来实现自定义 SSH。

比如这里用到的就是 SSH Config 中的 Host 定义,你可以以如下的规则来定义一个新的配置。

Host 主机名
    User 登录用的用户名
    HostName 登录用的主机名,可以是域名或者IP
    Port 登录用的端口号
    IdentityFile 需要使用的密钥文件

比如下图就是一个配置的例子

d2b5ca33bd970f64a6301fa75ae2eb22 3

通过在你的 Config 文件当中添加对应的定义,你就可以用 ssh 主机名 的方式来连接你的服务器了。

比如,我的树莓派的配置的主机名是 pi 那我就可以使用 ssh pi 的方式来连接到我的树莓派当中。

d2b5ca33bd970f64a6301fa75ae2eb22 5