1 điểm bởi GN⁺ 2025-09-09 | 4 bình luận | Chia sẻ qua WhatsApp
  • Trước đây, môi trường phát triển Ada đã từng giải quyết xong vấn đề định dạng mã
  • Các lập trình viên làm việc với mã ở dạng biểu diễn trung gian (IR) DIANA và xem theo thiết lập pretty-printing mà mỗi người mong muốn
  • Đến nay vẫn tồn tại sự kém hiệu quả và tranh cãi lặp đi lặp lại xoay quanh linter hay chính sách định dạng
  • Máy trạm Rational R1000 khi đó cung cấp môi trường phát triển và các tính năng mang tính đột phá
  • Từ vấn đề định dạng code, bài viết gợi ý tham khảo cách làm của một thế hệ trước để thay đổi thực hành phát triển ngày nay

Tranh luận về định dạng mã – lời giải từ thập niên 1980

  • Tác giả nhắc lại trải nghiệm với thầy giáo khoa học máy tính của mình, ông Paige, người từng tham gia làm việc trên một trình biên dịch Ada khi còn ở bậc trung học
  • Khi phàn nàn về sự bất tiện trong việc thiết lập công cụ linter vào năm 2016 và hỏi rằng “vì sao chúng ta vẫn phải chịu đựng chuyện này”, tác giả được nghe kể rằng vấn đề này đã được giải quyết từ hơn 40 năm trước

Sự xuất hiện của Ada và DIANA

  • Thay vì lưu mã nguồn dạng văn bản, các lập trình viên Ada sử dụng một biểu diễn trung gian tên là DIANA (Descriptive Intermediate Attributed Notation for Ada)
  • Mỗi lập trình viên có thể xem mã theo thiết lập pretty-printing của riêng mình
  • Không tồn tại tranh cãi về định dạng hay vấn đề linter, và trong trình soạn thảo người ta có thể chỉnh sửa trực tiếp cây chương trình (tương tự projectional editing hiện đại)

Rational R1000 – môi trường phát triển tiên phong

  • Máy trạm Rational R1000 tích hợp nhiều tính năng cao cấp như biên dịch gia tăng, phân tích tĩnh, quản lý phiên bản, gỡ lỗi
  • Nó được dùng trong các dự án phần mềm quan trọng như dự án của DoD, Trạm Vũ trụ Quốc tế (ISS), tiêm kích F-22, và còn góp phần vào sự ra đời của UML
  • Theo Grady Booch, R1000 là cỗ máy dựa trên DIANA, không lưu mã nguồn mà chỉ dùng việc pretty-print cây DIANA

Lợi ích của phát triển dựa trên DIANA

  • Không cần tranh cãi về định dạng, thiết lập linter hay thống nhất môi trường biên tập
  • Nhờ tăng tốc phần cứng, nó mang lại trải nghiệm phát triển đột phá như biên dịch gia tăng, refactoring dễ dàng, tích hợp nhanh
  • Điều này có ảnh hưởng quan trọng tới hiệu suất phát triển và công việc trên các hệ thống lớn

Hàm ý cho hiện tại

  • Biên dịch tăng tốc bằng phần cứng ngày nay bớt quan trọng hơn, nhưng việc giải quyết bài toán định dạng vẫn còn chưa thỏa đáng
  • Dù cách làm chủ đạo hiện nay không phải projectional editing hay môi trường live, đây là lúc nên cân nhắc áp dụng những thực hành phát triển hiệu quả hơn và ít gây tranh cãi hơn như cách tiếp cận trong quá khứ

Tài liệu tham khảo

  • Khi tìm hiểu chủ đề này, tác giả đã trích dẫn nhiều tài liệu và báo cáo kỹ thuật liên quan đến R1000

4 bình luận

 
ndrgrd 2025-09-10

Theo tôi biết, hiện đã có chức năng tự động định dạng mã nguồn được chia sẻ bằng một cấu hình thống nhất, và các công ty cũng dùng khá nhiều.

 
euphcat 2025-09-10

Có vẻ trọng tâm không phải là chuyện tự động định dạng, mà là ngay từ việc tồn tại nhận thức rằng một kiểu định dạng cụ thể nào đó vượt trội hơn, hay quá trình phải từ bỏ kiểu định dạng của mình để thích nghi với một kiểu định dạng xa lạ, đã là điều không cần thiết rồi. Vì lập luận ở đây là lưu một biểu diễn trung gian không bị ràng buộc bởi định dạng, rồi tùy người dùng mà pretty-print theo cách họ thấy thuận tiện.

 
ndrgrd 2025-09-10

Điều tôi muốn nói là với việc định dạng tự động, ta vẫn có thể làm cùng một việc bằng các ngôn ngữ hiện có mà không cần những biểu đạt trung gian, nhưng có lẽ phần giải thích của tôi còn thiếu.

 
GN⁺ 2025-09-09
Ý kiến trên Hacker News
  • Tôi không hiểu vì sao mọi người lại quá bận tâm đến việc cấu hình linter như vậy, rõ ràng đây là một cuộc tranh cãi vô ích; cứ chốt một kiểu rồi chạy linter tự động là xong, tôi cảm thấy thời gian để làm kỹ thuật phần mềm thực sự quan trọng hơn. Dù là định dạng nào thì khi cả nhóm đã quyết định dùng, chỉ trong vòng một tuần là cũng quen thôi.
    • Cần nói rõ rằng source code formatter và chương trình lint là hai thứ khác nhau; formatter chỉ chỉnh bố cục mã nguồn, còn linter giúp tìm các bug hoặc lỗi tiềm ẩn trong mã. Có công cụ hỗ trợ cả hai, nhưng đó chỉ là chi tiết triển khai. Nếu muốn biết lint là gì, có thể xem https://en.wikipedia.org/wiki/Lint_(software).
    • Tôi dành phần lớn thời gian để "đọc" code, và bố cục code có liên quan đến tốc độ đọc của tôi. Có thể rồi cũng sẽ quen với các bố cục khác nhau, nhưng không phải mọi bố cục đều dễ đọc như nhau. Tôi ghi nhớ code bằng các mẫu thị giác, và đọc code bằng cách chia khối dựa trên bố cục. Trớ trêu là dù tôi bị aphantasia, tức không thể hình dung bằng con mắt tâm trí, tôi vẫn ghi nhớ tốt hơn nhờ các dấu hiệu thị giác.
    • Một số thiết lập linter rõ ràng có lợi ích. Ví dụ, nếu dùng trailing comma trong bảng, khi thêm mục mới vào dòng cuối chỉ cần sửa một dòng, và diff cũng gọn hơn. Vì vậy lựa chọn sai sẽ khiến cuộc sống của tôi khó khăn hơn. Với danh sách được sắp xếp hoặc include cũng vậy; nếu không sắp xếp thì người ta luôn thêm vào cuối, khiến xung đột merge xảy ra thường xuyên hơn. Cũng như lợi ích của auto-formatter, việc sắp xếp cũng nên giúp tiết kiệm thời gian. Và tôi khá nhạy cảm với phong cách không nhất quán. Điều quan trọng nhất là thống nhất chỉ một kiểu duy nhất.
    • Tôi không quá bận tâm đến từng tùy chọn chi tiết, nhưng tôi cho rằng việc có cấu hình linter chung để giảm nhiễu vô ích khi xem PR là điều thiết yếu. Tôi thấy việc git hay các PL chỉ xử lý theo từng dòng là không được thanh lịch. Trong 20 năm sự nghiệp, tôi mới chỉ thấy một ví dụ dùng cách tinh tế hơn, như ngôn ngữ Ada. Thật khó để tạo ra thứ gì đó thực sự thanh lịch và hiệu quả, và nếu đã có nhiều lựa chọn đủ tốt rồi thì lại càng khó phổ biến rộng rãi.
    • Hồi trẻ tôi cũng từng sa vào cuộc tranh cãi này. Lý do là vì tôi lầm tưởng mình là người thông minh nhất trong nhóm. Tôi nghĩ ý kiến của mình là quan trọng nhất. Tôi tin rằng mọi người phải lắng nghe. Nhưng tôi đã sai.
  • Điểm đánh đổi cần cân nhắc ở đây là nếu dùng định dạng khác ngoài văn bản, khả năng dùng các công cụ phổ dụng như grep, diff, sed, quản lý phiên bản sẽ giảm đi. Cuối cùng sự phụ thuộc vào công cụ, định dạng hay phần mở rộng IDE chuyên biệt sẽ tăng lên. Điểm mạnh của triết lý Unix là tính kết hợp thông qua plain text. Có một câu hỏi có thể cắt gọn tranh luận này: nếu trong editor chỉ cần có thể tự do thiết lập độ rộng khoảng trắng ban đầu, thì những người thích tab còn lập luận gì nữa không?
    • Cách tiếp cận dựa trên văn bản có ưu điểm, nhưng tôi cảm thấy chúng ta chỉ đi xuống một đoạn rồi đáng lẽ có thể leo lên một ngọn núi cao hơn nhiều là projectional editing, nhưng lại không làm được và cứ loay hoay dưới chân núi. Tất cả các ví dụ này đều hoạt động tốt hơn với code có thông tin cấu trúc: chẳng hạn tìm symbol, thứ tôi dùng thường xuyên hơn grep văn bản rất nhiều, có thể dùng ast-grep; diff thì có thể hỗ trợ di chuyển có cấu trúc hoặc bỏ qua thay đổi không mang ý nghĩa như semanticdiff.com; còn thay thế cho sed thì có thể dùng @codemod/cli. Quản lý phiên bản cũng đã được thử nghiệm khá nhiều trong các ngôn ngữ như Unison để tự động tránh xung đột do các thay đổi không mang ý nghĩa như thứ tự, khoảng trắng, v.v.
    • Ý tưởng này cứ lặp đi lặp lại, nhưng xét về hiệu quả chi phí thì không đáng vì thay vì chỉ chạy một formatter đơn giản, ta lại cần những công cụ cực kỳ phức tạp. Việc để mỗi lập trình viên nhìn code theo đúng ý mình trên thực tế cũng không có nhiều ý nghĩa. Trong mọi đội tôi từng làm cùng, mọi người cùng đặt ra quy tắc và dùng formatter để áp dụng cưỡng chế; ban đầu có thể không đồng ý, nhưng rồi cũng nhanh chóng quen. Tranh cãi về formatting là dạng lãng phí thời gian điển hình.
    • grep, diff, sed, merge theo dòng thật ra là những công cụ tệ để thao tác code. Tôi nghĩ chúng ta nên suy nghĩ về công cụ tốt hơn thay vì tiếp tục tranh cãi ở hướng này.
    • Nếu biểu diễn trung gian (Intermediate Representation) được giữ ở dạng văn bản thì grep/diff/sed vẫn có thể hoạt động. Nếu chỉ dùng formatter dựa trên AST, thì code sẽ được lưu ở dạng đã chuẩn hóa theo một AST nhất định, còn editor sẽ parse AST rồi hiển thị theo định dạng người dùng mong muốn; khi lưu thì lại chuyển về dạng chuẩn hóa.
    • Toàn bộ hệ điều hành đã được tạo dựng dựa trên các file nguồn kiểu này. Triết lý Unix cũng chỉ thực sự tỏa sáng khi mọi công cụ đều chỉ cần nghĩ đến plain text và biết cách parse nó.
  • Tôi nghĩ source code formatting rõ ràng có cả khía cạnh typography, và tôi không đồng ý với quan điểm cho rằng đó hoàn toàn chỉ là sở thích cá nhân. Một số kiểu formatting là công cụ truyền tải ý nghĩa và cấu trúc rất hiệu quả. Nếu dùng công cụ tự động chỉ tuần tự hóa các token tối thiểu rồi phục hồi lại, giá trị này sẽ biến mất. https://naildrivin5.com/blog/2013/05/17/source-code-typograp...
    • Những người làm in ấn chuyên nghiệp từ lâu đã rất chăm chút khoảng cách và căn chỉnh trong bảng biểu hay công thức. Người ngoài có thể khó nhận ra, nhưng những phần đó cực kỳ quan trọng. Tôi nghĩ code cũng có thể phát triển theo hướng formatting tinh tế hơn như vậy.
    • Ví dụ, có phàn nàn rằng formatter black của Python tách các truy vấn SQLAlchemy ra quá nhiều dòng, khiến độ dễ đọc còn giảm đi.
    • Tôi luôn thấy lạ vì sao nhiều người không cảm nhận được tầm quan trọng của typography trong code.
    • Tôi cảm thấy việc vừa quan tâm đến typography, nhưng đồng thời lại chấp nhận vô phê phán các quy ước của ngôn ngữ lập trình, mới là hướng đi sai nhất. Ví dụ, có một nền văn hóa sẵn sàng dùng những từ như register, trong khi lại dùng dấu sao (*) để biểu diễn con trỏ; từng ký hiệu đều hoàn toàn có thể trực quan và rõ ràng hơn, vậy mà người ta vẫn cố chấp giữ những cách thể hiện phức tạp, khó đọc. Ngay cả ký hiệu hay từ khóa dành riêng cũng có thể đổi sang thuật ngữ quen thuộc và tự nhiên hơn, nhưng vì quá bám vào truyền thống và quy ước cũ nên người ta hy sinh khả năng đọc hiểu. Lấy ví dụ hàm strcpy trong C, người viết giải thích rằng hoàn toàn có thể tái cấu trúc lại bằng thuật ngữ và cú pháp rõ ràng, dễ đọc hơn nhiều.
    • Sau khi giải thích rằng khai báo parameter trong C gồm modifiers, kiểu dữ liệu và tên, người viết chỉ ra vấn đề về tính dễ đọc của những khai báo phức tạp như char *argv[]. Và họ cũng cho rằng kiểu formatting theo phong cách C++ như char* a, b có thể gây hiểu nhầm, nên nên tránh kiểu đó.
  • Tôi không đồng ý với tiền đề của cuộc tranh luận này. Code formatting là một phương tiện giao tiếp cực kỳ quan trọng. Formatting tốt là tín hiệu cho thấy lập trình viên (1) nhận thức được tầm quan trọng của formatting, (2) tuân thủ quy tắc tốt, (3) có gu tốt và (4) có khả năng phán đoán để xử lý tốt các trường hợp ngoại lệ. Bốn điều này là những năng lực quan trọng, vượt xa chuyện formatting đơn thuần và ảnh hưởng đến năng lực phát triển nói chung. Nhưng auto-formatter hay các quy tắc linter lại làm yếu đi tín hiệu đó, và giống như luật Goodhart, khiến mục đích ban đầu bị đánh mất.
    • Họ khuyên nên tự đọc bài blog vì nó ngắn và đơn giản, đừng phản ứng ngay chỉ vì tiêu đề; sẽ tốt hơn nếu hiểu bối cảnh của các từ shouldunnecessary.
    • Tôi nghĩ người thờ ơ với formatting là người либо (1) chưa từng trải nghiệm nhiều người cùng sửa một file, hoặc (2) chưa từng merge branch, hoặc (3) chưa từng bảo trì codebase lớn, hoặc (4) chưa từng refactor quy mô lớn, hoặc (5) chưa từng lần theo lịch sử code hay dùng công cụ diff/so sánh, hoặc (6) chưa từng phát triển công cụ tự động hóa cho codebase, hoặc (7) là kiểu chỉ quan tâm đến bản thân mà thiếu ý thức hợp tác.
    • Nếu câu trả lời cho tất cả các mục đó đều là "không", thì tôi cho rằng chỉ cần tự động hóa formatting lúc pass/commit, và nếu không qua tự động hóa thì CI báo lỗi là đủ. Thay vì ám ảnh với chi tiết, cứ theo mặc định và từ bỏ phong cách cá nhân đôi khi còn là một lợi thế. Nếu để nguyên mặc định thì bất kỳ ai nhìn vào code cũng sẽ thấy quen thuộc hơn. Hơn nữa, formatting và linting là hai thứ khác nhau, nhưng cả hai đều có thể được giải quyết cùng lúc bằng công cụ tự động.
    • Họ nói đùa rằng có thể học cách đánh giá tương tự qua chữ viết tay, vậy nên từ giờ hãy yêu cầu mọi người nộp PR bằng chữ viết thảo.
    • Họ khuyên rằng phụ thuộc vào "gu để chọn quy tắc formatting tốt" chỉ càng dễ kéo theo tranh cãi và lãng phí thời gian; tốt hơn là giao cho formatter tích hợp sẵn như của Go và Rust.
  • Việc lưu code theo cây cú pháp rồi xử lý phần code con người nhìn thấy như một dạng render đã có từ 20~25 năm trước. Đây là một xu hướng bắt nguồn từ thập niên 90, khi refactoring lần đầu trở nên phổ biến. Những IDE như Visual Age từng áp dụng mô hình lưu code trong cơ sở dữ liệu thay vì hệ thống file. Intentional programming hay model-based development cũng là một phần của chu kỳ này. Bản chất của refactoring là biến đổi AST; việc đổi tên symbol cực kỳ dễ, không cần trực tiếp tìm rồi thay trong source code. Nhưng con người vẫn quen với việc sửa file, và có nhiều sự kháng cự cũng như ma sát trong việc phổ cập cách tiếp cận lưu chính cấu trúc đó. Việc sau ngần ấy thời gian người ta vẫn tiếp tục tranh cãi về formatting chính là dấu hiệu cho thấy nhu cầu đối với giải pháp thay thế này. Không hiếm trường hợp ngay cả việc đổi tên symbol một cách robust ở cấp ngôn ngữ hay editor cũng còn chưa được hỗ trợ đúng nghĩa.
    • Có ý kiến cho rằng đây là sự pha trộn các lớp trừu tượng. AST cuối cùng vẫn phải được lưu vào file, nên cũng không khác nhiều so với việc chạy công cụ hiểu AST trên một file mà con người vẫn đọc được. Định dạng lưu trữ không quá quan trọng; cốt lõi là bài toán công cụ thông minh hơn. Họ đưa ra ví dụ như Roslyn của Microsoft hay hướng đi của các compiler hiện đại muốn tương tác với codebase thông qua API.
  • Người viết trực tiếp đưa ra ví dụ rằng có những kiểu formatting không thể suy ra chỉ từ AST. Ví dụ, khi có nhiều dòng assignment, việc ba dòng được căn thẳng hàng với nhau, được căn theo dấu =, hay được thụt vào để làm nổi bật chiều sâu cấu trúc, đều là các lựa chọn khác nhau. Nếu muốn nhấn mạnh giá trị thì có thể căn phải các con số; nếu muốn nhấn mạnh cấu trúc thì có thể căn các member variable cho dễ nhìn. Tùy khía cạnh của code mà tác giả muốn nhấn mạnh, cách trình bày sẽ khác nhau. Họ cho rằng nếu không có metadata bổ sung thì những thông tin này không thể trích xuất từ AST.
    • Tôi hiểu ý, nhưng trên thực tế chỉ dùng hai cách đầu mà thôi. Mục tiêu thực tế không hẳn là nhấn mạnh mà là khả năng đọc. Dù có mất mát khi chuyển sang AST, tôi vẫn cho rằng cái đạt được lớn hơn nhiều. Hơn nữa, những biến thể để giữ phần nhấn mạnh trong AST cũng hoàn toàn khả thi. Chẳng hạn có thể dùng setValue([bar, glob], 1) hoặc cú pháp comment để override style, v.v.
    • "Formatting code mong muốn" cuối cùng vẫn là chủ quan; như ví dụ trên, có người thích 2/4/8 khoảng trắng, có người thích căn theo cột. AST không có thông tin formatting của source code nên không thể tự động suy ra.
    • Ví dụ formatting thứ hai và thứ ba thực ra là vấn đề thiết kế cấu trúc, tức vi phạm Law of Demeter, chứ không thuộc phạm vi formatting.
  • Projectional Editing cũng có thể áp dụng cho source text. Có video minh họa trong JetBrains MPS về việc render bảng từ code https://www.youtube.com/watch?v=XolJx4GfMmg&t=63s. Tôi muốn IDE có khả năng render dictionary thành bảng. Ngay cả hiện tại cũng đã có một phần của những khả năng tương tự như code folding, inlay hints, hay docstring được render dưới dạng HTML https://x.com/efortis/status/1922427544470438381.
  • Có ý kiến cho rằng chúng ta đang bị giới hạn bởi việc chưa thể chấp nhận ngay một thứ gì trừu tượng hơn plain text. Dù có thử thì cuối cùng cũng lại bị hạ cấp xuống projection plain text. Họ cho rằng "bảng tra cứu khổng lồ (GLUT)" được tích lũy từ Morse Code đến Unicode đã tạo ra văn hóa giải mã ký hiệu hiện tại. Khi nâng mức trừu tượng lên, có thể tạo ra tập ký hiệu phù hợp hơn cho ứng dụng, nhưng công cụ tương ứng lại không xuất hiện. Cuối cùng mọi thứ vẫn bị chuyển về dạng truyền tải văn bản rồi parse tiếp, như CSV hay Markdown. XML cũng đôi khi có editor riêng, nhưng con người cuối cùng vẫn muốn chỉnh bằng plain text. Tuy vậy, họ cũng không hoàn toàn tích cực với plain text vì các vấn đề như mã hóa ký tự hay ký tự đặc biệt.
  • Tôi đôi khi tự hỏi tại sao artifact đã lưu và phần code projection mà chúng ta thực sự nhìn thấy lại phải giống hệt nhau. Sẽ rất hay nếu ngay cả git diff cũng có thể xem dưới dạng projection của IR (biểu diễn trung gian). Nhờ sự xuất hiện của các công cụ AST như treesitter, người ta bắt đầu hình dung ra các giao diện để con người thao tác AST hay IR hiệu quả hơn. Ví dụ, cấu trúc ordered compilation của f# giúp đơn giản hóa code review. Ngược lại, ở các ngôn ngữ hay cấu trúc cho phép thứ tự tự do, để kiểm tra một diff nhỏ đôi khi phải đi qua rất nhiều chỗ khác nhau để nắm được toàn bộ ngữ cảnh thay đổi, khá phiền phức.
  • Có người chia sẻ sự bất tiện với eslint-config-airbnb. Các vấn đề tiêu biểu là #1271, #1122. Họ đã mất hơn một giờ vật lộn để áp dụng cấu hình Airbnb vào dự án sẵn có, trong khi code vốn đã hoàn toàn ổn; họ cảm thấy những quy tắc không cần thiết đó khiến mọi thứ trở nên kém năng suất. Cuối cùng họ chỉ tắt riêng các quy tắc đó ở local, và từ đó về sau không bao giờ dùng lại trong dự án nữa. Ví dụ này cho thấy các quy tắc lint sai có thể phá hỏng năng suất đến mức nào.