停止使用传统的 Commits 信息

下面是 Sumner Evans 的博文(原文地址是:https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/) ,让 Gemini 翻译了:

你几乎肯定以前遇到过 常规提交格式 。它可能在你使用过的某个开源项目的变更日志中出现过,也可能是你参与贡献的某个开源项目强制要求的提交格式。很多人对它赞不绝口,而我却 对它 深恶痛绝。

尽管许多流行的开源项目都在使用 传统 提交 它却 一个 糟糕的标准,它 鼓励 人们关注错误的事情 ,并且 未能兑现其承诺

焦点失败

Conventional Commits 承诺为提交信息添加语义,以帮助开发者和最终用户理解提交中所做的更改。然而,Conventional Commits 在这方面做得并不尽如人意。为了说明这一点,让我们来看一下传统提交的结构。根据 Conventional Commits 网站的 说明 ,提交信息的格式应如下所示:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

提交的主题行包含一个 <type> (例如\<commit> fix \feat \ chore <commit>\docs \<commit>\refactor [^1] )来描述更改类型。之后,可以指定更改范围,最后是更改描述。

这种格式存在一个重大缺陷: 类型优先于作用域 。这完全本末倒置。

范围 > 类型

变更范围(变更对象)是提交操作中最重要的部分。为了说明这一点,我们来探讨一下为什么以下每个利益相关者 更关心变更范围而不是变更类型:

  • 贡献者: 作为项目的贡献者,您经常需要阅读提交日志,以识别代码库中与特定代码区域相关的更改。原因有很多,包括:
    • 想了解一下自您上次贡献以来发生的事情。
      • 试图了解项目的整体惯性在哪里。
      • 查找在拉取或变基时可能与您正在进行的工作发生冲突的提交。
        阅读提交日志时,你关注的是 哪些区域 被修改过。你并不关心具体修改了什么 类型 ,你关心的是 修改的 范围。
  • 调试人员: 在调查 bug 时,您通常需要查看提交日志,了解哪些更改可能影响了与 bug 出现的组件相关的区域。再次强调,范围是最重要的信息。更改类型完全没有用,因为任何类型的更改都可能引入 bug。(我相信我们都经历过编写 bug 修复程序却导致另一个 bug 的情况。)
  • 事件响应人员: 当生产环境宕机时,扫描提交日志,查找宕机前后发生的变更,是识别问题根源的有效方法。此时,范围(scope)仍然是最重要的信息。例如,如果在 auth API 错误涌入高峰期发现与该范围相关的提交,那么它很可能就是问题的罪魁祸首。再次强调,类型(type)无关紧要,因为任何变更都可能引入错误。

那么,常规提交到底做了什么?它把作用域的优先级降到了最低,甚至变成了 可选项作用域 怎么会变成可选项呢?提交没有作用域就像句子没有主语一样!更糟糕的是,常规提交还把 类型 放在了提交信息的最前面。常规提交完全搞错了作用域和类型的优先级。

类型是冗余且具有限制性的

你可能会想:“所以这听起来有点本末倒置,但提交类型至少还是很重要的,对吧?”对此我的回答是“不”。提交描述几乎总是应该告诉你更改的类型! 例如,请看 以下提交信息:

fix(compiler): prevent namespaced SVG <style> elements from being stripped

即使只有描述,也能明显看出这是个 bug 修复!提交主题的空间本来就很宝贵,再浪费字符写类型说明毫无意义!而且,这样做往往比没用更糟糕,还会造成限制。 例如,看看 这条提交信息:

refactor(core): Update webmcp support to use document.modelContext

这次提交更新了 webmcp 组件的功能, core 使其同时支持 \and\ document.modelContext 和 \or\ navigator.modelContext ,所以这算是 bug 修复、重构还是新功能呢?我认为三者兼具!但话说回来,真正重要的是组件本身发生了变化 core/webmcp

传统的提交方式从根本上关注了错误的事情(提交类型),贬低了范围(这才是人们真正关心的事情)。

违背承诺

所以我们已经确定,常规提交格式很糟糕,但它肯定也有 一些 好处。让我们阅读 “为什么要使用常规提交” 部分,看看其中的理由是否合理。

  • 自动生成变更日志。
    这是 Conventional Commits 的最大优势:你可以运行像git-cliffconventional-changelog 这样的工具 ,根据你上次发布以来的提交记录生成变更日志。但这真的是个好主意吗?当然不是!变更日志的受众与提交日志的受众完全不同!
    变更日志是面向用户的,用户关心的是了解不同版本之间的功能差异。他们关心的是从 业务/功能 角度来看发生了哪些变化。
    提交日志是面向开发人员的,开发人员关心的是代码库随时间推移的变化历程。他们关心的是从 作用域 角度来看发生了哪些变化。
    如您所见,这是两种完全不同的谷物,任何试图将它们混合的尝试都会导致品质不佳的结果。原因有很多:

    • 在任何中等复杂程度的项目中,实现任何重要的功能都需要多次提交。功能实现的过程(由提交日志记录)对开发者和贡献者来说很有价值,但对最终用户来说毫无意义。最终用户只关心新功能本身,而不是它的实现方式!
      • 正如 Rich 指出的那样 ,回滚操作对于常规提交来说存在问题。从提交日志的角度来看,回滚提交对开发者来说很重要,但对最终用户而言,被回滚的更改就等同于没有进行过更改。
  • 自动确定语义版本号提升(基于已合并的提交类型)。
    这听起来不错,但软件工程的实际情况往往会严重影响这项任务的准确性。请考虑以下情况:

    • 回滚: 想象一下,你引入的破坏性变更实际上影响巨大,以至于你不得不回滚它?你的工具会检测到这一破坏性变更,并递增主版本号,即使实际上该变更已被回滚,不再存在任何破坏性变更。
      • 意外破坏: 也许破坏很细微,你在进行更改时并没有意识到这是一个破坏性更改。只有在事后才发现它造成了破坏。例如,你可能会在需要提升主版本号时错误地增加了次版本号/补丁版本号。
      • 追溯性修复: 假设你之后添加了一个提交,该提交与之前导致破坏的提交组合后,产生的差异不再是破坏性的。与回滚情况类似,工具会错误地将其识别为破坏性变更。
        在这种情况下,虽然可以使用变基操作来改写历史记录,但这通常会破坏工作流程或被工作流程阻止。此外,它还会向试图为项目做贡献的贡献者呈现一个经过修正的历史记录,从而降低提交日志所反映的信息的可靠性。
  • 向团队成员、公众和其他利益相关者传达变更的性质。
    正如我们之前所讨论的,团队成员和公众对变更日志和提交日志的需求截然不同。传统的提交方式无法满足这两种需求。
  • 触发构建和发布流程。
    这完全是个馊主意。假设你只对修改代码的提交运行自动化安全检查,然后有人创建了一个名为“tmp”的木马提交, docs: fix typos 该提交实际上在身份验证子系统中引入了漏洞?显然,这种恶意活动在代码审查中应该会被发现,但自动化工具却被绕过了,最终只能由人工来识别问题。
    计算成本很低,只需 git diff 用于识别已更改的文件(再次强调作用域),并基于此运行构建/发布流程即可。
  • 通过允许人们探索更结构化的提交历史记录,使他们更容易为您的项目做出贡献。
    结构更清晰了,没错。让贡献变得更容易了吗?完全没有(我们已经详细论证过了)。

传统承诺的“卖点”没有一个站得住脚。

传统提交方式也很难应用于项目。虽然它要求开发者定义自己的“类型”,但几乎所有人都直接采用 commitlint 的 默认设置,而这些默认设置往往与具体项目的具体情况不符。这个问题在企业环境中尤为突出,因为变更管理和审计要求通常规定每个提交消息都必须包含工单编号。虽然该 <scope> 字段显然是放置工单编号的最佳位置,但这最终却用一个完全无用的工单编号替换了传统提交方式中唯一有用的元数据。

更好的方法

那么,你应该怎么做呢?不妨借鉴 Linux、FreeBSD、Git、Go 和 NixOS 等真正成功的软件项目!这些项目有什么共同点?它们都使用带有 scope 前缀的提交信息(其中“scope”的定义与实际项目相关)。通常,特定项目应该使用哪个 scope 是显而易见的。对于 Linux 内核,子系统是自然的 scope。对于 Go 项目,包路径是自然的 scope。对于使用微服务架构的项目,微服务名称是自然的 scope。

以下是一些项目及其提交格式指南的示例。

项目 格式 例子
Linux subsystem: description i2c: virtio: mark device ready before registering the adapter
FreeBSD prefix: Description linuxulator: Return EINVAL for invalid inotify flags
Git area: description gitlab-ci: update macOS image
package: description net/http/cookiejar: add godoc links
nixpkgs pkg-name: description xwayland: 24.1.11 -> 24.1.12
Node.js subsystem: description stream: fast-path stateless transform flush results

遗憾的是,尽管一些最成功的开源项目都采用了这种提交方式,但它似乎在品牌推广方面已经败下阵来。我打算改变这种现状。隆重推出 scopedcommits.com 。该网站致力于倡导回归提交信息的清晰规范,并将变更日志的生成与提交日志的管理分开。

结论

传统提交模式所谓的优势实际上只是假象,业界并未从将其作为标准中看到任何实际益处。然而,不幸的是,传统提交模式似乎在开源项目中相当流行,因此人工智能似乎也习惯于默认使用它来编写提交信息。这导致了充斥着反模式的提交信息在项目中传播。

本文旨在挑战传统提交信息的垄断地位,并论证构建提交信息有更优方案。但如果本文未能说服你放弃使用传统提交信息,我期待在评论区看到一场激烈的辩论。

[^1]: 严格来说,Conventional Commits 规范只定义了 fix \commit\ 和 \ feat commitlint\,其他类型则留给各个项目自行指定。然而,大多数项目最终都使用 commitlint 定义的 类型,所以我在此列表中包含了其中一些

一节保健课

前几天看见张雪峰猝然离世的消息,想起来大一的时候曾经参加过他的讲座,当年他那彪悍幽默的话语,听完讲座之后我就发了条——简直是德云社分社。近些年见他在网上也一直语不惊人死不休,尽管不喜欢他的一些言论,但也佩服他如此彪悍地活着。

跟所有的意外一样,网友开始总结经验教训。在社交网络上,人们开始反思马拉松是不是对身体负担太大,研究早起还是下午运动更健康,推荐还是先确保睡眠再运动为好,更有甚者直接得出结论去电商平台下单辅酶Q10。我想头七这个传统不是没有道理,家人守夜、祭拜、默哀,消化那些情绪,外人在这时总结死者哪些地方做的不好,就显得有点不合时宜了。当然,这只是一点个人想法,人们关起门来在自己家讲讲健康道理就够了,没必要以死者为例发在网上,另一方面,再过七天还有多少人会因此保持早起早睡、饮食清淡、合理运动呢?如果每次都能记住并执行每一个健康事项,那么今天能科普的内容就不会那么多了。

你能隐约感觉到,人们对于这个世界总有一种很公平的假设:只要我做对事情,灾难就不会降临到我头上。因此每次都要复习一整套保健课程,就像刚背下来高年级同学留下的去年考试答案,内心也得到了短暂的平静。有人会说我的确学到了注意身体啊,是的,张雪峰的确过度运动了,他的确不注意心脏健康,今天我们可以告诉自己各种心脏和跑步注意事项。明天有人因为出车祸掉进河里的时候,我们学习蛙泳和如何砸窗户而不受伤。后天呢?

现代社会习惯去制作一场幻梦,把青春活力当做唯一的主题,往大大小小的屏幕里塞满唇红齿白、鲜衣怒马,那里总有更新鲜的生活、更先进的科技。死亡则隐没在医院和墓地那一小块地方,它是医疗失败,是运动过度,是超速行驶,被看成是急需解决的疾病,唯独不是自然的归宿。人们就像在参加一场永不结束的宴席,围坐圆桌旁大口咀嚼、推杯换盏,偶尔身旁就会有人黯然离席,人们会手足无措,会举手道别,但转过头来人们就会把酒杯碰在一起,就像谁也不曾离开。

那些觥筹交错的声音,能掩盖住心底的一丝惨淡。我们都受不了无常,受不了毫无道理的消亡,只要找出来那些意外的逻辑破绽,只要把那个破绽填上,把自己剔除出危险名单,人们这才敢安心地关灯睡觉。但酒席终将散场,无常来临的时候,不管有多少规律睡眠、适量运动、平衡饮食,中间再垒上多少瓶维生素和益生菌,它都会在那头等着。这一整套的保健课程更多是献给了我们的恐惧,又怎么可能避免无常带来的痛苦、遗憾呢?

在铺天盖地的信息中,我看见张雪峰的一张直播截图,他说他的人生目标,是在死的那一天各个平台热搜都有他的名字。在网上,讨厌他的人批评他张扬夸大,误导了学生家长。喜欢他的人为他惋惜,说他是个好老板,给了员工长假和各种福利,还身体力行为教育事业捐款。人们争吵着,两边都不断发出各种消息。我想这也许是一直在屏幕上彪悍的张雪峰会喜欢的结局,这也许是他认为的人生意义。如果说非要总结出一点保健课程,那最为根本的保健就是找到那点自己真正认同的意义,毕竟死亡不可避免,无常随时降临。


题图由 AI 生成

题图:宴席

图片生成算法:Midjourney v7.0

Prompt:

A vibrant, sunny modern illustration with rich textures and warm colors, depicting a large outdoor Chinese banquet in a sun-drenched courtyard. Multiple large round tables with red tablecloths are covered in traditional dishes, ceramic bowls, and bottles of liquor. Bright sunlight creates strong light and dappled shadow patterns. A joyful crowd, dressed in colorful modern Chinese attire, laughs, toasts, and eats. While most people are engaged in celebration, in the foreground on one table, several chairs are empty, with objects like a jacket or bag left on them, indicating people have left. In the background, a few solitary figures are seen walking away down a garden path lined with blooming flowers and traditional Chinese architecture. The scene is bright, uplifting, and painterly, not photographic. Wide-angle view. –s 750 –v 7.0

年终记一只流浪猫

对我来讲,写年终总结像回到了那个暑假快要结束的午后,笔耕不辍地完成剩下的作业。每次写工作要求的周报都已经绞尽脑汁在回忆周一周二在干嘛,年终总结?谁还记得上个月具体发生了什么呢。在理想情况下,人们应该写日报,基于日报写周报,然后月报,最后才是年终总结。但那是理想情况,就像每次放假前我背着满满一书包,信誓旦旦认为自己会每天学习,按时完成暑假作业。

但还是得记录——哪怕是在社交媒体写点小作文呢,于是我还是写了。记录是在马不停蹄的生活中,稍微弯下腰让人看看这蹄子到底要去哪的动作。如果人们不曾奔走,也不需要明确方向,那自然也不需要记录。人们的生活就是忙碌的,忙着去工作,急着去娱乐,然后去睡觉。如果其中有一点记录和觉察,在那样一个小时里安顿下来,也许那些思考和智慧的灵光会闪耀起来,从而撬动一点点改变。

翻翻日记,我经历了许多人生第一次,比如第一次参加好朋友的婚礼,过去一年里,也有些沉浮,更想写的是关于那只流浪猫:

四月下雨天,在小区发现一只猫咪,浑身湿漉漉,躲在车下面。是一只布偶猫,宠物猫,如果不救怕它活不下来。联系了物业和居委会,甚至去隔壁小区逛了看有没有告示,都没有找到原主人。只好养在自己家先看看,起了个名字叫南南,体型大,医生说他大概两岁。带他驱虫、治耳螨、洗澡、绝育,身上终于干干净净。可能是因为流浪过,很粘人,看见人就凑过来,看见手就站起来用头蹭。

我有两个原住民,蓝猫叫噗噗,银渐层叫青云。买了个两米高一米宽的围栏隔离开布偶和原住民,按教程让他们慢慢熟悉。让猫咪们一起玩逗猫棒,一起吃零食,让他们共享正向的回忆。某次围栏没有关牢,青云好奇去探头,南南直接冲过去把他揍了。是真揍。青云应激喘着气,下巴掉了一块皮,吓得一个星期不跑酷也不吃零食,南南因体型大没受伤。后来也不是没试过让他们打开围栏见面,只要我们在,还能相安无事,让他们单独相处时就会打起来,有一次拉架还给我手上留了疤。

只好把南南隔离在外面房间,南南想进卧室粘人,不停挠门,原住民在里面又怕又恨。既不能让他们好好相处,也不能让原住民享受原本的生活空间。出远门还要备两份猫砂和粮。回来时,那道隔离围栏常常倾斜了一半,又是差点打起来的痕迹。

想着还是为南南找个合适的家庭吧,在朋友圈、社交媒体上发表领养的图文,找一些人脉广的朋友发群里,甚至在二手交易平台上以极低价格筛选领养者,这些信息一经发出就石沉大海。

事情就是这样,你做了一切你认为科学正确的猫咪引入步骤,但事情就是不如人意。我做的是对的,救了没有生存能力的宠物猫。南南是对的,因为猫本就是独居动物,他也没有选择来到一个有两只猫的地方。没有人领养是对的,北京到处都可以领养猫咪,甚至有些刚出生的品种猫可以领养,何必领养一只成年的流浪猫呢。每个人都是对的,但我却觉得痛苦,有个朋友劝我,干脆送到他公司那的公园,那边喂养的人也多,我总觉得养都养了,好事要做到底。也许这就是苦的来源,我以为三只猫咪应该相处其乐融融,以为那么可爱的猫咪找到人领养会很容易,在那个应然的世界里,一切都是顺心如意的,一旦那个坚硬的现实撞过来就是头破血流。

往心里装入做好事要善始善终、生活应当井井有条的念头,塞下对完美结局的渴望。忙着把外界的反馈、他人的评价、事态的走向往那个已经很拥挤的心里塞,直到挤得喘不过气来。但如果仔细审视心中那些念头,会发现那里全是紧绷的控制欲,和对于自己无能为力的不可接受。但为什么非得是这样就是“成功”,那样就是“失败”,怎么就没有一个中间状态呢?有没有一种可能,即便是过隔离的生活,也能让大家都舒服起来?只要想通这点,起码心里会平静,动作就不会变形。

后面我又搬了更大的房子,或许是冥冥之中自有定数,搬完没几天,托宠物医院传播的信息,被一位爱猫人士转发,又让一个正好想养猫的人家看上。他们上周末坐长途过来看猫,最后一起协商领养走了。南南去了新家。我和原住民也终于拿回了曾经割让的领土。现在,我坐在因为南南而特意租来的两室一厅里,摸着正在打呼噜的青云。这一年确实没按计划过,全是意外,但还好,家里安静了。


题图由 AI 生成

题图:迈步走向2026

图片生成算法:Nano Banana Pro

Prompt:

An ultra-wide cinematic landscape shot, capturing the cold aesthetic of "Manchester by the Sea". A tiny, solitary figure in a dark coat is walking away, barely visible, along a desolate, snow-dusted coastal road that stretches infinitely towards a distant horizon line. The road acts as a timeline leading into 2026. Vast grey overcast sky, rough freezing ocean, muted blue and brown tones, sense of immense isolation and quiet acceptance. Film grain, photorealistic, natural diffused light.

如何在不安装 Transformers 的情况下使用 LLM Tokenizer

在构建 LLM 应用时,想对文本进行分词或者计算长度,却不得不安装了几 GB 的 transformers 和 torch(transformers 依赖 torch),这会导致 Docker 镜像体积剧增。网上对于这个问题提到的很少,但还是有人问,想着还是写下来吧。

其实只要利用 HuggingFace 的 tokenizers 库(Rust 实现),在不依赖 PyTorch 的前提下,就可以复现 AutoTokenizer 的功能(包括 apply_chat_template)。实测下来,在 Qwen 2.5 模型的 tokenizer 下,同样的 1k 长度的 Prompt,就 tokenize 的速度而言,rust 版的 tokenizer 相比于 transformers 中的 AutoTokenizer 可以提速十倍。

以 Qwen2.5 为例的 tokenizers 使用

import json
from tokenizers import Tokenizer
from jinja2 import Template
from pathlib import Path

model_dir = Path("models/Qwen2.5-14B-Instruct/")
tokenizer_json_path = str((model_dir / "tokenizer.json").resolve())
config_json_path = str((model_dir / "tokenizer_config.json").resolve())

tokenizer = Tokenizer.from_file(tokenizer_json_path)

# 加载 Chat Template
# transformers 的 apply_chat_template 实际上就是读取 tokenizer_config.json 里的 chat_template 字段
with open(config_json_path, 'r', encoding='utf-8') as f:
    config = json.load(f)
    chat_template = config.get("chat_template")
    # 获取特殊 token,用于传递给模板上下文
    bos_token = config.get("bos_token", "")
    eos_token = config.get("eos_token", "")

# 准备输入数据
prompt = "Give me a short introduction to large language model."
messages = [
    {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
    {"role": "user", "content": prompt}
]

# 手动渲染 Chat Template (替代 tokenizer.apply_chat_template)
# Qwen 的模板通常需要 messages, add_generation_prompt, bos_token, eos_token 等参数
template = Template(chat_template)
text = template.render(
    messages=messages,
    add_generation_prompt=True,
    bos_token=bos_token,
    eos_token=eos_token
)

print("=== Rendered Text ===")
print(text)

prompt = "你好,世界"

encoded = tokenizer.encode(prompt)

# 获取 input_ids 和 attention_mask
input_ids = encoded.ids
attention_mask = encoded.attention_mask

print(input_ids)

print(f"Total tokens: {len(input_ids)}")

给英语人士使用:

How to use LLM tokenizers without the transformers library.

When building LLM applications, if you simply want to tokenize text or calculate token lengths, you are often forced to install several GBs of transformers and torch (since transformers depends on torch). This causes the Docker image size to balloon significantly. There isn’t much discussion about this online, but since people keep asking about it, I thought I’d write it down.

Actually, by utilizing HuggingFace’s tokenizers library (implemented in Rust), you can replicate the functionality of AutoTokenizer (including apply_chat_template) without any dependency on PyTorch. In my tests using the Qwen 2.5 model’s tokenizer, for a prompt of the same 1k length, the Rust-based tokenizer was ten times faster than the AutoTokenizer found in transformers.

Using tokenizers with Qwen 2.5 as an Example

import json
from tokenizers import Tokenizer
from jinja2 import Template
from pathlib import Path

model_dir = Path("models/Qwen2.5-14B-Instruct/")
tokenizer_json_path = str((model_dir / "tokenizer.json").resolve())
config_json_path = str((model_dir / "tokenizer_config.json").resolve())

tokenizer = Tokenizer.from_file(tokenizer_json_path)

# Load Chat Template
# The apply_chat_template method in transformers actually just reads the 
# chat_template field from tokenizer_config.json
with open(config_json_path, 'r', encoding='utf-8') as f:
    config = json.load(f)
    chat_template = config.get("chat_template")
    # Get special tokens to pass to the template context
    bos_token = config.get("bos_token", "")
    eos_token = config.get("eos_token", "")

# Prepare input data
prompt = "Give me a short introduction to large language model."
messages = [
    {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
    {"role": "user", "content": prompt}
]

# Manually render Chat Template (replacing tokenizer.apply_chat_template)
# Qwen's template usually requires parameters like messages, add_generation_prompt, bos_token, eos_token, etc.
template = Template(chat_template)
text = template.render(
    messages=messages,
    add_generation_prompt=True,
    bos_token=bos_token,
    eos_token=eos_token
)

print("=== Rendered Text ===")
print(text)

prompt = "你好,世界" # "Hello, World"

encoded = tokenizer.encode(prompt)

# Get input_ids and attention_mask
input_ids =

 encoded.ids
attention_mask = encoded.attention_mask

print(input_ids)

print(f"Total tokens: {len(input_ids)}")

一只玳瑁猫的教授

某天晚上心情郁闷,骑车出去逛逛,也没有设定目的地。在网上搜寻过北京冬夜里既暖和又能散步的去处,得到的答案大多是戏谑——人们推荐了工位:暖气充足,凌晨下班。

在北京的冬夜里,街上车流寥落,只有外卖骑手偶尔疾驰而过。路过一个熟悉的公园,园内小路无灯,借着公路漫进来的光,行人都成了模糊的剪影,彼此默契维持着距离。广场上倒是热闹,孩童的欢笑声搅动着空中的冷气。一座寺塔立在广场旁边,据说明代建成,在射灯下,昏黄的寺塔嵌在紫黑色的夜空前,显得格外孤寂。

走到尽头折返时,看见一只黑棕相间的玳瑁猫,瘦小,趴在草坪上,大约一岁。北京的冬夜能冻透骨头,想领养回去,但家里已有一只流浪猫,还跟两只原住民大打出手。又想起那逼仄的房子,还是作罢。这只玳瑁察觉我的凝视,冲我喵喵叫。我伸手摸去,它一蹬腿蹿上矮树。我试图凑近,它跳下走远。我只好作罢,退回石板路。

路上只剩我的脚步声,偶尔夹杂着枯叶碎裂的细响。回头看去,还是那小家伙,要不是那尾巴在动,很难发现树影下还藏着一团黑黢黢。我往前走,走几步发现它还跟着,已走到椅子下。慢慢,我朝它走去,它发现我突破安全距离,又跑回树下。不让摸,那我就走,但回头仍见那黑影悄悄跟着。走到开阔明亮的地方,小猫跟到路灯下,我再次缓慢走去,同时用人类最常用的猫语跟它套近乎。它叫了两句,四脚腾空像要起飞一样。我紧追不放,距离拉近它飞奔,距离过远它又趴在原地慵懒地等着我,仿佛在玩真正的躲猫猫。

在这场拉锯战中,我几次远离它,让它靠近,再迅速反击,均以失败告终。如果有路人远远看见,怕不是觉得这边闹鬼了。寒意不知不觉消散,我决定一鼓作气,看看它到底逃到哪里。等它又一次跟过来,我穷追不舍,这小黑球也发了狠,四只脚踩得树叶哗哗作响。眼见它敏捷地穿过一棵又一棵树,只恨笨拙的两脚兽身躯无法跟上。再定睛一看,面前一片枯黄的草丛,它一个猛子扎进去,传来阵阵唰唰声,越来越远,直到整个公园再次归于宁静,好像什么也不曾发生过。


题图:猫咪回头望

图片生成算法:Midjounery

Prompt:

An impressionistic oil painting with a thick impasto texture, depicting a tortoiseshell stray cat about to disappear into the yellow grass of a park. The cat is rendered as a blurred mass of dark brown brushstrokes, blending into the deep shadows of the low-lying grass and trees. Light emanates from a distant, dim streetlamp, casting faint beams that subtly illuminate scattered dry leaves and the cat’s silhouette. The overall scene is dim, dominated by a wintery, withered yellow palette with no green. The style combines characteristics of Claude Monet and Vincent van Gogh, rich in emotion and atmosphere, with visible brushstrokes, emotional depth, and expressionistic techniques.

手中蜜罐

有一天你连接到了一个公开无密码的 wifi 网络,你感觉很好,终于不用浪费流量了。你用上免费的网络,黑客则撬走了你的隐私和钱包。这种蜜罐陷阱,是计算机安全中的一个技术手段。

人们往往遭受的不是这样的蜜罐攻击,打开 App,刷新列表,赫然显示着一位大家熟知的企业家离世的新闻,点进一看发现并不是那个人。或者打开某个内容 App,本来想学习怎么煎牛排,结果不知不觉就想要找一个最好的平底锅,一不小心半个小时过去了,菜还没买。只要上网的时间够长,这样的事情似乎无法避免。

从内容创作者的角度而言,创造蜜罐的目的和手段各有不同。

在文字内容平台,用户第一时间能看见的是标题和题图,因此内容创作者就像设计蜜罐一样去设计这两者,就像语文老师教的一样——制造悬念、对比反差。

在短视频平台,决定用户能否继续停留的是视频前几秒钟。由于可以创造更丰富的视听体验,视频创作者有更多手段,渲染出更夸张、更吸睛的开头。如果你去搜索短视频创作的教程,就会发现他们会告诉你要如何创造钩子,如何激发情绪、唤起共鸣、吸引人看完整个视频,这也是为了提升视频完播率,让平台将视频推荐给更多的人。

电商平台的逻辑则完全不同,在电商平台,你本来只想快速挑选,买完走人,但平台要尽可能提升用户的浏览时长,于是他们也创造了数量庞大的蜜罐。打开 App,映入眼帘的是文章、视频、直播构成的瀑布流。你明明只是想搜索具体商品,但在打开的瞬间,斑驳的信息瞬间涌入,占满大脑的内存,你的觉知和原初想买的商品欲求就变成了大脑的次级任务。除了超量的信息涌入,那些新奇的商品也像短视频平台一样,激发着人们的贪婪和好奇。

说到底,当今手机就是一个巨型蜜罐。手机里的巨头App,恨不得把能制造的所有蜜罐都塞到一个页面里。

手机里的生活服务,比如外卖、共享单车、地图,这些高频功能让你形成打开某个 App 的习惯。内容服务,比如小说、短视频、直播,则创造出缓解压力的幽默、填满空虚的陪伴、对现状不满的焦虑、对理想生活的渴爱,让人能沉溺其中,用近乎无限的信息淹没人的注意力。接着顺势告诉你,某个商品或服务就能满足你的需求,如果你囊中羞涩,还能花几秒钟开通借贷服务。如果你担心商品快递太慢或者服务太慢,还可以选择一小时内送达的外卖和上门的服务。这正是一个 App 主页塞满了豆腐块的原因 —— 巨头们或主动或被动创造了一个高效的蜜罐系统。

在现代生活里,人们在工作中已经消耗了相当多的心力,仅剩下晚上不多的休息时间,还要投入手机里贡献金钱和注意力。人们离正襟危坐在电脑面前、打开一个又一个页面比价的电脑冲浪时代已经过去了十年,离凭肉身货比三家、靠现金购买商品还会产生心痛的逛街购物也已过去了十五年,现在人们一般都是深夜瘫倒在床上,用所剩无几的思考能力,一键下单,对藏在支付软件后面的余额下降没有一点点感觉。

佛陀在世时,人心质朴,大多数人尚且吃不饱饭,认识到自己的欲望是相对容易的。如今,人们的欲望不仅在现实中涌现,还凭借一个完备的虚拟世界来生成。手机不同于烟酒这样的合法成瘾品,在推荐算法加持下,渗透进我们每一个脆弱、冲动的瞬间,同时又像衣物鞋履一样不可逃避。学会和这手中蜜罐相处,是我们现代生活的修行,就像《色戒》里的师父问达世,满足一千个欲望,或者战胜一个欲念,哪一个更重要?


题图:街拍2

图片生成算法:Midjounery

Prompt (源于和菜头):a photograph of a young dior model, wearing high heels, posing in the middle of the galleria vittorio emanuele ii. she is striking poses with one leg on top of another’s knee. the background features luxury fashion stores. people are walking around. –ar 3:4 –p 9s14d1j –v 7.0 –s 750