Přejít k navigační liště

Zdroják » JavaScript » Jak (ne)psat webové aplikace – verzování

Jak (ne)psat webové aplikace – verzování

Články JavaScript

Při psaní aplikací v Node.js občas narazím na moduly obsahující zbytečné nedostatky, které vývojářům komplikují jejich používání. Následující článek rozebírá první z nich, a to chybné verzování modulů. Článek se zaměřuje na Node.js, nicméně postupy zde uvedené platí i pro ostatní jazyky a platformy.

Nálepky:

Sémantické verzování

Moduly npm používají semantické verzování, které přesně říká, jak má být verze modulu specifikovaná a kdy se má změnit. Označení verze se skládá ze tří částí:

  • major
  • minor
  • patch

Máme-li např. číslo verze 4.3.1, pak 4 je major, 3 je minor a 1 je patch část. Major se povinně mění tehdy, když dojde k zpětně nekompatibilním zásahům. Minor se změní, pokud je přidána nová funkcionalita, která však musí být zpětně kompatibilní v aktuální major verzi. A konečně patch verze se mění jen tehdy, když dojde k zpětně kompatibilním opravám chyb. Tohle pravidlo neplatí jen tehdy, je-li major verze 0, pak mohou vznikat nekompatibility i mezi jednotlivými minor a patch verzemi.

Uvedení verze v package.json

Protože se moduly neustále vyvíjí a opravují se jejich chyby, mění se i neustále jejich verze. Abychom nemuseli neustále přepisovat novou verzi v package.json, používají se v něm zápisy verzí, které jsou prefixovány znaky ^ a ~. První znak říká, že požadujeme, aby byla major verze stejná, druhý znak dělá to samé pro minor verzi. Několik příkladů jistě pomůže (zápis verze z package.json):

~4.3.1

  • >= 4.3.1 < 4.4.0
  • min. požadovaná verze je 4.3.1 a každá vyšší, s major 4 a minor 3

 

^4.3.1

  • >= 4.3.1 < 5.0.0
  • min. požadovaná verze je také 4.3.1, ale platí i pro minor vyšší než 3

Jiná pravidla platí pro verzi začínající 0, zde mají oba znaky stejný význam:

^0.3.1 i ~0.3.1

  • >= 0.3.1 < 0.4.0

A ještě jinak je to ve chvíli, kdy je major i minor verze 0:

^0.0.2

  • = 0.0.2, platí tedy pouze pro uvedenou verzi

~0.0.2

  • >= 0.0.2 < 0.1.0, patch verze se může měnit

Protože npm ve výchozím stavu zapisuje znak ^ jako prefix verze (pokud zadáte příkaz npm install --save nazev-modulu), doporučuji se vždy ujistit, zda je daný modul vyvíjen kvalitním vývojářem, který daná pravidla skutečně dodržuje. Stává se, že tomu tak není a objeví se zpětně nekompatibilní změna v nové minor verzi (je dobré projít si starší issues na Githubu). V takovém případě je lepší zápis verze prefixovaný znakem ^ nepoužívat a raději použít zápis se znakem ~, případně specifikovat přesnou verzi.

Absence verze závislého modulu

Speciálním extrémem je absence specifikace verze, na které je modul závislý. Pokud je místo verze prázdný řádek nebo hvězdička, znamená to, že jakákoliv publikovaná verze je akceptovaná, všechny minulé i budoucí. To znamená, že když se API závislého modulu změní, může se celá aplikace sesypat.

Myslíte si, že jde o výjimečnou situaci? Tak třeba oficiální klient pro nejpopulárnější zápisník Evernote má v package.json verzi modulu oauth nespecifikovanou, a to dokonce v situaci, kdy modul oauth není ani v první verzi. Pokud zítra vyjde jeho nová nekompatibilní verze, aplikace využívající Evernote API mohou přestat fungovat.

Pokud jde konkrétně o Evernote, tak podobně neuvádí verze ani u příkladů, které tak nyní nefungují. Můžete si schválně zkusit Express sample aplikaci nainstalovat a spustit. Nejprve nepůjde příklad spustit kvůli novější verzi less-middleware. Až přijdete na to, jakou verzi autoři používali, spadne vám příklad na novější verzi frameworku express. A až nainstalujete tady správnou verzi, spadne vám příklad na nekompatibilitě s modulem jade.

Závěr

V package.json uvádějte verze modulů, na kterých je vyvíjený modul závislý. Verzi neuvádějte jen tehdy, pokud proto máte opravdu dobrý důvod. Pokud něco publikujete veřejně a chcete, aby to lidé používali, uvádějte verzi vždy.

Za review článku děkuji Pavlu Langovi

Komentáře

Subscribe
Upozornit na
guest
5 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Honza Javorek

Pěkný článek, díky! Kdyby někoho zajímalo něco k této problematice z Python světa, dávám odkaz na zajímavý článek – https://caremad.io/blog/setup-vs-requirement/ Tam se rozebírá například i rozdíl mezi závislostmi v setup.py a requirements.txt apod.

David Majda

Po mnoha letech programování v mnoha různých prostředích jsem došel k tomu, že verze závislostí specifikuju pokud možno exaktně. Omezuje to počet proměnných, které vstupují do hry. Výsledkem je shodné prostředí u všech uživatelů a vývojářů, méně problémů při rozběhávání aplikací, méně chybových hlášení u open source projektů atd.

Sémantické verzování je sice teoreticky pěkná věc, ale v praxi má málo projektů API popsané na takové úrovni, aby bylo jasné, co je součástí kontraktu (a tedy podléhá verzovacím pravidlům) a co nikoliv (a tedy se může kdykoliv rozbít). Snadno se tak stane, že se můj kód stane závislý na nějakém chování, které je v příští patch verzi změněno.

Na Node.js a npm je super, že strategie exaktních verzí je v něm technicky možná. Není problém, pokud používám knihovny A a B, přičemž A vyžaduje C ve verzi 1.2.3 a B ji chce ve verzi 1.3.0. Knihovna C je prostě nainstalována a nahrána dvakrát. Třeba v Ruby světě to takhle nefunguje, protože není možné nahrát najednou víc verzí jedné knihovny (kvůli tomu, že definované třídy jsou efektivně uložené v globálních proměnných, takže by se navzájem přepsaly). Verze závislostí u knihoven se tak musí specifikovat volněji, aby nedocházelo ke konfliktům při kombinování různých knihoven v aplikacích. Přiznávám, že se mi to vůbec nelíbí, ale Ruby takhle bohužel funguje.

Srigi

David dovolim si vyjadrit nesuhlas s tvojim tvrdenim „Třeba v Ruby světě to takhle nefunguje, protože není možné nahrát najednou víc verzí jedné knihovny„.

Este rok dva dozadu som toto riesil oklukou cez tzv. gemsety v rvm. A dnes uz netreba ani to, v Ruby je bundler. Ten umoznuje nainstalovat X verzii toho isteho gemu. Vsetky sa sice nainstaluju (by default) do globalneho gemsetu, ale ked je v projekte pritomny Gemfile a projekt sa „exekuuje“ pomocou bundle exec, projekt dostane vzdy pozadovanu verziu. Viac napr. tu (jeden z tisica podobnych clankov).

Nie je to take elegantne ako pri npm, ale ucel to plni rovnaky. Okrem toho, aj bundler je mozne nastavit aby gemy instaloval do adresara v projekte, nie na nejake globalne miesto.

David Majda

Nerozumíme si. Instalace víc verzí knihovny samozřejmě není problém a o zmiňovaných řešeních vím. Jde mi ale o to, že nejde nahrát (pomocí require) a současně používat dvě různé verze stejné knihovny v rámci jedné aplikace. Takže pokud mají dva gemy, které chci používat, konflikní závislosti (ve smyslu verzí), mám problém.

John

A čo urobí v package.json:

„socket.io“: „0.9.x“,

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.