Warning: Creating default object from empty value in /chroot/home/zerotohe/zerotohero.hu/html/wp-content/themes/salient/nectar/redux-framework/ReduxCore/inc/class.redux_filesystem.php on line 29
Extract method | zeroToHero

Helló!

Ahogy a nulladik, bevezető részből megtudhattad, a refactoringról mindig úgy fogok beszélni, hogy először tisztázom a refactoring mintához tartozó code smell-t és utána megmutatom hogyan tudod eltüntetni.

Az első mintánk az „extract method„. Nézzük meg mikor és hogyan kell használni!

 

Extract method code smell-ek:

Alapvetően két esetben szokott előfordulni, hogy szükség van az extract method-ra: az első amikor hosszú metódusokba futunk bele, a második amikor kód duplikációkat látunk.

Miért baj, ha hosszúak a metódusok?

Először is, a hosszú metódusokat nehezebb megérteni. Ha egyszerre 200 dolgot számol ki, meg még megadja a kérdést a 42-höz is, akkor elég könnyű elveszteni a fonalat, és ilyen esetben nagyon nehéz lesz ezen módosítást végezni. Plusz, minél nagyobb, komplikáltabb egy metódus, annál könnyebben bújnak el benne a bugok. Mivel senki nem érti és senkinek nincs kedve, türelme végig olvasni, így, hogy az egyik kedvenc kommentemet idézzem: „Amikor ezt a fejlesztő írta, csak ő és Isten tudta mit csinál. Most már csak Isten tudja”. :)

 

Mi a baj a kód duplikációkkal?

Gondolom, ezt nagyon nem kell túlmagyarázni, szinte ez az első és legtöbbet hangoztatott antipattern, amit egy fejlesztő megtanul. A duplikált kódot nehéz karbantartani, mert sok helyen kell módosítani. A duplikált kódban, ha hiba van, akkor mindenhol ott van. A duplikált kód sűrűn hiányosan esik át a változtatásokon, így az alkalmazás instabillá válik…, és még sorolhatnám.

 

Extract method minta:

A minta aránylag egyszerű, ha a fenti code smell-ekkel találkozol, akkor az adott kódrészletet át kell helyezni egy másik metódusba.

 

Példa:


class Printer {

	void printCustomer(Customer customer) {
		// írjuk ki a fejlécet:
		System.out.println("======= Customer Info: =======");

		// írjuk ki az alapadatokat:
		System.out.println("First name: " + customer.getFirstName());
		System.out.println("Last name: " + customer.getLastName());
		System.out.println("Age: " + customer.getAge());

		// írjuk ki a címét:
		System.out.println("City: " + customer.getAddress().getCity());
		System.out.println("Address: " + customer.getAddress().getAddress());
	}

}

 

Ebben a példában bár egyszerű, mégis van pár hiba. Most fókuszáljunk egyelőre csak arra, hogy hosszú a metódusunk, és egyértelműen vannak benne kiemelhető részek.

A kommentek a metódusban nagyon tipikusak. Most nem kezdem el kifejteni, hogy milyen problémáim vannak a kommentekkel, mert sok van. :) Inkább csak arra hívnám fel a figyelmed, hogy ha ilyet látsz, hogy egy komment leírja, hogy mit fog csinálni a kód alatta, akkor az egy alapvető jel arra, hogy ott bizony ki lehet emelni a kódot.

 

Tehát első körben én egy ilyen refactort hajtanék végre:


class Printer {

    void printCustomer(Customer customer) {
        printCustomerInfoHeader();

        printCustomerBasicInfo(customer);

        printCustomerAddress(customer.getAddress());
    }

    private void printCustomerInfoHeader() {
        System.out.println("======= Customer Info: =======");
    }

    private void printCustomerBasicInfo(Customer customer) {
        System.out.println("First name: " + customer.getFirstName());
        System.out.println("Last name: " + customer.getLastName());
        System.out.println("Age: " + customer.getAge());
    }

    private void printCustomerAddress(Address address) {
        System.out.println("City: " + address.getCity());
        System.out.println("Address: " + address.getAddress());
    }

}

Mint láthatod, semmi különöset nem kell hozzá csinálni, mint a metódusokat kiemelni és máris fölöslegesek lesznek a kommentek is.

Ha szemfüles vagy, akkor észrevehetted, hogy van itt egy másik probléma is, mégpedig, hogy a Printer osztály nem a saját területén dolgozik, azaz átnyúlkál a Customer-be és az Address-be. Ezt is érdemes megszüntetni:


class Customer {

	private String firstName;
	private String lastName;
	private int age;

	private Address address;

	// . . .

	public String getFormattedBasicInfo() {
		return new String.format("First name: %s \nLast name: %sAge: %d", firstName, lastName, age);
	}

}

class Address {

	private String city;
	private String address;

	public String getFormattedInfo() {
		return String.format("City: %s \nAddress: %s", city, address);
	}

}

class Printer {

	void printCustomer(Customer customer) {
        printCustomerInfoHeader();
        printCustomerBasicInfo(customer);
        printCustomerAddress(customer.getAddress());
    }

    private void printCustomerBasicInfo(Customer customer) {
    	System.out.println(customer.getFormattedBasicInfo());
    }

    private void printCustomerAddress(Address address) {
    	System.out.println(address.getFormattedInfo());
    }

}

Ez a szerkezet már sokkal objektum orientáltabb. Persze még mindig lehetne rajta csiszolni. (Például az address formázást kérhetné a customer is. Illetve a header is inkább a customer-hez tartozna. Ha azt átraknánk, akkor lehetne közös interfészt használni a kiíratható osztályok példányaira és akkor a Printer-ben nem kellene mindenre külön metódus. Persze ez csak akkor érdekes, ha feltételezzük, hogy nem csak Customer példányokat tud kiíratni.)
Viszont így elsőre megfelelő ez a kód, már így is sokkal olvashatóbb lett.
Amit fontos tudni, hogy ezt a refactoring pattern-t nagyon jól tudják kezelni az IDE-k. Tehát, ha csak teheted, ne kézzel csináld. Általában van ezekre egy nagyon könnyű billentyű kombináció. Általában csak ki kell jelölni a kiemelni kívánt kódrészletet, és megnyomni a billentyű kombinációt, és hipp-hopp ki lett emelve az adott metódus. A paraméterek kezelését és a visszatérési értékeket pedig mind megoldja helyettünk az IDE.

 

Egy utolsó gondolat

Amikor még újak vagyunk az objektum orientált világban nagyon furcsának érezzük ezt a rengeteg metódushívást, kicsit olyan érzésünk van, mintha folyamatosan csak tolnánk magunk előtt a problémát azzal, hogy folyton delegáljuk a feladatot más-más metódusokba. Ezt ellensúlyozni is szoktuk az elején azzal, hogy nah, bizony mi nem, majd mi mindent megírunk egy helyen, és milyen kis nett lesz.
Nos, a szomorú az, hogy nem lesz, és nagyon nehéz lesz később értelmezni. A folyamatos delegálásnak az az értelme, hogy a problémát feldaraboljuk apróbb és apróbb részekre, ezzel egyszerűsítjük magunk és mások számára is a dolgot.
Ha tanultál mesterséges intelligenciákról, akkor ismerős lehet az a módszer, hogy probléma redukció. Azaz a feladat olyan módú megoldása, hogy egészen addig bontjuk egyszerűbb és egyszerűbb problémákra az amúgy nagyon komplex feladatot, amíg el nem jutunk minden pontján egy-egy triviális feladathoz. Ez a lényege a sok-sok egymásba ágyazott metódushívásból álló szerkezetnek.
Amikor erről beszélek szinte minden alkalommal felmerül a kérdés, hogy oké-oké, de nem lesz ettől lassabb az alkalmazásom?
A válasz az, hogy a metódushívásokban lévő overhead-et szinte teljesen kiküszöböli a fordító és maga a JVM. Tehát ilyen formában nem fordulhat elő, hogy lassabb legyen a kódod az ilyen átalakítások után.

Tehát ennyi lenne az extract method minta. Ezt a mintát mindenképpen érdemes megjegyezned, vagy legalább azt, hogy milyen billentyű kombinációval tudod megkérni rá az IDE-t, mert erre sok későbbi minta fog építeni.