# Informatik BW Style Guide
## Hintergrund
Zwei der wichtigsten Eigenschaften von gutem Code sind Lesbarkeit und Verständlichkeit. Daher werden neben der Funktionalität Ihres eingereichten Codes auch die Einhaltung bestimmter Style-Regeln und die allgemeine Sauberkeit des Codes bewertet. Für jeden Verstoß kann eine bestimmte Anzahl von Punkten eines Assignments abgezogen werden, bis zu einer bestimmten Obergrenze. Das heißt, wenn zum Beispiel bei einem Assignment die Obergrenze für Codequalität bei 8 Punkten liegt, können Sie durch Style-Verstöße nicht mehr als 8 Punkte von ihrer erreichten Leistung verlieren.
Generell empfehlen wir Ihnen, sich an die Regeln und Richtlinien von *PEP8* zu halten, dem Style Guide, der von den Python-Entwicklern erstellt wurde ([pep8.org](https://pep8.org/)). Wir werden nicht jedes Detail in PEP8 durchsetzen aber legen besonderen Wert auf die weiter unten angeführten Punkte.
Um die Einhaltung der Richtlinien von PEP8 zu erleichtern, wurden sogenannte *Linter* (z.B. *pylint*) entwickelt. Diese überprüfen Ihren Code und warnen Sie vor allen PEP8-Verletzungen. Die meisten IDEs erlauben auch kontinuierliches Linting. Denken Sie daran, dass ein Linter nicht alle Regeln, die in diesem Kurs gelten, überprüfen kann!
## Python Style Rules
### Zeilenlänge
Die maximale Zeilenlänge beträgt *120 Zeichen* - **inklusive Einrückung und Kommentare**. Gelegentliche kleinere Verstöße gegen diese Regel sind in Ordnung, aber benutzen Sie Ihren gesunden Menschenverstand ;) Nutzen Sie den von Python implizierten Zeilenumbruch innerhalb von Klammern.
```python=3.9
# No:
something(foo: int, bar: str, very_long_argument: str, method=print, color="red", funny_number: int = 1337) -> List[int]:
# No:
x = "a very long long loooong long long loong over the maximum of 120 characters long string that does not reveal any particular new information"
# Yes:
something(foo: int, bar: str, very_long_argument: str, method=print,
color="red", funny_number: int = 1337) -> List[int]:
# Yes:
x = ("a very long long loooong long long loong over the maximum of 120 characters "
"long string that does not reveal any particular new information")
```
In den meisten IDEs kann eine vertikale Linie ("ruler") eingestellt werden, um dies zu vereinfachen.
### Einrückung
Verwenden Sie genau `4` Leerzeichen für jede Einrückungsebene. **Verwenden Sie keine Tabulatoren**. Die meisten IDEs und Texteditoren wandeln jedes Drücken von <kbd>Tab</kbd> in vier Leerzeichen um (falls Ihr Programm dies nicht tut, ändern Sie die Einstellungen entsprechend).
### Sprache
Alle Variablennamen und Kommentare müssen in Englisch sein.
## PEP8 Variablennamen
Befolgen Sie die [PEP8-Namenskonventionen](https://pep8.org/#prescriptive-naming-conventions).
* `snake_case`: Variablen, Argumente, Funktionen
* `CapWords`: Klassen, Exceptions
* `CONSTANT_CASE`: Konstanten (definiert auf Modulebene)
D.h. in diesem Kurs werden Sie in fast allen Fällen snake_case (Kleinschreibung mit durch Unterstriche getrennten Wörtern) verwenden.
### Semantisch falsche Variablennamen
Alle Variablennamen sollten semantisch korrekt sein. Die Semantik bezieht sich auf die Bedeutung einer Aussage. Für Variablen bedeutet dies, dass sie in dem Kontext, in dem sie verwendet werden, einen Sinn ergeben müssen.
```python=3.8
letters = [2,4,6,8] # This is semantically wrong
even_numbers = [2,4,6,8] # This is semantically correct
```
### Nicht aussagekräftige Variablennamen
Verwenden Sie aussagekräftige Namen für alle Variablen! Das bedeutet, dass der Zweck einer Variablen aus ihrem Namen deutlich werden sollte. Beispiele für nicht aussagekräftige Namen: `i,j,temp,tmp,list,dict,dic,sum,a,b,c,my_var,my_dict,my_list,val,value,key,string,str,check,flag,bool`.
Falls eine Variable keinen Zweck erfüllt (e.g. nicht verwendeter loop counter), verwenden Sie ein `_` (Einfacher Unterstrich).
### Übermäßige, unerwünschte Ausgaben
Sie sollten alle unnötigen `print()`-Anweisungen entfernen oder auskommentieren (fügen Sie ein `#` vor der Zeile ein, die Sie auskommentieren wollen, oder markieren Sie den zu kommentierenden Code und drücken Sie <kbd>Strg</kbd><kbd>#</kbd>). Natürlich können Sie die Funktion `print()` verwenden, um Ihren Code zu testen, aber kommentieren Sie ihn aus, bevor Sie Ihr Assignment abgeben.
### Nicht verwendete Imports
Alle Imports, die Sie nicht in Ihrem Code verwenden, müssen vor der Abgabe der Aufgabe entfernt werden. **Ihre IDE oder Ihr Linter wird Ihnen dabei helfen!**
### Nicht verwendete Variablen
Vergewissern Sie sich, dass Sie auch alle Variablen in Ihrem Code verwenden! Dies gilt auch für Funktionsparameter. **Auch hier wird Ihr Linter oder Ihre IDE eine große Hilfe sein!**
### Fehlender oder unvollständiger Comment-Header
Achten Sie darauf, dass der Comment-Header in jeder Quelldatei enthalten ist und ausgefüllt wird!
```
################################################################################
# Author: Firstname Lastname
# MatNr: 01234567
# File: assignmentX.py / assignmentX.ipynb
# Description: ... short description of the file ...
# Comments: ... comments for the tutors ...
# ... e.g. things that you know don't work ...
# ... can be multiline ...
################################################################################
```
### Dateinamen (Bis zu 100% der vergebenen Punkte für Style)
Reichen Sie Ihren Code genau so ein, wie auf der Assignmentseite oder in der Assignmentbeschreibung angegeben. Das Aktivieren von Dateierweiterungen in Ihrem Dateiexplorer erleichtert die Namensgebung. Wenn ein Python-Skript (.py) verlangt wird, reichen Sie kein Jupyter-Notebook (.ipynb) ein - oder umgekehrt). **Die Umbenennung einer .ipynb-Datei in ein .py-Skript funktioniert nicht!**
## Häufige Stylebrüche
Versuchen Sie, "pythonische" und einfache Konstrukte zu schreiben. Um Ihnen dabei zu helfen, haben wir ein paar häufige Stylebrüche gesammelt, die in Ihrem Code nicht vorkommen sollten.
### Ungeschlossene Dateien
Schließen Sie eine Datei, nachdem Sie sie geöffnet haben. Die `with`-Anweisung schließt eine Datei automatisch, nachdem der Code im eingerückten Block verarbeitet wurde.
```python
# NO! This keeps the file open
book = open(cookbook_filename, "w")
book.write("Something")
# Yes
with open(cookbook_filename, "w") as book:
book.write("Something")
```
### If-pass-else
```python=3.9
# No
if some_condition:
pass
else:
do_something()
# Yes
if not some_condition:
do_something()
```
### Globale Variablen
Vermeiden Sie globale Variablen. Da wir beim Testen nur die Funktionen Ihrer Abgabe importieren, wird Code, der sich auf globale Variablen beruft, nicht funktionieren. Daher sollten alle Ihre Funktionen in sich geschlossen sein (self-contained), d. h. sie sollten nicht von Variablen außerhalb der Funktion abhängig sein.
```python=3.9
# No
a = 5
def add_number(b):
return a + b
# Yes
def add_number(a, b):
return a + b
```
### While-/range-len-loops (In den meisten Fällen)
Versuchen Sie, `while`-loops zu vermeiden, wenn Sie das gleiche Problem mit einer eleganteren `for`-Schleife lösen können. Verwenden Sie keine `range(len(...))`-Schleifen, um den Index eines Iterables zu erhalten und dann über dasselbe Objekt mit diesem Index zu iterieren. Sie können das elegante `in`-Statement verwenden. Es gibt Situationen, in denen es in Ordnung ist, den Index eines Iterables zu verwenden, um über mehrere Iterables zu loopen. In den meisten dieser Fälle führt jedoch die Verwendung des `zip`-Statements zum gleichen Ergebnis.
```python=3.9
names = ["Anna", "Martin", "Moritz", "Karin", "Katharina", "Thomas"]
studies = ["BW", "BW", "ICE", "BME", "BME", "Sound Engineering"]
# Yes
for name in names:
print(name)
# No
for i in range(len(names)):
print(names[i])
# Nooooo
i = 0
while i < len(names):
print(names[i])
i += 1
# Iterate over multiple lists using the zip-statement
for name, study in zip(names, studies):
print(f"{name}, {study}")
# when you really need the index as well, use enumerate:
for index, name in enumerate(names):
print(f"#{index}: {name}")
```
### Iteration über Iterables, um zu prüfen, ob ein Element vorhanden ist
Das "in"-Statement prüft das Vorhandensein eines Elements innerhalb eines Iterables oder Strings.
```python=3.9
numbers = [1,2,3,4,5]
target_number = 6
# No
for number in numbers:
if number == target_number:
print("found the target")
# Yes
if target_number in numbers:
print("found the target")
```
### Unangemessene Vergleiche mit `is`
`is` vergleicht Objekt-Identität (Die einzigartige ID eines Objektes) und nicht Werte.
```python=3.9
random_number = 4 # definitely random
# No
guess = int(input("Guess a number"))
if guess is random_number:
print("Yay")
else:
print("Nay")
random_boolean = True # definitely random
if random_boolean == True: # same for False and None
print("Yay")
else:
print("Nay")
# Yes
guess = input("Guess a number")
if guess == random_number:
print("Yay")
else:
print("Nay")
if random_boolean is True:
print("Yay")
else:
print("Nay")
a = range(10)
b = range(10)
print(a is b) # -> False
print(a == b) # -> True
```
### Vermeidbare und verwirrende Verschachtelungen
Übermäßige Verschachtelung macht den Code schwer lesbar.
```python=3.9
# No
if correct_type(arg1):
if correct_type(arg2):
if correct_type(arg3):
do_something()
else:
raise TypeError("Argument 3 has the wrong type")
else:
raise TypeError("Argument 2 has the wrong type")
else:
raise TypeError("Argument 1 has the wrong type")
# Yes
if not correct_type(arg1):
raise TypeError("Argument 1 has the wrong type")
if not correct_type(arg2):
raise TypeError("Argument 2 has the wrong type")
if not correct_type(arg3):
raise TypeError("Argument 3 has the wrong type")
do_something()
```
### dict.update für das Einfügen einzelner Key-Value-Paare
Die `dict`-Methode `update` sollte nur verwendet werden, wenn mehrere `Key`-`Value`-Paare zu einem dictionary hinzugefügt werden.
```python=3.9
# No
cities.update({6010: "Innsbruck"})
# Yes
cities[6010] = "Innsbruck"
```
### Sinnlose if/else-Konstrukte, die praktisch das Gleiche bewirken
```python=3.9
import matplotlib.pyplot as plt
# No
def plot_something(x: list, y: list, yscale: bool) -> None:
if yscale == False:
plt.figure()
plt.plot(x, y, yscale=False)
plt.title("A plot showcasing the dependence of y on x.")
plt.show()
elif yscale == True:
plt.figure()
plt.plot(x, y, yscale=True)
plt.title("A plot showcasing the dependence of y on x.")
plt.show()
# Yes
def plot_something(x: list, y: list, yscale: bool) -> None:
plt.figure()
plt.plot(x, y, yscale=yscale)
plt.title("A plot showcasing the dependence of y on x.")
plt.show()