Co oznacza to stwierdzenie, że C # i Java są połową języka? [Zamknięte]


32

W artykule: Dlaczego POCO znajduje się zdanie:

Maciej Sobczak dobrze to ujmuje: „Po prostu nie lubię, kiedy ktoś daje mi połowę języka i mówi, że to dla mojej własnej ochrony”.

Nie rozumiem, o co mu chodzi, mimo że C # jest własnością firmy Microsoft i Java jest własnością Oracle , to nie znaczy, że posiadają pół języka, prawda? Nie znalazłem żadnych dowodów na potwierdzenie tego zdania i jestem bardzo ciekawy tego. I jeszcze bardziej ciekawy części „dla mojej własnej ochrony”.


12
Zinterpretowałem to jako krytykę pozwalającą programiście robić takie rzeczy, jak swobodne przydzielanie i zwalnianie pamięci oraz tego rodzaju „ochrona”, ale nie jestem pewien, czy o to właśnie starał się powiedzieć.
Kayaman

15
Nie jestem do końca pewien, ponieważ artykuł, który cytuje, wydaje się martwy, ale wygląda na to, że mówi, że Java i C # nie mają wielu bardziej „niebezpiecznych” lub kontrowersyjnych funkcji C ++, takich jak wielokrotne dziedziczenie lub metaprogramowanie szablonów.
GoatInTheMachine

3
Brak kontekstu cytatu (link to 404), więc jedyne, co tu dostaniesz, to ludzie zgadywający, co prawdopodobnie miał na myśli, lub (bardziej prawdopodobne) ludzie, którzy przedstawiają własne zdanie. Jeśli naprawdę chcesz poznać kontekst, tj. To, co znajduje się na zagubionej stronie, najlepszym rozwiązaniem jest prawdopodobnie napisanie autora bezpośrednio, a może próba odnalezienia zagubionej strony za pomocą maszyny wyjściowej lub podobnego.
JacquesB

2
W stwierdzeniu nie ma sensu, że nawet jeśli sobie z tym poradzisz, nie zawsze chcesz ujawniać każdy możliwy aspekt rozwoju oprogramowania w języku. Na pewno nie masz problemu z odczytaniem kodu zarządzania pamięcią, ale inni programiści mogą nie być bardzo podekscytowani utrzymaniem tego kodu. Jest podobny do koncepcji enkapsulacji. Również C # pozwala na dostęp do całkiem sporo rzeczy, dyrektyw kompilatora, specjalnych atrybutów i refleksji, ale nie należy tego używać.
Mark Rogers

32
Dla własnego dobra nie zwracaj uwagi na ludzi, którzy uważają, że język musi mieć wszystkie funkcje C ++, aby można go było uznać za „prawdziwy” i „kompletny”. Nie zwracaj uwagi na ludzi, którzy uważają, że bezpieczeństwo typu, bezpieczeństwo pamięci i dobrze określone zachowanie to „koła treningowe”. Poprawność staje się najważniejszym aspektem oprogramowania w większości branż, a ludzie, którzy są dumni z tego, że się nim nie przejmują, wkrótce przestaną mieć znaczenie.
Theodoros Chatzigiannakis

Odpowiedzi:


162

Sobczak nie mówi o własności korporacyjnej. „Pół” język, którego brakuje, to wszystkie rzeczy, których nie można zrobić w wielu współczesnych językach, chociaż jako dobrze wykształcony ekspert komputerowy wie, że można je uczynić: dziedziczyć po tylu klasach, ile chcesz. Przypisz dowolny obiekt do dowolnego innego bez ograniczeń typu. Kontroluj alokację i zwalnianie zasobów ręcznie zamiast ufać kompilatorowi i czasowi działania, aby zrobić to za niego.

Chodzi o to, że wszystkie te ograniczenia zostały wprowadzone z jakiegoś powodu w językach programowania. My nie mają języków, które pozwoliły tym wszystkim. Z czasem okazało się, że przeciętnemu programatorowi lepiej jest z pewnymi ograniczeniami i trzymaniem za rękę, ponieważ potencjał popełniania naprawdę złych błędów jest po prostu zbyt wielki, aby wart był dodatkowej mocy i ekspresji.

(Oczywiście czasami denerwuje to programistów, którzy tak naprawdę nie potrzebowali tyle trzymania się za ręce. Ich skargi są czasem uzasadnione. Ale ludzie notorycznie źle oceniają swoje umiejętności, a wielu, którzy uważają, że nie potrzebują zabezpieczeń, tak naprawdę bardzo ich potrzebują. Nie zawsze łatwo jest odróżnić rzeczywistych wyższych intelektów, którzy czują się powstrzymywani przez ograniczenia w językach wysokiego poziomu od przeciętnych programistów, którzy myślą, że narzekanie sprawi, że będą wyglądać lepiej lub nie wiedzą lepiej.)


67
To jest mój goto odpowiedź.
Neil

71
Dodałbym również, że nie ma czegoś takiego jak wyższe intelekty, które nie potrzebują ograniczeń. Zawsze można bezpiecznie założyć, że prędzej czy później wszyscy się zepsują. I zwykle im wyższy intelekt, tym większy błąd.
Neil,

29
Java i C # to coś więcej niż tylko zapobieganie strzelaniu sobie w stopę. Na przykład zarządzanie pamięcią wymagało dużo czasu i wysiłku programisty, zanim pojawiło się wyrzucanie elementów bezużytecznych, i trudno jest właściwie ręcznie zarządzać pamięcią. Odśmiecanie poprawia wydajność programisty.
Robert Harvey,

12
@RobertHarvey I 100% zgadzam się. Jako długoletni programista w C ++ byłem sceptyczny wobec automatycznego zarządzania pamięcią przy przejściu do C #. Kiedy to przeszedłem, było niesamowicie wyzwalające, że nie musiałem się tym przejmować przez 99% czasu. Dzięki temu mogłem myśleć o innych problemach.
17 z 26

8
„Przypisz dowolny obiekt do dowolnego innego bez ograniczeń typu.” ... Więc dynamic?
Arturo Torres Sánchez

34

Całkiem ładnie to wyjaśniono w oryginalnym źródle cytatu :

Postanowiłem dowiedzieć się więcej o C ++ i stałem się jego wiernym pasjonatem - obejmuje to także moje zainteresowanie ewolucją tego języka. Ponadto zauważyłem, że do opracowania użytecznych bibliotek potrzebne są najbardziej zaawansowane i najnowocześniejsze techniki , a nie rzeczywiste aplikacje. Mając to na uwadze, próbowałem napisać kilka własnych bibliotek do różnych celów (patrz moja strona pobierania), a także staram się przejrzeć ramiona programistów C ++ Boost (patrz moja strona z linkami), aby dowiedzieć się, co to jest zaawansowane techniki są. Poświęcenie czasu na tworzenie bibliotek, które powinny być ogólne i przydatne jednocześnie, jest naprawdę wymagające. Dlatego programiści nigdy nie przestają się uczyć.

[…]

Wciąż gram z C ++ i technikami pisania solidnego oprogramowania. Aby zyskać szerszą perspektywę w dziedzinie niezawodnego oprogramowania, postanowiłem poświęcić trochę czasu na naukę Ada (i pokrewnych rzeczy), który jest językiem, który wydaje się całkowicie opuszczony przez biznes, mimo że to Ada naprawdę została zaprojektowana z myślą o złożonym i niezawodnym systemy. Muszę przyznać, że nauka Ady była dla mnie naprawdę korzystna w tym sensie, że pozwoliła mi bardziej świeżo spojrzeć na moje podejście do pracy i rozwoju. Co najważniejsze, niektóre pomysły ze świata Ada można mniej więcej bezpośrednio zastosować do C ++ z dobrymi wynikami w zakresie niezawodności i poprawności.

[…]

OK, zapomniałem. Pewnego dnia przysiągłem, że nie będę uczył się Java. Ale to zrobiłem. Cóż, w zakresie, który pozwala mi czytać i pisać działający kod. Przeczytałem „Myślenie w Javie” (dostępne on-line, za darmo) i „Core Java” (nie online, nie za darmo), byłem również pośrednio zaangażowany w rozwój Java i ... Cóż, nie kupuję to. Po prostu nie lubię, gdy ktoś podaje mi połowę języka i mówi, że to dla mojej własnej ochrony. Jest jak papierowy młotek, wykonany tak, aby nikt nie zranił się po uderzeniu w palec ... To samo dotyczy C #. Wybieram stalowy młot, aby mieć pewność, że gdy będę chciał grać w macho, wytrzyma.
Pytanie brzmi - dlaczego korzysta z niego tak wiele osób (Java, C # itp.)? Hmmm ... Może dlatego, że w niektórych miejscach jest bardzo dobra. Są jednak sytuacje, w których zarówno język, jak i biblioteka pokazują, że zostały zaprojektowane raczej dla apletów (początkowo) niż po to, by stały się narzędziami do wszystkiego. Po prostu obiecuje za dużo i daje za mało jak na technologię catch-all. Lub jako rozwiązanie, które może przeorywać konkurencję.

Lubię C ++, gdy potrzebna jest maksymalna moc i najszersza perspektywa. W miejscach, w których ekspresja C ++ nie jest koniecznością, języki takie jak Tcl lub Python wydają się pasować. Są nie tylko otwarci pod względem ewolucji, ale można je rozszerzać i osadzać, w zależności od konkretnych potrzeb. Widzę wiele możliwości marzeń w tych technologiach. Zwykle rezygnuję z języka C jako języka do regularnego programowania - wydaje się to rozsądnym wyborem tylko jako cel do generowania kodu, w przeciwnym razie jest zbyt podatny na błędy. Dzisiaj Ada jest moim drugim wyborem dla poważniejszych projektów, pod warunkiem, że mam wolny wybór (co niestety nie jest tak przez większość czasu).

Innymi słowy, autor tego cytatu lubi C ++, a on nie lubi Javy i uważa, że ​​Java nie ma połowy C ++. I to wszystko, co zawiera ten cytat.


18
Jak na ironię, nie lubi C z tego samego powodu, dla którego lubi C ++, jest bardzo otwarty, pozwalając na dużą moc i wiele błędów.
GreySage,

8
Uważa, że ​​C ++ jest bardziej ekspresyjny niż Python
benxyzzy

12
@GreySage To też przykuło moją uwagę ... C jest zbyt podatny na błędy, ale C # nie daje wystarczającej mocy? C czy to jest tak dalekie od C ++? C # nie ma „niebezpiecznych” narożników, które dają ci większą kontrolę? Ciekawa mieszanka opinii, to na pewno ...
WernerCD,

10
@WernerCD tak naprawdę nie może powiedzieć o niebezpiecznym języku C #, ale C i C ++ nie mają ze sobą prawie nic wspólnego, z wyjątkiem tego, że można rozbić podstawowy fragment kodu C90 na poprawny fragment kodu C ++, na którym kompilator się nie udusi.
Quentin,

23

Artykuł, do którego link znajduje się na opublikowanym blogu, został usunięty, więc trudno być tego pewnym, ale jak mówi Kilian, prawdopodobne jest, że kiedy mówi „połowa języka”, oznacza to, że C # i Java czują się jak C ++, ale z dużą ilością funkcje i konstrukcje zostały usunięte, aby były łatwiejsze w użyciu lub bezpieczniejsze.

W 2006 roku, kiedy to napisano, gdy C # był stosunkowo młody, a Java była pod wieloma względami niedojrzała, a kiedy władza kontra bezpieczeństwo wydawały się kompromisem, w którym można wybrać tylko jedną, nie było to całkowicie nieracjonalne zajęcie .

W dzisiejszych czasach ta pozycja wcale nie jest rozsądna. Samo myślenie o językach głównego nurtu, C # i Java ogromnie dojrzewały, zapożyczając funkcje z innych języków (szczególnie funkcjonalnych) w celu promowania pisania bezpiecznego kodu. Mamy również języki takie jak Rust i Swift, które są zbudowane od podstaw, aby to zrobić.

Jeśli ktoś patrzy na język z góry, ponieważ trzyma go za rękę, lub mówi, że trudny w użyciu język jest w jakiś sposób dobrą rzeczą, wziąłbym wszystko, co powiedział, z odrobiną soli. Wystarczy spojrzeć na zawstydzającą liczbę błędów w kodzie, na których polegamy każdego dnia, napisanych przez najbystrzejsze umysły w branży, których można by trywialnie uniknąć za pomocą „bezpiecznych” języków, aby zobaczyć dlaczego.


6
Zgadzam się z twoim stanowiskiem z ostatniego akapitu. C ++ należy nazwać „Fountain of Exploits”.
Caleb Mauer

3
Również w celu uzupełnienia drugiego akapitu, zarówno Java, jak i C # mocno skrybowały składnię C i C ++ z różnych powodów, w tym zachęcając istniejących programistów C / C ++ obietnicą niższej krzywej uczenia się. Po osiągnięciu dojrzałości dodali własne funkcje i swój własny smak, ale na początku łatwiej było je postrzegać jako „C ++, ale mniej wydajne”, ponieważ były one bardziej bezpośrednio pozycjonowane jako alternatywa dla C ++.
Harrison Paine

12

Patrząc wstecz na archiwa , wydaje się, że cytat ten pochodzi z 2003 r. (Pomimo cytowanego artykułu z 2006 r.). W tym czasie C # był w wersji 1. x i brakowało mu wielu jego nowoczesnych funkcji :

Nowe funkcje

C # 2.0

  • Generics
  • Częściowe typy
  • Anonimowe metody
  • Iteratory
  • Typy zerowalne
  • Oddzielna dostępność Gettera / Settera
  • Metoda konwersji grupy (delegaci)
  • Współzależność i kontrast dla delegatów
  • Klasy statyczne
  • Delegowanie wnioskowania

C # 3.0

  • Zmienne lokalne niejawnie wpisane
  • Inicjatory obiektów i kolekcji
  • Właściwości automatycznie wdrażane
  • Anonimowe typy
  • Metody rozszerzenia
  • Wyrażenia zapytania
  • Wyrażenie lambda
  • Drzewa ekspresji
  • Metody częściowe

C # 4.0

  • Dynamiczne wiązanie
  • Argumenty nazwane i opcjonalne
  • Ogólna ko- i kontrawariancja
  • Wbudowane typy interopów („NoPIA”)

C # 5.0

  • Metody asynchroniczne
  • Atrybuty informacji o dzwoniącym

C # 6.0

  • Kompilator jako usługa (Roslyn)
  • Import elementów typu statycznego do przestrzeni nazw
  • Filtry wyjątków
  • Oczekiwanie na złapanie / wreszcie blokowanie
  • Automatyczne inicjalizowanie właściwości
  • Wartości domyślne dla właściwości tylko pobierających
  • Członkowie o wyrazistej treści
  • Propagator zerowy (operator warunkowy, zwięzłe sprawdzanie wartości zerowej)
  • Interpolacja ciągów
  • nazwa operatora
  • Inicjator słownika

C # 7.0

  • Nasze zmienne
  • Dopasowywanie wzorów
  • Krotki
  • Dekonstrukcja
  • Funkcje lokalne
  • Separatory cyfr
  • Literały binarne
  • Ref zwroty i miejscowi
  • Uogólnione typy zwrotów asynchronicznych
  • Wyraziste treści konstruktorów i finalizatorów
  • Wyraziste ciała pobierające i ustawiające

C # 7.1

  • Asynchronizacja główna
  • Domyślne wyrażenia dosłowne
  • Wnioskowane nazwy elementów krotkowych

- „C Sharp” , Wikipedia (usunięto odniesienia i linki)

Prawdopodobnie jest bardziej zrozumiałe, że C # wydawał się w tym kontekście pół-językiem, ponieważ brakowało mu dużo tego, co C # jest dzisiaj. Dziwnie jest myśleć, że nie ma nawet staticzajęć!

Brakowało też więcej rzeczy, ponieważ C # jest związany z .NET. Na przykład WPF nie było wtedy w pobliżu; to wszystko WinForms.


klasy statyczne mogą być kiepskim wyborem dla brakującej funkcji, ponieważ Java wciąż ich nie ma (rodzaj C #). Chyba że jest to dźgnięcie w Javę?
user253751,

1
@immibis Nie celowe dźgnięcie w Javę, ale, do cholery, naprawdę? staticklasy wydają się taką prymitywną cechą; Wydawało mi się, że wcześniej datowali instancje.
Nat

2
Wydaje się, że powiedzenie, że silniki odrzutowe były wcześniejsze niż odrzutowe; „klasa bez instancji” jest ogólnie nazywana modułem lub przestrzenią nazw , z wyjątkiem języków, w których cały kod musi znajdować się w klasie. (Lub nazywając rower ręcznym samochodem, lub dzwoniąc na telefon stacjonarny stacjonarny telefon komórkowy, lub ...)
user253751

@Nat - Posiadanie klas statycznych jest fajne, ale ich brak zmienia absolutnie nic. Możesz po prostu uczynić wszystkich członków klasy statycznymi, a wszystko, co stracisz, to kilka rodzajów błędów kompilatora, jeśli zapomnisz, że klasa miała pozostać statyczna.
Jirka Hanika

@JirkaHanika Tak, staticw większości przypadków nie jestem wielkim fanem zajęć. Szczerze mówiąc, wybrałem go jako funkcję do wywołania, ponieważ wydawało się to naprawdę prostą, prymitywną częścią C #; Nie sądziłem, że nie są w Javie.
Nat

3

Skarżył się na brak funkcji językowych, które umożliwiają precyzyjną kontrolę. Obejmują one narzędzia do

  • Egzekwowanie niezmienności (np. Słowo constkluczowe C ++ )
  • Kontrolowanie czasu życia i własności obiektu
  • Kontrolowanie wykorzystania pamięci, stylu kopiowania i alokacji

Przypomina mi to jedną z moich krytyki Javy:

wszystko jest wskaźnikiem, ale wskaźniki nie istnieją.

W obiektach C ++ wskaźniki i referencje to trzy odrębne pojęcia z wyraźną semantyką. W Javie masz tylko pseudo obiektowy wskaźnik. Dzięki połączeniu tych elementów i uniknięciu prawdziwej semantyki wskaźnika model obiektowy jest mniej przejrzysty.

W dobrze zdefiniowanym programie C ++ programista może oczekiwać, że odwołania będą poprawne i nie będą miały wartości null. Ze względu na uproszczony model Java nie może udzielać takich samych gwarancji.

Objawy tego mniej przejrzystego modelu obejmują wzorzec zerowego obiektu i warunki warunkowe yoda, takie jak 5.equals(potentiallyNullIntegerReference).


5
To jest bardzo zdezorientowane. Wskaźniki (w logicznym sensie istnieją w Javie) po prostu nie można ich zepsuć. Głównym celem uproszczenia modelu jest zapewnienie większej liczby gwarancji. Logika, w której można przyjąć więcej o kodzie w języku, zmniejszy ograniczenia. Więcej ograniczeń -> więcej gwarancji.
JimmyJames,

1
@JimmyJames to wyrażenie oznacza, że ​​chociaż wszystkie klasy java mają niejawną (fuj, btw) semantykę odniesienia, nie można mieć rzeczywistego wskaźnika. Na przykład nie ma sposobu, aby uzyskać „odwołanie” do odwołania. To paraliżuje język w kilku miejscach, czasem wymagając szalonych obejść (zobacz, Map.mergekiedy po prostu chcesz zaktualizować wartość na mapie).
Quentin

3
@JimmyJames: Niektórych przydatnych gwarancji nie można praktycznie zaoferować bez nałożenia pewnych ograniczeń. Ponadto niektóre przydatne optymalizacje mogą wymagać nałożenia pewnych ograniczeń. Niektóre języki nakładają jednak bezsensowne ograniczenia, które nie dają żadnych przydatnych gwarancji dla programistów i nie powinny być wymagane do przeprowadzania przydatnych optymalizacji. Niektóre ograniczenia są po prostu złe.
supercat,

3
@JimmyJames: Z drugiej strony, niektóre z bardziej fundamentalnych ograniczeń Javy i „bezpiecznego trybu” C # pozwalają im zaoferować bardzo przydatną gwarancję, że C ++ nie może: żadnego odniesienia (co w C ++ byłoby wskaźnikiem), które jest zawsze zaobserwowane w celu zidentyfikowania określonego obiektu nigdy nie będzie obserwowane w celu zidentyfikowania czegokolwiek innego .
supercat,

3
Czy możesz podać kilka cytatów na poparcie swojej odpowiedzi? Na przykład AFAIK, strona nie wspomina const. To czyni wzmiankę „Programowanie funkcyjne”, jednak językiem posługuje jako przykładem jest Program, który jest nie czysty język funkcjonalny (w rzeczywistości, projektanci programu są uważać, aby uniknąć użycia słowa „funkcja” i mówić o " procedur ”), więc wygląda na to, że stosuje on interpretację FP pierwszej klasy, a nie„ referencyjną przejrzystość ”.
Jörg W Mittag,

1

Zgadzam się z odpowiedzią @Kilian, ale dodam kilka elementów.

1- Uruchamianie na maszynie wirtualnej, a nie w systemie operacyjnym

Ponieważ Java i C # są uruchamiane przez maszynę wirtualną, logicznie oczekuje się, że nie możesz robić dokładnie tego, co chcesz, gdy jesteś bezpośrednio w systemie operacyjnym, ponieważ prawdopodobnie uszkodzisz coś na maszynie wirtualnej. Co więcej, ponieważ Java jest zorientowana jako niezależna od platformy, jest to jeszcze bardziej logiczne.

2-ton aplikacji nie wymaga takich rzeczy.

Istnieje mnóstwo aplikacji, które tak naprawdę nie wymagają przekopywania się przez tak wiele szczegółów, ale jeśli zrobisz to w języku, który tego wymaga, otrzymasz:

  • Więcej ryzyk związanych z błędami z powodu tych niepotrzebnych rzeczy.
  • Większy koszt programowania, zarządzanie pamięcią i testowanie jej zajmuje dużo czasu i pieniędzy!

3- Język jest dokonywany według pewnego wyboru ważenia kosztów / użytkowania / ryzyka, jak ... wszystko.

Dzięki C ++ możesz robić prawie wszystko, co chcesz, to jest wybór ludzi w C ++. Jednak im więcej, tym więcej trzeba obsługiwać.

Tak więc rzeczy, takie jak wielokrotne dziedziczenie, nie są porzucane tylko dlatego, że są niebezpieczne, są porzucane, ponieważ ich wdrożenie wiąże się z kosztami (programowanie, utrzymanie), a wszystko to za funkcję rzadko używaną właściwie i ogólnie przepisuje się inaczej.


Rzeczywisty koszt wielokrotnego dziedziczenia polega na tym, że nie można zachować obu następujących gwarancji: (1) Jeśli członek klasy podstawowej Bzostanie zastąpiony w klasie średniej M, wówczas B„wersja tego członka będzie dostępna tylko poprzez M” s zastąpienie; (2) biorąc pod uwagę dowolne odniesienie typu T, przekształcenie go w dowolny nadtyp i powrót do Tda odniesienie równoważne oryginałowi. Obie te gwarancje są przydatne, a obsługa wielokrotnego dziedziczenia wymagałaby rezygnacji z co najmniej jednej.
supercat

-1

Po prostu umieść wszystkie ograniczenia w językach wysokiego poziomu, takich jak C # i Java, aby chronić programistę. Istnieją nie tyle po to, by chronić programistę przed nim, ale raczej po to, by chronić programistę przed innymi programistami!

Ile razy my, programiści, spotykamy biblioteki, które były wręcz okropne w swoich praktykach kodowania i projektowaniu, ale których byliśmy zmuszeni używać z tego czy innego powodu?

Programy te zazwyczaj mają cechy starej proceduralnej metody programowania, z brakiem enkapsulacji, dużą ilością bezpośrednich zapisów w pamięci bez wychwytywania lub obsługi błędów. Segfaults realizują masę, próbując wykorzystać je w dowolnym projekcie na dużą skalę.

Właśnie tam języki takie jak Java i C # są niezwykle pomocne; to nie jest tak, że cieszymy się z faktu, że nie pozwalają nam robić wszystkich schludnych rzeczy, które robią inne języki. Chodzi o to, że lubimy bóle głowy, które musimy znosić, ponieważ inni programiści nadużywaliby tych schludnych rzeczy, które inne języki mogą robić.

Moim zdaniem interfejsy są warte wszelkich kompromisów w zakresie pamięci lub szybkości wykonywania. Mam nadzieję, że zauważysz, że w każdej ograniczonej czasowo aplikacji o krytycznym znaczeniu, wszystkie te zabezpieczenia, właściwe zarządzanie błędami i ogólnie pewność, że pamięć nie jest zepsuta, są dobre!


wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami poczynionymi i wyjaśnionymi w poprzednich 5 odpowiedziach
gnat

1
They exist not so much to protect the programmer from him/herself, but rather to protect the programmer from other programmers!czy ma chronić innych programistów przed programistą?
Tobia Tesan

@TobiaTesan That też :)
Akumaburn
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.