自動化與 Agent

不可逆動作的確認閘門:用顯式旗標,不要用互動式問句

你的保護機制一定會有失效的一天,真正該設計的是它失效時往哪邊倒。讓「什麼都不做」成為預設,讓「執行」需要使用者明確打出一個旗標,別讓程式去猜環境裡有沒有人。

2026/10/14 | 3 分鐘閱讀 | 約 8 分鐘讀完,含可直接照抄的 Python 程式骨架

一個核心觀念

保護機制一定會有失效的一天。真正該設計的不是「它會不會失效」,而是「它失效時往哪邊倒」。

大多數人寫確認關卡的思路是「怎麼確保有人看過」,這會讓你去偵測環境:有沒有終端機、是不是互動模式、是不是 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 秒內完全撤銷」的操作,
並依風險排序。<貼上你的程式碼>

一句話總結

不可逆的動作,用「使用者明確打出來的旗標」當閘門,不要用「程式猜測環境」當閘門;讓預設值是什麼都不做,讓失效時往拒絕倒。

支持這裡的內容:前往 Ko-fi 斗內