2011-08-07 14 views
14

Mam do czynienia z ideą uczynienia prymitywnych typów wartości .NET bardziej bezpiecznymi i bardziej "samokomponującymi" poprzez zawinięcie ich w niestandardowe struct s. Zastanawiam się jednak, czy w rzeczywistości jest to warte wysiłku w prawdziwym oprogramowaniu.Typowe prymitywne typy wartości .NET za pomocą niestandardowych struktur: czy to jest warte wysiłku?

(That „wysiłek” można zobaczyć poniżej. Mając ponownie i ponownie zastosować ten sam kod wzoru Jesteśmy deklarując struct s, a więc nie można użyć dziedziczenia do usunięcia kodu powtórzenia, a od przeciążonych operatorów należy uznać za static, muszą być określone dla każdego typu osobno)

Weź to (co prawda trywialne) przykład:.

struct Area 
{ 
    public static implicit operator Area(double x) { return new Area(x); } 
    public static implicit operator double(Area area) { return area.x; } 

    private Area(double x) { this.x = x; } 
    private readonly double x; 
} 

struct Length 
{ 
    public static implicit operator Length(double x) { return new Length(x); } 
    public static implicit operator double(Length length) { return length.x; } 

    private Length(double x) { this.x = x; } 
    private readonly double x; 
} 

Zarówno Area, jak i Length są w zasadzie double, ale rozszerzają je o określone znaczenie. Jeśli zdefiniowałeś metodę taką jak & hellip;

Area CalculateAreaOfRectangleWith(Length width, Length height) 

& hellip; nie byłoby możliwe, aby bezpośrednio przejść w Area przez przypadek. Jak na razie dobrze.

ALE: można łatwo ominąć to najwyraźniej poprawiła bezpieczeństwo typu po prostu przez odlewanie Area do double lub poprzez tymczasowe przechowywanie e Area w zmiennej double, a następnie przechodząc że do metody, gdzie oczekiwany jest Length:

Area a = 10.0; 

    double aWithEvilPowers = a; 
    … = CalculateAreaOfRectangleWith((double)a, aWithEvilPowers); 

Pytanie: Czy ktoś tutaj ma doświadczenie z szerokim wykorzystaniem takiego zwyczaju struct typów w świecie rzeczywistym/software produkcja? Jeśli tak, to:

  • Czy owijanie w prymitywnych typów wartości niestandardowych struct s kiedykolwiek bezpośrednio spowodowało mniej błędów, lub w kodzie bardziej zarządzalny, czy dana żadnego innego znaczącą przewagę (y)?

  • A może korzyści z niestandardowych struct są zbyt małe, aby można je było wykorzystać w praktyce?


P.S .: Około 5 lat minęło od czasu, kiedy zadał to pytanie. Zamieszczam some of my experiences that I've made since then jako osobną odpowiedź.

+1

** P.S.: ** Jestem świadomy, że jeden może być w stanie pozbyć się jednej z dwóch opisanych technik sidestepping, definiując 'Area -> double' cast operator 'explicit' zamiast' implicit'. – stakx

+0

Prawdopodobnie nie myślę wystarczająco duże, ale czy zauważyłeś, że ten rodzaj błędu pojawia się bardzo często dla Ciebie? To znaczy - kiedy masz dane z zewnętrznego źródła (być może bez typu) i do twojego programu, będzie ono w jakiejś strukturze, w której dostęp do niej ma pewne imię, które ma sens jak "object.length". Zazwyczaj nie kończysz na podawaniu liczb surowych, ale raczej przekazujesz struktury. Pomyślałbym, że ten rodzaj błędu jest rzadki, nie mogę myśleć o tym, by mnie wymyślić. –

+0

Podejrzewam, że ten typ błędu nie zdarza mi się często, @jamietre, ale nie mam żadnych twardych statystyk do poparcia lub odrzucenia tego założenia. Istnieją jednak inne potencjalne korzyści; łatwiejszy do zrozumienia kod może być jednym z nich. Po prostu nie jestem pewien z góry, jeśli to uzasadnia konieczność implementacji wielu bardzo podobnych typów 'struct' ... – stakx

Odpowiedz

11

Zrobiłem to w projekcie kilka lat temu, z kilkoma mieszanymi rezultatami.W moim przypadku bardzo pomogło śledzić różne rodzaje identyfikatorów i mieć błędy podczas kompilacji, gdy używany był niewłaściwy typ identyfikatorów. I pamiętam wiele okazji, w których zapobiegał on rzeczywistym powstawaniu błędów. Tak, to była strona plusowa. Z drugiej strony nie było to zbyt intuicyjne dla innych deweloperów - takie podejście nie jest zbyt powszechne i myślę, że inni deweloperzy wpadli w zakłopotanie, gdy pojawiły się nowe typy. Poza tym wydaje mi się, że mieliśmy problemy z serializacją, ale nie pamiętam szczegółów (przepraszam).

Więc jeśli masz zamiar pójść tą drogą, polecam kilka rzeczy:

1) Upewnij się, rozmawiać z innymi ludźmi w zespole najpierw wyjaśnić, co starasz się osiągnąć i sprawdź, czy możesz uzyskać "buy-in" od wszystkich. Jeśli ludzie nie zrozumieją wartości, będziecie ciągle walczyć z mentalnością "jaki jest cel tego dodatkowego kodu"?

2) Rozważ wygeneruj swój standardowy kod za pomocą narzędzia takiego jak T4. Ułatwi to utrzymanie kodu. W moim przypadku mieliśmy około tuzina takich typów, a pójście drogą generowania kodu spowodowało zmiany znacznie łatwiejsze i znacznie mniej podatne na błędy.

To moje doświadczenie. Powodzenia!

John

+0

Brzmi jak bardzo rozsądne podejście. +1 – stakx

+0

+1 za korzystanie z T4 - zaoszczędziło nam wiele pracy. Doskonały do ​​wytłaczania typów identyfikatorów i podobnych. I jak powiedział John, pomaga zapobiegać błędom. – Will

3

To jest logicznym krokiem do notacja węgierska, zobacz doskonały artykuł z Joelem tutaj http://www.joelonsoftware.com/articles/Wrong.html

Robiliśmy coś podobnego w jednym projekcie/API gdzie esp. inni programiści musieli używać niektórych naszych interfejsów i mieli znacząco mniej "przypadków fałszywego alarmu" - ponieważ to dało naprawdę oczywiste, co było dozwolone/potrzebne ... Podejrzewam, że oznacza to wymiernie mniej błędów, chociaż nigdy nie robiliśmy statystyk, ponieważ wynikające aplikacje nie były nasze ...

+0

+1, Czytałem ten artykuł ponad rok temu, ale nie zapamiętałbym go w kontekście mojego pytania. Ciekawe, aby myśleć o węgierskiej notacji w ten sposób. Dzięki! – stakx

1

Nie często używam structs. Jedną z rzeczy, którą możesz rozważyć, jest sprawienie, by twój typ wymagał wymiaru. Rozważmy to:

public enum length_dim { meters, inches } 
public enum area_dim { square_meters, square_inches } 
public class Area { 
    public Area(double a,area_dim dim) { this.area=a; this.dim=dim; } 
    public Area(Area a) { this.area = a.area; this.dim = a.dim; } 
    public Area(Length l1, Length l2) 
    { 
     Debug.Assert(l1.Dim == l2.Dim); 
     this.area = l1.Distance * l1.Distance; 
     switch(l1.Dim) { 
      case length_dim.meters: this.dim = square_meters;break; 
      case length_dim.inches: this.dim = square_inches; break; 
     } 
    } 
    private double area; 
    public double Area { get { return this.area; } } 
    private area_dim dim; 
    public area_dim Dim { get { return this.dim; } } 
} 
public class Length { 
    public Length(double dist,length_dim dim) 
    { this.distance = dist; this.dim = dim; } 
    private length_dim dim; 
    public length_dim Dim { get { return this.dim; } } 
    private double distance; 
    public double Distance { get { return this.distance; } } 
} 

Zauważ, że nic nie może być stworzone z podwójnego pojedynczego. Wymiar musi zostać określony. Moje obiekty są niezmienne. Wymaganie wymiaru i weryfikacja go uniemożliwiłoby katastrofę Mars Express.

+1

** 1) ** Śledzenie jednostek z pewnością byłoby powodem do stworzenia niestandardowego typu. Innym, bardziej ogólnym sposobem wykonania tego byłoby: 'class Scalar gdzie TUnit: IUnit {public Scalar (double value) {...}}' plus typ jednostki, np. 'class Meters: IUnit {...}'; następnie użyj go jak 'nowy Skalar (2.5)'. ** 2) ** Powodem, dla którego użyłem słowa 'struct' zamiast' class', wydaje się być właściwsze. Jest jedna wada: zawsze dostaje się c'tor bez parametrów. Są też zalety; na przykład Operator 'Is' VB.NET (' object.ReferenceEquals', który nie ma żadnego sensu dla wartości) nie będzie działał. – stakx

0

Nie mogę powiedzieć, z mojego doświadczenia, jeśli jest to dobry pomysł, czy nie. Na pewno ma swoje zalety i wady. Pro jest to, że dostajesz dodatkową dawkę bezpieczeństwa typu. Po co akceptować podwójne dla kąta, gdy można zaakceptować typ, który ma semantykę prawdziwego kąta (stopnie do/z radianów, ograniczone do wartości stopnia 0-360 itd.). Con, nie powszechne, więc może być mylące dla niektórych programistów.

Zobacz ten link na prawdziwy przykład prawdziwego produktu handlowego, które ma kilka rodzajów jak opisać:

http://geoframework.codeplex.com/

Rodzaje obejmują kątem, szerokość, długość, prędkość, dystans.

2

W ok. 5 lat, odkąd zadałem to pytanie, często bawiłem się definiowaniem typów wartości struct, ale rzadko to robiłem. Oto kilka powodów:

  • to nie jest tak dużo, że ich zaletą jest zbyt mały, ale to koszt definiująca typ struct wartość jest zbyt wysoka.Jest wiele standardowy kod zaangażowane:

    • Zastąp Equals i wdrażają IEquatable<T>, i zastąpić GetHashCode zbyt;
    • Implementacja operatorów == i !=;
    • Możliwe jest implementowanie i operatorów , <=, i >=, jeśli typ obsługuje zamawianie;
    • Zastąpienie ToString i zaimplementuj metody konwersji i operatorów rzutowania typu feom/do innych powiązanych typów.

    Jest to po prostu wiele powtarzalnych prac, które często nie są konieczne, gdy bezpośrednio korzysta się z podstawowych typów pierwotnych.

  • Często nie ma uzasadnionej wartości domyślnej (np. Dla typów wartości, takich jak kody pocztowe, daty, godziny itp.). Ponieważ nie można zabronić domyślnego konstruktora, określające takie typy jak struct jest to zły pomysł i definiowanie ich jako class oznacza niektórych napowietrznych Runtime (więcej pracy dla GC, więcej pamięci dereferencing, etc.)

  • I haven” t faktycznie stwierdził, że zawijanie typu pierwotnego w semantyczne struct naprawdę oferuje znaczące korzyści w odniesieniu do bezpieczeństwa typu; IntelliSense/code completion i dobry wybór nazw zmiennych/parametrów mogą przynieść wiele takich samych korzyści, jak wyspecjalizowane typy wartości. Z drugiej strony, jak sugeruje inna odpowiedź, niestandardowe typy wartości mogą być nieintuicyjne dla niektórych programistów, więc istnieje dodatkowe obciążenie poznawcze w ich używaniu.

Istnieją pewne przypadki, gdzie skończyło się owijając pierwotnych w struct zwykle, gdy istnieją pewne operacje określone na nich (na przykład typu z metodami DecimalDegrees i Radians przekształcenie między nimi).

Definiowanie metod równości i porównania nie musi oznaczać, że zdefiniowałem niestandardowy typ wartości. Zamiast tego mogę używać prymitywnych typów .NET i zamiast tego dostarczać dobrze nazwane implementacje IEqualityComparer<T> i IComparer<T>.

Powiązane problemy