Czy C # staje się trudniejszy do odczytania?

15

W miarę postępu C # dodano wiele funkcji językowych. Doszło do tego, że stało się dla mnie nieczytelne.

Jako przykład rozważ następujący fragment kodu z kodu Caliburn.Micro tutaj :

            container = CompositionHost.Initialize(
                   new AggregateCatalog(
                      AssemblySource.Instance.
                             Select(x => new AssemblyCatalog(x))
                               .OfType<ComposablePartCatalog>()
                )
            );

To tylko mały przykład.

Mam kilka pytań:

  • Czy to powszechny czy znany problem?
  • Czy społeczność C # znajduje to samo?
  • Czy to jest problem z językiem, czy to styl używany przez programistę?

Czy są jakieś proste rozwiązania pozwalające lepiej zrozumieć kod innych użytkowników i uniknąć pisania kodu w ten sposób?

Steven Evers
źródło
6
Jest to funkcja lambda, trudno je odczytać, dopóki naprawdę ich nie dostaniesz.
Ryathal
9
Myślę, że tak naprawdę nie znałeś składni tak dobrze, jak myślałeś. Dzięki praktyce czytanie Twojego przykładu jest proste.
ChaosPandion
6
@Thomas Owens: Cholera jasna, daj nam przynajmniej trochę czasu na edycję pytań, aby były otwarte. 15 minut? Chodź teraz.
Steven Evers
4
@DannyVarod: 1. „Nie” nie jest akceptowalną odpowiedzią 2. Pytanie jest zamknięte 3. Jeśli zgadzasz się, że nie powinno tu być pytania, zaznacz flagę / głosuj, aby je zamknąć lub edytować, aby było lepiej .
Steven Evers
2
@ Thorbjørn Ravn Andersen - Czego można się nauczyć z klapsa? Nie zawiera żadnych informacji!

Odpowiedzi:

12

Szybka uwaga na temat tego, gdzie jest język, powinna to wyjaśnić: C # jest językiem programowania ogólnego przeznaczenia ; w przeciwieństwie do C (jak C ++) dąży do wysokiej abstrakcji; w przeciwieństwie do dialektów Lisp ma praktyczną ekspresję, a w przeciwieństwie do Javy jest bardziej agresywnie napędzany - Microsoft szybko reaguje na popyt.

Właśnie dlatego zamienia się w mieszankę LINQ, lambd i dziwnych nowych słów kluczowych - szybko dostosowuje się do nowych domen problemowych i jest to rzeczywiście śliskie nachylenie w kierunku języka tak złożonego, że tylko nieliczni potrafią go poprawnie używać (jak C ++). To nie jest problem z samym C #, to problem z dowolnym językiem z tymi ambitnymi celami.

Społeczność zdaje sobie z tego sprawę, a co ważniejsze, osoby stojące za C # są tego w pełni świadome (kilka wpisów na blogu i podcastów na temat tego, co było za nowymi dodatkami w C # 5.0, pokazuje, jak źle ci faceci chcą uprościć sprawę). Microsoft jest próbuje przejąć część obciążenia od ich flagowego taki sposób, że nie stanie się Tarpit: wprowadzenie DLR, jasnym świetle reflektorów na nowe języki (F #).

Co więcej, materiały szkoleniowe na temat C # (w tym certyfikaty MS) zalecają różne (ale spójne) style użycia C # w zależności od problemu - wybierz broń i trzymaj się tego: płynne LINQ w przypadku niektórych problemów, lambdas z TPL w innych, zwykły - stary dla większości.

vski
źródło
2
Czy potrafisz wyjaśnić, co masz na myśli mówiąc „w przeciwieństwie do dialektów Lispa, ma on praktyczną ekspresję”?
Giorgio
27

Nie. C # daje nam więcej możliwości bardziej zwięzłego pisania kodu. Można to wykorzystać lub wykorzystać. Prawidłowe użycie sprawia, że ​​kod jest łatwiejszy do odczytania. Niewłaściwe użycie może utrudnić odczytanie kodu. Ale źli programiści zawsze mieli umiejętność pisania trudnego do odczytania kodu.

Weźmy dwa przykłady, oba w oparciu o przykłady znalezione w StackOverflow dotyczące tego samego problemu, znajdując typy o określonym atrybucie:

C # 2.0:

static IEnumerable<Type> GetTypesWithAttribute<TAttribute>(bool inherit) 
                              where TAttribute: System.Attribute
{
    foreach(Assembly assembly in AppDomain.Current.GetAssemblies())
    {
        foreach(Type type in assembly.GetTypes()) 
        {
            if (type.IsDefined(typeof(TAttribute),inherit))
                yield return type;
        }
    }
}

C # 4.0:

IEnumerable<Type> GetTypesWith<TAttribute>(bool inherit) 
                              where TAttribute: System.Attribute
{ 
    return from assembly in AppDomain.CurrentDomain.GetAssemblies()
           from type in assembly.GetTypes()
           where type.IsDefined(typeof(TAttribute),inherit)
           select type;
}

IMHO, nowa składnia znacznie ułatwia czytanie.

Pete
źródło
nowa składnia jest rzeczywiście łatwiejsza do odczytania, ale łatwiejsza do zrozumienia, co dzieje się pod maską? Pierwszy przykład jest zrozumiały, drugi bardziej czytelny.
nawfal
4
@nawfal Z mojego doświadczenia wynika, że ​​ludzie mają o wiele więcej problemów ze zrozumieniem konstrukcji „return return” niż instrukcje linq; więc twierdzę, że drugi jest bardziej czytelny i zrozumiały. Żadne z nich tak naprawdę nie mówi ci, co się dzieje pod maską, ponieważ obie mapy odwzorowują abstrakcje dalekie od tego, jak faktycznie działają procesory.
Chuu,
Szczerze mówiąc, nie lubię LINQ, ponieważ abstrahuje pętle i zachęca programistów do pobierania danych w osobnych zapytaniach LINQ (z oddzielnymi pętlami w tle) zamiast jednej pętli z bardziej skomplikowanym rdzeniem.
mg30rg
8

Kiedy dodano LINQ, spopularyzował on styl kodowania obejmujący wiele metod łączenia w stylu Fluent i przekazywanie lambdów jako parametrów.

Ten styl jest bardzo mocny, gdy czujesz się dobrze. Jednak może to być nadużywane do kodu dość nieczytelny. Nie sądzę, aby twój przykład był taki zły, chociaż wcięcie jest trochę losowe (i jest to wspólna cecha tego stylu kodu, Visual Studio nie wcina go zbyt konsekwentnie).

W moim miejscu pracy omawialiśmy ten styl kodowania podczas przeglądu naszych standardów kodowania na początku tego roku i postanowiliśmy zachęcić deweloperów do zerwania takiego kodu: w szczególności, aby nie tworzyć ani inicjować anonimowych obiektów wewnątrz wywołania funkcji. Twój kod stałby się:

var assemblyCatalog = AssemblySource.Instance
    .Select(x => new AssemblyCatalog(x))
    .OfType<ComposablePartCatalog>();
var aggregateCatalog = new AggregateCatalog(assemblyCatalog);
container = CompositionHost.Initialize(aggregateCatalog);
Carson63000
źródło
1
Czy to nie dziwne, że tworzysz nowy typ, a następnie dynamicznie włączasz ten typ zaraz po nim?
DeadMG,
Prawdopodobnie właśnie pociąłem fragment kodu, nie biorąc pod uwagę jego intencji. Chciałem tylko podzielić wszystkie instancje na własne wiersze kodu, aby pokazać, jaki wpływ miałoby to na wygląd kodu.
Carson63000,
Winię za wcięcie :(). Oto oryginał: devlicio.us/blogs/rob_eisenberg/archive/2010/07/08/…
1
W pełni zgadzam się z tą odpowiedzią. Problemem w przykładowym fragmencie kodu nie jest nowa składnia C #, to, że pisarz próbował być sprytny za pomocą kodu „one-liner”. Jest taka stara zasada, która pochodzi z UNIX: wszystko powinno robić tylko jedną rzecz i robić to dobrze. Odnosi się do klasy, odnosi się do metody i, oczywiście, z pewnością dotyczy wierszy kodu.
Laurent Bourgault-Roy,
4

Kod w twoim przykładzie nie jest łatwy do odczytania, ponieważ

  • Łączy wiele pojęć (Kompozycja, Katalogi, Agregaty, Złożenia, Części Composable ...) z kilkoma poziomami zagnieżdżania, podczas gdy kod wywołujący powinien zazwyczaj zajmować się tylko jednym poziomem. Wygląda trochę jak naruszenie Prawa Demetera tylko w przypadku zagnieżdżonych wywołań metod zamiast łańcucha podrzędnych właściwości. To nieco zaburza intencje stojące za linią kodu.

  • Dziwne jest użycie singletonu - AssemblySource.Instance.Select()oznacza to, że Instancja jest IEnumerable, co jest nieco niezręczne.

  • Zmienna x w wyrażeniu lambda jest brzydka. Zasadniczo próbujesz nadać swoim zmiennym lambda intencje ujawniające nazwy - pomaga czytelnikowi określić, jakiego rodzaju danych dotyczy ta funkcja, nawet jeśli nie zna lambdas.

  • Nie jest jasne, dlaczego należy filtrować za OfType<T>()pomocą kolekcji obiektów, które właśnie odkryłeś ...

Jednak wszystkie te wady dotyczą sposobu, w jaki pierwotny programista napisał kod i tego, jak wyrazisty go zaprojektował. Nie jest to coś specyficznego dla najnowszych wersji C # i wyglądu wyrażeń lambda.

Mówiąc bardziej ogólnie, pomaga to, jeśli czytelnik twojego kodu wie, jak działają lambdas, ale przy wystarczająco jasnym nazewnictwie prawie zawsze udaje ci się sprawić, że kod jest czytelny nawet dla osób z wcześniejszym niż .NET 3.5.

guillaume31
źródło
2

Za każdym razem, gdy nowa wersja popularnego języka zyskuje nowe konstrukcje, pojawiają się podobne debaty.

Miałem te wątpliwości także wiele lat temu (http://gsscoder.blogspot.it/2009/08/use-csharps-features-wisely.html).

Moim zdaniem C # ewoluuje w elegancki i konsekwentny sposób.

Kod w twojej próbce nie jest złożony, być może nie używasz wyrażeń lamda.

Problem polega na tym, że C # to nie tylko język zorientowany obiektowo, ale obsługuje również konstrukcje funkcjonalne (http://archive.msdn.microsoft.com/FunctionalCSharp/).

gsscoder
źródło
1

Trochę czasu zajęło mi zrozumienie składni Lambda, a ja jestem z matematyki i fizyki. Jest trochę podobny do wyrażeń matematycznych, mimo że używają -> zamiast =>:

Przykład matematyki f (x): = x-> x + 2 oznacza to, że „Funkcja f of x jest zdefiniowana jako x map na x + 2

c # przykład del myDelegate = x => x +2; to brzmi „myDelegate to funkcja taka, że ​​myDelegate (x) mapuje x na x + 2

Niespójność między matematyką a programowaniem tak naprawdę nie pomaga, chociaż przypuszczam, że -> zostało już uwzględnione w C ++ jako „właściwość” (urrgghh!)

http://en.wikipedia.org/wiki/List_of_mathematical_symbols

Andy R.
źródło
„chociaż przypuszczam, że -> został już uwzględniony w C ++ jako„ własność ”(urrgghh!)„ Jakby! Słabo w C ++ oznacza „właściwość dynamicznie przydzielanego ...”. Jeśli coś nie jest dynamiczne, używaj kropki jak w każdym innym języku.
mg30rg