CodeGym /Java blogg /Slumpmässig /Hur refactoring fungerar i Java
John Squirrels
Nivå
San Francisco

Hur refactoring fungerar i Java

Publicerad i gruppen
När du lär dig programmera lägger du mycket tid på att skriva kod. De flesta nybörjarutvecklare tror att det är vad de kommer att göra i framtiden. Detta är delvis sant, men i en programmerares jobb ingår också att underhålla och omstrukturera kod. Idag ska vi prata om refaktorering. Hur refactoring fungerar i Java - 1

Refaktorering på CodeGym

Refactoring behandlas två gånger i CodeGym-kursen: Den stora uppgiften ger en möjlighet att bekanta dig med verklig refactoring genom övning, och lektionen om refactoring i IDEA hjälper dig att dyka in i automatiserade verktyg som kommer att göra ditt liv otroligt enklare.

Vad är refaktorering?

Det ändrar kodens struktur utan att ändra dess funktionalitet. Anta till exempel att vi har en metod som jämför två tal och returnerar sant om det första är större och falskt annars:

    public boolean max(int a, int b) {
        if(a > b) {
            return true;
        } else if (a == b) {
            return false;
        } else {
            return false;
        }
    }
Det här är en ganska svårhanterlig kod. Även nybörjare skulle sällan skriva något liknande, men det finns en chans. Varför använda ett if-elseblock om man kan skriva 6-radsmetoden mer kortfattat?

 public boolean max(int a, int b) {
      return a > b;
 }
Nu har vi en enkel och elegant metod som utför samma operation som exemplet ovan. Så här fungerar refactoring: du ändrar kodens struktur utan att påverka dess väsen. Det finns många refaktoreringsmetoder och tekniker som vi ska titta närmare på.

Varför behöver du refaktorering?

Det finns flera skäl. Till exempel för att uppnå enkelhet och korthet i koden. Förespråkare av denna teori anser att kod bör vara så kortfattad som möjligt, även om flera dussin rader kommentarer behövs för att förstå den. Andra utvecklare är övertygade om att koden bör ändras för att göra den begriplig med ett minimum av kommentarer. Varje lag intar sin egen ståndpunkt, men kom ihåg att omfaktorering inte betyder minskning . Dess huvudsakliga syfte är att förbättra kodstrukturen. Flera uppgifter kan inkluderas i detta övergripande syfte:
  1. Refactoring förbättrar förståelsen för kod skriven av andra utvecklare.
  2. Det hjälper till att hitta och fixa buggar.
  3. Det kan påskynda mjukvaruutvecklingen.
  4. Sammantaget förbättrar det mjukvarudesign.
Om refactoring inte utförs under en längre tid kan utvecklingen stöta på svårigheter, inklusive ett helt stopp för arbetet.

"Kod luktar"

När koden kräver refactoring sägs den ha en "lukt". Naturligtvis inte bokstavligen, men sådan kod ser verkligen inte särskilt tilltalande ut. Nedan kommer vi att utforska grundläggande refactoring-tekniker för det inledande skedet.

Orimligt stora klasser och metoder

Klasser och metoder kan vara krångliga, omöjliga att arbeta med effektivt just på grund av deras enorma storlek.

Stor klass

En sådan klass har ett enormt antal rader kod och många olika metoder. Det är vanligtvis lättare för en utvecklare att lägga till en funktion till en befintlig klass istället för att skapa en ny, vilket är anledningen till att klassen växer. Som regel är det för mycket funktionalitet i en sådan klass. I det här fallet hjälper det att flytta en del av funktionaliteten till en separat klass. Vi kommer att prata om detta mer i detalj i avsnittet om refactoring-tekniker.

Lång metod

Denna "lukt" uppstår när en utvecklare lägger till ny funktionalitet till en metod: "Varför ska jag lägga en parameterkontroll i en separat metod om jag kan skriva koden här?", "Varför behöver jag en separat sökmetod för att hitta det maximala element i en array? Låt oss behålla det här. Koden blir tydligare på detta sätt", och andra sådana missuppfattningar.

Det finns två regler för refaktorisering av en lång metod:

  1. Om du känner för att lägga till en kommentar när du skriver en metod bör du lägga in funktionaliteten i en separat metod.
  2. Om en metod tar mer än 10-15 rader kod bör du identifiera de uppgifter och deluppgifter som den utför och försöka lägga in deluppgifterna i en separat metod.

Det finns några sätt att eliminera en lång metod:

  • Flytta en del av metodens funktionalitet till en separat metod
  • Om lokala variabler hindrar dig från att flytta en del av funktionaliteten kan du flytta hela objektet till en annan metod.

Använder många primitiva datatyper

Det här problemet uppstår vanligtvis när antalet fält i en klass växer över tiden. Till exempel om du lagrar allt (valuta, datum, telefonnummer etc.) i primitiva typer eller konstanter istället för små objekt. I det här fallet skulle en bra praxis vara att flytta en logisk gruppering av fält till en separat klass (extraktklass). Du kan också lägga till metoder till klassen för att bearbeta data.

För många parametrar

Detta är ett ganska vanligt misstag, speciellt i kombination med en lång metod. Vanligtvis inträffar det om en metod har för mycket funktionalitet eller om en metod implementerar flera algoritmer. Långa listor med parametrar är mycket svåra att förstå, och att använda metoder med sådana listor är obekvämt. Som ett resultat är det bättre att passera ett helt objekt. Om ett objekt inte har tillräckligt med data bör du använda ett mer generellt objekt eller dela upp metodens funktionalitet så att varje metod bearbetar logiskt relaterade data.

Datagrupper

Grupper av logiskt relaterade data visas ofta i kod. Till exempel databasanslutningsparametrar (URL, användarnamn, lösenord, schemanamn, etc.). Om inte ett enda fält kan tas bort från en lista med fält, bör dessa fält flyttas till en separat klass (extraktklass).

Lösningar som bryter mot OOP-principerna

Dessa "lukter" uppstår när en utvecklare bryter mot korrekt OOP-design. Detta händer när han eller hon inte helt förstår OOP-funktionerna och misslyckas med att helt eller korrekt använda dem.

Underlåtenhet att använda arv

Om en underklass bara använder en liten delmängd av föräldraklassens funktioner, så luktar det fel hierarki. När detta händer, åsidosätts vanligtvis inte de överflödiga metoderna helt enkelt eller så skapar de undantag. En klass som ärver en annan innebär att den underordnade klassen använder nästan all föräldraklassens funktionalitet. Exempel på en korrekt hierarki: Hur refactoring fungerar i Java - 2Exempel på en felaktig hierarki: Hur refactoring fungerar i Java - 3

Byt uttalande

Vad kan vara fel med ett switchuttalande? Det är dåligt när det blir väldigt komplext. Ett relaterat problem är ett stort antal kapslade ifpåståenden.

Alternativa klasser med olika gränssnitt

Flera klasser gör samma sak, men deras metoder har olika namn.

Tillfälligt fält

Om en klass har ett tillfälligt fält som ett objekt bara behöver ibland när dess värde är satt, och det är tomt eller, gud förbjude, nullresten av tiden, då luktar koden. Detta är ett tveksamt designbeslut.

Dofter som försvårar modifiering

Dessa lukter är allvarligare. Andra lukter gör det främst svårare att förstå kod, men dessa hindrar dig från att modifiera den. När du försöker introducera några nya funktioner slutar hälften av utvecklarna och hälften blir galna.

Parallella arvshierarkier

Detta problem visar sig när underklassning av en klass kräver att du skapar en annan underklass för en annan klass.

Jämnt fördelade beroenden

Alla ändringar kräver att du letar efter alla användningsområden för en klass (beroenden) och gör många små ändringar. En förändring — redigeringar i många klasser.

Komplext träd av ändringar

Denna lukt är motsatsen till den föregående: förändringar påverkar ett stort antal metoder i en klass. Som regel har sådan kod ett kaskadberoende: att ändra en metod kräver att du fixar något i en annan, och sedan i den tredje och så vidare. En klass - många förändringar.

"Skräp luktar"

En ganska obehaglig kategori av lukter som orsakar huvudvärk. Värdelös, onödig, gammal kod. Lyckligtvis har moderna IDE:er och linters lärt sig att varna för sådana lukter.

Ett stort antal kommentarer i en metod

En metod har många förklarande kommentarer på nästan varje rad. Detta beror vanligtvis på en komplex algoritm, så det är bättre att dela upp koden i flera mindre metoder och ge dem förklarande namn.

Duplicerad kod

Olika klasser eller metoder använder samma kodblock.

Lat klass

En klass tar mycket lite funktionalitet, även om den var planerad att vara stor.

Oanvänd kod

En klass, metod eller variabel används inte i koden och är dödvikt.

Överdriven anslutning

Denna kategori av lukter kännetecknas av ett stort antal omotiverade samband i koden.

Externa metoder

En metod använder data från ett annat objekt mycket oftare än sin egen data.

Olämplig intimitet

En klass beror på implementeringsdetaljerna för en annan klass.

Långa klasssamtal

En klass anropar en annan, som begär data från en tredje, som får data från en fjärde, och så vidare. En så lång kedja av samtal innebär stort beroende av den nuvarande klassstrukturen.

Task-dealer klass

En klass behövs bara för att skicka en uppgift till en annan klass. Kanske borde tas bort?

Refaktoreringstekniker

Nedan kommer vi att diskutera grundläggande refactoring-tekniker som kan hjälpa till att eliminera de beskrivna kodlukterna.

Extrahera en klass

En klass utför för många funktioner. En del av dem måste flyttas till en annan klass. Anta till exempel att vi har en Humanklass som också lagrar en hemadress och har en metod som returnerar hela adressen:

class Human {
    private String name;
    private String age;
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }
Det är god praxis att placera adressinformationen och den associerade metoden (databehandlingsbeteende) i en separat klass:

 class Human {
    private String name;
    private String age;
    private Address address;
 
    private String getFullAddress() {
        return address.getFullAddress();
    }
 }
 class Address {
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }

Extrahera en metod

Om en metod har någon funktionalitet som kan isoleras bör du placera den i en separat metod. Till exempel, en metod som beräknar rötterna till en andragradsekvation:

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            double x1, x2;
            x1 = (-b - Math.sqrt(D)) / (2 * a);
            x2 = (-b + Math.sqrt(D)) / (2 * a);
            System.out.println("x1 = " + x1 + ", x2 = " + x2);
        }
        else if (D == 0) {
            double x;
            x = -b / (2 * a);
            System.out.println("x = " + x);
        }
        else {
            System.out.println("Equation has no roots");
        }
    }
Vi beräknar vart och ett av de tre möjliga alternativen i separata metoder:

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            dGreaterThanZero(a, b, D);
        }
        else if (D == 0) {
            dEqualsZero(a, b);
        }
        else {
            dLessThanZero();
        }
    }
 
    public void dGreaterThanZero(double a, double b, double D) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
 
    public void dEqualsZero(double a, double b) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
 
    public void dLessThanZero() {
        System.out.println("Equation has no roots");
    }
Varje metods kod har blivit mycket kortare och lättare att förstå.

Passerar ett helt objekt

När en metod anropas med parametrar kan du ibland se kod så här:

 public void employeeMethod(Employee employee) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
Den employeeMethodhar 2 hela rader ägnade åt att ta emot värden och lagra dem i primitiva variabler. Ibland kan sådana konstruktioner ta upp till 10 rader. Det är mycket lättare att passera själva objektet och använda det för att extrahera nödvändiga data:

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

Enkelt, kortfattat och koncist.

Att logiskt gruppera fält och flytta dem till ett separat classDespitefaktum att exemplen ovan är väldigt enkla, och när du tittar på dem kan många av er fråga "Vem gör det här?", många utvecklare gör sådana strukturella fel på grund av slarv, ovilja att omstrukturera koden, eller helt enkelt en attityd av "det är bra nog".

Varför refaktorering är effektivt

Som ett resultat av bra refactoring har ett program lättläst kod, utsikterna att ändra sin logik är inte skrämmande och att introducera nya funktioner blir inte ett kodanalyshelvete, utan är istället en trevlig upplevelse för ett par dagar . Du bör inte refaktorera om det skulle vara lättare att skriva ett program från början. Anta till exempel att ditt team uppskattar att det arbete som krävs för att förstå, analysera och återställa kod kommer att vara större än att implementera samma funktion från grunden. Eller om koden som ska refaktoreras har massor av problem som är svåra att felsöka. Att veta hur man förbättrar kodstrukturen är viktigt i en programmerares arbete. Och att lära sig programmera i Java görs bäst på CodeGym, onlinekursen som betonar övning. 1200+ uppgifter med omedelbar verifiering, cirka 20 miniprojekt, speluppgifter — allt detta hjälper dig att känna dig säker på kodning. Bästa tiden att börja är nu :)

Resurser för att ytterligare fördjupa dig i refaktorering

Den mest kända boken om refactoring är "Refactoring. Improving the Design of Existing Code" av Martin Fowler. Det finns också en intressant publikation om refactoring, baserad på en tidigare bok: "Refactoring Using Patterns" av Joshua Kerievsky. På tal om mönster... Vid refaktorisering är det alltid väldigt användbart att känna till grundläggande designmönster. Dessa utmärkta böcker kommer att hjälpa till med detta: På tal om mönster... När du refaktorerar är det alltid mycket användbart att känna till grundläggande designmönster. Dessa utmärkta böcker hjälper till med detta:
  1. "Design Patterns" av Eric Freeman, Elizabeth Robson, Kathy Sierra och Bert Bates, från Head First-serien
  2. "The Art of Readable Code" av Dustin Boswell och Trevor Foucher
  3. "Code Complete" av Steve McConnell, som anger principerna för vacker och elegant kod.
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION