Archive for Sierpień, 2010

W pewnym projekcie, miałem mały problem związany z przejrzystością kodu – mianowicie w kodzie przewijały się różnego rodzaju strategie przechodzenia po elementach:

  • kolekcje (java.util.Collection) – for-each
  • enumeracja (java.util.Enumeration) – for z licznikiem i metoda hasMore elements
  • listy node’ów (org.w3c.dom.NodeList) – for z licznikiem i metoda getLength wyznaczająca koniec

Dodatkowym problemem zaciemniającym kod w przypadku ostatniego elementu jest fakt, że metoda NodeList.item zwraca ZAWSZE node’a, nawet jeżeli użytkownik WIE, że wszystkie node’y będą typu Element i tak musi je sobie zrzutować.

Rozwiązaniem problemu było dodanie metody szablonowej, która na wejsciu przyjmie obiekt, po ktorym będzie sobie chodzić iterator i będzie go zwracać, no to do dzieła:

package pl.bedkowski.util;

import java.util.Enumeration;
import java.util.Iterator;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class IterGenerator {

	public static <T> Iterable<T> iter(final Enumeration<T> e) {
		return new Iterable<T>() {

			public Iterator<T> iterator() {
				return new Iterator<T>() {

					public boolean hasNext() {
						return e.hasMoreElements();
					}

					public T next() {
						return e.nextElement();
					}

					public void remove() {
						throw new UnsupportedOperationException();
					}

				};
			}
		};

	}
}

Tutaj jak widać sprawa była prosta – interfejs Enumeration jest szablonem, więc zamiana jednego na drugi odbyła się w miarę bezboleśnie. Z NodeListą już nie pójdzie tak łatwo, no ale do dzieła :)

	public static <T extends Node> Iterable<T> iter(final NodeList e, Class<T> clazz) {
		final int size = e.getLength();
		return new Iterable<T>() {

			public Iterator<T> iterator() {
				return new Iterator<T>() {

					private int current = 0;

					public boolean hasNext() {
						return current < size;
					}

					@SuppressWarnings("unchecked")
					public T next() {
						return (T) e.item(current++);
					}

					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}

Jak widać w tym przypadku już nie jest tak pięknie, potrzeba dodać adnotację, żeby ukryć warninga, ale przynajmniej użytkownik nie musi sobie tym zaprzątać głowy. Dodajmy jeszcze małe ułatwienie:

	public static Iterable<Node> iter(final NodeList e) {
		return iter(e, Node.class);
	}

I teraz tylko jakiś przykładzik:


import static pl.bedkowski.util.IterGenerator.iter;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Example {
	public static void main(String[] args) {
		NodeList list = null; // wiem, wiem NPE, ale nie mam zadnej list pod reka
		for(int i=0;i<list.getLength();i++) {
			Node n = list.item(i);
			System.out.println(n.getLocalName());
		}

		// teraz wykorzsytamny nasz iter
		for(Node n : iter(list)) {
			System.out.println(n.getLocalName());
		}

		//teraz gdy wiemy, ze lista zawiera same obiekty typu Element
		for(int i=0;i<list.getLength();i++) {
			Element e = (Element) list.item(i);
			System.out.println(e.getTagName());
		}

		// z wykorzytaniem itera
		// teraz wykorzsytamny nasz iter
		for(Element n : iter(list, Element.class)) {
			System.out.println(n.getTagName());
		}
	}

}

Jak widać pracy wcale nie trzeba włożyć dużo a korzyść spora :)

Ten wpis będzie dotyczył zwiekszania czytelności kodu podczas dynamicznego nadpisywana metody w JavaScript’cie.

Problem jest banalny – mamy sobie jakas klase, która oferuje 2 metody, getName/setName i chcielibyśmy w zupełnie innym miejscu dla jednej instancji zamienić implementację setName (poprzednia nas nie interesuje).


function MyClass(){}

MyClass.prototype.getName(){}

MyClass.prototype.setName(){}

Jak widać na powyższym kawałku kodu, przykład jest banalny, teraz utwórzmy sobie instancję MyClass:


// pierwsza instancja
var m1 = new MyClass();

// druga z nadpisana metoda
var m2 = new MyClass();

m2.setName = function newSetName() {}

Na pozór wszystko wydaje się cacy – tworzymy sobie 2 instancje, z których jedna ma troche zmienione zachowanie, wszystko przecież jest czytelne. Owszem, ale co sie stanie jeżeli tworzenie instancji m1 i m2 będzie w znacznie oddalone od miejsca ich uzycia? Wtedy deklaracja MyClass nie da nam żadnej wskazówki, że jej zachowanie może sie zmienić. Jak temu zaradzić – można przesłać metode w konstruktorze, oto pomysł:


//dodajmy parametr i jakies jsdocki

/**
 *@param Function [setName=undefined]
 */
function MyClass(setName) {
   if (setName) {
      this.setName = setName;
   }
}

// reszta bez zmian

Jak widać mamy domyślną implementacje w prototypie a dodatkowo podczas tworzenia klasy, przesyłamy jej funkcję zastepująca setName, wtedy utowrzenie m1 i m2 wyglądałoby tak:


var m1 = new MyClass();

var m2 = new MyClass(function newSetName(){});

Jak widać dodanie parametru do konstruktora, któremu towarzyszy dopisanie krótkiego jsDoc’a zajmuje 5 minut a w zupełności wystarczya za dokumentację.

Dodalem nowa kategorie nazwana „czytelny kod”, bede w niej staral sie przedstawic przyklady zaczerpniete z projektow przy ktorych pracuje, wskazujace w jaki sposob czytac pisać kod z nastawieniem na jego czytelnosc. Najprostszy sposob to konwencje nazewnicze, ale postaram sie poglebic temat uzywajac przykladow roznych podejsc do tego samego problemu ze wskazaniem na to bardziej czytelne.