Quando le attività asincrone fanno una UX male

10

Sto scrivendo un componente aggiuntivo COM che sta estendendo un IDE che ne ha disperatamente bisogno. Ci sono molte funzionalità coinvolte, ma restringiamo il campo a 2 per il gusto di questo post:

  • C'è una finestra degli strumenti Code Explorer che mostra una vista ad albero che consente all'utente di navigare tra i moduli e i loro membri.
  • Esiste una finestra di strumenti Ispezioni di codice che visualizza una vista dati che consente all'utente di risolvere i problemi del codice e correggerli automaticamente.

Entrambi gli strumenti hanno un pulsante "Aggiorna" che avvia un'attività asincrona che analizza tutto il codice in tutti i progetti aperti; Code Explorer utilizza i risultati di analisi per creare treeview e le Ispezioni di codice utilizzano i risultati di analisi per trovare problemi di codice e visualizzare i risultati in la sua datagridview .

Quello che sto cercando di fare qui è condividere i risultati di analisi tra le funzioni, in modo che quando Code Explorer si aggiorna, allora le Ispezioni di codice ne sono a conoscenza e può aggiornarsi senza dover ripetere il lavoro di analisi che ha appena fatto Code Explorer .

Quindi quello che ho fatto, ho reso la mia classe di parser un provider di eventi che le funzionalità possono registrare in:

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

E funziona. Il problema che sto avendo è che ... funziona ... Voglio dire, quando le ispezioni del codice vengono aggiornate, il parser dice al programma di esplorazione del codice (e a tutti gli altri) "amico, qualcuno che sta analizzando, tutto quello che vuoi fare al riguardo? " - e quando l'analisi completa, il parser dice ai suoi ascoltatori "ragazzi, ho risultati di analisi freschi per voi, tutto ciò che volete fare al riguardo?".

Lascia che ti guidi attraverso un esempio per illustrare il problema che questo crea:

  • L'utente richiama il Code Explorer, che dice all'utente "aspetta, sto lavorando qui"; l'utente continua a lavorare nell'IDE, il Code Explorer si ridisegna da solo, la vita è bella.
  • L'utente fa comparire le ispezioni del codice, che indicano all'utente "aspetta, sto lavorando qui"; il parser dice al Code Explorer "amico, qualcuno sta analizzando, qualcosa che vuoi fare al riguardo?" - il Code Explorer dice all'utente "aspetta, sto lavorando qui"; l'utente può ancora lavorare nell'IDE, ma non può navigare nel Code Explorer perché è in fase di aggiornamento. E sta aspettando anche il completamento delle ispezioni del codice.
  • L'utente vede un problema di codice nei risultati dell'ispezione che desidera indirizzare; fanno doppio clic per navigare, confermano l'esistenza di un problema con il codice e fanno clic sul pulsante "Correggi". Il modulo è stato modificato e deve essere nuovamente analizzato, quindi le ispezioni del codice procedono con esso; il Code Explorer dice all'utente "aspetta, sto lavorando qui", ...

Vedi dove sta andando? Non mi piace, e scommetto che anche agli utenti non piacerà. Cosa mi manca? Come devo fare per condividere i risultati di analisi tra le funzionalità, ma lasciare comunque all'utente il controllo di quando la funzione deve fare il suo lavoro ?

Il motivo per cui lo chiedo è perché ho pensato che se ho rinviato il lavoro effettivo fino a quando l'utente non decide attivamente di aggiornare, e "memorizzato nella cache" i risultati dell'analisi mentre entrano ... beh, allora mi piacerebbe rinfrescare una treeview e l'individuazione di problemi di codice in un risultato di analisi potenzialmente obsoleto ... che mi riporta letteralmente al punto di partenza, in cui ogni funzione lavora con i propri risultati di analisi: c'è un modo per condividere i risultati di analisi tra le funzioni e hai una bella UX?

Il codice è , ma non sto guardando per il codice, sto cercando concepts .

    
posta Mathieu Guindon 08.05.2015 - 16:47
fonte

1 risposta

8

Il modo in cui probabilmente mi avvicinerei a questo sarebbe focalizzarsi meno sul fornire risultati perfetti, e invece concentrarsi su un approccio best-effort. Ciò comporterebbe almeno le seguenti modifiche:

  • Converti la logica che al momento avvia una ri-analisi per richiedere invece di iniziare.

    La logica per richiedere una ri-analisi può sembrare qualcosa del genere:

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

    Questo sarà accoppiato con la logica che avvolge il parser, che potrebbe essere simile a questo:

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

    L'importante è che il parser funzioni fino a quando la richiesta di riesame più recente non è stata rispettata, ma non più di un parser è in esecuzione in un dato momento.

  • Rimuovi il callback ParseStarted . Richiedere una ri-analisi è ora un incendio e dimenticare l'operazione.

    In alternativa, convertilo in altro modo che mostrare un indicatore rinfrescante in qualche modo fuori dalla parte della GUI che non blocca l'interazione dell'utente.

  • Cerca di fornire una gestione minima per risultati obsoleti.

    Nel caso di Code Explorer, può essere semplice come guardare un numero ragionevole di linee su e giù per un metodo che l'utente vuole raggiungere, o il metodo più vicino se non è stato trovato un nome esatto.

    Non sono sicuro di cosa sarebbe appropriato per l'ispettore del codice.

Non sono sicuro dei dettagli di implementazione, ma nel complesso questo è molto simile a come l'editor NetBeans gestisce questo comportamento. È sempre molto rapido sottolineare che al momento è in fase di aggiornamento, ma non blocca l'accesso alla funzionalità.

I risultati scaduti sono spesso abbastanza buoni, specialmente se confrontati con nessun risultato.

    
risposta data 08.05.2015 - 18:51
fonte