Skip to content

mallivastaus ja arvosteluperusteet

Matti Luukkainen edited this page May 11, 2016 · 15 revisions

a

Ohjelmiston suunnittelu tehdään yhden erillisen vaiheen sijaan vähitellen, iteraatio iteraatiolta. Alussa tehdään todennäköisesti vaan karkealla tasolla oleva arkkitehtuurisuunnittelu. Menetelmällä saatu arkkitehtuuri/design on useimmiten itseorganisoituvan tiimin omalla vastuulla.

Hyvää: Tiimillä on suurempi motivaatio sitoutua itse suunnittelemaansa arkkitehtuuriin (vrt norsunluutorni-arkkitehdin ulkoapäin pakottama arkkitehtuuri). Kuten vaatimusten suhteen, ketterässä on oletuksena, että myöskään arkkitehtuuria/suunnittelua ei pystytä etukäteen tekemään optimaalisesti, sillä tiimin tuntemus ongelma-alueeseen on vielä vajavainen. Näinollen parempi arkkitehtuuri/design saadaan aikaan muodostamalla se vähitellen prosessin kuluessa.

Huonoa: vaatii suurta kurinalaisuutta että ohjelman sisäinen laatu ei pääse rapistumaan. Tämä taas saattaa heijastua velositeetin alenemisena.

b

Backlog

luokalla on liikaa vastuita: storyjen arkistointi JA erilaisten listausformaattien tuottaminen

formaatin tuottamisen "kovakoodaamisen" (if-helvetti) takia laajennettavuus esim. uusien listausformaattien lisääminen vaikeutuu. metodi on myös turhan monella abstraktiotasolla liikkuva

luokalla turha riippuvuus BacklogReader-luokkaan, testaus vaikeutuu

koodissa toisteisuutta eri kriteerein tapahtuvan storyjen listaamisen koodissa

UserStory

primitive obsession: taskit ja status on "koodattu" stringeinä, se ei ole järkevää. Taskeille tulee todennäköisesti jossain vaiheessa uusia ominaisuuksia (esim taskin estimaatti). Statuksen koodaaminen stringinä altistaa esim kirjoitusvirheille, jos olisi oma tyyppi statusten, esim. enum, tyyppitarkastus hoitaisi virheet. Ratkaisu olisi myös luettavuudeltaan parempi.

pisteytys: 0.5p per identifioitu rike. 4 bongausta riitti täysiin pisteisiin

(c)

Status ja Task omiksi luokikseen:

public enum Status{
    TODO, STARTED, WAITING_FOR_TESTS, COMPLETED
}

public class Task {
    private String name;

    public Task(String name) {
        this.name = name;
    }   
}

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;
    }
   
}

Backlogiin geneerinen metodi, joka palauttaa ehdon toteuttavat storyt. Muut metodit käyttämään tätä. Oleellisesti tässä käytössä strategy-suunnittelumalli.

BacklogReader anntetaan konstruktoriparametrina, eli hyödynnetään riippuvuuksien injektointia.

Metodi export eriytetty omaksi luokaksi joka annetaan metodille export parametrina, tässäkin sovellus strategy-suunnittelumallin käytöstä. Metodi on myös nyt composed method -mallin mukainen, eli ei liiku monella eri abstraktiotasolla.

public class Backlog {

    private List<UserStory> stories;
    private BacklogReader reader;

    public Backlog(BacklogReader reader) {
        this.reader = reader;
        stories = reader.readFromFile();
    }

    public List<UserStory> matching(Predicate<UserStory> condition) {
        return stories.stream().filter(condition).collect(Collectors.toList());
    }

    public List<UserStory> completedStories() {
        return matching(s -> s.done());
    }

    public List<UserStory> remainingStories() {
        return matching(s -> !s.done());
    }

    public List<UserStory> epics() {
        return matching(s -> s.estimate() > 20);
    }

    public String export(StoryFormater formater, boolean onlyRemaining) {
        Filter filter = onlyRemaining ? s -> !s.done() : s -> true;

        String stories = "";

        // seuraavan voisi tehdä java8-onlinerillä mutta tällä kertaa perinteinen looppi
        for (UserStory story : matching(filter)) {
            stories += formater.format(story);
        }

        return formater.start() + stories + formater.end();
    }

}

Formatoinnin hoitava luokkahierarkia:

public abstract class StoryFormater {
    public String start(){
        return "";
    }

    public String end(){
        return "";
    }
    
    public abstract String format(UserStory story);
}


public class TextStoryFormater extends StoryFormater {

    @Override
    public String format(UserStory story) {
        return story.name() + " " + story.estimate() + " " + story.status() + "\n";
    }
    
}

public class XmlStoryFormater extends StoryFormater {
    @Override
    public String start() {
        return "</story>\n";
    }    
    
    @Override
    public String format(UserStory story) {
        return "<story>"
                + "<name>"     + story.name()       + "</name>"
                + "<estimate>" + story.estimate()   + "</estimate>"
                + "<status>"   + story.status()     + "</status>"
                + "<story>\n";
    }

    @Override
    public String end() {
        return "</stories>";
    }
}

kurssisivu

laskarit 1 2 3 4 5 6 7

Clone this wiki locally