Część 0
Część 1

Drugą część cyklu Ekstremalna obiektowość w praktyce czas zacząć. W głośnikach leci sobie Hammerfall, a my zajmiemy się drugą zasadą Jeff’a Bay’a.

Nie używaj słowa kluczowego else

Przecież to nic trudnego powiecie. Zatem na początek mały przykład kodu, w którym waszym zadaniem będzie zastosowanie tej zasady:

Listing 1. Jak by tu usunąć else

public AbstractServiceStub(Object object) {
		if (service == null) {
			super.service = new Service();
		} else {
			super.service = service;
		}
		((Service) super.service).setTypeMappingVersion("1.1");
	}

To jest kod wygenerowany przez AXIS. Trzeba z niego usunąć słówko kluczowe else. Proste, prawda?

W tym przypadku wystarczy użyć Extract Method w celu wyciągnięcia inicjalizacji do oddzielnego bloku:

Listing 2. Usunięcie else przez Extract Method

private void initService(final Service service) {
		if (Assertions.isNull(service)) {
			super.service = new Service();
			return;
		}
		super.service = service;
	}

Metoda prosta i skuteczna. Życie nie jest jednak takie proste za każdym razem.

Określony przepływ

Trochę bardziej skomplikowane zadanie. Jest sobie obiekt State reprezentujący różne stany konfiguracji – prawidłowy, nieprawidłowy i niezainicjowany. Trzeba zaprogramować prawidłową zmianę stanu, czyli:

  • Ze stanu niezainicjowany można zmienić stan na prawidłowy i nieprawidłowy.
  • Ze stanu prawidłowy można zmienić stan na nieprawidłowy.
  • Ze stanu nieprawidłowy nie można zmienić już stanu.

Oczywistą sprawą będzie użycie enuma. Mniej oczywiste jest zmienianie stanu:

Listing 3. Uzależnienie wartości zwracanej od różnych warunków

package pl.koziolekweb.eowp2;

public enum State {

	VALID, INVALID, NOT_INIT;

	public State changeState(State target) {
		if (target == VALID && this != INVALID) {
			return VALID;
		} else if (target == INVALID) {
			return INVALID;
		} else if (target == NOT_INIT && this == NOT_INIT) {
			return NOT_INIT;
		} else {
			return this;
		}
	}
}

Zaszalałem 😀 Ok, co można tu zrobić? Można oczywiście zamiast konstrukcji else if zastosować kilka ifów, a zamiast ostatniego else zwracać this jako wartość domyślną. wtedy będzie to wyglądało mniej więcej tak:

Listing 4. Uzależnienie wartości zwracanej od różnych warunków bez else

package pl.koziolekweb.eowp2;

public enum State {

	VALID, INVALID, NOT_INIT;

	public State changeState(State target) {
		if (target == VALID && this != INVALID) {
			return VALID;
		}
		if (target == INVALID) {
			return INVALID;
		}
		if (target == NOT_INIT && this == NOT_INIT) {
			return NOT_INIT;
		}
		return this;

	}
}

W sumie nie zmieniło się nic.

Drugi if w metodzie to tak naprawdę ukryty else

Nie ma w tym nic dziwnego. Po prostu formułkę „Jeżeli a to b w przeciwnym wypadku c” rozbijamy na „Jeżeli a to b. Jeżeli nie a to c”. Zysku żadnego, kod niby lepszy, a w praktyce gorszy, bo else choć złe to jednak coś oznacza. Kilka ifów z rzędu tylko gmatwa kod.
W tym konkretnym przypadku rozwiązaniem jest wykorzystanie szmocy… znaczy się wykorzystamy polimorfizmW. Zachowanie będzie zależało od konkretnej implementacji:

Listing 5. Uzależnienie wartości zwracanej od różnych warunków za pomocą polimorfizmu

package pl.koziolekweb.eowp2;

public enum State {

	VALID {
		@Override
		public State changeState(State target) {
			if (target != NOT_INIT) {
				return target;
			}
			return this;
		}
	},
	INVALID {
		@Override
		public State changeState(State target) {
			return this;
		}
	},
	NOT_INIT {
		@Override
		public State changeState(State target) {
			return target;
		}
	};

	public abstract State changeState(State target);
}

Z zestawu kilku ifów wybraliśmy jeden istotny, a resztę usunęliśmy. Nie ma ifów, nie ma elseów udających ify. Ładnie.

Motywacja

Po co tak naprawdę stosujemy tą regułę. Powód jest prosty i nazywa się złożoność cyklomatycznaW (mądre słowo). Jednostką złożoności jest ilość WTF/s. Inaczej mówiąc jest to liczba określająca zagmatwanie kodu. Ściślejsza definicja mówi coś o ilości brzegów, wierzchołków i punktów wejścia i wyjścia w grafie przepływu programu. Takie matematyczne pieprzenie, które nas malo obchodzi na chwilę obecną 😉
Generalnie sama złożoność nie będzie wiele mówić jeżeli programujemy obiektowo. Trochę lepszą metryką jest średnia złożoność na metodę w danej klasie. Jeżeli jednak korzystamy z tej metryki to trzeba pamiętać, że limity podane na wiki są znacznie niższe.

Jak mierzyć CC?

Potrafi to i Cobertura i JDepend i JavaNCSS.

Po co mierzyć CC?

Ponieważ nie tylko else wpływa na zagmatwanie kodu. Jest to dobra metryka obrazująca ogólną złożoność kodu i pozwala w połączeniu z prezentowaną tu regułą na uproszczenie kodu.

Jak stosować ta regułę

Istnieje kilka sposobów na to jak wprowadzić dziś omawianą regułę do kodu. Oto kilka z nich. Nie wszystkie, bo dużo w tym wypadku zależy od konkretnego przypadku, ale to te najpopularniejsze.

Odwróć warunek

Czasami można pozbyć się else odwracając warunek. Zamiast używać == można użyć !=. Trochę bardziej skomplikowaną sztuczką może być tu wykorzystanie prawa De MorganaW choć to czasami wprowadza znacznie więcej zamieszania. Warto tu mieć już gotowy komplet testów, które pokrywają wszystkie kombinacje warunków.

Polimorfizm

Czyli uzależnienie przebiegu programu nie tyle co od warunku co od dostarczonej wersji metody. Bardzo fajna sprawa, szczególnie jak popatrzymy na scalowe Case Classes. Na dłuższą metę jest to bardzo trudne ponieważ zacznie się biegunka klas spowodowana koniecznością dostarczanie nie tylko klas realizujących różne wersje algorytmu w zależności od warunku, ale też odpowiednich fabryk. Tu ratunkiem mogą okazać się wzorce dekorator i builder oraz rozsądna delegacja zadań, które będą choć trochę upraszczać tworzenie klas.

Delegacja do bibliotek zewnętrznych

Każdy zna konstrukcję jeżeli null to NPE. W Bean Validation API 1.1 będzie walidacja argumentów metod. To pozwoli nam na adnotowanie argumentu i nie martwienie się o walidacje lokalne. Podobnie ma się sprawa z metodami takimi jak equals, hashCode i toString, które są niewyczerpanym źródłem elseów.

Programowanie aspektowe

Tak jak wyżej, ale w trochę inny sposób. Niektóre instrukcje można przenieść do aspektu i nie martwić się ich obsługa w kodzie.

Kontrowersyjny Null Object

Na koniec pozostawiłem sobie najbardziej kontrowersyjną metodę wprowadzenia tej reguły. Generalnie znaczna część instrukcji warunkowych sprawdza czy dany obiekt nie jest null. W zamian można wprowadzić wzorzec Null Object. W skrócie jest to użycie specjalnej implementacji klasy, która „robi nic” zamiast zwrócenia null. Upraszcza to znacznie implementację ponieważ nie trzeba sprawdzać warunków. Z drugiej strony należy pamiętać, że jest to wzorzec wirusowy. Oznacza to, że jeżeli dana metoda będzie zwracać jakiś obiekt, to w implementacji Null Object powinniśmy zwracać Null Object danego obiektu. Zatem obok drzewka realnych obiektów otrzymujemy ich nullowe odpowiedniki. Coś za coś.

Podsumowanie

Kolejna stosunkowo prosta reguła. Pozwala ona na tworzenie stabilnego, prostego kodu, który jest bardzo łatwy w testowaniu. Ograniczenie ilości warunków w instrukcjach warunkowych pozwala na uzyskanie lepszego pokrycia testami dla tych instrukcji.
Wadą jest konieczność kombinowania ponieważ czasami ciężko wymyślić jakieś dobre rozwiązanie problemu i trzeba zmieniać wszystkie założenia co do klasy.
Warto jednak wprowadzać tą regułę tak często jak tylko się da ponieważ dzięki temu nasz kod staje się prosty, a ilość WTF/s dąży do 1.