Khi các tác vụ không đồng bộ tạo ra một UX xấu


9

Tôi đang viết một bổ trợ COM đang mở rộng một IDE rất cần nó. Có nhiều tính năng liên quan, nhưng hãy thu hẹp nó xuống còn 2 vì lợi ích của bài đăng này:

  • Có một công cụ Code Explorer hiển thị một treeview cho phép người dùng điều hướng các mô-đun và các thành viên của họ.
  • Có một công cụ kiểm tra mã kiểm tra hiển thị một datagridview cho phép người dùng điều hướng các vấn đề về mã và tự động sửa chúng.

Cả hai công cụ đều có nút "Làm mới" để bắt đầu một tác vụ không đồng bộ, phân tích tất cả mã trong tất cả các dự án đã mở; các luật Explorer sử dụng các kết quả phân tích cú pháp để xây dựng TreeView , và Mã Kiểm tra sử dụng các kết quả phân tích để tìm các vấn đề mã và hiển thị kết quả trong của nó DataGridView .

Những gì tôi đang cố gắng làm ở đây là chia sẻ kết quả phân tích giữa các tính năng, để khi Code Explorer làm mới, thì Kiểm tra mã biết về nó và có thể tự làm mới mà không phải làm lại công việc phân tích cú pháp mà Code Explorer vừa làm .

Vì vậy, những gì tôi đã làm, tôi đã tạo cho lớp trình phân tích cú pháp của mình một nhà cung cấp sự kiện mà các tính năng có thể đăng ký:

    private void _parser_ParseCompleted(object sender, ParseCompletedEventArgs e)
    {
        Control.Invoke((MethodInvoker) delegate
        {
            Control.SolutionTree.Nodes.Clear();
            foreach (var result in e.ParseResults)
            {
                var node = new TreeNode(result.Project.Name);
                node.ImageKey = "Hourglass";
                node.SelectedImageKey = node.ImageKey;

                AddProjectNodes(result, node);
                Control.SolutionTree.Nodes.Add(node);
            }
            Control.EnableRefresh();
        });
    }

    private void _parser_ParseStarted(object sender, ParseStartedEventArgs e)
    {
        Control.Invoke((MethodInvoker) delegate
        {
            Control.EnableRefresh(false);
            Control.SolutionTree.Nodes.Clear();
            foreach (var name in e.ProjectNames)
            {
                var node = new TreeNode(name + " (parsing...)");
                node.ImageKey = "Hourglass";
                node.SelectedImageKey = node.ImageKey;

                Control.SolutionTree.Nodes.Add(node);
            }
        });
    }

Và nó hoạt động. Vấn đề tôi gặp phải, đó là ... nó hoạt động - Ý tôi là, khi kiểm tra mã được làm mới, trình phân tích cú pháp báo cho trình thám hiểm mã (và mọi người khác) "anh bạn, phân tích cú pháp, bất cứ điều gì bạn muốn làm về nó? " - và khi quá trình phân tích cú pháp hoàn tất, trình phân tích cú pháp sẽ cho người nghe biết "các bạn, tôi có kết quả phân tích cú pháp mới cho bạn, bạn muốn làm gì về nó?".

Hãy để tôi dẫn bạn qua một ví dụ để minh họa vấn đề mà điều này tạo ra:

  • Người dùng đưa lên Code Explorer, thông báo cho người dùng "chờ đã, tôi đang làm việc ở đây"; Người dùng tiếp tục làm việc trong IDE, Code Explorer tự vẽ lại, cuộc sống thật đẹp.
  • Sau đó, người dùng sẽ đưa ra Kiểm tra mã, thông báo cho người dùng "chờ đã, tôi đang làm việc ở đây"; trình phân tích cú pháp nói với Code Explorer "anh bạn, đang phân tích cú pháp, bất cứ điều gì bạn muốn làm về nó?" - Code Explorer nói với người dùng "chờ đã, tôi đang làm việc ở đây"; Người dùng vẫn có thể làm việc trong IDE, nhưng không thể điều hướng Code Explorer vì nó rất mới mẻ. Và anh cũng đang chờ kiểm tra mã hoàn thành.
  • Người dùng nhìn thấy một vấn đề mã trong kết quả kiểm tra mà họ muốn giải quyết; họ bấm đúp để điều hướng đến nó, xác nhận có vấn đề với mã và nhấp vào nút "Khắc phục". Mô-đun đã được sửa đổi và cần được phân tích lại, vì vậy việc kiểm tra mã tiến hành với nó; Code Explorer nói với người dùng "chờ đã, tôi đang làm việc ở đây", ...

Thấy nơi đang tới không? Tôi không thích nó và tôi cá là người dùng cũng sẽ không thích nó. Tôi đang thiếu gì? Tôi nên chia sẻ kết quả phân tích cú pháp giữa các tính năng như thế nào, nhưng vẫn để người dùng kiểm soát khi nào tính năng này hoạt động ?

Lý do tôi hỏi, là vì tôi đoán rằng nếu tôi hoãn công việc thực tế cho đến khi người dùng chủ động quyết định làm mới và "lưu trữ" kết quả phân tích khi họ đến ... thì tôi sẽ làm mới một lượt xem và định vị các vấn đề về mã trong kết quả phân tích cú pháp có thể cũ ... điều này thực sự đưa tôi trở lại hình vuông, trong đó mỗi tính năng hoạt động với kết quả phân tích cú pháp riêng: có cách nào tôi có thể chia sẻ kết quả phân tích giữa các tính năng có UX đáng yêu không?

Mã là , nhưng tôi không tìm mã, tôi đang tìm khái niệm .


2
Chỉ cần một FYI, chúng tôi cũng có một trang web UserExperience.SE . Tôi tin rằng đây là chủ đề ở đây vì nó đang thảo luận về thiết kế mã nhiều hơn giao diện người dùng nhưng tôi muốn cho bạn biết trong trường hợp các thay đổi của bạn trôi dạt nhiều hơn về phía UI và không phải là khía cạnh mã / thiết kế của vấn đề.

Khi bạn đang phân tích cú pháp, đây có phải là một hoạt động tất cả hoặc không có gì? Ví dụ: một thay đổi trong tệp có kích hoạt hoàn toàn lặp lại hay chỉ đối với tệp đó và những thay đổi phụ thuộc vào tệp đó?
Morgen

@Morgen có hai điều: VBAParserđược tạo bởi ANTLR và cung cấp cho tôi một cây phân tích cú pháp, nhưng các tính năng không tiêu thụ điều đó. Cây RubberduckParserlấy cây phân tích, đi ngang qua nó và đưa ra một đối tượng VBProjectParseResultchứa Declarationtất cả các đối tượng đã Referencesgiải quyết xong - đó là những gì các tính năng đưa vào đầu vào .. vì vậy, đó là một tình huống hoàn toàn không có gì. Điều RubberduckParsernày đủ thông minh để không phân tích lại các mô-đun chưa được sửa đổi. Nhưng nếu có một nút cổ chai thì đó không phải là phân tích cú pháp, mà là với việc kiểm tra mã.
Mathieu Guindon

4
Tôi nghĩ rằng, tôi sẽ làm như thế này: Khi người dùng kích hoạt làm mới, công cụ đó sẽ kích hoạt phân tích cú pháp và cho thấy rằng nó đang hoạt động. (Các) công cụ khác chưa được thông báo, họ tiếp tục hiển thị thông tin cũ. Cho đến khi trình phân tích cú pháp kết thúc. Tại thời điểm đó, trình phân tích cú pháp sẽ báo hiệu tất cả các luồng công cụ để làm mới chế độ xem của chúng với thông tin mới. Người dùng nên chuyển sang một công cụ khác trong khi trình phân tích cú pháp đang hoạt động, cửa sổ đó cũng sẽ vào trạng thái "làm việc ..." và báo hiệu một tiếng vang. Trình phân tích cú pháp sau đó sẽ bắt đầu lại để cung cấp thông tin cập nhật cho tất cả các cửa sổ cùng một lúc.
cmaster - phục hồi monica

2
@cmaster Tôi cũng sẽ đưa ra nhận xét đó như một câu trả lời.
RubberDuck

Câu trả lời:


7

Cách mà tôi có thể sẽ tiếp cận điều này sẽ là tập trung ít hơn vào việc cung cấp kết quả hoàn hảo, và thay vào đó tập trung vào một cách tiếp cận nỗ lực tốt nhất. Điều này sẽ dẫn đến ít nhất các thay đổi sau:

  • Chuyển đổi logic hiện đang bắt đầu phân tích lại thành yêu cầu thay vì bắt đầu.

    Logic để yêu cầu phân tích lại có thể cuối cùng trông giống như thế này:

    IF parseIsRunning IS false
      startParsingThread()
    ELSE
      SET shouldParse TO true
    END
    

    Điều này sẽ được kết hợp với logic gói trình phân tích cú pháp, có thể trông giống như thế này:

    SET parseIsRunning TO true
    DO 
      SET shouldParse TO false
      doParsing()
    WHILE shouldParse IS true
    SET parseIsRunning TO false
    

    Điều quan trọng là trình phân tích cú pháp chạy cho đến khi yêu cầu phân tích lại gần đây nhất được thực hiện, nhưng không có nhiều hơn một trình phân tích cú pháp đang chạy tại bất kỳ thời điểm nào.

  • Xóa cuộc ParseStartedgọi lại. Yêu cầu phân tích lại bây giờ là một hoạt động cháy và quên.

    Thay phiên, chuyển đổi nó để không làm gì khác hơn là hiển thị một chỉ báo làm mới trong một số phần của GUI không chặn tương tác người dùng.

  • Cố gắng cung cấp xử lý tối thiểu cho kết quả cũ.

    Trong trường hợp của Code Explorer, điều đó có thể đơn giản như tìm kiếm một số dòng lên xuống hợp lý cho một phương thức mà người dùng muốn điều hướng đến hoặc phương thức gần nhất nếu không tìm thấy tên chính xác.

    Tôi không chắc những gì sẽ phù hợp với Thanh tra mã.

Tôi không chắc về các chi tiết triển khai, nhưng nhìn chung, điều này rất giống với cách trình soạn thảo NetBeans xử lý hành vi này. Luôn luôn rất nhanh chóng chỉ ra rằng nó hiện đang làm mới, nhưng cũng không chặn quyền truy cập vào chức năng.

Kết quả cũ thường đủ tốt - đặc biệt là khi so sánh với không có kết quả.


1
Điểm tuyệt vời, nhưng tôi có một câu hỏi: Tôi đang sử dụng ParseStartedđể tắt nút [Làm mới] ( Control.EnableRefresh(false)). Nếu tôi loại bỏ cuộc gọi lại đó và để người dùng nhấp vào nó ... thì tôi sẽ đặt mình vào tình huống tôi có hai nhiệm vụ đồng thời thực hiện phân tích cú pháp ... làm cách nào để tránh điều này mà không vô hiệu hóa làm mới tất cả các chức năng khác trong khi ai đó đang phân tích cú pháp?
Mathieu Guindon

@ Mat'sMug Tôi đã cập nhật câu trả lời của mình để bao gồm khía cạnh của vấn đề.
Morgen

Tôi đồng ý với cách tiếp cận này, ngoại trừ việc tôi vẫn sẽ ParseStartedtổ chức một sự kiện, trong trường hợp bạn muốn cho phép UI (hoặc thành phần khác) đôi khi cảnh báo người dùng đang xảy ra lỗi. Tất nhiên, bạn có thể muốn ghi lại tài liệu người gọi nên cố gắng không ngăn người dùng sử dụng kết quả phân tích cú pháp hiện tại (sắp có).
Đánh dấu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.