Clean Code mit dem Linux sed-Befehl

Clean Code ist ein wichtiges Thema, das manchmal aufwändig und langweilig sein kann. So musste ich vor kurzem in circa 100 Klassen überflüssige Annotationen löschen 😒
Statt Eclipse nutzte ich die Linux-Shell und sed, um alle Dateien mit einem Befehl aufzuräumen.
Wie das genau geht, zeige ich euch hier.


Das Problem bzw. der dreckige Code

Die Modell-Klassen unserer Anwendung wurden früher zum Parsen von JSON in Java-Objekte und zum Schreiben per JPA in eine relationale Datenbank verwendet. Nach einem Redesign werden die Modell-Klassen nur noch zum Parsen von JSON Texten verwendet, so wie ich es z.B. hier zeige rest-json-apis-in-java-leicht-gemacht.html. Im Zuge des Redesigns wurde leider vergessen die nun ungenutzten JPA Annotationen in den Modell-Klassen zu entfernen. Konkret sehen alle Klassen ungefähr so aus:

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.OneToOne;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import lombok.Data;

@Entity
@Data
public class ProductPrice {
    @Size(max = 150)
    @Pattern(regexp = "^[\\d]*$")
    private String id;

    @Size(max = 150)
    @Pattern(regexp = "^[A-Za-z]*$")
    private String name;

    @Enumerated(EnumType.STRING)
    @Column(name = "price_type_string")
    private PriceTypeEnum priceType;

    @Valid
    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    private Price price;
}

Sauberer Code bedeutet unter anderem, dass es keinen toten Code und damit auch keine ungenutzten bzw. toten Annotationen geben darf. Also legen wir los und machen folgendes:

  1. Zuerst löschen wir alle JPA Annotation:
    @Entity, @Column, @OneToOne, @Enumerated
  2. Danach löschen wir alle nicht mehr benötigten Imports:
    import javax.persistence...
    Dabei hilft uns Eclipse, indem es automatisch für uns alle Imports organisiert bzw. ungenutzte löscht. Tastenkürzel für "Organize Imports": strg + shift + O
Danach sieht unsere aufgeräumte, saubere Modell-Klasse so aus:

import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
public class ProductPrice {
    @Size(max = 150)
    @Pattern(regexp = "^[\\d]*$")
    private String id;

    @Size(max = 150)
    @Pattern(regexp = "^[A-Za-z]*$")
    private String name;

    private PriceTypeEnum priceType;

    @Valid
    private Price price;
}

Leider müssen wir jetzt noch 99 andere Modell-Klassen analog aufräumen 😲

Anmerkung: Wenn ihr euch für die übrig gebliebenen Annotationen interessiert,
schaut euch folgendes an:

  • validation-with-spring.html für @Valid, @Size und @Pattern
  • @Data ist eine Lombok Annotation und erzeugt für uns unter anderem automatisch Getter- und Setter-Methoden. Ich finde Lombok super, verweise hier aber auf die Lombok-Webseite: https://projectlombok.org/ 

Die Lösung mit dem Linux-Tool sed

Da ich keine 99 Klassen auf die selbe Art und Weise aufräumen wollte, kam mir die Idee sed dafür zu verwenden, so wie es viele Linux Administratoren gerne tun.
Der Stream EDitor (sed) ist ein Unix-Tool zum Verarbeiten von Text-Datenströmen, siehe auch https://de.wikipedia.org/wiki/Sed_%28Unix%29.

Mit sed kann man Text-Dateien einlesen, dabei nach bestimmten Textstellen suchen und diese durch andere Texte ersetzen. Ich kann aber auch mit folgendem Befehl die @Entity Annotation in der Datei ProductPrice.java finden und direkt löschen:

sed /"@Entity"/d ./ProductPrice.java

  • sed ist der Befehl zum Starten des Tools.
  • /"@Entity"/d definiert, dass das Textpattern "@Entity" gesucht und gelöscht (d) werden soll.
  • Dabei wird am Ende durch Angabe des relativen Pfades ./ProductPrice.java die zu verarbeitende Text-Datei definiert.
Nun sehen wir in der Shell das gewünschte Ergebnis, nur leider hat sich die Datei nicht verändert. Durch hinzufügen der Option -i legen wir fest, dass sed die Datei "in-place" editieren soll. Der folgende Befehl löscht also die per Textpattern definierte Zeile in einer Datei:

sed -i /"<Text_Pattern_To_Delete>"/d ./PathToFile.java

Soweit so gut, allerdings kann man das auch noch leicht mit Eclipse machen.
Mit sed habe ich die Möglichkeit in der Pfadangabe Wildcards (*) zu verwenden und somit alle Java-Dateien in einem bestimmten Verzeichnis (package) mit einem Befehl zu editieren:

sed -i /"@Entity"/d ./*.java

Diesen Befehl kann ich für alle anderen Annotationen analog anwenden.
Danach müssen noch die jetzt überflüssigen Imports gelöscht werden. Das geht sogar noch etwas einfacher, da sich die zu löschenden JPA Annotationen im selben Package befinden. Unser Befehl löscht die komplette Zeile, auch wenn sich noch andere Texte/Buchstaben in derselben Zeile befinden:

sed -i /"javax.persistence"/d ./*.java  

Durch diesen Befehl werden also folgende Zeilen gelöscht, weil sie alle den Textabschnitt "javax.persistence" enthalten:
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.OneToOne;
Die anderen Imports (import javax.validation... etc.) werden nicht gelöscht, weil sie ".persistence" nicht enthalten.

Wie versprochen zeige ich euch auch noch den sed-Befehl, um alle JPA Annotationen und Imports mit einem einzigen Befehl in beliebig vielen Java-Dateien zu löschen.

sed -i /"@Entity\|@Column\|@OneToOne\|@Enumerated\|\
javax.persistence"/d ./*.java 
  • \| ist das Zeichen, um diverse Text-Patterns OR zu verknüpfen. Jede Zeile die "@Entity" oder "@Column" oder ... enthält wird gelöscht. Backslash \ escaped dabei das OR Zeichen |, welches ohne vorgestellten Backslash als Teil des Textes interpretiert werden würde.
  • Der eine Backslash am Ende der ersten Zeile entfernt den Zeilenumbruch in der Shell, da mein Befehl hier nicht in eine Zeile gepasst hat.
  • Anstelle von @Entity\|@Enumerated könnte man auch nur @En angeben. Das hätte in der hier gezeigten Datei den gleichen Effekt, erhöht aber das Risiko in einer anderen Java-Datei benötigte Annotationen zu löschen, die mit "@En" anfangen. Ich empfehle beim Löschen möglichst lange und eindeutige Text-Patterns zu verwenden.

Git Bash verwenden, wenn kein Linux vorhanden ist

Falls ihr kein Linux Betriebssystem zur Verfügung habt, könnt ihr stattdessen die Git Bash verwenden. 
Git Bash ist ein für Windows kompiliertes Unix Paket, welches bash, ssh, cat, sed und viele weitere nützliche Unix Tools enthält.

Die Git Bash ist Teil von Git für Windows https://gitforwindows.org/
und wird bei der Installation mit installiert. Alle hier gezeigten sed Befehle habe ich unter Windows in der Git Bash ausgeführt.
  

Fazit

Das sed Tool kann noch viel mehr als hier gezeigt. Schaut euch dazu die Hilfe vom Tool an oder sucht im Internet nach anderen Beispielen für sed Kommandos:
sed --help

Mit diesem Blog-Artikel will ich euch zeigen, dass es auch außerhalb unserer IDEs gute Tools gibt, die beim Entwickeln helfen. Insbesondere die Linux Shell bietet viele hilfreiche Kommandos an, die wir beim Automatisieren von Prozessen (z.B. Build oder Deployment) sehr gut verwenden können.

Buch mit weiteren Linux Kommandos


Kommentare

Ein Kollege hat mich darauf hingewiesen, dass IntelliJ und Eclipse ebenfalls eine Funktion anbieten, um Ersetzungen in einer großen Menge von Dateien durchzuführen.

Eclipse bietet dazu den "Replace..." Button in der "File Search" Funktion an.

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

Authentifizierung in Web-Anwendungen mit Spring Security 6

Reaktive REST-Webservices mit Spring WebFlux