1 điểm bởi GN⁺ 1 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • zig fmt có thể được dùng như một trình định dạng có thể điều khiển được: nó phản ánh hình thức cú pháp đã có sẵn trong tệp và có thể sắp cùng một đoạn mã theo nhiều bố cục khác nhau
  • Trong lời gọi hàm, sự có mặt hay không của trailing comma sẽ làm thay đổi kết quả: không có dấu phẩy thì mã được gộp về một dòng, có dấu phẩy thì các đối số được đặt theo từng dòng
  • Quy trình thực tế là trước tiên quyết định cách bố trí mã mong muốn, thêm vài dấu phẩy rồi nhấn phím tắt định dạng để zig fmt xử lý phần còn lại
  • Với mảng, không chỉ trailing comma mà cả vị trí xuống dòng đầu tiên cũng được phản ánh, nên nếu lần xuống dòng đầu tiên nằm sau phần tử thứ ba thì các phần tử sẽ được căn theo nhóm 3
  • Nếu dùng nối mảng ++ một cách cẩn thận, có thể bố trí số phần tử khác nhau trên mỗi dòng; khi truyền cặp --keyvalue vào subprocess, có thể nối mảng đối số cố định với mảng cặp tùy chọn để căn chỉnh

Cách điều khiển zig fmt

  • zig fmt có thể được dùng như một trình định dạng có thể điều khiển vì nó nhìn vào hình thức cú pháp đã có trong tệp hiện tại và có thể sắp cùng một cú pháp theo nhiều cách khác nhau
  • Trong lời gọi hàm, sự có mặt hay không của trailing comma sẽ thay đổi bố cục
    f(1, 2,
          3);
    
    // -> zig fmt ->
    
        f(1, 2, 3);
    
    f(1, 2,
          3,);
    
    // -> zig fmt ->
    
        f(
            1,
            2,
            3,
        );
    
  • Luồng sử dụng thực tế là trước tiên quyết định cách bố trí mã mong muốn, thêm vài dấu ,, rồi nhấn phím tắt định dạng để zig fmt xử lý phần còn lại
  • Thay vì để trình định dạng tự đoán bố cục, có thể phù hợp hơn nếu người dùng tự để lại những lựa chọn cốt lõi
  • Kết luận là 90% của việc định dạng tốt phụ thuộc vào dòng trống giữa các khối logic và việc chọn biến trung gian phù hợp, nên tốt hơn là tận dụng những lựa chọn này thay vì loại bỏ chúng

Bố cục căn cột của mảng

  • Với mảng, không phải chỉ trailing comma mới khiến mỗi phần tử được đặt trên một dòng; vị trí xuống dòng đầu tiên cũng được zig fmt phản ánh
    .{ 1, 2, 3,
          4, 5, 6, 7, 8, 9, 10, 11,  };
    
  • Nếu lần xuống dòng đầu tiên nằm sau phần tử thứ ba, kết quả cũng sẽ được căn theo nhóm 3 phần tử
    .{
            1,  2,  3,
            4,  5,  6,
            7,  8,  9,
            10, 11,
        };
    
  • Nếu dùng nối mảng ++ một cách cẩn thận, có thể bố trí số phần tử khác nhau trên mỗi dòng
  • Khi truyền cặp --keyvalue vào subprocess, có thể nối mảng đối số cố định với mảng cặp tùy chọn để căn chỉnh như sau
    try run(&(.{ "aws", "s3", "sync", path, url } ++ .{
        "--include",            "*.html",
        "--include",            "*.xml",
        "--metadata-directive", "REPLACE",
        "--cache-control",      "max-age=0",
    }));
    

1 bình luận

 
Ý kiến trên Lobste.rs
  • Tôi nhớ là gofmt cũng từng có cách định hướng việc định dạng tương tự, và tôi thích kiểu formatter như vậy hơn rustfmt
    Dù vậy, tôi vẫn nghĩ có bất kỳ hình thức tự động hóa định dạng nào còn hơn là hoàn toàn không có formatter

    • Câu “có gì đó còn hơn là không có formatter” thật khó để bỏ qua
      Trình định dạng tự động ép mọi thứ trở nên tầm thường, kéo những người không biết định dạng lên nhưng cũng kéo cả những người làm tốt xuống
      Khi cộng tác, nếu những người khác muốn dùng nó hoặc khó tin vào quy tắc định dạng cá nhân của họ thì tôi sẽ dùng, nhưng khi làm một mình thì tôi tuyệt đối không dùng
      Tôi có gu của tôi, formatter có gu của formatter, và hai bên khác nhau đến mức không thể hòa giải
      Dù vậy, bản thân những gì bài viết này cho thấy vẫn rất ấn tượng
      Câu đó khiến tôi nhớ đến lời của bà mối Yente trong Fiddler on the Roof: “Một người chồng tệ—cầu Chúa ngăn lại—cũng còn hơn là không có chồng!”
    • Nếu tôi nhớ không nhầm thì formatter của Elm cũng hoạt động tương tự, và tôi thấy nó khá ổn so với những formatter vốn bỏ qua hoàn toàn định dạng gốc
    • Một số dự án C++ dùng clang-format, và nó thật kinh khủng
      Tính ổn định giữa các phiên bản quá thấp, nên việc nâng cấp clang-format dẫn đến một commit định dạng đụng vào mọi dòng mã
      Tôi thực sự không chắc nó có tốt hơn việc không có formatter hay không
    • Trước đây tôi từng nghĩ rất lâu rằng “bất kỳ formatter nào cũng tốt hơn không có”, nhưng gần đây tôi đã đổi ý hoàn toàn
      Trình định dạng tự động chủ yếu giải quyết vấn đề con người bằng cách loại bỏ tranh cãi vặt vãnh trong pull request
      Nhưng giờ khi đang chuyển sang phát triển kiểu agent, vấn đề đó ngày càng bớt quan trọng
      Hiện tại trong nhiều dự án, máy móc làm phần lớn công việc, và trong bối cảnh đó tôi lại thấy có lẽ tốt hơn là đừng chạy formatter
  • Khi tạo đối số dòng lệnh trong Python, tôi thích cách splat tuple vào list, nên có lẽ tôi sẽ viết ví dụ cuối bài như thế này

    [  
      "aws",  
      "s3",  
      "sync",  
      path,  
      url,  
            *("--include", "*.html"),  
      *("--include", "*.xml"),  
      *("--metadata-directive", "REPLACE"),  
      *("--cache-control", "max-age=0"),  
    ]  
    
  • Lần cuối tôi xem thì zig fmt không có cách cấu hình để dùng giới hạn 80 cột thay vì 100 cột, giờ vẫn thế à?
    Khi làm việc nhiều giờ mỗi ngày, tôi tăng cỡ chữ terminal để mắt đỡ mỏi hơn, và khác biệt giữa 80 cột với 100 cột quyết định việc có thể đặt cạnh nhau hai cửa sổ tách của vim cùng nerd tree hay không

    • zig fmt không có giới hạn số cột
  • Là người từng đưa rigid formatter vào một đội trước đó hoàn toàn không có formatter, đôi khi tôi nhớ khả năng có thể tác động đến định dạng bằng tay
    Về mặt đó, việc Zig linh hoạt thật sự rất tuyệt

  • Tuyệt vời!
    formatter TS/JS nào kiểu này không?
    Tôi có một dự án dùng maplibre-gl, mà các biểu thức trong đặc tả style đôi khi bị định dạng quá đà đến mức chẳng còn nhìn ra gì
    Hiện giờ tôi đã ngừng dùng formatter, nhưng vì phải debug, sao chép và comment code nên mã đang ngày càng bừa bộn
    Biết đâu có thể làm cho formatter của Zig định dạng cả ngôn ngữ khác nữa :)

    • Prettier cũng có tính năng tương tự, nhưng cụ thể chỉ giới hạn ở object literal và chỉ có thể chọn giữa “tất cả trên một dòng” với “mỗi phần tử một dòng”
      Ví dụ, không có cách nào bảo formatter hãy đặt bốn phần tử trên mỗi dòng
      Ngoài ra, nếu object literal quá dài thì dù văn bản đầu vào thế nào, Prettier cuối cùng vẫn sẽ đổi sang kiểu “mỗi phần tử một dòng”
  • Tôi từng thất vọng với các formatter kiểu nhẹ nhàng, về cơ bản chỉ là formatter xuống dòng, nên tôi thấy vừa ghen tị vừa thích ý tưởng có được sự linh hoạt này bên trong một khuôn mẫu chặt chẽ hơn :p
    Gần đây khi trả lời một câu hỏi trên fedi về việc viết và format Lisp bằng font tỷ lệ, tôi đã nhắc đến các biến thể s-expression dùng khoảng trắng có ý nghĩa như wisp, Readable/Sweet expressions, SRFI 119 và 110
    Tôi cũng bổ sung nhận xét rằng họ cú pháp này phần nào trao lại quyền kiểm soát việc xuống dòng bằng cách tận dụng phần mở rộng ký pháp trung tố tùy chọn

  • Thiết kế này thú vị, nhưng tôi không chắc mình có thích nó hay không
    Trong formatter của tôi, tôi làm khác: formatter sẽ bỏ qua dấu phẩy cuối khi quyết định định dạng, rồi nếu đã tách thành nhiều dòng thì luôn thêm dấu phẩy cuối, còn nếu chỉ một dòng thì luôn bỏ dấu phẩy cuối
    Vì vậy nó không thể “định hướng”, nhưng f(1, 2, 3) sẽ luôn được định dạng nhất quán bất kể có dấu phẩy cuối hay không, hay khoảng trắng giữa các token nhiều ít và kiểu gì
    Vẫn cần một mức độ định hướng nào đó
    Chẳng hạn, nếu có một list literal dài [<expr1>, <expr2>, ..., <expr100>] thì hầu hết formatter sẽ để mỗi biểu thức trên một dòng, nhưng đôi khi bạn lại muốn nhét được càng nhiều càng tốt trên một dòng
    Tôi thấy việc dùng dấu phẩy cuối để quyết định giữa hai kiểu đó là kỳ quặc, và nói chung số lựa chọn có thể là N chứ không chỉ 2
    Tôi nghĩ attribute sẽ phù hợp hơn cho mục đích này
    Ví dụ, có lẽ đã tồn tại rồi, nhưng có thể gắn thứ gì đó như #[rustfmt::list_layout(flow)] trước câu lệnh để ảnh hưởng đến cách định dạng list literal bên trong câu lệnh đó
    Nếu có quá nhiều kiểu định hướng thì sẽ làm tổn hại mục tiêu của formatter là tạo định dạng mã nhất quán trong toàn bộ hệ sinh thái và giúp review code dễ hơn, nên chỉ nên có trong những trường hợp giới hạn
    Tôi cho rằng list literal dài là một ví dụ thật sự cần thiết
    Trong dự án của tôi cũng có ví dụ mà định dạng giúp việc review giá trị kỳ vọng trong test dễ hơn, như ở đây
    Tôi cũng nghĩ ra một hành vi “định hướng” khác trong formatter của Dart: trong list literal dài, có thể thêm các dòng comment để nhóm các dòng lại với nhau
    Ví dụ, nếu có [1, 2, 3, ..., 1000] thì mỗi phần tử sẽ nằm trên một dòng, nhưng có thể nhóm thủ công như sau

    [1, 2, 3, 4, 5,  //  
     6, 7, 8, 9, 10, //  
     ...]  
    

    Tôi không rõ đây có phải tính năng được đưa vào có chủ đích không, hay chỉ là hệ quả phụ từ cách nó xử lý comment

    • Đó chính xác là cách rustfmt hoạt động, và nó làm tôi phát điên
      Đôi khi giữ nguyên một lời gọi hàm không bị tách ra còn dễ đọc hơn, ngay cả khi nó vượt giới hạn độ dài dòng, và sẽ thật tốt nếu tôi có thể phản ánh phán đoán đó của mình
      Một ví dụ tôi nghĩ đến là OpenGL
      Thông thường khi sửa đổi hoặc dùng một tài nguyên, chẳng hạn lúc khởi tạo texture, bạn sẽ có nhiều lời gọi gl.* liên tiếp, nhưng rustfmt cứ đẩy chúng đi mà không có chút cảm quan nào ngoài mục tiêu máy móc là “dòng quá dài, phải bẻ ra”
      Ví dụ này là ví dụ nhân tạo để minh họa hành vi, và không hoàn toàn giống hệt hành vi thực tế của rustfmt
      Dòng cũng không dài đến thế
      Tôi đang gõ trên điện thoại nên không có công cụ để làm ví dụ chính xác 100%
      gl.bind_texture(gl::TEXTURE_2D, tex);  
      gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST);  
      gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
      
      // -->
      
      gl.bind_texture(gl::TEXTURE_2D, tex);  
      gl.tex_parameteri(  
          gl::TEXTURE_2D,  
          gl::TEXTURE_MIN_FILTER,  
          gl::NEAREST,  
      );  
      gl.tex_parameteri(  
          gl::TEXTURE_2D,  
          gl::TEXTURE_MAG_FILTER,  
          gl::NEAREST,  
      );  
      
      Việc bẻ các lời gọi gl.tex_parameteri liên tiếp như thế thành nhiều dòng thực ra lại tệ hơn so với để nguyên mỗi lời gọi trên một dòng
      Vì khi các cột được căn hàng, việc tìm ra khác biệt giữa hai dòng sẽ dễ hơn nhiều
      Bản bị bẻ dòng làm mất sự gần gũi về mặt thị giác và khó đọc hơn
      Bạn không còn dễ dàng so sánh hai dòng bằng mắt nữa
      Cũng có những trường hợp buồn cười khi nó thất bại hoàn toàn vì không thể format để khớp với giới hạn số ký tự trên dòng
      Điều này hay xảy ra khi viết mã compiler và tạo thông báo chẩn đoán bằng string literal, vì thông báo có thể khá dài
      rustfmt không biết phải tách nó thế nào nên bỏ cuộc và không định dạng toàn bộ câu lệnh đó
      Thường là kiểu như sau
      match something {  
          // ... match arms above this one ...  
          _ => emit_diagnostic(&mut state, "This is a very long message to try and illustrate the problem. Help: please consult a doctor.")  
      }  
      
      Chỉ vì lời gọi emit_diagnostic là một biểu thức mà nó từ bỏ việc định dạng toàn bộ câu lệnh match, điều đó thật ngu ngốc
      Mọi chuyện đã có thể tránh được nếu nó không cố ép mã của tôi vào tối đa 100 cột
  • Viết thêm cho những ai như tôi đã phải đi tra ở phần cuối: ++toán tử nối mảng
    Vì vậy có thể tách một mảng thành hai phần để định dạng chúng khác nhau