Hvis bare utviklerne …

  • Hvis bare utviklerne hadde vært med i salgsmøtene,
  • Hvis bare utviklerne hadde estimert sakene bedre,
  • Hvis bare utviklerne hadde planlagt sprinten nøye,
  • Hvis bare utviklerne hadde blitt enige på forhånd,
  • Hvis bare utviklerne hadde satt seg ned sammen med interaksjonsdesigneren,
  • Hvis bare utviklerne hadde satt seg ned sammen med produkteieren,
  • Hvis bare utviklerne hadde satt seg ned sammen med arkitekten,
  • Hvis bare utviklerne hadde satt seg ned sammen med kunden,
  • Hvis bare utviklerne hadde brukt mindre tid i møter,
  • Hvis bare utviklerne hadde fått ting ferdig fortere,
  • Hvis bare utviklerne hadde skrevet testene først,
  • Hvis bare utviklerne hadde gjort en sak av gangen,
  • Hvis bare utviklerne hadde brukt bedre verktøy,
  • Hvis bare utviklerne hadde konsistent bruk av whitespace,
  • Hvis bare utviklerne hadde startet med de største sakene,
  • Hvis bare utviklerne hadde samme vokabular som kunden,
  • Hvis bare utviklerne hadde kjørt koden gjennom statisk analyse,
  • Hvis bare utviklerne hadde vært mer smidige,
  • Hvis bare utviklerne hadde jobbet konsentrert,
  • Hvis bare utviklerne hadde parprogrammert,
  • Hvis bare utviklerne hadde brukt samme editor,
  • Hvis bare utviklerne hadde startet med en velfundert arkitektur,
  • Hvis bare utviklerne hadde skrevet integrasjonstester,
  • Hvis bare utviklerne hadde ryddet opp i gammel kode,
  • Hvis bare utviklerne hadde lagd færre bugs,
  • Hvis bare utviklerne hadde sett alle konsekvenser av en endring,
  • Hvis bare utviklerne hadde fulgt prosessen til punkt og prikke,
  • Hvis bare utviklerne hadde skrevet utfyllende dokumentasjon,
  • Hvis bare utviklerne hadde funnet gode navn på alle variable,
  • Hvis bare utviklerne hadde samarbeidet på tvers av team,
  • Hvis bare utviklerne hadde jobbet godt alene,
  • Hvis bare utviklerne hadde brukt bunnsolide rammeverk,
  • Hvis bare utviklerne hadde fulgt formateringsreglene,
  • Hvis bare utviklerne hadde tatt en fot i bakken,
  • Hvis bare utviklerne hadde fortalt hvor mange timer som gjenstod,
  • Hvis bare utviklerne hadde brukt mindre tid på produksjonssaker,
  • Hvis bare utviklerne hadde kjørt alle testene før hver commit,
  • Hvis bare utviklerne hadde latt være å brekke bygget,
  • Hvis bare utviklerne hadde sjekket i alle nettleserne,
  • Hvis bare utviklerne hadde tatt høyde for framtidige endringer,
  • Hvis bare utviklerne hadde vært litt flinkere,

så hadde alt vært … ok.

Thursday, February 23, 2012   ()

En (proto)typisk snubletråd

I dag snublet jeg i en felle som jeg merkelig nok ikke har ramlet i før. Jeg bruker Raphaël for å lage en visualisering av sidene i eventyr til spillet mitt, og hadde dette objektet:

var page = {
    size: 10,
    alternative_spacing_dx: 4,
    alternative_spacing_dy: 20,
    
    create: function (number) {
        var self = Object.create(this);
        self.number = number;
        return self;
    },

    // ...
}

Så jeg lagde en haug med sider med page.create(). Noen av sidene endret sin state.

Problemet oppstod når jeg fikk den gode idéen å endre alternativenes spacing til

alternative_spacing: {
    dx: 4,
    dy: 20
}

Jeg syns det var en fin refaktorering, men testene mine gikk fullstendig bananas. Du ser kanskje hvor det gikk galt?

Sidene mine som før hadde endret sin egen state med this.alternative_spacing_dx, endret nå state til et felles alternative_spacing-objekt. Jeg var nødt til å endre create-metoden til å inneholde:

self.alternative_spacing = Object.create(this.alternative_spacing);

Men det føltes litt kjipt. Jeg liker ikke at alle objektene jeg refererer må creates, og hva om de har objekter igjen, og de også? Huff. Har du en bedre måte?

Og ja, beklager tittelen, det kan se ut til at jeg har blitt ødelagt av VGs forsider opp gjennom årene.

Saturday, April 2, 2011   ()

HTML5 - hva kan vi bruke NÅ?

Her er lyntalen jeg holdt på FINN.no sin techdag i går. Den går sikkert raskt ut på dato, så se den mens den er fersk. ^^

Og ja, det er den lille jenta mi som fant det for godt å våkne på slutten der.

Et par lenker fra lyntalen

Wednesday, October 27, 2010   ()

Sett opp TextMate for testing med Sinon

TextMate har en haug med bra snippets og kommandoer ut av boksen, men det er også lekende lett å legge til egne.

I min screencast om TDD med javascript og sinon brukte jeg noen hjemmelagde snippets. Jeg legger dem ut her. Bruk dem om du vil!

TextMate sin bundle editor

Her ser du Bundle Editoren som du raskt åpner med ctrl+opt+cmd+b. En herlig shortcut!

  1. Trykk på + nede i venstre hjørne, og lag en ny Bundle ved navn sinon.
  2. Lag en ny Snippet. Tab Trigger: tc, Scope Selector: source.js. Lim inn:
TestCase("${1:${TM_FILENAME/(?:\A|_)([A-Za-z0-9]+)(?:\.js)?/(?2::\u$1)/g}}", sinon.testCase({
${2:	setUp: function (stub, mock) {
		$3
	\},

}	"test ${4:should ${5:do stuff}}": function (stub, mock) {
		$0
	}
}));

Regexp-helvetet på første linje drar ut filnavnet, og lager CamelCase av den. Deretter tabulerer du deg fra $1 til $2 til $3 .. og til slutt ender cursoren på $0.

Så er det bare å lukke Bundle Editoren og prøve.

Et par ekstra hendige snippets

Her er et par andre jeg bruker:

Ny test. Tab Trigger: tt, Scope Selector: source.js

"test ${4:should ${5:do stuff}}": function (stub, mock) {
	$0
}

Sett inn test-HTML. Tab Trigger: doc, Scope Selector: source.js

/*:DOC += 
	<div>
		$0
	</div>
*/

Kjør testene fra TextMate

Det er også en smal sak å lage en shortcut for å kjøre testene rett fra TextMate. Lag en ny Command:

  • Save: Current file
  • Input: None
  • Output: Show as HTML
  • Activation: Key Equivalent: cmd+R
  • Scope Selector: source.js

Og kommando:

jstestdriver --tests all --html --reset --config "../jsTestDriver.conf"

Legg merke til at kommandoen kjøres relativt fra filen du har åpen.

En bra bok om emnet

Jeg kan anbefale TextMate: Power Editing for the Mac av James Edward Gray II. Der er det mange gode TextMate-triks å lære. Jeg kom meg ikke helt gjennom de siste kapitlene om Language Grammars, men hovedparten av boka om Editing og Automation er spennende og nyttig.

Friday, July 2, 2010   ()

5 JavaScript-uvaner du må legge av deg

Jeg tør påstå at JavaScript er et av språkene som er mest utsatt for cargo culting. For noen år siden var det utstrakt klipping og liming, og uvanene spredte seg fortere enn du kunne si globalt navnerom.

Her er noen saker du må slutte med:

1. Du trenger ikke støtte Netscape 1

Jeg ser fortsatt script-tagger som denne:

<script type="text/javascript">
<!--

// -->
</script>

De utkommenteringene er der for at browsere uten kjennskap til script-taggen ikke skal skrive kildekoden vår rett inn i DOMen. Det kan du få lov å slutte med i år 2010.

2. Slutt å forsøple det globale navnerommet

Alle variable som ikke er deklarert i en funksjon deler samme navnerom, på tvers av alle scripts. Etter hvert som mengden javascript på en side øker, blir sjansen også stadig større for at dere begynner å tråkke i hverandres bedd.

Ta dette høyst sannsynlige eksempelet:

function debug(message) {
    if (window.console && console.log) {
        console.log(message);
    }
}

Her har jeg lagd en hendig liten metode for å slippe å brekke browsere når jeg glemmer å fjerne mine console.log() linjer før innsjekk.

Så kommer det en annen kar med et annet script:

var debug = true;

Og med det er ikke min debug-funksjon lengre noen funksjon, men en boolsk verdi. Velkommen til hodepinen!

Løsningen? Lag et navnerom rundt scriptet ditt med denne sildesalaten:

(function () {
    // din kode her
}());

Les mer på Christian sin blog.

3. Nei, blocks har ikke scope

Så slutt å skrive

function iterate_over(a) {
    for (var i = 0; i < a.length; i++) {
    }
}

Du forvirrer bare deg selv og andre. I JavaScript er det bare funksjoner som skaper nytt navnerom. Du bør etterstrebe å deklarere variablene i toppen av funksjonen:

function iterate_over(a) {
    var i, length = a.length;
    for (i = 0; i < length; i++) {
    }
}

4. Ikke bruk stor forbokstav på funksjoner

JavaScript er ikke et perfekt designet språk. Det er mange feller å falle i. Derfor er det viktig at du bruker JSLint.

En særdeles graverende feil forekommer hvis du skulle komme til å kalle en “constructor” uten new foran. Constructorer i JavaScript er bare funksjoner, så du får ingen feilmelding. I stedet blir this satt til det globale navnerommet, og så fyller du det opp med alt mulig rask uten å mene det - eller vite om det.

Derfor har godeste herr Crockford foreslått at vi bruker stor forbokstav på funksjoner som er ment som constructorer. Da kan JSLint si i fra hvis vi glemmer oss ut, og dermed unngår vi hele spetakkelet.

5. JavaScript har ikke klasser

Så la oss slutte å late som.

JavaScript har prototypisk arv i bunnen, med et tynt lag pseudo-klassisk arv på toppen som:

  • har klønete syntaks
  • har leaky abstractions
  • ikke gjenspeiler språkets virkemåter og styrker

I stedet lager du et parent-objekt, og lar alle andre arve fra det med Object.create. Jeg er også glad i bruke jQuery.extend. Da ser det slik ut:

var feline = {
    legs: 4,
    claws: "sharp",
    speak: function () {
        print("roar!!");
    }
};

cat = jQuery.extend(Object.create(feline), {
    diet: ["mice", "birds"],
    speak: function () {
        print("purr...");
    }
});

garfield = jQuery.extend(Object.create(cat), {
    diet: ["lasagna"],
    owner: "Jon"
});

Dessverre er ikke Object.create implementert i alle browsere. Det løser du slik:

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Se også Christian sine kommentarer under for tilfeller der du heller bør definere create i ditt eget navnerom.

Har du noe på hjertet?

Hører gjerne fra deg i kommentarfeltet om du syns jeg er på viddene, bak mål, eller midt på blinken.

Wednesday, June 30, 2010   ()

Kom i gang med testdrevet JavaScript på 10 minutter

Testdrevet utvikling med JavaScript har hatt noen seriøse ulemper, men etter hvert som det har kommet stadig bedre verktøy, smaker det stadig flauere av unnskyldningene.

I dag skal vi raskt få opp et fullgodt utviklingsmiljø for testdrevet javascript:

  • JsTestDriver kjører testene våre, og løser de to største problemene fra andre rammeverk; halvautomatiske tester eller simulerte nettlesere.
  • jstdutil legger seg rundt JsTestDriver og tilbyr et hyggeligere grensesnitt og autotest.
  • sinon gir oss et lettvekts rammeverk å skrive testene i, og gir presise og lettleste tester.

Obs! Videoen har begynt å vise sin alder. Den bruker Sinon.js 0.5.0. Det er bittelitt endring i forhold til versjonen nå (1.1.1).

Der testmetodene tidligere tok inn (stub, mock) så får man nå tilgang til disse metodene via this.stub() eller this.mock(). Dermed fungerer testen "test should do stuff" bedre om den begynner slik:

"test should do stuff": function() {
  this.stub(jQuery, "getJSON");

Lykke til med testingen!

Wednesday, June 16, 2010   ()

3 hindere du må hoppe over for å bruke de nye HTML5-elementene

Jeg dyppet nylig tærne i den store HTML5-sjøen når jeg lagde kdoemkaer.no. Det var en hyggelig opplevelse. W3C er ikke engang ferdig med spesifikasjonen, men dersom du er eventyrlysten kan du begynne å bruke deler av moroa allerede i dag. 

Ikke akkurat “enterprise ready”

Før du bygger det nye nettstedet ditt med HTML5-elementer, så husk at:

  • Det krever JavaScript for å fungere i IE6+7+8.
  • Print-styling av de nye elementene er helt ute i IE6+7+8.

Hvis du kan svelge disse kamelene, så er du good to go; bare hopp over disse tre hinderne:

1. Default styling i alle browsere

Blant de nye godbitene er article, aside, footer, header, hgroup, nav og section definert som blokknivå-elementer. Det er ikke alle browsere som henger med, så derfor hjelper vi dem på veien:

article, 
aside, 
footer, 
header, 
hgroup, 
nav, 
section {display: block;}

Det er en god vane å starte stylingarbeidet med en CSS-reset. Om du velger å bruke YUI sin eller Eric Meyer sin, så syns jeg den ovenstående regelen hører fint hjemme der.

2. Få Internet Explorer med på laget

Du blir nok ikke overrasket når jeg sier at IE trenger litt ekstra hjelp. For at den skal tilgodese våre nye elementer med CSS-regler, så må de først ha blitt registrert vha document.createElement.

Heldigvis har Remy Sharp lagd den ferdig for oss. Alt du trenger å gjøre er å legge til denne snutten i head:

<!--[if lt IE 9]>
  <script
    src="http://html5shiv.googlecode.com/svn/trunk/html5.js">
  </script>
<![endif]-->

Her er det en fordel om du hotlinker, slik at vi kan ta nytte av caching på tvers av nettsteder.

Legg forøvrig merke til at IE9 har støtte for HTML5, så derfor inkluderer vi ikke shiv’en der.

3. Ytterligere IE-problemer

Når du skal sette inn HTML vha rammeverk som jQuery og prototype, bygger disse opp elementene i fragmenter utenfor DOMen før de settes inn. Det fungerer dårlig med de nye elementene i IE.

Derfor har jdbartlett laget html5 innerShiv. Den er uavhengig av rammeverk, og litt krøkkete i bruk, men løser også det siste gjenstående problemet. I stedet for:

$("#elem").load(url);

må du nå gjøre

$.get(url, function (html) {
    $("#elem").html(innerShiv(html));
});

Dette er problemet som trekker mest ned. Forhåpentligvis så vil innerShiv bli tatt inn i varmen av rammeverkene, slik at vi kan bli kvitt dette uvesenet. I mellomtiden kan du unngå html5-elementer i JS-koden din som en alternativ mellomløsning.

Konklusjon

Etter å ha brukt HTML5 når jeg lagde kdoemkaer.no, smerter det meg å skrive <div id="header"> og <div class="article">. Dessverre kan vi ikke kaste oss på HTML5-bølgen over alt riktig enda, på grunn av problemene nevnt i starten, men med disse triksene kan vi begynne som smått. Og det kjenns godt.

PS!

Lurer du på forskjellen på article, section og div?

  • div er en enkel oppdeling av siden, uten ytterligere semantisk innhold
  • informasjonen i en section er relatert
  • informasjonen i en article er relatert og selvstendig

Så article skal gi mening dersom den blir tatt ut av siden og står alene. Du bruker section for relatert informasjon som ikke er en selvstendig informasjonsbit. Og div er fortsatt fint å bruke for å dele opp siden slik at styling og javascript har noe å henge seg på.

Les mer på Mark Pilgrim sin utmerkede og høyst underholdende Dive Into HTML5.

Sunday, May 30, 2010   ()

En ny jQuery-kommando du bør begynne å bruke i dag

Du har naturligvis hørt om bind og live, men hva er egentlig delegate? Her får du se hvordan events kan knyttes til hundrevis av elementer uten ytelsesproblemer.

Bind sliter fort

Klassisk binding av events ved hjelp av jQuery sin bind-metode er utmerket når du skal binde håndtering av click på en lenke, eller submit på en form.

Problemene dukker opp når du skal binde samme funksjon til hundrevis av elementer i en liste:

  1. Du ender opp med hundrevis av event-handlere definert i DOMen.
  2. jQuery må finne fram og løkke seg gjennom alle elementene for å binde dem opp.
  3. Du må eksplisitt binde opp alle relevante events når du senere legger til nye elementer i lista. 

Live løser mye

Det første og siste problemet har lenge vært løst takket være live, i hvert fall for et subsett av event-typer. Ved hjelp av at de fleste events bobler oppover i DOM-treet, løser live problemet ved å binde en enkel handler til body. Denne delegerer videre basert på selectoren.

Slik ser det ut:

 $("#images a.delete").live("click", delete_this_image); 

Ser du hva som går galt?

jQuery starter med å tolke selectoren, kjører den gjennom Sizzle, finner fram hundrevis av ankerelementer, plukker ut de med klassen delete, luker bort de som ikke er etterfølgere av #images … og så blir disse dyrt ervervede DOM-elementene fullstendig ignorert av live. Den var jo bare interessert i selector-strengen "#images a.delete", for eventen skal knyttes opp til body.

Sizzle er utvilsomt en ytterst rask selectormotor, men dette er ren waste.

Delegate løser resten

Og med det ønsker vi velkommen til delegate, som kom i jQuery 1.4.2:

 $("#images").delegate("a.delete", "click", delete_this_image);

Ja, nå begynner det å bli bra.

I stedet for å hente ut hundrevis av elementer vi ikke skal bruke, henter vi ut bare #images. En lynrask operasjon. Resten av informasjonen som trengs sendes inn som parametere.

Dermed er også problem #2 løst. Vi har fått en effektiv måte å binde opp events på hundrevis av elementer, både nåværende og framtidige. 

I tillegg:

  • Eventen bindes ikke lengre til body, men i stedet rett på #images.
  • jQuery 1.4.x kom også med støtte for de resterende event-typene som normalt ikke bobler, eksempelvis mouseover og blur.

Her er det ingenting å lure på; løp ut og deleger!

Sunday, May 23, 2010   ()

Javascript og internasjonalisering

Internasjonalisering (kjærlig referert til som i18n) er en effektiv måte å gjøre strukturert semantisk markup til hodepineinduserende tagsuppe. Enda verre blir det når man skal internasjonalisere javascript.

La oss ta en titt på noen måter dette kan gjøres rent praktisk. Eller som oftest; upraktisk. Vi starter der.

Inline hele javascriptkoden

Jobben med å sette inn språksnutter gjøres av jsp-motoren. Det kan være fristende å la html og javascript leve side om side i samme dokument. Ikke gå i den fella. Javascript i egne filer gir oss

Render en separat javascriptfil

Okay, så legger vi javascripten i en egen fil. JSP-motoren kan fortsatt rendre koden. Alt som trenges er å endre filtypen til jsp og inkludere fila på vanlig måte.

<script src="/javascript.jsp"></script>

Problemet er at koden fortsatt må prosesseres før den er komplett. Får vi fjernet jsp-taggene fra javascriptkoden, så vil:

  • koden bli renere
  • minifisering blir enklere
  • og vi kan bruke JSLint

Men uten at taggene skrives rett inn av templatemotoren, så må vi finne en annen kontaktflate mellom scriptet og i18n-systemet.

Legg språkinfo i skjulte html-elementer

En mulighet er at scriptet plukker opp språkinfoen den trenger fra skjulte elementer på siden. HTMLen blir ikke akkurat noe vakrere:

<input type="hidden" id="i18n_confirm_delete"
  value="<i18n:output text="confirm.delete"/>"/>
<input type="hidden" id="i18n_no_undo"
  value="<i18n:output text="no.undo"/>"/>

Men vi har blitt kvitt jsp-tagger fra javascripten:

var text = {
    confirm_delete: document.getElementById('i18n_confirm_delete').value,
    no_undo: document.getElementById('i18n_no_undo').value
};

Og dermed er det viktigste på plass.

Vi kan nå bruke verktøy for å minifisere koden, slå sammen filer for raskere nedlasting, og sjekke for uheldig kodestil med JSLint.

Men alt er ikke vel på vestre frontend.

Avhengigheten som skapes mellom HTMLen og javascriptkoden er ikke tydelig nok, og virker tilfeldig. Når noen andre snubler over HTMLen så er det ikke åpenbart hvor de skjulte inputfeltene brukes.

Det kan bøtes på med noen velvalgte kommentarer, men la oss ikke gå dit. Kode bør være selvdokumenterende.

Legg språkinfo i et globalt tilgjengelig javascript-objekt

Istedet kan vi bygge et objekt med språksnuttene til bruk i scriptet: 

<script type="text/javascript">
  var TEXT = {
    confirm_delete: '<i18n:output text="confirm.delete"/>',
    no_undo: '<i18n:output text="no.undo"/>'
  }
</script>
<script src="/javascript/admin_panel.js"></script>

Vi får i18n-tags i noe javascriptkode, men begrenset kun til konfigurasjon. Resten av scriptet, der funksjonaliteten vår ligger, vil ligge i en egen fil og få alle fordelene av det. 

Problemet med denne løsningen er at vi får en avhengighet fra javascriptfila ut i det globale navnerommet. Andre som bruker samme i18n-teknikk i en annen del av siden kan lett komme til å overskrive vårt globale TEXT-objekt.

Men vi nærmer oss.

Snu på avhengigheten

I stedet for at scriptet henter språkinfoen den trenger fra DOM-elementer eller det globale navnerommet, så setter vi den inn ved initalisering.

<script src="/javascript/article_admin.js"></script>
<script type="text/javascript">
  KM.article_admin.initialize({
    text: {
      confirm_delete: '<i18n:output text="confirm.delete"/>',
      no_undo: '<i18n:output text="no.undo"/>'
    }
  });
</script>

All funksjonalitet som ikke har direkte med språksnutter å gjøre ligger i article_admin.js. I stedet for at vi setter opp progressive enhancement idet scriptet kjører, så flytter vi dette til en initialiseringsfunksjon som kjøres rett etterpå - med språksnuttene som trengs.

Rammeverket i article_admin.js for å motta tekstsnuttene ser slik ut:

(function () {
  var text;
    
  var delete_article = function () { 
    /* kode her har tilgang til text-objektet */
  };

  KM.article_admin = {
    initialize: function (data) {
      text = data.text;
    }
  };
}());

Legg merke til at vi definerer en anonym funksjon, slik at vi ikke forsøpler det globale navnerommet. Deretter binder vi text til hele funksjonsscopet, som settes i initialize.

Nå har vi separert den funksjonelle javascripten i egne filer, og samtidig samlet den i18n-spesifike koden på en plass.

Resultatet

Den endelige article_admin-koden kan se slik ut (med jQuery):

/*global KM, jQuery, confirm */
(function ($) {
  var text,

  delete_article = function ($article) {
    $.post("/admin/delete_article", 
      {id: $article.attr("id")}, 
      function () {
        $article.fadeOut();
      }
    );
  },

  confirm_to_delete_article = function (event) {
    var $article = $(this).closest(".article"),
      name = $article.find("h3.name").text();
    if (confirm(text.confirm_delete + name + text.no_undo)) {
      delete_article($article);
    }
    return false;
  },
  
  initialize = function (data) {
    text = data.text;
    $("#articles a.delete").live("click", confirm_to_delete_article);
  };
  
  KM.article_admin = {initialize: initialize};
}(jQuery));
Monday, April 5, 2010   ()