Was ist Clean Code?
Clean Code bezeichnet eine Art der Softwareentwicklung, die sich durch gut lesbaren, wartbaren und verständlichen Code auszeichnet. Ziel ist es, Code zu schreiben, der nicht nur für Maschinen, sondern vor allem für Menschen leicht zu verstehen ist. Dies wird durch verschiedene Prinzipien, Methoden und Praktiken erreicht.
Prinzipien
Don't Repeat Yourself
Das DRY-Prinzip zielt darauf ab, die Wiederholung von Logik innerhalb eines Codes zu vermeiden. Es fördert die Wiederverwendbarkeit und Wartbarkeit, indem es sicherstellt, dass jede Wissenseinheit in einem System eine einzige, eindeutige und autoritative Darstellung hat.
Beispiel
public class Smartphone
{
..
public decimal ApplyDiscount(decimal discountPercentage)
{
return Price - (Price * discountPercentage / 100);
}
}
public class Laptop
{
..
public decimal ApplyDiscount(decimal discountPercentage)
{
return Price - (Price * discountPercentage / 100);
}
}
public static class DiscountCalculator
{
public static decimal CalculateDiscount(decimal price, decimal discountPercentage)
{
return price - (price * discountPercentage / 100);
}
}
public class Smartphone
{
..
public decimal ApplyDiscount(decimal discountPercentage)
{
return DiscountCalculator.CalculateDiscount(Price, discountPercentage);
}
}
public class Laptop
{
..
public decimal ApplyDiscount(decimal discountPercentage)
{
return DiscountCalculator.CalculateDiscount(Price, discountPercentage);
}
}
Keep It Simple, Stupid
Das KISS-Prinzip steht für "Keep It Simple, Stupid" und betont, dass Systeme einfach gehalten werden sollten, um die Wartbarkeit und Verständlichkeit zu verbessern. Indem man unnötige Komplexität vermeidet, können Fehler leichter identifiziert und behoben werden.
Beispiel
Im folgenden Beispiel zeigt eine einfache Methode, wie das KISS-Prinzip angewendet werden kann.
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
In diesem Fall wird eine einfache Addition implementiert, ohne überflüssige Logik oder unnötige Features hinzuzufügen.
You Ain't Gonna Need It
YAGNI steht für "You Aren't Gonna Need It" und ist ein Prinzip der Softwareentwicklung. Es besagt, dass Entwickler nur die Funktionen implementieren sollten, die aktuell benötigt werden, und nicht die, von denen sie glauben, dass sie in der Zukunft nützlich sein könnten. Das Ziel ist es, die Projektkomplexität zu verringern und unnötige Arbeiten zu vermeiden.
Beispiel
Angenommen, ein Entwickler arbeitet an einer kleinen Anwendung und überlegt, ein Logging-System zu implementieren, weil es in Zukunft vielleicht nützlich sein könnte. Nach dem YAGNI-Prinzip sollte er das vermeiden und nur implementieren, was gerade gebraucht wird:
public class Greeter
{
public void Greet()
{
Console.WriteLine("Hello, User!");
}
}
In diesem Beispiel wurde das benötigte Feature erstellt, ohne zusätzlichen ungenutzten Code hinzuzufügen.
Tell, Don't Ask
Das TDA-Prinzip, oder Prinzip der "Tell, Don't Ask"-Methode, ermutigt dazu, Objekte ihre Zustände selbst verwalten zu lassen, anstatt von aussen nach ihren Zuständen zu fragen und darauf basierend Aktionen durchzuführen. Dies fördert die Kapselung und reduziert die Abhängigkeiten zwischen Objekten.
Beispiel
public class BankAccount
{
private decimal balance;
public void Deposit(decimal amount)
{
if (amount > 0)
{
balance += amount;
}
}
public void Withdraw(decimal amount)
{
if (amount > 0 && amount <= balance)
{
balance -= amount;
}
}
}
BankAccount account = new BankAccount();
account.Deposit(100);
account.Withdraw(50);
In diesem Beispiel wird der Kontostand (balance) nicht von aussen abgefragt, sondern durch Methoden verändert, die die erlaubten Operationen direkt kapseln.
Law Of Demeter
Die Law of Demeter (auch bekannt als das Prinzip des geringsten Wissens) ist ein Entwurfsmuster, das darauf abzielt, die Kopplung zwischen Modulen in einem Softwaredesign zu minimieren. Es besagt, dass ein Objekt nur mit unmittelbaren "Freunden" kommunizieren sollte und nicht mit Fremden. Im Wesentlichen sollte jede Methode nur auf folgende Informationen zugreifen:
Ihre eigenen Felder und Methoden.
Felder und Methoden von Objekten, die als direkte Parameter ĂĽbergeben werden.
Felder und Methoden von Objekten, die sie selbst erstellt.
Beispiel
public class Engine
{
public void Start()
{
Console.WriteLine("Engine started");
}
}
public class Car
{
private Engine engine = new Engine();
public void Start()
{
engine.Start();
}
}
Car myCar = new Car();
myCar.Start();
In dem obigen Beispiel vermeidet die Car
-Klasse unnötige Exposition ihres Engine
-Objekts und interagiert nur mit ihm durch die eigene Methode StartCar
, was das Prinzip der Law of Demeter demonstriert.
Separation Of Concern
Separation of Concern
Separation of Concern (SoC) ist ein Prinzip in der Softwareentwicklung, das besagt, dass ein Softwareprogramm in verschiedene, voneinander unabhängige Teile oder Module aufgeteilt werden sollte, von denen jedes eine spezifische Funktion innerhalb der Gesamtstruktur erfüllt. Dies erleichtert die Wartung, die Anpassung und das Testen von Software, da jedes Modul isoliert behandelt werden kann.
Beispiel
In diesem Beispiel wird das SoC-Prinzip angewendet, indem die Logik fĂĽr Datenzugriff und Business-Logik getrennt wird:
public class ProductRepository
{
public List<Product> GetAllProducts()
{
..
}
}
public class ProductService
{
private readonly ProductRepository _repository;
public ProductService(ProductRepository repository)
{
_repository = repository;
}
public void DisplayAllProducts()
{
var products = _repository.GetAllProducts();
..
}
}
Hier ĂĽbernimmt ProductRepository
die Aufgabe, sich nur mit dem Datenzugriff zu beschäftigen, während ProductService
die Business-Logik enthält.
SOLID
Single Responsibility
Das Single Responsibility Prinzip besagt, dass eine Klasse nur einen Grund für eine Änderung haben sollte, d.h. sie sollte nur für einen Teil der Softwarefunktionalität verantwortlich sein.
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
public class UserManager
{
public void CreateUser(string name, string email)
{
User user = new User { Name = name, Email = email };
Console.WriteLine($"User {name} created.");
}
}
public class EmailService
{
public void SendEmail(string email, string message)
{
Console.WriteLine($"Email sent to {email}: {message}");
}
}
UserManager userManager = new UserManager();
EmailService emailService = new EmailService();
userManager.CreateUser("Levin", "levin@example.com");
emailService.SendEmail("levin@example.com", "Welcome to our system!");
Open-Closed
Das Open-Closed-Prinzip besagt, dass Software-Module offen für Erweiterungen, aber geschlossen für Veränderungen sein sollten. Dies bedeutet, dass das Verhalten eines Moduls durch das Hinzufügen von neuem Code erweitert werden kann, ohne den bestehenden Code zu ändern.
Beispiel
public abstract class Shape
{
public abstract double GetArea();
}
public class Circle : Shape
{
private double _radius;
public Circle(double radius)
{
_radius = radius;
}
public override double GetArea()
{
return Math.PI * _radius * _radius;
}
}
public class Rectangle : Shape
{
private double _width;
private double _height;
public Rectangle(double width, double height)
{
_width = width;
_height = height;
}
public override double GetArea()
{
return _width * _height;
}
}
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
Console.WriteLine("Circle Area: " + circle.GetArea());
Console.WriteLine("Rectangle Area: " + rectangle.GetArea());
In diesem Beispiel kann die Klasse Shape
durch neue Formen erweitert werden, ohne den bestehenden Code zu ändern, indem einfach neue Unterklassen hinzugefügt werden. Dies zeigt, wie das Open-Closed-Prinzip angewendet werden kann.
Liskov Substition
Das Liskov Substitution Principle (LSP) besagt, dass Objekte einer abgeleiteten Klasse überall dort verwendet werden können, wo Objekte der Basisklasse erwartet werden, ohne das Verhalten des Programms zu ändern.
Mit anderen Worten: Eine Unterklasse darf die Funktionalität der Basisklasse nicht brechen oder unerwartetes Verhalten einführen.
Beispiel
public abstract class Shape
{
public abstract int GetArea();
}
public class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override int GetArea() => Width * Height;
}
public class Square : Shape
{
public int SideLength { get; set; }
public override int GetArea() => SideLength * SideLength;
}
Shape rect = new Rectangle { Width = 5, Height = 10 };
Shape square = new Square { SideLength = 5 };
Console.WriteLine($"Rechteck-Fläche: {rect.GetArea()}");
Console.WriteLine($"Quadrat-Fläche: {square.GetArea()}");
Interface Segregation
Das Interface Segregation Principle (ISP) besagt, dass ein Client niemals gezwungen sein sollte, Methoden zu implementieren, die er nicht benötigt.
Mit anderen Worten:
Grosse, allgemeine Interfaces sollten in kleinere, spezifische Interfaces aufgeteilt werden.
Dadurch mĂĽssen Klassen nur die Methoden implementieren, die fĂĽr sie relevant sind.
Beispiel
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public class HumanWorker : IWorkable, IEatable
{
public void Work() => Console.WriteLine("Mensch arbeitet.");
public void Eat() => Console.WriteLine("Mensch isst.");
}
public class RobotWorker : IWorkable
{
public void Work() => Console.WriteLine("Roboter arbeitet.");
}
IWorkable robot = new RobotWorker();
robot.Work();
IEatable human = new HumanWorker();
human.Eat();
Dependency Inversion
Das Dependency Inversion Principle (DIP) besagt:
Höhere Module sollten nicht von niedrigeren Modulen abhängen. Beide sollten von Abstraktionen (Interfaces oder abstrakten Klassen) abhängen.
Abstraktionen sollten nicht von konkreten Implementierungen abhängen. Konkrete Klassen sollten von Abstraktionen abhängen.
Ziel: Vermeidung direkter Abhängigkeiten zwischen Modulen, um Flexibilität und Testbarkeit zu erhöhen.
Beispiel
public interface ILogger
{
void Log(string message);
}
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Logging to file: {message}");
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Logging to console: {message}");
}
}
public class UserService
{
private readonly ILogger _logger;
public UserService(ILogger logger)
{
_logger = logger;
}
public void RegisterUser(string username)
{
Console.WriteLine($"User {username} registered.");
_logger.Log("User registered.");
}
}
ILogger logger = new ConsoleLogger();
UserService service = new UserService(logger);
service.RegisterUser("Levin");
Zuletzt aktualisiert