<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Error Handling on miti99</title><link>https://miti99.com/tags/error-handling/</link><description>Recent content in Error Handling on miti99</description><generator>Hugo -- gohugo.io</generator><language>vi</language><lastBuildDate>Wed, 08 Jul 2026 09:22:18 +0700</lastBuildDate><atom:link href="https://miti99.com/tags/error-handling/index.xml" rel="self" type="application/rss+xml"/><item><title>Newsletter #89</title><link>https://miti99.com/post/2026/03/14/</link><pubDate>Sat, 14 Mar 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/03/14/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #89.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="hybrid-quota-linear-rate-limiter"&gt;&lt;a class="link" href="https://dotat.at/@/2026-01-12-hqlr.html" target="_blank" rel="noopener"
&gt;Hybrid Quota-Linear Rate Limiter&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Tony Finch phân tích một vấn đề phổ biến với các thuật toán giới hạn tốc độ (rate limiter) tuyến tính như leaky bucket và GCRA: chúng có xu hướng &amp;ldquo;hào phóng&amp;rdquo; hơn mức cần thiết trong giai đoạn khởi động, cho phép client gửi khoảng &lt;code&gt;q*a/(a-1)&lt;/code&gt; yêu cầu trước khi bị giới hạn khi hoạt động ở tốc độ gấp &lt;code&gt;a&lt;/code&gt; lần cho phép.&lt;/p&gt;
&lt;p&gt;Để giải quyết vấn đề này, tác giả đề xuất một thuật toán lai hoạt động ở hai chế độ: &lt;strong&gt;chế độ bursty&lt;/strong&gt; dành cho các client có lưu lượng thấp, cho phép gửi theo đợt nhưng giới hạn tối đa một quota token mỗi cửa sổ thời gian; và &lt;strong&gt;chế độ smooth&lt;/strong&gt; dành cho client có lưu lượng cao, buộc phân phối yêu cầu đều đặn hơn. Thuật toán tự chuyển đổi giữa hai chế độ khi client tiêu hết quota hoặc khi bucket phục hồi hoàn toàn. Cách tiếp cận này sử dụng ít bộ nhớ hơn so với phương pháp quota-reset, đồng thời kiểm soát quota chặt chẽ hơn so với rate limiter tuyến tính thuần túy.&lt;/p&gt;
&lt;p&gt;Tuy nhiên, điểm thú vị là tác giả cuối cùng khuyên &lt;strong&gt;không nên&lt;/strong&gt; dùng thuật toán phức tạp này. Thay vào đó, hãy cân nhắc lại bài toán để có thể sử dụng một rate limiter tuyến tính đơn giản như GCRA — vì việc đo tốc độ trung bình theo thời gian dài tự nhiên khuyến khích client hoạt động mượt mà hơn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rate limiter tuyến tính (GCRA, leaky bucket) có thể cho phép nhiều yêu cầu hơn dự kiến trong giai đoạn khởi động&lt;/li&gt;
&lt;li&gt;Thuật toán lai kết hợp chế độ bursty và smooth để kiểm soát chặt chẽ hơn&lt;/li&gt;
&lt;li&gt;Đơn giản hóa vẫn là ưu tiên hàng đầu: GCRA đủ tốt cho hầu hết các trường hợp thực tế&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="go-is-the-best-language-for-ai-agents"&gt;&lt;a class="link" href="https://getbruin.com/blog/go-is-the-best-language-for-agents/" target="_blank" rel="noopener"
&gt;Go is the Best Language for AI Agents&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Burak Karakan — CEO của Bruin, người có 8 năm kinh nghiệm lập trình Go — lập luận rằng Go là ngôn ngữ lý tưởng để phát triển phần mềm bằng AI agent. Lý do cốt lõi xuất phát từ bản chất của ngôn ngữ biên dịch: khi agent tạo ra mã nguồn Go bị lỗi, trình biên dịch sẽ trả về thông báo lỗi rõ ràng ngay lập tức, tạo vòng phản hồi nhanh và chính xác để agent tự sửa lỗi. Điều này khác hoàn toàn so với Python hay JavaScript — vốn chỉ báo lỗi khi chạy thực tế.&lt;/p&gt;
&lt;p&gt;Hệ thống kiểu tĩnh của Go cũng là một lợi thế lớn: agent có thể xác minh tính đúng đắn của mã trước khi chạy, thay vì phải phụ thuộc vào các bài kiểm thử thủ công. So với Rust — ngôn ngữ biên dịch khác — Go vẫn chiếm ưu thế nhờ cú pháp đơn giản hơn, tốc độ biên dịch nhanh hơn, và lượng mã Go trong dữ liệu huấn luyện của các mô hình AI lớn hơn đáng kể. Ngoài ra, bộ kiểm thử của Go chạy cực nhanh và hoạt động nhất quán trên mọi hệ điều hành, giúp các background agent có thể xác nhận kết quả công việc mà không phụ thuộc vào môi trường chạy cụ thể.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go biên dịch nhanh và báo lỗi rõ ràng giúp AI agent có vòng phản hồi ngắn để tự sửa lỗi&lt;/li&gt;
&lt;li&gt;Kiểu tĩnh và cú pháp đơn giản giảm thiểu sai sót trong mã do agent tạo ra&lt;/li&gt;
&lt;li&gt;Go chạy nhất quán trên mọi nền tảng — lợi thế lớn cho background agent và môi trường sandbox&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="your-agent-needs-a-harness-not-a-framework"&gt;&lt;a class="link" href="https://x.com/djfarrelly/status/2028556984396452250" target="_blank" rel="noopener"
&gt;Your Agent Needs a Harness, Not a Framework&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Dan Farrelly từ Inngest.com đưa ra một góc nhìn sắc bén về kiến trúc AI agent: thay vì dùng framework, agent cần một &amp;ldquo;harness&amp;rdquo; — lớp kết nối, bảo vệ và điều phối các thành phần mà không làm thay công việc của chúng. Trong mọi ngành kỹ thuật, harness đều có vai trò như vậy: dây điện nối các cảm biến, test harness cung cấp giàn giáo kiểm thử, dây an toàn giữ bạn khi ngã. Agent runtime cũng cần điều tương tự.&lt;/p&gt;
&lt;p&gt;Vấn đề hiện tại là mỗi framework agent lại tự xây dựng lại từ đầu: retry logic, lưu trạng thái, job queue, định tuyến sự kiện. Thay vào đó, hãy dùng hạ tầng event-driven bền vững đã sẵn có — mỗi lần gọi LLM hay công cụ trở thành một &amp;ldquo;step&amp;rdquo; có thể thử lại độc lập. Nếu tiến trình chết ở vòng lặp thứ năm, bốn vòng trước đã được lưu lại. Nhóm tác giả đã xây dựng &lt;strong&gt;Utah&lt;/strong&gt; (Universally Triggered Agent Harness) để minh chứng: một agent hội thoại qua Telegram/Slack với công cụ, bộ nhớ, ủy quyền sub-agent và khả năng chịu lỗi — chỉ dùng Inngest, không framework. Bài học quan trọng rút ra: quản lý ngữ cảnh (context management) mới là thử thách thực sự, không phải việc gọi LLM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agent cần &amp;ldquo;harness&amp;rdquo; để xử lý retry, trạng thái, concurrency — không phải thêm framework&lt;/li&gt;
&lt;li&gt;Mỗi lần gọi LLM/công cụ là một step độc lập, có thể thử lại mà không mất kết quả trước&lt;/li&gt;
&lt;li&gt;Quản lý ngữ cảnh (cắt bớt, nén lịch sử, cảnh báo ngân sách) là thách thức lớn nhất khi xây agent thực tế&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="an-interactive-intro-to-quadtrees"&gt;&lt;a class="link" href="https://growingswe.com/blog/quadtrees" target="_blank" rel="noopener"
&gt;An Interactive Intro to Quadtrees&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết tương tác giải thích quadtree — cấu trúc dữ liệu dùng để tìm kiếm không gian 2D hiệu quả. Vấn đề đặt ra: khi xây dựng ứng dụng bản đồ với hàng triệu địa điểm, cách đơn giản nhất là tính khoảng cách từ vị trí người dùng đến từng điểm rồi lọc ra những điểm đủ gần — nhưng với hàng triệu điểm, cách này quá chậm.&lt;/p&gt;
&lt;p&gt;Quadtree giải quyết bài toán bằng cách chia không gian thành 4 ô con khi một ô chứa quá nhiều điểm (vượt ngưỡng capacity), tạo thành cây phân cấp. Khi truy vấn, chỉ cần đệ quy vào các ô giao với vùng tìm kiếm — bỏ qua hoàn toàn phần còn lại. Tham số capacity điều chỉnh sự cân bằng: capacity thấp tạo cây sâu, bỏ qua nhiều vùng hơn nhưng tốn bộ nhớ; capacity cao tạo cây nông, tiết kiệm bộ nhớ nhưng mỗi ô cần duyệt tuyến tính nhiều điểm hơn. Ngoài tìm kiếm địa lý (Uber dùng để tìm tài xế gần nhất), quadtree còn ứng dụng trong phát hiện va chạm game (broad-phase collision detection) và nén ảnh theo vùng.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quadtree chia không gian 2D thành 4 ô con đệ quy, giúp bỏ qua vùng không liên quan khi truy vấn&lt;/li&gt;
&lt;li&gt;Tham số capacity (thường từ 4–16) ảnh hưởng trực tiếp đến độ sâu cây và hiệu năng truy vấn&lt;/li&gt;
&lt;li&gt;Ứng dụng thực tế: tìm kiếm địa lý, phát hiện va chạm trong game, nén ảnh theo vùng&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="agentic-engineering-patterns"&gt;&lt;a class="link" href="https://simonwillison.net/guides/agentic-engineering-patterns/" target="_blank" rel="noopener"
&gt;Agentic Engineering Patterns&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Simon Willison — tác giả blog kỹ thuật nổi tiếng — ra mắt một hướng dẫn toàn diện về các mẫu thiết kế (pattern) khi làm việc với AI agent trong lập trình. Điều đáng chú ý là hướng dẫn phân biệt rõ ràng giữa &amp;ldquo;agentic engineering&amp;rdquo; và &amp;ldquo;vibe coding&amp;rdquo;: vibe coding là để LLM viết mã mà không cần giám sát, còn agentic engineering là kỹ sư chuyên nghiệp dùng agent để khuếch đại chuyên môn sẵn có của mình — không phải thay thế nó.&lt;/p&gt;
&lt;p&gt;Một số pattern nổi bật: &lt;strong&gt;&amp;ldquo;Viết mã giờ rất rẻ&amp;rdquo;&lt;/strong&gt; — chi phí sinh mã gần như bằng không, buộc phải định nghĩa lại &amp;ldquo;mã tốt&amp;rdquo; là mã có kiểm thử, tài liệu, xử lý lỗi đúng chuẩn; &lt;strong&gt;&amp;ldquo;Tích lũy những gì bạn đã biết làm&amp;rdquo;&lt;/strong&gt; — duy trì kho ví dụ hoạt động được (blog, repo, POC) để dùng làm đầu vào cho agent, vì biết rằng điều gì đó khả thi khác với đã tự mình làm được; &lt;strong&gt;&amp;ldquo;Red/green TDD&amp;rdquo;&lt;/strong&gt; — viết kiểm thử trước, xác nhận kiểm thử thất bại, rồi mới triển khai, đảm bảo agent tạo ra mã đúng chức năng và tối giản. Hướng dẫn được cập nhật liên tục mỗi tuần với 1–2 chương mới.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Agentic engineering là khuếch đại chuyên môn kỹ sư, không phải để LLM viết mã không kiểm soát&lt;/li&gt;
&lt;li&gt;Tích lũy ví dụ hoạt động được là tài sản nghề nghiệp quan trọng khi làm việc với agent&lt;/li&gt;
&lt;li&gt;Red/green TDD giúp agent tạo mã tối giản, đúng chức năng và có lưới an toàn kiểm thử&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="things-i-miss-about-spring-boot-after-switching-to-go"&gt;&lt;a class="link" href="https://sushantdhiman.dev/things-i-miss-about-spring-boot-after-switching-to-go/" target="_blank" rel="noopener"
&gt;Things I Miss About Spring Boot After Switching to Go&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Sushant Dhiman chia sẻ góc nhìn thực tế sau khi chuyển từ Spring Boot sang Go. Dù Go có nhiều ưu điểm, tác giả thừa nhận có những thứ trong Spring Boot mà Go chưa thể thay thế dễ dàng. Trước hết là triết lý &amp;ldquo;batteries included&amp;rdquo; của Spring Boot — hầu hết tính năng cần thiết cho môi trường production đều có sẵn và tích hợp chặt chẽ với nhau.&lt;/p&gt;
&lt;p&gt;Cụ thể, tác giả nhớ nhất: &lt;strong&gt;Dependency Injection&lt;/strong&gt; tự động qua annotation (&lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Autowired&lt;/code&gt;) thay vì phải tự nối dây thủ công qua constructor trong Go; &lt;strong&gt;validation khai báo&lt;/strong&gt; với &lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@Email&lt;/code&gt; thay vì viết điều kiện kiểm tra thủ công; và hệ sinh thái trưởng thành gồm Spring Security (xác thực JWT, OAuth), Spring Data (tự sinh câu truy vấn từ tên phương thức), Spring Boot Actuator (giám sát sức khỏe tích hợp sẵn), Spring Cloud (hỗ trợ microservices). Go có các thư viện tương đương nhưng thiếu sự gắn kết thống nhất trong một framework duy nhất.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring Boot tích hợp sẵn dependency injection, validation khai báo và hệ sinh thái production-ready&lt;/li&gt;
&lt;li&gt;Go theo triết lý tối giản — linh hoạt hơn nhưng đòi hỏi tự lắp ráp nhiều thành phần hơn&lt;/li&gt;
&lt;li&gt;Cả hai có đánh đổi rõ ràng: Spring Boot tiện lợi hơn, Go đơn giản và hiệu năng cao hơn&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="design-first-collaboration-with-ai"&gt;&lt;a class="link" href="https://martinfowler.com/articles/reduce-friction-ai/design-first-collaboration.html" target="_blank" rel="noopener"
&gt;Design-First Collaboration with AI&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết từ martinfowler.com giới thiệu phương pháp cộng tác &amp;ldquo;design-first&amp;rdquo; với AI để tránh &amp;ldquo;Implementation Trap&amp;rdquo; — bẫy mà AI sinh mã quá nhanh khiến các quyết định thiết kế quan trọng bị chôn vùi trong mã nguồn, và lập trình viên chỉ phát hiện ra bất đồng khi đọc lại mã — lúc đó đã quá tốn kém để sửa.&lt;/p&gt;
&lt;p&gt;Giải pháp là khung năm cấp độ, không được viết mã cho đến khi cấp độ 5 được phê duyệt: &lt;strong&gt;(1) Capabilities&lt;/strong&gt; — yêu cầu cốt lõi; &lt;strong&gt;(2) Components&lt;/strong&gt; — các khối xây dựng và trừu tượng; &lt;strong&gt;(3) Interactions&lt;/strong&gt; — luồng dữ liệu và giao tiếp; &lt;strong&gt;(4) Contracts&lt;/strong&gt; — chữ ký hàm và interface; &lt;strong&gt;(5) Implementation&lt;/strong&gt; — viết mã. Mỗi cấp độ chỉ kiểm tra một loại quyết định, giảm tải nhận thức đáng kể so với việc xem xét tất cả cùng lúc. Mức độ phức tạp quyết định điểm bắt đầu — tiện ích đơn giản có thể bắt đầu từ cấp 4, tính năng phức tạp bắt đầu từ cấp 1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Đừng để AI viết mã trước khi thống nhất thiết kế — phát hiện bất đồng sớm rẻ hơn rất nhiều&lt;/li&gt;
&lt;li&gt;Khung 5 cấp độ tách biệt từng loại quyết định thiết kế, giảm tải nhận thức khi xem xét&lt;/li&gt;
&lt;li&gt;Phương pháp này tạo điều kiện tự nhiên cho TDD và ngăn chặn &amp;ldquo;technical debt injection&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-two-kinds-of-error"&gt;&lt;a class="link" href="https://evanhahn.com/the-two-kinds-of-error/" target="_blank" rel="noopener"
&gt;The Two Kinds of Error&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Evan Hahn đề xuất một cách phân loại lỗi đơn giản nhưng hữu ích: lỗi &lt;strong&gt;expected&lt;/strong&gt; (có thể xảy ra trong vận hành bình thường, không phải lỗi của lập trình viên) và lỗi &lt;strong&gt;unexpected&lt;/strong&gt; (chỉ xảy ra khi có bug). Lỗi expected bao gồm: đầu vào của người dùng không hợp lệ, mất kết nối mạng, thiếu quyền truy cập — đây là lỗi có thể phục hồi, nên trả về kết quả lỗi và ghi log ở mức &lt;code&gt;WARN&lt;/code&gt; hoặc &lt;code&gt;INFO&lt;/code&gt;. Lỗi unexpected bao gồm: vi phạm assertion, logic sai, dependency chưa khởi tạo — đây là dấu hiệu của bug thực sự, nên log ở mức &lt;code&gt;ERROR&lt;/code&gt; hoặc &lt;code&gt;FATAL&lt;/code&gt;, thậm chí nên để chương trình crash hoàn toàn.&lt;/p&gt;
&lt;p&gt;Ranh giới giữa hai loại phụ thuộc vào ngữ cảnh: ứng dụng thử nghiệm có thể coi mọi lỗi là unexpected, hệ thống quan trọng (tàu vũ trụ) phải coi gần mọi thứ đều có thể xảy ra. Điều quan trọng là lập trình viên phải &lt;strong&gt;có ý thức&lt;/strong&gt; phân loại từng lỗi thay vì xử lý tất cả theo cùng một cách.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lỗi expected (mạng, đầu vào người dùng) nên được phục hồi và log WARN/INFO&lt;/li&gt;
&lt;li&gt;Lỗi unexpected (bug, logic sai) nên log ERROR/FATAL và có thể crash chương trình&lt;/li&gt;
&lt;li&gt;Phân loại có ý thức giúp thiết kế xử lý lỗi rõ ràng và đúng mức độ nghiêm trọng&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="secure-go-error-handling-best-practices"&gt;&lt;a class="link" href="https://blog.jetbrains.com/go/2026/03/02/secure-go-error-handling-best-practices/" target="_blank" rel="noopener"
&gt;Secure Go Error Handling Best Practices&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Blog JetBrains GoLand tập trung vào một khía cạnh thường bị bỏ qua: xử lý lỗi an toàn bảo mật trong Go. Nguyên tắc cốt lõi là &lt;strong&gt;tách biệt thông tin nội bộ và thông tin công khai&lt;/strong&gt; — tạo kiểu lỗi tùy chỉnh chứa cả thông báo nội bộ (chi tiết kỹ thuật cho lập trình viên) và thông báo công khai (thông báo an toàn cho người dùng), tránh việc lỗi nội bộ bị serialized ra HTTP response.&lt;/p&gt;
&lt;p&gt;Các thực hành quan trọng: dùng &lt;strong&gt;builder pattern&lt;/strong&gt; để kiểm soát trường metadata nào được ghi log, tránh ghi toàn bộ đối tượng request có thể chứa mật khẩu hay token; dùng &lt;strong&gt;opaque wrapping&lt;/strong&gt; thay vì &lt;code&gt;fmt.Errorf&lt;/code&gt; khi không muốn lộ chi tiết triển khai cho phía gọi; và khi truyền lỗi qua ranh giới dịch vụ, chuyển đổi sang lỗi chuẩn hóa (gRPC codes, JSON format), không bao giờ để lỗi do AI sinh ra tiếp cận API công khai. Logging nên dùng &lt;code&gt;log/slog&lt;/code&gt; với structured logging và implement interface redaction cho các trường nhạy cảm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tách lỗi thành thông báo nội bộ (kỹ thuật) và công khai (an toàn) để tránh rò rỉ thông tin&lt;/li&gt;
&lt;li&gt;Kiểm soát chặt metadata được ghi log, không ghi toàn bộ request object&lt;/li&gt;
&lt;li&gt;API công khai chỉ trả về thông báo tĩnh, định nghĩa sẵn — không bao giờ lỗi được sinh động&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ai-coding-tools-java--compounding-engineering"&gt;&lt;a class="link" href="https://www.the-main-thread.com/p/ai-coding-tools-java-compounding-engineering" target="_blank" rel="noopener"
&gt;AI Coding Tools, Java &amp;amp; Compounding Engineering&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết tổng hợp từ buổi nói chuyện tại NDC Manchester 2025, với luận điểm cốt lõi: &amp;ldquo;Nếu AI tạo ra mã tệ cho bạn, đó thường là vấn đề workflow, không phải vấn đề mô hình.&amp;rdquo; Phương pháp được đề xuất là &lt;strong&gt;Compounding Engineering&lt;/strong&gt; — huấn luyện AI như người cộng tác theo thời gian, tương tự onboard lập trình viên junior.&lt;/p&gt;
&lt;p&gt;Các yếu tố chính: xóa chat thường xuyên và lưu quyết định kiến trúc vào file markdown (như &lt;code&gt;CLAUDE.md&lt;/code&gt;) thay vì cố duy trì ngữ cảnh trong một cuộc trò chuyện dài; cập nhật &amp;ldquo;living rule files&amp;rdquo; mỗi khi sửa bug để lỗi không tái diễn; lên kế hoạch trước khi viết mã để phát hiện giả định sai ngay từ đầu; chia tính năng thành các tác vụ nhỏ, tập trung một trách nhiệm. Kỹ sư senior đóng góp giá trị qua &lt;strong&gt;taste&lt;/strong&gt; — khả năng đánh giá kiến trúc và đánh đổi, điều mà AI giỏi sinh mã nhưng yếu phán xét chất lượng. Kết quả tích lũy theo thời gian; không đầu tư vào workflow thì chỉ có &amp;ldquo;mediocrity nhanh hơn&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mã AI kém chất lượng thường do workflow kém, không phải do mô hình&lt;/li&gt;
&lt;li&gt;Lưu quyết định kiến trúc vào file markdown, cập nhật khi sửa bug để kiến thức tích lũy&lt;/li&gt;
&lt;li&gt;Giá trị của kỹ sư senior nằm ở khả năng phán xét kiến trúc — thứ AI chưa làm tốt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="yaml-that"&gt;&lt;a class="link" href="https://lab174.com/blog/202601-yaml-norway/" target="_blank" rel="noopener"
&gt;YAML? That&amp;rsquo;s Norway Problem&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết khám phá một lỗi phân tích cú pháp thú vị trong YAML: mã quốc gia &lt;code&gt;NO&lt;/code&gt; (Na Uy) bị các trình phân tích cú pháp phổ biến chuyển thành giá trị boolean &lt;code&gt;false&lt;/code&gt; thay vì chuỗi ký tự. Nguyên nhân là YAML xử lý một số từ tiếng Anh như &lt;code&gt;yes/no&lt;/code&gt;, &lt;code&gt;true/false&lt;/code&gt;, &lt;code&gt;on/off&lt;/code&gt; dưới dạng giá trị boolean — tính năng này được giới thiệu từ YAML v1.0 (2004) nhằm làm cho tệp cấu hình dễ đọc hơn.&lt;/p&gt;
&lt;p&gt;Dù YAML v1.2 (2009) đã loại bỏ hành vi này để tương thích với JSON, hầu hết các thư viện phổ biến như PyYAML và LibYAML vẫn triển khai theo đặc tả v1.1 — tạo ra khoảng cách lớn giữa tiêu chuẩn và thực tế sau hơn 15 năm. Người dùng có thể tránh vấn đề này bằng cách đặt chuỗi trong dấu ngoặc kép (&lt;code&gt;&amp;quot;NO&amp;quot;&lt;/code&gt;), dùng thẻ kiểu tường minh (&lt;code&gt;!!str&lt;/code&gt;), hoặc chuyển sang thư viện tương thích v1.2 như &lt;code&gt;ruamel.yaml&lt;/code&gt; hay &lt;code&gt;yq&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NO&lt;/code&gt; trong YAML bị hiểu là &lt;code&gt;false&lt;/code&gt; do quy tắc boolean từ đặc tả v1.1&lt;/li&gt;
&lt;li&gt;YAML v1.2 đã sửa lỗi này từ năm 2009 nhưng hầu hết thư viện chưa cập nhật&lt;/li&gt;
&lt;li&gt;Giải pháp: dùng dấu ngoặc kép, thẻ &lt;code&gt;!!str&lt;/code&gt;, hoặc thư viện tương thích v1.2&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được review và cập nhật bởi Claude Code với Opus 4.7 (1M context).&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #77</title><link>https://miti99.com/post/2026/01/24/</link><pubDate>Sat, 24 Jan 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/01/24/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #77.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="đừng-chuyển-tiếp-lỗi-hãy-thiết-kế-chúng"&gt;&lt;a class="link" href="https://fast.github.io/blog/stop-forwarding-errors-start-designing-them/" target="_blank" rel="noopener"
&gt;Đừng Chuyển Tiếp Lỗi, Hãy Thiết Kế Chúng&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết này phân tích sâu về các vấn đề trong xử lý lỗi hiện nay, đặc biệt là trong hệ sinh thái Rust. Tác giả chỉ ra rằng chúng ta thường &amp;ldquo;chuyển tiếp&amp;rdquo; lỗi hơn là thực sự &amp;ldquo;thiết kế&amp;rdquo; chúng - bắt lỗi, bọc (đôi khi), và ném lên stack nhanh nhất có thể. Điều này dẫn đến việc lỗi vẫn giữ nguyên thông điệp gốc nhưng mất đi toàn bộ ngữ cảnh quan trọng.&lt;/p&gt;
&lt;p&gt;Bài viết phê phán các cách tiếp cận phổ biến như &lt;code&gt;thiserror&lt;/code&gt; (phân loại theo nguồn gốc chứ không phải hành động), &lt;code&gt;anyhow&lt;/code&gt; (quá tiện lợi khiến lập trình viên quên thêm ngữ cảnh), stack traces (tốn kém và chỉ hiện điểm xuất phát chứ không phải đường đi logic), và API Provide/Request (quá phức tạp với hành vi khó dự đoán). Thay vào đó, tác giả đề xuất xử lý lỗi nên được thiết kế với hai đối tượng: máy móc (cần cấu trúc phẳng, các loại rõ ràng, mã có thể dự đoán cho việc tự động phục hồi) và con người (cần ngữ cảnh phong phú, đường đi luồng, thông tin cấp độ kinh doanh để gỡ lỗi).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xử lý lỗi nên được thiết kế dựa trên ý định: &amp;ldquo;người gọi có thể làm gì với lỗi này?&amp;rdquo; thay vì &amp;ldquo;lỗi này đến từ đâu?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Cho máy móc: sử dụng các loại lỗi phẳng dựa trên phân loại với trạng thái rõ ràng (Tạm thời/Liên tục/ persists) thay vì chuỗi lỗi lồng nhau&lt;/li&gt;
&lt;li&gt;Cho con người:捕获 ngữ cảnh tự động và thuận tiện, bắt buộc tại ranh giới module để không thể lười biếng&lt;/li&gt;
&lt;li&gt;Mô hình thực tế: kết hợp cấu trúc lỗi phân loại với cơ chế theo dõi ngữ cảnh (như thư viện &lt;code&gt;exn&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Sử dụng &lt;code&gt;#[track_caller]&lt;/code&gt; để捕获 vị trí với chi phí bằng không thay vì stack traces đắt đỏ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="đồng-bộ-hóa-thời-gian-là-cơn-ác-mộng"&gt;&lt;a class="link" href="https://arpitbhayani.me/blogs/clock-sync-nightmare/" target="_blank" rel="noopener"
&gt;Đồng Bộ Hóa Thời Gian Là Cơn Ác Mộng&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Đồng hồ là một thứ tưởng đơn giản nhưng lại là cơn ác mộng của các kỹ sư phần mềm làm việc với hệ thống phân tán. Bài viết này giải thích tại sao không có &amp;ldquo;đồng hồ toàn cục&amp;rdquo; và cách hàng ngàn máy tính rải rác across nhiều data centers bị trôi drift apart theo thời gian. Mỗi máy tính có đồng hồ nội tại dựa trên crystal thạch anh với tần số 32768 Hz, nhưng những crystal này không hoàn hảo - sự chênh lệch nhiệt độ chỉ khoảng 10°C có thể gây ra trôi drift khoảng 110 giây mỗi năm. Kết quả là hai máy tính khởi động cùng thời gian sẽ bị lệch nhau hàng trăm mili-giây chỉ sau một ngày.&lt;/p&gt;
&lt;p&gt;Sự lệch đồng hồ gây ra nhiều vấn đề nghiêm trọng. Ví dụ với hệ thống make phân tán, nếu đồng hồ máy client bị chậm và đồng hồ máy server chạy nhanh, file object đã biên dịch sẽ trông mới hơn file source bạn vừa edit, khiến lệnh make không biên dịch lại và những thay đổi của bạn bị bỏ qua. Hệ thống cơ sở dữ liệu còn nghiêm trọng hơn - trong hệ thống ngân hàng, nếu giao dịch rút tiền có timestamp sớm hơn giao dịch nộp tiền do đồng hồ không đồng bộ, snapshot read có thể hiển thị giao dịch rút nhưng không hiển thị giao dịch nộp, khiến khách hàng trông như đã rút tiền họ không có. Tác giả cũng thảo luận các giải pháp: Thuật toán Cristian (ước lượng độ trễ một chiều bằng một nửa vòng đi vòng về), Thuật toán Berkeley (đồng thuận giữa các máy, tính trung bình và gửi các điều chỉnh tương đối thay vì thời gian tuyệt đối), NTP với hệ thống phân tầng phân cấp (Stratum 0 là đồng hồ nguyên tử/GPS, Stratum 1-15 là các lớp phía dưới), và PTP (Giao thức Thời Gián Chính Xác) với đánh dấu thời gian bằng phần cứng đạt độ chính xác dưới micro-giây.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clock skew = sự khác biệt giữa hai đồng hồ tại một thời điểm, Clock drift = tốc độ các đồng hồ phân kỳ theo thời gian&lt;/li&gt;
&lt;li&gt;NTP đạt độ chính xác 10-100ms trên internet công cộng, 100-500µs trên mạng LAN, nhưng bị giới hạn bởi sự bất đối xứng mạng&lt;/li&gt;
&lt;li&gt;PTP sử dụng đánh dấu thời gian bằng phần cứng tại tầng NIC để đạt độ chính xác nano-giây, nhưng cần các switch hỗ trợ PTP&lt;/li&gt;
&lt;li&gt;Đồng hồ logic (Lamport Timestamps, Vector Clocks)捕获 quan hệ nhân quả thay vì thời gian vật lý&lt;/li&gt;
&lt;li&gt;Google Spanner&amp;rsquo;s TrueTime sử dụng GPS + đồng hồ nguyên tử, trả về khoảng [sớm nhất, muộn nhất] thay vì timestamp đơn&lt;/li&gt;
&lt;li&gt;Đồng hồ logic lai (CockroachDB, YugabyteDB) kết hợp thời gian vật lý với thành phần logic&lt;/li&gt;
&lt;li&gt;Giây nhuận sẽ bị loại bỏ vào năm 2035, nhưng hiện tại Google/AWS dùng &amp;ldquo;kéo dài giây nhuận&amp;rdquo; để trải đều qua nhiều giờ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="roadmap-học-java"&gt;&lt;a class="link" href="https://nemorize.com/roadmaps/java" target="_blank" rel="noopener"
&gt;Roadmap Học Java&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Đây là một lộ trình học Java toàn diện từ cơ bản đến nâng cao. Roadmap được chia thành năm phần chính: Nền tảng (Linux, Git, IDE), Lập trình hướng đối tượng (classes, objects, inheritance, polymorphism, encapsulation, abstraction), Java cốt lõi (Collections Framework, Exception Handling, Streams), Các khái niệm nâng cao (Concurrency &amp;amp; Multithreading, JVM internals, Design Patterns, Dependency Injection), và Phát triển chuyên nghiệp (Testing, Build Tools như Maven &amp;amp; Gradle, Databases), đặc biệt là Spring Boot Framework với REST APIs, Clean Code và SOLID principles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bắt đầu với nền tảng: Linux basics, Git version control, và chọn IDE phù hợp (IntelliJ, Eclipse, VSCode)&lt;/li&gt;
&lt;li&gt;Nắm vững OOP fundamentals: classes, objects, inheritance, polymorphism, encapsulation, abstraction&lt;/li&gt;
&lt;li&gt;Thành thạo Collections Framework: Lists, Sets, Maps, Queues, Optionals, Streams&lt;/li&gt;
&lt;li&gt;Hiểu sâu về Exception Handling: checked vs unchecked exceptions, custom exceptions&lt;/li&gt;
&lt;li&gt;Học Concurrency &amp;amp; Multithreading: thread basics, concurrent collections, ExecutorService, thread pools&lt;/li&gt;
&lt;li&gt;Tìm hiểu JVM internals và Design patterns phổ biến&lt;/li&gt;
&lt;li&gt;Làm việc với build tools (Maven, Gradle) và databases (JDBC, schema migration)&lt;/li&gt;
&lt;li&gt;Phát triển Enterprise Java với Spring Boot: Spring Core, Spring Data, JPA, REST APIs&lt;/li&gt;
&lt;li&gt;Áp dụng Clean Code, SOLID principles, và best practices về API design, logging, monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hướng-dẫn-toàn-diện-triển-khai-kotlin-trong-môi-trường-java"&gt;&lt;a class="link" href="https://blog.jetbrains.com/kotlin/2025/12/the-ultimate-guide-to-successfully-adopting-kotlin-in-a-java-dominated-environment/" target="_blank" rel="noopener"
&gt;Hướng Dẫn Toàn Diện Triển Khai Kotlin Trong Môi Trường Java&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Đây là hướng dẫn hoàn chỉnh để triển khai Kotlin từng bước, từ những bài kiểm tra ban đầu đến triển khai quy mô lớn. Adopting Kotlin trong công ty thiên về Java không phải là flipping a switch hay viết lại mọi thứ, mà là về con người, thời điểm, rủi ro và sự tin tưởng. Guide bao gồm năm phần: Bắt đầu với Kotlin cho Java developers (viết tests bằng Kotlin, dùng Kotest và MockK), Đánh giá Kotlin trong projects thực tế (start new service in Kotlin, add Kotlin modules, tránh &amp;ldquo;Java in Kotlin syntax&amp;rdquo; trap), Phát triển việc adoption Kotlin trong công ty (share examples, starter repository, pairing sessions, build community), Giúp decision-makers nói yes với Kotlin (less code, fewer defects, no rewriting, developer happiness, predictable costs), và Scaling adoption across organization (treat systems differently based on lifecycle, dùng right tools như IDE conversion, null-safety annotations, AI-assisted refactoring).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bắt đầu nhỏ: introduce Kotlin thông qua test suite để không ảnh hưởng production&lt;/li&gt;
&lt;li&gt;Tránh &amp;ldquo;Java in Kotlin syntax&amp;rdquo; trap: dùng extension functions thay vì static helpers, nullable types thay vì Optional, data classes thay vì boilerplate&lt;/li&gt;
&lt;li&gt;Xây dựng community trong nội bộ: share starter repository, tổ chức clinics và pairing sessions&lt;/li&gt;
&lt;li&gt;Thuyết phục managers: less code để maintain, fewer defects nhờ null safety, no rewriting required nhờ full Java interop&lt;/li&gt;
&lt;li&gt;Scaling strategy: leave end-of-life apps, default new builds to Kotlin, migrate active systems step-by-step không phải big-bang rewrites&lt;/li&gt;
&lt;li&gt;Sử dụng tools phù hợp: IDE conversion, JSpecify null-safety annotations, AI-assisted refactoring, rule-based automation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="so-sánh-rust-và-go-năm-2026"&gt;&lt;a class="link" href="https://bitfieldconsulting.com/posts/rust-vs-go" target="_blank" rel="noopener"
&gt;So Sánh Rust và Go Năm 2026&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết này so sánh hai ngôn ngữ lập trình phổ biến Rust và Go across nhiều khía cạnh: performance, simplicity, safety, features, scale, concurrency. Tóm tắt ngắn gọn: &amp;ldquo;Rust for high stakes, Go for low costs&amp;rdquo; - Rust cho mission-critical software cần safety và reliability, Go cho việc move fast và keep costs down. Cả hai đều là compiled languages với fast, compact executables, đều có pragmatic programming style và excellent tooling cho development at scale (gofmt, rustfmt, standard build tools). Tuy nhiên, Rust prioritizes safety, correctness, efficiency với borrow checker preventing memory errors, trong khi Go prioritizes simplicity và speed of development với garbage collector và minimal syntax. Go có goroutines và channels làm concurrency cực kỳ easy, Rust có match syntax và powerful features nhưng learning curve steeper. Error handling: Go dùng explicit &lt;code&gt;if err != nil&lt;/code&gt;, Rust dùng &lt;code&gt;Option&lt;/code&gt;/&lt;code&gt;Result&lt;/code&gt; types với &lt;code&gt;?&lt;/code&gt; operator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance: Rust beat Go ở run-time benchmarks nhờ no garbage collection và closer to metal optimizations&lt;/li&gt;
&lt;li&gt;Simplicity: Go cực kỳ easy to learn (productive sau 2 days), Rust có nhiều features hơn và learning curve steeper&lt;/li&gt;
&lt;li&gt;Concurrency: Go nổi tiếng với goroutines và channels cho high-scale concurrent apps, Rust có rayon crate và memory safety cho concurrency&lt;/li&gt;
&lt;li&gt;Safety: Rust&amp;rsquo;s borrow checker prevents memory safety bugs tại compile time, Go relies trên programmer discipline&lt;/li&gt;
&lt;li&gt;Scale: Go tốt cho big organisations với distributed teams nhờ uniform style và fast build times&lt;/li&gt;
&lt;li&gt;Garbage collection: Go has GC (simpler nhưng unpredictable pauses), Rust manual memory management (harder nhưng predictable)&lt;/li&gt;
&lt;li&gt;Error handling: Go explicit &lt;code&gt;if err != nil&lt;/code&gt;, Rust powerful &lt;code&gt;Option&lt;/code&gt;/&lt;code&gt;Result&lt;/code&gt; với &lt;code&gt;?&lt;/code&gt; operator&lt;/li&gt;
&lt;li&gt;Conclusion: &amp;ldquo;Should I learn Rust or Go?&amp;rdquo; → &amp;ldquo;Yes&amp;rdquo; - learn both, mỗi ngôn ngữ excels trong domain của nó&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dependency-phổ-biến-nhất-trong-go-là-gì"&gt;&lt;a class="link" href="https://blog.thibaut-rousseau.com/blog/the-most-popular-go-dependency-is/" target="_blank" rel="noopener"
&gt;Dependency Phổ Biến Nhất Trong Go Là Gì?&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết này trả lời câu hỏi về dependency phổ biến nhất trong hệ sinh thái Go thông qua việc phân tích toàn bộ Go modules ecosystem. Tác giả đã download toàn bộ index từ proxy.golang.org (từ 2019), build một dependency graph với 40 triệu nodes và 400 triệu relationships trong Neo4j database, từ đó extract ra các thống kê thú vị. Top 10 dependencies được sử dụng nhiều nhất: testify (259,237 dependents), google/uuid (104,877), golang.org/x/crypto (100,633), grpc (97,228), cobra (93,062), pkg/errors (92,491), golang.org/x/net (76,722), protobuf (74,971), logrus (71,730), viper (64,174). Testify là library được sử dụng rộng rãi nhất, đặc biệt là cho testing. Tác giả cũng demo cách query Neo4j để find direct/transitive dependents, ví dụ như &lt;a class="link" href="mailto:github.com/pkg/errors@v0.9.1" &gt;github.com/pkg/errors@v0.9.1&lt;/a&gt; vẫn có 16,001 dependents trong năm 2025 dù đã bị deprecated từ lâu.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go modules có trung bình 10 direct dependencies&lt;/li&gt;
&lt;li&gt;Testify (github.com/stretchr/testify) là dependency phổ biến nhất với 259,237 dependents&lt;/li&gt;
&lt;li&gt;Top dependencies bao gồm testing frameworks (testify), utilities (uuid, crypto, net), CLI tools (cobra, viper), logging (logrus)&lt;/li&gt;
&lt;li&gt;Extended stdlib (golang.org/x/) giữ vị trí mạnh trong ecosystem&lt;/li&gt;
&lt;li&gt;Các deprecated libraries như pkg/errors vẫn được sử dụng rộng rãi (92,491 dependents)&lt;/li&gt;
&lt;li&gt;Neo4j graph database非常适合 analyzing dependency relationships với Cypher query language&lt;/li&gt;
&lt;li&gt;Tác giả chia sẻ Neo4j dump via BitTorrent để community có thể chạy own queries&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gosum-không-phải-lockfile"&gt;&lt;a class="link" href="https://words.filippo.io/gosum/" target="_blank" rel="noopener"
&gt;go.sum Không Phải Lockfile&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết này làm rõ sự hiểu lầm phổ biến về go.sum trong Go modules. Nhiều người nghĩ go.sum là lockfile (như package-lock.json của Node hay Cargo.lock của Rust), nhưng thực tế go.sum chỉ là bộ nhớ đệm cục bộ cho Go Checksum Database - một ánh xạ từ các phiên bản module đến các mã băm mật mã của chúng. Những phiên bản này có thể được sử dụng hoặc không, không ảnh hưởng đến việc phân giải gói. go.sum thậm chí không được bật theo mặc định trong thiết kế modules ban đầu vì không có tác động quan sát được trên quá trình xây dựng! Mục đích duy nhất của nó là tăng cường câu chuyện bảo mật: Checksum Database đảm bảo toàn bộ hệ sinh thái chia sẻ cùng một nội dung cho một phiên bản module, bất kể cách tải xuống, và go.sum làm cho lời đảm bảo đó trở nên cục bộ và tự chứa.&lt;/p&gt;
&lt;p&gt;Thay vào đó, hãy nhìn vào go.mod - nó đóng vai trò既是 manifest又是 lockfile. Từ Go 1.17 (tháng 8 năm 2021), go.mod bao gồm tất cả các phụ thuộc chuyển tiếp cần thiết để xây dựng module chính và các bài kiểm tra của nó. Khác với các hệ sinh thái khác với manifest (các phạm vi phiên bản phức tạp) và lockfile (các phiên bản thực tế), Go đơn giản hơn với một tệp có thể đọc được bởi con người: go.mod liệt kê tất cả các phụ thuộc (trực tiếp và chuyển tiếp) và phiên bản chính xác của chúng. Không có xung đột phụ thuộc hình kim cương, không có cách nào vô tình sử dụng tính năng được giới thiệu trong phiên bản mà các phần phụ thuộc sẽ không có, và không có cập nhật tự động lên phiên bản mới nhất của các phụ thuộc khi thêm phụ thuộc mới.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;go.sum không phải lockfile, chỉ là bộ nhớ đệm cục bộ cho Go Checksum Database với các mã băm mật mã&lt;/li&gt;
&lt;li&gt;go.mod đóng vai trò既是 manifest又是 lockfile - liệt kê tất cả các phụ thuộc và phiên bản chính xác&lt;/li&gt;
&lt;li&gt;Từ Go 1.17, go.mod bao gồm tất cả các phụ thuộc chuyển tiếp cho module chính và bài kiểm tra&lt;/li&gt;
&lt;li&gt;Go modules đơn giản hơn nhiều so với các lựa chọn thay thế: không có phạm vi phiên bản, không có xung đột hình kim cương, không có vấn đề đồng bộ hóa lockfile&lt;/li&gt;
&lt;li&gt;Việc phân giải gói trong Go là tức thời nên không ai nhận thấy nó đang xảy ra&lt;/li&gt;
&lt;li&gt;Phân tích go.mod bằng golang.org/x/mod/modfile hoặc &lt;code&gt;go mod edit -json&lt;/code&gt;, không bao giờ phân tích go.sum&lt;/li&gt;
&lt;li&gt;Để phân tích đồ thị phụ thuộc, dùng go.mod, không phải go.sum&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="phát-triển-thông-báo-actions-cho-forgejo"&gt;&lt;a class="link" href="https://chris-besch.com/articles/forgejo_actions_notification/" target="_blank" rel="noopener"
&gt;Phát Triển Thông Báo Actions Cho Forgejo&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bài viết này chia sẻ kinh nghiệm đóng góp vào Forgejo - một mã nguồn tự chủ tương tự GitHub/GitLab. Tác giả cần tính năng gửi thông báo email và webhook khi CI workflow thất bại, nhưng Forgejo chưa có, nên ông ấy quyết định đóng góp. Bài viết giải thích chi tiết cấu trúc dự án Go, kiến trúc phân lớp của Forgejo, và mô hình publish-subscribe (pub-sub) được sử dụng để tránh các phụ thuộc vòng tròn. Tác giả chia quá trình thành nhiều pull requests: PR #7510 tái cấu trúc code từ layer thấp lên layer cao, PR #7491 thêm topic &lt;code&gt;ActionRunNowDone&lt;/code&gt; vào pub-sub system, PR #7509 triển khai gửi email khi workflow thất bại/phục hồi, và PR #7508 triển khai gửi webhook. Bài viết cũng hướng dẫn chi tiết cách thiết lập môi trường phát triển: Forgejo instance, Forgejo runner, mail server (MailDev), webhook tester, và cách chạy tests với Delve debugger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Điểm chính:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go projects sử dụng modules (với go.mod) và packages (nhóm code), với hệ thống phân tán không có central package index như NPM&lt;/li&gt;
&lt;li&gt;Forgejo dùng kiến trúc phân lớp 3 tầng: modules (tự chứa, có thể dùng như library), models (database abstraction), services (logic), routers (API/web UI) - code ở layer thấp không được import code ở layer cao&lt;/li&gt;
&lt;li&gt;Pub-sub pattern giải quyết cyclic dependencies: packages gửi messages đến topics (ví dụ &lt;code&gt;PullRequestReview&lt;/code&gt;), và các packages khác subscribe vào topics đó&lt;/li&gt;
&lt;li&gt;Tác giả chia feature thành 4 PRs để dễ review: refactoring, thêm topic, triển khai email, triển khai webhook&lt;/li&gt;
&lt;li&gt;Development setup cần Forgejo instance, runner, mail server (MailDev), webhook tester node script&lt;/li&gt;
&lt;li&gt;Debug với Delve debugger, chạy unit tests và integration tests với make commands&lt;/li&gt;
&lt;li&gt;Viết tests rất quan trọng để maintainers có thể verify code vẫn hoạt động đúng&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bonus"&gt;Bonus
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Hình ảnh:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://substackcdn.com/image/fetch/w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec94ed4-46b2-40bd-8d28-a25fdde639c8_2250x2624.png"
loading="lazy"
alt="Message Brokers 101"
&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!hXRV!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe99863c7-7ab7-4f61-9961-af7e4c6ee64f.png"
loading="lazy"
alt="Cloud Load Balancer Cheat Sheet"
&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!XvtW!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ded3402-4eb5-4818-861f-7661f1cefe82.tif"
loading="lazy"
alt="How CQRS Works?"
&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!SUjA!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd91a5538-a69e-468c-bf83-058bb78753ca_2360x2770.png"
loading="lazy"
alt="How does Docker Work?"
&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!4qBZ!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0945414-9cea-4e64-8dd4-3abcc404f73e_2360x2960.png"
loading="lazy"
alt="Containerization Explained: From Build to Runtime"
&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!n8BK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa51a75cc-09a9-4fed-af8a-c32a64f8ab60_2250x2624.png"
loading="lazy"
alt="Must-Know Message Broker Patterns"
&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được review và cập nhật bởi Claude Code với Opus 4.7 (1M context).&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>