TIE-20200 Ohjelmistojen suunnittelu Luento 10: Rajapintasuunnittelua & Singleton TIE-20200 Samuel Lahtinen 1
Ajankohtaista Välituotostapaamiset alkavat Ensi viikon luennoilla Marko Leppänen kertoilee erilaisten työkalujen käytöstä ketterässä ohjelmistoprosessissa (Need for Speed projekti ja firmavierailuiden & haastatteluiden antia) Continuous delivery, Jira, Git, Jenkins, analytiikka, yms. taikasanat
Ohjelmassa tänään Singleton: älä käytä (väärin) Rajapintasuunnittelun juttuja
Singleton Singleton: rajoittaa luokan instanssien määrän (yhteen). Plussat: tekee mitä lupaa, jos käytetään oikein. Instantiointi vasta kun ensimmäinen käyttäjä ilmestyy, jos käytetään oikein. Huomattavasti parempi, kuin globaalin muuttujan käyttö Koodiesmerkki: (viikkoharkat tehdaskoodi)
Singleton miinukset: erittäin helppo käyttää väärin. Tarjoaa laiskalle globaalin keinon päästä käsiksi tietoon mistä vaan. Rajapinnoista ei näe riippuvuussuhteita, missä vain toteutuspuolella voi nykäistä singleton-olion käyttöön. Ohjelman rakenne ja riippuvuudet vaikeaselkoisia Testaaminen vaikeutuu, yksikkötestit jne. (Kuka käyttää mitäkin, mitä oliot olettavat olevan olemassa?)
http://blog.code-cop.org/2012/01/why-singletons-are-evil.html
Singleton miinukset: erittäin helppo käyttää väärin. Tarjoaa laiskalle globaalin keinon päästä käsiksi tietoon mistä vaan. Rajapinnoista ei näe riippuvuussuhteita, missä vain toteutuspuolella voi nykäistä singleton-olion käyttöön. Ohjelman rakenne ja riippuvuudet vaikeaselkoisia Testaaminen vaikeutuu, yksikkötestit jne. (Kuka käyttää mitäkin, mitä oliot olettavat olevan olemassa?)
Singleton Tilaton singleton yleensä ongelmaton oikein käytettynä (eli takaa että tarjolla yksi instanssi) Esim. rakennuttajat, tehtaat yms. Jos singleton-oliolla on tila, kaikki monisäikeiset jutut & singleton saattavat tarjota ongelmia
Singleton: käyttöohje Käytä vain JOS, kaikki nämä ehdot täyttyvät: Oliosta tarvitaan vain yksi instanssi, mutta sille ei ole olemassa mielekästä omistajaa Laiska alustus/luominen toivottavaa Globaalia pääsyä olioon ei ole tarjottu muulla tavoin, globaali pääsy ei ole ongelma Lisäohjeistusta: Singletonin tarjoama olio ja rajapinnan käyttö, mahdollisuus vaihtaa käytettävä olio Muista säieturvallisuus, älä tee singleton-olioita, joiden tila vaihtelee käyttökutsuilla alustamisen jälkeen
Singleton: muistisäännöt Singleton on pahasta Singleton on pahuuden koodillinen ilmentymä Joka kerta kun singletonia väärinkäytetään, todennäköisyys sille, että Cheek/Celine Dion/Nickelback/lisää_tähän _vihaamasi_artisti tekee uuden levyn, kasvaa Nämä kun muistaa, niin singletonia ei tule viljeltyä turhaan koodin sekaan http://blog.code-cop.org/2012/01/why-singletons-are-evil.html http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/ http://blogs.msdn.com/b/scottdensmore/archive/2004/05/25/140827.aspx
Kertauskamaa: SOLID Hyvän rajapintasuunnittelun taustalla hyvä muistaa oliosuunnittelun periaatteita: SOLID Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle
Huono suunnittelu: STUPID Singleton Tight Coupling Untestability Premature Optimization Indescriptive Naming Duplication
Minkälainen on hyvä rajapinta? TIE-20200 Samuel Lahtinen
Hyvän rajapinnan tuntomerkkejä Kaunis Yksinkertainen Helppo Oppia, muistaa Käyttää, jopa ilman dokumentaatiota/kommentteja Dokumentaatio ja rajapinta vastaavat toisiaan Vaikea käyttää väärin Ei muuta/tuhoa saamiaan parametreja turhaan (esim. tietorakenteessa viiteen päässä olevaa tavaraa) Riittävän tehokas, tarjoaa riittävän korkean tason palveluita jotta käyttö mielekästä Sopii käyttökohteeseen/kohdeyleisölleen Helppo ymmärtää & ylläpitää koodia, joka käyttää rajapintaa
Minkälainen on huono rajapinta? TIE-20200 Samuel Lahtinen
Huonon rajapinnan tuntomerkit Ruma Vaikeasti lähestyttävä Hankala Oppia Käyttää, vaatii dokumentaation läpikahlaamista Ei jää mieleen Helppo käyttää väärin Tuskainen käyttää, vaatii paljon kutsuja yksinkertaisen asian tekemiseen Huonosti tarkoitukseensa sopiva/vaatii kikkailua Muuntelee/tuhoaa parametriksi saamiaan asioita (miten sattuu) Rajapintaa käyttävä koodi vaikeaselkoista, ylläpito hankalaa, pieni muutos aiheuttaa isojen osien uudelleen kirjoittamista Rajapintadokumentaatio ja rajapinta eivät vastaa toisiaan
Windows (MFC) HWND CreateWindow(LPCTSTR lpclassname, LPCTSTR lpwindowname, DWORD dwstyle, int x, int y, int nwidth, int nheight, HWND hwndparent, HMENU hmenu, HINSTANCE hinstance, LPVOID lpparam); HWND CreateWindowEx(DWORD dwexstyle, LPCTSTR lpclassname, LPCTSTR lpwindowname, DWORD dwstyle, int x, int y, int nwidth, int nheight, HWND hwndparent, HMENU hmenu, HINSTANCE hinstance, LPVOID lpparam);
public BufferedImage getscaledinstance(bufferedimage img, int targetwidth, int targetheight, Object hint, boolean higherquality) { int type = (img.gettransparency() == Transparency.OPAQUE)? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage ret = (BufferedImage)img; int w, h; if (higherquality) { // Use multi-step technique: start with original size, then // scale down in multiple passes with drawimage() // until the target size is reached w = img.getwidth(); h = img.getheight(); } else { // Use one-step technique: scale directly from original // size to target size with a single drawimage() call w = targetwidth; h = targetheight; } /** * Convenience method that returns a scaled instance of the * provided {@code BufferedImage}. * * @param img the original image to be scaled * @param targetwidth the desired width of the scaled instance, * in pixels * @param targetheight the desired height of the scaled instance, * in pixels * @param hint one of the rendering hints that corresponds to * {@code RenderingHints.KEY_INTERPOLATION} (e.g. * {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, * {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR}, * {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC}) * @param higherquality if true, this method will use a multi-step * scaling technique that provides higher quality than the usual * one-step technique (only useful in downscaling cases, where * {@code targetwidth} or {@code targetheight} is * smaller than the original dimensions, and generally only when * the {@code BILINEAR} hint is specified) * @return a scaled version of the original {@code BufferedImage} */ do { if (higherquality && w > targetwidth) { w /= 2; if (w < targetwidth) { w = targetwidth; } } if (higherquality && h > targetheight) { h /= 2; if (h < targetheight) { h = targetheight; } } BufferedImage tmp = new BufferedImage(w, h, type); Graphics2D g2 = tmp.creategraphics(); g2.setrenderinghint(renderinghints.key_interpolation, hint); g2.drawimage(ret, 0, 0, w, h, null); g2.dispose(); ret = tmp; } while (w!= targetwidth h!= targetheight); } return ret;
Rajapinnan suunnittelua, alkupiste Mikä tarkoitus rajapinnalla on, mitä se tarjoaa? Kenelle rajapinta on tarkoitettu? Sisäinen käyttö, julkinen rajapinta, komponenttien välinen, yhden ohjelman sisäiseen käyttöön Alan erityisosaajat, aloittelijat, tehokkuus vs. helppous jne.
Rajapinnan suunnittelua, jotain periaatteita Toiminnallisuuden kuvaamisen pitäisi olla yksinkertaista Jos moduulin tai funktion nimeäminen on vaikeaa: yleensä merkki huonosta suunnittelusta (Hyvä) nimi ohjaa sekä toteutustyötä että käyttöä Nimeäminen yhteneväistä muun projektin kanssa, esim. valittu nimeämiskäytäntö sama koko projektissa Valmius joustamaan Rajapintojen ja kokonaisuuksien palastelu tai yhdistäminen Yksi toimiva tapa suunnitella rajapinnan funktiot, kirjoita koodia, joka käyttää tulevan rajapinnan palveluita. Mitä kutsuja käytät, missä muodossa tarjoat tiedon?
Huonoja merkkejä ilmassa Kun rajapinnan nimeksi tulee useamman sanan yhdistelmiä (TilatiedotJaValikonHallinta) Tai funktion nimenä toisiinsa hatarasti tai ei lainkaan liittyviä asioita asetasijaintijapalautakayttajantiedot Kun useamman sanan mittainen nimikään ei pysty kertomaan mihin tarkoitukseen funktio on Useampi lähes saman niminen ja lähes samaa tekevä funktio Sama termi/nimi eri merkityksessä Paljon funktioita When in doubt, leave it out Parametrit, toiminnallisuus, metodit jne. Lisääminen onnistuu muuttamatta jo olemassa olevaa käyttöä, poistaminen ei ( huonosti suunnitellut funktiot, luokat, rajapinnat jne. jäävät elämään)
Huonoja merkkejä ilmassa Toteutusyksityiskohtien paljastaminen rajapinnassa, sen kommenteissa, ohjeistuksessa käyttäjä tulee riippuvaiseksi tietystä toteutuksesta toteutuksen muuttaminen ei onnistu esim. erilaiset järjestämis-, haku- jne. toiminnot, muun toiminnan ohessa, käyttäjän ei tarvitse tietää tarkkaa toteutustapaa (lupauksia suoritusajasta voi esittää, mutta älä kerro millä toteutettu) Tiedon tallentaminen, missä muodossa, mihin tietorakenteeseen yms. ei käyttäjälle tarpeellista tietoa Käytä abstraktioita (tietotyypit jne., mahdollista vaihtaa myöhemmin) Mutta muista suorituskyky (jos esim. funktiolle tulee miljoonia kutsuja ja joka kutsukerralla luodaan paluuarvooliolioita viite tai vakio-osoitin arvoon)
Funktioiden suunnittelua Vältä boilerplate koodia (toistuvaa koodia, jonka rajapintasi pakottaa käyttäjää kirjoittamaan) älä pakota käyttäjää tekemään asioita, jotka voitaisiin hoitaa moduulin sisällä Muuten saadaan turhaa copy-paste-koodia ärsyttää rajapinnan käyttäjää, virhealtista, rumaa yms.
Esimerkit // Java & tiedoston avaaminen kirjoittamista varten ennen 1.4 FileWriter fout = new FileWriter("fred.txt"); BufferedWriter bout = new BufferedWriter(fout); PrintWriter pout = new PrintWriter(bout); try { con = DriverManager.getConnection("jdbc:default:connection"); pstmt = con.preparestatement( "UPDATE EMPLOYEES SET ID =? " + "WHERE EMPLOYEE_NUMBER =?"); pstmt.setint(1, empid); pstmt.setint(2, empno); pstmt.executeupdate(); } catch( SQLException e ) { e.printstacktrace(); // What should I do?? } finally { if (pstmt!= null) pstmt.close(); }
Funktioiden suunnittelua Älä yllätä käyttäjää odottamattomilla asioilla Esim. Tilatiedon palauttava funktio, joka samalla nollaa kyseisin tilan Kerro virheistä mahdollisimman nopeasti Esim. Staattinen tyypitys, käännösaikaisten virheiden hyödyntäminen Jos parametrien tarkistus, tee heti alkuun jne. Sama funktio eri parametrein, helppo sekoittaa (etenkin jos toiminnallisuus eroaa riippuen parametrista) Älä pakota käyttäjää jäsentelemään tietoa esimerkiksi merkkijonojen seasta (esim. Tieto aina muotoa merkkijono-kokonaisluku, mutta käyttäjä joutuu hakemaan tiedot merkkijonon sisältä ja tekemään muunnokset)
Parametrit ja paluuarvot Vältä pitkiä parametrilistoja Vältä erityisesti pitkiä parametrilistoja, joissa samaa parametrityyppiä Koodintäydentelyvirhe, näppäilyvirhe, kääntyy & toimii, mutta väärin, virheet erittäin vaikeita löytää (kutsu näyttää ihan asialliselta) Tee tarvittaessa aputietue/luokka parameterja varten Välitä tietoa rajapintaviitteinä/osoittimina, älä konkreettisen luokan olioina Älä yritä luoda yleistä parametrina annettavaa tietotyyppiä, jolla välitetään osin tietoa rajapinnan suuntaan, osin takaisin rajapinnan käyttäjälle
Parametrit ja paluuarvot Käytä mahdollisimman tarkkaa parametrityyppiä virheet käännösaikaisia, ei ajoaikaisia Vältä totuurarvo (bool) parametreja, luettavuus: widget->piirra( false ); Mihin false viittaa? widget->piirrailmantaustaa(); widget->piirra(); Tai parempi (oletusparametrin kera): widget->piirra( Widget::ILMAN_TAUSTAA ); Käytä samaa järjestystä parametreilla, ei näin void kopioi( Tyyppi* lahde, Tyyppi* kohde ); void kopioijoskohdetyhja(tyyppi* kohde, Tyyppi* lahde );
Erilaisia rajapintoja Luokkarajapinnat, toteutustason asiat Julkisemmat, pakettien, isompien kokonaisuuksien väliset (esim. Mallin rajapinta) Ohjelmistotason rajapinnat, API:t (Application programming interface) Tuote, jonka valittuihin ominaisuuksiin tarjotaan pääsy Liitännäisjutut jne. Ohjelmistokehykset/tuoterungot Eri tason rajapinnat sisältävät erilaisia vaatimuksia, yleistasolla hyvä rajapinta kaikissa samanlainen
Erityisesti julkisemmiksi tarkoitetut rajapinnat (API:t) Dokumentaatio vs. rajapinnat Rajapinnan muutokset jo käytössä oleville järjestelmille Esim. E-resepti ja rajapintamuutokset käytössä olevaan järjestelmään apteekkien ja sairaaloiden tietojärjestelmät solmussa Muutetaan parametreja, poistetaan jotain tai muutetaan toimintaa niin, että esimerkiksi esi- tai jälkiehdot muuttuvat, rajapinnan käyttäjät joutuvat sopeutumaan
Laajemmat rajapinnat, suunnitteluratkaisuja Yleiskäyttöisyys ja helppous vs. monipuolisuus, täysi kontrolli Molempia vaikea saada samoilla rajapinnoilla Mahdollisia ratkaisuja (esim. Googlen Androidin rajapinnat) Tarjotaan yleiskäyttöiset korkemman tason rajapinnat (esim. Kameraan, sijaintitietoihin, jne.) Kohtuullisen yksinkertaisella tavalla mahdollisuus ohjata laitteita, saada niiltä tietoa Tarve päästä nysväämään kameraa fyysisesti, päästä käsiksi raakadataan, alemman tason laitteistoläheisemmät rajapinnat
Rajapintadokumentaatiota Rajapintojen dokumentointi voi olla tarpeen, esim. API:t Mihin rajapinta on tarkoitettu, mitä sillä pystyy tekemään? Lyhyt esimerkkikoodi ja kuva/kuvaus siitä, mitä koodi tekee, auttaa hahmottamaan käyttöä Dokumentaation muoto, usein jotain muuta kuin perinteinen dokumentti Tutorial, weppisivu, erillinen help-tiedosto, jopa video Hyvä rajapintadokumentti Lyhyt Ytimekäs, mutta kattava Ei huijaa (esim. Näin helposti tällä voi tehdä juttuja, mutta esimerkkiä seuraamalla homma menee metsään mitään mikkihiiri-juttuja monimutkaisemmalla) Ohjaa tarvittaessa paikkaan, josta voi saada lisätietoa Esimerkki ihan asiallisesta rajapintadokumentista: http://qt-project.org/doc/qt-5/qpushbutton.html
REST, CRUD yms. REST (GET,PUT,POST,DELETE) (Get, Delete idempotentit) CRUD (create, read(retrieve), update, delete) Ajatustapa, kutsut toisistaan riippumattomia, itsenäisiä Palvelupää ei pidä yllä tilatietoja käyttäjistään, vaan asiakkaat käyttävät palveluita REST ja web: rajapintojen käyttö, muut palvelut jne. Resource POST (create) GET (read) PUT (update) /students Luo uuden Lista opiskelijoista Päivitys, opiskelijalistalle /students/1234567 Virhe Näytä Tero Teekkari Jos olemassa, päivitä Tero DELETE (delete) Poista kaikki Poista Tero
Esimerkkejä Mukavia rajapintoja? http://www-01.ibm.com/support/knowledgecenter/api/content/ssw_i5_54/apis/aplist.htm http://www-01.ibm.com/support/knowledgecenter/api/content/ssw_i5_54/apis/qdfrtvfd.htm http://www-01.ibm.com/support/knowledgecenter/api/content/ssw_i5_54/apis/quidspp.htm EResepti http://www.kanta.fi/web/ammattilaisille/sahkoisen-reseptin-maarittelyt REST (ihan asiallinen rajapintadokumentaatio): https://www.blinksale.com/api/ The Worst API ever made, blog-post: http://mollyrocket.com/casey/stream_0029.html
Yhteenveto Opittiin singletonin pahuudesta Käytiin läpi rajapintasuunnittelua, opittiin hyvän ja huonon rajapinnan tunnusmerkistöä Muuta Your API is bad https://leanpub.com/yourapiisbad/read Framework design Guidelines: http://ptgmedia.pearsoncmg.com/images/9780321545619/samplepages/0321545613.pdf http://msdn.microsoft.com/en-us/library/czefa0ke.aspx http://www.newt.com/java/goodapidesign-joshbloch.pdf