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