Zasady czystego kodu: Don’t Repeat Yourself / DRY

Podczas mojego wieloletniego doświadczenia z programowaniem aplikacji poznałem kilka zasad, które doskonale mogą również Wam pomóc w utrzymaniu dobrej jakości kodu czy jego przejrzystości.

Jedną z takich zasad jest DRY – Don’t repeat Yourself. Jest to metoda programowania, w której unikamy powtarzania pewnych funkcjonalności poprzez wyłączanie odpowiednich części kodu do osobnych metod. Tak proste rozwiązanie nie tylko zaoszczędza nam czas związany z wytworzeniem i utrzymaniem oprogramowania, ale też upraszcza budowę aplikacji, sprawia że jest ona bardziej przejrzysta.

 

DRY w praktyce

Załóżmy, że mamy trzy trywialne metody, które przeszukują string w poszukiwaniu różnych fragmentów tekstu:

class ParserTekstu {
    
    private boolean czyJestSkrypt(String tekst) {
        return (tekst.toLowerCase.contains(„<script>”) ? true : false);
    }
    
    private boolean czyJestHtml(String tekst) {
        return (tekst.toLowerCase.contains(„<html>”) ? true : false);
    }
    
    private boolean czyJestObraz(String tekst) {
        return (tekst.toLowerCase.contains(„<img”) ? true : false);
    }
    
    public boolean czyJestCalyTekst(String tekst) {
        
        if (!czyJestSkrypt(tekst)) return false;
        if (!czyJestHTML(tekst)) return false;
        if (!czyJestObraz(tekst)) return false;
        
        return true;
    }
    
}

Kod, który przedstawiony jest powyżej pokazuje oczywiście trywialny przypadek nadużycia twórczości własnej. Stosując regułę DRY możemy za pomocą refaktoryzacji uzyskać następujący kod:

class ParserTekstu {
    
    private boolean czyJestWzorzec(String tekst, String wzorzec) {
        return (tekst.toLowerCase.contains(wzorzec) ? true : false);
    }
    
    public boolean czyJestCalyTekst(String tekst) {
        
        if (!czyJestWzorzec(tekst, „<script>”)) return false;
        if (!czyJestWzorzec(tekst, „<html>”)) return false;
        if (!czyJestWzorzec(tekst, „<img”)) return false;
        
        return true;
    }
    
}

W pierwszym kroku zmodfikowaliśmy metodę czyJestHTML w taki sposób, by była ona uniwersalna. Poprzednie metody stają się więc nieprzydatne a zatem usuwamy je. Kod z początkowych 23 wierszy zmniejszył się do 15 (-35%). Następnie staramy się znów zmodyfikować kod aplikacji, aby można było zastosować więcej parametrów wejściowych.

class ParserTekstu {
    
    private boolean czyJestWzorzec(String tekst, String[] wzorce) {
        for (int i=0; i<wzorce.length;i++) {
          if (!tekst.toLowerCase().contains(wzorce[i])){
            return false;
          };
        }
        return true;
    }
    
    public boolean czyJestCalyTekst(String tekst) {
        return czyJestWzorzec(tekst, new String[]{„<script>”, „<html>”, „<img”});
    }
    
}

W tym przypadku ilość linii nam się nie zmienia, jednak zauważmy, że pozostaje nam całkowicie nieprzydatna metoda – czyJestCalyTekst, która obecnie uzupełnia i przekazuje parametry wprost do metody czyJestWzorzec. W kolejnym kroku usuwamy więc metodę i zastępujemy ją metodą czyJestWzorzec.

class ParserTekstu {
    
    public boolean czyJestCalyTekst(String tekst, String[] wzorce) {
        for (int i=0; i<wzorce.length;i++) {
          if (!tekst.toLowerCase().contains(wzorce[i])){
            return false;
          };
        }
        return true;
    }
    
}

Po dokonaniu tej podmiany, kod zmniejszył się o 4 linie – w obecnej formie ma więc 11 linii.

 

Uniwersalne klasy narzędziowe

Jak zauważyliście, metoda jest na tyle uniwersalna, że możemy ją stosować w kolejnych aplikacjach/projektach czy innych klasach tego samego projektu. Zamieńmy więc naszą metodę na statyczną:

class ParserTekstu {
    
    public static boolean czyJestCalyTekst(String tekst, String[] wzorce) {
        for (int i=0; i<wzorce.length;i++) {
          if (!tekst.toLowerCase().contains(wzorce[i])){
            return false;
          };
        }
        return true;
    }
    
}

W obecnej chwili otrzmaliśmy klasę ze statyczną metodą, którą w prosty sposób możemy wykorzystać bez inicjalizacji nowej instancji klasy, tj:

ParserTekstu.czyJestCalyTekst(tekstHtml, new String[]{„<script>”, „<html>”, „<img”});

Rekomenduję Wam tworzenie różnego rodzaju klas narzędziowych do obsługi pewnych powtarzalnych fragmentów kodu. Tego typu podejście sprawi, że kwestia poprawki błędnego kodu w jednym miejscu naprawi funkcjonalność w obrębie całej aplikacji, oszczędzając Wam czas.

Pozdrawiam,

M.M.