Skip to content

Instantly share code, notes, and snippets.

@kubamarchwicki
Last active Oct 14, 2019
Embed
What would you like to do?
SOLID article for nljug
*.fodt
*.docx
*.html

Projektując nasz Kod Powszedni

Programistyczna codzienność: kurtka, kapcie, kawa. Po tym porannym rytuale zasiadamy do ulubionego IDE, rozprostowujemy palce i zaczynamy tworzyć kolejne linie kodu. W tyle głowy kołatają nam się dobrze utrwalone zasady: DRY, GRASP oraz SOLID. I właśnie ta ostatnia jest bohaterem moich dzisiejszych rozważań.

SOLID, jakkolwiek nie byłby ten skrót obecnie rozumiany, uznaje się za podstawę nowoczesnego programowania obiektowego - zaraz obok czystego kodu, praktyk TDD i pewnie kilku innych rzeczy. Co mnie jednak niepokoi, to pewien dogmatyzm, przyjmowanie reguł jako prawd objawionych, nie zastanawiając się, co jest pod spodem, z czego zasady wynikają. Co SOLID oznacza, skąd się wziął, czy te zasady są konieczne, czy są skuteczne i dlaczego tak do nich lgniemy?

Ciekawe? Interesujące? Takich rozważań jak w tym artykule będzie więcej podczas konferencji Segfault (np. w Krakowie https://segfault.events/krakow2019/), ale o tym na samym końcu artykułu.

SOLIDna analiza

Zacznijmy od odrobiny historii. SOLID to skrót powstały na bazie kilku artykułów Roberta C. Martina (Wujka Boba) na początku XXI wieku. Artykuły opisywały podstawowe pięć zasad programowania obiektowego: Single responsibility, Open-Closed, Liskov substitution, Interface segregation oraz Dependency inversion.

Dla tych którzy w tym momencie zetknęli się z SOLID po raz pierwszy - krótkie wprowadzenie. Nazwa SOLID pochodzi od pierwszych liter zasad programowania obiektowego: [1]

  • The Single Responsibility Principle - pojedyncza odpowiedzialność, klasa powinna mieć jeden (i tylko jeden) powód do zmian. Powód oznacza ewolucję lub zmianę pojedynczej koncepcji którą klasa enkapsuluje.

  • The Open Closed Principle - zasada otwartości, o tym, że należy rozszerzać istniejące klasy, a nie je modyfikować.

  • The Liskov Substitution Principle - klasy pochodne muszą móc przezroczyście zastępować klasy bazowe. Co przekładając na prosty język oznacza możliwość włączania w aplikacji różnych zachowań (funkcji), bazując na tym samym typie (bądź interfejsie).

  • The Interface Segregation Principle - można by napisać, że to SRP dla interfejsów. Interfejsy powinny być granularne i dostosowane do potrzeb klienta.

  • The Dependency Inversion Principle - odwrócenie zależności, klasy powinny bazować na abstrakcyjnych zależnościach, a nie na konkretnych implementacjach.

Kevlin Henney przygotował swego czasu doskonałą prezentację, rozbierającą poszczególne zasady SOLID na czynniki pierwsze - i nie sądzę abym mógł dodać do tego czego coś więcej. [2]

Jeżeliby przyjąć podejście Kevlina, to z pięciu zasad, które stoją za skrótem SOLID, zostałyby nam dwie: pojedyncza odpowiedzialności i zasada podstawienia Listkov. Pozostałe są albo archaiczne (zasada otwartości Open-Closed w swoim oryginalnym znaczeniu), bardzo zależne od języka (Interface segregation), albo są powtórzeniem (wstrzykiwanie zależności - Dependency Injection, będące sposobem implementacji zasady pojedynczej odpowiedzialności z wykorzystaniem reguły podstawienia Barbary Listkov).

Możemy próbować kopać głębiej - i to właściwie Kevlin zrobił podczas swojej prezentacji. Można zacząć zastanawiać się, co rozumiemy poprzez pojedynczą odpowiedzialność. W swojej interpretacji Kevlin posługuje się pojęciem spójności (ang. cohesion) w rozumieniu zaproponowanym przez Toma Demarco lub Glenna Vanderburga. [3]

(…​) cohesion is a measure of the strength of association of elements inside a module.
— Tom DeMarco
Structured Analysis and System Specification

Ale możemy też porzucić dalszą techniczną analizę, czym są zasady SOLID, i spróbować przyjrzeć się S z SOLID z nieco innej perspektywy.

Podstawy projektowania dla opornych

W momencie, gdy techniczne aspekty zasad wchodzących w skład SOLID są tak drobiazgowo opisane w literaturze, nie zostawia to wiele przestrzeni na „kolejny artykuł o zasadach czystego kodu”. Jaką zasadność miałyby kolejne odwołania do tych wspaniałych prezentacji i artykułów? Spróbujemy zatem innego podejścia i polejemy zasady zasady czystego kodu sosem kognitywistyki i psychologii. Zastanówmy się, jak to jest, że czysty, SOLIDny kod, jest łatwiejszy do utrzymania i modyfikacji, pracuje się z nim przyjemnie?

Codzienna praca programisty, sposób pisania kolejnych wersów kodu, wynika z wielu przyswojonych przez lata zasad. Mimo że przez wielu noszone głęboko w sercu, rzadko kiedy są przedmiotem głębszych analiz, próby zrozumienia. A jeszcze rzadziej podejmowany jest temat, co jest w tych zasadach takiego pociągającego, co sprawia, że z czystym kodem pracuje się tak po prostu lepiej.

Aby to pojąć, musimy wypłynąć na niezbadane wody psychologii behawioralnej i nauk kognitywistycznych. Jak szalenie by to nie brzmiało, wydaje się to być kluczowe dla zrozumienia znaczenia poszczególnych zasad SOLID. Jest kilka kluczowych pojęć, książek, prac naukowych, które stanowią podstawę dla pracy wielu projektantów. Spróbujmy zobaczyć, czy te same zasady znajdują zastosowanie w codziennej pracy programisty - bądź co bądź - także mocno kreatywnej. W dalszej części artykułu przyjrzymy się kilku takim pojęciom związanym z projektowaniem fizycznych przedmiotów, w jaki sposób projekt wpływa na późniejsze postrzeganie i wykorzystanie przedmiotu.

Świat fizyczny

(…​) afordancje to postrzegane i rzeczywiste właściwości rzeczy, które określają, w jaki sposób można tej rzeczy użyć.
— Donald A. Norman
The Design of Everyday Things

Pojęcie "afordancji" zostało zaproponowane przez Donalda Normana na początku lat dziewięćdziesiątych, w książce The Design of Everyday Things.[4] Pewnie niejedna osoba zastanawia się teraz, jaki ma to związek z programowaniem. Posłużę się przykładem. Jeżeliby spojrzeć i porównać dwie przykładowe klasy: Convoluted.java oraz Structured.java - mam nadzieję, że różnica jest naturalna i oczywista. Odpowiedzialność obu klas jest zbliżona - obie starają się nałożyć pewną warstwę abstrakcji na kilka operacji SQL (i robią to pewnie w nie najlepszy sposób). Ale to nie tzw. cieknąca abstrakcja (ang. leaky abstraction) jest tym, na co chciałbym zwrócić uwagę. Chodzi o czystość intencji która jest zawarta w obu tych klasach.

Pierwsza z przedstawionych klas (Convoluted.java) bezsprzecznie łamie kilka podstawowych zasad programowania obiektowego, jak chociażby pojedynczą odpowiedzialność oraz zamknięcie na modyfikacje. Implementacja nowego wymagania, dodania operacji SQL update, sprowadzałaby się najpewniej do zmiany istniejącej klasy, dodania nowej metody (najprawdopodobniej zgodnie z wytyczoną już w klasie konwencją) i…​ szybkiej ucieczki. Jakość tego kodu pozostawiałaby jednak zdecydowanie zbyt dużo do życzenia. Byłaby to sytuacja daleka od komfortowej, w której nikt nie chciałby się znaleźć.

Drugi przykład, implementacja wzorca projektowego Strategia dookoła zapytań SQL, wzbudza w programiście diametralnie inne reakcje. Jakakolwiek modyfikacja ma bardzo ograniczony zakres i jest bezpiecznym rozszerzeniem istniejącego kodu. Trwoga ustępuje miejsca swobodzie, z jaką poruszamy się po kodzie.

Listing 1. Convoluted.java
public class Sql {
   public Sql(String table, Column[] columns);
   public String create();
   public String insert(Object[] fields);
   public String selectAll();
   public String fieldByKey(String keyColumn, String keyValue);
   private String ColumnList(Column[] columns);
   private String valuesList(Object[] fields, final Column[] columns);
}
Listing 2. Structured.java
abstract public class Sql {
   public Sql(String table, Column[] columns)
   abstract public String generate();
}

public class CreateSql extends Sql {
   public CreateSql(String table, Column[] columns)
   @Override public String generate()
}

public class SelectSql extends Sql {
   public SelectSql(String table, Column[] columns)
   @Override public String generate()
}

public class InsertSql extends Sql {
   public InsertSql(String table, Column[] columns)
   @Override public String generate()
   private String valuesList(Object[] fields, final Column[] columns)
}

public class FindKeyBySql extends Sql {
   public FindKeyBySql(String table, Column[] columns, String keyColumn, String keyValue)
   @Override public String generate()
}

public class ColumnList {
   public ColumnList(Column[] columns)
   public String generate()
}

W praktyce projektowania, afordancje dostarczają szeregu wizualnych podpowiedzi, w jaki sposób używać przedmiotów. Co ciekawe, podobne zależności można zaobserwować w wirtualnym świecie oprogramowania komputerowego - chociażby bazując na wspomnianych już dwóch przykładów kodu. Afordancje umożliwiają użytkownikowi natychmiastowe podjęcie decyzji, jak obchodzić się z przedmiotem, na podstawie np. kształtu; bez komentarzy, bez instrukcji.

Wynika to z praktycznej aplikacji dwóch podstawowych pracy projektowania:

  • dobrego odzwierciedlenia modelu pojęciowego

  • zapewnienia widoczności najistotniejszych elementów

Model pojęciowy to abstrakcyjny opis rzeczywistych obiektów, z reguły odnoszący się do procesów myślowych i towarzyszących im wyobrażeń. Model pojęciowy umożliwia nam przewidzenie skutków naszych czynności (w odniesieniu np. do jakiegoś przedmiotu), zrozumienie tychże skutków i analizowanie ich. To jest model, który budujemy przez lata na bazie naszych doświadczeń, szkoleń, instrukcji - jak np. wzorce projektowe, najlepsze praktyki (rzeczy niesłychanie popularne w naszej branży). Afordancje wspierają nasz model pojęciowy poprzez dostarczenie wizualnych "sygnałów" - w jaki sposób obchodzić się z rzeczami dookoła. To wskaźniki, wizualne podpowiedzi, które mogą być przez nas interpretowane, które uwypuklają najistotniejsze z punktu widzenia użyteczności elementy przedmiotów.

Możemy te same reguły aplikować zarówno do fizycznych przedmiotów, jak i do kodu (szczególnie w mikro skali: pojedyncze bloki kodu, metody, funkcje, klasy). Jeżeli proste rzeczy wymagają opisu, dodatkowej instrukcji, obrazka - projekt nie dał rady. Niezależnie, czy mówimy o kolejnym modelu czajnika, czy o fragmencie kodu źródłowego, z którym przyszło nam pracować. Jedyna różnica, że osobą "poszkodowaną" nie jest klient, użytkownik końcowego produktu, a raczej programista siedzący obok, którego zadaniem jest (dosłowne) użycie kodu, który został przez nas napisany.

A co w momencie, kiedy sprawy stają się z natury rzeczy bardziej skomplikowane? Kiedy domena problemu, z którym pracujemy, sama w sobie jest złożona? Kiedy liczba możliwych przejść, ścieżek wykracza daleko poza pojemność naszej „głowy”?

Spamiętać to wszystko

Okazuje się bowiem że pojemność naszej "głowy" jest ograniczona - do siedmiu (mniej więcej).

Ta liczba nie wzięła się znikąd - pochodzi z bardzo popularnej pracy naukowej George’a Millera z zakresu psychologii The Magical Number Seven, Plus or Minus Two: Some Limits on our Capacity for Processing Information.[5] W swojej pracy George dowodzi, że pamięć krótkotrwała (operacyjna) ma szereg ograniczeń, m.in. w kwestii ilości informacji, którą potrafi jednocześnie przetworzyć. Czy jest to siedem, czy pięć czy jakakolwiek inna liczba - różne badania odmiennie to definiują. Niektórzy uważają, że w ogóle nie powinno się definiować pojemności pamięci w sposób dyskretny. Jeszcze inne eksperymenty dowodzą, że ta pojemność to tylko trzy.

W zasadzie każda z tych teorii jest prawdziwa, bowiem nie chodzi tutaj o liczbę; każdy eksperyment mówi, że nie możemy przeciążać użytkowników (odbiorców) ilością informacji, ponieważ pojemność pamięci jest skończona (i dodatkowo niewielka). Bądźmy życzliwi i ograniczmy ilość wątków, szczegółów, elementów koniecznych do zrozumienia kawałka kodu, nad którym pracujemy. Niezależnie, czy odnosimy się do liczby parametrów wejściowych metody, atrybutów klasy, czy liczby interfejsów, które klasa implementuje: to wszystko jest wstępem (inwestycją) do rozumienia istoty złożoności problemu (np. klasy). Jeżeli ta informacja jest ustrukturyzowana (poprzez rozłożenie na kilka rozdzielnych koncepcji), w małych porcjach (małych encjach) - to jest dokładnie to, o czym pisał Wujek Bob w rozdziale "Funkcje" książki o czystym kodzie.[6]

Wciąż do rozwiązania pozostaje kwestia, co się stanie, gdy liczba elementów, koncepcji potrzebnych do zrozumienia problemy wykracza poza magiczną liczbę, niezależnie czy jest to siedem, pięć czy trzy? To jest właśnie moment, kiedy rozpoczyna się proces uczenia.

Jak działa pamięć?

Posłużmy się kolejną pracą naukową z zakresu psychologii. W latach sześćdziesiątych Richard Atkinson i Richard Shiffrin napisali pracę "Human memory: A proposed system and its control processes". Proponuje ona pewien sposób modelowania ludzkiej pamięci poprzez wydzielenie trzech niezależnych poziomów, na których przechowywane są informacje oraz definiuje sposoby przepływu informacji pomiędzy poziomami.

Dobrą wiadomością jest to, że nie jesteśmy ograniczeni tylko do pojedynczej pamięci o niewielkiej pojemności. Mamy do czynienia z kilkoma poziomami (z kilkoma rejestrami), które mogą być wykorzystywane niezależnie, w reakcji na różne bodźce otoczenia. Tak jak przedstawiono to na ilustracji poniżej.

multistore memory model pl
Figure 1. Wielomagazynowy model pamięci Atkinsona i Shiffrina

Zaproponowany model opisuje ludzką pamięć za pomocą trzech komponentów:

  • pamięć sensoryczna (pamięć zmysłów)

  • pamięć krótkotrwała (operacyjna)

  • pamięć długotrwała

Od momentu, kiedy praca Atkinsona i Shiffrina została opublikowana, pojawiało się wiele wariacji, dyskusji i krytyki zaproponowanego modelu. Niemniej artykuł wywarł bardzo duży wpływ na późniejsze prace i badanie w kwestii ludzkiej pamięci oraz procesu uczenia jako takiego.

Ale wróćmy do programowania: z punktu widzenia pracy programisty najistotniejszy w całym modelu jest proces treningu (nauki i prób). Pamięć sensoryczna (rejestr o bardzo dużej pojemności, ale bardzo krótkotrwały - do pół sekundy) nie ma większego znaczenia dla codziennej pracy kodera. Interesujące rzeczy zaczynają się dziać, gdy bodziec zmysłowy zaczyna być analizowany przez pamięć operacyjną. Według badań obszar ten przechowuje kilka niezależnych elementów (np. siedem, aby nawiązać do wcześniejszej pracy Greorge’a Millera) przez okres około 30 sekund.

Okazuje się, że jest to szalenie istotny fakt z perspektywy pracy programisty, gdzie naturalnym jest proces budowania skomplikowanych abstrakcji podczas pracy z kodem źródłowym. Całość analizy w ogromnej większości przypadków przebiega w naszej głowie bądź przy okazji tworzenia nowego kodu, bądź analizy istniejącego. W obu przypadkach, bardzo mocno angażowana jest pamięć krótkotrwała: krucha, delikatna, której zawartość możemy utracić w ułamku sekundy. Doświadczanie ulotności tego procesu to niestety nasza codzienność, gdzie chwila nieuwagi lub oderwania się, skutecznie burzy model układany w głowie. Jednakże przeciwdziałanie temu potrafi być relatywnie proste: budując małe encje (klasy) jednoznacznie wyrażające swoją intencję (poprzez precyzyjne nazewnictwo), sprawiamy że model mentalny reprezentujący nasz kod pozostaje zwięzły.

W przeciwnym wypadku będziemy zmuszeni do ciągłego powtarzania procesu analizy - do momentu gdy poszczególne elementy trafią z pamięci krótkotrwałej do długoterminowej. Mówiąc wprost - aż się ich nauczymy. Pamięć długotrwała to obszar o nieskończonej pojemności i trwałości. Nie jest to jednak obszar łatwy w zapisie. Zapamiętanie czegokolwiek to dla większości ludzi długotrwały proces z dużym opóźnieniem. Coś jak dyski optyczne: przechowują dużo danych na zawsze, ale proces zapisu jest niewspółmiernie wolny. Jeżeli kod, który tworzymy, jest łatwy do zrozumienia (nie wymaga nauczenia się go aby z niego korzystać), cały twórczy proces ulega znacznemu skróceniu. Jest efektywniejszy.

Kilka wniosków

No więc po co nam to wszystko, kolejny festiwal wiedzy bezużytecznej? W naszej pracy musimy zapamiętywać niesłychaną ilość rzeczy. Klasy, metody, funkcje, parametry - rozrzucone po różnych miejscach systemu. Nauczyliśmy się żyć z tymi wszystkimi złożonościami, ale tak jak pisałem wcześniej - proces uczenia wymaga czasu.

Na szczęście są sposoby aby łatwiej było pracować z kodem, a klucze do nich to zrozumienie, w jaki sposób człowiek przetwarza i interpretuje bodźce napływające z otoczenia. Każda rzecz, z którą pracujemy (czy fizyczna, czy wirtualna), sama z siebie może być instrukcją obsługi. Także kod może zawierać szereg wskazówek, wynikających z umiejętności autora. Umiejętności jasnego i klarownego wyrażania myśli poprzez kod, wykorzystując rzeczy, które ludzie powinni już wiedzieć (np. odpowiednie wzorce projektowe). Niemal każda linia kodu to projektowanie w skali mikro. Projektowanie które albo zdezorientuje współprogramistów, albo uczyni ich pracę łatwiejszą. Zasady wywodzące się z projektowania fizycznych przedmiotów mogą znacząco ułatwić zrozumienie oraz poznać źródło wielu zasad programowania, do których podchodzimy dość dogmatycznie: SOLID, DRY i parę innych. A to rozumienie to duży krok w kierunku stania się lepszym, bardziej empatycznym programistą lub programistką.

Jeśli macie dość kolejnych festiwali bezużytecznej wiedzy i szukacie prawdziwych inspiracji, mam dziś dla Was ciekawą propozycję. Znacie konferencje SegFault? Jej kolejna - i zarazem - zupełnie nowa odsłona odbędzie się pod koniec listopada w Krakowie. Na czym polega jej nowość? To świeże podejście, które łączy tradycyjną formę konferencji z formułą unconference. SegFault Unconference jest wyjątkową okazją, by usiąść przy jednym stole z nie tylko z prelegentami, ale także z czołowymi nazwiskami z branży i porozmawiać o bolączkach (nie tylko) kodu naszego powszechnego, ale także o wszystkich blaskach i cieniach życia w świecie IT. To aż dwa dni odnowy i ładowania naszych skołatanych akumulatorów. Agenda SegFault Unconference? Mówi sama za siebie. Wszystkie szczegółów znajdziecie pod linkiem: https://segfault.events/krakow2019/. Do zobaczenia w Krakowie!

1. Eseje, które stały się podwalinami książki Roberta C. Martina - Clean Code dostępne są na starym blogu Object Mentor http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod (wraz z dyskusją, która czasem bywa ciekawsza od samego artykułu)
2. Prezentacja Kevlina z konferencji NDC 2016 https://vimeo.com/157708450
5. Klasyczne artykuły z historii psychologii: http://psychclassics.yorku.ca/Miller/

Projektując nasz Kod Powszedni

Programistyczna codzienność: kurtka, kapcie, kawa. Po tym porannym rytuale zasiadamy do ulubionego IDE, rozprostowujemy palce i zaczynamy tworzyć kolejne linie kodu. W tyle głowy kołatają nam się dobrze utrwalone zasady: DRY, GRASP oraz SOLID. I właśnie ta ostatnia jest bohaterem moich dzisiejszych rozważań.

SOLID, jakkolwiek nie byłby ten skrót obecnie rozumiany, uznaje się za podstawę nowoczesnego programowania obiektowego - zaraz obok czystego kodu, praktyk TDD i pewnie kilku innych rzeczy. Co mnie jednak niepokoi, to pewien dogmatyzm, przyjmowanie reguł jako prawd objawionych, nie zastanawiając się, co jest pod spodem, z czego zasady wynikają. Co SOLID oznacza, skąd się wziął, czy te zasady są konieczne, czy są skuteczne i dlaczego tak do nich lgniemy?

SOLIDna analiza

Zacznijmy od odrobiny historii. SOLID to skrót powstały na bazie kilku artykułów Roberta C. Martina (Wujka Boba) na początku XXI wieku. Artykuły opisywały podstawowe pięć zasad programowania obiektowego: Single responsibility, Open-Closed, Liskov substitution, Interface segregation oraz Dependency inversion.

Dla tych którzy w tym momencie zetknęli się z SOLID po raz pierwszy - krótkie wprowadzenie. Nazwa SOLID pochodzi od pierwszych liter zasad programowania obiektowego: [1]

  • The Single Responsibility Principle - pojedyncza odpowiedzialność, klasa powinna mieć jeden (i tylko jeden) powód do zmian. Powód oznacza ewolucję lub zmianę pojedynczej koncepcji którą klasa enkapsuluje.

  • The Open Closed Principle - zasada otwartości, o tym, że należy rozszerzać istniejące klasy, a nie je modyfikować.

  • The Liskov Substitution Principle - klasy pochodne muszą móc przezroczyście zastępować klasy bazowe. Co przekładając na prosty język oznacza możliwość włączania w aplikacji różnych zachowań (funkcji), bazując na tym samym typie (bądź interfejsie).

  • The Interface Segregation Principle - można by napisać, że to SRP dla interfejsów. Interfejsy powinny być granularne i dostosowane do potrzeb klienta.

  • The Dependency Inversion Principle - odwrócenie zależności, klasy powinny bazować na abstrakcyjnych zależnościach, a nie na konkretnych implementacjach.

Kevlin Henney przygotował swego czasu doskonałą prezentację, rozbierającą poszczególne zasady SOLID na czynniki pierwsze - i nie sądzę abym mógł dodać do tego czego coś więcej. [2]

Jeżeliby przyjąć podejście Kevlina, to z pięciu zasad, które stoją za skrótem SOLID, zostałyby nam dwie: pojedyncza odpowiedzialności i zasada podstawienia Listkov. Pozostałe są albo archaiczne (zasada otwartości Open-Closed w swoim oryginalnym znaczeniu), bardzo zależne od języka (Interface segregation), albo są powtórzeniem (wstrzykiwanie zależności - Dependency Injection, będące sposobem implementacji zasady pojedynczej odpowiedzialności z wykorzystaniem reguły podstawienia Barbary Listkov).

Możemy próbować kopać głębiej - i to właściwie Kevlin zrobił podczas swojej prezentacji. Można zacząć zastanawiać się, co rozumiemy poprzez pojedynczą odpowiedzialność. W swojej interpretacji Kevlin posługuje się pojęciem spójności (ang. cohesion) w rozumieniu zaproponowanym przez Toma Demarco lub Glenna Vanderburga. [3]

(…​) cohesion is a measure of the strength of association of elements inside a module.
— Tom DeMarco
Structured Analysis and System Specification

Ale możemy też porzucić dalszą techniczną analizę, czym są zasady SOLID, i spróbować przyjrzeć się S z SOLID z nieco innej perspektywy.

Podstawy projektowania dla opornych

W momencie, gdy techniczne aspekty zasad wchodzących w skład SOLID są tak drobiazgowo opisane w literaturze, nie zostawia to wiele przestrzeni na „kolejny artykuł o zasadach czystego kodu”. Jaką zasadność miałyby kolejne odwołania do tych wspaniałych prezentacji i artykułów? Spróbujemy zatem innego podejścia i polejemy zasady zasady czystego kodu sosem kognitywistyki i psychologii. Zastanówmy się, jak to jest, że czysty, SOLIDny kod, jest łatwiejszy do utrzymania i modyfikacji, pracuje się z nim przyjemnie?

Codzienna praca programisty, sposób pisania kolejnych wersów kodu, wynika z wielu przyswojonych przez lata zasad. Mimo że przez wielu noszone głęboko w sercu, rzadko kiedy są przedmiotem głębszych analiz, próby zrozumienia. A jeszcze rzadziej podejmowany jest temat, co jest w tych zasadach takiego pociągającego, co sprawia, że z czystym kodem pracuje się tak po prostu lepiej.

Aby to pojąć, musimy wypłynąć na niezbadane wody psychologii behawioralnej i nauk kognitywistycznych. Jak szalenie by to nie brzmiało, wydaje się to być kluczowe dla zrozumienia znaczenia poszczególnych zasad SOLID. Jest kilka kluczowych pojęć, książek, prac naukowych, które stanowią podstawę dla pracy wielu projektantów. Spróbujmy zobaczyć, czy te same zasady znajdują zastosowanie w codziennej pracy programisty - bądź co bądź - także mocno kreatywnej. W dalszej części artykułu przyjrzymy się kilku takim pojęciom związanym z projektowaniem fizycznych przedmiotów, w jaki sposób projekt wpływa na późniejsze postrzeganie i wykorzystanie przedmiotu.

Świat fizyczny

(…​) afordancje to postrzegane i rzeczywiste właściwości rzeczy, które określają, w jaki sposób można tej rzeczy użyć.
— Donald A. Norman
The Design of Everyday Things

Pojęcie "afordancji" zostało zaproponowane przez Donalda Normana na początku lat dziewięćdziesiątych, w książce The Design of Everyday Things.[4] Pewnie niejedna osoba zastanawia się teraz, jaki ma to związek z programowaniem. Posłużę się przykładem. Jeżeliby spojrzeć i porównać dwie przykładowe klasy: Convoluted.java oraz Structured.java - mam nadzieję, że różnica jest naturalna i oczywista. Odpowiedzialność obu klas jest zbliżona - obie starają się nałożyć pewną warstwę abstrakcji na kilka operacji SQL (i robią to pewnie w nie najlepszy sposób). Ale to nie tzw. cieknąca abstrakcja (ang. leaky abstraction) jest tym, na co chciałbym zwrócić uwagę. Chodzi o czystość intencji która jest zawarta w obu tych klasach.

Pierwsza z przedstawionych klas (Convoluted.java) bezsprzecznie łamie kilka podstawowych zasad programowania obiektowego, jak chociażby pojedynczą odpowiedzialność oraz zamknięcie na modyfikacje. Implementacja nowego wymagania, dodania operacji SQL update, sprowadzałaby się najpewniej do zmiany istniejącej klasy, dodania nowej metody (najprawdopodobniej zgodnie z wytyczoną już w klasie konwencją) i…​ szybkiej ucieczki. Jakość tego kodu pozostawiałaby jednak zdecydowanie zbyt dużo do życzenia. Byłaby to sytuacja daleka od komfortowej, w której nikt nie chciałby się znaleźć.

Drugi przykład, implementacja wzorca projektowego Strategia dookoła zapytań SQL, wzbudza w programiście diametralnie inne reakcje. Jakakolwiek modyfikacja ma bardzo ograniczony zakres i jest bezpiecznym rozszerzeniem istniejącego kodu. Trwoga ustępuje miejsca swobodzie, z jaką poruszamy się po kodzie.

Listing 1. Convoluted.java
public class Sql {
   public Sql(String table, Column[] columns);
   public String create();
   public String insert(Object[] fields);
   public String selectAll();
   public String fieldByKey(String keyColumn, String keyValue);
   private String ColumnList(Column[] columns);
   private String valuesList(Object[] fields, final Column[] columns);
}
Listing 2. Structured.java
abstract public class Sql {
   public Sql(String table, Column[] columns)
   abstract public String generate();
}

public class CreateSql extends Sql {
   public CreateSql(String table, Column[] columns)
   @Override public String generate()
}

public class SelectSql extends Sql {
   public SelectSql(String table, Column[] columns)
   @Override public String generate()
}

public class InsertSql extends Sql {
   public InsertSql(String table, Column[] columns)
   @Override public String generate()
   private String valuesList(Object[] fields, final Column[] columns)
}

public class FindKeyBySql extends Sql {
   public FindKeyBySql(String table, Column[] columns, String keyColumn, String keyValue)
   @Override public String generate()
}

public class ColumnList {
   public ColumnList(Column[] columns)
   public String generate()
}

W praktyce projektowania, afordancje dostarczają szeregu wizualnych podpowiedzi, w jaki sposób używać przedmiotów. Co ciekawe, podobne zależności można zaobserwować w wirtualnym świecie oprogramowania komputerowego - chociażby bazując na wspomnianych już dwóch przykładów kodu. Afordancje umożliwiają użytkownikowi natychmiastowe podjęcie decyzji, jak obchodzić się z przedmiotem, na podstawie np. kształtu; bez komentarzy, bez instrukcji.

Wynika to z praktycznej aplikacji dwóch podstawowych pracy projektowania:

  • dobrego odzwierciedlenia modelu pojęciowego

  • zapewnienia widoczności najistotniejszych elementów

Model pojęciowy to abstrakcyjny opis rzeczywistych obiektów, z reguły odnoszący się do procesów myślowych i towarzyszących im wyobrażeń. Model pojęciowy umożliwia nam przewidzenie skutków naszych czynności (w odniesieniu np. do jakiegoś przedmiotu), zrozumienie tychże skutków i analizowanie ich. To jest model, który budujemy przez lata na bazie naszych doświadczeń, szkoleń, instrukcji - jak np. wzorce projektowe, najlepsze praktyki (rzeczy niesłychanie popularne w naszej branży). Afordancje wspierają nasz model pojęciowy poprzez dostarczenie wizualnych "sygnałów" - w jaki sposób obchodzić się z rzeczami dookoła. To wskaźniki, wizualne podpowiedzi, które mogą być przez nas interpretowane, które uwypuklają najistotniejsze z punktu widzenia użyteczności elementy przedmiotów.

Możemy te same reguły aplikować zarówno do fizycznych przedmiotów, jak i do kodu (szczególnie w mikro skali: pojedyncze bloki kodu, metody, funkcje, klasy). Jeżeli proste rzeczy wymagają opisu, dodatkowej instrukcji, obrazka - projekt nie dał rady. Niezależnie, czy mówimy o kolejnym modelu czajnika, czy o fragmencie kodu źródłowego, z którym przyszło nam pracować. Jedyna różnica, że osobą "poszkodowaną" nie jest klient, użytkownik końcowego produktu, a raczej programista siedzący obok, którego zadaniem jest (dosłowne) użycie kodu, który został przez nas napisany.

A co w momencie, kiedy sprawy stają się z natury rzeczy bardziej skomplikowane? Kiedy domena problemu, z którym pracujemy, sama w sobie jest złożona? Kiedy liczba możliwych przejść, ścieżek wykracza daleko poza pojemność naszej „głowy”?

Spamiętać to wszystko

Okazuje się bowiem że pojemność naszej "głowy" jest ograniczona - do siedmiu (mniej więcej).

Ta liczba nie wzięła się znikąd - pochodzi z bardzo popularnej pracy naukowej George’a Millera z zakresu psychologii The Magical Number Seven, Plus or Minus Two: Some Limits on our Capacity for Processing Information.[5] W swojej pracy George dowodzi, że pamięć krótkotrwała (operacyjna) ma szereg ograniczeń, m.in. w kwestii ilości informacji, którą potrafi jednocześnie przetworzyć. Czy jest to siedem, czy pięć czy jakakolwiek inna liczba - różne badania odmiennie to definiują. Niektórzy uważają, że w ogóle nie powinno się definiować pojemności pamięci w sposób dyskretny. Jeszcze inne eksperymenty dowodzą, że ta pojemność to tylko trzy.

W zasadzie każda z tych teorii jest prawdziwa, bowiem nie chodzi tutaj o liczbę; każdy eksperyment mówi, że nie możemy przeciążać użytkowników (odbiorców) ilością informacji, ponieważ pojemność pamięci jest skończona (i dodatkowo niewielka). Bądźmy życzliwi i ograniczmy ilość wątków, szczegółów, elementów koniecznych do zrozumienia kawałka kodu, nad którym pracujemy. Niezależnie, czy odnosimy się do liczby parametrów wejściowych metody, atrybutów klasy, czy liczby interfejsów, które klasa implementuje: to wszystko jest wstępem (inwestycją) do rozumienia istoty złożoności problemu (np. klasy). Jeżeli ta informacja jest ustrukturyzowana (poprzez rozłożenie na kilka rozdzielnych koncepcji), w małych porcjach (małych encjach) - to jest dokładnie to, o czym pisał Wujek Bob w rozdziale "Funkcje" książki o czystym kodzie.[6]

Wciąż do rozwiązania pozostaje kwestia, co się stanie, gdy liczba elementów, koncepcji potrzebnych do zrozumienia problemy wykracza poza magiczną liczbę, niezależnie czy jest to siedem, pięć czy trzy? To jest właśnie moment, kiedy rozpoczyna się proces uczenia.

Jak działa pamięć?

Posłużmy się kolejną pracą naukową z zakresu psychologii. W latach sześćdziesiątych Richard Atkinson i Richard Shiffrin napisali pracę "Human memory: A proposed system and its control processes". Proponuje ona pewien sposób modelowania ludzkiej pamięci poprzez wydzielenie trzech niezależnych poziomów, na których przechowywane są informacje oraz definiuje sposoby przepływu informacji pomiędzy poziomami.

Dobrą wiadomością jest to, że nie jesteśmy ograniczeni tylko do pojedynczej pamięci o niewielkiej pojemności. Mamy do czynienia z kilkoma poziomami (z kilkoma rejestrami), które mogą być wykorzystywane niezależnie, w reakcji na różne bodźce otoczenia. Tak jak przedstawiono to na ilustracji poniżej.

multistore memory model pl
Figure 1. Wielomagazynowy model pamięci Atkinsona i Shiffrina

Zaproponowany model opisuje ludzką pamięć za pomocą trzech komponentów:

  • pamięć sensoryczna (pamięć zmysłów)

  • pamięć krótkotrwała (operacyjna)

  • pamięć długotrwała

Od momentu, kiedy praca Atkinsona i Shiffrina została opublikowana, pojawiało się wiele wariacji, dyskusji i krytyki zaproponowanego modelu. Niemniej artykuł wywarł bardzo duży wpływ na późniejsze prace i badanie w kwestii ludzkiej pamięci oraz procesu uczenia jako takiego.

Ale wróćmy do programowania: z punktu widzenia pracy programisty najistotniejszy w całym modelu jest proces treningu (nauki i prób). Pamięć sensoryczna (rejestr o bardzo dużej pojemności, ale bardzo krótkotrwały - do pół sekundy) nie ma większego znaczenia dla codziennej pracy kodera. Interesujące rzeczy zaczynają się dziać, gdy bodziec zmysłowy zaczyna być analizowany przez pamięć operacyjną. Według badań obszar ten przechowuje kilka niezależnych elementów (np. siedem, aby nawiązać do wcześniejszej pracy Greorge’a Millera) przez okres około 30 sekund.

Okazuje się, że jest to szalenie istotny fakt z perspektywy pracy programisty, gdzie naturalnym jest proces budowania skomplikowanych abstrakcji podczas pracy z kodem źródłowym. Całość analizy w ogromnej większości przypadków przebiega w naszej głowie bądź przy okazji tworzenia nowego kodu, bądź analizy istniejącego. W obu przypadkach, bardzo mocno angażowana jest pamięć krótkotrwała: krucha, delikatna, której zawartość możemy utracić w ułamku sekundy. Doświadczanie ulotności tego procesu to niestety nasza codzienność, gdzie chwila nieuwagi lub oderwania się, skutecznie burzy model układany w głowie. Jednakże przeciwdziałanie temu potrafi być relatywnie proste: budując małe encje (klasy) jednoznacznie wyrażające swoją intencję (poprzez precyzyjne nazewnictwo), sprawiamy że model mentalny reprezentujący nasz kod pozostaje zwięzły.

W przeciwnym wypadku będziemy zmuszeni do ciągłego powtarzania procesu analizy - do momentu gdy poszczególne elementy trafią z pamięci krótkotrwałej do długoterminowej. Mówiąc wprost - aż się ich nauczymy. Pamięć długotrwała to obszar o nieskończonej pojemności i trwałości. Nie jest to jednak obszar łatwy w zapisie. Zapamiętanie czegokolwiek to dla większości ludzi długotrwały proces z dużym opóźnieniem. Coś jak dyski optyczne: przechowują dużo danych na zawsze, ale proces zapisu jest niewspółmiernie wolny. Jeżeli kod, który tworzymy, jest łatwy do zrozumienia (nie wymaga nauczenia się go aby z niego korzystać), cały twórczy proces ulega znacznemu skróceniu. Jest efektywniejszy.

Kilka wniosków

No więc po co nam to wszystko, kolejny festiwal wiedzy bezużytecznej? W naszej pracy musimy zapamiętywać niesłychaną ilość rzeczy. Klasy, metody, funkcje, parametry - rozrzucone po różnych miejscach systemu. Nauczyliśmy się żyć z tymi wszystkimi złożonościami, ale tak jak pisałem wcześniej - proces uczenia wymaga czasu.

Na szczęście są sposoby aby łatwiej było pracować z kodem, a klucze do nich to zrozumienie, w jaki sposób człowiek przetwarza i interpretuje bodźce napływające z otoczenia. Każda rzecz, z którą pracujemy (czy fizyczna, czy wirtualna), sama z siebie może być instrukcją obsługi. Także kod może zawierać szereg wskazówek, wynikających z umiejętności autora. Umiejętności jasnego i klarownego wyrażania myśli poprzez kod, wykorzystując rzeczy, które ludzie powinni już wiedzieć (np. odpowiednie wzorce projektowe). Niemal każda linia kodu to projektowanie w skali mikro. Projektowanie które albo zdezorientuje współprogramistów, albo uczyni ich pracę łatwiejszą. Zasady wywodzące się z projektowania fizycznych przedmiotów mogą znacząco ułatwić zrozumienie oraz poznać źródło wielu zasad programowania, do których podchodzimy dość dogmatycznie: SOLID, DRY i parę innych. A to rozumienie to duży krok w kierunku stania się lepszym, bardziej empatycznym programistą lub programistką.


1. Eseje, które stały się podwalinami książki Roberta C. Martina - Clean Code dostępne są na starym blogu Object Mentor http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod (wraz z dyskusją, która czasem bywa ciekawsza od samego artykułu)
2. Prezentacja Kevlina z konferencji NDC 2016 https://vimeo.com/157708450
5. Klasyczne artykuły z historii psychologii: http://psychclassics.yorku.ca/Miller/

The Design of Everyday Code

There is something weird about our industry - we like memes; short acronyms that we cannot forget: DRY, GRASP and (the most famous one) - SOLID. The hero of my today’s ramblings.

Solid is considered the foundation of the modern software development practice, next to clean code, test driven development and probably a few others. What bothers me, is that we got a bit dogmatic here, not getting the essence of those principles. What does SOLID really stand for? Why are those practices essential? What’s so appealing about them?

SOLID deconstruction

Let’s start with a bit of history. SOLID was actually coined by Michael Feathers, based on early 2000s work of Robert C. Martin (better known as Uncle Bob) - who came up with a set of programming principles and called them SOLDI. Regardless of naming, it’s all about five distinct rules: Single responsibility, Open-Closed, Liskov substitution, Interface segregation and Dependency inversion.

Just to recap; SOLID acronym comes from first letters of each of the principles, which together constituents the basis of Object Oriented Design. In its original form SOLID stands for [1]:

  • The Single Responsibility Principle - a class should have one, and only one, reason to change. And to be precise, a reason means concept, a class should encapsulate a single concept.

  • The Open Closed Principle - you should be able to extend a classes behavior, without modifying it.

  • The Liskov Substitution Principle - derived classes must be substitutable for their base classes. In plain English - we must be able to plug-in different behaviour based on the same base type.

  • The Interface Segregation Principle - make fine grained interfaces that are client specific. Like a single responsibility, but for interfaces.

  • The Dependency Inversion Principle - depend on abstractions, not on concretions, which refers us back to the substitution principle.

Kevlin Henney did a great job drilling down those principles in his talk "SOLID deconstruction" - so that I don’t think I should elaborate more even touch upon this.[2]

Essentially, with Kevlin’s approach, we are left with the Single responsibility principle (SRP), as all other are either not relevant (Open-closed in it’s original sense), very language dependent (Interface segregation) or repetitive (Dependency injection being a way of implementing SRP with Liskov substitution).

We can obviously go further - and that’s what Kevlin essentially did in his talk. We can ponder, what single exactly stands for in the Single responsibility principle. Kevlin’s interpretation of single was related to cohesion, in the sense of Tom DeMarco’s definition or Glenn Vanderburg’s article on that topic.[3]

(…​) cohesion is a measure of the strength of association of elements inside a module.
— Tom DeMarco
Structured Analysis and System Specification

We can also leave it like this and try to tackle the S in SOLID slightly differently.

Entering the Design 101

With the technical aspects of SOLID being so neatly described and understood, that doesn’t leave much space for 'another clean code article'. Is there any purpose in referencing those great talks and articles (which apparently I already did)? So let me take a different approach and give SOLID, clean code etc a psychological twist. Let’s ponder a bit why that clean, SOLID code, flavored with appropriate test coverage, is easier to work with and easier to maintain, easier to extend. I’ve never seen any doubt about this.

Our work, the code we cut, the quality of software we deliver is driven by various principles we have deep in our heart. But we hardly ever question them or think what is so appealing about these? Where is this traction coming from?

That way we enter the uncharted waters of behavioral psychology and cognitive science. As ridiculous as this may sound, it appears to be critical for understanding the importance of SOLID principles. Some well known papers and researches in this area are the foundation of every designer’s creative work. Let’s see if they overlap with programmers' creative work as well. In the next few paragraphs, we will look at the essence of physical objects design, affordances and signifiers, how they impact our perception, how physical objects design take advantage of the way our brain and memory works.

Design of physical things

(…​) affordance refers to the perceived and actual properties of the thing, primarily those fundamental properties that determine just how the thing could possibly be used.
— Donald A. Norman
The Design of Everyday Things

The term affordance was introduced by Donald Norman in early nineties, in "The Design of Everyday Things".[4] One might wonder how on earth it’s related to programming. Let me run this by example. Have a look and compare these two classes: Convoluted.java class and Structured.java class strategy - I hope the difference is obvious and self explanatory.. Both do pretty much the same - abstract particular SQL operations (probably not in the best possible way). But it’s not the leaky abstraction I’d like to focus on - it’s the intention these two snippets reveal.

The first break multiple principles of OOP, starting with the single responsibility and closeness for modification. A new requirement for SQL update operation will require changing the class, adding new method (probably following the existing coding standards) and… running away. That’s not a situation we would like to find ourselves in.

On the other hand, the latter snippet, the SQL related class strategy, builds up a completely opposite attitude, when it comes to modification of the code. Limited scope of required changes (or safe extension of existing code) makes us feel far less anxious when it comes to this legacy.

Listing 1. Convoluted.java
public class Sql {
   public Sql(String table, Column[] columns);
   public String create();
   public String insert(Object[] fields);
   public String selectAll();
   public String fieldByKey(String keyColumn, String keyValue);
   private String ColumnList(Column[] columns);
   private String valuesList(Object[] fields, final Column[] columns);
}
Listing 2. Structured.java
abstract public class Sql {
   public Sql(String table, Column[] columns)
   abstract public String generate();
}

public class CreateSql extends Sql {
   public CreateSql(String table, Column[] columns)
   @Override public String generate()
}

public class SelectSql extends Sql {
   public SelectSql(String table, Column[] columns)
   @Override public String generate()
}

public class InsertSql extends Sql {
   public InsertSql(String table, Column[] columns)
   @Override public String generate()
   private String valuesList(Object[] fields, final Column[] columns)
}

public class FindKeyBySql extends Sql {
   public FindKeyBySql(String table, Column[] columns, String keyColumn, String keyValue)
   @Override public String generate()
}

public class ColumnList {
   public ColumnList(Column[] columns)
   public String generate()
}

In the design theory, affordances provides strong clues how to operate things, physical things. Surprisingly, when it comes to a virtualized world of computer programs, judging just by those two code listings, it tends to be exactly the same. With affordances taken advantage of, the user knows what to do with a glimpse of an eye: no comments, no instructions are needed.

This is based on two premises, two fundamental principles of Design for Understandability and Usability:

  • provide a good conceptual model,

  • make things visible.

The conceptual model allows us to predict the effects of our actions, to understand and be able to reason about them. It is the model, we’ve built through experience, trainings, instructions - like the design patterns, conventions, best practices (things extremely popular in our industry). Affordances support the conceptual models and through making the clues visible, give users a clear information how to operate everyday things. Affordances act as indication, visual signal that can be meaningfully interpreted and make the relevant parts visible.

We can apply similar rules to both physical objects and code (especially in micro scale - blocks, methods, functions, classes): if simple things needs labels, or instructions, or pictures - the design has failed. The difference is, we are not addressing a user in a sense of consumer of an end product, it is rather the fellow developer who sits next to us and who will literally use the code that has been written.

Problems starts when things get complex, inherently complex by the nature of the problem domain being solved; when a number of available actions exceeds our limited brain capacity. In fact…​ what are those brain limits?

Can’t remember all these

The answer is simple and might surprise you. It is seven (more or less).

This number hasn’t come up out of nowhere - it’s from one of the highly cited papers in psychology: "The Magical Number Seven, Plus or Minus Two: Some Limits on our Capacity for Processing Information", by George Miller.[5] In its essence, the paper states that immediate memory (or working memory - which might be a more natural term) imposes several limitations on the amount of information people are able to receive, process and remember. Whether it’s seven or five, or any other number - it is disputable and research dependent. Some theories are against measuring memory capacity in terms of discrete numbers at all. Others prove that the capacity can be as little as three (three words, three concepts, three numbers).

These are all valid points - but regardless of the exact number - they essentially tell us not to overwhelm users with information. Be kind, limit the numbers of clues, elements required for understanding the code that has just been written. Whether these are method parameters, class attributes, number of interfaces being implemented: these are all prerequisites (an upfront investment) for grasping the essential complexity of the problem. Properly structured information (through separate concepts), in a bite size portions (small entities) - that is exactly what the 'Functions' chapter of the clean code book was about.[6]

But what happens if the number of elements, concepts exceeds the magical number, no matter if it’s seven, five, four or three? That’s where the learning process needs to start.

How memory works?

Let me refer to another psychological paper, this time from from late sixties: "Human memory: A proposed system and its control processes" by Richard Atkinson and Richard Shiffrin. It suggests that memory is made of a series of stores and describes it in terms of the information flow through the system (called after authors, the Atkinson–Shiffrin model).

The good news is that we are no longer limited to a single, limited capacity working memory. There are multiple stores (or registries) which are used in different situations (and under different stimuli) - as depicted below.

multistore memory model

The model describes human memory as three separate components:

  • sensory register

  • short-term store

  • long-term store

Obviously, since the times the research was published, many discussions and criticism have aroused - nevertheless the model had some significant influence on subsequent memory research.

From our programming perspective, the most important bit is the rehearsal process, but we will get to this later. We don’t have much interest in the sensory memory: short duration (less than 1/2 second) but of large capacity. This is not something influencing how programmers work. Interesting things start when sensory information is processed to a short-term storage. This is storage of a limited capacity (seven items, seven chunks of information - which brings us back to the Miller’s work on the working memory) and limited duration (up to 30 seconds - depending on research).

This is becoming crucial in software development, where building complex abstractions is a natural process. The whole thing takes place in our brains during either writing new code or understanding an existing one and heavily use the short-term memory, a very fragile storage, where information can be lost as a matter of distraction or passage of time. The fragility of this process is something we’ve experienced badly with any desk visit, which ruined the whole model we had in mind. Avoidance is pretty straightforward and it’s the practical application of well known principles: build small entities (classes) with meaningful visual clues (good naming), so that the abstraction needed for understanding remains concise.

Otherwise, we will be forced to continuously rehearse the information - which ultimately means remembering things, putting into a long-term memory, learning things. A long-term memory is a store of unlimited capacity and duration, but not obvious to write. It requires significant effort (for many people) - it has a huge write latency (think of optical drives - it always works but the save process takes time). If the code we write is easy to understand and doesn’t require up front learning, the whole development cycle is becoming much faster and requires far less time to understand and becoming effective.

Conclusions

So what is the point of knowing all that? Well, there is an amazing number of things we need to remember on the daily basis. Classes, methods, functions scattered around different packages of our applications. We’ve found a way to handle all those complexities, but as I’ve shown, this learning comes with a price.

Thankfully, there are ways to work easier with code and the key to it is hold by the psychology of human thought and cognition - by how our mind works. There are instructions embedded to things we interact with - both physical as well as virtual (remember the code snippets?). These helpful signifiers are in the code itself and come from authors' abilities to make their intent clear and leverage things people are expected to know (like design patterns). With every line of code, a micro scale design is done. It will either confuse fellow programmers or will make their lives much easier. These physical objects design principles can help us understand the essence of coding rules, which we might have become dogmatic about: SOLID, DRY and many others. And ultimately - it will help us become a better programmer, a more emphatic programmer.

About the author Jakub Marchwicki has been a software engineer for over 10 years, architect for a few, sometimes analyst, manager - when required; passionate about coding, sharing knowledge. JUG leader in Gdańsk, Poland."


1. The original essays from Uncle Bob’s “Clean Code” book are available on the old Object Mentor blog, together with a discussion which reveals some details (from 2005) http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
2. Kevlin’s video from NDC 2016 https://vimeo.com/157708450
5. Classics in the History of Psychology: http://psychclassics.yorku.ca/Miller/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment