Co miał na myśli Rich Hickey, gdy powiedział: „Cała ta specyfika [interfejsów / klas / typów] zabija twoje ponowne użycie!”

41

W inspirującej myśli Richa Hickeya na konferencji goto „ Wartość wartości ” po 29 minutach mówi o narzutach związanych z językiem takim jak Java i mówi: „Wszystkie te interfejsy zabijają ponowne użycie”. Co on ma na myśli? Czy to prawda?

W poszukiwaniu odpowiedzi natknąłem się na:

  • Zasada najmniejszej wiedzy AKA Prawo Demeter, które zachęca do szczelnych interfejsów API. Wikipedia wymienia również niektóre wady.

  • Kryzys odzieżowy Kevlina Henneya, który dowodzi, że używanie, a nie ponowne wykorzystywanie jest właściwym celem.

  • Wystąpienie Jacka Diedericha „ Stop Writing Classes ” przemawia ogólnie przeciwko nadmiernej inżynierii.

Oczywiście wszystko, co napisano wystarczająco źle, będzie bezużyteczne. Ale w jaki sposób interfejs dobrze napisanego API zapobiegnie użyciu tego kodu? W całej historii są przykłady czegoś, co zostało wykonane w jednym celu i jest wykorzystywane bardziej do czegoś innego . Ale w świecie oprogramowania, jeśli używasz czegoś do celu, do którego nie było przeznaczone, zwykle się psuje.

Szukam jednego dobrego przykładu dobrego interfejsu uniemożliwiającego prawidłowe, ale niezamierzone użycie jakiegoś kodu. Czy to istnieje? Nie mogę tego sobie wyobrazić.

GlenPeterson
źródło
1
Nie oglądałem / nie przeczytałem (dodałem „Stop Writing Classes” do mojej listy do obejrzenia :)), ale może kłócą się pod kątem pisania dynamicznego vs. statycznego? ...jeszcze raz?
Andres F.,
oO Interfejsy programowania aplikacji
Thomas Eding
Dzięki za linki! Uważam, że przemówienie Jacka Diedericha nie jest szczególnie pouczające (patrz, jak nie odpowiada w przekonujący sposób na prawdziwe pytania publiczności ... „och, tak, może w tym przypadku ...”. Podobało mi się, że wydaje się, że kłóci się o programowanie funkcjonalne bez nawet to zauważam;)), ale „kryzys odzieży imperialnej” jest bardzo dobry i wnikliwy.
Andres F.,
1
MPO polega na tym, że ludzie, którzy nie wierzą w ponowne użycie, nie dzielą rzeczy na wystarczająco małe jednostki. Duża rzecz zbudowana dla jednego określonego celu nie może być ponownie użyta. Jednak małe rzeczy zwykle mają wystarczająco mały cel, aby mały cel był użyteczny w więcej niż jednym kontekście.
Amy Blankenship
1
@AmyBlankenship Uważam, że „Imperialny kryzys odzieżowy”, o którym mowa powyżej, jest wnikliwy. Autor uważa „ponowne użycie” za fałszywego idola (coś, co nie okazało się przydatne w praktyce, a także większość ludzi nawet tego nie rozumie, mimo że używa tego słowa). Nie uważa też bibliotek za „ponowne użycie”; Państwo korzystać z biblioteki, nie ponowne wykorzystanie go. Rozważa też zaprojektowanie czegoś do ponownego użycia „miecza obosiecznego”; coś, co ludzie zwykle uważają za korzystną sytuację, ale tak naprawdę nie jest: kiedy projektujesz coś do ponownego użycia, zawsze jest to kompromis (np. możesz stracić prostotę)
Andres F.

Odpowiedzi:

32

Nie oglądałem pełnej prezentacji Richa Hickeya, ale jeśli dobrze go rozumiem i sądząc po tym, co mówi o 29-minutowej ocenie, wydaje się, że kłóci się o typy zabijające ponowne użycie. Termin „interfejs” używa luźno jako synonim „nazwanego typu”, co ma sens.

Jeśli masz dwie jednostki { "name":"John" }typu Personi { "name": "Rover" }typu Dogw Javie, prawdopodobnie nie będą mogły współpracować, chyba że będą miały wspólny interfejs lub przodka (np. MammalCo oznacza pisanie większej ilości kodu). Tak więc interfejsy / typy „zabijają twoje ponowne użycie”: chociaż Personi Dogwyglądają tak samo, jednego nie można używać zamiennie z drugim, chyba że napiszesz dodatkowy kod, który to obsługuje. Uwaga Hickey żartuje także z projektów w Javie wymagających dużej liczby klas („Kto tutaj napisał aplikację Java z wykorzystaniem zaledwie 20 klas?”), Co wydaje się jedną z konsekwencji tego.

Jednak w językach „zorientowanych na wartość” nie będziesz przypisywać typów do tych struktur; są to tylko wartości, które dzielą tę samą strukturę (w moim przykładzie oba mają namepole z wartością String) i dlatego mogą łatwo współpracować, np. można je dodać do tej samej kolekcji, przekazać do tych samych metod itp.

Podsumowując, wydaje się, że wszystko to dotyczy równości strukturalnej vs. jawnej równości typu / interfejsu . Chyba że coś przeoczyłem z fragmentów filmu, których jeszcze nie obejrzałem :)

Andres F.
źródło
2
BTW, przemówienie Jacka Diedericha „Przestań pisać klasy” wydaje się niezwiązane z tym tematem i dotyczy bardziej YAGNI i „nie pisz kodu, dopóki go nie potrzebujesz, a potem pisz tylko prosty kod”.
Andres F.,
9
ERROR: Object doesn't have a property called "name"jest często wynikiem value-orientedjęzyków, a drugim problemem jest to, że nie chcesz już wywoływać tej właściwości name. Refaktoryzacja powodzenia, ponieważ prawdopodobnie istnieją setki obiektów z właściwością, nameale nie wszystkie są Personlub Dog.
Reactgular
2
@MathewFoscarini Tak, niekoniecznie się z tym zgadzam, to tylko moja interpretacja tego, co według mnie Hickey mówił :) Lubię typy i pisanie statyczne; Zaczynam nie lubić Java. I moja niechęć nie ma związku z interfaces, ale z bałaganem, który jest typowym projektem Java.
Andres F.,
1
Java to język programowania dla tych, którzy wolą za dużo myśleć. Jest to jeden z nielicznych języków, który pozwala programistom z łatwością ukryć jego próby przebudowania projektu.
Reactgular
„W językach„ zorientowanych na wartość ”nie będziesz przypisywać typów do tych struktur” - myślę, że powinieneś powiedzieć „W dynamicznych” zorientowanych na wartości ”… Haskell i Scala są zorientowani na wartości, ale ich statyczny system typów daje im dokładnie opisany problem. Myślę, że rozwiązaniem tego problemu są nie tyle wartości, co użycie map do przekazania parametrów do funkcji. Korzystanie z niezmiennych map (wartości) jest po prostu bezpieczniejsze.
GlenPeterson
28

Prawdopodobnie odnosi się do podstawowego faktu, że interfejsu nie można utworzyć. Nie możesz reuseinterfejsu. Możesz zaimplementować tylko kod, który go obsługuje, a kiedy piszesz kod interfejsu, nie ma możliwości ponownego użycia.

Java ma historię dostarczania struktur wielu interfejsów API, które przyjmują interfejs jako argument, ale zespół, który opracował interfejs API, nigdy nie wdrożył szerokiej gamy klas do ponownego użycia z tymi interfejsami.

To coś w rodzaju frameworku GUI z IWindowinterfejsem do okna dialogowego, a następnie można dodawać IButtoninterfejsy do kontrolek. Tyle że nigdy nie dali ci dobrej Buttonklasy, która implementuje IButton. Pozostajesz więc tworzyć własne.

Wyodrębnione frameworki, które mają szeroki zakres klas bazowych zapewniających podstawowe funkcje, są bardziej wielokrotnego użytku i działają najlepiej, gdy te abstrakcyjne klasy są dostępne dla osób korzystających z frameworka.

Programiści Java zaczęli to robić, gdy ich warstwy API były widoczne interfaces. Można zaimplementować te interfejsy, ale nigdy nie można ponownie użyć klas od programisty, który zaimplementował te interfejsy. To trochę jak peleryna i sztylet w stylu API.

Reactgular
źródło
4
Dziękuję za tę odpowiedź. Teraz czuję, że rozumiem pytanie i odpowiedź :)
MetaFight
2
+1 Naprawdę doceniam twoją odpowiedź i dodaje fascynującą warstwę interesujących informacji do tego pytania. Ale myślę, że odpowiedź Andreasa F. jest prawdopodobnie bliżej sedna tego, co miał na myśli pan Hickey, więc zamiast tego zaakceptowałem jego odpowiedź.
GlenPeterson
@GlenPeterson nie ma problemu, myślę, że on też może być na dobrej drodze.
Reactgular
1
Cóż, ta i przyjęta odpowiedź podkreślają dwie nieco odmienne, ale równie interesujące interpretacje. Jestem ciekawy, który z nich miał na myśli pan Hickey, mówiąc o tym ..
David Cowden
Nie możesz ponownie użyć interfejsu, ale możesz go rozszerzyć (i podać cenny numer wersji) bez zmiany starych klas. Możesz także dziedziczyć z wielu interfejsów, aby dodać nowe zadanie dla nowych klas lub dodać nowe dziedziczenie w starych, ponownie skompilowanych klasach. Możesz także rozszerzyć klasę, która implementuje ten interfejs dla nowego zadania.
cl-r
14

Myślę, że slajd 13 w jego prezentacji ( The Value of Values ) pomaga to zrozumieć:

http://i.stack.imgur.com/LVMne.png


Wartości

  • Nie potrzebuję metod
    • Mogę przesłać ci wartości bez kodu
      i wszystko w porządku

Rozumiem, że Hickey sugeruje, że jeśli muszę, powiedzmy, podwoić wartość, którą mi przesłałeś, po prostu piszę kod wyglądający jak

    MyValue = Double(YourValue)

Widzisz, powyższy kod jest taki sam, bez względu na to, jaką wartość przesłałeś - coś w rodzaju idealnego ponownego wykorzystania .

Jak by to wyglądało w języku posiadającym obiekty i interfejsy?

    Doublable MyValue = YourValue.Double()

zaczekaj! co jeśli YourValuesię nie wdraża Doublable? nie to, że nie można go podwoić, może być idealnie, ale ... co jeśli nie ma po prostu żadnej metody Double ? (a jeśli istnieje metoda o nazwie powiedz TwiceAsMuch?)

Och, mamy problem. YourValue.Doublenie będzie działać, nie będzie można go ponownie wykorzystać . Według mojej lektury powyższego slajdu chodzi o to, co Hickey miał na myśli, mówiąc: „Wszystkie te interfejsy zabijają twoje ponowne użycie!”

Widzicie, interfejsy zakładają, że obiekty są przekazywane „wraz z ich metodami” wraz z działającym na nich kodem. Aby używać obiektów, należy zrozumieć, jak wywołać ten kod i jaką metodę wywołać.

Gdy brakuje oczekiwanej metody , pojawia się problem, chociaż semantycznie pożądana operacja ma idealny sens dla obiektu. Jak stwierdzono w prezentacji, wartości nie potrzebują metod („Mogę przesyłać ci wartości bez kodu i wszystko w porządku”), co pozwala pisać kod, który zajmuje się nimi w sposób ogólny.


Uwaga dodatkowa: pojęcie przekazywania wartości bez kodu w jakiś sposób przypomina mi wzór Flyweight w OOP.

obiekt, który minimalizuje zużycie pamięci, udostępniając jak najwięcej danych innym podobnym obiektom; jest to sposób na użycie obiektów w dużych ilościach, gdy prosta powtarzana reprezentacja wymagałaby niedopuszczalnej ilości pamięci ... Obiekty Flyweight są z definicji obiektami o wartości . Tożsamość instancji obiektu nie ma znaczenia, dlatego dwa wystąpienia Flyweight o tej samej wartości są uważane za równe ...

Zastosowania Flyweight zwykle widziałem to samo podejście do usuwania kodu (metod, interfejsów) z obiektów i przekazywania rzeczy dookoła, a także wartości bez kodu , spodziewając się, że kod otrzymujący ma środki niezbędne do działania na nich.

Czuje się to tak, jak na slajdzie: „wartości nie wymagają metod. Mogę przesyłać ci wartości bez kodu i wszystko jest w porządku”.

komar
źródło
5
Generics właściwie zająłby się tym problemem. Podwojenie ma sens w przypadku niektórych obiektów, ale nie w przypadku innych. W języku Go istnieje niejawna implementacja interfejsu (forma pisania kaczego), więc nie musisz się martwić wszystkimi interfejsami. Z drugiej strony, musisz wiedzieć, który obiekt zostanie trafiony przez twoją sygnaturę metody; w przeciwnym razie możesz uzyskać nieoczekiwane wyniki. Zawsze są kompromisy.
Robert Harvey
1
Ciekawe ujęcie. Dobra odpowiedź!
wałek klonowy
2
Wzór flyweight nie jest tym, o czym mówi Rich. Jak stwierdza drugie zdanie tego artykułu, celem wzoru muchy jest zachowanie pamięci. Podejście Richa nie dąży do tego.
5
MyValue = Double(YourValue)nie ma sensu, jeśli YourValue jest ciągiem, adresem, użytkownikiem, funkcją lub bazą danych. W przeciwnym razie argument braku metody jest silny. OTOH, metody akcesora pozwalają egzekwować różne ograniczenia, aby Twoje Wartości były ważne, i aby tylko nowe rozsądne operacje były używane do tworzenia nowych Wartości. Jeśli później zdecydujesz się oddzielić adres od użytkownika i firmy, metody dostępu oznaczają, że nie złamiesz wszystkich klientów kodu. Mogą więc pomóc w ponownym użyciu w perspektywie długoterminowej, nawet jeśli czasami utrudniają to w perspektywie krótkoterminowej.
GlenPeterson
4
(Z drugiej strony zgadzam się, że w Javie eksplozja klas, interfejsów i frameworków to koszmar. Najprostsze rozwiązanie „przedsiębiorcze” w Javie to bałagan kodu. Wyciągam z tego cenną lekcję pytanie i odpowiedź, niekoniecznie zgadzając się z dynamicznym pisaniem na klawiaturze)
Andres F.,
2

W (tj. Moim) idealnym świecie klasy i interfejsy zawsze opisywałyby zachowanie, ale faktem jest, że zbyt często tak naprawdę kończą opisywanie danych. Dopiero wczoraj obejrzałem wideo kogoś, kto buduje tak zwaną BankAccountklasę, która była niczym innym jak tylko uwielbioną int(w rzeczywistości była znacznie mniej przydatna niż int, a zatem „zabijała” ponowne użycie, które miałbym, gdyby był po prostu pozostawiony jako int), wszystko w imię „dobrego” projektu. Ilość kodu, potu i łez zmarnowanych na ciągłe wymyślanie skomplikowanych reprezentacji danych jest oszałamiająca; jeśli nie używasz danych w znaczący sposób, pozwól, aby tak było.

Na tym etapie Rich Hickey jest zadowolony z wyrzucenia dziecka z kąpielą i powiedzenia, że ​​wszyscy powinniśmy żyć w krainie wartości (sąsiedzi królestwa rzeczowników). Z drugiej strony myślę, że OOP może i promuje ponowne wykorzystanie (i, co ważne, wykrywalność, której brakuje w programowaniu funkcjonalnym), gdy jest rozsądnie stosowane. Jeśli szukasz zasady OOP, która najlepiej wychwytuje to napięcie, myślę, że może to być http://c2.com/cgi/wiki?TellDontAsk (która oczywiście jest bliską kuzynką Demeter)

CurtainDog
źródło
Co masz na myśli mówiąc o wykrywalności? Czy to jest podobne do tego ?
1
Tak, myślę, że dotyczy to wielu głównych punktów. Jest to subtelna kwestia, ale wykrywalność jest działaniem równoważącym, dlatego też zbytnie przezroczystość jest niepożądana, ponieważ otrzymasz zły stosunek sygnału do szumu.
CurtainDog