CodeGym /Java Blog /Willekeurig /Hoe refactoring werkt in Java
John Squirrels
Niveau 41
San Francisco

Hoe refactoring werkt in Java

Gepubliceerd in de groep Willekeurig
Terwijl je leert programmeren, besteed je veel tijd aan het schrijven van code. De meeste beginnende ontwikkelaars geloven dat dit is wat ze in de toekomst zullen doen. Dit is gedeeltelijk waar, maar de taak van een programmeur omvat ook het onderhouden en herstructureren van code. Vandaag gaan we het hebben over refactoring. Hoe refactoring werkt in Java - 1

Refactoring op CodeGym

Refactoring komt twee keer aan bod in de CodeGym cursus: De grote taak biedt de mogelijkheid om door oefening kennis te maken met echte refactoring, en de les over refactoring in IDEA helpt je om in geautomatiseerde tools te duiken die je leven ongelooflijk gemakkelijker zullen maken.

Wat is refactoring?

Het verandert de structuur van de code zonder de functionaliteit ervan te veranderen. Stel dat we een methode hebben die 2 getallen vergelijkt en waar retourneert als de eerste groter is en anders onwaar :

    public boolean max(int a, int b) {
        if(a > b) {
            return true;
        } else if (a == b) {
            return false;
        } else {
            return false;
        }
    }
Dit is een nogal logge code. Zelfs beginners zouden zoiets zelden schrijven, maar er is een kans. Waarom een ​​blok gebruiken if-elseals je de 6-regelige methode beknopter kunt schrijven?

 public boolean max(int a, int b) {
      return a > b;
 }
Nu hebben we een eenvoudige en elegante methode die dezelfde bewerking uitvoert als in het bovenstaande voorbeeld. Dit is hoe refactoring werkt: je verandert de structuur van code zonder de essentie aan te tasten. Er zijn veel methoden en technieken voor refactoring die we nader zullen bekijken.

Waarom heb je refactoring nodig?

Er zijn verschillende redenen. Bijvoorbeeld om eenvoud en beknoptheid in code te bereiken. Voorstanders van deze theorie zijn van mening dat code zo beknopt mogelijk moet zijn, ook al zijn er enkele tientallen regels commentaar nodig om het te begrijpen. Andere ontwikkelaars zijn ervan overtuigd dat code moet worden aangepast om het begrijpelijk te maken met een minimum aan opmerkingen. Elk team neemt zijn eigen standpunt in, maar bedenk dat refactoring niet gelijk staat aan reductie . Het belangrijkste doel is om de structuur van de code te verbeteren. In dit algemene doel kunnen verschillende taken worden opgenomen:
  1. Refactoring verbetert het begrip van code die door andere ontwikkelaars is geschreven.
  2. Het helpt bij het vinden en oplossen van bugs.
  3. Het kan de snelheid van softwareontwikkeling versnellen.
  4. Over het algemeen verbetert het het softwareontwerp.
Als refactoring lange tijd niet wordt uitgevoerd, kan de ontwikkeling moeilijkheden ondervinden, waaronder een volledige stopzetting van het werk.

"Code stinkt"

Wanneer de code moet worden geherstructureerd, wordt er gezegd dat het een "geur" ​​heeft. Natuurlijk niet letterlijk, maar zo'n code ziet er echt niet erg aantrekkelijk uit. Hieronder zullen we basisrefactoringtechnieken voor de beginfase onderzoeken.

Onredelijk grote klassen en methoden

Klassen en methoden kunnen omslachtig zijn, onmogelijk om effectief mee te werken, juist vanwege hun enorme omvang.

Grote klasse

Zo'n klasse heeft een enorm aantal regels code en veel verschillende methoden. Het is meestal gemakkelijker voor een ontwikkelaar om een ​​functie aan een bestaande klasse toe te voegen in plaats van een nieuwe te maken, en daarom groeit de klasse. In de regel zit er te veel functionaliteit in zo'n klasse gepropt. In dit geval helpt het om een ​​deel van de functionaliteit naar een aparte klasse te verplaatsen. We zullen hier in meer detail over praten in het gedeelte over refactoringtechnieken.

Lange methode

Deze "geur" ​​ontstaat wanneer een ontwikkelaar nieuwe functionaliteit toevoegt aan een methode: "Waarom zou ik een parametercontrole in een aparte methode plaatsen als ik de code hier kan schrijven?", "Waarom heb ik een aparte zoekmethode nodig om de maximale element in een array? Laten we het hier houden. De code wordt op deze manier duidelijker", en andere soortgelijke misvattingen.

Er zijn twee regels voor het refactoren van een lange methode:

  1. Als je een opmerking wilt toevoegen bij het schrijven van een methode, moet je de functionaliteit in een aparte methode plaatsen.
  2. Als een methode meer dan 10-15 regels code nodig heeft, moet u de taken en subtaken identificeren die worden uitgevoerd en proberen de subtaken in een aparte methode te plaatsen.

Er zijn een paar manieren om een ​​lange methode te elimineren:

  • Verplaats een deel van de functionaliteit van de methode naar een aparte methode
  • Als lokale variabelen voorkomen dat u een deel van de functionaliteit verplaatst, kunt u het hele object naar een andere methode verplaatsen.

Veel primitieve gegevenstypen gebruiken

Dit probleem treedt meestal op wanneer het aantal velden in een klasse in de loop van de tijd toeneemt. Als u bijvoorbeeld alles (valuta, datum, telefoonnummers, etc.) opslaat in primitieve typen of constanten in plaats van kleine objecten. In dit geval zou het een goede gewoonte zijn om een ​​logische groepering van velden naar een aparte klasse te verplaatsen (extract class). U kunt ook methoden aan de klasse toevoegen om de gegevens te verwerken.

Te veel parameters

Dit is een vrij veel voorkomende fout, vooral in combinatie met een lange methode. Meestal komt het voor als een methode te veel functionaliteit heeft, of als een methode meerdere algoritmen implementeert. Lange lijsten met parameters zijn erg moeilijk te begrijpen en het gebruik van methoden met dergelijke lijsten is onhandig. Hierdoor is het beter om een ​​heel object te passeren. Als een object niet genoeg gegevens heeft, moet u een algemener object gebruiken of de functionaliteit van de methode verdelen zodat elke methode logisch gerelateerde gegevens verwerkt.

Groepen gegevens

Groepen logisch gerelateerde gegevens verschijnen vaak in code. Bijvoorbeeld databaseverbindingsparameters (URL, gebruikersnaam, wachtwoord, schemanaam, enz.). Als er geen enkel veld kan worden verwijderd uit een lijst met velden, dan moeten deze velden worden verplaatst naar een aparte klasse (extract class).

Oplossingen die de OOP-principes schenden

Deze "geuren" treden op wanneer een ontwikkelaar het juiste OOP-ontwerp schendt. Dit gebeurt wanneer hij of zij de OOP-mogelijkheden niet volledig begrijpt en deze niet volledig of correct gebruikt.

Het niet gebruiken van overerving

Als een subklasse slechts een kleine subset van de functies van de bovenliggende klasse gebruikt, ruikt het naar de verkeerde hiërarchie. Wanneer dit gebeurt, worden de overbodige methoden meestal niet overschreven of veroorzaken ze uitzonderingen. Als de ene klasse een andere overerft, betekent dit dat de onderliggende klasse bijna alle functionaliteit van de bovenliggende klasse gebruikt. Voorbeeld van een juiste hiërarchie: Hoe refactoring werkt in Java - 2Voorbeeld van een onjuiste hiërarchie: Hoe refactoring werkt in Java - 3

Schakel verklaring

Wat kan er mis zijn met een switchverklaring? Het is erg als het erg ingewikkeld wordt. Een gerelateerd probleem is een groot aantal geneste ifstatements.

Alternatieve klassen met verschillende interfaces

Meerdere klassen doen hetzelfde, maar hun methoden hebben verschillende namen.

Tijdelijk veld

Als een klasse een tijdelijk veld heeft dat een object slechts af en toe nodig heeft als zijn waarde is ingesteld, en het is leeg of, God verhoede, nullde rest van de tijd, dan stinkt de code. Dit is een twijfelachtige ontwerpbeslissing.

Geuren die aanpassing moeilijk maken

Deze geuren zijn ernstiger. Andere geuren maken het vooral moeilijker om code te begrijpen, maar deze voorkomen dat u deze wijzigt. Wanneer je nieuwe functies probeert te introduceren, stopt de helft van de ontwikkelaars en wordt de andere helft gek.

Parallelle overervingshiërarchieën

Dit probleem manifesteert zich wanneer het subklassen van een klasse vereist dat u een andere subklasse maakt voor een andere klasse.

Uniform verdeelde afhankelijkheden

Voor elke wijziging moet u zoeken naar alle toepassingen van een klasse (afhankelijkheden) en veel kleine wijzigingen aanbrengen. Eén wijziging: bewerkingen in veel klassen.

Complexe boom van wijzigingen

Deze geur is het tegenovergestelde van de vorige: veranderingen hebben invloed op een groot aantal methoden in één klas. In de regel heeft dergelijke code een trapsgewijze afhankelijkheid: als u de ene methode wijzigt, moet u iets in een andere repareren, en vervolgens in de derde, enzovoort. Eén klas - veel veranderingen.

"Afval stinkt"

Een nogal onaangename categorie geuren die hoofdpijn veroorzaakt. Nutteloze, onnodige, oude code. Gelukkig hebben moderne IDE's en linters geleerd om voor dergelijke geuren te waarschuwen.

Een groot aantal opmerkingen in een methode

Een methode heeft op bijna elke regel veel verklarend commentaar. Dit komt meestal door een complex algoritme, dus het is beter om de code op te splitsen in verschillende kleinere methoden en deze verklarende namen te geven.

Gedupliceerde code

Verschillende klassen of methoden gebruiken dezelfde codeblokken.

Luie klasse

Een klasse neemt heel weinig functionaliteit over, hoewel het de bedoeling was dat deze groot zou zijn.

Ongebruikte code

Een klasse, methode of variabele wordt niet gebruikt in de code en is dead weight.

Overmatige connectiviteit

Deze categorie geuren kenmerkt zich door een groot aantal onterechte verbanden in de code.

Externe methoden

Een methode gebruikt veel vaker gegevens van een ander object dan zijn eigen gegevens.

Ongepaste intimiteit

Een klasse is afhankelijk van de implementatiedetails van een andere klasse.

Lange klasgesprekken

De ene klasse roept een andere aan, die gegevens opvraagt ​​bij een derde, die gegevens ophaalt bij een vierde, enzovoort. Zo'n lange keten van oproepen betekent een grote afhankelijkheid van de huidige klassenstructuur.

Task-dealer klasse

Een klasse is alleen nodig om een ​​taak naar een andere klasse te sturen. Misschien moet het verwijderd worden?

Refactoring technieken

Hieronder bespreken we basale refactoringtechnieken die kunnen helpen de beschreven codegeuren te elimineren.

Extraheer een klasse

Een klasse voert te veel functies uit. Sommigen van hen moeten naar een andere klas worden verplaatst. Stel dat we een Humanklasse hebben die ook een thuisadres opslaat en een methode heeft die het volledige adres retourneert:

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();
    }
 }
Het is een goede gewoonte om de adresgegevens en de bijbehorende methode (gegevensverwerkingsgedrag) in een aparte klasse te plaatsen:

 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();
    }
 }

Extraheer een methode

Als een methode functionaliteit heeft die kan worden geïsoleerd, moet u deze in een aparte methode plaatsen. Een methode die bijvoorbeeld de wortels van een kwadratische vergelijking berekent:

    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");
        }
    }
We berekenen elk van de drie mogelijke opties op afzonderlijke manieren:

    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");
    }
De code van elke methode is veel korter en gemakkelijker te begrijpen geworden.

Een heel object passeren

Wanneer een methode wordt aangeroepen met parameters, ziet u soms code zoals deze:

 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;
 }
Het employeeMethodheeft 2 hele regels gewijd aan het ontvangen van waarden en het opslaan ervan in primitieve variabelen. Soms kunnen dergelijke constructies tot 10 regels in beslag nemen. Het is veel gemakkelijker om het object zelf door te geven en het te gebruiken om de benodigde gegevens te extraheren:

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

Eenvoudig, kort en bondig.

Velden logisch groeperen en ze naar een aparte locatie verplaatsen classDespiteHet feit dat de bovenstaande voorbeelden heel eenvoudig zijn, en als je ernaar kijkt, vragen velen van jullie misschien: "Wie doet dit?", veel ontwikkelaars maken dergelijke structurele fouten vanwege onzorgvuldigheid, onwil om de code te herstructureren, of gewoon een houding van "dat is goed genoeg".

Waarom refactoring effectief is

Als resultaat van goede refactoring heeft een programma gemakkelijk leesbare code, is het vooruitzicht om de logica te veranderen niet beangstigend, en het introduceren van nieuwe functies wordt geen hel van code-analyse, maar is in plaats daarvan een prettige ervaring voor een paar dagen . Je zou niet moeten refactoren als het gemakkelijker zou zijn om een ​​programma helemaal opnieuw te schrijven. Stel dat uw team inschat dat er meer werk nodig is om code te begrijpen, te analyseren en te herstructureren dan om dezelfde functionaliteit helemaal opnieuw te implementeren. Of als de te herstructureren code veel problemen heeft die moeilijk te debuggen zijn. Weten hoe de structuur van code kan worden verbeterd, is essentieel in het werk van een programmeur. En leren programmeren in Java doe je het best op CodeGym, de online cursus die de nadruk legt op oefenen. 1200+ taken met onmiddellijke verificatie, ongeveer 20 miniprojecten, speltaken - dit alles zal je helpen om zelfverzekerd te coderen. De beste tijd om te beginnen is nu :)

Bronnen om je verder te verdiepen in refactoring

Het bekendste boek over refactoring is "Refactoring. Improving the Design of Existing Code" van Martin Fowler. Er is ook een interessante publicatie over refactoring, gebaseerd op een eerder boek: "Refactoring Using Patterns" van Joshua Kerievsky. Over patronen gesproken... Bij refactoring is het altijd erg handig om basisontwerppatronen te kennen. Deze uitstekende boeken helpen hierbij: Over patronen gesproken... Bij refactoring is het altijd erg handig om basisontwerppatronen te kennen. Deze uitstekende boeken helpen hierbij:
  1. "Design Patterns" door Eric Freeman, Elizabeth Robson, Kathy Sierra en Bert Bates, uit de Head First-serie
  2. "De kunst van leesbare code" door Dustin Boswell en Trevor Foucher
  3. "Code Complete" door Steve McConnell, waarin de principes voor mooie en elegante code uiteen worden gezet.
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION