Должны ли мы определить типы для всего?

141

Недавно у меня возникла проблема с читабельностью моего кода.
У меня была функция, которая выполняла операцию и возвращала строку, представляющую идентификатор этой операции, для дальнейшего использования (немного похоже на OpenFile в Windows, возвращающий дескриптор). Пользователь будет использовать этот идентификатор позже, чтобы начать операцию и контролировать ее завершение.

Идентификатор должен был быть случайной строкой из-за проблем совместимости. Это создало метод с очень непонятной сигнатурой, например:

public string CreateNewThing()

Это делает цель возвращаемого типа неясной. Я думал обернуть эту строку в другой тип, который делает ее значение более понятным, например, так:

public OperationIdentifier CreateNewThing()

Тип будет содержать только строку и использоваться всякий раз, когда эта строка используется.

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

Считаете ли вы хорошей практикой упаковывать простые типы в классе по соображениям безопасности?

Зив
источник
4
Вы можете быть заинтересованы в чтении о Tiny Types. Я немного поиграл с этим и не рекомендовал бы это, но думать об этом весело. darrenhobbs.com/2007/04/11/tiny-types
Джероен Ванневел
40
Как только вы используете что-то вроде Haskell, вы увидите, насколько помогает использование типов. Типы помогают обеспечить правильную программу.
Нейт Симер
3
Даже такой динамический язык, как Erlang, выигрывает от системы типов, поэтому у него есть система типов и инструмент проверки типов, знакомый Haskellers, несмотря на то, что он является динамическим языком. Такие языки, как C / C ++, где реальным типом чего-либо является целое число, которое мы согласовываем с компилятором для обработки определенным образом, или Java, где у вас почти наверняка есть типы, если вы решаете притворяться, что классы являются типами, представляют либо проблему семантической перегрузки в язык (это "класс" или "тип"?) или двусмысленность типа (это "URL", "строка" или "UPC"?). В конце концов, используйте все возможные инструменты для устранения неоднозначности.
zxq9
7
Я не уверен, откуда взялась идея, что в C ++ накладные расходы на типы. В C ++ 11 никто из создателей компиляторов не видел проблемы в предоставлении каждой лямбде своего уникального типа. Но TBH, вы не можете ожидать, что сможете найти много понимания в комментарии о «системе типов C / C ++», как будто они разделяют систему типов.
MSalters
2
@JDB - нет, это не так. Даже не похоже.
Давор Адрало

Ответы:

152

Примитивы, такие как stringили int, не имеют значения в бизнес-сфере. Прямым следствием этого является то, что вы можете по ошибке использовать URL, когда ожидается идентификатор продукта, или использовать количество, когда ожидаете цену .

Вот почему вызов Object Calisthenics включает в себя обертывание примитивов как одно из его правил:

Правило 3: оберните все примитивы и строки

В языке Java int является примитивным, а не реальным объектом, поэтому он подчиняется другим правилам, чем объекты. Он используется с синтаксисом, который не является объектно-ориентированным. Что еще более важно, int сам по себе является просто скаляром, поэтому он не имеет смысла. Когда метод принимает int в качестве параметра, имя метода должно выполнять всю работу по выражению намерения. Если тот же метод принимает Hour в качестве параметра, гораздо проще увидеть, что происходит.

В том же документе объясняется, что есть дополнительное преимущество:

Небольшие объекты, такие как «Час» или «Деньги», также дают нам очевидное место для поведения, которое в противном случае было бы засорено другими классами .

Действительно, когда используются примитивы, обычно чрезвычайно трудно отследить точное местоположение кода, связанного с этими типами, что часто приводит к серьезному дублированию кода . Если есть Price: Moneyкласс, естественно найти диапазон проверки внутри. Если вместо этого для хранения цен на товары используется int(хуже, а double), кто должен проверять диапазон? Продукт? Скидка? Корзина?

Наконец, третье преимущество, не упомянутое в документе, - это возможность относительно легко изменить базовый тип. Если сегодня my ProductIdимеет в shortкачестве базового типа, а позже мне нужно использовать intвместо этого, скорее всего, код для изменения не будет охватывать всю базу кода.

Недостаток - и тот же аргумент применяется к каждому правилу упражнения Object Calisthenics - состоит в том, что если быстро становится слишком подавляющим, чтобы создать класс для всего . Если Productсодержит то, ProductPriceчто наследует, от PositivePriceкоторого наследует, от Priceкоторого, в свою очередь, наследуется Money, это не чистая архитектура, а скорее полный беспорядок, где для того, чтобы найти одну вещь, сопровождающий должен каждый раз открывать несколько десятков файлов.

Другим важным моментом является стоимость (с точки зрения строк кода) создания дополнительных классов. Если обертки являются неизменяемыми (как обычно и должно быть), это означает, что, если мы берем C #, вы должны иметь в обёртке как минимум:

  • Собственник добытчик,
  • Его поддерживающее поле,
  • Конструктор, который присваивает значение вспомогательному полю,
  • Обычай ToString(),
  • Комментарии к документации XML (что делает много строк),
  • А Equalsи GetHashCodeпереопределения (также много LOC).

и в конце концов, когда это уместно:

  • DebuggerDisplay атрибут,
  • Переопределение ==и !=операторы,
  • В конечном итоге перегрузка оператора неявного преобразования для плавного преобразования в и из инкапсулированного типа,
  • Контракты кода (включая инвариант, который является довольно длинным методом с тремя атрибутами),
  • Несколько конвертеров, которые будут использоваться во время сериализации XML, сериализации JSON или сохранения / загрузки значения в / из базы данных.

Сто LOC для простой обертки делает его довольно запретным, и поэтому вы можете быть полностью уверены в долгосрочной прибыльности такой обертки. Понятие объема, объясненное Томасом Джанком , особенно актуально здесь. Написание сотен LOC для представления ProductIdвсей используемой базы кода выглядит весьма полезным. Написание класса такого размера для фрагмента кода, который составляет три строки в одном методе, является гораздо более сомнительным.

Заключение:

  • Оборачивайте примитивы в классы, которые имеют значение в бизнес-области приложения, когда (1) это помогает уменьшить количество ошибок, (2) снижает риск дублирования кода или (3) помогает изменить базовый тип позже.

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

На практике public string CreateNewThing()возвращение экземпляра ThingIdкласса вместо вместо stringможет помочь, но вы также можете:

  • Вернуть экземпляр Id<string>класса, который является объектом универсального типа, указывая, что базовый тип является строкой. Вы получаете удобство чтения, без недостатка необходимости поддерживать много типов.

  • Вернуть экземпляр Thingкласса. Если пользователю нужен только идентификатор, это легко сделать с помощью:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    
Арсений Мурзенко
источник
33
Я думаю, что здесь есть возможность свободно менять базовый тип. Сегодня это строка, но, возможно, GUID или долгое завтра. Создание оболочки типа скрывает эту информацию, что приводит к меньшему количеству изменений в будущем. ++ Хороший ответ здесь.
RubberDuck
4
В случае денег, убедитесь, что валюта либо включена в объект Money, либо вся ваша программа использует тот же самый (который затем должен быть задокументирован).
Пауло Эберманн
5
@Blrfl: хотя существуют допустимые случаи, когда необходимо глубокое наследование, я обычно вижу такое наследование в переопределенном коде, написанном без внимания к удобочитаемости, что является следствием компульсивного отношения pay-by-LOC. Таким образом, отрицательный характер этой части моего ответа.
Арсений Мурзенко
3
@ArTs: Это аргумент «давайте осудим инструмент, потому что некоторые люди используют его неправильно».
Blrfl
6
@MainMa Пример, где значение могло бы избежать дорогостоящей ошибки: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar
60

Я бы использовал область действия в качестве практического правила: valuesчем меньше область создания и потребления , тем меньше вероятность создания объекта, представляющего это значение.

Скажем, у вас есть следующий псевдокод

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

тогда область действия очень ограничена, и я не вижу смысла делать ее типом. Но, скажем, вы генерируете это значение в одном слое и передаете его другому слою (или даже другому объекту), тогда имеет смысл создать тип для этого.

Томас Джанк
источник
14
Очень хороший момент. Явные типы являются важным инструментом для передачи намерений , что особенно важно через границы объектов и служб.
Авнер Шахар-Каштан
+1 хотя, если вы еще не реорганизовали весь код doFancyOtherstuff()в подпрограмму, вы можете подумать, что job.do(id)ссылка недостаточно локальна, чтобы сохранить ее в виде простой строки.
Марк Херд
Это абсолютно верно! Это тот случай, когда у вас есть частный метод, когда вы знаете, что внешний мир никогда не будет использовать этот метод. После этого вы можете немного больше подумать, когда класс защищен, и после этого, когда класс общедоступен, но не является частью общедоступного API. Сфера охвата и большой объем искусства позволяют поставить грань в хорошем положении между многими примитивными и множеством пользовательских типов.
Самуил
34

Системы статического типа предназначены для предотвращения неправильного использования данных.

Есть очевидные примеры типов, делающих это:

  • Вы не можете получить месяц UUID
  • Вы не можете умножить две строки.

Есть более тонкие примеры

  • Вы не можете заплатить за что-то, используя длину стола
  • Вы не можете сделать HTTP-запрос, используя чье-либо имя в качестве URL.

У нас может возникнуть соблазн использовать doubleкак для цены, так и для длины, или использовать как stringдля имени, так и для URL. Но это подрывает нашу удивительную систему типов и позволяет этим злоупотреблениям проходить статические проверки языка.

Смешение фунт-секунд с ньютон-секундами может привести к плохим результатам во время выполнения .


Это особенно проблема со строками. Они часто становятся «универсальным типом данных».

Мы привыкли в первую очередь текстовые интерфейсы с компьютерами, и мы часто расширяем эти человеческие интерфейсы (UI) для интерфейсов программирования (API). Мы думаем о 34,25 как символы 34,25 . Мы думаем о дате как символы 05-03-2015 . Мы рассматриваем UUID как символы 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .

Но эта ментальная модель вредит абстракции API.

Слова или язык, как они написаны или произнесены, похоже, не играют никакой роли в моем механизме мышления.

Альберт Эйнштейн

Точно так же текстовые представления не должны играть никакой роли в разработке типов и API. Осторожно string! (и другие чрезмерно общие "примитивные" типы)


Типы сообщают «какие операции имеют смысл».

Например, однажды я работал на клиенте с HTTP REST API. REST, правильно выполненный, использует гипермедиа сущности, которые имеют гиперссылки, указывающие на связанные сущности. В этом клиенте были введены не только сущности (например, Пользователь, Учетная запись, Подписка), но и ссылки на эти сущности (UserLink, AccountLink, SubscriptionLink). Ссылки были всего лишь обертками Uri, но отдельные типы делали невозможным использование AccountLink для получения пользователя. Если бы все было просто Uri- или даже хуже, string- эти ошибки были бы обнаружены только во время выполнения.

Кроме того, в вашей ситуации у вас есть данные, которые используются только для одной цели: для идентификации Operation. Это не должно использоваться ни для чего другого, и мы не должны пытаться идентифицировать Operations со случайными строками, которые мы составили. Создание отдельного класса повышает удобочитаемость и безопасность вашего кода.


Конечно, все хорошее можно использовать в избытке. Рассмотреть возможность

  1. насколько ясность это добавляет к вашему коду

  2. как часто это используется

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

Пол Дрэйпер
источник
21
«Строки часто становятся« универсальным »типом данных» - это происхождение ненормативной лексики «строково-типизированных языков».
MSalters
@MSalters, ха-ха, очень мило.
Пол Дрэйпер
4
И, конечно же, что 05-03-2015 означает, когда интерпретируется как дата ?
CVn
10

Считаете ли вы хорошей практикой упаковывать простые типы в классе по соображениям безопасности?

Иногда.

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

Тогда вам нужно учитывать стоимость использования другого типа. Насколько больно использовать? Сколько работы это сделать?

В некоторых случаях вы сэкономите время и силы, имея хороший конкретный тип для работы. В других это не будет стоить проблем.

В общем, я думаю, что такого рода вещи нужно делать больше, чем сегодня. Если у вас есть объект, который что-то значит в вашем домене, хорошо иметь его в качестве своего собственного типа, так как этот объект с большей вероятностью изменится / будет расти вместе с бизнесом.

Telastyn
источник
10

Я, как правило, согласен с тем, что много раз вам следует создавать тип для примитивов и строк, но поскольку приведенные выше ответы рекомендуют создавать тип в большинстве случаев, я перечислю несколько причин, почему / когда нет:

  • Представление. Я должен обратиться к реальным языкам здесь. В C #, если идентификатор клиента является коротким, и вы заключаете его в класс - вы только что создали много накладных расходов: память, поскольку теперь она составляет 8 байтов в 64-битной системе и скорость, поскольку теперь она выделяется в куче.
  • Когда вы делаете предположения о типе. Если идентификатор клиента короткий, и у вас есть какая-то логика, которая каким-то образом его упаковывает - обычно вы уже делаете предположения о типе. Во всех этих местах теперь вам нужно разбить абстракцию. Если это одно пятно, это не имеет большого значения, если оно повсюду, вы можете обнаружить, что половину времени вы используете примитив.
  • Потому что не каждый язык имеет typedef. А для языков, которые этого не делают, и кода, который уже написан, внесение таких изменений может быть большой задачей, которая может даже привести к ошибкам (но у каждого есть набор тестов с хорошим охватом, верно?).
  • В некоторых случаях это снижает читабельность. Как мне распечатать это? Как мне это проверить? Должен ли я проверять на ноль? Все вопросы, которые требуют, вы углубитесь в определение типа.
ytoledano
источник
3
Конечно, если тип обертки является структурой, а не классом, то short(например) имеет одинаковую стоимость, упакованную или развернутую. (?)
MathematicalOrchid
1
Разве не будет хорошим дизайном для типа инкапсулировать свою собственную проверку? Или создать вспомогательный тип для его проверки?
Ник Уделл
1
Упаковывать или складывать излишне - это не шаблон. Информация должна проходить прозрачно через код приложения, кроме случаев, когда преобразование явно и действительно необходимо . Типы хороши, поскольку они обычно заменяют небольшое количество хорошего кода, расположенного в одном месте, для большого количества плохой реализации, разбросанной по всей системе.
Томас В.
7

Нет, вы не должны определять типы (классы) для «всего».

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

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

Ответ Telastyn в и ответ Томаса Нежелательный в оба делают очень хорошую точку о контексте и использовании соответствующего кода. Если вы используете значение в одном блоке кода (например, метод, цикл, usingблок), тогда можно просто использовать примитивный тип. Можно даже использовать примитивный тип, если вы используете набор значений неоднократно и во многих других местах. Но чем чаще и шире вы используете набор значений, и чем менее близко этот набор значений соответствует значениям, представленным примитивным типом, тем больше вы должны рассмотреть инкапсуляцию значений в классе или типе.

Кенни Эвитт
источник
5

На первый взгляд все, что вам нужно сделать, это определить операцию.

Дополнительно вы говорите, что должна делать операция:

чтобы начать операцию [и] контролировать его отделку

Вы говорите это так, как будто это «просто как используется идентификация», но я бы сказал, что это свойства, описывающие операцию. Для меня это звучит как определение типа, есть даже шаблон, называемый «шаблон команды», который очень хорошо подходит. ,

Это говорит

шаблон команды [...] используется для инкапсуляции всей информации, необходимой для выполнения действия или запуска события позднее .

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

По поводу комментария

Было бы хорошо, если бы он назвал этот класс командой, а затем не имел бы внутри себя логики.

Нет, не будет. Помните, что шаблоны очень абстрактны , настолько абстрактны, что являются в некоторой степени мета. То есть они часто абстрагируют само программирование, а не какую-то концепцию реального мира. Шаблон команды - это абстракция вызова функции. (или метод) Как будто кто-то нажал кнопку паузы сразу после передачи значений параметров и непосредственно перед выполнением, которое будет возобновлено позже.

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

  1. Если бы в команде была вся эта логика, она была бы раздутой и грязной. Вы бы подумали: «Ну, все эти функции должны быть в отдельных классах». Вы бы рефакторинг логики из команды.
  2. Если бы вы использовали всю эту логику в команде, было бы сложно протестировать и помешать при тестировании. «Черт возьми, почему я должен использовать эту бессмысленную команду? Я только хочу проверить, выплевывает ли эта функция 1 или нет, я не хочу вызывать ее позже, я хочу проверить это сейчас!»
  3. Если бы в команде была вся эта логика, не было бы особого смысла сообщать об этом, когда закончите. Если вы думаете о функции, которая должна выполняться синхронно по умолчанию, нет смысла получать уведомления, когда она завершает выполнение. Может быть, вы запускаете другой поток, потому что ваша операция на самом деле заключается в рендеринге аватара в формат кино (может потребоваться дополнительный поток на raspberry pi), может быть, вам нужно извлечь некоторые данные с сервера, ... что бы это ни было, если есть причина для сообщая о завершении, это может быть потому, что происходит некоторая асинхронность (это слово?). Я думаю, что запуск потока или обращение к серверу - это логика, которой не должно быть в вашей команде. Это что-то вроде мета-аргумента и может быть спорным. Если вы думаете, что это не имеет смысла, пожалуйста, сообщите мне в комментариях.

Напомним: шаблон команды позволяет вам обернуть функциональность в объект, который будет выполнен позже. Ради модульности (функциональность существует независимо от того, выполняется она с помощью команды или нет), тестируемости (функциональность должна проверяться без команды) и всех тех умных слов, которые по существу выражают требование писать хороший код, вы бы не ставили фактическую логику в команду.


Поскольку шаблоны являются абстрактными, может быть трудно найти хорошие метафоры реального мира. Вот попытка:

«Эй, бабушка, не могли бы вы нажать кнопку записи на телевизоре в 12 часов, чтобы я не пропустил симпсонов на канале 1?»

Моя бабушка не знает, что происходит технически, когда она нажимает кнопку записи. Логика в другом месте (в телевизоре). И это хорошо. Функциональность инкапсулирована, а информация скрыта от команды, это пользователь API, не обязательно часть логики ... о, боже, я снова болтаю модные слова, мне лучше закончить это редактирование сейчас.

значение NULL
источник
Вы правы, я не думал об этом так. Но шаблон команды не подходит на 100%, потому что логика операции инкапсулирована в другом классе, и я не могу поместить его в объект, который является Командой. Было бы хорошо, если бы он назвал этот класс командой, а затем не имел бы внутри себя логики.
Зив
Это имеет большой смысл. Попробуйте вернуть операцию, а не идентификатор операции. Это похоже на C # TPL, где вы возвращаете задачу или задачу <T>. @Ziv: Вы говорите, что «логика операции заключена в другом классе». Но значит ли это, что вы также не можете предоставить его отсюда?
Moby Disk
@ Зив, я позволю себе не согласиться. Посмотрите на причины, которые я добавил в свой ответ, и посмотрите, имеют ли они смысл для вас. =)
ноль
5

Идея обертывания примитивных типов,

  • Установить предметно-ориентированный язык
  • Ограничьте ошибки, совершаемые пользователями, передавая неверные значения

Очевидно, что это будет очень сложно и непрактично делать везде, но важно обернуть типы там, где это необходимо,

Например, если у вас есть класс Order,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

Важными свойствами для поиска ордера являются главным образом OrderId и InvoiceNumber. А Amount и CurrencyCode тесно связаны между собой, и если кто-то изменит CurrencyCode без изменения Amount, Заказ больше не будет считаться действительным.

Таким образом, в этом сценарии имеет смысл только перенос OrderId, InvoiceNumber и введение композита для валюты, и, вероятно, перенос описания не имеет смысла. Таким образом, предпочтительный результат может выглядеть так:

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Таким образом, нет причин, чтобы обернуть все, но вещи, которые на самом деле имеет значение.

Низко летящий пеликан
источник
4

Непопулярное мнение:

Обычно вы не должны определять новый тип!

Определение нового типа для переноса примитива или базового класса иногда называется определением псевдотипа. IBM описывает это как плохая практика здесь (они сосредоточены именно на неправильное использование дженериков в данном случае).

Псевдотипы делают обычные функции библиотеки бесполезными.

Математические функции Java могут работать со всеми числовыми примитивами. Но если вы определяете новый класс Percentage (оборачивая двойной, который может находиться в диапазоне 0 ~ 1), все эти функции бесполезны и должны быть обернуты классами, которые (что еще хуже) должны знать о внутреннем представлении класса Percentage ,

Псевдотипы становятся вирусными

При создании нескольких библиотек вы часто обнаружите, что эти псевдотипы становятся вирусными. Если вы используете вышеупомянутый класс Percentage в одной библиотеке, вам нужно либо преобразовать его на границе библиотеки (потеряв всю безопасность / смысл / логику / другую причину, по которой вы создавали этот тип), либо вам придется создавать эти классы доступны для другой библиотеки, а также. Заражение новой библиотеки вашими типами, где простого двойника могло бы быть достаточно.

Забрать сообщение

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

Пример:

A uintможет прекрасно представлять UserId, мы можем продолжать использовать встроенные операторы Java для uints (например, ==), и нам не нужна бизнес-логика для защиты «внутреннего состояния» идентификатора пользователя.

Рой Т.
источник
Согласен. вся философия программирования хороша, но на практике вы должны заставить ее работать
Эван
Если вы видели какие-либо объявления о выдаче ссуд самым бедным людям в Великобритании, вы обнаружите, что процентное отношение к двойному
значению
1
В C / C ++ / Objective C вы можете использовать typedef, который дает вам просто новое имя для существующего примитивного типа. С другой стороны, ваш код должен работать независимо от его базового типа, например, если я изменю OrderId с uint32_t на uint64_t (потому что мне нужно обработать более 4 миллиардов заказов).
gnasher729
Я не буду осуждать вас за вашу храбрость, но псевдотипы и типы, определенные в ответе сверху, отличаются. Это типы, специфичные для бизнеса. Псуедотипы - это типы, которые все еще являются общими.
0
@ gnasher729: C ++ 11 также имеет пользовательские литералы, которые делают процесс обертывания очень приятным для пользователя: Расстояние d = 36.0_mi + 42.0_km; , msdn.microsoft.com/en-us/library/dn919277.aspx
damix911
3

Как и все советы, умение знать, когда применять правила. Если вы создаете свои собственные типы на языке управления типами, вы получаете проверку типов. В общем, это будет хороший план.

NamingConvention является ключом к удобочитаемости. Два вместе могут ясно выразить намерение.
Но /// все еще полезно.

Так что да, я бы сказал, создайте много собственных типов, когда их время жизни выходит за границы класса. Также рассмотрите возможность использования обоих Structи Classне всегда класса.

Фил Соеди
источник
2
Что ///значит?
Гейб
1
/// - это комментарий документации в C #, который позволяет intellisense показывать документацию подписи в точке вызова метода. Рассмотрим лучший способ документировать код. Можно обрабатывать инструментами и предлагать интеллигентное поведение. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
Фил Соеди