Ketika tugas-tugas tidak sinkron membuat UX buruk

9

Saya menulis add-in COM yang memperluas IDE yang sangat membutuhkannya. Ada banyak fitur yang terlibat, tetapi mari kita persempit menjadi 2 untuk kepentingan posting ini:

  • Ada jendela alat Code Explorer yang menampilkan tampilan pohon yang memungkinkan pengguna menavigasi modul dan anggota mereka.
  • Ada alat bantu inspeksi kode yang menampilkan tampilan datagridview yang memungkinkan pengguna menavigasi masalah kode dan secara otomatis memperbaikinya.

Kedua alat memiliki tombol "Refresh" yang memulai tugas asinkron yang mem-parsing semua kode di semua proyek yang dibuka; yang Kode Explorer menggunakan hasil parse untuk membangun treeview , dan Kode Inspeksi menggunakan hasil parse untuk menemukan masalah kode dan menampilkan hasilnya di dalam datagridview .

Yang saya coba lakukan di sini, adalah untuk berbagi hasil parse antara fitur, sehingga ketika Kode Explorer refresh, maka Kode Inspeksi tahu tentang hal itu dan bisa menyegarkan sendiri tanpa harus mengulang pekerjaan parsing bahwa Kode Explorer hanya melakukan .

Jadi apa yang saya lakukan, saya menjadikan kelas parser saya sebagai penyedia acara yang fitur-fiturnya dapat didaftarkan ke:

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

Dan itu berhasil. Masalah yang saya alami adalah apakah ... itu berfungsi - maksud saya, ketika inspeksi kode disegarkan, parser memberi tahu penjelajah kode (dan semua orang) "Bung, penguraian seseorang, apa yang ingin Anda lakukan? " - dan ketika parsing selesai, parser memberi tahu pendengarnya "kawan, saya punya hasil parsing baru untuk Anda, apa yang ingin Anda lakukan?".

Biarkan saya membimbing Anda melalui contoh untuk menggambarkan masalah yang diciptakan ini:

  • Pengguna menampilkan Kode Explorer, yang memberi tahu pengguna "tunggu, saya bekerja di sini"; pengguna terus bekerja di IDE, Code Explorer menggambar ulang sendiri, hidup itu indah.
  • Pengguna kemudian memunculkan Inspeksi Kode, yang memberi tahu pengguna "tunggu, saya sedang bekerja di sini"; parser memberi tahu Code Explorer "dude, parsing seseorang, apa yang ingin Anda lakukan?" - Code Explorer memberi tahu pengguna "tunggu, saya sedang bekerja di sini"; pengguna masih dapat bekerja di IDE, tetapi tidak dapat menavigasi Code Explorer karena ini menyegarkan. Dan dia juga menunggu inspeksi kode selesai.
  • Pengguna melihat masalah kode dalam hasil inspeksi yang ingin mereka atasi; mereka mengklik dua kali untuk menavigasi ke sana, mengkonfirmasi ada masalah dengan kode, dan klik tombol "Perbaiki". Modul telah dimodifikasi dan perlu diurai kembali, sehingga inspeksi kode dilanjutkan dengannya; Penjelajah Kode memberi tahu pengguna "tunggu, saya sedang bekerja di sini", ...

Lihat kemana ini? Saya tidak suka, dan saya yakin pengguna juga tidak akan menyukainya. Apa yang saya lewatkan? Bagaimana cara saya membagi hasil parse di antara fitur, tetapi tetap membiarkan pengguna mengontrol kapan fitur harus melakukan fungsinya ?

Alasan saya bertanya, adalah karena saya pikir jika saya menunda pekerjaan yang sebenarnya sampai pengguna secara aktif memutuskan untuk menyegarkan, dan "cache" hasil parse ketika mereka masuk ... baik maka saya akan menyegarkan treeview dan menemukan masalah kode pada hasil parse yang mungkin basi ... yang benar-benar membawa saya kembali ke titik awal, di mana setiap fitur bekerja dengan hasil parse sendiri: apakah ada cara saya dapat membagikan hasil parsing antara fitur dan memiliki UX yang indah?

Kode ini adalah , tetapi saya tidak mencari kode, saya mencari konsep .

Mathieu Guindon
sumber
2
Hanya sebagai FYI, kami juga memiliki situs UserExperience.SE . Saya percaya ini adalah topik di sini karena membahas desain kode lebih dari antarmuka pengguna tetapi saya ingin memberi tahu Anda jika perubahan Anda lebih mengarah ke sisi UI dan bukan sisi kode / desain masalah.
Saat Anda melakukan parsing, apakah ini operasi semua atau tidak sama sekali? Misalnya: apakah perubahan dalam file memicu reparse lengkap, atau hanya untuk file itu dan yang bergantung padanya?
Morgen
@Morgen ada dua hal: VBAParserdihasilkan oleh ANTLR dan memberi saya pohon parse, tetapi fitur tidak mengonsumsinya. The RubberduckParsertake the parse tree, berjalan, dan mengeluarkan a VBProjectParseResultyang berisi Declarationobjek yang memiliki semua Referencespenyelesaiannya - itulah yang dilakukan fitur untuk input .. jadi ya, ini cukup banyak situasi semua atau tidak sama sekali. The RubberduckParsercukup pintar untuk tidak kembali parse modul yang belum dimodifikasi sekalipun. Tetapi jika ada hambatan bukan dengan parsing, itu dengan inspeksi kode.
Mathieu Guindon
4
Saya pikir, saya akan melakukannya seperti ini: Ketika pengguna memicu refresh, toolwindow yang memicu penguraian dan menunjukkan bahwa itu berfungsi. Jendela alat lain belum diberitahukan, mereka terus menampilkan informasi lama. Sampai parser selesai. Pada titik itu, pengurai akan memberi sinyal semua jendela alat untuk menyegarkan pandangan mereka dengan informasi baru. Jika pengguna pergi ke jendela alat lain saat parser bekerja, jendela itu juga akan masuk ke status "berfungsi ..." dan memberi sinyal sebuah reparse. Parser kemudian akan memulai kembali untuk memberikan info terbaru ke semua jendela pada saat yang sama.
cmaster - mengembalikan monica
2
@ cmaster Saya akan mengubah komentar itu sebagai jawaban juga.
RubberDuck

Jawaban:

7

Cara saya mungkin akan mendekati ini akan menjadi kurang fokus pada memberikan hasil yang sempurna, dan sebaliknya fokus pada pendekatan upaya terbaik. Ini akan menghasilkan setidaknya perubahan berikut:

  • Konversikan logika yang saat ini memulai penguraian ulang untuk meminta alih-alih memulai.

    Logika untuk meminta penguraian ulang mungkin berakhir dengan tampilan seperti ini:

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

    Ini akan dipasangkan dengan logika yang membungkus parser, yang mungkin terlihat seperti ini:

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

    Yang penting adalah bahwa pengurai berjalan sampai permintaan penguraian ulang yang paling baru telah dihormati, tetapi tidak lebih dari satu pengurai berjalan pada waktu tertentu.

  • Hapus ParseStartedpanggilan balik. Meminta penguraian ulang sekarang merupakan operasi api dan lupa.

    Atau, konversikan untuk tidak melakukan apa pun selain menunjukkan indikator menyegarkan di beberapa bagian GUI yang tidak menghalangi interaksi pengguna.

  • Cobalah untuk memberikan penanganan minimal untuk hasil basi.

    Dalam kasus Code Explorer, itu mungkin sesederhana mencari jumlah garis yang wajar atas dan ke bawah untuk metode yang ingin dinavigasi oleh pengguna, atau metode terdekat jika nama persis tidak ditemukan.

    Saya tidak yakin apa yang cocok untuk Inspektur Kode.

Saya tidak yakin dengan detail implementasi, tetapi secara keseluruhan, ini sangat mirip dengan bagaimana editor NetBeans menangani perilaku ini. Selalu sangat cepat untuk menunjukkan bahwa saat ini menyegarkan, tetapi juga tidak memblokir akses ke fungsionalitas.

Hasil basi seringkali cukup baik - terutama bila dibandingkan dengan tanpa hasil.

Morgen
sumber
1
Poin luar biasa, tapi saya punya pertanyaan: Saya menggunakan ParseStarteduntuk menonaktifkan tombol [Refresh] ( Control.EnableRefresh(false)). Jika saya menghapus panggilan balik itu, dan membiarkan pengguna mengkliknya ... maka saya akan menempatkan diri saya dalam situasi di mana saya memiliki dua tugas bersamaan melakukan penguraian ... bagaimana cara menghindari ini tanpa menonaktifkan penyegaran pada semua fungsi lain saat seseorang Apakah penguraian?
Mathieu Guindon
@ Mat'sMug Saya memperbarui jawaban saya untuk memasukkan sisi masalah itu.
Morgen
Saya setuju dengan pendekatan ini, kecuali bahwa saya masih akan mengadakan suatu ParseStartedacara, jika Anda ingin mengizinkan UI (atau komponen lainnya) terkadang memperingatkan pengguna bahwa reparasi sedang terjadi. Tentu saja, Anda mungkin ingin mendokumentasikan penelepon harus berusaha untuk tidak menghentikan pengguna dari menggunakan (akan menjadi) hasil parse saat ini basi.
Mark Hurd