Felhasználóbarát Java könyvtár tervezése

1. Áttekintés

A Java a nyílt forráskódú világ egyik pillére. Szinte minden Java projekt más nyílt forráskódú projektet használ, mivel senki sem akarja újra feltalálni a kereket. Sokszor előfordul azonban, hogy szükség van egy könyvtárra a funkcionalitásához, de fogalmunk sincs, hogyan kell használni. Olyan dolgokba ütközünk, mint:

  • Mi van ezekkel a „* Service” osztályokkal?
  • Hogyan tudom ezt példázni, túl sok függőség kell hozzá. Mi az aretesz“?
  • Ó, összeraktam, de most elkezd dobni IllegalStateException. Mit csinálok rosszul?

A baj az, hogy nem minden könyvtártervező gondolkodik a felhasználóiról. A legtöbben csak a funkcionalitásra és a funkciókra gondolnak, de kevesen gondolkodnak azon, hogy az API-t hogyan fogják használni a gyakorlatban, és hogyan fog kinézni és tesztelni a felhasználói kód.

Ez a cikk néhány tanácsot tartalmaz arra vonatkozóan, hogyan menthetjük meg felhasználóinkat ezekből a küzdelmekből - és nem, nem a dokumentáció megírásával. Természetesen egy egész könyvet lehetne írni erről a témáról (és néhány ilyen is készült); ezek azok a kulcsfontosságú pontok, amelyeket megtanultam, miközben magam is több könyvtárral dolgoztam.

Az ötleteket itt két könyvtár segítségével példázom: charles és jcabi-github

2. Határok

Ennek nyilvánvalónak kell lennie, de sokszor nem az. Mielőtt bármilyen kódsort írnánk, egyértelmű kérdésre kell választ adnunk néhány kérdésre: milyen bemenetekre van szükség? mi az első osztály, amelyet a felhasználó látni fog? kell-e valamilyen megvalósítás a felhasználótól? mi a kimenet? Miután egyértelműen megválaszoltuk ezeket a kérdéseket, minden könnyebbé válik, mivel a könyvtárnak már van bélése, alakja.

2.1. Bemenet

Talán ez a legfontosabb téma. Biztosítanunk kell, hogy egyértelmű legyen, mit kell a felhasználónak átadnia a könyvtárnak ahhoz, hogy munkáját elvégezhesse. Bizonyos esetekben ez nagyon triviális kérdés: lehet, hogy csak egy String reprezentálja az API hitelesítési tokent, de lehet egy interfész vagy egy absztrakt osztály megvalósítása is.

Nagyon jó gyakorlat az összes függőség kivitelezése a kivitelezőkön, és ezek rövid megtartása néhány paraméter mellett. Ha három vagy négy paraméternél több konstruktorra van szükségünk, akkor a kódot egyértelműen fel kell dolgozni. Ha pedig módszereket alkalmaznak a kötelező függőségek injektálására, akkor a felhasználók nagy valószínűséggel az áttekintésben leírt harmadik csalódással járnak.

Ezenkívül mindig ajánlunk több kivitelezőt, adjunk alternatívákat a felhasználóknak. Hadd dolgozzanak mindkettővel Húr és Egész szám vagy ne korlátozza őket a FileInputStream, dolgozzon egy InputStream, hogy esetleg beküldhessék ByteArrayInputStream amikor az egység tesztelése stb.

Például itt mutatunk be néhány módot a Github API belépési pont példányosítására a jcabi-github használatával:

Github noauth = új RtGithub (); Github basicauth = new RtGithub ("felhasználónév", "jelszó"); Github oauth = új RtGithub ("token"); 

Egyszerű, semmi nyüzsgés, árnyékos konfigurációs objektumok inicializálása. És értelme van ennek a három konstruktőrnek lenni, mert a Github webhelyét használhatja kijelentkezés, bejelentkezés közben, vagy egy alkalmazás hitelesíthet az Ön nevében. Természetesen bizonyos funkciók nem fognak működni, ha nincs hitelesítve, de ezt már a kezdetektől fogva tudja.

Második példa: hogyan működnénk együtt a charles-szal, egy internetes bejáró könyvtárral:

WebDriver illesztőprogram = new FirefoxDriver (); Repository repo = új InMemoryRepository (); String indexPage = "//www.amihaiemil.com/index.html"; WebCrawl graph = új GraphCrawl (indexPage, illesztőprogram, új IgnoredPatterns (), repo); graph.crawl (); 

Azt hiszem, ez is elég magától értetődő. Ennek megírása közben azonban rájövök, hogy a jelenlegi verzióban van egy hiba: az összes konstruktor megköveteli a felhasználótól, hogy IgnoredPatterns. Alapértelmezés szerint semmilyen mintát nem szabad figyelmen kívül hagyni, de a felhasználónak nem kell ezt megadnia. Úgy döntöttem, hogy itt hagyom, így lát egy ellenpéldát. Feltételezem, hogy megpróbálna példányosítani egy webrobotot, és azon tűnődik: „Mi van ezzel IgnoredPatterns?!”

A változó indexPage az az URL, ahonnan a feltérképezést el kell kezdeni, az illesztőprogram a használni kívánt böngésző (nem állíthat be semmit alapértelmezés szerint, mivel nem tudjuk, melyik böngésző van telepítve a futó gépre). A repo változót az alábbiakban a következő szakaszban ismertetjük.

Tehát, amint a példákban látható, próbáljon egyszerű, intuitív és magától értetődő maradni. Olyan módon foglalja össze a logikát és a függőségeket, hogy a felhasználó ne kapkodja a fejét, amikor a konstruktőreit nézi.

Ha továbbra is kétségei vannak, próbáljon HTTP-kéréseket küldeni az AWS-hez az aws-sdk-java használatával: egy úgynevezett AmazonHttpClient-tel kell megküzdenie, amely valahol egy ClientConfiguration-t használ, majd valahol a kettő között kell végrehajtani egy ExecutionContext-et. Végül előfordulhat, hogy végrehajtja a kérését, és választ kap, de még mindig nem tudja, mi az az ExecutionContext.

2.2. Kimenet

Ez leginkább a külvilággal kommunikáló könyvtárak számára szól. Itt meg kell válaszolnunk a kérdést: "hogyan kezeljük a kimenetet?". Ismét meglehetősen vicces kérdés, de könnyű rosszul lépni.

Nézze meg újra a fenti kódot. Miért kell biztosítanunk a Tárház megvalósítását? Miért nem adja vissza a WebCrawl.crawl () metódus csak a WebPage elemek listáját? A feltérképezett oldalak nyilvánvalóan nem a könyvtár feladata. Honnan kellene még tudnia, mit szeretnénk kezdeni velük? Valami ilyesmi:

WebCrawl graph = új GraphCrawl (...); Listaoldalak = graph.crawl (); 

Semmi sem lehet rosszabb. Az OutOfMemory kivétel a semmiből történhet, ha a feltérképezett webhelynek mondjuk 1000 oldala van - a könyvtár mindet betölti a memóriába. Ennek két megoldása van:

  • Visszaadja az oldalakat, de valósítson meg valamilyen lapozási mechanizmust, amelyben a felhasználónak meg kell adnia a kezdő és a befejező számot. Vagy
  • Kérje meg a felhasználót, hogy valósítson meg egy interfészt az export (Lista) nevű módszerrel, amelyet az algoritmus minden alkalommal meghív, amikor elérik az oldalak maximális számát

A második lehetőség messze a legjobb; mindkét oldalon egyszerűbbé teszi a dolgokat és jobban tesztelhető. Gondolja át, mennyi logikát kellene megvalósítani a felhasználó oldalán, ha az elsővel járnánk. Ilyen módon megadják az oldalak tárházát (hogy elküldhesse őket egy DB-be, vagy írjon lemezre), és semmi mást nem kell tennie a metódus bejárása () meghívása után.

Egyébként a fenti Input szakasz kódja minden, amit írnunk kell a weboldal tartalmának lekérése érdekében (még mindig a memóriában van, ahogy a repo implementáció mondja, de ez a mi választásunk - ezt a megvalósítást azért biztosítottuk vállaljuk a kockázatot).

Összefoglalva ezt a részt: soha nem szabad teljesen elválasztanunk a munkánkat az ügyfél munkájától. Mindig el kell gondolkodnunk, mi történik az általunk létrehozott kimenettel. Hasonlóan ahhoz, mint egy teherautó-sofőrnek, segítsen az áruk kicsomagolásában, ahelyett, hogy egyszerűen kidobná őket a rendeltetési helyre érkezéskor.

3. Interfészek

Mindig használjon interfészeket. A felhasználó csak szigorú szerződések révén léphet kapcsolatba kódunkkal.

Például a jcabi-github könyvtár az RtGithub osztály, amelyet csak a felhasználó lát:

Repo repo = new RtGithub ("oauth_token"). Repos (). Get (new Coordinates.Simple ("eugenp / tutorials")); Issue issue = repo.issues () .create ("Példapéldány", "A jcabi-githubal készült");

A fenti részlet egy jegyet hoz létre az eugenp / tutorials repóban. A Repo és az Issue példányokat használják, de a tényleges típusokat soha nem tárják fel. Nem tehetünk ilyesmit:

Repo repo = új RtRepo (...)

A fentiek logikai okból nem lehetséges: nem tudunk közvetlenül létrehozni egy kérdést a Github repóban, igaz? Először be kell jelentkeznünk, majd meg kell keresni a repót, és csak ezután hozhatunk létre egy problémát. Természetesen a fenti forgatókönyv megengedett, de akkor a felhasználó kódja sok szennyeződéssel szennyeződik: RtRepo valószínűleg valamilyen jogosultsági objektumot kellene vennie a konstruktorán keresztül, engedélyeznie kell a klienst és el kell jutnia a megfelelő repóhoz stb.

Az interfészek a bővíthetőséget és a visszafelé kompatibilitást is megkönnyítik. Egyrészt fejlesztőként kötelesek vagyunk tiszteletben tartani a már kiadott szerződéseket, másrészt a felhasználó kiterjesztheti az általunk kínált felületeket - díszítheti őket, vagy alternatív megvalósításokat írhat.

Más szavakkal, amennyire csak lehetséges, elvonatkoztatni és befogadni. Az interfészek használatával ezt elegánsan és nem korlátozó módon tehetjük meg - kikényszerítjük az építészeti szabályokat, miközben szabadságot adunk a programozónak a kitett viselkedés javítására vagy megváltoztatására.

Ennek a szakasznak a befejezéséhez ne feledje: könyvtárunkat, szabályainkat. Pontosan tudnunk kell, hogy fog kinézni az ügyfél kódja, és hogyan fogja tesztelni az egység. Ha ezt nem tudjuk, senki nem fog, és könyvtárunk egyszerűen hozzájárul a nehezen érthető és fenntartható kód létrehozásához.

4. Harmadik felek

Ne feledje, hogy a jó könyvtár egy könnyű könyvtár. A kódod megoldhat egy problémát és működőképes, de ha a jar 10 MB-ot ad hozzá a buildemhez, akkor egyértelmű, hogy régen elvesztetted a projekt tervrajzait. Ha sok függőségre van szüksége, akkor valószínűleg túl sok funkcionalitást próbál lefedni, és több kisebb projektre kell bontania a projektet.

Legyen a lehető legátláthatóbb, amikor csak lehetséges, ne kössön a tényleges megvalósításokhoz. A legjobb példa, ami eszembe jut: használja az SLF4J-t, amely csak API a naplózáshoz - ne közvetlenül használja a log4j-t, esetleg a felhasználó más naplózókat szeretne használni.

Dokumentumtárak, amelyek átmenetileg átjönnek a projekten, és győződjön meg arról, hogy nem tartalmaz olyan veszélyes függőségeket, mint pl xalan vagy xml-apis (miért veszélyesek, nem ezt a cikket kell tovább részletezni).

Lényeg a lényeg: tartsa könnyű, átlátszó a felépítését, és mindig tudja, hogy miben dolgozik. Ez nagyobb nyüzsgést takaríthat meg a felhasználóinak, mint azt el tudná képzelni.

5. Következtetés

A cikk felvázol néhány egyszerű ötletet, amelyek segíthetnek egy projektnek a vonalon tartásában a használhatóság szempontjából. A könyvtárnak, mivel egy olyan összetevőnek kell lennie, amelynek nagyobb kontextusban kell megtalálnia a helyét, hatékonynak kell lennie, ugyanakkor sima és jól kidolgozott felületet kell kínálnia.

Ez könnyű lépés a vonal felett, és rendetlenséget okoz a dizájnban. A közreműködők mindig tudják, hogyan kell használni, de lehet, hogy valaki új, aki először szemet vet rá. A termelékenység a legfontosabb mind közül, és ezt az elvet követve a felhasználóknak percek alatt meg kell tudniuk kezdeni a könyvtár használatát.