# Effective Python bản 3: đọc mục nào trước khi sửa code Python của khách

> Lỗi đắt nhất trong code khách để lại hiếm khi là thuật toán chậm. Thường đó là một khối except nuốt mất lỗi, và cuốn sách của Brett Slatkin chỉ đúng chỗ cần tìm.

Bản gốc: https://fdetimes.net/vi/sach-khoa-hoc/sach-effective-python-brett-slatkin-125-cach/

Thử hình dung tuần đầu ở một khách hàng. Pipeline dữ liệu của họ thỉnh thoảng ghi ra những dòng rỗng mà không ai biết vì sao. Mở code ra, bạn thấy một hàm gọi API được bọc trong `except Exception:` và trả về `None` khi có lỗi. Không có log nào ghi lại chuyện gì đã xảy ra.

Cả hai thói quen này đều có mục riêng trong *Effective Python: 125 Specific Ways to Write Better Python*, bản 3, của Brett Slatkin. Addison-Wesley Professional phát hành sách ngày 20/11/2024 (ISBN-13 978-0-13-817218-3). Nhà xuất bản giới thiệu Slatkin là người có nhiều năm kinh nghiệm ở Google.

Với FDE, giá trị của cuốn sách không nằm ở chỗ dạy viết Python cho đẹp. Sách cho bạn một danh mục những thứ cần soi khi tiếp quản code của người khác, và cách sửa mà không làm hỏng hệ thống đang chạy.

## Có gì mới trong bản 3?

So với bản trước, bản 3 tăng từ 90 lên 125 mục, trong đó có 35 mục hoàn toàn mới. Nội dung bám theo các bản Python đến 3.13, nên những phần như `match` hay dataclasses đều viết theo cú pháp hiện đại.

Phần đáng mua nhất là hai chương mới về độ bền vững (Robustness) và hiệu năng (Performance). Sách cũng viết sâu về C-extension và cách gọi thư viện native, dành cho lúc bài toán hiệu năng không thể giải bằng Python thuần.

Tác giả có kho mã chính thức trên GitHub, `bslatkin/effectivepython`, trong đó mỗi mục có file hoặc thư mục ví dụ riêng. Vì vậy bạn có thể đọc theo kiểu tra cứu: gặp lỗi nào thì mở mục đó và chạy ví dụ ngay.

## Nên đọc theo thứ tự nào?

Đừng đọc từ trang đầu đến trang cuối. Khi chuẩn bị sửa code của khách, hãy bắt đầu từ chương Robustness. Chương này nói về cách dùng đủ try/except/else/finally, về việc không nên bắt cả lớp `Exception`, và về chuỗi exception.

Đó cũng chính là ba thứ cần để chữa cái pipeline ghi dòng rỗng ở trên. Tiếp theo là chương Functions: Item 32 khuyên raise exception thay vì trả về `None`, còn Item 30 nhắc rằng đối số truyền vào hàm có thể bị thay đổi (mutated). Đây là hai nguồn bug phổ biến khi đọc code lạ.

Sau đó đọc chương Testing và Collaboration, rồi mới đến Performance và Concurrency. Thứ tự này có lý do: muốn làm code nhanh hơn, trước hết bạn phải chắc mình sửa được nó mà không gây hỏng chỗ khác.

**Điểm mấu chốt:** Trước khi làm code của khách chạy nhanh hơn, hãy để nó báo lỗi rõ ràng.

## Bài học đầu tiên: đừng để lỗi chìm mất

Quay lại hàm gọi API. Với `except Exception: return None`, một lỗi xác thực, một timeout và một bug đánh máy trong code đều trông giống hệt nhau: đều là `None`. Đoạn code dưới đây là một ví dụ minh họa, đặt bản cũ cạnh bản đã sửa theo tinh thần của chương Robustness và Item 32.

```python
# Trước: mọi lỗi đều biến thành None
def fetch_orders(client, day):
    try:
        return client.get_orders(day)
    except Exception:
        return None

# Sau: chỉ bắt lỗi mình hiểu, giữ nguyên chuỗi exception
class OrderFetchError(Exception):
    pass

def fetch_orders(client, day):
    try:
        return client.get_orders(day)
    except TimeoutError as e:
        raise OrderFetchError(f"Timeout fetching orders for {day}") from e
```

Ở bản sau, timeout được đổi thành một lỗi có tên rõ ràng, còn `from e` giữ lại nguyên nhân gốc. Mọi lỗi khác, kể cả bug đánh máy, sẽ nổi lên thay vì bị nuốt. Lần sau dòng rỗng xuất hiện, sẽ có stack trace chỉ thẳng vào nguyên nhân.

Khi vào việc ở khách, hãy làm điều này trước tiên: tìm mọi chỗ bắt `Exception` và mọi hàm trả `None` để báo lỗi. Danh sách đó cho bạn biết hệ thống đang giấu những gì.

## Bài học thứ hai: có test che chắn rồi mới sửa từ từ

Item 109 khuyên ưu tiên integration test hơn unit test. Với code chưa có test, đây là lời khuyên thực tế: một test chạy qua cả luồng từ đầu vào đến đầu ra sẽ giữ được hành vi hiện tại, kể cả khi bạn chưa hiểu từng hàm bên trong.

Item 112 nói về việc đóng gói dependency để dễ mock, phù hợp khi luồng đó gọi ra database hoặc API của khách.

Chương Collaboration bổ sung hai công cụ để thay đổi dần. Item 123 dùng `warnings` để báo cho người gọi rằng một hàm sắp đổi cách dùng, nhờ vậy đội của khách có thời gian chuyển đổi. Item 124 dùng typing để phân tích tĩnh, bắt lỗi trước khi code chạy.

Kết hợp lại, bạn có thể refactor từng bước mà phần đang chạy không bị vỡ.

## Bài học thứ ba: đo trước, tối ưu sau

Chương Performance mở đầu bằng Item 92, "Profile Before Optimizing". Item 93 hướng dẫn microbenchmark bằng `timeit`, còn Item 94 bàn chuyện khi nào nên thay Python bằng ngôn ngữ khác. Thứ tự này là cả một quy trình: tìm chỗ chậm thật sự, đo trước và sau khi sửa, và chỉ nghĩ đến C-extension khi đã hết cách trong Python.

Concurrency cũng cần thận trọng như vậy. Item 68 khuyên dùng thread cho blocking I/O, không dùng để song song hóa tính toán. Nếu khách muốn chạy một job xử lý dữ liệu nặng nhanh hơn bằng cách thêm thread, hãy đọc mục này trước khi đồng ý.

## Ai nên đọc, và dùng nó cho sự nghiệp thế nào?

Nếu bạn đã viết Python vài năm nhưng chủ yếu trên code của chính mình, cuốn sách này đáng giá nhất với bạn. Người đang chuẩn bị chuyển sang vai trò FDE cũng nên đọc, vì công việc đó gần như luôn bắt đầu bằng việc đọc code của người khác.

Khi viết CV, đừng ghi "thành thạo Python". Hãy kể một việc cụ thể, chẳng hạn: thay các khối bắt `Exception` quá rộng bằng exception có kiểu, bổ sung integration test cho một luồng chưa có test, rồi profile để tìm đúng điểm nghẽn.

Trong phỏng vấn, nếu được giao một đoạn code lạ, hãy hỏi cách đo trước khi đề xuất tối ưu. Câu hỏi đó cho người phỏng vấn thấy bạn đã làm việc trên hệ thống thật.

Cả 125 mục đều có ích, nhưng chừng mười mục nêu ở trên đủ để thay đổi cách bạn làm trong tuần đầu ở khách hàng.

**Thử ngay tuần này:**

- Clone kho bslatkin/effectivepython, chạy ví dụ của Item 32 và Item 30. Sau đó grep codebase gần nhất bạn phải đọc để tìm các chỗ 'except Exception' và 'return None'.
- Chọn một hàm chậm trong dự án hiện tại và profile nó trước khi sửa (Item 92). Sửa xong thì dùng timeit so sánh bản cũ với bản mới (Item 93).
- Viết một integration test bao quanh một luồng chưa có test, rồi mới refactor bên trong (Item 109).

## Nguồn

- [Effective Python: 125 Specific Ways to Write Better Python, 3rd Edition (InformIT)](https://www.informit.com/store/effective-python-125-specific-ways-to-write-better-9780138172183)

- [Effective Python: 125 Specific Ways to Write Better Python, 3rd Edition (effectivepython.com)](https://effectivepython.com)

- [bslatkin/effectivepython (GitHub)](https://github.com/bslatkin/effectivepython)
