Czysty kod

Jestem na krótko po przeczytaniu Czystego Kodu od Wujka Boba i chciałbym się podzielić kilkoma wrażeniami z książki. Jest to zbiór przypowieści o tym, jak pisać kod, pozostał on czysty i czytelny. Aby utrzymanie kodu nie było karą za złe zachowanie, a pisanie testów nie było piekłem dla programistów. Poza tym testy piszemy przed napisaniem kodu – rajt?

Wszystkie złe rzeczy, które przez przypadek mogliśmy wyprodukować są opatrzone przykładowym kodem, rozwiązania także przedstawiają kod. Wskazówki sugerowane przez wujka, są dobre dla początkujących programistów jak i dla wypasionych developerów – nawet jako forma przypomnienia.

Poniżej kilka przykładów, które sobie zanotowałem, część z nich to oczywistość. Uważam jednak, że warto wracać do podstaw, kod pochodzi z książki.
Nazwy zmiennych
Dobrze aby nazwy było tak dobrane, aby dało się je wymówić. Ułatwia to zapamiętywanie struktury kodu, komunikację w projekcie oraz czytelność. Dotyczy to nazw klas/struktur, metod oraz zmiennych. Jak pisze wuj Bob: jeśli piszesz komentarz aby opisać zmienną, usuń komentarz i popraw nazwę zmiennej.
ZŁY KOD DOBRY KOD
class DtaRcrd102 
{
    private DateTime genymdhms;
    private DateTime modymdhms;
    private string pszqint = “102”;
};
class Customer 
{
    private DateTime generationTimestamp;
    private DateTime modificationTimestamp;
    private string recordId = “102”;
};
Magiczne wartości
Omijamy je z daleka, jeśli musisz wpisać gdzieś ‘7’, to utwórz zmienną, która swoją nazwą powie, że ‘7’ to jest dni w tygodniu.
ZŁY KOD DOBRY KOD
for (int j=0; j<34; j++) 
{
    s += (t[j]*4)/5;
}
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) 
{
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Tworzenie obiektów
Nie ograniczaj się tylko do konstruktorów i ich ciągłego przeciążenia, skorzystaj z wzorca metoda wytwórcza.
MOŻNA LEPIEJ
Complex fulcrumPoint = new Complex(23.0);

Complex fulcrumPoint = Complex.FromRealNumber(23.0);
Krótko zwięźle i na temat
Ilość przekazywanych argumentów do funckji powinna być możliwie najmniejsza:
1 – spoko
2 – jeśli musisz
3 – zastanów co robisz 
4 – WTF? 
5 – link
Może któryś z parametrów uda się przenieść na stałe do klasy, co zmniejszy ich ilość w sygnaturze funkcji. Jeśli nie, to zaproponuj strukturę przechowującą tą ogromną ilość parametrów i przekazuj taką strukturę.
Pamiętacie wpis o SOLID, otóż wyobraźcie to sobie, że SRP (single
responsibility principle) można też zastosować wobec metod i funkcji.
Niech będę zwięzłe, krótkie, monotematyczne i nie posiadają efektów ubocznych.
ZŁY KOD, ZŁY
public boolean checkPassword(String userName, String password) 
{
    User user = UserGateway.findByName(userName);
    if (user != User.NULL) 
    {
        String codedPhrase = user.getPhraseEncodedByPassword();
        String phrase = cryptographer.decrypt(codedPhrase, password);
        if (“Valid Password”.equals(phrase)) 
        {
            Session.initialize();
            return true;
        }
    }
    return false;
}

Rozdzielenie odpowiedzialności
CQS (command query separation)- Bob zaleca odseparowanie polecenia od zwracania wartości:
STARE NAWYKI NOWE NAWYKI
if (set(“username”, “unclebob”))
{…}
if (attributeExists(“username”))
{

     setAttribute(“username”, “unclebob”);

}

Trochę więcej o CQS można znaleźć na wiki.

Błędy i wyjątki
Preferuj wyjątki zamiast zwracania kodu błędu:
ŹLE DOBRZE
if (deletePage(page) == E_OK) 
{
    if (registry.deleteReference(page.name) == E_OK) 
    {
        if (configKeys.deleteKey(page.name.makeKey()) == E_OK)
        {
            logger.log(“page deleted”);
        }
         else 
        {
            logger.log(“configKey not deleted”);
        }
    } 
    else 
    {
        logger.log(“deleteReference from registry failed”);
    }

else 
{
    logger.log(“delete failed”);
    return E_ERROR;
}
try
{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey()
}
catch(…)
{
}

Obsługę błędów i wyjątków separuj od logiki kodu:

RAZEM OSOBNO
try 
{
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) 
{
    logger.log(e.getMessage());
}
public void delete(Page page) 
{
    try 
    {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) 
    {
        logError(e);
    }
}

private void deletePageAndAllReferences(Page page) throws Exception 
{
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) 
{
    logger.log(e.getMessage());
}

Komentarze
Nie powtarzaj się i nie opisuj kodu w komentarzach:
SAMO ZŁO
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) != 0); // a != b
assertTrue(ab.compareTo(ab) == 0); // ab == ab
// if the timeout is reached.
public synchronized void waitForClose(final long timeoutMillis) throws Exception
{
    if(!closed)
    {
        wait(timeoutMillis);
        if(!closed)
            throw new Exception(“MockResponseSender could not be closed”);
    }
}

Formatowanie kodu
Ustal formatowanie w projekcie i się go trzymaj, popraw czytelność stosując wcięcia, odstępy.
GORZEJ LEPIEJ
void foo()
{
    int zmiennaA;
    int zmiennaB;
    int zmiennaC;
    operacjaA()
    operacjaB()
    operacjaC()
    operacjaD()
}
void foo()
{
    int zmiennaA;
    int zmiennaB;
    int zmiennaC;

    
    operacjaA()
    operacjaB()

    
    operacjaC()
    operacjaD()
}
Abstrakcja i interfejsy
Udostępniaj tylko tyle informacji o klasie i jej stanie wewnętrznym ile potrzeba. Zasada ograniczonego zaufania w programowaniu też ma sens, korzystaj z interfejsów i abstrakcji
OK OKIEJEJ ( w sensie, że lepiej)
public interface Vehicle 
{
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
}
public interface Vehicle 
{
    double getPercentFuelRemaining();
}

Zastanów się nad tym co udostępniasz innym:

🙁 🙂
List<int> getList()
{…}
IEnumerable<int> getList()
{…}

Chyba że lubisz (lub chcesz), sytuację gdy klient może wywołać getList().Clear();

Tam sięgaj gdzie ręka dosięga:

Klasy powinny korzystać ze swoich zmiennych, otrzymanych jako parametr, lub stworzonych przez siebie. Jeśli musisz stworzyć  okrutne połączenia, lepiej jest rozbić wszystkie wyniki na poszczególne wywołania. Wyjątkiem jest sytuacja, gdy dostęp następuje przez publiczne pola struktur. Choć i tak najlepiej jest kazać klasie odpowiedzialnej zrobić wszystko za nas:
ZŁE LEPIEJ SUPERAŚNIE
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
final String outputDir = ctxt.options.scratchDir.absolutePath;
output.ctxt.GetAbsolutePathToScratchDir()
//sposób na uratowanie honoru
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

Law of Demeter

Obiekty NULL
O ile to możliwe zamiast NULL postaraj się zwracać neutralne obiekty, takie które nie wywalą kodu a jednocześnie nie spowodują jego zmiany:
BRZYDKO ŁADNO
List employees = getEmployees();
if (employees != null) {
    for(Employee e : employees) {
        totalPay += e.getPay();
    }
}
List employees = getEmployees();
for(Employee e : employees) {
    totalPay += e.getPay();
}

getEmployess() zamiast NULL zwraca pustą kolekcję.

To tylko część z tego co proponuje Martin. Jeśli jesteście zainteresowani to jeszcze raz zachęcam do przeczytania. Czyta się szybko i przyjemnie. Jedyny minus (taki na siłę) to przykłady są w JAVA – ale niech będzie. 

Uwagi i komentarza zawsze chętnie przyjmę.

One thought on “Czysty kod

  1. Nie zgodzę się, że przykłady w Javie to minus.

    Książki takie jak ta (ale nie tylko) czasem dobrze przeczytać z przykładami w innym języku niż "nasz codzienny". Dzięki temu zmusimy się to pomyślenia jak byśmy te przykłady napisali w naszym a przez to lepiej przyswoimy i nauczymy się tematu. Ja podobnie miałem z książki DesignPatterns z serii HeadFirst. Przykłady w Javie więc nie można skopiować 1:1 – trzeba myśleć, a to plus.

    Paweł

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.