Hallo zusammen,
folgendes Beispiel in C. Hier main.c
1
#include"main.h"
2
3
4
staticvoidTestFunction(TestStruct_T*t_TestVal)
5
{
6
if(t_TestVal==NULL)
7
{
8
printf(" NULL");
9
}
10
else
11
{
12
printf(" nicht NULL");
13
}
14
15
}
16
17
18
19
20
intmain()
21
{
22
TestStruct_Tt_Test;
23
24
TestFunction(&t_Test);
25
26
return0;
27
}
und noch main.h:
1
typedefstruct{
2
3
uint8_tu8_val;
4
}TestStruct_T;
Im Header wird einfach eine Struktur definiert, von der in main.c eine
Variable erzeugt wird. Diese wird denn per Referenz an eine Funktion
übergeben. In der Funktion wird die Variable auf NULL abgefragt. Macht
solch eine Abfrage überhaupt Sinn?
Meiner Meinung nach kann ich mir die Abfrage sparen, da ja nichts
dynamisch erzeugt wird. Oder sehe ich das falsch? Wenn ich mit Pointern
arbeite, dann initialisiere ich diese immer auf NULL, dann kommt das
alloc und danach frage ich immer auf NULL ab.
Oder macht die Abfrage im Beispiel doch Sinn?
Gruß Markus
TestFunction erwartet einen Pointer, keine Referenz. Daher macht die
Abfrage prinzipiell Sinn.
In deinem konkreten Fall wäre sie wohl überflüssig, aber du kannst ja
nicht garantieren, dass die Funktion nur an dieser einen Stelle und in
dieser Konstellation aufgerufen wird.
In diesem Beispiel bringt die Abfrage nichts, weil der einzige Zugriff
über t_TestVal die Abfrage ist. Das liegt aber am Beispiel.
Die Abfrage ist trotzdem sinnvoll, weil reale Programme durch reale
Programmierer selten perfekt sind und irgendwann mal die TestFunction
über ein paar Ecken herum einen Pointer verpasst bekommt, der NULL ist.
Markus schrieb:> Macht> solch eine Abfrage überhaupt Sinn?
Kommt auf die Dokumentation von TestFunction an: Wenn die sagt, dass
NULL kein gültiger Wert ist, dann hat da keiner NULL als Parameter zu
übergeben. Da Fehler aber nun mal passieren, ist es sinnvoll, die
nicht-nulli-ness des Parameters z.B. während der Entwicklungsphase zu
testen. Dafür haben wir asserts:
> Oder macht die Abfrage im Beispiel doch Sinn?
Im konkreten Beispiel nicht. Aber in der Regel solltest Du Dir schon
überlegen, was die Aufgabe einer Funktion ist, und ob einige der
Parameter der Funktion optional sein sollen oder nicht.
Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu
testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives
Programmieren. (Meiner Meinung nach, der direkte Weg in die
Wartungs-Hölle).
> Gruß Markus
mfg Torsten
Torsten R. schrieb:> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives> Programmieren. (Meiner Meinung nach, der direkte Weg in die> Wartungs-Hölle).
Weshalb die Programmiersprache D sogenannte Contracts kennt:
https://dlang.org/spec/contracts.html
A. K. schrieb:> Torsten R. schrieb:>> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu>> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives>> Programmieren. (Meiner Meinung nach, der direkte Weg in die>> Wartungs-Hölle).
Für defensives Programmieren brauchst du m.E. den Fehlercode gar nicht.
Gerade für GUI-Anwendungen ist ein if (!x) return; das genaue Gegenteil
der Wartungshölle ...
Sven B. schrieb:> Für defensives Programmieren brauchst du m.E. den Fehlercode gar nicht.> Gerade für GUI-Anwendungen ist ein if (!x) return; das genaue Gegenteil> der Wartungshölle ...
Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei
mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder
geschlossen hat.
Soetwas eignet sich meiner Meinung nach hervorragend, um Fehler im Code
möglist lange zu verbergen.
A. K. schrieb:> Torsten R. schrieb:>> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu>> testen und ggf. einen Fehlercode zurück zu geben. Sprichwort: Defensives>> Programmieren. (Meiner Meinung nach, der direkte Weg in die>> Wartungs-Hölle).>> Weshalb die Programmiersprache D sogenannte Contracts kennt:> https://dlang.org/spec/contracts.html
Der von Dir verlinkte Teil der Dokumentation passt aber eher zu meinem
Vorschlag, asserts zu verwenden. Und Contracts ist nichts, dass von D
erfunden wurde.
Torsten R. schrieb:> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder> geschlossen hat.
Und ein solches Verhalten, mitsamt klarer Aussage um Logfile, ist
irgendwie schlimmer als ein Programmabsturz?
Torsten R. schrieb:> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder> geschlossen hat.
Unangenehm, ja. Noch unangenehmer ist aber, wenn der Kunde anruft und
sagt, ihm sei das Programm jedes mal bei dem Versuch den Dialog zu
öffnen abgestürzt.
Im Grunde ist es doch aber so: Ein Pointercheck ist so gut wie immer
sinnvoll, das Fehlerhabdling danach ist aber stark programm-, bzw.
kontextabhängig.
Torsten R. schrieb:> Der von Dir verlinkte Teil der Dokumentation passt aber eher zu meinem> Vorschlag, asserts zu verwenden.
Das war auch nur ein Hinweis, wie man das in modernerer Umgebung machen
kann, um den Wildwuchs von Tests und eigentlichem Code in formale Bahnen
zu lenken. Denn das ist ja das Problem bei systematischen Tests: Wenn
man nicht sehr viel Disziplin walten lässt, sieht man irgendwann im Code
den Wald vor lauter Bäumen nicht mehr.
> Und Contracts ist nichts, dass von D erfunden wurde.
Wie man unschwer an der "Reading List" erkennen kann, die darin verlinkt
ist.
A. K. schrieb:> Torsten R. schrieb:>> Später kommt dann ein Anruf vom Kunden, der sagt: Ich hatte heute drei>> mal den Fall, dass ich Dialog X geöffnet habe und der sich sofort wieder>> geschlossen hat.>> Und ein solches Verhalten, mitsamt klarer Aussage um Logfile, ist> irgendwie schlimmer als ein Programmabsturz?
1) Stehen die Chancen ganz gut, dass der Fehler garnicht an den Kunden
ausgeliefert wurde, weil er bereits wärend der Entwicklung gefunden
wurde.
2) Kann man ggf. asserts auch im Code belassen, der an den Kunden geht.
Dann sürzt das Program zwar ab, liefert aber wertvolle Informationen, um
den Fehler zu finden und zu beseitigen.
Clemens W. schrieb:> Im Grunde ist es doch aber so: Ein Pointercheck ist so gut wie immer> sinnvoll, das Fehlerhabdling danach ist aber stark programm-, bzw.> kontextabhängig.
Wenn die Funktion dokumentiert, dass der Parameter nicht NULL zu sein
hat und der einzige Grund, warum die Funktion einen Fehler-Code zurück
liefert, dieser Test auf NULL ist, dann erweiterst Du die Komplexität
unnötig. Und dass ggf. für einen Fall, der in fehlerfreier Software
nicht auftauchen "dürfte". Wenn die Dokumentation der Funktion nun auch
noch dokumentiert, dass der einzige Fall, in dem die Funktion einen
Fehler meldet genau der Fall ist, dass der Aufrufer einen nicht
korrekten Parameter übergeben hat. Was soll der Aufrufer mit dem
Rückgabewert machen? :
1
TestStruct_T t_Test;
2
3
error_t rc = TestFunction( &t_Test );
4
assert( rc != ec_wrong_argument );
5
(void)rc;
Die Funktion kann natürlich versuchen, irgend etwas "Sinnvolles" zu
machen (aka: Shit in -> Shit out), aber wie Du ja selber schreibst, ist
das stark kontextabhängig und den Kontext legt in der Regel der Aufrufer
fest.
Und für (echte) Fehlerfälle, brauchen wir häufig Funktionen, die selber
keine Fehler erzeugen können (rollback). Hier aus Funktionen, die keine
Fehler erzeugen welche zu machen, die aus SW-Bugs, Fehler erzeugen, ist
da nicht hilfreich.
Torsten R. schrieb:> Es gibt auch Kollegen, die Dir raten würden, den Parameter immer zu> testen und ggf. einen Fehlercode zurück zu geben.
Das führt direkt in die Hölle. [Full Ack]
Gerade letztens musste ich wieder durch solch einen "Müll". Jeder
Funktion gibt fein säuberlich ihren Error Code zurück (für extrem
unwahrscheinliche Fehler oder interne Inkonsistenzen) und die
Problematik wird auf den Aufrufer verschoben (der kaum Infos hat um das
Problem sinnvoll zu behandeln).
Das ist doch echt ein Witz: Aufrufer übergibt einen Null Pointer und
bekommt den Error Code: Null Pointer, und macht jetzt was? Er gibt
seinen Aufrufer einen generischen Error Code zurück. Und was macht der
jetzt?
Ein guter Kompromiss ist assert(). Das Problem sollte beim Test
auflaufen. Im Produktionscode kann man dann mit NDEBUG übersetzen (wenn
man denn will).
Imho besser (aber auch aufwändiger und unübersichtlicher -- was wieder
kontraproduktiv sein kann) ist das Kapseln des Skalars in einer struct
(bspw. als typisierte non-NULL Pointer). Da kann der Compiler das
Problem schon vorneweg sehen. Das "lohnt" nur in umfangreichen
Projekten.
Bei (externen) Interfaces... ok, ja, da kann und sollte man wohl Error
Codes zurück geben.
Torsten R. schrieb:> 1) Stehen die Chancen ganz gut, dass der Fehler garnicht an den Kunden> ausgeliefert wurde, weil er bereits wärend der Entwicklung gefunden> wurde.
Schon mal das Tomcat-Logfile (catalina.out) einer ganz normalen beim
Kunden installierten Tomcat/Java-Anwendung gesehen? ;-)
Torsten R. schrieb:> Wenn die Funktion dokumentiert, dass der Parameter nicht NULL zu sein> hat und der einzige Grund, warum die Funktion einen Fehler-Code zurück> liefert, dieser Test auf NULL ist, dann erweiterst Du die Komplexität> unnötig. Und dass ggf. für einen Fall, der in fehlerfreier Software> nicht auftauchen "dürfte". Wenn die Dokumentation der Funktion nun auch> noch dokumentiert, dass der einzige Fall, in dem die Funktion einen> Fehler meldet genau der Fall ist, dass der Aufrufer einen nicht> korrekten Parameter übergeben hat. Was soll der Aufrufer mit dem> Rückgabewert machen?
Da hast Du natürlich recht, ein Fehler "nullptr übergeben" von einer
Funktion, die keinen nullptr erwartet ist irgendwie sinnlos. Dass die
Funktion intern diesen Fall aber dennoch handelt, halte ich für guten
Programmierstil. Ein Absturz wird verhindert und in diesem Fall ist der
Aufrufer einfach selbst schuld.
V.a. da Dokumentation und Programmierung teilweise zwei Paar Schuhe
sind. Selbst in der Qt Doku (die m.M. nach sehr gut und umfangreich ist)
finden sich immer mal wieder Fehler (auf die auch auch schon
reingefallen bin).
He, defensive Programmierung ist nicht "jede Funktion gibt einen
Fehlercode zurück". Das ist einfach völlig die falsche Interpretation
des Begriffs. Defensive Programmierung ist "ich versuche mal in den
Fällen wo das noch irgendwie geht irgendwas zu machen was nicht allzu
schlimm ist". Natürlich kann deine Funktion, die eine Datei öffnet ein
assert() machen wenn die Datei nicht vorhanden ist, weil das hätte ja
wasweißichwer vorher schon prüfen sollen. Sie kann aber auch einfach ein
return machen und eine Warnung in's Log schreiben. Das ist in den
meisten Fällen einfach das bessere Verhalten.