Mutasd az importjaidat, megmondom, ki vagy

Na jó, azt talán nem, de arra utalhat, hogy mennyire karbantartható a kódod.

Írtál-e már ilyet:

import { SomeClass } from 'somelib';
...
const s = new SomeClass();

Vagy épp ilyet:

import * as someFunc from 'somelib';
...
const s = someFunc();

Ismerős, ugye? Én is rengetegszer írtam le hasonló sorokat, már csak azért is, mert minden könyvtár, library, package README-je pontosan ezt ajánlja. Van ezzel a mintával azonban (legalább) két probléma, ami miatt egy ideje igyekszem kerülni.

Függőségek kezelése

Ha gátlástalanul követed ezt a mintát, egyetlen alkalmazáson belül ugyanazt a sort le fogod írni százszor (vagy még többször). Miért? Mert viszi a kezed a copy-paste. Egyszerű, kényelmes, működik, nem igaz?

Így viszont nagyon nehéz lesz meghatározni, hogy a Te alkalmazásodnak pontosan mire is van szüksége, mitől függ, mi a dependenciája. A kódból mindössze annyi fog látszódni (de csak miután a kedvenc editorod “Find in All Files” funkciójával rákeresel), hogy a te kódod számtalan helyen hivatkozik egy külső csomagra, tehát az a csomag kell neki, függ tőle.

A valóság azonban más: a te alkalmazás logikádnak nem egy csomagra van szüksége, hanem egy funkcióra, egy logikára, amit történetesen egy külső csomagban implementált valaki más. Csakhogy ez most abszolút nem látszik a kódodból.

Íme egy konkrétabb példa a népszerű lodash csomaggal (*):

var _ = require('lodash');

Ez a csomag számtalan segédfüggvényt tartalmaz, szinte kizárt, hogy a te alkalmazásodnak mindegyikre szüksége van. Hogyan derítheted ki, hogy pontosan mit használsz? Rákeresel az összes fájlodban arra, hogy “_”.

De miért érdekes ez?

Például mert előfordulhat, hogy a hivatkozott csomagot le akarod cserélni valami másra. A nyílt forráskódú világban csomagok jönnek, mennek, ma még ez volt a legjobb, holnap már az lesz. Mert a régiben van egy bug, amit a fejlesztője már sosem fog kijavítani, vagy mert csak az új kompatibilis a legújabb Node verzióval, ami meg kell az appod többi részének. Vagy egyszerűen azért, mert frissen akarod tartani az appod kódját, és csak olyan dependenciákat szeretnél, amik körül aktív a közösség.

Szinte biztos, hogy nem fogsz találni egy másik könyvtárt, csomagot, ami API szinten kompatibilis a régivel. Olyanra van szükséged, ami funkció szinten kompatibilis, amit csak akkor tudsz kideríteni, ha pontosan ismered, hogy milyen funkcióra hivatkozol.

Ha ez megvan, akkor már csak az kell, hogy a lehető legkevesebb helyen kelljen átírni a hivatkozást a kódodban.

Ebből rögtön következik is a megoldás: törekedj arra, hogy a külső csomagokra való hivatkozásokat pontosan egyetlen helyen írd le a kódodban, azaz csomagold be őket egy osztályba, ami csak azokat a funkciókat publikálja, amire a te appod épít. Ez a módszer ráadásul olvashatóbbá is tudja tenni a kódodat, például “sha256()” helyett sokkal jobb függvény nevet is ki tudsz találni, igaz?

Ez a módszer nekem nagyon bevált, bár megjegyzem, nem követem fanatikusként, mert a framewörk szintű libek (pl. Angular) csomagolása túl nagy overhead lenne.

Tesztelhetőség

Ha egy osztályod importon keresztül hivatkozik egy másikra, akkor annak az osztálynak a unit tesztelése rémálom lesz, mert a hivatkozott osztály mockolásához a fájl betöltő logikát kell meghekkelni (például Mocking Request-Promise).

A megoldás nem újdonság: DI. Nem csak azt nyerjük vele, hogy könnyebb a tesztelés, de elég ránézni tipikusan a konstruktorra, és messziről látszik, hogy az adott osztály milyen más osztályoktól függ.

TypeScriptben például sokszor importálunk interface-eket csak azért, hogy legyen típus leírónk, ami miatt nagyon el tudnak szaporodni az import sorok, és gyakran nem látszik ránézésre, hogy melyik import hoz be funkcionalitást, és melyik csak típus információt. Ezen is segít a DI, ha törekszünk arra, hogy az importot csak típus leírásokhoz használjuk, minden másra ott a DI.

Mi ebben az újdonság?

Semmi az ég világon. Tényleg. Ennek ellenére mégsem akarnak kipusztulni ezek a kódok. A README-k, tutorialok, cikkek, blogok mindig az egyszerűségre fognak törekedni, olyan példákat fogsz bennük találni, amik segítenek a megértésben, az elindulásban. Nem az a céljuk, hogy vakon kövesd őket egy komolyabb alkalmazásban, ahol neked a karbantarthatóság és fenntarthatóság legalább olyan fontos, mint az egyszerűség. Amikor mégis leírsz egy ilyen sort legközelebb, gondolj arra, hogy ezzel mit veszel a nyakadba.

programming-is-thinking

 

*: A példa kicsit sántít, mert a lodash lehetővé tesz finomabb importokat is, csak épp kevesen használják.

 

Technorati-címkék: ,,
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s