Kokeen tehtävistä arvioitu 2-6. Näet pisteesi täältä https://study.cs.helsinki.fi/stats/courses/ohtu2025/results. Vastauksesi voit tarkistaa Moodlesta.

Omasta arvioinnista voi tarvittaessa kysyä suoraan kunkin tehtävän arvijoijalta.

Tehtävä 1

Tehtävän arvioi Tony Lam. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi

a) Viiden hengen tiimi, joka kehittää ja ylläpitää R-kioskien itsepalvelukassajärjestelmää

  • Scrum ja/tai DevOps-malli (0,5p mallista ja 0,5p kuvauksesta)
    • Lyhyt kuvaus prosessista, mitkä ominaispiirteet sillä on ja miten sitä käytetään.
  • Perusteluina 0,5p per pointti max 1p:
  • Scrum
    • Scrumin soveltuvuus pienille tiimeille
    • Soveltuu hyvin 5 hengen tiimille, Scrum suosittelee (3-9 kehittäjää)
    • Kehitettävän ohjelmiston luonne on pitkäaikainen ja vaatii jatkuvaa kehitystä, bugikorjauksia.
      • Sitä ei saada “valmiiksi” perinteisessä mielessä ja laittaa pöytälaatikkoon
    • Mahdollistaa nopean reagoinnin asiakkaan vaatimuksiin, kun R-kioskien toiminta- ja bisnesympäristö muuttuu.
      • Esimerkiksi Halloweenina R-kioski haluaisi, että kioskien UI on oranssi ja siellä on kurpitsoja
  • Devops
    • Tiimi myös ylläpitää sovellusta, on hyvä jos tiimillä on mahdollisuus hoitaa itse tuotantojulkaisut, eli tiimi DevOps-hengessä sovelluskehityksen, bugikorjaukset ja sovelluksen tuotannossa operoinnin.
    • Cross functional, jolloin tiimi voi itse viedä alusta loppuun featureita ja bugifixejä tuotantoon, eikä tiimin tarvitse olla riippuvainen toisesta tiimistä.

b) 50 koodarin firma, joka kehittää ydinvoimalan kontrollijärjestelmää

  • Vesiputousmalli (0,5p mallista; 0,5p kuvauksesta)
    • Lyhyt kuvaus prosessista, mitkä ominaispiirteet sillä on ja miten sitä käytetään.
  • Perusteluina 0,5p per pointti, max 1p:
    • Vesiputousmalli soveltuu hyvin kriittisten järjestelmien, kuten ydinvoimalan kontrollijärjestelmän, kehittämiseen.
    • Malli korostaa kattavaa vaatimusmäärittelyä ja dokumentointia ennen varsinaista kehitystä, mikä on tärkeää turvallisuuden ja virheettömyyden varmistamiseksi. Kriittisessä järjestelmässä ei voi iteratiivisesti kokeilla mikä toimii ja mikä ei.
    • Malli korostaa kattavaa testaamista, jonka pitäisi taata turvallisen järjestelmän käyttöönottoa.
    • Muutosten minimointi kehityksen aikana auttaa varmistamaan, että järjestelmä täyttää tiukat turvallisuus- ja regulaatiovaatimukset.

c) 4 uuden generaation tekoälypohjaista koodausagenttia kehittävä neljän hengen startup

  • Lean startup (0,5p mallista; 0,5p kuvauksesta)
    • Lyhyt kuvaus prosessista, mitkä ominaispiirteet sillä on ja miten sitä käytetään.
  • Perusteluina 0,5p per pointti, max 1p:
    • Lean startup -menetelmä sopii hyvin innovatiivisten ja kokeilupainotteisten projektien kehittämiseen, joissa on runsaasti epävarmuutta lopullisesta tilanteesta.
    • Korostaa nopeaa iterointia ja jatkuvaa oppimista, mikä mahdollistaa hypoteesien testaamisen ja asiakkaiden tarpeiden ymmärtämisen aikaisessa vaiheessa. MVP:n (minimum viable product) luominen ja sen validointi käyttäjien kautta auttaa startupia kehittämään tuotetta tehokkaasti, vähentämään riskejä ja ohjaa tuotekehitystä oikeaan suuntaan.
    • Startupille on tärkeää tuottaa arvoa ja tuloksia nopeasti, minkä malli mahdollistaa.

Tehtävän maksimipisteet: 6p

Erinomaisesti perusteltuja vastauksia, jotka eivät olleet yllä olevissa, joissa opiskelija on näyttänyt, että on ymmärtänyt vaadittavan kontekstin, ovat voineet tuottaa myös pisteitä.

Tehtävä 2

Tehtävän arvioi Matti Luukkainen. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi tai Discordissa

Kurssimateriaalin (mm. INVEST-kriteerit) mukaan hyvä user story on

  • kuvaa käyttäjälle arvoa tuottavan toiminnallisuuden,
  • on asiakkaan kannalta ymmärrettävällä kielellä (ei teknistä jargonia),
  • on tarpeeksi pieni (toteutettavissa yhdessä sprintissä),
  • kuvaa järjestelmän toiminnallisuutta päästä päähän, ei vain tietoteknistä kerrosta,
  • testattavissa
  • neuvoteltavissa, eli ei liian tiukasti kiinnitetty toteutukselta
  • työmäärä on arvioitavissa
  • sisältää hyväksymiskriteerit

Ensimmäinen esimerkkistory

Ylläpitäjän tulee saada mahdollisuus suorittaa kaikki CRUD-operaatiot (eli luonti, listaus, muokkaus, poisto) PostgreSQL:n tallennettaville avoimen yliopiston kurssitoteutuksille. Näitä varten kantaan tehdä oma taulu, nimeltään esim. OPEN_UNI_COURSES. Operaatioiden tulee olla riittävän nopeita.

sisältää useita ongelmia

  • Ei ole kirjoitettu asiakkaan kielellä
  • Story on myös liian laaja
  • On osin sisällöltään vaikea arvioida valmiiksi (…riittävän nopeita)
  • Ei sisällä hyväksymäkriteerejä

Storyistä toinen

Tarvitaan AI-pohjainen mekanismi automaattisten tiedekunta- ja koulutusohjelmakohtaisten kurssipalautekoosteiden muodostamiseen

Story ei noudata kaikkia INVEST-kriteerejä, mutta toisaalta hyvän backlogin DEEP-kriteerit toteavat, että storyn tulee olla sopivan tarkka. Koska kyseessä on pitkän ajan päästä toteutettavaksi tuleva story, on sen nykyinen tarkkuus hyvä.

Alustava pisteytys:

Ensimmäinen stroty 4 pistettä (0.5 pistettä per puute/korjaus):

  • Ei asiakkaan kielellä
  • Liian iso
  • Valmius vaikea arvioida
  • Hyväksymäkriteerien puute

Lisäksi 0.5 jos todettiin eksplisiittisesti, että story ei ole negotiable.

Toinen story 2 pistettä:

  • perusteet sille miksi story on hyvä

Näiden lisäksi hyvistä oivalluksista tai havainnoista sai plussa, esim. INVEST-kriteerien maininta 0.5p

Tehtävä 3

Tehtävän arvioi Nea Kovalainen. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi

Pisteitä jaossa 1,5 per kohta.

0-0,5 käsitteiden/konseptien selityksestä,

0-0,5 ylipäätään perustelusta,

ja 0-0,5 siitä, onko väite oikein.

Mikäli esimerkiksi käsitteiden/konseptien selityksessä puutteita, mutta perustelu on muuten todella hyvä, on voinut perustelusta saada max 0,7.

a. Kahden eri ketterän tiimin kehitystyön etenemisnopeutta ei voi vertailla missään olosuhteissa

Velositeetilla tarkoitetaan…

  • storypointtien määrä, jonka tiimi saa sprintin aikana tehtyä
  • velositeetti vaihtelee varsinkin alussa paljon → loppuakohden stabiloituu
  • eri tiimeillä eri velositeetit
  • story pointtien selittäminen

Väite väärin:

Eri tiimin välisiä velositeetteja voi vertailla, jos tiimit työskentelevät yhteisen backlogin parissa ja hoitavat estimoimisen yhdessä.

b. Trunk based -development on parempi kehitysmalli kuin feature branchien käyttö

Feature branchien käyttö: käytäntö, jossa uudet toiminnallisuudet tehdään omiin brancheihinsä ja vasta kun toiminnallisuus on valmis se mergetään pääkehityshaaraan (esim. main, master). Pidetty usein best practicena, mutta voi aiheuttaa pahojakin merge konflikteja.

trunk based -development: käytäntö, jossa kaikki muutokset tehdään suoraan pääkehityshaaraan. (Julkaisuista versioista saatetaan tehdä omat release branchit) → muutosten oltava pieniä

Väite väärin tai oikein ja väärin.

Trunk based on voi olla parempi, mm. edesauttaa integraatio-ongelmien minimoinnissa, ja auttaa nopeampaan julkaisutahtiin. Trunk based -menetelmän soveltaminen edellyttää tiimiltä kypsyyttä ja kuria, ja tuskin soveltuu kokemattomille tiimeille. Se myös hankaloittaa koodikatselmointien tyypillistä tekotapaa.

c. Inkrementaalinen lähestymistapa tuottaa paremman arkkitehtuurin kuin vesiputousmallin hengessä tapahtuva tarkka etukäteissuunnittelu

inkrementaalinen tapa (myös iteratiivinen): ohjelmistotuotanto jaettu pienempiin aikaväleihin, määritelmät ja suunnitelmat muuttuvat iteraatioiden edetessä

Väite voi olla jopa oikein. Jos riittävää kurinalaisuutta noudatetaan, saattaa inkrementaalinen tuottaa paremman ratkaisun. Etuja tästä on se, että arkkitehtuuri muotoutuu projektin kuluessa, ja mukautuu tarvittaessa ymmärryksen kasvaessa ja olosuhteiden muuttuessa. Etukäteen lukkoon lyöty arkkitehtuuri voi osoittautua huonoksi kun sovellus kehittyy sen elinkaaren aikana. Usein se on myös ylhäältä annettu joten sitä ei välttämättä noudateta uskollisesti sovelluskehittäjien kesken.

d. Projektissa ei koskaan kannata kopioida koodia tiedostosta toiseen (ts. ei kannata copypasteta)

Väite väärin. ­Vaikka koodin toisteettomuus (DRY-periaate) on yleisesti ottaen hyvä käytäntö, copypaste voi olla perusteltua tietyissä tilanteissa, kuten nopeiden ratkaisujen tekemisessä tai MVP:n rakentamisessa. Refaktorointi voidaan tehdä myöhemmin, kun ratkaisu on vakiintunut. Liiallinen copypaste johtaa kuitenkin ongelmiin, kuten koodin monimutkaistumiseen, bugien korjaamisen vaikeuteen ja huonoon koheesioon. Hyvä periaate on “three strikes and you refactor”, eli jos sama logiikka toistuu kolmessa kohdassa, koodi kannattaa refaktoroida.

Tehtävä 4

Tehtävän arvioi Heidi Tapani. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi tai Discordissa

Sovelluksen tiheää julkaisutahtia (esim kerran viikossa, tai jopa kymmeniä kertoja päivässä) pidetään nykyään hyvänä asiana.

Mahdolliset edut tiheästä julkaisutahdista:

  • Pahoja integrointi ongelmia ei synny, koska muutokset viedään tuotantoon pienissä erissä jatkuvasti.
  • Sovellukseen syntyvät regressiot havaitaan ja voidaan korjata nopeasti.
  • Uudet ominaisuudet ja korjaukset saadaan nopeammin käyttäjille ja voidaan hyödyntää esimerkiksi nopeaa A/B-testausta tuotteen kehityksessä.
  • Pienissä toimituserissä työskentely ja keskeneräisen työn määrän rajoittaminen parantavat tehokkuutta ja vähentävät riskejä.
  • Jatkuvan julkaisun ja laadunhallinnan käytännöt vaikuttavat positiivisesti sekä työhyvinvointiin että organisaation tehokkuuteen.
  • Nopeasti julkaisemalla arvo saadaan nopeammin asiakkaalle ja voidaan tehdä päätöksiä paremmalla tiedolla.

Miten tiheään julkaisutahtiin on mahdollista päästä:

  • Jatkuva integraatio (CI/CD) ja automatisoidut testit ovat välttämättömiä. Jokainen kehittäjän commit kulkee automaattisen testauksen ja mahdollisesti analyysien läpi ennen kuin se siirtyy eteenpäin kohti tuotantoa.
  • Tuotantoon vienti on teknisesti tehty automatisoiduksi tai mahdollisimman helpoksi (“nappia painamalla”) ja työläät manuaaliset vaiheet on minimoitu
  • Kehitystiimien tulee olla kurinalaisia, ja tehdä työtä pienissä erissä ja uudet ominaisuudet viedään nopeasti tuotantoon heti kun ne valmistuvat.
  • Kehitystiimin on oltava osaava ja autonominen, jotta se pystyy reagoimaan nopeasti muuttuviin vaatimuksiin ja julkaisemaan muutokset ilman ulkopuolisia pullonkauloja.
  • Kehitystiimeillä tulee olla päästy tarvittaessa tuotantojärjestelmiin ja tietokantoihin vikatilanteiden varalta
  • Tuotannossa tapahtuva testaus ja tuotantojärjestelmien monitorointi voi olla eduksi
  • Koodin oltava sisäiseltä laadultaan riittävän hyvää, jotta se mahdollistaa nopean julkaisutahdin

Mitä haittoja tiheästä julkaisutahdista voi olla?

  • Sovelluksen virheiden määrä voi kasvaa, jos laadunhallinnan menetelmät eivät ole kunnossa
  • Sovelluksen käyttöliittymän muutokset voivat häiritä käyttäjiä
  • Käyttäjien ohjeistus ei välttämättä pysy ajantasaisen

Pisteytys:

a) 2.5 pistettä b) 2.5 pistettä c) 1 piste

Kaikissa kohdissa a-c on tarjolla 0.5 pistettä per perustelu. Huomaa, että mallivastauksessa olevat pointit ovat vain esimerkkejä, jotka voivat mahdollisesti sisältyä omaan vastaukseesi. Jokaista mallivastauksessa olevaa pointtia ei siis välttämättä ole tarpeen käsitellä vastauksessasi.

Tehtävä 5

Tehtävän arvioi Rasmus Viitanen. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi tai Discordissa

a) Mitä cross functional -tiimeillä ja feature-tiimeillä tarkoitetaan? (1p)

  • Cross functional -tiimi on tiimi, jossa on yhdessä kaikki tarvittava osaaminen tuotteen tai ominaisuuden viemiseksi ideasta tuotantoon.
  • Feature-tiimi on tämän laajempi, asiakkaalle arvoa tuottavien kokonaisuuksien ympärille rakentuva cross functional -tiimi.

Pisteytys:

Cross functional -tiimin oikea määritelmä: 0,5 p
Feature-tiimin oikea määritelmä: 0,5 p
Yhteensä max 1 p

b) Millä perusteilla tällaisia tiimejä pidetään hyvinä? (3p)

  • Vähentää riippuvuutta muista tiimeistä, mikä eliminoi monta Lean hukkaa (eli asiakkaalle arvoa tuottamatonta valmistusprosessin tekijää).
    • Ei turhaa odotusta (esim. tarvetta odottaa toisen tiimin tekemää backendia).
    • Eliminoi välivarastoja (esim. feature on koodattu, mutta odottaa integrointia muuhun koodikantaan).
    • Vikojen korjaaminen ei viivästy, kun tiimi vastaa itse laadunhallinnasta .
  • Mahdollistaa valmiin, testatun ja julkaistavan tuotteen osan (shippable product increment) jokaisen sprintin aikana.
  • Tiimi voi päättää itse työtavoistaan ja vastaa tekemisestään yhdessä.
  • Vähentää siiloutumista ja mahdollistaa oppimista yli kompetenssirajojen.
  • Kun tiimi vastaa suoraan asiakkaalle arvoa tuottavasta toiminnallisuudesta, sillä on selkeä omistajuus työnsä lopputuloksesta ja sen laadusta.

Pisteytys:

1 p per oikea ja kurssimateriaalin mukainen perustelu, kuitenkin enintään 3 p.
Myös muut vastaavat, hyvin perustellut näkökulmat on huomioitu arvostelussa.

c) Mitä mahdollisia huonoja puolia cross functional -tiimien käyttämisestä voi olla? (2p)

  • Hyvät käytänteet eivät välttämättä leviä tiimien välillä.
  • Autonomiset tiimit voivat johtaa siihen, että samoja ongelmia ratkotaan useita kertoja eri tiimeissä ilman tiedon jakamista.
  • Tiimien autonomisuus voi johtaa epäyhtenäisiin toimintatapoihin, mikä voi vaikuttaa kokonaisuuden hallintaan.
    • Cross functional -tiimimalli vaatii tiimiltä kypsyyttä ja yhteisiä toimintatapoja; ilman niitä työn organisointi ja koordinointi voivat kärsiä.
  • Tietyn erikoisosaamisen omaavat henkilöt (esim. UX-suunnittelu, tietoturva-osaaminen, infrastruktuuriosaaminen) voivat ajoittain olla alikuormitettuja yhdessä tiimissä, samalla kun toisissa tiimeissä on pulaa samasta erityisosaamisesta.
  • Cross functional -tiimien kokoaminen voi olla haastavaa, koska samaan tiimiin on koottava useita eri osaamisalueita, eikä sopivia resursseja ole aina helposti saatavilla.

Pisteytys: 1 p per oikea ja hyvin perusteltu huono puoli, kuitenkin enintään 2 p.
Myös mallivastauksen ulkopuoliset, hyvin perustellut havainnot on huomioitu arvostelussa.

Tehtävä 6

Tehtävän arvioi Jasse Merivirta. Omasta arvioinnista voi tarvittaessa kysyä etunimi.sukunimi@helsinki.fi tai Discordissa

1. Tiedostonkäsittelyn eriytys omaksi luokaksi 1.5 pistettä

Esimerkki:

class OrganisationRepository:
    def __init__(self, filename):
        self._filename = filename

    def get_clubs(self):
        clubs = []
        try:
            with open(self._filename) as file:
                for row in file:
                    parts = row.strip().split(";")
                    clubs.append({
                        "name": parts[0],
                        "university": parts[1],
                        "founded": int(parts[2]),
                        "colors": parts[3].split(","),
                    })
            return clubs
        except:
            return []

    def save_clubs(self, clubs):
        with open(self._filename, "w") as file:
            for club in clubs:
                colors_str = ",".join(club["colors"])
                file.write(f"{club['name']};{club['university']};{club['founded']};{colors_str}\n")

1 piste jos toteutettu metodina. Tällöin myöskään seuraavan kohdan injektoinnista ei voi saada täysiä pisteitä.

2. Riippuvuuksien injektointi 1.5 pistettä

Tarkoituksena injektoida eriytetty I/O pääluokalle ja poistaa kovakoodattu filepath.

Esimerkki:

class OrganisationRegister:
    def __init__(self, repository):
        self._repository = repository
        self._clubs = repository.get_clubs()

3. DRY hakumetodeissa 1.5 pistettä

find_by_color, find_by_founded ja find_by_university käyttävät copy-pastettua koodia, tälle on hyvä luoda apufunktio.

Esimerkki:

def _find_club_name_by(self, criteria):
    return [club["name"] for club in self._clubs if criteria(club)]

def find_by_color(self, color):
    return self._find_club_name_by(lambda club: color in club["colors"])

def find_by_founded(self, year, criteria="=="):
    criterias = {
        ">": lambda club: club["founded"] > year,
        "<": lambda club: club["founded"] < year,
        "==": lambda club: club["founded"] == year,
    }
    return self._find_club_name_by(criterias[criteria])

def find_by_university(self, university):
    return self._find_club_name_by(lambda club: club["university"] == university)

4. Yhden organisaation haulle apufunktio 1 piste

Esimerkki:

def _find_by_name(self, name):
    for club in self._clubs:
        if club["name"] == name:
            return club
    return None

def print_info(self, name):
    club = self._find_by_name(name)
    if not club:
        print(f"{name} not found")
        return

    print(f'{club["name"]} ({club["founded"]})')
    print(f'{club["university"]}')
    print(f'Haalarin värit: {", ".join(club["colors"])}')

Näin yhden organisaation haku on eriytetty siitä, mitä sillä tehdään.


5. Parannettu nimeäminen 1 piste

Koodissa on suomenkielisiä ja epäselviä muuttujanimiä, jotka on hyvä siistiä.

Huonoja:

  • apu
  • tulos
  • x

Esimerkki::

# Kuvaavampi muuttujanimi
for club in clubs:
    colors_str = ",".join(club["colors"])

# Samoin tässä
existing_club = self._find_by_name(name)

# Selkeämpi lambda parametri
lambda club: color in club["colors"]

Malliratkaisu kokonaisuudessaan:


class OrganisationRepository:
    def __init__(self, filename):
        self._filename = filename

    def get_clubs(self):
        clubs = []
        try:
            with open(self._filename) as file:
                for row in file:
                    parts = row.strip().split(";")
                    clubs.append(
                        {
                            "name": parts[0],
                            "university": parts[1],
                            "founded": int(parts[2]),
                            "colors": parts[3].split(","),
                        }
                    )
            return clubs
        except:
            return []

    def save_clubs(self, clubs):

        with open(self._filename, "w") as file:
            for club in clubs:
                colors_str = ",".join(club["colors"])
                file.write(f"{club['name']};{club['university']};{club['founded']};{colors_str}\n")


class OrganisationRegister:
    def __init__(self, repository):
        self._repository = repository
        self._clubs = repository.get_clubs()

    def _find_club_name_by(self, criteria):
        return [club["name"] for club in self._clubs if criteria(club)]

    def find_by_color(self, color):
        return self._find_club_name_by(lambda club: color in club["colors"])

    def find_by_founded(self, year, criteria="=="):
        criterias = {
            ">": lambda club: club["founded"] > year,
            "<": lambda club: club["founded"] < year,
            "==": lambda club: club["founded"] == year,
        }

        return self._find_club_name_by(criterias[criteria])

    def find_by_university(self, university):
        return self._find_club_name_by(lambda club: club["university"] == university)

    def _find_by_name(self, name):
        for club in self._clubs:
            if club["name"] == name:
                return club

        return None

    def print_info(self, name):
        club = self._find_by_name(name)
        if not club:
            print(f"{name} not found")
            return

        print(f'{club["name"]} ({club["founded"]})')
        print(f'{club["university"]}')
        print(f'Haalarin värit: {", ".join(club["colors"])}')

    def add_club(self, name, university, founded, colors):
        existing_club = self._find_by_name(name)
        if existing_club:
            return

        club_to_add = {
            "name": name,
            "university": university,
            "founded": founded,
            "colors": colors,
        }
        self._clubs.append(club_to_add)
        self._repository.save_clubs(self._clubs)