Czy przechodnie zależności są zawsze problemem?

0

Zastanowił mnie wpis na mikro a konkretniej fragment:

node.js z ich cudacznym środowiskiem i śmietnikiem jaki się wciąga przy każdej próbie spakowania wszystkiego do kupy (bo x polega na y, które polega na z, które wciąga niesławne is-even`

Czy takie przechodnie zależności są zawsze problemem czy tylko jak trzeba optymalizować wielkość wynikowej paczki? Np dla Haskella są dwie biblioteki do testów wydajnościowych criterion i gauge. Mają to samo api a główną zaletą gauge jest to że ma tylko 12 zależności a criterion ma - 63. Za to główną zaletą criteriona jest to że jest pobłogosławiony przez twórców Haskella (nie wiem na czym to błogosławieństwo polega konkretnie, ale repo criteriona jest w organizacji haskell na githubie

Pewnie też zalezy dużo od technologii w której piszemy. W JVM jest nierozwiązywalny (łatwo) problem gdy chcemy użyć bibliotek A i B a A zależy od C1, B zależy od C2 a C1 i C2 to ta sama biblioteka ale w różnych wersjach i zróżnym api. Ale załóżmy że środowisko poprawnie rozwiązuje takie sytuacje (mam wrażenie że Haskell rozwiązuje to dobrze i słyszałem że C# też dobrze)

1

To zawsze problem. Wydaje mi się, że w JSie jest to widoczne dużo bardziej, bo:

  • nie ma core bibliotek i potrzeba takich cudów jak is-even przez co drzewo zależności jest głębsze i jest większa szansa, że coś się wysypie na którymś poziomie

  • tego typu biblioteki nie powstają przez autorytety (fundacje, duże firmy) tylko przez pasjonatów

    czy tylko jak trzeba optymalizować wielkość wynikowej paczk

Tutaj widzę większy problem w toolingu. Normalnie używanie liba powinna działać w taki sposób, że tylko używane funkcje są wciągane do wynikowego artefaktu. Nawet w C/C++ da się to osiągnąć stosując LTO, który usuwa symbole, które nie są osiągalne. Nie wiem jak jest z JS/Haskellem. Zgaduje, że w JSie może to być trudne przez to jak dużo zależy od kontekstu

Pewnie też zalezy dużo od technologii w której piszemy. W JVM jest nierozwiązywalny (łatwo) problem gdy chcemy użyć bibliotek A i B a A zależy od C1, B zależy od C2 a C1 i C2 to ta sama biblioteka ale w różnych wersjach i zróżnym api. Ale załóżmy że środowisko poprawnie rozwiązuje takie sytuacje (mam wrażenie że Haskell rozwiązuje to dobrze i słyszałem że C# też dobrze)

IMO ekosystem Go fajnie rozwiązuje ten problem nie rozwiązując go wcale. W zamyśle kolejne wersje bibliotek mają być kompatybilne. A co jak nie są? Robisz nową bibliotekę więc masz dwie foo i foo/v2. Co jak ktoś wprowadza zmiany, które nie są wstecz kompatybilne? Najlepiej przestać używać takiej biblioteki

3

W node.js foldery node_modules potrafią się zagnieżdżać i instalując jedną paczkę, dociągamy także zależności z tamtej paczki w postaci kolejnego node_modules.

screenshot-20220520131336.jpg

Wszystkie foldery node_modules z tego projektu mają 64 356 plików i ważą 410 MB, czyli jest ich z 20 razy więcej, niż mojego kodu :D

Oczywiście robiąc produkcyjny build projektu to 99% z tych plików nie trafi do naszej finalnej paczki, którą dostaniemy, bo znajdują się tam rzeczy używane głównie podczas developmentu

  • narzedzia do statycznej analizy kodu (eslint, prettier),
  • server developerski do uruchemia projektu,
  • narzędzia do testowania
  • itd
1

nie ma core bibliotek i potrzeba takich cudów jak is-even

No ale bez jaj... Do czegoś takiego ściągać specjalną bibliotekę? Litości. Funkcję sprawdzającą parzystość jest w stanie napisać ktoś po kilku pierwszych lekcjach programowania, a dla dowolnego stażysty czy juniora to jest coś, czego nieumiejętność napisania w ciągu więcej niż 3-4 minut powinno skutkować natychmiastowym zakończeniem jakichkolwiek rozmów czy współpracy.

czy tylko jak trzeba optymalizować wielkość wynikowej paczki?

Myślę, że nie wielkość paczki ma tutaj główne znaczenie, zwłaszcza w obecnych czasach, gdzie standardem są łącza kilkaset Mbit i szybsze oraz dyski po kilka TB. Tutaj chodzi raczej o to, że po pierwsze - im więcej dziwnych zależności (nad którymi nie panujesz, bo to jakieś zewnętrzne kawałki kodu) tym większa szansa, że ktoś coś zmieni albo wycofa i nagle Twój projekt się posypie, bo korzystasz z biblioteki A, która opiera się na B, które potrzebuje do działania C, a to C jest napisane tak, że potrzebuje D, które przestało działać, bo ktoś wycofał bibliotekę E ;)

Zresztą takich afer w stylu is-even (albo jak kto woli - odd) było pełno - ktoś coś wycofał, celowo zmienił sposób działania, podstawił złośliwy kod itp. Zwłaszcza, że o ile opieranie się o kod stworzony przez coś dużego i szanowanego jest w miarę bezpiecznie, ale im większe się robi drzewko zależności, wym większa szansa, że któryś z elementów układanki będzie miało wiarygodny. Ale potem to Ty będziesz miał problemy i robił z siebie idiotę, że Twoja apka nie działa. A tłumaczenia w stylu bo jakiś ziomek zablokował bibliotekę z której korzysta coś 5 poziomów pod moją apką będzie słabe i mało profesjonalne.

Wszystkie foldery node_modules z tego projektu mają 64 356 plików i ważą 410 MB, czyli jest ich z 20 razy więcej, niż mojego kodu
[...]
Oczywiście robiąc produkcyjny build projektu to 99% z tych plików nie trafi do naszej finalnej paczki

Tutaj pełna zgoda - to, że rozmiar projektu jest jakiś absurdalny nie jest jakimś wielkim problemem. Pobranie tych 400MB to dosłownie 2-3 minuty, robisz to raz na początku a potem sobie już to siedzi na dysku. Powtarzam - problemem jest brak pełnej kontroli nad tym, co jest niezbędne do działania projektu. Oczywiście - zawsze jest takie ryzyko, ale jednak mniejsza szansa jest, że Microsoft wyłączy dotNeta, niż że jakiś koleś z Argentyny, które swoje biblioteki udostępnia w ramach własnej satysfakcji, nagle się obrazi na cały świat i zaorze projekt.

3
cerrato napisał(a):

nie ma core bibliotek i potrzeba takich cudów jak is-even

No ale bez jaj... Do czegoś takiego ściągać specjalną bibliotekę? Litości. Funkcję sprawdzającą parzystość jest w stanie napisać ktoś po kilku pierwszych lekcjach programowania, a dla dowolnego stażysty czy juniora to jest coś, czego nieumiejętność napisania w ciągu więcej niż 3-4 minut powinno skutkować natychmiastowym zakończeniem jakichkolwiek rozmów czy współpracy.

Kod biblioteki:

const isNumber = require('is-number');

module.exports = function isOdd(value) {
  const n = Math.abs(value);
  if (!isNumber(n)) {
    throw new TypeError('expected a number');
  }
  if (!Number.isInteger(n)) {
    throw new Error('expected an integer');
  }
  if (!Number.isSafeInteger(n)) {
    throw new Error('value exceeds maximum safe integer');
  }
  return (n % 2) === 1;
};

Jak dla mnie to dużo bardziej skomplikowane niż i%2 == 1, bo JS jest takim językiem. W żaden sposób się nie dziwię, że ktoś woli pobrać taki kod zamiast pisać go z palca, bo szansa na zrobienie czegoś źle jest duża.

1

Dodam jeszcze, że w innych językach często nie ma takich problemów, bo community nie potrafi into modularność i reużycie kodu (ach to OOP). Dobrym przykładem jest java, gdzie większość rozwiązań open source gryzie się ze sobą bo dostajesz ogromną kobyłę typu Spring, Guava czy Apache Commons z którą kolejne biblioteki nie chcą się integrować. Takie is-number czy is-even to szczerze mówiąc super pomysł, bo możemy użyć tego samego kodu wiele razy dzięki dobremu, prostemu designowi i małej ilości kodu w bibliotece. Przecież na tym ma polegać programowanie: na łączeniu gotowych klocków

To gdzie widzę problem to ludzie, którzy uznali, że używanie bibliotek, gdzie autor może wprowadzić dowolną minę do istniejącej biblioteki jest ok. Może jakieś automatyczne testy, które musiałyby przechodzić dla wszystkich kolejnych wersji rozwiązałyby ten problem?

1

jak dla mnie to dużo bardziej skomplikowane niż i%2 == 1, bo JS jest takim językiem. W żaden sposób się nie dziwię, że ktoś woli pobrać taki kod zamiast pisać go z palca, bo szansa na zrobienie czegoś źle jest duża

OK, masz rację że nie jest to i%2 == 1, ale mimo wszystko - skoro taka funkcja jest potrzebna, to można ją stworzyć samodzielnie. I potem stworzyć jakieś własne repo różnych utilsów, które będą linkowane w moich projektach. Myślę, że napisanie tego, co podlinkowałeś - łącznie z samodzielnym ogarnięciem isNumber (na co @KamilAdam słusznie zwrócił uwagę, że jest absurdem) i przetestowaniem to niech będzie godzina. Czy warto poświęcić godzinę, żeby mieć własny kod, który jest niezależny od widzimisię jakiegoś anomomowego developera z Kazachstanu czy innego Bukaresztu? Moim zdaniem - jak najbardziej. Warto zainwestować tą godzinę pracy, żeby uzyskać niezależność i zabezpieczyć apkę przed dziwnymi ruchami twórców bibliotek, na których bazujemy (i których nieraz nie potrzebujemy).

2

@cerrato:

zagram adwokata diabła

Czy warto poświęcić godzinę, żeby mieć własny kod, który jest niezależny od widzimisię jakiegoś anomomowego developera z Kazachstanu czy innego Bukaresztu? Moim zdaniem - jak najbardziej.

nie, po co?

Moim zdaniem - jak najbardziej. Warto zainwestować tą godzinę pracy, żeby uzyskać niezależność i zabezpieczyć apkę przed dziwnymi ruchami twórców bibliotek, na których bazujemy (i których nieraz nie potrzebujemy).

możesz się locknąć na danej wersji libki przecież, a przed updatem zerknąć na diffa i wykonać audyt

I potem stworzyć jakieś własne repo różnych utilsów, które będą linkowane w moich projektach.

no ale po co? w ten sposób tylko stworzyłeś kolejną company_library, której pewnie i tak nie będziesz utrzymywał zamiast użyć OSSowej za którą stoją jacyś ludzie i ją utrzymują

1

Za którą stoją jacyś ludzie i ją utrzymują

No właśnie o tym przecież pisałem :p o ile jeszcze są libki tworzone i/lub utrzymywane przez jakieś wiarygodne osoby czy organizacje albo firmy, to część z nich jest stworzona przez jakiegoś pasjonata czy amatora. I nie jest to nic złego, ale były już przypadki, że ktoś się obraził i wycofał swój projekt albo złośliwie coś w nim zmienił, przez co nagle połowa projektów w JS się wychrzaniła.

I żeby była jasność - nie twierdzę, że wszystko masz pisać sam, ale korzystanie z zewnętrznej biblioteki do zrobienia czegoś trywialnego - typu sprawdzania czy coś jest liczbą albo czy jest parzysta to mocne przegięcie.

stworzyłeś kolejną company_library

No i co w tym złego? Każda firma ma swoje rozwiązania, swoją bazę kodu, który wcześniej był już wykorzystany i nadaje się do adaptacji w kolejnych projektach. Na zasadzie "robiłem już cos bardzo podobnego, więc sobie skopiuje fragment projektu A do projektu C, nad którym teraz siedzę". A taka wewnętrzna libka jest czymś podobnym, tylko bardziej formalnie zorganizowanym. A co do jej utrzymania - jeśli działa, to nie ruszaj ;)

możesz się locknąć na danej wersji libki przecież

Mogę, ale za rok zapomnę, że muszę dodać akurat wersję 1.7.0.12.b3. albo przyjdzie ktoś nowy i po prostu doda gdzieś do zależności tą bibliotekę, nie wiedząc, że wymaga ona wskazania wersji. Plus jeszcze inna opcja - projekt działa i nagle jakaś libka się zmienia. Ile czasu zajmie ci dojście do tego, że wywalenie się apki nie ma związku z rzeczami, w których ostatnio grzebałes, tylko jakimiś zmianami w jakiejś wykorzystywanej (niekoniecznie bezpośrednio, moze być ona zassana kilka poziomów niżej) biblioteki? Zwłaszcza, że ta zmiana nie musi wychrzanic builda, ale jedynie w jakiś sposób zmienić sposób jej zachowania.

nie, po co?

Tak, to ma sens :p

5

@cerrato:

I żeby była jasność - nie twierdzę, że wszystko masz pisać sam, ale korzystanie z zewnętrznej biblioteki do zrobienia czegoś trywialnego - typu sprawdzania czy coś jest liczbą albo czy jest parzysta to mocne przegięcie.

Bo problemem jak dla mnie jest JavaScript i jak to tam jest rozwiązane. w Javie od takich róznych tzw. d*pereli masz jakieś Apache Commons czy Guave (biblioteka od Googla) i do tego żeby nie robić jakiś swoich StringUtils czy tym FileUtils potrzebujesz jednej czy dwóch zalezności, a w JavaScripcie potrzebowalbyś pewnie z 30 czy 40 ;]

1
slsy napisał(a):

Dodam jeszcze, że w innych językach często nie ma takich problemów, bo community nie potrafi into modularność i reużycie kodu (ach to OOP). Dobrym przykładem jest java, gdzie większość rozwiązań open source gryzie się ze sobą bo dostajesz ogromną kobyłę typu Spring, Guava czy Apache Commons z którą kolejne biblioteki nie chcą się integrować.

Spring to przegięcie w kompletnie drugą stronę. Ciężko uznać, ze to jest dobry argument na rzecz przechodnich zależności, bo sam wprowadza ich tonę.

Takie is-number czy is-even to szczerze mówiąc super pomysł, bo możemy użyć tego samego kodu wiele razy dzięki dobremu, prostemu designowi i małej ilości kodu w bibliotece. Przecież na tym ma polegać programowanie: na łączeniu gotowych klocków

Te biblioteki trochę przypominają żarty o socjalizmie, osiągającym mistrzostwo w rozwiązywaniu samodzielnie tworzonych problemów. Jeżeli przed sprawdzeniem parzystości numeru trzeba sprawdzać, czy ten numer jest numerem to nie świadczy zbyt dobre ani o języku, ani o projekcie.

To gdzie widzę problem to ludzie, którzy uznali, że używanie bibliotek, gdzie autor może wprowadzić dowolną minę do istniejącej biblioteki jest ok. Może jakieś automatyczne testy, które musiałyby przechodzić dla wszystkich kolejnych wersji rozwiązałyby ten problem?

Testy, potencjalnie rozwiążą ci problem "nie działa". Trochę dziwacznie, bo zamiast pisać samodzielnie te linijki z "isEven", piszesz samodzielnie linijki sprawdzające, czy to wciąż działa. Natomiast wciąż nie rozwiązują problemu z bezpieczeństwem, wynikających z prostej przyczyny, że używasz jakiejś biblioteki i często nawet nie wiesz że to robisz. Kilka ostatnich aferek ze Springiem, czy Log4jem pokazuje, że na backendzie problem też występuje, ale po tej stronie Internetu chociaż ktoś się tym przejmuje. Na froncie ogólnie, programiści mają wyrąbane, bo przecież podatności są otwierane głównie na maszynie klienta - nie ma się co przejmować.

2
piotrpo napisał(a):
slsy napisał(a):

Dodam jeszcze, że w innych językach często nie ma takich problemów, bo community nie potrafi into modularność i reużycie kodu (ach to OOP). Dobrym przykładem jest java, gdzie większość rozwiązań open source gryzie się ze sobą bo dostajesz ogromną kobyłę typu Spring, Guava czy Apache Commons z którą kolejne biblioteki nie chcą się integrować.

Spring to przegięcie w kompletnie drugą stronę. Ciężko uznać, ze to jest dobry argument na rzecz przechodnich zależności, bo sam wprowadza ich tonę.

Bardziej chodzi mi o tzw. multiliby czyli biblioteki, które spokojnie można podzielić na mniejsze. Takie liby lubią puchnąć z czasem: te słabe nie giną śmiercią naturalną, bo są powiązane z resztą. Takich bibliotek nikt nie chce używać, bo są duże.

Testy, potencjalnie rozwiążą ci problem "nie działa". Trochę dziwacznie, bo zamiast pisać samodzielnie te linijki z "isEven", piszesz samodzielnie linijki sprawdzające, czy to wciąż działa.

Taka wersja na szybko to TE SAME testy dla każdej kolejnej wersji. Jak mamy wersję 1.10 to odpalamy testy na tym samym kodzie dla wszystkich poprzednich 10 czy tam 11 wersji.
Do tego standardowe miejsce na stronce z modułami np. jaki jest coverage albo dodatkowa odznaka, że są włączone testy mutacyjne albo fuzzyng. Jeśli testy przestałyby przechodzić to wymagany byłby approval od jakiś zaufanych osób z community.
Do tego odpalanie w jakimś sandboxie, który sprawdzałby czy np. biblioteka nie zaczęła robić jakiś podejrzanych calli HTTP albo nie formatuje dysku w losowych momentach
Generalnie na pewno da się dużo zrobić w tej materii, po prostu do tej pory nikt nie zaczął tego robić, bo w sumie problem jest całkiem nowy i rzadki

0

@slsy: Ogólnie to biblioteki typu Guava to dziwactwo, które nie powinno mieć miejsca, bo ogólnie rzecz biorąc to próba dosztukowania funkcjonalności języka, której ewidentnie brakuje (typu przerzucanie jednego byte stream w drugi). Czy lepiej mieć to wszystko w jednym pakiecie, czy w kilku - kwestia dyskusyjna. Wolę jeden duży pakiet od Google, niż 150 zależności od przypadkowych ludzi.

3

Ogólnie to biblioteki typu Guava to dziwactwo, które nie powinno mieć miejsca

Chyba wszystkie języki cierpią na to że mają niewystarczającą bibliotekę standardową (może za wyjątkiem Javy MS C#). JS miał JQuery, Java miała Guave a teraz ma Vavra, Scala ma Catsy, C++ ma (miał ?) Boosta. Haskell w zasadzie prawie w ogóle nie ma biblioteki standardowej. Niby jest base, ale można w ogóle nie używać i napisać wszystko samodzielnie

2

@KamilAdam: Język zawsze będzie się rozwijać wolniej niż biblioteki, bo wprowadzenie czegoś szkodliwego wiąże się z wyższymi stratami. Ogólnie Java przez wiele lat rozwijała się wg. filozofii "nie dorobimy tego, bo to niezgodne z filozofią Java. Ten język musi pozostać do d....y". Trochę się ostatnio zaczęło zmieniać, ale wciąż nie są to zmiany dostatecznie szybkie. Dlatego jest tyle rakowych / druciarskich rozwiązań. Nie potrafimy zrobić normalnej obsługi czasu, to powstał joda (no dobra, to wciągnęli i nie zepsuli - musiała być impreza z napradę dobrymi używakmi), normalnych monad, jest vavr, wrzucić do języka paru sensownych kolekcji (przy drugim podejściu zresztą) i kopiowania strumieni - wpada Guava, brak propertisów - leci lombok (bleee....). Serio mam wrażenie, że ta rada od standardu języka podchodzi do zmian "nie możemy zrobić dobrze, bo język będzie za fajny i korporacje nas przestaną używać".

0
KamilAdam napisał(a):

Ogólnie to biblioteki typu Guava to dziwactwo, które nie powinno mieć miejsca
C++ ma (miał ?) Boosta.

Tutaj mogę się wypowiedzieć, bo znam temat. Generalnie boost to g**no, bo:

  • wspiera starsze standardy C++. A to się wiąże z hackami, żeby osiągnąć to samo co w nowszych (głównie chodzi o templaty i metaprogramowanie). W wyniku mamy kod, który kompiluje się strasznie wolno i jest nieczytelny. Dodanie losowego headeru z boosta to często wydłużenie kompilacji jednego pliku cpp o 2 sekundy (a bywa gorzej)
  • dużo standardowych building blocków jest teraz w standardzie np. variant (taki type union), optional, string_view (co ciekawe jest też stary string_ref), kontenery czy różne algorytmy na stringach. Normalnie biblioteki korzystające z starych wersji weszłyby w tryb maitanance i tylko nowe byłby rozwijane ale tak nie jest przez co trzeba się użerać z dwoma typami, które robią to samo i nie są kompatybilne
  • słabe biblioteki (np. date_time) nie umierają, tylko są utrzymywane
  • są biblioteki, które robią prawie to samo np. LEAF i outcome

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