layout | title | inheader | permalink |
---|---|---|---|
page |
Viikko 2 |
false |
/tehtavat2/ |
{% include miniproj_ilmo.md %}
{% include laskari_info.md part=2 %}
Viikon ensimmäisessä ja toisessa tehtävässä tutustutaan koodin staattiseen analyysin pylint-työkalun avulla. Gitiin tutustuminen jatkuu tehtävissä 3-7. Laskarien lopuksi jatketaan riippuvuuksien injektoinnin parissa.
{% include typo_instructions.md %}
{% include norppa.md %}
Osa git-tehtävistä (tehtävät 5-7) tehdään ainoastaan paikalliseen repositorioon, eli ne eivät näy palautuksessa mitenkään.
Muut Tehtävät palautetaan GitHubiin, sekä merkitsemällä tehdyt tehtävät palautussovellukseen <{{site.stats_url}}> välilehdelle "my submission".
Tehtävät 3 ja 4 laajentavat viime viikon ensimmäistä tehtäväsarjaa, eli ne palautetaan ohtuvarasto-repositorioon, siis samaan mitä käytettiin viikon 1 tehtävissä 2-13. Muut tehtävät palautetaan palautusrepositorioon, eli samaan mihin palautit ensimmäisen viikon tehtävät 14-17.
Katso tarkempi ohje palautusrepositorioita koskien täältä.
Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin tehtiin viikon 1 tehtävät 14-17
- Tee palautusrepositorioon hakemisto viikko2 ja sen sisälle hakemisto poetry-web tätä tehtävää varten
- Lue täältä lisää tehtävien palautusrepositorioista
HUOM: Kurssilla käytetään riippuvuuksien hallinnassa ainoastaan Poetrya, eli pip:iä ei tule käyttää riippuvuuksien asennuksessa. Tämä tarkoittaa käytännössä sitä, että riippuvuudet asennetaan komennolla poetry add <kirjasto>
komennon pip install <kirjasto>
sijaan.
Tässä tehtävässä harjoittelemme lisää Poetryn käyttöä ja tutustumme semanttiseen versiointiin. Apua tehtävän tekoon saa mm. Ohjelmistotekniikka-kurssin Poetry-ohjeesta ja Poetryn dokumentaatiosta.
Kuvitellaan tilanne, jossa työskentelet ohjelmistokehittäjänä kehitystiimissä, joka on alkamassa kehittämään web-sovellusta. Olette päätyneet kehittämään sovelluksen Pythonilla ja käyttämään Poetrya riippuvuuksien hallinnassa.
Tee seuraavat toimenpiteet:
- Aluksi Poetry-pohjainen projekti täytyy alustaa. Alusta projekti Poetryn avulla poetry-web nimiseen hakemistoon tehtävien palautukseen käyttämäsi repositorion hakemiston viikko2 sisälle. Muista käyttää alustuksessa komentoa
poetry init --python "^3.8"
, jotta projektin Python-version vaatimus asetetaan oikein. - Etsit Googlettamalla sopivia kirjastoja web-sovellusta varten ja törmäät Flask-viitekehykseen. Asenna Flask projektin riippuvuudeksi Poetryn avulla
- Sovelluksessa ilmenee ensimmäinen bugi. Syynä oli luultavasti se, ettei sovellukselle ole toteutettu vielä yhtään testiä. Päädyt käyttämään testauksessa pytest-viitekehystä. Asenna pytest projektin kehityksen aikaiseksi riippuvuudeksi
- Huom: jos käytössäsi on tätä syksyä aiemmin asennettu Poetry (versio 1.1.x) niin komennon
poetry add pylint --group dev
sijaan tulee käyttää komentoapoetry add pylint --dev
kehitysaikaisten riippuvuuksien asentamisessa - Pohdi itseksesi, miksi on hyödyllistä määritellä riippuvuus erikseen kehityksen aikaiseksi riippuvuudeksi
- Huom: jos käytössäsi on tätä syksyä aiemmin asennettu Poetry (versio 1.1.x) niin komennon
- Sovelluksessa käsitellään paljon JSON-muotoista dataa, joten päädyt etsimään sen serialisointiin ja deserialisointiin sopivia kirjastoja. Törmäät tarkoitukseen sopivaan kirjastoon nimeltä jsonpickle. Asenna jsonpickle projektin riippuvuudeksi
- Huomaat bugin jsonpickle-kirjastossa, joten alat tutkimaan sen GitHub repositorion issueita. Eräässä issuessa kerrotaan, että löytämäsi bugi ei ilmene kirjaston versiossa
1.3.0
. Asenna jsonpickle-kirjastosta versio1.3.0
.- Tutustu semanttiseen versiointiin täällä
- Pohdi itseksesi, mitä hyötyjä semanttisesta versioinnista on. Jos kirjasto noudattaa semanttista versiointia, miksi kirjaston version
1.1.2
päivittäminen versioon2.0.0
saattaa sisältää riskejä? Miksei samoja riskejä luultavasti ole version1.1.3
kanssa? - Versiovaatimuksissa on mukana usein
^
-, tai~
-etuliite. Pohdi itseksesi, mitä näillä ilmaistaan. Asiaa käsitellään mm. Poetryn dokumentaatiossa
- Päätät, että jsonpickle-kirjastosta on ollut vain harmia ja voit helposti toteuttaa sen tarjoaman toiminallisuuden itse. Poista jsonpickle projektin riippuvuuksien joukosta
Palautettavasta poetry-web-hakemistosta ei tarvitse löytyä muita tiedostoja kuin pyproject.toml ja poetry.lock.
Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin teit edellisen tehtävän
Ohjelmistokehittäjälle tulee usein vastaan tilanne, jossa pitäisi löytää tiettyyn käyttötarkoitukseen sopiva kirjasto. Harjoittelemme kyseistä tilannetta tässä tehtävässä.
TOML on eräs helppolukuinen datan esitysformaatti, jota käytetään usein konfiguraatiotiedostoissa, kuten Poetryn pyproject.toml-tiedostossa. Kurssirepositorion hakemistossa koodi/viikko2/project-reader on pohja ohjelmalle, jonka tarkoituksena on lukea projektin tietoja annetusta osoitteesta löytyvästä pyproject.toml-tiedostosta.
- Kopioi aluksi projekti palautusrepositorioon hakemiston viikko2 sisälle.
Tehtävänäsi on ensin löytää sopiva kirjasto, jonka avulla TOML-muotoisista merkkijonoista voi muodostaa Pythonin tietorakenteita. Voit hyödyntää tässä esimerkiksi PyPI-sivuston hakua tai Googlea. PyPI:ssä eräs hyvä hakusana voisi olla esimerkiksi "toml". Tutustu kirjastojen kuvauksiin ja päättele sen perusteella, sopiiko kirjasto käyttötarkoitukseen. Kun löydät sopivan kirjaston, asenna se projektiin Poetryn avulla.
HUOM: PyPI:n asennusohjeista löytyy usein pip-asennuksen ohje pip install <kirjasto>
. Kaikki kirjastot pystyy kuitenkin asentamaan yhtä lailla Poetryn avulla komennolla poetry add <kirjasto>
.
Ota sen jälkeen kirjasto käyttöön projektin src/project_reader.py-tiedoston ProjectReader
-luokan metodissa get_project
. Metodin content
-muuttujaan on tallennettu tiedoston sisältö:
def get_project(self):
# tiedoston merkkijonomuotoinen sisältö
content = request.urlopen(self._url).read().decode("utf-8")
print(content)
# deserialisoi TOML-formaatissa oleva merkkijono ja muodosta Project-olio sen tietojen perusteella
return Project("Test name", "Test description", [], [])
Tulosta jokainen välivaihe (tiedoston sisältö ja kirjaston avulla deserialisoitu sisältö) print
-funktion avulla, jotta tiedät, minkä muotoista data on. Muodosta tämän jälkeen tiedoista Project
-olio antamalla sille konstruktorin kautta projektin nimi, kuvaus, lista riippuvuuksista ja lista kehityksen aikaisista riippuvuuksista. Kun ohjelma toimii halutulla tavalla, voit poistaa debuggauksessa käytetyt tulostukset.
Ohjelman voi käynnistää virtuaaliympäristössä komennolla python3 src/index.py
. Esimerkkinä käytetyn pyproject.toml-tiedoston tapauksessa ohjelman tulostuksen tulisi olla seuraava:
Name: web-login-robot
Description: -
Dependencies: python, Flask
Development dependencies: robotframework, robotframework-seleniumlibrary, requests
Tämä ja seuraava tehtävä tehdään viime viikon tehtävissä 2-13 käytettyyn ohtuvarasto-repositorioon
Kurssin kolmannessa osassa teemana on ohjelmien laadun varmistaminen. Eräs ohjelman laatua useimmiten edistävä tekijä on järkevän koodityylin noudattaminen. Koodin tyyliä voidaan tarkkailla automatisoidusti niin sanottujen staattisen analyysin työkaluilla.
Tutustutaan nyt staattisen analyysin työkaluun nimeltään pylint. Pylint on jo ehkä tullut tutuksi kurssilta Ohjelmistotekniikka. Ennen kuin syvennymme aiheeseen, tutustu pylintin käyttöön lukemalla Ohjelmistotekniikka-kurssin Pylint-ohje.
Mene nyt viikon 1 varasto-projektiin liittyvien tehtävien palautusrepositorioosi.
Ota varasto-projektissa käyttöön pylint noudattamalla lukemiasi ohjeita. Konfiguraationa käytettävän .pylintrc-tiedoston sisältö tulee toistaiseksi olla tämän tiedoston sisällön mukainen.
Pylintin tarkistamat säännöt konfiguroidaan .pylintrc-tiedostoon oikeiden osioiden alle. [MASTER]
-osio sisältää yleistä konfiguraatio, kuten mitkä hakemistot tai tiedostot pitäisi jättää tarkistuksien ulkopuolelle. [MESSAGE CONTROL]
-osiossa taas voidaan määritellä esimerkiksi tarkistuksia, joista ei tarvitse huomauttaa. Loput osiot ovat eri sääntöjen konfigurointia varten, jotka on dokumentoitu pylintin dokumentaatiossa. Jos haluamme esimerkiksi asettaa funktioiden ja metodien argumenttien maksimilukumäärään neljään, voimme lisätä sen [DESIGN]
-osioon seuraavasti:
[DESIGN]
max-args=4
Helpoin tapa löytää sääntöjä on hakemalla sopivalla hakusanalla niitä dokumentaatiosta tai Googlettamalla. Oikean osion löytää dokumentaatiosta (esimerkiksi max-args
-sääntö löytyy dokumentaatiosta Design checker -osion alta).
Toimi nyt seuraavasti:
-
Siirry virtuaaliympäristöön komennolla
poetry shell
ja suorita sen sisällä komentopylint src
. Jos tarkistuksissa löytyy virheitä, korjaa ne -
Määrittele nyt tiedostoon .pylintrc seuraavat säännöt (katso lista säännöistä pylintin dokumentaatiosta):
- Rivin pituus on maksimissaan 80 merkkiä
- Vinkki: sääntö löytyy Format checker -osiosta ja tulee määrittää
[FORMAT]
-osion alle
- Vinkki: sääntö löytyy Format checker -osiosta ja tulee määrittää
- Ei yli kahta sisäkkäistä lohkoa (esimerkiksi if- tai for-lohkoa) funktion tai metodin sisällä
- Vinkki: sääntö löytyy Refactoring checker -osiosta ja tulee määrittää
[REFACTORING]
-osion alle)
- Vinkki: sääntö löytyy Refactoring checker -osiosta ja tulee määrittää
- Funktiossa tai metodissa on enintään 15 lausetta (statements), etsi sääntö dokumentaatiosta
- Syklomaattinen koodikompleksisuus korkeintaan 3
- Selvitä mitä syklomaattisella kompleksisuudella tarkoitetaan
- Vinkki: sääntö löytyy Design checker -osiosta tämän lisäosan avulla. Saat sen käyttöön lisäämällä
[MASTER]
-osioonload-plugins=pylint.extensions.mccabe
-rivin
- Rivin pituus on maksimissaan 80 merkkiä
-
Muuta koodiasi siten, että saat jokaisen määritellyistä pylint-säännöistä rikkoutumaan
-
Korjaa koodisi ja varmista, että se noudattaa kaikkia sääntöjä
Varasto
-luokan konstruktori luultavasti rikkoomax-complexity
-sääntöä. Voit esimerkiksi miettiä, miten voisit esittäätilavuus
-attribuutin arvon if-lauseen sijaan jotenkin muuten.
Usein .pylintrc-konfiguraatiota ei ole järkevää kirjoittaa tyhjästä käsin, vaan käytetään lähtökohtana pylintin suosittelemaa konfiguraatiota. Suoitellun konfiguraation voi tulostaa komentoriville komennolla pylint --generate-rcfile
.
Tämä tehtävä tehdään viime viikon tehtävissä 2-13 käytettyyn ohtuvarasto-repositorioon
Laajenna ohtuvarastosi GitHub Actionien määritelmää siten, että myös pylint-tarkastukset suoritetaan aina kun koodi pushataan GitHubiin.
Varmista, että GitHub huomaa tilanteen, missä koodi rikkoo projektin pylint-sääntöjä:
![]({{ "/images/py-lh2-11.png" | absolute_url }})
Varmista myös, että kun korjaat koodin, kaikki toimii taas moitteettomasti:
![]({{ "/images/py-lh2-12.png" | absolute_url }})
Tätä tehtävää ei palauteta mihinkään
Lue brancheja käsittelevät osuudet seuraavasta https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
- jos haluat lukea hieman perusteellisemman selityksen asiasta, lue <https://git-scm.com/book/en/v2:n luku kolme kokonaisuudessaan
Kannattaa huomioida myös erittäin hyvä brancheja käsittelevä visuaalinen materiaali osoitteessa https://learngitbranching.js.org/
Varsin selkeältä vaikuttaa myös https://www.atlassian.com/git/tutorials/using-branches
Huom: kun liikut branchien välillä kannattaa pitää working tree ja staging -alue tyhjinä!
Tee seuraavat paikalliseen git-repositorioosi (tehtävää ei palauteta, eli kyseessä ei siis tarvitse olla tehtävien palautusrepositorio)
-
Huom: seuraavassa käytetään versionhallinnan päähaarasta gitin oletusarvoista nimeä master. GitHubissa päähaarasta käytetään nykyään nimeä main, ja jos teet tehtävän palautusrepositorioosi, on päähaarasi nimi todennäköisesti main
-
Luo repositorio ja committaa masteriin tiedosto index.py jonka sisältö on seuraava
x = int(input("luku 1: "))
y = int(input("luku 2: "))
- Luo branch laskut, siirry branchiin (tämä tapahtuu esim. komennolla
git checkout -b laskut
), luo sinne tiedosto summa.py jolla on seuraava sisältö
def summa(x, y):
return x+y
-
lisää ja committaa tiedosto versionhallintaan
-
Siirry takaisin master-branchiin (komennolla
git checkout master
), tiedoston summa.py ei pitäisi nyt näkyä- huom: muistutus vielä siitä, että kun siirryt branchista toiseen varmista aina komennolla
git status
että kaikki muutokset on committoitu
- huom: muistutus vielä siitä, että kun siirryt branchista toiseen varmista aina komennolla
-
Luo tiedosto logger.py, jolla on seuraava sisältä
from datetime import datetime
def logger(viesti):
print(f"{datetime.now()}: {viesti}")
- Muuta myös tiedostoa index.py seuraavasti:
from logger import logger
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
logger("lopetetaan")
-
Committaa nämä muutokset master-haaraan
-
Mene branchiin laskut ja tarkasta, että masteriin lisätty tiedosto ei ole branchissa ja että tiedostoon index.py tehty muutos ei näy
-
Lisää ja committaa branchiin tiedosto erotus.py jolla on seuraava sisältö
def erotus(x, y):
return x-y
- Siirry takaisin master-branchiin
- Tarkasta että laskut-branchiin lisätyt muutokset eivät ole masterissa
- Tarkastele komennolla
gitk --all
miltä repositorio ja branchit näyttävät (gitk
-komento toimii Windowsilla ainakin GitHub for Windowsin Git Shellissä.)- Saat asennettua Maciin
gitk
:n tämän ohjeen avulla- jos asennus ei onnistu, on hyvä korvaaja gitk:lle sourcetree
- Saat asennettua Maciin
- Mergeä branchin laskut sisältö masteriin (tämä tapahtuu komennolla
git merge laskut
)- Mergeäminen aiheuttaa ns merge-commitin, ja avaa tekstieditorin mihin joudut kirjoittamaan commit-viestin
- Jos et ole määritellyt gitille editoria viime viikon tehtävän 2 ohjeiden mukaan, avautuu ehkä gitin oletusarvoinen editori vim
- Vimistä poistuminen saattaa osoittautua ensikertalaiselle hankalaksi, Google auttaa tarvittaessa
- Mergeäminen aiheuttaa ns merge-commitin, ja avaa tekstieditorin mihin joudut kirjoittamaan commit-viestin
- Muuta tiedostoa index.py seuraavasti ja commitoi muutos:
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan")
- Katso jälleen miltä näyttää
gitk --all
-komennolla
Tätä tehtävää ei palauteta mihinkään
- Olet nyt repositoriosi master-haarassa
- Luo uusi tiedosto README.md, älä kuitenkaan lisää ja commitoi tiedostoa versionhallintaan
- Tiedoston sisällöllä ei ole merkitystä, se voi olla esim. seuraava
## git-harjoituksia
Harjoitellaan branchien käyttöä
- Komennon
git status
tulostuksen pitäisi olla seuraava
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
- Siirry nyt branchiin laskut
- Suorita uudelleen komento
git status
- Huomaat, että tulostus on edelleen sama, tiedosto ei edelleenkään ole versionhallinnan alla
- Eli vaikka olit master-haarassa kun loit tiedoston, ei master-haara eikä koko git tiedä tiedostosta vielä mitään ennen kuin lisäät sen versionhallinnan alaisuuteen komennolla
git add
- Lisää tiedosto nyt versionhallinnan alaisuuteen ja commitoi se
- Tiedosto menee nykyiseen branchiisi, eli branchiin laskut, master ei edelleenkään tiedä tiedostosta mitään
- Luo uusi tiedosto LICENSE ja lisää se versionhallintaan (komennolla add), älä kuitenkaan commitoi
- Tiedoston sisällöllä ei ole merkitystä, se voi olla esim. seuraava
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
For more information, please refer to <https://unlicense.org>
- Tarkasta että komennon
git status
tulos on seuraava:
On branch laskut
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENCE
- Olet siis branchissa laskut ja LICENSE on lisätty staging-alueelle, sitä ei kuitenkaan ole vielä committoitu
- Siirry nyt branchiin master
- Komennon
git status
tulos on edelleen sama, LICENSE on edelleen staging-alueella mutta committoimattomana - Staging-alue ei kuulu mihinkään branchiin, eli jos staging-alueella on committoimattomia muutoksia ja vaihdat branchia, säilyvät samat asiat stagingissa
- Muutokset siirtyvät stagingista branchiin ainoastaan komennolla
git commit
- Committoi nyt staging-alueen muutokset eli LICENSE masteriin
- Komennon
git status
tulos kertoo nyt että staging-alue on tyhjä:
On branch master
nothing to commit, working tree clean
- Siirry jälleen branchiin laskut ja huomaat, että LICENSE ei ole olemassa
- Mergeä master branchiin laskut
- Siirry nyt masteriin ja tuhoa branchi laskut
- Tuhoaminen ei onnistu suoraan komennon
git branch
branchin poistavalla flagilla-d
, jos branchin sisältö ei ole kokonaisuudessan mergetty masteriin. Jos näin on, tee ensin merge masteriin, tai jos tarkoituksena on poistaa branch silti vaikka siinä on vielä eriäviä muutoksia, käytägit branch -D
poistaaksesi branch eriävine muutoksineen
- Tuhoaminen ei onnistu suoraan komennon
- Tämän tehtävän ideana oli siis havainnollistaa, että working tree (muutokset joista git ei ole tietoinen) ja staging (gitiin lisättyihin tiedostoihin tehdyt committoimattomat muutokset)
eivät liity mihinkään branchiin, muutokset siirtyvät staging-alueelta branchiin ainoastaan komennon
git commit
suorituksen seurauksena
Tätä tehtävää ei palauteta mihinkään
Tee paikalliseen git-repoon seuraavat
- Muuta master-branchin tiedostoa index.py seuraavasti:
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan")
-
alkuun on siis lisätty kommentti ja tyhjä rivi
-
committaa muutos
-
Tee uusi branchi bugikorjaus, mene branchiin ja editoi tiedoston index.py loppua (esim. seuraavasti ) ja committaa
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan ohjelma")
print("goodbye!") # lisäys bugikorjaus-branchissa
- Mene takaisin master-branchiin, editoi tiedoston index.py alkupuolta esim.seuraavasti (muutos on funktion logger parametrissa) ja committaa muutokset:
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma") # muutos masterissa
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan ohjelma")
-
Mergeä branchin bugikorjaus sisältö masteriin
- Katso tiedoston index.py-sisältöä, sen pitäisi sisältää nyt molemmissa brancheissa tehdyt muutokset
- Huom: jo tässä vaiheessa saattaa syntyä konflikti jos olet vahingossa muuttanut merkkejä väärästä kohtaa tiedostoa! Toimi tällöin ao. ohjeen mukaan.
-
Muuta tiedostoa print-komentojen osalta seuraavasti
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{x} + {y} = {summa(x, y)}") # muutos masterissa
print(f"{x} - {y} = {erotus(x, y)}") # muutos masterissa
logger("lopetetaan ohjelma")
print("goodbye!")
-
Committoi muutokset
-
Siirry branchiin bugikorjaus
-
Muuta nyt tiedostoa (jälleen print-komentojen osalta) seuraavasti ja committaa
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"Lukujen {x} ja {y} summa on {summa(x, y)}") # muutos bugikorjaus-branchissa
print(f"Lukujen {x} ja {y} erotus on {erotus(x, y)}") # muutos bugikorjaus-branchissa
logger("lopetetaan ohjelma")
print("goodbye!")
- Mergeä branchin master sisältö branchiin bugikorjaus
- Nyt pitäisi aiheutua konflikti, komento aiheuttaa tulostuksen
Auto-merging index.py
CONFLICT (content): Merge conflict in index.py
Automatic merge failed; fix conflicts and then commit the result.
-
Git ei siis osannut yhdistää tiedostoon tehtyjä muutoksia, koska ne kohdistuvat samoille riveille, seurauksena on konflikti.
-
Ratkaise konflikti:
- Editoi tiedoston index.py sisältö haluamaksesi
- Ja toimi edellä mainitun artikkelien ohjeen mukaan, eli lisää konfliktoinut tiedosto staging-alueelle ja committoi
Jotkut editorit, esim Visual Studio Code sisältävät sisäänrakennetusti niin sanotun merge toolin, joka osaa jossain määrin helpottaa konfliktien ratkaisua:
![]({{ "/images/lh2-merge.png" | absolute_url }}){:height="350px" }
Tämä tehtävä tehdään palautusrepositorioon
HUOM tässä tehtävässä on tunnetusti välillä haastava seurata jokaista askelta siten, että päätyy aina samaan tilaan mitä tehtävä odottaa. Ei kannata stressata tästä liikaa. Pääasia tehtävässä on oppia miten branchit saadaan toimimaan lokaalisti ja GitHubissa siten, että git push ja git pull toimivat kaikille brancheille.
Aloita lukemalla ProGit-kirjasta luku Remote Branches.
Lisätään seuraavaksi branch GitHubiin:
- Luo palautusrepositorion paikalliseen kopioon branchit haara1 ja haara2
- Mene branchiin haara1, lisää sinne (hakemiston viikko2 juureen) tiedosto haara1.txt ja committaa
- Mene branchiin haara2, lisää sinnekin jokin (hakemiston viikko2 juureen) tiedosto haara2.txt ja committaa
- Pushaa uudet branchit GitHubiin
- Tarkastele GitHub-repositoriota selaimella, varmista että branchit syntyvät ja niillä on haluttu sisältö:
![]({{ "/images/lh2-branch1.png" | absolute_url }}){:height="350px" }
Kloonaa GitHub-repositoriosta koneellesi toinen kopio:
- Kuten huomaat, eivät branchit tule kloonattuun kopioon
- Tee paikalliseen kopioon branch joka "träkkää" GitHubissa olevan projektisi branchia haara1 (ks. http://git-scm.com/book/en/Git-Branching-Remote-Branches kohta Tracking Branches)
- Lisää "träkkäävään" branchiin jokin tiedosto (hakemistoon viikko2), committaa ja pushaa branchi GitHubiin
- Tarkastele GitHub-repositoriota selaimella, varmista että branchi päivittyy
Mene GitHub-repon alkuperäiseen paikalliseen kopioon:
- Mene branchiin haara1 ja pullaa muutokset GitHubiin vastaavasta branchista
- huom: koska kyseessä ei ole "träkkäävä" branchi, joudut pullaamaan komennolla
git pull origin haara1
- huom: koska kyseessä ei ole "träkkäävä" branchi, joudut pullaamaan komennolla
- Mene branchiin haara2, lisää sitten tiedosto, committaa ja pushaa branchi GitHubiin
- Koska kyseessä ei ole "träkkäävä" branchi, ei komento
git push
riitä vaan joudut määrittelemään branchin jonne push kohdistuu eli antamaan komennongit push origin haara2
- Koska kyseessä ei ole "träkkäävä" branchi, ei komento
- Komennon
git push
tuloste antaa ohjeen, miten saat komennon toimimaan haaran sisältä ilman lisäparametreja:
$ git push
fatal: The current branch haara2 has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin haara2
Mene jälleen toiseen kopioon:
- Suorita komento
git remote show origin
- Komento kertoo "originissa" eli GitHubissa olevien branchien ja paikallisten branchien suhteen
* remote origin
Fetch URL: [email protected]:mluukkai/ohtu-s22-palautukset.git
Push URL: [email protected]:mluukkai/ohtu-s22-palautukset.git
HEAD branch: main
Remote branches:
haara1 tracked
haara2 tracked
main tracked
Local branches configured for 'git pull':
haara1 merges with remote haara1
main merges with remote main
Local refs configured for 'git push':
haara1 pushes to haara1 (up to date)
main pushes to main (up to date)
- Komennon tulosteesta selviää, että main ja haara1 ovat konfiguroitu toimimaan suoraan
git pull
jagit push
-komennoilla - Tee toiseen lokaaliin kopioon GitHubissa olevan projektisi branchia haara2 träkkäävä branch
- Suorita jälleen
git remote show origin
, mitä muutoksia huomaat? - Tee branchiin haara2 muutoksia ja pushaa ne githubiin
- Huom: koska kyseessä on träkkäävä branch, riittää git push
- tarkastele GitHub-repositoriota selaimella, varmista että branchi päivittyy
Palaa vielä alkuperäiseen lokaaliin repositorioon:
- Suorita komento
git remote show origin
- Tulostus kertoo, että lokaaleista haaroista ainoastaan master on konfiguroitu komennon
git pull
osalta:
* remote origin
Fetch URL: [email protected]:mluukkai/ohtu-s22-palautukset.git
Push URL: [email protected]:mluukkai/ohtu-s22-palautukset.git
HEAD branch: master
Remote branches:
haara1 tracked
haara2 tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local refs configured for 'git push':
haara1 pushes to haara1 (up to date)
haara2 pushes to haara2 (up to date)
master pushes to master (up to date)
- Suorita
git pull
branchissä haara1 - Komennon tuloste antaa ohjeen, miten saat konfiguroitua
git pull
komennon toimimaan haara1:n sisällä ilman lisäparametreja:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> haara1
- Kun annat komennon, sen jälkeen haara träkkää GitHubissa olevaa haaraa ja komento
git pull
voidaan antaa ilman parametreja
Branchien kanssa työskentely voi aluksi tuntua sekavalta varsinkin jos GitHubissa on myös useita brancheja.
Ohjelmistotiimi voi soveltaa Gitin branchaystä hyvin monella eri tyylillä. Artikkeli https://www.atlassian.com/git/tutorials/comparing-workflows esittelee tähän muutamia vaihtoehtoja. Eräs yleinen tapa branchien käyttöön ovat ns. featurebranchit:
The core idea behind the Feature Branch Workflow is that all feature development should take place in a dedicated branch instead of the master branch. This encapsulation makes it easy for multiple developers to work on a particular feature without disturbing the main codebase. It also means the master branch will never contain broken code, which is a huge advantage for continuous integration environments.
Jos kiinnostaa, lue lisää yllä olevasta dokumentista.
Demonstroidaan usein esiintyvää tilannetta, jossa epäajantasaisen repositorion pushaaminen GitHubissa olevaan etärepositorioon epäonnistuu.
- Mene alkuperäisen repositorion paikallisen kopion main-haaraan, tee jokin muutos, commitoi ja pushaa se GitHubiin
- Mene toisen kopion main-haaraan ja tee sinne jokin muutos
- commitoi ja pushaa muutos GitHubiin
- Kaikki ei kuitenkaan mene hyvin, seurauksena on seuraavantyylinen virheilmoitus:
$ git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to '[email protected]:mluukkai/ohtu-s22-palautukset.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Virheen syynä on se, että GitHubissa oleva main-haara oli edellä paikallisen repositorion main*-haaraa. Ongelma korjaantuu tekemällä ensin git pull
, ratkaisemalla mahdolliset konfliktit ja pushaamalla sitten uudelleen.
- Komennon
git pull
yhteydessä syntyy merge-commit, ja avautuu tekstieditori mihin joudut kirjoittamaan commit-viestin - Eli toimi näin ja varmista, että tekemäsi muutokset menevät GitHubiin
Tämä ja seuraavat kaksi tehtävää tehdään palautusrepositorioon
- lue täältä lisää tehtävien palautusrepositorioista
Tutustuimme viime viikon tehtävissä 14-17 riippuvuuksien injektointiin ja sovelsimme periaatetta yksikkötestauksen helpottamiseen.
Jos asia on päässyt unohtumaan, voit kerrata asian lukemalla tämän.
Kurssirepositorion hakemistossa koodi/viikko2/verkkokauppa-1 on yksinkertaisen verkkokaupan ohjelmakoodi
- Hae projekti kurssirepositoriosta
- Järkevintä lienee että kloonaat kurssirepositorion paikalliselle koneellesi jos et ole sitä jo tehnyt, jos olet, niin pullaa repositorio ajantasalle
- Tämän jälkeen kannattaa kopioida projekti palautusrepositorioon, hakemiston viikko2 sisälle
- Tutustu koodiin, piirrä luokkakaavio ohjelman rakenteesta
- Luokkakaavioita ei tarvitse palauttaa
- Ohjelman luokista
Pankki
,Varasto
,Viitegeneraattori
jaKirjanpito
ovat sellaisia, että niistä on tarkoitus olla olemassa ainoastaan yksi olio. Tälläisiä ainutkertaisia olioita sanotaan singletoneiksi. Koodissa singletonit ovat toteutettu "klassisella tavalla", eli piilottamalla konstruktori ja käyttämällä staattista muuttujaa ja metodia säilömään ja palauttamaan luokan ainoa olio- Singleton on ns. GoF-kirjan yksi alkuperäisistä suunnittelumalleista, lue lisää singletoneista esim. täältä
- Singleton ei ole erinäisistä syistä enää oikein muodissa, ja korvaamme sen seuraavassa tehtävässä
- Kuten huomaamme, on koodissa toivottoman paljon konkreettisia riippuvuuksia:
- Varasto --> Kirjanpito
- Pankki --> Kirjanpito
- Kauppa --> Pankki
- Kauppa --> Viitegeneraatori
- Kauppa --> Varasto
- Poista luokan
Kauppa
konkreettiset riippuvuudet yllä mainittuihin luokkiin- Määrittele luokalle
Kauppa
sopiva konstruktori, jotta voit injektoida riippuvuudet - Riippuvuus luokkaan
Ostoskori
voi jäädä, sillä se on ainoastaan luokan Kauppa sisäisesti käyttämä luokka ja täten varsin harmiton - Muut riippuvuudet jätetään vielä
- Määrittele luokalle
- Älä käytä luokan
Kauppa
sisällä enää konkreettisia luokkiaVarasto
,Viitegeneraattori
jaPankki
vaan ainoastaan niitä vastaavia konstruktorin kautta saatuja olioita! - Muokkaa index.py-tiedoston
main
-funktiota, siten että se luo kaupan seuraavasti:
kauppa = Kauppa(
Varasto.get_instance(),
Pankki.get_instance(),
Viitegeneraattori.get_instance()
)
- Asenna projektin riippuvuudet komennolla
poetry install
- Varmista ohjelman toimivuus suorittamalla se virtuaaliympäristössä komennolla
python3 src/index.py
- Singleton-suunnittelumallia pidetään osittain ongelmallisena, poistammekin edellisestä tehtävästä singletonit
- Poista kaikista luokista
get_instance
-metodit ja staattinen__instanssi
-muuttuja - Poista rajapintojen ja riippuvuuksien injektoinnin avulla edellisen tehtävän jäljiltä jääneet riippuvuudet, eli
- Varasto --> Kirjanpito
- Pankki --> Kirjanpito
- Muokkaa index.py-tiedoston
main
-funktiota vastaamaan uutta tilannetta, eli suunnilleen muotoon:
viitegeneraattori = Viitegeneraattori()
kirjanpito = Kirjanpito()
varasto = Varasto(kirjanpito)
pankki = Pankki(kirjanpito)
kauppa = Kauppa(varasto, pankki, viitegeneraattori)
Varmista ohjelman toimivuus suorittamalla se virtuaaliympäristössä komennolla python3 src/index.py
.
Pythonin tapauksessa perinteisen singleton-suunnittelumallin mukaiset luokat tuottavat turhan monimutkaista koodia, joka on yksi syy niiden vähäiseen käyttöön. Esimerkiksi Viitegeneraattori
-luokasta voisi yksinkertaisesti luoda olion, jota muut moduulit voivat käyttää:
class Viitegeneraattori:
def __init__(self):
self._seuraava = 1
def uusi(self):
self._seuraava = self._seuraava + 1
return self._seuraava
the_viitegeneraattori_olio = Viitegeneraattori()
Nyt muut moduulit voivat käyttää the_viitegeneraattori_olio
-muuttujaan tallennettua oliota.
Edellisen tehtävän päätteeksi huomasimme, että Kauppa
-luokan olion alustaminen vaatii melko paljon toimenpiteitä:
viitegeneraattori = Viitegeneraattori()
kirjanpito = Kirjanpito()
varasto = Varasto(kirjanpito)
pankki = Pankki(kirjanpito)
kauppa = Kauppa(varasto, pankki, viitegeneraattori)
Korjataan tilanne antamalla riippuvuuksille oletusarvot.
Tee seuraavat toimenpiteet:
- Tallenna viitegeneraattori.py-tiedostossa muuttujaan
the_viitegeneraattori_olio
luokanViitegeneraattori
olio edellisen esimerkin tavoin - Tallenna kirjanpito.py-tiedostossa muuttujaan
the_kirjanpito_olio
luokanKirjanpito
olio - Muokkaa
Varasto
-luokkaa siten, että sen konstruktorinkirjanpito
-parametrin arvo on oletusarvoisesti kirjanpito.py-tiedostossa määriteltythe_kirjanpito_olio
-muuttujan arvo. Parametrien oletuarvojen antaminen onnistuu seuraavasti:
from kirjanpito import the_kirjanpito_olio
class Varasto:
def __init__(self, kirjanpito=the_kirjanpito_olio):
self._kirjanpito = kirjanpito
# ...
# ...
- Tallenna varasto.py-tiedostossa muuttujaan
the_varasto_olio
luokanVarasto
olio. Huomaa, että olion voi alustaa ilman argumentteja (muodossaVarasto()
), koskakirjanpito
-parametrille on annettu oletusarvo. - Tee sama
Pankki
-luokan konstruktorille ja tallennaPankki
-luokan olio muuttujaanthe_pankki_olio
- Käytä
Kauppa
-luokan konstruktorissavarasto
-,pankki
- javiitegeneraattori
-parametrien oletusarvoina edellisissä askelissa määrittelemiäsi muuttujia - Muokkaa index.py-tiedoston
main
-funktiota siten, ettäKauppa
-olion alustaminen ei käytä argumentteja:
kauppa = Kauppa()
Huomaa, että luokalle voi silti halutessaan määritellä riippuvuudet argumentteina. Tämä on kätevää esimerkiksi testeissä:
class PankkiStub:
# ...
kauppa = Kauppa(pankki=PankkiStub())
{% include submission_instructions.md %}