Проверка типов примитивных типов значений .NET с помощью пользовательских структур: стоит ли усилий? - PullRequest
14 голосов
/ 07 августа 2011

Я согласен с идеей сделать примитивные типы значений .NET более безопасными по типу и более «самодокументируемыми», обернув их в пользовательские struct s. Тем не менее, мне интересно, стоит ли когда-либо усилий в реальном программном обеспечении.

(Это «усилие» можно увидеть ниже: необходимость применять один и тот же шаблон кода снова и снова. Мы объявляем struct s и поэтому не можем использовать наследование для удаления повторения кода; перегруженные операторы должны быть объявлены static, они должны быть определены для каждого типа отдельно.)

Возьмите этот (по общему признанию тривиальный) пример:

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;
}

И Area, и Length в основном являются double, но дополняют его определенным значением. Если вы определили метод, такой как & hellip;

    Area CalculateAreaOfRectangleWith(Length width, Length height)

& hellip; невозможно было бы напрямую перейти в Area случайно. Пока все хорошо.

НО: Вы можете легко обойти эту явно улучшенную безопасность типов, просто приведя Area к double или временно сохранив Area в переменной double, а затем передав что в метод, где ожидается Length:

    Area a = 10.0;

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

Вопрос: Кто-нибудь здесь имеет опыт широкого использования таких пользовательских struct типов в реальном / производственном программном обеспечении? Если так:

  • Приносит ли когда-либо перенос типов примитивных значений в пользовательские struct s непосредственно меньше ошибок, или более поддерживаемый код, или дает какие-либо другие основные преимущества?

  • Или преимущества пользовательских struct s слишком малы, чтобы их можно было использовать на практике?


P.S .: Прошло около 5 лет с тех пор, как я задал этот вопрос. Я публикую некоторые из моих опытов, которые я сделал с тех пор как отдельный ответ.

Ответы [ 5 ]

11 голосов
/ 07 августа 2011

Я сделал это в проекте пару лет назад, с некоторыми смешанными результатами. В моем случае это очень помогло отслеживать различные типы идентификаторов и иметь ошибки во время компиляции, когда использовался неправильный тип идентификаторов. И я могу вспомнить ряд случаев, когда это предотвращало фактические ошибки в создании. Итак, это была плюс сторона. С другой стороны, это было не очень интуитивно понятно для других разработчиков - такой подход не очень распространен, и я думаю, что другие разработчики запутались со всеми этими новыми типами, возникающими. Кроме того, я помню, что у нас были некоторые проблемы с сериализацией, но я не могу вспомнить детали (извините).

Так что, если вы собираетесь пойти по этому пути, я бы порекомендовал пару вещей:

1) Обязательно сначала поговорите с другими людьми в вашей команде, объясните, что вы пытаетесь достичь, и посмотрите, сможете ли вы получить «бай-ин» от всех. Если люди не понимают ценности, вы будете постоянно бороться с менталитетом «в чем смысл всего этого дополнительного кода»?

2) Подумайте, сгенерируйте свой шаблонный код, используя такой инструмент, как T4. Это значительно облегчит обслуживание кода. В моем случае у нас было около дюжины таких типов, и путь создания кода позволил сделать изменения намного проще и намного менее подвержен ошибкам.

Это мой опыт. Удачи!

John

3 голосов
/ 07 августа 2011

Это логический следующий шаг к венгерской нотации, см. Отличную статью от Джоэля здесь http://www.joelonsoftware.com/articles/Wrong.html

Мы сделали нечто подобное в одном проекте / API, где esp. другим разработчикам нужно было использовать некоторые из наших интерфейсов, и у них было значительно меньше «случаев поддержки ложных тревог» - потому что это действительно показало, что было разрешено / необходимо ... Я подозреваю, что это означает значительно меньше ошибок, хотя мы никогда не делали статистику, потому что Получающиеся приложения не были нашими ...

2 голосов
/ 18 сентября 2016

в ок.5 лет с тех пор, как я задавал этот вопрос, я часто играл с определением типов значений struct, но редко делал это.Вот несколько причин, по которым:

  • Не так уж много, что их выгода слишком мала, но стоимость определения типа значения struct слишком высока.Здесь используется много шаблонного кода:

    • Переопределить Equals и реализовать IEquatable<T>, а также переопределить GetHashCode;
    • Операторы реализации == и !=;
    • Возможно реализовать IComparable<T> и операторы <, <=, > и >=, если тип поддерживает упорядочение;
    • Переопределить ToString и реализовать методы преобразования и type-приведение операторов feom / к другим связанным типам.

    Это просто много повторяющихся работ, которые часто не нужны при непосредственном использовании базовых примитивных типов.

  • Часто разумное значение по умолчанию отсутствует (например, для типов значений, таких как почтовые индексы, даты, время и т. Д.).Поскольку нельзя запретить конструктор по умолчанию, определение таких типов, как struct, является плохой идеей, а определение их как class означает некоторые накладные расходы времени выполнения (больше работы для GC, больше разыменования памяти и т. Д.)

  • На самом деле я не обнаружил, что упаковка примитивного типа в семантический struct действительно дает значительные преимущества в отношении безопасности типов;IntelliSense / завершение кода и хороший выбор имен переменных / параметров могут принести большую часть тех же преимуществ, что и специализированные типы значений.С другой стороны, как показывает другой ответ, пользовательские типы значений могут быть неинтуитивными для некоторых разработчиков, поэтому при их использовании возникают дополнительные когнитивные издержки.

В некоторых случаях я заканчивалоборачивать примитивный тип в struct, обычно, когда для них определены определенные операции (например, DecimalDegrees и Radians с методами для преобразования между ними). ​​

Определение методов равенства и сравненияс другой стороны, это не обязательно означает, что я бы определил пользовательский тип значения.Вместо этого я мог бы использовать примитивные типы .NET и предоставить вместо них хорошо названные реализации IEqualityComparer<T> и IComparer<T>.

1 голос
/ 07 августа 2011

Я не часто использую структуры.Одна вещь, которую вы можете рассмотреть, это заставить ваш тип требовать измерения.Учтите это:

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; } }
}

Обратите внимание, что ничто не может быть создано из одного двойника.Размер должен быть указан.Мои объекты неизменны.Требование измерения и его проверка предотвратили бы катастрофу Mars Express.

0 голосов
/ 07 августа 2011

По своему опыту не могу сказать, хорошая это идея или нет.Это, безусловно, имеет свои плюсы и минусы.Плюсом является то, что вы получаете дополнительную дозу безопасности типа.Зачем принимать двойное значение для угла, если вы можете принять тип, который имеет истинную семантику угла (градусы к радианам / от радиан, ограниченные значениями градусов 0-360 и т. Д.).Con, не распространенный, поэтому может сбить с толку некоторых разработчиков.

См. Эту ссылку для реального примера реального коммерческого продукта, который имеет несколько типов, как вы описываете:

http://geoframework.codeplex.com/

Типы включают Угол, Широта, Долгота, СкоростьРасстояние.

...