Laden...

Frage Design Exception-Basisklasse / mehrsprachige Exceptions

Erstellt von mosspower vor 15 Jahren Letzter Beitrag vor 15 Jahren 1.999 Views
mosspower Themenstarter:in
456 Beiträge seit 2007
vor 15 Jahren
Frage Design Exception-Basisklasse / mehrsprachige Exceptions

Hallo "Kollegen",

Ich habe eine Metaklasse für Exceptions geschrieben, so wie ich die jetzt immer in Zukunft brauche. Die Klasse CustomException ist von Exception abgeleitet und bietet darüber hinaus noch die Möglichkeit, ein errorCode vom Typ T zu definieren, ferner können Argumentobjekte genutzt werden, hier z.B. wenn die Fehlermeldungen internationalisiert werden sollen (dann steht in message der Resource-Key). OK, so sieht das dann aus ....


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Util {
  /// <summary>
  /// Custom exception class
  /// </summary>
  public class CustomException<T> : Exception {
    private T errorCode;
    private Object[] args = null;

    /// <summary>
    /// Public constructor
    /// </summary>
    /// <param name="errorCode">The error code</param>
    /// <param name="message">The error message</param>
    /// <param name="innerException">The inner Exception object</param>
    /// <param name="args">Error arguments</param>
    public CustomException(T errorCode, String message,
      Exception innerException, params Object[] args)
        : base(message, innerException) {
      this.errorCode = errorCode;
      this.args = args;
    }

    /// <summary>
    /// See <see cref="CustomException&lt;T&gt;(T, String, Exception, Object[])"/>
    /// </summary>
    public CustomException(T errorCode, String message,
      Exception innerException)
        : this(errorCode, message, innerException, null) {
    }

    /// <summary>
    /// See <see cref="CustomException&lt;T&gt;(T, String, Exception, Object[])"/>
    /// </summary>
    public CustomException(T errorCode, String message, params Object[] args)
      : base(message) {
      this.errorCode = errorCode;
      this.args = args;
    }

    /// <summary>
    /// See <see cref="CustomException&lt;T&gt;(T, String, Exception, Object[])"/>
    /// </summary>
    public CustomException(T errorCode, String message)
      : this(errorCode, message, (Object[])null) {
    }

    /// <summary>
    /// Property ErrorCode
    /// </summary>
    public T ErrorCode {
      get {
        return this.errorCode;
      }
    }

    /// <summary>
    /// Property Args
    /// </summary>
    public Object[] Args {
      get {
        return this.args;
      }
    }

    /// <summary>
    /// Gets the full message, replaces all arguments, if any
    /// within the message string
    /// </summary>
    /// <returns>The full message</returns>
    /// <exception cref="FormatException" />
    public String GetFormattedMessage() {
      String formattedMessage = this.Message;

      if(this.args != null) {
        formattedMessage = String.Format(formattedMessage, this.args);
      }

      return formattedMessage;
    }

    /// <summary>
    /// See <see cref="Object.ToString()"/>
    /// </summary>
    public override string ToString() {
      return this.GetType().FullName + " [" + 
        this.errorCode.ToString() + "] > " + this.GetFormattedMessage() + 
          " > stack [" + ExceptionUtil.GetFullStackTrace(this) + "]";
    }
  }
}

Jetzt möchte ich natürlich eigene Exceptions bilden, die (in der Regel) lediglich einen anderen Namen haben. Meine Frage wäre, ob ich wirklich dann immer in dieser eigenen Klasse alle Konstruktoren überschreiben muss. Kann man das nicht anders machen? Ist doch dann eigentlich immer die gleiche Kopiererei, nur der Klassenname (und der Namespace) sind anders.

Hier ein Beispiel ... (nur einmal überschrieben)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Util;

namespace Coding {
  /// <summary>
  /// Coder exception class
  /// </summary>
  public class CoderException : CustomException<CoderConstants.ErrorCodes> {
    /// <summary>
    /// See <see cref="CustomException&lt;T&gt;(T, String, Exception, Object[])"/>
    /// </summary>
    public CoderException(CoderConstants.ErrorCodes errorCode, String message,
      Exception innerException, params Object[] args)
        : base(errorCode, message, innerException, args) {
    }
  }
} // ... and more constructors ...

OK, ich könnte ja gleich die CustomException hernehmen, und zusätzlich ein Name-Property einbauen, aber das scheint eine sehr schlechte Lösung, denn ich kann dann nicht die spezifischen Exceptions catchen, sondern müsste dann im Catch-Block noch die Namen abfragen, was eine total komisches Exceptionhandling wäre.

Hat jemand eine Idee? Befinde ich mich auf dem Holzweg oder würdet ihr einfach immer wieder den gleichen "Käse", also die Konstruktoren, in jede neue Exception-Klasse kopieren?

Gruß und danke schon mal für etwaige Antworten im Voraus.

Gelöschter Account
vor 15 Jahren
  1. eine eigene exception sollte immer direkt von exception erben und sonst von nichts anderem.
  2. sollen exceptions niemals direkt angezeigt werden. daher ist das mitschleppen von bereits regionalisiertem text, ressourcen und performanceverschwendung.
  3. was macht denn das genau: "ExceptionUtil.GetFullStackTrace(this)" ?
  4. finde ich die idee mit den args nicht so gut, da man ansonsten dazu verleitet wird, eine exception als datenobjekt zu vergewaltigen und dann ist der schritt zum verwenden von normalen abläufen (also keine ausßnahmen mehr...) wirklich nah.
49.485 Beiträge seit 2005
vor 15 Jahren

Hallo mosspower,

deine Frage kann man auf zwei Ebene betrachten. Die Sprach-Ebene und die Diesign-Ebene.

Auf der Sprach-Ebene ist es so: Konstruktoren werden nicht vererbt. Somit muss jede Unterklasse für sich alleine die Konstruktoren definieren, die sie anbieten will. Dabei kann sie die Konstruktoren der Oberklasse "aufrufen", mehr aber nicht.

Auf der Design-Ebene muss ich leider sehr hart urteilen (nicht traurig sein). Deine Klasse ist für die Tonne. Alles was du anbietest kann die normale Exception-Klasse entweder schon (wenn vielleicht auf einem anderen Weg) oder sollte es aus den von JAck30lena genannten Gründen nicht anbieten. Meine klare Empfehlung ist daher, die Klasse einzustampfen.

herbivore

mosspower Themenstarter:in
456 Beiträge seit 2007
vor 15 Jahren

Hallo und erst mal vielen Dank für die Antworten,

ich bin offen für Anregungen und Kritik, kein Problem.

Ich schildere mal kurz meine Problematik. Ich habe bisher immer Englisch programmiert (hatte mich da eben einmal entschieden), das bedeutet, dass ich auch das Exceptionhandling in Englisch mache, was ich übrigens, aus Erfahrung, zeitmäßig nie vernachlässige. Jetzt ist es aber so, dass ich in das auch mehrsprachig Anbieten möchte, bzw. weil ich eingesehen habe, dass ich das auch muss, nicht jeder kann Englisch, bzw. reicht nicht immer etwas Schulenglisch aus.

Wie löse ich aber in Zukunft z.B. folgende Fehlermeldung Mehrsprachig?

....


                catch(FormatException) {
                  throw new EdiException(EdiErrorConstants.FILE_VALIDATION_ERROR,
                    String.Format("Cannot cast boolean out of '{0}' for field name '{1}' " +
                      "= mapping name '{2}' in field configuration number '{3}' " + 
                        "for column position in import file number '{4}' in " +
                          "import file line '{5}' for config key '{6}'",
                            new Object[] { fieldValue, 
                                           ediField.Name, 
                                           ediField.MappingName, 
                                           (i + 1),
                                           positionCounter, 
                                           (j + 1), 
                                           ediConfig.Id } )); 
                }

Wie löse ich das jetzt? Ich dachte, dass ich diese Exception "nur noch" mit dem Typen, hier FILE_VALIDATION_ERROR, mit dem Resource key und mit all den Parametern weiterschmeiße, dann kann die Anwendung, je nach CultureInfo, sich den Text holen, oder aber man baut die CultureInfo auch noch in die CustomException (Constructor) ein und bekommt dann die "richtige" Fehlermeldung.

Ich bin jetzt hier dabei, eine Möglichkeit zu suchen, wo ich mehrere kleine Projekte mehrsprachig machen muss. Bisher habe ich immer englische Exceptions geworfen, wie im Beispiel.

F
10.010 Beiträge seit 2004
vor 15 Jahren

Die Texte einer Exception sind für Programmierer gedacht, nicht für Endanwender.

49.485 Beiträge seit 2005
vor 15 Jahren
F
722 Beiträge seit 2005
vor 15 Jahren

Hallo mosspower,

wie schon gesagt sollte man sich genau überlegen, ob man die Exception Texte lokalisieren möchte.
Völlig unüblich ist das nicht, Microsoft macht es im .NET Framework ja auch. Eine elegante Lösung des Problems ist es, eine Resource Datei mit VS anzulegen, nennen wir sie mal StringTable.resx. Du trägst deinen (Quell-) String dort ein und gibst ihm einen Namen, z.b. MyExceptionText. Danach ersetzt du den String im Code mit dem von VS generieren Property.


catch(FormatException) {
                  throw new EdiException(EdiErrorConstants.FILE_VALIDATION_ERROR,
                    String.Format(StringTable.MyExceptionText,
                            new Object[] { fieldValue,
                                           ediField.Name,
                                           ediField.MappingName,
                                           (i + 1),
                                           positionCounter,
                                           (j + 1),
                                           ediConfig.Id } ));

Jetzt ist natürlich darauf zu achten, dass zukünftige Übersetzungen auch entsprechend der Anzahl der Parameter in String.Format formatiert sind.
Jetzt musst du die resx Datei nur noch in die Zielsprachen übersetzen, wie das geht beantwortet dir das Forum unter den Stichpunkten Internationalisierung/Lokalisierung.

mosspower Themenstarter:in
456 Beiträge seit 2007
vor 15 Jahren

Hallo mosspower,

siehe auch
>

herbivore

Danke für den Hinweis,

ein sehr heikles Thema. Wenn ich es wirklich so machen würde, dass ich einen Fehler (z.B. mit Fehlercode) in der GUI übersetze und dann den Fehler in der jeweiligen Sprache anzeige, dann kann manchmal doch nur rauskommen, dass in der Fehlermeldung "fast nix" steht, z.B. Ein Fehler Validierungsfehler beim Einlesen der Datei ist passiert, Fehlercode ERR_00123XYZ ... Jetzt kann der Anwender nix weiter machen, wenn er Glück hat, wird gelogged und kann im Logfile nachsehen, das erfordert aber schon mehr Kenntnisse. In der Hilfe könnte der Fehler auch beschrieben sein. Das ist jetzt aus Kundensicht.

Wenn ich jetzt aber die gekapselte Funktionalität (nehmen wir einfach mal das obige Beispiel zum Lesen einer Datei mit Validierungsprüfung) bei mehreren Projekten verwende (also mehrere GUI-Projekte), dann müsste ich doch jedesmal die gleiche oder eine ähnliche Fehlermeldung in meinen Sourcecode (oder eigenes Resourcefile) schreiben, das wiederholt sich doch dann so oft, wie ich die Komponente entwickle.

Was spricht denn dann dagegen, dass man das gleich so aufbaut wie vorgeschlagen? Hier hätte ich einen resource-key und x-beliebige Parameter, und nach Anforderungen (GUI-Anwendung) wird dann, nach übergabe der CultureInfo die Meldung generiert. Das bedeutet, ich muss die Meldung nur einmal "eincompilieren" ... ein schwieriges Thema 😁 ... naja, werde mir das erst noch mal durch den Kopf gehen lassen.

@feadur ,

an so eine ähnliche Lösung hatte ich gedacht, wobei ich herkömmliche Propertyfiles nehmen, mit Key-Value-Paaren und keine .NET Resourcefiles. Ich benutze lieber die Keys und nicht die vom Framework generierten Resourcenamen. Aber es läuft im Endefekt aufs gleiche hinaus - nur bin ich mir gegenwärtig nicht sicher ob ich es überhaupt so machen soll, denn eigentlich sind Exceptionmeldungen, wie oben schon beschrieben, für Entwickler gedacht. (Obwohl hier, Stichwort Mehrsprachigkeit, Microsoft auch einen eigenen Weg geht.)

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo mosspower,

was die Fehlercodes angeht: in dem direkt verlinkten Thread wurde doch garnicht vorgeschlagen, Fehlercode zu verwenden und in dem indirekt verklinkten Thread wird der entsprechende Vorschlag doch nicht weiter verfolgt. Es wurde im Gegenteil gesagt, dass es nicht sinnvoll ist, nur einen Fehlercode anzuzeigen.

Andersherum sollte eine Exception also schon alle Informationen enthalten, die nötig sind, um eine vernünftige Meldung zu erzeugen.

Was die Redundanz angeht: Es spricht ja nichts gegen eine GUI-Bibliothek, die die Fehlermeldungen erzeugt und die im mehreren Projekten verwendet werden kann. Ich hatte mich ja nur dagegen gewandt, dass Fehlermeldungen im Business-Code stehen.

Insgesamt scheint es mir da jedoch so oder so keinen Königsweg zu geben.

herbivore

Gelöschter Account
vor 15 Jahren

im falle des frameworks ist der exceptiontext der gui-text. oder würdest du gerne als entwickler sehen: "Feher beim Dateizugriff" anstatt der differenzierung, warum etwas falsch gelaufen ist? in den meisten fällen übersetzt die CLR ohnehin nur winapi-errorcodes oder sonstige errorcodes von irgendwelchen nativen dll´s.

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo JAck30lena,

meinst du mich? Ich habe ja in den anderen Threads schon geschrieben, dass es nicht gut ist, wie es im .NET Framework gemacht ist.

herbivore

Gelöschter Account
vor 15 Jahren

äh, nein. ich bezog mich auf die aussage, das das framewok die exceptions lokalisiert. ich wollte nur klarstellen warum das so ist.

allerdings kann ich die passage nciht mehr finden?... evtl im edit verschwunden?

F
10.010 Beiträge seit 2004
vor 15 Jahren

Die Fehlermeldungen des FW sind für den Entwickler gedacht, nicht für den Endanwender.

Auch wenn ich das jetzt zum 2. mal in diesem Thread schreibe.

Ein Endanwender kann nichts damit anfangen, wenn da irgendwo steht,
das der DB zugriff schief gelaufen ist, weil irgend ein Feld gegen die FK_WAS_AUCH_IMMER
verstossen hat.
Das sollte der Entwickler dann dem Anwender erklären, und diese Meldung
lässt sich gut Lokalisieren.

Und wenn man mal informationen in der Exception weiter geben will, um
z.b. die Zeile/Spalte des Parsingerrors zu merken, dann gibt es dafür in
Exception extra Data als IDictionary.


    public class FileParsingException : Exception
    {
        System.Collections.IDictionary m_Data;
        public override System.Collections.IDictionary Data { get { return m_Data; } }
        public FileParsingException(string text, string filename, int lineNum, int colNum):base(text)
        {
            m_Data = new Dictionary<string, object>() 
           { 
               { "Filename", filename }, 
               { "LineNum", lineNum }, 
               { "ColNum", colNum } 
           };
        }
    }

Und benutzen kann man es dann mit :


throw new FileParsingException("Error parsing File. Additionalinfo in Data", parsedFileName, errorLine, errorColumn);

Und in der SW dann ganz einfach so Lokalisieren:


try
{
....
}
catch(FileParsingException fex)
{
  string MsgString = string.format( Properties.Resource.FileParseError_1_2_3, 
    few.Data["FileName"], few.Data["LineNum"], few.Data["ColNum"]);
}

Der Entwickler hat so genug infos beim entwicklen, und alles andere ist auch einfach
umzusetzen.