JALS-9
For å gjøre kodeendringer i hovedbranchen i Vake må det lages PR. For disse PR-ene bruker vi Github Action workflows som kjører tester/sjekker på kodebasen som må bli grønne for å kunne merge endringene. Testene omfatter logikk (Python tester), kodeformat, typing, at docker imaget bygger m.m. Kodeendringene i en PR må altså gjennom alle disse stegene for å bli godkjent.
Grunnen til at vi “enforcer” dette på alle (via PR) er at det holder kodebasen konsistent, mer leselig og forhåpentligvis fri for bugs. Siden det meste går i Python syns vi det er spesielt greit å være litt strenge. For det som går på kodeformat, typing osv. gjør vi tilsvarende sjekker lokalt gjennom pre-commits for å raskere luke ut småting før man pusher ting remote (denne har vi holdt optional, men alle (?) bruker det).
Ulempen med denne flyten er at det kan ta lengre tid å få inn en PR, og innimellom blir man utålmodig, ventende på sjekker man kanskje vet er helt unødvendig for endringen man prøver å gjøre. For å gjøre dette mer effektivt har vi laget en GA jobb som sjekker hvilke filtyper som er endret i PR-en, og så blir de andre sjekkene utført på bakgrunn av dette. Da slipper vi f.eks. å vente på å sjekke om Docker imaget bygger hvis man kun har gjort en endring i dokumentasjonen.
I sum syns vi det er greit med sjekkene selv om det tar litt tid. Hva tenker dere andre, hvordan gjøres det (eller ikke) i deres team?
JALS-8
I det siste har jeg jobbet litt med metodene vi bruker for å lage treningsbilder fra satellittbilder i Vake. Vi støtter nå snart fire ulike satellittkilder som alle kommer med litt forskjellig struktur og kompleksitet.
Metodene ble opprinnelig skrevet for den ene satellittkilden vi startet med (Sentinel-2). Denne kilden er ganske kompleks siden “ett” bilde består av 13 bånd (RGB + 10 til) som igjen er strukturert i 4 bildefiler etter den romlige oppløsningen båndene har. Resultatet ble metoder som støtter denne strukturen, men som har blitt veldig komplekse og utfordrende å overføre til nye, mindre komplekse kilder som de jeg holder på med nå, uten at de blir enda mer komplekse og uleselige.
Målet mitt er enklere og ryddigere metoder som støtter alle kildene, og som gjør det enklere å legge til flere kilder i fremtiden. Jeg har begynt å utvikle en lovende løsning basert på arv, men på grunn av den opprinnelige kompleksiteten har dette vært utfordrende og dermed tatt mer tid enn jeg har lyst til å bruke akkurat nå (det er andre mer kritiske ting å gjøre). Det er samtidig digg å fullføre noe man har startet på, kompleksiteten har plaget oss en stund, og det er teknisk/faglig sett en interessant utfordring. På den andre siden har det sneket seg inn en følelse jeg har hatt før, at vi prøver å være smarte og “future-proofe” noe, bare for å innse et halvt år senere at vi ikke visste så mye vi trodde, eller at vi f.eks. finner en pakke som gjør alt mye lettere. Faktisk utforsker Adrian en pakke akkurat nå som kan gjøre mye av det jeg startet på redundant.
Hva er konklusjonen? Jeg vet ikke, men ofte synes jeg vi bruker for mye tid på “future-proofing” eller optimalisering av metoder basert på hypoteser om fremtidig bruk. Men ofte har det også blitt motbevist, som har gjort at vi raskt og effektivt kan levere på nye muligheter som oppstår.
JALS-7
Jeg jobber nå med refactorering (igjen). Målet er å fjerne kompleksitet og øke fleksibilitet i en flyt. Denne flyten kan beskrives som: hvordan lese et satellittbilde for:
- trening (ML)
- prediksjon i prod (inference)
Bildene er veldig store, og passer ikke i minnet (“CPU-” eller GPU-minne), så vi chunker opp bildet i mindre vinduer. Kan sammenlignes med convolution, “bare ett lag ut”.

Se for dere at proposjonene er 10.000 x 10.000 for satellittbilde (blå rute) og 512 X 512 for hver chunk (vindu vi laster inn i minnet om gangen). Som i gif’en vil vi også la hver chunk overlappe med den forrige med feks. 50 pixler.
Jeg fant et relativt nytt bibliotek TorchGeo som kan erstatte mye av den kompliserte koden. Denne har vi en del krav til for at den skal kunne støtte vår bruk.
- Den må ikke være tregere enn den eksisterende løsningen
- Den må ikke stille strenge krav til oss på feks formater eller fil-struktur
- Det må være enkelt å tweake den
- … jeg kommer på fler og fler
Jeg gjorde noe de-risking ved å én-til-én teste med den eksisterende koden og fant fort ut at den kom til kort på ett punkt. Den kan ikke lese bilder direkte fra en cloud bucket, slik som vår kode kan.
Her har heller ikke kildekoden lagt opp til at man kan tweake den så
lett. Jeg må override hele init
-metoden kun for å fjerne en
sjekk på om filen eksisterer. Selve lese-metoden klarer fint å lese fra
bucket, men sjekken gjør ikke.
Da har jeg to alternativer:
- Overskrive
init
- Lage en PR til biblioteket
I nummer 1. vil jo min implementasjon overskrive resten av logikken i
init
for alltid, og fremtidige oppdateringer i kildekoden
går tapt.
Det riktige er helt sikkert nummer 2. med en fiks som løser problemet for alle andre brukere av bibliotek. Men det tar tid, og jeg må gjøre det skikkelig og sette meg inn i nye ting jeg ikke kan. Samtidig så har jeg jo en stor gjeld til open-source-miljøet. Samtidig har jeg ikke fått de-risket biblioteket fullt ut enda. Jeg tror jeg må gå for 2 likevel, må jeg ikke?
JALS-6 - Lage en editor i browseren
Jeg jobber med å lage en editor Unicad for ren tekst (uten formatering). Men vi ønsker å lagre informasjon om hva brukeren gjør, ikke kun enderesultatet.
Dvs, istedet for å lagre teksten hei
, vil vi lagre det
brukeren gjorde for å komme dit. Om brukeren skriver hi
,
ser brukeren har gjort en feil, visker vekk i
og skriver
ei
så det blir hei
, vil vi lagre:
- Skrev
h
på posisjon 0 - Skrev
i
på posisjon 1 - Visket ut posisjon 1
- Skrev
e
på posisjon 1 - Skrev
i
på posisjon 2
Dette for å få til collaborative editing, og for effektivt kontinuerlig kunne sende meldinger frem og tilbake til serveren.
Det finnes en løsning for dette, og det er et event som heter
beforeInput
. Det er er en event som skjer rett etter
brukeren har gjort en handling, men før handlingen “får effekt” i
DOM-en. Her kan vi både se hva brukeren gjør, og også stoppe handlingen.
Dette er det vi bruker for rik-tekst i Unicad.
Denne eventen støttes av <textarea>
. Så alt ser
bra ut. Trodde jeg. Helt til jeg fant ut at når man kobler dette på en
<textarea>
så fungerer ikke
getTargetRanges()
, som er en funksjon som returnerer
hvor endringen skjedde. Det hjelper ikke om vi vet at brukeren
skrev i
hvis vi ikke vet hvor i
-en skal
være.
Vi kan trikse oss rundt det ved å bruke <textarea>
sin selectionStart
og selectionEnd
. Men disse
returnerer hvor selection er nå (dvs før hendelsen skjedde,
siden DOM-en ikke er oppdatert enda), ikke hvor endringen kommer til å
skje.
Om brukeren trykker Ctrl+Backspace vil man i de fleste browsere viske
ut et helt ord. Men for å støtte denne med beforeInput
i et
textarea
må vi faktisk selv finne ut hvilket ord brukeren
har visket ut, vi får kun vite at brukeren har gjort input action
deleteWordBackward
, og hvor cursoren stod når brukeren
gjorde det.
Det er også flere ting som gjorde koden vanskelig å ha med å gjøre
når jeg brukte textarea
og beforeInput
.
Resultatet var at jeg måtte endre til en <div>
med
content-editable=true
som har sine egne quirks, blant annet
dårligere støtte for universell utforming.
Så konklusjonen er: Editering i browseren er broken (men ikke så
broken som før vi fikk beforeInput
, som var så sent som
2021). Skal du lage en editor i browseren, bruk et tredjepartsbibliotek
som CodeMirror (for ren tekst) eller ProseMirror (for rik tekst). Det er
en grunn til hvorfor vi velger å ikke gjøre det i Unicad, selv om vi på
ingen måte er sikre på at vi tok riktig valg (men fremdeles heller mot
at vi gjorde det riktige valget). Hvorfor vi ikke bruker CodeMirror og
ProseMirror i Unicad får være en annen jals.
Har du spørsmål eller kommentarer, ta kontakt med meg på slack (for Iterate-ansatte) eller på mail (for andre).
– Sindre
JALS-5: Mange veier til Rom
Som en programmerer både hater jeg og setter pris på på hvordan det samme resultatet kan oppnås på forskjellige måter. Ta for eksempel følgende react-komponenter.
const PermissionCell = ({
permission,
}: {
permission: LibraryPermissionType;
}) => {
const t = useT();
return (
<div>
{(() => {
switch (permission.scope) {
case 'owner':
return t('Administrator');
case 'write':
return t('Can edit content');
case 'read':
return t('Can use content');
}
})()}
</div>
);
};
export const PermissionCell = ({
permission,
}: {
permission: LibraryPermissionType;
}) => {
const t = useT();
return (
<div>
{permission.scope === 'owner' && t('Administrator')}
{permission.scope === 'write' && t('Can edit content')}
{permission.scope === 'read' && t('Can use content')}
</div>
);
};
De utfører alle samme oppgave, men på forskjellige måter. Dette spekteret av muligheter kan noen ganger gjøre koding utfordrende. Likevel gir det også rom for kreativitet og tilpasning. Og mens noen kanskje vil diskutere hvilken metode som er “best”, synes jeg ikke det spiller noen rolle så lenge koden fungerer som den skal og sjeldnere og sjeldnere gidder jeg å ha en mening i pr-reviews så lenge jeg skjønner hva som skjer.
Det viktigste for meg er at neste utvikler som leser koden forstår hva som skjer. Og det er denne balansen mellom funksjonalitet og forståelighet som er det sanne målet god kode. Som med så mye annet i livet, er det mange veier til samme mål.
(Skrevet 96% av chat gpt)
JALS-4: Vi rakk det
JALS intromøte. Det er satt av 50 minutter. Jeg liker ikke møter som er lengre enn 30 minutter fordi jeg ikke klarer å holde fokus så lenge. Folk flest klarer ikke det. Jeg opplever også at det sjelden er en møte-agenda som krever mer enn 30 minutter, man bare setter av ekstra tid i tilfelle. Dette fører veldig ofte til at man bruker 10 minutter bare på å komme gang med møtet. Så bruker man 20 minutter på første møtepunkt fordi man tenker man har god tid, før man innser at man har 4 møtepunkter igjen på de siste 20 minuttene. De 4 viktigste. I utgangspunktet hadde 20 minutter holdt til de 4 punktene, men nå har det jo gått 30 minutter og lufta er ute av ballongen. Jeg har brukt mine tilmålte 30 minutter konsentrasjon på møtejazzing. Jeg går på tomgang, og for hvert minutt som går vil det kreve 2 minutter ekstra etter møtet for å få hodet i balanse igjen.
Adrian kom inn i møtet med en tidsfrist på 15 minutter. Et uforutsett styremøte i Vake. Vi kom oss igjennom agendaen.
JALS-3
Jeg syns det var passende at min første JALS kunne handle om
installasjon av jals/olorm cli siden jeg hadde fått en liten advarsel om
at noe kunne mangle i README
-en, og jeg lærte litt av
prosessen.
For å installere fulgte jeg stegene for å klone repo-et, installere
babashka
og bbin
og eventuelt legge til noe på
PATH
uten problemer. Da jeg skulle installere
jals
derimot, ble jeg møtt av en error som etter litt om og
men tydet på problemer med Java-installasjonen min. Siden
jals
er skrevet i Clojure som er koblet til Java, ga det
litt mening.
Hva var problemet med Java-installasjonen? Den fantes ikke. Java JDK
blir ikke (lenger?) forhåndsinstallert på Mac, og jeg har aldri hatt
bruk for det. Det er flere måter å installere Java JDK på Mac, men den
enkleste for meg syntes å være via brew
.
Jeg kjørte brew install java
som installerer Java under
/opt/homebrew/
, men det i seg selv er ikke nok da systemet
leter etter Java JDK under
/Library/Java/JavaVirtualMachines/
. For å løse dette kan
man bruke symlinks (symbolic links), som er en fil som har som formål å
peke på en annen fil eller mappe. Ved å lage en sånn link i systemets
mappe for JVMs (/Library/Java/JavaVirtualMachines/
) vil
systemet automatisk finne Java uten at man trenger å f.eks. gjøre noe
med PATH
. Symlinks kan lages med kommandoen
ln -s
på kommandolinjen:
Som man ser på bildet, kjørte jeg ln
kommandoen og en
link/fil dukket opp i mappa. Det gjorde at systemet fant Java, og da
gikk installasjonen av jals
smertefritt!
JALS-2: Salg i Unicad
Jeg har akkurat vært i møte med en potensiell kunde for Unicad, startupen hvor jeg er CTO (og en av to foundere) Som utvikler er det ikke veldig vanlig å være i direktekontakt med kunder, men for meg er et slikt møte verdifullt.
Jeg får feedback til hva kundene synes gir verdi, og det er viktig å vite når man lager et produkt. Spesielt når man er i tidlig fase slik som oss.
Jeg blir inspirert ved å vite at det jeg lager blir brukt av ekte mennesker som løser ekte problemer.
Og ikke minst gir det muligheten for teamet ha en god samtale om hva vi synes er verdi og hva vi vil prioritere videre. Har vi de samme tankene etter møtet, eller sitter vi igjen med forskjellig inntrykk? Uansett om vi er enige eller uenige så gir det oss verdifull innsikt.
For et lite startup-team på to personer så er det kanskje ikke så uvanlig at den som er CTO er med i salgsmøter, men kanskje man burde innføre det også for større selskaper. Hvordan skal vi videreføre denne verdien når vi vokser og er så store at vi har egne selgere?
JALS-1
Jeg refaktorerer hvordan vi håndterer trente maskinlæringsmodeller. Vi bruker et verktøy kalt neptune som, etter en trening, lagrer vektene i sin sky. Vektene til en modell representerer det modellen har lært. Når vi skal kjøre inference på ny data (bilder), må vi laste ned disse vektene igjen for å instansiere modellen.
Problembeskrivelse:
- Under trening lagres vektene lokalt i en mappestruktur som neptune velger selv, før den lastes opp til sky.
- Vi ønsker å bruke den samme mappestrukturen når vi laster ned vektene igjen. Følge standarder
- Når vi skal kjøre inference forholder vi oss kun til en “modell-id”, og vet ikke denne mappestrukturen.
- Vi kan gjøre kall mot neptune for å få denne mappestrukturen.
- For “viktige” modeller/vekter ønsker vi å cache vektene i vår egen bucket, slik at vi ikke er avhengig av neptune i produksjon.
Hovedproblem:
Hvordan gjenskape mappestrukturen uten å gjøre kall til neptune, og uten å hardkode masse greier?
Dette er et kjedelig problem som jeg ikke finner mye kvalitet i. Jeg tror det er grunnet i en forventning om at verktøyet (neptune) burde håndtere dette annerledes, eller tilby mer fleksibilitet enn det gjør.