Bài học từ một buổi phỏng vấn Python: Tư duy thực chiến và kiến thức sách vở

Nhiều năm làm trong ngành, tôi nhận ra có những bài học không đến từ sách vở hay khóa học, mà đến từ chính những lần va vấp thực tế. Hôm nay, tôi muốn kể lại một trong những trải nghiệm như vậy: một buổi phỏng vấn kỹ thuật về Python tưởng chừng đơn giản nhưng lại thay đổi hoàn toàn cách tôi nhìn nhận về công việc của một kỹ sư.

Nó đã dạy cho tôi một sự thật không thể chối cãi: kiến thức sách vở là nền tảng, nhưng tư duy giải quyết vấn đề trong môi trường thực tế mới là thứ quyết định giá trị của chúng ta.

0199bf31-a3e5-7fc7-bd5f-2be09b6950dc

Khi sự tự tin đến từ kiến thức nền tảng

Người phỏng vấn bắt đầu một cách khá cơ bản:

“Hãy cho biết sự khác biệt giữa listtuple trong Python.”

Đây là một câu hỏi cơ bản. Tôi đã trình bày rõ ràng:

  • List: Là một cấu trúc dữ liệu mutable (khả biến), có thể thay đổi sau khi khởi tạo.
  • Tuple: Là một cấu trúc immutable (bất biến), không thể thay đổi.

Tôi cũng bổ sung thêm về hiệu năng: “Tuple thường có tốc độ truy xuất nhanh hơn và tối ưu hơn về bộ nhớ so với List trong một số trường hợp”. Người phỏng vấn gật đầu tán thành, và tôi tin rằng mình đã có một khởi đầu thuận lợi.

Nhưng đó cũng là lúc các thử thách thực sự bắt đầu.

Câu hỏi 1: Lỗi tiềm ẩn trong default argument

Họ đưa ra một đoạn mã và hỏi về những vấn đề tiềm tàng:

def log_message(message, history=[]):
    history.append(message)
    print(f"Logged: {message}")
    return history

Sau khi xem xét, tôi cho rằng hàm này hoạt động đúng như mô tả. Người phỏng vấn đề nghị tôi chạy thử hàm này hai lần liên tiếp.

log_1 = log_message("User logged in")
print(f"Current log: {log_1}")

log_2 = log_message("Failed payment")
print(f"Current log: {log_2}")

Kết quả thực thi đã cho thấy vấn đề:

Logged: User logged in
Current log: ['User logged in']
Logged: Failed payment
Current log: ['User logged in', 'Failed payment']

Vấn đề nằm ở chỗ, tham số mặc định history=[] chỉ được khởi tạo một lần duy nhất khi hàm được định nghĩa, không phải mỗi lần hàm được gọi. Điều này biến history thành một đối tượng được chia sẻ giữa tất cả các lần gọi hàm, dẫn đến kết quả không mong muốn. Đây là một lỗi logic phổ biến nhưng rất nguy hiểm.

Giải pháp đúng là phải tránh sử dụng các kiểu dữ liệu mutable làm tham số mặc định:

# Phiên bản đã được khắc phục
def log_message(message, history=None):
    if history is None:
        history = []
    history.append(message)
    print(f"Logged: {message}")
    return history

Nguyên tắc quan trọng: Luôn sử dụng giá trị bất biến như None cho các tham số mặc định và khởi tạo đối tượng khả biến bên trong thân hàm.

Câu hỏi 2: Viết code có tính defensive

Bài toán tiếp theo mô phỏng một kịch bản xử lý dữ liệu thực tế:

Yêu cầu: Cho một danh sách sản phẩm, hãy trích xuất mã sku của những sản phẩm có trạng thái in_stock: true và đơn giá dưới 50.

Dữ liệu đầu vào:

products = [
    {"sku": "A101", "price": 45, "in_stock": True},
    {"sku": "B205", "price": 60, "in_stock": True},
    {"sku": "C330", "price": 30, "in_stock": False},
    {"sku": "D400", "price": 25, "in_stock": True},
]

Tôi đã đề xuất giải pháp sử dụng list comprehension để tối ưu sự ngắn gọn:

[p["sku"] for p in products if p["in_stock"] and p["price"] < 50]

Giải pháp này hoạt động tốt với dữ liệu đầu vào. Tuy nhiên, người phỏng vấn đã đặt ra một câu hỏi then chốt:

Điều gì sẽ xảy ra nếu một dictionary trong danh sách thiếu key in_stock hoặc price?

Rõ ràng, mã của tôi sẽ gây ra KeyError và làm dừng chương trình. Đây là lúc tư duy defensive programming trở nên cần thiết, đảm bảo ứng dụng hoạt động ổn định ngay cả khi dữ liệu đầu vào không hoàn hảo.

Phiên bản chắc hơn phải sử dụng phương thức .get() để xử lý các trường hợp thiếu key:

[
    p["sku"] for p in products
    if p.get("in_stock") and p.get("price", 0) < 50 and "sku" in p
]

Bài học rút ra là: Mã nguồn không chỉ cần chạy đúng với kịch bản lý tưởng, mà còn phải đủ mạnh mẽ để xử lý các ngoại lệ và dữ liệu không nhất quán trong môi trường production.

Câu hỏi quyết định: Tư duy hệ thống thay vì tư duy lập trình

Cuối cùng, người phỏng vấn đặt ra một vấn đề hoàn toàn không liên quan đến mã nguồn cụ thể:

Giả sử bạn có một script Python đang chạy trên server, nó gặp sự cố và dừng đột ngột mỗi ngày một lần vào một thời điểm ngẫu nhiên. Bạn sẽ tiếp cận vấn đề này như thế nào?

Đây là câu hỏi đòi hỏi tư duy vượt ra ngoài phạm vi lập trình đơn thuần. Ban đầu, tôi đã lúng túng và chỉ đề xuất kiểm tra logs. Nhưng khi được yêu cầu giả định rằng logs không cung cấp đủ thông tin, tôi nhận ra giới hạn trong cách tiếp cận của mình.

Người phỏng vấn đã phân tích một phương pháp chẩn đoán lỗi mang tính hệ thống:

Đây không phải là một vấn đề của riêng Python, mà là một vấn đề hệ thống. Chúng ta cần điều tra từ nhiều góc độ:

  1. Instrumentation & Logging: Tăng cường cơ chế ghi log. Bọc các khối mã quan trọng trong try...except để bắt và ghi lại mọi ngoại lệ trước khi script kết thúc.
  2. System Call Tracing: Sử dụng các công cụ như strace (trên Linux) để theo dõi các lời gọi hệ thống mà tiến trình thực hiện. Lỗi có thể đến từ việc tương tác với file, network, hoặc các tài nguyên hệ điều hành khác.
  3. Resource Monitoring: Giám sát việc sử dụng tài nguyên (CPU, bộ nhớ). Một sự cố rò rỉ bộ nhớ (memory leak) có thể là nguyên nhân khiến proccess bị kill (ví dụ, bởi OOM Killer).
  4. Environmental Analysis: Kiểm tra các yếu tố bên ngoài. Có cron job hay tiến trình nào khác chạy song song và gây xung đột không? Môi trường hoạt động có thay đổi gì bất thường không?

Lúc này, tôi hiểu rằng nhà tuyển dụng không chỉ tìm kiếm một người lập trình viên. Họ tìm kiếm một kỹ sư có khả năng phân tích, chẩn đoán và giải quyết các vấn đề phức tạp trong một hệ thống lớn.

Tổng kết

Buổi phỏng vấn đó đã cho tôi một cái nhìn rõ ràng về những gì thực sự quan trọng:

  • Hiểu về generator là cần thiết, nhưng quan trọng hơn là biết khi nàotại sao nên dùng yield để xử lý các tập tin lớn mà không gây cạn kiệt bộ nhớ.
  • Biết về đệ quy là tốt, nhưng hiểu được giới hạn chiều sâu đệ quy (recursion depth limit) của Python và tại sao nó tồn tại để bảo vệ bộ nhớ stack mới là kiến thức thực tiễn.

Sau trải nghiệm đó, tôi đã thay đổi phương pháp học tập của mình. Thay vì chỉ tập trung vào lý thuyết, tôi chủ động:

  • Viết code và tự tạo ra các kịch bản lỗi để fix bug.
  • Nghiên cứu mã nguồn của các thư viện uy tín.
  • Mô phỏng các vấn đề thường gặp trong môi trường vận hành.

Kiến thức nền tảng là bệ phóng, nhưng chính tư duy giải quyết vấn đề và kinh nghiệm thực chiến mới là thứ quyết định giá trị của một kỹ sư.

Tôi hy vọng bài chia sẻ này sẽ hữu ích cho các bạn.

Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận