Почему бы вам не использовать директиву using в C #?

71

Существующие стандарты кодирования в большом проекте C # включают правило, согласно которому все имена типов должны быть полностью квалифицированными, что запрещает использование директивы using. Итак, а не знакомые

using System.Collections.Generic;

.... other stuff ....

List<string> myList = new List<string>();

(Это, вероятно, не удивительно, что varтакже запрещено.)

Я заканчиваю с:

System.Collections.Generic.List<string> myList = new System.Collections.Generic.List<string>();

Это увеличение на 134% при наборе текста, и ни одно из них не дает полезной информации. На мой взгляд, 100% увеличения - это шум (беспорядок), который на самом деле мешает пониманию.

За 30 с лишним лет программирования я видел такой стандарт, предложенный один или два раза, но так и не реализованный. Логическое обоснование этого ускользает от меня. Человек, навязывающий стандарт, не глуп, и я не думаю, что он злой. Что оставляет заблуждение как единственную альтернативу, если я что-то упустил.

Вы когда-нибудь слышали о введении такого стандарта? Если так, то в чем причина? Можете ли вы вспомнить другие аргументы, кроме «это глупо» или «все используют using», которые могут убедить этого человека снять этот запрет?

аргументация

Причины этого запрета были:

  1. Наводить указатель мыши на имя, чтобы получить полностью определенный тип, будет неудобно. Лучше всегда иметь полностью определенный тип, видимый все время.
  2. Фрагменты кода, отправленные по электронной почте, не имеют полностью определенного имени, и поэтому могут быть трудны для понимания.
  3. При просмотре или редактировании кода вне Visual Studio (например, Notepad ++) невозможно получить полное имя типа.

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

Потенциальные проблемы конфликта пространства имен, которые, как я ожидал, будут главной проблемой, даже не упоминались. Это особенно удивительно, потому что у нас есть пространство имен MyCompany.MyProject.Core, что является особенно плохой идеей. Я давно узнал, что именование чего-либо Systemили CoreC # - это быстрый путь к безумию.

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

Джим Мишель
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
maple_shaft
1
Реальный пример того, почему это может быть хорошей идеей (но в мире C ++): ответ на вопрос « Почему использование пространства имен std;» считается плохой практикой? рядом с тем, что «использование» директив и объявлений запрещено.
Питер Мортенсен
Перед прочтением раздела «Рассуждение» я как-то догадался о непрактичных причинах, потому что в моей компании есть похожие правила.
Gqqnbig

Ответы:

69

Более широкий вопрос:

Вы когда-нибудь слышали о введении такого стандарта? Если так, то в чем причина?

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


Пример. Этот тип сценария, вероятно, лучше объяснить на примере.

Допустим, у нас есть два, Lists<T>принадлежащих к двум отдельным проектам.

System.Collections.Generic.List<T>
MyCorp.CustomCollections.Optimized.List<T>

Когда мы используем полное имя объекта, становится ясно, какое из List<T>них используется. Эта ясность, очевидно, достигается ценой многословия.

И вы можете спорить: «Подождите! Никто никогда не использовал бы два таких списка!» Вот где я укажу сценарий обслуживания.

Вы написали модуль Fooдля своей корпорации, который использует одобренную, оптимизированную корпорацию List<T>.

using MyCorp.CustomCollections.Optimized;

public class Foo {
    List<object> myList = ...;
}

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

using MyCorp.CustomCollections.Optimized;
using System.Collections.Generic;

И вы можете видеть, как дела идут плохо в спешке.

Следует отметить, что вы можете иметь два проприетарных класса с одним и тем же именем, но в разных пространствах имен в одном проекте. Так что речь идет не только о столкновении с классами, поставляемыми в .NET Framework.

MyCorp.WeightsAndLengths.Measurement();
MyCorp.TimeAndSpace.Measurement();

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

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

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


Альтернативы:
Хотя вы не спрашивали, стоит отметить, что это не лучшее решение проблемы. Не стоит создавать классы, которые будут сталкиваться без объявлений их пространств имен.

List<T>в частности, его следует рассматривать как зарезервированное слово, а не как имя для ваших классов.

Аналогично, отдельные пространства имен в проекте должны стремиться иметь уникальные имена классов. Необходимость попытаться вспомнить, с каким пространством имен Foo()вы работаете, - это ментальные накладные расходы, которых лучше избегать. Сказал по-другому: иметь MyCorp.Bar.Foo()и MyCorp.Baz.Foo()собирается сбить с толку ваших разработчиков и лучше избегать.

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

Bar.Foo() 
Baz.Foo()

Конкретные причины вашего текущего проекта:

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

Наводить указатель мыши на имя, чтобы получить полностью определенный тип, будет неудобно. Лучше всегда иметь полностью определенный тип, видимый все время.

«Громоздкий?» Нет Раздражает возможно. Перемещение нескольких унций пластика для перемещения экранной указки не является громоздким . Но я отвлекся.

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

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

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

Эта (продолжение?) Аргументация усиливает мое подозрение, что классы в настоящее время плохо названы. Опять же, наличие плохих имен классов не является хорошим оправданием для требования, чтобы все использовали полное имя класса. Если имя класса трудно понять, есть намного больше ошибок, чем то, что могут исправить полные имена классов.

При просмотре или редактировании кода вне Visual Studio (например, Notepad ++) невозможно получить полное имя типа.

Из всех причин эта чуть не заставила меня выплюнуть напиток. Но я опять отвлекся.

Мне интересно, почему команда часто редактирует или просматривает код за пределами Visual Studio? И теперь мы смотрим на обоснование, которое довольно хорошо ортогонально тому, что должны предоставить пространства имен. Это аргумент, поддерживаемый инструментами, тогда как пространства имен служат для обеспечения организационной структуры кода.

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

Принимая во внимание, что существуют плохо названные классы, и при условии, что вы не можете выполнить рефакторинг, правильный ответ - использовать Visual Studio IDE в полной мере. Возможно, рассмотрите возможность добавления в плагин, такой как пакет VS PowerTools. Затем, когда я смотрю, AtrociouslyNamedClass()я могу щелкнуть имя класса, нажать F12 и перейти непосредственно к определению класса, чтобы лучше понять, что он пытается сделать. Кроме того, я могу нажать Shift-F12, чтобы найти все места в коде, которые в настоящее время нуждаются в использовании AtrociouslyNamedClass().

Что касается внешних проблем с инструментами, лучше всего просто остановить их. Не посылайте фрагменты по электронной почте взад и вперед, если они не сразу понимают, на что ссылаются. Не используйте другие инструменты вне Visual Studio, так как эти инструменты не обладают интеллектом вокруг кода, который нужен вашей команде. Notepad ++ - потрясающий инструмент, но он не предназначен для этой задачи.

Поэтому я согласен с вашей оценкой в ​​отношении трех конкретных обоснований, которые вам были представлены. Тем не менее, я думаю, что вам сказали: «У нас есть основные проблемы в этом проекте, которые не могут / не будут решаться, и именно так мы их« исправили »». И это, очевидно, говорит о более глубоких проблемах в команде, которые могут служить для вас «красными флагами».


источник
1
+1. Я также действительно столкнулся с некоторыми неприятными столкновениями в наших внутренних пространствах имен. Как при переносе унаследованного кода в проект «2.0», так и при выборе нескольких имен классов, которые семантически допустимы в различных контекстах пространства имен. Действительно хитрая вещь в том, что две вещи с одинаковыми именами часто имеют одинаковые интерфейсы, поэтому компилятор не будет жаловаться ... ваша среда выполнения будет!
svidgen
23
Эта проблема решается путем использования псевдонимов пространства имен, а не путем навязывания глупых правил, требующих кода, из-за беспорядочного использования пространства имен.
Дэвид Арно
4
@DavidArno - я не согласен с тобой. Тем не менее, крупные проекты могут не иметь такой возможности. Я просто указываю, почему крупный проект может навязать это правило. Я не сторонник правила, хотя. Просто иногда это требуется, потому что у команды нет других вариантов.
4
@ GlenH7 Вы можете указать другие доступные решения в своем ответе. Я не хотел бы видеть, как люди натыкаются на это и думают: «О, это отличная идея!» +1 за оставшийся объективный и прагматичный, хотя.
RubberDuck
3
@JimMischel - Похоже, что правило используется как опора для чего-то . Хотя может быть невозможно определить, что это за вещь. На высоком уровне, да, иногда есть основания не разрешать, usingsно это не похоже на то, что ваш проект квалифицируется как один из таких случаев.
16

Я работал в одной компании, где у них было много классов с одинаковыми именами, но в разных библиотеках классов.

Например,

  • Domain.Customer
  • Legacy.Customer
  • ServiceX.Customer
  • ViewModel.Customer
  • Database.Customer

Вы можете сказать, что плохой дизайн, но вы знаете, как эти вещи развиваются органично, и какова альтернатива - переименовать все классы, чтобы они включали пространство имен? Главный рефакторинг всего?

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

С usingпрямо сверху, это было основной проблемой в заднице, ReSharper делал бы странные вещи, пытаясь заставить это работать, переписывая usingдирективы на лету, вы получали бы Customer-not-be-cast-as- Ошибки стиля клиента и не знаю, какой тип был правильным.

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

var cust = new Domain.Customer

или же

public void Purchase(ViewModel.Customer customer)

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

Это не был стандарт кодирования, это не было полное пространство имен, и он не применялся ко всем классам в качестве стандарта.

Ewan
источник
13
"Какова альтернатива, переименовать все классы, чтобы включить пространство имен? Главный рефакторинг всего?" Да, это (правильная) альтернатива.
Дэвид Арно
2
Только в краткосрочной перспективе. Это намного дешевле, чем использование явных пространств имен в коде в долгосрочной перспективе.
Дэвид Арно
3
Рад принять этот аргумент, если вы платите мне наличными, а не акциями.
Ewan
8
@ Дэвид: Нет, это НЕ правильная альтернатива. Полное определение всех идентификаторов или, по крайней мере, тех, которые фактически сталкиваются, является правильным способом, и причина, по которой пространства имен существуют в первую очередь, заключается в предоставлении коллизий, когда одно и то же имя используется в разных модулях. Дело в том, что некоторые модули могут быть от сторонних разработчиков, кодовую базу которых вы не можете изменить. Итак, что вы делаете, когда идентификаторы из двух разных сторонних библиотек сталкиваются, и вам нужно использовать обе библиотеки, но не может реорганизовать ни одну из них? Пространства имен существуют, потому что рефакторинг не всегда возможен.
Кайзерлуди
4
@CarlSixsmith, если кто-то подписывается на мнения Мартина Фаулера о рефакторинге, то, если изменение влияет на пользователя, то вы правы, это не рефакторинг; это еще одна форма перестройки. Это поднимает два вопроса: 1. Все ли публичные методы автоматически становятся частью API? 2. Фаулер сам признает, что борется за проигрышную битву за это определение, и все больше людей используют термин «рефакторинг» для обозначения общей реструктуризации, включая публичные изменения.
Дэвид Арно
7

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

В C # примером этого являются имена аргументов. Я несколько раз сталкивался с проблемой, когда я часами искал решение, в то время как более подробный стиль написания мог бы предотвратить эту ошибку. Проблема состоит в ошибке в порядке аргументов. Например, выглядит ли это правильно для вас?

if (...) throw new ArgumentNullException("name", "The name should be specified.");
if (...) throw new ArgumentException("name", "The name contains forbidden characters.");

На первый взгляд выглядит отлично, но есть ошибка. Подписи конструкторов исключений:

public ArgumentException(string message, string paramName)
public ArgumentNullException(string paramName, string message)

Заметили порядок аргументов?

Приняв более подробный стиль, в котором имя аргумента указывается каждый раз, когда метод принимает два или более аргумента одного типа , мы предотвращаем повторное возникновение ошибки, и поэтому:

if (...) throw new ArgumentNullException(paramName: "name", message: "The name should be specified.");
if (...) throw new ArgumentException(paramName: "name", message: "The name contains forbidden characters.");

очевидно, что писать дольше, но в результате отладка затрачивается меньше времени.

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

В вашем случае, мотивация usingправила «Нет » состоит в том, что оно должно усложнить использование неправильного типа, например « MyCompany.Something.List<T>против» System.Collections.Generic.List<T>.

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

Арсений Мурзенко
источник
Подобные ситуации можно обнаружить с помощью инструментов. ReSharper отмечает paramNameс InvokerParameterNameатрибутом и выдает предупреждение , если такой параметр не существует.
Johnbot
2
@Johnbot: они, конечно, могут, но в очень ограниченном числе случаев. Что если у меня есть SomeBusiness.Something.DoStuff(string name, string description)? Ни один инструмент не достаточно умен, чтобы понять, что такое имя и описание.
Арсений Мурзенко
@ArseniMourzenko в этом случае даже человеку нужно время, чтобы выяснить, правильный вызов или нет.
Gqqnbig
6

Пространства имен дают коду иерархическую структуру, допускающую более короткие имена.

   MyCompany.Headquarters.Team.Leader
   MyCompany.Warehouse.Team.Leader

Если вы разрешите ключевое слово "using", существует цепочка событий ...

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

Поэтому, чтобы избежать риска, все начинают использовать действительно длинные имена:

   HeadquartersTeamLeader
   WarehouseTeamLeader

Ситуация обостряется ...

  • Возможно, кто-то уже выбрал имя, поэтому вы используете более длинное.
  • Имена становятся длиннее и длиннее.
  • Длина может превышать 60 символов без максимума - это становится смешным.

Я видел, как это произошло, поэтому могу посочувствовать компаниям, которые запрещают «использование».


источник
11
Запрет using- это ядерный вариант. Псевдонимы пространства имен и частичная квалификация являются предпочтительными.
Джим Мишель
1

Проблема в usingтом, что он волшебным образом добавляет пространство имен без явного объявления. Это гораздо большая проблема для программиста по обслуживанию, который сталкивается с чем-то подобным List<>и не знаком с доменом, не знает, какое usingпредложение ввело его.

Аналогичная проблема возникает в Java, если кто-то пишет, import Java.util.*;а не, import Java.util.List;например; последняя декларация документирует, какое имя было введено, так что когда это List<>происходит позже в источнике, его происхождение известно из importдекларации.

Майкл Монтенеро
источник
6
В то время как это правда, что у кого-то, незнакомого с доменом, будут некоторые трудности, все, что ему нужно сделать, это навести указатель мыши на имя типа, и он получит полностью определенное имя. Или он может щелкнуть правой кнопкой мыши и перейти непосредственно к определению типа. Для типов .NET Framework он, вероятно, уже знает, где что находится. Для доменных типов ему понадобится, возможно, неделя, чтобы стать комфортным. В этот момент полностью определенные имена - это просто шум, который мешает пониманию.
Джим Мишель
Джим ... Я просто попытался навести курсор мыши на определение, и оно не сработало. Рассматривали ли вы возможность того, что некоторые программисты в мире не используют Microsoft IDE? В любом случае, ваш счетчик не опровергает мою точку зрения, что источник с использованием больше не является самодокументированным
Майкл Монтенеро
2
Да, есть люди, работающие в C #, которые мешают себе, не используя лучшие доступные инструменты. И хотя аргумент о том, что полная квалификация является «самодокументируемой», имеет некоторые достоинства, такой код имеет огромную стоимость в удобочитаемости. Весь этот посторонний код создает шум, который затемняет части кода, которые действительно имеют значение.
Джим Мишель