Hãy tự tay gõ mã
(haskellforall.com)- Tự tay gõ mã và luyện tập dựng lại từ trí nhớ là cách kiểm chứng sự hiểu và ghi nhớ nghiêm ngặt hơn so với việc sao chép
- Freecoding là khả năng viết mã ở cấp độ từng ký tự trong khi vẫn giữ cú pháp, kiểu và tên trong đầu, và điều này vẫn cần thiết ngay cả trong thời đại công cụ
- Cú pháp không phải là nhiễu cản trở tư duy ở mức cao, mà là sự nén của ý nghĩa chính xác để giúp tư duy ở mức cao hơn trở nên khả thi
- Nếu chỉ tra cứu lỏng lẻo kiểu và schema, thiết kế hệ thống sẽ trở nên mờ nhạt, và nếu không hiểu mô hình kiểu thì các cách né tránh như
as anysẽ xuất hiện ngày càng nhiều - Nếu thiếu khả năng nhớ tên và hiểu các phần đã có trước đó, tác nhân rất dễ tạo ra các phần triển khai trùng lặp, và việc rà soát đầu ra cũng như test sẽ trở nên khó khăn
Vì sao phải tự tay gõ mã
- Khóa học “Learn X the hard way” của Zed Shaw khuyên không nên copy-paste ví dụ mà phải tự nhập, và gần đây còn khuyến khích mạnh hơn việc sau khi hoàn thành bài tập thì xóa đi rồi dựng lại từ trí nhớ
- Trong tâm lý học nhận thức, hiện tượng chủ động tạo ra nội dung giúp hiểu tốt hơn so với chỉ thụ động tiếp nhận cùng nội dung đó được gọi là hiệu ứng tạo sinh
- Như câu nói của Richard Feynman “What I cannot create, I do not understand”, việc luyện tập dựng lại mã từ trí nhớ đồng thời kiểm tra cả sự hiểu lẫn trí nhớ
- Điều này không có nghĩa là trong thời đại agent coding phải ngừng dùng công cụ, nhưng thỉnh thoảng vẫn cần rèn khả năng viết mã ở cấp độ từng ký tự mà không dựa vào sự tiện lợi của công cụ
- Kiểu luyện tập này tập trung vào việc giữ trong đầu các chi tiết như cú pháp, kiểu và tên để có thể “freecoding”
Freecoding và tri thức chi tiết trong đầu
- Freecoding là khả năng tự viết mã dựa trên trí nhớ, và để làm được điều đó cần giữ trong đầu những yếu tố nền tảng như cú pháp, kiểu và tên
-
Cú pháp và cấu trúc
- Cần quen với từ khóa, dấu câu và các thành phần của ngôn ngữ
- Đây không chỉ là ghi nhớ ngữ pháp đơn thuần, mà còn gắn với khả năng hình dung chính xác hình dạng của đoạn mã
-
Kiểu và schema
- Cần quen thuộc và cảm thấy thoải mái với hệ thống kiểu và mô hình dữ liệu
- Nếu chỉ dừng ở mức mỗi lần đều phải tra cứu bảng, cột, quan hệ và cấu trúc kiểu của dự án thì sẽ khó thiết kế ở cấp độ hệ thống
-
Tên
- Cần có thể nhớ chính xác tên hàm, phương thức, lớp, import, file và package
- Khi dự án và dependency thay đổi, phần tri thức này cũng phải luôn được cập nhật
- Nếu không thể gõ với độ chính xác nhất định đoạn mã đã hình dung trong đầu, thì trên thực tế rất có thể bạn không hiểu rõ nó mà chỉ đang tưởng mình hiểu
- Tiếng Anh không phải là một ngôn ngữ chính xác như mã, và điều này cũng liên hệ với bài viết riêng A sufficiently detailed spec is code: một bản đặc tả đủ chi tiết thì cuối cùng cũng sẽ tiến gần đến mã
Cú pháp giúp tư duy ở mức cao trở nên khả thi
- Dù IDE hay coding agent có thể sửa cú pháp giúp bạn, khả năng tự xử lý cú pháp vẫn rất quan trọng
- Nếu bạn còn chật vật với việc khớp dấu ngoặc, thì cũng có thể bị đặt dấu hỏi về khả năng kết nối trôi chảy các tiền đề và kết luận logic của người khác
- Thái độ phớt lờ chi tiết có thể dẫn đến mù chữ chức năng, tức là giao tiếp bằng cảm giác thay vì phân biệt rõ nghĩa của từ ngữ
- Ví dụ về prompt cho LLM nhìn qua có vẻ hợp lý, nhưng nếu đọc kỹ thì lại đồng thời chứa các chỉ dẫn mâu thuẫn nhau
- “Không đề xuất công cụ bên ngoài hay lựa chọn thay thế không nằm trong các skill được liệt kê ở trên”
- “Nếu công việc đòi hỏi năng lực vượt quá các skill sẵn có thì hãy nói rõ điều đó”
- Khả năng xử lý chính xác ngữ pháp, chính tả và cấu trúc nhỏ không tách rời khỏi khả năng hiểu chính xác bức tranh lớn
- Cú pháp không phải là chi tiết gây cản trở tư duy cấp cao, mà là công cụ tinh thần giúp nén và mở đường cho tư duy ở mức cao hơn
- Ký hiệu kiểu đôi khi còn chính xác và ngắn gọn hơn mô tả bằng ngôn ngữ tự nhiên
x là một mảng đối tượng, trong đó mỗi đối tượng có thuộc tính domain bắt buộc lưu chuỗi và thuộc tính port tùy chọn lưu sốx : { domain: string, port?: number }[]
Kiểu và schema là manh mối cốt lõi của thiết kế hệ thống
- Trong The Mythical Man-Month, Fred Brooks nói rằng chỉ cần nhìn bảng là trong đa số trường hợp có thể thấy rõ cấu trúc ngay cả khi không có lưu đồ
- Nếu dùng cơ sở dữ liệu, bạn phải nắm rất rõ các bảng, tên cột và quan hệ của dự án
- Thay vì tra cứu lỏng lẻo thông tin này mỗi khi cần, cần chủ động hiểu trước và giữ nó trong đầu thì mới có thể thiết kế ở cấp độ hệ thống một cách hiệu quả
- Nếu không làm vậy, schema cơ sở dữ liệu cũng dễ trở thành cấu trúc phi chuẩn hóa với nhiều dữ liệu trùng lặp hoặc na ná nhau, đúng bằng mức độ rối rắm trong suy nghĩ
- Kinh nghiệm với các ngôn ngữ kiểu mạnh như Rust hay Haskell cho thấy rất rõ lợi ích của việc có một mô hình tinh thần mạnh mẽ về kiểu
- Việc rèn một “trình kiểm kiểu trong đầu” hay “borrow checker trong đầu” chính xác vẫn hữu ích ngay cả khi dùng ngôn ngữ kiểu yếu hơn
- Nếu không dùng đến nhóm cơ bắp suy luận trừu tượng này, bạn cũng dễ bỏ lỡ cả những nền tảng mô hình hóa dữ liệu như khiến các trạng thái không hợp lệ trở nên không thể biểu diễn
- Nếu không hiểu các kiểu khớp nối với nhau thế nào, bạn sẽ bắt đầu rải
as anykhắp mã TypeScript - Nếu không chịu nổi sự bất tiện của việc nghĩ về kiểu trên đường đi bình thường, thì đường đi bất thường là debug lỗi kiểu sẽ còn khó chịu hơn nữa
- Điều này không có nghĩa Haskeller hay Rustacean luôn viết mã tốt hơn, nhưng nếu các điều kiện khác như nhau thì sự lưu loát về kiểu rõ ràng có ích
Phải nhớ tên thì mới tái sử dụng được
- Các tên hàm, phương thức, lớp, import, package và file thường dùng trong dự án hoặc dependency cần phải hiện ra dễ dàng trong đầu
- Đây là một dạng đặc biệt của nguyên tắc tổng quát hơn: phải quen với những gì đã được tạo ra trước đó, tức prior art
- Nhiều người dựa vào coding agent vì họ không biết đã tồn tại prior art có thể tái sử dụng để làm đúng chức năng họ muốn, và kết quả là để agent phát minh lại bánh xe
- Nếu không biết đến SaaS boilerplate projects, bạn rất dễ nghĩ rằng agent phải scaffold mọi thứ từ đầu
- Với mục đích này, clone một dự án mã nguồn mở đã được kiểm chứng và sinh ra để làm việc đó thường nhanh hơn, rẻ hơn và đáng tin hơn so với yêu cầu agent làm cùng việc
- Khi ai đó dự đoán rằng chỉ vài năm nữa LLM sẽ có thể tạo ra cả trình duyệt, vẫn có thể phản biện rằng fork Chromium thì điều đó đã làm được ngay hôm nay và còn dễ sửa đổi, bảo trì hơn
- Ngay cả trong việc tái sử dụng mã bên trong cùng một dự án hay công ty, khả năng nhớ tên vẫn rất quan trọng
- Nếu bạn không biết cần tìm gì, bạn sẽ không thể xây tiếp trên công việc của đồng đội
Việc rà soát đầu ra của agent cần đến sự hiểu biết của con người
- Agent có thể giúp dò tên và sinh mã, nhưng một vấn đề mới là liệu con người có đủ khả năng rà soát đầu ra đó một cách có ý nghĩa hay không
- Nếu không biết các tính năng sẵn có, bạn sẽ khó đánh giá liệu agent có đang triển khai trùng lặp chức năng hay không
- Nếu bạn hiểu mã còn kém hơn cả agent, thì cũng khó mà rà soát đúng chất lượng đầu ra của agent
- Bạn có thể yêu cầu agent sinh test, nhưng nếu không xem xét kỹ các test được tạo ra thì bản thân test có thể chỉ là mã vô nghĩa
- Ví dụ thực tế là một bộ test chỉ kiểm tra giá trị kỳ vọng bằng cách áp trực tiếp
somehoặcincludeslên mảng và chuỗi mà không hề gọi mã triển khaidescribe("abort detection logic", () => { it("detects aborted stopReason in messages", () => { const messages = [ { role: "assistant", stopReason: "aborted", content: [] }, ]; const isAborted = messages.some((m: any) => m.stopReason === "aborted"); expect(isAborted).toBe(true); }); it("detects abort in error string", () => { const error = "The operation was aborted"; const isAborted = error.includes("abort"); expect(isAborted).toBe(true); }); it("does not false-positive on normal errors", () => { const error = "Network timeout"; const isAborted = error.includes("abort"); expect(isAborted).toBe(false); }); it("does not false-positive on normal stop reasons", () => { const messages = [ { role: "assistant", stopReason: "stop", content: [] }, ]; const isAborted = messages.some((m: any) => m.stopReason === "aborted"); expect(isAborted).toBe(false); }); }); - Phát triển phần mềm có rất nhiều ma sát thường nhật, và nếu bạn chọn không vượt qua ma sát nhỏ là nhớ tên, thì cũng sẽ dễ không vượt qua ma sát lớn hơn là rà soát test
- Coding agent lấy chỉ thị của người dùng làm bối cảnh chính, nên một người dùng lười suy nghĩ về mặt trí tuệ cũng có thể kéo agent đi theo hướng lười tương tự
Kiên trì và thành thạo không tách rời nhau
- Eustress là dạng căng thẳng có lợi; khi bạn tự thúc mình làm việc mới và khó, bạn sẽ tăng khả năng chịu đựng những khó chịu nhỏ và từ đó vượt qua được cả những khó chịu lớn hơn
- Nếu lúc nào cũng né tránh sự khó chịu, bạn sẽ rơi vào vòng xoáy đi xuống của cảm giác bất lực và thất vọng ngày càng lớn
- Khi rèn sự chính xác, trí nhớ và tư duy có cấu trúc trong một lĩnh vực, năng lực ở lĩnh vực khác cũng được cải thiện theo
- Giống như LLM khái quát hóa từ dữ liệu huấn luyện, con người cũng khái quát hóa những thói quen và năng lực được rèn trong một lĩnh vực sang lĩnh vực khác
- Về bản chất, phát triển phần mềm luôn bao gồm việc thường xuyên bước ra khỏi vùng thoải mái, và điều này cùng mạch với bài viết liên quan Software engineers are not (and should not be) technicians
1 bình luận
Ý kiến trên Lobste.rs
Hoàn toàn đồng ý. Sau khi làm xong bài tập thực hành, xóa ngay những gì vừa làm và thử làm lại chỉ bằng trí nhớ là điều cực kỳ quan trọng
Nếu bị mắc thì có thể xem gợi ý, nhưng có rất ít điều quan trọng bằng việc tập thói quen cố tái dựng lại quy trình vừa làm từ trong trí nhớ nhiều nhất có thể
Thông điệp commit và tài liệu nên được tự tay gõ
Lập trình viên thường ghét kiểu viết lách này nên rất dễ nghĩ “để LLM viết là được”, nhưng khi tự viết thì bạn sẽ phải đối diện trải nghiệm người dùng và các quyết định triển khai trong cùng một chỗ. Kiểu rà soát như “Để làm X thì truyền
-Y... khoan, đây có thật là tốt nhất không?” hoặc “Cái này sửa X bằng Y... nhưng chẳng phải cũng có thể làm bằng Z sao?” sẽ tự nhiên xuất hiệnTôi đã cười ở đoạn “nếu bạn thấy khó cân bằng dấu ngoặc, thì tôi nghi ngờ khả năng bạn có thể nối tiền đề logic và kết luận của người khác một cách trôi chảy đến đâu”, nhưng cũng hơi chột dạ trong chốc lát
Nói đỡ thì đôi khi hàm thực sự phình ra quá lớn
Sửa đống rác vibe coding mà mình không hiểu rõ cũng thực ra là một cách học khá tốt. Bạn sẽ phải gõ rất nhiều, và làm việc trong ngữ cảnh thực tế chứ không phải trong môi trường chân không
Tôi đã xem việc viết lại nó bằng tay như một cơ hội để lấy lại cảm giác lập trình đã bị gỉ sét suốt 8 tháng qua. Tôi thích kết quả sau khi viết lại, và nó thực sự hoạt động tốt hơn. LLM vẫn hữu ích để giải thích hoặc gỡ chỗ bị kẹt, nhưng cảm giác mình hiểu toàn bộ codebase vẫn tốt hơn rất nhiều
Tôi không thật sự hiểu vì sao prompt “tuyệt đối không đề xuất công cụ bên ngoài hay phương án thay thế nào không có trong các năng lực được liệt kê. Nếu cần khả năng vượt quá những năng lực sẵn có để làm tác vụ, hãy nói như vậy” lại là hai chỉ thị đối nghịch nhau
Nói “không thể làm việc này chỉ với các năng lực đã cung cấp” là phù hợp với cả hai chỉ thị. Nếu câu cuối là “hãy nói cần khả năng gì” thì mới xung đột, nhưng với cách viết hiện tại thì tôi không thấy mâu thuẫn
Câu thứ hai đọc như đang yêu cầu model không phải ám chỉ bằng phủ định rằng có tồn tại công cụ ngoài hay phương án thay thế cần thiết, mà là khẳng định mang tính xây dựng về sự tồn tại đó
Câu “những người không thể diễn đạt rõ ràng về mặt chức năng thì gần như không ngoại lệ cũng gặp khó khăn khi viết câu đúng chính tả hoặc ngữ pháp” nghe có vẻ là một định kiến khá nặng nề với những người bị chứng khó đọc hoặc đơn giản là suy nghĩ theo cách khác
Tất nhiên có lẽ tác giả không định nói theo chiều ngược lại rằng không viết đúng chính tả hay ngữ pháp thì lập tức không rõ ràng về mặt chức năng, nhưng dù theo hướng nào thì đây cũng là cách diễn đạt thiếu bao dung
Nếu nhìn theo hướng xây dựng hơn, ta có thể nghĩ tới các trình biên tập dạng node. Hệ thống dựa trên node hiện tại có nhiều vấn đề trong cách triển khai, nhưng nếu được làm tốt thì chúng có thể kiểm soát cách viết chương trình để loại bỏ hẳn một số lỗi cú pháp. Ví dụ nếu một node nhận chuỗi thì bạn không thể truyền số vào đó. Không phải vì ràng buộc được ép ở thời điểm build hay runtime, mà vì ngay từ đầu bạn đã không thể “nói” số ở vị trí đó. Những thứ như phạm vi lặp sai, sai số lượng đối số, hay mất cân bằng dấu ngoặc cũng có thể được làm cho không thể sai về mặt cấu trúc
Việc viết phần mềm bằng công cụ cưỡng chế một số ràng buộc theo cấu trúc không có nghĩa là bạn không hiểu những ràng buộc đó. Chỉ có nghĩa là bạn bớt phải lo vô tình nhập sai ở kiểu lỗi đó. Cảm thấy khó chịu khi công cụ không tự lo các chi tiết cú pháp như số lượng dấu ngoặc nhọn của hàm hay thụt lề của khối logic cũng không có nghĩa là bạn không hiểu vì sao cần quan tâm tới cú pháp đó
Tôi lấy ví dụ hệ thống node vì nó loại bỏ yếu tố “ném đó rồi quên” của LLM hay vibe coding tắt não, và chỉ tách riêng vấn đề cú pháp ra để nhìn. Bỏ LLM ra thì lập luận tập trung vào cú pháp trở nên khá yếu
Tôi đồng ý với phần còn lại của bài viết, nhưng xem năng lực cú pháp như một chỉ dấu nền tảng cho mức độ hiểu code hay khả năng viết code tốt của ai đó thì theo tôi là đi sai hướng rất nhiều
Tôi cũng hiểu đây là phản hồi thiện chí và không muốn tỏ ra phòng thủ. Tôi nghĩ mình đã cố diễn đạt sao cho không nghe như một khẳng định thiếu bao dung như vậy, nhưng nếu có cách viết hay cấu trúc khác để truyền đạt cùng ý tốt hơn thì tôi sẵn sàng tiếp thu
Tuy vậy, tôi không muốn xóa hẳn ý đó. Điều cốt lõi tôi muốn truyền đạt là khi một người bắt đầu né tránh theo trải nghiệm sự khó chịu về mặt tinh thần, thì chính tả và ngữ pháp thường là thứ sụp đổ đầu tiên. Có lẽ vẫn có cách điều chỉnh lập luận để những người mắc chứng khó đọc không bị ảnh hưởng tiêu cực trong quá trình đó