Chuyển đến nội dung
MINH VO Ghi chép từ công việc
của một kỹ sư tại Việt Nam
Cơ bản11 phút đọc

Hỏi về lỗi API với bằng chứng tái hiện được

Tạo câu hỏi API rõ ràng từ ví dụ HTTP chạy thật: phân biệt 404, fetch bị từ chối, lỗi đọc JSON và kết luận mà bằng chứng chưa xác lập.

Tranh màu nước các cuốn sổ, một cuốn sổ phác thảo mở và bút chì gỗ trên nền kem

Tôi muốn một yêu cầu hỗ trợ giúp người nhận dễ đưa ra phản hồi hữu ích tiếp theo: nêu điều bạn mong đợi, điều bạn quan sát được và phần kiến thức bạn cần kiểm tra lại. Điều đó không đòi hỏi một cuộc điều tra hoàn hảo. Kỹ sư mới cần được hỏi sớm, nhất là khi quyền truy cập, thời gian hoặc công cụ chưa quen giới hạn những gì họ có thể xác minh.

Thói quen hữu ích là tách quan sát khỏi lời giải thích. “API bị hỏng” là lời giải thích gần như không kèm bằng chứng. “Yêu cầu GET này trả về 404 nhưng trình xử lý lỗi của tôi không chạy” chứa hai quan sát mà người khác có thể giúp bạn kết nối. Khác biệt ấy quan trọng vì mã trạng thái phản hồi, yêu cầu bị từ chối và lỗi đọc nội dung phản hồi xảy ra ở các ranh giới khác nhau.

Bắt đầu bằng một phép so sánh tái hiện được

Hãy hình dung bảng thông tin một món đồ vẫn trống. Bạn mong đợi bản ghi sổ tay và nghi ngờ dịch vụ không hoạt động. Trước khi viết lại giao diện, so sánh yêu cầu thực tế với một đường dẫn cục bộ đã biết. Giữ rõ phương thức yêu cầu, origin và phiên bản bộ dữ liệu thử. “Nó chạy trên máy tôi” kém hữu ích hơn nhiều so với chỉ ra chính xác yêu cầu nào đã chạy thành công.

Bộ thử dưới đây làm rõ khác biệt. Nó có một đường dẫn món đồ hợp lệ, một đường dẫn cố ý trả JSON sai cú pháp và một đường dẫn đóng kết nối mà không gửi phản hồi. Mọi đường dẫn khác trả một lỗi JSON nhỏ với trạng thái 404. Bộ thử dùng dữ liệu tự tạo và chỉ lắng nghe trên địa chỉ loopback cục bộ.

Lưu mã thành server.py. Tạo tệp probe.html bên cạnh, chứa <!doctype html><meta charset="utf-8"><title>Request probe</title>. Chạy python3 server.py từ thư mục đó, mở http://127.0.0.1:8765/ và dùng console của trang trong trình duyệt để chạy JavaScript bên dưới. Có thể truyền một đối số số nguyên trên dòng lệnh để chọn cổng khác nếu cần. Dừng máy chủ bằng Ctrl+C khi làm xong.

from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
import json
import sys

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        path = self.path.split('?', 1)[0]
        if path == '/':
            payload = Path('probe.html').read_bytes()
            status, kind = 200, 'text/html; charset=utf-8'
        elif path == '/api/items/42':
            payload = json.dumps({'id': 42, 'name': 'Notebook'}).encode()
            status, kind = 200, 'application/json'
        elif path == '/api/broken-json':
            payload = b'{'
            status, kind = 200, 'application/json'
        elif path == '/api/disconnect':
            self.close_connection = True
            self.connection.close()
            return
        else:
            payload = json.dumps({'error': 'route-not-found'}).encode()
            status, kind = 404, 'application/json'
        self.send_response(status)
        self.send_header('Content-Type', kind)
        self.send_header('Content-Length', str(len(payload)))
        self.send_header('Cache-Control', 'no-store')
        self.end_headers()
        self.wfile.write(payload)

port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765
HTTPServer(('127.0.0.1', port), Handler).serve_forever()

Mã thăm dò cố ý ghi nhận trạng thái HTTP trước khi đọc JSON. Nó cũng đọc nội dung lỗi nhỏ, đã biết trước, để minh họa rằng phản hồi 404 vẫn có thể chứa JSON hợp lệ. Lựa chọn phục vụ chẩn đoán này không phải khuyến nghị xử lý mọi phản hồi lỗi như dữ liệu ứng dụng.

async function inspect(path) {
  let response;
  try {
    response = await fetch(path);
  } catch (error) {
    return { path, fetch: 'rejected', status: null, ok: null,
             body: 'not-read', error: error.name };
  }
  const result = { path, fetch: 'fulfilled',
                   status: response.status, ok: response.ok };
  try {
    return { ...result, body: 'parsed', value: await response.json() };
  } catch (error) {
    return { ...result, body: 'read-rejected', error: error.name };
  }
}
const paths = ['/api/items/42', '/api/widgets/42',
               '/api/broken-json', '/api/disconnect'];
const results = [];
for (const path of paths) results.push(await inspect(path));
console.table(results);

Trình xem cục bộ trong trình duyệt hiển thị bốn yêu cầu đã chạy: JSON hợp lệ với 200, JSON lỗi hợp lệ với 404, lỗi đọc JSON sau 200 và fetch bị từ chối không có trạng thái HTTP.Mở ảnh kích thước đầy đủ ↗

Ảnh chụp trình duyệt thực tế của trình xem cục bộ hiển thị các đối tượng do mã thăm dò trả về. Dữ liệu được tự tạo; yêu cầu và thao tác đọc nội dung đã được thực thi. Trình xem trình bày giá trị đã phân tích, không phải ứng dụng đang phục vụ người dùng hay phép đo thời gian mạng.

Đường dẫn thành công trả đối tượng sổ tay. Đường dẫn sai vẫn làm fetch() hoàn thành thành công ở cấp Promise, báo trạng thái 404 và cung cấp đối tượng lỗi có thể phân tích được. Đường dẫn sai JSON cũng làm fetch() hoàn thành với trạng thái 200, nhưng lần đọc json() sau đó bị từ chối với SyntaxError. Đường dẫn ngắt kết nối khiến fetch() bị từ chối và không tạo đối tượng Response để đoạn mã này kiểm tra.

Tài liệu Using the Fetch API của MDN mô tả các bước riêng biệt ấy: fetch() có thể hoàn thành khi trạng thái và header phản hồi tới nơi; bản thân trạng thái lỗi HTTP không làm Promise đó bị từ chối; đọc JSON là một thao tác bất đồng bộ khác. response.ok bao gồm dải trạng thái từ 200 đến 299. Nó không bảo đảm nội dung phân tích được hoặc khớp cấu trúc dữ liệu mà ứng dụng yêu cầu.

Giữ ranh giới lỗi trong câu hỏi

Với quan sát 404 ban đầu, “Vì sao catch không chạy?” là câu hỏi hữu ích nếu nói rõ đang theo dõi Promise nào. catch gắn với fetch() không tự xem HTTP 404 là yêu cầu bị từ chối. Ứng dụng có thể chủ động ném lỗi khi response.ok bằng false. Lần đọc nội dung sau đó cũng có thể bị từ chối riêng, như dòng thứ ba minh họa.

Mã thăm dò gắn nhãn kết quả cuối ấy là read-rejected, thay vì mặc định mọi lỗi khi đọc nội dung đều là lỗi cú pháp JSON. Trong trường hợp nội dung sai có kiểm soát này, lỗi là SyntaxError. Các lỗi đọc nội dung khác cần bằng chứng riêng. Tương tự, TypeError chung từ fetch() không xác định được lỗi mạng hoặc chính sách trình duyệt nào gây ra việc từ chối. Ta biết bộ thử cố ý đóng kết nối vì kiểm soát được mã triển khai của nó.

Trao đổi ở đường dẫn sai trả đối tượng phản hồi 404 trước khi JSON lỗi được phân tích; phân loại trạng thái và đọc nội dung là hai bước riêng.Mở ảnh kích thước đầy đủ ↗

Trao đổi này theo đường dẫn 404 của bộ thử. Nó không biểu diễn kết nối thất bại và không đưa ra kết luận về tốc độ nội dung phản hồi tới nơi.

Báo cáo hữu ích giữ lại phương thức và đường dẫn, mã trạng thái dạng số, kiểu lỗi liên quan, đoạn mã gọi tối thiểu và kết quả mong đợi. Nêu chi tiết môi trường có thể làm thay đổi câu trả lời: trang và API có cùng origin hay không, dịch vụ hoặc proxy nào nhận yêu cầu, bạn chạy phiên bản bộ thử hay ứng dụng nào. Bổ sung ngữ cảnh khi được đề nghị; danh sách phụ thuộc dài nhưng không liên quan có thể che mất phép so sánh quan trọng.

Viết câu hỏi mà người khác trả lời được

Bài How to get useful answers to your questions của Julia Evans, xuất bản tháng 10 năm 2021, khuyến nghị trình bày cách hiểu hiện tại và dùng câu hỏi cụ thể để kiểm tra cách hiểu ấy. Tác giả cũng bàn về việc hỏi tiếp khi lời giải thích chưa đi đúng trọng tâm. Tôi thích cách tiếp cận này vì nó đưa cho người kia một nhận định để xác nhận hoặc sửa lại, mà không xem họ là người luôn đúng.

Với ví dụ này, yêu cầu có thể là:

Trong bộ thử món đồ cục bộ, tôi mong đợi đối tượng sổ tay. GET /api/widgets/42 trả HTTP 404 với {"error":"route-not-found"}, còn GET /api/items/42 trả 200 với món đồ. Cả hai lời gọi đều làm fetch() hoàn thành thành công ở cấp Promise. Hiện tôi hiểu rằng bên gọi ban đầu dùng sai đường dẫn và trạng thái HTTP cần được kiểm tra rõ ràng. Cách hiểu đó đúng không, và phía client này nên phân loại phản hồi không OK ở đâu trước khi dùng nội dung của nó?

Nội dung ấy đã gần với lời giải, và đó là điều hữu ích. Nếu bằng chứng giải quyết được câu hỏi ban đầu trong lúc bạn viết, hãy sửa vấn đề có phạm vi rõ và nhờ rà soát khi phù hợp. Không cần tạo ra một bí ẩn chỉ để thể hiện rằng mình đã nhờ hỗ trợ.

Trong dịch vụ thực tế, cùng mã 404 có thể liên quan đến định tuyến, trạng thái tài nguyên hoặc chính sách truy cập có chủ đích. Bộ thử cục bộ không xác lập nguyên nhân nào trong những hệ thống bên ngoài ấy. Giữ khác biệt đó trong báo cáo: “Phép thử này tái hiện việc thiếu kiểm tra trạng thái” có cơ sở hơn “Điều này chứng minh hệ thống đang vận hành có cùng lỗi.”

Xác định bằng chứng hỗ trợ điều gì

Bảng bằng chứng tách điều từng kết quả quan sát xác lập được khỏi kết luận chưa thể đưa ra, gồm việc nhận phản hồi HTTP, phân tích nội dung và nguyên nhân bị từ chối.Mở ảnh kích thước đầy đủ ↗

Mỗi dòng giúp thu hẹp câu hỏi. Không dòng nào thay thế việc kiểm tra đường dẫn dự kiến, chính sách truy cập hay cấu trúc dữ liệu ứng dụng.

Hướng dẫn viết câu rõ ràng của Google ưu tiên chủ thể, động từ chính xác và thông tin thực tế thay cho từ bổ nghĩa mơ hồ. Áp dụng vào báo cáo: viết “yêu cầu trả về 404” thay vì “API hoạt động lạ.” Nêu quan sát thực tế thay vì bịa số liệu gây ấn tượng. Lời khuyên dùng bằng chứng số của nguồn không cho phép khẳng định về độ trễ khi bạn chưa đo độ trễ.

Chọn ví dụ tái hiện nhỏ nhưng giữ được ranh giới đang bàn. Đổi URL, xác thực, nội dung yêu cầu và phiên bản máy chủ cùng lúc có thể làm triệu chứng biến mất mà không xác định được thay đổi nào có tác dụng. Một yêu cầu trực tiếp chạy thành công cũng không xác lập rằng lời gọi trình duyệt ban đầu dùng cùng origin, thông tin xác thực hay tuyến qua proxy. Giữ phép so sánh có kiểm soát ở phạm vi hẹp và nói rõ khác biệt bạn chưa loại bỏ được.

Đừng dán cookie phiên, header xác thực hay nội dung phản hồi riêng tư vào câu hỏi công khai. Dùng kênh được chấp thuận cho chi tiết nhạy cảm cần thiết và thay bằng dữ liệu tự tạo khi vẫn giữ được lỗi. Ảnh chụp giúp người rà soát định hướng, nhưng đường dẫn, lỗi và bước tái hiện vẫn cần văn bản có thể sao chép. Nếu không truy cập được log cần thiết, hãy nói rõ thay vì ngụ ý đã kiểm tra.

Dùng câu trả lời để cập nhật cách hiểu chung

Khi người khác trả lời “kiểm tra CORS,” đừng mặc nhiên chấp nhận cũng đừng phản bác trong thế phòng thủ. Hỏi quan sát nào giúp phân biệt việc trình duyệt từ chối theo chính sách với phản hồi 404 mà fetch() vẫn nhận được trong ví dụ này. Nếu lời giải thích dùng thuật ngữ lạ, hỏi về thuật ngữ ấy trước khi thử một cách sửa khác không liên quan. Một câu hỏi tiếp đúng trọng tâm thường tiết kiệm thời gian hơn mở rộng tìm kiếm.

Sau khi sửa, chạy lại yêu cầu lỗi và một trường hợp liên quan. Ở đây, sửa đường dẫn món đồ không được che khuất trường hợp nội dung sai JSON hoặc biến mọi lỗi thành món đồ rỗng. Ghi nhận hành vi đã đổi và giới hạn còn lại để người đọc tiếp theo biết câu hỏi nào đã được trả lời. Bộ thử chạy thành công xác lập hành vi cục bộ; nó không phải bằng chứng bản sửa đã được triển khai lên dịch vụ.

Để luyện tập, bỏ bốn dòng kết quả khỏi báo cáo và chỉ giữ “bảng thông tin trống.” Viết ba quan sát bạn sẽ thu thập trước, rồi khôi phục kết quả thực tế và sửa câu hỏi. Kết thúc bằng cách đổi qua lại trường hợp nội dung sai JSON và ngắt kết nối. Giải thích vì sao ranh giới lỗi khác nhau dẫn tới bước kiểm tra tiếp theo khác nhau, dù cả hai đều không cung cấp món đồ dùng được. Mục tiêu là câu hỏi mang đủ bằng chứng để cuộc trao đổi tiến thêm một bước.

Nguồn tham khảo & đọc thêm

  1. Google Technical Writing: câu rõ ràng
  2. MDN: sử dụng Fetch API
  3. Julia Evans: cách nhận câu trả lời hữu ích, ngày 21 tháng 10 năm 2021
← Về trang bài viết

Hình minh họa

100%