标签归档:开发经验

9a1f326b911de6c1629837f3b57551e5 1

给你的 console.log 添加一些特定的输出

在写 Node.js 代码时,常常会使用 console.log 来输出内容,以便于调试。但默认的 console.log 只能标准的输出,在很多需要上下文 debug 的时候,可能信息是不足的。除了使用 debugger 以外,你还可以试着改造 console.log

在你的 index.js 顶部添加如下代码,即可实现在使用 console.log 时自动在前面加上时间信息。当然,你也可以实现自己需要的上下文,比如当前的文件、当前的行数等。

console.log = (function() {
  var console_log = console.log;
  return function() {
    var args = [];
    args.push(`${new Date().toLocaleString()}` + ' -> ');
    for(var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    console_log.apply(console, args);
  };
})();
Code language: JavaScript (javascript)

这个函数的逻辑不复杂,对 console.log 进行了覆盖,写如了新的函数,并通过 arguments 将开发者传入的参数重新打印,以确保不丢失开发者传入的参数。

linux terminal

如何将 Zed 编辑器设置为你的命令行默认编辑器

我最近在使用 Zed 作为我的主要编辑器,在编辑一些命令行文件时,也会使用 Zed 来编辑。但有些时候,一些应用程序的命令会自动调用默认的编辑器,这个时候会默认使用 nano 或者 vim ,而不是 Zed,体验略差。所以我希望将命令行默认的编辑器修改为 Zed 编辑器。

如果需要将 Zed 作为自己的命令行编辑器,首先需要确认 Zed 是否支持等待模式(wait model),在命令行中执行 zed --help,可以看到 Zed 是支持等待模式的。接下来就可以进行后续的步骤来进行测试了。

d2b5ca33bd970f64a6301fa75ae2eb22 3

这里为是在写 Rails 时用到的,因此,我继续使用 rails credentials 来测试。执行如下命令来确认该命令是否可用。

EDITOR="zed --wait" rails credentials:edit
Code language: JavaScript (javascript)

执行后,可以正常唤起,则说明整个链路已经通畅,接下来只需要将其配置在系统的默认环境变量里即可,将如下代码放在 .zshrc 中即可。

export EDITOR="zed --wait"
Code language: JavaScript (javascript)

其他编辑器的命令参考

# Sublime Text
export EDITOR="subl --wait"
# VSCode
export EDITOR="code --wait"
Code language: PHP (php)
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,也是一个不错的选择。

man in black jacket sitting on white chair

走正道,可能有点慢,但更安全

我在开发 ChatGPT-Feishu 这个项目的时候,并没有选择使用网页版的 chat.openai.com 的服务来进行 Hack。我在社区里看到大量通过 Hack 的方式,来提供了 ChatGPT 的 API 能力。

d2b5ca33bd970f64a6301fa75ae2eb22 19

不可否认,网页版的能力是要比其通过 API 的方式提供的产品能力更强(毕竟模型更新),但对于一个成型的产品而言,稳定是远比能力更强更重要的。能力更强,我们可以通过一些技术手段来实现 — 比如通过数据库来实现多轮对话,可能功能不够强,但确是一个明确可以演进的方向。

Hack 官方未开放的 API ,虽然可以使用,但也带来了极高的维护成本。你可能需要不停的去更新和维护未开放的 API 的实现方式,与官方斗智斗勇。

作为一个 Side Project,没必要做的那么累。

black and white penguin toy

如何处理 Github Action 报出的 remote: Permission to xx x denied to github-actions[bot] 问题

在帮 @MikeyWei 搭建 Beyond-the-World 的网站时,他希望我能够实现 Hexo 自动的部署能力,这样作为一个写作者,他可以只关注于写作(只需要复制粘贴 Markdown)本身,不需要去处理 Hexo 本身的配置问题。所以我便借助 GitHub Action 来实现自动部署,帮助他实现想要的目标。

不过,在执行过程中遇到了一个问题,Github 提示 remote: Permission to xx x denied to github-actions[bot]

d2b5ca33bd970f64a6301fa75ae2eb22 20
GitHub 提示

但我使用的是 Github 默认的 Secret Token ,并没有手动配置,所以并没有发现有配置权限的地方。而过去同样方式又是可以的,所以可能的问题便是 Github 提供了新的配置,导致这个 Token 默认没有权限了。

d2b5ca33bd970f64a6301fa75ae2eb22 21
我的配置文件

仔细研究后发现, Github 的确是新增了一个配置,在项目的 Settings - Actions - General 当中,新增了一个选项 workflow permissions。你可以通过直接修改这个选项,来为你的 Flow 提供默认的读写权限。

d2b5ca33bd970f64a6301fa75ae2eb22 22

不过,由于我不是组织的管理员,所以没办法设置,所以我选择了另外一个方式,在 Action 的 yaml 中添加了 permission 的描述,为这个 Job 新增了写权限,默认的 Token 便拥有了写权限。

d2b5ca33bd970f64a6301fa75ae2eb22 23

延展阅读

https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token

MacBook Pro on brown wooden table

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

如何解决 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

person using iMac

使用 Github 作为 Logseq 的数据同步

继之前体验 Obsidian ,如今我在使用 Logseq 作为我的日常信息记录:

  1. 有想法就放在 Journal 当中,并通过大纲的方式,让我的想法逐渐变得丰满。
  2. 使用 TAG 来区分不同的内容分类(比如编程、生活之类的)

在使用 Logseq 的时候,必然会涉及到需要做数据同步的问题 —— 没有同步万一跪了怎么办?

好在是 Logseq 提供了 Git 版本控制的能力,你只需要在设置当中开启 Git Commit 的能力,就可以让其自动使用 Git 来添加版本,从而实现将你的变更通过 Git 本身的能力来记录。

d2b5ca33bd970f64a6301fa75ae2eb22 16
Logseq 自带的 Git 功能

当完成了 Logseq 的 Git 初始化后,自然而然的,我们便会想 —— 我能不能将其上传到 GitHub 上来完成存储?即使不分发协作,也可以很好的用来存储。答案当然是可以的,配置版本控制之后,Logseq 的仓库就是一个标准的 Git 仓库,你直接推送即可。

当发现可以推送之后,也就不担心数据的版本化问题了。那随之而来的便是 —— 我如何做数据同步?如何不让我手动上传数据到 Github 当中?

你可以借助 Git 的 Hooks 机制来完成:Github 上的开发者 CharlesChiuGit 有一个项目 Logseq Git Sync 101,其中介绍了如何实现自动的 Git 同步。

你只需将其仓库中的 Pre-commit 和 Post Commit 两个文件放置在 Logseq 目录下的 .git/hooks 目录中,即可借助 Git 自身的 Hook 能力,实现在 Commit 前主动拉取配置,避免出现数据冲突的问题,并在 Commit 之后自动推送结果,实现数据的及时上 Github。

d2b5ca33bd970f64a6301fa75ae2eb22 17

具体操作也不复杂,只需要在 Logseq 根目录的 .git/hooks 目录下创建 pre-commitpost-commit文件即可;随后,将 Logseq-Git-Sync-101 中的文件内容复制到这两个文件中;最后执行 chmod a+x post-commit pre-commit 来实现给其添加可执行权限,即可实现在 Logseq 当中执行操作提前推送一次更新 & 拉取一次内容。

d2b5ca33bd970f64a6301fa75ae2eb22 18
tree 效果

有了 Git Sync 101 ,我几乎可以不用担心同步数据了 —— 毕竟做研发的人,谁电脑上还能没有个 Git 了?

text

一个支持 ES3 环境的 querystring

相比于使用 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 ? 需要验证一下。
text

使用 Taro 的一些小配置

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

1. 开启压缩

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

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

diff 见下

d2b5ca33bd970f64a6301fa75ae2eb22 34

2. 开启 webpack 持久化缓存

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

d2b5ca33bd970f64a6301fa75ae2eb22 35