RSS-Feed

Eleganz vs. Verständlichkeit

von Kai um 18:05 am Montag, 28. Juni 2010 in Programmiersprachen, Studium | 2 Kommentare

Fremden Code zu lesen ist grausam. Man muss sich nicht nur in eine völlig andere Denkweise hinein versetzen, sondern versuchen einzelne Stückchen zu einem großen Ganzen zusammenzusetzen.
Eigenen Code zu lesen ist aber mindestens genauso grausam, wenn der vorliegende Quelltext sagen wir älter als zwei Monate ist. Man vergisst schnell, was man sich beim entwickeln gedacht hat und warum man das entstehende Programm in eine bestimmte Richtung und nicht anders programmiert hat.

Wenn ich nun also zum Beispiel ein geschriebenes Programm aus dem ersten Semester um eine bestimmte Funktionalität erweitern müsste, dann wäre ich wahrscheinlich schneller, wenn ich das komplette Programm “from-scratch” noch einmal komplett neu schreiben würde.
Diese Erkenntnis ist natürlich schon lange bekannt und es gibt daher auch viele Konzepte, wie man diesem Problem entgegnen kann. Auf diese möchte ich hier aber gar nicht weitergehen, sondern versuchsweise aufzeigen, wie man im Kleinen schon darauf achten kann, lesbaren Code zu produzieren.
Als Beispielsprache verwende ich Python, weil mir erstens die Sprache sehr gut liegt und weil sie den Benutzer schon konzeptuell zwingt “saubereren” Code zu schreiben.

Kommentare sind dazu da, den eigenen Code zu erläutern bzw. zu erklären und verwendete Parameter auf ihre Bedeutung hin zu verdeutlichen. Im Prinzip wohl eine gute Sache. Ich finde den Einsatz von Kommentaren dennoch nicht gut, weil sie meiner Meinung nach das Problem nicht an der Wurzel packen: Wenn ich Code erst erklären muss, bevor sie einem dritten verständlich erscheinen, ist die Wahrscheinlichkeit hoch, dass entweder das zu lösende (Teil-)problem noch nicht ganz geknackt ist, oder meine Bezeichnungen für Funktionen und Variablen Mumpitz sind. Selbst Bezeichnungen für Hilfsvariablen sollten eindeutig bestimmen, wofür diese gebraucht werden.

Als Beispiel soll folgendes Szenario dienen:
Hier soll die Eingabe des Benutzer verarbeitet und an eine Funktion weitergegeben werden. Im ersten Beispiel ohne genauere Variablenbezeichnung und mit Kommentar, im zweiten Beispiel mit treffenden Bezeichnungen.
Obwohl die Lebensdauer der Variablen mindestgebot, auktionsdauer, dateipfad gerade mal zwei Zeilen beträgt, tragen diese doch erheblich zur Lesbarkeit bei:

# Eingabe: versteigere 50,60,/tmp/bild.jpg
eingabe = raw_input()
if eingabe.split()[0] == 'versteigere':
    data = eingabe.split()[1].split(',')
    if len(data) != 3:
        print "error: wrong input"
    else:
        # Startet neue Auktion mit Mindestgebot,
        # Dauer der Auktion und Pfad zum Bild
        auktion.versteigere(data[0],data[1],data[2])

Und jetzt nochmal mit eindeutigen Bezeichnungen

eingabe = raw_input()
userBefehl = eingabe.split()[0]
if userBefehl == 'versteigere':
    befehlsParameter = eingabe.split()[1].split(',')
    if len(befehlsParameter) != 3:
        print "error: wrong input"
    else:
        mindestgebot, auktionsdauer, dateipfad = befehlsParameter[:]
        auktion.versteigere(mindestgebot, auktionsdauer, dateipfad)

Wie man sieht, hat man also nur durch eine bessere Bezeichnung der übergebenen Variablen ein Kommentar gespart und (hoffentlich) erheblich zur Lesbarkeit beigetragen.

Meines Erachtens darf hier auch ruhig redundante Information im Code vorkommen, wenn sie der Lesbarkeit dient. Im obigen Code ist das zum Beispiel das unnötige Slicing (befehlsParameter[:]), weil Python schlau genug ist, auch ohne das Slicing zu erkennen, dass die Liste mit drei Parameter auf die drei vorstehenden Variablen übergeben werden soll. Dennoch wird man durch das ‘[:]‘ beim Lesen noch einmal explizit darauf hingewiesen.

Onlinetest in C

von Kai um 19:19 am Montag, 23. November 2009 in Programmiersprachen, Studium | 0 Kommentare

Heute fand nach sechswöchiger Einführung in C in dem Modul “Programmieren 3″ der Onlinetest in C statt. Nächste Woche gehts dann mit einer Einführung in Python weiter.

Da ich mir mittlerweile meiner akuten Prüfungsangst und der damit einhergehenden Blackouteritis bewusst bin, gehe ich davon aus, dass ich nur knapp bestanden hab, obwohl (zumindest die ersten beiden Aufgaben) nicht so schwer waren.

Nachfolgend nun die ersten beiden Aufgaben des Onlinetests. Die dritte Aufgabe reiche ich wie schonmal nach, sobald mein Kopf nicht mehr ganz so matschig ist.

Aufgabe 1


/****************************************************
* Aufgabe: Es werden Noten (Note '1' bis Note '6') *
* auf der Kommandozeile eingegeben. Als Ausgabe *
* erscheint dann die Summe der einzeln eingegeben- *
* en Noten *
****************************************************/

#include

int main(void) {

int n1=0,n2=0,n3=0,n4=0,n5=0,n6=0;
int note;
while(scanf("%d", &note) != EOF) {
switch (note) {
case 1:
n1++;
break;
case 2:
n2++;
break;
case 3:
n3++;
break;
case 4:
n4++;
break;
case 5:
n5++;
break;
case 6:
n6++;
break;
}
}

printf("Note 1: %d mal\n", n1);
printf("Note 2: %d mal\n", n2);
printf("Note 3: %d mal\n", n3);
printf("Note 4: %d mal\n", n4);
printf("Note 5: %d mal\n", n5);
printf("Note 6: %d mal\n", n6);
}

Aufgabe 2


/****************************************************
* Aufgabe: Versehentlich wurden einige Lottozahlen *
* doppelt gezogen. Es soll eine beliebige Reihe *
* von Zahlen auf der Kommandozeile eingegeben und *
* die doppelt vorkommenden Zahlen geklammert *
* werden *
****************************************************/

#include
#include

typedef struct zahlen {
int zahl;
struct zahlen *next;
} Zahl;

Zahl *neueZahl(void) {
Zahl *z;

z = malloc(sizeof(Zahl));
assert(z);
z->zahl = 0;
z->next = NULL;

return z;
}

Zahl *start;
Zahl *tmp;

int main(void) {

int eingabe;
int erstesMal = 0;
while(scanf("%d", &eingabe) != EOF) {
if(erstesMal == 0) {
Zahl *neu = neueZahl();
neu->zahl = eingabe;
neu->next = NULL;
start = neu;
tmp = neu;
erstesMal = 1;
} else {
Zahl *neu = neueZahl();
neu->zahl = eingabe;
tmp->next = neu;
tmp = neu;
}

}

printf("%d ",start->zahl);
tmp = start->next;
while(tmp != NULL) {
if(start->zahl == tmp->zahl) {
printf("[%d] ",tmp->zahl);
} else {
printf("%d ",tmp->zahl);
}
start = tmp;
tmp = start->next;
}
}

Ultimative Emacs Cheat Sheet Übersicht

von Kai um 14:17 am Samstag, 31. Oktober 2009 in Produktivität, Studium | 1 Kommentar

emacsCheatSheetNotgedrungen aus dem Umstand, dass ich mir für das Studium momentan den Emacs Editor anschauen muss, habe ich mir die Mühe gemacht, eine kleine Überlebenshilfe zu basteln. (Auch wenn ich Vim selbstverständlich viel besser finde ;-) )
Die Infos auf dem DIN A5 großen Blatt habe ich mir quer durchs Internet zusammengesucht.

Der Verweis über das Vorschaubild führt zum Original und damit zur vollen Auflösung von 300 DPI. Viel Spaß damit.

Letzte Klausur dieses Semester: Hands-On Programmieren 1

von Kai um 18:38 am Freitag, 13. März 2009 in Gadgets, Klausuren, Studium | 5 Kommentare

Update: Ungetestete Lösung Aufgabe 8 noch angehängt

Nachdem ich gestern die letzte Klausur in diesem Semester in Programmieren 1 geschrieben habe und deren Ausgang noch unklar ist, habe ich mich mal hingesetzt und versucht die ganzen Aufgaben zu rekapitulieren (danke auch an Fabian und Seba) und korrekt zu lösen:

1.) Man sollte die EBNF für beliebig lange Summen-Terme hinschreiben und danach das entsprechende Syntaxdiagramm dazu bilden (z.B. a+b, a+b+c+b+a+c, a ). Als EBNF ist wohl folgendes korrekt:
Summe = (a|b|c) {"+"(a|b|c)}

und das entsprechende Syntaxdiagramm sieht dann wohl so aus:

Syntaxdiagramm

2.) Als nächtes musste man ein paar Code-Fragmente auf Korrektheit überprüfen. Da ich die einzelnen Abschnitte nicht mehr rekonstruieren kann, lasse ich diese hier außen vor.

3.) Die Aufgabe war anhand eines übergebenen Monats (1 – 12) die korrekte Jahreszeit wiederzugeben. Einmal mittels Switch-Anweisung und einmal mit einem Array gelöst: Ich hab folgendes hingeschrieben:


public static String Jahreszeit (int monat)
{
// Lösung mit switch
switch(monat) {
case 1: case 2: case 12:
return "Winter";
case 3: case 4: case 5:
return "Frühling";
case 6: case 7: case 8:
return "Sommer";
case 9: case 10: case 11:
return "Herbst";
default:
return "Ungültige Monatsangabe";
}

// Lösung mit String-Array
String[] jahreszeiten = {"","Winter","Winter","Frühling","Frühling","Frühling","Sommer","Sommer","Sommer","Herbst","Herbst","Herbst","Winter"}

return jahreszeiten[monat];

4.) Bei der nächsten Aufgabe sollten für eine Zahl n alle ungeraden Teiler ausgegeben werden. Einmal mit einer while-Schleife, mit einer do-while-Schleife und mit einer for-Schleife. Meine Lösung:


public static void ungeradeTeiler(int n) {

// while-Schleife
int i=1;
while(i < n) {
if(n%i==0 && i%2 != 0) {
System.out.println(i);
}
i++;
}

// do-while-Schleife
int j=1;
do {
if(n%j==0 && j%2 != 0) {
System.out.println(j);
}
j++;
} while(j < n);

// for-Schleife
for (int k=1; k < n;k++)
if(n%k==0 && k%2 != 0) {
System.out.println(k);
}
}

5.) Die nächste Aufgabe bestand darin, eine Reihe von Zahlen nach Größe sortiert wieder auszugeben. Hier war es von Vorteil, wenn man sich eine der üblichen Sortierverfahren vorher eingeprägt hatte. Obwohl der QuickSort-Algorithmus mit einer der effizientesten Algorithmen ist, hab ich mich dennoch für den BubbleSort entschieden, weil ich mir den leichter merken kann:

public static void main(String[] args) {

// Einlesen der Zahlen von der Kommandozeile
int[] array = new int[args.length];
int i=0;
while(i array[i] = Integer.parseInt(args[i]);
i++;
}

// sortieren mit BubbleSort-Verfahren
i= 1;
while(i< array.length) {
int j = array.length -1;
while (j >= i) {
if (array[j] < array[j-1] ) {
// vertauschen der beiden Stellen
int tmp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
}
j--;
}
i++;
}

// sortierte Liste ausgeben
i=0;
while (i < array.length ) {
System.out.println(array[i]);
i++;
}
}

6.) Die sechste Aufgabe behandelte die binäre Suche. Das ist ein Algorithmus, der auf einem Array recht schnell ein gesuchtes Element finden kann. Der Algorithmus funktioniert folgendermaßen:
Als erstes wird das mittlere Element des Array auf geprüft. Wenn es gleich dem gesuchten Element ist, ist die Suche beendet, ist es kleiner als das gesuchte Element, muss man in der hinteren Hälfte des Arrays weitersuchen, ist es größer, wird in der vorderen Hälfte des Arrays weitergesucht.
Im nächsten Schritt wird dann wieder wie vorher weitergemacht: Also mittleres Element überprüfen, falls das gesuchte Element gleich dem mittleren Element ist, abbrechen, ansonsten jeweilige Hälfte überprüfen usw.
Ein Programmbeispiel konnte ich in der Klausur leider nicht liefern, werde mir aber dafür das Beispiel in der Wikipedia anschauen. Dort ist es neben einem Pseudocode ebenfalls in Java gelöst worden (neben Python und C).

7.) In der siebten Aufgabe sollte ein Skalarprodukt zweier Vektoren mit gleicher Länge ausgerechnet werden. Das Problem sollte einmal iterativ und einmal rekursiv gelöst werden:


// iterative Lösung
public static int skalar(int[] v, int[] w) {
int result = 0;
for(int i=0;i {
result += v[i] * w[i];
}
return result;
}

// rekursive Lösung
public static int skalar(int[] v, int[] w) {
int l = v.length;
if (l == 0)
return 0;
else {
int[] vv = new int[l - 1];
int[] ww = new int[l - 1];
for (int i = 0; i <= l - 2; i++) {
vv[i] = v[i];
ww[i] = w[i];
}
return v[l - 1] * w[l - 1] + skalar(vv, ww);
}
}

8.) Die letzte Aufgabe war es, eine Verbindungslinie zwischen zwei Punkten zu berechnen. Vorgegeben war eine fiktive Funktion setPixel(int[][] a, int[][] b). Zuerst sollte man den Mittelpunkt M zwischen dem Anfangs- und Endpunkt berechnen. Lagen dann alle berechneten Punkte in einem jeweils benachbartem Feld, war man fertig und konnte die errechneten Punkte an die Funktion setPixel übergeben, ansonsten musste man wieder jeweils den Mittelpunkt zwischen dem Anfangs- bzw. Endpunkt berechnen usw. Zum besseren Verständnis eine Grafik:
Verbindungen
Die Lösung erspare ich mir heute und wird morgen nachgereicht. Update: Ungetestete Lösung noch beigefügt:

public static int[][] getMiddlePixel(int A[][], int B[][]) {
int[][] M = new int[(A.length/2)+(B.length/2)][(A[0].length/2)+(B[0].length/2)];

if (A.length == B.length && A[0].length == B[0].length) {
// Die Punkte A und B liegen übereinander
return M;
} else if (A.length == B.length && A[0].length != B[0].length) {
// Der x-Wert der Punkte A und B sind korrekt
return getMiddlePixel(A,M);
} else {
// Der y-Wert der Punkte A und B sind korrekt
return getMiddlePixel(M,B);
}
}