В главе «Дядя Боб» об именах в « Чистом коде» рекомендуется избегать кодировок в именах, главным образом в отношении венгерских обозначений. Он также особо упоминает удаление I
префикса из интерфейсов, но не показывает примеров этого.
Давайте предположим следующее:
- Использование интерфейса в основном для достижения тестируемости через внедрение зависимостей
- Во многих случаях это приводит к наличию единого интерфейса с одним исполнителем
Так, например, как эти два должны быть названы? Parser
а ConcreteParser
? Parser
а ParserImplementation
?
public interface IParser {
string Parse(string content);
string Parse(FileInfo path);
}
public class Parser : IParser {
// Implementations
}
Или я должен игнорировать это предложение в случаях с одиночной реализацией, подобных этому?
c#
naming
clean-code
naming-standards
Винко Врсалович
источник
источник
Ответы:
Хотя многие, в том числе "Дядя Боб", советуют не использовать
I
в качестве префикса для интерфейсов, это является хорошо известной традицией в C #. В общих чертах этого следует избегать. Но если вы пишете на C #, вы действительно должны следовать соглашениям этого языка и использовать его. Невыполнение этого требования приведет к огромной путанице с кем-либо еще, знакомым с C #, который пытается прочитать ваш код.источник
Интерфейс является важной логической концепцией, поэтому интерфейс должен иметь общее имя. Итак, я бы предпочел
чем
Последний имеет несколько проблем:
источник
Default
илиReal
илиImpl
во многих моих именах классовIList<T>
и ей все равно, как она хранится внутри; возможность просто перебиратьI
и иметь пригодное для использования имя класса кажется более приятным, чем необходимость магически знать (как в Java), чтоList<T>
должен использовать код, требующий обычной реализацииArrayList<T>
.CFoo : Foo
. Так что эта опция на самом деле не доступна. Во-вторых, вы получите странное несоответствие, потому что некоторые классы будут иметь маркер, а другие - нет, в зависимости от того, могут ли быть другие реализации того же интерфейса.CFoo : Foo
ноSqlStore : Store
. Это одна из проблем, сImpl
которой я столкнулся , и которая, по сути, просто более уродливый маркер. Возможно, если бы существовал язык с соглашением всегда добавлятьC
(или что-то еще), это могло бы быть лучше, чемI
Это не просто соглашения об именах. C # не поддерживает множественное наследование, поэтому это унаследованное использование венгерской нотации имеет небольшое, хотя и полезное преимущество, когда вы наследуете от базового класса и реализуете один или несколько интерфейсов. Так что это ...
... предпочтительнее для этого ...
IMO
источник
inherits
противimplements
.extends
.Нет нет нет.
Соглашения об именах не являются функцией вашего фэндома дяди Боба. Они не являются функцией языка, C # или иначе.
Они являются функцией вашей кодовой базы. В гораздо меньшей степени ваш магазин. Другими словами, первый в кодовой базе устанавливает стандарт.
Только тогда вы сможете решить. После этого просто будьте последовательны. Если кто-то начинает быть непоследовательным, заберите у него ружье и заставьте его обновлять документацию, пока он не покается.
При чтении новой кодовой базы, если я никогда не вижу
I
префикса, у меня все хорошо, независимо от языка. Если я вижу один на каждом интерфейсе, я в порядке. Если я иногда вижу один, кто-то собирается заплатить.Если вы оказались в разряженном контексте установки этого прецедента для вашей кодовой базы, я призываю вас рассмотреть это:
Как клиентский класс я оставляю за собой право ни черта не говорить о том, что я говорю. Все, что мне нужно, это имя и список вещей, которые я могу назвать против этого имени. Те не могут измениться, если я не скажу так. Я СОБСТВЕННО, что часть. То, что я говорю, интерфейс или нет, это не мой отдел.
Если вы подкрадываетесь к
I
этому имени, клиенту все равно. Если нетI
, клиенту все равно. То, с чем это говорит, может быть конкретным. Возможно, нет. Так как это не вызываетnew
этого в любом случае, это не имеет значения. Как клиент, я не знаю. Я не хочу знать.Теперь, когда кодовая обезьяна смотрит на этот код, даже в Java, это может быть важно. В конце концов, если мне нужно изменить реализацию на интерфейс, я могу сделать это, не сообщая клиенту. Если нет
I
традиции, я бы даже не переименовал ее. Что может быть проблемой, потому что теперь мы должны перекомпилировать клиент, потому что, хотя исходный код не заботится о двоичном файле. Что-то легко пропустить, если вы никогда не меняли имя.Может быть, это не проблема для вас. Возможно, нет. Если это так, это хороший повод для заботы. Но из-за любви к настольным игрушкам не говорите, что мы должны делать это вслепую, потому что это так, как это делается в каком-то языке. Либо есть чертовски веская причина, либо все равно.
Дело не в том, чтобы жить с этим вечно. Речь идет о том, чтобы не давать мне дерьма о том, что кодовая база не соответствует вашему конкретному менталитету, если вы не готовы везде разрешить «проблему». Если у вас нет веской причины, я не должен идти по пути наименьшего сопротивления единообразию, не приходите ко мне, проповедуя соответствие. У меня есть дела поважнее.
источник
I
s. Если ваш код использует их, вы увидите их повсюду. Если ваш код их не использует, вы увидите их из кода платформы, что приведет именно к тому миксу, который вам не нужен.Есть одно крошечное различие между Java и C #, которое уместно здесь. В Java каждый член по умолчанию является виртуальным. В C # каждый элемент запечатан по умолчанию - за исключением элементов интерфейса.
Предположения, которые идут с этим, влияют на руководство - в Java каждый открытый тип должен считаться не окончательным, в соответствии с принципом замещения Лискова [1]. Если у вас есть только одна реализация, вы назовете класс
Parser
; если вы обнаружите, что вам нужно несколько реализаций, вы просто измените класс на интерфейс с тем же именем и переименуете конкретную реализацию в нечто описательное.В C # главное предположение состоит в том, что когда вы получаете класс (имя не начинается с
I
), это тот класс, который вам нужен. Имейте в виду, что это далеко не на 100% точно - типичным контрпримером были бы классы вродеStream
(которые действительно должны были быть интерфейсом или парой интерфейсов), и у каждого были свои собственные рекомендации и фоны из других языков. Есть и другие исключения, такие как довольно широко используемыйBase
суффикс для обозначения абстрактного класса - как и в случае с интерфейсом, вы знаете, что тип должен быть полиморфным.Есть также хорошая удобная функция, позволяющая оставить имя без префикса для функциональности, относящейся к этому интерфейсу, не прибегая к тому, чтобы сделать интерфейс абстрактным классом (что повредит из-за отсутствия множественного наследования классов в C #). Это было популяризировано LINQ, который использует
IEnumerable<T>
в качестве интерфейса иEnumerable
в качестве хранилища методов, которые применяются к этому интерфейсу. Это не нужно в Java, где интерфейсы также могут содержать реализации методов.В конечном счете,
I
префикс широко используется в мире C # и, соответственно, в мире .NET (поскольку большая часть кода .NET написана на C #, имеет смысл следовать рекомендациям C # для большинства общедоступных интерфейсов). Это означает, что вы почти наверняка будете работать с библиотеками и кодом, который следует этой нотации, и имеет смысл принять традицию, чтобы предотвратить ненужную путаницу - это не то же самое, что пропуск префикса сделает ваш код лучше :)Я предполагаю, что рассуждения дяди Боба были примерно такими:
IBanana
это абстрактное понятие банана. Если может быть какой-либо реализующий класс, имя которого было бы не лучше, чемBanana
абстракция, то абстракция совершенно бессмысленна, и вам следует отказаться от интерфейса и просто использовать класс. Если есть лучшее имя (скажем,LongBanana
илиAppleBanana
), нет причин не использовать егоBanana
в качестве имени интерфейса. Следовательно, использованиеI
префикса означает, что у вас есть бесполезная абстракция, что делает код сложнее для понимания без какой-либо выгоды. А так как строгий ООП всегда заставит вас кодировать интерфейсы, единственное место, где вы не увидитеI
префикса для типа, это конструктор - это совершенно бессмысленный шум.Если вы примените это к вашему примеру
IParser
интерфейса, вы можете ясно увидеть, что абстракция полностью находится на «бессмысленной» территории. Или есть что - то конкретное о конкретной реализации синтаксического анализатора (напримерJsonParser
,XmlParser
...), или вы должны просто использовать класс. Нет такой вещи, как «реализация по умолчанию» (хотя в некоторых средах это действительно имеет смысл - особенно COM), либо есть конкретная реализация, либо вы хотите использовать абстрактный класс или методы расширения для «значений по умолчанию». Однако в C #, если ваша кодовая база уже не опускаетI
префикс -pre, сохраните его. Просто запоминайте каждый раз, когда вы видите подобный кодclass Something: ISomething
- это означает, что кто-то не очень хорошо следит за YAGNI и создает разумные абстракции.[1] - Технически, это специально не упоминается в статье Лискова, но это одна из основ оригинальной статьи ООП, и в моем чтении Лискова она не оспаривала это. В менее строгой интерпретации (используемой большинством языков ООП) это означает, что любой код, использующий открытый тип, предназначенный для подстановки (т. Е. Не финальный / запечатанный), должен работать с любой соответствующей реализацией этого типа.
источник
В идеальном мире вы не должны ставить свое имя перед префиксом («I ...»), а просто позволять имени выражать концепцию.
Я где-то читал (и не могу его получить) мнение, что это соглашение ISomething побуждает вас определять концепцию «IDollar», когда вам нужен класс «Dollar», но правильным решением будет концепция, называемая просто «Currency».
Другими словами, соглашение облегчает сложность именования вещей, а упрощение является слегка неправильным .
В реальном мире клиенты вашего кода (которые могут быть просто «вы из будущего») должны иметь возможность прочитать его позже и быть знакомыми с ним как можно быстрее (или с минимальными усилиями), насколько это возможно (дать клиентам вашего кода, что они ожидают получить).
Соглашение ISomething, вводит жестокие / плохие имена. Тем не менее, это не настоящая ерунда *), если только соглашения и практики, используемые при написании кода, больше не актуальны; если соглашение гласит «использовать ISomething», то лучше использовать его, чтобы соответствовать (и работать лучше) другим разработчикам.
*) Я могу сойти с ума, говоря, что это потому, что "круть" не совсем точное понятие :)
источник
Как уже упоминалось в других ответах, добавление префиксов к именам интерфейсов
I
является частью руководства по кодированию для платформы .NET. Поскольку с конкретными классами чаще взаимодействуют, чем с общими интерфейсами в C # и VB.NET, они являются первоклассными в схемах именования и поэтому должны иметь самые простые имена - следовательно,IParser
для интерфейса иParser
для реализации по умолчанию.Но в случае Java и аналогичных языков, где вместо конкретных классов предпочтительнее использовать интерфейсы, дядя Боб прав в том, что их
I
следует удалить, а интерфейсы должны иметь самые простые имена - например,Parser
для вашего интерфейса синтаксического анализатора. Но вместо имени на основе ролейParserDefault
класс должен иметь имя, описывающее, как реализован синтаксический анализатор :Это, например, как
Set<T>
,HashSet<T>
иTreeSet<T>
названы.источник
sealed
по умолчанию, и вы должны сделать их явноvirtual
. Быть виртуальным - это особый случай в C #. Конечно, так как рекомендуемый подход к написанию кода менялся с годами, многие пережитки прошлого должны были остаться для совместимости :) Ключевым моментом является то, что если вы получаете интерфейс в C # (который начинается сI
), вы знаете, что это полностью абстрактный тип; если вы получаете неинтерфейс, типичное допущение состоит в том, что это тот тип, который вы хотите, LSP будь проклятВы правы в том, что это соглашение об интерфейсе I = нарушает (новые) правила
В старые времена вы использовали префикс всего типа intCounter boolFlag и т. Д.
Если вы хотите называть вещи без префикса, но также избегать использования ConcreteParser, вы можете использовать пространства имен
источник
intCounter
илиboolFlag
. Это неправильные «типы». Вместо этого вы должны написатьpszName
(указатель на NUL-завершенную строку, содержащую имя),cchName
(количество символов в строке «имя»),xControl
(координата x элемента управления),fVisible
(флаг, указывающий, что что-то видно),pFile
(указатель на файл),hWindow
(дескриптор окна) и так далее.xControl
вcchName
. Они оба типаint
, но у них очень разная семантика. Префиксы делают эту семантику очевидной для человека. Указатель на NUL-концевую строку выглядит точно так же, как указатель на строку в стиле Pascal для компилятора. Точно так жеI
префикс для интерфейсов появился в языке C ++, где интерфейсы на самом деле являются просто классами (и, действительно, для C, где нет такого понятия, как класс или интерфейс на уровне языка, это всего лишь соглашение).