Anzeige

C++ im Wandel


sheel

I love Asm
#1
Mal etwas Offtopic von mir...

was halten die C++ - Kenner hier von der Richtung, in die die Sprache entwickelt wird?

...

C++11 war ja im Großen und Ganzen wirklich nett. Aber imho bringt jede neue Standardversion (14, 17, das kommende 20...) mehr und mehr Unsinn - und es könnte ruhig mal wieder aufgehört werden, an der Sprache herumzudoktern.

Zwei grandiose und komplett unnötige "Fails", die mir gerade einfallen, wären:
a) Der relativ neue Variablentyp "byte". Ist es ein Byte groß und speichert Werte von 0 bis 255? Nein, in C++ ist beides anders.
b) Es ist eine Änderung geplant, dass (unter bisher noch unbekannten Bedingungen) 4294967295 "kleiner" als 0 sein kann. Und zwar gerade "nicht" weil irgendwas zu signed konvertiert wird. Wtf.

...

Und da ist natürlich auch noch das anscheinend unwichtige Problem, dass "C++ lernen" immer mehr zur Lebensaufgabe wird :D

Von riesigen Umbauten wie Modulen fang ich wohl besser nicht an.
 
Zuletzt bearbeitet:

cwriter

Erfahrenes Mitglied
#2
was halten die C++ - Kenner hier von der Richtung, in die die Sprache entwickelt wird?
Es ist ein zweischneidiges Schwert. Manche Dinge, die in Entwicklung sind, wären echt nett, z.B. transactional memory
Anderes wiederum ist völlig sinnbefreit (std::iota, std::byte).
b) Es ist eine Änderung geplant, dass (unter bisher noch unbekannten Bedingungen) 4294967295 "kleiner" als 0 sein kann. Und zwar gerade "nicht" weil irgendwas zu signed konvertiert wird. Wtf.
Hö? Erinnert mich an die Leute, die sagen, dass abs() eine schlechte Funktion ist, weil int abs(int i) { return -i; } bei INT_MIN einen Overflow hat. Dass man das mit einem Rückgabewert von unsigned int beheben kann, ist da egal.
Falls die Idee ist, -1 zu fangen: Dafür gibt es 0xffffffff.

Und da ist natürlich auch noch das anscheinend unwichtige Problem, dass "C++ lernen" immer mehr zur Lebensaufgabe wird :D
Ich denke, kein C++-Programmierer nutzt alles existente C++, aber ja: Wenn man Code lesen will, kommt man sehr schnell an noch nie Gesehenes. Hach, die unschuldige Einfachheit von C++89.

Ich sehe eher ein Grundsatzproblem: C++ ist wirklich gross geworden. So gross, dass man es nicht für Low-Level einsetzen kann (neben anderen Problemen wie RTTI). Der Standard umfasst sehr viel nutzloses Zeug (wie std::byte), statt die Wurzel des Problems zu packen und z.B. Bit-Order festlegen zu können.

Ich denke nicht, dass C++ zu einer schlechteren Sprache wird. Ich denke aber, dass dem Komitee nicht ganz klar zu sein scheint, in welche Richtung sie gehen wollen: Universalsprache oder Performancesprache.
Grundsätzlich halte ich es mit neuen Features immer so: Sie sind gut, kann man sie benutzen (wie z.B. [[nodiscard]]). Sind sie sinnlos/schlecht, benutzt man halt etwas anderes.
Nur einen C++-Compiler mit STL und Co. würde ich heutzutage nicht mehr selbst machen wollen. :p

Von riesigen Umbauten wie Modulen fang ich wohl besser nicht an.
Ich hoffe, die kommen nicht. Die Cyclic Inheritence ist bei C++ ja noch schlimmer, da auch die Member nicht zyklisch sein dürfen. Das momentane System hat zwar durchaus Macken, aber es zwingt einen zu einigermassen sauberem Code.

cwriter
 

Technipion

Erfahrenes Mitglied
#3
Meine Meinung dazu ist leider auch noch nicht so ganz gefestigt. Ich habe beruflich viel mit der Datenverarbeitung in der Teilchenphysik zu tun, und dort kommen vor allem C, C++ und Python zum Einsatz. In letzter Zeit auch immer öfter Java. C ist imho DIE vielseitigste Sprache, die es im Moment gibt. Ausreichend low-level, ausreichend high-level, aber nicht zu kompliziert zu lesen und ziemlich leichtgewichtig. Aber natürlich hat jede Sprache ihre Anwendungen. Ich möchte auch Python nicht mehr missen müssen.
Von C++ bin ich schon sehr lange ein großer Fan, für mich war es immer die "Erweiterung" von C (liegt ja gewisserweise im Namen). Trotzdem ist viel von dem alten Kram bis heute noch in C. Teilweise meckern die Distros wenn man nur ein int im Kopf einer for-Schleife definieren will :p
Als dann C++11 rauskam war ich ziemlich happy darüber und habe mich auf die neuen Features gefreut. Auch wenn es ewig gedauert hat bis ich das doofe auto endlich gecheckt hatte. Aber der wichtigste Punkt war: Es tut sich noch was. Die Sprache lebt noch, und jemand arbeitet aktiv daran. Und naja, es wurde ehrlichgesagt auch Zeit. Also C++11 habe ich absolut begrüßt, und sehr gefallen hat mir die (nahezu) vollständige abwärtskompatibilität. (Sehr wichtig! Müssen sie unbedingt weiterhin einhalten!)
Naja dann kam C++14 und habe mir so gedacht: Komm, das waren viele Änderungen auf einmal, und die wollen jetzt bloß noch etwas nachbessern... was soll's. Aber mittlerweile denke ich auch das Komitee will jetzt irgendwas überkompensieren. Alle 3 Jahre ist einfach zu oft. Vielleicht hätte man alle 5 oder 8 Jahre anstreben sollen.

Obwohl wir alle dazu angehalten sind das "neue" C++11 zu benutzen, sind die meisten Quellcodes bunt durcheinander gewürfelt. Manche nutzen auto, manche nicht. Manche benutzen Verschiebesemantik, andere nicht. Und es gibt so viele Stile wie es Programmierer gibt (und gab). Richtiger Spaghetticode teilweise.

Manchmal frage ich mich, ob das "neue" C++ (ist ja nicht neu, ist der de-facto Standard) nicht vielleicht im Geheimen den Konkurrenten (Java, Python, etc.) ein kleinwenig nacheifert. Wäre meines Erachtens schade. Auf jeden Fall müsste man eine klare Linie fahren.

Gruß Technipion
 
#4
Danke schonmal für die Antworten ... :)

Falls die Idee ist, -1 zu fangen: Dafür gibt es 0xffffffff.
Ja, es hat mit Underflow zu tun, mehr Details zu dem angeblichen Problem weiß ich aber auch nicht (und nein, eigentlich ist 0xffffffff nicht portabel :p)
Nur scheint das Kommitee zu vergessen, dass C++ nicht Mathematik ist. Variablen haben eben Eigenschaften aufgrund ihrer Größe usw., C++ hat genaue Regeln zu impliziten Umwandlungen bei Operationen mit verschiedenen Typen usw., und die ganzen bisherigen Regeln mit einer Ausnahme für irgendeinen seltsamen Spezialfall zu durchbrechen ist ... :(
Ich denke, kein C++-Programmierer nutzt alles existente C++,
Wie auch :D

So gross, dass man es nicht für Low-Level einsetzen kann
Was irgendwelche Microcontroller angeht war aber das erste C++ auch schon ein großer Schritt weg. C kann man sehr "verbiegen" - C++ hat aber ein paar Garantien im Standard die sich auch vielen Geräten einfach nicht erfüllen lassen.
neben anderen Problemen wie RTTI
Btw., es gibt Bestrebungen in Richtung von vollen Compile-time Reflections (und evt. später auch für dynamischere Sachen Richtung virtual-Auflösung in Klassen usw.)
Klingt derzeit nach einer von den besseren Änderungen (sehr sogar) - solang dafür nichts anderes grob kaputtgemacht wird.

Ich denke nicht, dass C++ zu einer schlechteren Sprache wird. Ich denke aber, dass dem Komitee nicht ganz klar zu sein scheint, in welche Richtung sie gehen wollen:
Jedes Mitglied in eine andere :D

Sind sie sinnlos/schlecht, benutzt man halt etwas anderes.
Zum Glück wird meistens hoher Wert auf Abwärtskompatibilität gelegt, ja ...

Leider bin ich mir ziemlich sicher, dass das keiner mehr aufhalten kann. Auch wenn es ziemlichen Widerstand dagegen gibt (speziell außerhalb vom Komittee).
... aber, wie schon im vorderen Punkt, keiner zwingt einen zum Verwenden.
Und beim Vergleichen vom Aufwand (Einlernen, Tools, Code) mit dem Nutzen ist die Möglichkeit jedenfalls da, dass das eine Totgeburt wird.Btw., federführend ist ein MS-Mitarbeiter. Natürlich. :D

Obwohl wir alle dazu angehalten sind das "neue" C++11 zu benutzen
Warum :D

Sicher gibt es manche "Blasen", wo man mit einer anderen Meinung virtull totgeprügelt wird, aber sonst ...
Und bisher hat noch keines der nennenswerten neuen C++-Features irgendein anderes "vollständig" ersetzt. Es gibt immer Fälle, wo das alte besser (oder sogar das einzig richtige) ist. Und natürlich spielt auch der Stil eine große Rolle...

Manche nutzen auto, manche nicht. Manche benutzen Verschiebesemantik, andere nicht.
...und manche benutzen es nur dann, wenn es etwas einfacher/schneller/etc. macht. Imho ist genau das der Sinn von einem Werkzeug, und nicht die krampfhafte Verwendung wo immer es auch geht, auch wenn dadurch alles umständlicher wird.

Manchmal frage ich mich, ob das "neue" C++ (ist ja nicht neu, ist der de-facto Standard) nicht vielleicht im Geheimen den Konkurrenten (Java, Python, etc.) ein kleinwenig nacheifert.
Naja, alle großen Sprachen beeinflussen sich irgendwie; das war schon immer so. Und persönlich seh ich auch kein Problem darin, die guten Teile von anderen Sprachen als Anregung zu nehmen wie man das eigene Ding machen könnte.
 

cwriter

Erfahrenes Mitglied
#5
Ja, es hat mit Underflow zu tun, mehr Details zu dem angeblichen Problem weiß ich aber auch nicht (und nein, eigentlich ist 0xffffffff nicht portabel :p)
Dann geht halt sowas wie INT_MAX/LONG_MAX und co. (Wobei: Zeig mir ein modernes System, das nicht auf two's-complement basiert...).
Das Problem greift aber auch tiefer: CPUs werden nicht plötzlich beginnen, ihre Definition zu ändern. Die Sign/Overflow/Carry-Flags werden ja nach anderen Regeln gesetzt.

Was irgendwelche Microcontroller angeht war aber das erste C++ auch schon ein großer Schritt weg. C kann man sehr "verbiegen" - C++ hat aber ein paar Garantien im Standard die sich auch vielen Geräten einfach nicht erfüllen lassen.
Ja, aber manchmal vermisse ich auf Lowlevel echt Klassen oder zumindest namespaces. Alles mit defines vollzupflastern ist mir ein bisschen zu mühsam, und enums übernehmen ja auch den Elternscope. Mehr bräuchte es nicht, aber wenn man sich einmal durch einige Kerneldriver liest, bestehen die ersten 300 Zeilen aus defines, von denen man nicht genau weiss, zu welchem Registerfile sie jetzt gehören, dann wird immer mit Offsets gearbeitet (Registerfile_base + register_offset) usw. Und warum? Weil der C-Standard nicht stabil genug ist, um z.B. bitfields und Konsorten für so etwas verwenden zu können.
Sowas wie das hier wäre echt etwas nettes: https://www.systems.ethz.ch/sites/default/files/file/aos2012/Reading/week3/TN-002-Mackerel.pdf
Aber das kommt wohl nie in den Standard.

Btw., es gibt Bestrebungen in Richtung von vollen Compile-time Reflections (und evt. später auch für dynamischere Sachen Richtung virtual-Auflösung in Klassen usw.)
Klingt derzeit nach einer von den besseren Änderungen (sehr sogar) - solang dafür nichts anderes grob kaputtgemacht wird.
Das wäre in der Tat sehr interessant.
Btw., federführend ist ein MS-Mitarbeiter. Natürlich. :D
Herb Sutter oder Lavadej(?, finde ihn gerade nicht) ? Wobei, wenn VS das standardmässig einführt, dann wird es ziemlich sicher ein Standard, zumindest unter Windows.

Zum Glück wird meistens hoher Wert auf Abwärtskompatibilität gelegt, ja ...
Ist zwar schon etwas länger her, aber: export (bzw. die Entfernung davon) :p

Manche nutzen auto, manche nicht.
...und manche benutzen es nur dann, wenn es etwas einfacher/schneller/etc. macht. Imho ist genau das der Sinn von einem Werkzeug, und nicht die krampfhafte Verwendung wo immer es auch geht, auch wenn dadurch alles umständlicher wird.
Auto kann beissen. Wenn ich in irgendeiner Form auf den Typ angewiesen bin, dann nehme ich den expliziten Typ. Aber auto ist extrem praktisch bei Templates. Manchmal spart man sich damit schon 20 Zeichen lange Typen.


cwriter
 

Technipion

Erfahrenes Mitglied
#7
Btw., federführend ist ein MS-Mitarbeiter. Natürlich. :D
Ich glaube leider nicht, dass sich das jemals wieder ändern wird. Die Industrie hat ja überall ihre Finger drin. Das ist prinzipiell nichts schlechtes, solange der Herr MS Entwickler auch wirklich an einem Standard interessiert ist, und keinen Alleingang unternimmt (jopp, ich bin vertraut mit der Vergangenheit).

Was z.B. die Geschichte mit auto angeht, da ziehe ich jetzt ganz dreist das Buch "Effective Modern C++" von Scott Meyers heran. Auf Seite 38 schreibt er: "auto variables have their type deduced from their initializer, so they must be initialized." Ist nur eines von vielen Beispielen, aber es gibt zumindest Motivatoren auto zu benutzen. Das macht natürlich "altes" C++ nicht ungültig, es ist lediglich eine neue Art Stil. Und wie schon gesagt, es gibt wohl so viele Stile wie es Programmierer gibt. Wir sind jedenfalls dazu angehalten. Unterm Strich ist es besser es gibt in einem größeren Projekt Vorschriften (auch wenn sie eigenartig sind), als dass jeder macht was er will. Meiner Hand sind deshalb schon solche Zeilen entsprungen:
C++:
auto i = (int)0;
Man kann sich natürlich darüber streiten...
Wo auto definitiv ein Vorteil ist, ist bei den berüchtigten foreach-Schleifen (mittlerweile werden sie range-based for loops genannt, am Anfang hießen sie aber glaube ich alle noch foreach):
C++:
langer_und_ermüdender_typ a;
// fülle a

for (const auto& i : a) {
    // benutze i
}
Ich hatte leider noch keine Zeit mich richtig in C++17 einzulesen (chronische Zeitknappheit :D), aber ich habe aufgeschnappt, dass da sogar folgendes gehen soll:
C++:
std::map<foo, bar> test = { /* Zeugs */ };

for (const auto& [key, value] : test) {
    std::cout << key << " = " << value << std::endl;
}
Damit sind wir dann ja schon verdammt nahe an der pythonischen Variante dran:
Python:
a_dict = {'a': 1, 'b': 2, 'c': 3}

for key, value in a_dict.items():
    print(key, '=', value)
Wie ich schon sagte, ich begrüße es sehr dass aktiv an der Sprache entwickelt wird. Und obiger Code ist natürlich in C++ enorm (!!) schneller als in Python, dem Compiler macht niemand was vor. Und prinzipell finde ich es auch gut, dass das Komitee ständig nach neuen Funktionen sucht um die Sprache zu ergänzen - SOLANGE sie abwärtskompatibel bleiben. Ich glaube die Diskussion läuft eigentlich eher auf die Frage raus: Muss die Sprache alle 3 Jahre erneuert werden? Bzw. muss man dann mit Gewalt alle neuen Features (teilweise noch unausgereift) reindrücken?

Wie ich schon sagte, ich glaube das Komitee eifert dann ein wenig Java/Python/etc. nach. In Python z.B. kommen sehr regelmäßig neue Versionen heraus (in Python3, Python2 wird nur zur Abwärtskompatibilität behalten - sehr lobenswert). Ist ja nichts schlechtes, aber braucht C++ das?

Und warum hauen die soetwas wie std::byte rein? Es wirkt einfach nicht komplett durchdacht...
Unser einziger Trost ist wohl, dass die Einführung eines std::byte nichts kaputt macht. Und imho braucht das Komitee nicht auf Teufel komm raus einen neuen Standard verabschieden, die Konkurrenz ist auch nicht schneller. Man bedenke nur, dass Java erst in Version 8 ein switch-Statement erhalten hat.
Wie wir alle wissen, bleiben die meisten Menschen bei der Erlernung von C++ spätestens bei Templates hängen, und kommen gar nicht erst zu auto und co. Von daher finde ich es viel wichtiger den Neulingen die Konzepte von Clean Code beizubringen, als ihnen unbedingt C++17 Features einzutrichtern. Und "klassisches" C++ ist nicht schlechter, wenn es beherrscht wird.

Ich freue mich aber immer über eine Diskussion über die Sprache, nur so entwickelt sie sich schließlich weiter!

Gruß Technipion
 
#8
Ich glaube leider nicht, dass sich das jemals wieder ändern wird. Die Industrie hat ja überall ihre Finger drin. Das ist prinzipiell nichts schlechtes, solange der Herr MS Entwickler auch wirklich an einem Standard interessiert ist, und keinen Alleingang unternimmt (jopp, ich bin vertraut mit der Vergangenheit).
Ich will ja auch nicht sagen, dass es schlecht ist - aber ja, eben keine MS-Features im IE6-Style (nur ein Beispiel von vielen)
Ich hatte leider noch keine Zeit mich richtig in C++17 einzulesen (chronische Zeitknappheit :D)
So wirklich solide, wie ich es gern hätte, ging auch bei mir noch nicht :D
aber ich habe aufgeschnappt, dass da sogar folgendes gehen soll:
Ja
Damit sind wir dann ja schon verdammt nahe an der pythonischen Variante dran:
Was da zB. indirekt sichtbar ist, was in C++ derzeit nicht möglich ist (denk ich zumindest): Strukturen/Klassen mit mehreren Memberwerten zu initialisieren und dabei immer den Membernamen anzugeben (statt ein paar Werte einfach so hinschreiben und "raten" ob die Reihenfolge passt).

Und warum hauen die soetwas wie std::byte rein? Es wirkt einfach nicht komplett durchdacht...
So wie zB. vector<bool> und ziemlich viel anderes... :rolleyes:

Speziell Byte macht im Kontext vom C++-Standard ja eigentlich sehr viel Sinn - nur muss das Komitee sich endlich mal kollektiv klar werden, dass 99.9999% der Programmierer was anderes unter Byte verstehen.
Von daher finde ich es viel wichtiger den Neulingen die Konzepte von Clean Code beizubringen, als ihnen unbedingt C++17 Features einzutrichtern.
Für letzteres ist eine neue Arbeitsgruppe im Komittee geplant :D (oder vielleicht gibts es sie ja inzwischen schon...)
 

cwriter

Erfahrenes Mitglied
#9
Ich führe die Diskussion mal weiter, da ich das einfach loswerden muss:

Ich habe mittlerweile Qt Creator 4.6rc installiert, wo mit Clang5.1 ein "besseres" Code Model vorhanden ist. "Besser", weil es 1.5GB RAM frisst (1GB mehr als mit dem klassischen Modell) und jede Code Completion einige Sekunden geht und einen Kern voll auslastet. Naja, ist ja alles noch Beta(TM).
Jedenfalls gibt es jetzt so nette Codestyle-Warnings:
upload_2018-4-21_12-33-15.png
(m_serial_monitor ist ein std::unique_ptr).

Hier sehe ich mittlerweile das grösste Problem von C++: Je neuer der Standard, desto mehr wird die Herkunft geleugnet. Ich habe echt nichts gegen Syntaxerweiterungen, die Boilerplate wegnehmen, wie die foreach-Loops.
Aber irgendwie wird krampfhaft versucht, Pointer zu verstecken, eigentlich beginnend schon mit Referenzen in C++98(?), dann weiterführend mit auto_ptr, shared_ptr, unique_ptr, etc.
Ich finde diese Hilfen sehr nett, gerade durch RAII kann man sich viele Kopfschmerzen ersparen, aber den Grundsatz "never use new/delete", der u.a. auf SO so dermassen beliebt ist, ist doch irgendwie falsch.

Das artet dann in solchen Artikeln aus: https://www.quora.com/Why-is-C++-considered-a-bad-language
(Wobei der nette Mensch vergisst, dass GCs dafür einfach irgendwann mal einspringen, und der Optimizer ziemlich gut ist für Function calls).

Wie schon gesagt, was gebraucht wird, ist ein C, das syntaktisch einfacher/kürzer ist, namespacing/(Classes) besitzt, und eine Art Smartpointer hat, um Leaks zu vermeiden. Dazu noch ein bisschen Würze wie Typesafety, meinetwegen auch Templates. Das wäre dann ein C++. Exceptions meide ich persönlich wie die Pest, da es in der Regel zu sehr schlimmen Code führt (try-catches als control flow oder für Rückgabewerte (schauder)). CryptoPP spielt in dem Gruselkabinett recht weit vorne mit, mit Exceptions für falsche Schlüssel, self-deleting Parameter Pointers etc.

An unserer Uni hatten wir kürzlich eine Code Review für Java-Code. Codefragmente, die mehr als 20 Zeilen lang waren, wurden als schlecht erachtet, da man mehr in Funktionen auslagern sollte. OOP wird nicht mehr als Ausweg für zu unübersichtlichen Code, sondern als Standard gelesen.
Jedes mal, wenn ich sage, meine Klassen haben etwa 50 Funktionen und manche Funktionen sind 200 Zeilen lang heisst es, dass das schlechter Stil ist. Wenn ich in einer Gruppenarbeit eine Referenz auf ein Objekt übergebe, das eine State-Variable hat, die dann innerhalb der Funktionen verändert wird, kommt jemand daher und refactort das Ganze, sodass bei jedem Iterationsschritt (State-Change) ein Objekt erstellt wird, das den letzten State und den nächsten State hält, das dann als State übergeben wird, da das ja eher Funktional und daher besser sei. Da die Klasse aber auch den Kontext hielt, wird die Referenz dann einfach per Konstruktor an den Arbeiter übergeben. Somit macht man dasselbe quasi doppelt, kopiert sich und den Speicher zu Tode, aber es sieht funktionaler aus! Ziel erreicht, Performance ist ja eh egal.

Wenn ich aber jeweils die privaten Projekte vergleiche, machen meine Projekte echte Arbeit (CPU-Emulation, Logik-Emulation) in einem annehmbaren Zeitrahmen (1 durchschnittliche ARMv6-Instruktion auf einem 7500U @ 2.7GHz in ~200ns, keine besonderen Performancemassnahmen), während die Fans von OOP und 2-Zeilen-Funktionen einfach ein paar Standardfunktionen zusammenkleben und Bilder ausgeben oder aus Prinzip mit JSON arbeiten.
Ich will damit nicht sagen, dass ein aufgeräumter Stil schlecht ist, sondern, dass es auf den Anwendungsbereich ankommt. Man kann keine komplexen Bitoperationen in 5 Zeilen abschliessen; man kann nicht SmartPointer verwenden, wenn man innerhalb eines Scratch Buffers arbeiten will, etc. Wenn man aber ein PoC machen will, dann ist JSON legitim. Für Apps am Mobilnetz ist es mittlerweile Standard, danke auch für den Datenverbrauch.
Aber irgendwie ist dieser "Nutz doch die riesige Standardbibliothek für alles" aus Java, C# und mittlerweile Rust ein erklärtes Ziel von C++.
Und genau das, denke ich, ist das Problem des Komitees. Statt die Stärken von C zu betonen und die Schwächen auszumerzen, will man unbedingt modern sein und alles haben, was andere auch haben, ungeachtet dessen, was C so gut macht.
Das artete dann auch in diesem Aprilscherz aus: https://www.heise.de/developer/artikel/No-New-New-Das-Ende-von-Zeigern-in-C-4009347.html

Was mich aber wundert, ist der scheinbare Konsens auf dem OOP/Funktional/"Moderne Sprache"-Wahn. Kaum jemand jemand, mal von den Kernel-Entwicklern abgesehen, scheint den alten Stil zu verteidigen.
Ich weiss nicht, ob es daran liegt, dass heutzutage niemand mehr direkt auf Hardware arbeitet (da ist mir oft selbst C zu ungenau, bzw. GCC mit seinen Register-readbacks after write) oder dass mittels IDEs das Nachverfolgen von Codepfaden einfacher ist, aber irgendwie missfällt mir diese Entwicklung. Aber wer weiss, vielleicht bin ich auch der einzige ¯\_(ツ)_/¯

Gruss
cwriter
 
#10
Danke für die Fortführung :D
...
Zu Stackoverflow und Pointer-verstecken zuerst ... hab da so eine kleine Theorie, die sich relativ gut hält
  • Leute kennen sich mit Pointern nicht aus. Bzw. dass die überwiegende Mehrheit der Menschen sich damit nicht auskennt ist sowieso klar, aber auch beim Rest sind viele dabei, die es zwar in normalen Fällen richtig anwenden können, sichd abei aber nicht so recht wohl fühlen
  • Daraus entsteht Angst - Angst etwas falsch zu machen, deswegen Probleme zu bekommen (alles von "Im Codereview blöd dastehen" bis zu "gefeuert werden" etc.). Und/oder sowas ist schon passiert und man sucht die Schlud bei der Sprache, statt bei sich.
  • Im Internet gibts bekannten Blog X, der sagt, Pointer sind schlecht. Dadurch fühlt man sich bestätigt, Pointer müssen weg.
  • Weitere Verstärker Richtung "Generation Schneeflocke" usw.
  • Und natürlich gibts noch die "Hipsters", die bei teilweise/ganz überlappenden Features nur das Neuere als die einzige Wahrheit akzeptieren.

.... um fair zu sein, die Hipstereinstellung ist in offiziellen Sachen von Kommitee nicht zu finden (afaik). Pointer sind im Standard also sind sie erlaubt, Punkt. ... Aber da das Kommitee auch aus Leuten besteht, und immer wieder mal neue dazukommen die anfangen Proposals schreiben, wird das Ganze immer mehr unterwandert; von zB. solchen Leuten die auch diese Clang-Warnungen machen.

Speziell SO betreffend, würde mir über die einfach nicht den Kopf zerbrechen. Zwar eine der größten "Communities" (haha, vergifteter gehts nicht mehr), aber im Durchschnitt auch eine der dümmsten (auch immer wieder mal mit öffentlichen Statistiken bestätigt).

...

Zu diesem Quorapost, bei einem (C++-unabhängiger) Punkt muss ich dem sehr zustimmen: Krampfhafte Patternisierung ist böse (was sich übrigens wieder darauf zurückführen lässt, dass viele Leute OOP nie wirklich verstanden haben. So wie Pointer. usw.usw.).

...

Zu Resourcenverbrauch, meine einfache Erklärung dazu, die Meisten mussten einfach noch nie was schreiben was schnell+speichersparend ist. In ihren Entwicklungsumgebungen war alles akzeptabel, kein Chef/Kunden/etc. haben sich über lLangsamkeit beschwert, usw ... und deshalb verstehen sie es nicht, warum man BequemesLangsamesFeatureX nicht als zwingenden Teil in die Sprache einbauen kann.

(zu den Kunden - was nicht bedeuten soll, dass alles ok ist. Mein aktuelles Topproblem auf der Arbeit: Ein RestAPI-Ding, bei dem der einfachste Request 20sec braucht. Und im Idealfall würde ich gern für ca. jede Minute Arbeit einen Request starten und brauch das Ergebnis dann zum weitermachen. Es zu verbessern, oder sich beim Ersteller beschweren, ist leider beides explizit nicht genehmigt :rolleyes:
... Und da ist auch noch der bitte-mehr-als-16GB-Firefox :D
...)

...

try-catches als control flow oder für Rückgabewerte (schauder)
Außer den zwei Sachen bleibt aber nichts übrig :)
... wie immer gibts Situationen, wo es gut geeignet ist, und andere.

(Die Exception für falsche Keys hört sich für mich gar nicht so schlimm an...)

An unserer Uni hatten wir kürzlich eine Code Review für Java-Code. Codefragmente, die mehr als 20 Zeilen lang waren, wurden als schlecht erachtet, da man mehr in Funktionen auslagern sollte.
Jaa...das hab ich auch mal gehört ... zusammen mit der Aussage, dass man mehr als drei verschachtelte Level von Schleifen/if/... in der Realität nie sehen wird :D lol

Auf die Frage, was man bei Funktionen mit 20 Parametern (bei der man die Parameterliste nicht ädnern kann!) und gleich am Anfang 200 Zeilen Validierung der Parameter, bevor es zum eigentlichen Sinn der Funktion kommt, tun soll, konnte mir bisher niemand von solchen Leuten eine überzeugende Antwort geben.
meineFunktion_validateParam1_Part1(), meineFunktion_validateParam1_Part2(), meineFunktion_validateParam1_Part3()? Die Werte für die lokalen Variablen immer oorgendwie mit Referenzen mitübertragen? Die 60 validate-Funktionen dann in 3 weitere zu je 20 Funktionsaufrufen zusammenfassen, die drei wieder in einer, und die dann in der Hauptfunktion aufrufen? Und am besten jede Funktion noch mit einer eigenen Klasse?
Wohl kaum.

...Erinnert mich an einen Kollegen, der tatsächlich jedes if-foo-then-error-4 in zwei (!) Klassen ausgelagert hat (eine für das if, eine mit den Fehlerdaten). Könnte ja wiederverwendbar sein. War es teilweise auch, aber natürlich keine Nettoersparnis an Zeilen und Arbeitszeit. Inzwischen hat sogar er eingesehen, dass es Unsinn ist.

Jedes mal, wenn ich sage, meine Klassen haben etwa 50 Funktionen und manche Funktionen sind 200 Zeilen lang heisst es, dass das schlechter Stil ist.
Hast du auch mal einen Grund bekommen? Also einen außer "ich mag es nicht" und/oder "RandomPersonImInternet mag es nicht"?
Erinner sie doch bitte mal, dass wir nicht Modedesigner sind, sondern was praxistaugliches machen wollen.

(Zum Niveau von "RandomPersonImInternet": Ein Artikel a la "Austrias [X] is the only thing worldwide that has [Y]" auf einer englischen Zeitungsseite. Ca. 50 Nutzerkommentare, die Hälfte davon waren Witze über Känguruhs. ... Ein paar andere Kommentare waren der Meinung, dass der Artikel nicht stimmt, weil auf den ganzen Bildern teilweise deutscher Text zu sehen war und man in Austria ja ganz offensichtlich nicht german spricht).

...
du bis nicht allein :)
 

cwriter

Erfahrenes Mitglied
#11
(haha, vergifteter gehts nicht mehr)
Jo. Gerade heute wieder einen Kommentar auf eine Antwort gelesen:
upload_2018-4-21_21-54-22.png

Krampfhafte Patternisierung ist böse
Mhm, hatte erst gerade kürzlich die Pattern in einer Vorlesung. Da wurde z.B. ernsthaft gesagt, man solle das Factory-Pattern benutzen, um einfacheres Refactoring zu erlauben. Nur in einem einzigen Fall finde ich das zu bevorzugen: Wenn idiomatisch ein ::create() benutzt werden kann, das einige immergleiche Parameter des Basisklassenkonstruktors bindet.
(Manche Pattern, wie das Visitor-Pattern bei ASTs, sind für einige wenige Anwendungsbereiche extrem gut geeignet).

Aber selbst Wikipedia schreibt korrekt:
Der erfolgreiche Einsatz von Entwurfsmustern in der Vergangenheit kann dazu verleiten, die Entwurfsmuster als Wunderwaffe und Garant für gutes Design anzusehen. Unerfahrene Entwickler können geneigt sein, möglichst viele bekannte Muster zu verwenden, und dabei übersehen, dass in ihrem Fall vielleicht eine elegantere Lösung ohne den Einsatz von Mustern möglich wäre. Entwurfsmuster garantieren nicht, dass der Entwurf gut ist. Insofern ist die Anwendung zu vieler oder ungeeigneter Entwurfsmuster ein Antimuster.
Nur kommt das in den Vorlesungen nicht so rüber. Es heisst schlicht, Patterns seien das einzig Wahre, dann bekommt man in einer Prüfung irgendeinen 100-Zeilen-Code vorgesetzt und muss dann sagen, dass es schlecht ist, new String statt StringBuilderFactory().build().make() zu benutzen, weil letzeres ja so viel erweiterbarer sein soll.
Das sind die Professoren, die ihre Semantischen Analyzer auch ohne Overflow arbeiten lassen, weil es ja so viel Sinn macht, einen möglichen Resultatbereich eines Integers von [0, \inf[ zu haben :confused:
Ein RestAPI-Ding, bei dem der einfachste Request 20sec braucht.
Hui, wird da Pi in die tausendste Stelle berechnet oder 1GB Daten durchsucht?

(Die Exception für falsche Keys hört sich für mich gar nicht so schlimm an...)
Das ist zwar fast wieder eine andere Diskussion, aber wieso wären hier Returnvalues nicht zu bevorzugen (die meisten Funktionen von CryptoPP geben ohnehin void zurück)? Das Problem mit Exceptions ist ja gerade, dass sie irgendwann mal mitten in einem Code, der Libraryfunktionen benutzt, auftreten können. Catchen muss man die Exceptions ohnehin möglichst nahe am throw, sonst bekommt man sehr schnell einen Inconsistent State. Und ähnlich wie mit new, dessen throw ich wirklich fürchte, welcher eigentlich nicht nötig ist, da man auch auf nullptr prüfen kann, sehe ich keinen Grund für Exceptions, da man ja einfach einen bool/bitset/errnum zurückgeben könnte, der dann halt zu prüfen ist.
Ich weiss schon, dass u.a. bei Java die Exceptions meistens recoverable sind, und ich sehe auch das Argument, dass es eher unerwartet ist, dass Fehler geschehen (eine sehr optimistische Einstellung). Aber warum wird dann eine Exception so lange automatisch rethrowt, bis es jemand handelt, und zerschiesst dabei alle Stacks davor? Warum wird nicht erzwungen, dass Exceptions nur eine Stufe durchdringen können, um es überschaubar zu halten? Bei einem Retry würde dann ja immer alles komplett neu aufgebaut, was auch wieder kostet. Und wenn etwas wirklich kaputtgehen kann, dann müsste das ja schon im Vorherein klar sein, und man könnte ein Callback setzen.

dass man mehr als drei verschachtelte Level von Schleifen/if/... in der Realität nie sehen wird :D lol
Wobei das meines Wissens im Styleguide für Kernelcode ist. Wie sehr das durchgesetzt wird, weiss ich aber auch nicht, und i.d.R. sind auf den tieferen Stufen auch weniger Unterscheidungen nötig.

Auf die Frage, was man bei Funktionen mit 20 Parametern (bei der man die Parameterliste nicht ädnern kann!) und gleich am Anfang 200 Zeilen Validierung der Parameter, bevor es zum eigentlichen Sinn der Funktion kommt, tun soll, konnte mir bisher niemand von solchen Leuten eine überzeugende Antwort geben.
20 Setter :)
Immer beliebt ist der Spruch: "Wer 11 Parameter hat, hat vermutlich einen vergessen." Ja ne is klar. Das sind dann aber auch die Vögel, die 300 Werte in ein JSON-Objekt schreiben und dann 2 davon benutzen. Sind ja keine Parameter oder so. Oder sie machen es viel klüger und erstellen einzig für die Verwaltung der Parameter ein Objekt, selbstverständlich auf dem Heap, damit der ach so lahme Stack nicht benutzt werden muss. Beim ersten WinAPI-Code würden die Leute ja einen Schreikrampf kriegen...
Ich meine, keiner mag viele Parameter, aber manchmal müssen sie hin und dann sieht es halt nicht sehr gut aus, aber meine Güte, diese Funktionen werden i.d.R. nicht als Interface gegeben, sondern intern benutzt.

Doch, so oder so ähnlich machen das recht viele Leute.

...Erinnert mich an einen Kollegen, der tatsächlich jedes if-foo-then-error-4 in zwei (!) Klassen ausgelagert hat (eine für das if, eine mit den Fehlerdaten). Könnte ja wiederverwendbar sein. War es teilweise auch, aber natürlich keine Nettoersparnis an Zeilen und Arbeitszeit. Inzwischen hat sogar er eingesehen, dass es Unsinn ist.
Ah ja, Wiederverwendbarkeit. Eigentlich nichts schlechtes, aber die Leute rollen immer erst die Strukturen aus und machen dann den eigentlichen Arbeitscode, sodass immer etwa 3 Wrapperklassen sinnlos vor sich hindümpeln. Sieht halt schon viel besser aus, wenn man keinen Code sieht :rolleyes: Ich halte es grundsätzlich eher konservativ: Solange es lesbar ist, ist alles ok. Wird es zu komplex, wird ausgelagert.

Hast du auch mal einen Grund bekommen? Also einen außer "ich mag es nicht" und/oder "RandomPersonImInternet mag es nicht"?
"Wenn eine Funktion so viele Zeilen hat, macht sie zu viel gleichzeitig" (auch auf Klassen anwendbar, halt mit Member etc.).
Und es ist wohl wahr, ich lagere nur dann aus, wenn es wirklich der Lesbarkeit hilft und Performance nicht allzu wichtig ist oder wenn ich dieselbe Funktionalität mehrmals brauche. Aber bei langen if-elses mit klarer Sortierung; warum sollte ich da gross auslagern? Dass langer Code per se schlecht sein soll, ist auch eine Folge von missverstandenem OOP (wie so vieles), aber leider hält sich das ja hartnäckig.

Erinner sie doch bitte mal, dass wir nicht Modedesigner sind, sondern was praxistaugliches machen wollen.
Das ist, denke ich, eines der Probleme: Man schreibt immer nur Samplecode an den Bildungseinrichtungen, nie richtigen Code. Will man irgendwas komplexes machen, nimmt man dann die einfachste Bibliothek und fertig; man muss wenig Code schreiben. Entsprechend wird auch eher die Form statt die Funktion bewertet, und das zieht sich halt so durch.
Letztens über Swift gelesen: Hat ja so geile Features wie return type overloads. Ja, man kann es sich denken:
Code:
let a: Double = -(1 + 2) + -(3 + 4) + -(5)
Das gibt allen Ernstes den Output "error: Expression too complex". Warum? https://www.cocoawithlove.com/blog/...es.html#errors-compiling-otherwise-valid-code
In Kurzform: Durch die overloaded return types gibt es exponentielle Type-Inference-Komplexität. Aber hey: Immerhin zahlt man nur mit 20s compiletime bei solch einfachen Expressions für dieses tolle Feature, das keiner je vermisste. Und: Es sieht halt besser aus, da spielt Performance keine Rolle.

Aber ich bin ja auch der Meinung, man dürfe
Code:
//Für kurze statements (~< 10 LOC)
if () {
    stmt();
}

//Für "normale" statements
if()
{
    stmt();
}

//Für 1 LOC statements
if()
    stmt();
frei mischen und die Lesbarkeit ändere sich nicht wesentlich (oder verbessert sich sogar), aber viele beharren auf einem einheitlichen Stil. Das artet auch schnell darin aus, dass manche TABs verbieten wollen oder ein Leerzeichen nach "//" verlangen, aber ich verstehe auch, wenn jemand homogenen Code haben will. Wobei dann auch einfach pretty-print angeschmissen werden könnte...
Das war sicherlich eine Übertreibung, aber diese Meinung ist sicher in der Minderheit (oder die anderen sind einfach lauter, kann ja auch sein).

Eieiei, was für ein Rant. Aber muss ja auch ab und zu sein.

Gruss
cwriter
 

ComFreek

Mod | @comfreek
Moderator
#12
Aber warum wird dann eine Exception so lange automatisch rethrowt, bis es jemand handelt, und zerschiesst dabei alle Stacks davor? Warum wird nicht erzwungen, dass Exceptions nur eine Stufe durchdringen können, um es überschaubar zu halten? Bei einem Retry würde dann ja immer alles komplett neu aufgebaut, was auch wieder kostet. Und wenn etwas wirklich kaputtgehen kann, dann müsste das ja schon im Vorherein klar sein, und man könnte ein Callback setzen.
Gefühlt gibt es zwei Arten von Exceptions in einem gegebenen Kontext (dort wo sie auftreten):
  • Die, die recht nah behandelt werden. Wobei "nah" je nach Vorhandensein von Bibliotheken, Hilfsfunktionen/-klassen etc. variieren kann.
  • Die, die recht weit außen behandelt werden, weil sie fundamentale Fehler darstellen, z. B. eine NullPointerException, die zum Protokollieren des Fehlers, Anzeige einer Fehlermeldung und dann Termination der Applikation führt. Weit außen ist hier relativ zu sehen, das kann die main()-Funktion sein, ein Einstiegspunkt eines Threads oder auch die äußerste Methode einer Sandbox/Pluginumgebung. Beim letzten Fall hat ein externes Plugin zum Fehler geführt.
IMO ist das Problem, dass der Übergang recht fließend sein kann. Dementsprechend lässt du dir Flexibilität beim Exception-Modell. Im Gegensatz dazu kann man das auch einschränken zu Checked und Unchecked Exceptions, siehe Java. Das erhöht auf erste Sicht erst einmal massig den Boilerplate-Code, an sich finde ich solch eine Trennung auf Typebene gar nicht mal so schlecht. Vielleicht könnte man beim Aufruf einer Funktion gleich sagen, für welche Exceptions man sich interessiert:
Java:
try {
  File file = openFile("non-existent.txt") with fatal FileNotFoundException, caught MissingPermissionException
}
catch (MissingPermissionException e) {
  // Dass diese hier abgefangen wird, erzwingt der Compiler wegen obiger Zeile
}
Ist das Mitschleifen eines Callbackpointers auf eine ganz außen definierten Callbackfunktion nicht dasselbe wie das Erlauben der zweiten Art von Exceptions? Konzeptionell vollziehen sie beide dasselbe.

Das Problem mit Exceptions ist ja gerade, dass sie irgendwann mal mitten in einem Code, der Libraryfunktionen benutzt, auftreten können. Catchen muss man die Exceptions ohnehin möglichst nahe am throw, sonst bekommt man sehr schnell einen Inconsistent State.
Das hängt stark davon ab, ob man mit der Außenwelt, sei es Hardware oder ein externes Programm, agiert.

Da C++ durchaus als High-Level-Sprache benutzt werden kann, sind solche Flexibilitäten wie bei Exceptions imo unumgänglich für die Sprachdesigner. Aber ja, für OS- oder hardwarenahe Programmierung möchte man darauf und auf die damit verbundenen Zusatzkosten eher verzichten.
 

cwriter

Erfahrenes Mitglied
#13
Die, die recht weit außen behandelt werden, weil sie fundamentale Fehler darstellen, z. B. eine NullPointerException, die zum Protokollieren des Fehlers, Anzeige einer Fehlermeldung und dann Termination der Applikation führt.
NullPointerExceptions sind aber doch ein Argument gegen Exceptions: Warum kann man nicht einfach prüfen, dass ein Wert nicht null ist, bevor man ihn verwendet? Oder mittels Contracts/Asserts verlangen, welche Vorgaben erfüllt sein müssen? Mit NullPointerExceptions sieht man ja nicht, wo die Ursache des Problems ist, sondern nur, wo die Symptome auftreten.
Weit außen ist hier relativ zu sehen, das kann die main()-Funktion sein, ein Einstiegspunkt eines Threads oder auch die äußerste Methode einer Sandbox/Pluginumgebung. Beim letzten Fall hat ein externes Plugin zum Fehler geführt.
Bei wirklich schlimmen Fehlern macht das Sinn, aber eine Funktion, die z.B. ein Netzwerkpaket erwartet und bei Timeout throwt, verstehe ich schlicht nicht. Warum nicht einfach die Anzahl gelesener Bytes zurückgeben, die dann halt 0 oder -1 sind? Warum braucht man eine Exception, die bis zur main() durchgereicht wird, wo dann ein String dargestellt wird? Ermuntert das nicht dazu, gar keine Exception mehr zu fangen und bei Problemen einfach gar nichts zu machen, da der Nutzer ja eine Nachricht bekommt? Und da z.B. Timeouts nicht allzu selten sind, warum macht man sich die Mühe von Objekten mit Strings, wo ein Integer den Fehler genausogut beschreiben kann?
Die, die recht nah behandelt werden. Wobei "nah" je nach Vorhandensein von Bibliotheken, Hilfsfunktionen/-klassen etc. variieren kann.
Aber diese Art von Fehler ginge ja recht gut mit einem Rückgabewert.
Ob man jetzt
C:
FILE* f = fopen("test.txt", "r");
if(f == NULL) {
    //"Exception" handler
}

fclose(f);
oder
Java:
try {
  File file = openFile("non-existent.txt");
}
catch (Exception e) {
   //Exception handler
}
schreibt, kommt ja echt auf dasselbe raus. Also wieso verwendet man Exceptions für eine Stufe?

IMO ist das Problem, dass der Übergang recht fließend sein kann. Dementsprechend lässt du dir Flexibilität beim Exception-Modell.
Flexibilität ist immer Fluch und Segen. Wer erfahren ist, kann sehr interessante Konstrukte bauen. Wer es nicht ist, vertut sich aufgrund mangelnder Regeln schnell.
Im Gegensatz dazu kann man das auch einschränken zu Checked und Unchecked Exceptions, siehe Java. Das erhöht auf erste Sicht erst einmal massig den Boilerplate-Code
Ich finde das einen der schlimmsten Fehler von Java. Seien wir ehrlich: Die allermeisten Leute lassen die IDE die nötigen Stubs erstellen und belassen es dabei. Man gewinnt nichts, hat mehr Code, und man verwirrt die Leute, warum einige Exceptions anders sind. C# hat checked exceptions ja auch entfernt. Hier ist wieder die Flexibilität (oder eher, dass man nicht wusste, was man wollte) ein Problem. Entweder man sagt, Exceptions sind schwere Fehler, die aber Recoverable sind und gefangen werden müssen, oder man sagt, Exceptions sind eine andere Form von Rückgabepfaden. Letzteres wäre aber Redundant mit Returnvalues (gut, syntaktisch vielleicht ein bisschen aufgeräumter), ersteres wird nicht gemacht, weil es schnell viel zu verbose würde.

Vielleicht könnte man beim Aufruf einer Funktion gleich sagen, für welche Exceptions man sich interessiert:
Das wäre sicherlich ein Ansatz.

Ist das Mitschleifen eines Callbackpointers auf eine ganz außen definierten Callbackfunktion nicht dasselbe wie das Erlauben der zweiten Art von Exceptions? Konzeptionell vollziehen sie beide dasselbe.
Callbacks würden das Stack-unwinding verhindern. Vielleicht wäre es besser, eine Strategie zu übergeben, wie z.B. bei Netzwerktimeouts eines aus {resend, keep_waiting, return} oder so.

Da C++ durchaus als High-Level-Sprache benutzt werden kann, sind solche Flexibilitäten wie bei Exceptions imo unumgänglich für die Sprachdesigner.
Nur, weil die meisten High-Level Languages Exceptions bieten, heisst das nicht, dass es eine gute Idee ist.
Exceptions sind ja in erster Linie ein Hack für Bequemlichkeit.
Z.B.:
C++:
void f()
{
    if(!g())
    {
        //Handle this error
    }
    if(!h())
    {
        //Handle this error and revert what g() did if necessary
    }
}
//Wird zu:
void f() throws
{
    g();
    h();
}
Soweit, so gut. Aber was wurde vergessen? Genau, "Revert what g() did". Bis heute gibt es keine klare Lösung für dieses Problem. Oft wird dann einfach ein check eingeführt, der zuerst prüft, ob weder g noch h fehlschlagen, und entsprechend abbrechen kann. Das wiederum funktioniert aber nur, wenn weder g noch h aus anderen Gründen fehlschlagen, die ein Check nicht zuverlässig prüfen kann, z.B. out-of-memory-errors. Und genau hier giessen Exceptions Öl ins Feuer: Die erste Variante geht nicht mehr, da durch eine Exception in einer Funktion der Rückgabewert umgangen wird, d.h. man muss dann Catchen. Aber damit hat man den Code ja wieder zu einem If-Else gemacht, das einfach stattdessen try-catch heisst:
C++:
void f()
{
    try { g(); }
    catch (...) {
        //Handle this error
    }
    try { h(); }
    catch(...)  {
        //Handle this error and revert what g() did if necessary
    }
}
Damit hat man also durch Exceptions genau gar nichts gewonnen und viele sich Nachteile wie Overhead und Runtimeunterstützungsnotwendigkeit eingebrockt.
Eine echte Lösung wäre etwas wie Transactions:
C++:
void g()
{
:onabort = {
    //Auto-Revert
}
}

transactional void f() //If unrecoverable errors occur, call onabort for all previously executed functions in that scope
{
    g();
    h(); //h() schlägt fehl -> g():onabort()
}
Damit wäre sichergestellt, dass nie ein inconsistent State erreicht wird, es ist idiomatisch (etwas ähnliches lässt sich mit RAII und einem Flag, ob bei Destruktoraufruf committed oder aborted werden soll, schon erreichen) und man hat sauberen Code. Also ist das eigentlich das, was Exceptions sein wollten, mit dem Unterschied, dass diese onabort in vielen Fällen vom Compiler automatisch generiert werden könnten (sofern jede in der Funktion aufgerufene atomare Operation auch ein onabort hat, der Rest folgt durch Induktion).

Aber ja, für OS- oder hardwarenahe Programmierung möchte man darauf und auf die damit verbundenen Zusatzkosten eher verzichten.
Wie ich hoffentlich nun klarer ausgedrückt habe, sehe ich das Problem mit Exceptions nicht nur in der Performance, sondern auch in Redundanz, Inkonsistenz und Einladung zu schlechtem Code.

Gruss
cwriter
 

ComFreek

Mod | @comfreek
Moderator
#14
Warum kann man nicht einfach prüfen, dass ein Wert nicht null ist, bevor man ihn verwendet?
Kannst du. Und was machst du, wenn er NULL ist? Fehlerausgabe auf stderr und exit(0)? Eine Exception werfen?
Assertions sind eine sinnvolle Ergänzung zu Exceptions: Assertions innerhalb einer Library, Exceptions an der Schnittstelle. Assertions stellen für mich eher das Checken der Korrektheit innerhalb einer Library dar, sie behandeln Fälle, die nie auftreten dürfen, aus rein logischen Argumenten. Im Idealfall könnte man beweisen, dass Assertions innerhalb einer Library nie ausgelöst werden. Fehlerhafte Schnittstellenbenutzung kann sehr wohl auftreten und der Anwender muss darüber informiert werden und muss die Exception ggf. abfangen können.
Zugegeben: eine AssertionException kann man auch abfangen und je nach Programmiersprache kann man auch den zu werfenden Ausdruck angeben, sodass die Linie zwischen Assertion und Exception verschwimmt.

Bei wirklich schlimmen Fehlern macht das Sinn, aber eine Funktion, die z.B. ein Netzwerkpaket erwartet und bei Timeout throwt, verstehe ich schlicht nicht.
Da gebe ich dir Recht. Schande auf mein Haupt! Etwas Ähnliches habe ich erst letztens verbrochen: meine Queue<T>#poll()-Methode wirft eine Exception, wenn kein Element verfügbar ist. Der Hintergrund ist, dass man jeden anderen Wert u. U. nicht vom generischen Parameter T (welcher auch null oder undefined sein kann in TypeScript) unterscheiden könnte. Die richtige Lösung wäre es, immer ein Optional<T> zurückzugeben.

Also wieso verwendet man Exceptions für eine Stufe?
Als Entwickler der Libraryfunktion fopen kannst du ja nicht wissen, wie der Aufrufer verfahren möchte. Möchte er den Fehler selbst behandeln oder sich nicht darum kümmern?
Na klar kannst du auch "if (f == NULL) return NULL" als Aufrufer schreiben, diesen Boilerplate nehmen dir Exceptions ab.

Aber damit hat man den Code ja wieder zu einem If-Else gemacht, das einfach stattdessen try-catch heisst:
Mit finally-Konstrukten geht das ein bisschen besser, aber immer noch nicht zufriedenstellend:
Java:
void f() {
  try {
    g();
    h();
  }
  finally {
    if (gHasBeenExecuted) {
      // revert g
    }
  }
}
Eine echte Lösung wäre etwas wie Transactions:
Ui, hat das eine general-purpose (nein, nicht SQL :p) Programmiersprache?
 
#15
Jo. Gerade heute wieder einen Kommentar auf eine Antwort gelesen:
Das ist noch gar nichts ... wie wäre es mit offenem Machtmissbrauch ganzer Gruppen von Moderatoren, mit OK von ganz oben? usw.usw.
Nur kommt das in den Vorlesungen nicht so rüber...
Naja, solhe Leute gibts eben überall - auch als Professoren auf der Uni.

Hui, wird da Pi in die tausendste Stelle berechnet oder 1GB Daten durchsucht?
Es liefert die aktuelle Zeit am Server als Timestamp. Das ist alles :rolleyes:
...Inzwischen hab ich meine "Lobbyarbeit" so weit gebracht, dass auch der Vorgesetzte versteht, dass man so nicht arbeiten kann

Warum wird nicht erzwungen, dass Exceptions nur eine Stufe durchdringen können, um es überschaubar zu halten?
Naja, was ist dann überhaupt noch der Sinn von Exceptions?

Doch, so oder so ähnlich machen das recht viele Leute.
Ja leider - wollte ausdrücken, dass das fast nie sinnvoll ist.

Bis heute gibt es keine klare Lösung für dieses Problem.
...was Beweis ist, dass Exceptions eben nicht perfekt sind bzw. manche Sachen umständlicher als vorher machen.
(Aber wir hier erwarten uns ja auch nichts Perfektes - manche Leute aber scheinbar schon, udn denken dann auch es ist schon perfekt)

...
Zu Transactions:
In Sprachen mit Möglichkeiten für Nebenwirkungen (= alles außerhalb vom Programm/Thread/...) wird das wohl kaum möglich sein...
 
Zuletzt bearbeitet:

cwriter

Erfahrenes Mitglied
#16
Schön, das der Thread weitergeführt wird :) Diesen Meinungsaustausch finde ich immer sehr bereichernd.

Kannst du. Und was machst du, wenn er NULL ist? Fehlerausgabe auf stderr und exit(0)? Eine Exception werfen?
Kommt sehr drauf an. Wenn der Wert erwartet NULL sein kann (nach malloc/fopen/etc.), dann gebe ich i.d.R. ein return mit einer negativen Zahl aus und lasse den Caller entscheiden.
Bei Parametern mache ich oft ein assert an den Anfang der Funktion, ganz im Sinne von Design By Contract.
Assertions sind eine sinnvolle Ergänzung zu Exceptions
Assertions sind m.E. anders als Exceptions: Sie sollten nicht für erwartetes oder seltenes verwendet werden, sondern als Indikatoren für Fehler während der Entwicklung/des Testens. Im Releasemode werden sich auch meist deaktiviert.
Fehlerhafte Schnittstellenbenutzung kann sehr wohl auftreten und der Anwender muss darüber informiert werden und muss die Exception ggf. abfangen können.
Da bin ich anderer Meinung. Bibliotheken sollten keine Exceptions verwenden, da sie den Anwender zwingen, auch Exceptions zu verwenden. In Java macht das nichts, in anderen Sprachen, wie C++, schon. Assertions in Debug-Bibliotheken: Ja, klar, gerne! Unerfüllte Preconditions mit asserts zu fangen erspart einem sehr viele mühsame Fehler. Im Releasemode hingegen sollten diese Checks aus Performancegründen deaktiverbar sein, was Exceptions halt prinzipbedingt nicht sind.
Wenn die Parameter, die man an eine Schnittstelle übergibt, den Preconditions nicht entsprechen könnten, muss der Caller dafür sorgen, dass die Preconditions erfüllt sind, d.h. der Caller soll die Checks machen, nicht der Callee. Denn sonst würden die "guten" Programmierer, die nur die "guten" Parameter übergeben, immer gebremst. Bei Funktionen wie fopen, die ohnehin schnarchlangsam sind, machen mehr Checks nicht viel aus, aber sobald man in einen Loop geht, zählt jede Instruktion.

Zugegeben: eine AssertionException kann man auch abfangen und je nach Programmiersprache kann man auch den zu werfenden Ausdruck angeben, sodass die Linie zwischen Assertion und Exception verschwimmt.
Ja, aber das finde ich dann echt skurril. Eine Assertion sollte dann verwendet werden, wenn man nicht vorhat, sich wieder zu fangen (denn sonst könnte man ja "normal" mit ifs prüfen).

Die richtige Lösung wäre es, immer ein Optional<T> zurückzugeben.
Ja, so mache ich das auch immer (oft etwas mühsamer mit success als Rückgabewert und dem Wert als Parameterreferenz, aber sinngemäss dasselbe).

Als Entwickler der Libraryfunktion fopen kannst du ja nicht wissen, wie der Aufrufer verfahren möchte. Möchte er den Fehler selbst behandeln oder sich nicht darum kümmern?
Wenn ich der Entwickler wäre, würde mich das ehrlich gesagt nicht interessieren. Der Aufrufer hat sich an meine Spezifikation zu halten, Punkt. Ich schreibe meine Funktion so, dass sie das Maximum an Effizienz erreicht. Und wie gesagt verwende ich keine Exceptions, wenn ich Libraries schreibe, da ich den Nutzer nicht zur Benutzung von Exceptions zwingen will.
Na klar kannst du auch "if (f == NULL) return NULL" als Aufrufer schreiben, diesen Boilerplate nehmen dir Exceptions ab.
Hier sind wir am Kern der Sache: Ja, Exceptions nehmen den Boilerplate ab. Aber sie tun das ungefragt.
Java:
myfunc();
System.out.println("Hello World");
Wird "Hello World" ausgegeben? Nein, myfunc() hat in einer Kette von Funktionaufrufen einen Funktionsaufruf, der 42 Funktionen tief geht und mit einer Wahrscheinlichkeit von 10e-6 eine Exception wirft, die nicht gefangen wird. Man hat keine Chance, das zu sehen; man muss einen Debugger verwenden, was aber immer noch schwierig ist, da die Exception nicht deterministisch auftritt. Ok, man kann sagen, dass das in die Spezifikation gehört, und man hätte damit sicher recht. Aber gute Spezifikationen sind ja bekanntlich rar.
Im Gegensatz dazu:
C:
int ok = myfunc();
printf("Hello World\n");
0) Ich sehe immer, dass es einen Rückgabewert hat. Mit C++17 gibt es mit [[nodiscard]] sogar die explizite Anweisung, den Rückgabewert nicht zu ignorieren. (Warnung)
1) Ich kann aussuchen, ob mich das Resultat von myfunc() zu diesem Zeitpunkt überhaupt interessiert.
2) Ich kann den Fehler ignorieren
3) Ich kann den Fehler behandeln

Das Argument des Boilerplate ist aber tatsächlich ernstzunehmen. Es ist nicht zu leugnen, dass Exceptions zum Zeitpunkt des Schreiben des Codes sehr praktisch sind, da Programmierer bei Fehlern gerne und oft sagen: "Ist nicht mein Problem". In C/C++ hat man dann gröbere Probleme, das Problem wieder zu lokalisieren, mit Exceptions sieht man den Ursprung aber recht schnell (ich mache es jeweils mit assert(false && "Message"), was bisher immer recht gut funktionierte).
Weniger Boilerplate ist mir auch ein Anliegen, aber es muss doch einen anderen Weg geben.

Mit finally-Konstrukten geht das ein bisschen besser, aber immer noch nicht zufriedenstellend:
Ja, finally wurde ja exakt eingeführt, um das Problem, das Exceptions konstruieren, wieder aufzuräumen. Aber gerade in diesem Beispiel: Wenn man nicht nur 2, sondern 100 Funktionen nacheinander ausführen würde, braucht man ja auch im finally wieder 100 checks; man gewinnt also nichts.
Ui, hat das eine general-purpose (nein, nicht SQL :p) Programmiersprache?
Dieses Beispiel habe ich mir gerade aus den Fingern gesaugt, ich glaube nicht, dass es eine Sprache gibt, die genauso aussieht. Aber:
http://en.cppreference.com/w/cpp/language/transactional_memory
(Experimental)
Aber ich meine auch gar nicht "echte" transactions, sondern etwas in diese Richtung. Dazu auch:
Zu Transactions:
In Sprachen mit Möglichkeiten für Nebenwirkungen (= alles außerhalb vom Programm/Thread/...) wird das wohl kaum möglich sein...
Als Beispiel:
Code:
int i;

void f() {
    print("Setting i");
    i = 42;
} : onabort() {
    print("Aborted");
    i = 0;
}
Hier ist klar: Wenn i concurrent accesses hat, ist das nicht genügend. Ebenfalls kann ein print() nicht rückgängig gemacht werden. Aber macht das hier etwas? Ich brauche ja nicht "pure" transactions, nur ein leichteres RAII. Oder, vielleicht ein etwas sinnvollerer Anwendungszweck:
Code:
void* ptr;
void* ptr2;
void f() {
    ptr = malloc(123);
    ptr2 = malloc(123);
} : onabort() {
    free(ptr);
    free(ptr2);
}
Das zweite Beispiel wird zwar schon mit RAII gelöst, aber dort zahlt man ja immer mit teuren Kopien/Destruktoraufrufen wo immer ein Wert verwendet wird. C++ führte ja u.a. für die Vermeidung von Aufrufen von Konstruktor/Destruktor von grösseren Objekten References ein, die aber auch wieder die Probleme von Pointern haben (indirection). RAII ist sehr breitband: Fängt fast alles, ist aber auch sehr teuer, gerade, wenn man noch virtual Destructors hat oder inlining aus einem anderen Grund nicht möglich ist.

Naja, was ist dann überhaupt noch der Sinn von Exceptions?
Sag du's mir ;)
(Bisschen weniger Boilerplate lasse ich, wie schon gesagt, gelten)

...was Beweis ist, dass Exceptions eben nicht perfekt sind bzw. Sachen umständlicher als vorher machen.
Ein bisschen wie letzte Frage, aber: Warum hat sie denn jede Sprache, die einigermassen cool sein will? Gewohnheit?

Gruss
cwriter
 
Zuletzt bearbeitet:

ComFreek

Mod | @comfreek
Moderator
#17
Bei Parametern mache ich oft ein assert an den Anfang der Funktion, ganz im Sinne von Design By Contract.
Aber wenn die Asserts im Releasemode abgeschaltet werden, läuft du sehr stark Gefahr, enorm inkonsistente Zustände zu erreichen. Der Endnutzer bekommt nicht mal eine Fehlermeldung, wie es bei Exceptions der Fall wäre - auch wenn sie bis zur main hochblubbern. Außerdem kann dein Programm für nichts mehr garantieren, wenn es mit externen Schnittstellen arbeitet, etwa: Wird der Motor zu schnell angesteuert? Hält es sich an HTTP Requests Limits meines eingekauften API Endpoints von {Google, Microsoft, ...}? Werden Konfigurationsdateien vielleicht auf der Festplatte falsch persistiert?

Deswegen finde ich {Exceptions, return -1, return NULL} unbedingt notwendig bei Schnittstellen von Bibliotheken. Innerhalb einer Bibliothek kann man sich meinetwegen auf Asserts verlassen. Da kommt es allerdings auch wieder auf die Größe der Bibliothek an. Wann fangen die Interna einer Bilbiothek an, selbst Schnittstellen zu besitzen?

Assertions sind m.E. anders als Exceptions: Sie sollten nicht für erwartetes oder seltenes verwendet werden, sondern als Indikatoren für Fehler während der Entwicklung/des Testens. Im Releasemode werden sich auch meist deaktiviert.
Genau! Fehlbenutzung einer Bilbiotheksschnittstelle gehört für mich zu etwas Erwartetem.

Im Releasemode hingegen sollten diese Checks aus Performancegründen deaktiverbar sein, was Exceptions halt prinzipbedingt nicht sind.
Von mir aus sind dann auch Errorcodes via return/Parameter in Ordnung, siehe oben. Hauptsache, es wird irgendwie signalisiert und nicht stillschweigend übersprungen, wie im Falle deaktivierter Assertions im Releasemode.

Der Aufrufer hat sich an meine Spezifikation zu halten, Punkt.
Das kann man leider nicht erwarten. Man könnte vielleicht hoffen, dass dies der Compiler und Typchecker übernehmen. Das geht auch zum Teil, z. B. für Werte != null und Typconstraints für Generics. Allerdings können Spezifikationen arbiträr kompliziert werden. Interessant wird's, wenn du deine Funktionen so auslegst, dass sie einen zusätzlichen Parameter erwarten, nämlich einen Beweis dafür, dass die Spezifikation eingehalten worden ist. Ein solches System dafür kenn ich: MMT. Auch wenn das theoretisch imo sehr cool klingt, so ist der Sprung zu Spezifikationen aus der realen Welt ein seeehr großer!

Aber ich meine auch gar nicht "echte" transactions, sondern etwas in diese Richtung.
Genau, so habe ich das auch von dir oben verstanden ;) Transaktionen mit onabort-Methoden im Sinne einer besten Approximation an Ungeschehenmachen und Konsistenzwiederherstellung.

(Bisschen weniger Boilerplate lasse ich, wie schon gesagt, gelten)
Übrigens kapseln Exceptions auch Fehlerinformationen wie Funktion, Dateiname, Zeilennummer, Fehlernachricht und auch arbiträte Daten desjenigen, der sie geworfen hat. Wie machst du das ohne Exceptions?
Einfach auf stderr zu schreiben kommt nicht an dasselbe ran m. E.
 

cwriter

Erfahrenes Mitglied
#18
Aber wenn die Asserts im Releasemode abgeschaltet werden, läuft du sehr stark Gefahr, enorm inkonsistente Zustände zu erreichen.
Aber Asserts waren ja immer da, um die Fehler des Programmierers am Ursprung abzufangen, nicht, um die Fehler einer Umgebung zu fangen. Oder anders gesagt: An Orten, wo Nichtdeterminismus erwartet wird, braucht es etwas anderes als Asserts. Nutzereingaben z.B. Aber wenn ich mich nicht darauf verlassen kann, dass der Programmierer seine Arbeit gemacht hat und ungetesteten Code auf die Nutzer loslässt (release), dann könnte man gar keine Programme mehr haben. Man müsste ja bei jedem Funktionsaufruf, nach jedem Statement, nach jeder Expression nach allen möglichen Side-Effects testen. Kann ja sein, dass der Programmierer mir in den Speicher gespuckt hat, oder eine Klasse speichert und nach einem Programmneustart wieder in den Speicher lädt und erwartet, dass alle Pointer noch identisch sind, etc...
Der Endnutzer bekommt nicht mal eine Fehlermeldung, wie es bei Exceptions der Fall wäre - auch wenn sie bis zur main hochblubbern.
Vor 5-10 Jahren war ja die Welle der 1000 Java-Programme. Ich hatte noch nie zuvor so viele Fehlermeldungen auf dem Bildschirm gesehen. Allermeistens NullPointerException oder OutOfBoundsException. Nützt mir das als Endanwender etwas? Ein Programmierer, der denkt, dem Nutzer eine Exception vorsetzen zu können, kann das Programm auch einfach abstürzen lassen. Ein Endnutzer sagt nicht "Ach geil, eine hübsche generische Fehlermeldung. Wenn's einfach ein "Programm reagiert nicht mehr" wäre, wäre ich angepisst, aber eine Exception gefällt mir".

Außerdem kann dein Programm für nichts mehr garantieren, wenn es mit externen Schnittstellen arbeitet, etwa: Wird der Motor zu schnell angesteuert?
Wie gesagt: Out of control. Bei wirklich wichtigen Anwendungen nützen asserts dann ja meist auch nichts, weil dort bewiesen werden muss, dass die Asserts nicht fehlschlagen können. Bei nicht-kritischen Anwendungen kann auch ein Absturz in Kauf genommen werden.
Hält es sich an HTTP Requests Limits meines eingekauften API Endpoints von {Google, Microsoft, ...}? Werden Konfigurationsdateien vielleicht auf der Festplatte falsch persistiert?
Ok, aber wenn man die Anforderung "der, dem ich einen Auftrag gebe, muss prüfen, ob ich nicht dumm bin" weiterzieht, müsste jede Stufe bis hin zum Netzwerkstack überprüfen, ob du das darfst. Irgendwo muss die Grenze gezogen werden, ab wo es halt schiefläuft. Aber die Erfahrung zeigt: Wenn man überprüft, dass man das richtige übergibt, und rekursiv annimmt, dass alle Aufgerufenen das auch tun, hat man die Anforderung erfüllt.
Um wieder auf Asserts zurückzukommen: Wenn man etwas prüfen will, von dem man annimmt, dass es in Produktivumgebungen vorkommt, darf man es nicht mit asserts prüfen, sondern macht ifs (oder halt Exceptions, wenn man die bevorzugt).
Man ist immer selbst verantwortlich für das, was man in Auftrag gibt. Oder gehst du nach einem Fehlkauf zum Verkäufer und sagst, er sei schuld, weil er dir das, was du wolltest, verkaufte? Und vor allem: Beim ersten Mal, wenn du etwas kaufst und noch nicht ganz sicher bist, ob das richtig ist, hast du sicher gerne eine Beratung (assert). Aber wenn du dann täglich vorbeikommst und dasselbe kaufst, willst du dir dann immer ein paar Minuten lang erzählen lassen, was du schon weisst?


Deswegen finde ich {Exceptions, return -1, return NULL} unbedingt notwendig bei Schnittstellen von Bibliotheken. Innerhalb einer Bibliothek kann man sich meinetwegen auf Asserts verlassen. Da kommt es allerdings auch wieder auf die Größe der Bibliothek an. Wann fangen die Interna einer Bilbiothek an, selbst Schnittstellen zu besitzen?
Genau das meinte ich vorhin. Der Caller hat zu prüfen, sonst Checkst du dieselben facts mehrmals, ohne eine Chance, sie zu entfernen, da sie ja innerhalb der Bibliothek sind.
Man könnte sonst ja noch weiter gehen und z.B. Checken, ob die Calling Convention immer korrekt verwendet wird etc. etc.

Genau! Fehlbenutzung einer Bilbiotheksschnittstelle gehört für mich zu etwas Erwartetem.
Ja, beim Programmieren/Debuggen. Wer im Produktivcode eine Schnittstelle falsch verwendet, gehört geschlagen. Bei WebAPIs ist das ein bisschen anders, da sich da ja viel ändert, aber bei nativen Schnittstellen ist das nicht verhandelbar.

Hauptsache, es wird irgendwie signalisiert und nicht stillschweigend übersprungen, wie im Falle deaktivierter Assertions im Releasemode.
Wie gesagt: Asserts beschweren sich, wenn ein Vertrag (Gib mir korrekte Eingaben, ich gebe dir korrekte Ausgaben) gebrochen wird. Nach der Kennenlernphase ("Debuggen") nimmt man an, dass man den Vertrag nun verstanden hat und nicht mehr bricht. Asserts im Releasemode noch zu checken ist viel zu teuer.

Das kann man leider nicht erwarten.
Na dann ist's aber auch nicht mein Problem. Interessiert mich doch nicht, wenn ein Caller falsche Werte eingibt. Z.B. negative Werte bei malloc(). Dann schreibe ich die Eingabe halt zu unsigned um und versuche es damit. Geht nicht? Tja, bekommst halt einen Fehler zurück.
fread() gibt zurück, wie viele Bytes tatsächlich gelesen wurden. Der Anwender ignoriert's und schaut sich Bytes dahinter an? Was kann ich schon dagegen tun?
Übrigens halten es die allermeisten Bibliotheken so: Im Debugmode gibt es einen Assert, im Releasemode schmiert das Programm halt ab.
Wieder als Analogie: Der Autohersteller ist nicht schuld, wenn du das Auto in einen Baum steuerst.

Aber klar: Statische Analyzer können sehr helfen - aber auch nur beim Caller.
Übrigens kapseln Exceptions auch Fehlerinformationen wie Funktion, Dateiname, Zeilennummer, Fehlernachricht und auch arbiträte Daten desjenigen, der sie geworfen hat. Wie machst du das ohne Exceptions?
Einfach auf stderr zu schreiben kommt nicht an dasselbe ran m. E.
Und wie kommt man auf diese Information?
Im Prinzip ist das ja auch Boilerplate: Kein Mensch, der einigermassen bei Sinnen ist, würde Rückgabewerten Meta-Informationen mitgeben. Exceptions machen es, wie du sagst, versteckt. Also dürfte man Exceptions dann gar nicht mehr verwenden, wenn man auch nur den Hauch einer Chance haben will, performant zu sein.

Ich sehe schon, dass Exceptions "angenehm" sind, aber aber ein Rolls Royce ist auch angenehm, wenn du ihn benutzen kannst. Wenn du ihn auch bezahlen musst, sieht es wieder anders aus.

Gruss
cwriter
 

ComFreek

Mod | @comfreek
Moderator
#19
An Orten, wo Nichtdeterminismus erwartet wird, braucht es etwas anderes als Asserts. Nutzereingaben z.B.
Ah, hier sieht man wohl sehr gut, wo unsere Meinungen auseinander gehen ;) Ich finde, dass Eingaben an einer Bibliotheksschnittstelle (in gewissen Teilen) auch dem Nichtdeterminismus unterworfen sind.

Nützt mir das als Endanwender etwas? Ein Programmierer, der denkt, dem Nutzer eine Exception vorsetzen zu können, kann das Programm auch einfach abstürzen lassen.
Meine Kernbehauptung war, dass du a) den Fehler überhaupt bemerkst und ggf. das Programm terminieren kannst, ganz im Sinne von Defensive Prorgamming und "lieber terminieren als inkonsistent weiterzurechnen". Das ist bei deaktivierten Assertions im Releasemode unmöglich. Ferner kannst du b) den Fehler auch protokollieren. Wie genau man das dem Nutzer präsentiert, ist eher eine UX-Frage. Jedenfalls lässt sich der Fehler im Protokoll wiederfinden (falls der Nutzer Hilfe in Foren oder bei einer Hotline sucht) und ggf. per Telemetrie an dich als Vertreiber schicken lassen.

Um wieder auf Asserts zurückzukommen: Wenn man etwas prüfen will, von dem man annimmt, dass es in Produktivumgebungen vorkommt, darf man es nicht mit asserts prüfen, sondern macht ifs (oder halt Exceptions, wenn man die bevorzugt).
Da stimme ich dir zu!

Aber wenn du dann täglich vorbeikommst und dasselbe kaufst, willst du dir dann immer ein paar Minuten lang erzählen lassen, was du schon weisst?
Da kommt's drauf an, wie teuer das Produkt ist, also wie schlimm es ist, uninformiert unter falschen Annahmen über das Produkt (= inkonsistent) das Produkt zu erwerben (= weiterzurechnen). (Wow "das Produkt das Produkt", ist der Satz grammatikalisch? :D)
Aber selbst bei teuren Produkten wird der Käufer wohl irgendwann relativ selbstsicher werden, weil der Kontext (= Annahmen, Vertrauen in den Verkäufer, Anwendungsfälle des Produkts, ...) immer derselbe ist. Nur als Bibliothek ist das nicht so. Deine Schnittstelle wird von jedermann genutzt, ggf. auf verschiedenen Geräten, OS, Sprachen.

Z.B. negative Werte bei malloc(). Dann schreibe ich die Eingabe halt zu unsigned um und versuche es damit. Geht nicht? Tja, bekommst halt einen Fehler zurück.
Das sollte idealerweise vom Typchecker / Static Analyzer bereits bei der Expression des Funktionsaufrufs im AST abgefangen werden.

Und wie kommt man auf diese Information?
Dateiname, Zeilennummer, Funktionsname, Stack Trace werden alle autogeneriert, aber auch nur wenn die Exception auftritt. Allgemein ist der Performanceverlust in normalen Ablaufszenarien ohne Exceptions Folgender:
  • der von den IF-Bedingungen um den Exceptionwurf herum
  • der des zusätzlich autogenerierten Codes im Assembler
Ich meine mal gelesen zu haben, dass der zweite Punkt gar nicht mal so viel Einfluss auf die Performance zur Laufzeit (d. h. ohne Laden der Executable) hat. Diese Antwort stützt die These. (Es gibt allerdings auch einige 'falsche' Microbenchmarks, z. B. diesen hier.)

Im Prinzip ist das ja auch Boilerplate: Kein Mensch, der einigermassen bei Sinnen ist, würde Rückgabewerten Meta-Informationen mitgeben.
Ist die errno im Falle, dass fopen NULL zurückgibt, nicht auch ein Metawert?


PS: Bevor ich das nächste Mal antworte, lasse ich lieber mal wieder neuen Input von @sheel (oder jeden, der sich angesprochen fühlt) vorher in diesen Thread einfließen ;)
 
Anzeige
Anzeige