[JAVA] Code, der schwer zu debuggen und zu analysieren ist

Haben Sie jemals das Buch "Readable Code" gelesen? Es enthält viele Tipps zum Schreiben von einfach zu lesendem Code, z. B. zum Benennen von Variablen und zum Schreiben von Kommentaren. Es ist ein Buch, das Programmierer lesen sollten. Wenn Sie wissen, was in diesem Buch geschrieben steht, können Sie einfach zu lesenden Code schreiben.

Ich denke jedoch nicht, dass Code, der leicht zu lesen ist, guter Code ist. ** Es ist wichtig, dass es nicht nur leicht zu lesen, sondern auch leicht zu debuggen und zu analysieren ist ** (nicht nur "lesbar", sondern auch "debuggerbar" und "analysierbar"). Um den Code so schnell wie möglich zu verstehen und ihn beim Verbessern oder Behandeln eines Problems richtig zu korrigieren, ist es meiner Meinung nach besser, den Code aus dieser Perspektive im Voraus zu schreiben.

Ich lese und analysiere den Quellcode verschiedener OSSs aufgrund meiner Arbeit, aber selbst wenn der Code schön aussieht und keine Verschwendung enthält, gibt es viele Codes, die schwer zu debuggen und zu analysieren sind.

Ich möchte mit einigen Beispielen erklären.

* Alle zur Erläuterung verwendeten Technologien und der Quellcode beziehen sich auf Java. Aufgrund von Unwissenheit und voreingenommenem Wissen kann es falsch sein oder an Objektivität mangeln. Wenn Sie Meinungen oder Vorschläge haben, wäre ich Ihnen dankbar, wenn Sie dies kommentieren könnten.

Missbrauch der Reflexion

Reflexion und Missbrauch von Techniken, die Reflexion verwenden (z. B. DI), erschweren die Analyse des Quellcodes.

DI hat den Effekt, dass der Grad der Kopplung zwischen Klassen geschwächt wird und die Entwicklung und Prüfung rationalisiert wird.

DI1.png

Es ist jedoch schwierig, Abhängigkeiten zu betrachten. Selbst wenn Sie die Anrufhierarchie mit einer IDE wie Eclipse überprüfen, können Sie Anrufe nicht durch DI (Reflection) erkennen. Um einen Fehler zu beheben und festzustellen, ob sich der Fix auf andere Funktionen auswirkt, müssen Sie alle Anrufer überprüfen. Es ist kein Problem, wenn die Implementierung klar zeigt, wo der DI verwendet wird, aber wenn dies nicht der Fall ist, ist es schwierig, den Einflussbereich zu identifizieren.

DI2.png

Sie können auch mithilfe von Reflection auf private Methoden zugreifen. Ich glaube nicht, dass ein Programmierer überhaupt in Betracht zieht, durch Reflexion aufzurufen, um das Ausmaß der Auswirkungen der Änderung der Logik einer privaten Methode zu sehen. Sie würden nicht viel zeitaufwändige Nachforschungen anstellen und denken, dass es keinen so kniffligen Code geben könnte. Das Schreiben von kniffligem Code birgt Risiken.

Missbrauch von Anmerkungen

Anmerkungen ähneln der Einführung einer neuen Grammatik mit Lernkosten. Es kann effektiv zur Verbesserung der Produktivität beitragen, aber unter dem Gesichtspunkt der Wartbarkeit ist es nicht alles gut.

Der folgende Code verwendet eine Bibliothek namens Lombok, um und zu kommentieren

public class Person {
    private String firstName;
    private String familyName;
 
    public Person(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getFamilyName() {
        return familyName;
    }
 
    public void setFamilyName(String familyName) {
        this.familyName = familyName;
    }
 
    @Override
    public String toString() {
        return "Person(firstName=" + firstName + ", familyName=" + familyName + ")";
    }
}

Es kann so vereinfacht werden.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;

@ToString
@AllArgsConstructor
@Data
public class Person
{
   private String lastName;
   private String firstName;
}

Der Quellcode enthält keine Getter-, Setter- oder toString-Methoden, diese sind jedoch vorhanden. Es ist sicherlich einfacher zu erkennen, und ich denke, es ist schwieriger, Fehler zu erstellen, da langweilige Fehler beim Kopieren und Einfügen auftreten. Wenn Sie viele dieser Objekte für eine groß angelegte Entwicklung benötigen, ist Lombok eine Lösung zur Verbesserung Ihrer Entwicklungsproduktivität.

Letzteres ist jedoch für Programmierer ohne Lombok-Kenntnisse nicht sofort verständlich. Andererseits kann kein Java-Programmierer den vorherigen Code nicht lesen.

Darüber hinaus ist die Verarbeitung von Anmerkungen häufig schwierig zu debuggen. Wenn Sie einen Haltepunkt für getFirstName () festlegen möchten, wissen viele Leute nicht, was sie mit letzterem tun sollen. Abhängig von der IDE ist es nicht möglich, nur "toString ()" im Debug-Modus zu stoppen oder die Implementierung von "toString ()" zu überprüfen, ohne "delombok" zu verwenden.

Moderne Frameworks und Bibliotheken fügen häufig ihre eigenen Annotationen hinzu, und ihre Verwendung in Kombination leidet unter der "Annotation Hell".

@PreAuthorize("#user.id == #u?.id")
public UserDTO access(@P("user") @Current UserEntity requestUser,
                      @P("u") @PathVariable("user-id") UserEntity user)

@PreAuthorize("#user.id == #uid && (#order == null || #order?.user?.id == #uid)")
public Message access(@Current @P("user") UserEntity user,
                      @PathVariable("user-id") @P("uid") Long uid,
                      @PathVariable("order-id") @P("order") OrderEntity order)

Ich denke nicht, dass es gut ist, einfach eigene Anmerkungen zu erstellen.

Missbrauch anderer "schwarzer Magie"

Neben Reflexionen und Anmerkungen gibt es viele knifflige Techniken. Unter diesen ist es am besten, die Verwendung einer Technik namens "schwarze Magie" so weit wie möglich zu vermeiden.

Beispielsweise können Sie "sun.misc.Unsafe" verwenden, um ein "statisches endgültiges" Klassenfeld neu zu schreiben. Mit Javassist können Sie den Bytecode einer kompilierten Klasse direkt neu schreiben, um ihr Verhalten zur Laufzeit zu ändern. Schwarze Magie kann eine effektive Methode sein, wenn es keine anderen Mittel gibt, um sie zu realisieren, oder wenn es eine bestimmte Skala gibt, aber sie sollte nicht unnötig angewendet werden.

Missbrauch des Cache

Caches werden in verschiedenen Teilen der Software verwendet, um die Verarbeitungszeit zu verkürzen. Es kann jedoch auch ein Ärgernis bei der Analyse sein. Da das Verhalten je nach Status des Caches unterschiedlich sein kann, scheint es, dass das Verhalten zum Zeitpunkt der Analyse nicht regelmäßig ist. Beispielsweise werden Datenaktualisierungen möglicherweise nicht sofort auf dem Bildschirm angezeigt, oder es kann sich ändern, ob ein DB-Zugriffsfehler aufgetreten ist oder nicht.

cache.png

Dies kann auch ein Faktor beim Erstellen komplexer Fehler wie Speicherlecks sein. Daher sollten Sie Optionen wie das Deaktivieren des Caches bei der Bereitstellung in Betracht ziehen.

Missbrauch von Technologie

Wenn der Programmierer Java beherrscht, kann er Java-Anwendungen implementieren. Wenn Sie Servlet / JSP beherrschen, können Sie außerdem eine Webanwendung erstellen. Wenn Sie Spring Boot beherrschen, können Sie Ihre Entwicklungsproduktivität steigern. Wenn Sie Thymeleaf und Bootstrap beherrschen, können Sie Design und Logik trennen und die Qualität und Lesbarkeit Ihres Designs verbessern. Wenn Sie SAML 2.0 und Sprig Security beherrschen, können Sie die Authentifizierung an ein externes System delegieren. Wenn Sie mit JUnit und Gradle vertraut sind, können Sie das Testen und Erstellen automatisieren. Es gibt viele andere Dinge, die durch Lernen erreicht werden können.

Die Anzahl der Programmierer, die alle ausführen können, ist jedoch begrenzt. Unterschiedliche Personen haben unterschiedliche Lesbarkeitsstandards. Je mehr Fähigkeiten Sie verwenden, desto weniger Programmierer können den gesamten Code lesen. Wenn Sie die Anzahl der Techniken erhöhen, die Sie in den dunklen Wolken anwenden, verringert sich die allgemeine Lesbarkeit. Selbst wenn Sie die Implementierung teilweise in einer anderen Sprache oder Technologie umschreiben, weil sie präziser geschrieben werden kann, verringert sich die Lesbarkeit und die Analysezeit erhöht sich, wenn weniger Programmierer damit umgehen können.

Lügner Kommentare, Javadoc, Protokolle

Ich denke, viele Leute denken nicht so sehr an Kommentare und Javadoc wie an den Quellcode (wie ich ...).

Trotz der folgenden Beschreibung bricht die Tatsache, dass eine Klasse aus Version 3.0.0 eingeführt wurde, die Voraussetzungen für die Untersuchung und führt zu Verwirrung.

/**
 * ...
 * @since 2.0.1
 */

Es ist besser, nichts zu schreiben, als falsche Kommentare zu schreiben, Javadoc. Da Sie dies anhand des Verlaufs im Versionsverwaltungssystem sehen können, ist es möglicherweise sicherer, nichts zu verwenden, bei dem die Möglichkeit einer falschen Doppelverwaltung besteht, z. B. "@ seit".

Es gibt andere Dinge, bei denen ein hohes Risiko besteht, Lügen zu verursachen. Durch Kopieren und Einfügen der Feldvariablen "Logger" ist es einfach, den folgenden Code zu implementieren, dessen Klassenname und tatsächliche Klasse unterschiedlich sind.

public class DeleteController {

    private static final Logger log = LoggerFactory.getLogger(ImportController.class);

Die von diesem Logger ausgegebenen Protokolle können verwirrend sein. Möglicherweise möchten Sie den gesamten Quellcode überprüfen, bevor Sie ihn für die Produktion freigeben.

Schlüsselwerte aufteilen

Das Problem bei der Untersuchung ist die Implementierung, die einen Teil des in der Eigenschaftendatei definierten Eigenschaftsschlüssels in Konstanten unterteilt. Angenommen, die folgenden Eigenschaften sind definiert.

account.lockout.count=10
account.lockout.interval=60000

Wenn Sie den folgenden Code schreiben, um dies zu erhalten,

private static final String ACCOUNT_LOCK_PREFIX = "account.lockout.";
   ...

private void doSomething() {
	ResourceBundle rb = ResourceBundle.getBundle("application");
	String count = rb.getString(ACCOUNT_LOCK_PREFIX + "count");
	String interval = rb.getString(ACCOUNT_LOCK_PREFIX + "interval");

Selbst wenn ich den gesamten Quellcode mit dem Eigenschaftsschlüssel " account.lockout.count "greife, wird er nicht getroffen. Wenn Sie basierend auf dem Ergebnis entscheiden, dass diese Eigenschaft nicht verwendet wird, und diesen Schlüssel aus der Eigenschaftendatei entfernen, erhalten Sie zur Laufzeit eine "MissingResourceException". Wir empfehlen, die Tastenketten nicht zu teilen.

Nutzloses Protokoll

Es ist erforderlich, Fehlerprotokollmeldungen sowie Eigenschaftsschlüssel zu berücksichtigen. Die Nachricht sollte so sein, dass Sie den Quellcode durchsuchen können, um festzustellen, wo er verwendet wird. Wenn mehrere identische Protokollnachrichten vorhanden sind, kann der Speicherort nicht identifiziert werden. Es wäre schön, wenn die Ausgabeformateinstellung der Protokollbibliothek den Dateinamen und die Anzahl der Zeilen des Quellcodes in das Protokoll ausgeben würde, aber unter Berücksichtigung der Leistung (oder weil die Standardeinstellung nicht überprüft wurde) ist dies nicht der Fall. Es gibt auch viele.

Zum Beispiel die folgende Protokollausgabeverarbeitung

log.warn("{} not found", data.getId());

Wenn möglich, ist es besser, es in die folgenden zwei zu unterteilen, damit der Ausgabeort leicht identifiziert werden kann.

log.warn("User does not exist in user table: {}", user.getId());

Wann

log.warn("Group does not exist in group table: {}", group.getId());

Bei der Protokollausgabe sind noch viele andere Dinge zu beachten. Eine Implementierung, die eine Ausnahme abfängt und diese Ausnahme nicht ausnutzt, wird das Problem verbergen.

} catch (Exception e) {
}

Die Analyse des Quellcodes ist einfacher, wenn Sie Protokolle ausgeben, die für die Untersuchung auf der richtigen Protokollebene und zur richtigen Zeit nützlich sind.

Darüber hinaus wäre es für Webanwendungen sehr nützlich, die Protokollstufe ohne Neustart ändern zu können. Die Software OpenAM, mit der ich arbeite, verfügt über diese Funktion, mit der Sie die Protokollstufe in einer Webanwendung für Administratoren ändern können. Selbst wenn in der Produktionsumgebung ein Problem auftritt, können Sie die Protokollstufe vorübergehend erhöhen und dann das Ereignis reproduzieren, um das Protokoll abzurufen. Sobald Sie es erhalten haben, können Sie die Protokollstufe wiederherstellen.

OpenAM verfügt außerdem über eine REST-API zur Fehlerbehebung. Wenn Sie eine Anfrage an eine bestimmte URL senden, werden die Systemkonfigurationsdatei, das Debug-Protokoll für einen bestimmten Zeitraum, der Thread-Dump usw. gemeinsam in eine Zip-Datei komprimiert und ausgegeben. Das Erstellen einer Funktion zur Analyse auf diese Weise ist während des Betriebs eine große Hilfe.

Wie die Einstellungen das Verhalten bestimmen

Bei der Entwicklung eines Frameworks ist es häufig schwierig, das Verhalten zu verfolgen, wenn Sie einen Mechanismus erstellen, in dem Einstellungen wie XML-Dateien und Java Config das Verhalten bestimmen.

Vor kurzem (auf schlechte Weise) war ich süchtig danach, Spring Security-Zugriffskontrollen festzulegen (Authentifizierung, Autorisierung, Sitzungsverwaltung, CSRF-Maßnahmen usw.). Wenn Sie den folgenden Code schreiben, wird der Zugriff basierend auf dem Inhalt gesteuert. Wenn Sie jedoch versuchen, eine etwas komplizierte Sache zu realisieren, läuft er nicht wie erwartet.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/admins/**").authorizeRequests().antMatchers("/admins/login").permitAll()
            .antMatchers("/admins/**").authenticated().and().formLogin().loginProcessingUrl("/admins/login")
            .loginPage("/admins/login").failureUrl("/admins/login?error").defaultSuccessUrl("/admins/main")
            .usernameParameter("username").passwordParameter("password").and().logout().logoutUrl("/logout")
            .logoutSuccessUrl("/").deleteCookies("JSESSIONID").and().sessionManagement().sessionFixation()
            .none().and().csrf();
}

Obwohl es "ein wenig kompliziert" war, war es eine Ebene, die leicht implementiert werden konnte, indem dem Servlet-Filter Logik hinzugefügt wurde. Welche Einstellungen wurden vorgenommen, wenn versucht wurde, diese Logik in die Regeln von Spring Security zu integrieren? Ich kann mich nicht entscheiden, welche Art von Klasse oder Methode implementiert werden soll.

Sie können auch Ihren eigenen Servlet-Filter debuggen, um schnell zu erkennen, wo das Problem liegt. Es ist jedoch nicht einfach, die generische Logik von Spring Security zu debuggen. Wenn Sie eine Implementierung oder Einstellung anfordern, die zur Laufzeit nicht wie die oben beschriebene Methode configure () debuggt werden kann, ist die Analyse schwierig.

Wenn Sie einen neuen Mechanismus erstellen, der komplizierte Dinge nur durch Festlegen realisieren kann, müssen Sie verschiedene Muster annehmen und den Entwickler darauf aufmerksam machen, warum dies nicht funktioniert. Es sollte auch berücksichtigt werden, dass die Lernkosten der Entwickler für die Verwendung steigen werden.

Streben nach übermäßiger Kürze

Obwohl es im lesbaren Code geschrieben wurde, ist der Quellcode nicht leicht zu lesen, wenn er gekürzt wird. Es ist nicht verwunderlich, dass der folgende Code für den Golf-Konkurrenz-Quellcode keineswegs leicht zu lesen ist.

class G{static public void main(String[]b){for(int i=0;i<100;System.out.println((++i%3<1?"Fizz":"")+(i%5<1?"Buzz":i%3<1?"":i)));}}

Sie können damit auch keine Haltepunkte setzen. Ich denke, es ist besser, redundanten Code zu schreiben, als Techniken zu verwenden, um ihn zu verkürzen. Ich bin sogar der Meinung, dass das Stereotyp "Kurzcode = einfach zu lesender Code" ein Faktor bei der Erstellung von Code ist, der schwer zu analysieren ist.

Neue Grammatik

In Java 8 wurden Lambda-Ausdrücke hinzugefügt, aber es gibt viele Fälle, in denen dies auch schwierig zu debuggen ist. Zum Beispiel die folgende for-Anweisung

for (String n : notifications) {
    System.out.println(n);
}

In Java8 können Sie auch schreiben:

descriptions.forEach(n -> System.out.println(n));

Wenn Sie in dieser for-Anweisung die Schleife nur stoppen möchten, wenn die Länge der Zeichenfolge n 100 überschreitet, setzen Sie die folgenden bedingten Haltepunkte.

cond.png

Wenn Sie in der ersten Zeile "System.out.println (n)" einen bedingten Haltepunkt festlegen, wird das Programm unter dieser Bedingung angehalten. Wenn Sie jedoch in der zweiten Zeile einen Haltepunkt festlegen, wird dies der Fall sein Ich erhalte eine Fehlermeldung wie (* Ich verwende STS 3.8.4).

condBP.png

Es wäre schön, wenn die IDE die neuesten Grammatiken unterstützen würde, aber das ist nicht immer der Fall. Manchmal können Sie nur dann debuggen, wenn Sie einen einzeiligen Lambda-Ausdruck in drei Zeilen schreiben. Wenn Sie es mit einer neuen Syntax implementieren, müssen Sie möglicherweise einmal einen Haltepunkt festlegen und die Bewegung überprüfen.

Methodenkette

Methodenverkettungen wie "person.setName (" Peter "). SetAge (21) ...", die im aktuellen Quellcode häufig vorkommen, sind in der Analyse ebenfalls schwer zu sagen.

class Person {
	private String name;
	private int age;

	// In addition to having the side-effect of setting the attributes in question,
	// the setters return "this" (the current Person object) to allow for further chained method calls.

	public Person setName(String name) {
		this.name = name;
		return this;
	}

	public Person setAge(int age) {
		this.age = age;
		return this;
	}

	public void introduce() {
		System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
	}

	// Usage:
	public static void main(String[] args) {
		Person person = new Person();
		// Output: Hello, my name is Peter and I am 21 years old.
		person.setName("Peter").setAge(21).introduce();
	}
}

Aus objektorientierter Sicht fühlt es sich nicht intuitiv an, dass der Setter seine eigene Instanz zurückgibt. Die Lesbarkeit von Methodenketten kann von Person zu Person unterschiedlich sein, aber angesichts der Leichtigkeit des Debuggens ist es schwer zu sagen. In dieser Zeile

person.setName("Peter").setAge(21).introduce();

Sie können das Programm nicht dort anhalten, wo Sie es benötigen, da Sie den Haltepunkt nicht auf den richtigen Punkt setzen können. Wenn eine dieser Methoden eine Ausnahme auslöst, sind die Zeilennummern alle gleich, sodass Sie nicht wissen, welche Methode das Problem verursacht hat.

Exception in thread "main" java.lang.NullPointerException
	at tdd.Person.main(Person.java:27)

Wenn dies wie folgt aufgeteilt ist,

person.setName("Peter"); //27. Zeile
person.setAge(21);       //Zeile 28
person.introduce();      //Zeile 29

Sie können anhand des Stack-Trace erkennen, welche Methode das Problem ist.

Exception in thread "main" java.lang.NullPointerException
	at tdd.Person.main(Person.java:29)

Mechanismus, der schwer zu debuggen und Korrekturen schwer zu reflektieren ist

In der vor einigen Jahren entwickelten Webanwendung analysierte das Framework die vom Tool ausgegebene XML-Datei unter Verwendung der Excel-Bildschirmspezifikationen als Eingabe, und der Bildschirm wurde generiert. process.png

Obwohl es möglich ist, das Bildschirmdesign auf diese Weise zu vereinheitlichen und zu homogenisieren, war es sehr mühsam zu analysieren, wenn das Layout und der Betrieb nicht den Erwartungen entsprachen.

Natürlich können weder Excel-Bildschirmspezifikationen noch XML-Dateien debuggt werden. Haltepunkte können im Quellcode des Frameworks festgelegt werden, das das XML analysiert. Es braucht Zeit, um den Quellcode des Frameworks zu debuggen und den Versuch und Irrtum zu wiederholen, die Spezifikationen zu ändern. Darüber hinaus dauert es einige Zeit, das XML zu ändern, auszugeben, bereitzustellen und zu überprüfen. Selbst wenn das automatische Laden von Tomcat aktiviert ist, hat dies eine Weile gedauert ...

Für die Entwicklung mit Spring Boot und Thymeleaf, die heutzutage beliebt ist, muss der Container nach dem Ändern des Bildschirms oder der Logik nicht (wenn überhaupt nicht) neu gestartet werden. Thymeleaf speichert die HTML-Vorlagendatei nicht zwischen, sodass die Änderungen sofort übernommen werden können. Wenn Sie JRebel, Spring Loaded usw. verwenden, können Sie die Änderung des Java-Quellcodes sofort reflektieren und überprüfen.

Wenn Sie einen Mechanismus erstellen, der schwer zu debuggen und Korrekturen nur schwer widerzuspiegeln ist, führt dies zu einem großen Zeitverlust bei der nachfolgenden Arbeit.

Zeitaufwändiger Startvorgang

Ich denke, dass der Initialisierungsprozess einer Webanwendung, die in einem Container wie Tomcat ausgeführt wird, häufig beim Start ausgeführt wird. Beispielsweise wird der DB-Verbindungspool zu diesem Zeitpunkt erstellt, muss jedoch nicht unbedingt beim Start erfolgen. Es ist nicht immer langsam, selbst wenn Sie dies beim ersten DB-Zugriff oder nach dem Start im Hintergrund tun. Nicht nur im Entwicklungsprozess, sondern auch im Betrieb und bei der Fehlerbehebung wird der Container mehrmals neu gestartet. Selbst wenn sich eine Startzeit um einige Sekunden erhöht, wird die Zeit von (erhöhte Anzahl von Sekunden) x (Anzahl von Entwicklern) x (Anzahl von Neustartausführungen) verschwendet.

An einem Standort gab es eine Webanwendung, deren Start 5 Minuten dauerte, und niemand hatte Zweifel daran und entwickelte sie. Ich fand das seltsam, und als ich die Ursache der Verzögerung untersuchte, machte ein Prozess ungefähr 90% der Initialisierungsverarbeitungszeit aus, und durch diese Verbesserung wurde die Startzeit auf ungefähr 20 Sekunden verbessert. Wenn die Startzeit des Containers erheblich variiert, je nachdem, ob Sie die Webanwendung bereitstellen oder nicht, möchten Sie möglicherweise den Inhalt des Initialisierungsprozesses der Webanwendung einmal überprüfen.

Zusammenfassung

Ich habe viel geschrieben, als ich darüber nachdachte, daher ist es nicht sehr gut organisiert, aber es gibt viele andere Codes, die schwer zu analysieren und zu debuggen sind. Um dies zu verhindern, sollten Sie meines Erachtens die folgenden Punkte beachten.

  • Zusätzlich zum Schreiben von einfach zu lesendem Code sollten Sie berücksichtigen, ob das Debuggen oder Analysieren einfach ist.
  • Versuchen Sie, die IDE auszuführen, um festzustellen, ob sie debuggt werden kann und ob das Debuggen einfach ist.
  • Denken Sie immer daran, dass Protokoll- und Kommentarmeldungen für die Analyse nützlich sind
  • Gründliche Berücksichtigung bei der Einführung kniffliger Technologien und neuer Regeln
  • Nicht auf Technik stoßen ―― Es ist mir egal, kurz zu schreiben
  • Erstellen Sie keinen Mechanismus, dessen Debuggen und Analysieren schwierig oder zeitaufwändig ist

Am Ende habe ich etwas Großartiges gesagt, aber ich schreibe keinen solchen Code ...: bow:

Recommended Posts