Apua tehtävien tekoon kurssin Discord-kanavalla sekä zoom-pajassa:

  • Torstaisin klo 14-16 tällä linkillä

Tehtävissä 1–2 jatketaan gitin harjoittelua. Tehtävät 1 ja 2 eivät näy palautuksissa mitenkään.

Tehtävät 3–5 liittyvät materiaalin ohjelmistosuunnittelua käsittelevän osan 4 niihin lukuihin, joihin on merkitty [viikko 5].

Tehtävä 6 käsittelee retrospektiivitekniikoita.

Typoja tai epäselvyyksiä tehtävissä?

Tee korjausehdotus editoimalla tätä tiedostoa GitHubissa.

Tehtävien palauttaminen

Tehtävät palautetaan GitHubiin ja merkitsemällä tehdyt tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu-avoin-2022.

Katso tarkempi ohje palautusrepositorioita koskien täältä.

1. git: vahingossa tuhotun tiedoston palautus (versionhallinta)

Edellisessä tehtävässä palasimme jo menneisyyteen checkouttaamalla tagillä merkittyyn kohtaan. Katsotaan nyt miten voimme palauttaa jonkun menneisyydessä olevan tilanteen uudelleen voimaan.

Voit tehdä tämän ja seuraavan tehtävän mihin tahansa repositorioon, tehtävät eivät näy palautuksissa.

  • Tee jokin tiedosto, esim. nimeltään xxx, lisää ja committaa se.
  • Poista tiedosto ja committaa.
  • Tee jotain muutoksia johonkin muuhun tiedostoon ja committaa.
  • Historiasi näyttää seuraavalta:
(1) - (2) - (3)
  • Nykyhetki eli HEAD on (3). Commitissa (1) tiedosto xxx on olemassa nykyhetkellä ja (2):ssa xxx:ää ei ole. Historiaa voit tutkia komennoilla git log ja gitk.
  • Seuraavaksi palautetaan tiedosto xxx.
  • Aloita selvittämällä sen commitin id, jossa tiedosto xxx vielä on olemassa. Tämä onnistuu gitk:lla tai git log -komennolla (tai jos haluaa päästä helpolla, komennolla git log --pretty=oneline --name-status).
  • Jatka palauttamalla tiedosto xxx komennolla git checkout id -- xxx, jossa id on edellisessä vaiheessa selvittämäsi commitin id.
  • Varmista lopuksi tiedoston xxx ilmestyneen staging-alueelle komennolla git status.
  • Lisää vielä tiedoston palauttamisen jälkeen muuttunut tilanne versiohistoriaan komennoilla git add ja git commit.

Huom: Koko id:tä ei komennossa tarvitse antaa. Voit antaa alusta niin monta merkkiä, että niiden perusteella id voidaan päätellä yksikäsitteisesti repositoriosi historiassa:

“Generally, eight to ten characters are more than enough to be unique within a project. For example, as of October 2017, the Linux kernel (which is a fairly sizable project) has over 700,000 commits and almost six million objects, with no two objects whose SHA-1s are identical in the first 11 characters.” 7.1 Git Tools - Revision Selection

Samalla tavalla onnistuu olemassa olevan tiedoston vanhan version palauttaminen.

2. Git: commitin muutosten kumoaminen (versionhallinta)

  • Huomaamme, että juuri tehty commit oli virhe, joten kumotaan se sanomalla git revert HEAD --no-edit. Komennossa HEAD viittaa committiin, jonka kohdalla nyt ollaan (ns. “nykytilanne” versiohistoriassa).
  • Syntyy uusi commit, jossa edellisessä tehdyt muutokset on kumottu.

Ilman komentorivivalitsinta –no-edit pääset muokkaamaan kumoamiseen liittyvään commitiin tulevaa viestiä. Ja komennolla git checkout HEAD^ pääsemme takaisin kumottuun tilanteeseen, eli mitään ei ole lopullisesti kadotettu.

Vastaavalla tavalla voidaan revertata mikä tahansa commit, eli: git revert id, jossa id on kumottavan commit:n id.

3. Tenniksen pisteenlaskun refaktorointi

Kurssirepositorion hakemistosta koodi/viikko5/tennis löytyy ohjelma, joka on tarkoitettu tenniksen pisteenlaskentaan.

Pisteenlaskennan rajapinta on yksinkertainen. Metodi get_score kertoo voimassa olevan tilanteen tenniksessä käytetyn pisteenlaskennan määrittelemän tavan mukaan. Sitä mukaa kun jompikumpi pelaajista voittaa palloja, kutsutaan metodia won_point, jossa parametrina on pallon voittanut pelaaja.

Esim. käytettäessä pisteenlaskentaa seuraavasti:

game = TennisGame("player1", "player2")

print(game.get_score())

game.won_point("player1")
print(game.get_score())

game.won_point("player1")
print(game.get_score())

game.won_point("player2")
print(game.get_score())

game.won_point("player1")
print(game.get_score())

game.won_point("player1")
print(game.get_score())

tulostuu

Love-All
Fifteen-Love
Thirty-Love
Thirty-Fifteen
Forty-Fifteen
Win for player1

Tulostuksessa siis kerrotaan pelitilanne kunkin pallon jälkeen kun player1 voittaa ensimmäiset 2 palloa, player2 kolmannen pallon ja player1 loput 2 palloa.

Pisteenlaskentaohjelman koodi toimii ja sillä on erittäin kattavat testit. Koodi on kuitenkin sisäiseltä laadultaan kelvotonta.

Tehtävänä on refaktoroida koodi luettavuudeltaan mahdollisimman ymmärrettäväksi. Koodissa tulee välttää “taikanumeroita” ja huonosti nimettyjä muuttujia. Koodi kannattaa jakaa moniin pieniin metodeihin, jotka nimennällään paljastavat oman toimintalogiikkansa.

Etene refaktoroinnissa todella pienin askelin. Suorita testejä mahdollisimman usein. Yritä pitää ohjelma koko ajan toimintakunnossa.

Jos haluat käyttää jotain muuta kieltä kuin Pythonia, löytyy koodista ja testeistä versioita useilla eri kielillä osoitteesta https://github.com/emilybache/Tennis-Refactoring-Kata

Tehtävä on kenties hauskinta tehdä pariohjelmoiden. Itse tutustuin tehtävään kesällä 2013 Extreme Programming -konferenssissa järjestetyssä Coding Dojossa, jossa tehtävä tehtiin satunnaisesti valitun parin kanssa pariohjelmoiden.

Lisää samantapaisia refaktorointitehtäviä löytyy Emily Bachen GitHubista.

4. Laskin ja komento-oliot

Kurssirepositorion hakemistoissa koodi/viikko5/laskin, löytyy yksinkertaisen laskimen toteutus. Laskimelle on toteutettu graafinen käyttöliittymä Tkinter-kirjaston avulla. Tarvittaessa lue ensin kurssin Ohjelmistotekniikka materiaalissa oleva tkinter-tutoriaali.

Asenna projektin riippuvuudet komennolla poetry install ja käynnistä laskin virtuaaliympäristössä komennolla python3 src/index.py. Komennon suorittamisen tulisi avata ikkuna, jossa on laskimen käyttöliittymä.

Sovelluksen avulla pystyy tällä hetkellä tekemään yhteen- ja vähennyslaskuja, sekä nollaamaan laskimen arvon. Laskutoimituksen kumoamista varten on lisätty jo painike “Kumoa”, joka ei vielä toistaiseksi tee mitään. Sovelluksen varsinainen toimintalogiikka on luokassa Kayttoliittyma. Koodissa on tällä hetkellä hieman ikävä if-hässäkkä:

def _suorita_komento(self, komento):
    arvo = 0

    try:
        arvo = int(self._syote_kentta.get())
    except Exception:
        pass

    if komento == Komento.SUMMA:
        self._sovelluslogiikka.plus(arvo)
    elif komento == Komento.EROTUS:
        self._sovelluslogiikka.miinus(arvo)
    elif komento == Komento.NOLLAUS:
        self._sovelluslogiikka.nollaa()
    elif komento == Komento.KUMOA:
        pass

    self._kumoa_painike["state"] = constants.NORMAL

    if self._sovelluslogiikka.tulos == 0:
        self._nollaus_painike["state"] = constants.DISABLED
    else:
        self._nollaus_painike["state"] = constants.NORMAL

    self._syote_kentta.delete(0, constants.END)
    self._tulos_var.set(self._sovelluslogiikka.tulos)

Refaktoroi koodi niin, ettei _suorita_komento-metodi sisällä pitkää if-hässäkkää. Hyödynnä kurssimateriaalin osassa 4 esiteltyä suunnittelumallia command.

Tässä tehtävässä ei tarvitse vielä toteuttaa kumoa-komennon toiminnallisuutta!

Luokka Kayttoliittyma voi näyttää refaktoroituna esimerkiksi seuraavalta:

class Komento(Enum):
    SUMMA = 1
    EROTUS = 2
    NOLLAUS = 3
    KUMOA = 4


class Kayttoliittyma:
    def __init__(self, sovelluslogiikka, root):
        self._sovelluslogiikka = sovelluslogiikka
        self._root = root

        self._komennot = {
            Komento.SUMMA: Summa(sovelluslogiikka, self._lue_syote),
            Komento.EROTUS: Erotus(sovelluslogiikka, self._lue_syote),
            Komento.NOLLAUS: Nollaus(sovelluslogiikka, self._lue_syote),
            Komento.KUMOA: Kumoa(sovelluslogiikka, self._lue_syote)
        }
    
    # ...

    def _lue_syote(self):
        return self._syote_kentta.get()

    def _suorita_komento(self, komento):
        komento_olio = self._komennot[komento]
        komento_olio.suorita()
        self._kumoa_painike["state"] = constants.NORMAL

        if self._sovelluslogiikka.tulos == 0:
            self._nollaus_painike["state"] = constants.DISABLED
        else:
            self._nollaus_painike["state"] = constants.NORMAL

        self._syote_kentta.delete(0, constants.END)
        self._tulos_var.set(self._sovelluslogiikka.tulos)

Komennoilla on nyt siis metodi suorita ja ne saavat konstruktorin kautta Sovelluslogiikka-olion ja funktion, jota kutsumalla syötteen voi lukea.

5. Komentojen kumoaminen

Toteuta laskimeen myös kumoa-toiminnallisuus. Periaatteena on siis toteuttaa jokaiseen komento-olioon metodi kumoa. Olion tulee myös muistaa mikä oli tuloksen arvo ennen komennon suoritusta, jotta se osaa palauttaa laskimen suoritusta edeltävään tilaan.

Jos kumoa-nappia painetaan, suoritetaan sitten edelliseksi suoritetun komento-olion metodi kumoa.

Riittää, että ohjelma muistaa edellisen tuloksen, eli kumoa-toimintoa ei tarvitse osata suorittaa kahta tai useampaa kertaa peräkkäin. Tosin tämänkään toiminnallisuuden toteutus ei olisi kovin hankalaa, jos edelliset tulokset tallennettaisiin esimerkiksi listaan.

6. Retrospektiivitekniikat

Wikipedian mukaan retrospektiivi on “a meeting held by a project team at the end of a project or process (often after an iteration) to discuss what was successful about the project or time period covered by that retrospective, what could be improved, and how to incorporate the successes and improvements in future iterations or projects.”

Tutustu täällä esiteltyihin retrospektiivitekniikoihin Start, Stop, Continue, More of, Less of Wheel ja Glad, Sad, Mad.

Tee aiheesta noin 0.25 sivun (eli noin 125 sanaa) tiivistelmä repositorion juureen sijoitettavaan tiedostoon retro.md.

Pidä huoli siitä, että miniprojektitiimisi pitää ensimmäisen sprintin lopussa jompaakumpaa tekniikkaa noudattavan retrospektiivin!

Tehtävien palautus

Pushaa kaikki tekemäsi tehtävät (paitsi ne, joissa mainitaan, että tehtävää ei palauteta mihinkään) GitHubiin ja merkkaa tekemäsi tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu-avoin-2022