Java >> Java opplæring >  >> Java

Kodelukter – del II

I det siste innlegget, Code Smells – Part I, snakket jeg om bloaters:de er kodelukter som kan identifiseres som lange metoder, store klasser, primitive tvangstanker, lang parameterliste og dataklumper. I denne vil jeg gjerne grave iobjektorienterte misbrukere og Endringsforhindre .

Objektorienterte misbrukere

Denne typen kodelukt oppstår vanligvis når objektorienterte prinsipper er ufullstendige eller feilaktig brukt.

Bytt erklæringer

Denne saken er enkel å identifisere:vi har en bryterboks. Men du bør vurdere det som en lukt også hvis du finner en sekvens av hvis. (det er en koblingsboks i forkledning). Hvorfor er switch-utsagn dårlige? Fordi når en ny betingelse legges til, må du finne hver forekomst av det brytertilfellet. Så mens han snakket med David, spurte han meg:og hva skjer hvis jeg kapsler inn bryteren i en metode, er det akseptabelt da? Det er virkelig et godt spørsmål ... Hvis bryterdekselet ditt bare brukes til å "ta vare på" én atferd, og det er det, kan det være greit. Husk å identifisere en kodelukt betyr ikke at du alltid må bli kvitt den:det er en avveining. Hvis du finner switch-setningen din replikert og hver replikering har forskjellig oppførsel, kan du ikke bare isolere switch-setningen i en metode. Du må finne et skikkelig "hjem" for det å være i. Som en tommelfingerregel bør du tenke på polymorfisme når du befinner deg i denne situasjonen. Det er to refactoring-teknikker som vi kan bruke her:

  • Erstatt typekode med underklasser Denne teknikken består av å lage underklasser for hvert svitsjtilfelle og bruke den respektive oppførselen på disse underklassene.
  • Erstatt typekode med strategi I likhet med den ovenfor, bør du i dette tilfellet bruke ett av mønstrene:Stat eller Strategi.

Så når skal man bruke det ene eller det andre? Hvis Skriv kode endrer ikke oppførselen til en klasse kan du bruke Underklassene teknikk. Å separere hver atferd i dens passende underklasse vil håndheve Single Responsibility-prinsippet og gjøre koden mer lesbar generelt. Hvis du trenger å legge til en annen sak, legger du bare til en ny klasse i koden din uten å måtte endre noen annen kode. Så du bruker åpen/lukk-prinsippet. Du bør bruke strategitilnærmingen når Skriv kode påvirker atferden til klassene dine. Hvis du endrer tilstanden til klassen, feltene og mange andre handlinger, bør du bruke tilstandsmønsteret. hvis det bare påvirker hvordan du velger en atferd for klassen, er strategimønsteret et bedre valg.

Hmm... Det er litt forvirrende, ikke sant? Så la oss prøve med et eksempel.

Du har en oppregning EmployeeType:

public enum EmployeeType 
{
        
    Worker,
      
    Supervisor,
      
    Manager
  
}

Og en klasseansatt:

public class Employee

{
    
    private float salary;
    
    private float bonusPercentage;
    
    private EmployeeType employeeType;

    

    public Employee(float salary, float bonusPercentage, EmployeeType employeeType)
    
    {
        
        this.salary = salary;
        
        this.bonusPercentage = bonusPercentage;
        
        this.employeeType = employeeType;
    
    }

    

    public float CalculateSalary() 
    
    {
        
        switch (employeeType) 
        
        {
            
            case EmployeeType.Worker:
                
                return salary; 
            
            case EmployeeType.Supervisor:
                
                return salary + (bonusPercentage * 0.5F);
            
            case EmployeeType.Manager:
                
                return salary + (bonusPercentage * 0.7F);
        
        }

        return 0.0F;
    
    }
}

Det hele ser ok ut. Men hva skjer hvis du trenger å beregne årsbonusen? Du vil legge til en annen metode som denne:

public float CalculateYearBonus() 

    {
    
        switch (employeeType) 
    
        {
        
            case EmployeeType.Worker:
            
                return 0; 
        
            case EmployeeType.Supervisor:
            
                return salary + salary * 0.7F;
        
            case EmployeeType.Manager:
            
                return salary + salary * 1.0F;  
    
        }

        return 0.0F;
    }

Ser du repetisjonen av byttet? Så la oss prøve først underklassetilnærmingen:Her er superklassen:

abstract public class Employee
 
{ 

    

protected float salary;
    
    protected float bonusPercentage;

    

    public EmployeeFinal(float salary, float bonusPercentage)
    
    {
        
        this.salary = salary;
        
        this.bonusPercentage = bonusPercentage;
    
    }

    

    abstract public float CalculateSalary();

    

virtual public float CalculateYearBonus() 
    
    {

        return 0.0F;
    
    }

}

Og her har vi underklassene:

public class Worker: Employee
 
{
   two

    public Worker(float salary, float bonusPercentage)
  
        : base(salary, bonusPercentage)
 
    {}

  

     override public float CalculateSalary() 
    
     {
        
        return salary; 
    
     }

}

public class Supervisor : Employee

{
    

    public Supervisor(float salary, float bonusPercentage)

            : base(salary, bonusPercentage)
    
    {}

    

    override public float CalculateSalary() 
    
    {
        
        return salary + (bonusPercentage * 0.5F);
    
    }

    

    public override float CalculateYearBonus()
    
    {
        
        return salary + salary * 0.7F;
    
    }

}

Med strategitilnærmingen vil vi lage et grensesnitt for å beregne gjengjeldelsen:

public interface IRetributionCalculator 
 
{
        
    float CalculateSalary(float salary);
     
    float CalculateYearBonus(float salary);
  
}

Med grensesnittet på plass, kan vi nå overføre til den ansatte enhver klasse som er i samsvar med den protokollen og beregne riktig lønn/bonus.

public class Employee
{
    
    private float salary;
    
    private IRetributionCalculator retributionCalculator;

    

    public Employee(float salary, IRetributionCalculator retributionCalculator)
    
    {
        this.salary = salary;
        
        this.retributionCalculator = retributionCalculator;
    
    }

    

    public float CalculateSalary()
    
    {
        
        return retributionCalculator.CalculateSalary(salary);
    
    }
            
    

    public float CalculateYearBonus() 
    
    {
        
        return retributionCalculator.CalculateYearBonus(salary);
    
    }
}

Midlertidig felt

Dette tilfellet oppstår når vi beregner en stor algoritme som trenger flere inngangsvariabler. Å lage disse feltene i klassen har som oftest ingen verdi fordi de bare brukes til denne spesifikke beregningen. Og dette kan også være farlig fordi du må være sikker på at du reinitialiserer dem før du starter neste beregning. Her er den beste refactoring-teknikken å bruke Replace Method with Method Object , som vil trekke ut metoden til en egen klasse. Deretter kan du dele opp metoden i flere metoder innenfor samme klasse.

Avslått legat

Denne kodelukten er litt vanskelig å oppdage fordi dette skjer når en underklasse ikke bruker all oppførselen til overordnet klasse. Så det er som om underklassen "nekter" noen atferd ("legat") fra sin overordnede klasse.

I dette tilfellet, hvis det ikke gir mening å fortsette å bruke arv, er den beste refactoring-teknikken å endre til Delegering :vi kan kvitte oss med arven ved å lage et felt av foreldrenes klassetype i underklassen vår. På denne måten, hver gang du trenger metodene fra den overordnede klassen, delegerer du dem bare til dette nye objektet.

Når arven er den riktige tingen å gjøre, så flytt alle unødvendige felt og metoder fra underklassen. Trekk ut alle metoder og felt fra underklassen og overordnet klasse og sett dem i en ny klasse. Gjør denne nye klassen til SuperClass, som underklassen og overordnet klasse skal arve fra. Denne teknikken kalles Extract Superclass .

Alternative klasser med forskjellige grensesnitt

Hmm, denne saken får meg til å tenke på "mangel på kommunikasjon" mellom medlemmer av samme team fordi dette skjer når vi har to klasser som gjør det samme, men har forskjellige navn på metodene deres. Begynn med å Gi nytt navn til metoder eller Flyttemetode , slik at du kan ha begge klassene som implementerer det samme grensesnittet. I noen tilfeller er bare deler av atferden duplisert i begge klassene. I så fall, prøv Extract Superclass og gjør de originale klassene til underklassene.

Endre hindre

Å gutt! Denne typen kodelukter er de du virkelig vil unngå. Dette er de som når du gjør en endring på ett sted, må du stort sett gå gjennom kodebasen og gjøre endringer andre steder også. Så det er et mareritt som vi alle ønsker å unngå!

Divergent endring

Dette er tilfellet når du opplever at du endrer samme klasse av flere forskjellige grunner. Dette betyr at du bryter Single Responsibility Principle) (som har å gjøre med separasjon av bekymringer). Refaktoreringsteknikken som brukes her er Extract Class siden du vil trekke ut de forskjellige atferdene i forskjellige klasser.

haglekirurgi

Det betyr at når du gjør en liten endring i en klasse, må du gå og bytte flere klasser samtidig. Selv om det virker det samme som Divergent Change lukt, i virkeligheten er de motsatte av hverandre:Divergent Change er når det gjøres mange endringer i en enkelt klasse. haglekirurgi refererer til når en enkelt endring gjøres til flere klasser samtidig.

Her er refactoring-teknikken som skal brukes Move Method og/eller Flytt felt . Dette vil tillate deg å flytte de dupliserte metodene eller feltene til en felles klasse. Hvis den klassen ikke eksisterer, oppretter du en ny. I tilfelle den opprinnelige klassen forblir nesten tom, bør du kanskje tenke på om denne klassen er overflødig, og i så fall bli kvitt den ved å bruke Inline-klassen :flytt de resterende metodene/feltene til en av de nye klassene som er opprettet. Alt avhenger av om den opprinnelige klassen ikke har noe ansvar lenger.

Parallelle arvshierarkier

Dette tilfellet er når du finner deg selv å opprette en ny underklasse for klasse B fordi du legger til en underklasse til klasse A. Her kan du:først få en av hierarkiet til å referere til forekomster av et annet hierarki. Etter dette første trinnet kan du bruke Move Method og Flytt felt for å fjerne hierarkiet i den refererte klassen. Du kan også bruke besøksmønsteret her.

Konklusjon

Når det gjelder misbrukere av objektorientering og Change Preventers , jeg tror at de er enklere å unngå hvis du vet hvordan du bruker et godt design på koden din. Og det kommer med mye trening. I dag har jeg snakket om noen få refaktoriseringsteknikker, men det er mange flere. Du kan finne en god referanse til alt på Refactoring.com. Og som jeg sa i den første delen av denne serien, kan kodelukter ikke alltid fjernes. Studer hvert tilfelle og avgjør:husk at det alltid er en avveining.

Java Tag