2026/4/14 · AI 上線專家

AI 寫的程式碼上線檢查清單:讓 ChatGPT 產出真正可部署的 12 個關鍵步驟

AI 寫的程式碼上線檢查清單:讓 ChatGPT 產出真正可部署的 12 個關鍵步驟

您的團隊開始用 ChatGPT 或 Claude 加速開發了嗎?這是個聰明的決定。我們接手的專案中,有八成團隊已經在使用 AI 輔助寫程式,速度確實快了不少。但同時也發現一個普遍現象:直接把 AI 生成的程式碼 push 到 production,三個月後問題就會一個接一個浮現。

這不代表您的做法有問題,只是 AI 生成程式碼和「可上線程式碼」之間,確實還有一段需要人工把關的距離。以下是我們整理的 12 個技術檢查點,每一項都來自實際接手專案時發現的真實問題,也都有具體的解決方式。

一、錯誤處理:AI 總是假設一切順利

問題實例

ChatGPT 生成的 API 呼叫通常長這樣:

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()

看起來沒問題,但實務上網路會斷線、API 會回傳 500 錯誤、user_id 可能是惡意字串。三個月後您會在 log 裡看到滿滿的 ConnectionErrorJSONDecodeError

建議做法

補上完整的錯誤處理和重試機制:

def get_user_data(user_id, max_retries=3):
    if not isinstance(user_id, int) or user_id <= 0:
        raise ValueError(f"Invalid user_id: {user_id}")
    
    for attempt in range(max_retries):
        try:
            response = requests.get(
                f"https://api.example.com/users/{user_id}",
                timeout=5
            )
            response.raise_for_status()
            return response.json()
        except requests.Timeout:
            if attempt == max_retries - 1:
                logger.error(f"Timeout after {max_retries} attempts")
                raise
        except requests.HTTPError as e:
            logger.warning(f"HTTP {e.response.status_code} for user {user_id}")
            return None

這樣加上去大概多 15 行程式碼,但可以避免半夜被叫起來處理當機。

二、資料庫連線沒有正確關閉

問題實例

AI 生成的資料庫查詢常常這樣寫:

def update_order_status(order_id, status):
    conn = psycopg2.connect(database="shop", user="admin", password="...")
    cursor = conn.cursor()
    cursor.execute("UPDATE orders SET status = %s WHERE id = %s", (status, order_id))
    conn.commit()

上線一週後,您會發現資料庫連線數不斷攀升,最後達到上限整個服務掛掉。

建議做法

使用 context manager 確保連線釋放:

def update_order_status(order_id, status):
    try:
        with psycopg2.connect(database="shop", user="admin", password="...") as conn:
            with conn.cursor() as cursor:
                cursor.execute(
                    "UPDATE orders SET status = %s WHERE id = %s", 
                    (status, order_id)
                )
            conn.commit()
    except psycopg2.Error as e:
        logger.error(f"Database error updating order {order_id}: {e}")
        raise

或者更好的方式是用連線池 (connection pool),讓 20 個連線就能處理上千個請求。

三、SQL 注入風險未處理

問題實例

您問 AI「用 Python 查詢特定商品」,它可能給您:

def search_products(keyword):
    query = f"SELECT * FROM products WHERE name LIKE '%{keyword}%'"
    cursor.execute(query)

這個寫法讓攻擊者輸入 '; DROP TABLE products; -- 就能刪掉您的整張產品表。

建議做法

永遠使用參數化查詢:

def search_products(keyword):
    # 先清理輸入
    keyword = keyword.strip()[:100]  # 限制長度
    
    query = "SELECT * FROM products WHERE name LIKE %s"
    cursor.execute(query, (f"%{keyword}%",))

我們接手的一個電商專案,上線前發現有三處這樣的注入點。修好只花了 30 分鐘,但如果被駭客發現,損失可能是數百萬元。

四、敏感資料寫死在程式碼裡

問題實例

AI 生成的範例常常把設定直接寫在程式裡:

STRIPE_API_KEY = "sk_live_51H..."
DATABASE_URL = "postgresql://admin:P@ssw0rd@db.example.com/prod"
JWT_SECRET = "my_super_secret_key_123"

這些資料一旦 commit 到 Git,就算後來刪除,也會永遠留在歷史紀錄裡。我們接手的專案中,有兩成在 GitHub 上公開了資料庫密碼。

建議做法

使用環境變數和設定管理工具:

import os
from dotenv import load_dotenv

load_dotenv()

STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
JWT_SECRET = os.getenv("JWT_SECRET")

# 啟動時檢查必要設定
if not all([STRIPE_API_KEY, DATABASE_URL, JWT_SECRET]):
    raise RuntimeError("Missing required environment variables")

配合 .env.example 範本檔,讓新成員知道需要哪些設定,但實際值不進版本控制。

五、沒有日誌記錄,出問題找不到原因

問題實例

AI 生成的功能通常只有核心邏輯:

def process_payment(order_id, amount):
    charge = stripe.Charge.create(amount=amount, currency="twd")
    if charge.status == "succeeded":
        update_order_status(order_id, "paid")
        return True
    return False

三個月後客戶說「某筆訂單明明付款了卻顯示未付款」,您完全無從查起。

建議做法

加入結構化日誌:

import logging

logger = logging.getLogger(__name__)

def process_payment(order_id, amount):
    logger.info(f"Processing payment for order {order_id}, amount {amount}")
    
    try:
        charge = stripe.Charge.create(amount=amount, currency="twd")
        logger.info(f"Stripe charge created: {charge.id}, status: {charge.status}")
        
        if charge.status == "succeeded":
            update_order_status(order_id, "paid")
            logger.info(f"Order {order_id} marked as paid")
            return True
        else:
            logger.warning(f"Payment failed for order {order_id}: {charge.failure_message}")
            return False
    except stripe.error.CardError as e:
        logger.error(f"Card error for order {order_id}: {e.user_message}")
        raise

建議使用 ELK stack 或 CloudWatch 這類工具,讓日誌可搜尋、可分析。一個月的日誌成本約 1,500-3,000 元,但能省下數十小時的 debug 時間。

六、效能問題:N+1 查詢

問題實例

您請 AI「顯示所有訂單和對應的客戶名稱」,它可能寫:

def get_orders_with_customers():
    orders = Order.query.all()  # 1 次查詢
    result = []
    for order in orders:
        customer = Customer.query.get(order.customer_id)  # N 次查詢
        result.append({
            "order_id": order.id,
            "customer_name": customer.name
        })
    return result

100 筆訂單就要執行 101 次資料庫查詢,頁面載入時間從 200ms 暴增到 5 秒。

建議做法

使用 JOIN 或 eager loading:

def get_orders_with_customers():
    orders = Order.query.join(Customer).all()  # 1 次查詢
    return [{
        "order_id": order.id,
        "customer_name": order.customer.name
    } for order in orders]

我們接手的一個專案,光是修正 5 處 N+1 查詢,就讓 API 回應時間從平均 3.2 秒降到 0.4 秒。

七、時區處理不一致

問題實例

AI 產生的時間處理常常混用 naive datetime 和 aware datetime:

from datetime import datetime

def create_appointment(user_id, time_str):
    # time_str = "2024-03-15 14:00"
    appointment_time = datetime.strptime(time_str, "%Y-%m-%d %H:%M")
    # 這是哪個時區的 14:00? 台北? UTC? 使用者所在地?

結果台北使用者約下午 2 點,系統記錄成 UTC 14:00(台北時間晚上 10 點),客戶錯過預約還以為系統有問題。

建議做法

統一使用 UTC 儲存,顯示時轉換:

from datetime import datetime
import pytz

TAIPEI_TZ = pytz.timezone('Asia/Taipei')

def create_appointment(user_id, time_str, user_timezone="Asia/Taipei"):
    # 明確指定輸入是哪個時區
    user_tz = pytz.timezone(user_timezone)
    naive_dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M")
    local_dt = user_tz.localize(naive_dt)
    
    # 轉成 UTC 儲存
    utc_dt = local_dt.astimezone(pytz.UTC)
    appointment = Appointment(user_id=user_id, time=utc_dt)
    
    logger.info(f"Created appointment at {utc_dt} UTC ({local_dt} {user_timezone})")
    return appointment

建議全公司統一時區處理政策:資料庫一律 UTC,API 輸入輸出帶時區資訊,前端依使用者設定顯示。

八、沒有輸入驗證和長度限制

問題實例

AI 生成的表單處理:

def update_profile(user_id, bio):
    user = User.query.get(user_id)
    user.bio = bio
    db.session.commit()

使用者貼上一整本小說(500KB 文字)到自我介紹欄位,資料庫記錄變超大,查詢變慢,頁面載入時崩潰。

建議做法

加入完整的輸入驗證:

def update_profile(user_id, bio):
    # 驗證長度
    if len(bio) > 500:
        raise ValueError("自我介紹不得超過 500 字")
    
    # 清理危險字元
    import bleach
    clean_bio = bleach.clean(bio, tags=[], strip=True)
    
    # 檢查是否只是空白
    if not clean_bio.strip():
        clean_bio = ""
    
    user = User.query.get(user_id)
    if not user:
        raise ValueError(f"找不到使用者 {user_id}")
    
    user.bio = clean_bio
    db.session.commit()
    
    logger.info(f"User {user_id} updated bio ({len(clean_bio)} chars)")

實務上建議製作一個共用的 validate_input() 函式,統一處理常見的驗證邏輯。

九、併發問題:資料競爭

問題實例

AI 寫的庫存扣減邏輯:

def purchase_product(product_id, quantity):
    product = Product.query.get(product_id)
    if product.stock >= quantity:
        product.stock -= quantity
        db.session.commit()
        return True
    return False

兩個客戶同時購買最後 1 件商品,兩個請求都通過 if 檢查,結果賣出 2 件但庫存變成 -1。

建議做法

使用資料庫層級的原子操作:

from sqlalchemy import and_

def purchase_product(product_id, quantity):
    # 使用 UPDATE ... WHERE 確保原子性
    result = db.session.query(Product).filter(
        and_(
            Product.id == product_id,
            Product.stock >= quantity
        )
    ).update({
        "stock": Product.stock - quantity
    }, synchronize_session=False)
    
    db.session.commit()
    
    if result == 0:
        logger.warning(f"Insufficient stock for product {product_id}")
        return False
    
    logger.info(f"Purchased {quantity} units of product {product_id}")
    return True

或使用樂觀鎖 (version field) 或悲觀鎖 (SELECT FOR UPDATE),視業務需求選擇。

十、缺少健康檢查端點

問題實例

AI 生成的 API 服務通常只有業務邏輯端點,沒有健康檢查:

@app.route("/api/orders", methods=["GET"])
def get_orders():
    return jsonify(Order.query.all())

部署到 Kubernetes 或 AWS ECS 後,Load Balancer 不知道服務是否正常,可能把流量導到已經掛掉的容器。

建議做法

加入標準的健康檢查端點:

@app.route("/health", methods=["GET"])
def health_check():
    return {"status": "ok"}, 200

@app.route("/health/ready", methods=["GET"])
def readiness_check():