Självkonfigurerande applikationer

”Jag hittar själv det grönaste gräset” – S.T. Allion

Som utvecklare koncentrerar man sig på hur applikationen skall fungera i produktion. Ibland glömmer man bort att göra det enkelt att deploja och köra applikationen på sin egen utvecklingsmaskin. När man lägger till ett externt beroende tänker man kanske inte på att kollegorna blir tvungna att göra samma inställningar på sina maskiner som du med möda och besvär gjort på din maskin. Den utvecklare som kanske kommer att få mest problem blir du själv när du försöker sätta upp utvecklingsmiljön på en ny dator.

Ett typiskt externt beroende för en normal webbapplikation är åtkomsten till en databasserver. Om inte databasaccessen är korrekt konfigurerad kommer applikationen att fallera. Den enda ”hjälp” att fixa problemet du får är oftast några hundra raders stacktrace. Felmeddelandena handlar om följdproblem där man själv måste knyta symptomet till rotproblemet.

Men vänta lite, detta är ju ett lösbart problem! Det handlar om att tillämpa en enkel uppsättning regler.

Grundregler för externa beroenden

  1. Kontrollera vid uppstart om allt är på plats. Om inte, gå vidare till nästa punkt.
  2. Kan vi automatiskt lösa problemet genom en enkel sökning? Om inte, gå vidare till nästa punkt.
  3. Mänsklig assistans behövs. Kan vi först reducera antalet potentiella lösningar? I så fall presentera dessa val för användaren. Om det inte är möjligt, gå vidare till nästa punkt.
  4. Informera tydligt vad applikationen behöver, och hur man normalt sett går tillväga för att göra dessa inställningar.

Låt oss tillämpa dessa regler på beroendet av en databas. Här kommer ett förslag i form av javascriptformaterad pseudokod.

Application.connectToDatabase()

Application.connectToDatabase = function()
{
   var db;
   var settingsFile = loadSettings();
   if (settingsFile && settingsFile.dbSettings.correctlyFormatted())
   {
      var dbServer = connectToDbServer(settingsFile.dbSettings);
      if (!dbServer.connected())
      {
         dbServer = UserDialog.helpUserStartUpTheDbServer();
      }
      db = dbServer.getDbForCurrentApplication();
      if (!db.isValid())
      {
         db = UserDialog.helpUserCreateNewDb(dbServer);
         settingsFile.saveDbSettings(db);
      }
   }
   else
   {
       var localDbServer = connectToDbServer(LOCAL_HOST_DB_SETTINGS);
       if (localDbServer.connected())
       {
          db = dbServer.getDbForCurrentApplication();
          if (!db.isValid() || !UserDialog.letUserCorfirmDbChoice(db))
          {
             db = UserDialog.helpUserFindDbServerAndSelectDb();
          }
       }
       else
       {
          db = UserDialog.helpUserFindDbServerAndSelectDb();
       }
       settingsFile.saveDbSettings(db);
   }
   return db;
}

Vänlighet

I koden ovan tillämpar jag grundreglerna rekursivt. Funktionerna i UserDialog hjälper användaren enligt samma regler och principer.

Egentligen är dessa Grundregler för externa beroenden samma som grundreglerna för användbarhet och användarvänlighet. Det handlar bara om att tillämpa dom i lite högre grad än vad som är brukligt. Det finns egentligen inget som säger att databasuppkopplingskoden ovan inte skall vara kvar i produktion. Även vid driftsättning till prod behöver man hjälp ibland. Åtminstone behöver man bra hjälpmeddelanden, inte dåliga felmeddelanden. Var snäll.

JavaScript && Ordning && Reda

Det är enkelt att med lite JavaScript öka användarvänligheten på en i övrigt statisk webbsida. Man stoppar in en funktion här, och lite jQuery där. Men så fort det blir fler än ett par funktioner behöver man lite mer ordning och reda. Nu skall jag beskriva ett enkelt sätt att få en vettig struktur på din JavaScript-kod. Kanske är jag färgad av Java och C# för strukturen liknar mycket en vanlig klassdefinition.

Vi skall göra ett knapp-objekt med dom publika metoderna enable() och disable(). Här är ett par screenshots av objektet in action.

buton_disabled

buton_enabled1

Sidan

Htmlkoden för ovanstående är följande:

<label for="inputField">Please type "enable":</label>
<input id="inputField" type="text">
<a id="button" href="javascript: alert('Button clicked');">
    <img src="button.png">
</a>

Och här kommer javascriptkoden på samma sida:

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js">
</script>
<script src="Button.js"></script>
<script>
    $(document).ready(function ()
    {
        // Här skapar vi vårt button-objekt med en fabriksmetod som
        // finns i filen Button.js.
        var button = DDT.Button($("#button"));

        // Vid varje tangenttryckning anropar vi antingen enable()
        // eller disable() på vårt button-objekt.
        $("#inputField").keyup(function (e)
        {
            if ($("#inputField").val().indexOf("enable") != -1)
                button.enable();
            else
                button.disable();
        });
    });
</script>

Mer än så behövs inte för att använda vårt knappobjekt. All intressant kod ligger i Button.js.

Button.js

// Skapa först ett "namespace"/"package". DDT står för
// Datema Dev Team.
var DDT = DDT || {};

// Detta är fabriksfunktionen som skapar och returnerar
// Button-objekt.
DDT.Button = function(buttonAnchor)
{
    // Privat medlemsvariabel
    var href;

    // Privata "konstanter"
    var SUFFIX_GRAYED_OUT = "_grayed_out.png";
    var SUFFIX_NORMAL = ".png";

    // Detta är vår konstruktor
    var make = function()
    {
        // Först sparar vi undan aktuell href på den anchor-tagg som
        // skickades in från sidan.
        href = buttonAnchor.attr("href");

        // Nu är vi redo att skapa och returnera själva
        // Button-objektet med dess två publika metoder.
        return {
            enable: enable,
            disable: disable
        }
    };

    // Den publika metoden enable
    var enable = function (id)
    {
        if (isEnabled()) return;
        buttonAnchor.attr("href", href);
        var imgSrc = buttonAnchor.children("img").attr("src");
        var imgSrcNormal =
               imgSrc.replace(SUFFIX_GRAYED_OUT, SUFFIX_NORMAL);
        buttonAnchor.children("img").attr("src", imgSrcNormal);
    };

    // Den publika metoden disable
    var disable = function()
    {
        if (!isEnabled()) return;
        buttonAnchor.removeAttr("href");
        var imgSrc = buttonAnchor.children("img").attr("src");
        var imgSrcGrayedOut =
               imgSrc.replace(SUFFIX_NORMAL, SUFFIX_GRAYED_OUT);
        buttonAnchor.children("img").attr("src", imgSrcGrayedOut);
    };

    // En privat metod som används från enable och disable.
    var isEnabled = function()
    {
        return buttonAnchor.attr("href");
    };

    // Här anropar vi konstruktorn och returnerar Button-objektet.
    return make();
};

Ordning och reda

Som synes får man en klasslik struktur på koden. Överst kommer privata medlemsvariabler och konstanter.

Därefter följer konstruktorn som jag döpt till make(). Den ser inte ut att att ta några argument men det gör den! Själva fabriksmetoden DDT.Button() tar argumentet buttonAnchor vilket därmed blir tillgängligt för alla lokala metoder inklusive make().

Funktionen make() skapar och returnerar själva Button-objektet. Det enda som blir synligt utifrån blir metoderna enable() och disable(). Dessa metoder kan vi på ett snyggt sätt implementera efter make() eftersom make() ännu inte körts.

Efter enable() och disable() kommer den privata metoden isEnabled(). Det enda som skiljer den från dom publika metoderna är att den inte returneras som en del av Button-objektet i make().

Till sist måste vi faktiskt göra något annat än att skapa lokala variabler och funktioner. Vi anropar nu make() och returnerar dess resultat.

Slutsats

Det finns många sätt att organisera sin kod i JavaScript. Ovanstående mönster tycker jag är ett enkelt sätt att skapa en lättläst struktur.