Newsletter #116

Mời bạn thưởng thức Newsletter #116.

8 myths on software engineering and AI

Brian Houck giới thiệu bài báo “8 Myths on Software Engineering and GenAI” mà ông đồng tác giả, gom tám hiểu lầm phổ biến về AI trong kỹ thuật phần mềm thành ba nhóm: lập trình viên thực sự dùng thời gian thế nào, đo tác động của AI ra sao, và AI được áp dụng trong tổ chức thật như thế nào. Một nghiên cứu năm 2025 cho thấy lập trình viên chỉ dành khoảng 14% thời gian để viết mã, phần còn lại là thiết kế, họp, rà soát mã và phối hợp. Vì vậy công cụ sinh mã chỉ là một đòn bẩy nhỏ, và tăng tốc một khâu dễ đẩy nút thắt sang khâu rà soát. Đo năng suất bằng số dòng mã, kể cả dòng do AI sinh ra, làm phình thêm gánh nặng rà soát; PR throughput tốt hơn nhưng vẫn cần đặt trong một bộ chỉ số cân bằng.

Kết quả nghiên cứu về công cụ AI rất khác nhau: có nghiên cứu thấy tăng tốc rõ, có nghiên cứu trung tính, và một nghiên cứu cho thấy lập trình viên giàu kinh nghiệm còn mất thêm 18% thời gian khi dùng AI. Nhiệm vụ, kinh nghiệm và cả cách viết prompt đều ảnh hưởng đến kết quả. Về áp dụng, 80% lập trình viên dùng công cụ AI nhưng chỉ 29% tin vào độ chính xác của chúng. Thông điệp cuối cùng là tác động của AI phụ thuộc vào hệ thống công việc quanh lập trình viên nhiều hơn vào mô hình: tổ chức cần xây dựng niềm tin, quy trình, guardrail và cách đo ở cấp hệ thống, thay vì chỉ mua giấy phép rồi chờ năng suất tự tăng.

You Don’t Love systemd Timers Enough

Tác giả cho rằng cụm từ “cron job” vẫn được dùng cho mọi tác vụ chạy định kỳ, nhưng năm 2026 không nhất thiết phải dùng cron thật nữa. systemd timer là một loại unit dùng để lên lịch cho unit khác, thường là một .service cùng tên, nhờ đó tách rõ “khi nào chạy” với “chạy cái gì”. Cách này giải quyết các vấn đề quen thuộc của cron: $PATH khó đoán, stdout/stderr bị thất lạc, lịch sử chạy khó tra cứu và cú pháp lịch khó đọc. Kết quả chạy nằm trong journal, còn systemctl status và systemctl list-timers cho biết lần chạy trước và lần chạy kế tiếp. Bài viết cũng lưu ý ExecStart= mặc định không chạy như lệnh shell và không kế thừa biến môi trường, nên cần viết unit file rõ ràng thay vì giả định nó giống một script shell; ngoài ra nên tận dụng các tùy chọn sẵn có như ExecCondition=, OnFailure= hay Restart= thay vì tự viết logic.

Phần giá trị nhất là các tính năng vượt qua cron truyền thống. Lệnh systemd-analyze calendar giúp kiểm tra và giải thích biểu thức thời gian trước khi dùng. Bên cạnh lịch theo đồng hồ với OnCalendar=, timer còn hỗ trợ lịch tương đối như OnBootSec= và OnUnitActiveSec=, tức chạy sau khi máy khởi động hoặc sau lần chạy trước, thường khớp với ý định vận hành hơn lịch theo phút hay giờ cố định. Các tùy chọn WakeSystem=, Persistent=, RandomizedDelaySec=, FixedRandomDelay= và RandomizedOffsetSec= giúp đánh thức máy khi cần, bù lại lần chạy bị bỏ lỡ sau thời gian tắt máy và giảm hiện tượng nhiều máy cùng chạy một lúc (thundering herd). Nói ngắn gọn, systemd timer là công cụ vận hành giàu ngữ nghĩa hơn hẳn cron.

Life is too short for a slow terminal

Mijndert Stuij viết về việc giữ terminal thật nhanh vì đây là công cụ được dùng gần như cả ngày, và mỗi độ trễ nhỏ khi mở tab, gõ phím hay nhấn tab để hoàn thành lệnh sẽ lặp lại hàng trăm lần. Shell zsh của tác giả khởi động trong khoảng 30ms, không nhờ một dự án tối ưu lớn mà nhờ thói quen giữ cấu hình tối giản. Thắng lợi lớn nhất đến từ những thứ không có mặt: không oh-my-zsh, không plugin manager, chỉ source trực tiếp ba plugin thật sự cần. compinit là điểm nóng phổ biến, nên tác giả dùng cache .zcompdump để chỉ chạy kiểm tra đầy đủ khoảng một lần mỗi ngày, các lần còn lại dùng compinit -C.

Với những công cụ nặng như nvm hay completion của kubectl, tác giả dùng lazy-loading: bọc chúng trong một hàm chỉ nạp thật khi được gọi lần đầu. Bất cứ công cụ nào hướng dẫn thêm eval "$(tool init zsh)" vào .zshrc đều đáng cân nhắc nạp trễ. Prompt cũng cần tránh chạy git status đồng bộ trong repository lớn; prompt bất đồng bộ như pure hiển thị ngay rồi bổ sung trạng thái Git sau. Cuối cùng, tác giả khuyên đo thay vì đoán, phân biệt ba loại độ trễ là thời gian khởi động, độ trễ prompt và độ trễ nhập liệu: dùng time zsh -i -c exit, hyperfine, zprof hoặc trace quá trình khởi động để tìm điểm chậm nhất rồi sửa lần lượt. Theo tác giả, dưới 100ms là ổn và dưới 50ms là rất tốt.

OpenCodeReview

OpenCodeReview là công cụ CLI mã nguồn mở của Alibaba dùng AI để rà soát mã. Dự án xuất phát từ trợ lý rà soát nội bộ đã phục vụ hàng chục nghìn lập trình viên trong hai năm, sau đó được mở cho cộng đồng và chỉ cần cấu hình một model endpoint tương thích OpenAI hoặc Anthropic là dùng được. Thay vì gửi một đoạn diff cho mô hình rồi nhận nhận xét chung chung, công cụ đọc Git diff, để agent đọc toàn bộ file, tìm kiếm trong codebase và xem các file thay đổi liên quan, rồi tạo comment chính xác theo từng dòng. Ngoài rà soát theo diff, lệnh ocr scan có thể rà soát cả file hoặc thư mục khi cần kiểm tra một codebase chưa quen.

Điểm thiết kế đáng chú ý là kiến trúc lai giữa logic xác định và agent. Những bước không được phép sai như chọn file, gom nhóm file liên quan, khớp rule với từng file và định vị comment được xử lý bằng pipeline có ràng buộc cứng; agent chỉ tập trung vào phần cần suy luận động như đọc ngữ cảnh và gọi công cụ, với prompt và bộ công cụ được tinh chỉnh riêng cho việc rà soát. Công cụ có sẵn bộ rule cho các lỗi như NPE, thread-safety, XSS và SQL injection. Benchmark dựng từ 200 pull request thật cho thấy so với agent đa năng dùng cùng mô hình, OpenCodeReview có precision và F1 cao hơn, chỉ tốn khoảng 1/9 lượng token, nhưng recall thấp hơn; đây là đánh đổi có chủ đích để giảm cảnh báo sai.

Golang code review notes II

Zoltan Madarassy và Alex Brown tiếp tục loạt ghi chú rà soát mã Go dưới góc nhìn bảo mật. Luận điểm chính là công cụ tự động và trợ lý AI đã bắt được phần lớn lỗi hiển nhiên, nhưng phần nguy hiểm còn lại nằm ở những góc khuất của ngôn ngữ và thư viện chuẩn mà người rà soát phải chủ động biết để tìm. Bài viết điểm qua vài cải thiện tích cực: os.Root từ Go 1.24 giúp chặn lỗi truy cập vượt thư mục (path traversal), còn các thay đổi quanh math/rand và crypto/rand khiến các hàm sinh khóa luôn dùng nguồn ngẫu nhiên an toàn, loại bỏ khả năng vô tình dùng nguồn yếu.

Trọng tâm là các cái bẫy vẫn dễ bị bỏ sót. Tràn số nguyên diễn ra âm thầm, chẳng hạn khi ép độ dài sang kiểu hẹp hơn như uint32(len(s)), có thể phá vỡ ranh giới dữ liệu trong thư viện tuần tự hóa. httputil.ReverseProxy.Director có thể bị ảnh hưởng bởi hop-by-hop header, nên chuyển sang Rewrite vì hàm này chạy sau bước loại bỏ các header đó. Sao chép nhầm con trỏ trong net/url dẫn tới thay đổi trạng thái dùng chung và điều kiện tranh chấp; byte null đi qua biên CGO hoặc thư viện C có thể giúp vượt qua kiểm tra xác thực; encoding/json có thể bỏ qua custom marshaller khi method dùng pointer receiver nhưng struct lại được encode theo giá trị. JSON API cũng không tự miễn nhiễm CSRF nếu không kiểm tra Content-Type, cookie SameSite và nguồn gốc request. Tác giả phát hành thêm các Semgrep rule để phát hiện một số mẫu rủi ro này.

Thoughts on starting new projects with LLM agents

Eli Bendersky chia sẻ kinh nghiệm dùng agent để xây từ đầu dự án watgo viết bằng Go. Tác giả bắt đầu bằng việc cùng agent lặp lại thiết kế và API, lưu vào một file Markdown được commit trong kho mã để cả người và agent cùng bám theo. Sau đó ông yêu cầu agent viết các changelist (CL) nhỏ theo thứ tự hợp lý, tự rà soát bằng diff view của VSCode, chỉnh tay khi cần rồi mới tự commit. Khi agent tạo ra một thay đổi quá lớn, cách thực tế là commit ở điểm chấp nhận được rồi tiếp tục yêu cầu tái cấu trúc bằng các CL riêng; nếu đi sai hướng, cả chuỗi vẫn có thể được revert.

Thông điệp chính là phải giữ con người trong vòng kiểm soát với dự án cần bảo trì lâu dài. Vibe coding có thể phù hợp với nguyên mẫu hoặc mã dùng một lần, nhưng với dự án quan trọng, người viết phải hiểu, rà soát và dẫn hướng mọi quyết định. Tác giả nhấn mạnh CL nhỏ và một chiến lược kiểm thử vững, đồng thời cảnh báo không nên để agent viết cả kiểm thử lẫn phần triển khai rồi tự củng cố lỗi sai của chính nó. Go được xem là ngôn ngữ rất hợp để agent viết vì ít thay đổi, ít cách làm trùng lặp, thư viện chuẩn phong phú, định dạng thống nhất và xử lý lỗi tường minh; khi phần lớn thời gian của con người chuyển từ viết sang đọc mã, tính dễ đọc trở thành lợi thế lớn. Tác giả cũng khuyên lập trình viên junior thận trọng khi dựa vào LLM.

The History of Pets vs Cattle and How to Use the Analogy Properly

Randy Bias kể lại nguồn gốc và ý nghĩa đúng của ẩn dụ “pets vs cattle” trong hạ tầng đám mây. Ông mượn ý tưởng từ một bài trình bày về mở rộng SQL Server, rồi đặt nó vào bối cảnh cloud và nhấn mạnh tính độc nhất của “pet” so với tính có thể thay thế của “cattle”. “Pet” là máy chủ hoặc cặp máy chủ được coi là không thể thiếu, thường được dựng và chăm sóc thủ công, khi hỏng thì cả đội phải lao vào xử lý; “cattle” là nhóm từ ba máy trở lên được dựng bằng công cụ tự động, thiết kế để chịu lỗi, nên mất một vài node thì hệ thống vẫn tự vận hành mà không cần con người can thiệp. Ẩn dụ này thành công vì giải thích nhanh sự chuyển dịch từ cách làm cũ sang kiến trúc cloud-native.

Bài viết cũng cảnh báo việc dùng sai ẩn dụ làm mất giá trị ban đầu. Ví dụ điển hình là tính năng “Pet Sets” của Kubernetes: các hệ thống có trạng thái được nêu làm ví dụ như Cassandra, Kafka hay MongoDB thực chất đều được thiết kế theo kiểu “cattle”, vì chúng chịu lỗi theo mô hình scale-out. Nếu định nghĩa “pet” là hệ thống cần xử lý đặc biệt thay vì hệ thống không được phép hỏng, người học sẽ khó phân biệt cách làm cũ và mới. Trọng tâm của ẩn dụ là khả năng thay thế từng máy chủ và thiết kế chịu lỗi ở cấp hệ thống, không phải chuyện workload có trạng thái hay không.

Every byte matters

Farid Zakaria nhắc rằng hiệu năng không chỉ nằm ở độ phức tạp Big-O. Ngay cả trong một vòng lặp tuyến tính, kích thước dữ liệu và cách bố trí bộ nhớ vẫn tạo khác biệt rất lớn vì CPU nạp dữ liệu theo từng cache line, thường là 64 byte. Nếu một struct lớn nhưng ta chỉ cần một trường nhỏ như is_alive, cách tổ chức array of structs sẽ kéo theo nhiều byte không dùng tới vào cache. Ngược lại, struct of arrays gom cùng một trường vào vùng nhớ liên tục, giúp mỗi lần nạp cache phục vụ được nhiều phần tử hơn; trong thử nghiệm của tác giả, mức cải thiện lên tới 30 lần khi struct có kích thước 1KiB.

Bài viết cũng giải thích vì sao kích thước working set quan trọng với truy cập ngẫu nhiên. Khi truy cập tuần tự, bộ prefetcher của CPU đoán được bước kế tiếp và che bớt độ trễ. Nhưng với hash map, cây, đồ thị hay các cấu trúc nhiều con trỏ, địa chỉ kế tiếp khó đoán, nên dữ liệu phải nằm gọn trong cache nếu muốn tránh chờ bộ nhớ. Chỉ cần tăng kích thước struct từ 64B lên 128B, cùng một số lượng phần tử đã bị đẩy xuống tầng cache chậm hơn: với 512 phần tử, struct 64B vừa trong L1d với độ trễ khoảng 3 ns, còn struct 128B đã tràn sang L2 với khoảng 11 ns. Bài học thực tế là mỗi byte trên đường nóng đều có chi phí, và hiểu kích thước struct, cache line cùng working set giúp tối ưu hiệu quả hơn nhiều so với chỉ phân tích thuật toán.


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.

Made by miti99 with ❤️
Built with Hugo
Theme Stack thiết kế bởi Jimmy