Eigentlich DAS Standardthema, wenn’s um Coding Guidelines geht: Wo breche ich Zeilen um und wie rücke ich ein?
Empfehlung:
* LINQ-Ausdrücke sollten konsequent und einheitlich umgebrochen werden.
* Unterausdrücke sollten einheitlich eingerückt werden.
* Die Einrückungstiefe sollte dem Standard im restlichen Code entsprechen.
Man sollte meinen, dass sich das Thema nach jahrzehntelanger Grundsatzdiskussion und automatischer Code-Formatierung in modernen IDEs von selbst erledigt hätte. Aber weit gefehlt.
Als extremes Beispiel der Anfang des RayTracers:
Einrückungstiefen von 1, 4, 5, 7 und noch mehr Zeichen. Umbrüche in einzelnen Unterausdrücken und Funktionsaufrufen. Und der Rest der Methode wird nicht besser.
Ebenso ein typischer Vertreter:
1: public ApiExpert Get(int id)
2: {
3: return this.db.eXperts
4: .Where(x => x.Id == id)
5: .ToArray()
6: .Select(x => new ApiExpert
7: {
8: Id = x.Id,
9: IsAdmin = x.IsAdmin,
10: Name = x.Name,
11: CustomerResponse = x.CustomerStatements
12: .Select(c =>
13: new KeyValuePair<string, string>(
14: c.Id.ToString(CultureInfo.InvariantCulture),
15: c.Statement))
16: })
17: .FirstOrDefault();
18: }
Willkürliche Einrückungen. Umbrüche, die sich nicht an der Logik der Abfrage orientieren. Man hat den Eindruck, der Quelltext flüchtet zum rechten Bildschirmrand.
Die gleiche Methode wird gleich besser lesbar…
1: public ApiExpert Get(int id)
2: {
3: return this.db.eXperts
4: .Where(x => x.Id == id)
5: .ToArray()
6: .Select(x => new ApiExpert
7: {
8: Id = x.Id,
9: IsAdmin = x.IsAdmin,
10: Name = x.Name,
11: CustomerResponse = x.CustomerStatements.Select(c =>
12: new KeyValuePair<string, string>(c.Id.ToString(CultureInfo.InvariantCulture), c.Statement)
13: )
14: })
15: .FirstOrDefault();
16: }
… wenn sich die Einrückungstiefe am Standard (4 Zeichen) orientiert, und wenn man beim Unterausdruck auf Umbrüche verzichtet.
Ein weiteres Beispiel aus der Praxis:
1: IQueryable<AllocationTimeSpan> allTimeSpans = _context.AllocationSet.OfType<Allocation>().Select(a => new AllocationTimeSpan
2: {
3: From = a.AllocatedFrom,
4: To = a.AllocatedTo,
5: Status = (Status)(a.Status),
6: }).Distinct().OrderBy(ts => ts.From);
Durch die Umbrüche dominiert das Mapping der Daten anstatt der Abfragelogik. Bricht man hingegen an den einzelnen Operationen um, wird die Logik gleich deutlicher, die Kette der Aufrufe offensichtlich:
1: IQueryable<AllocationTimeSpan> allTimeSpans = _context
2: .AllocationSet
3: .OfType<Allocation>()
4: .Select(a => new AllocationTimeSpan
5: {
6: From = a.AllocatedFrom,
7: To = a.AllocatedTo,
8: Status = (Status)(a.Status),
9: AvailableMDRatio = a.AvailableMDRatio
10: })
11: .Distinct()
12: .OrderBy(ts => ts.From);
Leider unterstützt einen die Code-Formatierung im Visual Studio bei der Formatierung von LINQ-Ausdrücken nicht immer so ganz ideal. Aber wenn man sich an ein oder zwei Eigenheiten gewöhnt hat (z.B. den Lambda-Ausdruck in einem Select in der gleichen Zeile zu beginnen, statt in die nächste Zeile zu rücken), dann kann man auch damit gut lesbaren Code produzieren – wenn man das will.