All we do is looking for some way to fulfill our needs.

piątek, 27 maja 2011

Tagged under: , , ,

Naturalny porządek refaktoryzacji pod lupą cz. 1

(Rozwinięcie tego wpisu powinno pojawić się w najbliższym SDJ http://sdjournal.pl/)


Refaktoryzacja to odwieczny (to chyba nie najlepsze słowo jak na krótki czas funkcjonowania dyscypliny inżynierii oprogramowania ;-)) problem - bo wszyscy wiedzą, że powinno się to robić, a nikt nie ma na to czasu. 
Jak już zaczniesz refaktoryzować, najczęściej nie wystarczy po prostu refaktoryzać, bo łatwo wpaść w szał refaktoryzacji, który polega na refaktoryzowaniu wszystkiego i za wszelką cenę. Przedstawię poniżej strategię refaktoryzacji, która w sposób systemowy pozwala podejść do tego często niewdzięcznego zadania, jednocześnie przyczyniając się do ewolucyjnego rozwoju projektu i architektury. Podpowiada też, jak się zabrać za kod odziedziczony i zacząć go refaktoryzować. Oto magiczne punkty:
1.   Zacznij od prostego rozwiązania, zgodnie zasadą Keep it simple stupid. Nie myśl zbyt dużo o wzorcach, o wprowadzaniu wszelkiej możliwej elastyczności.
2.   Z dużych metod zaczynaj wyodrębniać mniejsze składowe metody (refaktoryzacja Extract Method). Dąż do realizacji wzorca Compose Method – niech Twoja główna metoda składa się z serii wywołań mniejszych metod.
3.   Kiedy Twoje klasy będą składały się z dużej ilości małych metod, zacznij analizować odpowiedzialność klasy. Przesuń metody, które realizują inne odpowiedzialności do bardziej odpowiednich dla nich klas.
4.   Z czasem zaczynasz dostrzegać, że w Twoich rozwiązaniach potrzeba elastyczności – zacznij wprowadzać wzorce projektowe, tam gdzie potrzeba.
5.   Raz na jakiś czas (raz na kilka miesięcy przy większych projektach), analizuj to, co dzieje się z Twoim projektem. Architektura wymaga regularnego odświeżania i wprowadzania zmian, aby przystawała do pojawiających się wymagań.


Załóżmy, że mamy napisaną klasę udostępniającą metodą służącą do zaciemniania tekstu, będącego parametrem metody. Przykładowy kod znajdziesz na poniżej.
public class TextManager
{
    private TextManagerHelper _hlp = null;
    // ...
    public TextManager(TextManagerHelper _helper)
    {
        this._hlp = _helper;
    }

    private Random rnd = new Random();

    public string Convert(string text)
    {
        String result = "";
        List<TextPart> prts = _hlp.Convert(text);

        for (int i = 0; i < prts.Count; i++)
        {
            if (prts[i].Type.Equals(TextPartType.WORD) && rnd.NextDouble() < 0.2)
            {
                if (i + 2 < prts.Count)
                {
                    TextPart t = prts[i];
                    prts[i] = prts[i + 2];
                    prts[i + 2] = t;
                }
            }
        }

        for(int i1 = 0; i1 < prts.Count; i1++)
        {
            if (rnd.NextDouble() < 0.2)
            {
                prts.Insert(i1, new TextPart(" ", TextPartType.NONWORD)); i1++;
                String[] wds = new String[] { "i", "a", "aczkolwiek", "poniekąd" };
                int ind = rnd.Next(wds.Length);
                prts.Insert(i1, new TextPart(wds[ind], TextPartType.WORD)); i1++;
                prts.Insert(i1, new TextPart(" ", TextPartType.NONWORD));
            }
        }

        String result2 = "";

        foreach (TextPart part in prts)
        {
            result2 += part.Contents;
        }

        result = result2;
        result = Regex.Replace(result, @"[\?!-\.,:;'\(\)]", "", RegexOptions.CultureInvariant);

        String result1 = "";

        for ( int i2 = 0; i2 < result.Split(' ').Length - 1; i2++)
        {
            if (rnd.NextDouble() < 0.5)
            {
                char [] chs = new char[] { '.', ',', '!' };
                int j = rnd.Next(chs.Length);
                result1 += result.Split(' ')[i2] + chs[j];
            }
            else
            {
                result1 += result.Split(' ')[i2] + " ";
            }
        }
        result1 += result.Split(' ')[result.Split(' ').Length - 1];
        result = result1;

        result = result
            .Replace("ą", "a")
            .Replace("ł", "l")
            .Replace("ę", "e")
            .Replace("ń", "n")
            .Replace("ż", "z")
            .Replace("ź", "z")
            .Replace("ó", "o")
            .Replace("ś", "s")
            .Replace("ć", "c")
            .Replace("Ą", "A")
            .Replace("Ł", "L")
            .Replace("Ę", "E")
            .Replace("Ń", "N")
            .Replace("Ż", "Z")
            .Replace("Ź", "Z")
            .Replace("Ó", "O")
            .Replace("Ś", "S");

        String result3 = "";
        char[] tArray = result.ToCharArray();

        foreach (char ch in tArray) {
            if (rnd.NextDouble() < 0.3)
            {
                char? newCh = null;
                if (Char.IsLower(ch))
                {
                    newCh = Char.ToUpper(ch);
                } else
                {
                    newCh = Char.ToLower(ch);
                }

                result3 += newCh;
            }
            else
            {
                result3 += ch;
            }
        }

        result = result3.Replace(" ", "");

        return result;
    }
}



C. D. N.

3 komentarze:

Mirek pisze...

Zawsze mam problem z refaktoryzacja na etapie "extract class". Co zrobić w sytuacji kiedy wyodrebniona metoda korzysta z wstrzyknieę w serwisie

np

public class SomeService{
@Inject
OtherService other;

public void extractedMethod(){
other.someWork();
.....
}
}

Nie wiem czy wtedy tworzyć kolejny serwis który obsługuje wstrzyknięcia czy lepiej stowrzyć zwykłą klasę i zależności przekazać jej jako parametry konstruktora ewentualnie jaka fabryczka

Mariusz Sieraczkiewicz pisze...

Jeśli dobrze Cię rozumiem, to pytasz, co zrobić w sytuacji, kiedy wyodrębniona metoda korzysta z metody obiektu, który jest zależnością (czyli polem w klasie, kompozycją).

Najprawdopodobniej (choć może się to różnić w konkretnych przypadkach):
1) jeśli korzystasz z tej metody other.someWork() tylko w wyodrębnionej metodzie, to przenosisz wyodrębnioną metodę i zależność (pole other) do nowej/innej klasy np. ExtractedClass, i to ona będzie wstrzykiwana
2) jeśli metody klasy OtherService są wykorzystywane również w innych metodach danej klasy SomeService, to być może je wszystkie można przenieść do nowej/innej klasy ExtractedClass.
3) jeśli nie można przenieść, choć to dzieje się rzadziej (zgodnie z zasadą SRP - pojedynczej odpowiedzialności), to obydwie klasy: SomeService i ExtractedClass mają zależność OtherService.

Skonkretyzuj przykład to będzie można wskazać bardziej konkretnie rozwiązanie :)

Mirek pisze...

Cyt "jeśli korzystasz z tej metody other.someWork() tylko w wyodrębnionej metodzie, to przenosisz wyodrębnioną metodę i zależność (pole other) do nowej/innej klasy np. ExtractedClass, i to ona będzie wstrzykiwana"

Właśnie o to mi chodziło, zastanawiałem się czy nowo wyodrębniona klasa ma być beanem zarządzalnym aby było w nim możliwe wstrzyknięcie czy zwykłą klasą POJO