Es gibt sie einfach nicht: Coding Guidelines für LINQ.
Eigentlich ist das verwunderlich, denn für C# im allgemeinen haben wir mehr als genug Auswahl: Automatische Code-Formatierung in Visual Studio, diverse Coding Guidelines als Dokumentation (msdn, lh), StyleCop zur Prüfung.
Und alle ignorieren LINQ.
Eine unmittelbare Konsequenz daraus sehe ich immer wieder: Komplexe LINQ-Ausdrücke die an Unlesbarkeit kaum zu überbieten sind; deren Logik sich nur durch “mind compiling and execution” erfassen lässt – sprich, man muss sie gedanklich durchkompilieren und ausführen um sie zu verstehen; einfache LINQ-Ausdrücke, die sich alle Mühe geben, komplex zu erscheinen.
Und das, obwohl LINQ doch eigentlich als deklaratives Sprachmittel der Hort der Lesbarkeit sein sollte.
Ein schönes Beispiel hat Luke mit seinem RayTracer, auch wenn er das zugegebenermaßen nur als Experiment ansieht.
Doch auch vergleichsweise moderate LINQ-Ausdrücke lassen sich oft in ihrer Lesbarkeit deutlich verbessern. Das folgende Beispiel stammt aus einer Beispielanwendung eines Kollegen (mit dessen Einverständnis ich das hier verwende ;-)):
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: }
Der folgende Ausdruck ist aus einem Kundenprojekt (anonymisiert, um die Schuldigen zu schützen):
1: return (from item in repository.ServersThisApplicationDependsOn
2: where (item.CONSUMING_APPLICATION == applicationId) && (includeWithdrawn || (item.DEPENDENCY_STATUS != WithDrawnIdentifier))
3: orderby item.SERVER_NAME
4: select item).AsEnumerable().Select(item => new
5: {
6: item.CONSUMING_APPLICATION,
7: item.SERVER_NAME,
8: item.DEPENDENCY_STATUS,
9: item.SERVER_STATUS,
10: item.SERVER_OS,
11: item.SERVER_LOCATION,
12: item.SERVER_FQDN,
13: LATEST_TARGET_HARDWARE_REMOVAL_DATE = GetFormatedNullableDateTime(item.LATEST_TARGET_HARDWARE_REMOVAL_DATE)
14: });
Dito der Code aus der Suchseite einer Anwendung:
1: var condition = LinqHelper.True<EntityModel.Transaction>();
2: var conditionBalance = condition.And(t => (((searchCriteria.MinimumAmount == null) || (t.Amount >= searchCriteria.MinimumAmount)) &&
3: ((searchCriteria.MaximumAmount == null) || (t.Amount <= searchCriteria.MaximumAmount))));
4: var conditionDate = conditionBalance.And(t => (((searchCriteria.StartDate == null) || (t.Date > searchCriteria.StartDate.Value.AddDays(-1))) &&
5: ((searchCriteria.EndDate == null) || (t.Date < searchCriteria.EndDate.Value.AddDays(1)))));
6: string transactionType = GetTransactionType(searchCriteria.CashTransactionType);
7: var conditionTransactionType = conditionDate.And(t => ((searchCriteria.CashTransactionType == TransactionType.All) || (transactionType == t.Type)));
8:
9: result = entities.Transaction
10: .Join(
11: entities.Account.Where<Account>(ac => ac.AccountID == accountId),
12: t => t.Account.AccountID,
13: a => a.AccountID,
14: (t, a) => t)
15: .Where(conditionTransactionType.Compile());
Zur schlechten Lesbarkeit kommen hier noch Bugs.
Beispiele für schlecht lesbaren LINQ-Code gibt es also zuhauf. Bei Guidelines, wie man das verbessern kann, wird es hingegen dünn:
- Es gibt einige wenige StyleCop-Regeln (SA1102-SA1105)
- C# Coding Conventions (C# Programming Guide) hat im Abschnitt “LINQ Queries” ein paar sehr grundlegende Ratschläge, kratzt damit aber nur an der Spitze des Eisbergs.
- Die 101 LINQ Samples folgen einer einheitlichen Formatierung, ohne diese allerdings zu beschreiben. Außerdem sind sie als Beispielcode nicht immer der beste Ratgeber für Implementierungen in einem Projekt, da sie sich üblicherweise auf genau einen Aspekt konzentrieren.
Dummerweise gibt es auch schlechte Ratschläge, etwa:
- Die C# Coding Guidelines von Aviva, die deferred execution kaputt machen (AV1250), und die den Unterschied zwischen Query und Method Syntax nicht verstanden haben (AV2220).
Jetzt wäre es an der Zeit, dass ich mit Coding Guidelines für LINQ dagegenhalte. Allerdings sprechen zwei Dinge gegen dieses Vorgehen:
- Der Beitrag wird zu lang 😉
- Ich habe keine schlüsselfertigen Coding Guidelines. (Sorry!)
Was ich aber habe sind eine ganze Reihe von Hinweisen, wie man es besser machen kann. Auslöser sind dabei immer reale Probleme, die mir in Reviews untergekommen sind.
DISCLAIMER: Die Beispiele, die ich hier und in den folgenden Beiträgen zeige, entstammen – soweit nicht anders angegeben – realen Projekten. Sie wurden von mir lediglich anonymisiert (um die Schuldigen zu schützen ;-)) und ggf. gekürzt, wenn es der Sache dient.
Wer das in seine Coding Guidelines einfließen lassen will ist natürlich willkommen.
Sie sehen gerade einen Platzhalterinhalt von Facebook. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von Instagram. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr InformationenSie sehen gerade einen Platzhalterinhalt von X. Um auf den eigentlichen Inhalt zuzugreifen, klicken Sie auf die Schaltfläche unten. Bitte beachten Sie, dass dabei Daten an Drittanbieter weitergegeben werden.
Mehr Informationen