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 裡看到滿滿的 ConnectionError 和 JSONDecodeError。
建議做法
補上完整的錯誤處理和重試機制:
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():