Wie kann man am besten vermeiden, aufgeblähten GUI-Code zu schreiben?

48

Wenn ich mit GUI-Code arbeite, neigt der Code dazu, schneller als andere Arten von Code aufzublähen. Es scheint auch schwieriger zu refactorieren. Während ich in anderen Arten von Code ziemlich einfach umgestalten kann - ich finde, dass ich eine größere Klasse in kleinere Teile der Funktionalität zerlegen kann - bin ich mit den meisten GUI-Frameworks oft an ein Framework gebunden, das mein Widget / Steuerelement / welche Klasse auch immer erfordert Implementiere viel mehr Dinge direkt im Widget / control / whatever. Manchmal liegt dies daran, dass Sie entweder (a) von einem Basis-Widget / Steuerelement / Objekt erben oder (b) auf geschützte Methoden zugreifen müssen.

Typischerweise muss ich beispielsweise auch auf eine Vielzahl von Eingaben über Signale / Ereignisse / was auch immer aus dem Framework reagieren, um alle Arten der Interaktion mit dem Benutzer zu implementieren. Möglicherweise muss ich in einem GUI-Widget / Steuerelement eine Vielzahl von Ein- / Ausgaben verarbeiten, die Folgendes umfassen können:

  1. Ein Rechtsklick / Kontextmenü
  2. Reagieren auf Auswahlen aus dem Kontextmenü - das können viele sein
  3. eine besondere Art, die GUI zu malen
  4. auf Tastatureingaben reagieren
  5. Schaltflächen, Kontrollkästchen,
  6. etc etc

... die ganze Zeit über die Klassen unter der GUI verwalten, die die Geschäftslogik darstellt.

Bei einer einfachen, übersichtlichen GUI kann der Code ziemlich schnell wachsen, auch wenn die Geschäftslogik herausgefiltert und MVC verwendet wird. Ich finde, dass GUI-Code ein großer Magnet für Veränderungen ist.

Gibt es eine Möglichkeit, GUI-Code auf vernünftige Weise zu verwalten und zu vermeiden, dass er zu einem kaputten Fenster wird? Oder ist eine Masse von zufälligen Ereignishandlern / überschriebenen Methoden wirklich das Beste, was wir für GUI-Code tun können?

Doug T.
quelle
4
Was ist Ihre genaue Definition von "aufblähen"?

Antworten:

36

Das Wichtigste an GUI-Code ist, dass er ereignisgesteuert ist und dass ereignisgesteuerter Code immer wie eine Masse zufällig organisierter Ereignishandler aussieht. Wenn Sie versuchen, nicht ereignisgesteuerten Code in die Klasse einzuschleusen, wird es sehr unübersichtlich. Sicher, es sieht so aus, als ob es Unterstützung für die Event-Handler bietet, und Sie können Ihre Event-Handler nett und klein halten, aber all dieser zusätzliche Support-Code lässt Ihre GUI-Quelle aufgebläht und chaotisch erscheinen.

Was können Sie dagegen tun und wie können Sie die Umgestaltung vereinfachen? Nun, ich würde zuerst meine Definition des Umgestaltens von etwas, das ich gelegentlich mache, in etwas ändern, das ich während des Codierens kontinuierlich mache. Warum? Weil Sie möchten, dass das Refactoring es Ihnen ermöglicht, Ihren Code einfacher zu ändern, und nicht umgekehrt. Ich bitte Sie nicht nur, die Semantik hier zu ändern, sondern stattdessen ein wenig mentale Calisthenics durchzuführen, um Ihren Code anders zu sehen.

Die drei Refactoring-Techniken, die ich am häufigsten verwende, sind Umbenennen , Methode extrahieren und Klasse extrahieren . Wenn ich nie ein anderes Refactoring gelernt hätte, würden diese drei es mir dennoch ermöglichen, meinen Code sauber und gut strukturiert zu halten, und nach dem Inhalt Ihrer Frage klingt es für mich so, als würden Sie wahrscheinlich die gleichen drei Refactorings in verwenden Um Ihren GUI-Code dünn und sauber zu halten.

Sie können die bestmögliche Trennung von GUI und Geschäftslogik in der Welt erzielen, und dennoch kann der GUI-Code so aussehen, als ob eine Code-Mine in der Mitte explodiert ist. Mein Rat ist, dass es nicht schadet, ein oder zwei zusätzliche Klassen zu haben, die Ihnen helfen, Ihre GUI richtig zu verwalten, und dies müssen nicht unbedingt Ihre View- Klassen sein, wenn Sie das MVC-Muster anwenden - obwohl Sie es häufig finden Die Zwischenklassen ähneln Ihrer Ansicht so sehr, dass Sie häufig den Drang verspüren, sie der Einfachheit halber zusammenzuführen. Ich gehe davon aus, dass es nicht wirklich schadet, eine zusätzliche GUI-spezifische Ebene hinzuzufügen, um die gesamte visuelle Logik zu verwalten. Wahrscheinlich möchten Sie jedoch die Vorteile und Kosten abwägen.

Mein Rat ist daher:

  • Tun Sie nichts direkt hinter Ihrer GUI, außer aufzurufen und zu definieren, wie die GUI in die Ansicht (oder eine Zwischenebene) eingebunden wird.
  • Versuchen Sie nicht, jede Ansicht in eine einzelne Klasse oder sogar eine einzelne Klasse pro GUI-Fenster zu zerlegen, es sei denn, dies ist für Sie sinnvoll. Alternativ können Sie viele kleine und einfach zu verwaltende Klassen erstellen, um Ihre GUI-Logik zu verwalten.
  • Wenn Ihre Methoden anfangen, etwas größer als 4-5 Codezeilen auszusehen, prüfen Sie, ob dies erforderlich ist und ob es möglich ist, eine oder zwei Methoden zu extrahieren, damit Sie Ihre Methoden schlank halten können, auch wenn dies eine Klasse bedeutet mit viel mehr Methoden.
  • Wenn Ihre Klassen sehr umfangreich aussehen, entfernen Sie zunächst ALLE duplizierten Funktionen und prüfen Sie dann, ob Sie Ihre Methoden logisch gruppieren können, sodass Sie eine oder zwei weitere Klassen extrahieren können.
  • Denken Sie daran, jedes Mal, wenn Sie eine Codezeile schreiben, ein Refactoring durchzuführen. Wenn Sie eine Codezeile zum Laufen bekommen, prüfen Sie, ob Sie sie umgestalten können, um doppelte Funktionen zu vermeiden oder sie ein wenig schlanker zu gestalten, ohne das Verhalten zu ändern.
  • Akzeptieren Sie das Unvermeidliche, dass Sie immer das Gefühl haben werden, dass sich der eine oder andere Teil Ihres Systems etwas aufgebläht anfühlt, besonders wenn Sie das Refactoring auf Ihrem Weg vernachlässigen. Selbst mit einer gut faktorisierten Codebasis haben Sie immer noch das Gefühl, dass Sie noch mehr tun können. Dies ist die Realität beim Schreiben von Software, bei der Sie immer das Gefühl haben, dass mehr "besser" getan werden könnte. Daher müssen Sie ein Gleichgewicht zwischen professioneller Arbeit und Vergoldung finden.
  • Akzeptieren Sie, dass der Code umso weniger aufgebläht erscheint, je sauberer Sie versuchen, Ihren Code zu behalten.
S.Robins
quelle
3
+1 Ob es Ihnen gefällt oder nicht, eine GUI kümmert sich um eine Unmenge detaillierter Operationen und das bedeutet Code.
Patrick Hughes
Entwickler sollten lernen, ereignisgesteuerte Codierung für die Benutzeroberfläche zu verwenden.
David Gao
23

Ich denke, viele der Probleme, die Sie haben, lassen sich auf eine einfache Ursache zurückführen. Die meisten Entwickler behandeln GUI-Code nicht wie echten Code. Ich habe hier keine Beweise oder Statistiken, nur mein Bauchgefühl.

Vielleicht denken sie, es sei " nur Präsentation " und nicht wichtig. " Es gibt dort keine Geschäftslogik ", sagen sie, " warum Unit-Test es "? Sie lachen, wenn Sie die Objektorientierung erwähnen und sauberen Code schreiben. Sie versuchen nicht einmal, die Dinge besser zu machen. Am Anfang gibt es keine Struktur, sie schlagen einfach Code ein und lassen ihn verrotten, während andere mit der Zeit ihre eigene Note hinzufügen. Ein schönes Durcheinander, Graffiti-Code.

GUI-Code hat seine einzigartigen Herausforderungen und muss daher anders und mit Respekt behandelt werden. Es braucht Liebe und Entwickler, die es schreiben wollen. Die, die es dünn halten und ihm gute Struktur und korrekte Muster geben.

c_maker
quelle
2
+1 für den Hinweis auf die Wahrnehmung von GUI-Code, der anders behandelt wird als Nicht-GUI-Code. Ich habe nicht gezählt, wie oft ich jemanden sagen hörte: "Mach dir nicht die Mühe, die GUI zu testen, weil sie nicht kosteneffektiv ist und außerdem so schwierig zu tun ist." Ich übersetze normalerweise in "Es ist schwierig und ich bin zu faul, um zu lernen, wie man es macht!".
S.Robins
1
+1 Wo ich arbeite, überprüfen wir oft keinen GUI-Code - "es ist nur GUI, überspringe es". Und ich bin genauso schuldig wie jeder andere. Seltsam ist, dass ich in meinen persönlichen Projekten viel Zeit damit verbringe, schönen, sauberen GUI-Code zu bekommen. Ich schätze, es ist nur eine Kultursache.
HappyCat
8

Aus irgendeinem Grund erzeugt GUI-Code bei Entwicklern einen toten Winkel in Bezug auf die Trennung von Bedenken. Vielleicht liegt es daran, dass alle Tutorials alles in einer Klasse zusammenfassen. Vielleicht liegt es daran, dass die physische Repräsentation die Dinge enger zusammenwirkt als sie sind. Vielleicht liegt es daran, dass der Unterricht langsam aufgebaut wird, sodass die Leute nicht erkennen, dass sie umgestalten müssen, wie wenn der sprichwörtliche Frosch durch langsames Aufdrehen der Hitze gekocht wird.

Was auch immer der Grund ist, die Lösung ist, Ihre Klassen viel kleiner zu machen. Dazu frage ich mich ständig, ob es möglich ist, das, was ich schreibe, in eine separate Klasse einzuteilen. Wenn es möglich ist, in eine andere Klasse einzusteigen, und ich mir einen vernünftigen und einfachen Namen für diese Klasse vorstellen kann, dann mache ich es.

Karl Bielefeldt
quelle
6

Vielleicht möchten Sie sich das Muster Model View Presenter / Passive View ansehen. Ray Ryan hielt auf einem Google IO einen guten Vortrag über bewährte Architekturverfahren für GWT.

http://www.google.com/events/io/2009/sessions/GoogleWebToolkitBestPractices.html

Es ist einfach, die Ideen auf andere Frameworks und Sprachen zu abstrahieren. Der Hauptvorteil von MVP (meiner Meinung nach) ist die Unittestbarkeit. Und das bekommen Sie nur, wenn Ihr Code nicht aufgebläht ist und keine Spaghetti (Ihrer Frage nach ist dies das, was Sie wollen). Dazu wird eine Ansichtslogikebene namens Presenter eingeführt. Die eigentliche Ansicht wird über eine Schnittstelle von dieser entkoppelt (und kann so in Unit-Tests leicht nachgeahmt werden). Da Ihre Ansichtslogikschicht (der Präsentator) von den internen Elementen des konkreten GUI-Frameworks befreit ist, können Sie sie jetzt wie normalen Code organisieren und sind nicht an z. B. die Vererbungshierarchie von Swings gebunden. Idealerweise können Sie GUI-Implementierungen in verschiedenen Frameworks wechseln, sofern sie mit derselben Schnittstelle übereinstimmen.

Narbenkühlschrank
quelle
1
+1. MVP konzentriert sich genau darauf, wie die GUI-Logik in separate Klassen extrahiert wird. Dies unterscheidet sich häufig erheblich von dem, was die Benutzer verstehen, wenn sie über MVC sprechen.
Doc Brown
5

Meine Antwort besteht aus vier Teilen: Struktur, Einfachheit, Test und Syntax.

Die ersten drei sind wirklich schwer zu machen!

Struktur bedeutet, viel Aufmerksamkeit auf die Verwendung der geringsten Menge an Code und der maximalen Menge an Frameworks, Bibliotheken usw. zu richten.

Einfachheit bedeutet, die Dinge vom ersten Entwurf bis zur tatsächlichen Implementierung einfach zu halten. Hier hilft es, die Navigation einfach zu halten, einfache Plugins zu verwenden und das Layout relativ einfach zu halten. Sie können jetzt an Kunden / Benutzer verkauft werden, die schnell die Vorteile von Seiten erkennen, die auf PCs, iPads, Mobilgeräten und anderen Geräten funktionieren.

Test bedeutet, Browser-Test-Tools einzuschließen (Webrat und Capybara fallen mir bei meiner Arbeit mit Rails ein), die browserübergreifende Probleme von vornherein aufdecken, wenn ein besserer Code entwickelt werden kann, um sie am Anfang zu behandeln, als das häufige "Patchen" von Code von verschiedenen Entwicklern, wie sie von Benutzern verschiedener Browser "entdeckt" werden.

Syntax. Es ist sehr hilfreich, einen Code Checker / IDE / Editor-Plugin usw. für HTML, CSS, Javascript usw. zu verwenden. Der Vorteil, den Browser durch den Umgang mit schlechtem HTML erhalten haben, wirkt sich negativ auf Sie aus, wenn verschiedene Browser unterschiedliche Leistungen erbringen Es ist also ein Tool, das Ihr HTML-Format überprüft, unerlässlich. Gut geformtes HTML ist sehr hilfreich, wenn es darum geht, nicht aufgeblähtes HTML zu haben, da schlechter Code eine bessere Sichtbarkeit haben sollte.

Michael Durrant
quelle
4

Die Lösung, die ich gefunden habe, ist deklarativer Code. Die Verwendung von nur prozeduralem Code ist ein Rezept für Spaghetti-GUI-Code. Sicher, eine "besondere Art, das Widget zu malen" wird wahrscheinlich Code bleiben. Dies ist jedoch Code, der in einer Klasse isoliert ist. Ereignishandler, Tastaturkürzel, Fenstergrößen - all diese unordentlichen Dinge werden am besten deklariert.

MSalters
quelle
4

Hier gibt es viele gute Antworten.

Eine Sache, die mir geholfen hat, den GUI-Code zu vereinfachen, ist sicherzustellen, dass die GUI über ein eigenes Datenmodell verfügt.

Um ein einfaches Beispiel zu nennen: Wenn ich eine GUI mit 4 Texteingabefeldern habe, dann habe ich eine separate Datenklasse, die den Inhalt dieser 4 Texteingabefelder enthält. Kompliziertere GUIs erfordern mehr Datenklassen.

Ich gestalte eine GUI als Modellansicht. Das GUI-Modell wird vom Application Controller des Application Model-View-Controllers gesteuert. Die Anwendungsansicht ist das GUI-Modell und nicht der GUI-Code.

Gilbert Le Blanc
quelle
2

Anwendungen wie Textverarbeitung, Grafikeditoren usw. haben komplexe Oberflächen und ihr Code kann nicht einfach sein. Für Geschäftsanwendungen muss die GUI jedoch nicht so komplex sein, sondern so, wie sie ist.

Einige der Schlüssel zur Vereinfachung der Benutzeroberfläche sind (die meisten gelten für .NET):

  1. Streben Sie nach einfacherem Design, wann immer dies möglich ist. Vermeiden Sie ausgefallene Verhaltensweisen, wenn dies nicht vom Unternehmen gefordert wird.

  2. Verwenden Sie einen guten Kontrollanbieter.

  3. Erstellen Sie keine benutzerdefinierten Steuerfunktionen im Clientcode. Erstellen Sie stattdessen Benutzersteuerelemente, die das ursprüngliche Steuerelement so erweitern, dass Sie Ihr spezifisches Verhalten in den Steuerelementen und nicht im Code des verwendeten Formulars / der Seite widerspiegeln können.

  4. Verwenden Sie ein Framework (auch ein selbst entwickeltes), um Internationalisierung, Ressourcenverwaltung, Stile usw. zu verwalten, damit Sie diesen Code nicht in jeder Benutzeroberfläche wiederholen.

  5. Verwenden Sie eine Komponente (oder ein Framework) für die Navigation.

  6. Erstellen Sie Standarddialoge für Fehler, Warnungen, Bestätigungen usw.

Keine Chance
quelle
1

Wenden Sie objektorientiertes Design auf Ihren Code und für die Entwicklung der Benutzeroberfläche an:

  1. Separate Präsentation und Modell Verwenden Sie eine MV-beliebige Bibliothek / ein MV-Framework oder schreiben Sie ein eigenes, um die Ansichts- / Controller-Logik vom Datenmodell zu trennen. Die gesamte Kommunikation mit dem Backend sollte innerhalb des Modells erfolgen und der Modellstatus sollte immer mit dem Backend synchronisiert sein.
  2. Entkopplung Wenn Objekt A über Objekt B Bescheid weiß, kann A Methoden für B aufrufen, aber B sollte nicht über A Bescheid wissen. Stattdessen kann A Ereignisse von B abhören. Dadurch wird sichergestellt, dass keine zirkuläre Abhängigkeit besteht. Wenn Ihre App viele Ereignisse zwischen Komponenten enthält, erstellen Sie einen EventBus oder nutzen Sie ein ereignisgesteuertes Framework wie Twitter Flight.
  3. Teilweise oder vollständige Darstellung Wenn es sich bei Ihrer Ansicht um eine Tabelle oder eine Liste von Elementen handelt, könnten Sie versucht sein, Methoden wie "Hinzufügen" oder "Entfernen" zu erstellen, um ein Element in die Sammlung einzufügen oder daraus zu löschen. Ihr Code kann sich leicht aufblähen, wenn Sie Sortierung und Paginierung unterstützen müssen. Mein Rat ist also: Rendern Sie einfach die gesamte Ansicht neu, auch wenn sich eine teilweise Änderung ergibt. Was ist mit der Leistung? Nun, wenn Ihre Sammlung groß ist, sollten Sie trotzdem paginieren. Webentwickler: Stellen Sie sicher, dass Ihre Ereignishandler an das Stammelement der Ansicht delegiert sind, das sich nicht ändert.
  4. View - Modell Wenn Ihre Sicht des Staat zu kompliziert wird , aufrecht zu erhalten, beispielsweise eine Tabellenansicht hat den Überblick über die Zeilendaten zu halten, Spaltendaten, Sortierreihenfolge, die derzeit geprüft Zeilen (wenn es unterstützt Multi-Check), usw., sollten Sie wahrscheinlich Erstellen Sie ein ViewModel-Objekt für diese Status. Ihr View-Objekt sollte Setter im ViewModel aufrufen, wenn sich auf der Benutzeroberfläche etwas ändert (z. B .: Benutzer überprüft eine Zeile). und es sollte auf ViewModels Änderungsereignis reagieren, indem es die Benutzeroberfläche aktualisiert. Normalerweise sollten Sie die Aktualisierung der Benutzeroberfläche vermeiden, wenn das Änderungsereignis von der Benutzeroberfläche ausgelöst wird.

Hier ist eine kleine, aber nicht triviale App, um einige meiner Punkte zu veranschaulichen. Das Code- und Ansichts- / Modell-Interaktionsdiagramm finden Sie hier: https://github.com/vanfrankie/pushpopbox

frank
quelle
0

Sie wollen sich das Konzept der "Datenbindung" ansehen . Auf diese Weise können Sie Benutzeroberflächenelemente deklarativ mit abstrakten Modellelementen verbinden, sodass die Modellelemente automatisch mit dem Inhalt der Benutzeroberfläche synchronisiert werden. Dieser Ansatz bietet viele Vorteile, z. B. müssen Sie keine Ereignishandler selbst schreiben, um Daten zu synchronisieren.

Es gibt Unterstützung für die Datenbindung für viele UI-Frameworks, z. B. .NET und Eclipse / JFace .

JesperE
quelle