Refactoring - (Extract Method)
Av: Johan Normén
Publicerad: 2004-06-04
- Att skriva kod som en maskin kan förstå kan nästan vem som helst göra, men att skriva kod som en människa kan förstå är inte alls lika lätt får många.
Hej,
Tänkte ta upp en väldigt vanlig och användbar metod, den kallas Extract Method.
Extract Method är ett namn på en Refactoring, ett sätt att strukturera sin kod. Vad Extract Method innebär är att man extraherar upprepande kod till en eller flera olika metoder. För att inte göra det för svårt har jag valt att extrahera en rätt vanlig kod snutt till en metod, som jag sedan lägger i en klass för återanvända den i alla mina applikationer, därefter går jag in på lite mer djupare exempel. Vi börjar med en helt vanlig strängkontroll.
|
Det är inte alls ovanligt att vi gör en sådan kontroll när vi vill veta om en sträng är null eller tom. Den kan upprepas på över 100 ställen om inte mer i en applikation.
Låt säga att denna kontroll if(val == null || val.Length == 0) skulle innehålla en bugg, nu är det ju inte så men låtsas att == 0 var fel kontroll för att se om strängen var tom. Vi har implementerat denna kontroll på alla våra metoder som har input parametrar av typen sträng. I en applikation kan det lätt bli över 100 sådana kontroller. Vi skulle nu helt plötsligt ha 100 ställen att fixa vår lilla bugg på. För att slippa detta kan vi lätt extrahera ovan rad till en metod som vi på alla hundra ställen anropar.
Vad vi gör är en kontroll på en stäng, passande ord för vår nya metod kan vara: IsNullOrEmpty. För att göra den åtkomlig genom hela applikationen skapar vi en ny klass som vi döper till StringUtil och i denna lägger vi IsNullOrEmpty som en statisk metod. Metoden returnerar en bool true eller false beroende på om strägen var null eller empty. När vi är klara med detta skriver vi följande kontroll istället:
|
Vår metod i StringUtil kan se ut på följande sätt:
|
Oftast ger Extract Method oss mindre kod. I detta fall blev det fler rader kod men kortare anrop för att göra kontrollen. I senare exempel kommer du få se ett fall då vi minimerar rader kod mer mer än hälften men utför ändå exakt samma sak.
Att använda Extract Method ger inte bara generellt mindre kod utan även bättre struktur och hantering av bugg fixar.
Vid fel skulle vi bara behöva ändra i de återanvändbara metoderna vi gör istället för att justera kod i hela vår applikation. Otroligt effektivt och smidigt och du får en minimerad arbetstid.
Som en liten bonus till er StrinUtil kan ni exempelvis lägga in andra användbara metoder som säkerligen kommer att användas flitigt. Exempelvis en ConvertToNonNull. En metod som gör en Null sträng till en string.empty eller ger det värde den hade om den inte var tom.
|
Nu till ett mer komplext exempel.
Extract Method är ju inte enbart ett refactoring mönster för att hantera strängar, man kan extrahera precis vad man vill.
Du kanske har en stor och klumpig error hantering?
Du har en klass som skapar saker, uppdaterar saker och tar bort saker. Ta en Membership.CreateUser(användarnamn,password) som ett exempel. Du har här CreateUser,UdateUser och ValidateUser metoder.
I dessa metoder kommer vi säkerligen göra en hel del validerings kontroller.
Exempelvis kontroll att användarnamnet vi skickar in inte är tomt, kontrollera så användarnamnet bara innehåller tillåtna tecken och inte är för långt etc... Samma med Lösenord.
Om något skulle vara fel kastar vi ett exception. Kanske MembershipCreateUserException med text som ev. lyder. "Fel användarnamn", "Fel lösen", "Användarnamnet finns redan".
Vanligtvis skulle man nog göra if-satser i dessa metoder som sedan kastar en Exception med fel text. Som koden nedan.
|
Tänk om du nu skulle vilja byta text på "Fel på användarnmanet!" till ”Fel format på användarnamnet!" I detta fall skulle vi vara tvungna att byta texten på tre ställen. Inte så jobbigt kanske? men onödigt. Vem vet kanske vi presenterar texten "Fel på användanamnet!" i andra klasser? Dessa måste då också och justeras.
Det finns flera sätt för att underlätta detta, ett sätt är att du skapar en Enum med olika statusar.
Om användarnamnet är felaktigt sätter man en MembershipCreateStatus.InvalidUsername, om lösen är fel sätter man en MembershipCreateStatus.InvalidPassword, och användaren skapades korrekt kanske en MembershipCreateStatus.Success.
När man sedan anropar sin CreateUser metod får man ut en enum som ger dig en status hur det gick. Om statusen inte är Success så vill vi kasta ett exception med rätt felmeddelande. Kan se ut som nedan:
|
Än så länge är det ingen större skillnad från vår tidigare kod. Det är inget större fel på denna kod snutt. Men vi kan effektivisera den.
Vad skulle hända om man gör en UpdateUser? och den också returnerar en MembershipCreateUserStatus? Jo vi får skriva samma felhanteting en gång till.
|
Det är nu en Extract Method kommer in i bilden. Vi ser att i våra två Iffar i koden är exakt samma. Vi kan nu är att minska raderna från 8 sammanlagt till bara två genom att anropa en metod som gör ifkontrollen åt oss.
Vi skapar en metod som vi döper till ett passande namn ex: GetMembershipCreateUserExcetion. Då ifsatserna ovan kollar på status tar vi den som inputparameter och gör samma kontroll som ovan fast ersätter throw new till return och säger att vår metod returnerar en MembershipCreateUserException.
Först får Exception klass.
|
och sedan metoden:
|
Smidigt va? Sedan ändrar vi vår tidigare kod till:
|
Om vi nu inser att vi stavat fel i vårt exceptionmeddelande behöver vi bara gå in och ändra i GetMemeberShipUserException metoden och det slår genom på båda ställen ovan. Smidigt.
Om man vill kan man bryta ner detta ännu mera. Ex flytta in GetMembershipCreateUserException() till Exception klassen. Där byter vi namn på metoden skapar en case-sats istället för If-staser och låter den ge en sträng som retur och sätter denna sträng till basklaseens message.
Bygg nu om MembershipCreateUserException till följande:
|
Vi ändrar vår tidigare kod från throw GetMembershipCreateuserException(status);
till throw new MembershipCreateUserException(status);
|
Vill vi göra det ännu smidigare kan vi göra felhanteringen i vår Membership klass. vi skapar då en överlagrad metod med samma namn och sätter till en out MembershipCreateStatus parameter i slutet. I den nya metoden gör vi vår validering och sätter därefter statusen. Sedan i vår tidigare metod gör vi kontrollen om användaren skapades utan fel, om inte kastar vi en MembershipCreateUserException precis som ovan.
|
Sedan kan vi ropa på våra tre metoder, på bara tre rader.
|
Så. Det blev väl mycket stiligare? Om ett fel skulle uppstå kastas ett Exception. Vill vi ta hand om detta exception är det bara att slänga till ett try och catch block.
Japp det var väl allt för idag...
Hälsa gärna på min blog, http://www.johannormen.com/blog
Mvh Johan
|
|
Avsluta med en länk.. |