自動化與 Agent
不可逆動作的確認閘門:用顯式旗標,不要用互動式問句
你的保護機制一定會有失效的一天,真正該設計的是它失效時往哪邊倒。讓「什麼都不做」成為預設,讓「執行」需要使用者明確打出一個旗標,別讓程式去猜環境裡有沒有人。
一個核心觀念
保護機制一定會有失效的一天。真正該設計的不是「它會不會失效」,而是「它失效時往哪邊倒」。
大多數人寫確認關卡的思路是「怎麼確保有人看過」,這會讓你去偵測環境:有沒有終端機、是不是互動模式、是不是 CI。而環境訊號會隨作業系統、shell、呼叫方式而變。
正確的思路是:讓「什麼都不做」成為預設值,讓「執行」需要使用者付出一個明確、不可能誤觸的動作。
四條檢查原則
原則 1:用顯式旗標,不要用互動式問句
| 做法 | 依賴什麼 | 風險 |
|---|---|---|
| 程式跳出「確定嗎?(y/N)」 | 「現在有人在鍵盤前」 | 被排程、被腳本呼叫時,標準輸入可能接到檔案或管線,讀到現成的 y 就往下走 |
要求打出 --send --yes |
「使用者有沒有真的把那串字打出來」 | 沒打就拒絕,不問、不等、不猜 |
問句依賴「環境裡有沒有人」,環境會騙你;旗標依賴「使用者有沒有真的打字」,這件事騙不了。
原則 2:預設值必須是「什麼都不做」
程式什麼參數都不加時,應該是空跑模式(dry-run):把「將會發生什麼」完整印出來,但一個外部 API 都不呼叫、一個檔案都不覆蓋。
判準:新手第一次亂打你的指令,最壞情況應該是「什麼都沒發生」,不是「東西送出去了」。
原則 3:關卡失效時要往「拒絕」倒
想像你的判斷式本身出錯:回傳相反的值、拋出例外、依賴的變數是空的。這時程式應該走到「拒絕執行」,而不是「繼續執行」。
- 往拒絕倒:
if not 明確授權: 拒絕,任何非預期狀態都落在「不授權」 - 往放行倒:
if 偵測到沒授權: 拒絕,判斷式本身失敗時就跳過了拒絕
原則 4:批次的不可逆動作要能「部分成功」
如果一次要做十件不可逆的事(例如發一串十則的推文),第五件失敗時,前四件已經發生、收不回來。最危險的設計是自動重試整批,你會得到重複的結果。
正確做法:回報「已經完成到第幾件」,然後停下來交給人決定是接續、回收、還是放著。
按步驟做
Step 1:找出系統裡所有不可逆的動作
問自己一句:這個動作做完後,我能不能在 10 秒內完全撤銷、且不留痕跡?不能的就是不可逆。常見的有:對外發布、刪除檔案或資料列、轉帳付款、寄出郵件、覆蓋既有檔案、推送到公開倉庫。
Step 2:把每個不可逆動作包進「三段式」結構
def main(args):
# 第一段:預設空跑。什麼都不加=只印出將要發生什麼
dry_run = not args.send
# 第二段:顯式授權閘門。不打完整旗標就直接退出,不問不等
if args.send and not args.yes:
print("--send 必須同時帶 --yes 才會執行。這是不可逆動作。")
return 2 # 用非零離開碼,讓呼叫方也知道沒做
# 第三段:真正執行,且逐項回報結果
results = []
for item in items:
result = do_one_thing(item, dry_run=dry_run)
results.append(result)
if not result.ok:
# 不要自動重試整批,前面已完成的收不回來
print(f"已完成 {len(results)-1} 項後失敗,停止。請人工確認後續。")
break
return 0 if all(r.ok for r in results) else 1
Step 3:把「將要發生什麼」印得夠具體
空跑模式的輸出,要讓人光看就能決定要不要放行。不要只印「將發布 3 篇」,要印出實際會送出去的內容本體、目標位置、以及會產生的成本。如果動作會花錢(呼叫付費 API),在空跑階段就把預估金額算出來。
Step 4:加一道與平台無關的上限
涉及花錢或配額時,別只依賴服務商後台的上限。在自己程式裡也存一個累計值與上限,超過就拒絕執行。兩道閘總比一道好。
Step 5:實際測試「沒有人在」的情境
這步最多人跳過。把你的指令用排程、用管線、用另一個腳本呼叫,確認它在無人環境下的行為跟預期一致。快速測法是把標準輸入導成空的,看程式是崩潰、傻等,還是乾脆放行。這三種結果都不合格,合格的是「明確拒絕並說明原因」。
你一定會踩的坑
坑 1:用「偵測是不是互動環境」當閘門
各種語言都有偵測標準輸入是否連到終端機的函式。它在你自己的終端機測起來一切正常,但換一個 shell、換一個作業系統,或被別的程式呼叫時,可能給出完全相反的答案。不要用它決定「要不要執行不可逆動作」。拿它決定「要不要輸出彩色文字」沒問題,猜錯不會有人受傷。
坑 2:失敗後自動重試整批
重試對「查詢」是好習慣,對「不可逆動作」是災難,你會得到重複的貼文、重複的轉帳、重複的郵件。判準:這個操作重跑一次,結果會不會變成兩份?會,就不能自動重試。
坑 3:報表說謊
執行完印出「已完成」,實際上全部失敗了,因為那行字是寫死的。這在不可逆動作上特別危險,你會據此以為「已經做過了」而不再檢查。狀態標題一定要從實際結果推導,並區分「全部成功/部分成功/全部失敗」三種。
萬用救援:卡住怎麼問 AI
把下面這段複製給任何 AI 助手,把角括號內容換成你的情況:
我在寫一個會造成不可逆後果的功能:<描述那個動作,例如「發布貼文到社群平台」>。
我用的語言是 <語言>,執行方式是 <指令列工具/排程/網頁後端>。
請幫我檢查我的確認機制有沒有問題,具體回答這四點:
1. 我的閘門是依賴「偵測執行環境」還是「使用者明確表態」?
2. 什麼參數都不給的時候,預設會發生什麼?是不是完全不動作?
3. 如果我的判斷式本身出錯或拋例外,程式會拒絕還是會放行?
4. 這個操作重跑一次會不會產生兩份結果?如果會,我的失敗處理是不是在自動重試?
請一步一步說明,並且直接給我修改後的程式碼。
我是新手,請把每個修改的理由講清楚。
如果你連「我的程式算不算不可逆」都不確定,先問這句:
以下這段程式碼,請幫我列出裡面所有「執行後無法在 10 秒內完全撤銷」的操作,
並依風險排序。<貼上你的程式碼>
一句話總結
不可逆的動作,用「使用者明確打出來的旗標」當閘門,不要用「程式猜測環境」當閘門;讓預設值是什麼都不做,讓失效時往拒絕倒。
這是踩坑實錄與 AI 工具實測系列的一部分。想收到後續完整 SOP,訂閱電子報。
支持這裡的內容:前往 Ko-fi 斗內