Logo

site iconPiglei | 朱雷

《Python 工匠》作者,现居深圳,负责蓝鲸 aPaaS 平台的后端开发工作。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Piglei | 朱雷 RSS 预览

我看见的软件设计:胖瘦客户端

2025-06-03 17:49:31

设计软件是一个不断产生疑问、解决疑问的过程。设计者们面对一个需求,会产生许许多多的疑问,在这些疑问中,有一个看似幼稚,却直击灵魂的小问题:“业务逻辑(复杂度)放在哪?” 项目每引入一个新功能,所增加的总复杂度几乎是确定的,但如何把这些复杂度分配到各模块中,其中的方式方法却变化无穷。

比如,一个采用“客户端/服务端”架构的软件,在增加某项业务功能时,工程师们可能会发现:它既可以(主要)放在服务端实现,也可以放在客户端实现。不同决策直接影响后续的分工模式、开发效率以及功能扩展性等方方面面。

此类场景中,一种常见的设计策略是 “瘦客户端,胖服务端” ,“胖/瘦”指的并非身材,而是组件所承担的职责多寡。采用“瘦客户端”设计,代表主要的业务逻辑均由服务端承担,客户端尽量简单。

让我们通过 kubectl apply 命令的故事,看看如何把“瘦客户端”理念应用在现实世界的软件中。

kubectl apply 的故事

作为当下最流行的容器编排系统,Kubernetes 最为人所熟知的设计之一,是它的声明式资源配置功能。简单来说,人们将应用的“目标运行状态”写进一份 YAML 文件,然后执行 kubectl apply,Kubernetes 便会遵循描述,将应用运行起来。

举个例子,以下是一个简单的 Nginx 应用的 Deployment 资源描述:

# test_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  annotations:
    the_app_name: nginx  # *一个小小的注解,“后面会考”*
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9

对该文件执行 kubectl apply,之后稍等片刻,便可以看到 nginx-deployment 正常运行在了集群中。之后,如果想要对该资源进行任何调整,只需修改 test_nginx.yaml 文件,重新执行 apply 命令即可。

下面做一个小小的实验,来深入理解 kubectl apply 命令的能力。

首先,执行 kubectl get 来查看集群中的资源定义:

❯ kubectl get deploy -o yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "1"
      kubectl.kubernetes.io/last-applied-configuration: ...
      the_app_name: nginx  # YAML 文件中定义的注解
# ... 已省略 ...

主要观察该资源的注解(annotations)部分。

Tip:注解(annotations)是 Kubernetes 中的一个通用资源字段,保存了一些对系统运行有用的信息,它采用键值对结构,可以简单当成一个 Python 里的字典或 Go 中的 map[string]string

可以看到,之前定义在 test_nginx.yaml 文件中的注解项 the_app_name: nginx 正常出现在了资源中。除此之外,注解字段中还有几个新面孔,比如 deployment.kubernetes.io/revision 等。它们并未定义在 YAML 文件里,而是在资源被提交后,由 Kubernetes 的系统组件(比如 Deployment Controller)写入,可以被统一归为“系统注解”。

然后,我们修改 test_nginx.yaml 文件,将其中的注解 the_app_name 改个名,改成 the_name_of_app

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  annotations:
    # 已删除:the_app_name: nginx
    the_name_of_app: nginx
# ...

之后重新执行 kubectl apply 命令,然后查看集群中的资源定义:

❯ kubectl get deploy -o yaml
apiVersion: v1
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    annotations:
      deployment.kubernetes.io/revision: "1"
      kubectl.kubernetes.io/last-applied-configuration: ...
      the_name_of_app: nginx
# ...

可以发现,改动已经生效,注解字段中的 the_app_name 成功被替换为了 the_name_of_app

回顾前面的整个 “apply -> 修改 -> 重新 apply” 的过程,会发现它非常符合直觉。如果再仔细思考,你会发现里面暗藏玄机。

比如,在最后一次执行 apply 命令时,Kubernetes 服务端接收到的 YAML 实际只有一个注解键:the_name_of_app,没有其他信息,但最终服务端决定用它来替换 the_app_name,而不是增加一个新的注解键,为什么?此外,服务端又是如何在更新注解(annotations)字段时,避开那些“系统注解”的呢?

以上这些,全都要归功于 kubectl apply 的实现。

客户端侧 apply

如前所述,kubectl apply 的职责是将一份资源定义“应用”到集群中,但它并非用本地定义完整替换服务端的资源(这样会影响到那些“系统注解”),也不是简单地打一个没头没脑的补丁(这样就无法感知到“旧注解” the_app_name 应被删除)

为了让结果符合用户预期,kubectl apply 采用了一种类似于“智能打补丁”的方式。具体来说,在每次执行 apply 命令时,kubectl 客户端会先读取以下 3 份数据:

  1. 本地文件中的资源定义(test_nginx.yaml
  2. 服务端上次被 apply 的完整资源定义(从系统注解 kubectl.kubernetes.io/last-applied-configuration 中获取)
  3. 服务端目前活跃的资源定义(kubectl get ... 看到的内容)

基于这些数据,客户端使用一种名为“三路合并(3-way merge)”的算法生成一份最符合逻辑的资源补丁对象(patch)。以前面的小实验举例,步骤如下:

  1. 客户端读取服务端的资源定义
  2. 客户端读取本地文件中的资源定义,发现注解 the_name_of_app: nginx
  3. 客户端获取服务端上一次 apply 的资源定义,发现注解 the_app_name: nginx
  4. 基于以上 3 份数据,kubectl 产生最符合逻辑的 PATCH 对象: {"the_app_name":null,"the_name_of_app":"nginx"}——删旧添新
胖客户端 -客户端侧 Apply 功能示意图

因为以上整个过程主要在客户端完成,服务端仅提供基础的读写 API 支持,采用这种工作模式的 kubectl apply 也被称为“客户端侧 apply(client-side apply)”。

客户端侧 apply 的局限性

就像前面所演示的,客户端 apply 很好地满足了用户需求。但是,随着时间的推移,越来越多的人发现这种模式存在许多局限性。最显著的,当时其羸弱的冲突处理能力。

一份资源定义在被提交到 Kubernetes 集群后,可能存在许多个修改者,比如 CLI 工具、系统 controller、第三方 operator ,等等。它们都可以采取各自偏好的方式来修改资源定义。用前面的 Deployment 再来做个简单的演示。

在 nginx-deployment 的 Deployment 资源定义中,副本数(replicas)被设置为 1。因此执行 kubectl apply 后,集群中实际运行的副本数也是 1 :

❯ kubectl get deploy/nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           24h

这时,假设出现了另一个修改者,他跳过了本地 YAML 文件,直接用 kubectl edit 命令,将副本数调整成了 2:

# 第二位修改者:kubectl edit
❯ kubectl edit deploy/nginx-deployment
# .. 将其中的 replicas 字段修改为 2 后保存

# 修改生效,副本数变成了 2
❯ kubectl get deploy/nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           24h

最后再回到 kubectl apply。在不知道副本数已改变的情况下,重新执行 kubectl apply -f test_nginx.yaml,我们会发现副本数马上变回了 1。

❯ kubectl apply -f test_nginx.yaml
deployment.apps/nginx-deployment configured
❯ kubectl get deploy/nginx-deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           24h

也就是说, kubectl apply 直接重置了第二位修改者对副本数的改动。换句话说,kubecl apply 无法感知和处理多方修改的冲突场景,导致其他修改者的改动丢失。

除了冲突处理能力不佳,客户端侧 apply 还有许多其他问题。比方说,虽然 apply 命令功能强大,但大部分实现都在 kubectl 中,是 kubectl 的专属命令。如果其他客户端想进行“类似 apply”的资源操作,则需要自行实现“三路合并”算法,成本相当高。

正因为以上种种问题,2018 年 3 月,社区起草了一篇名为 Apply 的项目改进提议:KEP-555 。在提议中,人们设计了一种 kubectl apply 的一种全新实现:服务端侧 apply。

服务端侧 apply

如果用一句话来总结服务端侧 apply,可以说:服务端侧 apply 将“apply”从一个客户端功能变成了一种服务端的内置功能,用户只要发起一个简单的 API 请求,便能调用 apply 算法来“应用”一份资源定义。它带来了许多显而易见的好处。

瘦客户端 - 服务端侧 Apply 功能示意图

首先,客户端 kubectl 变得更简单了,它不再需要在本地进行复杂的“三路合并”,而是抄起本地资源,丢给 apply 接口即可。同时,任何第三方脚本、服务,都可以直接使用 apply 这种方便的资源修改能力,便利至极。

其次,服务端侧 apply 极大提升了多方修改场景下的冲突探测能力。

在客户端侧 apply 方案下,Kubernetes 通过系统注解 kubectl.kubernetes.io/last-applied-configuration 保存了上一次 apply 的完整数据,以此生成“智能补丁”,一定程度上回避了部分冲突。

而服务端 apply 采用了一种更为彻底的冲突解决模式。它在系统字段 managedFields 中,存下了资源的每个字段的修改者。基于 managedFields 中的数据,服务端得以快速识别出每个可能发生数据冲突的场景,给用户提供必要的信息,以避免发生意料外的数据覆盖。

举例来说,如果是服务端 apply,那么在上一节执行最后一次 apply 命令时,服务端会直接提示数据冲突报错:

❯ kubectl apply --server-side -f test_nginx.yaml
error: Apply failed with 1 conflict: conflict with "kubectl-edit" using apps/v1: .spec.replicas
Please review the fields above--they currently have other managers. Here
# ...

此时,用户既可强制写入数据,也可放弃对冲突字段的管理权(删除该字段),总之,服务端侧 apply 给了用户气定神闲处理冲突的机会。

小结

通过将逻辑从客户端移动到服务端,Kubernetes 的 apply 功能获得了更好的冲突处理能力,也变得更为易用。在新设计中,客户端 kubectl 由胖变瘦,服务端 apiserver 从瘦变胖。

如果你想更深入地了解 kubectl apply,可以阅读以下文档:

  1. Server Side Apply Is Great And You Should Be Using It | Kubernetes
  2. Server-Side Apply | Kubernetes

软件设计建议

客户端是胖还是瘦,在于所承受的职责多少,胖瘦并无高下之分,只是各自适合的场景有所不同。选胖还是选瘦?多数情况下这答案是显而易见的,因为许多功能天然只适合某种实现。就像全局搜索,只会是“瘦客户端,胖服务端”——它依赖服务端数据库里的全部数据。

让我们头疼的,往往是那些答案不够显而易见、模棱两可的情况。这时,如何挑选更恰当的策略?以下是我的几条建议。

1. 善用服务端功能零成本复用、变更实时触达的特点

回顾 kubectl apply 的演进过程,可以发现“服务器端 apply”相比“客户端 apply”的一大优势是它能轻松支持多种不同客户端。服务器端 apply,不光 kubectl 工具能用,任何一个人直接抄起 curl 也能用,毕竟它无需任何本地计算,只需要发起一个普通的 HTTP 请求即可。

正因如此,当你在纠结应当采用“瘦客户端”还是“胖客户端”时,请向自己提一个问题:“该功能有可能(需要)被多种不同客户端使用吗?” 如果答案是肯定的,那么“瘦客户端”可能是更优的选择。

除了能“零成本”复用外,在服务端实现功能的另一个好处是变更能实时触达用户。

在许多场景中(如移动端软件开发),发布一个新客户端版本需要层层审核,变更无法实时推送到用户侧。这时,“瘦客户端,胖服务端”设计就有了很大的优势。功能有变更?只需更新一下服务端代码或配置即可。

2. 别让服务端因客户端的定制需求过载

《论语》有云:过犹不及。有些情况下,假如我们过度追求“瘦客户端”,将所有复杂度一股脑塞进服务端,会导致后者不必要的臃肿,反而催生出不好的设计。

这次我们换换口味,不说软件,用一家烤肉店来举例。

烤肉店故事:如何调味?

软件市的设计二路上新开了一家烤肉店,主打烤肉口味丰富。

为符合各类顾客的口味偏好,店内烤肉提供了多种不同风味,如甜辣、咸甜、酸辣,等等。同时,遵循“顾客至上”的原则,烤肉店采取了后厨调味的策略:顾客在点单时标记想吃的口味,后厨在备肉时调好味。 刚开始,这样的方式很受用户欢迎。

一个月后,店内生意越来越好,许多五湖四海的顾客慕名而来。这时后厨发现,更多的顾客带来了烤肉口味的爆炸性增长,一天下来,自己需要调配出几十种不同口味满足顾客,忙得眼冒金星。

面对困境,老板小 R 想到了一个天才般(才怪)的解决办法:让顾客自助调味。在每张餐桌上,摆好辣椒、番茄酱、椒盐、酱油等五花八门的调味料,后厨只负责完成对肉完成基础处理(腌点盐),客人喜欢什么口味,自己添加即可。

切换成这种模式后,后厨压力得以释放,餐厅的运作效率得到了极大提升。

识别服务端复杂度的过载风险

就像“给肉调味”,在客户端/服务端架构中,天生有一类功能是更为贴近用户和客户端的,这类功能就是针对不同用户和客户端的定制化需求

如果服务端总是一视同仁,尽全力满足所有用户和客户端的定制化需求,那么这虽然方便了客户端,自己却极易因复杂度过度增长而过载,导致后续很难维护。

因此,在软件开发过程中,开发者们需要敏锐地识别出这种过载风险。如判断某功能天生与客户端更为亲近,且不同客户端可能有不同的定制需求,那服务端最好点到即止,只提供基础功能,将更多定制逻辑交由客户端处理,切忌越俎代庖。

3. 客户端的计算力是独特的优质资源

在可供运用的计算(存储)资源层面上,服务端与客户端天生不同:

  • 服务端:计算能力强大且集中,但单价通常较昂贵,以及和用户间隔着客户端;
  • 客户端:直接触达用户,但可供调配的计算能力有限;每个用户通常独享客户端——每单位弱但数量多

这些特点将如何影响软件设计?还是通过烤肉店故事来看看。

烤肉店故事:谁来烤肉?

除了风味多种多样,设计二路上的烤肉店还有另一个杀手锏:服务员代烤肉。肉送到餐桌后,剪肉、摊肉、翻面、滋油,烤肉所需的各项劳动全都由服务员完成,顾客不需要动一个手指头。

同“后厨调味”一样,开业前一个月,这种代烤肉模式运作得非常好。但很快,老板小 R 发现这种模式难以为继。因为为了保证“代烤肉”服务的效率,店里需要为每一桌顾客配一位全职烤肉的服务员。这直接导致店内人员成本高涨,入不敷出。

发愁好几天后,小 R 又蹦出一个天才般(才怪)的想法:“为什么不让每个顾客自己动手呢?”

说干就干,第二天,烤肉店就变成了自助模式。每位来店用餐的顾客都需要自己烤肉,不再有服务员代劳。于是,烤肉店终于不用再雇佣海量服务员,很快扭亏为盈。

善用客户端的独特资源

如果用软件设计来类比,故事中烤肉店的变化,其实是一个从“瘦客户端”到“胖客户端”的变化:

  • “服务员代烤肉” = “瘦客户端”:烤肉需要人来付出劳动,而这主要由烤肉店服务员(服务端资源)完成;
  • “顾客自助烤肉” = “胖客户端”:烤肉所需的劳动,不再由烤肉店(服务端)承担,而是由每一个顾客(客户端)完成;
  • 作为客户端,顾客天生拥有“自己动手烤肉”这种计算能力,“胖客户端”设计合理利用了这种能力,将服务端(烤肉店)的烤肉需求分摊了出去。

综上所述,和服务端有所不同,客户端拥有独特的优质资源(计算/存储),并且随着用户数量增长,这种资源天然呈现出水平扩展的特点。如果软件能利用好这份资源,去采用“胖客户端”设计,往往可以出奇制胜。

“自助烤肉”的弊端

再回到烤肉店,当“代烤肉”变成“自助烤肉”后,店内支出虽然变少,但整个就餐体验也发生了天翻地覆的变化。

如果说“服务员代烤肉”提供的是一种标准化的服务,总能让顾客吃到火候恰到好处的食物,“自助烤肉”所带来的就餐体验,其实是反标准化、参差不齐的。一些擅长烤肉的顾客,确实能吃到美味的肉,但部分动手能力较差的顾客,则很有可能在焦糊味中度过一个糟糕的夜晚。

这很好揭示了一个事实:不同于服务端,客户端天生就是层次不齐、不可靠的。不同客户端因其可调配的资源不同,提供的用户体验可能天差地别。在一些特殊领域(比如电子游戏)中,客户端的这种不可靠性,会成为软件设计时的一个重要考量。

结语

以 kubectl apply 的变迁史开头,本文对软件设计时的“胖/瘦客户端”进行了简单介绍,在末尾,我总结了一些与之相关的软件设计建议。希望这些内容能对你有所启发。

文末彩蛋

虽然服务端侧 apply 很好,但它目前仍未成为 kubectl apply 的默认选项,截止到目前,人们仍需要显式传入 --server-side 选项来启用服务端侧 apply。

服务端侧 apply 的稳定版本发布于 2021 年 8 月,距今已长达四年。修改一项客户端的默认行为,四年都无法完成,维护 Kubernetes 这种巨无霸软件背后的难度,可想而知。

相关讨论:kubectl: Use Server-Side-Apply by default · Issue #3805 · kubernetes/enhancements


题图来源:Photo by Isaac N.C. on Unsplash

程序员阅读清单:我喜欢的 100 篇技术文章(41-50)

2025-03-06 07:52:36

程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。

作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为 《程序员阅读清单:我喜欢的 100 篇技术文章》

受限于本人的专业与兴趣所在,清单中的文章对以下几个领域有所偏重:程序员通识、软件工程、后端开发、技术写作、Python 语言、Go 语言

下面是阅读清单的第三部分,包含第 41 到 50 篇文章。

系列索引:

  • 第一部分(1-20):链接
  • 第二部分(21-40):链接
  • 第三部分(41-50):链接

清单

41. 《抽象泄露法则》

用 AI,花 5 分钟开发一个新功能。验证时,却发现新功能在某个特殊情况下无法正常工作。为了解决这个 bug,你只能逐行排查调试。等修复好问题,一看表, 1 个小时过去了。

上面的经历对你来说是否有些似曾相识?早在 2002 年,程序员 Joel Spolsky 就敏锐地发现了这类现象,并将它们总结为:“抽象泄露法则”。软件世界是一层抽象套着另一层的千层饼,就好像 HTTP 协议下有 TCP、TCP 下有 IP,每一层抽象都声称自己是完美的:“你无需关注在我之下的任何细节”。

但事实却是,所有抽象必定泄露。而当抽象泄露时,就像要从 AI 的 1000 行代码里找到那个错误——事情非常棘手,但我们别无选择。

42. 《如何设计一个好的 API 及其重要性》

这份资料来自 Joshua Bloch(时任首席 Java 架构师)在 Google 公司的内部演讲。虽然距今已 17 年,但它读起来却没有任何过时的感觉,对现代软件开发仍具备指导价值。

Joshua 系统性地阐述了 API 设计的方方面面。包括:

  • 带着怀疑的眼光收集用户用例(use cases);
  • 写代码前,先用最简单的文字描述 API(一页纸以内),并和相关人员讨论完善;
  • 如果迟疑于是否提供某个功能,就先不要提供(后续新增比删除要简单得多);
  • API 应当和它所被使用的平台和谐共存,比如 SDK 不应被原样从一门语言搬运到另一门。

如果你之前从未深入思考过 API 设计,读读看,它极有可能改变你未来开发软件的方式。

43. 《我构建软件的原则+实践“让无效状态不可表示”》

关于软件开发原则的文章有很多,这篇的特别之处在于,作者 Kevin 着重强调了数据对于软件设计的影响。

比如,Kevin 提出在设计时,应当优先考虑数据结构而不是代码,因为前者更为重要。正如《人月神话》的作者 Fred Brooks 曾经说过:“如果提供了程序流程图,而没有表数据,我仍然会很迷惑。而给我看表数据,往往就不再需要流程图,程序结构是非常清晰的。”

Kevin 提到的另一条原则是“让无效状态不可表示”。软件的业务逻辑中,难免会存在一些“无效状态”。为了处理它们,代码常需要做一些额外工作。然而,通过调整数据结构设计,使得数据层无法表现无效状态后,程序复杂度就可以降低。《实践“让无效状态不可表示”》中有本原则的一个具体应用案例。

除了上述原则外,文章中的其他原则,比如“关注基础概念而不是具体技术”、“避免用局部简单换取全局复杂”,等等,都充满智慧。

44. 《不,不是编译器的问题,从来都不是》

一段代码的正常运行,依赖着无数隐藏在其背后的组件和库。当程序出现 bug 时,程序员不在第一时间怀疑自己的代码,而是去质疑那些久经考验的依赖库,从来不是一个明智的选择。正如文章的标题所言:“从来都不是编译器的问题。”

然而,“编译器”也是由人编写,并非真的永远正确。“编译器”一旦犯错,问题的诡异程度常常会出乎意料。在文章的后半段,常年信奉“编译器不出错”的作者,还真就遇上了一次“编译器错误”。

45. 《关于在除夕前一天换了一个洗衣机的故事》

一名程序员家中服役 6 年的洗衣机坏了,不能脱水。因为之前花大价钱换过一次排水泵,他以为这次是旧病复发,便决定置换一台新机器。可没想到的是,新洗衣机装好后同样不能脱水。

本来只是一件普普通通的糟心事,但作者显然不这么想,他在文章后半居然从洗衣机转向了软件开发。从故障码到说明书,从 debug 到选品牌,真是很有意思。相当好的观察与思考。

46.《你的函数是什么颜色?》

有人发明了一门编程语言,它非常特别,因为它的函数以颜色来区分类型。函数一共有两种颜色:“红色”和“蓝色”。函数的颜色不止影响外观,更会影响你使用它们的方式,比方说:红函数只能调用红函数,不能调用蓝函数。

虽然以上面这略为不知所云的内容开场,但这篇文章讨论的主题实际上相当严肃。在文章中,作者 Bob 分享了自己对异步编程风格一些思考(猜猜函数的“颜色”代表什么?),从回调、Promise,到线程和 await/async,均有涉及。

除了观点鞭辟入里,文章的写作质量也相当高。严肃内容间不时穿插一点作者的小幽默。对于爱好异步编程的人来说,这是一篇不可错过的佳作。

47. 《健康的文档》

程序员们是一个奇怪的群体,他们对许多事物持有矛盾态度,“文档”就是其中之一。

作为消费者时,每位程序员都希望自己所使用的每个 API、函数,接手的每个系统都能找到详尽而准确的文档。而当他摇身一变,变成生产者时,却很少愿意在“写文档”这件事上投入精力——常常是“宁编百行码,不写一行字”。

然而,文档对于软件开发的重要性毋庸置疑。正如作者提到:“每个未被记录下的东西,都等同于一种资源的浪费,会在未来带来麻烦。”通过写文档,我们将自己脑中的知识具象化,从而在未来帮助到其他人。对于个人而言,文档不仅是一种学习、交流和分享知识的工具,也是一种建立个人影响力的捷径。而对于团队来说,如果每位成员都重视文档的价值,乐于编写清晰、可靠的文档来替代无休止的会议,那么这种“文档优先”的氛围,对于团队的长期发展大有裨益。

48. 《如何像人类一样做代码评审》

一篇关于代码评审的文章,里面涵盖了许多入门和进阶经验,包括:别把评审时间花在风格与样式问题上,让工具来代劳;评论应该以“请求”的口吻,而不是“命令”;评审不是只找缺点,对于好代码应该不吝赞美,等等。

强烈推荐给每一位需要参与代码评审的程序员。

49. 《关于 Python 3.13 你需要了解的一切 - JIT 和 GIL》

Python 3.13 版本引入了许多激动人心的改动,比如基于 “copy-and-patch”技术的即时编译(JIT),以及终于去掉了全局解释器锁(GIL)的“自由线程”模式,等等。

Drew 的这篇文章介绍了以上改动。文章的写作风格非常友好,内容也很全面。既有零基础的概念科普,也有实际的代码实验与 benchmark 环节。知识多,篇幅却控制得恰到好处,推荐阅读。

50. 《入行 14 年,我还是觉得编程很难》

这是清单的第 50 篇,也标记着整个“程序员阅读清单”系列完成了一半。考虑再三,决定奉上拙作一篇,我把这作为对自己的一个小小鼓励。

编程难吗?不同的人会有不同的答案。十几岁时,还在上学的我觉得编程很难,各类算法、API 让人头晕目眩。我期望多年以后,大量的开发经验会让编程变得像吃饭一样简单。

如今十几年过去,编程好像只是变简单了那么一丁点,距离“像吃饭一样简单”还差得很远。

在这篇文章里,我分享了自己对编程这件事的一些思考与总结。比如:打造高效试错的环境至关重要,编程的精髓是“创造”,等等,希望能对你有所启发。

结语

以上就是“程序员阅读清单”第三期的全部内容,祝你阅读愉快!

题图来源:Photo by Jametlene Reskp on Unsplash

程序员阅读清单:我喜欢的 100 篇技术文章(21-40)

2024-10-23 08:36:27

程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。

作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为《程序员阅读清单:我喜欢的 100 篇技术文章》

受限于本人的专业与兴趣所在,清单中的文章对以下几个领域有所偏重:程序员通识、软件工程、后端开发、技术写作、Python 语言、Go 语言

下面是阅读清单的第二部分,包含第 21 到 40 篇文章。

系列索引:

  • 第一部分(1-20):链接
  • 第二部分(21-40):链接

清单

21. 《人生短暂》

人生很短,到底该如何花费自己的时间?传奇投资人、程序员 Paul Graham 在文章中给出了他的建议。总结起来,一共 3 条:尽你所能地避免 bullshit 类事务,比如无用会议、网上吵架;对重要的事情不拖拉,意识到有些东西不会永远停在原地等你;珍惜你所拥有的每一滴时间。

从任何角度看,上面这些建议都称不上有多新奇。但是,作者通过真诚地分享自身经历和感受,给内容注入了不一样的灵魂。或许你会像我一样,读后能获得一些新的感悟。

22. 《有“产品意识”的软件工程师》

从事程序员越久,你大概率会越来越频繁地听到一个词:“产品意识”。人人都说产品意识好,但是它看不见摸不着,到底是个什么东西?是指程序员该自己画线框图?还是说程序员应该写用户故事?

本文作者以软件工程师的视角,对“产品意识”做了全面的解读。简单来说,产品意识就是关注产品、对产品拥有好奇心、对用户拥有同理心;有产品意识的人在做技术方案时,不光思考工程角度,更能靠全局的“产品+工程”视角思考决策。

“产品意识”——工程师们最为强大的思维杠杆之一。

23. 《Python 的 range 不是迭代器》

range 是 Python 语言中最常用的内置对象之一,功能是生产一段数字序列,比如 range(10) => 0, 1, ..., 9。作为循环语句中被迭代的常客,range 常被误认为是一种迭代器(iterator)。但是,正如文章标题所说,虽然可被迭代,但 range 却并不是迭代器。

可如果不是迭代器的话,range 究竟是什么?在文章中,作者用精要的说明和代码片段做出了解答。看起来像咬文嚼字,实则是相当重要的 Python 基础概念。

😊 有关迭代器和可迭代对象这个主题,我也很推荐另一篇自己写的内容:《Python工匠》第六章 6.1.1 “迭代器与可迭代对象”

24. 《有关 TLS/SSL 证书的一切》

一篇和证书有关的科普文。

虽是科普,但这篇和其他科普文章不太一样。你除了能读到一些轻松愉快的小故事,还会被一些不知从哪里冒出来的 shell 命令和大段伪代码“突然袭击”。看似不协调的素材,在作者的精心编排下,却如交响乐团般演奏出一段优美流畅的乐章,让人读来如沐春风。

25. 《让困难的事情变容易》

也许是胡说八道,但我还是想说:技术人普遍有一种“复杂崇拜”情结。实践一门技术,人们常常会踩进许多坑、遇到很多困难,但大部分人对此绝口不提,仿佛抱怨一门技术过于复杂,会显得自己能力不足似的。

尤其,当这些技术是大家口中公认的“基础技术”(比如 DNS、HTTP)时,更是如此。技术人接受复杂、理解复杂,最终认同复杂为理所当然。

正因如此,我很喜欢 Julia Evans 的这个分享。它指出在许多所谓的“基础技术”背后,隐藏着太多难以掌握的复杂元素。不少人都会在它们上面栽跟斗,但并非所有人都会站出来,改善现状。

所以,我们需要让复杂事物变得更容易。针对这一点,文章挑选了几种有代表性的技术,比如 DNS、BASH、SQL 等,提供了切实可行的建议,包括:分享有用的工具和参考文档、从大的功能列表中筛选你真正使用的、展示不可见的内容,等等。

26. 《The Hiring Post》

作者在一家名为 Matasano 的安全公司任职。一天,他接到一份报告,其中描述了一种针对 DSA 的新型攻击手法。由于步骤复杂、条件苛刻,作者认为这种攻击方式有些不切实际,难以实施(时间以月为单位计算)。不过,他还是把报告分享到了团队中(忘了提及“不切实际”)。

两天后,团队里一位名叫 Alex 的新人找到他,说自己完成了一个可工作的漏洞利用程序。

Alex 非常优秀,但是,如果把时间拨回几年前,他根本不会被招进公司。他的简历平平无奇,而当时公司依赖简历和面试来招聘人才。直到后来,Matasano 公司优化了招聘策略,才挖掘出越来越多像 Alex 的人才。

接着开篇的小故事,作者探讨了技术行业在人才招聘方面的一些问题。比方说,许多能力出众的候选人常因招聘环节不合理而无法通过面试。与之相对的是,一些善于面试、对抽象概念总能侃侃而谈的人,却能轻松拿到 offer。针对这些问题,文章给出了一些建议,比如:让候选人热身、使用接近工作场景的测试问题,等等。值得一读。

27. 《13 年后,我如何用 Go 写 HTTP 服务》

一篇 Go 语言方面的最佳实践类文章,只涉及标准库中的 HTTP 基建,不涉及其他第三方 Web 框架或库。作者有十余年的 Go 编程经验,经验丰富。

文章除了展示具体的代码编写与组织技巧,也谈了一些“为什么如此处理”背后的设计考量,包括:长参数列表的函数、请求编解码处理、用闭包结合 http.Handler、E2E 测试和单元测试,等等。透过这些考量,能感受到作者多年经验与智慧的沉淀。

28. 《Rust std fs 比 Python 更慢!?》

一篇精彩的短篇侦探小说。

有一天,Xuanwo 接到用户上报一个奇怪的案件:一段 Rust 实现的 Python SDK 中的文件操作代码,执行起来却比原生 Python 代码更慢。一通排查后,更离谱的事件出现,不止 Rust,甚至同样的 C 代码也比 Python 更慢。但这怎么可能,Python 语言解释器本身都是用 C 写的呀?!

就像任何一篇精彩的侦探小说一样,最后,悬疑气氛推到最高点,凶手身份被揭露时,你会自言自语道:“意料之外,情理之中”。

29. 《选择乏味的技术》

作为技术人员,我们喜欢尝试新技术,这让我们感到快乐。但许多时候,比起闪闪发光的新玩意,“乏味”的技术才是更优的选择。

当我们觉得一项技术“乏味”、痛恨它时,根本原因是我们过于了解它,无法从它身上获得任何新鲜感(比如 Django 之于我)。但别忘了,这同时也意味着我们对这项技术的每个坑都了如指掌。在项目中采用它,能让我们更容易专注在核心业务问题上。

很喜欢本文里的“创新代币”比喻。“创新代币”是一种用来处理创造性任务的有限能力。假设你一共拥有 3 枚“创新代币”,你会如何花费它们?也许,和某个新奇的技术栈比起来,产品核心功能上的创新,更需要那枚代币。

30. 《Python 3.10 中的结构化模式匹配》

在 3.10 版本中,Python 新增了“结构化模式匹配”语法( match ... case)。因为看上去和 switch ... case 语句十分相似,不少人认为“结构化模式匹配”就是 switch 换皮。但事实上,它和 switch 语句有着比较大的差异,用作者的话讲:它更适合被当成“迭代式解包”来理解。

本文发布于 2021 年(Python 3.10 发布前夕),其中简单介绍了“结构化模式匹配”的功能,并列举了一些它最适用的代码场景。在总结中,针对该语法的未来,作者持略为悲观的复杂态度。

和“结构化模式匹配”相关的文章中,除几篇 PEP 之外,我认为这是最值得阅读的一篇。

31. 《你想要的是模块,不是微服务》

文章的开头很有意思。从一篇介绍微服务的文章中,作者摘抄出了微服务架的 10 条优势。随后,他逐条分析这些优势,发现其中至少有一半,可以原封不动地套用在“模块”上。

“只关注一小块代码”、“独立开发”、“版本化”、“独立发布”——以上能力模块无一不具备。对了,此处谈及的“模块”,就是那个诞生于 20 世纪 70 年代的技术概念,也是如今所有编程语言的标配能力。

分析完模块和微服务的相似性后,文章继续层层推进,试着回答一个重要问题:微服务架构解决的本质矛盾究竟是什么?

32. 《我不喜欢 Go 语言默认的 HTTP Handlers》

在编写 HTTP handler 函数时,作者意识到这类函数存在一个设计问题,它会促使人们写出有 bug 的代码。该问题大多数 Go 开发者都知道(也可能犯过):回写响应体后忘记 return,导致代码错误地继续执行。为了优化它,作者提出了一种思路。

技术层面上,这是一篇非常简单的文章,最终方案也无非是“多封装一层”而已。不过,我喜欢作者对细节的关注,也认可文章的价值观:通过优化工具与环境,来杜绝人类犯错的可能性。

33. 《对人类更友好的“超时”与“取消”》

做网络编程时,“超时配置”是一个非常重要但又常常被忽视的细节。不当的超时配置就像是鞋底里的一粒沙,开始你甚至觉察不到它的存在,但随着时间累积,沙子会磨破脚底,产生巨大危害。

“作为最常见的超时配置方式,为什么 get(url, timeout=10) 这类 API 不够好?”

从这个问题出发,作者列举并分析了一些常见的超时 API 设计,最后详细介绍了 trio 库的相关功能。作者认为它是一种“对人类更友好”的设计。

34. 《20 年软件工程师生涯,学到 20 件事》

从业 20 年后,软件工程师 Justin Etheredge 回顾自己的职业生涯,总结出了 20 条经验。这些经验短小精悍、富有洞见,我读后对其中大部分都很有共鸣。

比如其中的第 5 条:“最好的工程师像设计师一样思考”。有许多次,我在一个问题卡住,苦思冥想,寻不到最优解。但当我转换思路,学着像设计师一样站在用户(或调用方、依赖方)角度思考时,答案呼之欲出。再比如其中的第 9 条:“问‘为什么‘,永远不嫌多”——旺盛的好奇心和求知欲,正是助我们精进技术的最佳催化剂。

35. 《为什么你的 mock 不工作》

用 Python 写测试代码时,经常会用到 mock 模块。初次接触 mock,不少人都遇到过 mock 不生效的问题。明明用 mock.patch(...) 替换了模块,代码执行时,引用到的却依旧是原始值。

Ned Batchelder 的这篇文章细致解释了“mock 不生效”问题。因为写的是个常见问题,所以文章中的知识点对你来说可能并不新鲜。但即便如此,我还是很推荐它。文章结构清晰、措辞准确,里面的每张示意图和每段代码,都出现得恰到好处。哪怕不为学知识,略读一遍后,也让人心情舒畅。在技术写作方面,能从中学到不少。

同时推荐作者的另一篇文章:《Python 的名字和值》,内容与 mock 这篇有关联。

36. 《实用的 Go:来自真实世界的编写可维护 Go 程序的建议》

互联网上,“Go 代码可读性“方面的资料不算太多,这篇或许是你能找到的最好的之一。

本文包含数十条与提升 Go 代码可维护性有关的建议,覆盖从变量命名到 API 设计等多项主题,十分全面。我喜欢它最重要的原因,除了其写作质量上佳之外,还在于作者为每条建议精心搭配了示例代码,这些代码使得文章内容非常容易阅读,知识很好消化。一篇干货满满的经典之作,值得每位 Go 工程师阅读。

37. 《编写系统软件:代码注释》

在“代码注释”这个主题上,Redis 作者 antirez 的这篇文章是我的最爱之一。通过整理 redis 项目里的所有注释,antirez 将注释一共划分成 9 类,各自承担不同功用。

本文的独到之处,在于立足“用注释解释代码中的 ‘why?’”这条共识上,重点介绍了“教学性/指引性注释”这类不太常规的注释。文章提到,指引性注释是 redis 中数量最多的注释,充斥整个项目,人们认为 Redis 的源码可读性佳,指引性注释功不可没。

某种程度上,这篇文章影响了我的编码习惯。再次回顾它,脑海闪过那句人们重复提及的老话: “代码主要是写给人看的,顺便被计算机执行。”

38. 《编写易于删除,而不是易于扩展的代码》

程序员们有一条朴素的共识:“重复代码坏,复用代码好“。这篇文章站在另一个角度,反思了这条共识。人们习惯于讨论复用的好处,却往往忽视了它的缺点:一段代码被复用越多,意味着它与更多的使用方产生了耦合关系,自然也导致它更难被修改。

代码写出来后便需要被维护,而业务发展又会让旧代码不断过时。以这个为前提,重新思考软件项目的可维护性,会发现“易于删除”变成了一个形容代码的好特征。这篇文章或许写得没那么易读,但个中观点确能引发思考。

39. 《如何提出好问题》

在人际沟通中,“善于提问”是一种顶级技能( 评级:SSR✨)。在关键时刻提出一个好问题,能让沟通事半功倍,事情水到渠成。

Julia Evans 的这篇文章,囊括了与提问有关的若干条经验和技巧,比如:向对方陈述并确认你所知道的现状;选择向谁提问;通过提问让不够显而易见的概念变得明确,等等。文章不止内容好,写作风格也是一如既往的友善、清晰易读,强力推荐。

40. 《每天写代码》

程序员 John Resig (JQuery 库作者) 遇上了一件烦心事。他想完成一些兴趣项目(side projects),却发现在保证全职工作效率的前提下,很难推进。他常在每个周末疯狂赶工,力求完成更多,但压力和焦虑感总是爆棚,状态难以维系。

有一天,在他人启发下,John 决定换一种策略:每天写代码。原本用整个周末投入兴趣项目,如今拆分到每一天,花不少于 30 分钟编程。半年后,他发现新策略产生了神奇的效果,他取得了超多成果:开发多个新网站、重写若干个框架、完成大量新模块。更重要的是,曾经困扰他的焦虑感,也烟消云散。

我很喜欢这篇文章,它是程序员版本的“日拱一卒”,John 也是一位极好的榜样。

结语

以上就是“程序员阅读清单”第二部分的全部内容,祝你阅读愉快!

题图来源:Photo by Roman Kraft on Unsplash

程序员阅读清单:我喜欢的 100 篇技术文章(1-20)

2024-08-26 07:48:57

程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。

作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为《程序员阅读清单:我喜欢的 100 篇技术文章》

受限于本人的专业与兴趣所在,清单中的文章对以下几个领域有所偏重:程序员通识、软件工程、后端开发、技术写作、Python 语言、Go 语言

下面是阅读清单的第一部分,包含第 1 到 20 篇文章。

系列索引:

  • 第一部分(1-20):链接
  • 第二部分(21-40):链接

清单

1. 《开发者应学习的 10 件有关“学习”的事》

学习对于任何一个人都很重要,对于软件开发者来说更是如此。这是一篇有关“学习”的科普类文章,从介绍人类记忆的工作原理开始,引出专家与新手的区别、间隔与重复的重要性等主题。

文章中的一些观点相当具有启发性。比如“抽象和具象”:新知识对于初学者来说先是抽象的,然后通过大量例子将其具象化,最终彻底掌握后又重新变回抽象。又比如:做智力题和编程能力并没有关联性——这和我们认知中的“聪明人更会编程”大不相同。

2. 《开发者如何管理自驱力》

作者是一名单兵作战的开发者,分享在管理自驱力方面的心得。文章提供了许多提高自驱力的切实可行的小点子,比如:

  • 开发一个通知机器人,当自己的软件有新订阅时通知自己——外力驱动;
  • 每天的开发任务做到 90% 后停止,留到第二天完成——让新一天有盼头;
  • 为了避免自己被“今日一事无成”的罪恶感击溃,先干点高产出的正事,再做其他。

3. 《用 Go 语言分析 10 亿行数据,从 95 秒到 1.96 秒》

一篇很不错的 Go 语言性能优化文章,涉及到这些知识点:文件读取性能优化、生产者消费者模型优化、channel 对比 mutex、自定义 hash 算法,等等。

作者的思维模式、用到的工具链及优化手法非常规范,整个调优过程层层递进,文章行文也很工整。非常值得一读。

4. 《在开发高质量软件上的花费值得吗?》

对于大多数事物而言,如果想要追求更高的质量,必然要花费更多的成本,但对软件而言是否也是如此?作者 Martin Fowler 将软件质量分为两类:外在与内在。

由于软件的内在质量很难被外人所感知,因此花在改善内在质量上的成本常被质疑。但实际上,在内在质量上投入并不增加成本,反而能降低整体花费。文章会通过详细的分析与对比告诉你为什么。

5. 《错误抽象》

如果你想要建造一栋楼房,假如地基不正,最终只能收获一栋歪歪扭扭的残次品。对编程而言,抽象便是地基,良好的抽象是一切美好事物的前提。

这篇文章探讨了复用与抽象间的关系,作者犀利地指出一个事实:对“沉没成本”的恐惧常常孕育出错误抽象,而后者将引发项目质量恶化。

一篇短小精悍的经典之作,不容错过。

6. 《谷歌技术写作课:编写有帮助的错误信息》

在软件开发中,错误信息是一种极为微妙的存在,糟糕的错误信息使人沮丧,时刻提醒着我们:“魔鬼藏在细节中”。

对此,谷歌团队提供了一份关于错误信息的写作建议,包含:精确描述、提供解决方案、面向目标读者写作、用正确的语气写作,等等。我认为这应该成为每位程序员的必修课。

7. 《深入 Python 字典——一份“可探索”的解释》

毫不夸张的说,网上介绍 Python 字典原理的文章多到泛滥。但这篇比较特别,它的特别主要体现在标题里的“可探索”上。

在文章中,作者用一些 Python 代码模拟了字典数据类型。这些代码可在页面上点击执行,过程完全可视化。比如当字典中出现哈希冲突时,会有非常细致的动画,看起来妙趣横生。

8. 《愿意让自己显得愚蠢》

人们天生在意他人的看法,每个人都希望自己是别人眼里的“聪明人”,而不是“傻瓜”。不过,本文作者分享了一个不太常见的观点:做一些让自己显得愚蠢的事,利远大于弊。 比方说:提出愚蠢问题往往能获得对事物更深入的理解;用别人眼中的蠢办法学习,效果更好。

9. 《我们为什么坚持使用 Ruby on Rails》

著名的开源软件 GitLab 的大部分代码都在一个 Rails 单体项目里。GitLab 采用“模块化单体”架构,并未使用近年颇为流行的微服务架构。作者在文章中解释了 GitLab 这么做的原因:微服务架构徒增偶然复杂度,却对降低本质复杂度帮助不大。

我很认同文章中的一句话:架构该为需求服务,而不是反过来。

10. 《ChatGPT 是互联网的一张模糊的 JPEG 图》

这篇文章发表于大语言模型爆发前夜:GPT-3.5 已经问世,GPT-4 蓄势待发。虽然文章的主体论调偏(有理由的)消极,但是文章中的大量精彩类比,以及作者优美的文笔,令人击节称叹。也许你不一定认同作者关于大模型的观点,但你很难不被作者字里行间所流露出的深邃思考所打动。

阅读这篇文章时,我曾多次感叹:“怎么写得这么好?”。我将页面拖动到顶部,仔细检查作者的名字——谜底揭开:“难怪,作者是特德·姜!”

注:特德·姜,当代美国著名科幻作家,小说作品曾获得星云奖、雨果奖等多项大奖。

11. 《重新发明 Python notebook 时学到的教训》

一篇与产品设计有关的总结文章。文章主角是 marimo——一个类似 Jupyter 的 Python 笔记本软件。本文所涉及的内容包括:如何利用有向无环图让笔记总是可重现;为什么强约束的简单设计优于弱约束的复杂,等等。

我很爱读这类文章,因为由技术人写的优秀产品设计经验,如珍珠般少见。

12. 《断点单步跟踪是一种低效的调试方法》

曾经的我以为编程像解数学题,不同人的解法或稍有区别,但终究殊途同归。然而最近两年,我发现编程更像是画画或写作,每个人信奉着自己的道。

云风的这篇文章的标题,坦率来说有些骇人听闻,但仔细读过后,的确能感受到一种独特的编程智慧,一种专属于有着数十年经验的编程匠人的哲思。

13. 《作为“胶水”》

软件工程师的日常工作除编码以外,还有大量其他事务,比如总结文档、优化工具链等,作者将这类事务统称为“胶水工作”。

胶水工作看似不起眼,但对于项目的成败至关重要。本文指出了一个被人忽视的事实:承担更多胶水工作的有责任心的工程师,反而更不易晋升。针对这一点,作者提供了一些有用的建议。

14. 《拥抱苦差事》

本文以一个魔术揭秘开头,引出作者如何通过完成“苦差事”,将整个开发团队拉出泥沼的故事;之间穿插着对程序员金句“懒惰是程序员的美德”的思考。

重读这篇文章时,我想起最近在一本书上看到的另一句话,大意是这样的:“外行人做事时渴求及时反馈与成就感,而专业人士在一切变得乏味后,仍然继续向前。”

15. 《也许是时候停止推荐〈代码整洁之道了〉》

作为一本经典书籍,《代码整洁之道》长期出现在各类编程书单中。但是,本文作者发现,这本出版于十几年前的书中的大量内容已经过时,其中的不少代码示例质量糟糕。

在这篇文章中,本文作者对书中的部分 Java 代码片段进行了几乎称得上是“凶残”的 Code Review。文章观点有一定争议性,但也不乏道理。

16. 《我在编辑时考虑的事》

作为一名专业的技术写作者,作者 Eva 常常帮其他人编辑技术文档。久而久之,她总结出了 9 条编辑建议,比如:明确文章主题、有理由的重复,等等。

虽然文章中的部分建议更适用于英文写作场景,但我仍然很推荐它。因为你很容易发现,这篇文章虽然信息量大,但读来非常流畅、舒服——我想这就是优秀的“编辑”带来的魔力。

17. 《修复流行 Python 库的内存泄露问题》

这篇文章的标题很大,但其实只是一篇短文,里面的 Python 示例代码不超过 10 行。

在一次黑客马拉松活动中, 本文作者和同事一起定位了 py-amqp 库的一个内存泄露问题。提交 PR 后,他在 redis-py 等流行的库中发现了类似的情况。问题和 Python 中的 try/except 语句块有关,迷惑性很强。

18. 《UI 设计原则》

文章总结了 19 条 UI 设计原则,包括:清晰最重要、让用户有掌控感、渐进式披露,等等。我最喜欢的是第 17 条原则:“伟大的设计是隐形的”,它让我想起一些优秀的开源软件库。

虽然名为 UI 设计,但这些原则并不只属于设计师,我认为每个人都可以从中受益。作为程序员,每当我们写下一个函数定义语句,实际就是在做一次 UI 设计。

19. 《你的数据库技能不是“锦上添花”》

在文章中,作者 Andrei 先分享了一个 20 年前的故事:用 MySQL 巧妙完成了一项困难的业务需求。然后引出文章主题:如今大家对数据库技能的关注度不应该这么低。

我很认同作者对于关系数据库和 ORM 等工具的观点。有时候,当项目遇到性能问题时,分明加个索引、优化下查询就能解决,许多人却大喊着:“快点,上缓存!换 DB!”——实在大可不必。

20. 《预估开发时间很难,但还是得做》

在软件开发中,“估时间”是一项令人头疼的事。我们都曾有过类似的经历:拍胸脯说 3 天搞定的任务,最后足足耗费了大半个月。

到后来,“估时间”成了到底留 1 倍还是 2 倍 buffer 的无聊游戏。但正如本文的标题所言,预估开发时间虽然难,却不可避免。这篇文章(系列)提供了一些与之相关的技巧,相信可以给你一些启发。

结语

以上就是“程序员阅读清单”第一期的全部内容,祝你阅读愉快!

题图来源:Photo by Farsai Chaikulngamdee on Unsplash

人人都能写英文博客

2024-07-05 06:56:02

时间过得很快,转眼间,2024 年的进度条已经走到了 50% 的位置。作为一名博主,我很惭愧 🥹,过去半年我只写了一篇新文章,算是相当低产。不过,虽然没写太多新文章,但我干了另一件值得记录的大事。

在今年 2 月份,我给博客增加了“英文”板块,并在其中发表了 4 篇英文文章,几乎每一篇都获得了不错的反响:

  1. "After 14 years in the industry, I still find programming difficult"Hacker News 217 points(Top 20) 评论数 190+,reddit r/programming 773 votes(Top 1) 评论数 310+,被翻译成俄语、韩语
  2. "6 ways to improve the architecture of your Python project (using import-linter)":登上 PyCoder's Weekly 周刊,被播客节目 Python Bytes 推荐,被 Real Python 官方账号推荐
  3. "3 important things I overlooked during code reviews"reddit p/programming 90 votes(Top 5),评论数 16,Lobster 21 votes 评论数 46

图:piglei.com 登上 Reddit /p/programming TOP1,并在上面停留了整整一天

简单来说,在阅读量和读者反馈方面,这些英文文章成绩斐然。并且在某些维度(比如评论数量)上的数据表现,远远超我所写过的任何一篇中文文章。

不过,也许现在屏幕前的你已经皱起了眉头,想说:“行了,行了,piglei 你这个货别显摆了,现在我知道你英文很牛逼了,能写出流利的英文文章来,满意了吧!”

先别急着下结论,其实我的英文能力非常普通(大学六级考两次的水平)。因此,这些文章其实也并非由我从零开始写就,或许眼尖的你已经发现,它们都是由我写过的中文文章翻译而来。

我借助了 GPT 4 和 DeepL Write 等先进工具完成了翻译,并尽全力保证译文“信雅达”,让它们读起来就像是出自一位熟练的英文写作者之手(此处有吹牛成分)。

图:一位读者表示“根本看不出来文章是由中文翻译而来”,incredible!

我使用的翻译方式没有什么门槛,任何一位中文写作者,都能用它来创作属于自己的英文文章。我将在本文教会你具体的方法。

但在进入正题前,让我们先把时钟往回拨一拨,回到今年的二月份,看看究竟是什么事情,在我心里埋下了想开始“英文写作”的种子。

开始“英文写作”的契机

二月份的某个周五,在用谷歌搜索资料时,我无意读到一篇关于 ChatGPT 的英文文章。不读不要紧,一读吓一跳,这篇文章的内容,根我在 2022 年写的一篇中文文章《ChatGPT 正在杀死编程里的乐趣》一模一样。

然后,我顺藤摸瓜点进作者的主页,发现了更多源自我的博客 piglei.com 的文章,比如《Python 工匠》系列,等等。

图:名为 bo leo 用英文大量洗稿了我的博客文章

这些文章没有注明原始出处,作者 bo leo 也从未联系过我征求授权,明显侵犯了我身为原创作者的权益。于是,我在 Medium 平台上举报了这些文章,几个小时后,它们就被下线了(也可能是被作者主动删除,因为事情在推特上被曝光)。

这件事看似得到了圆满的解决,但我的心情却无法平静。因为在点开几篇翻译质量粗糙的“英文盗版文章”后,我在评论区发现了不少高质量的读者评论,部分观点极具启发性——若是它们出现在自己博客的正版文章的评论区,那该多好啊!

图:《ChatGPT 正在杀死编程里的乐趣》英文盗版,右边的读者评论写得很棒

既然那么糟糕的英文版都能获得读者认可,假如翻译质量再好一点呢?再进一步,为什么要给这些偷偷洗稿的卑鄙小人可乘之机,为什么我不干脆自己动手,直接为每一篇自己的得意之作发布对应的“官方英文版”呢?

就这样,在一个月朗星稀的周五晚上,piglei 坐在笔记本电脑前,下决心开始自己的“英文写作”之路——当然,如果你非得要较真的话,他走上的并非真正意义上的“写作”之路,而是一条名为“LLM 英文翻译 + 工具润色”的捷径 😅。

用 LLM 翻译初稿

“机翻(机器翻译)”,在很长一段时间里都是“低质量翻译”的代名词。然而在 ChatGPT 3.5 等大语言模型横空出世后,“机翻”的质量跃升到了一个前所未有的高度。不论是在准确度、流畅度和专业名词翻译方面,优秀的大语言模型所生成的译文,几乎接近普通人类的翻译水准。

因此,我选择用 LLM 来完成译文的初稿。在模型方面,我用到了 OpenAI 的 GPT-4(通过 API 调用),你也可以用其他模型来替代。

为了尽量提升译文的质量,我编写了以下提示语(prompt)来帮助 GPT-4 更好地完成翻译:

You are a professional English translator. I'll send you Chinese content, please translate it into American English.

Requirements:

- Correct any grammatical errors in the original content before translating it.
- The English version should use a concise, direct and clear writing style.
- The content may use markdown format, please keep the format as it is.

Respond only with the translated content.

之后的操作流程比较简单:把中文发给大模型,它就会吐给你一份英文。一篇文章的篇幅通常很长,你需要将其拆分为多个段落,分段完成翻译。

一些注意事项:

  • 在翻译每个新的段落前,记得清空当前聊天的上下文,否则会被多收很多钱(按 token 计费时)
  • 不同段落的译文,对同一个专业名词的翻译需保持一致
  • 如果原文是 Markdown 格式,注意让译文维持原格式
  • 如果原文中的书名或引用段落的原始语言就是英文,那就不要用 LLM 的译文,找到被引用内容的原始英文表达

图:使用 GPT-4 翻译一个段落

这样重复执行多次“复制 -> 聊天 -> 粘贴”后,一篇由 LLM 完成的初稿便落到了我们的手中。

一眼看上去,初稿的翻译质量似乎已然十分出色(前提是使用的模型能力足够强大)。但是如果细读,还是会发现文本中藏着许多小瑕疵,值得进一步优化。因此,我建议继续对初稿实施二次校对和润色。

用 DeepL Write 润色

假如你的英文水平非常过硬,那么你可以直接自己动手来完成润色。但是,对于我这种英文半吊子来说,借助工具是更合适的选择。工具方面,我挑选了知名翻译网站 DeepL 出品的写作助手:DeepL Write

DeepL Write 用起来很简单,只要把原文粘贴到左侧文本框,选择语言为 English,右侧便马上会出现优化过的版本。

图:DeepL Write 界面截图,右侧带下划线的文字是建议优化的内容

但是请注意,虽然 DeepL Write 工具会提供一些优化建议,但它们就像 LLM 的翻译一样——并非 100% 可靠。

所以,作为唯一的人类创作者,我们仍需亲自决定每一个词语、每一种句式。 也正是因为如此,在润色阶段,拥有优秀的英文语感非常重要,因为你要凭借这份语感,来判断哪种写法会给读者提供更优的阅读体验。

在培养语感方面,我认为长期阅读高质量英文文章很有帮助。

推广你的文章

万事俱备,只欠东风。有了英文文章后,下一步便是给它找到最匹配的读者群。幸运的是,在英文世界中推广自己的文章,比在中文世界要方便太多,大多数时候,你只需要轻点小手,把文章的 URL 提交到心仪的资讯站点即可。

目前,我尝试过以下几种渠道:

  1. Hacker News:最知名的科技类资讯站点,流量巨大
  2. Reddit r/programming:知名的编程相关资讯节点,流量很大
  3. Lobster:流行的科技资讯站点,与 HN 风格类似,流量较大,技术讨论氛围非常好
  4. PyCoder's Weekly:知名 Python 编程语言周刊,订阅量巨大

除了以上渠道以外,你也可尝试一些契合文章调性的其他渠道,打个比方,一篇 Go 语言的技术文章,就很适合提交到 Reddit 的 /r/golang 节点上。

⚠️ 注意:虽说积极推广自己的文章不是什么坏事,但也请不要滥用。每次投稿前,请确保内容质量达到标准,并契合对应渠道的读者群。否则会讨人嫌哦!

❤️ 我喜欢这种创作模式

如你所见,我使用的“英文写作(翻译)”方式非常非常非常简单,似乎稍微有点脑子的人就能想到。但其实,在实际上手用 GPT-4 + DeepL 完成第一篇文章前,我的心情极度忐忑。我能听到心中有个小人不停小声念叨:“机器翻译的文章,真的能让英文读者满意吗?”

待到第一篇文章发布,收获了大量的正反馈后,我才敢真正确认,这确实是一条相当可行的创作模式。

我个人非常喜欢这种模式。因为在这之前,我从未想过自己能用第二语言,写出被数万人喜爱的技术文章。 即便这算不上“一字一句”完成的那种真正的写作,但当你读到最终的英文成品时,会发现无论从语言、节奏还是腔调上,它都同自己的创作灵魂契合得天衣无缝,让你满心欢喜。

结语

我花了整整三十年,才学会如何用自己的母语写出文通字顺的文章。假如,我从现在开始学习完全用英文写作,不知还得练习多少年,才能勉强达到及格线。但如今借助 LLM 等现代化工具,我轻松实现了自己的“英文写作梦”。

记得发布完第一篇英文文章后,深圳已经进入深夜,但因为时差原因,文章在 Reddit 和 Hacker News 的热度却在一路走高。看着 vote 数和评论数不断增长,我兴奋得完全无法入睡,几乎每隔三十分钟就要抓起手机,看一遍最新的访问数据。

后来几经辗转,终于进入了梦乡。我已经忘了那晚梦见了什么,但我能想起的是,第二天早晨醒来后,我感受到了一种就像是刚刚学会写字时的喜悦。