Skip to content

Latest commit

 

History

History
442 lines (350 loc) · 20.7 KB

koe2020.md

File metadata and controls

442 lines (350 loc) · 20.7 KB
layout title inheader permalink
page
Kokeen mallivastaukset ja arvosteluperusteet
false
/koe2020/

Syksyn 2020 koe

Näet kustakin tehtävstä saamasi pisteet moodlessa.

Tehtävä 1

Tehtävän arvioi Petrus Peltola. Omasta arvioinnista voi tarvittaessa kysyä [email protected] tai telegramissa

Tehtävässä oli kahdeksan alakohtaa, jokaisesta sai siis enintään 0.5p. Tehtävässä oli tarkoitus eritellä ja avata Scrumin tarjoamia konkreettisia apukeinoja annettujen tilanteiden ratkaisemiseksi. Joka kohdassa oli ennalta mietitty jokin tai joitakin keinoja joita haettiin, mutta jos perusteli ratkaisunsa hyvin niin pisteitä sai myös muista keinoista. Yleisluontoisista toteamuksista (esim. ‘Scrum kannustaa läpinäkyvyyteen’) ilman tarkempaa selitystä ei saanut pisteitä. Myöskään pelkästä keinon nimeämisestä ilman selostusta sen tuomasta avusta ei saanut täysiä pisteitä, eikä järkevästä keinosta liian suppeilla perusteilla. Jos järkevä keino oli selitetty sanallisesti hyvin, sai siitä pisteitä vaikka sitä ei olisi mainittu suoraan nimeltä.

  • Kohta 1: Scrum ei ota suoraan kantaa testauksen toteuttamiseen, mutta kehottaa tiimiä sopimaan definition of donesta, jossa testauskäytännöt kannattaa määritellä. Mm. retrospektiivin tai hyväksymiskriteerien tarjoamisesta ratkaisuna saattoi saada pisteitä, jos perustelut olivat hyvät.

  • Kohta 2: Jami ei voi vaatia ideoidensa toteuttamista välittömästi, onhan Scrumissa sprintin scope vakio, eli se päätetään sprintin suunnittelussa. Myös Scrum master suojaa tiimiä markkinointiosaston älynväläyksiltä. Mm. DEEP product backlogista kertomisesta saattoi saada pisteitä, jos perustelut olivat hyvät.

  • Kohta 3: Kristian ja Petrus eivät enää tee turhaa työtä, jos firmassa aletaan käyttämään tehokkaasti sprint backlogia, johon taskien status ja tekijä kirjataan (‘Kanban’ ja ‘taskboard’ myös ok). Täydet pisteet sai myös jos perusteli hyvin miksi päivittäiset dailyt auttaisivat tilanteeseen. Mm. sprint planningin tarjoamisesta ratkaisuna saattoi saada pisteitä, jos perustelut olivat hyvät.

  • Kohta 4: Sprintin storyista tiimi sai paremmin selvää jos toimari Luukkaisen ja muiden managerien sijaan konsultoitiin vain Scrumin asettamaa yksittäistä product owneria. Täydet pisteet sai myös jos perusteltiin hyvin miksi perusteellinen sprint planning oli paikallaan. Mm. product backlogin soveltamisen kertomisesta saattoi saada pisteitä, jos perustelut olivat hyvät.

  • Kohta 5: Toistuvista ongelmista parhaiten päästään eroon retrospektiivissa, kunhan perusteet käydään läpi. Mm. definition of donen päivittämisestä kuvattujen ongelmien ratkaisuilla saattoi saada pisteitä, jos perustelut olivat hyvät.

  • Kohta 6: Kallen sankarikoodaus-epicit vesitettiin vakiomittaisilla sprinteillä. Koodausrauhan rikkomiseen riitti myös kertomus sprintin lopuksi toimivasta versiosta (potentially shippable product increment). Mm. dailyjen, sprint reviewin taikka työmäärän estimoinnin tarjoaminen saattoi tuoda pisteitä, jos perustelut olivat hyvät.

  • Kohta 7: Kaoottiset sprintit laitettiin kuriin hyvällä sprint planningilla, jossa huolellisesti käytiin läpi miten valitut storyt toteutetaan taskeina. Mm. backlog refinement saattoi tuoda pisteitä, jos perustelut olivat hyvät.

  • Kohta 8: Ei tarvitse viedä testaamatta tuotantoon, kunhan tiimi on cross-functional, eli pitää sisällään kaiken tarvittavan osaamisen tuotantovalmiiden storyjen kehittämiseksi (eli osaa myös testata storyt itse). Huomaa että kaikkien tiimin kehittäjien ei tarvitse osata joka askelta itse, kunhan tiimissä on tarpeeksi monipuolista osaamista.

Tehtävä 2

Tehtävän arvioi Kristian Krok. Omasta arvioinnista voi tarvittaessa kysyä [email protected] tai telegramissa

Hyvä user story sisältää asiakkaan kielellä kuvatun, asiakkaalle tai loppukäyttäjälle arvoa tuottavan, toiminnallisuuden. Projektipäällikkö Jamin, Kaverin kirjoittama story on laaja sekä rönsyilevä ja sisältää kuvaukset useammasta toteutettavasta toiminnallisuudesta. Kaverin kirjaaman storyn tekstuaaliseen kuvaukseen sisältyy prioriteettikuvaus, jonka perusteella pyydetyn toiminnallisuuden toteuttaminen tiedetään korkean prioriteetin tehtäväksi. Storyn tulisi siis olla INVEST-kriteeristön mukainen, sisältää hyväksymiskriteerit ja sen vaatiman työmäärän tulisi olla osapuilleen selvillä.

Vastaukset pisteytettiin seuraavien kriteerien perusteella, tehtävän enimmäispistemäärä 4p:

  • Storyn ei tulisi sisältää teknistä jargonia, vaan toiminnallisuuden kuvauksen asiakkaan ymmärtämällä kielellä (0.25p). Teknisten yksityiskohtien kuten tietokantakerroksen toteutuksen paikka on taskeina sprint backlogilla (0.25p).
  • Storyltä puuttuu hyväksymäkriteerit (0.25p) ja estimaatti (0.25p).
  • Story on liian suuri toteutettavaksi (epiikki) 0.5p.
  • Storyn kuvauksen ei tule ottaa kantaa toteutusaikatauluun (‘kannattaa samantien koodata mahdollisuus...’) 0.5p. Toteutusjärjestykseen vaikutetaan priorisoimalla storyja backlogilla 0.25p.
  • Ei-toiminnallisia vaatimuksia, kuten verkkokaupan suorituskykyä ja skaalautuvuutta käyttäjäpiikkeihin, ei ole aina järkevää ottaa osaksi muuta toiminnallisuutta määrittävää storya (0.25p mikäli ainoastaan eriytetty käyttäjäpiikin tarkastelu omaksi storyksi ilman mainintaa testattavuudesta). Jos ne kuitenkin ovat tarkoituksenmukaisia, ne tulee määritellä muodossa jota kyetään testaamaan (0.25p), kuten:
    • “Käyttäjän vasteaika saa olla korkeintaan 𝑥 millisekuntia 99 % tapauksista, kun sivulla on korkeintaan 𝑦 yhtäaikaista käyttäjää”, missä 𝑥,𝑦 ∈ ℕ. Storyn kuvauksessa ei tule käyttää ylimalkaisia ilmaisuja kuten “muut mahdollliset maksuvaihtoehdot“ tai ”muut toimitustavat” 0.5p.
  • Story tulisi pilkkoa osiin: (pelkkä maininta pilkkomisesta ilman ehdotusta 0.25p TAI story jaettu taskeihin useammaksi storyksi pilkkomisen asemesta 0.25p) s.e.
    • tuotteiden maksutavat 0.25p
      • Storyssa mainitut maksutavat luottokortti, MobilePay ja muut maksutavat kannattaa pilkkoa omiksi storyiksi. Näistä eritoten muut maksutavat tarvitsevat vielä lisäselvitystä, mm. Voiko ostoksia maksaa kiiltävillä kivillä tai paketilla suolaa, ja jos voi, mistä sovellus tarkistaa käypiä vaihtokursseja?
    • Toimitusmahdollisuudet 0.25p
      • Storyssa mainitut toimitustavat posti, UPS ja muut toimitustavat kannattaa myös pilkkoa omiksi storyiksi. Erillistä lisäselvitystä tulee tehdä muista maksutavoista
      • Arvostelussa ei oteta huomioon UDP-verkkoprotokollan eriyttämistä omaksi storyksi ja tuotteiden toimitusvaihtoehdoksi, vaikka tätä useammassa vastauksessa ehdotettiinkin.
  • Mikäli vastauksessa on pilkottu maksutavat ja toimitusmahdollisuudet omiksi storyiksi, pisteytetään 0.75p sijasta 1p.
  • Storyssa mainittujen ostosten toimitusosoitteen tallennuksen sekä varastosaldon päivittämisen todettu kuuluvan storyn teknisiin taskeihin 0.5p.
  • Vastauksessa on perattu storya INVEST-kriteeristön pohjalta 0.5p (pelkkä maininta INVEST-kriteeristöstä 0.25p).
  • Vastauksessa on esitelty parannusehdotuksia INVEST-kriteeristön pohjalta 0.5p.
  • Muista kurssimateriaalin pohjalta tai muutoin hyvin perustelluista huomioista voi kertaluonteisesti saada 0.25, 0.5 tai 0.75p.

Tehtävä 3

Tehtävän arvioi Jussi Laisi. Omasta arvioinnista voi tarvittaessa kysyä [email protected] tai telegramissa

Käsite 'Just In Time -tuotanto (JIT)'

  • Tavoitteena aloittaa tuotteen valmistaminen vasta kun ostaja jo tilannut tuotteen: tässä hyötynä asiakkaiden muuttuviin tarpeisiin valmistautuminen toisin kuin massatuotannossa
  • Toyotan JIT-tuotantomallissa optimoinnin kohteena tuotteen sykliaika, ja sitä kautta tuotteen nopeaa virtaamista (engl. flow) tilauksesta asiakkaalle hidastavan hukan (engl. waste) eliminointi; sovellettavissa yhtä lailla ohjelmistotuotantoon

Käsite 'sykliaika'

  • Tavoitteena tuote eli ohjelmiston toiminnallisuus tilauksesta asiakkaalle mahdollisimman nopeasti, toisin sanoen mahdollisimman lyhyt sykliaika (engl. cycle time) tilauksesta toimitukseen

Lyhyen sykliajan hyödyt ohjelmistotuotannossa

  • Ketterässä ohjelmistotuotannossa pyritään arvon virtauksen tehostamiseen; halutut ominaisuudet valmiina nopeasti asiakkaan käyttöön
  • Koska tuotteen eli halutun toiminnallisuuden sykliaika tilauksesta tuotantoon on lyhyt, mitkä tahansa muutostarpeet tai ongelmat paljastuvat nopeasti ja niihin voidaan reagoida ripeästi

Miten sykliaikaa pyritään lyhentämään ohjelmistotuotannossa

  • Yleisten Agile- ja Lean- ym. periaatteiden soveltaminen: runsaasti esimerkkejä kurssimateriaalissa esim. pull-systeemi, Work in Progress -rajoitteet, erilaiset hukkatyypit ja niiden eliminointi
  • Esim. Scrumin pyrkimyksenä viedä uusia ominaisuuksia tuotantoon sprinteittäin
  • Esim. viime vuosina sykliä tihennetty, ja jatkuva käyttöönotto eli continuous deployment voi realisoitua commiteittain tehtävänä uuden version julkaisuna jopa useita kertoja päivittäin

Käsite 'DevOps'

  • Toimintamalli, jossa kehittäjät ja ylläpitäjät eli dev- ja ops-ihmiset työskentelevät tiiviisti yhdessä
  • Yhteinen työnteon ja kommunikaation tapa, jonka pyrkimyksenä tehdä sovelluskehityksen aikaansaannosten käyttöönotto mahdollisimman sujuvaksi

Mitä tekemistä DevOpsilla on sykliajan lyhentämisen kanssa

  • DevOps on tapa viedä ketteryyttä pidemmälle ja mahdollistaa ketterien tiimien todellinen monialaisuus (cross-functionality)
  • Tiimillä kyky viedä toteuttamansa uudet toiminnallisuudet tuotantoympäristöön asti sekä jopa testaamaan ja operoimaan niitä tuotannossa lyhyemmän sykliajan puitteissa

Tehtävä 4

Tehtävän arvioi Silva Perander. Omasta arvioinnista voi tarvittaessa kysyä [email protected] tai telegramissa

Mitä tarkoitetaan ohjelmiston sisäisellä laadulla? (1p)

Ohjelman sisäisellä laadulla tarkoitetaan koodin sisäisen rakenteen hyvyyttä. Siihen vaikuttavia seikkoja ovat mm. koodin jatkokehityksen helppous, virheiden jäljityksen ja korjaamisen helppous ja kuinka vaivatonta koodin toiminnallisuuden oikeellisuus varmistaminen on muutoksia tehtäessä.

Mitä sisäisen laadun kannalta ongelmallisia asioita esimerkkikoodissa on? (2p)

Neljä näistä riittää täysiin pisteisiin:

  • Backlog-luokan on konkreettinen riippuvuus BacklogReader-luokasta ja se on turhan tietoinen sen toteutuksen yksityiskohdista. (0.5p)
  • Backlog-luokan metodeissa epics ja completedStories on paljon toistoa. Molemmissa metodeissa loopataan backlogin storyt läpi ja lisätään tietyn ehdon perusteella storeja palautettavaan listaan. (0.5p)
  • Storyn statuksen tyyppi on String, joka antaa statuksen arvolle turhan laajan joukon vaihtoehtoja. Luokan käyttäjä saattaa vahingossa esimerkiksi kirjoittaa “COMPLETED” statuksen pienillä kirjaimilla tai tehdä muun kirjoitusvirheen, jota ei huomata ohjelman käännöksen aikana ja joka muuttaa ohjelman suoritusta merkittävästi. (0.5p)
  • Backlog-luokka on sille kuulumattomia vastuita, kuten formatointi sen export-metodissa. Tämä rikkoo single-responsibility-periaatetta. (0.5p)
  • Kaikkien eri export-formaattien tuottamisen ei tulisi kuulua saman metodin/luokan vastuulle (0.5p)
  • Muista huomioita (0.25-0.5p):
    • Tyyli ei täysin yhtenäistä esim aaltosulkujen käytön ja tyhjien rivien osalta
    • Storyissä taskit ovat merkkijonoja, ei välttämättä hyvä idea sillä taskilla ei voi olla nyt statusta, tai estimaattia
    • Sprintti ilmaistaan numerolla, onko hyvä idea? Ei voi esim sisältää alkamis- ja päättymispäivää

Tehtävä 5

Tehtävän arvioi Matti Luukkainen. Omasta arvioinnista voi tarvittaessa kysyä [email protected] tai telegramissa

Selitä miten refaktoroisit alla olevan koodin soveltaen suunnittelumalleja tai muita tilanteeseen sopivia ratkaisuja

  • BacklogReader-luokan voisi refaktroida rajapinnaksi BacklogReader, jossa on metodit read ja save. Nykyiselle BacklogReader-luokalle voisi antaa nimen FileBacklogReader ja refaktoroida se toteuttamaan BacklogReader-rajapinta. Backlog-luokka saisi BacklogReader-rajapinnan toteuttavan luokan olion konstruktorissa. (0.5p)
  • Backlog-luokan metodien completedStories ja epics loopit voisi korvata Stream-luokan filter-metodilla. Toinen vaihtoehto olisi toteuttaa StoryFilter-rajapinta, jolla on metodi match, joka kertoo läpäiseekö story-olio filtterin. Tämän rajapinnan toteuttavia luokkia voisi olla CompletedFilter ja EpicFilter. Nyt filtteröintiin riittää yksi metodi, jolle annetaan haluttu StoryFilter-rajapinnan toteuttava olio. Filtteröinnin voisi myös kokonaisuudessaan siirtää omaan luokkaansa. (0.5p)
  • Story-luokan status-attribuutin tyypin voisi muuttaa enumiksi. (0.5p)
  • Backlogin formatoinnin eri formaateissa Backlog-luokan export-metodissa voisi eristää omaksi luokakseen. (0.5p)
  • Eri formaatit kannattaisi tuottaa omina abstraktin luokan BacklogFormatter template metodia hyödyntävinä luokkina. Erikoistavat luokat luokat TextBacklogFormatter ja XMLBacklogFormatter. (0.5p)
  • Muita ideoita: (0.25p)
    • Tyylilliset parannukset mm tyhjien rivien, välilyöntien, kaarisulkujen paikan suhteen
    • Luokka Sprint sensijaan että ilmaistaan asia numerolla
    • Luokka Task

Alkuperäinen koodi

public class UserStory {
    private int sprint;
    private String name;
    private int estimate;
    private String status;
    private List<String> tasks;
    public UserStory(String name, int sprint, int estimate, String status, List<String> tasks) {
        this.name = name;
        this.sprint = sprint;
        this.estimate = estimate;
        this.status = status;
        this.tasks = tasks;
    }
    public boolean done(){
       return status.equals("COMPLETED");
    }
    public void changeStatus(String status) {
        this.status = status;
    }
    public int estimate() {
        return estimate;
    }
    public String status() {
        return status;
    }
    public String name() {
        return name;
    }
}

public class BacklogReader {
    private String file;
    public BacklogReader(String file) {
        this.file = file;
    }
    public List<UserStory> readFromFile(){
        List<UserStory> items = new ArrayList<>();
        // read stories from file
        return items;
    }
    public void saveToFile(){
        // save stories to file
    }
}

public class Backlog {
    private List<UserStory> stories;
    private BacklogReader reader;
    public Backlog() {
        reader = new BacklogReader("tmc-cli");
        stories = reader.readFromFile();
    }

    public List<UserStory> completedStories() {
        ArrayList<UserStory> completed = new ArrayList<>();
        for (UserStory story : stories) {
            if ( story.done() ) {
                completed.add(story);
            }
        }
        return completed;
    }

    public List<UserStory> epics() {
        ArrayList<UserStory> epicStories = new ArrayList<>();
        for (UserStory story : stories) {
            if ( story.estimate()>20 ) {
                epicStories.add(story);
            }
        }
        return epicStories;
    }

    public String export(String format, boolean onlyRemaining) {
        String output = "";
        if ( format.equals("xml")){
            output = "<stories>";
        }
        for (UserStory story : stories) {
            if (onlyRemaining && !story.done() ) {
                continue;
            }
            if ( format.equals("text")) {
                output += story.name()+ " " + story.estimate()+ " " + story.status() + "\n";
            } else if ( format.equals("xml"))  {
                output += "<story><name>" + story.name() +
                            "</name><estimate>" + story.estimate() +
                            "</estimate><status>" + story.status() +
                          "</status><story>\n";
            } else {
                throw new IllegalArgumentException("format not supported");
            }
        }
        if ( format.equals("xml")){
            output += "</stories>";
        }
        return output;
    }
    // other methods...
}

Eräs tapa refaktorida koodi:

enum Status {
    NEW, STARTED, COMPLETED
}

public class Task {
    private String content;
    private boolean status;
}

public class UserStory {
    private int sprint;
    private String name;
    private int estimate;
    private Status status;
    private List<Task> tasks;
    
    public UserStory(String name, int sprint, int estimate, Status status, List<Task> tasks) {
        this.name = name;
        this.sprint = sprint;
        this.estimate = estimate;
        this.status = status;
        this.tasks = tasks;
    }
    
    public boolean done(){
       return status == Status.COMPLETED;
    }
    
    public void changeStatus(Status status) {
        this.status = status;
    }
    public int estimate() {
        return estimate;
    }
    public Status status() {
        return status;
    }
    public String name() {
        return name;
    }
}

public class Backlog {
    private List<UserStory> stories;
    private BacklogReader reader;
    
    // injektoidaan readed-olio
    public Backlog(BacklogReader reader) {
        stories = reader.read();
    }
    
    // yleinen filtteröintimerodi
    public List<UserStory> storiesBy(StoryFilter filter) {
        ArrayList<UserStory> filtered = new ArrayList<>();
        for (UserStory story : stories) {
            if ( filter.pass(story) ) {
                filtered.add(story);
            }
        }
  
        return filtered;
    }
    
    public List<UserStory> completedStories() {
        return storiesBy(new CompletedFilter());
    }
   
    public List<UserStory> remainingStories() {
        return storiesBy(new NotCompletedFilter());
    }    
    
    public List<UserStory> epics() {
        return storiesBy(new EpicFilter());
    }
    
    public String export(String format, boolean onlyRemaining) {
        return Exporter.getExporter(format).export(onlyRemaining ? remainingStories() : stories);
    }
    
    // other methods...
}

// reader rajapinnan taakse

public interface BacklogReader {
    List<UserStory> read();
    void save(); 
}

public class FileBacklogReader implements BacklogReader {
    private String file;
    
    public FileBacklogReader(String file) {
        this.file = file;
    }
    
    @Override
    public List<UserStory> read() {
        List<UserStory> items = new ArrayList<>();
        // read stories from file
        return items;
    }
    
    @Override
    public void save(){
        // save stories to file
    }
}

// storylistojen filtteröinti

interface StoryFilter {
    boolean pass(UserStory story);
}

public class EpicFilter implements StoryFilter {
    @Override
    public boolean pass(UserStory story) {
        return story.estimate()>20; 
    }  
}

public class CompletedFilter implements StoryFilter {
    @Override
    public boolean pass(UserStory story) {
        return story.done();
    }
}

// exportaus hoidetaan templatemetodia hyvödynäten

public abstract class Exporter {

    public static Exporter getExporter(String format) {
        if ( format.equals("text")) {
            return new TextExporter();
        } else if ( format.equals("xml"))  {
            return null;
        } else {   
            throw new IllegalArgumentException("format not supported");
        }
    }
    
    // eksporttauksen hoitava template-metodi
    public String export(List<UserStory> stories) {
        String output = prefix();
        
        for (UserStory story : stories) {
            output += exportStory(story);
        }
        
        return output +  postfix();
    }    

    protected abstract String exportStory(UserStory story);   
    protected abstract String postfix(); 
    protected abstract String prefix();
}


public class XmlExporter extends Exporter {

    @Override
    protected String exportStory(UserStory story) {
        return "<story><name>" + story.name() +
            "</name><estimate>" + story.estimate() +
            "</estimate><status>" + story.status() +
            "</status><story>\n";
    }

    @Override
    protected String postfix() {
    return "</stories>";
    }

    @Override
    protected String prefix() {
        return "<stories>";
    }
    
}


public class TextExporter extends Exporter {
    @Override
    protected String exportStory(UserStory story) {
        return story.name()+ " " + story.estimate()+ " " + story.status() + "\n";
    }

    @Override
    protected String postfix() {
        return "";
    }

    @Override
    protected String prefix() {
        return "";
    }
}

Tehtävät 6-

Tehtävän arvioi Moodle.

Huomaa, että moodle näyttää oikein/väärin-tehtävien pistemäärän väärin. Jokainen oikein vastattu väittämä tuo todellisuudessa 0.25 pistettä.