hogyan lehet kijavítani a csúnya körkörös függőségi problémákat egyszer és mindenkorra a JavaScript & TypeScript programban
-
index.js
szükséges:AbstractNode.js
- a modulbetöltő megkezdi a
AbstractNode.js
betöltését és a modul kódjának futtatását. A dolog, amellyel először találkozik, egy require (import) utasítás aLeaf
- – hez, így a modulbetöltő elkezdi betölteni a
Leaf.js
fájlt. Ami viszont azzal kezdődik, hogyAbstractnode.js
szükséges. -
AbstractNode.js
már be van töltve, és azonnal visszatér a modul gyorsítótárából. Mivel azonban ez a modul még nem futott túl az első soron (aLeaf
követelménye), aAbstractNode
osztályt bevezető utasítások még nem kerültek végrehajtásra! - tehát a
Leaf
osztály megpróbálja kiterjeszteni aundefined
értéket, nem pedig egy érvényes osztályt. Amely a fent bemutatott futásidejű kivételt dobja. Bumm!
Fix kísérlet 1
tehát kiderül, hogy körkörös függőségünk csúnya problémát okoz. Ha azonban alaposan megvizsgáljuk, elég könnyű meghatározni, hogy mi legyen a rakodási sorrend:
- töltse be a
AbstractNode
osztály Első - töltse be a
Node
ésLeaf
osztály után.
más szavakkal, először definiáljuk a AbstractNode
osztályt, majd tegyük szükségessé a Leaf
és Node
osztályt. Ennek működnie kell, mert a Leaf
és Node
osztály meghatározásakor még nem kell ismerni őket. Mindaddig, amíg meg vannak határozva, mielőtt a AbstractNode.from
– et először hívják meg, rendben kell lennünk. Tehát próbáljuk meg a következő változást:
kiderül, van néhány probléma ezzel a megoldással:
először is, ez csúnya és nem méretezhető. Egy nagy kódbázisban ez azt eredményezi, hogy az import véletlenszerűen mozog, amíg a dolgok csak működnek. Ami gyakran csak átmeneti, mivel egy kis refaktorálás vagy az importálási nyilatkozatok módosítása a jövőben finoman módosíthatja a modul betöltési sorrendjét, újra bevezetve a problémát.
másodszor, hogy ez működik-e, nagymértékben függ a modulcsomagolótól. Például a codesandbox alkalmazásban, amikor alkalmazásunkat csomaggal (vagy Webcsomaggal vagy összesítéssel) csomagoljuk, ez a megoldás nem működik. Ha azonban ezt helyben futtatja a csomóponttal.js és commonJS modulok ez a megoldás jól működhet.
a probléma elkerülése
tehát nyilvánvalóan ezt a problémát nem lehet könnyen megoldani. Elkerülhető lett volna? A válasz igen, a probléma elkerülésének számos módja van. Először is, megtarthattuk volna a kódot egyetlen fájlban. Amint az a kezdeti példánkban látható, így megoldhatjuk a problémát, mivel teljes ellenőrzést biztosít a modul inicializálási kódjának sorrendje felett.
másodszor, egyesek a fenti problémát érvként használják olyan állítások megfogalmazására, mint “nem szabad osztályokat használni” vagy “ne használja az öröklést”. De ez a probléma túlzott egyszerűsítése. Bár egyetértek azzal, hogy a programozók gyakran túl gyorsan folyamodnak az örökléshez, bizonyos problémák esetén ez csak tökéletes, és nagy előnyökkel járhat a kódszerkezet, az újrafelhasználás vagy a teljesítmény szempontjából. De ami a legfontosabb, ez a probléma nem korlátozódik az osztály öröklésére. Pontosan ugyanaz a probléma vezethető be, ha körkörös függőségek vannak a modulváltozók és a modul inicializálása során futó függvények között!
újraszervezhetjük a kódunkat oly módon, hogy a AbstractNode
osztályt kisebb darabokra bontjuk, így AbstractNode
-nek nincs függősége Node
vagy Leaf
– tól. Ebben a homokozóban a from
metódus kihúzta a AbstractNode
osztályt, és egy külön fájlba helyezte. Ez megoldja a problémát, de most a projektünk és az API másképp van felépítve. Nagy projektekben nagyon nehéz lehet meghatározni, hogyan lehet ezt a trükköt kihúzni, vagy akár lehetetlen! Képzeljük el például, mi történne, ha a print
módszer függ Node
vagyLeaf
a következő iteráció a mi app…
bónusz: egy további csúnya trükk, amit korábban használtam: vissza bázis osztályok függvények és tőkeáttétel funkció emelő, hogy a dolgok betöltött a megfelelő sorrendben. Nem is tudom, hogyan magyarázzam el rendesen.
a belső modul minta a mentéshez!
számos projekt során többször is küzdöttem ezzel a problémával, néhány példa a Mendix, a MobX, a MobX-state-tree és számos személyes projekt munkája. Néhány évvel ezelőtt még egy szkriptet is írtam az összes forrásfájl összefűzésére és az összes importálási utasítás törlésére. A szegény mans modul bundler csak azért, hogy egy fogást a modul betöltése érdekében.
a probléma néhányszor történő megoldása után azonban megjelent egy minta. Az egyik, amely teljes mértékben ellenőrzi a modul betöltési sorrendjét, anélkül, hogy át kellene alakítania a projektet, vagy furcsa hackeket kellene húznia! Ez a minta tökéletesen működik az összes eszközlánccal, amelyen kipróbáltam (összesítő, Webcsomag, csomag, csomópont).
ennek a mintának a lényege egy index.js
és internal.js
fájl bevezetése. A játék szabályai a következők:
- a
internal.js
modul mind importál, mind exportál mindent a projekt minden helyi moduljából - a projekt minden más modulja csak a
internal.js
fájlból importál, és soha nem közvetlenül a projekt többi fájljából. - a
index.js
fájl a fő belépési pont, és importál és exportál mindent ainternal.js
– ből, amit ki szeretne tenni a külvilágnak. Ne feledje, hogy ez a lépés csak akkor releváns, ha mások által fogyasztott könyvtárat tesz közzé. Tehát kihagytuk ezt a lépést a példánkban.
vegye figyelembe, hogy a fenti szabályok csak a helyi függőségekre vonatkoznak. Külső modul import marad, ahogy van. Végül is nem vesznek részt a körkörös függőségi problémáinkban. Ha ezt a stratégiát alkalmazzuk a demo alkalmazásunkra, a kódunk így fog kinézni:
amikor először alkalmazza ezt a mintát, nagyon mesterkéltnek érezheti magát. De van néhány nagyon fontos előnye!
- először is megoldottuk a problémánkat! Amint azt itt bemutatjuk, alkalmazásunk boldogan fut újra.
- ennek oka, hogy ez megoldja a problémánkat: most már teljes mértékben ellenőrizzük a modul betöltési sorrendjét. Bármi legyen is a
internal.js
importálási sorrend, a modul betöltési sorrendje lesz. (Érdemes ellenőrizni az alábbi képet, vagy olvassa el újra a fenti modulrendelési magyarázatot, hogy megtudja, miért van ez így) - nem kell olyan refaktorokat alkalmaznunk, amelyeket nem akarunk. Azt sem vagyunk kénytelenek használni csúnya trükköket, mint a mozgó igényel nyilatkozatok alján a fájlt. Nem kell veszélyeztetnünk a kódbázis architektúráját, API-ját vagy szemantikai szerkezetét.
- bónusz: az importálási utasítások sokkal kisebbek lesznek, mivel kevesebb fájlból importálunk dolgokat. Például a
AbstractNode.js
csak az importálási utasításban van, ahol korábban kettő volt. - bónusz: a
index.js
segítségével egyetlen igazságforrásunk van, amely finom szemcsés ellenőrzést biztosít a külvilág számára.