Laden...

Programmierstil: Singleton für Meldungsfenster ok? Lokalisierung von Fehlermeldungen

Erstellt von plugnpray vor 16 Jahren Letzter Beitrag vor 16 Jahren 4.585 Views
P
plugnpray Themenstarter:in
78 Beiträge seit 2006
vor 16 Jahren
Programmierstil: Singleton für Meldungsfenster ok? Lokalisierung von Fehlermeldungen

Hallo,

ich habe mir ein Nachrichten-Fenster erstellt, die ich über einen Menüpunkt im Hauptmenü aufrufen kann.
Da ich von mehreren anderen Klassen die Meldungen in dieses Fenster schreiben lassen möchte, habe ich das Form als Singleton deklariert.

Und damit ich nicht immer in jeder Klasse das immer wieder neu instanziieren muss, habe ich eine Funktion als static deklariert und rufe diese dann auf.

Die statische Funktion erzeugt einmalig eine Instanz und schreibt den Text dann in das Nachrichtenfenster.

Hier ist mal der wichtigste Code:



public partial class MessageWindow : Form
{
    private static MessageWindow m_Instance;
    private static readonly object m_Lock = new object();

  
    private MessageWindow()
    {
        InitializeComponent();

    }

    public static MessageWindow getInstance()
    {
        if (m_Instance == null)
        {
            lock (m_Lock)
            {
                if (m_Instance == null)
                {
                    m_Instance = new MessageWindow();
                }
            }
        }
        return m_Instance;
    }

    new public void Show()
    {
        if (Visible)
        {
            BringToFront();
        }
        else
            Visible = true;
    }

    delegate void DisplayMessageDelegate(Message msg);
    public void DisplayMessage(Message msg)
    {
        // Make sure we're on the right thread
        if (msgTree.InvokeRequired == false)
        {
            string newMsgLine = msg.ToString();
            TreeNode newNode = msgTree.Nodes.Add(newMsgLine);

        }
        else
        {
            // Show message asynchronously
            DisplayMessageDelegate showMessage = new DisplayMessageDelegate(DisplayMessage);
            msgTree.Invoke(showMessage, msg);
        }
    }


    public static void ShowMessage(Message msg)
    {
        MessageWindow newwindow = MessageWindow.getInstance();
        newwindow.DisplayMessage(msg);
    }
}

Ich wollte jetzt von euch wissen, ob ich damit so arbeiten kann, oder ob es noch bessere Möglichkeiten gibt.

mfg

plugnpray

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo plugnpray,

an sich ist deine Klasse MessageWindow ganz ok (auch wenn ich nicht verstehe, warum du TreeView und nicht ListView oder ListBox verwendest, aber das ...

Und damit ich nicht immer in jeder Klasse das immer wieder neu instanziieren muss, habe ich eine Funktion als static deklariert und rufe diese dann auf.

... ist ganz und gar nicht ok. Deine Business- bzw. Modellklassen sollten nie auf das GUI zugreifen und das ist auch nie nötig. Der Zugriff muss immer in der anderen Richtung erfolgen, also vom GUI in Richtung Modell. Das Modell darf das GUI überhaupt nicht kennen.

herbivore

4.207 Beiträge seit 2003
vor 16 Jahren

Deine Singleton-Implementierung mit dem Double-Checked Locking Idiom ist ... na ja ... nicht so ganz das Wahre.

Näheres unter http://www.yoda.arachsys.com/csharp/singleton.html

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

P
plugnpray Themenstarter:in
78 Beiträge seit 2006
vor 16 Jahren

Okay, schon mal danke für die Informationen.

@herbivore:

Hast du denn vielleicht eine Idee, wie ich das umbauen kann.
Ich hatte dabei auch schon an eine Art Warteschlange gedacht, bei der die Nachrichten in die Warteschlange geschickt werden und die GUI holt die neuen Nachrichten dann ab und zeigt sie an...
Hab nur nich wirklich eine Idee, wie ich das anstellen soll.
Weder programmtechnisch noch performancemäßig.

@Golo:
Hab mir den Link mal angeguckt und werd das Singleton entsprechend abändern.

mfg

plugnpray

2.187 Beiträge seit 2005
vor 16 Jahren

Hi plugnpray,

Versuch es doch mal mit einer Statischen Klasse (static class)
und einem System.Collection.Generic.Queue.
Dort kannst du dann zwei Methoden schreiben "AddMessage" und "ReadMessage".
Eine Message-Klasse versteht sich ja von selbst, oder?

Gruß
Juy Juka

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo plugnpray,

was sind das denn für Meldungen? Normalerweise ist es besser, wenn die (textuellen) Meldungen erst im GUI erzeugt werden. Textuelle Meldungen sind eigentlich grundsätzlich Teil des GUIs. Meist ist es ungünstig mit Meldungstexten in Modellklassen zu opertieren oder diese dort sogar als Literale fest zu hinterlegen.

Zeige in der Modellklasse einfach durch Rückgabewert oder per Exception an, was Sache ist und lass das GUI dann die eigentliche Meldung erzeugen/anzeigen.

Apropos Exceptions: Diese enthalten ja auch Meldungstexte. Und auch da bin ich hin- und hergerissen. Einerseits ist es schon sehr praktisch, einfach 1:1 die Texte anzuzeigen, die man durch die Exception geliefert bekommt. Andererseits stammen diese Meldungstexte ja aus dem Modellklassen des Frameworks und gehören da eigentlich nicht hin. Das sieht man schon an der Problematik, wenn man das GUI lokalisieren will. Die Exception-Meldungen sträuben sich gegen die Lokalisierung und sind immer in der Sprache des Frameworks. Texte gehören also eigentlich immer in die GUI-Klassen bzw. in die Ressourcen der GUI-Klassen.

herbivore

3.728 Beiträge seit 2005
vor 16 Jahren
Lokalisierung der Ausnahme-Texte

Ich verwalte die Texte von Ausnahmen der Geschäftslogik auch in den Assemblies der Geschäftslogik. Da Visual Studio für Ressourcen automatisch eine Klasse erzeugt, ist die lokalisierung kinderleicht. Auch die Ausgaben in Log-Dateien oder im Ereignisprotokoll sollten lokalisiert sein (Auch Admins sprechen verschiedene Sprachen). Logs, die z.B. von einem Server-Dienst geschrieben werden sind eindeutig nicht Teil der GUI. Und auch Außnahmen nicht, die z.B. von entfernten Methodenaufrufen zurückgegeben werden.

Jede Assembly enthält bei mir deshalb eine Ressource, die "LanguageResource" heißt. Dort liegen solche Texte. Dank string.Format lassen sich Parameter auch leicht automatisch an den richtigen Stellen einsetzen.

Beispiel:


// Meldung ist in der Resource so abgelegt: 
// DE: "Das Sitzungsticket '{0}' ist abgelaufen."
// EN: "The session ticket '{0}' is expired."
throw new ApplicationException(string.Format(LanguageResource.ExpiredSessionTicket,ticketGuid));

// Ausgabe der vollständigen Ausnahmemeldung: 
// DE: "Das Sitzungsticket '9047096D-254D-4e70-86C1-EB4CF097456A' ist abgelaufen."
// EN: "The session ticket '9047096D-254D-4e70-86C1-EB4CF097456A' is expired."

Die Lokalisierung ist demnach unproblematisch.

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Rainbird,

was ich im folgenden schreibe, soll keineswegs bedeuten, dass ich es besser wüsste. Im Gegenteil, mir ist noch keine gute, generelle Lösung zu dem Thema eingefallen. Ich meine eine, die auch dann funktioniert, wenn man Komponenten und Klassen verschiedener Hersteller verwendet. Ich möchte also nicht mehr als den "Finger in die Wunde legen" und eine Gegenposition einnehmen, um die Diskussion voranzutreiben und somit bei der Suche einer Lösung näher zu kommen.

Da Visual Studio für Ressourcen automatisch eine Klasse erzeugt, ist die lokalisierung kinderleicht.

ja, für eigene Klassen. Eigene Klassen kann man aber sogar dann lokalisieren, falls man die Texte wirklich als String-Literale direkt in den Code geschrieben hat.

Logs, die z.B. von einem Server-Dienst geschrieben werden sind eindeutig nicht Teil der GUI.

Richtig, nicht Teil des GUIs, aber wenn man so will Teil des UIs. Wobei mir schon klar ist, dass der "Benutzerkreis" ein anderer ist. In Lokalisierung von Exceptions wird dieser Punkt genauer diskutiert. Mir ist auch klar, dass die Situation beim Logging auch insofern speziell ist, als dass Logging sich als klassischer Aspekt (im Sinne von AOP) quer durch die Anwendung zieht und Logging-Klassen insofern auch ganz unten und nicht wie die GUI-Klassen ganz oben angesiedelt sind.

Bei den Exceptions, wie sie das Framework anbietet, kommt hinzu, dass die Texte nicht aus der Exceptions-Klasse selbst kommen (dann hätte man ja noch einen halbwegs zentralen Ort, an dem man ansetzen könnte), sondern per Parameter an den Konstruktor der Exception übergeben wird. Als Konsequenz sind die Texte in dem die Exception auslösenden Code hinterlegt. Wenn ich solchen Code Dritter benutze, steckt der Text für mich so gesehen irgendwo in der von mir nicht änderbaren Assembly des Dritten.

Ok, Microsoft benutzt hier immer die ThrowHelper-Klasse, die das Erzeugen (und werfen) der Exception kapselt. Dabei werden die Strings mit der internen Methode Environment.GetResourceString geladen. Das ändert m.E. nichts daran, dass man als Anwendungsentwickler diese Texte nicht selbst lokalisieren kann. Hier hat man also die genau die Situation, dass unlokalisierbare Texte in den Produkt eines Dritthersteller liegen.

Die Lokalisierung ist demnach unproblematisch.

Und wie lokalisierst du Ausnahmen, die u.B. von entfernten Methodenaufrufen zurückgegeben werden? 🙂 Meine These ist weiterhin, dass Meldungstexte in Modellklassen (oder in Ressourcen von Modellklassen) grundsätzlich problematisch sind. Ich habe aber bisher noch keine Lösung, wie man vollkommen ohne solche Texte auskommt.

herbivore

G
40 Beiträge seit 2005
vor 16 Jahren

Hallo

In meinem Programm wird, bevor irgendein Fenster erscheint, zunächst in einer Modellklasse, die sich um Verzeichnisse etc. kümmert, geprüft, ob das Verzeichnis des Servers verfügbar ist, wenn nicht, gibt es aus dieser Modellklasse heraus eine Fehlermeldung mit Text aus dieser Modellklasse heraus. (Bei mehreren Sprachen müsste da dann ein Verweis auf eine Ressource her). In einem anderen Teil meines Programms erstelle ich automatisiert Termine, die voneinander abhängen. Sie werden in der GUI in Benutzersteuerelementen ausgegeben und können aber dort auch geändert werden. Für die Validierung bzw. Fehlerausgabe ist es egal, ob Termin A oder ob Termin B geändert wird, die Generierung der Fehlerausgabe geschieht daher nicht in Benutzersteuerelement A oder B, sondern in einer 'tiefer' liegenden Modellklasse.

Aus der Sicht eines in erster Linie auch Anwenders von Software ist das Hauptproblem aber die Frage, was für den Anwender als Fehlermeldung ausgegeben wird. Mit 'Debug assertion failed', irgendwelchen Angaben von Speicheradressen und Verweis auf die MFC-classes weiss man, das muss wohl ein C++ Programm sein, mehr aber nicht. Die Fehlermeldung 'Objektvariable oder With-Blockvariable nicht festgelegt' sagt einem, dass es sich bei dem gerade erst angeschafften innovativen Programm wohl um ein VB 6 - Programm handelt. Wenn es nach dieser Fehlermeldung sich mit einem Absturz verabschiedet muss man es halt neu starten.
Ärgerlicher ist es bei einer solchen Meldung: 'Serverseitiger Fehler, Errorcode 999', damit fängt man als Anwender gar nichts an. Man probiert dreimal einen Neustart und verliert kostbare Zeit. Zeit, die im Einzelfall bei uns auch einmal lebenswichtig sein könnte. Hier hätte ich mir eine Fehlermeldung in dieser Art gewünscht:
'Server und auch Ersatz-Server derzeit nicht verfügbar, bitte Anforderungen von Hand Papier-gestützt abschicken' damit man sofort weiss, wo man dran ist.

Dies bedeutet, es wäre eigentlich Aufgabe der Software-Entwickler Fehlermeldungen, die in welcher Sprache auch immer aus der Tiefe der Methodenaufrufe und Klassen kommen, zu kapseln und mit einer Fehlermeldung zu versehen, die für den jeweiligen Anwender verständlich ist. Ich denke, dass das so auch von Microsoft vorgesehen ist. Notwendig wäre dann eine bessere Kommunikation zwischen Entwicklern und Endanwendern, da naturgemäß nicht primär alle (Laufzeit-)Fehler von den Entwicklern vorhersehbar sind.

viele Grüße

3.728 Beiträge seit 2005
vor 16 Jahren

Original von herbivore
Und wie lokalisierst du Ausnahmen, die u.B. von entfernten Methodenaufrufen zurückgegeben werden? 🙂 Meine These ist weiterhin, dass Meldungstexte in Modellklassen (oder in Ressourcen von Modellklassen) grundsätzlich problematisch sind. Ich habe aber bisher noch keine Lösung, wie man vollkommen ohne solche Texte auskommt.

Im Falle von Remoting wird die Kulturinformation des aufrufenden Clients einfach in den Aufrufkontext geschrieben. Der Server prüft, ob sich eine Kulturinformation im Aufrufkontext befindet und ändert, falls zutreffend, die Kultur des Worker-Threads. Damit gibt mein Remoting-Server automatisch Meldungstexte in der Sprache des Aufrufers zurück.

Mit WCF oder Webservices hatte nich noch kein konkretes Projekt, aber da wird die Lösung ähnlich aussehen. Die Kulturinformationen werden bei der Übertragung unauffällig mitgegeben. Clientseitig sorgt ein Stückchen eigene API dafür (z.B. eine Factory), dass die Zusatzinformationen wie Kultur, Sitzungsticket etc. auch wirklich automatisch im Aufrufkontext landen, ohne dass der Client-Entwickler daran denken muss.

Was Komponenten von Fremdherstellern angeht, akzeptiere ich einfach, dass diese möglicherweise keine lokalisierten Meldungstexte zurückgeben. Nachdem aber fast ausnahmslos alle meine Erfahrungen mit externen Komponenten (ich meine z.B. Steuerelemente oder auch einzelne Werkzeug-Bibliotheken) schlecht waren (positiv fällt mir spontan nur die TAPI-Komponente TopTAPI ein), versuche ich deren Einsatz zu vermeiden.
Bei Fremdkomponenten kann ich sowieso nicht sicherstellen, dass die Rahmenbedingungen des Projekts genau eingehalten werden.