Проблемы с именами: следует ли переименовать «ISomething» в «Something»? [закрыто]


68

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

Давайте предположим следующее:

  • Использование интерфейса в основном для достижения тестируемости через внедрение зависимостей
  • Во многих случаях это приводит к наличию единого интерфейса с одним исполнителем

Так, например, как эти два должны быть названы? Parserа ConcreteParser? Parserа ParserImplementation?

public interface IParser {
    string Parse(string content);
    string Parse(FileInfo path);
}

public class Parser : IParser {
     // Implementations
}

Или я должен игнорировать это предложение в случаях с одиночной реализацией, подобных этому?


30
Это не делает его менее религиозным. Ты должен искать причины, а не просто кто-то, кто говорит Х.
Майк Данлавей

35
@MikeDunlavey И это причина вопроса!
Винко Врсалович

14
Если существует существующее тело кода (которое вы не собираетесь переписывать), придерживайтесь того соглашения, которое оно использует. Если новый код, используйте то, с чем большинство людей, которые будут над ним работать, будут довольны / привыкли. Только если ни то, ни другое не применимо, это должно стать философским вопросом.
TripeHound

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

35
Книга «Дяди Боба» посвящена JAVA, а не C #. Большинство парадигм одинаковы с C #, но некоторые соглашения об именах различаются (также посмотрите на имена функций lowerCamelCase в Java). Когда вы пишете в C #, используйте соглашения об именах C #. Но в любом случае, следуйте главному пункту его главы об именовании: читаемые имена, которые передают значение предмета!
Бернхард Хиллер

Ответы:


191

Хотя многие, в том числе "Дядя Боб", советуют не использовать Iв качестве префикса для интерфейсов, это является хорошо известной традицией в C #. В общих чертах этого следует избегать. Но если вы пишете на C #, вы действительно должны следовать соглашениям этого языка и использовать его. Невыполнение этого требования приведет к огромной путанице с кем-либо еще, знакомым с C #, который пытается прочитать ваш код.


Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
maple_shaft

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

4
Принцип «в общих чертах» прост: клиент должен иметь право даже не знать, говорит ли он с интерфейсом или реализацией. Оба языка ошиблись. C # получил неправильное соглашение об именах. Ява неправильно поняла байт-код. Если я переключаю реализацию на интерфейс с тем же именем в Java, я должен перекомпилировать все клиенты, даже если имя и методы не изменились. Это не «выбери свой яд». Мы просто делали это неправильно. Пожалуйста, учитесь на этом, если вы создаете новый язык.
candied_orange

39

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

interface Something
class DefaultSomething : Something
class MockSomething : Something

чем

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Последний имеет несколько проблем:

  1. Что-то является лишь одной реализацией ISomething, но это та, которая имеет общее имя, как если бы оно было каким-то особенным.
  2. MockSomething, кажется, происходит от Something, но реализует ISomething. Может быть, это следует назвать MockISomething, но я никогда не видел этого в дикой природе.
  3. Рефакторинг сложнее. Если что-то сейчас является классом, и вы только потом узнаете, что вам нужно ввести интерфейс, этот интерфейс должен быть назван одинаково, поскольку он должен быть прозрачным для клиентов, независимо от того, является ли тип конкретным классом, абстрактным классом или интерфейсом. , Что-то ломает это.

59
Почему бы вам не следовать общепринятым стандартам, которые предоставляет C #? Какая польза от этого, которая превышает стоимость путаницы и раздражения каждого программиста C #, который следует за вами?
Роберт Харви

6
@wallenborn Это нормально, но на самом деле у вас часто есть только одна легитимная реализация интерфейса, потому что в юнит-тестах интерфейсы обычно используются для модности. Я не хочу ставить Defaultили Realили Implво многих моих именах классов
Бен Ааронсон

4
Я согласен с вами, но если вы путешествуете по этой дороге, вам придется сражаться с каждым линтером, когда-либо сделанным для C #.
RubberDuck

3
Я не вижу проблем с наличием экземпляра класса с тем же именем, что и у интерфейса, если подавляющее большинство экземпляров, которые реализуют интерфейс, будут этим классом. Например, большая часть кода нуждается в простой реализации общего назначения, IList<T>и ей все равно, как она хранится внутри; возможность просто перебирать Iи иметь пригодное для использования имя класса кажется более приятным, чем необходимость магически знать (как в Java), что List<T>должен использовать код, требующий обычной реализации ArrayList<T>.
суперкат

2
@ArtB Пара причин. Во-первых, нет языка, на котором условное обозначение является хорошим коротким маркером для класса CFoo : Foo. Так что эта опция на самом деле не доступна. Во-вторых, вы получите странное несоответствие, потому что некоторые классы будут иметь маркер, а другие - нет, в зависимости от того, могут ли быть другие реализации того же интерфейса. CFoo : Fooно SqlStore : Store. Это одна из проблем, с Implкоторой я столкнулся , и которая, по сути, просто более уродливый маркер. Возможно, если бы существовал язык с соглашением всегда добавлять C(или что-то еще), это могло бы быть лучше, чемI
Бен Ааронсон

27

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

class Foo : BarB, IBarA
{
    // Better...
}

... предпочтительнее для этого ...

class Foo : BarB, BarA
{
    // Dafuq?
}

IMO


7
Стоит отметить , что Java аккуратно избегает этой проблемы, используя разные ключевые слова для наследования класса и реализации интерфейса, то есть inheritsпротив implements.
Конрад Рудольф

6
@KonradRudolph вы имеете в виду extends.
Стоп Harm Моника

2
@Andy Дополнительным преимуществом является то, что мы можем избежать путаницы в венгерской нотации, сохраняя при этом всю ясность, изложенную в этом ответе.
Конрад Рудольф

2
Там действительно не должно быть никакой путаницы; если вы наследуете класс, он ДОЛЖЕН быть первым в списке, перед списком реализованных интерфейсов. Я уверен, что это обеспечивается компилятором.
Энди

1
@Анди ... если вы наследуете класс, он ДОЛЖЕН быть первым в списке. Это замечательно, но без префикса вы бы не знали, имеете ли вы дело с парой интерфейсов или базовым классом и интерфейсом ...
Робби Ди

15

Нет нет нет.

Соглашения об именах не являются функцией вашего фэндома дяди Боба. Они не являются функцией языка, C # или иначе.

Они являются функцией вашей кодовой базы. В гораздо меньшей степени ваш магазин. Другими словами, первый в кодовой базе устанавливает стандарт.

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

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

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

Как клиентский класс я оставляю за собой право ни черта не говорить о том, что я говорю. Все, что мне нужно, это имя и список вещей, которые я могу назвать против этого имени. Те не могут измениться, если я не скажу так. Я СОБСТВЕННО, что часть. То, что я говорю, интерфейс или нет, это не мой отдел.

Если вы подкрадываетесь к Iэтому имени, клиенту все равно. Если нет I, клиенту все равно. То, с чем это говорит, может быть конкретным. Возможно, нет. Так как это не вызывает newэтого в любом случае, это не имеет значения. Как клиент, я не знаю. Я не хочу знать.

Теперь, когда кодовая обезьяна смотрит на этот код, даже в Java, это может быть важно. В конце концов, если мне нужно изменить реализацию на интерфейс, я могу сделать это, не сообщая клиенту. Если нет Iтрадиции, я бы даже не переименовал ее. Что может быть проблемой, потому что теперь мы должны перекомпилировать клиент, потому что, хотя исходный код не заботится о двоичном файле. Что-то легко пропустить, если вы никогда не меняли имя.

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

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


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

Если вы используете C #, вы увидите те Is. Если ваш код использует их, вы увидите их повсюду. Если ваш код их не использует, вы увидите их из кода платформы, что приведет именно к тому миксу, который вам не нужен.
Себастьян Редл

8

Есть одно крошечное различие между 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] - Технически, это специально не упоминается в статье Лискова, но это одна из основ оригинальной статьи ООП, и в моем чтении Лискова она не оспаривала это. В менее строгой интерпретации (используемой большинством языков ООП) это означает, что любой код, использующий открытый тип, предназначенный для подстановки (т. Е. Не финальный / запечатанный), должен работать с любой соответствующей реализацией этого типа.


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

@KonradRudolph Я добавил сноску по этому поводу; спасибо за отзыв :)
Luaan

4

Так, например, как эти два должны быть названы? Парсер и Конкретный Парсер? Parser и ParserImplementation?

В идеальном мире вы не должны ставить свое имя перед префиксом («I ...»), а просто позволять имени выражать концепцию.

Я где-то читал (и не могу его получить) мнение, что это соглашение ISomething побуждает вас определять концепцию «IDollar», когда вам нужен класс «Dollar», но правильным решением будет концепция, называемая просто «Currency».

Другими словами, соглашение облегчает сложность именования вещей, а упрощение является слегка неправильным .


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

Соглашение ISomething, вводит жестокие / плохие имена. Тем не менее, это не настоящая ерунда *), если только соглашения и практики, используемые при написании кода, больше не актуальны; если соглашение гласит «использовать ISomething», то лучше использовать его, чтобы соответствовать (и работать лучше) другим разработчикам.


*) Я могу сойти с ума, говоря, что это потому, что "круть" не совсем точное понятие :)


2

Как уже упоминалось в других ответах, добавление префиксов к именам интерфейсов Iявляется частью руководства по кодированию для платформы .NET. Поскольку с конкретными классами чаще взаимодействуют, чем с общими интерфейсами в C # и VB.NET, они являются первоклассными в схемах именования и поэтому должны иметь самые простые имена - следовательно, IParserдля интерфейса и Parserдля реализации по умолчанию.

Но в случае Java и аналогичных языков, где вместо конкретных классов предпочтительнее использовать интерфейсы, дядя Боб прав в том, что их Iследует удалить, а интерфейсы должны иметь самые простые имена - например, Parserдля вашего интерфейса синтаксического анализатора. Но вместо имени на основе ролей ParserDefaultкласс должен иметь имя, описывающее, как реализован синтаксический анализатор :

public interface Parser{
}

public class RegexParser implements Parser {
    // parses using regular expressions
}

public class RecursiveParser implements Parser {
    // parses using a recursive call structure
}

public class MockParser implements Parser {
    // BSes the parsing methods so you can get your tests working
}

Это, например, как Set<T>, HashSet<T>и TreeSet<T>названы.


5
Интерфейсы также предпочтительны в C #. Кто сказал, что в dotnet предпочтительнее использовать конкретные типы?
RubberDuck

@RubberDuck Это скрыто в некоторых предположениях, которые навязывает вам язык. Например, тот факт, что участники sealedпо умолчанию, и вы должны сделать их явно virtual. Быть виртуальным - это особый случай в C #. Конечно, так как рекомендуемый подход к написанию кода менялся с годами, многие пережитки прошлого должны были остаться для совместимости :) Ключевым моментом является то, что если вы получаете интерфейс в C # (который начинается с I), вы знаете, что это полностью абстрактный тип; если вы получаете неинтерфейс, типичное допущение состоит в том, что это тот тип, который вы хотите, LSP будь проклят
Луаан

1
Члены по умолчанию запечатаны, так что мы явно рассказываем о том, что наследники могут перевезти @Luaan. Правда, иногда это PITA, но это никак не связано с предпочтением конкретных типов. Виртуальный, конечно, не является частным случаем в C #. Похоже, у вас было несчастье работать в кодовой базе C #, написанной любителями. Мне жаль, что это был ваш опыт работы с языком. Лучше помнить, что dev - это не язык, а наоборот.
RubberDuck

@RubberDuck Ха, я никогда не говорил, что мне это не нравится. Я очень предпочитаю это, потому что большинство людей не понимают косвенности . Даже среди программистов. Запечатанный по умолчанию лучше на мой взгляд. А когда я начинал с C #, все были любителями, включая команду .NET :) В «реальном ООП» все должно быть виртуальным - каждый тип должен быть заменен производным типом, который должен иметь возможность переопределять любое поведение , Но «настоящий ООП» был разработан для специалистов, а не людей, которые переключились с замены лампочек на программирование, потому что это меньше работы за большие деньги :)
Luaan

Нет ... Нет. "Настоящий ООП" работает совсем не так. В Java вы должны уделять время, чтобы запечатать методы, которые не должны быть переопределены, не оставляя это как Дикий Запад, где каждый может перевернуть все. LSP не означает, что вы можете переопределить все, что хотите. LSP означает, что вы можете смело заменять один класс другим. Smh
RubberDuck

-8

Вы правы в том, что это соглашение об интерфейсе I = нарушает (новые) правила

В старые времена вы использовали префикс всего типа intCounter boolFlag и т. Д.

Если вы хотите называть вещи без префикса, но также избегать использования ConcreteParser, вы можете использовать пространства имен

namespace myapp.Interfaces
{
    public interface Parser {...
}


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}

11
новый? старый? Подождите, я думал, что программирование было больше о рациональности, чем обмана. Или это было в старые времена?

3
нет, все дело в том, чтобы составлять соглашения и вести блог о том, как они «улучшают читабельность»
Эван

8
Я полагаю, вы думаете о венгерской нотации, в которой вы действительно добавили имена к типам. Но не было времени, когда ты писал что-то вроде intCounterили boolFlag. Это неправильные «типы». Вместо этого вы должны написать pszName(указатель на NUL-завершенную строку, содержащую имя), cchName(количество символов в строке «имя»), xControl(координата x элемента управления), fVisible(флаг, указывающий, что что-то видно), pFile(указатель на файл), hWindow(дескриптор окна) и так далее.
Коди Грей,

2
В этом все еще есть большая ценность. Компилятор не собирается ловить ошибку при добавлении xControlв cchName. Они оба типа int, но у них очень разная семантика. Префиксы делают эту семантику очевидной для человека. Указатель на NUL-концевую строку выглядит точно так же, как указатель на строку в стиле Pascal для компилятора. Точно так же Iпрефикс для интерфейсов появился в языке C ++, где интерфейсы на самом деле являются просто классами (и, действительно, для C, где нет такого понятия, как класс или интерфейс на уровне языка, это всего лишь соглашение).
Коди Грей,

4
@CodyGray Джоэл Спольски написал замечательную статью «Неправильный взгляд на код» о венгерской нотации и способах ее (неправильного) понимания. У него аналогичное мнение: префикс должен быть более высокого уровня, а не типа хранения необработанных данных. Он использует слово «вид», объясняя: «Я специально использую слово« вид » , потому что Симони по ошибке использовал тип слова в своей статье, и многие программисты не поняли, что он имел в виду».
Джошуа Тейлор
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.