# 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()