Newsletter #36

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

What Makes Strong Engineers Strong

Bài viết này phân tích những đặc điểm quan trọng khiến một kỹ sư phần mềm trở nên xuất sắc. Tác giả Sean Goedecke đã chỉ ra bốn yếu tố chính, được sắp xếp theo thứ tự quan trọng từ cao đến thấp.

Tự tin (Self-belief) được xếp hạng đầu tiên và quan trọng nhất. Những kỹ sư giỏi tin tưởng vào khả năng giải quyết vấn đề của mình, ngay cả khi đối mặt với những thử thách phức tạp và chưa từng gặp. Họ có tinh thần “tôi có thể tìm ra cách giải quyết” thay vì tránh né những nhiệm vụ khó khăn. Sự tự tin này tạo ra một vòng lặp tích cực, càng giải quyết được nhiều vấn đề thì càng tự tin hơn.

Thực dụng (Pragmatism) đứng thứ hai. Kỹ sư mạnh tập trung vào các giải pháp khả thi thay vì theo đuổi sự hoàn hảo về mặt lý thuyết. Họ sẵn sàng thoả hiệp để có thể giao sản phẩm đúng hạn và ưu tiên kết quả thực tế hơn là lý thuyết suông.

Tốc độ (Speed) là yếu tố thứ ba. Làm việc nhanh và hiệu quả giúp họ có thể thử nghiệm nhiều hơn và tích lũy kinh nghiệm nhanh chóng. Tác giả nhấn mạnh rằng năng suất đến từ những “đợt làm việc ngắn nhưng chuyên sâu” chứ không phải làm việc nhiều giờ.

Khả năng kỹ thuật (Technical Ability) được xếp cuối cùng. Điều thú vị là tác giả cho rằng không cần phải là “thiên tài” mà chỉ cần có kỹ năng phù hợp với công việc cụ thể. Hiệu quả làm việc quan trọng hơn trí thông minh thuần túy.

Bài viết khuyến khích các lập trình viên trẻ nên tập trung vào việc xây dựng lòng tin vào bản thân và làm việc thực dụng, thay vì chỉ chú trọng vào việc nâng cao kỹ năng kỹ thuật.

How do AI Code Reviews Impact Engineering Teams?

Nghiên cứu về tác động của AI trong việc đánh giá mã nguồn đã mang lại những kết quả khá thú vị. Theo dữ liệu thu thập được, 73.8% các nhận xét do AI tạo ra đã được các lập trình viên thực hiện, cho thấy độ hữu ích thực tế của công cụ này.

Tuy nhiên, nghiên cứu cũng phát hiện ra một số hạn chế đáng chú ý. Thời gian đóng pull request đã tăng từ 5 giờ 52 phút lên 8 giờ 20 phút, cho thấy việc xử lý các gợi ý của AI cần thêm thời gian. Đồng thời, AI không giảm đáng kể số lượng bình luận từ con người trong quá trình đánh giá mã.

Về chất lượng, 68.8% người tham gia khảo sát nhận thấy có sự cải thiện nhỏ trong chất lượng mã nguồn. AI đặc biệt hiệu quả trong việc phát hiện lỗi, lỗi chính tả và các test case bị thiếu. Tuy nhiên, vẫn có 26.2% gợi ý của AI bị gắn nhãn “Won’t Fix” hoặc “Closed”, cho thấy không phải tất cả đề xuất đều hữu ích.

Nghiên cứu khuyến nghị các nhóm phát triển nên đặt ra kỳ vọng rõ ràng về các gợi ý từ AI, theo dõi các chỉ số quy trình làm việc và liên tục cải tiến cách sử dụng công cụ. Hiện tại, AI đánh giá mã vẫn đóng vai trò bổ trợ chứ chưa thể thay thế hoàn toàn việc đánh giá của con người.

Writing that changed how I think about PL

Max Bernstein đã tổng hợp một danh sách những bài viết có ảnh hưởng sâu sắc đến cách ông hiểu về ngôn ngữ lập trình và trình biên dịch. Đây là những tài liệu đã thay đổi quan điểm của tác giả về nhiều khái niệm kỹ thuật quan trọng.

Bài viết giới thiệu 16 tài liệu khác nhau bao gồm các bài báo khoa học, blog post và bài thuyết trình về những chủ đề như bộ thu gom rác, kỹ thuật tối ưu hóa trình biên dịch, phương pháp phân tích cú pháp, triển khai machine learning và thiết kế trình thông dịch.

Một số điểm nổi bật bao gồm bài viết của Andy Wingo về “a simple semi-space collector” giúp làm rõ các khái niệm về bộ thu gom rác, bài của Russ Cox về “Regular Expression Matching” đơn giản hóa việc hiểu về regex engine, và bài thuyết trình của Chandler Carruth về thiết kế trình biên dịch Carbon.

Đặc biệt, tác giả nhấn mạnh sức mạnh của việc viết kỹ thuật rõ ràng và súc tích trong việc truyền đạt những khái niệm phức tạp. Danh sách này không chỉ phản ánh niềm đam mê của tác giả với lý thuyết ngôn ngữ lập trình mà còn cho thấy tầm quan trọng của những tài liệu có thể thay đổi cách nhìn nhận kỹ thuật của chúng ta.

LLMs are Making Me Dumber

Vincent Cheng đã đặt ra một câu hỏi thú vị và gây tranh cãi: liệu các mô hình ngôn ngữ lớn (LLMs) có đang khiến chúng ta trở nên “ngu đần” hơn không? Tác giả chia sẻ những quan sát cá nhân về cách AI đang ảnh hưởng đến quá trình học tập và phát triển kỹ năng của mình.

Tác giả chỉ ra những “đường tắt” trong học tập mà AI mang lại: sử dụng LLMs để hoàn thành các dự án lập trình mà không hiểu sâu về mã nguồn, giải bài tập toán bằng cách để AI tạo ra đáp án, hoặc soạn thảo email mà không luyện tập kỹ năng viết. Những hành vi này có thể dẫn đến việc “thoái hóa” khả năng giải quyết vấn đề và hy sinh độ sâu của việc học để đổi lấy tốc độ đầu ra.

Tuy nhiên, Vincent cũng thừa nhận những lợi ích ngắn hạn về năng suất và đưa ra những phép so sánh lịch sử với máy tính bỏ túi, GPS hay cuộc cách mạng công nghiệp. Ông nhận ra sự không chắc chắn về tác động dài hạn và đề xuất một chiến lược cân bằng.

Giải pháp mà tác giả đưa ra là có ý thức bảo tồn các kỹ năng cốt lõi như tư duy độc lập, ra quyết định và tập trung dài hạn, đồng thời sử dụng LLMs một cách chiến lược mà vẫn duy trì được tính chủ động cá nhân. Như ông viết: “Việc chuyển giao hoàn toàn sẽ làm tê liệt việc học thực sự nhưng tối đa hóa tốc độ và đầu ra ngắn hạn, và việc tìm ra sự cân bằng phù hợp là rất quan trọng.”

How Cursor Indexes Codebases Fast

Bài viết này tiết lộ cách Cursor - một IDE AI phổ biến - lập chỉ mục mã nguồn một cách nhanh chóng và hiệu quả. Cursor sử dụng cây Merkle để theo dõi và đồng bộ hóa thay đổi trong codebase, cho phép cập nhật tăng dần chỉ các file đã được chỉnh sửa.

Quy trình bắt đầu với việc chia nhỏ mã nguồn (code chunking) thành các phần có ý nghĩa về mặt ngữ nghĩa. Cursor sử dụng các chiến lược tiên tiến như phân tích cây cú pháp trừu tượng (AST) để đảm bảo việc chia nhỏ không phá vỡ ranh giới ngữ nghĩa của mã.

Sau đó, hệ thống tạo ra các biểu diễn vector (embeddings) cho từng đoạn mã bằng các mô hình embedding chuyên biệt cho code. Những embedding này được lưu trữ trong cơ sở dữ liệu vector (Turbopuffer) cùng với metadata như số dòng và tham chiếu file, trong khi đường dẫn file được làm mờ để bảo vệ quyền riêng tư.

Khi người dùng tương tác với các tính năng AI của Cursor, hệ thống sẽ tính toán embedding cho truy vấn, thực hiện tìm kiếm tương đồng ngữ nghĩa trong codebase, và truy xuất các đoạn mã liên quan để cung cấp context cho việc tạo mã và hỗ trợ thông minh.

Phương pháp này mang lại nhiều lợi ích: cập nhật tăng dần hiệu quả, xác minh tính toàn vẹn dữ liệu, tối ưu hóa bộ nhớ đệm, lập chỉ mục bảo vệ quyền riêng tư, và tích hợp với lịch sử Git. Điều này cho phép Cursor cung cấp các tính năng như hoàn thành mã thông minh, hỏi đáp về codebase, và đề xuất tái cấu trúc một cách chính xác.

A Leap Year Check in Three Instructions

Bài viết này trình bày một cách tiếp cận sáng tạo và cực kỳ tối ưu để kiểm tra năm nhuận chỉ với ba lệnh CPU thông qua kỹ thuật thao tác bit tinh vi.

Thông thường, việc kiểm tra năm nhuận yêu cầu nhiều điều kiện: chia hết cho 4, không chia hết cho 100 trừ khi chia hết cho 400. Tác giả đã phát triển một phương pháp sử dụng các hằng số “ma thuật” được tính toán cẩn thận để giảm toàn bộ logic thành một phép toán bit duy nhất: return ((y * 1073750999u) & 3221352463u) <= 126976u;

Điểm đột phá trong cách tiếp cận này là việc sử dụng Z3 solver - một công cụ tìm kiếm tự động - để tìm ra các hằng số tối ưu cho phép thực hiện kiểm tra năm nhuận thông qua thao tác bit. Thuật toán được xác minh hoạt động chính xác cho các năm từ 0 đến 102,499.

Về hiệu suất, phương pháp này nhanh hơn 3.8 lần so với cách triển khai truyền thống khi xử lý dữ liệu ngẫu nhiên, và duy trì hiệu suất ổn định qua các mẫu đầu vào khác nhau với chi phí tối thiểu.

Tuy nhiên, cách tiếp cận này cũng có hạn chế: chỉ được đảm bảo chính xác cho năm từ 0-102,499 và tính hữu ích thực tế phụ thuộc vào trường hợp sử dụng cụ thể. Bài viết này thể hiện một bài tập thao tác bit hấp dẫn hơn là một giải pháp thay thế hoàn toàn cho tính toán năm nhuận chuẩn, nhưng cho thấy tiềm năng của việc tối ưu hóa thuật toán sáng tạo.

Reservoir Sampling

Reservoir sampling là một thuật toán để chọn mẫu ngẫu nhiên công bằng khi bạn không biết trước tổng kích thước của tập dữ liệu đang lấy mẫu. Đây là một kỹ thuật cực kỳ hữu ích trong xử lý dữ liệu streaming và các tình huống có khối lượng dữ liệu không xác định.

Thuật toán hoạt động bằng cách duy trì một “hồ chứa” (reservoir) có kích thước cố định chứa các item được chọn. Khi xử lý từng item mới trong luồng dữ liệu, mỗi item có xác suất 1/n được chọn, trong đó n là số lượng item đã thấy cho đến thời điểm đó. Đối với việc chọn nhiều item, xác suất trở thành k/n, với k là số lượng item muốn chọn.

Bài viết minh họa ứng dụng thực tế thông qua tình huống dịch vụ thu thập log. Khi một dịch vụ nhận được khối lượng log khổng lồ, reservoir sampling cho phép chọn ngẫu nhiên một số lượng log cố định (ví dụ 5 log mỗi giây), đảm bảo đại diện công bằng trong các giai đoạn có lưu lượng cao, duy trì việc sử dụng bộ nhớ có thể dự đoán, và ngăn chặn quá tải dịch vụ.

Những lợi ích chính của thuật toán này bao gồm khả năng làm việc với các luồng có kích thước không biết trước hoặc vô hạn, cung cấp việc lấy mẫu công bằng về mặt thống kê, sử dụng bộ nhớ hiệu quả, và có thể thích ứng với nhiều tình huống streaming dữ liệu khác nhau.

Reservoir sampling giải quyết vấn đề tưởng chừng không thể: chọn mẫu đại diện khi không thể dự đoán tổng số lượng item trước.

Beware the Complexity Merchants

Bài viết này cảnh báo về “những kẻ buôn bán độ phức tạp” trong phát triển phần mềm - những người có xu hướng tạo ra sự phức tạp không cần thiết trong hệ thống. Tác giả lập luận rằng độ phức tạp tình cờ (accidental complexity) có thể gây tổn hại nghiêm trọng đến tốc độ phát triển của nhóm và tạo ra nền tảng hệ thống không ổn định.

Bài viết chỉ ra những động cơ đằng sau việc tạo ra độ phức tạp không cần thiết: mong muốn thể hiện bản thân thông qua việc tạo ra các hệ thống phức tạp để tự khẳng định tầm quan trọng, bảo vệ vị thế cá nhân bằng cách xây dựng những hệ thống đòi hỏi bảo trì liên tục, và tạo ra các vấn đề giả tạo để biện minh cho việc cần thêm tài nguyên và quyền kiểm soát.

Tác giả dẫn lời của Ray Ozzie, người tạo ra Lotus Notes: “Độ phức tạp giết chết mọi thứ. Nó hút cạn sức sống của các lập trình viên; nó khiến sản phẩm trở nên khó lập kế hoạch, xây dựng và kiểm thử.”

Để chống lại xu hướng này, bài viết đề xuất một số giải pháp: yêu cầu tài liệu rõ ràng cho mọi hệ thống phức tạp, ưu tiên các giải pháp đơn giản và “nhàm chán”, bắt buộc kỹ sư dọn dẹp độ phức tạp hiện có trước khi thêm hệ thống mới, và hoài nghi với các giải pháp “đạn bạc”.

Thông điệp cốt lõi là độ phức tạp nên được giảm thiểu tối đa, tập trung vào việc tạo ra các hệ thống đơn giản, dễ bảo trì và mang lại giá trị kinh doanh thực sự.

Asserting Implications

Bài viết ngắn gọn này từ TigerBeetle thảo luận về một kỹ thuật lập trình để viết các assertion logic một cách dễ đọc hơn. Tác giả đề xuất thay thế cú pháp assertion truyền thống cho các phép tính toán logic bằng cách tiếp cận conditional assertion rõ ràng hơn.

Thông thường, phép tính toán logic (logical implication) được biểu diễn dưới dạng assert(!a or b), theo công thức toán học A⇒B ⇔ ¬A∨B. Tuy nhiên, cách viết này có thể khó hiểu và không trực quan.

Thay vào đó, tác giả khuyến nghị sử dụng if (a) assert(b); để làm cho ý nghĩa của assertion trở nên rõ ràng hơn. Ví dụ, thay vì viết assert(header_b != null or replica.commit_min == replica.op_checkpoint);, ta có thể viết if (header_b == null) assert(replica.commit_min == replica.op_checkpoint);

Cách tiếp cận này làm cho mã nguồn dễ đọc và trực quan hơn bằng cách sử dụng cấu trúc conditional đơn giản thay vì các phép toán logic phức tạp. Đây là một ví dụ nhỏ nhưng hiệu quả về cách cải thiện khả năng đọc mã thông qua việc thay đổi cú pháp đơn giản.

Bài viết thể hiện triết lý thiết kế của TigerBeetle trong việc ưu tiên sự rõ ràng và đơn giản trong mã nguồn, giúp các lập trình viên dễ dàng hiểu và bảo trì hệ thống.

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