Päätin harjoitella Go-ohjelmointia tekemällä yksinkertaisen IRC-yhteysohjelman. Se toimii Macissa terminaalissa ja käyttää ncurses-kirjastoa tekstipohjaisen käyttöliittymän piirtämiseen ruudulle.

Varsinainen IRC-protokollakirjasto on eroteltu omaksi moduuliksi, jotta sitä voisi jatkokehittää ja hyödyntää esimerkiksi bottien toteuttamisessa. Tällä hetkellä se osaa lähinnä avata yhteyden serverille ja liittyä kanavalle sekä kutsua sovelluskohtaisia callback-funktioita, kun jotain tapahtuu. Pääohjelma sijaitsee irc.IRCClient.Run()-metodissa, joka kuuntelee joukkoa go-channeleita ja reagoi niistä tuleviin syötteisiin.

Tässä lähdekoodi BSD-lisenssillä. Makefile on tehty Mac OS X:lle ja kääntää ohjelman 64-bittiseksi binääriksi. Se pitäisi olla melko triviaalia muokata toimimaan Linuxissa. Monimutkaisuutta aiheuttaa lähinnä ncurses-wrapperin kääntäminen.

Koodaamisen yhteydessä tuli tutustuttua ainakin seuraaviin asioihin:

  • Omien Go-packagejen toteutus ja kääntäminen (ncurses.a, irc.a).
  • C-kirjastojen kutsuminen omasta Go-koodista (ncurses).
  • Asynkroninen ohjelmointi goroutineilla ja channeleilla.
  • Slicejen käyttö perinteisten array-taulukoiden sijaan.
  • Komentoriviparametrien parsiminen flag-packagella.
  • Regexpien hyödyntäminen regexp-packagella.
  • Socket-yhteyksien avaaminen net-packagella.

Yhteenvetona voin sanoa, että olen positiivisesti yllättynyt Go:n helppoudesta ja toimivuudesta. Vertaan sitä nyt lähinnä C-kieleen. Pythonin kaltaiset korkeamman tason kielet ovat toki omalla tavallaan tuottavampia ja helpompia käyttää. Yleisesti ottaen tuli kuitenkin eteen aika vähän sellaisia tilanteita, että ottaisi päähän, kun jotain asiaa ei voi tehdä lyhyesti ja siististi.

Go:n hieman omalaatuinen objektiorientoituneisuus sopii mielestäni hyvin systeemiohjelmointiin, jossa toimitaan tyypillisesti komentorivillä ja käsitellään lähinnä verkkoyhteyksiä, merkkijonoja, listoja ja tiedostoja. Se on selvästi saanut vahvoja vaikutteita Pythonista siinä mielessä, että kaiken ei tarvitse olla "väkisin" objekti. Monessa tilanteessa on yksinkertaisempaa tehdä vain irrallisia funktioita. Package-malli takaa sen, että funktiot, struktuurit ja metodit pysyvät organisoituina.

Hieman negatiivisina havaintoina voisin luetella seuraavaa:

Slicet: En oikein tajunnut vielä, miten slicejä pitäisi hyödyntää niin, että ei joutuisi koodaamaan paljon turhaa logiikkaa niiden jatkuvaan kasvattamiseen. Tyypillisesti allokoidaan esimerkiksi tilaa 100 yksikön slicelle ja sitten kasvatetaan sliceä tähän tapaan:

list := make([]string, 0, 100);

func addToList(item string) { if len(list) == cap(list) { newlist := make([]string, len(list), len(list)+1); for i, item := range list { newlist[i] = item } list = newlist; } list = list[0:len(list)+1]; list[len(list)] = item; }

Joka kerta listan perään lisätessä pitää siis tarkistaa, loppuiko tila kesken, sekä manuaalisesti allokoida uusi lista ja kopioda vanhan sisältö siihen. Ehkä tähän on joku fiksumpi keino?

For-loopit: Usein joutuu kirjoittamaan loopattavan funktiokutsun kahteen kertaan virheenkäsittelyn takia. Esimerkiksi näin:

for line, err = reader.ReadString('\n');
    err == nil;
    line, err = reader.ReadString('\n') {
      ...
}

Olisi mukavampaa, jos virheentarkistuksen voisi yhdistää syntaksissa yhteen kutsuun, joka toimisi samalla for-loopin jatkumisen ehtona. Sinänsä pidän kyllä paljon Go:n multi-return-value-ominaisuudesta, mutta se hankaloittaa funktion paluuarvojen hyödyntämistä joustavasti, kun arvot pitää aina ensin kiinnittää väliaikaismuuttujiin.

Dynaaminen select: Go-kanavia on helppo kuunnella select-lausekkeella, mutta se edellyttää kaikkien kuunneltavien kanavien listaamista koodissa case-lauseina. En keksinyt keinoa miten voisi kuunnella dynaamisesti N kappaletta kanavia, jotka on tallennettu esimerkiksi sliceen.

Pointterit: Go:ssa voi välittää parametrejä ja muuttujia joko pointtereina tai struktuureina. Ei ole ihan itsestään selvää, kumpaa kannattaa missäkin tilanteessa käyttää. Korkeamman tason ohjelmointikielissä yleensä kaikki objektit ovat pointtereita, mutta Go:ta koodatessa tulee helposti käyttäneeksi tavallisia struktuureja, mikä voi olla tehotonta. Toivottavasti tähän tottuu, jos koodaa enemmän.

Unicode: Perusohjelmoinnissa string-merkkijonot koostuvat 8-bittisistä tavuista. Jos niitä haluaa tulkita UTF-8:na, täytyy käyttää erillistä utf8-packagea. Luulen että tämä lähestymistapa tulee vielä kostautumaan pitkällä tähtäimellä. Olisi ollut parempi tehdä natiivista string-tyypistä suoraan Unicodea.

Isot alkukirjaimet: Go käyttää isoja alkukirjaimia merkitsemään julkiset funktiot, metodit ja muuttujat. Toisin sanoen pienellä kirjoitetut nimet näkyvät ainoastaan packagen sisällä, ja isolla alkukirjaimella kirjoitetut nimet näkyvät packagen ulkopuolelle. Tähän kyllä tottuu, mutta ei se kovin kaunista ole. Itse ehkä kannattaisin mieluummin perinteistä _alaviiva-prefiksiä yksityisten nimien erottelemiseen.