Exempel på problemlösning

I denna artikeln går vi igenom hur man på ett bra sätt kan skriva kod som hanterar ett visst problem (utan OOP).

För att visa hur man på ett bra sätt kan lösa ett problem (och vad som gör det till ett bra sätt) ska vi gå igenom ett problem från början till slut. Problemet har vi fått från vår arbetskamrat, Roffe.

Problemet är att Roffe behöver en bit kod som hanterar ett datum, men han har inte tid att skriva den själv, utan han behöver din avlastande hjälp. Allt du får veta av Roffe är att han ska kunna göra följande saker med datumet:

  • Skapa ett datum (så klart) utifrån angivet år, månad och dag i månaden.
  • Få datumet som en sträng i formatet "ÅÅÅÅ-MM-DD".
  • Öka datumet en dag.
  • Minska datumet en dag.

Han skiter fullständigt i hur du löser detta, bara han kan använda det du skriver på ett enkelt sätt utan att behöva sätta sig in i hur koden du skrivit fungerar alltför mycket.

Ok, så låt oss ta itu problemet och börja med att titta närmare på hur man ska kunna skapa ett nytt datum. När vi skapar ett nytt datum vet vi året, månaden och dagen i månaden för datumet, så en associativ array med nycklarna "år", "månad" och "dag" låter som en lämplig representation för detta. För till exempel datumet 2011-10-13 skulle det kunna se ut såhär:

PHP - PHP: Hypertext Preprocessor
<?php // Skapar det nya datumet 2011-10-13 $datum = array( 'år' => 2011, 'månad' => 10, 'dag' => 13 ); ?>

Det här fungerar, men det är inte så schysst mot Roffe som kommer använda koden senare. Dels måste han veta att datumet är representerat som en associativ array på detta sätt, sedan blir det en hel del kod att skriva bara för att skapa ett nytt datum, och slutligen framgår det inte så tydligt att ett nytt datum faktiskt skapas när man skriver denna koden. Det blir mycket bättre om man istället har en funktion som Roffe får anropa när han vill skapa ett nytt datum, och skicka med året, månaden och dagen i månaden som parametrar. Notera att en associativ array fortfarande verkar vara en lämplig representation för datumet.

PHP - PHP: Hypertext Preprocessor
<?php // Funktion som skapar ett nytt datum function nyttDatum($år, $månad, $dag){ // Lägg all info om datumet i en associativ array... $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); // ...och skicka tillbaka den! return $datum; } // Skapar det nya datumet 2011-10-13 $datum = nyttDatum(2011, 10, 13); ?>

Notera rad 19. När vi nu anropar en funktion vid namn nyttDatum framgår det mycket mer tydligt att vi skapar ett nytt datum, jämfört med hur vi gjorde innan, och koden som behövs skrivas för att skapa ett nytt datum har minskat en hel del (Roffe blir glad!).

Nästa sak Roffe vill kunna göra med datumet är att få det som en sträng i formatet "ÅÅÅÅ-MM-DD", så han till exempel kan skriva ut det med echo. Det är enkelt gjort, eller?

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } // Skapar det nya datumet 2011-10-13 $datum = nyttDatum(2011, 10, 13); // Skriver ut datumet echo $datum['år'].'-'.$datum['månad'].'-'.$datum['dag']; ?>
2011-10-13

Det var ju inte så svårt, den här koden borde väll Roffe kunna använda varje gång han vill skriva ut ett nytt datum, eller? Bara kopiera och klistra in? Nej, ska sanningen fram fungerar den inte ens för alla datum. Hur blir det till exempel för datumet 2011-04-13? Jo, såhär:

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Skriver ut datumet echo $datum['år'].'-'.$datum['månad'].'-'.$datum['dag']; ?>
2011-4-13

Månaden ska visas med en inledande nolla om den är mindre än 10, så det krävs faktiskt mer kod än den vi har använt för att korrekt skriva ut datumet varje gång. Samma problem uppstår med dagen i månaden om den är mindre än 10, så vår kod borde se ut såhär (vi bryr oss inte om att året ska börja med inledande nolla, vi utgår från att det är ett fyrsiffrigt tal):

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Skriver ut datumet echo $datum['år'].'-'; if($datum['månad'] < 10){ echo '0'; } echo $datum['månad'].'-'; if($datum['dag'] < 10){ echo '0'; } echo $datum['dag']; ?>
2011-04-13

Nu skriver den ut alla datum korrekt, men jösses vad mycket kod det blev för att skriva ut ett enda datum. Ska Roffe verkligen behöva kopiera och klistra in all den här koden varje gång han vill skriva ut ett datum? Svaret är så klart nej. Vi ska lägga den här koden i en funktion som Roffe får anropa när han vill ha datumet som en sträng, så samma kod kommer köras varje gång Roffe vill skriva ut ett datum, men den kommer bara finnas på ett ställe.

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } // Funktion som gör om ett datum till en sträng function datumTillSträng($datum){ // Bygg upp strängen... $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; // ...och skicka tillbaka den! return $sträng; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-13

Det finns en stor fördel med att använda en funktion för att göra om datumet till en sträng, och det är att koden för att göra om datumet till en sträng enbart finns på ett ställe. Om man senare upptäcker att man har gjort ett fel i koden behöver man bara rätta till det på ett ställe, jämfört med att ändra på alla ställen där man kopierat och klistrat in koden om man inte hade använt en funktion.

Visst, men om man är erfaren och inte gör fel, då? Jo, det finns en fördel här med. Just nu vill Roffe skriva ut datumet i formatet "ÅÅÅÅ-MM-DD", men någon annan gång kanske han vill skriva ut datumet i formatet "DD/MM/ÅÅÅÅ", ett format som används i något annat land, och då behöver man bara ändra på ett ställe i koden om vi lägger koden i en funktion. Sedan kanske Roffe tycker att man visst ska se till att året skrivs ut med inledande nollor om det är mindre än 999, och då behöver vi (ännu igen) bara ändra detta på ett ställe, vilket gör det enklare för oss.

Sedan skulle Roffe även kunna öka datumet med en dag. Det verkar inte vara så svårt.

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Öka datumet en dag $datum['dag']++; // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-14

Återigen fungerar koden korrekt, men den fungerar inte korrekt för alla datum. Just nu påstår den att till exempel dagen efter 2011-01-31 är 2011-01-32, men det ska ju egentligen vara 2011-02-01! Samma problem får vi om månaden är 12 och ökas med 1, då ska året ökas med 1 och månaden börja om på 1. En lösning på det kan vara följande kod (där vi inte har tagit hänsyn till skottår!):

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Öka datumet en dag // Ren data över antalet dagar i varje månad $allaMånadersDagar = array( 1 => 31, // Januari 2 => 28, // Februari (tar ej hänsyn till skottår) 3 => 31, // Mars 4 => 30, // April 5 => 31, // Maj 6 => 30, // Juni 7 => 31, // Juli 8 => 31, // Augusti 9 => 30, // September 10 => 31, // Oktober 11 => 30, // November 12 => 31 // December ); // Antal dagar i denna månaden $dennaMånadensDagar = $allaMånadersDagar[$datum['månad']]; // Kolla om man ska byta månad if($datum['dag'] == $dennaMånadensDagar){ $datum['dag'] = 1; // Kolla om man även ska byta år if($datum['månad'] == 12){ $datum['år']++; $datum['månad'] = 1; }else{ $datum['månad']++; } }else{ $datum['dag']++; } // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-14

Jösses, nu blev det mycket kod igen, tror ni Roffe tycker det är ok att skriva den här koden varje gång han vill öka ett datum med en dag? Nej, självklart inte, så vi lägger den här koden i en funktion istället. I framtiden vill vi kanske ändra så att datumet även tar hänsyn till skottår, och då behöver vi enbart ändra i denna funktionen, istället för ändra på alla ställen där man kopierat och klistrat in denna koden, så även vi kan tjäna på att använda en funktion.

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } // Funktion som ökar ett datum en dag function ökaDatumEnDag($datum){ // Ren data över antalet dagar i varje månad $allaMånadersDagar = array( 1 => 31, // Januari 2 => 28, // Februari (tar ej hänsyn till skottår) 3 => 31, // Mars 4 => 30, // April 5 => 31, // Maj 6 => 30, // Juni 7 => 31, // Juli 8 => 31, // Augusti 9 => 30, // September 10 => 31, // Oktober 11 => 30, // November 12 => 31 // December ); // Antal dagar i denna månaden $dennaMånadensDagar = $allaMånadersDagar[$datum['månad']]; // Kolla om man ska byta månad if($datum['dag'] == $dennaMånadensDagar){ $datum['dag'] = 1; // Kolla om man även ska byta år if($datum['månad'] == 12){ $datum['år']++; $datum['månad'] = 1; }else{ $datum['månad']++; } }else{ $datum['dag']++; } // Skicka tillbaka det nya datumet return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Öka datumet en dag $datum = ökaDatumEnDag($datum); // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-14

Roffe skulle ju även kunna minska datumet en dag. Då blir det såhär (vi har lärt oss av föregående exempel och lägger koden direkt i en egen funktion):

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } function ökaDatumEnDag($datum){ $allaMånadersDagar = array( 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 ); $dennaMånadensDagar = $allaMånadersDagar[$datum['månad']]; if($datum['dag'] == $dennaMånadensDagar){ $datum['dag'] = 1; if($datum['månad'] == 12){ $datum['år']++; $datum['månad'] = 1; }else{ $datum['månad']++; } }else{ $datum['dag']++; } return $datum; } // Funktion som minskar ett datum en dag function minskaDatumEnDag($datum){ // Kolla om man ska byta månad if($datum['dag'] == 1){ // Kolla om man även ska byta år if($datum['månad'] == 1){ $datum['år']--; $datum['månad'] = 12; }else{ $datum['månad']--; } // Ren data över antalet dagar i varje månad $allaMånadersDagar = array( 1 => 31, // Januari 2 => 28, // Februari (tar ej hänsyn till skottår) 3 => 31, // Mars 4 => 30, // April 5 => 31, // Maj 6 => 30, // Juni 7 => 31, // Juli 8 => 31, // Augusti 9 => 30, // September 10 => 31, // Oktober 11 => 30, // November 12 => 31 // December ); // Antal dagar i denna månaden $dennaMånadensDagar = $allaMånadersDagar[$datum['månad']]; $datum['dag'] = $dennaMånadensDagar; }else{ $datum['dag']--; } // Skicka tillbaka det nya datumet return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Minska datumet en dag $datum = minskaDatumEnDag($datum); // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-01-12

Här har vi dock stött på en sak som kan göras bättre. Både funktionen ökaDatumEnDag och funktionen minskaDatumEnDag behöver veta hur många dagar det är i varje månad, vilket de just nu gör genom att de vardera innehåller arrayen $dagarIAllaMånader. Att ha samma data på två olika ställen är både dåligt och onödigt. Onödigt för att det tar upp dubbelt så mycket plats i minnet, och dåligt för att om man behöver ändra det vid ett senare tillfälle (Roffe kanske vill att vi tar hänsyn till skottår i framtiden så att februari får 28 eller 29 dagar) måste man ändra på två ställen.

Vad är den bästa lösningen på det här, då? Det är troligtvis att använda en funktion för att få reda på hur många dagar en månad innehåller. Då blir vår kod såhär istället:

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } // Funktion som returnerar antalet dagar i angiven månad function antalDagarIMånad($månad){ // Ren data över antalet dagar i varje månad $allaMånadersDagar = array( 1 => 31, // Januari 2 => 28, // Februari (tar ej hänsyn till skottår) 3 => 31, // Mars 4 => 30, // April 5 => 31, // Maj 6 => 30, // Juni 7 => 31, // Juli 8 => 31, // Augusti 9 => 30, // September 10 => 31, // Oktober 11 => 30, // November 12 => 31 // December ); // Returnera antalet dagar i angiven månad return $allaMånadersDagar[$månad]; } // Funktion som ökar ett datum en dag function ökaDatumEnDag($datum){ // Få reda på antalet dagar genom funktionen istället $dennaMånadensDagar = antalDagarIMånad($datum['månad']); if($datum['dag'] == $dennaMånadensDagar){ $datum['dag'] = 1; if($datum['månad'] == 12){ $datum['år']++; $datum['månad'] = 1; }else{ $datum['månad']++; } }else{ $datum['dag']++; } return $datum; } // Funktion som minskar ett datum en dag function minskaDatumEnDag($datum){ if($datum['dag'] == 1){ if($datum['månad'] == 1){ $datum['år']--; $datum['månad'] = 12; }else{ $datum['månad']--; } // Få reda på antalet dagar genom funktionen istället $dennaMånadensDagar = antalDagarIMånad($datum['månad']); $datum['dag'] = $dennaMånadensDagar; }else{ $datum['dag']--; } return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Minska datumet en dag $datum = minskaDatumEnDag($datum); // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-12

Nu har vi faktiskt all kod som behövs för att Roffe ska bli nöjd, men kan vi förbättra den på något sätt? Funktionerna ökaDatumEnDag och minskaDatumEnDag gör ju i princip samma sak, men åt olika håll. Sedan ökar/minskar de datumet med enbart en dag, det skulle vara mycket trevligare om man kunde skicka med antalet dagar man vill de ska öka/minska datumet med som en extra parameter som är ett heltal. Om heltalet är positivt ska datumet öka med det antalet dagar, annars, om det är negativt, ska datumet minska med det antalet dagar. Vi lägger till funktionen ändraDatum för att implementera denna funktionen.

PHP - PHP: Hypertext Preprocessor
<?php function nyttDatum($år, $månad, $dag){ $datum = array( 'år' => $år, 'månad' => $månad, 'dag' => $dag ); return $datum; } function datumTillSträng($datum){ $sträng = $datum['år'].'-'; if($datum['månad'] < 10){ $sträng .= '0'; } $sträng .= $datum['månad'].'-'; if($datum['dag'] < 10){ $sträng .= '0'; } $sträng .= $datum['dag']; return $sträng; } function antalDagarIMånad($månad){ $allaMånadersDagar = array( 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 ); return $allaMånadersDagar[$månad]; } function ökaDatumEnDag($datum){ $dennaMånadensDagar = antalDagarIMånad($datum['månad']); if($datum['dag'] == $dennaMånadensDagar){ $datum['dag'] = 1; if($datum['månad'] == 12){ $datum['år']++; $datum['månad'] = 1; }else{ $datum['månad']++; } }else{ $datum['dag']++; } return $datum; } function minskaDatumEnDag($datum){ if($datum['dag'] == 1){ if($datum['månad'] == 1){ $datum['år']--; $datum['månad'] = 12; }else{ $datum['månad']--; } $dennaMånadensDagar = antalDagarIMånad($datum['månad']); $datum['dag'] = $dennaMånadensDagar; }else{ $datum['dag']--; } return $datum; } // Funktion som ändrar datumet det angivna antalet dagar function ändraDatum($datum, $antalDagar){ // $ökaEllerMinska är true om vi ska öka, annars false if(0 < $antalDagar){ $ökaEllerMinska = true; }else{ $ökaEllerMinska = false; $antalDagar = -$antalDagar // Gör $antalDagar positiv } // Iterera $antalDagar gånger och utför rätt funktion på datumet for($i=1; $i<=$antalDagar; $i++){ if($ökaEllerMinska){ $datum = ökaDatumEnDag($datum); }else{ $datum = minskaDatumEnDag($datum); } } // Skicka tillbaka det nya datumet return $datum; } // Skapar det nya datumet 2011-04-13 $datum = nyttDatum(2011, 4, 13); // Minska datumet 5 dagar $datum = ändraDatum($datum, -5); // Skriver ut datumet echo datumTillSträng($datum); ?>
2011-04-08

Nu kan vi nog inte förbättra koden på något sätt, så vi lägger alla funktioner vi har skapat i en egen fil (datum.php) och skickar den till Roffe tillsammans med kommentaren "Se raderna 3, 15 och 115 för hur du ska anropa de funktioner du kommer använda", och Roffe blir jätteglad!

Vad är det som gör den lösningen vi har gjort till en bra lösning? Jo, som du ser är det ganska komplext att hantera ett datum. Vår kod är uppe i cirka 150 rader, och ändå kan man bara skapa datum, få datum som en sträng och lägga till/dra ifrån dagar till ett datum. Men trots detta är det enkelt för Roffe att hantera datum med koden vi har skrivit. Han behöver bara känna till hur funktionerna skapaDatum, datumTillSträng och ändraDatum ska anropas för att kunna arbeta med olika datum, vilket är vår styrka: koden är enkel att använda. Roffe behöver inte ens känna till att vi representerar datumet med hjälp av en associativ array, för han använder sig enbart av funktionerna.

Man skulle kunna säga att vi har skapat en ny datatyp, datatypen datum. Andra som använder vår kod (anropar funktionerna vi har skapat) har ingen aning om vad som egentligen händer, men de kan använda den i alla fall för att hantera datatypen. Precis likadant är det med de datatyper som finns inbyggda i PHP. Ta datatypen integer (heltal) till exempel. Du vet exakt vad man kan göra med den (bland annat addera, subtrahera, dividera och multiplicera med ett annat tal), men hur utför PHP dessa operationer egentligen? Det har du ingen aning om, så att säga att vi har skapat en helt ny datatyp är inte helt fel eftersom den används på exakt samma sätt som de som finns inbyggda i PHP.