1 điểm bởi GN⁺ 4 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • k10s là một TUI Kubernetes có nhận biết GPU, được tạo rất nhanh bằng vibe-coding với Claude, nhưng sau khi thêm fleet view thì trạng thái của nhiều màn hình bị hỏng
  • model.go phình ra thành một Model đơn lẻ dài 1690 dòng cùng Update() dài 500 dòng, phải gánh toàn bộ trạng thái UI, client, cache, navigation và view
  • AI thêm tính năng rất nhanh nhưng cũng làm god object và global key handler ngày càng lớn, khiến mỗi view mới lại phải thêm branch vào handler sẵn có
  • Dữ liệu []string dựa trên vị trí và việc mutation trực tiếp trong background tea.Cmd có thể gây ra lỗi cột và data race rõ ràng
  • Phiên bản k10s mới sẽ được viết lại bằng Rust, và trước prompt đầu tiên sẽ cố định interface, message type, ownership rule và scope trong CLAUDE.md

Bối cảnh phải viết lại k10s

  • k10s khởi đầu là một dashboard Kubernetes có nhận biết GPU, một công cụ TUI được tạo ra để operator của các cụm NVIDIA có thể xem ngay mức sử dụng GPU, metric DCGM, node nhàn rỗi và chi phí $32/hr
  • Dự án được viết bằng Go và Bubble Tea, được tạo ra trong khoảng 7 tháng, 234 commit và khoảng 30 cuối tuần thông qua các phiên vibe-coding với Claude
  • Giai đoạn đầu, các chức năng nền tảng kiểu bản sao k9s như pods, nodes, deployments, services, command palette, live updates dựa trên watch và Vim keybindings đã chạy được chỉ sau khoảng 3 cuối tuần
  • Tính năng cốt lõi là GPU fleet view, màn hình hiển thị GPU allocation, mức sử dụng, chỉ số dựa trên DCGM, nhiệt độ, điện năng, bộ nhớ và trạng thái theo màu của từng node; Claude đã tạo một lần cả struct FleetView, bộ lọc tab GPU/CPU/All và phần render allocation bars
  • Sau khi thêm fleet view, khi quay lại pods view bằng :rs pods, bảng bị trống, live updates dừng lại, nodes view hiển thị stale data từ bộ lọc fleet view và cả số đếm fleet tab cũng sai
  • Trong lúc lần theo vấn đề, tác giả lần đầu tiên đọc toàn bộ model.go dài 1690 dòng do Claude tạo ra và thấy một struct Model duy nhất đang ôm hết UI widget, Kubernetes client, trạng thái logs/describe/fleet, navigation history, cache và xử lý chuột
  • Phương thức Update() là một hàm dispatch msg.(type) dài 500 dòng với 110 nhánh switch/case
  • AI có thể tạo tính năng rất nhanh, nhưng nếu cứ giao tiếp tục mà không có ràng buộc, kiến trúc sẽ sụp đổ; cảm giác tốc độ sẽ trông như thành công cho đến lúc toàn bộ hệ thống vỡ cùng lúc

Năm nguyên tắc rút ra từ đống đổ nát

  • Nguyên tắc 1: AI tạo tính năng nhưng không tạo kiến trúc

    • Claude làm tốt các tính năng riêng lẻ như fleet view, log streaming, mouse support, nhưng mỗi tính năng đều được triển khai trong ngữ cảnh “làm cho nó chạy ngay bây giờ” và không tính đến quan hệ với các tính năng khác cùng chia sẻ trạng thái
    • Trong handler resourcesLoadedMsg xuất hiện điều kiện như msg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nil, khiến logic riêng cho fleet view bị trộn vào generic resource loading path
    • Mỗi khi view mới cần custom behavior, cùng một handler lại có thêm branch, và nhiều field phải được xóa thủ công để dữ liệu từ view trước không rò sang view mới
    • Trong model.go, các đoạn cleanup thủ công như m.logLines = nil, m.allResources = nil, m.resources = nil xuất hiện rải rác 9 lần; chỉ cần sót một chỗ là ghost data từ view cũ sẽ còn lại
    • Cách thay thế là tự viết trước các interface cụ thể, message type và ownership rule trước khi viết code, rồi đưa chúng vào CLAUDE.md như các architecture invariant
    • Ví dụ quy tắc có thể là mỗi view phải triển khai trait/interface View, view không được truy cập state của view khác, dữ liệu async chỉ đi vào qua các biến thể AppMsg, còn struct App chỉ phụ trách navigation và message dispatch
  • Nguyên tắc 2: god object là sản phẩm mặc định AI rất dễ tạo ra

    • AI có xu hướng chọn cấu trúc một struct ôm tất cả để đáp ứng prompt trước mắt với ít ceremony nhất
    • Ngay cả key handling cũng không được tách theo từng view; một phím s trong logs view là autoscroll, trong pods view là shell, còn trong containers view là container shell
    • Yêu cầu “thêm shell support cho pods” được triển khai bằng cách chèn thêm một branch cạnh global key handler cũ
    • Phím Enter cũng phân nhánh giữa contexts view, namespaces view, logs view và logic generic drill-down trong cùng một flat dispatch bằng cách so sánh chuỗi m.currentGVR.Resource
    • Trong riêng file model.go, biểu thức m.currentGVR.Resource == được dùng hơn 20 lần như một type discriminator, khiến mỗi lần thêm view mới lại phải chạm vào nhiều handler
    • Cách thay thế là không thêm field state riêng theo view vào App/Model, tạo mỗi view thành một struct riêng và để key binding trong keymap của view đang active; các quy tắc này nên được đưa vào CLAUDE.md
    • Cần có guardrail như “thêm view phải là thêm file; nếu phải sửa view hiện có thì dừng lại và hỏi” để AI không chọn con đường ngắn nhất là chèn thêm branch
  • Nguyên tắc 3: Ảo giác tốc độ làm scope phình ra

    • Ban đầu k10s là công cụ cho một nhóm người dùng hẹp, những người vận hành GPU training cluster, nhưng vibe-coding khiến các tính năng như pods, deployments, services, command palette, mouse support, contexts, namespaces trông như “miễn phí”
    • Kết quả là sản phẩm mở rộng khỏi một công cụ tập trung vào GPU để thành TUI general-purpose cho mọi người dùng Kubernetes, thực chất là đang làm lại k9s
    • keyMap phẳng chứa lẫn lộn nhiều binding dành riêng cho các view khác nhau như Fullscreen, Autoscroll, ToggleTime, WrapText, CopyLogs, ToggleLineNums, Describe, YamlView, Edit, Shell, FilterLogs, FleetTabNext, FleetTabPrev
    • AutoscrollShell đều là s; do dispatch kiểm tra resource hiện tại nên nó vẫn “chạy”, nhưng keybinding không còn thể được hiểu trong ngữ cảnh cục bộ
    • Tốc độ viết code trông giống như đang “shipping”, nhưng mỗi feature thực ra đang cộng thêm một branch vào god object
    • Cách thay thế là ghi rõ scope boundary trong CLAUDE.md: k10s là công cụ cho GPU cluster operator, các view được hỗ trợ chỉ gồm fleet, node-detail, gpu-detail, workload, và không thêm generic resource views hay các tính năng trùng với k9s
    • AI có thể cho ngân sách dòng code gần như vô hạn, nhưng ngân sách độ phức tạp vẫn hữu hạn, nên cần từ chối scope ngay từ đầu
  • Nguyên tắc 4: Dữ liệu dựa trên vị trí là bom nổ chậm

    • k10s làm phẳng resource lấy từ Kubernetes API ngay thành type OrderedResourceFields []string
    • Hàm sort của fleet view xử lý ra[3] như Alloc, ra[2] như Compute, ra[0] như Name; danh tính cột chỉ dựa vào comment và thứ tự cột trong resource.views.json
    • Nếu thêm một cột vào giữa Instance và Compute trong resource.views.json, thì các đoạn sort, conditional render và drill target tham chiếu ra[2], ra[3] có thể âm thầm sai đi
    • Compiler không thể biết ý nghĩa của []string, và JSON config cũng không diễn đạt được sort behavior, conditional rendering hay custom drill target, nên Go code phải hardcode các giả định theo vị trí
    • AI rất dễ chọn []string hoặc Vec<String> vì chúng thuận tiện để đổ thẳng vào table widget, còn typed struct đòi hỏi ceremony ban đầu nhiều hơn nên hay bị bỏ qua trong con đường nhanh
    • Cách thay thế là giữ dữ liệu có cấu trúc dưới dạng typed struct như FleetNode, PodInfo cho đến sát lúc render, và sort trên named field thay vì positional access như row[3]
    • Ví dụ struct như FleetNode { name, instance_type, compute_class, alloc } biểu diễn danh tính cột bằng type, từ đó ngăn không cho xuất hiện các trạng thái bất khả như sort nhầm cột
    • “Making impossible states impossible” là cách nói phổ biến trong cộng đồng Elm/Rust, nghĩa là thiết kế type để invalid state không thể được tạo ra thay vì chờ runtime check
  • Nguyên tắc 5: AI không nên sở hữu state transition

    • Cốt lõi trong mô hình của Bubble Tea là state chỉ thay đổi trong Update() chạy theo message, nhưng k10s đã phá vỡ nguyên tắc này
    • Handler updateTableMsg trả về một closure tea.Cmd, và bên trong closure đó có các lệnh như m.updateColumns(m.viewWidth), m.updateTableData(), m.table.SetCursor(savedCursor) làm thay đổi field của Model
    • Bubble Tea chạy tea.Cmd trong goroutine riêng, nên trong lúc closure đọc/ghi m.resources, m.table, m.viewWidth, thì View() trên goroutine chính vẫn có thể đọc cùng các field đó
    • Không có lock hay mutex, và <-m.updateTableChan chỉ chờ tín hiệu update chứ không ngăn View() đọc phải trạng thái đang được ghi dở
    • Đây là một data race rõ ràng; phần lớn thời gian vẫn chạy được nhưng thỉnh thoảng màn hình sẽ hỏng
    • Cách thay thế là background worker không được mutate UI state trực tiếp, mà gửi typed message qua channel, để main event loop nhận message rồi mới áp dụng mutation state
    • Quy tắc concurrency là background task không được đổi UI state trực tiếp, phải gửi kết quả bằng typed message, và render()/view() phải là pure function không có side effect, I/O hay channel operation

Các quy tắc bảo vệ nên đưa vào CLAUDE.mdagents.md

  • Các điều kiện bất biến của kiến trúc

    • Mỗi view phải triển khai trait/interface View và không được truy cập state của view khác
    • Mọi dữ liệu async phải đi vào qua các biến thể AppMsg, background task không được mutate field trực tiếp
    • Việc thêm view mới không được yêu cầu sửa các view hiện có
    • Struct App phải là một router mỏng chỉ phụ trách navigation và message dispatch
  • Quy tắc ownership của state

    • Không được thêm field state riêng cho từng view vào struct App/Model
    • Mỗi view phải tồn tại dưới dạng struct riêng và tự khai báo key binding của mình
    • Ứng dụng phải dispatch phím cho active view, và keybinding mới phải được thêm vào keymap của view đó thay vì global handler
    • Nếu việc thêm view đòi hỏi sửa các view cũ thì phải dừng lại và xác nhận
  • Phạm vi

    • k10s phải là công cụ cho GPU cluster operator chứ không phải cho mọi người dùng Kubernetes
    • Các view được hỗ trợ phải giới hạn ở fleet, node-detail, gpu-detail, workload
    • Không được thêm generic resource view như pods, deployments, services
    • Không được thêm các tính năng sao chép chức năng của k9s
    • Những feature request không giúp ích cho operator vận hành GPU training jobs phải bị từ chối
  • Biểu diễn dữ liệu

    • Không được làm phẳng structured data thành []string, Vec<String> hay positional array
    • Dữ liệu phải được giữ dưới dạng typed struct cho đến ngay trước lệnh render
    • Danh tính cột phải đến từ tên field của struct thay vì array index
    • Hàm sort phải chạy trên typed field thay vì positional access như row[3]
    • Việc tạo string chỉ để hiển thị chỉ được diễn ra bên trong hàm render()/view()
  • Quy tắc đồng thời

    • Background task như watcher, scraper, API call không được mutate UI state trực tiếp
    • Background task phải gửi kết quả qua channel dưới dạng typed message
    • Chỉ main event loop mới được áp dụng state mutation từ message đã nhận
    • render()/view() phải là pure function không có side effect, I/O hay channel operation
    • Nếu cần đổi state từ kết quả của async work, phải định nghĩa một biến thể AppMsg mới

Cách làm lại

  • k10s sẽ được viết lại bằng Rust, không phải vì Rust tốt hơn, mà vì đây là ngôn ngữ mà tác giả cảm thấy mình có thể trực tiếp lèo lái
  • Với một ngôn ngữ đã dùng đủ lâu, có thể cảm nhận điều gì đó sai trước cả khi diễn đạt được nó thành lời, và cảm giác này không thể bị thay thế bởi vibe-coding
  • Khi AI tạo ra đoạn code trông có vẻ hợp lý, cần có khả năng nhận ra liệu đó có phải rác hay không
  • Ở phiên bản mới, con người sẽ tự làm phần design work như concrete interface, message type và ownership rule trước khi viết code
  • Thay vì để AI tự đưa ra các architecture decision sai như trước, lần này các quyết định đó sẽ được chốt thành tài liệu ngay trước prompt đầu tiên
  • Liên kết tới TUI hiện tại và dự án có tại k10s GithubK10S.DEV

Phụ lục

  • Bubble Tea là framework Go TUI dựa trên The Elm Architecture, và các vấn đề kiến trúc của k10s không đến từ Bubble Tea mà từ cách k10s được triển khai
  • “Making impossible states impossible” là cách nói trong cộng đồng Elm/Rust về việc dùng thiết kế type để ngăn invalid state được tạo ra, thay vì kiểm tra ở runtime
  • Giống như “em-dash” trong văn viết AI, coding bằng AI cũng có thể để lại mùi “god-object”; vibe-coding khiến chi phí triển khai có vẻ rẻ, từ đó dẫn đến mất tập trung và bloat

1 bình luận

 
Ý kiến Hacker News
  • Những người nói rằng mã được sinh ra là ổn nhìn chung chỉ là những người không đọc đoạn mã đó
    Các biện pháp giảm nhẹ được đề xuất trong bài cũng khó mà trụ lâu. Khi thiết kế hệ thống hay component, sẽ xuất hiện các bất biến như “view không truy cập trạng thái của view khác”, và đến lúc nào đó bạn sẽ phải thêm một tính năng xung đột với bất biến đó
    Khi ấy thường chỉ còn chọn bỏ tính năng, chồng nó lên bất biến một cách gượng ép và kém hiệu quả, hoặc thay đổi chính bất biến đó. Đây không đơn thuần là vấn đề ngữ cảnh mà là vấn đề phán đoán, và các mô hình hiện tại sai quá thường xuyên ở chỗ này
    Nếu ghi rõ các ràng buộc kiến trúc, agent sẽ cố uốn mình theo các ràng buộc đó ngay cả khi lẽ ra cần thay đổi, tạo ra mã phức tạp và không thể bảo trì. Nếu bạn không đọc kỹ hơn cả khi đọc mã do người viết, cuối cùng sẽ sinh ra thứ “mã tự ăn thịt chính nó”, và đến khi nhận ra thì đã quá muộn

    • Nếu bạn biết cách viết mã tốt, có nhiều kỹ thuật để khiến AI viết mã tốt và điều đó hoàn toàn khả thi
      Cốt lõi là xác định những điểm AI gặp khó rồi làm chúng trở nên dễ hơn. Ví dụ cần ngữ cảnh cực nhỏ, mô-đun hóa với ranh giới rõ ràng, các mô-đun thuần tách khỏi input/output, ẩn sau interface, 100 bài test chạy trong dưới 1 giây, benchmark, v.v.
      AI hoạt động tốt khi có ranh giới và ngữ cảnh nhỏ. Nếu không cung cấp điều đó thì hiệu năng sẽ giảm, và trách nhiệm thuộc về người dùng công cụ
    • Tôi cho rằng việc “đến lúc nào đó sẽ phải thêm tính năng xung đột với bất biến” là vấn đề lớn của phát triển hướng đặc tả
      Không có đặc tả nào chịu nổi thực tế mãi mãi, và dù nghiên cứu thiết kế kỹ đến đâu thì một số bất biến trong đặc tả cuối cùng cũng sẽ lộ ra là sai
      Khi con người gặp tình huống này trong lúc phát triển, họ có thể lùi lại một bước để nghĩ lại xem bất biến có sai không, nếu thay đổi thì ảnh hưởng là gì. Trong khi đó AI thường cố nặn ra một giải pháp chắp vá dưới các giả định hay thiết kế sai, và thiếu sự sáng suốt để đánh giá lại toàn cục
      Có thể cải thiện bằng quy trình làm việc và xác minh tốt hơn, nhưng đây không phải lĩnh vực mà các công cụ như Claude Code xử lý tốt theo mặc định, và rõ ràng có giới hạn
    • Tôi đã trải qua chuyện tương tự ở công ty khi làm framework nội bộ mới và di chuyển các chỗ dùng framework cũ sang đó
      Ban đầu chúng tôi đặt ra các nguyên tắc mạnh mẽ và tự tay chuyển vài chỗ để lấy niềm tin. Toàn bộ đợt chuyển đổi lớn và tốn kém đến mức bị hoãn gần 10 năm, nên để giảm chi phí chúng tôi muốn tăng tốc bằng AI
      AI làm khá ổn với 80% trường hợp mang tính cơ học và đơn giản. 20% còn lại cần thay đổi framework, đa phần chỉ là thay đổi nhỏ như thêm field API, nhưng một hai trường hợp cần tái thiết kế khái niệm
      Backend của một hệ thống có thể tạo dữ liệu cụ thể trong 99% trường hợp, nhưng với vài trường hợp quan trọng thì về mặt logic không thể tạo được và phải nhận báo cáo từ bên ngoài. Thế mà một tối ưu hóa quan trọng lại được xây trên giả định rằng “điều đó là không thể”
      Công cụ AI không phát hiện được tình huống này và đã thêm logic chuyển đổi như thể nó sẽ hoạt động bình thường. Nhờ cách triển khai nên nó chưa thành bug production, nhưng trong lúc đặt đúng câu hỏi cho team đối tác, chúng tôi phát hiện nhu cầu tương tự còn xuất hiện ở nơi khác
      Cuối cùng không thành thảm họa chỉ vì có một người đào rất sâu vào vấn đề. Có lẽ trong tương lai các công cụ xác minh và mô hình thông minh hơn sẽ làm những đợt chuyển đổi như vậy dễ hơn, nhưng hiện tại mã sinh ra tuy có lúc đẹp mà vẫn dễ vỡ, nên phải liên tục theo sát
    • Không chỉ là đọc đầu ra mã, mà ít nhất theo kinh nghiệm của tôi, bạn còn phải tự tay viết mã
      Tôi có một pattern kiến trúc khá lạ đã dùng khoảng hai tháng, và mỗi lần dùng đều thấy hơi khó chịu, nhưng mãi tối qua mới nhận ra đó không phải một abstraction tốt và có cách chia tách tốt hơn
      Nếu để LLM sinh mã thì cảm giác khó chịu đó mờ đi rất nhiều, khiến tôi mất lâu hơn để nhận ra vấn đề và tìm giải pháp. Phần rìa có thể để sinh tự động, nhưng chức năng cốt lõi thì phần lớn vẫn phải tự viết
    • Các bất biến được ghi chép không chính thức rất khó chứng minh là đã bị phá vỡ hay chưa, kể cả khi có reviewer con người xen vào, và ngôn ngữ tự nhiên không đủ chính xác cho việc đó
      Ngay cả nếu biểu diễn bằng ngôn ngữ hình thức chính xác, LLM nằm dưới agent vẫn thiếu khả năng hiểu vì sao bất biến đó cần thiết, vì sao nó quan trọng. Có thể sẽ xuất hiện LLM đủ khả năng nối token với đặc tả hình thức và thậm chí viết cả chứng minh, nhưng mã kỳ quặc sinh ra từ phần phi hình thức của prompt vẫn sẽ tiếp tục xuất hiện
      Chỉ thêm ràng buộc và prompt vào danh sách kỹ thuật hay đặc tả là không ngăn được. Dù có làm cái bẫy tốt hơn thì sinh vật vẫn chui ra được
      Vấn đề là sự phình to của mã do cố vá thêm cho vừa prompt hay task. Nhiều khi ít mã hơn lại tốt hơn, và cần có người đoán được người khác muốn gì, kỳ vọng gì. Trình sinh mã rất tốt, nhưng nên dùng tiết chế hơn, đừng như vòi cứu hỏa
  • Hồi Copilot tự động hoàn thành một dòng, người ta nói “dù sao thì cả hàm vẫn phải do bạn viết”, rồi khi nó hoàn thành cả hàm thì lại nói “logic xung quanh hàm vẫn phải do bạn viết”, rồi khi xong cả phần logic đó thì lại nói “tính năng vẫn phải do bạn viết”
    Giờ nó làm xong cả tính năng thì lại bảo “nhưng kiến trúc vẫn phải do bạn viết”. Tôi không biết các mô hình này có giải được bài toán kiến trúc hay không, nhưng việc kỳ vọng cứ liên tục dịch chuyển thì khá thú vị

    • Những “con người” giả định đó ngay từ đầu đã luôn sai
      Dù AI hoàn thành một dòng, cả hàm, hay cả tính năng và ticket, bạn vẫn phải đọc và hiểu mã
    • Mô hình cũng có thể làm kiến trúc, nhưng ở thời điểm hiện tại thì thường làm rất tệ trừ khi được dẫn dắt rất mạnh
      Tôi dùng AI mọi lúc và nó đang dần tốt hơn, nhưng vẫn review từng dòng. Ở mức từng dòng riêng lẻ thì ngày nay chưa chắc đã tốt hơn tab autocomplete của năm ngoái, có lúc rất hay nhưng có lúc cũng thực sự tệ
    • Tôi nghĩ lời giải nằm giữa các dòng của bài viết
      LLM rất tuyệt cho phát triển phần mềm, nhưng chỉ khi không để chúng viết kiến trúc. Hãy tự tạo module, struct, enum, và nếu có thể thì tự thêm field cùng các biến thể
      Gắn doc comment cho từng struct, enum, field, module, rồi cho LLM trỏ vào module và cấu trúc dữ liệu đó để điền phần thân hàm cần thiết, cách này khá ổn
    • Với các ngôn ngữ hiện tại, codebase phức tạp ở quy mô toàn cục và các bất biến mong muốn không lộ rõ, nên khó mở rộng
      Dù có nói đi nói lại rằng “tuyệt đối không được blocking trên critical path”, LLM vẫn chèn blocking vào critical path, và dù bảo “làm X thì cần loại test Y”, nó vẫn chỉ làm X rồi quên test
      Con người cũng không thể làm theo chỉ dẫn 100%, nhưng LLM thì ngẫu nhiên hơn nhiều. Sai sót của con người tương đối hiếm khi chính xác làm điều ngược hẳn với điều bạn muốn
      LLM có thể nhìn thấy các bất biến quan trọng trong mã rồi vẫn tạo đường vòng, viết test khiến thất bại trông như thành công, nói rằng đã làm đúng yêu cầu rồi chôn nó trong một commit 5.000 dòng
      Tôi tin LLM rất tuyệt và là tương lai, nên vì thế tôi đang làm một ngôn ngữ cho chúng là https://GitHub.com/Cuzzo/clear. Chúng ta phải vượt qua vấn đề của các ngôn ngữ đòi hỏi ngữ cảnh toàn cục ở những nơi lẽ ra không cần, thì mới dễ cộng tác cùng chúng hơn
      Cũng đã có thành công, nhưng đôi khi nó gây bực bội đến mức tôi tự hỏi có đáng phải tiêu tốn sự tỉnh táo hay không
    • Tôi gọi đây là kiến trúc dùng một lần
      Không phải kiến trúc không quan trọng, mà là kiến trúc phù hợp hôm qua chưa chắc hôm nay vẫn còn phù hợp
  • Khi dùng coding agent, tôi đã đặt ra vài quy tắc
    Thứ nhất, nếu tạo mã bằng agent thì đó phải là thứ mà nếu có thời gian tôi hoàn toàn tin chắc mình có thể tự viết đúng
    Thứ hai, nếu không phải vậy thì tôi sẽ không đi tiếp cho đến khi hiểu hoàn toàn phần được sinh ra đến mức có thể tự tái tạo nó
    Thứ ba, nếu vi phạm quy tắc thứ hai thì có thể tạo ra nợ nhận thức, nhưng phải trả hết trước khi tuyên bố dự án hoàn thành
    Nợ tích lũy càng nhiều thì khả năng chất lượng mã sinh ra về sau giảm xuống càng cao, và nó cũng có cảm giác phình ra như lãi kép. Với dự án cá nhân, cách này vừa vui, vừa học được nhiều, lại giữ được một codebase mà mình có thể thoải mái hiểu rõ

    • Đây là bộ quy tắc hợp lý để giữ một mô hình tinh thần vững vàng về tính lành mạnh của mã và sự phát triển của codebase, nhưng ở nơi làm việc nơi kỳ vọng về tốc độ deadline đã thay đổi mạnh sau AI thì rất khó tuân thủ
      Cần tìm điểm cân bằng để vẫn gắn kết với codebase mà không trở thành nút thắt của cả team
    • Tôi từng cố làm theo quy tắc tương tự nhưng gặp một bài toán toán học khó
      Claude là nhà toán học cỡ tiến sĩ còn tôi thì không, nhưng tôi biết chính xác tính chất của nghiệm mình muốn và cách kiểm tra nó có đúng không. Vậy nên thay vì giữ lời giải đơn giản và ngây thơ của mình, tôi giữ lời giải của Claude, ghi rõ điều đó trong pull request, và mọi người đều thấy đó là lựa chọn đúng
      Tôi tò mò liệu nên có ngoại lệ trong kiểu trường hợp này không. Nếu AI không chỉ giỏi toán cao cấp mà còn giỏi coding hơn tôi rất nhiều, thì câu hỏi thú vị hơn là liệu tôi có dừng hẳn việc tự viết mã hay không, dù có mất khả năng tự đánh giá mã, miễn là vẫn giả định rằng tôi có thể đánh giá được test
    • Tôi thích cách gọi nợ hiểu biết hơn là “nợ nhận thức”
      Vì khoản nợ tích lũy đó chính xác là sự thiếu hiểu biết về mã, nên cách gọi này chuẩn hơn
    • Với dự án cá nhân thì cách nào vui hơn là điều quan trọng nên không sao, nhưng ở công việc thì chúng ta cũng đâu phải hiểu sâu toàn bộ từng tầng từ dependency, việc của đồng nghiệp, dịch vụ bên ngoài, cho tới cả silicon mới làm việc được
      Tôi không hiểu vì sao chỉ riêng AI lại đột nhiên bị đối xử khác đi
      Cuối cùng vẫn phải đánh giá theo rủi ro và phần thưởng. Cần cân nhắc thiệt hại nếu sai, xác suất bị phát hiện qua test và review, và lợi ích nếu làm đúng. Thư viện và dịch vụ bên ngoài cũng vậy
      Các quy tắc tài chính phức tạp trong một smart contract crypto không thể cập nhật và không có test hoàn toàn khác với một viewer trực quan hóa dữ liệu log nội bộ
    • Tôi cũng từng thử cách tiếp cận tương tự, nhưng cuối cùng thấy rằng việc tuân thủ đủ nghiêm ngặt quy tắc thứ hai là không thực tế
      Trên lý thuyết nghe có vẻ hay, nhưng thực tế thì bạn luôn chọn những lối tắt tinh thần mà chính mình cũng không nhận ra
      Khi sửa lỗi trong một codebase xa lạ, nếu so việc tự làm với việc nghĩ rằng mình đã “hiểu hoàn toàn” những gì agent làm, thì lượng thứ còn lưu lại trong đầu sau một tuần là khác hẳn. Tự làm thì nó tích lại thành tri thức chung và phần quan trọng thường còn nhớ, còn nếu cố chiếm hữu như thể phần agent làm là của mình thì lúc đó có vẻ như hiểu nhưng rồi quên rất nhanh
      Vì vậy trong các trường hợp như thế tôi kết luận rằng trợ giúp từ LLM phần lớn có hại cho mục tiêu của mình, ngay cả khi chưa tính tới các lo ngại khác như áp lực thời gian hay kinh doanh
  • Tôi cũng đã trải qua chuyện y hệt
    Cú lừa diễn ra như thế này. Với một codebase tốt, AI có thể làm ra rất nhiều tính năng, thậm chí trông còn nhanh hơn, an toàn hơn và chính xác hơn. Đặc biệt ở những lĩnh vực bạn không rành thì lại càng thấy như vậy
    Theo thời gian, codebase lớn lên, thời gian điều hướng kéo dài, tỷ lệ thất bại tăng lên. Vì không muốn thừa nhận, bạn càng ép mạnh hơn, rồi chỉ dừng lại khi việc thay đổi đã gần như bất khả thi
    Khi nhìn lại mã thì gọi là spaghetti còn chưa đủ, nó giống Vạn Lý Trường Thành hơn
    Cuối cùng tôi đã xóa 75.000 dòng trên tổng 140.000 dòng, và cảm thấy 3 tháng lao đầu mạnh vào agent coding là lãng phí. Tôi đã tạo ra tính năng vô dụng, tăng thêm bug, đánh mất mô hình tinh thần về mã, bỏ lỡ những quyết định khó chỉ nhìn thấy khi ở trong mã, và cũng làm người dùng thất vọng

    • Điều thú vị là nhiều người thấy kết quả này đáng ngạc nhiên
      Tôi không mỉa mai đâu, mà thật sự tò mò kỳ vọng ban đầu là gì và đến từ đâu
      Có vẻ người ta kỳ vọng khác với LLM. Nếu bạn đưa một mô tả tính năng đã tóm tắt cho một “lập trình viên” ngẫu nhiên chỉ gặp trên mạng rồi nhận lại một đống triển khai nửa hỏng, sẽ chẳng ai ngạc nhiên cả
      Nhưng đôi khi người ta lại kỳ vọng ở cái máy hay nói thao thao và ảo giác những phép màu mà họ còn chẳng kỳ vọng ở con người. Tôi tò mò niềm tin đó đến từ đâu
    • Tôi cho rằng codebase lớn nên là tập hợp của những codebase nhỏ
      Giống như một thành phố lớn là tập hợp của các thành phố nhỏ, bạn có bản đồ, có thể phóng to vào khu vực cục bộ và làm việc trong phạm vi đó. Muốn uống một ly cà phê thì đâu cần biết hết mọi chi tiết của New York
      Xây dựng kiến trúc lành mạnh có thể bảo trì là trách nhiệm của người dùng công cụ. AI không ngăn cản điều đó, và nếu cầm công cụ đúng cách thì thậm chí còn có thể hỗ trợ
    • Có lẽ vẫn còn lời giải về quy trình làm việc ngoài chuyện bỏ hẳn AI
      Chẳng hạn coi mã do AI sinh ra ngay lập tức là mã legacy, đặt ranh giới đóng gói mạnh cùng interface được định nghĩa rõ, rồi tích hợp nó theo một luồng thủ công hơn
      Có cả một phổ cách dùng, từ prompt một phát tới sinh mã inline, và tùy bài toán cũng như vị trí trong codebase mà cách phù hợp sẽ khác nhau
      Sinh một lần phù hợp hơn ở giai đoạn prototype khi phải lặp lại đặc tả nhiều, còn khi prototype đã định hình thì nên hạ xuống mức sinh theo module hoặc file để làm có hệ thống hơn, đồng thời ở tầng đó vẫn phải giữ được một mô hình tinh thần tương đối tốt
    • Tôi thắc mắc liệu có phải họ không đọc mã sinh ra mà cứ tự động commit hết hay không
      Nếu có đọc nhưng không hiểu thì đáng lẽ nên yêu cầu chú thích chi tiết cho từng đầu ra; nếu đã biết mô hình sẽ chật vật hơn khi codebase lớn lên thì độ nghiêm trong việc xem xét đầu ra cũng phải tăng tương ứng với độ phức tạp
    • Tôi chưa từng xử lý codebase lớn như vậy, nhưng có lẽ có thể áp dụng quy trình kiểu Working Effectively with Legacy Code
      Tạo ra những “hòn đảo” mã chất lượng cao hơn, dùng AI hỗ trợ tái dựng ý đồ của lập trình viên và quy tắc nghiệp vụ, rồi tạo seam và unit test cho module mục tiêu
      AI không nhất thiết chỉ để tăng throughput; nó cũng có thể là công cụ thăm dò và refactor linh hoạt để hỗ trợ việc tự viết mã hoặc triển khai bằng agent về sau
  • Mỗi lần đọc những bài kiểu này, tôi lại so tốc độ mà người ta nói AI mang lại với tốc độ tôi có được khi đơn giản là tự tay code
    Tình cờ là tôi đang làm một dự án 3D MMO được 7 tháng, hiện đã chơi được, mọi người cũng thấy vui, đồ họa ổn, và có thể dễ dàng nhét vài trăm người vào server. Kiến trúc cũng khá tốt nên mở rộng tính năng dễ, và có lẽ sau khoảng 1 năm phát triển là có thể phát hành
    Trong khi đó bài gốc sau 7 tháng vibe coding còn chưa làm nổi một TUI cơ bản. Tốc độ ra tính năng có thể tạo cảm giác cao, nhưng để làm kiểu UI cơ bản như vậy thì chậm đến khó tin. Có rất nhiều thư viện TUI tốt, và đây chỉ là loại việc điền dữ liệu cần thiết vào bảng, tự làm bằng tay chỉ mất vài tuần
    Khi dùng AI, cảm giác tiến rất nhanh và làm được rất nhiều là rất mạnh, nhưng thực tế dường như trong nhiều trường hợp còn chậm hơn tự code rất nhiều. Dữ liệu về năng suất cũng có vẻ ủng hộ điều đó: người dùng AI cảm thấy nhanh hơn nhưng sản lượng thực tế lại ít hơn

    • Chỉ số này phụ thuộc rất lớn vào ai dùng AI và dùng vào việc gì
      Trong công việc phát triển phần mềm, phần tốn thời gian nhất là các cuộc họp để căn chỉnh kỳ vọng của stakeholder và giải pháp. Ở góc độ đó AI gần như không giúp gì, nên nếu so số giờ công từ lúc đề xuất đến lúc vào vòng lặp test thì kết quả sẽ khá thất vọng
      Nhưng trong việc giải quyết vấn đề, sửa bug, và triển khai giải pháp đã được phê duyệt, tôi cảm thấy nó đã tốt hơn ít nhất 10 lần so với trước. Không chỉ về thời gian thuần túy mà còn ở khả năng diễn giải hành vi quan sát được và điều tra vấn đề
      Dù vậy, cũng có người không thể tạo ra kết quả chính xác, có giá trị bằng AI. Nếu bạn biết chính xác mình muốn gì và muốn như thế nào thì AI giúp rất nhiều. Nếu giao cho nó điều mà đằng nào tôi cũng sẽ làm, nó làm nhanh hơn. Nhưng nếu bạn còn chưa biết rõ mình muốn gì thì AI lại gây hại cho tiến độ
    • Gần đây tôi cũng đi đến cùng kết luận đó
      Lý do những thứ người ta khoe là làm bằng LLM không mấy ấn tượng là vì đa số đều là thứ có thể tự làm bằng tay trong thời gian rất ngắn
      Tôi cũng không quan sát thấy phần mềm ấn tượng tăng lên, và điều đó có vẻ khớp với thực tế là hiện nay LLM chủ yếu được dùng để giải quyết các vấn đề đơn giản thay vì những vấn đề quan trọng
    • Những người cảm nhận lợi ích lớn nhất từ LLM rất có thể vốn dĩ không biết cách làm phần mềm tốt, hoặc không đủ khả năng làm điều đó
    • Tôi cũng thấy mốc 7 tháng khá lạ. Kể cả viết bằng ngôn ngữ mới chắc cũng không lâu đến thế
      Một điểm khác ít được nhắc tới là chất lượng mã
      Codebase vibe coding là ví dụ rất điển hình cho việc LLM không hề giỏi viết mã đến vậy. Nó sửa sai lầm của chính mình rồi lại tạo lại ngay, và việc dùng pattern cũng không nhất quán
      Gần đây Claude còn có những lựa chọn style code “thú vị” nhưng lại không khớp với style hiện tại của codebase
    • Dòng GPT được xây dựng với mục tiêu sống còn là tạo ra văn bản, tức ngôn ngữ và mã, nên về mặt cấu trúc hệ thống bên trong có vẻ nghiêng về hướng tự tạo mọi thứ trực tiếp
      Cần dùng kiểu ngôn ngữ của “senior developer” để chặn những vòng lặp đó
  • Đoạn “trước khi viết mã, tự thiết kế các interface, kiểu message, quy tắc ownership cụ thể” chính là phần khó của việc coding
    Khi đã có kiến trúc rồi thì viết mã rất dễ. Nếu không tự viết mã, sẽ khó nhận ra rằng bạn đã thiết kế một API cho phép null nhưng database thì không cho, hoặc dù có cho thì bạn vẫn bỏ lỡ các vấn đề nhỏ khác
    Tôi không hiểu sao viết xong bài này mà tác giả vẫn chưa nhận ra vấn đề là AI. Không chỉ vì giao kiến trúc cho AI, mà còn vì đã không theo dõi cẩn thận mọi việc AI làm
    AI là một trình sinh mã được tô vẽ hào nhoáng, và bạn phải kiểm tra mọi thứ nó làm. Phần khó của kỹ nghệ phần mềm chưa bao giờ là viết mã, mà là tất cả những thứ khác

    • Tôi nghĩ có hai loại lập trình viên. Loại cho rằng phần khó là viết mã, và loại không nghĩ vậy
      Những lập trình viên thấy coding là khó thì rất thích AI coding. Vì việc trước đây khó giờ đã thành dễ
      Ngược lại, với những người nghĩ coding là dễ, coding là bài toán về abstraction, khả năng bảo trì, khả năng mở rộng. Điều khó là đặt nền móng hợp lý để phần mềm có thể lớn lên, và khi tìm được abstraction đúng thì phần còn lại tương đối dễ
      Với những người này, AI coding là công cụ hữu ích nhưng không phải công cụ thần kỳ. Tác giả bài gốc đã nhận ra giới hạn của AI nên thuộc nhóm thứ hai, và đã nhìn thấy phần khó mà AI không làm được
    • Hiện giờ còn có vấn đề là định nghĩa đang rất rối
      Một bên là những người dùng tab autocomplete rất mạnh hay chatbot ở cửa sổ bên cạnh nhưng vẫn review rõ ràng mọi thứ, bên kia là những người như Steve Yegge quảng bá editor mới để điều phối hàng chục agent như thể sẽ không đọc phần lớn mã nữa: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
      Nhóm thứ nhất vẫn suy nghĩ sâu về thiết kế, interface, cấu trúc dữ liệu và review rất chặt. Nhóm thứ hai thì không như vậy nên đáng lo hơn
    • Tôi cho rằng agent gần như luôn thất bại ở khoảng giữa lập kế hoạch và thực thi
      Chúng theo cách tiếp cận plan → red/green/refactor, và bản thân phần kế hoạch thì khá thuyết phục, có vẻ có cơ sở vì đã hút hết tài liệu và thảo luận diễn đàn vào
      Vấn đề là khi bắt đầu làm việc thì chắc chắn sẽ lòi ra chỗ tài liệu và triển khai thực tế khác nhau. Có thể bộ công cụ chưa từng được dùng theo cách đó, tài liệu đã cũ, hoặc đơn giản là bug
      Dù vậy, nếu mục tiêu dự án hay tính năng đủ rõ và có thể chạy/test cục bộ, agent vẫn có thể lặp đi lặp lại trong ngõ cụt kiến trúc rồi thoát ra được. Nó còn đi xem cả dependency và code thư viện, thậm chí đề xuất sửa upstream, khá giống việc tôi sẽ làm trong một phiên debug sâu
      Vì thế tôi khá hài lòng với cách chỉ đạo và giám sát thay vì tự làm việc chán ngắt. Tuy nhiên, khá nhiều đồng đội của tôi vốn cũng không đào vấn đề kiến trúc sâu đến mức này mà thường mặc định “escalate cho kiến trúc sư”, nên về dài hạn điều đó có lẽ không tốt
      Cửa sổ để có thể chạy và hiểu mọi thứ dường như đang đóng lại rất nhanh. Dù vậy, có lẽ chúng ta cũng sẽ thích nghi bằng cách tạo công cụ và framework mới, giống như vẫn dùng compiler dù không hiểu hết quá trình nó chuyển sang mã máy hay cơ chế branch prediction và caching của CPU hiện đại
    • Có vẻ nhiều người bỏ qua chuyện phải kiểm tra mọi thứ AI làm
      Ở vị trí một người chưa có quá nhiều kinh nghiệm code, tôi đang học được nhiều hơn bao giờ hết bằng cách kiểm tra kết quả, xem cái gì đúng cái gì sai
      Vì vậy tôi cũng không nghĩ chuyện này sẽ sớm cải thiện vượt bậc. Khi người ta hỏi “làm sao đầu ra của Claude lại tốt thế”, câu trả lời của tôi luôn là “tôi xem rất kỹ, tìm vấn đề rồi bảo Claude sửa”. Thực sự chỉ có vậy thôi, nhưng nghe xong là mắt họ đã đờ ra
      Nó giống việc Google làm cho tìm kiếm thông tin dễ hơn, nhưng không loại bỏ yếu tố con người trong việc phân biệt thông tin tốt và xấu
    • Đây là cách duy nhất để tôi dùng agent mà không hoàn toàn ghét nó hay thất bại với nó
      Trước hết hãy nghĩ về bài toán, thiết kế cấu trúc và API, rồi mới giao phần triển khai cho AI
  • Tiêu đề là “quay lại viết code bằng tay”, nhưng việc thực sự đang làm là “tự làm công việc thiết kế trước khi mã được viết ra”
    Như vậy thì có vẻ mã vẫn do Claude sinh ra
    Nghiêm trọng hơn, thật khó hiểu khi trong suốt 7 tháng tác giả nghĩ rằng dự án vibe coding chạy tốt dù còn chưa xem mã nguồn được sinh ra, thậm chí còn mua cả domain

    • Nói ngắn gọn thì đây là tiêu đề câu click, và mục tiêu của bài viết có vẻ là kéo sự chú ý về dự án của tác giả
    • Tôi từng mua domain cho dự án chỉ vài phút sau khi nảy ra ý tưởng
      Nếu đó là side project và bạn theo diff để kiểm tra dần dần, thì việc không đọc quá sâu vào mã cũng không phải hoàn toàn kỳ quặc. Chắc chắn là một cách làm khác, nhưng chưa đến mức điên rồ
  • Cảm giác như đang xem các lập trình viên speedrun những bài học của quản lý dự án và quản lý sản phẩm
    Giờ họ đang nhận ra đặc tả là hữu ích, và viết ra nhiều mã sai không làm dự án nhanh hơn. Lập trình viên thường khó chịu vì họp hành và thảo luận cản trở việc viết mã, nhưng những quá trình đó thường tồn tại để ngăn mọi người cùng nhau viết thêm nhiều thứ sai
    Họ cũng đang nhận ra quản lý công việc là hữu ích, và giờ khi ngày càng nhiều người nói phải làm hết thiết kế từ trước thì lại đang tiến về mô hình waterfall
    Tiếp theo sẽ là đặt tên cho prototyping, rồi nói về tính năng phát triển dần dần để quản lý đồng thời yêu cầu cũ và mới, rồi cuối cùng sẽ đi đến chuyện khách hàng cần tham gia nhiều hơn
    Cần nhìn vào việc project manager và product manager thực sự làm gì. Họ dẫn dắt một sản phẩm là mã, nhưng lại không được kỳ vọng sẽ đọc mã, và phải đạt được điều đó chỉ bằng ngôn ngữ tự nhiên

    • Chuẩn luôn. Có vẻ những người này chưa từng làm quản lý
      Họ nghĩ con người không viết ra đồ hỏng à? Họ nghĩ team không bao giờ đi sai đường rồi đốt cả tuần, thậm chí cả tháng à? Giờ với vibe coding thì có thể trải nghiệm toàn bộ chuyện đó chỉ trong 30 phút. Từ góc nhìn của một cựu technical product manager thì cảm giác giống hệt
  • Thực ra có vẻ không phải đang tự viết mã bằng tay, nên sự khác biệt giữa tiêu đề và kết luận khá gây bối rối

    • Tôi nghĩ đó là chủ ý đặt tiêu đề giật gân để HN cắn câu và đưa bài lên trang đầu
    • Bài viết có khi cũng không phải tự tay viết. Thứ được đưa lên đầu HN có vẻ là tiêu đề chứ không phải nội dung bài viết