Laden...

Frage zum Programmierstil: zwei Rückgabewerte [und Alternativen dazu, z.B. Exceptions]

Erstellt von DERKLAUS vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.321 Views
D
DERKLAUS Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren
Frage zum Programmierstil: zwei Rückgabewerte [und Alternativen dazu, z.B. Exceptions]

Ein häufiges Problem, Auslesen von Dateien und Rückgabewerte dazu. Man wünscht sich eigentlich 2 Rückgabewerte, nämlich bzgl. der auszulesenden Datei plus den Inhalt derselben. Dabei ist es egal, ob Textdatei oder binär. Wir kriegen aber nur einen Rückgabewert.

Beispiel Textdatei:

List<string>lies_textdatei (string dateiname) ... muß auch bei Dateifehler eine List zurückgeben, die dann natürlich leer ist. Eine leere Liste ist hier natürlich nicht das, was man sich vorstellt.

Die aufrufende Methode kann zudem aus der leeren Liste nur indirekt ermitteln, was da gelaufen ist (Datei nicht vorhanden oder tatsächlich leer oder fehlende Zugriffsrechte?)

Mein vorläufiger Lösungsansatz dazu (um quasi zwei Rückgabewerte zu erzwingen):


       public bool lies_textdatei(string dateiname,ref List<string> stringlist)
        {
            if (!File.Exists(dateiname))return false; // Aus und weg
            try
                {
                    string[] stringarr =File.ReadAllLines(dateiname);
                    foreach (string element in stringarr) stringlist.Add(element);
                    return true;
                }
            catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                    return false;  // Dateifehler
                }
        }

Ist das in C# eine gute Lösung oder eher davon abzuraten? Ich bin am Anfang der Programmierung könnte die Methoden auch anders aufbauen.

2.298 Beiträge seit 2010
vor 10 Jahren

Das ref im Listenparameter kannst du getrost weg lassen. - Da List<T> ein Referenztyp ist, arbeitest du auch ohne das ref-Schlüsselwort auf genau der Liste.

Ob der Ansatz gut oder schlecht ist, möchte ich jedoch nicht beurteilen. Nur nennen, dass ich einen anderen Ansatz gewählt hätte. Ich hätte eine Klasse erstellt, die das Ergebnis der Methode entgegennimmt und als Rückgabetyp verwendet.

Als konkretes Beispiel:


public class ReadFileResult
{
     // gets or sets if the read was successfull
     public bool Success { get; set; }

     // gets or sets the lines read from the file
     public List<string> Lines { get; set; }

     // gets or sets the error message if some occured
     public string ErrorMessage { get; set; }
}

Und in der Methode würde dann folgendes stehen:


public ReadFileResult ReadFile(string sFileName)
{
     ReadFileResult result = new ReadFileResult();

     if (File.Exists(sFileName))
     {
           try
           {
              string[] stringArr =File.ReadAllLines(sFileName);
              foreach (string element in stringarr) 
                      result.Lines.Add(element);

              result.Success = true;
           }
           catch(Exception ex)
           {
              result.ErrorMessage = ex.Message;
              result.Success = false;
           }
     }
     else
     {
          result.Success = false;
          result.ErrorMessage = "Datei nicht gefunden!";
     }
     return result;
}

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

2.207 Beiträge seit 2011
vor 10 Jahren

Hallo DERKLAUS,

solche Sachen werden oft im Caching-bereich oder im Validierungsbereich angewendet. Stichwort out-Parameter. Schau dir mal etwaige TryParse-Methoden an.

Dort kann man ein bool abfragen und bekommt im true-Fall eben xyz heraus.


MyItem item;
if(!TryGetItemOutOfCache(int id, out item)
{
   // item is not in Cache
}

//item was in cache

Also in deinem Fall würde ich den Rückgabewert eines bools lassen und als Out-Parameter eben eine Liste von Strings.

Eine leere Liste ist gar nicht so schlecht, denn dann würde es bei einer folgenden ForEach nicht knallen. Besser als null auf jeden Fall. (Defensive Programmierung)

EDIT: Man kann meine und inflames2k-Lösung auch kombinieren. Out-Parameter wäre ein Klasse, die alle Infos enthält. Das wäre vielleicht noch angenehmer.

Gruss

Coffeebean

D
DERKLAUS Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Ob der Ansatz gut oder schlecht ist, möchte ich jedoch nicht beurteilen. Nur nennen, dass ich einen anderen Ansatz gewählt hätte. Ich hätte eine Klasse erstellt, die das Ergebnis der Methode entgegennimmt und als Rückgabetyp verwendet.

Als konkretes Beispiel:
...

Das sieht unbestritten sehr gut aus. Danke für die Anregung. Mit einer eigenen Klasse kann man die Funktion später ja noch beliebig erweitern.

Das ref im Listenparameter kannst du getrost weg lassen. - Da List<T> ein Referenztyp ist, arbeitest du auch ohne das ref-Schlüsselwort auf genau der Liste.

Ich dachte, das ref wäre erforderlich für die Schreibrechte.

Darf in C# die Methode ihre Parameter auch ohne ref einfach überschreiben?

16.842 Beiträge seit 2008
vor 10 Jahren

Das sind C# Grundlagen. Beachte [Hinweis] Wie poste ich richtig? 1.1.1
Das wurde Dir - mir wenn ich mich richtig erinnere - schon 2-3 mal gesagt.

[FAQ] Wie finde ich den Einstieg in C#?

5.942 Beiträge seit 2005
vor 10 Jahren

Hallo DERKLAUS

Darf in C# die Methode ihre Parameter auch ohne ref einfach überschreiben?

Die Daten sind Aussen wie Innen die gleichen. Wenn du also die Liste änderst, geht das problemlos.

"ref" musst du nur angeben, wenn du die Referenz der Liste überschreiben willst.

Das heisst, innerhalb der Methode eine Zuweisung deines Parameters selber machst, z.B.:


// Innerhalb der Methode.
stringList = new List<string>();

Siehe:

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

D
DERKLAUS Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Super Artikel parameter-übergabe ....

Insgesamt sind alle Antworten hier genau in dem Bereich, wonach ich gesucht habe. Man möge mir etwas Zeit zugestehen, das zu verarbeiten. Meine C# Karriere hat begonnen vor ca. 2 Monaten. Da braucht es noch etwas.

Daß man versehentlich die Grundlagen streift und von den Mods dafür abgewatscht wird, hat hier mit den Unterschieden von C zu C# zu tun. In C ist es definitiv anders.

Jedenfalls vielen Dank für die tollen Beiträge.

Hinweis von herbivore vor 10 Jahren

Das Thema der Semantik von ref/out wird in der FAQ ausfürhlich besprochen und fällt zudem unter Grundlagen, also [Hinweis] Wie poste ich richtig? Punkt 1.1 und 1.1.1. Daher bitte keine weitere Diskussion dazu.

1.029 Beiträge seit 2010
vor 10 Jahren

Hi,

also ich seh das mal aus der Sicht:

Datei nicht vorhanden? Aufrufer ist schuld und sollte demnach mit Hilfe einer Exception darüber informiert werden.

Datei leer? Leere Liste und fertig...

Mit dem bisherigen Konzept handelt ihr ähnlich wie bei ReturnCodes & Co - eher untypisch für .NET

  • mir würde das zumindest komisch aufstossen...

LG

R
212 Beiträge seit 2012
vor 10 Jahren

Ich nehme dazu immer
System.Tuple<T1,T2>
oder
System.Tuple<T1,T2,T3>
oder
..
..
..

D
DERKLAUS Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren
Hinweis von herbivore vor 10 Jahren

Taipi88 hat lediglich gesagt, dass eine Exception geworfen werden soll. Es wurde nichts über die Anzeige einer Exception gesagt (Aufrufer != Endbenutzer). Daher geht der folgende Einwand vollkommen der Sache vorbei.

Ich denke, in einem gut entwickelten Programm darf der Text einer Exception niemals auf dem Bildschirm erscheinen. Das Programm muß das abfangen, weil die Info für den Anwender eigentlich sinnlos ist, und dem Anwender einen Dialog zur Verfügung stellen, um das Problem zu lösen.

Anwender != Programmierer.

In einer FiBu z. B. könnte die Datei Buchungssätze nicht vorhanden sein. Da gibt es dann die Möglichkeit, eine leere Datei anzulegen oder die Datei ist im falschen Verzeichnis und muß umkopiert werden oder man importiert von der Vorgängerversion die benötigte Datei. Das muß dem Anwender so erklärt werden, daß er weiß, worum es geht.

Wenn zu der Datei Buchungssätze die Datei Kontenrahmen nicht vorhanden ist, kann so nicht gebucht werden (weil die Datei Buchungssätze auf den Kontenrahmen zugreift), aber auch hier gilt es zu unterscheiden, ob vergessen wurde, den Kontenrahmen ins Verzeichnis zu kopieren oder zu importieren oder ob beabsichtigt ist, den Kontenrahmen von Grund auf neu anzulegen.

Letzteres paßt natürlich nicht, wenn schon Buchungssätze vorhanden sind.

Abgesehen davon, die Auswertungsdateien BWA und Kontenjournale oder Steuererklärung haben andere Anforderungen und benötigen weitere Dateien.

Ich finde, ein Programm, das Exceptions in Textform auf den Bildschirm wirft, ist ein Programm, das noch nicht fertig ist.

Sowas ist für den Anwender nicht zielführend.

W
113 Beiträge seit 2006
vor 10 Jahren

Hallo DERKLAUS,

ich denke Taipi88 hat damit nicht gemeint dem Benutzer eine Exception anzuzeigen. Im anderen Fall zeigt man dem Benutzer ja auch nicht true oder false an, sondern eine Meldung in der genau steht was nicht funktioniert hat.

Eine Exception kann man ja abfangen, und auswerten um dem Benutzer dann etwas anzuzeigen dass dieser auch versteht.

Die Lösung mit den Tuplen finde ich nicht gerade schön. Da ist die Lösung von inflames2k schon um einiges eleganter.

mfg,
xan

2.298 Beiträge seit 2010
vor 10 Jahren

Datei nicht vorhanden? Aufrufer ist schuld und sollte demnach mit Hilfe einer Exception darüber informiert werden.

Das kommt meines Erachtens nach auf den Anwendungsfall an. Bei einer Bibliothek, die an beliebige Entwickler geht, ist es klar besser eine Exception zu werfen wenn die Datei nicht vorhanden ist. Entwickle ich aber ein Programm, in dem ich die volle Kontrolle habe, muss ich nicht wild mit Exceptions um mich werfen, wenn die reine Information "Daten erfolgreich gelesen" oder "Daten nicht erfolgreich gelesen" ausreicht.

Mit dem bisherigen Konzept handelt ihr ähnlich wie bei ReturnCodes & Co - eher untypisch für .NET

  • mir würde das zumindest komisch aufstossen...

Was ist denn daran so schlimm Exceptions direkt zu vermeiden? In Fällen wo der Verwender der Methode nicht wissen muss, warum es schief ging reicht es doch ihm zu sagen "Ging nicht". Die TryParse-Methoden werfen ja auch keine Exceptions und sagen dir nicht woran es lag. Nur das es eben nicht ging.

Das Lesen einer Datei ist natürlich ein schlechtes Beispiel. Denn dort will der Benutzer schon wissen, ob die Datei nicht da war oder was sonst für ein Fehler aufgetreten ist. Dort würde ich die Exception natürlich nach außen werfen.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

D
DERKLAUS Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Da ist die Lösung von inflames2k schon um einiges eleganter.

Diese Lösung hat was. Die ist elegant und wirklich C# bzw. .NET

Nach sowas hatte ich gesucht.

Wirklich schöne Lösung.

C
2.122 Beiträge seit 2010
vor 10 Jahren

Wenns nur darum geht ob die Datei vorhanden war oder nicht, kann man auch List<string> (oder byte[] oder sonstiges) zurückgeben und das im Fehlerfall auf null setzen.

2.080 Beiträge seit 2012
vor 10 Jahren

Bei solchen Problemen pflege ich gerne folgenden Gedankengang:

Eine Methode ist ein kleiner Bereich mit genau einer Aufgabe, die sie perfekt erfüllen soll.
Sie bekommt Dinge übergeben, es ist aber nicht ihre Aufgabe, dafür zu sorgen, dass diese Parameter auch richtig sind.
Sollte also irgendein Parameter nicht so sein, wie es für die Benutzung der Methode vorgeschrieben ist (z.B. falsches Format der Datei), dann ist hiermit die Arbeit der Methode beendet.

Zu schauen, ob das Format stimmt und wenn nicht, eine leere Liste zurück geben, das gehört meiner Meinung nach nicht in den Aufgabenbereich der Methode, damit kann sich der Aufrufer rum schlagen, der ihr ja die fehlerhafte Datei gegeben hat.
Die Methode stellt also fest, dass da was nicht stimmt. Sie teilt dem Aufrufer das dann mit einer Exception mit, oder eben über ein Objekt, was das Ergebnis, oder die Fehler-Infos enthält.

Ich finde die Lösung von inflames2k auch sehr elegant, aber man muss abwägen, ob eine Exception nicht doch sinnvoller ist.
Exceptions sind langsam, daher würde ich die eher nicht werfen, wenn die Performance wichtig ist und Fehler häufiger auftreten können.
Z.B. beim durchsuchen von großen Datenmengen, die auch mal fehlerhaft sein können, werfe ich auch keine Exception. Da erwarte ich Fehler, die Fehler sind keine Ausnahme mehr und gehören damit in meinen Augen in ein solches Rückgabe-Objekt, nicht in eine Exception.
Anders sieht das aus, wenn keine Fehler erwartet werden, z.B. wenn eine Internet-Verbindung ab bricht, die aber benötigt wird. Das kann ich beim entwickeln der Methode nicht einplanen ohne eine weitere Aufgabe mit einzubauen, weshalb ich auch die Exception nicht ab fange, oder eine werfe.

Mehrere Rückgabe-Werte sind für mich in den meisten Fällen gleichbedeutend mit mehrere Aufgaben und damit für mich tabu.
Ein out oder ein ref wirst du bei mir nur dann finden, wenn diese eine Aufgabe das direkt fordert und es nicht anders geht, oder Anders einfach nur noch umständlich wäre.
Die Try-Methoden sind so ein Fall, sie haben eine Aufgabe, kommen aber nicht ohne zwei Ergebnisse aus. Zum Einen, ob die Aufgabe erfolgreich abgearbeitet werden konnte und zum Anderen das Ergebnis, das hängt zusammen, alles Andere Blödsinn. In so einem seltenen Fall nutze ich auch mal out. Oder wieder der extra Typ, der dann beide Informationen bereit stellt, hier ist dann wieder der Entwickler gefragt, der entscheiden muss, was besser ist. Ich tendiere da eher zu out, weil mir irgendwann eine Klasse auch zu klein und nutzlos ist. Eine Klasse, die nur dafür da ist um ein out aus den Parametern zu bekommen, ist für mich sinnlos.

Tupel nutze ich aus dem selben Grund ist, wobei hier noch hinzu kommt, dass ich nur dann weiß, was im Tupel steht, wenn ich selber der Entwickler bin, der das geschrieben hat, eine Doku habe, wo das drin steht, oder fleißig mit dem Debugger nach schaue.
Es ist rein durch die Methoden-Definition nicht ersichtlich, was genau drin steht, da sehe ich nur den Typ und mir reicht das nicht.
In so einem Fall erstelle ich dann lieber eine Klasse, die dann zu benannten Properties noch den Vorteil hat, dass ich sie erweitern kann. Außerdem empfinde ich das als leichter in der Benutzung.

Allgemein denke ich, reicht in den meisten Fällen schon ein ganz genaues Durchdenken "Single-Responsibility-Prinzip".
Eine Methode hat exakt eine Aufgabe, kein bisschen mehr und wenn doch, habe ich was falsch gemacht oder man kann die Methode noch auf splitten.
Sollte es beim besten Willen nicht möglich sein, das zu ändern, dann geht immer noch eine Typ für die mehreren Ergebnisse als Rückgabetyp und eine Anpassung des Namens, damit die Aufgaben auch klar werden.
Ausnahme sind hier natürlich wieder z.B. die Try-Methoden.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.