JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
Reference · The Glossary

Every word, made clear.

A plain-language glossary for anyone getting started in code. Every term gets one clear, friendly definition — no jargon to explain the jargon — in English, Simplified and Traditional Chinese. Look one up the moment it trips you.

92
terms · and growing
9
topics
3
languages · EN / 简 / 繁
2

双因素验证(2FA)在你的密码之外,再要求第二重身份证明——通常是手机上的一串短验证码。道理很简单:哪怕小偷偷走了你的密码,也照样进不来,因为他没有第二样东西。它就像门锁之上再加的那道防盗插销。

这「两个因素」是故意选成两类不同的东西:你「知道」的东西(你的密码)和你「拥有」的东西(你的手机,或像 Google Authenticator 这类应用生成的验证码)。泄露的密码只是「知道」这一类——它并不会把你那部实体手机送到攻击者手里。

你最常见到它的形式,是每 30 秒刷新一次的六位数验证码,或是一条点一下就批准的通知。对你来说只是稍微多一点麻烦,对攻击者来说却是大得多的工作量——所以为一个重要账户开启它,是你能做的最划算的一件事。

又称2-step verificationmfamulti-factor authentication
A

ADR(架构决策记录)是一份简短的文档,用来记录一个重要的技术决策,以及同样重要的——「为什么」这么决定。几个月后,当有人问「我们当初到底为什么要这样做?」,答案早已白纸黑字写在那里。

每一份 ADR 都很短,并遵循同样的结构:背景(迫使你做选择的处境)、决策(我们选了什么)、理由(为什么)、备选方案(我们考虑过的其他做法),以及后果(我们因此得到什么、又要承担什么)。

你把它们作为纯 markdown 文件放在项目里,按顺序编号(0001、0002……),于是团队的思考过程会沉淀成一段可读的历史,而不是消失在某个人的记忆里。

又称architecture decision recorddecision logdecision record

算法(algorithm)是一份精确的、一步接一步的「菜谱」,用来解决某个问题或得到某个结果。在你的代码动手做任何事之前,背后总有一个方案——一连串确切的步骤,把你手上的输入变成你想要的答案。算法就是这个方案;代码只是把它写下来的其中一种方式。

有个好办法去体会它:做菜的食谱就是一种算法。拿这些食材,按这个顺序做这几步,你就能稳稳地做出那道菜。给朋友指路也是。让它成为「算法」而不只是个模糊念头的,是那份精确——没有哪一步留给瞎猜,每一步都清楚到机器也能照着做。

同一个问题可以有许多种算法,而它们并不一样好。给一串名字排序有十几种做法;有的眨眼就完,有的在长列表上慢慢爬。所以挑对算法——够快、够简单、在每种情况下都正确——是这门手艺实打实的一部分,和敲代码本身是两回事。

又称proceduremethod

API(应用程序接口)是两个程序之间约定好的「对话方式」。它是一份契约:「你可以提出这些请求,而我会准确地回给你这些东西。」你不需要知道对方程序内部怎么运作——你只要知道该问什么,以及答案会长成什么样子。

最经典的比喻是餐厅菜单。你不会冲进厨房自己炒菜;你看菜单、指着第 4 道,然后一盘菜就端出来了。菜单就是 API:一份固定的、你能点的东西清单,把背后所有的切菜、油炸都藏了起来。厨房就算把自己彻底重新布置一遍,只要菜单不变,你点的菜照样上得来。

这就是为什么 API 无处不在。当一个天气应用告诉你明天的天气时,它并没有自己去测量天空——它向某个天气服务的 API 发出请求,拿回了一个整整齐齐的答案。API 让程序互相借用彼此的本事,却从不暴露各自乱糟糟的内部。

又称interfacerest apiweb apiapplication programming interface

数组(array)是一串有序排列的值,被你装在同一个变量(variable)里。与其为三个名字开三个分开的变量,你把这三个全放进一个数组,拎着它们一起走——就像一排编了号的储物柜,每个格子放一样东西,全在同一个名字底下。

你靠位置去取数组里的东西,这个位置叫「下标」(index)。每个新手都会被绊一下的地方:计数从 0 开始,不是从 1。所以第一项在下标 0,第二项在下标 1,依此类推。当你第一次要「第 1 项」却拿到第二样东西时,你这辈子都会记住这件事。

正因为这些项是按顺序排好的,数组天生适合任何「序列」性质的东西——待办清单、一周七天、搜索结果。而它和循环(loop)是绝配:你写一小段代码,让它沿着整个数组走一遍,轮流对每一项做同样的事。

又称listvector

异步(async)是一种做法:先去启动某件慢吞吞的事——比如从网络取数据、读一个大文件——然后不傻等,继续去做别的活。等那件慢事终于办完了,代码再绕回来处理它的结果。

可以想象点咖啡:你不会僵在柜台前一直等到做好。你拿个取餐器,先找位子坐下,等饮料做好取餐器就会响。这个取餐器就是「承诺(promise)」——一个还没到货的答案的占位符。而「等待(await)」,不过是你主动选择:先坐下来等这一声响,再去做下一件事。

这就是为什么初学者很早就会遇到 promise 和 await:一次网络请求可能要花半秒,而半秒对计算机来说是漫长的永恒。异步让程序保持灵活、随叫随应,而不是干等时整个卡死。

又称asynchronouspromiseasync/awaitnon-blockingcallback

身份验证(authentication)是系统用来证明「你是谁」的过程。它就是登录那一步:你出示密码、一次性验证码、指纹,服务器据此确信你确实是你声称的那个人——就像门口的保安在查你的证件。

关键在于,它只回答「你是谁?」,而不回答「你能做什么?」。后一个问题属于授权(authorization),而人们老是把这两者搞混。一个好记的小窍门:身份验证是那道门,授权是你进门后获准进入的那些房间。

一旦通过验证,服务器通常会发给你一个令牌(token)或设置一个 cookie,这样你就不必在每一个请求上都重新输入密码——这是你在整个会话期间随身携带的「身份凭证」。

又称authnloginsign in

授权(authorization)决定的是:在系统已经知道你是谁之后,你「被允许做什么」。你登录了——没问题——但你能删掉这个文件吗?能看别人的工资吗?能打开管理后台吗?授权就是为每一个这种问题把关的那道关卡。

它总是发生在身份验证(authentication)之后。系统先确认你的身份(authn),再检查你的权限(authz)。知道你是谁,并不能说明你能碰什么——一个登录的访客和一个登录的管理员同样都「通过了验证」,但「被授予的权限」天差地别。

当你撞上一堵过不去的墙,那通常就是授权失败:服务器很清楚你是谁,只是不让你做这件事。在网络上,它常常表现为一个 403 Forbidden(禁止访问)。

又称authzpermissionsaccess control
B

后端(backend)是一个应用里跑在服务器上、看不见的那一部分。它保管数据、执行规则、干真正的活儿——核对你的密码、保存你的订单、把你的账单加总。你从来不会直接看到它;你看到的,永远只是它送回来的那些答案。

想想一家餐厅。你坐着的那间大堂是前端(frontend)——菜单、桌子、和气的服务员。后端则是厨房和后台办公室:菜真正在那里被烹饪、库存在那里被清点、收银机在那里把钱算清楚。客人从不会晃到后头去,也不需要去——他们只管享用端上来的那顿饭。

这两半通过 API(接口)对话:前端发出请求(「帮这个人登录」「把他的订单给我」),后端就完成那些繁重的工作再回复。它也是放敏感东西的地方——数据库、密码、业务逻辑——之所以都留在服务器上,正是为了让它们落不到用户手里。

又称server-sideback endserver

样板代码(boilerplate)是那种每次都几乎照搬、写法大同小异的固定设置代码——是你在程序真正有趣的部分开始之前,必须先铺好的那几行「开场白」。

每个新项目都得先通好管线:导入库、配置好设置、把基础部件接起来。这些都不是你想做的那个巧妙创意——只是它周围那套一模一样的脚手架,从这个项目到那个项目几乎不变。这个词来自旧时印刷厂:现成的整块文字被铸在钢板(boiler plate)上,在许多报纸之间原样反复使用。

正因为它如此可预测,各种工具乐于替你生成它——一句「create-app」命令或一个起步模板,就把样板代码递到你手上,让你直接跳到好玩的部分。你照抄它而没怎么细读,这很正常;只要明白:它是你脚下的地板,而不是你专程来布置的那个房间。

又称boilerplate codescaffoldingstarter codetemplate

分支(branch)是一条平行的工作线——你自己的一份项目私有副本,让你能在上面做新东西,而不碰别人正依赖着的那一份。共享的、正式的版本通常待在一个叫 main 的分支上;你从它身上拉出自己的分支,尽情捣鼓,等做好了再把成果并回去。

它比听起来要轻巧得多。分支并不是一个塞满了拷贝文件的文件夹——它只是一个可移动的指针,指向某个提交(commit)。创建一个分支是瞬间完成的,在分支之间切换就像把项目原地翻到另一个版本。这种「廉价」是故意设计的:你本就该频繁地开分支,哪怕只是一个五分钟的小试验。

日常的节奏是「一个任务一个分支」。修个 bug?开分支。想试个有风险的点子?开分支。成了,就把它合并进去;没成,就直接把这个分支扔掉,main 始终一无所知。在你决定分享之前,你捣的乱都只是你自己的事。

又称git branchfeature branchmainmaster

浏览器(browser)是你用来访问网站的那个程序——Chrome、Safari、Firefox、Edge。它的活儿是:从远处某台服务器把一个网页取回来,再把它画到你的屏幕上,变成你能读、能点的东西。你在「网上」做的一切,其实都是经由浏览器发生的。

在底层,它运行着网站发给它的三样东西。HTML 是页面的结构和文字;CSS 是样式——颜色、字体、排版;JavaScript 则是让页面在你输入或点击时作出反应的行为。浏览器把这三样拿过来,拼装成你看到的页面,并让它活起来。从这个意义上说,现代浏览器与其说是个「查看器」,不如说是一台专门运行别人程序的小机器。

它还替你打理那些繁琐的底层杂事:查找地址、打开安全连接、下载图片、让你保持登录状态。你输入「去这里」,浏览器就悄悄地把取回、解码、绘制都做了——于是整个网就感觉像是一个顺滑的地方,而不是成千上万台各自为政的服务器。

又称web browserChromeSafariFirefox

打包工具(bundler)会把你那一大堆源文件——JavaScript、CSS、图片,以及它们引入的各种库——拼接成几个又小又优化的文件,让浏览器能快速下载并运行。你把代码拆在几十个清爽的小文件里写,打包工具则把它们打包好、准备上路。

它一路上还会顺手收拾:删掉没用到的代码、压缩文件体积、把较新的语法翻译成老浏览器也能看懂的写法。结果是下载次数大大减少,页面加载更快。

如今常见的有 Vite、webpack 和 esbuild。你多半是通过一个「构建(build)」步骤接触到打包工具——它把你易读的项目,变成真正发布出去的那份精简包。

又称vitewebpackesbuildrollupbuild tool
C

缓存(cache)是把算好的结果存放在又近又快的地方,这样就不必每次都重做那些慢吞吞的工作。第一次开销很大——要从远方的服务器取数据,或跑一遍繁重的计算——但你把答案存了下来,之后每次直接拿现成的副本就行。

可以把它想成:把每天都用的杯子放在台面上,而不是每天早上都爬上柜子顶层去拿。浏览器会缓存图片,于是你第二次访问同一个页面时它瞬间就加载好了;数据库会缓存常用的查询,省得反复重算。

麻烦之处在于「过期」:缓存里的副本可能跟真实数据对不上了。决定什么时候该把旧副本丢掉,就是所谓的「缓存失效(cache invalidation)」——人们半开玩笑地说,它是计算机科学里两大难题之一。

又称cachingmemoizationcdnbrowser cache

CI/CD 就像一个不知疲倦的机器人:每当你推送(push)一次改动,它就自动帮你测试并发布代码。你不必再去记着「跑一遍测试、把文件拷到服务器」,机器会每一次都用同样细致的方式替你完成。

CI——持续集成(Continuous Integration)——是「测试」的那一半。每当有人推送新代码,CI 就构建项目并运行测试,于是错误在几分钟内就会被抓出来,而不是拖到下周。它像那位在别人看到之前先帮你复查作业的朋友。

CD——持续交付(Continuous Delivery)——是「发布」的那一半。测试一通过,CD 就把代码打包并部署出去,让最新的可用版本自动送到用户手里。两者合起来就构成一条「流水线」(pipeline):你一推送代码,它就从你的笔记本一路流到生产环境,中间不需要任何让人提心吊胆的手工步骤。

又称cicdcontinuous integrationcontinuous deliverypipelinebuild pipeline

提交(commit)就是把你的改动保存成一个快照,并盖上一句简短的说明,解释你为什么这么改。这是你告诉 Git「把项目就照现在这个样子记下来」的那一刻——一个你随时能回到的存档点。

提交是历史的基本单元。把它们串起来,就得到整个项目的时间线:每一个提交都知道改了什么、是谁改的、什么时候改的,以及作者用自己的话写下的原因。

那句说明比新手以为的更重要。半年之后,一句清楚的「修复空密码时登录崩溃」是给未来自己的礼物;而「改了点东西」或「asdf」帮不了任何人。写清楚「为什么」,而不只是「改了什么」。

又称git commitsnapshotrevision

你写的代码是给人看的,但机器只会执行它自己那套底层指令——所以中间必须有人来「翻译」。翻译大体上有两种方式,而这个区别会影响你如何构建、发布和调试程序。

编译型语言(比如 C 或 Rust)会在程序运行之前,一次性把整份代码全部翻译成一个可以独立运行的机器文件。这是单独的一道「构建(build)」工序——启动前要等一会儿,但成品跑起来往往很快,而且很多错误在编译时就被揪出来了。

解释型语言(比如 Python 或 JavaScript)则是一边运行一边翻译,逐行就地执行。它没有构建这道工序,所以你改完就能立刻重跑——很适合随手折腾——但代价是:错误往往要等真正执行到那一行时才冒出来。

实际上两者的界线挺模糊:许多现代语言会先编译成一种中间的「字节码(bytecode)」,再配上各种巧妙的技巧。但这个心智模型依然管用——是「先把全部翻译完」,还是「走到哪翻到哪」。

又称compilerinterpretercompilationbuild stepruntime

条件语句(conditional)是会做决定的代码。它检查某件事是不是真的,再根据答案选择走哪条路:如果(IF)这是真的,就做这件事——否则(OTHERWISE)就做另一件。程序就是这样做选择的。

它的作用像岔路口。程序走到岔口,问一个是/否的问题——用户登录了吗?购物车是空的吗?——再根据答案往左或往右走。同一段代码,面对不同情况给出不同的结果。

这些问题可以一环扣一环:如果第一个不成立,就检查第二个(else if),要是都不匹配,最后落到一个「兜底」的分支。就这样,几个简单的是/否判断叠加起来,凑出了看似聪明的行为。

又称if statementif/elsebranch

容器(container)是一个轻量、密封的小盒子,把你的应用和它运行所需的一切打包在一起——正确的语言版本、依赖库、各种配置,全都装进去。把这个盒子交给任何一台机器,应用的表现都一模一样,从此告别经典的「在我电脑上明明能跑」的烦恼。

可以把它想象成码头上的标准集装箱:起重机不在乎里面装的是冰箱、香蕉还是汽车零件,它只负责吊起一个标准尺寸的箱子。你的笔记本、同事的电脑、线上的生产服务器,跑的都是同一个容器、同一种方式。

容器比完整的虚拟机更轻,因为它共用宿主机的操作系统,而不必各自再背一套。Docker 正是让容器流行起来的工具,你常会听到「起一个容器」来表示启动它。

又称docker containerimagecontainerized appdocker

Cookie 是网站请你的浏览器替它保管的一张小纸条——而且每次你再访问同一个网站时,浏览器都会自动把它递回去。网站就是靠它记住你的。HTTP 本身很健忘:每一次请求到来时,都像你从没来过一样,于是网站塞给你一张小名牌,你的浏览器就悄悄替你别上,留着下次用。

它最经典的用途,就是让你保持登录状态。你登录时,服务器回给你一个 Cookie,里面装着类似会话 id 或令牌(token)的东西。从此你的浏览器会把这个 Cookie 附在每一次请求上,网站一看便知「啊,是你」——不用在每个页面都重新输一遍密码。

说到底,它只是网站设置、由浏览器存下的一小段文本,而且只属于那一个域名。Cookie 不是程序,读不了你的文件;它更像网站递给你的一张寄存牌,你每次回来时礼貌地再亮一下而已。

又称http cookiebrowser cookiesession cookieset-cookie

CORS(跨域资源共享)是浏览器的一条规矩:它会拦住一个网页,不让它悄悄去调用另一个网站的 API——除非那个网站明确说过「可以,我允许你」。你的页面来自一个「源」(origin,比如 myapp.com),而 API 住在另一个源(api.bank.com);默认情况下,浏览器会把这两者之间的门「砰」地关上。CORS 就是把这扇门重新打开的那次礼貌握手。

它的存在是有道理的。要是没有它,你随手点开的任何一个可疑网站,都能借着你浏览器里已经存着的登录 cookie,朝你的银行、你的邮箱、你的一切发请求。所以浏览器默认就是「一个页面只能自由地跟自己的源对话」,而服务器必须主动选择放行——回送一个请求头(Access-Control-Allow-Origin),点名谁是受欢迎的。

下面这点是每个新手都会栽的坑:所谓「CORS 错误」并不是你代码里的 bug,其实也不算服务器报的错——它是浏览器拒绝把一个服务器其实已经发回来的响应交给你,因为服务器忘了带上那个许可请求头。修法在 API 服务器那边(加上那个头),而不在你的前端。明白了这一点,控制台里那一串红字就不再吓人了。

又称cross-origin resource sharingcors errorcross-originsame-origin policyaccess-control-allow-origin

中央处理器(CPU)是真正在做计算的那块芯片——机器的大脑。你运行的每个程序里的每一条指令,从把两个数相加到画出一个像素,最终都在这里被执行,一次一小步。让它显得神奇的,纯粹是速度:一颗现代 CPU 每秒钟要跑数十亿个这样的小步,于是堆积如山的琐碎操作,汇成了流畅的视频、飞快的页面加载、以及你一动它就立刻回应的游戏。

单看 CPU 自己,它其实「头脑简单」得出奇。它基本上就是:取来一条指令、执行它、再取下一条——取、做、重复,永不停歇。你在屏幕上看到的那些聪明本领,是把数十亿个这种笨笨的小步骤叠起来才成就的。程序住在内存(RAM)里;CPU 伸手进那块快速工作台,抓取下一条指令和它要处理的数据。

现代 CPU 里塞着好几个「核心(core)」,每个核心本质上都是一个独立的小脑袋,能在同一时间处理一件不同的事。这就是为什么一颗「四核」芯片能让你的音乐继续放着、下载继续跑着、编辑器还反应灵敏——四个工人而不是一个,各自啃着自己那一堆指令。

又称processorthe chipcore

CRUD 是几乎每个应用都要对数据做的四件基本事:Create(新建)、Read(读取)、Update(更新)、Delete(删除)。记一条笔记、过会儿翻出来看、改掉一个错字、再把它丢掉——这就是全部四件事,你一辈子都在做 CRUD,只是没用过这个缩写。

它到处都是,因为能干净地对应到底层工具。在 SQL 数据库里,这四件事是 INSERT、SELECT、UPDATE、DELETE;在 REST API 的 HTTP 里,通常对应 POST、GET、PUT/PATCH、DELETE。还是同样的四个意思,只是换了种方言来说。

当有人说某个应用「只是个 CRUD」,意思是它的活儿大多是把记录搬进搬出数据库、没什么花哨逻辑——而这正是大多数软件的样子,一点也不丢人。

又称create read update deletecrud operations

CSS(层叠样式表)是给网页「上妆」的语言——颜色、字体、间距、布局都归它管。如果说 HTML 是骨架,那 CSS 就是皮肤和衣服:它接过那副朴素的结构,决定每样东西到底长什么样。

你把 CSS 写成一条条「规则」。每条规则先挑出页面上的某个东西(叫「选择器」),再设定它的属性:把所有标题改成深蓝色、给段落一个舒服的行距、让导航排成一行。改一条规则,所有匹配的元素就一起跟着变。

「层叠」是它聪明的地方:当好几条规则都作用在同一个元素上时,CSS 有一套清楚的优先级来决定谁说了算。正因如此,整个网站才能保持一致的外观,同时又允许你在需要时只覆盖某一个按钮、某一个页面。

又称cascading style sheetsstylesstylesheet
D

守护进程(daemon)是一种静静在后台运行、随时待命的程序——它没有窗口,你几乎看不到它。它就安静地待在那里,准备好了:监听登录、收发邮件、按时执行任务。

认出它有个小技巧:名字结尾常带一个字母「d」。sshd 负责远程登录,cron 跑定时任务,nginx 提供网页,dockerd 运行你的容器。

它读作「DEE-mun」(音近「迪门」)。这个词借自古希腊语,原指一位在暗处默默替你忙碌的「守护神灵」——用来称呼一段始终安静待命的软件,意外地贴切又浪漫。

又称servicebackground processsshdcronnginxdockerd
另见进程

数据类型(data type)说的是一个值到底属于哪一「种」。它是文本?数字?一个简单的真/假?还是一串东西的列表?类型告诉计算机——也告诉你——这个值究竟是什么,而这又决定了你能拿它做什么。

常见的几种到处都是:字符串(string)是像 "hello" 这样的文本,数字(number)是像 42 或 3.14 这样的东西,布尔值(boolean)就只有 true 或 false,数组(array)则是一串有顺序的列表。每一种都自带一套专属的「工具箱」。

类型之所以重要,是因为它定下了规则。两个数字相加,得到它们的和;两个字符串「相加」,则是首尾拼接成一段。想用一个词去乘以另一个词,程序根本做不到——这正是类型在默默替你把关。

又称typedatatype

数据库(database)是一个有条理地存放应用数据的地方,让你能可靠地保存、查找、修改数据——哪怕成千上万人同时在用你的应用也不出错。可以把它想成一个永远不会丢页、还能瞬间翻到任意一页的文件柜。

你当然可以把所有东西一股脑塞进一个文本文件,但只要两个人同时改动,或者你想问「上周哪些订单发货了?」,它立刻就垮了。数据库替你处理这些棘手的部分:快速查找、保持数据一致、写到一半断电也不会丢东西。

最常见的一种是关系型(relational):数据存放在由行和列组成的表里(就像一张很聪明的电子表格),你用 SQL 来和它对话。常见的有 PostgreSQL、MySQL 和 SQLite。

又称dbdatastorepostgresmysqlsqlite

调试器(debugger)是一种能让你正在运行的程序「半路定格」、好让你钻进去查看内部的工具。与其瞎猜哪里出了错,你不如在选定的某一行把代码暂停下来,然后一步一步往前走——看清每一刻每个变量里到底装着什么。它回答的是真正要紧的问题:不是「哪里坏了」,而是「为什么坏」。

诀窍在于断点(breakpoint)——你在某一行上放的一个标记,意思是「到这里停下」。程序一跑到这里,一切都停住,控制权交到你手上。你可以查看每一个值、「步入」某个函数去跟进更深一层,或者让它继续跑到下一个断点。这就像把电影一帧一帧地暂停,好抓住魔术究竟是在哪一刻变出来的。

在调试器出现以前,大家靠在代码里到处撒打印语句来偷看程序的状态——直到今天,很多人随手查个小问题时还是这么干。调试器则是它的成熟版:不用改你的代码,也不用重跑十几遍。你把时间按停,四下看看,bug 往往就自己露馅了。

又称breakpointstep debuggergdbstep through

依赖(dependency)就是你的项目要正常运转所倚靠的、别人写好的代码。如今几乎没什么是从零造起的:你想解析一个日期、画一张图表、或连一个数据库,与其自己从头写,不如拿来一个已经做好这件事的库——于是你的项目就「依赖」上它了。

你不会把那段代码一行行手动粘进项目里。你只要在一个小小的清单文件里写下它的名字(以及你能接受的版本),你的包管理器(package manager)就会读这份清单,替你逐个装好,连它们各自所需的东西也一并装上。

这是个美妙的捷径——你站在了别人早已堆好的一座工作大山上。代价是你也把他们的代码揽了过来:当一个依赖有 bug、出了更新、或被作者弃坑,那从此也有你一份心要操。尽管放心倚靠依赖,但要清楚自己倚靠的到底是什么。

又称dependencieslibrarypackagethird-party codedep

部署(deployment)就是把在你自己电脑上跑的代码,搬到一台对外的服务器上,让真实的用户能够实实在在地访问到它。在这之前,你的应用还是私密的——只是一场排练。部署,就是把门打开。开发者干脆把它叫「上线」或「发布」(ship):你把代码送出去,送进真实世界。

它填补的,正是「在我电脑上能跑」和「对所有人都能跑」之间的那道缺口。一次部署,会把你写完的代码复制到一台始终开着的服务器上,装好它所需要的东西,再把你的网址指向它。一旦完成,地球另一端的某个人就能打开你的页面了。

过去,部署是一场紧张、全靠手动、常常熬到深夜的仪式。如今它大多自动化了:你推送代码,一条自动化流水线替你构建、测试,只要一路绿灯,它就自己部署上线。做得好的话,发布一次改动会变成一件安静的日常小事——很多团队一天部署好几次,没有人需要屏住呼吸。

又称deployshippingreleasego livepush to production

当某样东西被标记为「已弃用(deprecated)」,意思是官方正式不建议你再用它,它已经在退场的路上了。眼下它还能正常工作——今天不会坏——但维护者其实在明白地告诉你:别再用这个了,已经有更好的替代品,而且总有一天它会被彻底移除。

可以把它理解成功能被请出门前那句客气的「最后通知」。你通常会以「弃用警告(deprecation warning)」的形式遇到它,出现在控制台或文档里,而且往往会直接告诉你该改用什么。

明智的做法是:趁它还只是个警告时就动手处理。已弃用的代码是在「借来的时间」里苟活——现在就按自己的节奏迁移到替代方案,别等到将来某次升级真把它删掉时手忙脚乱。

又称deprecationdeprecation warningsunsetlegacy

DNS(域名系统)是互联网的电话簿。你输入的是一个好记的名字,比如 example.com,但电脑之间其实并不靠名字互相找——它们靠数字,靠一个像 93.184.216.34 这样的 IP 地址。DNS 就是那个系统:每一次,在你的浏览器能连上任何东西之前,它都会悄悄把名字查成数字、再把数字交回来。

它之所以存在,是因为没人愿意为了访问几个常去的网站去背一堆数字。名字是给人看的,数字是给机器用的。所以每当你点一个链接、加载一个 API,看不见的第一步永远是一次 DNS 查询:「这个名字对应的 IP 是多少?」只有等这个答案回来,真正的请求才发得出去。它快得、自动得让你都忘了它的存在——直到它出问题。

而这正是值得记住的实用一点:相当多「网站打不开了」的时刻,其实都是 DNS 问题——名字指向了错误的数字,或者一个刚注册的域名,它的记录还没传遍全世界的服务器(大家把这叫「等 DNS 生效 / 等它传播」)。当某个东西连到了错误的地方、或者干脆连不上时,电话簿是第一个该翻的地方。

又称domain name systemname resolutionnameservera recorddns lookup
E

端点(endpoint)是 API 对外开放的一个具体地址,对应一个具体的动作。如果说 API 是整张菜单,那端点就是上面的某一行:一个像 /users 这样的 URL,你可以「访问」它来完成某件事。往那个地址发一个请求,API 就把那一件事做好、然后回复你。

每个端点通常由一个路径加上一个「动词」组成,动词说明你想干什么:GET /users 意思是「把用户列表给我」,POST /users 意思是「新建一个用户」,GET /users/42 意思是「让我看看 42 号用户」。同一个地址,换个动词,就是不同的动作——就像对着同一个柜台,要么让它给你看一份记录,要么让它帮你新建一份。

所以 API 说到底就是一组整整齐齐的端点,每一个都是通往某项具体能力的一扇有名字的小门。学一个 API,大半就是在学它的端点清单:有哪些门,每扇门各自做什么。

又称routeurlapi routeresource path

环境变量(environment variable)是一个有名字的值,它住在你的代码之外、住在程序运行的「环境」里——程序启动时会去读它。你可以把它想成贴给程序的一张便利贴:PORT=3000、DATABASE_URL=…、API_KEY=…。代码只说「PORT 设成什么,我就在那个端口监听」,从不把数字写死在里面。

这正是同一份代码在不同地方表现不同的奥秘。在你笔记本上,DATABASE_URL 指向一个小小的测试数据库;上了生产环境,它指向真正的那个——而代码一行都不用改。你只需要在每个环境里把变量设成不同的值。

它也是放秘密的地方。API 密钥、密码就该放进环境变量,正是为了让它们待在源代码之外——如果把秘密写死进一个你提交了的文件,它就会永远留在 git 历史里,谁拿到仓库都能看见。放在环境里,它们就永远碰不到代码库。

又称env varenvironmentprocess.envgetenv$path

退出码(exit code)是一个程序结束时交还回来的那一个数字——它用来说「这次顺利完成」或者「出了点问题」。按照一条几乎人人都遵守的约定,0 表示成功,其他任何值(1、2、127……)都表示某种失败。这是程序临走前的最后一句话。

你很少见到它,因为它不会被打印出来——而是悄悄留给下一个程序去读。在 shell 里,你可以在某条命令刚跑完后用 $? 偷看一眼;或者干脆把命令串起来:「build && deploy」只有在 build 以 0 退出时才会运行 deploy,因为 && 的意思就是「而且只有在它成功时」。

正是这个不起眼的数字,让自动化得以运转。当 CI 说你的构建「失败了」,真正发生的其实是某一步以非零退出码结束,而流水线注意到了。计算机读不懂你的测试输出,但它永远读得懂一个 0。

又称exit statusreturn codestatus codeerrorlevel$?
F

文件权限(file permissions)决定了谁可以对一个文件做什么:读它、改它,还是运行它。在类 Unix 系统(Linux、macOS)上,每个文件都带着三个小开关——r(读)、w(写)、x(执行)——而且会分别为三类人设置:文件所有者、所有者所在的组,以及其他所有人。

ls -l 里那串看着费解的「rwxr-xr-x」说的就是这件事:所有者可读、可写、可运行,而组和其他人只能读和运行。同样的东西也能写成一个数字,比如 755,每一位数字其实就是 r=4 + w=2 + x=1 相加而来。你用 chmod 来修改它们。

最容易让新手栽跟头的是 x。一个脚本并不会因为它存在就被允许运行——它需要执行位(execute bit)。这就是为什么你常常要先敲 chmod +x deploy.sh,之后 ./deploy.sh 才跑得起来;也是为什么少了那个 x,系统就冷冷地丢给你一句「Permission denied」。

又称chmodrwx755permission deniedexecutable bit

文件系统(file system)是操作系统把磁盘上的一切,整理成一个个有名字、归在文件夹里的文件,并能在日后重新找到它们的方式。一块光秃秃的磁盘,其实只是一片浩瀚的、编了号的数据块汪洋;文件系统就是架在上面的那位图书管理员,守着一份目录,写明「你叫它 beach.jpg 的那张照片,住在这几个数据块里,在 Pictures 文件夹中」。没有它,你的文件虽以原始比特的形式存在,却没有任何东西能说清一个文件到哪儿结束、下一个从哪儿开始。

你靠路径(path)在里面穿行——一串文件夹名,精确地钉住一个文件待在哪儿,比如 /Users/jo/notes/todo.txt 或者 C:\Users\Jo\todo.txt。从左往右读:从顶端出发,一层层走进每个文件夹,最后那个名字就是文件本身。这串字符是个地址,顺着它走,总能落到这棵树里某一个精确的位置。

文件夹层层嵌套成一棵树,从唯一的顶端——叫做根(root)——向下分叉。正是这种形状,让同一个 notes.txt 能安然地待在两个不同的文件夹里:它们的完整路径不同,系统就绝不会把它们搞混。文件系统还记着每个文件的大小、时间戳、以及谁有权打开它——正是这些记账的功夫,把一堆原始存储变成了你真能浏览翻找的东西。

又称filesystemdirectory treefs

外键(foreign key)是一张表里的一个列,它指向另一张表的主键(primary key)——这就是数据库把行与行连接起来的方式。比如一笔订单里存着下单顾客的 id,这个 customer_id 就是外键,是从订单射向那位顾客的一支小箭头。

正是这个小手法,把一张张独立的表织成了一张相连的网。你不必在每笔订单上把顾客的全名和地址重抄一遍——只需把他的 id 存一次,需要时再顺着取。顺着箭头,你就能从一笔订单跳到它的顾客,或者把属于同一位顾客的所有订单都收集起来。

数据库可以替你守护这些链接:它会拒绝让一笔订单引用一个并不存在的顾客,也能阻止你删掉一个名下还挂着订单的顾客。这道护栏叫做参照完整性(referential integrity)——它让你的数据不至于指向虚空。

又称fkreferencerelationship

复刻(fork)是你为别人的仓库(repository)做的一份完整副本,在托管平台上(GitHub、GitLab)一键完成。它挂在你自己的账号下,所以你想怎么改就怎么改,完全不用经过原作者同意。

开源协作就是这么运转的:你把一个自己没有写入权限的项目复刻过来,把改动推到你这份副本里,再发起一个拉取请求(pull request),请原项目的维护者把你的成果合并回去。他们会审查;觉得不错,它就成了真正项目的一部分。

别把 fork 和克隆(clone)或分支(branch)搞混。分支是同一个仓库里的一条工作线;克隆是拉到你自己电脑上的副本;而 fork 是服务器上一个独立的仓库,署着你的名字——是你向一个不属于你的项目提建议的「发射台」。

又称fork a repoforkingserver-side copy

框架(framework)是搭一个应用用的现成「脚手架」——一套已经把常见部件摆好的结构,让你不必每次都从一张白纸开始。像 React、Django、Rails 这样的工具,会递给你一个合理的布局、一堆「无聊但必要」问题的现成解法,外加一个个清楚的位置,让你把真正属于你这个应用的独特部分插进去。

有一点最能定义框架:是它来调用你的代码,而不是反过来。用一个普通的库(library)时,主动权在你手上——需要哪个工具,你就去拿哪个。用框架时,则是框架掌着大局,在恰当的时机来调用你的函数(「刚来了一个请求——喏,你来处理」)。这有时叫「控制反转」,也正是这桩交易的全部:你交出一部分对结构的自由,换来的是省掉一大堆繁琐的底层杂活。

回报是「快」和「共同的地基」。路由、表单、页面渲染你都不必重新发明——框架早替你做好了。又因为成千上万人用的是同一个框架,你能从一口很深的井里打水:教程、补丁、现成的组件。代价是,你得照框架的方式来做事;学一个框架,有一半是在学它的「主张」。

又称web frameworkReactDjangoRails

前端(frontend)就是一个应用里你真正看得见、摸得着的部分——按钮、文字、布局,以及你点击时会动起来的那些东西。它在你的浏览器里、在你自己的设备上运行,由 HTML、CSS 和 JavaScript 搭成。

它是一对搭档里的一半。前端是店面:橱窗、货架、站在柜台后的店员。后端则是墙后的一切——仓库、收银机、账本——这些可以去看「后端(backend)」那条。前端的任务,就是让墙后这一切用起来既简单又舒服。

因为它跑在访客自己的机器上,前端必须友好又快:它把内容展示出来,收集你输入或点按的东西,再去和后端对话,取回或保存真正的数据。任何你在页面上右键就能查看的东西,都属于前端。

又称front-endclient-sideclient

函数(function)是一台可重复使用的小机器。你喂给它一些输入,它干一件活,然后把结果交还给你。写一次,之后在任何需要这件活的地方按名字调用它——不用再把步骤重写一遍。

想象一台咖啡机:放进水和咖啡豆(输入),它跑完自己那套流程,咖啡就出来了(结果)。你不会每天早上重造一台机器——你只是按一下按钮。

这正是程序保持整洁、易读的办法。与其把同样的十行代码到处复制粘贴,不如把它们包进一个有名字的函数里去调用。在一个地方改好逻辑,每一个调用方都免费跟着改好了。

又称methodproceduresubroutine
G

Git 是一个会记住你代码每一个版本的工具,让你能随时保存进度、回头查看改了什么、出错了也能撤销而不会全盘丢失。可以把它想成给整个项目准备的无限「撤销」历史,而且每次保存都附带一条说明,记录你做了什么。

它是「分布式」的——说白了就是每一份项目副本都很完整:你的笔记本上有全部历史,队友那里有全部历史,服务器上也有全部历史。哪怕断了网,你照样能干活、保存、翻看过去。

有一点要分清:Git 是工具,GitHub 是一个把你的 Git 项目托管到网上的网站。你完全可以整天用 Git 却从不碰 GitHub——两者并不是一回事。

又称version controlvcsgit scm
H

哈希(hash)是从任意一份数据算出来的一个简短、固定长度的「指纹」。无论喂进去的是一个单词还是一整部电影,哈希函数都吐出一串等长的字符——一个整整齐齐的小签名,用来代表那整份东西。同样的输入永远得出同样的哈希,哪怕只改动一个字母,结果也会面目全非。

有两个特性让它特别有用。第一,它是「单向」的:从数据算到哈希一瞬间就好,但你没法把它倒推回去还原出原文——指纹里并不包含那个人本身。第二,它是个可靠的摘要:如果两个文件的哈希相同,它们几乎肯定一模一样,所以你只要比对指纹、而不必逐字节核对,就能确认下载没出错。

同样的把戏也撑起了「快速查找」。哈希表(hash map)会把像「email」这样的键直接算成一个数字,告诉你该去哪个格子里找——不用一行行扫列表,算出哈希直接去取就行。这正是为什么哪怕在一个巨大的集合里,按键查东西也快得像瞬间完成。

又称hashinghash functionchecksumdigestsha256md5

HTML(超文本标记语言)是给网页搭出结构和内容的语言——标题、段落、链接、图片、列表都靠它。可以把它想成页面的「骨架」:它决定页面上有什么、每一块是什么,但还不管这些东西长什么样。

你用「标签」来搭页面——一个个用尖括号包起来、成对出现的小标记。<h1>…</h1> 标记大标题,<p>…</p> 标记段落,<a href="…">…</a> 标记链接。浏览器读懂这些标签,再把它们变成你看到的页面。

光是原始的 HTML 看起来很朴素——白底黑字。这是故意的:HTML 负责骨架,CSS 添上外观,JavaScript 添上行为。三者合在一起,才造出网上的每一个页面。

又称hypertext markup languagemarkuptags

HTTP 是整个网络赖以建立的那一来一回:你的浏览器发出一个请求(「请把这个页面给我」),服务器回一个响应(「给你」)。你打开的每个页面、加载的每张图片、提交的每个表单,本质上都是这样一次小小的「请求—回复」往来。

每个请求都会指明一个方法——用 GET 去取东西、用 POST 去送东西——指向一个 URL,然后带着一个状态码回来(一切顺利是 200,东西不在是 404)。这是一场礼貌而无状态(stateless)的对话:每个请求各自独立,服务器一处理完就把你忘得一干二净。

HTTPS 是一模一样的对话,只是被锁进了一个信封里。那个 S 代表安全(secure):连接经过加密(用 TLS),所以在线路上偷看的人既读不到、也改不了途中传输的内容——这正是地址栏里那把小锁的来由。如今裸的 HTTP 被视为不安全,HTTPS 已经成了理所当然的默认。

又称hypertext transfer protocolhttp requesthttp responsetlsssl

HTTP 状态码(HTTP status code)是服务器在每一个网络响应最前面放的那个三位数字,一眼就告诉你这次请求的结果如何。那几个出名的你早就见过了:200 意思是「好的,给你」,404 意思是「我找不到那个东西」,500 意思是「我这边出错了」。你打开的每一个页面、发出的每一次 API 调用,回来时都盖着这样一个数字。

第一位数字把它们分成五大家族,这也是唯一值得记住的部分。2xx = 成功(成了)。3xx = 重定向(你要的东西在别处)。4xx = 你出错了(请求有问题、找不到、不允许)。5xx = 服务器出错了(不怪你)。所以哪怕一个你从没见过的码——比如 429——你也能一眼读懂:4 字头,说明问题出在「提问的一方」(它的意思是「请求太多了,慢一点」)。

排查问题时,状态码是第一个该看的东西。401 告诉你这是登录问题;404 告诉你地址写错了;500 则告诉你别再盯着自己的代码改了,去看看服务器的日志吧。它就是服务器对「刚才那次怎么样?」给出的一字回答。

又称status coderesponse code200404500http code
I

IDE(集成开发环境,Integrated Development Environment)是一个加了超能力的文本编辑器——在同一个软件里,你就能写代码、运行代码、修代码,全程不用切来切去。它的名字已经把意思讲清楚了:它把程序员原本要分别打交道的各种工具,整合(integrate)进了一个窗口。你最常听到的是 VS Code、IntelliJ 和 Xcode。

大家爱它,是有原因的。你打字时,它会自动补全单词的后半截、提示接下来该写什么,甚至在你还没运行之前就用红线标出错误——像一个真正看得懂代码的拼写检查器。点一个按钮就能运行程序;再点一个就打开调试器(debugger),把程序暂停下来看看里面发生了什么。

你当然也可以用普通的记事本写代码——很多人就是这么起步的。但 IDE 会悄悄帮你抹掉一天里上百个小麻烦:一键跳到某个函数的定义处、一次性把某个名字到处改掉、刚打错字就立刻给你标出来。这就像手写和「身边有个特别细心的助手帮你盯着」之间的差别。

又称code editorVS CodeIntelliJPyCharmXcode

如果一个操作做一次和做十次的结果完全一样,它就是「幂等(idempotent)」的。再按一下电梯按钮,并不会召来十部电梯;按钮已经亮着,再按也改变不了什么。无论你重复多少遍,结果都一样。

拿它和「给我的余额加 1」对比一下——跑五次你就多了五块钱,这显然不是幂等的。但「把我的余额设为 100」就是幂等的:跑一次还是跑五十次,余额都是 100。区别就在于:重复执行是会层层累加效果,还是只会落到同一个答案上。

这正是让「重试」变得安全的关键。网络会断、请求会超时,你常常根本分不清一个请求到底有没有发出去。只要操作是幂等的,你就可以放心地再试一次,不用担心把卡刷两遍、或建出两份副本——重复它毫无副作用。

又称idempotencyidempotencesafe to retryrepeatable
J

JavaScript(简称 JS)是让网页「活起来」的编程语言。HTML 给页面结构、CSS 给页面外观,而 JavaScript 给页面行为——按钮能响应点击、表单能自我检查、信息流能在不刷新整页的情况下加载更多内容,全靠它。

它就在浏览器里、在访客自己的电脑上运行。当你点「赞」、爱心瞬间亮起来,那就是 JavaScript 在当场反应:悄悄地只去取、只去更新变化的那一小块,而不是重新下载一整个新页面。

别被名字骗了,它和 Java 语言其实没什么关系——这点相似纯属 1990 年代的营销乌龙。如今 JavaScript 也能跑在浏览器之外(服务器上、各种工具里),但它的老家,始终是你眼前的这个页面。

又称jsecmascript

JSON 是一种简单的文本格式,用来记录结构化数据——把名字和值一一配对——而且人和程序都能轻松读懂。如果你见过 { "name": "Ada", "age": 36 } 这样的写法,那就是 JSON:用花括号装着一串键(key)和它们对应的值。

它是接口(API)之间的通用语言。当一个程序通过网络向另一个程序要数据时,返回的内容几乎总是 JSON,因为几乎每种编程语言都能毫不费力地读写它。

它的组成部件非常少:带「引号」的文本、数字、true/false、用「方括号」括起的列表,以及用「花括号」括起的对象(object)。整门「语言」就这么多——一下午就能学会,却灵活到几乎能装下任何东西。

又称jsonjavascript object notation.jsonjson payload

JWT 是一种紧凑、自带信息的令牌(token),它写明了「你是谁」,并经过签名,所以服务器可以信任它。把它想成音乐节上那种防伪手环:上面印着你的信息,还有一枚证明确实是工作人员发放的封印。亮一下手环你就被放行了——没人需要再去名单里查你。

巧妙之处正在这里。传统登录意味着服务器要在自己的内存里保存一份会话,每次请求都去核对。而 JWT 把这些事实直接装在令牌内部,服务器只需验证那个签名。不用查数据库——这正是 JWT 在「跨多台服务器保持登录」这件事上大受欢迎的原因。

一个 JWT 由三段用点号隔开的部分组成——header.payload.signature(头部.载荷.签名)——而前两段只是 Base64 编码,并非加密。所以千万别把秘密塞进 JWT;任何拿到它的人都能读出载荷。签名能阻止他们篡改,却挡不住他们查看。

又称json web tokenbearer token
K

内核(kernel)是操作系统的核心——它是唯一直接和硬件对话、并决定谁能使用硬件的那一段软件。任何程序想读文件、想走网络、想用一块内存,都得先向内核申请。

它最重要的工作是「分配」。CPU 只有一个(顶多几个核心),内存也是有限的一堆,可几十个程序都想同时占用它们。内核把时间切成极小的份额飞快地轮流发放,于是一切看起来像在同时运行;它还把每个程序隔开,这样一个崩溃也不会拖垮其他的。

你从不直接运行内核——它垫在一切之下,看不见。你的 shell、浏览器、编辑器,都是在向它提出请求。人们说「Linux」时,严格来讲指的就是这个内核;其余的一切,都是围绕着这块安静的核心聚拢起来的软件。

又称os kernellinux kerneloperating system core
L

代码检查工具(linter)会读你的代码,在你还没运行程序之前,就悄悄标出可能的错误和不规范的写法。它就像一位友好的校对员在你肩头看着,把你正要提交的小毛病一一画出来。

它能揪出没用到的变量、漏掉的分号、写错的名字、忽宽忽窄的缩进等等。有些必须改掉,更多则是温和的提醒,让代码更整洁、更一致——你的同事会为此感谢你。

这个名字来自「lint」,也就是烘干机里积下的那层绒毛。linter 就是帮你把代码里的小绒毛挑出来。大多数编辑器会边打字边运行它,所以你一犯错,问题就立刻标红。

又称eslintpylintruffstatic analysislint

锁定文件(lockfile)是一份自动生成的清单,它把你的项目装进来的每一段代码的确切版本都钉死——不只是你点名要的那些库,连那些库自己所需要的库也一并钉住。它存在的全部意义,就是让「安装」这一步每一次、在每台机器上都得出完全相同的结果。

它填的是这样一个缺口。你在清单文件里通常写得比较宽松,类似「给我 4 点几版的 lodash 都行」。这很方便,但也意味着两个人在不同的日子去安装,可能悄悄装出略有差异的代码。锁定文件则把答案冻结下来:它明确记下你实际装到的是 4.17.21、就是这一个、来自这个确切的源。

锁定文件不用你手写——你的包管理器(package manager)会自动生成并更新它。你要做的,只是把它和代码一起提交进仓库。这样队友、构建服务器、乃至未来的你,只用一条安装命令,拿到的就是一字不差、完全一致的环境,而不是一句含糊的「应该差不多」。

又称lock filepackage-lock.jsonyarn.lockpnpm-lock.yamlcargo.lockpoetry.lock

循环(loop)是一段会重复执行的代码,省得你把同一件事一遍遍手写出来。如果你要向 100 个人问好,你不会打 100 句问候语——你只写一遍,再用一个循环把它跑 100 次。它就是程序员对「再来一次,再来一次,再来一次」这件事的解法。

循环有两种日常的样子。一种是遍历一个集合,对列表里的每一项都做同样的事——当你手上有个数组(array)、想挨个处理每个元素时特别好用。另一种是只要某个条件还成立就一直跑,条件一变假就立刻停下;这个条件就是循环的刹车。

要当心的是忘了停下来。如果条件永远不变假——或者你永远走不到列表的尽头——循环就会一直跑下去,把你的程序卡死。「死循环」是新手最经典的栽跟头,而解法永远一样:确保循环体里有什么东西在把它推向出口。

又称for loopwhile loopiteration
M

合并(merge)就是把一个分支上的改动织进另一个分支,让两条原本分开的工作线汇成一条。你在自己的分支上做了一些工作,合并就是把它接回 main 的那一刻——从此项目的历史里同时容纳了两边的故事。

大多数时候,Git 会安静而准确地替你完成这件事。它看一看两边各自改了什么,再把这两组改动缝在一起——哪怕两个分支都动过同一个文件,只要动的是文件里不同的部分就行。你敲一条命令,两段历史就接上了。

当 Git 拿不准该怎么合并两处改动——因为两个分支改的正好是同一行——它就会停下来,请你来定夺。这就是合并冲突(merge conflict);与其说它是个错误,不如说是 Git 礼貌地拒绝瞎猜。你挑出对的那一份,合并便完成了。

又称git mergemerge branchfast-forward

合并冲突(merge conflict)发生在两个分支以不同的方式改动了文件里的同一行,而 Git 分不清你到底想要哪个版本的时候。它不会乱猜、冒着挑错的风险,而是停下来,把决定权交给你。这不是失败——而是 Git 诚实地承认:有些选择只有人才能做。

Git 会用三行你很快会认得的标记,把冲突直接标在文件里:<<<<<<< 开启你这一侧,======= 把两个版本分开,>>>>>>> 收尾对方那一侧。第一个标记到 ======= 之间是你这个分支的版本;======= 到最后一个标记之间,是对方的版本。

解决它是件需要静下心来的活:打开文件,想清楚最终这几行到底该写成什么——留你的、留对方的,或者把两者揉在一起——然后把那三行标记全部删掉,让剩下的内容读起来就是正常的代码。保存,把文件加入暂存(stage),再完成合并。那些标记必须消失干净;只要还剩一个,就说明你还没真正解决。

又称conflictmerge conflict markersconflict markers
N

NoSQL 是一类数据库的统称,它们不把所有东西都塞进 SQL 所依赖的那种规整的「行与列」表格里。它们不强求你预先把数据塞进一张固定的网格,而是让每一条记录想长什么样就长什么样——当你的数据杂乱、经常变动、或干脆套不进电子表格时,这就很省心。

它有好几种风味。文档型(如 MongoDB)把每条记录存成一团灵活的、类似 JSON 的数据;键值型(如 Redis)就是一本超大的字典——给它一个键,立刻还你一个值,快得飞起;此外还有宽列型和图(graph)数据库,对付别的形状的问题。

代价是实打实的。舍弃了死板的表格,你换来了灵活,往往还更容易横跨多台机器扩展——但也放弃了关系型数据库白送给你的那种严格的一致性和清爽的关联关系。这名字有点误导:应理解为「不只是 SQL」,而非「不许用 SQL」。

又称non-relationaldocument databasekey-value storemongodbredis
O

OAuth 是每一个「用 Google 登录」或「用 GitHub 继续」按钮背后的标准。它的全部意义在于:让一个应用借用另一个应用,却永远看不到你的密码。你并不是把 Google 账号交给那个新应用——你是在请 Google 代表你把它放进来。

最贴切的日常类比是酒店房卡。前台(Google)只查一次你的证件,然后给那个应用一张卡,这张卡只能打开几扇特定的门——比如你的邮箱地址和姓名——别的都打不开。应用永远拿不到你的总钥匙,而你随时可以注销这张卡,根本不用改你真正的密码。

应用拿到的,是一个访问令牌(access token):一张临时、受限的通行证。这就是值得记住的区别——OAuth 关乎的是「授予有限的访问权」,而不是「证明你是谁」。密码始终留在你和你信任的那个服务那里。

又称log in with googlesocial logindelegated authorization

对象(object)是一捆相关的值,靠带名字的「键」绑在一起。数组(array)给每个值一个编号,对象则给每个值一个标签——name、age、email——这样你就能把一整样东西(比如一个人)描述成一个整整齐齐的包裹。

可以把它想成「贴了标签的抽屉」,对照数组那「编了号的一排」。你不是按位置去取(「给我第 0 项」),而是按名字去取(「给我 email」)。这让对象特别适合那些由若干个有意义、各不相同的部分组成、标签比顺序更要紧的东西。

每一对「名字—值」叫做一个属性(property):键是标签,值是标签底下存的东西。对象可以装下任何类型的值,包括别的对象和数组,所以你能把它们嵌套起来,描述出意外地丰富的结构。这种「键—值」的样子,也正是 JSON 在程序之间传输数据时所用的形状。

又称dictionarymaprecordhash

操作系统(OS)是掌管整台机器的总管程序——Windows、macOS、Linux 就是你听过的那几个。它是你一按电源键就最先加载起来的东西,并且自始至终都坐镇指挥:管理硬件,并在自己之上运行其他所有程序。没有它,电脑只是一堆不知道该干什么的硅片。

它最主要的活儿,是当你的应用和底层硬件之间的中间人。当你的编辑器想保存一个文件时,它不会自己去戳硬盘——而是去拜托操作系统,由操作系统来料理那些琐碎脏活。显示一个窗口、播放一段声音、连上网络,都是同样的道理。这让应用开发者只需写一句「保存这个文件」,而不必去摸透上千种各不相同的硬盘的脾气。

它还是那个交通警察。几十个程序同时都想要 CPU、内存和屏幕,操作系统来裁决谁在什么时候拿到什么——它把 CPU 的时间在它们之间切得飞快,快到看上去它们都在一起运行。它也拦着它们别去踩彼此的内存,这样一个程序崩溃,不至于把整台机器一起拖垮。

又称OSWindowsmacOSLinux

ORM 是一种工具,它让你能把数据库里的行当作代码里的普通对象(object)来用,于是你读写数据时不必为每一件小事都手写 SQL。你调用 user.save() 或 User.find(1),ORM 便默默把它翻译成数据库真正想要的 SELECT 和 INSERT 语句。

可以把它想成一个双语中间人。你的代码说的是对象和方法这门话,数据库说的是表和 SQL 那门话;ORM 居中而坐,把一行行的列变成一个你能随手摆弄的整洁对象,等到要保存时,再把你的改动翻回 SQL。

它的好处是少写样板代码,写出的代码读起来像在讲你的业务问题,而不是数据库的水管活。代价是这份便利可能遮住真正发生的事——一个看着无害的循环,背地里也许偷偷发了几百条查询——所以即便 ORM 替你写 SQL,懂得底层的 SQL 仍然划算。

又称object-relational mappingdata mapperprismasqlalchemyactive record
P

包管理器(package manager)是一种工具,负责安装并记录你的项目所依赖的第三方库。可以把它想成「代码的应用商店」:你按名字点一个库,它就帮你把那个库——连同那个库自己所需要的一切——全部下载好,安放进你的项目里。

它还会替你记住选择。一个小小的清单文件会精确记下你要的是哪些库、哪个版本,于是队友(或一台服务器)只用一条命令,就能重现出一模一样的环境。

每个生态都有它自己的那一个:JavaScript 用 npm,Python 用 pip,Rust 用 cargo。名字不同,活儿一样——这样你就再也不用满世界找别人的代码、再一行行手动拷贝了。

又称npmpipcargoyarnpnpmdependency manageraptbrew

端口(port)是计算机上一扇带编号的门,某个服务就守在那儿等访客上门。一台机器只有一个网络地址,却有成千上万扇这样的编号门——所以许多不同的服务可以并排运行,各守一扇门,互不打扰。

有些号码是大家约定俗成的:80 是普通网页流量走的门,443 是走安全的 https,而 3000 常常是你在开发时本地服务器打开的那扇门。

localhost:3000 说的就是这件事:localhost 指「就是这台计算机」,:3000 则是要敲的那扇门。在浏览器里打开它,你就连上了刚刚启动的应用——完全不需要联网。

又称network porttcp portlocalhostport 80port 443port 3000:3000

主键(primary key)是数据库表里的一个列,它的值能唯一地标识每一行——是你随时都能精确指向某一条记录的那个稳定把手。就像学号或订单号,它能毫不含糊地回答「你说的到底是哪一行?」。

两条规则让它与众不同:它必须唯一(任何两行都不能共用同一个值),而且永远不能为空。数据库本身会强制执行这一点,所以你不会一不小心弄出两个都挂着 42 号的顾客。它往往就是数据库替你自动递增、每新增一行就发一个的普通编号。

为什么非要它?因为名字和邮箱会变、还可能多人共用,而主键是你唯一能指望永远代表同一行的东西。当别的表需要引用这一行时,指向的也正是它——是外键(foreign key)紧紧抓住的那个锚点。

又称pkid columnunique identifier

进程(process)就是一个正在运行的程序——不是躺在硬盘上的那个文件,而是它活起来后的一个实例,拥有自己的一块内存、自己的执行进度,还有一个独一无二的编号。你双击打开一个应用,就启动了一个进程;关掉它,进程也就结束了。

你的电脑此刻同时跑着成百上千个进程:浏览器、编辑器、各种后台小帮手,还有系统自己。操作系统在这里就像个杂耍演员——它给每个进程分配 CPU 的运行时间,把它们的内存隔开(这样一个程序崩溃也不会乱涂另一个的数据),运行完了再帮忙收尾。

有意思的是:同一个程序你可以跑两遍,得到两个互不相干的独立进程。这就是为什么你能开两个浏览器窗口、各管各的——它们是同一个程序的两个实例,过着完全独立的小日子。

又称running programtaskpid

拉取请求(pull request,简称 PR)是你说「这是我的改动,能把它们合进项目里吗?」的方式。你在自己的分支上做了工作,PR 就提议把这些改动并入主分支,好让团队在一切定下来之前先看一看。

它本质上是一场对话。队友会逐行审查你的改动、留下评论、提出修改建议,你再推送修复——全都在同一个地方进行。在有人点头之前,什么都不会被合并。它就像你走进共享代码库之前那一下礼貌的敲门。

这个名字来自请求把你的改动「拉取(pull)」进去。在 GitHub 和 GitLab 上它们看起来几乎一模一样,只不过 GitLab 把它叫作「合并请求(merge request)」——同一个意思,换了个叫法。

又称prmerge requestmr
Q

查询(query)是你发给数据库的一次请求——去取一些数据,或去改动它。它就是一个问题或一条指令:「把所有未付款的发票给我看看」,或者「把 42 号订单标记为已发货」。数据库照办,再把结果递回给你。

大多数查询你都用 SQL 来写。读查询会要回若干行(SELECT … WHERE …);写查询则新增、修改或删除它们。无论哪种,一次查询就是一个来回:你问,数据库答,完事。

查询也是性能的生死之地。一个要扫遍百万行大表的查询会很慢;同一个查询,靠索引(index)帮一把,就能快得像瞬间完成。所以当应用变卡时,查询往往是第一个该去查看的地方。

又称db querylookuprequest
R

竞态条件(race condition)是一类 bug:最终结果取决于两件事里哪一件先完成——而你又控制不了它们的先后时机,于是结果每次跑都可能不一样。它十次里有九次都好好的,然后毫无征兆地失败一次,正是最让人抓狂、最难定位的那种 bug。

想象两个人共用一个银行账户,在同一瞬间各取 100 块。两人都查了余额(150),都觉得「够」,都取了款——结果账户透支了。单看任何一个人都没做错;问题在于他们「抢着」对同一个数字下手,谁都还没来得及结束。

只要有两段工作同时进行、又碰了同一样东西,这类 bug 就会潜伏其中——两个线程、两个请求、两次点击都算。修法是确保同一时刻只有一个能动手,或者干脆把设计改成「谁先谁后真的无所谓」。它们之所以难缠,恰恰是因为你一放慢速度想看清楚,它往往就消失了。

又称racetiming bugdata raceconcurrency bug

内存(RAM)是电脑里那块又快又临时的工作台——你此刻打开的每个程序,都把正在用的东西放在这里。当你启动一个应用、打开一个文件或加载一个网页时,数据会从慢吞吞的硬盘被搬进内存,好让 CPU 几乎瞬间就能取用。可以把它想成你正在用的一张办公桌:桌子越大,你能同时摊开、同时招呼的东西就越多。

麻烦之处在于,内存天生就会「遗忘」。它只在通着电的时候才记得住数据——一关机(或者一崩溃),内存里的一切就烟消云散。这正是没保存的工作会丢失的原因:保存,就是把东西从这张转瞬即逝的桌子复制到硬盘上,在那儿它才稳稳留得住。这就是内存(RAM,快但临时)和存储(硬盘,慢但永久)的区别。

当你听到「这台笔记本有 16 GB」时,这个数字说的就是桌子的大小。同时跑太多吃资源的程序,桌子就会被塞满;系统只好把一些东西倒腾回慢硬盘腾地方,于是一切都变得卡顿。内存越大,通常就意味着你能同时进行更多事情,而不必忍受这种拖慢。

又称memorymain memoryworking memory

变基(rebase)就是把你分支上的提交一个个拎起来,重新「放映」到另一个分支的最新版本之上——仿佛你的工作是此刻才开始的,而不是上周。结果是一条干净、笔直的历史线,中间没有任何「合并」的拐弯。

它解决的是一个实实在在的烦恼。在你开发功能的这段时间里,main 已经往前走了。用合并(merge),会把两条线接起来,在历史里留下一个看得见的「分叉又汇合」;而变基则改写你的提交,让它们整整齐齐地坐在 main 的新顶端上,就好像那次分岔从未发生过。最终代码一样,故事却更利落。

有一条规则要刺在胳膊上:永远不要对已经分享出去的提交做变基。变基会改写历史——它给你的提交换上全新的编号(ID)——所以对别人已经拉取(pull)过的工作动手,会把大家的副本全搅乱。在分享之前,对你自己的私有分支变基;如果工作已经公开,那就老老实实用合并。

又称git rebaserebase ontolinear history

递归(recursion)是指一个函数通过「调用它自己」来解决问题——而且每次调用的,都是同一个问题里更小的一块。它不写一个大循环,而是说「先处理一小点,剩下的再交回给我」,一遍又一遍,每次咬掉稍微小一点的一口,直到没什么可做为止。

想象你排在一条长队里,想知道自己排第几。你看不到队首,于是拍拍前面那个人问「你是第几号?」他也不知道,便去问他前面的人,如此一路往前——直到最前面那个人说「我是 1 号。」这个答案再顺着队伍传回来,每个人加上一,最后传到你这儿。

让它不会无限转下去的关键是「基准情形(base case)」:问题最简单、能立刻给出答案、不再调用自己的那个版本。「队首的人是 1 号」就是基准情形。一旦忘了写它,函数就会没完没了地调用自己、最终崩溃——也就是「栈溢出(stack overflow)」。每个递归都需要两样东西:一条让问题变小的路,和一个停下来的地方。

又称recursiverecursive functionself-referencebase case

重构(refactor)是改善代码的结构——让它更清晰、更简单、更好改——但不改变它实际做的事。同样的输入、同样的输出,只是内部变得更整洁了。

这是在收拾厨房,而不是改菜谱。端出来的菜味道一模一样,但现在刀具都在你预期的位置,台面擦干净了,下一个人来做饭不用满屋子翻找。

好的重构是小而持续的:给一个含糊的变量改个名,把一个巨型函数拆成几个清楚的小函数,删掉没人用的代码。正因为行为不该改变,一套扎实的测试就是你的安全网——重构后它们若依然全部通过,你就知道自己没有悄悄弄坏什么。

又称refactoringclean up coderestructure

正则表达式(regex)是一种小巧的「模式语言」,用来描述你要找的文本长什么样——然后批量地查找、匹配或替换它。你不再去搜某一个固定的词,而是描述它的「形状」:「一个数字,后面还跟着数字」,或者「任何看起来像电子邮件的东西」。

它精炼得令人惊喜。模式 \d+ 表示「一个或多个数字」;\w+ 表示「一个单词」;^ 锚定到一行的开头,$ 锚定到结尾。几个符号,就能替你省下一整个下午的手动查找替换。

它也以「天书」著称——一段密密麻麻的正则,看起来就像猫在键盘上走了一趟。诀窍是一小块一小块地拼出来、边写边测,而不是试图一口气读懂一长串。

又称regexpregular expressionpattern matchinggrep

仓库(repository,简称 repo)就是你项目的文件夹,只不过有 Git 在一旁看着它。除了你的文件,它还悄悄保存着项目的全部历史:每一个保存过的版本、谁改了什么、为什么改。

一个仓库可以同时存在于两个地方。一份在你自己机器上(叫「本地」仓库,你实际干活的地方),通常还有一份在 GitHub 这类服务上(叫「远程」仓库,团队在那里共享和备份)。你随手在两者之间同步。

把一个普通文件夹变成仓库只是一次性的动作:运行 git init,从此 Git 就会追踪里面的一切。它创建的那个隐藏的 .git 文件夹就是所有历史的存放处——别去动它,让 Git 自己管。

又称repogit repocodebase

REST 是一种流行又整洁的 Web 接口(API)设计风格——它是一套习惯做法,而不是某个软件。核心思路简单得讨人喜欢:把你的服务提供的一切都当作一个住在某个 URL 上的「资源(resource)」,再用普通的 HTTP 动词去操作它。URL 说的是「对什么」,动词说的是「做什么」。

于是 /users/42 指代某一个用户,而动词决定动作:用 GET 读取、用 POST 创建、用 PUT 更新、用 DELETE 删除。同一个地址,换个动词,意思就变了。一旦你看出这个套路,一个设计得当的 REST 接口读起来就像在句子里认名词和动词——还没翻文档,你都能猜个八九不离十。

当一个接口遵循这些约定时,人们就称它是 RESTful 的。REST 并不是什么铁律,大家也常为其中的细枝末节争论不休,但它的朴素恰恰是精髓:不用学什么特殊协议,用的就是网络早已会说的那套日常 HTTP。

又称restfulrest apirestful api

运行时(runtime)是真正运行你代码的引擎。源文件不过是一堆文本,直到有东西读取它、让它活起来——Node.js 在服务器上运行 JavaScript,浏览器在网页上运行它,JVM 则运行 Java。运行时就是你的程序登台演出的那个舞台。

搞清楚自己面向哪个运行时很重要:同样一段 JavaScript,在 Node.js 里和在浏览器里能做的事并不一样,因为各自提供了不同的环境和内置功能。

「runtime」也指某个时间点。错误可以分成两类:一类在早期就被抓住——发生在「编译时(compile time)」,程序还没运行;另一类则要到「运行时」、程序真正跑起来、某个坏值终于绊住它时,才暴露出来。

又称node.jsjvmexecution environmentat runtime
S

模式(schema)是你数据的蓝图:它写明有哪些表、每张表有哪些字段(列)、每个字段放什么类型的值、以及它们必须遵守的规则。在你存下第一行数据之前,数据就先约定好了自己该长什么样。

比如你有一张 users 表——模式会声明每个用户都有 id(一个数字)、email(文本,而且必须唯一)和 created_at(一个日期)。要是你想存一个没有邮箱的用户,或者在该放数字的地方填了「香蕉」,数据库会客气地拒绝。这道护栏,正是模式在尽职。

好的模式有种安静的力量:它能及早拦住错误,让你的数据值得信赖。日后再改动它(叫「迁移」migration)是件真功夫的活,所以一开始就值得多想一想。

又称data modeltable definitionstructure

语义化版本(semantic versioning,简称 semver)是一套大家公认的版本号写法,把版本号写成三段——主版本.次版本.修订号,比如 2.4.1——每一段都对「改了什么」做出明确承诺。它的妙处在于:光看这个号码,你就能判断一次更新是可以放心拿来,还是很可能把你弄崩。

从左往右读。修订号(PATCH)加一(2.4.1 → 2.4.2)表示一个向后兼容的 bug 修复:安全。次版本(MINOR)加一(2.4.1 → 2.5.0)是加了新功能,但没破坏旧的:依然安全。主版本(MAJOR)加一(2.4.1 → 3.0.0)则是最响亮的那声警告——它在喊「我删掉或改动了某些东西,你的代码可能得跟着改」。

你清单文件里那些小符号,说的也正是这件事。脱字符「^1.2.3」意思是「更新的 1.x 都行,但绝不跳到 2.0」——除了主版本升级,它什么都信。波浪号「~1.2.3」更严格:「只要 1.2 里更新的修订号就好」。靠它们,你能自动收下那些安全的修复,同时绕开那些会咬人的改动。

又称semverversion numbermajor.minor.patchcarettilde

shell(命令行外壳)是负责读取并执行你所输入命令的程序。当你打开终端、看到一个闪烁的提示符时,那个提示符就是 shell 在等你下令——列出这些文件、复制那一个、启动这个服务器。

它之所以叫「外壳」(shell),是因为它是包在操作系统核心外面的那一层:你对 shell 说话,shell 再请内核(kernel)去真正干活。常见的有 bash 和 zsh(现代 Mac 的默认 shell);它们看上去几乎一样,做的也是同一件事。

shell 不只是个「命令执行器」——它本身就是一门小型编程语言。你可以把一连串命令存成脚本,使用变量和循环,还能把一个命令的输出直接管道(pipe)给下一个命令。开发者日常的许多威力,正是从这里来的。

又称command linecommand-line interpreterbashzshterminal prompt

SQL 注入(SQL injection)是一种经典攻击:用户输入的不只是数据,还夹带了额外的数据库命令,而程序一不留神就把它们执行了。它发生在代码把原始的用户输入直接拼进 SQL 查询的时候——于是那段输入不再是一个名字,而摇身变成了一条指令。

想象一个表单让你填用户名,并围绕它拼出一条查询。你填个正常名字,没事。但你要是填进一段精心构造的东西,比如 ' OR '1'='1,这条查询的含义就被悄悄改写了,也许让你能以任何人的身份登录、把整张表拖走,甚至直接删掉。应用本以为这段输入只是普通文字;数据库却把其中一部分当成了命令来读。

修复之道简单得令人愉快,而且绝对有效:永远别把原始输入拼进查询。改用「参数化查询」(也叫预编译语句),把 SQL 和值分两条独立的通道送过去。这样一来,值就只会被当作数据——绝不会被当成命令——这套把戏便彻底失效了。

又称sqli

SQL 是你用来向关系型数据库提问、以及增删改数据的语言。你不必写循环去文件里一点点翻找,只需写一句简短的话描述你想要什么,数据库自会想办法去取。

它读起来几乎像英语:SELECT name FROM users WHERE age > 18 的意思就是「把年龄大于 18 的用户的名字给我」。你最常用到的四个动词是 SELECT(读取)、INSERT(新增)、UPDATE(修改)和 DELETE(删除)。

它有两种念法——逐字母念「ESS-cue-ell」,或者念成「SEE-kwl」(怎么念别人都懂)。几乎每种关系型数据库都说 SQL,所以这门手艺从 PostgreSQL 到 MySQL 再到 SQLite 都通用,只是方言上略有差别。

又称structured query languagesequel

SSH(安全外壳协议,Secure Shell)是登录另一台计算机、像坐在它面前一样运行命令的标准方式。你之所以能从自己的笔记本连上地球另一端机房里的服务器,靠的就是它。

「安全」二字才是关键:你和远程机器之间的一切都是加密的,所以网络上偷听的人读不到你的命令、密码,也读不到返回的数据。它取代的那些老工具是把所有东西以明文发送的——这实在是个糟糕的主意。

大多数人不会每次都敲密码,而是用一对密钥:一把私钥(private key)安静地留在你自己机器上,一把公钥(public key)放到服务器上。两者一对上,就证明确实是你本人——每次连接时,都在不到一秒里悄悄完成。

又称secure shellssh intoremote loginssh key
T

技术债(technical debt)是为了更快上线,你此刻选择走一条又快又糙的捷径,将来要付出的代价——这个捷径以后得靠认真返工来偿还。

和真正的债一样,它有时是笔聪明的交易。为了赶上截止日期借点时间没问题,只要你记得自己借了。危险的是「利息」:你留下的每一个捷径,都会让下一次改动慢一点、险一点,而利息会复利滚动。拖得太久,一个本该一天搞定的功能开始要花一周,因为你不停被自己当初留下的烂摊子绊倒。

不是所有的债都鲁莽——有时先糊弄一下,才是验证某个想法到底行不行的正确做法。成熟的做法是把它说出口(「这是临时的」)、记下来,并预留时间去偿还,趁利息还没把你生吞活剥之前。

又称tech debtcode debtdesign debt

令牌(token)是服务器在你登录后发给你的一串带签名的字符串,之后你在每个请求里带上它,就能证明你是谁——而不用再输一次密码。可以把它想成音乐节入口给你戴的手环:亮一下,就被放行了。

它带有签名,所以服务器一眼就能看出它是真的、还是被人动过手脚。常见的一种是 JWT(JSON Web Token),它内部其实装着一小段可读的内容——你的用户 id、也许还有你的角色,以及一个过期时间——外面再裹上那层防篡改的签名。

你通常把它放在 HTTP 头里发送,像「Authorization: Bearer <token>」这样。因为它代替了你的密码,就得像对待密码一样对待它:别粘到公开聊天里、别提交进仓库,并让它会过期,这样就算被偷走也不会永远有效。

又称access tokenjwtbearer tokenauth token

事务(transaction)把好几处数据库改动捆成一个「要么全成、要么全不成」的单元:要么每一步一起成功,要么一步都不发生。这是数据库用来保证你绝不会卡在半途的办法——那些本该是一个不可分割动作的事,不会做到一半就停住。

最经典的例子是转账。转 100 元意味着从一个账户扣钱、并往另一个账户加钱——这两步必须成对出行。把它们裹进一个事务,万一第二步失败了(或者两步之间断了电),数据库会把第一步也倒回去。钱永远不会「移动到一半」——从一边消失了,却没到另一边。

实际操作中,你先 BEGIN 开启一个事务,做出改动,再 COMMIT 把它们一并锁定生效——或者一旦出岔子,就 ROLLBACK 把整批撤销。在你提交之前,这一切就当还没发生过,别的用户也看不到你做了一半的活儿。

又称db transactioncommit/rollbackatomic operation
U

单元测试(unit test)是一小段代码,它唯一的任务,就是检查你真正的代码里某一小块有没有按预期工作。你喂给它一个已知的输入,说明你期待得到什么答案,一旦现实和期待对不上,测试就会喊出来。这里的「单元」(unit)指的是能单独拿来测的最小的合理单位——通常就是一个函数。

每个单元测试都简单得不行:「我把 2 和 3 相加,我期待得到 5。」单看一个,简直简单到不值得专门去写。它的回报,来自你有几百个这样的测试、并能在几秒内把它们整批跑完。改动一行代码,跑一遍测试,它们立刻告诉你:你是不是悄悄把三个文件之外的某个东西弄坏了。

这才是它真正的礼物——一张安全网。没有测试时,每次改动都是一场小赌博:这会不会弄坏什么?有了测试,你就能放手大胆地改,跑一遍测试套件,然后信任那一排绿色的对勾。它们还是会呼吸的文档:读一个测试,就能清楚看到某段代码本该怎么用。

又称testtest caseassertiontest suite
V

变量(variable)就是一个有名字的盒子,用来装一个值。你给盒子起个名字——比如 score 或 username——往里放点东西,之后随时可以读出里面的内容,或者换成新的。它是几乎每个程序里最基本的building block(积木块)。

名字正是关键所在:与其在代码里到处重复同一个值,不如把它存一次,然后用名字来引用。在一个地方改动盒子里的内容,所有读取它的地方就都会看到新值。

为什么叫「变量」?因为里面的内容是会「变」的——它随时间改变。score 可能从 0 开始,涨到 10,再清零。标签始终不变,盒子里的东西却可以自由变动。

又称varbindingidentifier

漏洞(vulnerability)是软件里的一处缺陷,攻击者可以利用它去做本不该做的事——读取隐私数据、接管账户、把系统搞崩。它就像一栋本来很结实的房子上一扇没锁的窗:没人注意时它无害,一旦被人发现,那一刻就变得危险。

漏洞通常并非有意留下;它们是从无心之失里溜进来的——一个漏掉的检查、一个过时的库、一个谁都没想到要清理的输入。整个安全循环讲的就是:把它们找出来、悄悄上报,然后在攻击者抢先发现之前发布修复(一个「补丁」)。

重大的漏洞会被分配一个公开编号——CVE 号——好让全世界都能追踪它。最可怕的一种叫「零日漏洞(zero-day)」:在制造方都还不知道它存在时,攻击者就已经在利用了,留给防守方准备的时间为零。

又称vulnsecurity holeweakness
W

网络钩子(webhook)是一种「反过来的 API」:与其让你三番五次地问某个服务「有新东西了吗?有新东西了吗?」,不如让这个服务在事情一发生的那一刻主动来叫你。你事先把自己的一个 URL 交给它,它就承诺:每当你关心的那件事发生时,就往那个地址送一条小消息。

想象一下两种取信方式的差别:一种是你每隔五分钟跑去信箱开一次盖、看有没有信;另一种是信来了,邮差直接按你家门铃。第一种(你一遍遍地问)叫「轮询」(polling),很浪费——大多数时候答案都是「没有,啥也没有」。webhook 就是那个门铃:由服务主动来叫你,而且只在真有消息时才叫,于是你能第一时间知道,而你的代码其余时间都可以安心睡觉。

看懂了这个模式,你会发现它无处不在。Stripe 在付款成功时来敲你的 URL;GitHub 在有人推送代码时来敲;聊天应用每来一条新消息就敲一下。你写一个安静等待的小端点(endpoint),外面的世界一有事要告诉你,就来敲它的门。

又称web hookcallback urlhttp callbackreverse apievent notification

WebSocket 是一种在浏览器和服务器之间保持一条「活的、双向」线路常开的办法,让消息一旦发生,就能立刻在两个方向上来回流动。聊天应用、实时比分、多人游戏、「对方正在输入……」——凡是需要服务器不等你开口就主动把新消息推给你的场景,背后都靠它。

要明白它为什么重要,先想想网络平常的工作方式:一来一回的「请求—响应」,就像寄信。浏览器寄出一个问题,服务器寄回一个答案,然后这条线就「断了」,直到浏览器再写一封。服务器没法自己主动开口。要是想用这种方式拿实时更新,你就只能不停地追问——「有新的吗?有新的吗?」——一遍又一遍,又慢又费。

WebSocket 把「寄信」换成了一通「一直没挂断的电话」。只要做一次快速的建立动作(一个把普通连接「升级」过去的「握手」),这条线就保持常开,哪一边想说话随时都能说——不必重新发问,也没有延迟。这条常开的通道正是它的全部意义:它让服务器能在事情一变化的那一刻,就拍拍你的肩。

又称wswssreal-time connection