Programmering

Programmering (fra oldgræsk πρόγραμμα prógramma) er en proces, som går ud på at udvikle computerprogrammer (software) til elektronisk databehandling på en computer, ofte ved hjælp af et programmeringssprog. Programmering er en underafdeling af softwareudvikling. Formålet med et program er overordnet at modtage nogle input-data, udføre en opgave på disse (f.eks. beregninger, udskrivning, kommunikation med database) og som resultat tilbagelevere nogle output-data.

Programmeringsprocessen 
Udviklingen af et program er en proces bestående af det at skrive kildekode, testning, fejlretning (også kaldet aflusning, eng. debugging) og vedligeholdelse af kildekoden. Programmet skrives som regel som ren tekst i et programmeringssprog, som har en defineret syntaks for koden. Hvis der er tale om et compileret sprog, oversættes (compileres) kildekoden først til maskinkode eller et anden mere maskinnær kode. I denne compilering opdages også eventuelle syntaksfejl. Den oversatte maskinkode kan derefter køres af computeren. Hvis der er tale om et fortolket sprog, kører fortolkeren programmet direkte fra kildekoden, og først undervejs i kørselen opdages eventuelle syntaksfejl.

Programmeringen er en del af hele systemudviklingsprocessen, og forudsætningen for at programmere er, at der først er gennemført en analyse af, hvilke krav systemet skal opfylde over for brugerne. Dette er beskrevet i en kravspecifikation. Herefter designes systemet, og programmeringen gennemføres.

Der er en løbende debat om hvorvidt kodning af programmer er en kunst, et håndværk eller en ingeniør disciplin. Generelt anses god programmering for en afmålt anvendelse af alle tre, hvor målet er at producere en effektiv software-løsning som kan videreudvikles. Disciplinen afviger fra mange andre tekniske professioner, da programmører, generelt set, ikke behøver licens eller bevis på en standardiseret (eller offentligt reguleret) certifikation for at kalde sig selv "programmører" eller endda "softwareingeniør".

Datatyper 
Data i et program skal tilhøre en bestemt datatype. Hvilke datatyper, der kan anvendes, afhænger af programmeringssproget, men groft sagt kan de deles op i:

 Primitive datatyper 
 Heltal
 Her er der flere typer afhængig af, hvor store heltal man har behov for at kunne beregne.
 Decimaltal
 Her er der også flere typer afhængig af behovet for regnenøjagtighed (eng. precision) på decimaler.
 Sandhedsværdi (boolean)
 Disse kan kun antage to værdier, sandt eller falsk.
 Tegn / karakter
 Kan være et hvilket som helst tegn i det tegnsæt, programmet anvender.
 Peger (eng. pointer)
 Indeholder en reference til en variabel og består egentlig af variabelens adresse i hukommelsen.

 Sammensatte datatyper 

 Tekststreng
 En tekststreng (eng. string) er et stykke tekst bestående af et antal tegn (karakterer). Grundlæggende er alle inputdata og outputdata til et program af denne type, der så af programmet skal oversættes til f.eks. heltal.
 Array
 Er en statisk, indiceret samling af variable af samme datatype.
 Post (eng. record)
 Består af felter af forskellig datatype. Denne afspejler bl.a. en post i en database-tabel.

 Objekter 

 Objekt
 En avanceret datatype, som i nyere objektorienteret programmering er en type med både har data og adfærd. I et objektorienteret sprog dækker et objekt over andre sammensatte datatyper.

Operatorer 
For at regne på de enkelte datatyper opstiller man regneudtryk, der består af dataværdier og operatorer. Operatorerne kan deles op i:
 Aritmetiske operatorer, der regner på talværdier.
 Konkatenering, hvor to tekststrenge sættes sammen til én.
 Logiske operatorer, der sammenligner værdier og regner på sandhedsværdier.
Derudover har de enkelte programmeringssprog en række ekstra operatorer for at håndtere sprogets sætninger og datatyper (især objekterne).

En vigtig del af et programmeringssprog er dets operatorpræcedens, der angiver rækkefølgen for at bruge de enkelte operatorer, hvis der optræder flere forskellige i samme udtryk.

Variable og konstanter 
En dataværdi placeres ofte i en eller flere variable (pladsholder). En variabel er kendetegnet ved et alfanumerisk variabelnavn og en bestemt datatype, men dens konkrete værdi kan skifte undervejs i programmet. Har man brug for en pladsholder, der ikke må skifte værdi, har de fleste sprog mulighed for at definere en konstant.

Sætninger 

For at opfylde sine formål gennemgår programmet nogle algoritmer, der er sekvenser af beregninger. De enkelte trin i sprogets implementering af en algoritme kaldes sætninger, som grundlæggende opdeles i:
 Variabelerklæringer, hvor man opretter en variabel og angiver dens datatype.
 Tildelinger (eng. assignment), hvor man giver en variabel en bestemt dataværdi, der måske udregnes i den samme sætning.
 Forgreninger, hvor et sandhedsudtryk afgør, om programmet skal udføre den ene eller anden gruppe sætninger.
 Løkker, hvor et sandhedsudtryk er afgørende for, hvor mange gange man skal udføre en gruppe sætninger.
 Funktionskald, hvor en gruppe sammenhørende beregninger er blevet samlet i en selvstændig programdel (funktion, procedure, metode), som så kaldes fra andre dele af programmet.

Forgreninger 
Forgreninger er noget af det mest basale i et program, fordi grundlaget for computerberegninger netop er forgreninger, hvor der overalt evalueres på, om noget er sandt eller falsk. For at en algoritme ikke blot er en lineær sekvens af beregninger, men kan variere beregningerne afhængig af de givne data, er man nødt til at vurdere på, hvorvidt en betingelse er opfyldt eller ej. Enhver forgrening indledes derfor med en betingelse. Forgreninger kan også være indlejrede i hinanden (eng. nested), så man kan gå tre eller flere veje det givne sted i algoritmen.

I de ældste programmeringssprog implementerede man forgreninger ved hjælp af etiketter (eng. labels) ved bestemte sætninger eller linienumre kombineret med GOTO-sætninger, der fortalte, hvor i programmet man skulle gå hen. GOTO-sætninger kunne dog nemt medføre fejl i programmets flow, fordi der til enhver "gren" i en forgrening skulle være en GOTO-sætning både for at starte på grenen og for at afslutte den. I dag har man andre metoder til at vise, at en gruppe sætninger hører sammen i en "gren". Som regel bruger man nøgleord som IF ... THEN ... ELSE ... for at implementere en forgrening, eller SWITCH ..., SELECT ... el.lign. ved en bredere forgrening.

Løkker 
Hvis en gruppe sætninger skal udføres flere gange, indfører man en løkke i programmet. Også her skal der anvendes en betingelse, der skal evalueres hver gang en iteration i løkken indledes eller afsluttes. Et meget vigtigt element i programmeringen af en løkke er at sikre sig, at gruppen af sætninger i løkken ændrer på data på en sådan måde, at betingelsen på et tidspunkt får programmet til at hoppe væk fra løkken. Hvis ikke, går programmet ind i en uendelig løkke med fatale følger for dets gennemførelse.

Også løkker blev i de ældste programmeringssprog implementeret vha. GOTO-sætninger, og ligesom i en forgrening kunne det være komplekst at placere GOTO-sætninger rigtigt i programmet. I dag er løkker implementeret med nøgleord som f.eks. WHILE ... DO ... , REPEAT ... UNTIL ...  eller <tt>FOR ... NEXT ... </tt>.

Funktionskald 
Hvis man har brug for at udføre den samme type beregninger flere steder i et program, eller hvis en gruppe sætninger hører så meget sammen i forhold til sætningerne omkring dem, at man har et ønske om at "pakke dem ind", kan man med fordel oprette en funktion (kaldes også procedure, (sub)rutine, metode). Funktionen kan modtage én til flere parametre som sine "input-data", og den kan tilsvarende returnere et resultat som "output-data". De steder i programmet, hvor beregningerne skal udføres, gennemfører man så et kald af funktionen med nogle aktuelle værdier som parametre, og en eventuelt returværdi fra funktionskaldet kan man så opfange og arbejde videre med. En fordel ved funktioner er, at variable, som kun oprettes af hensyn til beregningerne internt i funktion, kan erklæres lokalt i funktionen, så andre dele af programmet ikke kan manipulere, endsige se dem.

Funktionskald var svære at gennemføre, da man kun havde GOTO-sætninger til rådighed. Det kunne lade sig gøre med lidt datadisciplin, for alle variable, også de lokale i funktionen, kunne i princippet anvendes overalt (globalt). I det mindste skulle både parametrene og returværdierne (grænsefladen mellem det kaldende sted i programmet og funktionen), anvendes både inden for og uden for funktionens gruppe af sætninger. Endelig skulle der være en variabel, der viste, hvilken etiket programmet skulle returnere til, når funktionen var færdig. Endnu mere komplekst blev det, hvis man skulle lave en såkaldt rekursion, hvor en funktion internt i sine beregninger kalder sig selv. Da hver funktionskald skulle kunne finde tilbage til det kaldende sted i programmet, var man nødt til programmeringsmæssigt at lave en begrænsning for, hvor mange rekursive kald der kunne være inden i hinanden.

Struktureret programmering 
Den omfattende brug af GOTO-sætninger i implementeringen af forgreninger, løkker og funktionskald kunne meget nemt føre til uoverskuelig spaghettikode. Især var det begrænsningerne ved funktionskald, der fremkaldte et behov for at kunne "indhegne" en gruppe sammenhørende sætninger i en blok, f.eks. med en parentetisk BEGIN ... END-struktur eller andre typer parenteser. Sådanne blokke anvendes både omkring de sætninger, der skal udføres i en forgrening og en iteration i en løkke, men de er især anvendelige til at erklære funktioner med parametre, returværdier og lokale variable. Sammen med de nye sprog med sådanne faciliteter opstod programmeringsparadigmet struktureret programmering.

I de fleste sprog designet til struktureret programmering har man også muligheden for at erklære en funktion inde i "kroppen" af en anden funktion. På den måde kan en funktion ikke kun indeholde lokale variable, men også lokale funktioner. Der gælder nemlig som regel, at lokale variable eller funktioner i en funktion ikke kan "ses" udefra; man taler om, at deres scope kun er funktionen selv eller funktioner erklæret inde i funktionen. I sprog, hvor dette ikke gælder automatisk, er det op til programmøren selv at udvise datadisciplin ved anvendelsen af variable. Udover at undgå anvendelsen af GOTO-sætninger er konsekvens omkring variables og funktioners scope nok det vigtigste middel til at undgå spaghettikode i struktureret programmering.

Kompleksitet 
Struktureret programmering åbner også større muligheder for at arbejde med abstrakte datatyper, dvs. komplekse datastrukturer, der vha. reference-typer (pegere/pointere), post-typer og specielle funktioner er optimeret til at kunne udføre f.eks. indsættelse af elementer eller søgning efter elementer. Med de større muligheder blev der også større behov for at vurdere effektiviteten af et program, særligt med henblik på en algoritmes kompleksitet. Hermed menes en vurdering af, i hvilket omfang beregningstiden i algoritmen vokser, når datamængden stiger. Hermed fik programmøren redskaber til at finde frem til den mest effektive algoritme for sit program.

Objektorienteret programmering 
I de første computere interagerede brugeren ved hjælp af en tekstbaseret kommandolinjegrænseflade til computerens styresystem. På en kommandolinie kalder man f.eks. programmet ved at skrive dets navn efterfulgt af eventuelle parametre som input. Når programmet har udført sin opgave, svarer den tilbage med eventuelt tekst-output og giver brugeren mulighed for at indtaste en ny kommando. Der er tale om en såkaldt lineær kommunikation mellem bruger og computer, hvor én af dem hele tiden venter på den anden.

Med fremkomsten af computere med en grafisk brugergrænseflade (GUI, fra eng. Graphical user interface) blev både input og output væsentligt mere komplekst. Brugeren kan ikke blot levere tekstinput flere forskellige steder på skærmen, men der kommunikeres også ved hjælp af forskellige enheder som tastatur, mus og scanner, og der er ingen regler for, hvilken rækkefølge kommunikationen foregår. Computeren kører med flere tråde.

Dette satte skub i udviklingen af objektorienteret programmering. I dette programmeringsparadigme arbejder man med begrebet et objekt, som er defineret som en helhed med identitet, tilstand og adfærd. Sådanne objekter kan eksempelvis være de enkelte komponenter, som et skærmbillede er opdelt i, eller de forskellige stykker hardware, som brugeren anvender. Men det kan også være genstande eller begreber fra it-systemets problemområde, den del af systemets omverden, som systemet skal afspejle. Det er opgaven for objektorienteret analyse og design at finde frem til, hvilke objekter der skal medtages.

Desuden kan et objekt være en avanceret datastruktur til at manipulere en dynamisk samling af variable af samme datatype, optimeret til søgning eller sortering eller hurtig indsættelse og fjernelse af elementer.

Klasser 
I den objektorienterede programmering opdeles programkoden i klasser, der har hver sit velafgrænsede ansvarsområde i programmet. Et objekt er en forekomst (instans) af klassen. Et objekt kan altså opfattes som en variabel i programmet, og klassen er variabeltypen. Objektets tilstand beskrives vha. medlemsvariable, der som regel kun har scope i klassen. Objektets adfærd beskrives vha. metoder, funktioner med parametre, der kan kaldes udefra og som manipulerer med medlemsvariablene. Endelig sikres objektets identitet, ved at andre dele af programmet kun har dette objekt som variabel i form af en reference til objektet. To variable forskellige steder i programmet kan altså godt være ét og samme objekt, der interageres med.

Objektorienteret programmering har tre grundprincipper:
 Indkapsling
 Objektets tilstand er usynlig udefra, men dens adfærd bestemmes af en grænseflade af metoder, som omverdenen anvender (ved metodekald).
 Nedarvning
 Klassens definitioner af variable og metoder kan udvides til at definere en ny klasse. Man siger at en subklasse arver fra en superklasse. Dermed er et objekt fra subklassen også en forekomst af superklassen.
 Polymorfi
 To subklasser med den samme superklasse, kan arve samme metode fra superklassen, men udviser forskellig adfærd, når metoden kaldes. De har hver sin implementering af metoden, og eventuelt overskriver de superklassens implementering af metoden.

Med disse principper har man gode muligheder for f.eks. at få en GUI til at udvise den ønskede adfærd. Hvis man vil have en bestemt komponent til at opføre sig anderledes, definerer man en ny klasse, der arver fra denne komponent, og giver den en ny implementering af metoderne.

De fleste muligheder for kodning i struktureret programmering er også overført til objektorienteret programmering. Dog er muligheden med at definere en funktion lokalt i en anden funktion ikke overført til en klasses metoder. Derimod kan man i en klasse definere en anden såkaldt indre klasse, som på mange måder kan anvendes, hvor man i struktureret programmering ville anvende lokale funktioner.

Ansvarsfordeling 
I et program med mange klasser kan det ofte være svært at afgøre, præcist hvilke ansvarsområder, man skal fordele til hvilke klasser. En hovedregel i denne problemstilling – som egentlig er en design-problematik – er, at man skal lade den klasse, som besidder den nødvendige information, løse opgaven. Det betyder, at hvis der f.eks. opstår en runtime-fejl i en klasse, og klassen har tilstrækkelig information til at håndtere fejlen, skal den selv håndtere den i stedet for at sende problemet videre til en anden klasse.

En anden hovedregel er, at man så vidt muligt skal lade afhængighed mellem to klasser kun gå den ene vej, hvor "afhængighed" betyder, at koden i den ene klasse er afhængig af kendskab til den anden klasses grænseflade. Også i objektorienteret programmering kan man nemlig lave spaghetti-kode. Det kan f.eks. være fristende at definere et GUI-vindue, der udover at levere en brugergrænseflade også tager sig at at evaluere data, som brugeren taster ind, og gemme disse i en database. Dette kan måske være overskueligt, hvis der kun er få typer data i et system, men hvis der f.eks. er mange bindinger mellem data i en database, der kræver flere forskellige GUI-vinduer til brugerens indtastninger, kan der blive så mange afhængigheder mellem de enkelte klasser, at det besværliggør vedligeholdelse af koden. Der er løsningen at få lavet en hensigtsmæssig fordeling af ansvarsområderne ud på enkelte klasser.

En sådan opdeling er der f.eks. i den såkaldte 3-lags-model (eng. 3-tier-model), også kaldet MVC (fra eng. Model-View-Control). I en sådan vil man fordele ansvarsområderne således:
 Model: Her definerer man klasser, der afspejler klasserne i genstandsområdet. Klasserne skal primært opbevare data og evaluere data, den modtager udefra. I størst muligt omfang skal klasserne opdeles hierarkisk, så afhængigheden kun går "nedad".
 View: Denne står for brugergrænsefladen, som skal levere data til de enkelte objekter i modellen. Klasser her skal have kendskab til én eller flere klasser i modellen. Den skal kun evaluere indtastede data i det omfang, de helt åbenlyst ikke kan være brugbare.
 Control: Denne skal styre forretningsgangen i programmet og have kendskab til både modellen og brugergrænsefladen. Desuden skal den hente data om modellen fra en database og levere dem til brugergrænsefladen, ligesom den skal gemme data om modellen i databasen.

Fodnoter

Se også 
 Programbiblioteker
 Datalogi

 
Datalogi
Programmeringssprog