LINQ Coding Guidelines #3–Einrückungen und Umbrüche

2. Dezember 2014

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:

image

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.