Bei einem neuen Job wurde ich in Code-Reviews für Code wie diesen markiert:
PowerManager::PowerManager(IMsgSender* msgSender)
: msgSender_(msgSender) { }
void PowerManager::SignalShutdown()
{
msgSender_->sendMsg("shutdown()");
}
Mir wurde gesagt, dass die letzte Methode lauten sollte:
void PowerManager::SignalShutdown()
{
if (msgSender_) {
msgSender_->sendMsg("shutdown()");
}
}
dh ich muss einen setzen NULL
Wache rund um die msgSender_
Variable, auch wenn es ein privates Daten Mitglied ist. Es fällt mir schwer, mich davon abzuhalten, mit Sprengsätzen zu beschreiben, wie ich über dieses Stück „Weisheit“ stehe. Wenn ich nach einer Erklärung frage, bekomme ich eine ganze Reihe von Horrorgeschichten darüber, wie ein Junior-Programmierer eines Jahres verwirrt darüber wurde, wie eine Klasse funktionieren sollte, und löschte versehentlich ein Mitglied, das er nicht haben sollte (und setzte es NULL
danach auf (anscheinend) und die Dinge explodierten gleich nach einer Produktveröffentlichung, und wir haben "auf die harte Tour gelernt, vertraue uns", dass es besser ist, einfach alles zu NULL
überprüfen .
Für mich fühlt sich das schlicht und einfach nach Frachtkult-Programmierung an . Ein paar wohlmeinende Kollegen versuchen ernsthaft, mir zu helfen, es zu verstehen und zu sehen, wie mir das helfen wird, robusteren Code zu schreiben, aber ... ich kann nicht anders, als mich zu fühlen, als wären sie diejenigen, die es nicht verstehen .
Ist es für einen Codierungsstandard sinnvoll, zu verlangen, dass jeder einzelne Zeiger, der in einer Funktion dereferenziert wird, NULL
zuerst auf private Datenelemente überprüft wird ? (Hinweis: Um einen gewissen Zusammenhang zu schaffen, stellen wir ein Unterhaltungselektronikgerät her, kein Flugsicherungssystem oder ein anderes Produkt, das dem Menschen gleichkommt.)
BEARBEITEN : Im obigen Beispiel ist der msgSender_
Mitbearbeiter nicht optional. Wenn es jemals ist NULL
, zeigt es einen Fehler an. Der einzige Grund, warum es an den Konstruktor übergeben wird, besteht darin, dass PowerManager
es mit einer Scheinunterklasse getestet werden kann IMsgSender
.
ZUSAMMENFASSUNG : Es gab einige wirklich gute Antworten auf diese Frage, danke an alle. Ich habe den von @aaronps hauptsächlich wegen seiner Kürze angenommen. Es scheint eine ziemlich breite allgemeine Übereinstimmung zu geben, dass:
- Mandatierung
NULL
Schutz für jeden einzelnen dereferenzierten Zeiger ist übertrieben, aber - Sie können die gesamte Debatte durch Verwenden einer Referenz (falls möglich) oder eines
const
Zeigers auslassen assert
Anweisungen sind eine aufschlussreichere Alternative zuNULL
Guards, um zu überprüfen, ob die Voraussetzungen für eine Funktion erfüllt sind.
quelle
null
und Nichtstun ist nur eine Möglichkeit, den Fehler während der Ausführung zu beheben, was es weitaus schwieriger macht, die Quelle zu finden.Antworten:
Es kommt auf den 'Vertrag' an:
Wenn
PowerManager
muss ein gültiges habenIMsgSender
, nie für null überprüfen, lassen Sie es früher sterben.Wenn auf der anderen Seite, es KANN eine haben
IMsgSender
, dann müssen Sie jedes Mal , wenn Sie überprüfen verwenden, so einfach ist das.Abschließender Kommentar zur Geschichte des Junior-Programmierers, das Problem ist eigentlich das Fehlen von Testverfahren.
quelle
assert()
in jeder Member-Funktion, die einen Zeiger dereferenziert, ist ein Kompromiss, der wahrscheinlich viele Leute beschwichtigen wird, aber selbst das fühlt sich für mich wie spekulative Paranoia an. Die Liste der Dinge, die sich meiner Kontrolle entziehen, ist unendlich, und wenn ich mir erst einmal Gedanken darüber mache, wird es zu einem wirklich rutschigen Hang. Wenn ich gelernt habe, meinen Tests, meinen Kollegen und meinem Debugger zu vertrauen, konnte ich nachts besser schlafen.Ich finde, der Code sollte lauten:
Dies ist eigentlich besser als die NULL zu schützen, da es sehr deutlich macht, dass die Funktion niemals aufgerufen werden sollte, wenn sie
msgSender_
NULL ist. Es stellt auch sicher, dass Sie feststellen, wenn dies passiert.Die geteilte "Weisheit" Ihrer Kollegen wird diesen Fehler stillschweigend ignorieren und zu unvorhersehbaren Ergebnissen führen.
Im Allgemeinen sind Fehler leichter zu beheben, wenn sie näher an ihrer Ursache erkannt werden. In diesem Beispiel würde der vorgeschlagene NULL-Schutz dazu führen, dass die Meldung zum Herunterfahren nicht festgelegt wird, was möglicherweise zu einem erkennbaren Fehler führt. Es fällt Ihnen schwerer, mit der
SignalShutdown
Funktion zurückzukehren, als wenn die gesamte Anwendung gerade gestorben wäre und ein handliches Backtrace oder ein Core-Dump erstellt würde, auf das direkt verwiesen wirdSignalShutdown()
.Es ist ein wenig eingängig, aber ein Absturz, sobald etwas nicht stimmt, macht Ihren Code robuster. Das liegt daran, dass Sie die Probleme tatsächlich finden und tendenziell auch sehr offensichtliche Ursachen haben.
quelle
Wenn msgSender niemals sein darf
null
, sollten Sie dienull
Prüfung nur im Konstruktor ablegen . Dies gilt auch für alle anderen Eingaben in die Klasse - setzen Sie die Integritätsprüfung an der Stelle des Eintritts in das 'Modul' - Klasse, Funktion usw.Meine Faustregel lautet, Integritätsprüfungen zwischen Modulgrenzen durchzuführen - in diesem Fall der Klasse. Darüber hinaus sollte eine Klasse klein genug sein, um die Integrität der Lebenszeiten der Klassenmitglieder geistig schnell überprüfen zu können. So wird sichergestellt, dass Fehler wie falsche Lösch- / Nullzuweisungen vermieden werden. Die Null-Prüfung, die im Code Ihres Posts durchgeführt wird, setzt voraus, dass eine ungültige Verwendung dem Zeiger tatsächlich Null zuweist - was nicht immer der Fall ist. Da "ungültige Verwendung" impliziert, dass Annahmen über regulären Code nicht zutreffen, können wir nicht sicher sein, alle Arten von Zeigerfehlern abzufangen - zum Beispiel ein ungültiges Löschen, Inkrementieren usw.
Wenn Sie sicher sind, dass das Argument niemals null sein kann, sollten Sie Referenzen verwenden, je nachdem, wie Sie die Klasse verwenden. Verwendung Ansonsten betrachten
std::unique_ptr
oderstd::shared_ptr
anstelle eines Rohzeiger.quelle
Nein, es ist nicht sinnvoll, jede Zeiger-Dereferenzierung auf das Zeiger-Sein zu überprüfen
NULL
.Nullzeigerprüfungen sind nützlich für Funktionsargumente (einschließlich Konstruktorargumente), um sicherzustellen, dass die Voraussetzungen erfüllt sind, oder um geeignete Maßnahmen zu ergreifen, wenn kein optionaler Parameter angegeben ist, und um die Invariante einer Klasse zu überprüfen, nachdem Sie die Interna der Klasse offengelegt haben. Aber wenn der einzige Grund, warum ein Zeiger NULL geworden ist, das Vorhandensein eines Fehlers ist, hat das Prüfen keinen Sinn. Dieser Fehler hätte genauso gut den Zeiger auf einen anderen ungültigen Wert setzen können.
Wenn ich mit einer Situation wie Ihrer konfrontiert wäre, würde ich zwei Fragen stellen:
assert(msgSender_)
statt der Null-Prüfung einfach schreiben ? Wenn Sie nur den Null-Check-In eingegeben haben, haben Sie möglicherweise einen Absturz verhindert, aber möglicherweise eine schlimmere Situation geschaffen, da die Software unter der Voraussetzung fortfährt, dass ein Vorgang stattgefunden hat, während dieser Vorgang in Wirklichkeit übersprungen wurde. Dies kann dazu führen, dass andere Teile der Software instabil werden.quelle
In diesem Beispiel geht es eher um die Objektlebensdauer als darum, ob ein Eingabeparameter null † ist oder nicht. Da Sie erwähnen , dass die
PowerManager
Muss immer ein gültiges habenIMsgSender
, durch den Zeiger das Argument übergeben ( und damit die Möglichkeit einer Null - Zeiger erlaubt) scheint mir ein Konstruktionsfehler ††.In solchen Situationen würde ich es vorziehen, die Benutzeroberfläche zu ändern, damit die Anforderungen des Anrufers durch die Sprache durchgesetzt werden:
Wenn Sie es auf diese Weise umschreiben,
PowerManager
müssen Sie einen VerweisIMsgSender
für sein gesamtes Leben aufbewahren. Dies wiederum stellt auch eine implizite Anforderung dar,IMsgSender
die länger leben muss alsPowerManager
und negiert die Notwendigkeit einer Überprüfung oder Behauptung von NullzeigernPowerManager
.Sie können dasselbe auch mit einem intelligenten Zeiger (über boost oder c ++ 11) schreiben, um explizit zu erzwingen
IMsgSender
, dass Sie länger leben alsPowerManager
:Diese Methode wird bevorzugt, wenn möglicherweise
IMsgSender
nicht garantiert werden kann, dass die Lebensdauer länger ist alsPowerManager
(dhx = new IMsgSender(); p = new PowerManager(*x);
).† In Bezug auf Zeiger: Die zügellose Nullprüfung erschwert das Lesen von Code und verbessert die Stabilität nicht (sie verbessert das Erscheinungsbild der Stabilität, was viel schlimmer ist).
Irgendwo hat jemand eine Adresse zur Erinnerung bekommen, an der die
IMsgSender
. Es liegt in der Verantwortung dieser Funktion, sicherzustellen, dass die Zuordnung erfolgreich war (Überprüfung der Rückgabewerte der Bibliothek oder ordnungsgemäße Behandlung vonstd::bad_alloc
Ausnahmen), um ungültige Zeiger nicht zu umgehen.Da das
PowerManager
nicht der Besitzer istIMsgSender
(es leiht es sich nur für eine Weile aus), ist es nicht dafür verantwortlich, diesen Speicher zuzuweisen oder zu zerstören. Dies ist ein weiterer Grund, warum ich die Referenz bevorzuge.†† Da Sie neu in diesem Job sind, gehe ich davon aus, dass Sie vorhandenen Code hacken. Mit Konstruktionsfehler meine ich, dass der Fehler in dem Code liegt, mit dem Sie arbeiten. Die Leute, die Ihren Code kennzeichnen, weil er nicht auf Nullzeiger prüft, kennzeichnen sich selbst, um Code zu schreiben, der Zeiger erfordert :)
quelle
Wie bei Ausnahmen sind Schutzbedingungen nur dann nützlich, wenn Sie wissen, wie Sie den Fehler beheben können, oder wenn Sie eine aussagekräftigere Ausnahmemeldung anzeigen möchten.
Das Verschlucken eines Fehlers (ob als Ausnahme oder als Guard-Check) ist nur dann richtig, wenn der Fehler keine Rolle spielt. Die häufigste Ursache für das Verschlucken von Fehlern ist der Fehlerprotokollierungscode. Sie möchten keine App zum Absturz bringen, da Sie keine Statusmeldung protokollieren konnten.
Jedes Mal, wenn eine Funktion aufgerufen wird und dies kein optionales Verhalten ist, sollte sie laut und nicht lautlos fehlschlagen.
Bearbeiten: Beim Nachdenken über Ihre Junior-Programmierer-Geschichte klingt es so, als ob ein privates Mitglied auf null gesetzt wurde, wenn dies niemals zugelassen werden sollte. Sie hatten ein Problem mit unangemessenem Schreiben und versuchen, es durch Überprüfen beim Lesen zu beheben. Das ist rückwärts. Zum Zeitpunkt der Identifizierung ist der Fehler bereits aufgetreten. Die Lösung, die Code-Review / Compiler durchsetzen kann, ist keine Schutzbedingung, sondern Get-and-Setter oder Const-Mitglieder.
quelle
Wie andere angemerkt haben, hängt dies davon ab, ob
msgSender
dies legitim sein kann oder nichtNULL
. Das Folgende geht davon aus, dass es niemals NULL sein sollte.Die vorgeschlagene "Korrektur" durch die anderen in Ihrem Team verstößt gegen das Prinzip " Dead Programs Tell No Lies ". Bugs sind so schwer zu finden. Eine Methode, die ihr Verhalten aufgrund eines früheren Problems stillschweigend ändert, erschwert nicht nur das Auffinden des ersten Fehlers, sondern fügt auch einen eigenen zweiten Fehler hinzu.
Der Junior hat Chaos angerichtet, als er nicht nach einer Null gesucht hat. Was ist, wenn dieser Teil des Codes Schaden anrichtet, wenn er weiterhin in einem undefinierten Zustand ausgeführt wird (Gerät ist eingeschaltet, aber das Programm "denkt", dass es ausgeschaltet ist)? Vielleicht tut ein anderer Teil des Programms etwas, das nur sicher ist, wenn das Gerät ausgeschaltet ist.
Mit beiden Ansätzen werden stille Fehler vermieden:
Verwenden Sie die in dieser Antwort vorgeschlagenen Zusicherungen , stellen Sie jedoch sicher, dass sie im Produktionscode aktiviert sind. Dies könnte natürlich zu Problemen führen, wenn andere Zusicherungen mit der Annahme verfasst würden, dass sie in der Produktion ausfallen würden.
Eine Ausnahme auslösen, wenn sie null ist.
quelle
Ich bin damit einverstanden, die Null im Konstruktor abzufangen. Wenn das Mitglied in der Kopfzeile als deklariert ist:
Dann kann der Zeiger nach der Initialisierung nicht mehr geändert werden. War er also beim Erstellen in Ordnung, ist er für die Lebensdauer des Objekts in Ordnung, das ihn enthält. (Das Objekt gerichtet zu werden nicht const sein.)
quelle
NULL
.const
Zeiger muss man sich nichtassert()
außerhalb des Konstruktors befinden, daher scheint diese Antwort sogar ein bisschen besser zu sein als die von @Kristof Provost (die ebenfalls hervorragend war, aber die Frage auch etwas indirekt ansprach). Ich hoffe, dass andere dies stimmen werden, da dies der Fall ist Es macht wirklich nichtassert
in jeder Methode Sinn, wenn Sie nur den Zeiger machen könnenconst
.Das ist absolut gefährlich!
Ich habe unter einem leitenden Entwickler in einer C-Codebasis mit den schäbigsten "Standards" gearbeitet, die sich für dasselbe einsetzten, um blind alle Zeiger auf Null zu prüfen. Der Entwickler würde am Ende Folgendes tun:
Ich habe einmal versucht, eine solche Überprüfung der Vorbedingung in einer solchen Funktion zu entfernen und durch eine
assert
zu ersetzen, um zu sehen, was passieren würde.Zu meinem Entsetzen habe ich Tausende von Codezeilen in der Codebasis gefunden, die Nullen an diese Funktion übergeben haben, aber wo die Entwickler, wahrscheinlich verwirrt, herumgearbeitet und einfach mehr Code hinzugefügt haben, bis die Dinge geklappt haben.
Zu meinem weiteren Entsetzen stellte ich fest, dass dieses Problem an allen möglichen Stellen in der Codebasis auftrat und nach Nullen suchte. Die Codebasis war über Jahrzehnte gewachsen, um sich auf diese Überprüfungen zu verlassen, um im Stillen selbst die am deutlichsten dokumentierten Voraussetzungen zu verletzen. Durch das Entfernen dieser tödlichen Schecks zu Gunsten von würden
asserts
alle logischen menschlichen Fehler über Jahrzehnte in der Codebasis aufgedeckt, und wir würden darin ertrinken.Es dauerte nur zwei scheinbar unschuldige Codezeilen wie diese + Zeit und ein Team, um am Ende tausend angesammelte Bugs zu maskieren.
Dies sind die Arten von Methoden, die dazu führen, dass Fehler von anderen Fehlern abhängen, damit die Software funktioniert. Es ist ein Alptraumszenario . Jeder logische Fehler im Zusammenhang mit der Verletzung derartiger Voraussetzungen wird auf mysteriöse Weise in einer Entfernung von einer Million Codezeilen von der tatsächlichen Stelle angezeigt, an der der Fehler aufgetreten ist, da alle diese Nullprüfungen nur den Fehler verbergen und den Fehler verbergen, bis wir einen Ort erreichen, der vergessen hat um den Fehler zu verbergen.
Das blinde Überprüfen auf Nullen an jeder Stelle, an der ein Nullzeiger eine Vorbedingung verletzt, ist für mich völliger Wahnsinn, es sei denn, Ihre Software ist für Durchsetzungsfehler und Produktionsabstürze so geschäftskritisch, dass das Potenzial dieses Szenarios vorzuziehen ist.
Also würde ich sagen, absolut nicht. Es ist nicht einmal "sicher". Es kann durchaus das Gegenteil sein und alle Arten von Fehlern in Ihrer Codebasis verbergen, die im Laufe der Jahre zu den schrecklichsten Szenarien führen können.
assert
ist der Weg hierher. Verstöße gegen die Voraussetzungen dürfen nicht unbemerkt bleiben, sonst kann das Gesetz von Murphy leicht greifen.quelle
assert
sowohl einen Dokumentationsmechanismus zur Ermittlung der Voraussetzungen als auch eine Möglichkeit zu sehen, einen Abbruch zu erzwingen. Aber ich mag auch die Art des Assertionsfehlers, die genau anzeigt, welche Codezeile einen Fehler verursacht hat und die Assertionsbedingung fehlgeschlagen ist ("Dokumentation des Absturzes"). Kann sich als nützlich erweisen, wenn wir einen Debugbuild außerhalb eines Debuggers verwenden und unvorbereitet sind.In Objective-C wird beispielsweise jeder Methodenaufruf für ein
nil
Objekt als No-Op behandelt, das zu einem Null-Wert ausgewertet wird. Diese Entwurfsentscheidung in Objective-C bietet aus den in Ihrer Frage angegebenen Gründen einige Vorteile. Das theoretische Konzept des Null-Guarding jedes Methodenaufrufs hat einige Vorteile, wenn es gut bekannt gemacht und konsequent angewendet wird.Das heißt, Code lebt in einem Ökosystem, nicht in einem Vakuum. Das null-geschützte Verhalten wäre in C ++ nicht idiomatisch und überraschend und sollte daher als schädlich angesehen werden. Zusammenfassend kann gesagt werden, dass niemand C ++ - Code auf diese Weise schreibt. Tun Sie es also nicht ! Als Gegenbeispiel, beachten Sie jedoch, dass anrufen
free()
oderdelete
auf einNULL
in C und C ++ ist garantiert ein no-op zu sein.In Ihrem Beispiel wäre es wahrscheinlich sinnvoll, eine Behauptung im Konstruktor abzulegen, die
msgSender
nicht null ist. Wenn der Konstruktor eine Methode namens aufmsgSender
sofort, dann keine solche Behauptung wäre notwendig, da es genau dort sowieso abstürzen würde. Da es jedoch nur für die zukünftige Verwendung gespeichertmsgSender
wird, ist es nicht offensichtlich,SignalShutdown()
wie der Wert im Stack ermittelt wurdeNULL
, sodass eine Zusicherung im Konstruktor das Debuggen erheblich vereinfachen würde.Noch besser, der Konstruktor sollte eine
const IMsgSender&
Referenz akzeptieren , die unmöglich sein kannNULL
.quelle
Der Grund, warum Sie aufgefordert werden, Null-Dereferenzen zu vermeiden, besteht darin, sicherzustellen, dass Ihr Code robust ist. Beispiele für Junior-Programmierer vor langer Zeit sind nur Beispiele. Jeder kann den Code versehentlich brechen und eine Null-Dereferenzierung verursachen - insbesondere für globale und Klassen-Globale. In C und C ++ ist dies mit der direkten Speicherverwaltung sogar noch versehentlicher möglich. Sie werden vielleicht überrascht sein, aber so etwas passiert sehr oft. Selbst von sehr sachkundigen, sehr erfahrenen und sehr erfahrenen Entwicklern.
Sie müssen nicht alles auf Null setzen, aber Sie müssen sich vor Dereferenzen schützen, die mit ziemlicher Wahrscheinlichkeit auf Null gesetzt sind. Dies ist häufig der Fall, wenn sie in verschiedenen Funktionen zugewiesen, verwendet und dereferenziert werden. Es ist möglich, dass eine der anderen Funktionen geändert wird und Ihre Funktion beeinträchtigt. Es ist auch möglich, dass eine der anderen Funktionen nicht in der richtigen Reihenfolge aufgerufen wird (z. B. wenn Sie einen Deallocator haben, der separat vom Destruktor aufgerufen werden kann).
Ich bevorzuge den Ansatz, den Ihre Mitarbeiter Ihnen in Kombination mit der Verwendung von Assert mitteilen. In der Testumgebung kommt es zu einem Absturz, sodass es offensichtlicher ist, dass es ein Problem gibt, das in der Produktion behoben werden muss und das ordnungsgemäß fehlschlägt.
Sie sollten auch ein robustes Code-Korrektheitstool wie Coverity oder Fortify verwenden. Und Sie sollten alle Compiler-Warnungen berücksichtigen.
Bearbeiten: Wie andere bereits erwähnt haben, ist es im Allgemeinen auch falsch, stillschweigend zu versagen, wie in mehreren Codebeispielen. Wenn Ihre Funktion den Wert Null nicht wiederherstellen kann, sollte sie einen Fehler (oder eine Ausnahme) an ihren Aufrufer zurückgeben. Der Anrufer ist dafür verantwortlich, dass er entweder seine Anrufreihenfolge festlegt, einen Fehler behebt oder einen Fehler zurückgibt (oder eine Ausnahme auslöst) und so weiter. Schließlich kann eine Funktion ordnungsgemäß wiederherstellen und fortfahren, ordnungsgemäß wiederherstellen und fehlschlagen (z. B. eine Datenbank, bei der eine Transaktion aufgrund eines internen Fehlers für einen Benutzer fehlschlägt, der jedoch nicht ordnungsgemäß beendet wird), oder die Funktion stellt fest, dass der Anwendungsstatus beschädigt ist und nicht wiederherstellbar und die Anwendung wird beendet.
quelle
NULL
Schecks der Weg ist, aber ich freue mich über eine Stellungnahme von jemandem außerhalb meines Arbeitsplatzes, der im Grunde sagt: "Aigh. Scheint nicht zu unvernünftig."Die Verwendung eines Zeiger anstelle einer Referenz würde mir sagen , dass
msgSender
ist nur eine Option , und hier ist der Null - Check würde richtig sein. Das Code-Snippet ist zu langsam, um das zu entscheiden. Vielleicht gibt es noch andere ElementePowerManager
, die wertvoll (oder überprüfbar) sind ...Bei der Wahl zwischen Zeiger und Referenz wäge ich beide Optionen sorgfältig ab. Wenn ich einen Zeiger für ein Mitglied verwenden muss (auch für private Mitglieder), muss ich die
if (x)
Prozedur jedes Mal akzeptieren , wenn ich sie dereferenziere.quelle
Es kommt darauf an, was passieren soll.
Sie haben geschrieben, dass Sie an "einem Unterhaltungselektronikgerät" arbeiten. Wenn irgendwie ein Fehler durch Setzen von
msgSender_
auf eingeführt wirdNULL
möchten Sie dies tunSignalShutdown
aber den Rest seines Betriebs fortsetzt, oderAbhängig von der Auswirkung des nicht gesendeten Abschaltsignals ist Option 1 möglicherweise eine sinnvolle Wahl. Wenn der Benutzer weiterhin Musik hören kann, das Display jedoch weiterhin den Titel des vorherigen Titels anzeigt, ist dies möglicherweise einem vollständigen Absturz des Geräts vorzuziehen.
Wenn Sie Option 1 wählen, ist natürlich eine
assert
(wie von anderen empfohlen) von entscheidender Bedeutung , um die Wahrscheinlichkeit zu verringern, dass ein solcher Fehler während der Entwicklung unbemerkt auftritt. Derif
Nullwächter ist nur für die Ausfallminderung in der Produktion gedacht.Persönlich bevorzuge ich auch den "Crash Early" -Ansatz für Produktions-Builds, aber ich entwickle gerade eine Business-Software, die im Falle eines Fehlers einfach repariert und aktualisiert werden kann. Bei Geräten der Unterhaltungselektronik ist dies möglicherweise nicht so einfach.
quelle