2 điểm bởi GN⁺ 2025-06-05 | 1 bình luận | Chia sẻ qua WhatsApp
  • Định dạng Unified Diff hiện có còn tồn tại những giới hạn chưa phản ánh đầy đủ các yêu cầu của môi trường phát triển
  • DiffX tương thích hoàn toàn với định dạng hiện có, đồng thời cung cấp cấu trúc hướng tới tương lai và khả năng mở rộng metadata
  • Có thể lưu trữ nhiều thông tin commit, tệp nhị phân, mã hóa ký tự và metadata theo cách có cấu trúc
  • Việc áp dụng quy tắc phân tích cú pháp được chuẩn hóa giúp nhiều công cụ khác nhau (patch, code review, v.v.) dễ dàng tích hợp
  • Có thể sử dụng bình thường trong các công cụ và workflow hiện có, và chỉ các tính năng mới mới cần công cụ hỗ trợ

Nhà phát triển và tệp Diff

  • Các nhà phát triển phần mềm thường kiểm tra nội dung thay đổi mã bằng tệp diff trong Git, Subversion, CVS
  • Tệp diff có cấu trúc bao gồm chèn văn bản (+)·xóa (-) cùng với tên tệp, đường dẫn, dấu thời gian và một phần metadata
  • Hầu hết công cụ và người dùng sử dụng định dạng Unified Diff, và cách này trực quan hóa sự khác biệt theo cách tương đối đơn giản

Giới hạn của Unified Diff

  • Unified Diff chỉ chuẩn hóa việc nhận diện tệp, phạm vi thay đổi và các dòng được chèn/xóa, còn mã hóa, revision, metadata mở rộng thì không được chuẩn hóa
  • Khó hỗ trợ nhiều hệ thống quản lý mã nguồn, phân tích cú pháp đáng tin cậy và trích xuất thông tin phong phú
  • Những vấn đề sau vẫn liên tục xảy ra
    • Không thể biểu diễn nhiều commit cùng lúc
    • Thiếu định dạng chuẩn chuyên dụng cho tệp nhị phân
    • Không thể biết mã hóa ký tự, gây mất thông tin và nhầm lẫn
    • Việc chuẩn hóa metadata tùy ý còn thiếu nên hình thức khác nhau tùy theo từng công cụ

Hướng cải thiện

  • Unified Diff hiện có thiếu cấu trúc và tiêu chuẩn, nhưng lại linh hoạt và đã được sử dụng rộng rãi trong nhiều môi trường
  • Git Diff hiện đang đóng vai trò tiêu chuẩn trên thực tế, nhưng vẫn thiếu đặc tả chính thức của định dạng và khả năng mở rộng phổ dụng
  • Nhu cầu về một định dạng mới kết hợp ưu điểm của Unified Diff hiện tại với khả năng mở rộng và cấu trúc chuẩn hóa ngày càng tăng

DiffX là gì

  • DiffX là một định dạng Diff có khả năng mở rộng, tương thích hoàn toàn với các công cụ hiện có, vẫn giữ được khả năng đọc hiểu cho con người, đồng thời có thể chứa metadata và cấu hình theo cách có cấu trúc
  • Ví dụ cú pháp:
    • Lưu trữ metadatanội dung chính cho tệp, commit, toàn bộ diff, v.v. bằng cách sử dụng cấu trúc và cơ chế mở rộng
    • Trong đầu ra ví dụ có các cú pháp như #diffx: cùng section, metadata (JSON), đường dẫn tệp, thông tin commit, v.v.

Những điểm mạnh chính của DiffX

  • Cung cấp quy tắc phân tích cú pháp được chuẩn hóa, giúp công cụ có thể đọc và ghi thông tin một cách đáng tin cậy
  • Chính thức hóa việc lưu trữ và quản lý metadata: có thể dùng ở cấp toàn bộ diff, commit và từng tệp
  • Tương thích với mọi công cụ như parser hiện có, patcher, code review, v.v. (các tính năng mới cần được hỗ trợ, nhưng các tính năng cũ vẫn được đảm bảo tương thích)
  • Có thể biểu diễn hiệu quả trong một tệp các nội dung như nhiều commit, binary diff, thông tin mã hóa văn bản
  • Hỗ trợ khả biến (mutability), cho phép công cụ mở diff, ghi lại và chỉnh sửa thông tin cần thiết rồi lưu lại

Định hướng và những điều DiffX không hướng tới

  • Không ép buộc mọi công cụ phải hỗ trợ định dạng này, cũng không tạo ra vấn đề tương thích
  • Không gây ra vendor lock-in, và không phá vỡ workflow hiện có
  • Giải quyết các vấn đề của tệp Diff hiện tại, đồng thời mang lại trải nghiệm sử dụng nhất quán và đáng tin cậy trong các công cụ phát triển, review và phân tích

1 bình luận

 
GN⁺ 2025-06-05
Ý kiến Hacker News
  • Tôi không thích các định dạng phức tạp theo kiểu phân cấp như ..meta...meta. Để diễn đạt rõ ràng hơn, tôi nghĩ chỉ nên phân biệt bằng ba cấp: toàn bộ diff, tệp và chunk, rồi đặt tên khác nhau cho từng cấp thì định dạng sẽ dễ hiểu hơn. Ngay cả khi không có meta block, vẫn có thể nhận ra đối tượng chỉ trong một cái nhìn, nhờ đó cũng giảm sai sót hoặc lỗi. Việc metadata của toàn bộ diff và metadata ở đơn vị tệp lại dùng cùng một trường cũng là điều không hợp lý. Ngoài ra tôi không hiểu vì sao cần hai định dạng là JSON và key=value; nếu số thứ phải quản lý không nhiều thì chỉ dùng một định dạng sẽ có lợi hơn rất nhiều cho việc triển khai hoặc tích hợp với công cụ hiện có (grep, sed hoặc jq, chỉ cần một trong số đó là đủ). Thêm nữa, sẽ tốt hơn nếu danh sách cho phép trailing comma, và tôi cũng tò mò định dạng này ảnh hưởng thế nào đến đặc tính vốn có của diff là có thể áp dụng từng phần (ví dụ nếu chỉ muốn áp dụng một phần của diff thì phải sao chép preamble rồi lại sao chép riêng cả block nên tôi thấy khá bất tiện). Tôi cũng thắc mắc revision là thuộc tính của tệp hay là checksum của commit.

    • Chúng tôi đã thử nhiều cách tiếp cận khác nhau về cấu trúc, và cuối cùng đã chốt theo dạng #<section_level><section_type> để giữ sự đơn giản từ góc độ parsing. Với mỗi meta block, chỉ cần kiểm tra cấp độ theo chiều dọc, và chỉ cần đếm số dấu chấm là có thể tự nhiên phân biệt metadata thuộc cấp nào. Định dạng header key/value được dùng để chứa những thuộc tính đơn giản mà parser biết trước, còn metadata tự do thì được thiết kế để đưa vào một block meta riêng. Không chỉ JSON hiện tại, mà cả khi sau này cần những cách tuần tự hóa khác, chúng tôi cũng có thể ghi rõ định dạng trong header để giữ khả năng mở rộng. Đây là kết quả của nỗ lực cân bằng giữa sự đơn giản và tính linh hoạt. Cá nhân tôi cũng muốn thêm trailing comma, nhưng do vấn đề tương thích với JSON cơ sở nên khó có thể bắt buộc dùng parser JSON5. Diff vẫn có thể được chia nhỏ, và nhờ đưa thông tin vào vùng mà Unified Diff bỏ qua nên trong GNU patch v.v. nó sẽ bị bỏ qua và không gây vấn đề. Tuy vậy, nếu tách thành hai tệp DiffX thì phải thêm lại header nên có thể hơi phức tạp. Diff của một số SCM dù có thể chia nhỏ thì vẫn có thể làm mất một phần metadata (ví dụ thông tin parent commit), hoặc tùy đối tượng áp dụng mà có thể bị thất thoát thông tin. Revision thì khác nhau theo từng SCM; có nơi là commit ID, có nơi là ID theo từng tệp hoặc kết hợp của chúng, hoặc thêm các thông tin khác nữa. Cấu trúc này được cân nhắc để đáp ứng các nhu cầu đa dạng của từng SCM.
  • Theo tôi, trong bốn điểm chỉ ra dưới đây thì thứ duy nhất thực sự hợp lý khi khái quát hóa tệp diff là cách biểu diễn binary patch. Những điểm còn lại là vấn đề dữ liệu hoặc giao thức nội bộ của từng hệ thống quản lý phiên bản (SCM), nên chỉ có ý nghĩa trong client, server hoặc hệ thống sao lưu riêng của họ. Tất cả những thứ khác đều có vẻ không cần thiết.

    • Không thể liệt kê nhiều commit trong một diff

    • Không có tiêu chuẩn cho binary patch

    • Không nhận biết được text encoding (đây âm thầm là một vấn đề)

    • Không có định dạng tiêu chuẩn cho metadata tùy ý

    • Chúng tôi đã phát triển một sản phẩm code review tích hợp hơn 12 SCM trong suốt 20 năm, và đã gặp vô số vấn đề về định dạng diff cũng như vấn đề riêng của từng SCM mà người ngoài khó tưởng tượng được. Quả thật đây không phải là thứ end user cần trực tiếp bận tâm, nhưng từ phía phát triển công cụ thì đó là những pain point bắt buộc phải giải quyết. Một số SCM không có định dạng diff riêng, hoặc thiếu quá nhiều thông tin (ví dụ không thể biểu thị tệp đã xóa), khiến các công cụ khác không thể nhận diện đúng tệp, nên chúng tôi cảm thấy cần những cải tiến như vậy.

    • Bây giờ thì ít phổ biến hơn, nhưng tôi cũng vẫn thỉnh thoảng dùng các công cụ giống patch(1). Khi các lập trình viên trên nhiều nền tảng cùng cộng tác thì các vấn đề như phân biệt chữ hoa/chữ thường trong tên tệp, text encoding v.v. vẫn còn phát sinh khá nhiều.

  • Nếu nhúng JSON theo kiểu định dạng tự phân tách kèm thông tin độ dài, chỉ cần thay đổi một dấu cách trong nội dung JSON thôi thì JSON vẫn hợp lệ nhưng toàn bộ DiffX có nguy cơ bị hỏng. Đây là một sự kết hợp tạo cảm giác cồng kềnh và lộn xộn về mặt cấu trúc (trộn header riêng với JSON payload, không đếm số dấu chấm thì không phân biệt được các meta block khác nhau, phải cần hai parser, v.v.). Ý tưởng tiêu chuẩn hóa một diff mở rộng cho metadata là tốt, nhưng cách hiện thực lần này trông giống như một bước thử-sai.

  • Tôi nghĩ định dạng patch đã giải quyết hết những vấn đề này rồi: liên kết giải thích git format-patch

    • Hôm nay là lần đầu tôi biết có một định dạng như vậy, nên đã tham khảo thử (tôi chỉ là một người dùng Internet bình thường, không phải tác giả).

    • Trong git thì có thể giải quyết được, nhưng với sản phẩm như Review Board thì phải tích hợp với nhiều VCS như SVN, CVS, Perforce, nên có vẻ đó là bối cảnh khiến định dạng này xuất hiện. Tôi cũng từng dùng Review Board với SVN, và khi nhiều lập trình viên trộn lẫn git-svn với svn thì việc tải diff lên để review thường gặp vấn đề. Nếu có một định dạng diff tiêu chuẩn mà cả hai bên đều hỗ trợ thì hẳn sẽ giúp dùng công cụ thuận tiện hơn nhiều.

  • Cá nhân tôi không thực sự cảm nhận được rằng những vấn đề được nêu ra thực sự tồn tại (trừ tệp nhị phân).

    • Dù encoding khác nhau thì thuật toán patch vẫn giống nhau nên không cần bận tâm (ký tự cũng không nhất thiết phải là utf-8 hợp lệ).

    • Cũng chẳng có lý do gì muốn nhét nhiều commit vào một diff; tách thành nhiều diff sẽ trực quan hơn.

    • Tôi nghĩ metadata chỉ có hiệu lực ở bên trong hệ thống thôi chăng.

    • Về encoding thì patch data dù sao cũng phải được xử lý như dữ liệu nhị phân dựa trên ASCII. Vì ngay cả với mixed encoding người ta vẫn có thể sửa tệp, nên việc cố định encoding cũng không có nhiều ý nghĩa.

    • Tôi không nghĩ đây hoàn toàn là vấn đề; có lẽ tốt hơn là nghe trải nghiệm thực tế từ những người thật sự dùng diff nhiều, thay vì cố overengineering một định dạng vốn đang hoạt động ổn.

    • Tôi nghĩ vấn đề dữ liệu nhị phân thì chắc chắn là có thật.

    • Thông thường chỉ khi trực tiếp làm công cụ hoặc phải giao tiếp với một SCM cụ thể thì bạn mới gặp những vấn đề này.

      1. Vấn đề encoding tồn tại ở cả tên tệp lẫn phần nội dung. Git có để ý tới encoding tên tệp, nhưng phần lớn SCM thì không, nên có trường hợp diff được tự động tạo ở một môi trường lại không tìm được tên tệp ở môi trường khác (tôi đã thấy điều này ở Perforce, Subversion, v.v.). Phần nội dung cũng có thể bị hỏng tùy theo encoding cục bộ của từng SCM. Khi qua lại giữa môi trường Windows và Linux, tôi cũng từng gặp chuyện ký tự xuống dòng bị trộn lẫn khiến patch không áp dụng được, hoặc GNU patch bị lỗi do BOM.
      2. Khi xử lý hoặc chuyển cho công cụ nhiều commit cùng lúc, có thể phát sinh đủ loại vấn đề như thiếu tệp hoặc thiếu tính nhất quán, và việc sanity check từng diff một thì khá phiền. Mỗi công cụ lại hỗ trợ định dạng khác nhau nên càng bất tiện.
      3. Để tìm tệp trong kho lưu trữ, mỗi hệ thống lại cần các kiểu định danh khác nhau như cấp commit, cấp tệp, tổ hợp của chúng, hoặc thông tin quan hệ. Những dữ liệu không nằm trong Unified diff như symbolic link, file mode hay thông tin đặc thù của SCM cũng là bắt buộc.
  • Toàn bộ tài liệu khá khó đọc. Với tôi, “diff” nghĩa là sự khác biệt giữa hai đối tượng (tệp, thư mục, v.v.), nhưng diff trong TFA thực ra là thứ tôi vẫn gọi là “patch”. Điều đang được bàn ở đây không phải diff mà là quản lý metadata của patch. Nếu metadata được tiêu chuẩn hóa dưới một định dạng bắt buộc như JSON thì cũng ổn, nhưng ở đây nó lại là một cấu trúc self-describing, length-delimited hơi mơ hồ, tạo cảm giác như đang che giấu vấn đề. Bản thân việc tiêu chuẩn hóa là tốt, nhưng tôi thấy cần một giải pháp được sắp xếp rõ ràng hơn. Cũng thú vị khi tôi cảm thấy kiểu git diff trên thực tế còn gần với tiêu chuẩn de facto hơn.

    • Tôi hoàn toàn đồng ý với câu cuối cùng, cứ chia ra thành nhiều diff là được.
  • Tôi tò mò không rõ định dạng này đang cố giải quyết vấn đề gì. Người ta nói định dạng patch/diff hiện tại chưa đủ tốt, nhưng cần nói rõ hơn là đang cải thiện cho ai, có phải cộng đồng GNU Patch đang ngày càng bất mãn hay không, hay cụ thể lý do là gì. Tôi vẫn thấy còn thắc mắc vì sao lại nhất thiết cần một định dạng patch tốt hơn.

    • Tôi có một bài viết riêng quá dài nên không thể dán hết vào đây; tóm lại đây là mối bận tâm dành cho những người trực tiếp xây SCM hoặc phát triển công cụ tích hợp với SCM. Người dùng thông thường không cần phải quan tâm. Định dạng diff cũng khác nhau giữa các SCM; có loại được thiết kế rất tốt, nhưng cũng có loại thiếu sót nghiêm trọng hoặc thậm chí không có định dạng nào cả. Từ góc độ của một sản phẩm như Review Board, vốn phải bao phủ nhiều SCM, thì một tiêu chuẩn tích hợp như thế này thực sự cần thiết trong thực tế. Đây là một nỗ lực cải tiến có phản ánh phản hồi khi hợp tác với các vendor SCM.

    • Có vẻ đây là định dạng chủ yếu dùng xoay quanh Review Board; sản phẩm này hỗ trợ nhiều VCS khác nhau và diff là phần cốt lõi của quá trình source review, nên có lẽ vì vậy họ đưa nó vào.

  • Cách biểu diễn diff phổ biến và rõ ràng nhất là cứ đính kèm thẳng hai tệp. Bây giờ dung lượng dữ liệu không còn là vấn đề, nên thay vì diff a b | patch c có thể làm kiểu apply a b c; khi đó cách biểu diễn bên trong là gì cũng không quan trọng. Diff vốn khó đọc với con người, còn dạng hiển thị có màu, đặt cạnh nhau thì tốt hơn nhiều, nên ngay từ đầu cứ nhận cả hai tệp để xử lý sẽ trực quan hơn. Tôi không nghĩ cần phải truyền một loại diff chưa được chuẩn hóa.

    • Diff tạo từ hai tệp không chỉ có một kiểu duy nhất; tùy mục đích có thể có nhiều biến thể khác nhau. Nếu có một định dạng diff, ta có thể tách riêng logic tạo diff và logic áp dụng diff, từ đó giảm bài toán n*m xuống n+m.

    • Trong các tình huống như cập nhật chương trình, việc mỗi lần đều phải tải lại toàn bộ 130GB là rất khó chịu, nhưng giữa các tệp gần như giống nhau thì cũng rất dễ nén, nên cách chỉ truyền phần chênh lệch giữa hai phiên bản vẫn mang lại lợi ích thực tế lớn. Thậm chí còn có thể có những cách hiệu quả hơn, như chỉ gửi hash của tệp gốc rồi truyền phần thân tệp đã nén.

    • Việc truyền và quản lý hai cặp tệp là điều khó khăn nếu không có một container chuyên dụng, và nếu phải gửi qua email nhiều thay đổi cùng lúc (sửa 10 tệp + xóa + thêm, v.v.) thì lại giống như quay lùi về thời tiền VCS, khi phải dùng những cấu trúc phức tạp như tar/zip.

    • Dù việc gửi toàn bộ tệp thay cho diff có vẻ trực quan hơn, nhưng tùy mục đích và môi trường thực tế mà diff vẫn mang ý nghĩa rất lớn. Gần đây khi dùng LLM để tạo ra kết quả như chỉnh sửa mã nguồn, nếu yêu cầu bằng diff thì tiết kiệm được rất nhiều token và giảm độ trễ phản hồi tới 5-10 lần, tức là cải thiện hiệu quả cực kỳ rõ rệt. Gửi cả hai tệp là lãng phí token và tốn chi phí. Nếu muốn áp dụng nhanh lên code sandbox hoặc máy từ xa thì diff có lợi thế tối ưu hóa rất lớn. Nếu có tệp A, A2 và diff AxA2 thì việc tái dựng A2 cũng dễ, đồng thời còn có ích cho tối ưu lưu trữ. Nếu khi merge có xung đột thì lúc đó mới cần can thiệp trực tiếp. Tóm lại, diff rất tuyệt.

  • Tôi vẫn còn không hài lòng với việc công cụ diff phụ thuộc quá nhiều vào đơn vị xuống dòng. Khi một dòng quá dài (ví dụ JSON, mảng dài, v.v.) thì việc review rất khó khăn.

    • Tôi cũng đồng ý. Tôi nghĩ vẫn còn nhiều chỗ để khám phá những cách biểu diễn diff tốt hơn cho dữ liệu có cấu trúc (ví dụ AST diff, v.v.). Định dạng này (DiffX) tập trung vào việc là một phần mở rộng của định dạng Unified Diff hiện có, và nếu sau này những định dạng cụ thể hơn như AST được dùng rộng rãi thì nó cũng được thiết kế để có thể dễ dàng nhúng vào và hỗ trợ.

    • Dạng thường dùng hiện nay là một kiểu thỏa hiệp nửa vời giữa khả năng con người đọc được và công cụ dễ parse, nên cấu trúc có phần lưng chừng. Lần này có vẻ họ muốn giải quyết một phần vấn đề bằng cách mở rộng metadata, nhưng có lẽ một giải pháp thật sự tốt sẽ là xác định một định dạng mới không phải plain text nhưng vừa dễ đọc vừa dễ parse. Việc tạo ra một thuật toán diff tốt hơn cho các dòng dài hoặc dữ liệu có cấu trúc là một bài toán khó, nhưng tôi nghĩ hoàn toàn có thể giải quyết được.

    • git cũng hỗ trợ word diff chi tiết hơn line diff, và bộ phân tách mặc định là khoảng trắng.

  • Tôi thấy nghi ngờ việc JSON là định dạng metadata duy nhất được hỗ trợ. Với một tiêu chuẩn metadata được thiết kế cho mục đích phổ quát, JSON lại có vẻ quá phức tạp.

    • Tôi muốn nghe giải thích cụ thể hơn vì sao bạn nghĩ JSON lại phức tạp quá mức.