非同期タスクが悪いUXを作るとき


9

私は、必死にそれを必要とするIDEを拡張するCOMアドインを書いています。関係する機能はたくさんありますが、この投稿のために2つに絞ってみましょう。

  • ユーザーがモジュールとそのメンバーをナビゲートできるツリービューを表示するコードエクスプローラーツールウィンドウがあります。
  • ユーザーがコードの問題をナビゲートして自動的に修正できるようにするdatagridviewを表示するコードインスペクションツールウィンドウがあります。

どちらのツールにも、開いているすべてのプロジェクトのすべてのコードを解析する非同期タスクを開始する「更新」ボタンがあります。コードエクスプローラは、構築するために、解析結果を使用してツリービューをし、コード検査は、コードの問題を発見し、その中で結果を表示する解析結果を使用するDataGridView

ここで私がやろうとしているのは、機能間で解析結果を共有することです。これにより、コードエクスプローラーが更新されたときに、コードインスペクションがそれを認識し、コードエクスプローラーが行った解析作業をやり直すことなく更新できるようになります。。

それで私がやったことは、パーサークラスを、機能が登録できるイベントプロバイダーにしたことです。

    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);
            }
        });
    }

そしてそれは機能します。私が抱えている問題は、それは...それが機能することです-つまり、コード検査が更新されると、パーサーはコードエクスプローラー(および他のすべての人)に、 」-そして解析が完了すると、パーサーはそのリスナーに「みんな、私には新鮮な解析結果があります。それについて何をしたいですか?」と伝えます。

これが作成する問題を説明するために例を紹介します。

  • ユーザーはコードエクスプローラーを起動し、「ここで作業します」とユーザーに通知します。ユーザーがIDEで作業を続けると、コードエクスプローラーが再描画され、人生は美しいです。
  • 次にユーザーはコード検査を表示し、「ここで私はここで作業している」とユーザーに伝えます。パーサーはコードエクスプローラーに「おい、誰かの解析、それについて何をしたいのか?」と伝えます。-コードエクスプローラーは、ユーザーに「お待ちください、私はここで働いています」と伝えます。ユーザーは引き続きIDEで作業できますが、コードエクスプローラーは更新されているため、ナビゲートできません。また、コード検査が完了するのを待っています。
  • 対処したい検査結果にコードの問題が表示されます。ダブルクリックして移動し、コードに問題があることを確認して、[修正]ボタンをクリックします。モジュールが変更され、再解析する必要があるため、コードインスペクションはそれを続行します。コードエクスプローラーは、ユーザーに「お待ちください、私はここで働いています」と伝えます。

これがどこに向かっているのかわかりますか?気に入らないし、ユーザーも気に入らないだろう。何が欠けていますか?機能間で解析結果を共有する方法はありますか?それでも、機能がいつ機能するかをユーザーが制御できるようにしますか?

私が質問している理由は、ユーザーが積極的に更新することを決定するまで実際の作業を延期し、解析結果が入ってくるときに解析結果を「キャッシュ」すると考えたためです...まあ、私はツリービューを更新し、文字通り、それぞれの特徴は、独自の解析結果と連携振り出しに私を思い出させる可能性が陳腐化し、解析結果...で、コードの問題を見つける:私は特徴間の解析結果を共有することができますどのような方法がある素敵なUXを持っていますか?

コードはですが、コードを探しているのではなく、概念を探しています


2
参考までに、UserExperience.SEサイトもあります。私はそれが議論されているので、これはここにトピックであると考えているコードのデザインをより多くのユーザーインターフェースが、私は、あなたの変更がより多くのUI側ではなく、問題のコード/設計側に向かってドリフトする場合に知ってもらいたいです。

解析しているとき、これは全か無かの操作ですか?たとえば、ファイルの変更によって完全な再解析がトリガーされますか、それともそのファイルとそれに依存するファイルのみが対象になりますか?
モーゲン

@Morgen VBAParserは2つあります。ANTLR によって生成され、解析ツリーを提供しますが、機能はそれを消費しません。RubberduckParser、解析木を受け取り、それを歩くと、問題VBProjectParseResult含まれDeclarationていたオブジェクトを、すべての彼らのReferences解決- のこと機能は、入力のために取る何が...そうええ、それはほとんどすべてか無かの状況です。RubberduckParserかかわらず変更されていないいない再解析モジュールにスマート十分です。しかし、ボトルネックがある場合、それは解析ではなく、コード検査にあります。
Mathieu Guindon

4
ユーザーが更新をトリガーすると、そのツールウィンドウが解析をトリガーし、機能していることを示します。他のツールウィンドウはまだ通知されず、古い情報が表示され続けます。パーサーが終了するまで。その時点で、パーサーはすべてのツールウィンドウにシグナルを送り、新しい情報でビューを更新します。パーサーが動作しているときにユーザーが別のツールウィンドウに移動すると、そのウィンドウも「working ...」状態になり、再解析を通知します。次に、パーサーは最初からやり直して、最新の情報をすべてのウィンドウに同時に配信します。
cmaster-2015年

2
@cmaster私もそのコメントを回答として賛成します。
RubberDuck、2015年

回答:


7

私がこれにおそらく取り組む方法は、完璧な結果を提供することに焦点を合わせるのではなく、代わりにベストエフォートのアプローチに焦点を当てることです。これにより、少なくとも次の変更が行われます。

  • 現在再解析を開始しているロジックを、開始ではなく要求に変換します。

    再解析を要求するためのロジックは、次のようになる可能性があります。

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

    これは、次のようなパーサーをラップするロジックとペアになります。

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

    重要なことは、最新の再解析要求が受け入れられるまでパーサーは実行されますが、一度に実行されるパーサーは1つだけであることです。

  • ParseStartedコールバックを削除します。再解析の要求は、Fire and Forget操作になりました。

    または、ユーザーの操作をブロックしないGUIの邪魔になる部分に、更新インジケーターを表示する以外に何もしないように変換します。

  • 古い結果の処理を最小限に抑えるようにしてください。

    コードエクスプローラーの場合は、ユーザーが移動したいメソッド、または正確な名前が見つからなかった場合は最も近いメソッドについて、妥当な数の行を上下に検索するのと同じくらい簡単です。

    コードインスペクタに何が適切かはわかりません。

実装の詳細はわかりませんが、全体として、これはNetBeansエディタがこの動作を処理する方法と非常に似ています。現在更新中ですが、機能へのアクセスがブロックされることはありません。

多くの場合、古い結果で十分です-特に結果がない場合と比較すると。


1
すばらしい点ですが、質問がありParseStartedますControl.EnableRefresh(false)。[更新]ボタン()を無効にするために使用しています。そのコールバックを削除してユーザーがクリックできるようにすると、解析を実行する2つのタスクが同時に発生する状況に陥ります...他のすべての機能の更新を無効にせずに誰かがいる間にこれを回避するには解析していますか?
Mathieu Guindon

@ Mat'sMug問題のその側面を含めるために私の回答を更新しました。
モーゲン

私はこのアプローチに同意しParseStartedますが、UI(または他のコンポーネント)がユーザーに再解析の発生を警告することを許可したい場合に備えて、イベントを保持します。もちろん、ユーザーが(現在の)古くなった現在の解析結果を使用するのを止めないようにする必要がある呼び出し元を文書化することもできます。
Mark Hurd
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.