[PL] Jak programować aby nie zwariować

148

description

 

Transcript of [PL] Jak programować aby nie zwariować

Page 1: [PL] Jak programować aby nie zwariować
Page 2: [PL] Jak programować aby nie zwariować

Jak programować aby nie zwariować?

Jakub Marchwicki

20.05.2013

Page 3: [PL] Jak programować aby nie zwariować

Na początek

Page 4: [PL] Jak programować aby nie zwariować

?Co to za gość?

Page 5: [PL] Jak programować aby nie zwariować

Co ja tu robię?

Page 6: [PL] Jak programować aby nie zwariować

Co ja tu robię?

Page 7: [PL] Jak programować aby nie zwariować

Co ja tu robię?

Page 8: [PL] Jak programować aby nie zwariować

Co ja tu robię?b

ól

Page 9: [PL] Jak programować aby nie zwariować

Co ja tu robię?b

ól

liczba slajdów

Page 10: [PL] Jak programować aby nie zwariować

Co ja tu robię?b

ól

liczba slajdów

OK

Ryzykoutraty

zdrowia

35 slajdów

Page 11: [PL] Jak programować aby nie zwariować

Co ja tu robię?b

ól

liczba slajdów

OK

Ryzykoutraty

zdrowia

148 slajdów

Page 12: [PL] Jak programować aby nie zwariować
Page 13: [PL] Jak programować aby nie zwariować

Punkt wyjścia

Page 14: [PL] Jak programować aby nie zwariować

Kiedy software jest dobry?Oprogramowanie musi działać

Musi być na czas

Musi być rozbudowywalne

Modyfikowalne

Musi mieć odpowiednią jakość

Punkt wyjścia

Page 15: [PL] Jak programować aby nie zwariować

Punkt wyjścia

Page 16: [PL] Jak programować aby nie zwariować

I po co to wszystko?

Bo software to nasze hobby

Page 17: [PL] Jak programować aby nie zwariować

I po co to wszystko?

Bo software to nasze hobby

fach

Page 18: [PL] Jak programować aby nie zwariować

I po co to wszystko?

Bo software to nasze hobby

fach

zawód

Page 19: [PL] Jak programować aby nie zwariować

I po co to wszystko?

Bo software to nasze hobby

fach

zawód

przyjemność

Page 20: [PL] Jak programować aby nie zwariować

I po co to wszystko?

bo płacą nam za pisanie dokumentacji

pisanie kodu to przyjemność

Page 21: [PL] Jak programować aby nie zwariować

Więc czym jest jakość

Dla kogoś Dla siebie

Dla kogo pracuje?

Page 22: [PL] Jak programować aby nie zwariować

Więc czym jest jakość

Dla kogoś Dla siebie

Dla kogo pracuje?

Page 23: [PL] Jak programować aby nie zwariować

„Jakość (jak piękno) jest sądem wartościującym, wyrażonym przez użytkownika. Jeśli nie ma takiego

użytkownika – nie ma takiego sądu”Platon

Więc czym jest jakość

Page 24: [PL] Jak programować aby nie zwariować
Page 25: [PL] Jak programować aby nie zwariować
Page 26: [PL] Jak programować aby nie zwariować
Page 27: [PL] Jak programować aby nie zwariować
Page 28: [PL] Jak programować aby nie zwariować
Page 29: [PL] Jak programować aby nie zwariować
Page 30: [PL] Jak programować aby nie zwariować
Page 31: [PL] Jak programować aby nie zwariować

Więc czym jest jakość

Dla kogoś Dla siebie

Dla kogo pracuje?

Page 32: [PL] Jak programować aby nie zwariować

„Jakość to sposób myślenia, który powoduje, że stosuje się i

bez przerwy poszukuje najlepszych rozwiązań”

William Edwards Deming

Więc czym jest jakość

Page 33: [PL] Jak programować aby nie zwariować

Jak programować aby nie zwariować?

Page 34: [PL] Jak programować aby nie zwariować

Czysty kod

Page 35: [PL] Jak programować aby nie zwariować

Czysty kod

Page 36: [PL] Jak programować aby nie zwariować

• Uczymy się… bez wnikania w kontekst Nazywaj zmienne w taki a taki sposób Stosuj komentarze w takich a nie innych

przypadkach Dziel funkcje na części zgodnie z takimi

a takimi zasadami• Z czasem zobaczymy że z czystym

kodem lepiej się pracuje… tak po ludzku

Page 37: [PL] Jak programować aby nie zwariować

Nasz mózg lepiej reaguje na czysty kod• Utrzymujemy koncentrację• Nie gubimy wątków, swobodniej

podążamy tokiem myślenia • Cognitive load – możemy pomieścić

poszczególne kawałki kodu w głowie więc potrafimy się miedzy nimi swobodnie przemieszczać

Page 38: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 39: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 40: [PL] Jak programować aby nie zwariować

int d1; //dni od rozpoczęciaint d2; //dni do zakończeniaint d3; //dni wolnych

int daysSinceStart;int daysTillEnd;int daysOf;

public List<int[]> getThem() { List<int[]> list1 = new ArrayList<int[]>(); for (int[] x : theList) if (x[0] == 4) list1.add(x); return list1;}

public List<int[]> getDates() { List<int[]> dateList = new ArrayList<int[]>(); for (int[] week : theWeeksArray) if (week[0] == BEGIN_DATE) dateList.add(x); return dateList;}

Page 41: [PL] Jak programować aby nie zwariować

for (int i=0; i<10; i++){ k += ((l[i]*1.5) / 3 ); }

float milleageRate;const int NUMER_OF_EMPLOYEE = 3;float sum = 0;for ( int i=0; i<numberOfTrips; i++ ){ float totalCompensation = tripLength[i] * milleageRate; float deduction = totalCompensation / NUMER_OF_EMPLOYEE; sum += deduction;}

Page 42: [PL] Jak programować aby nie zwariować

public class CsmrDt { public void crtshpcrt() {/*...*/}; public void remcrt() {/*...*/}; private final int ssntm = 10; /*...*/}

public class CustomerDataset { public void createShoppingCart() {/*...*/}; public void removeCart() {/*...*/}; private final int sessionTimeout = 10; /*...*/}

Page 43: [PL] Jak programować aby nie zwariować

• Nazwy powinny sugerować co zwracają

• Nazwy muszą mówić o całym zakresie funkcjonalności

Metody

String findLastNameOfCustomerWithId(long customerId){...}

Map<Long, Customer> customers;

Customer getCustomer(Long id){Customer customer = customers.get(id);if(customer == null){

customer = createNewCustomer(id);}return customer;

}

Page 44: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 45: [PL] Jak programować aby nie zwariować

• Zasada pierwsza: funkcje powinny być małe

• Zasada druga:funkcje powinny być jeszcze mniejsze

Funkcje

Page 46: [PL] Jak programować aby nie zwariować

Functions should do one thing.Should do it wellShould do it only!

Funkcje

Page 47: [PL] Jak programować aby nie zwariować
Page 48: [PL] Jak programować aby nie zwariować
Page 49: [PL] Jak programować aby nie zwariować

writeField(outputStream, name);

outputStream.writeField(name);

createSquare(width, height);

calculateRectangularPrismVolume(double height, double width, double depth);

calculateRectangularPrismVolume(Area rectangle, double depth);

Page 50: [PL] Jak programować aby nie zwariować

Funkcje

DRY – Don’t repeat yourself• Duplikacja zmniejsza czytelność• Zwiększa koszty utrzymania, refactoringu i

poprawiania błędów• Prowadzi do rozbieżności funkcjonalnej

modułów wykonujących to samo• Zmniejsza reusability kodu

Page 51: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 52: [PL] Jak programować aby nie zwariować

Komentarze

DON’T

Page 53: [PL] Jak programować aby nie zwariować

Komentarze

„Nie komentuj złego kodu – popraw go”

Brian W. Kernighan i P.J. Plaugher

Page 54: [PL] Jak programować aby nie zwariować

//Sprawdzenie czy klient ma możliwość korzystania ze zniżkiif (customer.isStudent() || (customer.age < 18) || (customer.age > 65))

if (customer.isEligibleForDiscount())

Page 55: [PL] Jak programować aby nie zwariować

„If you decide to write a comment, then spend the time necessary to make sure it

is the best comment you can write”

Robert C. Martin

Komentarze

Page 56: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 57: [PL] Jak programować aby nie zwariować

Formatowanie

• Konwencje formatowania w zespole. Ustal i się ich trzymaj

Page 58: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 59: [PL] Jak programować aby nie zwariować

• Prawo Demeter – zasada minimalnej wiedzy

• Moduł powinien nie wiedzieć nic o wnętrzu obiektów, którymi manipuluje

Prawo Demeter

Page 60: [PL] Jak programować aby nie zwariować

• Prawo Demeter głosi, że metoda f klasy C powinna wywoływać tylko metody z:• Klasy C,• Obiektu utworzonego przez f,• Obiektu przekazanego jako argument f,• Obiektu umieszczonego w zmiennej

instancyjnej klasy C.

Prawo Demeter

Page 61: [PL] Jak programować aby nie zwariować

• Możesz bawić się ze sobą• Możesz bawić się własnymi

zabawkami (ale nie możesz ich rozbierać)

• Możesz bawić się zabawkami które dostałeś

• Możesz bawić się zabawkami które zrobiłeś samodzielnie

Prawo Demeter

Page 62: [PL] Jak programować aby nie zwariować

final String outputDir = context.getOptions().getScratchDir().getAbsolutePath();

Options options = context.getOptions();File scratchDir = options.getScratchDir();final String outputDir = scratchDir.getAbsolutePath();

Page 63: [PL] Jak programować aby nie zwariować

• Nazwy• Funkcje• Komentarz• Formatowanie kodu• Obiekty i struktury danych• Obsługa błędów

Poziom I

Page 64: [PL] Jak programować aby nie zwariować

Zwracanie null

DON’T

Page 65: [PL] Jak programować aby nie zwariować

• Wykorzystuj zgłaszanie wyjątków lub zwracanie obiektu specjalnego przypadku

Zwracanie null

List<Item> items = getItems();if (items != null) { for (Item i : items) { totalCost += i.getCost(); }}

List<Item> items = getItems();for(Item i : items) { totalCost += i.getCost();}

Page 66: [PL] Jak programować aby nie zwariować

public interface Animal { public void makeSound();}

public class Dog implements Animal { public void makeSound() { System.out.println("woof!"); }}

public class NullAnimal implements Animal { public void makeSound() { }}

Page 67: [PL] Jak programować aby nie zwariować

• Przekazywanie null jest gorsze od jego zwracania

Przekazywanie null

public class MetricsCalculator { public double rectanglePerimeterCalculate( double x, double y) { return 2 * (y + x); } /* ... */}

Page 68: [PL] Jak programować aby nie zwariować

• Defensive programming

Przekazywanie null

public class MetricsCalculator { public double rectanglePerimeterCalculate(

double x, double y) { if (x == null || y == null) { throw InvalidArgumentException("Niewłaściwy

argument."); } return 2 * (y + x); }} public class MetricsCalculator {

public double rectanglePerimeterCalculate(double x, double y) {

assert x != null : "x nie może być null"; assert y != null : "y nie może być null"; return 2 * (y + x); }}

Page 69: [PL] Jak programować aby nie zwariować

Miara czystego kodu

Page 70: [PL] Jak programować aby nie zwariować

Czysty projekt

Page 71: [PL] Jak programować aby nie zwariować

Poziom II - SOLIDny programista

• The Single Responsibility Principle – klasa powinna mieć tylko jeden powód do zmiany

• The Open Closed Principle – klasę można łatwo rozszerzać, nie modyfikując jej

• The Liskov Substitution Principle – klasy pochodne muszą być przeźroczystymi zamiennikami klasy nadrzędnej

Page 72: [PL] Jak programować aby nie zwariować

• The Interface Segregation Principle – dla różnych klientów twórz osobne interfejsy

• The Dependency Inversion Principle – bądź zależny od abstrakcji a nie od konkretnych implementacji

SOLIDny programista

Page 73: [PL] Jak programować aby nie zwariować

To znaczy jaki?

Kod obiektowy

Page 74: [PL] Jak programować aby nie zwariować

• Odpowiedzialność – tylko jedna obiekty maja własną osobowość,

unikaj schizofrenicznych obiektów• Enkapsulacja – to co się zmienia jest

hermetyzowane• Preferencja kompozycji ponad

dziedziczenie

Page 75: [PL] Jak programować aby nie zwariować

• Dokładanie ponad modyfikacje– Gdy dodajemy nową funkcjonalność raczej dokładamy

nowe byty niż modyfikujemy istniejące.• Lokalne zmiany

– Zmiana ma konsekwencje lokalne, a nie globalne. Zasięg rażenia zmian jest jak najmniejszy.

• Nieinwazyjność zmian– Dodanie nowych rzeczy (odpowiedzialności,

funkcjonalności, zachowań) do istniejących bytów jest przezroczyste ich dla klientów.

Page 76: [PL] Jak programować aby nie zwariować

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)}

Page 77: [PL] Jak programować aby nie zwariować

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()}

Page 78: [PL] Jak programować aby nie zwariować

Odpowiedzialność. Enkapsulacja. Kompozycja

• To co leży u podstaw możemy stosować na każdym poziomie

• Projektując klasy• Projektując moduły, komponenty• Projektując systemy

Page 79: [PL] Jak programować aby nie zwariować

• Każdy wzorzec opisujemy w trzech kontekstach: Odpowiedzialność Enkapsulacja / hermetyzacja Kompozycja

Wzorce projektowe

Page 80: [PL] Jak programować aby nie zwariować

• Rozdziela i przesłania implementację zachowań, czynności.

• Hermetyzujemy zmienny sposób tworzenia obiektów

• Nowe funkcjonalności uzyskujemy poprzez dodawanie komend, poprzez kompozycję.

Wzorce projektowe: komenda

Page 81: [PL] Jak programować aby nie zwariować

Wzorce projektowe: komenda

Page 82: [PL] Jak programować aby nie zwariować

• Każdy element systemu: Ma swoją odpowiedzialność Hermetyzuje pewne zachowania Składa się z kilku współpracujących

elementów

Moduły

Page 83: [PL] Jak programować aby nie zwariować

• Każdy framework opisujemy w trzech zdaniach: Odpowiedzialność Enkapsulacja Preferowanie kompozycji

Frameworki

Page 84: [PL] Jak programować aby nie zwariować

• Spring czy EJB

Nie ma idealnych rozwiązań

Page 85: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP

Nie ma idealnych rozwiązań

Page 86: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity

Nie ma idealnych rozwiązań

Page 87: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis

Nie ma idealnych rozwiązań

Page 88: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis czy JDBC

Nie ma idealnych rozwiązań

Page 89: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis czy JDBC• MVC czy MVP

Nie ma idealnych rozwiązań

Page 90: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis czy JDBC• MVC czy MVP• Java czy .NET

Nie ma idealnych rozwiązań

Page 91: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis czy JDBC• MVC czy MVP• Java czy .NET czy Python

Nie ma idealnych rozwiązań

Page 92: [PL] Jak programować aby nie zwariować

• Spring czy EJB• JSF czy JSP czy Velocity• JPA czy iBatis czy JDBC• MVC czy MVP• Java czy .NET czy Python czy PHP

Nie ma idealnych rozwiązań

Page 93: [PL] Jak programować aby nie zwariować

Nie ma idealnych złoytych środków

Page 94: [PL] Jak programować aby nie zwariować

SOLIDny programista

Page 95: [PL] Jak programować aby nie zwariować
Page 96: [PL] Jak programować aby nie zwariować

• Kod jest podstawowym medium komunikacji w projekcie

• Kod to miejsce w którym spędzamy najwięcej czasu

• Zły kod to jak solenie herbaty koledze z zespołu albo plucie do kanapki – a przecież nie jesteśmy złośliwi

Wartości

Page 97: [PL] Jak programować aby nie zwariować

• Jako zespół jesteśmy jednością– Jak ja pójdę na skróty, to kolega

będzie się męczył– I jako całość i tak będziemy

nieefektywni

Wartości

Page 98: [PL] Jak programować aby nie zwariować
Page 99: [PL] Jak programować aby nie zwariować

• Programy są częściej czytane niż pisane

• Więcej czasu poświęcamy na modyfikację istniejącego kodu niż na tworzenie nowego

Implementation Patterns

Page 100: [PL] Jak programować aby nie zwariować
Page 101: [PL] Jak programować aby nie zwariować

• Komunikacja – kod źródłowy powinno się czytać jak książkę

• Prostota – wprowadzaj złożoność tylko wtedy, kiedy jest to konieczne

• Elastyczność – elastyczność to dodatkowa złożoność, więc wprowadzaj ją tylko tam gdzie to konieczne

Implementation patterns

Page 102: [PL] Jak programować aby nie zwariować

• Lokalne konsekwencje – zmiana w jednym miejscu nie powoduje zmian w innych

• Minimalne powtórzenia – DRY

Implementation patterns

Page 103: [PL] Jak programować aby nie zwariować

• Dane i logika razem – ponieważ dane i logika z reguły zmieniają się w tym samym czasie

• Symetria – utrzymuj podobny poziom abstrakcji w obrębie metody / klasy

Implementation patterns

Page 104: [PL] Jak programować aby nie zwariować

„Czysty kod jest prosty i bezpośredni. Czysty kod czyta się jak dobrze

napisaną prozę. Czysty kod nigdy nie zaciemnia zamiarów projektanta; jest pełen trafnych abstrakcji i prostych

ścieżek sterowania.” Grady Booch – to jeden z tych panów od UMLa

Page 105: [PL] Jak programować aby nie zwariować

Po co to wszystko?

Page 106: [PL] Jak programować aby nie zwariować

Complexity and confusion

Page 107: [PL] Jak programować aby nie zwariować

Complexity and confusion

Page 108: [PL] Jak programować aby nie zwariować

Affordance

Page 109: [PL] Jak programować aby nie zwariować

Affordance

a quality of an object, which allows an individual to perform an action. For example, a knob

affords twisting, and perhaps pushing, while a cord affords

pulling

Page 110: [PL] Jak programować aby nie zwariować

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)}

Page 111: [PL] Jak programować aby nie zwariować

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()}

Page 112: [PL] Jak programować aby nie zwariować

Po co to wszystko?

Page 113: [PL] Jak programować aby nie zwariować

Budujemy nawyki

Page 114: [PL] Jak programować aby nie zwariować

By żyło się lepiej

Page 115: [PL] Jak programować aby nie zwariować

Szukamy doświadczenia, pytamy

Page 116: [PL] Jak programować aby nie zwariować

Samodzielnie poszukujemy drogi

Page 117: [PL] Jak programować aby nie zwariować

Stosujemy framework na siłę

Page 118: [PL] Jak programować aby nie zwariować

Porzucamy framework i robimy po staremu

Page 119: [PL] Jak programować aby nie zwariować

By w sytuacjach trudnych…

Page 120: [PL] Jak programować aby nie zwariować

… brniemy nie wiadomo gdzie …

Page 121: [PL] Jak programować aby nie zwariować

… mimo że cel był wyraźny

Page 122: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 123: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 124: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 125: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 126: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 127: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

Page 128: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

Page 129: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

Page 130: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

Page 131: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

Page 132: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

2009

Page 133: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

2009

Page 134: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

2009

2008

Page 135: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

2009

2008

Page 136: [PL] Jak programować aby nie zwariować

Jak żyć?

2010

2009

2009

2008

2007

Page 137: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 138: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

Page 139: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

Page 140: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

1996 / 2000

Page 141: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

1996 / 2000

Page 142: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

1996 / 2000

2002

Page 143: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

1996 / 2000

2002

Page 144: [PL] Jak programować aby nie zwariować

Jak żyć?

2002

1996 / 2000

2002

1994

Page 145: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 146: [PL] Jak programować aby nie zwariować

Jak żyć?

Page 147: [PL] Jak programować aby nie zwariować

Shu-Ha-Ri

Page 148: [PL] Jak programować aby nie zwariować

[email protected]: @kubem

Stoisko 25

Shu-Ha-Ri