Mời bạn thưởng thức Newsletter #124.
Riding Technology Waves
Các làn sóng công nghệ như Internet, điện toán đám mây, thiết bị di động và AI ngày càng lớn và đến đột ngột hơn; chúng tạo ra doanh nghiệp khổng lồ nhưng cũng xóa sổ nhiều ngành cũ, trong khi in 3D hay thực tế ảo lại thoái trào. Để định hướng sớm mà không bị cuốn theo sự cường điệu, tác giả đề xuất trả lời lần lượt bốn câu hỏi: công nghệ này có thực sự quan trọng, tức có người dùng thật không; nó vận hành thế nào, ở mức giải thích được cho người ngoài ngành; nó mở ra khả năng mới nào, mô tả bằng sự thật cụ thể thay vì khẩu hiệu; và những khả năng đó có tác động đến sản phẩm, chi phí hay khách hàng của doanh nghiệp không.
Hai cái bẫy phổ biến nằm ở hai thái cực. Lười thay đổi khiến doanh nghiệp phớt lờ tín hiệu thật, dù sai lầm này thường tự điều chỉnh khi công nghệ đã hiển nhiên. Nguy hiểm hơn là nỗi sợ bị xem là lạc hậu: lãnh đạo không hiểu công nghệ chạy theo đám đông, đặt cược vào thứ chưa có người dùng hoặc hủy cả lộ trình để gắn nhãn AI trong khi khách hàng cần tính năng khác. Theo tác giả, xoay trục mù quáng là sai lầm chiến lược mặc định trong thời kỳ biến động. Còn sai lầm lớn nhất là bỏ lỡ một làn sóng hiển nhiên rồi vì cái tôi mà phủ nhận; khi đó cần thừa nhận vấn đề, nhận trách nhiệm, dồn nguồn lực và người giỏi nhất vào việc bắt kịp, rồi trực tiếp theo sát đến khi sửa xong.
Find a new role
Khi cân nhắc chuyển việc hay đổi vai trò, Lara Hogan đề xuất lập bốn danh sách, có thể điền vào một bảng 2×2: những điều bắt buộc không thể thương lượng; những điều mong muốn, quan trọng nhưng chấp nhận thiếu một vài thứ; những yếu tố không quan trọng ở thời điểm này, dù có thể từng quan trọng hoặc quan trọng với người khác; và một câu ngắn nêu điều duy nhất bạn muốn tối ưu hơn mọi thứ khác. Cách làm này biến một quyết định nghề nghiệp mơ hồ thành các tiêu chí cụ thể để tìm kiếm, phỏng vấn và so sánh cơ hội.
Tác giả khuyên không đưa chức danh vào bất kỳ danh sách nào, vì cùng một chức danh có thể mang ý nghĩa rất khác giữa các tổ chức; hãy đánh giá nội dung thực tế của vai trò. Danh sách bắt buộc phải thật ngắn: điều gì bạn vẫn chấp nhận nếu thiếu thì thuộc về danh sách mong muốn, và gần như chắc chắn bạn sẽ phải đánh đổi ít nhất một điều mong muốn. Các tiêu chí nên phản ánh nhu cầu hôm nay, không phải điều bạn từng muốn hay có thể cần trong tương lai xa; mức lương có thể nhường chỗ cho bảo hiểm sức khỏe, trách nhiệm có thể nhường chỗ cho cân bằng giữa công việc và cuộc sống. Câu “tối ưu cho” là công cụ quan trọng nhất: hãy dán nó cạnh máy tính, chia sẻ với người bạn tin cậy và dùng nó để tự hỏi cơ hội nào giúp bạn tiến gần mục tiêu ấy nhất. Bài tập này có thể làm lại mỗi khi hoàn cảnh thay đổi.
I Stored a Website in a Favicon
Sau lần giấu hai byte vào chuột máy tính, Tim Wehrle thử lưu cả nội dung một trang web vào favicon. Ý tưởng rất đơn giản: favicon chỉ là ảnh, ảnh là các điểm ảnh, và mỗi điểm ảnh có ba kênh màu đỏ, xanh lá, xanh dương, tức ba byte. Tác giả chuyển đoạn HTML thành byte UTF-8 bằng TextEncoder, thêm bốn byte đầu ghi độ dài dữ liệu để bộ giải mã biết chính xác nơi kết thúc, vì cuối ảnh có thể còn điểm ảnh thừa, rồi ghi lần lượt từng byte vào các kênh màu. Với 208 byte nội dung cộng phần đầu, tổng cộng 212 byte cần 71 điểm ảnh và vừa trong một ảnh PNG 9×9, sử dụng 87% dung lượng; kết quả trông giống như nhiễu.
Để đọc lại, trình duyệt tải favicon và vẽ lên canvas, rồi JavaScript lấy các giá trị RGB, khôi phục mảng byte, đọc độ dài và giải mã UTF-8 thành đoạn HTML ban đầu. Điểm cần lưu ý là favicon chỉ chứa nội dung trang và không tự thực thi được; vẫn cần một đoạn mã khởi động nhỏ để giải mã và thay trang hiện tại bằng nội dung tái tạo. Tác giả thừa nhận thử nghiệm không có giá trị thực tế, vì dung lượng rất nhỏ và có nhiều cách tốt hơn để phân phối một tài liệu HTML, nhưng nó minh họa rõ rằng mọi định dạng tệp rốt cuộc chỉ là byte và có thể được dùng ngoài mục đích quen thuộc. Bài viết cũng gợi ý các hướng khác như nhúng mã đánh dấu trực tiếp vào favicon SVG, dùng các đoạn chú thích văn bản của PNG, hoặc định dạng ICO.
AntiPatterns Never Left, We Just Stopped Calling Them by Name
Năm 1998, cuốn sách AntiPatterns lập danh mục những kiểu thất bại lặp lại trong phát triển phần mềm, mỗi kiểu có tên gọi, triệu chứng và lối thoát. Leon Pennings cho rằng chúng chỉ khoác tên gọi mới và khó nhận ra vì hệ thống vẫn chạy và vượt qua kiểm thử; thành công trước mắt khiến lựa chọn kiến trúc được xem là đúng, trong khi không ai thử phương án đơn giản hơn để so sánh. Vì vậy, sự phổ biến không chứng minh giải pháp phù hợp với bối cảnh, và gọi đúng tên kiểu thất bại giúp đội ngũ nhìn thấy vấn đề bị che khuất.
Tác giả chia các ví dụ hiện đại thành bốn nhóm. Thứ nhất là tổ chức hệ thống theo tầng kỹ thuật thay vì theo nghiệp vụ, dẫn đến khái niệm nghiệp vụ bị phân mảnh, mô hình miền thiếu hành vi và phụ thuộc sâu vào khung phần mềm. Thứ hai là áp dụng công nghệ mà không đánh giá, như dùng Kubernetes hay Kafka làm chiếc búa vạn năng. Thứ ba là phương thuốc tái tạo chính căn bệnh: mã nguồn rối thành mạng lưới vi dịch vụ rối, độ phức tạp chỉ chuyển sang ranh giới mạng, hay đội nền tảng trở thành bức tường vận hành mới. Thứ tư là người chịu hậu quả không được quyết định, như vai trò trung gian trong Scrum ngăn cách lập trình viên với người dùng. Ở phần kết, tác giả cho rằng tự viết vài chục dòng mã nguồn đôi khi rẻ hơn về lâu dài so với một phụ thuộc lớn. Thông điệp không phải phản đối khung phần mềm, vi dịch vụ hay Scrum, mà là đánh giá mọi lựa chọn theo bối cảnh.
5 More Advanced Java Tips That Senior Engineers Actually Use
Bài viết giới thiệu năm khả năng của Java 21 trở lên. Structured Concurrency gom một nhóm tác vụ đồng thời thành một đơn vị có chung vòng đời: khi một tác vụ lỗi, các tác vụ còn lại bị hủy, việc hủy từ luồng cha được truyền xuống, và khi khối try kết thúc thì không còn gì chạy ngầm, khác hẳn cách tự quản lý nhiều CompletableFuture độc lập vốn dễ gây rò rỉ công việc. Record Patterns cho phép tách cấu trúc bản ghi lồng nhau ngay trong biểu thức switch; kết hợp với kiểu niêm phong, trình biên dịch kiểm tra đầy đủ mọi trường hợp, nên khi thêm một kiểu con mới, những chỗ chưa xử lý sẽ báo lỗi biên dịch.
Sequenced Collections thống nhất cách lấy phần tử đầu, cuối và khung nhìn đảo ngược cho danh sách, tập hợp và ánh xạ có thứ tự; reversed() trả về khung nhìn chứ không sao chép, rất tiện khi xử lý nhật ký sự kiện theo thời gian. Foreign Function and Memory API (Project Panama) thay thế Unsafe và JNI bằng cách truy cập bộ nhớ ngoài vùng quản lý và gọi hàm gốc hoàn toàn bằng Java, có kiểm tra biên và giải phóng tài nguyên xác định nhờ mô hình Arena. Cuối cùng, Gatherers lấp khoảng trống của Stream API: nếu Collectors định nghĩa cách một luồng dữ liệu kết thúc thì Gatherers cho phép tự viết phép biến đổi trung gian có trạng thái như chia lô, cửa sổ trượt, loại bỏ phần tử trùng liên tiếp hay tính trung bình động mà vẫn giữ chuỗi xử lý dễ đọc. Theo tác giả, các tính năng này ăn khớp với nhau thành một hệ thống nhất quán.
Go’s Type System — Structs, Interfaces, and Life Without Inheritance
Sau sáu năm với Java và Kotlin, tác giả nhận ra điều thay đổi tư duy thiết kế nhiều nhất khi học Go là ngôn ngữ này không có kế thừa lớp. Struct chỉ chứa dữ liệu, không có hàm dựng hay từ khóa implements; một hàm như NewOrder chỉ là quy ước, và vẫn có thể tạo struct trực tiếp với các giá trị mặc định. Phương thức được khai báo riêng với bộ nhận theo giá trị, làm việc trên bản sao, hoặc theo con trỏ, có thể thay đổi đối tượng gốc; vì vậy phương thức cần sửa trạng thái phải dùng bộ nhận con trỏ. Thay cho kế thừa, Go dùng nhúng struct: trường và phương thức của struct bên trong được đưa lên struct bên ngoài, nhưng đây là quan hệ kết hợp “có một” chứ không phải “là một”.
Điểm khác biệt lớn nhất là giao diện được thỏa mãn ngầm: bất kỳ kiểu nào có đủ phương thức đều tự động đáp ứng giao diện, kể cả kiểu không do bạn viết. Điều này đảo chiều thiết kế: giao diện thường nhỏ và do phía sử dụng định nghĩa theo đúng hành vi cần thiết, như io.Reader và io.Writer chỉ có một phương thức, thay vì bắt mọi kiểu tuân theo một hợp đồng lớn ngay từ đầu. Nhờ đó, các kiểu độc lập và không có hệ thống phân cấp mong manh. Tác giả cũng thẳng thắn nêu những điểm chưa quen: khó tìm tất cả kiểu triển khai một giao diện, nhúng không mang lại đa hình như kế thừa, và giá trị mặc định có thể khiến dữ liệu chưa khởi tạo trông như hợp lệ, nên cần chủ động kiểm tra các trường bắt buộc.
Conduit: The Gateway I Built to Forget About
Tác giả từng xây dựng một cổng API bằng Go đứng trước ba dịch vụ với hai cơ chế xác thực: phiên tự thiết kế có chữ ký HMAC cho người dùng và trò chuyện, JWT cho khu vực quản trị. Cổng chạy ổn định nhưng khó mở rộng, vì mỗi ngoại lệ về đường dẫn công khai lại là thêm một nhánh điều kiện trong mã Go và phải triển khai lại. Conduit giải quyết bằng cách tách phần ổn định khỏi phần hay thay đổi: Go phụ trách tiếp nhận HTTP, định tuyến, chuyển tiếp, CORS và bảo vệ SSRF; một môi trường Deno cách ly chạy các mô-đun chính sách viết bằng TypeScript cho xác thực, ghi nhật ký và biến đổi yêu cầu, chỉ cần sửa tệp mà không biên dịch lại.
Hai tiến trình trao đổi qua Unix socket theo hợp đồng ảnh chụp và bản vá. Go gửi ảnh chụp yêu cầu có thể tuần tự hóa; mô-đun chỉ trả về phần thay đổi tối thiểu thay vì sở hữu hay sửa trực tiếp đối tượng yêu cầu, còn nội dung lớn hơn 64 KiB vẫn nằm trong Go và được tham chiếu bằng mã định danh. Thiết kế cũng phân biệt rõ các chế độ lỗi: một bước xử lý quá thời gian chỉ ghi cảnh báo, còn khi cả môi trường Deno ngừng hoạt động thì từng tuyến chọn đóng để trả lỗi 503 hoặc mở để chuyển tiếp mà bỏ qua mô-đun. Theo tác giả, sự đơn giản đến từ ranh giới trách nhiệm rõ ràng chứ không phải số dòng mã. Conduit hướng tới vài dịch vụ có chính sách biên thay đổi thường xuyên, không nhằm thay thế Envoy, Kong hay xử lý luồng dữ liệu rất lớn.
The Reflect Package
Go là ngôn ngữ biên dịch tĩnh ra mã máy, vậy mà gói reflect vẫn đọc được tên trường, kiểu và cả thẻ struct lúc chạy. Bài viết giải thích rằng reflect không tính toán gì mà chỉ đọc siêu dữ liệu trình biên dịch đã ghi sẵn vào tệp thực thi. Một giá trị any gồm hai con trỏ: một trỏ đến dữ liệu, một trỏ đến mô tả kiểu, và địa chỉ của mô tả này là hằng số trình biên dịch đã biết từ trước. Vì vậy, reflect.TypeOf về bản chất chỉ là một lần đọc con trỏ, không tra bảng, không cấp phát bộ nhớ.
Thành phần reflectdata của trình biên dịch tuần tự hóa mỗi kiểu thành một ký hiệu type:<tên kiểu>, và trình liên kết đặt chúng vào vùng chỉ đọc; chạy strings trên tệp thực thi là thấy thẻ struct nằm nguyên văn. Mỗi mô tả gồm phần đầu chung cố định cho mọi kiểu, ghi kích thước, căn chỉnh, loại, giá trị băm, mặt nạ bit cho bộ thu gom rác, hàm so sánh và tên; ngay sau đó là dữ liệu riêng theo loại, với struct là mảng trường gồm tên, kiểu và độ lệch, rồi đến dữ liệu phương thức. Tên và thẻ được lưu một lần trong vùng dùng chung để mọi mô tả cùng trỏ tới, giúp tiết kiệm dung lượng. reflect.Value ghép mô tả với con trỏ dữ liệu, rồi cộng độ lệch đã lưu để đến đúng trường trong bộ nhớ. Việc đọc có thể thực hiện trên bản sao, nhưng muốn sửa phải truyền con trỏ, vì truyền giá trị vào any sẽ sao chép nó và thay đổi sẽ không tác động đến đối tượng gốc.
Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.