2024-10-23 08:36:27
程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。
作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为《程序员阅读清单:我喜欢的 100 篇技术文章》。
受限于本人的专业与兴趣所在,清单中的文章对以下几个领域有所偏重:程序员通识、软件工程、后端开发、技术写作、Python 语言、Go 语言。
下面是阅读清单的第二部分,包含第 21 到 40 篇文章。
系列索引:
人生很短,到底该如何花费自己的时间?传奇投资人、程序员 Paul Graham 在文章中给出了他的建议。总结起来,一共 3 条:尽你所能地避免 bullshit 类事务,比如无用会议、网上吵架;对重要的事情不拖拉,意识到有些东西不会永远停在原地等你;珍惜你所拥有的每一滴时间。
从任何角度看,上面这些建议都称不上有多新奇。但是,作者通过真诚地分享自身经历和感受,给内容注入了不一样的灵魂。或许你会像我一样,读后能获得一些新的感悟。
从事程序员越久,你大概率会越来越频繁地听到一个词:“产品意识”。人人都说产品意识好,但是它看不见摸不着,到底是个什么东西?是指程序员该自己画线框图?还是说程序员应该写用户故事?
本文作者以软件工程师的视角,对“产品意识”做了全面的解读。简单来说,产品意识就是关注产品、对产品拥有好奇心、对用户拥有同理心;有产品意识的人在做技术方案时,不光思考工程角度,更能靠全局的“产品+工程”视角思考决策。
“产品意识”——工程师们最为强大的思维杠杆之一。
range 是 Python 语言中最常用的内置对象之一,功能是生产一段数字序列,比如 range(10) => 0, 1, ..., 9
。作为循环语句中被迭代的常客,range 常被误认为是一种迭代器(iterator)。但是,正如文章标题所说,虽然可被迭代,但 range 却并不是迭代器。
可如果不是迭代器的话,range 究竟是什么?在文章中,作者用精要的说明和代码片段做出了解答。看起来像咬文嚼字,实则是相当重要的 Python 基础概念。
😊 有关迭代器和可迭代对象这个主题,我也很推荐另一篇自己写的内容:《Python工匠》第六章 6.1.1 “迭代器与可迭代对象”。
一篇和证书有关的科普文。
虽是科普,但这篇和其他科普文章不太一样。你除了能读到一些轻松愉快的小故事,还会被一些不知从哪里冒出来的 shell 命令和大段伪代码“突然袭击”。看似不协调的素材,在作者的精心编排下,却如交响乐团般演奏出一段优美流畅的乐章,让人读来如沐春风。
也许是胡说八道,但我还是想说:技术人普遍有一种“复杂崇拜”情结。实践一门技术,人们常常会踩进许多坑、遇到很多困难,但大部分人对此绝口不提,仿佛抱怨一门技术过于复杂,会显得自己能力不足似的。
尤其,当这些技术是大家口中公认的“基础技术”(比如 DNS、HTTP)时,更是如此。技术人接受复杂、理解复杂,最终认同复杂为理所当然。
正因如此,我很喜欢 Julia Evans 的这个分享。它指出在许多所谓的“基础技术”背后,隐藏着太多难以掌握的复杂元素。不少人都会在它们上面栽跟斗,但并非所有人都会站出来,改善现状。
所以,我们需要让复杂事物变得更容易。针对这一点,文章挑选了几种有代表性的技术,比如 DNS、BASH、SQL 等,提供了切实可行的建议,包括:分享有用的工具和参考文档、从大的功能列表中筛选你真正使用的、展示不可见的内容,等等。
作者在一家名为 Matasano 的安全公司任职。一天,他接到一份报告,其中描述了一种针对 DSA 的新型攻击手法。由于步骤复杂、条件苛刻,作者认为这种攻击方式有些不切实际,难以实施(时间以月为单位计算)。不过,他还是把报告分享到了团队中(忘了提及“不切实际”)。
两天后,团队里一位名叫 Alex 的新人找到他,说自己完成了一个可工作的漏洞利用程序。
Alex 非常优秀,但是,如果把时间拨回几年前,他根本不会被招进公司。他的简历平平无奇,而当时公司依赖简历和面试来招聘人才。直到后来,Matasano 公司优化了招聘策略,才挖掘出越来越多像 Alex 的人才。
接着开篇的小故事,作者探讨了技术行业在人才招聘方面的一些问题。比方说,许多能力出众的候选人常因招聘环节不合理而无法通过面试。与之相对的是,一些善于面试、对抽象概念总能侃侃而谈的人,却能轻松拿到 offer。针对这些问题,文章给出了一些建议,比如:让候选人热身、使用接近工作场景的测试问题,等等。值得一读。
一篇 Go 语言方面的最佳实践类文章,只涉及标准库中的 HTTP 基建,不涉及其他第三方 Web 框架或库。作者有十余年的 Go 编程经验,经验丰富。
文章除了展示具体的代码编写与组织技巧,也谈了一些“为什么如此处理”背后的设计考量,包括:长参数列表的函数、请求编解码处理、用闭包结合 http.Handler、E2E 测试和单元测试,等等。透过这些考量,能感受到作者多年经验与智慧的沉淀。
一篇精彩的短篇侦探小说。
有一天,Xuanwo 接到用户上报一个奇怪的案件:一段 Rust 实现的 Python SDK 中的文件操作代码,执行起来却比原生 Python 代码更慢。一通排查后,更离谱的事件出现,不止 Rust,甚至同样的 C 代码也比 Python 更慢。但这怎么可能,Python 语言解释器本身都是用 C 写的呀?!
就像任何一篇精彩的侦探小说一样,最后,悬疑气氛推到最高点,凶手身份被揭露时,你会自言自语道:“意料之外,情理之中”。
作为技术人员,我们喜欢尝试新技术,这让我们感到快乐。但许多时候,比起闪闪发光的新玩意,“乏味”的技术才是更优的选择。
当我们觉得一项技术“乏味”、痛恨它时,根本原因是我们过于了解它,无法从它身上获得任何新鲜感(比如 Django 之于我)。但别忘了,这同时也意味着我们对这项技术的每个坑都了如指掌。在项目中采用它,能让我们更容易专注在核心业务问题上。
很喜欢本文里的“创新代币”比喻。“创新代币”是一种用来处理创造性任务的有限能力。假设你一共拥有 3 枚“创新代币”,你会如何花费它们?也许,和某个新奇的技术栈比起来,产品核心功能上的创新,更需要那枚代币。
在 3.10 版本中,Python 新增了“结构化模式匹配”语法( match ... case)。因为看上去和 switch ... case 语句十分相似,不少人认为“结构化模式匹配”就是 switch 换皮。但事实上,它和 switch 语句有着比较大的差异,用作者的话讲:它更适合被当成“迭代式解包”来理解。
本文发布于 2021 年(Python 3.10 发布前夕),其中简单介绍了“结构化模式匹配”的功能,并列举了一些它最适用的代码场景。在总结中,针对该语法的未来,作者持略为悲观的复杂态度。
和“结构化模式匹配”相关的文章中,除几篇 PEP 之外,我认为这是最值得阅读的一篇。
文章的开头很有意思。从一篇介绍微服务的文章中,作者摘抄出了微服务架的 10 条优势。随后,他逐条分析这些优势,发现其中至少有一半,可以原封不动地套用在“模块”上。
“只关注一小块代码”、“独立开发”、“版本化”、“独立发布”——以上能力模块无一不具备。对了,此处谈及的“模块”,就是那个诞生于 20 世纪 70 年代的技术概念,也是如今所有编程语言的标配能力。
分析完模块和微服务的相似性后,文章继续层层推进,试着回答一个重要问题:微服务架构解决的本质矛盾究竟是什么?
在编写 HTTP handler 函数时,作者意识到这类函数存在一个设计问题,它会促使人们写出有 bug 的代码。该问题大多数 Go 开发者都知道(也可能犯过):回写响应体后忘记 return,导致代码错误地继续执行。为了优化它,作者提出了一种思路。
技术层面上,这是一篇非常简单的文章,最终方案也无非是“多封装一层”而已。不过,我喜欢作者对细节的关注,也认可文章的价值观:通过优化工具与环境,来杜绝人类犯错的可能性。
做网络编程时,“超时配置”是一个非常重要但又常常被忽视的细节。不当的超时配置就像是鞋底里的一粒沙,开始你甚至觉察不到它的存在,但随着时间累积,沙子会磨破脚底,产生巨大危害。
“作为最常见的超时配置方式,为什么 get(url, timeout=10)
这类 API 不够好?”
从这个问题出发,作者列举并分析了一些常见的超时 API 设计,最后详细介绍了 trio 库的相关功能。作者认为它是一种“对人类更友好”的设计。
从业 20 年后,软件工程师 Justin Etheredge 回顾自己的职业生涯,总结出了 20 条经验。这些经验短小精悍、富有洞见,我读后对其中大部分都很有共鸣。
比如其中的第 5 条:“最好的工程师像设计师一样思考”。有许多次,我在一个问题卡住,苦思冥想,寻不到最优解。但当我转换思路,学着像设计师一样站在用户(或调用方、依赖方)角度思考时,答案呼之欲出。再比如其中的第 9 条:“问‘为什么‘,永远不嫌多”——旺盛的好奇心和求知欲,正是助我们精进技术的最佳催化剂。
用 Python 写测试代码时,经常会用到 mock 模块。初次接触 mock,不少人都遇到过 mock 不生效的问题。明明用 mock.patch(...)
替换了模块,代码执行时,引用到的却依旧是原始值。
Ned Batchelder 的这篇文章细致解释了“mock 不生效”问题。因为写的是个常见问题,所以文章中的知识点对你来说可能并不新鲜。但即便如此,我还是很推荐它。文章结构清晰、措辞准确,里面的每张示意图和每段代码,都出现得恰到好处。哪怕不为学知识,略读一遍后,也让人心情舒畅。在技术写作方面,能从中学到不少。
同时推荐作者的另一篇文章:《Python 的名字和值》,内容与 mock 这篇有关联。
互联网上,“Go 代码可读性“方面的资料不算太多,这篇或许是你能找到的最好的之一。
本文包含数十条与提升 Go 代码可维护性有关的建议,覆盖从变量命名到 API 设计等多项主题,十分全面。我喜欢它最重要的原因,除了其写作质量上佳之外,还在于作者为每条建议精心搭配了示例代码,这些代码使得文章内容非常容易阅读,知识很好消化。一篇干货满满的经典之作,值得每位 Go 工程师阅读。
在“代码注释”这个主题上,Redis 作者 antirez 的这篇文章是我的最爱之一。通过整理 redis 项目里的所有注释,antirez 将注释一共划分成 9 类,各自承担不同功用。
本文的独到之处,在于立足“用注释解释代码中的 ‘why?’”这条共识上,重点介绍了“教学性/指引性注释”这类不太常规的注释。文章提到,指引性注释是 redis 中数量最多的注释,充斥整个项目,人们认为 Redis 的源码可读性佳,指引性注释功不可没。
某种程度上,这篇文章影响了我的编码习惯。再次回顾它,脑海闪过那句人们重复提及的老话: “代码主要是写给人看的,顺便被计算机执行。”
程序员们有一条朴素的共识:“重复代码坏,复用代码好“。这篇文章站在另一个角度,反思了这条共识。人们习惯于讨论复用的好处,却往往忽视了它的缺点:一段代码被复用越多,意味着它与更多的使用方产生了耦合关系,自然也导致它更难被修改。
代码写出来后便需要被维护,而业务发展又会让旧代码不断过时。以这个为前提,重新思考软件项目的可维护性,会发现“易于删除”变成了一个形容代码的好特征。这篇文章或许写得没那么易读,但个中观点确能引发思考。
在人际沟通中,“善于提问”是一种顶级技能( 评级:SSR✨)。在关键时刻提出一个好问题,能让沟通事半功倍,事情水到渠成。
Julia Evans 的这篇文章,囊括了与提问有关的若干条经验和技巧,比如:向对方陈述并确认你所知道的现状;选择向谁提问;通过提问让不够显而易见的概念变得明确,等等。文章不止内容好,写作风格也是一如既往的友善、清晰易读,强力推荐。
程序员 John Resig (JQuery 库作者) 遇上了一件烦心事。他想完成一些兴趣项目(side projects),却发现在保证全职工作效率的前提下,很难推进。他常在每个周末疯狂赶工,力求完成更多,但压力和焦虑感总是爆棚,状态难以维系。
有一天,在他人启发下,John 决定换一种策略:每天写代码。原本用整个周末投入兴趣项目,如今拆分到每一天,花不少于 30 分钟编程。半年后,他发现新策略产生了神奇的效果,他取得了超多成果:开发多个新网站、重写若干个框架、完成大量新模块。更重要的是,曾经困扰他的焦虑感,也烟消云散。
我很喜欢这篇文章,它是程序员版本的“日拱一卒”,John 也是一位极好的榜样。
以上就是“程序员阅读清单”第二部分的全部内容,祝你阅读愉快!
题图来源:Photo by Roman Kraft on Unsplash
2024-08-26 07:48:57
程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。
作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为《程序员阅读清单:我喜欢的 100 篇技术文章》。
受限于本人的专业与兴趣所在,清单中的文章对以下几个领域有所偏重:程序员通识、软件工程、后端开发、技术写作、Python 语言、Go 语言。
下面是阅读清单的第一部分,包含第 1 到 20 篇文章。
系列索引:
学习对于任何一个人都很重要,对于软件开发者来说更是如此。这是一篇有关“学习”的科普类文章,从介绍人类记忆的工作原理开始,引出专家与新手的区别、间隔与重复的重要性等主题。
文章中的一些观点相当具有启发性。比如“抽象和具象”:新知识对于初学者来说先是抽象的,然后通过大量例子将其具象化,最终彻底掌握后又重新变回抽象。又比如:做智力题和编程能力并没有关联性——这和我们认知中的“聪明人更会编程”大不相同。
作者是一名单兵作战的开发者,分享在管理自驱力方面的心得。文章提供了许多提高自驱力的切实可行的小点子,比如:
一篇很不错的 Go 语言性能优化文章,涉及到这些知识点:文件读取性能优化、生产者消费者模型优化、channel 对比 mutex、自定义 hash 算法,等等。
作者的思维模式、用到的工具链及优化手法非常规范,整个调优过程层层递进,文章行文也很工整。非常值得一读。
对于大多数事物而言,如果想要追求更高的质量,必然要花费更多的成本,但对软件而言是否也是如此?作者 Martin Fowler 将软件质量分为两类:外在与内在。
由于软件的内在质量很难被外人所感知,因此花在改善内在质量上的成本常被质疑。但实际上,在内在质量上投入并不增加成本,反而能降低整体花费。文章会通过详细的分析与对比告诉你为什么。
如果你想要建造一栋楼房,假如地基不正,最终只能收获一栋歪歪扭扭的残次品。对编程而言,抽象便是地基,良好的抽象是一切美好事物的前提。
这篇文章探讨了复用与抽象间的关系,作者犀利地指出一个事实:对“沉没成本”的恐惧常常孕育出错误抽象,而后者将引发项目质量恶化。
一篇短小精悍的经典之作,不容错过。
在软件开发中,错误信息是一种极为微妙的存在,糟糕的错误信息使人沮丧,时刻提醒着我们:“魔鬼藏在细节中”。
对此,谷歌团队提供了一份关于错误信息的写作建议,包含:精确描述、提供解决方案、面向目标读者写作、用正确的语气写作,等等。我认为这应该成为每位程序员的必修课。
毫不夸张的说,网上介绍 Python 字典原理的文章多到泛滥。但这篇比较特别,它的特别主要体现在标题里的“可探索”上。
在文章中,作者用一些 Python 代码模拟了字典数据类型。这些代码可在页面上点击执行,过程完全可视化。比如当字典中出现哈希冲突时,会有非常细致的动画,看起来妙趣横生。
人们天生在意他人的看法,每个人都希望自己是别人眼里的“聪明人”,而不是“傻瓜”。不过,本文作者分享了一个不太常见的观点:做一些让自己显得愚蠢的事,利远大于弊。 比方说:提出愚蠢问题往往能获得对事物更深入的理解;用别人眼中的蠢办法学习,效果更好。
著名的开源软件 GitLab 的大部分代码都在一个 Rails 单体项目里。GitLab 采用“模块化单体”架构,并未使用近年颇为流行的微服务架构。作者在文章中解释了 GitLab 这么做的原因:微服务架构徒增偶然复杂度,却对降低本质复杂度帮助不大。
我很认同文章中的一句话:架构该为需求服务,而不是反过来。
这篇文章发表于大语言模型爆发前夜:GPT-3.5 已经问世,GPT-4 蓄势待发。虽然文章的主体论调偏(有理由的)消极,但是文章中的大量精彩类比,以及作者优美的文笔,令人击节称叹。也许你不一定认同作者关于大模型的观点,但你很难不被作者字里行间所流露出的深邃思考所打动。
阅读这篇文章时,我曾多次感叹:“怎么写得这么好?”。我将页面拖动到顶部,仔细检查作者的名字——谜底揭开:“难怪,作者是特德·姜!”
注:特德·姜,当代美国著名科幻作家,小说作品曾获得星云奖、雨果奖等多项大奖。
一篇与产品设计有关的总结文章。文章主角是 marimo——一个类似 Jupyter 的 Python 笔记本软件。本文所涉及的内容包括:如何利用有向无环图让笔记总是可重现;为什么强约束的简单设计优于弱约束的复杂,等等。
我很爱读这类文章,因为由技术人写的优秀产品设计经验,如珍珠般少见。
曾经的我以为编程像解数学题,不同人的解法或稍有区别,但终究殊途同归。然而最近两年,我发现编程更像是画画或写作,每个人信奉着自己的道。
云风的这篇文章的标题,坦率来说有些骇人听闻,但仔细读过后,的确能感受到一种独特的编程智慧,一种专属于有着数十年经验的编程匠人的哲思。
软件工程师的日常工作除编码以外,还有大量其他事务,比如总结文档、优化工具链等,作者将这类事务统称为“胶水工作”。
胶水工作看似不起眼,但对于项目的成败至关重要。本文指出了一个被人忽视的事实:承担更多胶水工作的有责任心的工程师,反而更不易晋升。针对这一点,作者提供了一些有用的建议。
本文以一个魔术揭秘开头,引出作者如何通过完成“苦差事”,将整个开发团队拉出泥沼的故事;之间穿插着对程序员金句“懒惰是程序员的美德”的思考。
重读这篇文章时,我想起最近在一本书上看到的另一句话,大意是这样的:“外行人做事时渴求及时反馈与成就感,而专业人士在一切变得乏味后,仍然继续向前。”
作为一本经典书籍,《代码整洁之道》长期出现在各类编程书单中。但是,本文作者发现,这本出版于十几年前的书中的大量内容已经过时,其中的不少代码示例质量糟糕。
在这篇文章中,本文作者对书中的部分 Java 代码片段进行了几乎称得上是“凶残”的 Code Review。文章观点有一定争议性,但也不乏道理。
作为一名专业的技术写作者,作者 Eva 常常帮其他人编辑技术文档。久而久之,她总结出了 9 条编辑建议,比如:明确文章主题、有理由的重复,等等。
虽然文章中的部分建议更适用于英文写作场景,但我仍然很推荐它。因为你很容易发现,这篇文章虽然信息量大,但读来非常流畅、舒服——我想这就是优秀的“编辑”带来的魔力。
这篇文章的标题很大,但其实只是一篇短文,里面的 Python 示例代码不超过 10 行。
在一次黑客马拉松活动中, 本文作者和同事一起定位了 py-amqp 库的一个内存泄露问题。提交 PR 后,他在 redis-py 等流行的库中发现了类似的情况。问题和 Python 中的 try/except 语句块有关,迷惑性很强。
文章总结了 19 条 UI 设计原则,包括:清晰最重要、让用户有掌控感、渐进式披露,等等。我最喜欢的是第 17 条原则:“伟大的设计是隐形的”,它让我想起一些优秀的开源软件库。
虽然名为 UI 设计,但这些原则并不只属于设计师,我认为每个人都可以从中受益。作为程序员,每当我们写下一个函数定义语句,实际就是在做一次 UI 设计。
在文章中,作者 Andrei 先分享了一个 20 年前的故事:用 MySQL 巧妙完成了一项困难的业务需求。然后引出文章主题:如今大家对数据库技能的关注度不应该这么低。
我很认同作者对于关系数据库和 ORM 等工具的观点。有时候,当项目遇到性能问题时,分明加个索引、优化下查询就能解决,许多人却大喊着:“快点,上缓存!换 DB!”——实在大可不必。
在软件开发中,“估时间”是一项令人头疼的事。我们都曾有过类似的经历:拍胸脯说 3 天搞定的任务,最后足足耗费了大半个月。
到后来,“估时间”成了到底留 1 倍还是 2 倍 buffer 的无聊游戏。但正如本文的标题所言,预估开发时间虽然难,却不可避免。这篇文章(系列)提供了一些与之相关的技巧,相信可以给你一些启发。
以上就是“程序员阅读清单”第一期的全部内容,祝你阅读愉快!
题图来源:Photo by Farsai Chaikulngamdee on Unsplash
2024-07-05 06:56:02
时间过得很快,转眼间,2024 年的进度条已经走到了 50% 的位置。作为一名博主,我很惭愧 🥹,过去半年我只写了一篇新文章,算是相当低产。不过,虽然没写太多新文章,但我干了另一件值得记录的大事。
在今年 2 月份,我给博客增加了“英文”板块,并在其中发表了 4 篇英文文章,几乎每一篇都获得了不错的反响:
简单来说,在阅读量和读者反馈方面,这些英文文章成绩斐然。并且在某些维度(比如评论数量)上的数据表现,远远超我所写过的任何一篇中文文章。
不过,也许现在屏幕前的你已经皱起了眉头,想说:“行了,行了,piglei 你这个货别显摆了,现在我知道你英文很牛逼了,能写出流利的英文文章来,满意了吧!”
先别急着下结论,其实我的英文能力非常普通(大学六级考两次的水平)。因此,这些文章其实也并非由我从零开始写就,或许眼尖的你已经发现,它们都是由我写过的中文文章翻译而来。
我借助了 GPT 4 和 DeepL Write 等先进工具完成了翻译,并尽全力保证译文“信雅达”,让它们读起来就像是出自一位熟练的英文写作者之手(此处有吹牛成分)。
我使用的翻译方式没有什么门槛,任何一位中文写作者,都能用它来创作属于自己的英文文章。我将在本文教会你具体的方法。
但在进入正题前,让我们先把时钟往回拨一拨,回到今年的二月份,看看究竟是什么事情,在我心里埋下了想开始“英文写作”的种子。
二月份的某个周五,在用谷歌搜索资料时,我无意读到一篇关于 ChatGPT 的英文文章。不读不要紧,一读吓一跳,这篇文章的内容,根我在 2022 年写的一篇中文文章《ChatGPT 正在杀死编程里的乐趣》一模一样。
然后,我顺藤摸瓜点进作者的主页,发现了更多源自我的博客 piglei.com 的文章,比如《Python 工匠》系列,等等。
这些文章没有注明原始出处,作者 bo leo 也从未联系过我征求授权,明显侵犯了我身为原创作者的权益。于是,我在 Medium 平台上举报了这些文章,几个小时后,它们就被下线了(也可能是被作者主动删除,因为事情在推特上被曝光)。
这件事看似得到了圆满的解决,但我的心情却无法平静。因为在点开几篇翻译质量粗糙的“英文盗版文章”后,我在评论区发现了不少高质量的读者评论,部分观点极具启发性——若是它们出现在自己博客的正版文章的评论区,那该多好啊!
既然那么糟糕的英文版都能获得读者认可,假如翻译质量再好一点呢?再进一步,为什么要给这些偷偷洗稿的卑鄙小人可乘之机,为什么我不干脆自己动手,直接为每一篇自己的得意之作发布对应的“官方英文版”呢?
就这样,在一个月朗星稀的周五晚上,piglei 坐在笔记本电脑前,下决心开始自己的“英文写作”之路——当然,如果你非得要较真的话,他走上的并非真正意义上的“写作”之路,而是一条名为“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.
之后的操作流程比较简单:把中文发给大模型,它就会吐给你一份英文。一篇文章的篇幅通常很长,你需要将其拆分为多个段落,分段完成翻译。
一些注意事项:
这样重复执行多次“复制 -> 聊天 -> 粘贴”后,一篇由 LLM 完成的初稿便落到了我们的手中。
一眼看上去,初稿的翻译质量似乎已然十分出色(前提是使用的模型能力足够强大)。但是如果细读,还是会发现文本中藏着许多小瑕疵,值得进一步优化。因此,我建议继续对初稿实施二次校对和润色。
假如你的英文水平非常过硬,那么你可以直接自己动手来完成润色。但是,对于我这种英文半吊子来说,借助工具是更合适的选择。工具方面,我挑选了知名翻译网站 DeepL 出品的写作助手:DeepL Write。
DeepL Write 用起来很简单,只要把原文粘贴到左侧文本框,选择语言为 English,右侧便马上会出现优化过的版本。
但是请注意,虽然 DeepL Write 工具会提供一些优化建议,但它们就像 LLM 的翻译一样——并非 100% 可靠。
所以,作为唯一的人类创作者,我们仍需亲自决定每一个词语、每一种句式。 也正是因为如此,在润色阶段,拥有优秀的英文语感非常重要,因为你要凭借这份语感,来判断哪种写法会给读者提供更优的阅读体验。
在培养语感方面,我认为长期阅读高质量英文文章很有帮助。
万事俱备,只欠东风。有了英文文章后,下一步便是给它找到最匹配的读者群。幸运的是,在英文世界中推广自己的文章,比在中文世界要方便太多,大多数时候,你只需要轻点小手,把文章的 URL 提交到心仪的资讯站点即可。
目前,我尝试过以下几种渠道:
除了以上渠道以外,你也可尝试一些契合文章调性的其他渠道,打个比方,一篇 Go 语言的技术文章,就很适合提交到 Reddit 的 /r/golang 节点上。
⚠️ 注意:虽说积极推广自己的文章不是什么坏事,但也请不要滥用。每次投稿前,请确保内容质量达到标准,并契合对应渠道的读者群。否则会讨人嫌哦!
如你所见,我使用的“英文写作(翻译)”方式非常非常非常简单,似乎稍微有点脑子的人就能想到。但其实,在实际上手用 GPT-4 + DeepL 完成第一篇文章前,我的心情极度忐忑。我能听到心中有个小人不停小声念叨:“机器翻译的文章,真的能让英文读者满意吗?”
待到第一篇文章发布,收获了大量的正反馈后,我才敢真正确认,这确实是一条相当可行的创作模式。
我个人非常喜欢这种模式。因为在这之前,我从未想过自己能用第二语言,写出被数万人喜爱的技术文章。 即便这算不上“一字一句”完成的那种真正的写作,但当你读到最终的英文成品时,会发现无论从语言、节奏还是腔调上,它都同自己的创作灵魂契合得天衣无缝,让你满心欢喜。
我花了整整三十年,才学会如何用自己的母语写出文通字顺的文章。假如,我从现在开始学习完全用英文写作,不知还得练习多少年,才能勉强达到及格线。但如今借助 LLM 等现代化工具,我轻松实现了自己的“英文写作梦”。
记得发布完第一篇英文文章后,深圳已经进入深夜,但因为时差原因,文章在 Reddit 和 Hacker News 的热度却在一路走高。看着 vote 数和评论数不断增长,我兴奋得完全无法入睡,几乎每隔三十分钟就要抓起手机,看一遍最新的访问数据。
后来几经辗转,终于进入了梦乡。我已经忘了那晚梦见了什么,但我能想起的是,第二天早晨醒来后,我感受到了一种就像是刚刚学会写字时的喜悦。
2024-04-18 08:00:11
Code Review(代码评审)是一种流行的软件开发实践。通过在代码合入主分支前引入人工评审,能有效促进成员间的知识交流,提升软件质量。
我以评审者的身份参与过大量代码评审。在评审一份代码时,有些事项长期处在我的关注榜头部,比如设计是否考虑到了边界情况、代码是否有合理的单测覆盖。也有一些事项,因看似无关痛痒一直未引起足够重视,直到最近,我才渐渐发现它们的重要性。
以下是曾被我忽视的 3 件重要的小事。
小女孩千寻误入汤婆婆为神明开设的浴场。为了留在浴场内工作,千寻与汤婆婆签订了一份协议,但协议并非重点,重点是另一件看似无关紧要的小事——汤婆婆给千寻改了个名:从“千寻”改为“千”。一旦失去了原本的名字,人们便失去了逃离浴场所在的异世界的能力,甘心永世被汤婆婆所奴役。
——电影《千与千寻》
程序员们对“命名”的关注程度似乎呈一个“倒 U 形”曲线。缺乏经验时,对命名的关注度很低,代码中充斥着各类不准确、不精确的名字,无法有效描述各种抽象概念。
下面这段代码中的命名就存在不少问题:
def get_var(u):
"""获取环境变量列表"""
data1 = UserVarsManager.get(u)
data2 = SiteVarsManager.get(u.site)
return data1 + data2
随着经验逐渐增加,大家对命名的关注度逐步提升。项目中的名字开始变得更具有描述性,含糊不清的名字渐渐绝迹。名字至少不会成为他人理解代码时的屏障。
这个阶段,代码会逐渐演变成像是这样:
def list_environment_vars(user):
"""获取环境变量列表"""
items_user = UserVarsManager.get(user)
items_site = SiteVarsManager.get(user.site)
return items_user + items_site
在绝大多数评审中,这绝对算是一份合格的代码,至少不大可能因为命名应发争议。
自此之后,大部分程序员们对命名的关注度进入“倒 U 形”曲线的后半段:不再如从前那般关注命名,名字只要有一定描述性,不造成歧义就足够。我也曾经是其中一员。
但不应在这个阶段停留太久,作为代码评审人,我们应该不断提升自己对于名字的敏感度。比方说,对于前面那份代码,也许应该提出以下评审建议:
def list_environment_vars(user): # 1
"""获取环境变量列表"""
items_user = UserVarsManager.get(user) # 2
items_site = SiteVarsManager.get(user.site)
return items_user + items_site
env_variables /env_vars
,此处应保持一致,使用 list_env_variables
或 list_env_vars
。UserVarsManager.get
的命名可优化,因为 Manager
是一个“万金油”名词,虽然放在各种场景下都不违和,但也是以损失名字(等同于“职责”)的精确指向性为代价,此处可考虑改用一个更精确的名字,比如:UserEnvVarsRetriever.get(user)
;SiteVarsManager
同理。虽然只是两处小改进,但是积少成多。
每一次代码评审,必定涉及到许多新名字。但名字并非生来平等,不是所有名字都值得我们花费时间,应当尽量把关注点聚焦在那些最常被使用、最靠近用户的名字上,比如 URL 路径的资源名、数据库模型与字段名、工具函数(类方法)名,等等。
此外,与业务直接相关的领域词汇重要程度极高。评审时,每一个关键的领域词汇都值得仔细斟酌、反复推敲。举个例子,开发一个影评功能,”用户评分“、“媒体评分”、“平均分”分别该用哪些名字表示?你绝不会想要在一个文件里看到 movie_score
,在另一个文件里看到 movie_rating
。
命名这件小事,虽然看似不起眼,但项目规模越大、所跨越的时间维度越长,在名字质量上的细微差别就越容易累加出不可估量的巨大影响。
夏洛已经在网上织出了光彩照人四个大字,威尔伯站在金色的阳光里,真是光彩照人。自从蜘蛛开始扶助它,它就尽力活得跟它的名声相衬。夏洛的网说它是王牌猪,威尔伯尽力让自己看上去是只王牌猪;夏洛的网说它了不起,威尔伯尽力让自己看上去了不起;现在网上说它光彩照人,它尽力让自己光彩照人。
——《夏洛的网》
关于注释,我向来信奉 Bob 大叔在《代码整洁之道》里的观点:“注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。” 这就是说,好代码应该总是能清晰说明自身意图,无需注释再来画蛇添足,注释只应该被用来描述那些代码之外的信息,比如解释“为什么”。
正因如此,注释总是应该被谨慎使用。假如一段代码很难理解,第一反应不应该是补注释,而是应该去追求用一种更易理解的方式重写它。
但随着时间的推移,我渐渐意识到,事情不能一概而论。“指引性注释”,或者说常被人们诟病为“近乎复述代码意图”的描述性文字,也有着不可替代的重要作用。
Redis 的作者 antirez 就是“指引性注释”的忠实拥护者,他曾写过一篇文章详细分析过指引性注释在 Redis 项目中的应用。下面这段代码摘自 Redis 源码,里面就有不少“指引性注释”:
/* Call the node callback if any, and replace the node pointer
* if the callback returns true. */
if (it->node_cb && it->node_cb(&it->node))
memcpy(cp,&it->node,sizeof(it->node));
/* For "next" step, stop every time we find a key along the
* way, since the key is lexicographically smaller compared to
* what follows in the sub-children. */
if (it->node->iskey) {
it->data = raxGetData(it->node);
return 1;
}
在这段代码中,两段注释并未提供任何在代码之外的新信息。所以,好处是什么?
最直观的好处,就是这些注释让代码变得更容易理解了,它们极大地降低了人们阅读代码时所需付出的心智成本。同样一份代码,在缺少指引性注释的情况下,完全理解它的行为可能得花费 10 分钟,而有了注释的帮助,时间也许就能缩短到 5 分钟甚至更短。
当新开发者加入项目时,这些指引性注释也能助力他们更快上手。
正因如此,在评审一份代码时,我常常会在一段复杂的代码逻辑上评论:“Nit:考虑增加一小段指引性注释,帮助理清代码行为。”(Nit=nitpick,表示“鸡蛋里挑骨头”式的并不强烈要求修改的意见)。
此外,如果一段代码曾在评审过程中引发过一些深度讨论,那么那些讨论内容,也许很适合被二次加工后,作为指引性注释加入代码中。对于理解代码来说,它们有时有奇效。
不过,在追求“指引性注释”的路上,也要避免踩入以下几个陷阱:
总而言之,你可以把指引性注释当成有针对性的代码“教学文本”。审阅代码时,如果你发现一段逻辑理解起来很吃力,而代码本身也没有太多优化空间,请不要迟疑,勇敢表达出你对于“教学文本”的需求吧!
“我因为鲁思和萨拉不得不离开我们而痛苦万分。而令我感觉更加痛苦的是我当时以为自己是完全孤立无援的。”
“说真的,肯顿小姐……”我端起那个我用来放使用过的瓷器的托盘。“对那样的解雇我自然是极不赞同的。我还以为那是不言自明的。”
——《长日将尽》
时至今日,仍有许多人认为软件开发是一种单打独斗的工作。一位程序员捡起一块键盘,就能源源不断地产出代码,根本不需要其他人。但事实是,程序员单打独斗的黄金时代早已过去,现代软件开发已演变成一种多人参与的协作事务。正因如此,程序员的日常充斥着各类沟通工作,参与代码评审正是其中之一。
在代码评审时,评审者的工作内容似乎一句话就可简单概括:指出他人代码中的不足。 这听起来易如反掌,对不对?我曾经正是这么以为,所谓评审,只要做完下面的“123”即可:
而现实总是和理想相去甚远,代码评审很少会像上面这样顺利。因为一旦涉及到人与人之间的沟通,尤其其中一方还在给另外一方“挑毛病”,事情又能简单到哪儿去呢?
人类是一种神奇的智慧生物,阅读一段文字,不仅能从中获取到信息,更能从字里行间感受到情绪,有时,这份情绪甚至会盖过信息,影响他们做出判断。因此,当你在参与评审时,请谨记这一点:保持谦逊、尊重他人,无论对方的经验或背景如何。优秀的表达,能做到内容即使在批评,也能让对方感受到自己仍是被尊重的。
让我来举一个例子。团队内来了一位新人,用他不太熟悉的 Python 语言提交了一个 PR。作为 PR 的评审人,你在代码里发现了一段冗长的循环代码,于是写下评论:
代码比较啰嗦,建议改成列表推导式。
虽然你的观点没错,但这种表达方式值得商榷。下面是这条评论的另一种写法:
这里的循环体较简单,只有过滤和转换逻辑,很适合改成列表推导式,代码更精简。举个例子:
items = [to_item(obj) for obj in objs if obj.is_valid()]
参考: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
比起第一条评论,后面这条显然更不容易引起新人的抵触情绪,更可能被采纳。这就像那句老话所说:“你表达的方式跟所要表达的内容同样重要,如果不是更重要的话。”
除了保持谦逊和尊重,还有一些其他值得采用的评审沟通小技巧:
我相信也许大部分人在心底都认同:代码评审是一个“对事不对人”的过程,不应该把对代码的批评当成对人的否定。但这和提倡“好好说话”并不冲突。一次让双方满意的沟通,几乎等同于一次更高效的沟通。所以,改善沟通方式就能提升工作效率,你又何乐而不为呢?
代码评审作为保障软件质量的重要手段,是大型软件开发中不可或缺的重要一环。本文总结了我作为评审的参与者,在命名、指引性注释和沟通方式三个方面的一些思考,要点如下:
代码评审是一项涉及多人协作的复杂事务,里面藏着许许多多的学问。质量高的评审,对于提升质量和塑造团队氛围有着不可替代的作用。质量低下的评审,则可能沦落为形式主义,甚至让团队内部滋生矛盾和不满。
而影响评审质量的因素,往往藏在那些不起眼的小细节、小事情中。以上这些关于“小事情”的经验之谈,希望能对你的工作有所启发。
2023-11-03 08:25:46
人物说明:
一天,学徒问大师:“我每天都写很多代码,实现很多需求,为何编码水平却停滞不前?”
大师回答道:“让我看看你在写些什么。”
学徒打开电脑。大师指着屏幕上一行普通的赋值语句,说:“当你有一天意识到,需要在这前面补充 20 行注释时,你就成长了。”说完,大师转身离开了。
在编程时,人们理想中的代码应该是“自说明”的——无需任何注释也很容易理解。
但也有些时候,程序要处理的场景过于复杂,导致我们实际上没法只用代码就将重要信息全部表达出来。此时,如何找到那几行重要的代码,并用大段注释将隐藏的知识有效表达出来,就变得很重要。
作为一名程序员,有能力编写可读性高的代码固然很好,但若是还能信手写出言简意赅的注释与文档,更是锦上添花。
一天,学徒问大师:“我每天都写很多代码,实现很多需求,为何编码水平却停滞不前?”
大师回答道:“让我看看你的代码。”
学徒打开电脑,大师指着屏幕上的一段注释,说:“删掉它。”
学徒照做。沉吟片刻后,学徒大声说道:“我明白了,你是说要关注代码本身的描述性,而不是过度依赖注释来解释代码!”
大师摇摇头,说:“不,我只是想看看你的指法,而据我观察,你甚至不能盲打。”然后,大师转身离开了。
学徒口中的“关注代码的描述性,不要过度依赖注释”诚然很对,但这次,顽皮的大师的关注点实际上在别处:基础技能(盲打)。
在工作中,一名程序员要用到的工具多种多样。我见过一些程序员,他们就像段子中不会盲打的学徒一样,对每日使用的编辑器、IDE 和命令行工具只是略通皮毛,从未花时间系统性地学习过。
但是,深入掌握那些日常工具,以及有意识地增进那些底层技能(比如说打字速度),实际上会对工作效率带来意想不到的丰厚回报。
一天,学徒问大师:“我该如何提升自己的微服务架构能力?”
大师回复道:“让我看看你的代码。”
学徒打开电脑,大师看到项目的 utils 目录,其代码规模数倍于其他功能模块的总和。大师说道:“如果你不懂如何组织模块,那么你实际上也无法‘架构’任何其他东西。”
然后,大师转身离开了。
设计一个大的单体项目,与设计由一堆微服务构成的分布式系统之间有许多相通之处,二者遵循一些类似的指导原则。
比方说“单一职责原则”,该原则所回答的问题是:”应该如何设计项目的模块(或微服务),才能让我们在开发功能时,不牵扯太多无关的模块(或微服务)?“它对于两种架构风格同样有效。
“模块化”是软件开发中最重要的指导思想之一,无关架构模式。
一天,学徒问大师:“我将 10 行 Python 代码用推导式优化成了 1 行,新代码非常漂亮,为何提的 PR(代码合并请求)却被拒绝了?”
大师说:“你的 PR 是我拒绝的。”
见学徒有些吃惊,大师又补充道:“我一个月前写了那 10 行代码。”
学徒的脸有些红,不过仍不想放弃自己的 PR,于是他争辩道:“但是,就在我改动的函数旁边有个类似的函数,那里面有许多更复杂的单行推导式代码,为什么把它们合并进来?”
“哦,那是我 10 年前写的代码。”大师答道。
“什么样的代码是好代码?”
对于这个问题,随着“编码工龄”的增长,我们的答案会发生天翻地覆的变化。
段子里的大师,十年前醉心于用最少的代码表现最复杂的逻辑。十年后,却更倾向于写那些平平无奇的简单代码。
对于代码来说,“可读性”永远是第一位。将诸多逻辑压缩在一行代码中,是一种有趣的思维训练(容易让作者自我感觉良好),但常常以损害可读性为代价。
在编程时,我们需要在“华丽的代码”和“朴实的代码”间找到一个平衡点,而据我观察,随着经验的增长,那个平衡点会持续向着“朴实”那一端移动。
此外,我还有一句话想对学徒说:“既然没 bug,咱不如就别动那些代码了呗?”