Juggling the Real World — Làm sao để code chạy giữa đời thực? (Pragmatic Programmer #29)
Chào mấy bạn! Lại là mình đây — Phong đang ngồi đọc tiếp cuốn The Pragmatic Programmer (20th Anniversary Edition) và cảm xúc vẫn còn nguyên như hồi mới mở sách ra. Hôm nay tụi mình cùng khám phá Chapter 5: Bend, or Break — chương này nói về sự linh hoạt trong code, và Topic 29: Juggling the Real World (tạm dịch: Tung hứng giữa thế giới thực).
Một cái title nghe có vẻ rất… tung hứng đúng không? Nhưng mà đúng thiệt đó, vì code của mình không chạy trong môi trường lý tưởng đâu. Internet lag, user click loạn xạ, sensor gửi tín hiệu liên tục, database thì lúc nào cũng có hàng tá request. Làm sao để code mình vẫn chạy ngon, không crash, không leak? Đó chính xác là câu hỏi mà topic này giải đáp.
Ảnh: Los Muertos Crew — Pexels
Juggling the Real World — Code phải biết tung hứng
Tác giả mở đầu topic này bằng một nhận xét rất thực tế: "An event represents the availability of information." — một sự kiện đơn giản là thông tin vừa có sẵn. Có thể là user click chuột, cảm biến báo nhiệt độ, network packet vừa tới, hay một message từ Kafka. Điểm chung là: mọi thứ xảy ra không đồng bộ (asynchronous), và code của mình phải biết cách phản ứng (react) lại.
Cuốn sách giới thiệu bốn chiến lược để code của bạn có thể "tung hứng" tốt giữa thế giới đầy sự kiện này:
1. Finite State Machines (FSM)
FSM là một trong những khái niệm cơ bản nhất mà mình nghĩ ai học CS cũng từng gặp. Bạn có một tập các state (trạng thái), một state hiện tại, và các event (sự kiện) sẽ kích hoạt việc chuyển state.
Cái hay của FSM là nó cực kỳ dễ suy luận. Bạn có thể vẽ hết tất cả trạng thái ra giấy, nhìn vào đó và biết chính xác chương trình sẽ làm gì ở mỗi tình huống. Trong sách có ví dụ về parse string — một state machine nhỏ xinh để tìm tất cả các chuỗi trong dấu ngoặc kép. Dễ đọc, dễ maintain, dễ test.
Mình thấy FSM rất hữu ích cho: workflow approval, game logic, network protocol parsing, UI navigation flows.
Ảnh: Christina Morillo — Pexels
2. Observer Pattern
Pattern này thì quá quen thuộc với dân frontend rồi — một Observable (đối tượng được quan sát) giữ một danh sách các Observer (người quan sát) và thông báo cho họ khi có sự kiện xảy ra. JavaScript EventEmitter, DOM events, hay RxJS Subjects… tất cả đều dựa trên pattern này.
Tuy nhiên, sách cũng chỉ ra hai điểm yếu của Observer: (1) Observers phải đăng ký trực tiếp với Observable — tạo ra một dạng coupling; (2) callbacks chạy đồng bộ (synchronous), có thể gây nghẽn cổ chai nếu có quá nhiều observers.
3. Publish / Subscribe (Pub/Sub)
Pub/Sub là bước tiến hóa của Observer. Thay vì observers đăng ký trực tiếp với observable, giờ đây có một kênh (channel) trung gian. Publisher đẩy message lên channel, subscriber lắng nghe channel đó. Cả hai không biết mặt nhau, hoàn toàn decoupled.
Trong thực tế, Kafka, RabbitMQ, Redis Pub/Sub, AWS SNS/SQS đều hoạt động theo cơ chế này. Sách nhấn mạnh: "Pubsub is a good technology for decoupling the handling of asynchronous events. It allows code to be added and replaced, potentially while the application is running, without altering existing code."
Nhưng cũng có cái giá phải trả: càng xài Pub/Sub nhiều, hệ thống càng khó debug. Bạn publish một event, nhưng không dễ để biết ai đang subscribe nó. Traceability (khả năng truy vết) trở thành thách thức.
4. Reactive Programming & Streams
Đây là cách tiếp cận hiện đại nhất. Thay vì xử lý từng event riêng lẻ, bạn xem các event như một dòng dữ liệu (stream) và áp dụng các phép biến đổi (transform, filter, merge, combine) lên stream đó.
Sách lấy ví dụ rất dễ hiểu: giống như Google Sheets — bạn gõ công thức =A1+B1 vào ô C1, khi A1 thay đổi, C1 tự động cập nhật. Đó là reactive! Trong lập trình, các thư viện như RxJS, Project Reactor, hay Kafka Streams giúp bạn làm điều tương tự với dữ liệu.
Ảnh: Lisa — Pexels
Cảm nhận của mình
Cá nhân mình thấy Topic 29 này là một trong những topic thực tế nhất của cuốn sách tính tới giờ. Lý do? Vì hầu như ngày nào mình cũng phải đối mặt với events — user click, API response, websocket message, database trigger… Nếu không có framework hay strategy nào, code sẽ nhanh chóng biến thành mớ spaghetti.
Điều mình tâm đắc nhất là cách tác giả không chỉ liệt kê bốn pattern, mà còn chỉ ra trade-off của từng cái. FSM thì rõ ràng nhưng không scale tốt với quá nhiều state. Observer thì đơn giản nhưng gây coupling. Pub/Sub thì linh hoạt nhưng khó debug. Reactive Streams thì mạnh mẽ nhưng learning curve cao.
Mình từng làm một dự án IoT nhỏ, dùng MQTT (Pub/Sub) để nhận dữ liệu từ cảm biến. Lúc đầu rất sướng — thêm sensor mới chỉ cần publish lên topic mới. Nhưng tới lúc debug cả đống sensor cùng gửi tín hiệu lộn xộn, mình gần như phát điên vì không biết cái nào đang trigger cái nào. Giờ nghĩ lại, nếu kết hợp Pub/Sub với một FSM nhỏ để quản lý trạng thái thiết bị, chắc mọi chuyện đã dễ thở hơn nhiều.
Tip cuối cùng trong topic này: đừng chỉ dùng một pattern. Kết hợp chúng lại. Dùng Pub/Sub để decouple communication, FSM để quản lý state, Reactive Streams để xử lý data flow — đó mới là cách "tung hứng" thực sự.
Kết
Topic 29 dạy mình rằng: code không thể đứng yên giữa một thế giới luôn chuyển động. Cũng như người tung hứng phải liên tục đưa tay đón bóng, phần mềm phải được thiết kế để phản ứng và thích nghi với hàng loạt sự kiện bất ngờ từ thế giới thực.
Bốn công cụ — FSM, Observer, Pub/Sub, Reactive Streams — mỗi cái có điểm mạnh riêng, và một pragmatic developer biết khi nào nên dùng cái nào. Hy vọng bài viết này giúp mấy bạn có thêm perspective về cách xử lý sự kiện trong code của mình.
Hẹn gặp lại ở bài sau — chúng ta sẽ nói tiếp về Topic 30: Transforming Programming!