Najlepsza praktyka oznaczania metody, która jest wywoływana przez odbicie?

11

Nasze oprogramowanie ma kilka klas, które należy dynamicznie znaleźć poprzez odbicie. Wszystkie klasy mają konstruktor z określoną sygnaturą, za pośrednictwem której kod odbicia tworzy instancję obiektów.
Jednak gdy ktoś sprawdzi, czy metoda jest przywołana (na przykład za pomocą soczewki Visual Studio Code Lens), referencja za pomocą odbicia nie jest liczona. Ludzie mogą przegapić swoje referencje i usunąć (lub zmienić) najwyraźniej nieużywane metody.

Jak powinniśmy oznaczać / dokumentować metody, które mają być wywoływane poprzez refleksję?

Najlepiej byłoby, gdyby metoda została oznaczona w taki sposób, aby zarówno współpracownicy, jak i Visual Studio / Roslyn i inne zautomatyzowane narzędzia „zobaczyli”, że metoda ma zostać wywołana poprzez odbicie.

Znam dwie opcje, których możemy użyć, ale obie nie są całkiem satysfakcjonujące. Ponieważ Visual Studio nie może znaleźć referencji:

  • Użyj niestandardowego atrybutu i oznacz konstruktorem tym atrybutem.
    • Problem polega na tym, że właściwości atrybutu nie mogą być odwołaniem do metody, dlatego konstruktor nadal będzie pokazywał, że ma 0 odwołań.
    • Koledzy nieznający atrybutu niestandardowego prawdopodobnie go zignorują.
    • Zaletą mojego obecnego podejścia jest to, że część refleksyjna może użyć atrybutu do znalezienia konstruktora, który powinien wywołać.
  • Użyj komentarzy, aby udokumentować, że metoda / konstruktor ma być wywoływany przez odbicie.
    • Zautomatyzowane narzędzia ignorują komentarze (i koledzy też mogą to robić).
    • Komentarze do dokumentacji XML można wykorzystać, aby Visual Studio zliczało dodatkowe odwołanie do metody / konstruktora:
      Niech MyPluginbędzie klasą, której konstruktor może wywoływać poprzez odbicie. Załóżmy, że wywołujący kod refleksyjny szuka konstruktorów, które pobierają intparametr. Poniższa dokumentacja pokazuje, że soczewka kodu pokazuje konstruktor mający 1 referencję:
      /// <see cref="MyPlugin.MyPlugin(int)"/> is invoked via reflection

Jakie są lepsze opcje?
Jaka jest najlepsza praktyka oznaczania metody / konstruktora, który ma zostać wywołany przez odbicie?

Kasper van den Berg
źródło
Żeby było jasne, dotyczy to jakiegoś systemu wtyczek, prawda?
whatsisname
2
Zakładasz, że twoi współpracownicy będą ignorować lub przegapić wszystko, co robisz ... Nie możesz zapobiec takiemu nieskuteczności kodu w pracy. Dokumentowanie wydaje mi się łatwiejsze, czystsze, tańsze i wskazane. W przeciwnym razie programowanie deklaratywne nie istniałoby.
Laiv
1
Resharper ma atrybut [UsedImplictly].
CodesInChaos
4
Wydaje mi się, że opcja komentarza do dokumentu Xml jest prawdopodobnie najlepszą opcją. Jest krótki, samokumentujący i nie wymaga żadnych „hacków” ani dodatkowych definicji.
Doc Brown
2
Kolejny głos na komentarze do dokumentacji XML. Jeśli i tak tworzysz dokumentację, powinna ona się wyróżniać w wygenerowanej dokumentacji.
Frank Hileman

Odpowiedzi:

12

Połączenie sugerowanych rozwiązań:

  • Użyj znaczników Dokumentacja XML, aby udokumentować, że konstruktor / metoda jest wywoływana przez odbicie
    To powinno wyjaśnić zamierzone użycie kolegom (i mojemu przyszłemu ja).
  • Użyj „ <see>lewy ” za pomocą -tag, aby zwiększyć liczbę referencji dla konstruktora / metody.
    To sprawia, że ​​kod obiektywu i referencje wyszukiwania wskazują, że odwołuje się do konstruktora / metody.
  • Adnotuj za pomocą Resharpera UsedImplicitlyAttribute
    • Resharper jest de facto standardem i [UsedImplicitly]ma dokładnie zamierzoną semantykę.
    • Osoby nie korzystające z Resharper mogą zainstalować Adnotacje ReSharper JetBrains za pośrednictwem NuGet:
      PM> Install-Package JetBrains.Annotations.
  • Jeśli jest to metoda prywatna i korzystasz z analizy kodu Visual Studio, użyj SupressMessageAttributetej wiadomości CA1811: Avoid uncalled private code.

Na przykład:

class MyPlugin
{
    /// <remarks>
    /// <see cref="MyPlugin.MyPlugin(int)"/> is called via reflection.
    /// </remarks>
    [JetBrains.Annotations.UsedImplicitly]
    public MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }

    /// <remarks>
    /// <see cref="MyPlugin.MyPlugin(string)"/> is called via reflection.
    /// </remarks>
    [JetBrains.Annotations.UsedImplicitly]
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(string arg)
    {
        throw new NotImplementedException();
    }
}

Rozwiązanie zapewnia zamierzone użycie konstruktora zarówno ludzkim czytelnikom, jak i 3 systemom analizy kodu statycznego najczęściej używanym w C # i Visual Studio.
Minusem jest to, że zarówno komentarz, jak i jedna lub dwie adnotacje mogą wydawać się nieco zbędne.

Kasper van den Berg
źródło
Zauważ, że istnieje również MeansImplicitUseAttributemożliwość tworzenia własnych atrybutów, które działają UsedImplicitly. Może to zmniejszyć wiele szumów atrybutów w odpowiednich sytuacjach.
Dave Cousineau,
Link do JetBrains jest zepsuty.
John Zabroski
5

Nigdy nie miałem tego problemu w projekcie .Net, ale regularnie mam ten sam problem z projektami Java. Moim zwykłym podejściem jest użycie @SuppressWarnings("unused")adnotacji z komentarzem wyjaśniającym dlaczego (udokumentowanie przyczyny wyłączenia ostrzeżeń jest częścią mojego standardowego stylu kodu - za każdym razem, gdy kompilator nie może czegoś wymyślić, zakładam, że człowiek może się zmagać też). Ma to tę zaletę, że automatycznie zapewnia, że ​​narzędzia analizy statycznej są świadome, że kod nie powinien zawierać bezpośrednich odniesień, i podaje szczegółowy powód dla czytelników.

Odpowiednikiem C # języka Java @SuppressWarningsjest SuppressMessageAttribute. W przypadku metod prywatnych możesz użyć komunikatu CA1811: Unikaj nieprzywołanego prywatnego kodu ; na przykład:

class MyPlugin
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
        "Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
        Justification = "Constructor is called via reflection")]
    private MyPlugin(int arg)
    {
        throw new NotImplementedException();
    }
}
Jules
źródło
(Nie wiem, ale zakładam, że CLI obsługuje atrybut podobny do Java, o którym wspomniałem; jeśli ktoś wie, co to jest, proszę edytować moją odpowiedź jako odniesienie do niego ...)
Jules
stackoverflow.com/q/10926385/5934037 wygląda jak to.
Laiv
1
Niedawno odkryłem, że wykonywanie testów i mierzenie zasięgu (w java) to dobry sposób na sprawdzenie, czy blok kodu jest naprawdę nieużywany. Następnie mogę go usunąć lub sprawdzić, dlaczego jest nieużywany (jeśli oczekuję czegoś przeciwnego). Podczas wyszukiwania zwracam większą uwagę na komentarze.
Laiv
Istnieje SuppressMessageAttribute( msdn.microsoft.com/en-us/library/… ). Najbliższym komunikatem jest CA1811: Avoid uncalled private code( msdn.microsoft.com/en-us/library/ms182264.aspx ); Nie znalazłem jeszcze wiadomości dla kodu publicznego.
Kasper van den Berg
2

Alternatywą dla dokumentacji byłoby przeprowadzenie testów jednostkowych upewniających się, że wywołania odbicia działają poprawnie.

W ten sposób, jeśli ktoś zmieni lub usunie metody, proces kompilacji / testowania powinien ostrzec cię, że coś zepsułeś.

Nick Williams
źródło
0

Cóż, nie widząc twojego kodu, brzmi to tak, jakby to było dobre miejsce do wprowadzenia dziedziczenia. Może wirtualna lub abstrakcyjna metoda, którą może wywołać konstruktor tych klas? Jeśli jesteś metodą, którą próbujesz oznaczyć, jest tylko konstruktorem, to naprawdę próbujesz oznaczyć klasę, a nie metodę, tak? Coś, co robiłem w przeszłości, aby zaznaczyć klasy, to zrobić pusty interfejs. Następnie narzędzia do inspekcji kodu i refaktoryzacji mogą szukać klas implementujących interfejs.

Jeff Sall
źródło
Prawdziwym normalnym dziedzictwem byłaby droga. A klasy są rzeczywiście powiązane przez dziedziczenie. Ale stworzenie nowej instancji poza dziedziczeniem. Poza tym polegam na „dziedziczeniu” metod statycznych, a ponieważ C # nie obsługuje gniazd klas; istnieje sposób, który wykorzystuję, ale wykracza to poza zakres tego komentarza.
Kasper van den Berg