Skip to content

Latest commit

 

History

History
280 lines (186 loc) · 13.7 KB

tehtavat5.md

File metadata and controls

280 lines (186 loc) · 13.7 KB
layout title inheader permalink
page
Viikko 5
false
/tehtavat5/

{% include miniproj_ilmo.md %}

{% include paivitys_kesken.md current=true %}

{% include laskari_info.md part=5 %}

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

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

Typoja tai epäselvyyksiä tehtävissä?

{% include typo_instructions.md %}

{% include norppa.md %}

{% include poetry_ongelma.md %}

Tehtävien palauttaminen

Tehtävät palautetaan GitHubiin, sekä merkitsemällä tehdyt tehtävät palautussovellukseen <{{site.stats_url}}> välilehdelle "my submission".

Tämän viikon tehtävät 3-6 palautetaan jo edellisillä viikoilla käyttämääsi palautusrepositorioon, sinne tehtävän hakemiston viikko5 sisälle.

Katso tarkempi ohje palautusrepositoriota koskien täältä.

1. Git: vahingossa tuhotun tiedoston palautus [versionhallinta]

Viikon 4 tehtävässä 6 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 important.txt, lisää ja committaa se
  • Poista tiedosto ja committaa
  • Tee jotain muutoksia johonkin tiedostoon ja committaa
  • Historiasi näyttää seuraavalta
(1) - (2) - (3)
  • Nykyhetki eli HEAD on (3). Commitissa (1) tiedosto important.txt on olemassa ja (2):ssa important.txt:ää ei ole.

    • Huom: komennolla gitk voit tutkia historiaa
  • Haluamme palauttaa tiedoston

  • Selvitä sen commitin id, jossa tiedosto vielä on olemassa, tämä onnistuu gitk:lla tai git log -komennolla

  • Anna komento git checkout 3290b03cea08af987ee7ea57bb98a4886b97efe0 -- important.txt missä pitkä merkkijono on siis kyseisen commitin id

    • varmista että tiedosto on ilmestynyt staging-alueelle komennolla git status
  • Tee commit

  • important.txt on palannut!

  • Huom: koko id:tä ei komennossa tarvitse antaa, riittää 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." Ks. lisää täältä
  • Täsmälleen samalla tavalla onnistuu olemassa olevan tiedoston vanhan version palauttaminen.

2. Git: commitin muutosten kumoaminen [versionhallinta]

  • Huomaamme, että juuri tehty commit oli virhe, kumotaan se sanomalla git revert HEAD --no-edit
    • HEAD siis viittaa siihen committiin, jonka kohdalla nyt ollaan
  • Syntyy uusi commit, jossa edellisessä tehdyt muutokset on kumottu
    • Ilman optiota no-edit pääset editoimaan kumoamiseen liittyvään commitiin tulevaa viestiä
    • Huom: sanomalla git checkout HEAD^ pääsemme takaisin kumottuun tilanteeseen, eli mitään ei ole lopullisesti kadotettu
  • Vastaavalla tavalla voidaan kumota, eli revertata mikä tahansa commit, eli: git revert kumottavancommitinid

3. Tenniksen pisteenlaskun refaktorointi

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

  • Kopioi projekti palatusrepositorioosi, hakemiston viikko5 sisälle.

Pisteenlaskennan rajapinta on yksinkertainen. Metodi get_score kertoo voimassa olevan tilanteen tenniksessä käytetyn pisteenlaskennan määrittelemän tavan mukaan. Sitä mukaa kun jompi kumpi 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 mikä on pelitilanne kunkin pallon jälkeen kun player1 voittaa ensimmäiset 2 palloa, player2 kolmannen pallon ja player1 loput 2 palloa.

Tenniksen pisteenlaskennasta voit lukea enemmän esim. Wikipediasta, mutta se ei ole välttämätöntä tämän tehtävän tekemisen kannalta.

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, eli älä hajota testejä. Testeihin ohjelmassa ei tarvitse eikä edes saa koskea.

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. IntJoukon testaus ja siistiminen

Kurssirepositorion hakemistossa viikko5/int-joukko on alun perin Javalla tehty, mutta nyt Pythoniksi alkuperäiselle tyylille uskollisena käännetty aloittelevan ohjelmoijan ratkaisu syksyn 2011 Ohjelmoinnin jatkokurssin viikon 2 tehtävään 3.

  • Kopioi projekti palatusrepositorioosi, hakemiston viikko5 sisälle.

Kyseinen opiskelija on edennyt urallaan pitkälle, hän on työskennellyt mm. Googlella ja useassa korkean profiilin Piilaakson start upissa.

Koodi simuloi vanhanaikaista ohjelmointikieltä kuten C:tä missä ei ole Pythonin listan tapaista valmista tietorakennetta, vaan ainoastaan listoja, joiden koko on kiinteä, ja joka määritellään listan luomishetkellä. Koodissa listan luominen tapahtuu metodilla _luo_lista:

class IntJoukko:
    # tämä metodi on ainoa tapa luoda listoja
    def _luo_lista(self, koko):
        return [0] * koko

    def __init__(self, kapasiteetti=None, kasvatuskoko=None):
        # ...
        
        # luodaan lista, jolla haluttu kapasiteetti
        self.ljono = self._luo_lista(self.kapasiteetti)
        self.alkioiden_lkm = 0

Kun joukkoon lisätään riittävä määrä uusia lukuja, tulee eteen tilanne, että joukon sisäistä listaa on kasvatettava. Tämä tapahtuu luomalla uusi lista metodilla _luo_lista:

    def lisaa(self, n):
        # ...
                
        # ei enää tilaa, luodaan uusi lista lukujen säilyttämiseen
        self.ljono = self._luo_lista(self.alkioiden_lkm + self.kasvatuskoko)

Koodi jättää hieman toivomisen varaa sisäisen laatunsa suhteen. Refaktoroi luokan IntJoukko koodi mahdollisimman siistiksi:

  • Poista copypaste
  • Vähennä monimutkaisuutta
  • Anna muuttujille selkeät nimet
  • Tee metodeista pienempiä ja hyvän koheesion omaavia

Ratkaisusi tulee toimia siten, että edelleen joukon sisäisen listan koko on kiinteä, ja lista luodaan metodilla _luo_lista, eli jos lista täyttyy, luodaan uusi lista metodin avulla.

Koodissa on joukko yksikkötestejä, jotka helpottavat refaktorointia.

HUOM: Suorita refaktorointi mahdollisimman pienin askelin, pidä koodi koko ajan toimivana. Suorita testit jokaisen refaktorointiaskeleen jälkeen!

5. Laskin ja komento-oliot

HUOM jos olet käyttänyt kontainerisoitua Poetry-ympäristöä, tämä tehtävä tulee tuottamaan haasteta, sillä sovelluksella on graafinen käyttöliittymä. Googlaa esim. hakusanoilla linux docker gui apps jos haluat saada tehtävän tehtyä kontainerissa. Toinen vaihtoehto on esim. pajaan meneminen...

Kurssirepositorion hakemistossa viikko5/laskin löytyy yksinkertaisen laskimen toteutus. Laskimelle on toteutettu graafinen käyttöliittymä Tkinter-kirjaston avulla.

  • Kopioi projekti palatusrepositorioosi, hakemiston viikko5 sisälle.

Jos tarvitsee, 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.arvo() == 0:
        self._nollaus_painike["state"] = constants.DISABLED
    else:
        self._nollaus_painike["state"] = constants.NORMAL

    self._syote_kentta.delete(0, constants.END)
    self._arvo_var.set(self._sovelluslogiikka.arvo())

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) # ei ehkä tarvita täällä...
        }

    # ...

    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.arvo() == 0:
            self._nollaus_painike["state"] = constants.DISABLED
        else:
            self._nollaus_painike["state"] = constants.NORMAL

        self._syote_kentta.delete(0, constants.END)
        self._arvo_var.set(self._sovelluslogiikka.arvo())

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

6. 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 toiminallisuuden toteutus ei olisi kovin hankalaa, jos edelliset tulokset tallennettaisiin esimerkiksi listaan.

Vapaaehtoinen bonus-tehtävä

Laajenna ohjelmaasi siten, että se mahdollistaa mielivaltaisen määrän peräkkäisiä kumoamisia. Eli jos olet esim. laskenut summan 1+2+3+4+5 (jonka tulos 16), napin kumoa peräkkäinen painelu vie laskimen tilaan missä tulos on ensin 10 sitten 6, 3, 2, 1 ja lopulta 0.

Myös esim. seuraavanlaisen monimutkaisemman operaatiosarjan pitää toimia oikein: Summa 10, Erotus 6, Erotus 2, Kumoa (kumoaa komennon Erotus 2), Summa 4, Kumoa (Kumoaa komennon Summa 4), Kumoa (kumoaa komennon Erotus 6), Kumoa (kumoaa komennon Summa 10)

{% include submission_instructions.md %}