Improved progrsmming style chapter.

This commit is contained in:
Jan Benda 2015-11-14 21:15:25 +01:00
parent 108210210a
commit af1faf941c
3 changed files with 152 additions and 103 deletions

View File

@ -171,6 +171,7 @@
\newcommand{\enterm}[1]{``#1''}
\newcommand{\determ}[1]{\textit{#1}}
\newcommand{\codeterm}[1]{\textit{#1}}
\newcommand{\keycode}[1]{\texttt{#1}}
\newcommand{\file}[1]{\texttt{#1}}
%%%%% code/matlab commands: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -12,7 +12,7 @@ function sines = calculate_sines(x, amplitudes, frequencies)
sines = zeros(length(x), length(amplitudes), length(frequencies));
for i = 1:length(amplitudes)
sines(:, i, :) = sines_with_frequencies(x, amplitudes(i), frequencies);
sines(:,i,:) = sines_with_frequencies(x, amplitudes(i), frequencies);
end
end
@ -27,4 +27,4 @@ end
function sine = sinewave(x, amplitude, frequency)
sine = sin(2 .* pi .* x *frequency) .* amplitude;
end
end

View File

@ -8,8 +8,8 @@ machen.
Programme sollten so geschrieben und strukturiert sein, dass es sowohl
einem Au{\ss}enstehenden als auch einem selbst, nach ein paar Monaten,
leicht f\"allt den Programmablauf nachzuvollziehen und zu
verstehen. Saubere Programmierung zahlt sich auch f\"ur den Sch\"opfer
eines Programmes aus.
verstehen. Saubere Programmierung zahlt sich aber in erster Linie
f\"ur den Verfasser eines Programmes aus.
Guter Programmierstil greift auf unterschiedlichen Ebenen an:
\begin{enumerate}
@ -22,49 +22,48 @@ Guter Programmierstil greift auf unterschiedlichen Ebenen an:
\item Auslagerung von Funktionalit\"at in eigene Funktionen.
\end{enumerate}
\section{Struktur von Programmen, Organisation von m-Files im Dateisystem}
\section{Organisation von m-Files im Dateisystem}
In der Einf\"uhrung zu Funktionen und Skripten wurde schon einmal ein
typisches Programmlayout vorgestellt (Abbildung
\ref{programlayoutfig}). Hier wurde vorgeschlagen ein Skript als
Kontrollskript zu verwenden. Dieses kontrolliert den weiteren
Programmablauf, ruft Funktionen auf, \"ubergibt Argumente und nimmt
R\"uckgabewerte entgegen. Eine solche Struktur macht es einfach den
Ablauf zu verstehen. Es bleibt aber die Frage, wie man das
Kontrollskript unter den anderen \codeterm{m-files} als solches
erkennt. Um dieses zu erleichtern gilt es zwei Dinge zu beachten:
1. Wie werden Programme im Dateisystem organisiert? 2. Wie werden
Programme benannt?
typisches Programmlayout vorgestellt (\figref{programlayoutfig}). Hier
wurde vorgeschlagen ein Skript als Kontrollskript zu verwenden. Dieses
kontrolliert den weiteren Programmablauf, ruft Funktionen auf,
\"ubergibt Argumente und nimmt R\"uckgabewerte entgegen. Eine solche
Struktur macht es einfach den Ablauf zu verstehen. Es bleibt aber die
Frage, wie man das Kontrollskript unter den anderen \codeterm{m-files}
als solches erkennt. Um dieses zu erleichtern gilt es zwei Dinge zu
beachten: (i) Wie werden Programme im Dateisystem organisiert? (ii) Wie
werden Programme benannt?
Es hilft ungemein, wenn zusammengeh\"orige Skripte und Funktionen im
gleichen Ordner auf der Festplatte zu finden sind. Es bietet sich also
an f\"ur jede Analyse einen eigenen Ordner anzulegen und in diesem die
an, f\"ur jede Analyse einen eigenen Ordner anzulegen und in diesem die
zugeh\"origen \codeterm{m-files} abzulegen. Auf eine tiefere
Schachtelung in weitere Unterordner kann in der Regel verzichtet
werden. \matlab{} erzeugt einen ``MATLAB'' Ordner im eingenen
``Documents'' (Linux) oder ``Eigene Dokumente'' (Windows) Ordner. Es
bietet sich an diesen als Wurzelverzeichnis f\"ur eigene Arbeiten zu
werden. \matlab{} erzeugt einen ``MATLAB'' Ordner im eigenen
\file{Documents} (Linux) oder \file{Eigene Dokumente} (Windows) Ordner. Es
bietet sich an, diesen Ordner als Wurzelverzeichnis f\"ur eigene Arbeiten zu
verwenden. Nat\"urlich kann auch jeder andere Ort gew\"ahlen
werden. In dem Beispiel in Abbildung \ref{fileorganizationfig} wird
werden. In dem Beispiel in \figref{fileorganizationfig} wird
innerhalb dieses Ordners f\"ur jedes Projekt ein eigener Unterordner
erstellt in welchem widerum f\"ur jedes Problem, jede Analyse ein
erstellt, in welchem widerum f\"ur jedes Problem, jede Analyse ein
weitere Unterodner erstellt wird. In diesen liegen sowohl die
ben\"otigten m-files also auch die Resultate der Analyse (Abbildungen,
Dateien). Zu bemerken sind noch zwei weitere Dinge. Im Projektordner
ben\"otigten \codeterm{m-files} also auch die Resultate der Analyse (Abbildungen,
Daten-Dateien). Zu bemerken sind noch zwei weitere Dinge. Im Projektordner
existiert ein Skript (analysis.m), das dazu gedacht ist, alle Analysen
aufzurufen. Des Weiteren gitb es parallel zu den Projektordnern einen
``functions'' Ordner in dem Funktionen liegen, die in mehr als einem
\file{functions}-Ordner in dem Funktionen liegen, die in mehr als einem
Projekt oder einer Analyse gebraucht werden.
Wenn man sich dieses Layout anschaut f\"allt auf, dass es sehr
Beim Betrachten dieses Layouts f\"allt auf, dass es sehr
wahrscheinlich ist, dass bestimmte Namen f\"ur Funktionen und Skripte
mehrfach verwendet werden. Es ist nicht verwunderlich, wenn eine
``load\_data.m'' Funktion in jeder Analyse vorkommt. In der Regel wird
dies nicht zu Konflikten f\"uhren, da \matlab{} zuforderst im
aktuellen Ordner nach passenden Dateien sucht (mehr Information in Box
\ref{matlabpathbox}).
\file{load\_data.m} Funktion in jeder Analyse vorkommt. In der Regel
wird dies nicht zu Konflikten f\"uhren, da \matlab{} zuerst im
aktuellen Ordner nach passenden Dateien sucht (mehr Information zum
\matlab-Suchpfad in Box~\ref{matlabpathbox}).
\begin{figure}
\begin{figure}[tp]
\includegraphics[width=0.75\textwidth]{program_organization}
\titlecaption{\label{fileorganizationfig} M\"ogliche Oganisation von
Programmcode im Dateisystem.}{ F\"ur jedes Projekt werden
@ -74,7 +73,7 @@ aktuellen Ordner nach passenden Dateien sucht (mehr Information in Box
\end{figure}
\begin{ibox}[t]{\label{matlabpathbox}Der \matlab{} Suchpfad}
\begin{ibox}[tp]{\label{matlabpathbox}Der \matlab{} Suchpfad}
Der Suchpfad definiert, wo \matlab{} nach Skripten und Funktionen
sucht. Wird eine Funktion aufgerufen wird zun\"achst im aktuellen
Arbeitsverzeichnis einem Treffer gesucht. Schl\"agt diese Suche
@ -109,14 +108,24 @@ aktuellen Ordner nach passenden Dateien sucht (mehr Information in Box
\matlab{} sucht Funktionen und Skripte ausschlie{\ss}lich anhand der
Namen. Dabei spielt die Gro{\ss}- und Kleinschreibung eine Rolle. Das
hei{\ss}t, dass die Namen ``test\_funktion.m'' und ``Test\_funktion.m''
zwei unterschiedliche Funktionen benennen k\"onnen. Diese Art
Variation des Namens ist nat\"urlich nicht sinnvoll. Sie tr\"agt keine
Information \"uber den Unterschied der beiden Funktionen. Auch sagt
der Name nahezu nichts \"uber den Zweck der Funktion aus. Die
Namensgebung f\"allt mitunter nicht leicht, es lohnt sich aber
ausdruckstarke Namen zu finden. Ausdrucksstark bedeutet, dass sich aus
dem Namen ein R\"uckschluss auf den Zweck ziehen lassen sollte.
hei{\ss}t, dass die beiden Dateien \file{test\_funktion.m} und
\file{Test\_funktion.m} zwei unterschiedliche Funktionen benennen
k\"onnen. Diese Art der Variation des Namens ist nat\"urlich nicht
sinnvoll. Sie tr\"agt keine Information \"uber den Unterschied der
beiden Funktionen. Auch sagt der Name nahezu nichts \"uber den Zweck
der Funktion aus.
Die Namensgebung f\"allt mitunter nicht leicht --- manchmal ist es
sogar der schwierigste Aspekt des Programmierens! Ausdrucksstarke
Namen zu finden lohnt sich aber. Ausdrucksstark bedeutet, dass sich
aus dem Namen ein R\"uckschluss auf den Zweck ziehen lassen sollte.
\begin{important}
Die Namen von Funktionen und Skripte sollten m\"oglichst viel \"uber
die Funktionsweise oder den Zweck aussagen (\file{firingrates.m}
statt \file{uebung.m}). Gute Namen f\"ur Funktionen und Skripte sind
die beste Dokumentation.
\end{important}
\matlab{} macht keine weiteren Vorgaben, was die Namen
angeht. Allerdings folgt die Benennung der vordefinierten Funktionen
@ -131,12 +140,12 @@ gewissen Mustern:
Wertes in einen Text) benannt.
\end{itemize}
Andere \"ubliche Muster sind der \emph{camelCase} bei dem die
Andere \"ubliche Muster sind der \emph{camelCase}, bei dem die
Anf\"ange zusammengesetzter Worte jeweils gro{\ss} geschrieben werden
oder auch die Verwendung von Unterstrichen zur Trennung von
Namenskomponenten. Eine Funktion, die die Anzahl Aktionspotentiale
berechnet k\"onnte etwa \codeterm{spikeCount.m} oder auch
\codeterm{spike\_count.m} benannt werden. Leerzeichen, Sonderzeichen
Namenskomponenten. Eine Funktion, die die Anzahl von
Aktionspotentialen bestimmt k\"onnte etwa \file{spikeCount.m} oder
\file{spike\_count.m} benannt werden. Leerzeichen, Sonderzeichen
oder Anf\"ange mit Zahlen sind in Namen nicht erlaubt.
@ -146,15 +155,26 @@ F\"ur die Bennennung von Variablen und Konstanten gelten die gleichen
Regeln wie f\"ur die Namen von Funktionen und Skripten. Die Maxime von
gutem Programmierstil ist: \emph{``Programmcode muss lesbar
sein.''}. Dabei helfen gute Namen ungemein. Auch wenn es schwer
f\"allt passende Namen zu finden, die nicht zu lang werden sollte man
sich auch da M\"uhe geben.
f\"allt passende und nicht zu lange Namen zu finden, sollte einer gute
Namensgebung ernst genommen werden.
Bei den Funktionen und Skripten fragt man danach, welchen Zweck sie
erf\"ullen, bei Variablen fragt man nach dem Inhalt. Eine Varaible die
die mittlere Anzahl Aktionspotentiale speichert k\"onnte also
\codeterm{average\_spike\_count} hei{\ss}en. Wenn die Variable nicht nur
einen sondern mehrere Werte aufnimmt, dann ist der Plural angebracht
(\codeterm{average\_spike\_counts}).
W\"ahrend die Namen von Funktionen und Skripten ihren Zweck
beschreiben, sollten die Namen von Variablen ihren Inhalt
beschreiben. Eine Variable, die die mittlere Anzahl von
Aktionspotentialen speichert, k\"onnte also
\codeterm{average\_spike\_count} hei{\ss}en. Wenn die Variable nicht
nur einen sondern mehrere Werte aufnimmt, dann ist der Plural
angebracht (\codeterm{average\_spike\_counts}).
Die Laufvariablen von \code{for}-Schleifen werden oft nur \code{i},
\code{j} oder \code{k} benannt und sollten aber die einzige Ausnahme
bzgl. ausdrucksstarker Namensgebung bleiben.
\begin{important}
Die Namen von Variablen sollten m\"oglichst viel \"uber ihren Inhalt
aussagen (\code{spike\_count} statt \code{x}). Gute Namen
f\"ur Variablen sind die beste Dokumentation.
\end{important}
\section{Codestil}
@ -163,19 +183,21 @@ Die Lesbarkeit von Programmen wird sehr durch den Codestil
beeinflusst. Ein Programm, in dem z.B. Schleifenk\"orper nicht (oder
zuf\"allig) einger\"uckt sind ist deutlich schwerer zu lesen und zu
verstehen, als eines, in dem eine konsistente Einr\"uckung vorgenommen
wurde.
wurde. Mit der Tastenkombination \keycode{Ctrl-I} % XXX Oder wie war das? XXX
kann ein markierter
Bereich im \matlab{} Editor automatisch richtig einger\"uckt werden.
Gerne werden Leerzeilen eingef\"ugt um Abschnitte im Programm zu
trennen. Das ist v\"ollig ok, wenn es konsistent und sparsam benutzt
wird. Hier sollte eine Leerzeile ausreichen. Zu gro{\ss}e Abst\"ande
f\"uhren dazu, dass das Programm nicht mehr auf eine Seite passt und man
leicht den \"Uberblick verliert.
Sparsam und konsistent eingef\"ugte einzelne Leerzeilen sind
hervorragend geeignet, um logische Abschnitte eines Programm zu
trennen. Zu viele Leerzeilen haben den Nachteil, dass das Programm
nicht mehr auf eine Seite passt und dadurch leichter der \"Uberblick
verlorgen geht.
Die beiden folgenden Listings \ref{chaoticcode} und \ref{cleancode}
zeigen die Implementation des random-walk einmal eher chaotisch und
einmal aufger\"aumt.
einmal aufger\"aumt und \"ubersichtlich.
\begin{lstlisting}[label=chaoticcode, caption={Random-walk Implementation unaufgr\"aumt.}]
\begin{lstlisting}[label=chaoticcode, caption={Un\"ubersichtliche Implementation des Random-walk.}]
num_runs = 10;
max_steps = 1000;
@ -198,7 +220,7 @@ end
end
\end{lstlisting}
\begin{lstlisting}[label=cleancode, caption={Random-walk Implementation etwas ordentlicher.}]
\begin{lstlisting}[label=cleancode, caption={\"Ubersichtliche Implementation des Random-walk.}]
num_runs = 10;
max_steps = 1000;
positions = zeros(max_steps, num_runs);
@ -217,64 +239,89 @@ end
\section{Verwendung von Kommentaren}
Kommentare k\"onnen ebenfalls sehr zum Verst\"andnis beitragen. Bei
allen vordefinierten \matlab{} Funktionen findet sich am Anfang eine
Kommentarblock, der den Zweck der Funktion, die verschiedenen
M\"oglichkeiten des Funktionsaufrufs und die Argumente und
R\"uckgabewerte beschreibt. Auch in eingenen Funktionen, vor allem
wenn auch andere Personen sie benutzen sollen, sind diese Kommentare
hilfreich. H\"aufig werden kurze Kommentare eingesetzt um Abschnitte
im Programm zu trennen. Hierbei sollte man auch sparsam sein. Jede
Zeile zu erkl\"aren kann in der Entwicklungsphase eines Programms sehr
hilfreich sein, bl\"aht aber den Code auf und bei der Verwendung guter
Variablennamen sind viel Zeilen weitestgehend selbsterkl\"arend.
Kommentarzeilen werden in \matlab{} mit dem Prozentzeichen \cide{\%}
gekennzeichnet. Gezielt und sparsam eingesetzte Kommentare sind f\"ur
das Verst\"andnis eines Programms sehr n\"utzlich. Am wichtigsten
sind kurze Kommentare, die den Zweck und das Ziel eines Abschnitts im
Programm erl\"autern (z.B. \code{\% compute mean firing rate over all
trials}).
Zu viele Kommentare k\"onnen in der Entwicklungsphase eines Programms
sehr hilfreich sein, bl\"ahen aber den Code auf. Durch die Verwendung
guter Variablen- und Funktionsnamen sollten die meisten Zeilen sowieso
weitestgehend selbsterkl\"arend sein.
Die beste Dokumentation ist der Code selbst. Gut geschriebener Code
mit ausdrucksstarken Variablen- und Funktionsnamen ben\"otigt keine
Kommentare, um den Zweck einzelner Zeilen zu erkl\"aren. z.B. ist\\
\code{ x = x + 2; \% add two to x}\\
ein v\"ollig unn\"otiger Kommentar.
\begin{important}
\begin{itemize}
\item Kommentare sind gut und wichtig aber: Sie m\"ussen richtig
\item Kommentare sollen die Absicht eines Programmabschnitts beschreiben.
\item Kommentare sind gut und wichtig --- sie m\"ussen aber richtig
sein!
\item Ein Kommentar, der l\"ugt, ist schlimmer als gar kein Kommentar!
\item Kommentare m\"ussen gepflegt werden, sonst sind sie mehr als
wertlos!
\item Ein falscher Kommentar ist schlimmer als gar kein Kommentar!
\item Kommentare m\"ussen gepflegt werden, sonst sind sie wertlos!
\end{itemize}
\end{important}
\section{Dokumentation von Funktionen}
Bei allen vordefinierten \matlab{} Funktionen findet sich am Anfang
eine Kommentarblock, der den Zweck der Funktion, die verschiedenen
M\"oglichkeiten des Funktionsaufrufs und die Argumente und
R\"uckgabewerte beschreibt. Auch in eingenen Funktionen sind diese
Kommentare sehr hilfreich. Siehe Listing~\ref{localfunctions} f\"ur
ein Beispiel einer gut Dokumentierten Funktion.
\begin{important}
Funktionen m\"ussen unbedingt kommentiert werde!
\begin{itemize}
\item In wenigen Zeilen kurz den Zweck der Funktion beschreiben.
\item F\"ur jedes Funktionsargument die Bedeutung, der erwartete
Datentyp (Zahl, Vektor, Matrix, etc.), und eventuell die Einheit,
in der die Zahlen erwartet werden (z.B. Sekunden).
\item Ebenso m\"ussen die R\"uckgabewerte beschrieben werden.
\end{itemize}
\end{important}
\section{Auslagerung von Aufgaben in Funktionen}
Kommentare oder Leerzeilen werden benutzt um Abschnitte des Codes
abzutrennen und nazudeuten, dass ab da etwas inhaltlich anderes
gemacht wird. Wenn man im Zuge ist, eine solche inhaltliche Trennung
einzuf\"ugen muss man sich immer fragen, ob dieser Teil des Programms
nicht in eine eigene Funktion ausgelagert werden sollte. Fast immer
kann man das bejahen.
Kommentare oder Leerzeilen werden benutzt, um logische Abschnitte des
Codes abzutrennen und kurz zu erkl\"aren. Wenn eine
solche inhaltliche Trennung einzuf\"ugt wird, sollte man sich immer fragen,
ob dieser Teil des Programms nicht in eine eigene Funktion ausgelagert
werden sollte. Fast immer kann dies bejaht werden.
Abschnitte nicht auszulagern f\"uhrt zu sehr langen m-Files, die
leicht un\"ubersichtlich sind. Man nennt sie
\codeterm{Spaghetticode}. Es ist h\"ochste Zeit \"uber Auslagerung in
Funktionen nachzudenken.
Abschnitte nicht auszulagern f\"uhrt zu sehr langen
\codeterm{m-Files}, die leicht un\"ubersichtlich werden. Diese Art von
Code wird \codeterm{Spaghetticode} genannt. Es ist h\"ochste Zeit
\"uber Auslagerung in Funktionen nachzudenken.
\begin{important}
Wann sollte man Programmteile in eigene Funktionen auslagern?
Wann sollten Programmteile in eigene Funktionen ausgelagert werden?
\begin{itemize}
\item Wenn man innerhalb einer Funktion, eines Skripts mehr als zwei
Einr\"uckungsebenen hat.
\item Wenn man wiederholte Strukturen im Code hat.
\item Wenn man versucht ist, diese mit Copy and Paste zu erzeugen.
\item Wenn innerhalb einer Funktion oder eines Skripts mehr als zwei
Einr\"uckungsebenen gebraucht werden.
\item Wenn sich Strukturen im Code mehr als einmal wiederholten.
\item Wenn man versucht ist, wiederholte Strukturen mit Copy and Paste zu erzeugen.
\end{itemize}
\end{important}
\subsection{Lokale Funktionen und geschachtelte Funktionen}
Eine M\"oglichkeit Spaghetticode zu vermeiden ist das Auslagern von
Funktionalit\"at in eigene Funktionen. Dies f\"uhrt dazu, dass man
eine F\"ulle von Dateien erzeugt, die die \"Ubersichtlichkeit nicht
unbedingt erh\"ohen. Wenn die auszulagernde Funktionalit\"at an vielen
Stellen ben\"otigt werden k\"onnte ist es dennoch sinnvol dies zu
tun. Wenn nicht, dann bietet \matlab{} die M\"oglichkeit sogenannte
\codeterm{lokale Funktionen} oder auch \codeterm{geschachtelte
Funktionen} (\enterm{nested functions}) zu erstellen (Listing
\ref{localfunctions} zeigt ein Beispiel f\"ur eine lokale Funktion).
Das Auslagern von Funktionalit\"at in eigene Funktionen f\"uhrt
dazu, dass eine F\"ulle von Dateien erzeugt wird, die die
\"Ubersichtlichkeit nicht unbedingt erh\"oht. Wenn die auszulagernde
Funktionalit\"at an vielen Stellen ben\"otigt wird ist es
dennoch sinnvol dies zu tun. Wenn nicht, dann bietet \matlab{} die
M\"oglichkeit sogenannte \codeterm{lokale Funktionen} oder auch
\codeterm{geschachtelte Funktionen} (\enterm{nested functions}) zu
erstellen. Listing \ref{localfunctions} zeigt ein Beispiel f\"ur eine
lokale Funktion.
\lstinputlisting[label=localfunctions, caption={\codeterm{Lokale Funktionen} erh\"ohen die Lesbarkeit sind aber nur innerhalb der definierenden Datei verf\"ugbar.}]{calculate_sines.m}
@ -288,6 +335,7 @@ ist das anders. Diese werden innerhalb eines Funktionsk\"orpers
``Mutterfunktion'' zugreifen und diese auch ver\"andern. Folglich
sollten sie nur mit Bedacht eingesetzt werden.
\section{Besonderheiten bei Skripten}
Ein \"ahnliches Problem wurde schon bei der Einf\"uhrung der Skripte
@ -302,7 +350,7 @@ aussieht.
\begin{important}
Es empfiehlt sich zu Beginn eines Skriptes alle Variablen im
\codeterm{Workspace} zu l\"oschen (\code{clear}). Meist ist auch
ein \code{close all} angebracht.
ein \code{close all}, um alle Figures zu schlie{\ss}en, angebracht.
Am Ende eines Skriptes sollte der \codeterm{Workspace} mithilfe von
\code{clear} wieder von all den Variablen ges\"aubert werden, die
@ -319,9 +367,9 @@ machen Programmiersprachen gibt es Traditionen und \"Ubereink\"unfte,
diese sollten dann beachtet werden.
Wiederholte Programmabschnitte sollten in Funktionen ausgelagert
werden. Wenn diese nich von globalem Interesse sind, kann man auch mit
werden. Wenn diese nich von globalem Interesse sind, kann mit
\codeterm{lokalen} oder \codeterm{geschachtelten Funktionen} die
Lesbarkeit erh\"ohen.
\"Ubersichtlichkeit erh\"oht werden.
\noindent Es lohnt sich auf den eigenen Programmierstil zu achten!\footnote{Literatur zum Programmierstil: z.B. Robert C. Martin: \textit{Clean
Code: A Handbook of Agile Software Craftmanship}, Prentice Hall}