Interfejsy nad implementacje

0
Wibowit napisał(a):

Ekhm, no czasami to i metoda Kopiego-Pejsta się sprawdza. Musiałbyś pokazać konkretny przykład na to w jakiej jesteś sytuacji.

No i nawet nie rzadko ;)
Ciężko o dobry przykład na GH, ogladając prezentację nt. np hexagonal architecture prowadzący mówi, że tak stosuje do podejście w produkcyjnych projektach, ale nie ma takiego przykładowego żeby pokazać. Bottega ma na pewno przykładowy kod, ale nie przygladałem mu się zbytnio, może bedzie tam pokazany anti corruption layer

Wibowit napisał(a):

Ja lubię podejście mikroserwisów które nie mają dodatkowego przekombinowanego podziału na moduły. Mikroserwisy gadają sobie z użyciem DTO, każdy mikroserwis ma własne obiekty domenowe, które nie muszą być ukrywane pomiędzy modułami i wszystko gra.

Zgodzę się, ale są mikroserwisy, które mają złożoną domenę i generalnie nie wiadomo jak to pociąć. Wtedy generalnie cały mikroserwis składa się z głównego modułu biznesowego i kilku innych modułów - moduł aplikacji, infrastruktury, a także pomniejsze moduły biznesowe, które są za małe żeby je wyciąć lub z innego powodu ich nikt nie wyciał - może nikt nie zauważył, że to czas. Jednak gdy trzeba wyciąć moduł poboczny - jest to proste. Nawet wyciągnięce modułu głównego nie powinno być problemem.

Wibowit napisał(a):

Z mojego doświadczenia dokładanie fikuśnej hierarchii modułów do mikroserwisów (które z definicji już mają być małe, więc po co je jeszcze intensywnie ciąć?) to przerost formy nad treścią. Zysku z tego nie ma, a zwykle jest strata, bo mało kto rozumie na czym polega ta fikuśna hierarchia i ostatecznie klasy domenowe lądują w losowych modułach, aby tylko kod się kompilował.

Też nie uważam, żeby to był silver bullet, ale warto próbować różnych podejść i tak jak pisalem wcześniej tego obecnie próbuję i wiem, że przez to dokłada się więcej czasu do projektu, jeżeli doświadczenie pokaże że to się opłaca w przyszłości to będzie fajnie, jak nie to trzeba będzie spróbować czegoś innego.

3
Wibowit napisał(a):
  1. Ile zespołów programistów pisze biblioteki, które są używane przez inne zespoły? Moim zdaniem bardzo mało. Dopóki nie piszesz biblioteki to tego nie udawaj. YAGNI.

Zdarzają się organizacje, które działają w oparciu o mikroserwisy i każdy zespół dostarcza własne moduły komunikacyjne do swoich serwisów. W takim przypadku właściwie wszyscy się tym zajmują.

hcubyc napisał(a):

Dodatkowy argument, który mi przychodzi na myśl to interfejs robię publiczny, tak by kod spoza mojego pakietu mógł go zaimplementować, klas nie robię publicznych (pozą jedną, która jest punktem wejścia do pakietu), ale to kwestia konwencji

Ja tak właśnie często robię. (Oczywiście w tych przypadkach, które uznałem uprzednio za takie, w których interfejsy mają sens, więc "często" tutaj nie oznacza codziennie kilka razy.)

Wibowit napisał(a):

W jaki sposób przedwczesne wydzielenie interfejsu miałoby w czymś pomóc? Podaj przykład.

A w jaki sposób szkodzi? (Cały czas skupiam się wyłącznie na swoim zastosowaniu dla interfejsów.)

Ja rozumiem, że można mockować klasy, ale jak już pisałem w tych przypadkach, w których mock ma sens, wolę mieć interfejs, bo:

  1. Mockowanie klas często prowadzi do transwestytyzmu kodu i powstawania partial mocków, które są właściwie nie wiadomo czym i nie wiadomo po co, trzeba stracić czas na zastanawianie się, który kod zostanie użyty w danym teście.
  2. W C# musiałbym dodatkowo oznaczyć metodę kodu produkcyjnego jako virtual, żeby móc ją mockować. Mam duże opory przed zmienianiem kodu produkcyjnego pod testy, kojarzy mi się to z Hindusami, którzy robią prywatne metody public, aby napisać do nich testy.
  3. Nie mógłbym zapieczętować klasy. To jest złe, bo klasy powinny być albo abstrakcyjne, albo zapieczętowane. Klasy niezamknięte na dziedziczenie trzeba inaczej projektować tak, aby programista chcący z tego korzystać nie dostał szału np. przez to, że jakieś pole, które on potrzebuje zmodyfikować jest prywatne. Ogólnie trzeba włożyć sporo wysiłku, aby dobrze zaprojektować taką klasę, a z drugiej strony zazwyczaj nie ma takiej potrzeby, więc lepiej klasę zapieczętować, co jest jasną deklaracją, że klasa nie została przemyślana pod tym względem. Dodatkowo pieczętowanie klas utrudnia łamanie Zasady Liskov poprzez pisanie jakiejś dzikiej ifologii dla klas ze środka hierarchii. Nieabstrakcyjne i niezapieczętowane klasy mają sens chyba tylko jako kontrolki GUI.
0

A w jaki sposób szkodzi? (Cały czas skupiam się wyłącznie na swoim zastosowaniu dla interfejsów.)

Ja rozumiem, że można mockować klasy, ale jak już pisałem w tych przypadkach, w których mock ma sens, wolę mieć interfejs, bo:

Ja piszę o Javce, a w Javce pisze się inaczej niż w C#. Przede wszystkim łatwiej się mockuje, dziedziczy, etc (przynajmniej to widać z twojego wywodu) skoro C#-owcy mają alergię na metody wirtualne.

Zdarzają się organizacje, które działają w oparciu o mikroserwisy i każdy zespół dostarcza własne moduły komunikacyjne do swoich serwisów. W takim przypadku właściwie wszyscy się tym zajmują.

Mój zespół zajmuje się pisaniem systemu zbudowanego na mikroserwisach i sytuacja jest taka, że my jako jeden zespół rozwijamy wszystkie te nasze mikroserwisy naraz.

Oczywiście integrujemy się z innymi systemami, nawet wieloma, ale te wewnętrzne raczej nie udostępniają nam żadnych bibliotek. Biblioteki z klientami dostajemy zwykle od zewnętrznych firm.

Takie podejście ma sens, bo jeśli np my piszemy sobie system w Javce i integrujemy się z systemem napisanym w C# (w tej samej firmie) za pośrednictwem RESTa to sensownym rozwiązaniem jest byśmy sami napisali sobie klienta wedle specyfikacji publicznego API, a nie liczyli na to, że C#-owcy będą pisać w Javce klienta specjalnie dla nas. Każdy przypadek jest inny więc lepiej nie uogólniać. Z mojego doświadczenia w pracy w banku wynika jednak iż spora część integracji wygląda w taki właśnie sposób (tzn jeśli system ma klientów to zwykle można policzyć na palcach jednej ręki).

Ponadto (w zasadzie najważniejszy argument) ta biblioteka o której mówisz to po prostu klient RESTowy. Klient RESTowy to raczej niewielka część systemu i nawet niespecjalnie widzę sens by szaleć w nim z interfejsami. Klient RESTowy jest także niezależny od serwera implementującego API, no może oprócz wspólnych klas DTO. A skoro tylko klienta RESTowego chcemy rozpowszechniać innym to tylko w przypadku pisania tego klienta trzeba się trzymać specjalnych zasad (o których pisałem wcześniej).

0
Wibowit napisał(a):

Ja piszę o Javce, a w Javce pisze się inaczej niż w C#. Przede wszystkim łatwiej się mockuje, dziedziczy, etc (przynajmniej to widać z twojego wywodu) skoro C#-owcy mają alergię na metody wirtualne.

To raczej w C# się łatwiej dziedziczy, bo wystarczy do tego jeden znak, a nie całe słowo. Tylko to tylko lukier składniowy.
Używanie narzędzi języka w sposób przemyślany to nie jest alergia lecz rozwaga. Jeśli metoda nie musi być wirtualna, bo żadna dziedzicząca klasa kodu biznesowego jej nie nadpisuje, to nie ma sensu ją w ten sposób deklarować. I to podejście jest niezależne od języka.

Ale przynajmniej dzięki Twoim wypowiedziom tutaj rozumiem już, czemu w ostatnio przejętym projekcie mam w kodzie biznesowym masę klas z losowymi metodami oznaczonymi jako wirtualne, które następnie są nadpisywane w jakichś chorych testowych implementacjach. Pisali go programiści Javy.

Każdy przypadek jest inny więc lepiej nie uogólniać.

Ja nie uogólniałem, pisałem o sytuacji innej niż Twoja. Mnie chodziło o przypadek, w którym jest dwadzieścia zespołów i trzysta mikroserwisów. Jeśli do tego niektóre mikroserwisy mają kilkudziesięciu konsumentów, a do tego jest homogeniczność technologiczna, to warto takiego klienta napisać i udostępniać. Nakład pracy autorów nie jest duży, a jakby każdy miał sobie to pisać od nowa, to tylko strata czasu i energii. A tym bardziej się to opłaca, jeśli to nie jest REST.

Ponadto (w zasadzie najważniejszy argument) ta biblioteka o której mówisz to po prostu klient RESTowy. Klient RESTowy to raczej niewielka część systemu i nawet niespecjalnie widzę sens by szaleć w nim z interfejsami. OKlient RESTowy jest także niezależny od serwera implementującego API, no może oprócz wspólnych klas DT. A skoro tylko klienta RESTowego chcemy rozpowszechniać innym to tylko w przypadku pisania tego klienta trzeba się trzymać specjalnych zasad (o których pisałem wcześniej).

To jest dokładnie ten przypadek, o którym ja pisałem na początku - komunikacja z zewnętrznym modułem. A interfejsy w takim kliencie są użyte w sposób najbardziej intuicyjny z nazwą - stanowią interfejs do zewnętrznego modułu. Analogicznie jak używamy interfejsu USB do podłączania zewnętrznych modułów do komputera.

0

uwazam ze interfejsy sa troche przereklamowane, zwlaszcza podejscia typu "kazda klasa musi miec interfejs".
tworze je glownie w ramach wstrzykiwania prostych (1-2 metody) strategii/callbackow albo do kompozytow i null objects. w innych wypadkach bardzo sporadycznie.

Wibowit napisał(a):

Ja piszę o Javce, a w Javce pisze się inaczej niż w C#. Przede wszystkim łatwiej się mockuje, dziedziczy, etc (przynajmniej to widać z twojego wywodu) skoro C#-owcy mają alergię na metody wirtualne.

w c# metody nie sa domyslnie wirtualne co imo jest zaleta a nie wada, szkoda ze klasy tez nie sa domyslnie sealed. to prawda ze w javie latwiej sie mockuje i dziedziczy co oczywiscie ulatwia prace z beznadziejnym legacy kodem ale niestety tez wspiera tworzenie wlasnie takiego kodu przez niedbalych i/lub niedoswiadczonych programistow.

0

Przeczytałem sobie wywiad z Andersem Hejlsbergiem nt tego czemu w C# jest w ogóle wymóg stosowania słowa virtual jeśli chcemy mieć metodę wirtualną: http://www.artima.com/intv/nonvirtual.html
Są dwa argumenty.

Pierwszy to wydajność i jego rozumiem. .NET ma słabego JITa i nie radzi sobie z metodami wirtualnymi. JVM natomiast nie ma problemu i nawet wirtualne gettery, settery, hashcode'y, equalsy itd nie powodują spadku wydajności. Hejlsberg popełnił też błąd przy opisywaniu Javy: We can observe that as people write code in Java, they forget to mark their methods final. Therefore, those methods are virtual.. final wcale nie wyklucza wirtualności metody, bo mogę mieć np:

class A {
  void m() {
    System.out.println("A");
  }
}

class B extends A {
  @Override
  final void m() {
    System.out.println("B");
  }
}

Metoda m w klasie B jest finalna i wirtualna jednocześnie.

Drugi argument to jakieś bajeczki o wersjonowaniu i tym, że konflikty nazw w Javie powodują problemy przy aktualizacji wersji Javy. Tu się gość grubo pomylił, bo problemy z aktualizacją Javy wynikają z intensywnego użycia refleksji i wzbogacania bajtkodu. Jeżeli narzędzie analizujące bajtkod jest przystosowane do Javy X, a Java X+1 ma dodatkowe konstrukcje w bajtkodzie to to narzędzie się sypnie.

Uważam też, nawiązując do argumentacji Hejlsberga, że jeśli ktoś rozszerza klasę z obcej biblioteki i dorzuca masę metod to robi to źle. Widziałem kod, który np rozszerza interfejs Map z biblioteki standardowej Javy. Przecież to jest obłęd. Mapa powinna być szczegółem implementacyjnym naszej klasy, a nie publicznym API. Powinno się użyć kompozycji (czyli umieścić mapę w polu), a nie dziedziczenia (po pełnej mapie). To dość oczywiste. Jak dla mnie to zabawa z override, new itd to próba ratowania syfiastego kodu (nadużywającego dziedziczenia) zamiast naprawiania go (poprzez kompozycję, czyli trzymanie się zasady SRP).

W Javce zachowanie metody wywołanej na obiekcie zawsze zależy od typu obiektu, a nie typu referencji do obiektu. Dla mnie to kluczowa zaleta. W kodzie:

X referencja = new Y();
referencja.metoda();

Zachowanie drugiej linijki zależy zawsze od typu obiektu, więc przeglądana będzie zawsze klasa Y w poszukiwaniu metody. W C# nie ma tej gwarancji, podobnie jak np w C++. Z C++em już miałem do czynienia, to strasznie upierdliwy język. By uzyskać zachowanie jak przy metodach wirtualnych bez użycia metod wirtualnych (bo w C++ są wolne, podobnie jak w C#) używa się namiętnie szablonów. Każdy szablon jest instancjonowany dla konkretnego typu, więc efektywnie wywołana metoda zależy od typu parametru szablonu. W Javce dzięki wydajnym wirtualnym metodom nie trzeba się tak wyginać.

1
Wibowit napisał(a):

Hejlsberg popełnił też błąd przy opisywaniu Javy: We can observe that as people write code in Java, they forget to mark their methods final. Therefore, those methods are virtual.. final wcale nie wyklucza wirtualności metody

javowcy czesto nie uzywaja final nawet jesli override metody powoduje katastrofe, w c# musisz jawnie zdeklarowac ze metoda jest wirtualna co imo jest bardzo ok. final w pewien sposob zatrzymuje wirtualnosc metody, jesli w twoimi przykladzie zrobilibysmy sobie klase C rozszerzajaca B to ona juz nie moze zrobic override na m i wlasnie o to chodzilo hejselbergowi, watpie zeby nie ogarnial tego konceptu.

Jak dla mnie to zabawa z override, new itd to próba ratowania syfiastego kodu (nadużywającego dziedziczenia) zamiast naprawiania go (poprzez kompozycję, czyli trzymanie się zasady SRP).

masz racje z new, przyznam ze nigdy tego nie uzywalam i spotkalam sie wylacznie pare razy (i zawsze byly to hacki), niemniej explicite virtual i override na poziomie jezyka to fajna rzecz i w javie takie cos by sie przydalo.

W Javce zachowanie metody wywołanej na obiekcie zawsze zależy od typu obiektu, a nie typu referencji do obiektu. Dla mnie to kluczowa zaleta.

tyle ze jesli jest jakas zryta hierarchia dziedziczenia i wirtualna metoda jest wywolywana z dala od inicjalizacji obiektu to patrzac po kilkunastu plikach gdzie nie ma ani jednego slowka final to mozna sobie zgadywac co tak naprawde sie stanie wiec nie powidzialabym ze java jest niesamowita pod tym wzgledem.

W C# nie ma tej gwarancji, podobnie jak np w C++.

tyle ze w c# uzywanie new na metodach to naprawde jakis niewielki margines i w praktyce malo szkodliwy choc z pewnoscia brzydki koncept

0

tyle ze jesli jest jakas zryta hierarchia dziedziczenia i wirtualna metoda jest wywolywana z dala od inicjalizacji obiektu to patrzac po kilkunastu plikach gdzie nie ma ani jednego slowka final to mozna sobie zgadywac co tak naprawde sie stanie wiec nie powidzialabym ze java jest niesamowita pod tym wzgledem.

Zaskoczę cię, można też robić zrytą kompozycję albo zryty przepływ sterowania bez nadpisywania metod (na przykład komponując lambdy w potworne konstrukcje). W ogóle dużo można zrytych rzeczy robić.

Natomiast jeśli chcę wiedzieć czy dana metoda została gdzieś nadpisana to rozsądne IDE mi to od razu pokazuje. Przy metodzie nadpisanej w podklasach jest ikonka po kliknięciu której widzę te podklasy i mogę od razu przejść do nadpisujących metod. Nie ma żadnego problemu z ich znalezieniem. Jeśli ikonki nie ma to metoda jest efektywnie finalna (bo nie nadpisana nigdzie). IDE może też pokazać hierarchię klas w postaci drzewka. Oferuje też nawigację po kodzie uwzględniającą dziedziczenie (tzn jeśli jestem w kodzie klasy i wywoływana jest metoda z tej klasy to IDE nawiguje do konkretnej metody, która będzie wykonana, bo zna przecież typ aktualnej klasy).

tyle ze w c# uzywanie new na metodach to naprawde jakis niewielki margines i w praktyce malo szkodliwy choc z pewnoscia brzydki koncept

Według Hejlsberga to konieczna właściwość potrzebna do uchronienia się przed rzekomymi problemami istniejącymi w Javie. Ma taką argumentację:

As a result, C# doesn't have the particular versioning problem I described earlier in which we introduce a method in a base class that you already have in a derived class. In your class, you would have declared foo virtual. Now we introduce a new virtual foo. Well, that's fine. Now there are two virtual foos. There are two VTBL slots. The derived foo hides the base foo, but that's fine. The base foo wasn't even there when the derived foo was written, so it's not like there's anything wrong with hiding this new functionality. And things continue to work the way they're supposed to.

A więc według niego możliwość posiadania dwóch osobnych hierarchii metod wirtualnych (o tej samej sygnaturze) w jednej hierarchii dziedziczenia klasy to zaleta nad Javą i główny powód (bo drugi powód to kiepski JIT w .NETu) dla wprowadzenia takich kombinacji w C#. mind=blown

W ogóle o tym rzekomym problemie z wersjonowaniem w Javie pierwszy raz przeczytałem właśnie w wywiadzie z Hejlsbergiem. Wygląda więc na to, że Hejlsberg "naprawił" problem który nie istnieje.

Kolejny argumentem za tym, że problem z wersjonowaniem w Javie jest wyimaginowany jest to, że w Javce 8 autorzy Javy dorzucili masę nowych metod (oczywiście wirtualnych) do interfejsów w bibliotece standardowej. Świat się nie zawalił z tego powodu, klasy kompilowały się jak dawniej (problem był w zasadzie tylko przy refleksji i obrabianiu bajtkodu, ale to nie ma nic wspólnego z metodami wirtualnymi). C# z powodu swojej alergii na metody wirtualne wybrał statyczne metody rozszerzające by dodać funkcjonalności do istniejących klas w bibliotece standardowej. Ma to tę wadę, iż konkretne klasy nie są w stanie bezpośrednio nadpisać nowej metody implementując funkcjonalność w bardziej efektywny sposób, tzn przy użyciu referencji o typie interfejsu zawsze zostanie użyta domyślna metoda statyczna (rozszerzająca).

1

Dorzuce swoje 3 grosze, tez uwazam ze final by default byloby lepsze np jak w kotlinie. Tylko po to, zeby swiadomie otwierac mozliwosc dziedziczenia, gdy zajdzie taka potrzeba lub klasa zostala do tego zaprojektowana. Tyle dobrego, ze mozna korzystac z lomboka, szkoda ze to nie sa domyslne zachowania

2

To ja tylko coś z perspektywy javy i jej JIT'a.
Majć metode taką metode na hotpath

public void iAmRlyHot(final Collection a)
{
 a.costam();
}

to zakładając że ta metoda będzie używana tylko przez ArrayListe(monomorphic) albo ArrayList i LinkedListe (biomorphic), JIT będzie w stanie zrobić z tego, coś takiego

public void iAmRlyHot(final Collection a)
{
  if(a instanceOf ArrayList) {
  {
   / direct link do natywnego kodu, zero vtable lookup
  } if else (a instance of LinkedList) {
   // direct link do natywnego kodu, zero vtable lookup
  } else {
   //uncommon trap - cały misterny plan ....  
  } 
}

w przypadku megamorphic calls, czyli jak ta metoda będzie obsługiwać więcej niż 2 typy (np: 3 XD) to JIT będzie mógł tam wstawić tylko i wyłącznie vtable lookup ( co jest oczywiście zdecydowanie wolniejsze).

P.S oczywiście JIT jest sobie poradzić z megamorphicznymi wywołaniami w jakichś przypadkach, taką optymalizacją jest np: *dominant target *

Ciekaw jestem jak to wygląda z perspektywy C# i jego VM'a.

A, i wracając do tematu, jak widzicie konkluzja jest prosta, pakujcie wszystko do jednej klasy, tylko nie przesadzajcie z metodami, bo może się okazać że JIT nie jest w stanie jej zoptymalizować, bo jest za duza.

#java #mojKodJestSzybkiJakKubica

0

To ja tylko coś z perspektywy javy i jej zretardowanego JIT'a.

W sensie co jest zretardowane? JIT to nie czarodziej. To co opisałeś jest strategią dobrego JITa. Jeśli masz megamorphic calls to vtable lookup będzie szybszy niż generowanie ogromnej drabinki ifów. W jaki niby inny sposób chciałbyś to obsłużyć (zakładając ten sam scenariusz, czyli wywoływanie metod wirtualnych)? Drabinki ifów mogą czasem nawet spowalniać kod, dlatego JVM zawiera coś takiego jak dynamiczna deoptymalizacja (sterowana ciągłym profilowaniem kodu, które działa przecież przez cały czas życia programu Javowego) takich wywołań wirtualnych.

Ciekaw jestem jak to wygląda z perspektywy C# i jego VM'a.

ZTCW to .NET ma jakieś optymalizacje nazwane "global interface table" dla metod z interfejsów które można zaimplementować, ale nie można tych zaimplementowanych metod nadpisać (nie mylić z przesłanianiem niewystępującym w Javce). To wygląda jak jakaś trzecia płeć, bo ani to nie jest zwykła metoda wirtualna, ani metoda wiązana na etapie kompilacji. Poprawcie mnie jeśli źle to zrozumiałem.

W każdym języku pisze się inaczej, więc wzięcie ulubionej konstrukcji z języka X, zaimplementowanie jej wprost w języku Y (ignorując idiomy języka Y) i narzekanie, że coś działa gorzej jest słabe. Sensowne jest porównywanie idiomatycznych (a więc typowych) konstrukcji z każdego języka. W Javce wydajnie działają wywołania wirtualne monomorficzne i bimorficzne, jest też skuteczna deoptymalizacja (co oznacza, iż drabinka ifów może się z czasem zmieniać w zależności od rodzaju obciążenia), więc są to narzędzia z których można spokojnie korzystać.

Dorzuce swoje 3 grosze, tez uwazam ze final by default byloby lepsze np jak w kotlinie. Tylko po to, zeby swiadomie otwierac mozliwosc dziedziczenia, gdy zajdzie taka potrzeba lub klasa zostala do tego zaprojektowana.

Są też inne wyjścia, np adnotacje. Scala ma następujące (z czego ostatnie dwie dotyczą otwartości na zmiany):

0

Po pierwsze @Wibowit , musisz się wyluzować ;D

W sensie co jest zretardowane?

np: to XD "Compilers are glorified regex-es" ~ Douglas Hawkins <3 , proszę nie próbuj tego tłumaczyć.

W jaki niby inny sposób chciałbyś to obsłużyć (zakładając ten sam scenariusz, czyli wywoływanie metod wirtualnych)? Drabinki ifów mogą czasem nawet spowalniać kod,

Jednym z takich sposobów jest *dominant target * - wspomniałem ale nie opisałem (nie myślałem że ktoś się doczepi ;D).
JVM jest w stanie, nawet dla megamorphic calls, wygenerowąc sensowny native code.

public void iAmRlyHot(final Collection a)
{
  if(a instanceOf ArrayList) {
  {
   / direct link do natywnego kodu, zero vtable lookup
  } else {
    //vtable lookup  
  } 
}

ale tylko w przypadku, kiedy (tutaj strzelam) 99% wywołań ma target na ArrayListe.

W jaki niby inny sposób chciałbyś to obsłużyć

Oczywiście że dałoby się to zrobić - ja nawet studiów nie mam, to sie nie znam - ale nie chodzi o to żeby obługiwać wszystkie casy, bo dla 99% obługa biomorphic calls wystarczy - kiedy robisz coś ponad to - jesteś dziwny (przynajmniej tak mówi Douglas Hawkins)

ZTCW to .NET ma jakieś optymalizacje nazwane "global interface table" dla metod z interfejsów które można zaimplementować, ale nie można tych zaimplementowanych > metod nadpisać (nie mylić z przesłanianiem niewystępującym w Javce).

Ma się te wiedze widzę ;D

0

Po pierwsze @Wibowit , musisz się wyluzować ;D

Nie jestem spięty. Po prostu nie lubię jak ktoś mi wciska kit :]

Oczywiście że dałoby się to zrobić - ja nawet studiów nie mam, to sie nie znam - ale nie chodzi o to żeby obługiwać wszystkie casy, bo dla 99% obługa biomorphic calls wystarczy - kiedy robisz coś ponad to - jesteś dziwny (przynajmniej tak mówi Douglas Hawkins)

Napisałeś "zretardowany JIT" więc zasugerowałeś wyraźnie, że JVM robi coś co łatwo można zrobić lepiej. Tymczasem teraz piszesz, że w ogóle nie wiesz jak podejść do rozwiązania problemu. Gdzie tu logika?

np: to
XD "Compilers are glorified regex-es" ~ Douglas Hawkins <3 , proszę nie próbuj tego tłumaczyć.

Nie chce mi się oglądać całego filmiku, skoro tobie się nie chce opisać dokładnie o co chodzi, więc spróbuję się domyślić. Z twojego cytatu i wskazanego momentu chodzi zapewne o klejenie stringów w Javie. Zamiast oglądać kozaczenie gościa na filmiku można przeczytać formalny opis problemu i formalny opis rozwiązania tutaj: JEP 280: Indify String Concatenation. Usprawnienie to weszło w Javce 9. Nie ma to związku z optymalizacją metod wirtualnych, więc nie wiem czemu nagle zmieniasz temat.

W skrócie problem polega na tym, że klejenie stringów można zrobić na nieskończoną ilość sposobów co oznacza nieskończoną ilość różnych sekwencji bajtkodów. Każdy z tych sposobów jest w zasadzie nieoptymalny, więc JVMka musi rozpoznać w którym miejscu w bajtkodzie kleimy stringi i podmienić implementację na implementację mocno zoptymalizowaną. Wtedy faktycznie JVM jest czymś w rodzaju "glorified regex" bo dopasowuje wzorce w bajtkodzie. Jest to jednocześnie kosztowne (zarówno kosztowne jest pisanie takich "regexów" jak i odpalanie ich w czasie wykonywania programu) jak i zawodne (kompilatory Javy, a jest ich wiele, mogą optymalizować bajtkod i produkować sekwencje bajtkodów w sposób który ciężko przewidzieć).

Rozwiązanie natomiast polega na użyciu instrukcji bajtkodu o nazwie invokedynamic (dostępnej od Javy 7). Ta instrukcja może wywołać metodę z dowolną ilością parametrów dowolnych typów bez narzutu typowego dla varargsów (w przypadku których wymagane jest stworzenie tablicy referencji do obiektów oraz opakowanie każdego argumentu metody w obiekt). W momencie wywołania JVM generuje dobrze zoptymalizowany kod dla obsługi klejenia stringów. Odpada więc wyszukiwanie wzorców w bajtkodzie i JVM przestaje być "glorified regexem".

Sprawdziłem też jak wygląda klejenie stringów w .NETu. Wziąłem przykład z https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/how-to-concatenate-multiple-strings wrzuciłem do dekompilatora C# https://sharplab.io/ , ustawiłem "IL" jako "Results" i wyszło na to, że klejenie stringów kończy się takim wywołaniem w bajtkodzie: call string [mscorlib]System.String::Concat(string[]) Przed tym wywołaniem musi być oczywiście stworzenie tablicy i przerobienie każdego argumentu z osobna na osobnego stringa, więc jeśli JIT .NETowy ma to jakoś dobrze optymalizować to musiałby to robić jak Java przed poprawką. Inaczej mówiąc .NET, jeśli chce to dobrze zoptymalizować, to też musi być "glorified regexem" (a czy jest to nie wiem, nie chce mi się wnikać).

0

Nie jestem spięty. Po prostu nie lubię jak ktoś mi wciska kit :]

Gdzie napisałem nieprawdę ?

http://openjdk.java.net/jeps/280

nie znałem tego.

Zamiast oglądać kozaczenie gościa na filmiku można przeczytać formalny opis problemu i formalny opis rozwiązania tutaj:

Czyli był problem czy go nie było ?

1

Gdzie napisałem nieprawdę ?

Napisałeś, że jest problem z optymalizacją wywołań wirtualnych, a potem zmieniłeś temat pisząc o optymalizacji klejenia stringów. Optymalizacje klejenia stringów nie polegają na dewirtualizacji metod tylko na całościowym zastępowaniu kodu.

Ponawiam więc prośbę: podaj argument za tym, że Java ma "zretardowany JIT" w kontekście optymalizacji metod wirtualnych.

0
Wibowit napisał(a):

Gdzie napisałem nieprawdę ?

Napisałeś, że jest problem z optymalizacją wywołań wirtualnych, a potem zmieniłeś temat pisząc o optymalizacji klejenia stringów. Optymalizacje klejenia stringów nie polegają na dewirtualizacji metod tylko na całościowym zastępowaniu kodu.

Ponawiam więc prośbę: podaj argument za tym, że Java ma "zretardowany JIT" w kontekście optymalizacji metod wirtualnych.

""zretardowany JIT" napisałem to dla beki XD a Ty ciśniesz temat jakbym naprawdę chciał zdeprecjonować JVM'a ... jeszcze napisałem wcześniej że to dla jaj. Usunąłem "zretardowany" - już lepiej ?

Napisałeś, że jest problem z optymalizacją wywołań wirtualnych

nic takiego nie napisałem. Nie opisałem tego jako problem, tylko jako ciekawostkę.

Ponawiam więc prośbę: podaj argument za tym, że Java ma "zretardowany JIT" w kontekście optymalizacji metod wirtualnych.

nie podam takiego argumentu bo nic takiego nie pisałem.

1
Wibowit napisał(a):

Zaskoczę cię, można też robić zrytą kompozycję albo zryty przepływ sterowania bez nadpisywania metod (na przykład komponując lambdy w potworne konstrukcje). W ogóle dużo można zrytych rzeczy robić.

tez zawodowo programuje :)

Natomiast jeśli chcę wiedzieć czy dana metoda została gdzieś nadpisana to rozsądne IDE mi to od razu pokazuje. Przy metodzie nadpisanej w podklasach jest ikonka po kliknięciu której widzę te podklasy i mogę od razu przejść do nadpisujących metod. Nie ma żadnego problemu z ich znalezieniem. Jeśli ikonki nie ma to metoda jest efektywnie finalna (bo nie nadpisana nigdzie). IDE może też pokazać hierarchię klas w postaci drzewka. Oferuje też nawigację po kodzie uwzględniającą dziedziczenie (tzn jeśli jestem w kodzie klasy i wywoływana jest metoda z tej klasy to IDE nawiguje do konkretnej metody, która będzie wykonana, bo zna przecież typ aktualnej klasy).

tez uzywam IDE i nawet znam skroty klawiszowe zeby nie klikac po ikonkach. twoje narzekanie na new i override w c# traci sens z tym argumentem, bo vs tez takie przypadki jasny sposob oznacza.
nie twierdze ze niemozliwym jest ustalenie kolejnosci wywolan, tylko narzekam ze mozna sie naciac przy skomplikowanej hierarchii z wieloma metodami i byloby latwiej gdyby jezyk to ograniczal. wprowadzenie adnotacji @Override tez nie bylo przypadkowe.

swoja droga to nie kupuje tego porownywania wydajnosci przy metodach wirtualnych w javie vs c#. pracujac w projektach gdzie mikrooptymalizacje maja praktyczne znaczenie nie spotkalam sie z tworzeniem hierarchii obiektow i uzywaniu polimorfizmu w kluczowych wydajnosciowo czesciach aplikacji, zarowno w javie jak i c# dane pakowalismy raczej do tablic prymitywow/value types i uzywalismy codegened kursorow, fasad, handlerow, proxy etc

0

Apple w Swifcie idzie jeszcze w inną stronę -> https://www.raywenderlich.com/148448/introducing-protocol-oriented-programming

0

tez uzywam IDE i nawet znam skroty klawiszowe zeby nie klikac po ikonkach. twoje narzekanie na new i override w c# traci sens z tym argumentem, bo vs tez takie przypadki jasny sposob oznacza.
nie twierdze ze niemozliwym jest ustalenie kolejnosci wywolan, tylko narzekam ze mozna sie naciac przy skomplikowanej hierarchii z wieloma metodami i byloby latwiej gdyby jezyk to ograniczal.

C# przecież niczego nie ogranicza sam z siebie. virtual w C# jest, aczkolwiek niektórzy mają czkawkę jak go widzą. Ja np nie lubię liberalnego podejścia do przeciążania metod, bo nie widzę od razu jaka metoda się wywołuje. Inni to lubią. Co zrobić? Są ludzie, którzy w ogóle kodują w językach kaczo typowanych i śmieją się z reszty. Dziwaczne hierarchie dziedziczenia istnieją też w C++, gdzie virtuala unika się jeszcze bardziej niż w C#.

wprowadzenie adnotacji @Override tez nie bylo przypadkowe.

Override jest OK, bo zapobiega literówkom. Poza tym większość ludzi to olewa (ja nie olewam) i wpisuje wtedy kiedy kompilator się ciska, albo jak IDE automatycznie wstawi.

swoja droga to nie kupuje tego porownywania wydajnosci przy metodach wirtualnych w javie vs c#

..a jednak unikanie wywołań wirtualnych w C# jest po części spowodowane kwestiami wydajnościowymi (reszta to kwestie ideologiczne). Zamiast używania ich można optymalizować kod C#-owy ręcznie przez ostrożne używanie właściwości języka.

Microsoft tworzy nowego JITa o nazwie RyuJIT, który ma potencjał by zbliżyć się do Javy z wydajnością (w przypadku kodu, który nie jest tak pieczołowicie ręcznie optymalizowany jak to C#-owcy lubią robić), ale jak na razie Microsoft nie spieszy się z jego upowszechnieniem.

https://blogs.msdn.microsoft.com/dotnet/2013/09/30/ryujit-the-next-generation-jit-compiler-for-net/
https://github.com/dotnet/announcements/issues/10

A tu jest napisane, że RyuJIT ma być domyślny w .NET Core, ale nie w .NET Framework:
https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/

Rich Lander [MSFT]
Close Matt. dotnet/announcements #10 is specific to .NET Core. The question is about .NET Framework.

After we get a bit more mileage with the x86 RyuJIT with .NET Core, we could consider making it an opt-in feature for .NET Framework. We are unlikely to make it the default option (and certainly not initially) due to compatibility concerns. The JIT is used by all apps, so its behavior is pretty impactful. A change that negatively affects only 0.1% of apps is still a very big deal.

Jak dobra dewirtualizacja zagości w .NETowym JITu to w C# virtual będzie wszędzie :)

Apple w Swifcie idzie jeszcze w inną stronę ->

Na pierwszy rzut oka wygląda bardzo podobnie jak Rustowe traity, ale artykuł przejrzałem tylko na szybko, bez wczytywania się.

Poza tym, nie bardzo widzę jak metody wirtualne mogą być główną przyczyną popsutych hierarchii dziedziczenia. Nadpisywanie metod nieabstrakcyjnych nie zdarza się specjalnie często. A jak piszę w C++ to zdarza mi się zrobić dziwaczne hierarchie dziedziczenia nie pisząc ani jednej metody wirtualnej. Podajcie jakieś konkretne przykłady.

Napiszę jeszcze o ciekawym przypadku jakim jest Rust. W Ruście:

  • każda metoda jest wirtualna i niewirtualna jednocześnie. Sposób wywołania zależy od sposobu użycia obiektu (czyli struktury, krotki lub enuma). Jeśli obiektu użyjemy jako 'trait object' to użyte będą wywołania wirtualne. Jeśli obiektu użyjemy w inny sposób to wiązanie będzie na etapie kompilacji.
  • są traity (nie mylić z np Scalowymi traitami), które są interfejsami z metodami które mogą mieć domyślne implementacje. Interfejsy mogą po sobie dziedziczyć i przesłaniać domyślne implementacje metod.
  • implementacje traitów dla konkretnych typów obiektów mogą nadpisywać domyślne metody z tych traitów. Nie ma jednak dziedziczenia ani konkretnych typów obiektów (np nie ma dziedziczenia struktur) ani implementacji traitów dla nich.
  • sporo osób narzeka na ten brak dziedziczenia typowego dla OOP, bo kompozycja nie zawsze daje radę.
0
Wibowit napisał(a):

Microsoft tworzy nowego JITa o nazwie RyuJIT, który ma potencjał by zbliżyć się do Javy z wydajnością (w przypadku kodu, który nie jest tak pieczołowicie ręcznie optymalizowany jak to C#-owcy lubią robić), ale jak na razie Microsoft nie spieszy się z jego upowszechnieniem.

https://blogs.msdn.microsoft.com/dotnet/2013/09/30/ryujit-the-next-generation-jit-compiler-for-net/
https://github.com/dotnet/announcements/issues/10

A tu jest napisane, że RyuJIT ma być domyślny w .NET Core, ale nie w .NET Framework:
https://blogs.msdn.microsoft.com/dotnet/2017/06/29/performance-improvements-in-ryujit-in-net-core-and-net-framework/

RyuJIT w wersji 64 bitowej jest domyślnym jiterem od wersji NET Framework 4.6 (May 27, 2015):
https://blogs.msdn.microsoft.com/clrcodegeneration/2015/05/27/ryujit-and-net-4-6/
przytoczony przez Ciebie link dotyczy wersji 32 bitowej, na której mało komu zależy ...

Natomiast .net core pod względem wydajności już przegonił javke:
https://benchmarksgame.alioth.debian.org/u64q/csharp.html

RyuJIT jest jedno warstwowym jiterem, i to nad czym Microsoft obecnie pracuje to wprowadzenie warstwowej kompilacji na wzór Java Hotspot compiler, co by stosować bardziej agresywne optymalizacje takiej jak w javie. No ale póki co nawet bez tego net core wygrywa w benchmark game :)

1
neves napisał(a):

Natomiast .net core pod względem wydajności już przegonił javke:
https://benchmarksgame.alioth.debian.org/u64q/csharp.html

RyuJIT jest jedno warstwowym jiterem, i to nad czym Microsoft obecnie pracuje to wprowadzenie warstwowej kompilacji na wzór Java Hotspot compiler, co by stosować bardziej agresywne optymalizacje takiej jak w javie. No ale póki co nawet bez tego net core wygrywa w benchmark game :)

W benchmark game nie ma ani jednego interfejsu czy metody wirtualnej, więc są to zupełnie inne typy obciążenia niż typowe aplikacje biznesowe. Mimo wszystko gratulacje dla MS. Chociaż z drugiej strony to w C#-owych implementacjach są jakieś bloki "unsafe", surowe wskaźniki itd więc to jest pewnego rodzaju oszukiwanie, bo Javka musi robić więcej (tzn sprawdzać indeksy i wskaźniki), a nie po to się kodzi w językach zarządzanych, by mieć te same problemy co w C++.

Wielopoziomowy JIT w .NETu to też świetna wiadomość. Kilkanaście lat po Javie, ale w końcu .NET dorobi się porządnego JITa.

0

Natomiast .net core pod względem wydajności już przegonił javke:
https://benchmarksgame.alioth.debian.org/u64q/csharp.html

Czy to jest ok że ten benchmarki nie dają się rozgrzać JITowi ? nie ma tam żadnego warmupu.

EDIT

ok - by design XD https://benchmarksgame.alioth.debian.org/sometimes-people-just-make-up-stuff.html

4
Wibowit napisał(a):

Przeczytałem sobie wywiad z Andersem Hejlsbergiem nt tego czemu w C# jest w ogóle wymóg stosowania słowa virtual jeśli chcemy mieć metodę wirtualną: http://www.artima.com/intv/nonvirtual.html
Są dwa argumenty.

Pierwszy to wydajność i jego rozumiem. .NET ma słabego JITa i nie radzi sobie z metodami wirtualnymi. JVM natomiast nie ma problemu i nawet wirtualne gettery, settery, hashcode'y, equalsy itd nie powodują spadku wydajności.

To nie tak, że .NET ma słabego JITa - po prostu ponieważ mało metod jest wirtualnych, to dewirtualizacja nie była nigdy istotnym problemem. Gdyby JVM nie miał dewirtualizacji to by strasznie ssał wydajnościowo, więc musiano to zaimplementować. A Hejlsberg w przeciwieństwie do Ciebie widział Javę w czasach, gdy brak dewirtualizacji powodował problemy wydajnościowe.

Hejlsberg popełnił też błąd przy opisywaniu Javy: We can observe that as people write code in Java, they forget to mark their methods final. Therefore, those methods are virtual.. final wcale nie wyklucza wirtualności metody

Nie popełnił błędu, po prostu nie znasz Javy. To się nie skompiluje:

class C extends B {
  @Override
   void m() {
    System.out.println("C");
  }
}

Final kończy możliwości nadpisywania metody: You use the final keyword in a method declaration to indicate that the method cannot be overridden by subclasses. I o to mu chodziło, metoda jest wirtualna dopóki jej jawnie nie sfinalizujesz. A nadmiar wirtualnych metod to problem dla maszyny wirtualnej.

Drugi argument to jakieś bajeczki o wersjonowaniu i tym, że konflikty nazw w Javie powodują problemy przy aktualizacji wersji Javy. Tu się gość grubo pomylił, bo problemy z aktualizacją Javy wynikają z intensywnego użycia refleksji i wzbogacania bajtkodu. Jeżeli narzędzie analizujące bajtkod jest przystosowane do Javy X, a Java X+1 ma dodatkowe konstrukcje w bajtkodzie to to narzędzie się sypnie.

Tak, to on się pomylił, mimo że widział Javę wcześniej niż Ty, a języki programowania projektował kiedy jeszcze Ciebie na świecie nie było. :-)

Jak dla mnie to zabawa z override, new itd to próba ratowania syfiastego kodu (nadużywającego dziedziczenia) zamiast naprawiania go (poprzez kompozycję, czyli trzymanie się zasady SRP).

Tyle, że jak już wiemy kod nadużywający dziedziczenia łatwiej napisać w Javie, bo trzeba włożyć większy wysiłek, żeby dziedziczenia zabronić.

I w sumie po całej tej dyskusji zrozumiałem chyba narzekanie @jarekr000000 na IoC i interceptory. Faktycznie, skoro domyślnie każda metoda jest wirtualna, to nieostrożnie napisany i użyty interceptor może odstawiać w aplikacji jakąś niedebugowalną czarną magię. W C# nie ma tego problemu - od razu widać w kodzie, co potencjalnie może być wywoływane przez interceptor.

Wibowit napisał(a):

A więc według niego możliwość posiadania dwóch osobnych hierarchii metod wirtualnych (o tej samej sygnaturze) w jednej hierarchii dziedziczenia klasy to zaleta nad Javą i główny powód (bo drugi powód to kiepski JIT w .NETu) dla wprowadzenia takich kombinacji w C#. mind=blown

W ogóle nie zrozumiałeś problemu, który w ten sposób został rozwiązany:

Whenever they introduce a new method in a base class, if someone in a derived class had a method of that same name, that method is now an override—except if it has a different return type, it no longer compiles. The problem is that Java, and also C++, does not capture the intent of the programmer with respect to virtual.

W ogóle o tym rzekomym problemie z wersjonowaniem w Javie pierwszy raz przeczytałem właśnie w wywiadzie z Hejlsbergiem. Wygląda więc na to, że Hejlsberg "naprawił" problem który nie istnieje.

Tak właśnie było, sam widziałem! :-)

C# z powodu swojej alergii na metody wirtualne wybrał statyczne metody rozszerzające by dodać funkcjonalności do istniejących klas w bibliotece standardowej. Ma to tę wadę, iż konkretne klasy nie są w stanie bezpośrednio nadpisać nowej metody implementując funkcjonalność w bardziej efektywny sposób, tzn przy użyciu referencji o typie interfejsu zawsze zostanie użyta domyślna metoda statyczna (rozszerzająca).

To nie jest alergia na metody wirtualne - to pragmatyzm, w programowaniu występujący pod nazwą YAGNI, na który rzekomo też się powołujesz.
Podobnie jak pragmatyzmem jest czynienie rzeczy bezstanowych statycznymi.

Wibowit napisał(a):

Nie jestem spięty. Po prostu nie lubię jak ktoś mi wciska kit :]

No tak, o swój monopol trzeba dbać. ;)

0

Tak, to on się pomylił, mimo że widział Javę wcześniej niż Ty, a języki programowania projektował kiedy jeszcze Ciebie na świecie nie było. :-)

Bardzo rzeczowy argument. Może papieża mam teraz zacytować?

To nie tak, że .NET ma słabego JITa - po prostu ponieważ mało metod jest wirtualnych, to dewirtualizacja nie była nigdy istotnym problemem. Gdyby JVM nie miał dewirtualizacji to by strasznie ssał wydajnościowo, więc musiano to zaimplementować. A Hejlsberg w przeciwieństwie do Ciebie widział Javę w czasach, gdy brak dewirtualizacji powodował problemy wydajnościowe.

No i Microsoft zrzucił zadanie przyspieszania kodu na barki programistów C#. Pragmatyzm MS w pigułce, który pokutuje już kilkanaście lat. Prawdziwy pragmatyzm to skupienie się na logice biznesowej, a nie zastanawianiu się w jaki sposób pomóc słabemu kompilatorowi.

Poza tym, masz jakiś dowód, że w 2002 roku (rok wydania .NET Framework 1.0) dewirtualizacja w JVMie od Suna nie istniała? Ja nie jestem w stanie się doszukać.

No i najbardziej istotna kwestia: gdzie te przykłady pokazujące jakie te metody wirtualne są straszne? Chętnie bym takie zobaczył, a jedyne co widzę to tylko ideologie czy inne fanatyzmy.

0

Mojej pamięci bardzo nie ufam... ale w 2000 roku java 1.3 miała optymalizację wywołań polimorficznych. Ba! normalnie HotSpot je nawet inlinował jak się dało.
Kojarzę, bo to (miedzy innymi) mnie przekonało do wyjścia z grajdołka C++.

0
Wibowit napisał(a):

Bardzo rzeczowy argument. Może papieża mam teraz zacytować?

Jeśli dla kogoś papież jest autorytetem w IT, to współczuje kolegom z zespołu. Musi być im ciężko, gdy mimo modlitw kolegi, build się po raz kolejny wysypał.

Zarzucasz bezpodstawnie gościowi, który ma spore doświadczenie w tej branży, że "rozwiązywał nieistniejące problemy", nie jest to zbyt poważne.

No i Microsoft zrzucił zadanie przyspieszania kodu na barki programistów C#.

Niczego nie zrzucił, bo dużego problemu z tym nigdy nie było. Teraz mają czas na zabawy, to to optymalizują.

Prawdziwy pragmatyzm to skupienie się na logice biznesowej

No i np. po to wprowadzono lambdy i LINQ... Tak bardzo wyśmiewane przez Javowców przez 8 lat jako całkowicie zbędny cukier składniowy. Aż nadeszła Java 8 i zaczęła się nowa epoka hipokryzji w dziejach cywilizacji.

Poza tym, masz jakiś dowód, że w 2002 roku (rok wydania .NET Framework 1.0) dewirtualizacja w JVMie od Suna nie istniała? Ja nie jestem w stanie się doszukać.

Nie mam dowodu, ale wiem, że C# był projektowany wcześniej.

No i najbardziej istotna kwestia: gdzie te przykłady pokazujące jakie te metody wirtualne są straszne? Chętnie bym takie zobaczył, a jedyne co widzę to tylko ideologie czy inne fanatyzmy.

Nie chodzi o zło metod wirtualnych, ale nie otwieranie na dziedziczenie klas, które nie powinny być dziedziczone. O to, czemu nadmierne dziedziczenie jest złe, możesz spytać Seligi.

0

Jeśli dla kogoś papież jest autorytetem w IT, to współczuje kolegom z zespołu. Musi być im ciężko, gdy mimo modlitw kolegi, build się po raz kolejny wysypał.
Zarzucasz bezpodstawnie gościowi, który ma spore doświadczenie w tej branży, że "rozwiązywał nieistniejące problemy", nie jest to zbyt poważne.

Rozwój Javy przeczy jego obawom. Zamiast wierzyć, ufać i wielbić nieomylnego kolesia można skonfrontować jego teorie z faktami. Co prawda wysnuł swoje teorie dobre kilkanaście lat temu, a po czasie to każdy mądry, ale mimo wszystko obecna sytuacja w Javie przeczy jego założeniom.

Niczego nie zrzucił, bo dużego problemu z tym nigdy nie było. Teraz mają czas na zabawy, to to optymalizują.

Javowcy nie muszą się zastanawiać czy użyć metody wirtualnej czy nie. W Javce nie ma takiego dylematu. A wybór między metodą wirtualną, a niewirtualną jest moim zdaniem dalece bardziej trudny niż wybór między intem, a Integerem (tutaj wybór jest banalny - używamy inta tam gdzie się da, a gdzie się nie da używamy Integera).

No i np. po to wprowadzono lambdy i LINQ... Tak bardzo wyśmiewane przez Javowców przez 8 lat jako całkowicie zbędny cukier składniowy. Aż nadeszła Java 8 i zaczęła się nowa epoka hipokryzji w dziejach cywilizacji.

Nie wiem kto wyśmiewał, ale przymiarki do wprowadzenia lambd były już od wielu lat. Po drodze była sprzedaż Suna, poślizgi z Javą 7, wybranie planu B (czyli przesunięcia części bajerów z Javy 7 do Javy 8) i dlatego lambdy w Javce weszły późno. A zanim weszły to lukier składniowy oferował np IntelliJ, który sam zwijał SAMy w edytorze.

Ja za to czekam, aż C# pozbędzie się zbędnej zabawy ze słówkiem kluczowym virtual. Kiedy to może nastąpić?

Nie chodzi o zło metod wirtualnych, ale nie otwieranie na dziedziczenie klas, które nie powinny być dziedziczone. O to, czemu nadmierne dziedziczenie jest złe, możesz spytać Seligi.

Podaj link do jakichś konkretnych wypowiedzi Seligi. Nie jest moim autorytetem, więc oczekuję konkretów, a nie napinki (a on lubi się napinać).

Jak się okazuje Java ma od zarania dziejów narzędzie do organizacji kodu. Nazywa się to private package scope. Szczegóły tutaj: Toruń JUG #28 - "Keep IT clean, or how to hide your shit" Jakub Nabrdalik
Do tego w Javce 9 doszedł Jigsaw, więc można jeszcze inaczej ograniczać widoczność klas między modułami czy bibliotekami. C# pewnie też ma coś podobnego.

1
Wibowit napisał(a):

Rozwój Javy przeczy jego obawom. Zamiast wierzyć, ufać i wielbić nieomylnego kolesia można skonfrontować jego teorie z faktami. Co prawda wysnuł swoje teorie dobre kilkanaście lat temu, a po czasie to każdy mądry, ale mimo wszystko obecna sytuacja w Javie przeczy jego założeniom.

Ale on nie bazował na obecnej sytuacji tylko na tym, co widział w latach dziewięćdziesiątych.

Nie wiem kto wyśmiewał, ale przymiarki do wprowadzenia lambd były już od wielu lat.

Wszyscy ci, którzy twierdzili, że C# w porównaniu z Javą ma tylko zbędny cukier składniowy. Na czele z Tobą.

Ja za to czekam, aż C# pozbędzie się zbędnej zabawy ze słówkiem kluczowym virtual. Kiedy to może nastąpić?

Nigdy - to po pierwsze byłoby ogromne breaking change, po drugie byłoby sprzeczne z ideą języka, po trzecie nikomu do niczego nie jest potrzebne i nikt nie jest szalony aby taki krok wstecz postulować.
Równie dobrze mógłbyś czekać aż Java pozbędzie się zbędnej zabawy z typami danych.

Podaj link do jakichś konkretnych wypowiedzi Seligi. Nie jest moim autorytetem, więc oczekuję konkretów, a nie napinki (a on lubi się napinać).

W którejś ze swoich słynnych prezentacji o tym mówił. Na tłumaczenie czemu nadużywanie dziedziczenia jest złe i należy preferować kompozycję nie mam czasu, to zbyt podstawowa wiedza.

0

Ale on nie bazował na obecnej sytuacji tylko na tym, co widział w latach dziewięćdziesiątych.

A konsekwencje jego decyzji są do dziś.

Wszyscy ci, którzy twierdzili, że C# w porównaniu z Javą ma tylko zbędny cukier składniowy. Na czele z Tobą.

A zacytuj :) Gdzie napisałem, że lambdy są zbędne?

Nigdy - to po pierwsze byłoby ogromne breaking change, po drugie byłoby sprzeczne z ideą języka, po trzecie nikomu do niczego nie jest potrzebne i nikt nie jest szalony aby taki krok wstecz postulować.
Równie dobrze mógłbyś czekać aż Java pozbędzie się zbędnej zabawy z typami danych.

Przy wprowadzaniu generyków w C# Microsoft zdecydował się na szalony krok stworzenia niekompatybilnego zestawu kolekcji standardowych. Dlaczego tamto wydarzenie było niewystarczająco szalone by go unikać? Czyżby dlatego, że C# 1.0 był jeszcze mało popularną niszą i można było go dowolnie zmieniać?

W którejś ze swoich słynnych prezentacji o tym mówił. Na tłumaczenie czemu nadużywanie dziedziczenia jest złe i należy preferować kompozycję nie mam czasu, to zbyt podstawowa wiedza.

Ja zaczynam sobie od dziedziczenia, a jak widzę że można mieć wymierny zysk z kompozycji to wprowadzam kompozycję. Zamiana dziedziczenia na kompozycję to zupełnie inna sprawa niż zakopywanie się z jakimiś mikro-optymalizacjami dotyczącymi słówka virtual.

0
Wibowit napisał(a):

A zacytuj :) Gdzie napisałem, że lambdy są zbędne?

Nie będę przekopywał wszystkich hejtów, które pod adresem C# napisałeś do tej pory, sam sobie przeszukaj. :)

Przy wprowadzaniu generyków w C# Microsoft zdecydował się na szalony krok stworzenia niekompatybilnego zestawu kolekcji standardowych. Dlaczego tamto wydarzenie było niewystarczająco szalone by go unikać? Czyżby dlatego, że C# 1.0 był jeszcze mało popularną niszą i można było go dowolnie zmieniać?

Ale stare kolekcje wciąż zostały w i są dostępne nawet dzisiaj, co więcej nie są nawet oznaczone jako obsolete. Kompilacja starego kodu dla frameworka 2.0 nie powodowała błędów kompilacji ani nie zmieniała jego sposobu działania. Uczynienie metod domyślnie wirtualnymi wymagałoby usunięcia z języka modyfikatorów virtual, new i być może override, stary kod mógłby się przestać kompilować albo zacząć działać inaczej. Nie warto tego robić dla tak bardzo nieistotnej i nikomu normalnego nieprzeszkadzającej podstawy języka.
Zwłaszcza, że już niedługo i tak będzie podobnej wielkości problem z wprowadzeniem nullable reference types w C# 8. Tylko to akurat jest znacznie ważniejsze i warte dużego zamieszania.

Ja zaczynam sobie od dziedziczenia, a jak widzę że można mieć wymierny zysk z kompozycji to wprowadzam kompozycję. Zamiana dziedziczenia na kompozycję to zupełnie inna sprawa niż zakopywanie się z jakimiś mikro-optymalizacjami dotyczącymi słówka virtual.

Dobrzy programiści najpierw się zastanawiają, czy potrzebne będzie dziedziczenie czy kompozycja, czy dziedziczenie w ogóle ma sens w danym przypadku - a ma go raczej w niewielu, a potem to implementują. W przeciwnym razie wychodzą albo kosmicznie koszmarne hierarchie dziedziczenia albo programowanie przez permutacje.

1 użytkowników online, w tym zalogowanych: 0, gości: 1