Komplexität: Unterschied zwischen den Versionen
(Fix examples) |
Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
{{ | {{CategoryBlock | ||
| | |Baustelle=Ja | ||
| | |Java Grundlagen=Nein | ||
| | |Organisation=Nein | ||
|Programmierstil=Nein | |||
public static int max3(int a, int b, int c) { | |Bewertungsrichtlinie=Ja | ||
|blattAnnotation=1 | |||
|blattAbzug=2 | |||
}} | |||
{{Inhaltsblock | |||
|vorher====Beschreibung=== | |||
Ein wichtiges Ziel beim Programmieren ist, dass der geschriebenen Quellcode einfach von anderen verstanden werden kann. Deshalb sollte immer der einfachste Weg gewählt werden, um eine vorgegebene Funktionalität zu implementieren. Dies bedeutet nicht, dass der Quellcode aus möglichst wenigen Anweisungen und Zeichen bestehen soll, wie zum Beispiel hier | |||
|Beispiel=public static int max3(int a, int b, int c) { | |||
return a >= b && a >= c ? a : b >= c ? b : c; | return a >= b && a >= c ? a : b >= c ? b : c; | ||
} | } | ||
|nachher=sondern dass auf unnötige Konstrukte, innerhalb der Vorgaben des Checkstyle und der weiteren Bewertungskriterien, verzichtet werden soll. | |||
}} | |||
sondern dass auf unnötige Konstrukte, innerhalb der Vorgaben des Checkstyle und der weiteren Bewertungskriterien, verzichtet werden soll. | {{Inhaltsblock | ||
|vorher=Möglichkeiten, Code zu vereinfachen, werden häufig erst im Laufe der Entwicklung sichtbar. Beim Überarbeiten und Weiterentwickeln sollte stets ein Augenmerk darauf gelegt werden, ob der bestehende Code unnötige Komplexität enthält und einfacher und verständlicher gestaltet werden kann. | |||
Möglichkeiten, Code zu vereinfachen, werden häufig erst im Laufe der Entwicklung sichtbar. Beim Überarbeiten und Weiterentwickeln sollte stets ein Augenmerk darauf gelegt werden, ob der bestehende Code unnötige Komplexität enthält und einfacher und verständlicher gestaltet werden kann. | |||
Zudem sollte man sich mit existierenden Klassen und Methoden aus der Java Standardbibliothek vertraut machen. Zum Beispiel kann das obere Beispiel mithilfe von <syntaxhighlight lang="Java" inline>Math.max</syntaxhighlight> zu folgendem vereinfacht werden: | Zudem sollte man sich mit existierenden Klassen und Methoden aus der Java Standardbibliothek vertraut machen. Zum Beispiel kann das obere Beispiel mithilfe von <syntaxhighlight lang="Java" inline>Math.max</syntaxhighlight> zu folgendem vereinfacht werden: | ||
|Beispiel=public static int max3(int a, int b, int c) { | |||
public static int max3(int a, int b, int c) { | |||
return Math.max(a, Math.max(b, c)); | return Math.max(a, Math.max(b, c)); | ||
} | } | ||
}} | |||
{{Inhaltsblock | |||
|vorher===== Verschachtelungstiefe ==== | |||
==== Verschachtelungstiefe ==== | |||
Die Logik eines Programms wird hauptsächlich durch Kontrollstrukturen gegeben. Diese lassen sich auch ineinander schreiben um dadurch sehr komplexe Logiken zu implementieren. Für das Verschachteln gibt es allerdings Regeln, sodass der Code dadurch nicht unleserlich oder gar unverständlich wird. | Die Logik eines Programms wird hauptsächlich durch Kontrollstrukturen gegeben. Diese lassen sich auch ineinander schreiben um dadurch sehr komplexe Logiken zu implementieren. Für das Verschachteln gibt es allerdings Regeln, sodass der Code dadurch nicht unleserlich oder gar unverständlich wird. | ||
| Zeile 27: | Zeile 31: | ||
Kleine Methoden sind besser zu lesen als große Methoden. In kleineren Methoden fallen zudem Fehler besser auf und Sichtbarkeiten können geeigneter gewählt werden (z.B. private Hilfsmethoden). Geeignete Methodennamen ermöglichen es, selbst komplexe Logiken, leicht verständlich zu implementieren. | Kleine Methoden sind besser zu lesen als große Methoden. In kleineren Methoden fallen zudem Fehler besser auf und Sichtbarkeiten können geeigneter gewählt werden (z.B. private Hilfsmethoden). Geeignete Methodennamen ermöglichen es, selbst komplexe Logiken, leicht verständlich zu implementieren. | ||
}} | |||
= | {{Inhaltsblock | ||
|color=red | |||
public static List<Integer> primesBad(final int max) { | |vorher=Negativbeispiel: | ||
|Beispiel=public static List<Integer> primesBad(final int max) { | |||
final List<Integer> primes = new ArrayList<>(); | final List<Integer> primes = new ArrayList<>(); | ||
| Zeile 49: | Zeile 54: | ||
return primes; | return primes; | ||
} | } | ||
|nachher=* Das Beispiel ist für die Verständlichkeit hier nicht zu komplex gewählt | |||
* Das Beispiel ist für die Verständlichkeit hier nicht zu komplex gewählt | |||
* Es muss verstanden werden, wie sich der Zustand von primes und divisors entwickelt | * Es muss verstanden werden, wie sich der Zustand von primes und divisors entwickelt | ||
* Bei kompleceren Problemen wird das schnell anstrengend und fehleranfällig | * Bei kompleceren Problemen wird das schnell anstrengend und fehleranfällig | ||
}} | |||
= | {{Inhaltsblock | ||
|color=green | |||
public static List<Integer> primesGood(final int max) { | |vorher=Positivbeispiel: | ||
|Beispiel=public static List<Integer> primesGood(final int max) { | |||
final List<Integer> primes = new ArrayList<>(); | final List<Integer> primes = new ArrayList<>(); | ||
| Zeile 83: | Zeile 88: | ||
return dividend % divisor == 0; | return dividend % divisor == 0; | ||
} | } | ||
|nachher=* Die beiden Hilfsmethoden helfen bei der Erklärung was passiert bereits durch ihre gewählten Bezeichner | |||
* Die beiden Hilfsmethoden helfen bei der Erklärung was passiert bereits durch ihre gewählten Bezeichner | |||
* Jede Methode hat höchstens einen veränderlichen Zustand, der nach der Ausführung vergessen werden kann | * Jede Methode hat höchstens einen veränderlichen Zustand, der nach der Ausführung vergessen werden kann | ||
* Die Korrektheit jeder einzelnen Funktion ist offensichtlich (für n > 0) | * Die Korrektheit jeder einzelnen Funktion ist offensichtlich (für n > 0) | ||
}} | |||
{{Inhaltsblock | |||
|color=red | |||
|vorher=Negativbeispiel: | |||
|Beispiel=boolean isValid() { | |||
| | |||
| | |||
boolean isValid() { | |||
if (this.sold == false && !(this.price <= 0)) { | if (this.sold == false && !(this.price <= 0)) { | ||
return true; | return true; | ||
| Zeile 104: | Zeile 101: | ||
return false; | return false; | ||
} | } | ||
|nachher=Der Ausdruck ist hier unnötig komplex und kann vereinfacht werden. <syntaxhighlight lang="Java" inline>this.sold == false</syntaxhighlight> entspricht dem Ausdruck <syntaxhighlight lang="Java" inline>!this.sold</syntaxhighlight> (<syntaxhighlight lang="Java" inline>!</syntaxhighlight> invertiert <syntaxhighlight lang="Java" inline>this.sold</syntaxhighlight>, entsprechend ist der Ausdruck nur wahr, wenn <syntaxhighlight lang="Java" inline>this.sold</syntaxhighlight> false ist). | |||
Der Ausdruck ist hier unnötig komplex und kann vereinfacht werden. <syntaxhighlight lang="Java" inline>this.sold == false</syntaxhighlight> entspricht dem Ausdruck <syntaxhighlight lang="Java" inline>!this.sold</syntaxhighlight> (<syntaxhighlight lang="Java" inline>!</syntaxhighlight> invertiert <syntaxhighlight lang="Java" inline>this.sold</syntaxhighlight>, entsprechend ist der Ausdruck nur wahr, wenn <syntaxhighlight lang="Java" inline>this.sold</syntaxhighlight> false ist). | |||
Entsprechend ist <syntaxhighlight lang="Java" inline>this.price > 0</syntaxhighlight> eine vereinfachte Variante von <syntaxhighlight lang="Java" inline>!(this.price <= 0)</syntaxhighlight>. | Entsprechend ist <syntaxhighlight lang="Java" inline>this.price > 0</syntaxhighlight> eine vereinfachte Variante von <syntaxhighlight lang="Java" inline>!(this.price <= 0)</syntaxhighlight>. | ||
| | }} | ||
boolean isValid() { | {{Inhaltsblock | ||
|color=green | |||
|vorher=Positivbeispiel: | |||
|Beispiel=boolean isValid() { | |||
return !this.sold && this.price > 0; | return !this.sold && this.price > 0; | ||
} | } | ||
|nachher=Hier wurde der Ausdruck entsprechend vereinfacht, sodass er leichter zu lesen und verstehen ist. | |||
Hier wurde der Ausdruck entsprechend vereinfacht, sodass er leichter zu lesen und verstehen ist. | }} | ||
| | {{Inhaltsblock | ||
|vorher==== Weitere Beispiele für unnötige Komplexität === | |||
}} | }} | ||
= | {{Inhaltsblock | ||
|color=blue | |||
|Beispiel=if (a == true) { | |||
if (a == true) { | |||
// ... | // ... | ||
} | } | ||
| Zeile 130: | Zeile 128: | ||
// same for a == false | // same for a == false | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
if (a) { | |Beispiel=if (a) { | ||
return true; | return true; | ||
} else { | } else { | ||
| Zeile 154: | Zeile 152: | ||
// ... | // ... | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
if (a) { | |Beispiel=if (a) { | ||
doA(); | doA(); | ||
} else { | } else { | ||
| Zeile 172: | Zeile 170: | ||
doB(); | doB(); | ||
} | } | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
if (a) { | |Beispiel=if (a) { | ||
if (b) { | if (b) { | ||
// ... | // ... | ||
| Zeile 186: | Zeile 184: | ||
// ... | // ... | ||
} | } | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
|Beispiel=public class Point { | |||
public class Point { | |||
private int x = 0; | private int x = 0; | ||
private int y = 0; | private int y = 0; | ||
| Zeile 201: | Zeile 198: | ||
} | } | ||
} | } | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
int a = 5; | |Beispiel=int a = 5; | ||
// there is no reason to assign a | // there is no reason to assign a | ||
// variable to itself. | // variable to itself. | ||
| Zeile 225: | Zeile 222: | ||
} | } | ||
} | } | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
return !(a == 1) | |Beispiel=return !(a == 1) {{!}}{{!}} !(b < 5); | ||
// could be simplified to | // could be simplified to | ||
return a != 1 | return a != 1 {{!}}{{!}} b >= 5; | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
int a; | |Beispiel=int a; | ||
a = 5; | a = 5; | ||
| Zeile 242: | Zeile 239: | ||
int a = 5; | int a = 5; | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
System.out.println(a + a + a + a + a); | |Beispiel=System.out.println(a + a + a + a + a); | ||
// instead of | // instead of | ||
System.out.println(a * 5); | System.out.println(a * 5); | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
// For each primitive datatype like int or double, | |Beispiel=// For each primitive datatype like int or double, | ||
// there exists a class like Integer or Double. | // there exists a class like Integer or Double. | ||
// | // | ||
| Zeile 280: | Zeile 277: | ||
// Using the boxed class even though it is not necessary | // Using the boxed class even though it is not necessary | ||
// is considered unnecessary complexity. | // is considered unnecessary complexity. | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
// this is redundant, because java.lang is already imported | |Beispiel=// this is redundant, because java.lang is already imported | ||
import java.lang.String; | import java.lang.String; | ||
// NOTE: There are special cases where this might be necessary, | // NOTE: There are special cases where this might be necessary, | ||
| Zeile 291: | Zeile 288: | ||
// unnecessary as well: | // unnecessary as well: | ||
import java.util.ArrayList; | import java.util.ArrayList; | ||
}} | |||
{{Inhaltsblock | |||
|color=blue | |||
a = a + 5; | |Beispiel=a = a + 5; | ||
// could be simplified to | // could be simplified to | ||
a += 5; | a += 5; | ||
| Zeile 304: | Zeile 301: | ||
a <<= 2; // a = a << 2; | a <<= 2; // a = a << 2; | ||
a >>= 3; // a = a >> 3; | a >>= 3; // a = a >> 3; | ||
}} | |||
Version vom 13. Oktober 2025, 15:58 Uhr
| 🚧 | Diese Seite befindet sich in Bearbeitung | 🚧 |
| 🤓 | Diese Seite ist eine Bewertungsrichtlinie, die ab Blatt 1 annotiert und ab Blatt 2 abgezogen wird. | 🤓 |
Beschreibung
Ein wichtiges Ziel beim Programmieren ist, dass der geschriebenen Quellcode einfach von anderen verstanden werden kann. Deshalb sollte immer der einfachste Weg gewählt werden, um eine vorgegebene Funktionalität zu implementieren. Dies bedeutet nicht, dass der Quellcode aus möglichst wenigen Anweisungen und Zeichen bestehen soll, wie zum Beispiel hier
public static int max3(int a, int b, int c) {
return a >= b && a >= c ? a : b >= c ? b : c;
}
Möglichkeiten, Code zu vereinfachen, werden häufig erst im Laufe der Entwicklung sichtbar. Beim Überarbeiten und Weiterentwickeln sollte stets ein Augenmerk darauf gelegt werden, ob der bestehende Code unnötige Komplexität enthält und einfacher und verständlicher gestaltet werden kann.
Zudem sollte man sich mit existierenden Klassen und Methoden aus der Java Standardbibliothek vertraut machen. Zum Beispiel kann das obere Beispiel mithilfe von Math.max zu folgendem vereinfacht werden:
public static int max3(int a, int b, int c) {
return Math.max(a, Math.max(b, c));
}
Verschachtelungstiefe
Die Logik eines Programms wird hauptsächlich durch Kontrollstrukturen gegeben. Diese lassen sich auch ineinander schreiben um dadurch sehr komplexe Logiken zu implementieren. Für das Verschachteln gibt es allerdings Regeln, sodass der Code dadurch nicht unleserlich oder gar unverständlich wird.
Generell gilt:
Kleine Methoden sind besser zu lesen als große Methoden. In kleineren Methoden fallen zudem Fehler besser auf und Sichtbarkeiten können geeigneter gewählt werden (z.B. private Hilfsmethoden). Geeignete Methodennamen ermöglichen es, selbst komplexe Logiken, leicht verständlich zu implementieren.
Negativbeispiel:
public static List<Integer> primesBad(final int max) {
final List<Integer> primes = new ArrayList<>();
for(int n = 1; n <= max; n++) {
int divisors = 0;
for(int i = 1; i <= n; i++) {
if(n % i == 0) {
divisors++;
}
}
if(divisors == 2) {
primes.add(n);
}
}
return primes;
}
- Das Beispiel ist für die Verständlichkeit hier nicht zu komplex gewählt
- Es muss verstanden werden, wie sich der Zustand von primes und divisors entwickelt
- Bei kompleceren Problemen wird das schnell anstrengend und fehleranfällig
Positivbeispiel:
public static List<Integer> primesGood(final int max) {
final List<Integer> primes = new ArrayList<>();
for(int n = 1; n <= max; n++) {
if(isPrime(n)) {
primes.add(n);
}
}
return primes;
}
private static boolean isPrime(final int n) {
int divisors = 0;
for(int i = 1; i <= n; i++) {
if(divides(i, n)) {
divisors++;
}
}
return divisors == 2;
}
private static boolean divides(final int divisor, final int dividend) {
return dividend % divisor == 0;
}
- Die beiden Hilfsmethoden helfen bei der Erklärung was passiert bereits durch ihre gewählten Bezeichner
- Jede Methode hat höchstens einen veränderlichen Zustand, der nach der Ausführung vergessen werden kann
- Die Korrektheit jeder einzelnen Funktion ist offensichtlich (für n > 0)
Negativbeispiel:
boolean isValid() {
if (this.sold == false && !(this.price <= 0)) {
return true;
}
return false;
}
Der Ausdruck ist hier unnötig komplex und kann vereinfacht werden. this.sold == false entspricht dem Ausdruck !this.sold (! invertiert this.sold, entsprechend ist der Ausdruck nur wahr, wenn this.sold false ist).
this.price > 0 eine vereinfachte Variante von !(this.price <= 0).Positivbeispiel:
boolean isValid() {
return !this.sold && this.price > 0;
}
Weitere Beispiele für unnötige Komplexität
if (a == true) {
// ...
}
// can be simplified to
if (a) {
// ...
}
// same for a == false
if (a) {
return true;
} else {
return false;
}
// this can be `return a;`
// other variations of this are:
if (!a) {
return true;
} else {
return false;
}
if (a) {
return false;
}
return true;
// ...
if (a) {
doA();
} else {
if (b) {
doB();
}
}
// can be simplified to
if (a) {
doA();
} else if (b) {
doB();
}
if (a) {
if (b) {
// ...
}
}
// can be simplified to
if (a && b) {
// ...
}
public class Point {
private int x = 0;
private int y = 0;
public Point() {
// This constructor is redundant.
// The same constructor (default constructor)
// will be generated by java,
// when none is declared.
}
}
int a = 5;
// there is no reason to assign a
// variable to itself.
a = a;
// ...
// more commonly this happens in methods where
// one forgets to use `this.`:
class Point {
private int x;
private int y;
Point(int x, int y) {
// The below code does assign the
// parameter x and y the values of
// x and y, which is redundant.
x = x;
y = y;
}
}
return !(a == 1) || !(b < 5);
// could be simplified to
return a != 1 || b >= 5;
int a;
a = 5;
// instead of
int a = 5;
System.out.println(a + a + a + a + a);
// instead of
System.out.println(a * 5);
// For each primitive datatype like int or double,
// there exists a class like Integer or Double.
//
// Java automatically converts primitive datatypes to
// their classes, so you can do this:
int a = 5;
Integer b = a; // a is automatically converted to Integer
// These classes are useful for generics, because you can
// not write List<int>, you have to use the class: List<Integer>
// or when you want to return a nullable number, like this:
public static Integer getNumber() {
if (this.scanner.hasNextInt()) {
return this.scanner.nextInt();
}
return null;
}
// NOTE: In the above case, you should consider
// returning Optional<Integer> or a dedicated
// class instead, so you don't forget to handle
// the null case.
// Because of the above explained automatic conversion,
// one can use the boxed class (e.g. Integer) everywhere
// an int could be used.
//
// Using the boxed class even though it is not necessary
// is considered unnecessary complexity.
// this is redundant, because java.lang is already imported
import java.lang.String;
// NOTE: There are special cases where this might be necessary,
// in these cases please consider renaming your class.
// Imports of classes that are never used, is considered
// unnecessary as well:
import java.util.ArrayList;
a = a + 5;
// could be simplified to
a += 5;
// same for %, -, *, /, <<, >>
a %= 2; // a = a % 2;
a *= 5; // a = a * 5;
a /= 3; // a = a / 3;
a <<= 2; // a = a << 2;
a >>= 3; // a = a >> 3;