Frage zu MySQL und PHP
AnonymousMapping
- datenbank
1 Matthias Apsel0 AnonymousMapping0 Pitter
ich habe grade vor ein kleines CMS für meine Homepage zu schreiben.
Ich habe mich auf die Erstellung von Maps für Source-basierende Spiele konzentriert!
Nun hab ich eine extra Seite erstellt wo man die Map runterladen kann (Alle Maps sind in einer MySQL Datenbank eingetragen). Wenn man auf eine Map klickt wird eine nächste Seite geöffnet: in dem Fall download.php?id=(Nehmen wir an 1) also download.php?id=1 .
Dort sollen jetzt die Einträge von der Map mit der ID 1 gezeigt werden dies funktioniert leider nicht :/.
Bei der Liste wo alle Maps angezeigt werden hab ich folgenden PHP Code benutzt:
require_once('config.php');
$query = mysql_query("SELECT * FROM Downloads ORDER BY id DESC");
$_GET['id'];
if($id == 0)
{
while($row = mysql_fetch_assoc($query))
{
$mapid = $row['id'];
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
echo "<a href=\"download.php?id={$mapid}\"><p id=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.png\" \></a><br />Spiel: {$mapgame} Map-Art: {$maptyp}</p>";
}
}
Om nah hoo pez nyeetz, AnonymousMapping!
$\_GET['id'];
Was willst du damit bezwecken?
if($id == 0)
Wo kommt diese Variable her?
$mapid = $row['id']; $maptitle = $row['Name']; $mapgame = $row['Spiel']; $maptyp = $row['Map-Art'];
Das umkopieren ist sicher nicht notwendig.
Vielleicht sollte deine Abfrage besser "SELECT * FROM Downloads WHERE
id = " . mysql_realescape($_GET['id'])
heißen, wenn du sowieso nur eine Zeile haben möchtest und die ID per get übergeben wird.
Matthias
mysql_realescape($_GET['id'])
Einerseits heisst die Funktion mysql_real_escape_string() und andererseits ist hier ggf. ein casting nach (int) vernünftiger, da es sich bei der ID wohl hoffentlich nicht um einen String handelt.
Tach!
mysql_realescape($_GET['id'])
Einerseits heisst die Funktion mysql_real_escape_string() und andererseits ist hier ggf. ein casting nach (int) vernünftiger, da es sich bei der ID wohl hoffentlich nicht um einen String handelt.
Wieso "hoffentlich"? Eine ID muss identifizieren. Solange sie das tut, ist es egal, ob sie eine Zahl oder eine andere Zeichenfolge ist.
Escapen oder Casten ergibt möglicherweise daselbe Eregebnis. Aber am Ende nimmt sich beides nichts. Das SQL-Statement ist ein String und Zahlen darin müssen in jedem Fall erstmal geparst werden, egal ob sie in Anführungszeichen stehen oder nicht.
"SELECT * FROM Downloads WHERE
id
= " . mysql_realescape($_GET['id'])
Apropos Anführungszeichen. Ein Escapen bringt natürlich nichts, wenn der Wert nicht auch in Anführungszeichen eingeschlossen ist. Ohne diese ist man ja nicht im String-Kontext sondern immer noch im Befehlskontext. Bei einem $_GET['id'], das
42 AND 1
enthält, hätte die Escape-Funktion nichts zu tun und die SQL-Injection wäre trotz ihrer Verwendung gelungen.
Wenn Escapen, dann immer mit Anführungszeichen. Ob Escapen mit Anführungszeichen oder Typecast die bessere Wahl ist, lässt sich so leicht nicht sagen. In beiden Fälle wird früher beim expliziten Typecast) oder später (beim Typecast im SQL-Parser) ein ungültiger Wert zu 0 werden. Dazu muss man sich für den jeweiligen Anwendungsfall fragen, ob das Schaden anrichten kann oder nicht. Gegebenenfalls muss man nicht nur sein Statement sichern sondern auch die Eingabewerte einer genaueren Wertebereichsprüfung unterziehen.
dedlfix.
Wieso "hoffentlich"? Eine ID muss identifizieren. Solange sie das tut, ist es egal, ob sie eine Zahl oder eine andere Zeichenfolge ist.
Das ist richtig, aber in einer MySQL-Datenbank eignet sich eine (eindeutige, automatisch erstellte/hochgezählte) Ganzzahl sehr gut als Identifikationsmerkaml.
Wenn Escapen, dann immer mit Anführungszeichen. Ob Escapen mit Anführungszeichen oder Typecast die bessere Wahl ist, lässt sich so leicht nicht sagen. In beiden Fälle wird früher beim expliziten Typecast) oder später (beim Typecast im SQL-Parser) ein ungültiger Wert zu 0 werden. Dazu muss man sich für den jeweiligen Anwendungsfall fragen, ob das Schaden anrichten kann oder nicht. Gegebenenfalls muss man nicht nur sein Statement sichern sondern auch die Eingabewerte einer genaueren Wertebereichsprüfung unterziehen.
Hier sind wir wieder bei der Ganzzahl, der Zähler sollte hier sinnvollerweise bei 1 beginnen - 0 ist dann kein gültiger Datensatz - selbstverständlich ist ein negativer Wert ebenfalls unpraktisch, darum am besten unsigned :)
Om nah hoo pez nyeetz, suit!
mysqlreal_escape oder eine sonstige falsche Schreibweise endet ja glücklicherweise mit einer aussagekräftigen Fehlermeldung.
@AnonymousMapping
Die fehlenden Anführungszeichen sollten auch zu einem Fehler oder zumindest zu unerwartetem Verhalten führen: mysql_error ist mein Freund.
Ich habe übrigens während der Entwicklung das Error-Reporting voll aufgedreht und lass mir bei Bedarf wichtige Variablen usw. ausgeben (etwa über include('kontrollausgaben')
$_GET, $_POST, $_SESSION, mysql_errno, mysql_error, $db_abfrage, $abfrage_ergebnis.
Matthias
Tach!
mysqlreal_escape oder eine sonstige falsche Schreibweise endet ja glücklicherweise mit einer aussagekräftigen Fehlermeldung.
Ja.
Die fehlenden Anführungszeichen sollten auch zu einem Fehler oder zumindest zu unerwartetem Verhalten führen: mysql_error ist mein Freund.
Njein. Nur wenn du beim SQL-Injection-Versuch einen Syntaxfehler einbaust. Solange du es ohne einen solchen schaffst, gibt es keinen Grund etwas zu beanstanden. Das ist zwar aus deiner Sicht als Autor eventuell als unerwartetes Verhalten einzustufen, aber das System kann das nicht erkennen.
Ich habe übrigens während der Entwicklung das Error-Reporting voll aufgedreht und lass mir bei Bedarf wichtige Variablen usw. ausgeben (etwa über include('kontrollausgaben')
Das ist gut und hilfreich, aber für 100% Fehlerfreiheit im Sinne der Aufgabenstellung reicht das noch lange nicht.
dedlfix.
leider hat nichts geholfen :/
Hab mal durch probiert, mein Code sieht nun so aus:
require_once('config.php');
$ID = $_GET['id'];
if($ID="")
{
$ID = 0;
}
if($ID!=0)
{
$queryway = "SELECT * FROM Downloads WHERE `id` = " . mysql_real_escape_string($ID);
$query = mysql_query($queryway);
while($row = mysql_fetch_assoc($query))
{
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
$mapdesc = nl2br($row['Map-Beschreibung']);
echo "<div class=\"post\"><p class=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.png\" \><br />Spiel: {$mapgame} Map-Art: {$maptyp}<br />{$mapdesc}<br /><a href=\"Maps/{$maptitle}.bsp\">Download!</a></p></div>";
}
}
else
{
$query = mysql_query("SELECT * FROM Downloads ORDER BY id DESC");
while($row = mysql_fetch_assoc($query))
{
$mapid = $row['id'];
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
echo "<div class=\"post\"><a href=\"download.php?id={$mapid}\"><p class=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.jpg\" \></a><br />Spiel: {$mapgame} Map-Art: {$maptyp}</p></div>";
}
}
Ich habe nun zum Beispiel ein neuen Artikel erstellt:
nun wenn ich die URL download.php?id=1 , dann werden immer noch alle Artikel angezeigt.
Vielen Dank habs nun hinbekommen:
if($ID=="")
{
$ID = 0;
}
if($ID!==0)
{
$queryway = "SELECT * FROM Downloads WHERE `id` = " . mysql_real_escape_string($ID);
$query = mysql_query($queryway);
while($row = mysql_fetch_assoc($query))
{
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
$mapdesc = nl2br($row['Map-Beschreibung']);
echo "<div class=\"post\"><p class=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.png\" \><br />Spiel: {$mapgame} Map-Art: {$maptyp}<br />{$mapdesc}<br /><a href=\"Maps/{$maptitle}.bsp\">Download!</a></p></div>";
}
}
else
{
$query = mysql_query("SELECT * FROM Downloads ORDER BY id DESC");
while($row = mysql_fetch_assoc($query))
{
$mapid = $row['id'];
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
echo "<div class=\"post\"><a href=\"download.php?id={$mapid}\"><p class=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.jpg\" \></a><br />Spiel: {$mapgame} Map-Art: {$maptyp}</p></div>";
}
}
Der Fehler lag in den if-Anweisungen.
Ein = bedeutet ja festlegen und zwei = bedeuten abfragen.
Ja und ich habe festlegen benutzt.
Vielen Dank für euere Hilfe :)
Tach!
Hab mal durch probiert, mein Code sieht nun so aus:
Problem ist ja gelöst, hier noch einige allgemeine Anmerkungen:
$ID = $\_GET['id'];
Auch wenn es sehr oft so zu sehen ist, du brauchst die Werte aus $_GET/$_POST nicht zu kopieren. Du kannst sie einfach so verwenden.
if($ID="") { $ID = 0; } if($ID!=0)
Diese Zeilen inklusive der Umkopierzeile lassen sich zusammenfassen zu
if (!empty($_GET['id']))
$queryway = "SELECT \* FROM Downloads WHERE `id` = " . mysql\_real\_escape\_string($ID);
Bitte beachte auch den weiteren Verlauf der Escape-Diskussion. Ohne Anführungszeichen verhindert das mysql_real_escape_string() gar nichts. Das Ergebnis von mysql_real_escape_string() ist nur im String-Kontext sinnvoll und kann auch nur dort vor SQL-Injection schützen.
$queryway = sprintf("SELECT * FROM Downloads WHERE id
= '%s'", mysql_real_escape_string($_GET['id']));
Mit sprintf() bleibt der Statement-String in einem Stück stehen und macht die Sache nicht noch durch Stringverkettungen unübersichtlicher.
$query = mysql\_query($queryway); while($row = mysql\_fetch\_assoc($query))
(My)SQL-Querys können aus verschiedenen Gründen sich mit einer Fehlersignalisierung zurückmelden. Das Funktionsergebnis von mysql_query() ist dann false, was kein gültiger Wert für die Fetch-Funktion ist. Wenn du robust programmieren willst, beachtest du solche Fehlermöglichkeiten in deinem Programmablauf.
{ $maptitle = $row['Name']; $mapgame = $row['Spiel']; $maptyp = $row['Map-Art']; $mapdesc = nl2br($row['Map-Beschreibung']); echo "<div class=\"post\"><p class=\"title\">{$maptitle}</p><p><img src=\"images/maps/{$maptitle}.png\" \><br />Spiel: {$mapgame} Map-Art: {$maptyp}<br />{$mapdesc}<br /><a href=\"Maps/{$maptitle}.bsp\">Download!</a></p></div>";
Auch hier ist das Umkopieren nicht notwendig. Es gibt eine Systax für komplexe Variablenausdrücke in ""-Strings. Die brauchst du aber nicht, weil du bei Beachtung des Kontextwechsels sowieso noch htmlspecialchars() aufrufen musst, wozu du den String jedoch unterbrechen musst. Oder du nimmst wieder printf() mit Platzhaltern. (Diesmal printf() mit ohne s am Anfang, weil die Ausgabe ja sofort erfolgen soll.)
dedlfix.
Ein ganz anderer Hinweis:
Bitte gewöhn Dir schnellstens:
select * .....
ab!
Gerade wenn man mit vielen Leuten bzw. über lange Zeiträume so arbeitet, wird man irgendwann an dieser Stelle stolpern, weil jemand noch eine Spalte dazu benötigt u.ä. Dinge.
Besser:
select dat1, dat2, dat3,.....
(also konkrete Felder)
abfragen.
Tach!
Bitte gewöhn Dir schnellstens:
select * .....
ab!
Eine solche Dringlichkeit sehe ich nicht als geboten an. Wenn man alle Felder benötigt, warum soll man dann nicht * verwenden? Weil irgendwann später mal ein Feld in der Tabelle hinzukommen kann (aber nicht zwangsläufig muss) und man zu $irgendwas ist, sämtliche Referenzen im Code auf diese Tabelle anzupassen? Potentielles menschliches Versagen kann ja wohl kaum eine Begründung sein.
Gerade wenn man mit vielen Leuten bzw. über lange Zeiträume so arbeitet, wird man irgendwann an dieser Stelle stolpern, weil jemand noch eine Spalte dazu benötigt u.ä. Dinge.
Diese Begründung finde ich nicht sehr verständlich formuliert. Designänderungen im wichtigen Unterbau eines Systems muss man gebührend berücksichtigen. Punkt. Alles andere ist nur Augenauswischerei. Wenn du befürchten musst, dass durch Änderungen das System unbemerkt gravierend beeinflusst werden kann, hast du ein ganz anderes Problem, was du nicht im Programmcode finden wirst.
Die Diskussion um SELECT * ist schon öfter geführt wurden, jedoch soweit ich weiß in diesem Raum lange nicht mehr. Die bisherigen Argumente dagegen waren für mich nicht überzeugend genug, dem SELECT * ein generelles Hausverbot auszusprechen. Kennst du Argumente, nach denen es pasuchal und nicht nur im Einzelfall wirklich sinnvoll ist, darauf zu verzichten? Allgemein betrachtet bringt ein * bei weniger benötigten Daten gegenüber einer Einzelaufzählung nicht zwangsläufig einen Nachteil. Wenn die Datenmenge der zusätzlichen Spalten zu gering ist, bemerkt den Unterschied niemand. Wenn man viele Spalten oder ein großes Blob-Feld der Tabelle hinzufügt, hat man noch ganz andere Probleme zu lösen.
dedlfix.
Tach!
Bitte gewöhn Dir schnellstens:
select * .....
ab!Eine solche Dringlichkeit sehe ich nicht als geboten an. Wenn man alle Felder benötigt, warum soll man dann nicht * verwenden? Weil irgendwann später mal ein Feld in der Tabelle hinzukommen kann (aber nicht zwangsläufig muss) und man zu $irgendwas ist, sämtliche Referenzen im Code auf diese Tabelle anzupassen? Potentielles menschliches Versagen kann ja wohl kaum eine Begründung sein.
Doch!
Ich sage das aus einer Erfahrung bei meiner täglichen Arbeit als DB-Admin.
Programmierer sollen sich sowas gar nicht erst angewöhnen, denn ist einfach ein lachser Umgang mit Daten.
Gerade wenn man mit vielen Leuten bzw. über lange Zeiträume so arbeitet, wird man irgendwann an dieser Stelle stolpern, weil jemand noch eine Spalte dazu benötigt u.ä. Dinge.
Diese Begründung finde ich nicht sehr verständlich formuliert. Designänderungen im wichtigen Unterbau eines Systems muss man gebührend berücksichtigen. Punkt. Alles andere ist nur Augenauswischerei. Wenn du befürchten musst, dass durch Änderungen das System unbemerkt gravierend beeinflusst werden kann, hast du ein ganz anderes Problem, was du nicht im Programmcode finden wirst.
In meiner Firma arbeiten 450 Leute, davon ca. 80 Entwickler...
Wie oben angedeutet, führen solche vermeidbaren selects über lange Zeiträume _immer_ zu Problemen. Wenn man es zudem nicht mit wenigen DBs zu tun hat, hinterfragt man den Sinn einer Spalte viel zu spät bzw. merkt u.U. gar nicht, dass die nicht genutzt wird. Es gehört einfach ebenso wie es dazu gehört, Variablen in Programmiersprachen zu deklarieren dazu, auch DB-Queries gezielt auszuführen.
Die Diskussion um SELECT * ist schon öfter geführt wurden, jedoch soweit ich weiß in diesem Raum lange nicht mehr. Die bisherigen Argumente dagegen waren für mich nicht überzeugend genug, dem SELECT * ein generelles Hausverbot auszusprechen. Kennst du Argumente, nach denen es pasuchal und nicht nur im Einzelfall wirklich sinnvoll ist, darauf zu verzichten? Allgemein betrachtet bringt ein * bei weniger benötigten Daten gegenüber einer Einzelaufzählung nicht zwangsläufig einen Nachteil. Wenn die Datenmenge der zusätzlichen Spalten zu gering ist, bemerkt den Unterschied niemand. Wenn man viele Spalten oder ein großes Blob-Feld der Tabelle hinzufügt, hat man noch ganz andere Probleme zu lösen.
Doch generell, zumindest langfristig und wenn man nicht nur für eine 3-Mann-Bude arbeitet! (siehe oben)
Und direkt aus dem Code des Fragestellers:
###############################
$query = mysql_query("SELECT * FROM Downloads ORDER BY id DESC");
while($row = mysql_fetch_assoc($query))
{
$mapid = $row['id'];
$maptitle = $row['Name'];
$mapgame = $row['Spiel'];
$maptyp = $row['Map-Art'];
###############################
Warum ruft man "alles" ab und nutzt dann nur ein paar Werte?
"Kann" ja sein, dass dies genau hier passt... "kann" sein!
Ich finde bei solchen Dingen immer Analogien aus dem realen Leben hilfreich:
Packst Du beim Einkauf alles, was du siehst und was Dir gefällt in den Wagen aber legst an der Kasse nur das aufs Band, was Du wirklich haben willst? Nein? Aber _hier_ soll das sinnvoll sein?
Tach!
Und direkt aus dem Code des Fragestellers:
[...]
Warum ruft man "alles" ab und nutzt dann nur ein paar Werte?
"Kann" ja sein, dass dies genau hier passt... "kann" sein!
Wir beiden kennen die Struktur der abgefragten Tabelle nicht. Möglich sein kann alles. Möglich sein kann, dass er alle Felder benötigt und nie den Bedarf hat, die Tabelle zu erweitern ohne die Daten an allen SELECT-*-Stellen zu benötigen.
Ich finde bei solchen Dingen immer Analogien aus dem realen Leben hilfreich:
Packst Du beim Einkauf alles, was du siehst und was Dir gefällt in den Wagen aber legst an der Kasse nur das aufs Band, was Du wirklich haben willst? Nein? Aber _hier_ soll das sinnvoll sein?
Er hat von einer Tabelle selektiert, nicht von Supermarkt und nicht vom gesamten DBMS. Nicht alles was hinkt ist ein Vergleich. Und dieser Vergleich scheint mir eher eine Schleifspur auf dem Boden hinterlassen zu haben, neben der ein paar ausgerissene Haare liegen, die der Zugkraft nicht standhielten.
dedlfix.
Ich finde bei solchen Dingen immer Analogien aus dem realen Leben hilfreich:
Packst Du beim Einkauf alles, was du siehst und was Dir gefällt in den Wagen aber legst an der Kasse nur das aufs Band, was Du wirklich haben willst? Nein? Aber _hier_ soll das sinnvoll sein?Er hat von einer Tabelle selektiert, nicht von Supermarkt und nicht vom gesamten DBMS. Nicht alles was hinkt ist ein Vergleich. Und dieser Vergleich scheint mir eher eine Schleifspur auf dem Boden hinterlassen zu haben, neben der ein paar ausgerissene Haare liegen, die der Zugkraft nicht standhielten.
Du verstehst die Analogie nur nicht.
Warum holst Du ein Feld aus der Tabelle (= unnötiger Traffic), wenn Du es nicht benötigst?
Tach!
Warum holst Du ein Feld aus der Tabelle (= unnötiger Traffic), wenn Du es nicht benötigst?
Wer sagt denn, dass das im Falle des OP passiert oder dass ich das mache?
dedlfix.
Tach!
Warum holst Du ein Feld aus der Tabelle (= unnötiger Traffic), wenn Du es nicht benötigst?
Wer sagt denn, dass das im Falle des OP passiert oder dass ich das mache?
Dement?
select * -> hole alles!
Von mir der Hinweis, dass * nicht unbedingt das ist, was Du verarbeiten willst und auf lange Sicht (bei mehr als nur Dir als Programmierer) mit Sicherheit kaum so bleibt!
Tach!
Warum holst Du ein Feld aus der Tabelle (= unnötiger Traffic), wenn Du es nicht benötigst?
Wer sagt denn, dass das im Falle des OP passiert oder dass ich das mache?
Dement?
Nein, durchaus noch nicht.
select * -> hole alles!
Und was ist mit deinem Nachsatz? "wenn Du es nicht benötigst" - hat jemand behauptet dass ich alles abfrage, wenn ich es nicht benötigt oder geht aus dem Posting des OP hervor, dass er nicht alles benötigt?
Von mir der Hinweis, dass * nicht unbedingt das ist, was Du verarbeiten willst und auf lange Sicht (bei mehr als nur Dir als Programmierer) mit Sicherheit kaum so bleibt!
Das ist alles Spekulatius. Du geht von großen Projekten aus, bei denen aus welchen Gründen auch immer die Beteiligten die Übersicht verloren haben und nicht in der Lage sind, die Auswirkungen von Änderungen an einer Stelle im Rest angemessen zu berücksichtigen. Das muss beim OP nicht der Fall sein, weswegen man nicht "schnellstens" die dortigen Maßnahmen zum Bergen des Kindes aus dem Brunnen auf dieses oder alle anderen Szenarien überstülpen muss.
dedlfix.
Das ist alles Spekulatius. Du geht von großen Projekten aus, bei denen aus welchen Gründen auch immer die Beteiligten die Übersicht verloren haben und nicht in der Lage sind, die Auswirkungen von Änderungen an einer Stelle im Rest angemessen zu berücksichtigen. Das muss beim OP nicht der Fall sein, weswegen man nicht "schnellstens" die dortigen Maßnahmen zum Bergen des Kindes aus dem Brunnen auf dieses oder alle anderen Szenarien überstülpen muss.
Nein, das hat nichts mit "Übersicht verloren" zu tun. In manchen Firmen ist das Statement "select * ...." (nicht zu verwechseln mit "select count(*) ....."!) _grundsätzlich_ verboten. Man fragt sich schon, wieso, oder?
Es ist einfach unsinnig und fehlerträchtig.
Und ärgerlich, dass hier sowas an viele potentiell angehende Entwickler/Admins weitergegeben wird! Die Leute stolpern irgendwann drüber und verstehen es dann auf die harte Tour - mir solls egal sein, solange ich mit denen nicht zusammen arbeiten muss!
Bye!
Tach!
Nein, das hat nichts mit "Übersicht verloren" zu tun.
Na, was denn dann? Du kannst als Gründe nur anführen,
In manchen Firmen ist das Statement "select * ...." (nicht zu verwechseln mit "select count(*) ....."!) _grundsätzlich_ verboten. Man fragt sich schon, wieso, oder?
Es ist dann nicht unsinnig, wenn man alle Daten braucht und die Reihenfolge im Resultset nicht relevant ist (z.B. bei Abfrage der Daten über Spaltennamen). Für andere Fälle propagiere ich es ja gar nicht.
Hier noch eine sinnvolle Anwendung, bei der es sogar unsinnig ist, kein * zu verwenden: Subqueries with EXISTS or NOT EXISTS.
Es ist einfach unsinnig und fehlerträchtig.
Der Grund ist also schlicht und einfach nur: weil manche die Konsequenzen ihres Tun nicht genügend bedenken, legt man mal eben grundsätzlich für alle künstliche Gesetze auf, auf dass man diese auch ja nicht wieder hinterfragen muss oder darf. Dabei gibt es zum Beispiel mit TDD bessere Ansätze negative Auswirkungen von fehlerhaften Änderungen zu erkennen.
Aber egal, gegen Prinzipienreiter kommt man nur schwer an.
dedlfix.