Phong trào từ đặc tả đến mã nguồndựa vào AI thường tạo ramã nguồnchất lượng kém và không bền vững, cho thấy sự thiếu hiểu biết vềnguyên tắc cơ bản về phần mềm.- Để cộng tác hiệu quả với
AI, điều cần thiết là phải xây dựngsự hiểu biết chungvềthiết kế, thiết lậpngôn ngữ phổ quátvà áp dụng các phương pháp đã được kiểm chứng nhưPhát triển hướng kiểm thử (TDD). - Việc tập trung vào
kiến trúc cơ sở mãchất lượng cao vớideep modulesgiúp giảmđộ phức tạp, tăng khả năngkiểm thửvà cho phép cácnhà phát triểnủy quyềnviệc triển khaichi tiết choAI, từ đó giảmgánh nặng nhận thức.
"Software Fundamentals Matter More Than Ever" — Matt Pocock
- Chống lại quan niệm "mã nguồn rẻ":
Mã nguồnchất lượng kém doAItạo ra từđặc tảmà không có sự giám sát sẽ rất đắt đỏ để bảo trì và thay đổi. - Sử dụng kỹ năng "Grill Me" để đạt
sự hiểu biết chung: KhiAIkhông hiểuý định, dùng prompt "Phỏng vấn tôi không ngừng..." để buộcAIđặt câu hỏi chuyên sâu, xây dựngkhái niệm thiết kếchung trước khi bắt đầulập kế hoạch. - Xây dựng
ngôn ngữ phổ quátvớiAI: Để giải quyết vấn đềAIdài dòngvà thiếungôn ngữ chung, tạo mộttệp Markdownchứa cácthuật ngữquan trọng từcơ sở mã(domain model) đểAIvànhà phát triểncùng sử dụng nhất quán. - Áp dụng
Phát triển hướng kiểm thử (TDD): ĐểAIkhông tạo ramã nguồnkhônghoạt độngvà tránhvượt quá tầm đèn pha, yêu cầuAIviếtbài kiểm thửtrước, thực hiện cácbước nhỏ, có chủ đíchtrong quá trìnhtriển khai. - Đầu tư vào
Deep Modulestrongkiến trúc cơ sở mã: Xây dựngcơ sở mãvớideep modules(nhiều chức năng,giao diệnđơn giản) thay vìshallow modules(ít chức năng,giao diệnphức tạp) đểcơ sở mãdễkiểm thửvà dễ hiểu hơn cho cảAIvà con người. - Thiết kế
giao diện, ủy quyềnviệc triển khai: Vớideep modules,nhà phát triểntập trung vàothiết kếcácgiao diệnbên ngoài mạnh mẽ và đáng tin cậy, sau đó có thể an tâm giao phóviệc triển khainội bộ choAI, giảmgánh nặng nhận thức.
kỹ năng— skillphong trào từ đặc tả đến mã nguồn— specs to code movementmã nguồn— source code (code)Mô hình Ngôn ngữ Lớn (LLM)— Large Language Model (LLM)cơ sở mã— codebasenguyên tắc cơ bản về phần mềm— fundamental software principleschế độ lỗi— failure modekhái niệm thiết kế— design conceptngôn ngữ phổ quát— ubiquitous languagethiết kế hướng nghiệp vụ (DDD)— domain-driven design (DDD)Phát triển hướng kiểm thử (TDD)— Test Driven Development (TDD)vòng lặp phản hồi— feedback loopsmodule "sâu"— deep modulemodule "nông"— shallow modulegiao diện— interfaceviệc triển khai— implementationgánh nặng nhận thức— cognitive load
Chào mọi người, hội nghị của chúng ta diễn ra tốt đẹp chứ? Mọi người cảm thấy thế nào về hội nghị cho đến bây giờ? Tốt, tuyệt vời. Tôi có một thông điệp dành cho các bạn, một thông điệp an ủi những người tin rằng bộ kỹ năng của họ không còn giá trị trong kỷ nguyên mới này. Thông điệp đó là: Tôi tin rằng các nguyên tắc cơ bản về giá trị bản thân giờ đây quan trọng hơn bao giờ hết. Tôi là một giáo viên, và gần đây tôi đã giảng dạy một khóa học mang tên 'Claude Code cho các Kỹ sư Thực thụ,' một cái tên khá khiêu khích. Trong quá trình xây dựng khóa học này, tôi phải tạo ra một chương trình giảng dạy về AI coding, điều này khá khó khăn vì mọi thứ thay đổi liên tục, đúng không?
Phản bác phong trào "từ đặc tả đến mã nguồn"
Chúng ta đang có một mô hình hoàn toàn mới. Chắc chắn chúng ta cần loại bỏ tất cả các quy tắc cũ để đón nhận những điều mới mẻ. Và có một phong trào đã xuất hiện xung quanh ý tưởng này, đó là phong trào từ đặc tả đến mã nguồn (specs to code movement). Phong trào từ đặc tả đến mã nguồn cho rằng, bạn có thể viết một đặc tả về cách một ứng dụng nên hoạt động. Sau đó, bạn có thể dùng AI để biến nó thành mã nguồn. Nếu có vấn đề với ứng dụng, bạn quay lại đặc tả. Bạn có thể thực sự xem xét mã nguồn, chỉ cần thay đổi đặc tả, chạy lại trình biên dịch, và bạn sẽ có thêm mã nguồn. Giơ tay nếu bạn đã từng nghe về điều này. Giữ tay nếu bạn đã thử nó. Được rồi, tôi cũng đã thử rồi. Mọi người có thể bỏ tay xuống. Điều tôi nhận thấy là khi tôi chạy nó, tôi cố gắng không nhìn vào mã nguồn, nhưng cuối cùng tôi vẫn phải nhìn. Và tôi nhận ra rằng, đầu tiên tôi nhận được mã nguồn, sau đó khi tôi chạy nó, tôi lại nhận được mã nguồn tệ hơn. Tôi thử lại, cứ chạy trình biên dịch hết lần này đến lần khác, và cuối cùng chỉ nhận được rác. Giơ tay nếu điều đó đã từng xảy ra với bạn. Vâng, tôi không nghĩ cách này hiệu quả. Ý tưởng rằng chúng ta có thể bỏ qua mã nguồn và để mã nguồn tự quản lý chính nó chỉ là một kiểu coding dưới một cái tên khác. Và tôi không tin vào điều đó.
Mã nguồn chất lượng và Nguyên tắc Cơ bản Phần mềm
Tôi nghĩ, được rồi, làm thế nào để tôi sửa trình biên dịch? Làm thế nào để nó không tạo ra mã nguồn tồi hoặc mã nguồn tệ hơn mỗi lần? Và thế là tôi nghĩ, tôi cần giải thích cho Mô hình Ngôn ngữ Lớn (LLM) bằng tiếng Anh về một cơ sở mã tốt trông như thế nào. Hãy để tôi lôi ra một trong những cuốn sách yêu thích cũ của mình, đó là A Philosophy of Software Design của John Ousterhout. Hãy lên Amazon và mua nó. Ông ấy có một định nghĩa về mã nguồn tồi trông như thế nào. Ông gọi đó là mã phức tạp (complex code). Độ phức tạp (complexity) là bất cứ thứ gì liên quan đến cấu trúc của một hệ thống phần mềm mà làm cho hệ thống đó khó hiểu và khó thay đổi. Vì vậy, một cơ sở mã tồi là một cơ sở mã khó thay đổi. Nếu bạn không thể thay đổi một cơ sở mã mà không gây ra lỗi, thì đó là một cơ sở mã tồi. Các cơ sở mã tốt không dễ thay đổi. Vậy là tôi nghĩ, điều đó tốt. Hãy thử một cuốn sách khác. Hãy thử The Pragmatic Programmer. Lên Amazon và mua nó. Họ có một chương dành cho thứ gọi là entropy phần mềm (software entropy). Và đây chính xác là điều tôi đang thấy. Entropy là ý tưởng rằng mọi thứ có xu hướng tiến tới thảm họa, phân rã và sụp đổ. Và đây cũng chính xác là cách hầu hết các hệ thống phần mềm hoạt động: mỗi khi bạn thực hiện một thay đổi đối với một cơ sở mã, nếu bạn chỉ nghĩ về thay đổi đó mà không nghĩ về thiết kế của toàn bộ hệ thống, cơ sở mã của bạn sẽ ngày càng tệ hơn. Và đó là điều tôi đang chứng kiến. Mọi thứ trong ý tưởng từ đặc tả đến mã nguồn, rằng bạn chỉ cần chạy lại trình biên dịch hết lần này đến lần khác, đã tạo ra mã nguồn tệ hơn. Bây giờ, có một ý tưởng thúc đẩy phong trào từ đặc tả đến mã nguồn, đó là mã nguồn thì rẻ. Giơ tay nếu bạn đã từng nghe cụm từ đó, rằng mã nguồn thì rẻ. Chà, tôi không nghĩ điều này đúng. Tôi nghĩ mã nguồn không rẻ. Trên thực tế, mã nguồn tệ là thứ đắt đỏ nhất từ trước đến nay. Bởi vì nếu bạn có một cơ sở mã khó thay đổi, bạn sẽ không thể tận dụng tất cả những lợi ích mà AI có thể mang lại, bởi vì AI thực sự hoạt động rất tốt trong một cơ sở mã tốt. Và điều này có nghĩa là các cơ sở mã tốt quan trọng hơn bao giờ hết, tức là các nguyên tắc cơ bản về phần mềm quan trọng hơn bao giờ hết. Đó là luận điểm của bài nói chuyện này. Vậy thì, hãy cùng đi vào các vấn đề thực tế.
Các Chế độ Lỗi Thường gặp khi làm việc với AI
Tôi muốn nói về các chế độ lỗi khác nhau mà bạn có thể đã trải nghiệm hoặc chưa từng gặp phải khi làm việc với AI, và cách bạn có thể tránh chúng chỉ bằng cách quay lại các cuốn sách cũ và xem xét các thực hành phần mềm truyền thống. Được thôi.
Chế độ Lỗi 1: AI Không Thấu Hiểu Ý định
Vậy chế độ lỗi đầu tiên là AI không làm những gì tôi muốn. Tôi nghĩ mình có một ý tưởng hay trong đầu và AI lại làm một điều hoàn toàn khác. Hoặc nó tạo ra những đặc tả mà tôi không muốn. Giơ tay nếu bạn đã gặp chế độ lỗi này. Tuyệt. Được rồi. Trong The Pragmatic Programmer, họ nói rằng không ai biết chính xác họ muốn gì. Giữa bạn và AI có một rào cản giao tiếp, đúng không? Và khi bạn nói chuyện với AI, đó giống như việc AI đang thu thập yêu cầu. Về cơ bản, nó đang tìm hiểu từ bạn những gì bạn cần. Và tôi nhận ra rằng có một cuốn sách khác, The Design of Design. Cuốn sách này nói về ý tưởng gọi là khái niệm thiết kế (design concept) rằng khi có nhiều hơn một người cùng thiết kế một thứ gì đó, bạn sẽ có một ý tưởng lơ lửng giữa các bạn, một ý tưởng phù du về thứ bạn đang xây dựng. Và thứ bạn đang xây dựng hay ý tưởng về nó được gọi là khái niệm thiết kế. Nó không phải là một tài sản. Nó không phải là thứ bạn có thể đặt vào một tệp Markdown. Nó là một lý thuyết vô hình về những gì bạn đang xây dựng. Và tôi nghĩ, được rồi, đó là điều đang xảy ra. Tôi và AI không chia sẻ một khái niệm thiết kế chung.
Giải pháp: Kỹ năng "Grill Me" để có Sự Thấu Hiểu Chung
Vì vậy, tôi đã tạo ra một kỹ năng. Kỹ năng này rất, rất đơn giản. Nó được gọi là Grill Me. Và nó trông như thế này: "Phỏng vấn tôi không ngừng về mọi khía cạnh của kế hoạch này cho đến khi chúng ta đạt được một sự hiểu biết chung. Đi sâu vào từng nhánh của cây thiết kế (design tree) – một khái niệm khác của Frederick P. Brooks – giải quyết các phụ thuộc giữa các quyết định từng cái một." Kỹ năng này, repository chứa nó có khoảng 13.000 sao hoặc gì đó, nó thực sự bùng nổ, trở nên viral, mọi người rất yêu thích nó. Vài dòng này có nghĩa là AI sẽ hỏi bạn khoảng 40, 60 câu hỏi. Tôi đã từng thấy có người bị hỏi tới hàng trăm câu trước khi AI cảm thấy hài lòng rằng đã đạt được sự hiểu biết chung. Điều đó có nghĩa là nó biến AI thành một dạng đối thủ nơi nó liên tục trao đổi ý tưởng với bạn và cố gắng đạt được sự hiểu biết chung. Và điều đó có nghĩa là cuộc trò chuyện mà bạn tạo ra sau đó, bạn có thể lấy nó và biến nó thành tài liệu yêu cầu sản phẩm (product requirements documents) hoặc một thứ gì đó tương tự. Hoặc nếu đó là một thay đổi nhỏ, bạn có thể biến nó trực tiếp thành vấn đề (issues). Và sau đó, tác nhân AFK của bạn sẽ tiếp nhận nó. Đừng công kích tôi về điều này, nhưng cá nhân tôi tin rằng cách này tốt hơn chế độ lập kế hoạch (plan mode) mặc định trong công cụ mà tôi sử dụng, được gọi là Code. Chế độ lập kế hoạch rất ham muốn tạo ra một tài sản. Nó thực sự chỉ muốn tạo ra một kế hoạch và bắt đầu làm việc. Trong khi đó, tôi nghĩ sẽ tốt hơn nhiều nếu chúng ta đạt được một khái niệm thiết kế chung trước. Đó là mẹo số một.
Chế độ Lỗi 2: AI Quá Dài dòng
Bây giờ, chế độ lỗi thứ hai là AI quá dài dòng. Giống như bạn và AI đang nói chuyện không cùng mục đích vậy. Giơ tay nếu bạn cảm thấy điều này. Nếu bạn đã trải nghiệm chế độ lỗi đó. Phải. Giống như AI chỉ đang dùng quá nhiều từ để cố gắng truyền đạt những gì nó đang làm. Nó không giống như bạn đang nói cùng một ngôn ngữ. Và điều này đối với tôi cảm thấy rất, rất quen thuộc. Đúng vậy. Nếu bạn đã là một nhà phát triển trong một thời gian dài và bạn đã làm việc với, ví dụ, chuyên gia nghiệp vụ (domain experts), người đang xây dựng một ứng dụng. Giả sử chuyên gia nghiệp vụ muốn bạn xây dựng một thứ gì đó về vi mạch. Bạn không hề biết vi mạch là gì. Bạn cần thiết lập một loại ngôn ngữ chung. Đúng không? Bởi vì nếu không, họ sẽ sử dụng các thuật ngữ mà bạn không hiểu. Bạn sẽ dịch điều đó thành mã nguồn mà có thể bạn cũng không hiểu. Và chắc chắn chuyên gia nghiệp vụ cũng sẽ không hiểu. Vì vậy, có một loại khoảng cách ngôn ngữ giữa bạn và chuyên gia nghiệp vụ. Và thế là tôi quay lại thiết kế hướng nghiệp vụ (DDD) (domain-driven design). Đây là thứ mà tôi vẫn đang trong quá trình khám phá. Nhưng mọi thứ tôi đọc về DDD đều như âm nhạc đối với tôi. Tôi thực sự yêu thích nó.
Giải pháp: Xây dựng Ngôn ngữ Phổ quát với AI
Và DDD có một khái niệm về ngôn ngữ phổ quát (ubiquitous language). Nơi mà ngôn ngữ phổ quát, các cuộc trò chuyện giữa các nhà phát triển và các biểu thức trong mã nguồn cũng như các cuộc trò chuyện với chuyên gia nghiệp vụ đều xuất phát từ cùng một mô hình nghiệp vụ (domain model). Về cơ bản, đó là một tệp Markdown chứa danh sách các thuật ngữ mà bạn và AI có điểm chung. Và bạn thực sự tập trung vào những thuật ngữ đó và đảm bảo rằng chúng phù hợp với ý nghĩa thực sự của chúng. Và bạn sử dụng chúng mọi lúc trong mã nguồn khi nói về mã nguồn, khi nói chuyện với chuyên gia nghiệp vụ. Hoặc trong trường hợp của chúng ta, khi nói chuyện với AI. Vì vậy, tôi đã tạo ra một kỹ năng. Kỹ năng này là kỹ năng ngôn ngữ phổ quát về cơ bản chỉ quét cơ sở mã của bạn, tìm kiếm thuật ngữ, và sau đó tạo một tệp Markdown, tạo tệp Markdown ngôn ngữ phổ quát, một loạt các bảng Markdown với tất cả các thuật ngữ. Và sau đó tôi chuyển nó cho AI và tôi cũng có thể đọc nó. Và tôi thực sự luôn mở nó khi tôi trao đổi với AI và lập kế hoạch, v.v. Điều tôi nhận thấy khi đọc dấu vết suy nghĩ của AI không chỉ cải thiện việc lập kế hoạch mà còn cho phép AI suy nghĩ một cách ít dài dòng hơn và thực sự có nghĩa là việc triển khai sẽ phù hợp hơn với những gì bạn thực sự lập kế hoạch. Vì vậy, điều này thực sự là một công cụ mạnh mẽ. Nó tốt một cách đáng kinh ngạc. Vậy đó là mẹo số hai: tạo một ngôn ngữ chung với AI.
Chế độ Lỗi 3: AI Tạo ra Mã không Hoạt động
Vậy được rồi, hãy tưởng tượng bạn đã đồng nhất với AI. Bạn biết mình phải xây dựng cái gì. AI đã xây dựng đúng thứ nhưng nó không hoạt động. Giơ tay nếu điều đó đã xảy ra với bạn. Đúng vậy, nó chỉ đơn giản là không hoạt động. Chà, có một điều hiển nhiên chúng ta có thể làm để cải thiện điều đó, đó là sử dụng vòng lặp phản hồi (feedback loops). Chúng ta có thể sử dụng kiểu tĩnh (static types), bạn biết đấy, nếu bạn không dùng TypeScript thì thật điên rồ. Nếu bạn không dùng, nếu bạn đang xây dựng một ứng dụng giao diện người dùng (front-end app) và bạn không cấp cho LLM quyền truy cập vào trình duyệt để nó có thể kiểm tra xung quanh, thì đó là điều thực sự cần thiết. Và rõ ràng bạn cũng cần kiểm thử tự động (automated tests). Và một điều tôi nhận thấy ở đây là ngay cả với những vòng lặp phản hồi này, LLM không sử dụng chúng hiệu quả lắm. Nó không tận dụng tối đa vòng lặp phản hồi của mình theo cách mà một nhà phát triển kỳ cựu sẽ làm. Và vì vậy, điều nó có xu hướng làm là thực hiện quá nhiều thứ cùng một lúc, nó sẽ tạo ra một lượng lớn mã nguồn và sau đó mới nghĩ, "Ồ, có lẽ tôi nên kiểm tra kiểu (type check) đó," hoặc "Có lẽ tôi nên kiểm tra một bài kiểm thử (test) về điều đó." Và điều này trong The Pragmatic Programmer được mô tả là vượt quá tầm đèn pha (outrunning your headlights), về cơ bản là lái xe quá nhanh vì tốc độ phản hồi là giới hạn tốc độ của bạn. Điều đó có nghĩa là bạn nên kiểm thử trong quá trình thực hiện, thực hiện các bước nhỏ, có chủ đích, và AI theo mặc định thực sự không giỏi điều đó.
Giải pháp: TDD và Thách thức trong Kiểm thử
Vì vậy, kỹ năng số ba là TDD. Bạn nên sử dụng Phát triển hướng kiểm thử (Test Driven Development) vì TDD buộc Mô hình Ngôn ngữ Lớn (LLM) phải thực hiện các bước nhỏ. Bạn tạo một bài kiểm thử trước, làm cho bài kiểm thử đó đạt, và sau đó bạn tái cấu trúc (refactor) mã nguồn để làm cho nó tốt hơn và xem xét thiết kế. Vấn đề ở đây là kiểm thử thực sự khó. Kiểm thử luôn khó, và lý do là có rất nhiều quyết định khác nhau mà bạn cần đưa ra khi viết một bài kiểm thử. Bạn cần xác định đơn vị (unit) mà bạn muốn kiểm thử lớn đến mức nào. Bạn cần xác định những gì cần giả lập (mock). Bạn cần xác định những hành vi (behaviors) nào bạn thậm chí muốn kiểm thử ngay từ đầu. Và tất cả những quyết định này đều phụ thuộc lẫn nhau, vì vậy nếu bạn đang kiểm thử một đơn vị thực sự lớn như toàn bộ một ứng dụng khổng lồ, thì nó có thể khá không ổn định (flaky), bạn có thể không muốn kiểm thử quá nhiều hành vi. Nếu bạn chỉ kiểm thử đơn vị này, bạn cần giả lập đơn vị này, bạn biết đấy, tất cả đều liên kết với nhau. Và tôi đã suy nghĩ về điều này trong nhiều năm, suốt sự nghiệp phát triển của mình. Và điều chúng ta nhận thấy là các cơ sở mã tốt là các cơ sở mã dễ kiểm thử, đúng không?
Cải thiện Codebase và Khái niệm Deep Module
Vì vậy, ở đây chúng ta bắt đầu quay trở lại ý tưởng về tầm quan trọng của mã nguồn: cơ sở mã (codebase) của bạn càng tốt thì các vòng phản hồi (feedback loops) của bạn càng hiệu quả, bởi vì bạn có thể cung cấp phản hồi tốt hơn cho Mô hình Ngôn ngữ Lớn (LLM), giúp nó tạo ra mã tốt hơn. Do đó, tôi đã nghĩ: một cơ sở mã tốt, một cơ sở mã có thể kiểm thử (testable codebase) sẽ trông như thế nào? Chúng ta quay lại với suy nghĩ của Joe Out to, về việc có các module "sâu" (deep modules) trong cơ sở mã của bạn, chứ không phải các module "nông" (shallow modules), không phải nhiều module phơi bày quá nhiều hàm. Chúng nên là tương đối ít, các module lớn, sâu với các giao diện (interface) đơn giản.
Hãy so sánh chúng một cách nhanh chóng:
Deep modules: nhiều chức năng được ẩn đằng sau một giao diện đơn giản, che giấu sự phức tạp. Bạn có thể xem bên trongdeep modulenếu muốn, nhưng bạn không cần thiết, bạn chỉ cần sử dụng giao diện.Shallow modules: không nhiều chức năng, giao diện phức tạp.
Các shallow modules trong một cơ sở mã trông giống như thế này: chúng ta có vô số khối nhỏ khác nhau mà Trí tuệ nhân tạo (AI) phải "đi qua" và điều hướng. Điều này thực sự khó khăn để AI khám phá. Do đó, thường thì bạn sẽ thấy rằng nếu bạn có một cơ sở mã như thế này (mà AI lại rất giỏi trong việc tạo ra những cơ sở mã như vậy), bạn sẽ rơi vào tình huống mà AI không hiểu mã của bạn đang làm gì. Nó sẽ cố gắng khám phá mã, nhưng vì nó được sắp xếp kém, đầy các shallow modules, có thể nó không đến được đúng module kịp thời hoặc không hiểu tất cả các phần phụ thuộc, tất cả những thứ đó; nó không hiểu mã của bạn.
Vậy, một cơ sở mã đầy các deep modules trông như thế nào? Chà, nó trông như thế này: đó là cùng một mã, nhưng nó được cấu trúc bên trong các ranh giới nơi bạn có các giao diện này ở phía trên. Và bạn có thể sẽ phải kiểm soát rất nhiều và thiết kế rất tốt các giao diện này, nếu không AI có thể làm hỏng thiết kế. Nhưng việc triển khai (implementation) thì bạn có thể giao cho AI một chút.
Chuyển đổi kiến trúc Codebase
Vậy làm thế nào để bạn biến một cơ sở mã trông như thế này thành một cơ sở mã trông như thế kia? Chà, tôi có một kỹ năng (skill) cho việc đó: cải thiện kiến trúc cơ sở mã. Hóa ra điều này khá phức tạp, nhưng nó chỉ là một tập hợp các bước. Giống như một tập hợp các bước mà bạn có thể thực hiện lại nhiều lần: bạn chỉ cần khám phá cơ sở mã, tìm kiếm các cơ hội mà có những đoạn mã có liên quan và gói tất cả chúng vào một deep module. Và đây là một cơ sở mã có thể kiểm thử (testable codebase) bởi vì các ranh giới xung quanh mã này rất, rất đơn giản: bạn kiểm thử ở giao diện, bạn xác minh bằng cách sử dụng giao diện đó và bạn đã sẵn sàng. Vì vậy, đây là một cơ sở mã mang lại lợi ích cho bạn.
Giảm gánh nặng nhận thức với Deep Module
Còn về chế độ lỗi (failure mode) số sáu thì sao? Giả sử các vòng phản hồi (feedback loops) của bạn đang hoạt động, mọi thứ đang tiến triển, bạn có thể tạo ra nhiều mã hơn bao giờ hết, nhưng bộ não của bạn không thể theo kịp. Đúng không? Hãy giơ tay nếu bạn cảm thấy mệt mỏi hơn bao giờ hết trong sự nghiệp phát triển của mình. Vâng, tôi cũng vậy, điều đó không ổn. Và tôi nghĩ đây là một cơ sở mã thực sự làm cho bộ não của bạn khó khăn hơn, bởi vì bạn, cũng như AI, cần phải giữ tất cả thông tin đó trong đầu.
Trong khi đó, việc này không chỉ đơn giản hơn để bạn đọc và hiểu, nó còn có nghĩa là bạn có thể coi các module này, hoặc các deep modules này, như những hộp xám (gray boxes). Bạn có thể nói: "Được rồi, tôi sẽ chỉ thiết kế giao diện nhưng tôi sẽ không lo lắng quá nhiều hoặc không xem xét quá kỹ việc triển khai." Bạn có thể làm điều này rõ ràng với những thứ ít quan trọng hơn trong ứng dụng của mình. Áp dụng điều này với nhiều thứ như tài chính hoặc bất cứ thứ gì, nhưng trong rất nhiều module trong ứng dụng của bạn, bạn không cần phải suy nghĩ quá nhiều về việc triển khai miễn là bạn có một ranh giới có thể kiểm thử (testable boundary) bên ngoài module và miễn là bạn hiểu mục đích của nó và có thể thiết kế nó từ bên ngoài.
Tôi đã thấy điều này thực sự giúp bảo vệ bộ não của mình vì tôi có thể nói: "Được rồi, AI, tôi sẽ để bạn xử lý những gì bên trong khối lớn đó. Tôi sẽ chỉ kiểm thử từ bên ngoài và xác minh nó."
Thiết kế Giao diện và Tư duy Chiến lược
Vậy đó là mẹo số năm: Thiết kế giao diện, ủy quyền việc triển khai. Nhưng điều này có nghĩa là bất cứ khi nào chúng ta chạm vào mã nguồn, bất cứ khi nào chúng ta lập kế hoạch mọi thứ, chúng ta cần phải suy nghĩ và nhận thức được các module trong ứng dụng của chúng ta. Chúng ta cần hiểu rõ bản đồ đó. Nó cần phải là một phần của ngôn ngữ chung (ubiquitous language) của chúng ta. Chúng ta cần xây dựng nó vào các kỹ năng lập kế hoạch (planning skills) của mình. Vì vậy, trong Tài liệu Yêu cầu Sản phẩm (PRD) của tôi, tôi cụ thể về những thay đổi module và các giao diện bên trong các module đó, cách chúng đang được sửa đổi. Tôi luôn nghĩ về chúng, và điều này đến từ Kent Beck: "Đầu tư vào thiết kế của hệ thống mỗi ngày."
Và đây là cốt lõi của vấn đề, phải không? Bởi vì nếu là mã nguồn, chúng ta không đầu tư vào thiết kế của hệ thống. Chúng ta đang rút lui khỏi nó. Chúng ta đang loại bỏ điều đó. Trong khi điều này, tôi nghĩ, là hoàn toàn then chốt.
Kết luận và Tài nguyên
Vì vậy, mã nguồn không hề rẻ. Đó là thông điệp tôi muốn bạn ghi nhớ. Mã nguồn rất quan trọng. Và nếu chúng ta coi AI là một lập trình viên (programmer) giỏi thực địa, một lập trình viên mang tính chiến thuật, một hạ sĩ quan thực địa thực hiện các thay đổi mã, thì bạn cần một người ở cấp cao hơn. Bạn cần một người suy nghĩ ở cấp độ chiến lược. Và đó là bạn. Điều đó đòi hỏi những kỹ năng nền tảng phần mềm mà chúng ta đã sử dụng trong 20 năm, hoặc lâu hơn.
Bây giờ, nếu bạn quan tâm đến bất kỳ kỹ năng nào tôi đã trình bày ở đây, chúng có trong kho lưu trữ GitHub (GitHub repo) - MacPokec skills. Và nếu bạn quan tâm đến khóa đào tạo mà tôi thực hiện hoặc bất kỳ tài liệu miễn phí nào, tôi có mặt trên YouTube, Twitter, và cả trên AIhero.dev nơi tôi có một bản tin (newsletter) mà bạn có thể tìm hiểu. Cảm ơn rất nhiều. Tôi hy vọng điều này mang lại cho bạn sự tự tin trong kỷ nguyên AI mới này. Bạn thực sự có thể tạo ra tác động tốt. Cảm ơn.
TL;DR
Phong trào từ đặc tả đến mã nguồndựa vào AI thường tạo ramã nguồnchất lượng kém và không bền vững, cho thấy sự thiếu hiểu biết vềnguyên tắc cơ bản về phần mềm.- Để cộng tác hiệu quả với
AI, điều cần thiết là phải xây dựngsự hiểu biết chungvềthiết kế, thiết lậpngôn ngữ phổ quátvà áp dụng các phương pháp đã được kiểm chứng nhưPhát triển hướng kiểm thử (TDD). - Việc tập trung vào
kiến trúc cơ sở mãchất lượng cao vớideep modulesgiúp giảmđộ phức tạp, tăng khả năngkiểm thửvà cho phép cácnhà phát triểnủy quyềnviệc triển khaichi tiết choAI, từ đó giảmgánh nặng nhận thức.
Điểm chính
- Chống lại quan niệm "mã nguồn rẻ":
Mã nguồnchất lượng kém doAItạo ra từđặc tảmà không có sự giám sát sẽ rất đắt đỏ để bảo trì và thay đổi. - Sử dụng kỹ năng "Grill Me" để đạt
sự hiểu biết chung: KhiAIkhông hiểuý định, dùng prompt "Phỏng vấn tôi không ngừng..." để buộcAIđặt câu hỏi chuyên sâu, xây dựngkhái niệm thiết kếchung trước khi bắt đầulập kế hoạch. - Xây dựng
ngôn ngữ phổ quátvớiAI: Để giải quyết vấn đềAIdài dòngvà thiếungôn ngữ chung, tạo mộttệp Markdownchứa cácthuật ngữquan trọng từcơ sở mã(domain model) đểAIvànhà phát triểncùng sử dụng nhất quán. - Áp dụng
Phát triển hướng kiểm thử (TDD): ĐểAIkhông tạo ramã nguồnkhônghoạt độngvà tránhvượt quá tầm đèn pha, yêu cầuAIviếtbài kiểm thửtrước, thực hiện cácbước nhỏ, có chủ đíchtrong quá trìnhtriển khai. - Đầu tư vào
Deep Modulestrongkiến trúc cơ sở mã: Xây dựngcơ sở mãvớideep modules(nhiều chức năng,giao diệnđơn giản) thay vìshallow modules(ít chức năng,giao diệnphức tạp) đểcơ sở mãdễkiểm thửvà dễ hiểu hơn cho cảAIvà con người. - Thiết kế
giao diện, ủy quyềnviệc triển khai: Vớideep modules,nhà phát triểntập trung vàothiết kếcácgiao diệnbên ngoài mạnh mẽ và đáng tin cậy, sau đó có thể an tâm giao phóviệc triển khainội bộ choAI, giảmgánh nặng nhận thức.
Từ vựng
kỹ năng— skillphong trào từ đặc tả đến mã nguồn— specs to code movementmã nguồn— source code (code)Mô hình Ngôn ngữ Lớn (LLM)— Large Language Model (LLM)cơ sở mã— codebasenguyên tắc cơ bản về phần mềm— fundamental software principleschế độ lỗi— failure modekhái niệm thiết kế— design conceptngôn ngữ phổ quát— ubiquitous languagethiết kế hướng nghiệp vụ (DDD)— domain-driven design (DDD)Phát triển hướng kiểm thử (TDD)— Test Driven Development (TDD)vòng lặp phản hồi— feedback loopsmodule "sâu"— deep modulemodule "nông"— shallow modulegiao diện— interfaceviệc triển khai— implementationgánh nặng nhận thức— cognitive load
Nội dung chi tiết
Chào mọi người, hội nghị của chúng ta diễn ra tốt đẹp chứ? Mọi người cảm thấy thế nào về hội nghị cho đến bây giờ? Tốt, tuyệt vời. Tôi có một thông điệp dành cho các bạn, một thông điệp an ủi những người tin rằng bộ kỹ năng của họ không còn giá trị trong kỷ nguyên mới này. Thông điệp đó là: Tôi tin rằng các nguyên tắc cơ bản về giá trị bản thân giờ đây quan trọng hơn bao giờ hết. Tôi là một giáo viên, và gần đây tôi đã giảng dạy một khóa học mang tên 'Claude Code cho các Kỹ sư Thực thụ,' một cái tên khá khiêu khích. Trong quá trình xây dựng khóa học này, tôi phải tạo ra một chương trình giảng dạy về AI coding, điều này khá khó khăn vì mọi thứ thay đổi liên tục, đúng không?
Phản bác phong trào "từ đặc tả đến mã nguồn"
Chúng ta đang có một mô hình hoàn toàn mới. Chắc chắn chúng ta cần loại bỏ tất cả các quy tắc cũ để đón nhận những điều mới mẻ. Và có một phong trào đã xuất hiện xung quanh ý tưởng này, đó là phong trào từ đặc tả đến mã nguồn (specs to code movement). Phong trào từ đặc tả đến mã nguồn cho rằng, bạn có thể viết một đặc tả về cách một ứng dụng nên hoạt động. Sau đó, bạn có thể dùng AI để biến nó thành mã nguồn. Nếu có vấn đề với ứng dụng, bạn quay lại đặc tả. Bạn có thể thực sự xem xét mã nguồn, chỉ cần thay đổi đặc tả, chạy lại trình biên dịch, và bạn sẽ có thêm mã nguồn. Giơ tay nếu bạn đã từng nghe về điều này. Giữ tay nếu bạn đã thử nó. Được rồi, tôi cũng đã thử rồi. Mọi người có thể bỏ tay xuống. Điều tôi nhận thấy là khi tôi chạy nó, tôi cố gắng không nhìn vào mã nguồn, nhưng cuối cùng tôi vẫn phải nhìn. Và tôi nhận ra rằng, đầu tiên tôi nhận được mã nguồn, sau đó khi tôi chạy nó, tôi lại nhận được mã nguồn tệ hơn. Tôi thử lại, cứ chạy trình biên dịch hết lần này đến lần khác, và cuối cùng chỉ nhận được rác. Giơ tay nếu điều đó đã từng xảy ra với bạn. Vâng, tôi không nghĩ cách này hiệu quả. Ý tưởng rằng chúng ta có thể bỏ qua mã nguồn và để mã nguồn tự quản lý chính nó chỉ là một kiểu coding dưới một cái tên khác. Và tôi không tin vào điều đó.
Mã nguồn chất lượng và Nguyên tắc Cơ bản Phần mềm
Tôi nghĩ, được rồi, làm thế nào để tôi sửa trình biên dịch? Làm thế nào để nó không tạo ra mã nguồn tồi hoặc mã nguồn tệ hơn mỗi lần? Và thế là tôi nghĩ, tôi cần giải thích cho Mô hình Ngôn ngữ Lớn (LLM) bằng tiếng Anh về một cơ sở mã tốt trông như thế nào. Hãy để tôi lôi ra một trong những cuốn sách yêu thích cũ của mình, đó là A Philosophy of Software Design của John Ousterhout. Hãy lên Amazon và mua nó. Ông ấy có một định nghĩa về mã nguồn tồi trông như thế nào. Ông gọi đó là mã phức tạp (complex code). Độ phức tạp (complexity) là bất cứ thứ gì liên quan đến cấu trúc của một hệ thống phần mềm mà làm cho hệ thống đó khó hiểu và khó thay đổi. Vì vậy, một cơ sở mã tồi là một cơ sở mã khó thay đổi. Nếu bạn không thể thay đổi một cơ sở mã mà không gây ra lỗi, thì đó là một cơ sở mã tồi. Các cơ sở mã tốt không dễ thay đổi. Vậy là tôi nghĩ, điều đó tốt. Hãy thử một cuốn sách khác. Hãy thử The Pragmatic Programmer. Lên Amazon và mua nó. Họ có một chương dành cho thứ gọi là entropy phần mềm (software entropy). Và đây chính xác là điều tôi đang thấy. Entropy là ý tưởng rằng mọi thứ có xu hướng tiến tới thảm họa, phân rã và sụp đổ. Và đây cũng chính xác là cách hầu hết các hệ thống phần mềm hoạt động: mỗi khi bạn thực hiện một thay đổi đối với một cơ sở mã, nếu bạn chỉ nghĩ về thay đổi đó mà không nghĩ về thiết kế của toàn bộ hệ thống, cơ sở mã của bạn sẽ ngày càng tệ hơn. Và đó là điều tôi đang chứng kiến. Mọi thứ trong ý tưởng từ đặc tả đến mã nguồn, rằng bạn chỉ cần chạy lại trình biên dịch hết lần này đến lần khác, đã tạo ra mã nguồn tệ hơn. Bây giờ, có một ý tưởng thúc đẩy phong trào từ đặc tả đến mã nguồn, đó là mã nguồn thì rẻ. Giơ tay nếu bạn đã từng nghe cụm từ đó, rằng mã nguồn thì rẻ. Chà, tôi không nghĩ điều này đúng. Tôi nghĩ mã nguồn không rẻ. Trên thực tế, mã nguồn tệ là thứ đắt đỏ nhất từ trước đến nay. Bởi vì nếu bạn có một cơ sở mã khó thay đổi, bạn sẽ không thể tận dụng tất cả những lợi ích mà AI có thể mang lại, bởi vì AI thực sự hoạt động rất tốt trong một cơ sở mã tốt. Và điều này có nghĩa là các cơ sở mã tốt quan trọng hơn bao giờ hết, tức là các nguyên tắc cơ bản về phần mềm quan trọng hơn bao giờ hết. Đó là luận điểm của bài nói chuyện này. Vậy thì, hãy cùng đi vào các vấn đề thực tế.
Các Chế độ Lỗi Thường gặp khi làm việc với AI
Tôi muốn nói về các chế độ lỗi khác nhau mà bạn có thể đã trải nghiệm hoặc chưa từng gặp phải khi làm việc với AI, và cách bạn có thể tránh chúng chỉ bằng cách quay lại các cuốn sách cũ và xem xét các thực hành phần mềm truyền thống. Được thôi.
Chế độ Lỗi 1: AI Không Thấu Hiểu Ý định
Vậy chế độ lỗi đầu tiên là AI không làm những gì tôi muốn. Tôi nghĩ mình có một ý tưởng hay trong đầu và AI lại làm một điều hoàn toàn khác. Hoặc nó tạo ra những đặc tả mà tôi không muốn. Giơ tay nếu bạn đã gặp chế độ lỗi này. Tuyệt. Được rồi. Trong The Pragmatic Programmer, họ nói rằng không ai biết chính xác họ muốn gì. Giữa bạn và AI có một rào cản giao tiếp, đúng không? Và khi bạn nói chuyện với AI, đó giống như việc AI đang thu thập yêu cầu. Về cơ bản, nó đang tìm hiểu từ bạn những gì bạn cần. Và tôi nhận ra rằng có một cuốn sách khác, The Design of Design. Cuốn sách này nói về ý tưởng gọi là khái niệm thiết kế (design concept) rằng khi có nhiều hơn một người cùng thiết kế một thứ gì đó, bạn sẽ có một ý tưởng lơ lửng giữa các bạn, một ý tưởng phù du về thứ bạn đang xây dựng. Và thứ bạn đang xây dựng hay ý tưởng về nó được gọi là khái niệm thiết kế. Nó không phải là một tài sản. Nó không phải là thứ bạn có thể đặt vào một tệp Markdown. Nó là một lý thuyết vô hình về những gì bạn đang xây dựng. Và tôi nghĩ, được rồi, đó là điều đang xảy ra. Tôi và AI không chia sẻ một khái niệm thiết kế chung.
Giải pháp: Kỹ năng "Grill Me" để có Sự Thấu Hiểu Chung
Vì vậy, tôi đã tạo ra một kỹ năng. Kỹ năng này rất, rất đơn giản. Nó được gọi là Grill Me. Và nó trông như thế này: "Phỏng vấn tôi không ngừng về mọi khía cạnh của kế hoạch này cho đến khi chúng ta đạt được một sự hiểu biết chung. Đi sâu vào từng nhánh của cây thiết kế (design tree) – một khái niệm khác của Frederick P. Brooks – giải quyết các phụ thuộc giữa các quyết định từng cái một." Kỹ năng này, repository chứa nó có khoảng 13.000 sao hoặc gì đó, nó thực sự bùng nổ, trở nên viral, mọi người rất yêu thích nó. Vài dòng này có nghĩa là AI sẽ hỏi bạn khoảng 40, 60 câu hỏi. Tôi đã từng thấy có người bị hỏi tới hàng trăm câu trước khi AI cảm thấy hài lòng rằng đã đạt được sự hiểu biết chung. Điều đó có nghĩa là nó biến AI thành một dạng đối thủ nơi nó liên tục trao đổi ý tưởng với bạn và cố gắng đạt được sự hiểu biết chung. Và điều đó có nghĩa là cuộc trò chuyện mà bạn tạo ra sau đó, bạn có thể lấy nó và biến nó thành tài liệu yêu cầu sản phẩm (product requirements documents) hoặc một thứ gì đó tương tự. Hoặc nếu đó là một thay đổi nhỏ, bạn có thể biến nó trực tiếp thành vấn đề (issues). Và sau đó, tác nhân AFK của bạn sẽ tiếp nhận nó. Đừng công kích tôi về điều này, nhưng cá nhân tôi tin rằng cách này tốt hơn chế độ lập kế hoạch (plan mode) mặc định trong công cụ mà tôi sử dụng, được gọi là Code. Chế độ lập kế hoạch rất ham muốn tạo ra một tài sản. Nó thực sự chỉ muốn tạo ra một kế hoạch và bắt đầu làm việc. Trong khi đó, tôi nghĩ sẽ tốt hơn nhiều nếu chúng ta đạt được một khái niệm thiết kế chung trước. Đó là mẹo số một.
Chế độ Lỗi 2: AI Quá Dài dòng
Bây giờ, chế độ lỗi thứ hai là AI quá dài dòng. Giống như bạn và AI đang nói chuyện không cùng mục đích vậy. Giơ tay nếu bạn cảm thấy điều này. Nếu bạn đã trải nghiệm chế độ lỗi đó. Phải. Giống như AI chỉ đang dùng quá nhiều từ để cố gắng truyền đạt những gì nó đang làm. Nó không giống như bạn đang nói cùng một ngôn ngữ. Và điều này đối với tôi cảm thấy rất, rất quen thuộc. Đúng vậy. Nếu bạn đã là một nhà phát triển trong một thời gian dài và bạn đã làm việc với, ví dụ, chuyên gia nghiệp vụ (domain experts), người đang xây dựng một ứng dụng. Giả sử chuyên gia nghiệp vụ muốn bạn xây dựng một thứ gì đó về vi mạch. Bạn không hề biết vi mạch là gì. Bạn cần thiết lập một loại ngôn ngữ chung. Đúng không? Bởi vì nếu không, họ sẽ sử dụng các thuật ngữ mà bạn không hiểu. Bạn sẽ dịch điều đó thành mã nguồn mà có thể bạn cũng không hiểu. Và chắc chắn chuyên gia nghiệp vụ cũng sẽ không hiểu. Vì vậy, có một loại khoảng cách ngôn ngữ giữa bạn và chuyên gia nghiệp vụ. Và thế là tôi quay lại thiết kế hướng nghiệp vụ (DDD) (domain-driven design). Đây là thứ mà tôi vẫn đang trong quá trình khám phá. Nhưng mọi thứ tôi đọc về DDD đều như âm nhạc đối với tôi. Tôi thực sự yêu thích nó.
Giải pháp: Xây dựng Ngôn ngữ Phổ quát với AI
Và DDD có một khái niệm về ngôn ngữ phổ quát (ubiquitous language). Nơi mà ngôn ngữ phổ quát, các cuộc trò chuyện giữa các nhà phát triển và các biểu thức trong mã nguồn cũng như các cuộc trò chuyện với chuyên gia nghiệp vụ đều xuất phát từ cùng một mô hình nghiệp vụ (domain model). Về cơ bản, đó là một tệp Markdown chứa danh sách các thuật ngữ mà bạn và AI có điểm chung. Và bạn thực sự tập trung vào những thuật ngữ đó và đảm bảo rằng chúng phù hợp với ý nghĩa thực sự của chúng. Và bạn sử dụng chúng mọi lúc trong mã nguồn khi nói về mã nguồn, khi nói chuyện với chuyên gia nghiệp vụ. Hoặc trong trường hợp của chúng ta, khi nói chuyện với AI. Vì vậy, tôi đã tạo ra một kỹ năng. Kỹ năng này là kỹ năng ngôn ngữ phổ quát về cơ bản chỉ quét cơ sở mã của bạn, tìm kiếm thuật ngữ, và sau đó tạo một tệp Markdown, tạo tệp Markdown ngôn ngữ phổ quát, một loạt các bảng Markdown với tất cả các thuật ngữ. Và sau đó tôi chuyển nó cho AI và tôi cũng có thể đọc nó. Và tôi thực sự luôn mở nó khi tôi trao đổi với AI và lập kế hoạch, v.v. Điều tôi nhận thấy khi đọc dấu vết suy nghĩ của AI không chỉ cải thiện việc lập kế hoạch mà còn cho phép AI suy nghĩ một cách ít dài dòng hơn và thực sự có nghĩa là việc triển khai sẽ phù hợp hơn với những gì bạn thực sự lập kế hoạch. Vì vậy, điều này thực sự là một công cụ mạnh mẽ. Nó tốt một cách đáng kinh ngạc. Vậy đó là mẹo số hai: tạo một ngôn ngữ chung với AI.
Chế độ Lỗi 3: AI Tạo ra Mã không Hoạt động
Vậy được rồi, hãy tưởng tượng bạn đã đồng nhất với AI. Bạn biết mình phải xây dựng cái gì. AI đã xây dựng đúng thứ nhưng nó không hoạt động. Giơ tay nếu điều đó đã xảy ra với bạn. Đúng vậy, nó chỉ đơn giản là không hoạt động. Chà, có một điều hiển nhiên chúng ta có thể làm để cải thiện điều đó, đó là sử dụng vòng lặp phản hồi (feedback loops). Chúng ta có thể sử dụng kiểu tĩnh (static types), bạn biết đấy, nếu bạn không dùng TypeScript thì thật điên rồ. Nếu bạn không dùng, nếu bạn đang xây dựng một ứng dụng giao diện người dùng (front-end app) và bạn không cấp cho LLM quyền truy cập vào trình duyệt để nó có thể kiểm tra xung quanh, thì đó là điều thực sự cần thiết. Và rõ ràng bạn cũng cần kiểm thử tự động (automated tests). Và một điều tôi nhận thấy ở đây là ngay cả với những vòng lặp phản hồi này, LLM không sử dụng chúng hiệu quả lắm. Nó không tận dụng tối đa vòng lặp phản hồi của mình theo cách mà một nhà phát triển kỳ cựu sẽ làm. Và vì vậy, điều nó có xu hướng làm là thực hiện quá nhiều thứ cùng một lúc, nó sẽ tạo ra một lượng lớn mã nguồn và sau đó mới nghĩ, "Ồ, có lẽ tôi nên kiểm tra kiểu (type check) đó," hoặc "Có lẽ tôi nên kiểm tra một bài kiểm thử (test) về điều đó." Và điều này trong The Pragmatic Programmer được mô tả là vượt quá tầm đèn pha (outrunning your headlights), về cơ bản là lái xe quá nhanh vì tốc độ phản hồi là giới hạn tốc độ của bạn. Điều đó có nghĩa là bạn nên kiểm thử trong quá trình thực hiện, thực hiện các bước nhỏ, có chủ đích, và AI theo mặc định thực sự không giỏi điều đó.
Giải pháp: TDD và Thách thức trong Kiểm thử
Vì vậy, kỹ năng số ba là TDD. Bạn nên sử dụng Phát triển hướng kiểm thử (Test Driven Development) vì TDD buộc Mô hình Ngôn ngữ Lớn (LLM) phải thực hiện các bước nhỏ. Bạn tạo một bài kiểm thử trước, làm cho bài kiểm thử đó đạt, và sau đó bạn tái cấu trúc (refactor) mã nguồn để làm cho nó tốt hơn và xem xét thiết kế. Vấn đề ở đây là kiểm thử thực sự khó. Kiểm thử luôn khó, và lý do là có rất nhiều quyết định khác nhau mà bạn cần đưa ra khi viết một bài kiểm thử. Bạn cần xác định đơn vị (unit) mà bạn muốn kiểm thử lớn đến mức nào. Bạn cần xác định những gì cần giả lập (mock). Bạn cần xác định những hành vi (behaviors) nào bạn thậm chí muốn kiểm thử ngay từ đầu. Và tất cả những quyết định này đều phụ thuộc lẫn nhau, vì vậy nếu bạn đang kiểm thử một đơn vị thực sự lớn như toàn bộ một ứng dụng khổng lồ, thì nó có thể khá không ổn định (flaky), bạn có thể không muốn kiểm thử quá nhiều hành vi. Nếu bạn chỉ kiểm thử đơn vị này, bạn cần giả lập đơn vị này, bạn biết đấy, tất cả đều liên kết với nhau. Và tôi đã suy nghĩ về điều này trong nhiều năm, suốt sự nghiệp phát triển của mình. Và điều chúng ta nhận thấy là các cơ sở mã tốt là các cơ sở mã dễ kiểm thử, đúng không?
Cải thiện Codebase và Khái niệm Deep Module
Vì vậy, ở đây chúng ta bắt đầu quay trở lại ý tưởng về tầm quan trọng của mã nguồn: cơ sở mã (codebase) của bạn càng tốt thì các vòng phản hồi (feedback loops) của bạn càng hiệu quả, bởi vì bạn có thể cung cấp phản hồi tốt hơn cho Mô hình Ngôn ngữ Lớn (LLM), giúp nó tạo ra mã tốt hơn. Do đó, tôi đã nghĩ: một cơ sở mã tốt, một cơ sở mã có thể kiểm thử (testable codebase) sẽ trông như thế nào? Chúng ta quay lại với suy nghĩ của Joe Out to, về việc có các module "sâu" (deep modules) trong cơ sở mã của bạn, chứ không phải các module "nông" (shallow modules), không phải nhiều module phơi bày quá nhiều hàm. Chúng nên là tương đối ít, các module lớn, sâu với các giao diện (interface) đơn giản.
Hãy so sánh chúng một cách nhanh chóng:
Deep modules: nhiều chức năng được ẩn đằng sau một giao diện đơn giản, che giấu sự phức tạp. Bạn có thể xem bên trongdeep modulenếu muốn, nhưng bạn không cần thiết, bạn chỉ cần sử dụng giao diện.Shallow modules: không nhiều chức năng, giao diện phức tạp.
Các shallow modules trong một cơ sở mã trông giống như thế này: chúng ta có vô số khối nhỏ khác nhau mà Trí tuệ nhân tạo (AI) phải "đi qua" và điều hướng. Điều này thực sự khó khăn để AI khám phá. Do đó, thường thì bạn sẽ thấy rằng nếu bạn có một cơ sở mã như thế này (mà AI lại rất giỏi trong việc tạo ra những cơ sở mã như vậy), bạn sẽ rơi vào tình huống mà AI không hiểu mã của bạn đang làm gì. Nó sẽ cố gắng khám phá mã, nhưng vì nó được sắp xếp kém, đầy các shallow modules, có thể nó không đến được đúng module kịp thời hoặc không hiểu tất cả các phần phụ thuộc, tất cả những thứ đó; nó không hiểu mã của bạn.
Vậy, một cơ sở mã đầy các deep modules trông như thế nào? Chà, nó trông như thế này: đó là cùng một mã, nhưng nó được cấu trúc bên trong các ranh giới nơi bạn có các giao diện này ở phía trên. Và bạn có thể sẽ phải kiểm soát rất nhiều và thiết kế rất tốt các giao diện này, nếu không AI có thể làm hỏng thiết kế. Nhưng việc triển khai (implementation) thì bạn có thể giao cho AI một chút.
Chuyển đổi kiến trúc Codebase
Vậy làm thế nào để bạn biến một cơ sở mã trông như thế này thành một cơ sở mã trông như thế kia? Chà, tôi có một kỹ năng (skill) cho việc đó: cải thiện kiến trúc cơ sở mã. Hóa ra điều này khá phức tạp, nhưng nó chỉ là một tập hợp các bước. Giống như một tập hợp các bước mà bạn có thể thực hiện lại nhiều lần: bạn chỉ cần khám phá cơ sở mã, tìm kiếm các cơ hội mà có những đoạn mã có liên quan và gói tất cả chúng vào một deep module. Và đây là một cơ sở mã có thể kiểm thử (testable codebase) bởi vì các ranh giới xung quanh mã này rất, rất đơn giản: bạn kiểm thử ở giao diện, bạn xác minh bằng cách sử dụng giao diện đó và bạn đã sẵn sàng. Vì vậy, đây là một cơ sở mã mang lại lợi ích cho bạn.
Giảm gánh nặng nhận thức với Deep Module
Còn về chế độ lỗi (failure mode) số sáu thì sao? Giả sử các vòng phản hồi (feedback loops) của bạn đang hoạt động, mọi thứ đang tiến triển, bạn có thể tạo ra nhiều mã hơn bao giờ hết, nhưng bộ não của bạn không thể theo kịp. Đúng không? Hãy giơ tay nếu bạn cảm thấy mệt mỏi hơn bao giờ hết trong sự nghiệp phát triển của mình. Vâng, tôi cũng vậy, điều đó không ổn. Và tôi nghĩ đây là một cơ sở mã thực sự làm cho bộ não của bạn khó khăn hơn, bởi vì bạn, cũng như AI, cần phải giữ tất cả thông tin đó trong đầu.
Trong khi đó, việc này không chỉ đơn giản hơn để bạn đọc và hiểu, nó còn có nghĩa là bạn có thể coi các module này, hoặc các deep modules này, như những hộp xám (gray boxes). Bạn có thể nói: "Được rồi, tôi sẽ chỉ thiết kế giao diện nhưng tôi sẽ không lo lắng quá nhiều hoặc không xem xét quá kỹ việc triển khai." Bạn có thể làm điều này rõ ràng với những thứ ít quan trọng hơn trong ứng dụng của mình. Áp dụng điều này với nhiều thứ như tài chính hoặc bất cứ thứ gì, nhưng trong rất nhiều module trong ứng dụng của bạn, bạn không cần phải suy nghĩ quá nhiều về việc triển khai miễn là bạn có một ranh giới có thể kiểm thử (testable boundary) bên ngoài module và miễn là bạn hiểu mục đích của nó và có thể thiết kế nó từ bên ngoài.
Tôi đã thấy điều này thực sự giúp bảo vệ bộ não của mình vì tôi có thể nói: "Được rồi, AI, tôi sẽ để bạn xử lý những gì bên trong khối lớn đó. Tôi sẽ chỉ kiểm thử từ bên ngoài và xác minh nó."
Thiết kế Giao diện và Tư duy Chiến lược
Vậy đó là mẹo số năm: Thiết kế giao diện, ủy quyền việc triển khai. Nhưng điều này có nghĩa là bất cứ khi nào chúng ta chạm vào mã nguồn, bất cứ khi nào chúng ta lập kế hoạch mọi thứ, chúng ta cần phải suy nghĩ và nhận thức được các module trong ứng dụng của chúng ta. Chúng ta cần hiểu rõ bản đồ đó. Nó cần phải là một phần của ngôn ngữ chung (ubiquitous language) của chúng ta. Chúng ta cần xây dựng nó vào các kỹ năng lập kế hoạch (planning skills) của mình. Vì vậy, trong Tài liệu Yêu cầu Sản phẩm (PRD) của tôi, tôi cụ thể về những thay đổi module và các giao diện bên trong các module đó, cách chúng đang được sửa đổi. Tôi luôn nghĩ về chúng, và điều này đến từ Kent Beck: "Đầu tư vào thiết kế của hệ thống mỗi ngày."
Và đây là cốt lõi của vấn đề, phải không? Bởi vì nếu là mã nguồn, chúng ta không đầu tư vào thiết kế của hệ thống. Chúng ta đang rút lui khỏi nó. Chúng ta đang loại bỏ điều đó. Trong khi điều này, tôi nghĩ, là hoàn toàn then chốt.
Kết luận và Tài nguyên
Vì vậy, mã nguồn không hề rẻ. Đó là thông điệp tôi muốn bạn ghi nhớ. Mã nguồn rất quan trọng. Và nếu chúng ta coi AI là một lập trình viên (programmer) giỏi thực địa, một lập trình viên mang tính chiến thuật, một hạ sĩ quan thực địa thực hiện các thay đổi mã, thì bạn cần một người ở cấp cao hơn. Bạn cần một người suy nghĩ ở cấp độ chiến lược. Và đó là bạn. Điều đó đòi hỏi những kỹ năng nền tảng phần mềm mà chúng ta đã sử dụng trong 20 năm, hoặc lâu hơn.
Bây giờ, nếu bạn quan tâm đến bất kỳ kỹ năng nào tôi đã trình bày ở đây, chúng có trong kho lưu trữ GitHub (GitHub repo) - MacPokec skills. Và nếu bạn quan tâm đến khóa đào tạo mà tôi thực hiện hoặc bất kỳ tài liệu miễn phí nào, tôi có mặt trên YouTube, Twitter, và cả trên AIhero.dev nơi tôi có một bản tin (newsletter) mà bạn có thể tìm hiểu. Cảm ơn rất nhiều. Tôi hy vọng điều này mang lại cho bạn sự tự tin trong kỷ nguyên AI mới này. Bạn thực sự có thể tạo ra tác động tốt. Cảm ơn.