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.
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:
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(); |
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ę.
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ł