Визначення питань: Чи слід "ISomething" перейменувати на "Щось"? [зачинено]


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 - не завжди найкращий варіант. Якщо є причини, що деякі речі є кращими за інші, одна людина може переконати решту команди, яка складається з цих причин. Просто сліпе подання більшості без оцінки речей призводить до застою. З відповідей я бачу, що для цього конкретного випадку немає вагомих причин видаляти Is, але це не зважає на запит в першу чергу.
Вінко Врсалович

35
Книга "дядька Боба" зосереджена на JAVA, а не на C #. Більшість парадигм збігаються з C #, але деякі умови іменування відрізняються (дивіться також імена функцій нижньоїCamelCase на Java). Коли ви пишете на C #, використовуйте C # умовами іменування. Але в будь-якому випадку, дотримуйтесь головного пункту його глави щодо найменування: читабельні імена, які повідомляють значення елемента!
Бернхард Гіллер

Відповіді:


191

Хоча багато хто, включаючи «дядька Боба», радять не використовувати Iв якості префікса інтерфейси, але це є усталеною традицією для C #. Загалом кажучи, цього слід уникати. Але якщо ви пишете C #, ви дійсно повинні слідувати умовам цієї мови та користуватися нею. Якщо цього не зробити, це призведе до великої плутанини у всіх, хто знайомий із C #, хто намагається прочитати ваш код.


Коментарі не для розширеного обговорення; ця розмова перенесена в чат .
maple_shaft

9
Ви не надаєте підстав для "Загалом, цього слід уникати". На Яві мене слід уникати. У C # його слід прийняти. Інші мови слідують власним умовам.
Основні

4
Принцип "загалом" простий: Клієнт повинен мати право навіть не знати, чи говорить він з інтерфейсом чи реалізацією. Обидві мови зрозуміли це неправильно. C # неправильно прийняв умову іменування. Java неправильно отримала байт-код. Якщо я переключаю реалізацію на інтерфейс з тим самим іменем на Java, мені доведеться перекомпілювати всіх клієнтів, навіть якщо ім'я та методи не змінилися. Це не "забрати свою отруту". Ми щойно робимо це неправильно. Будь ласка, вчіться цьому, якщо ви створюєте нову мову.
candied_orange

39

Інтерфейс є важливою логічною концепцією, отже, інтерфейс повинен носити загальну назву. Отже, я вважаю за краще

interface Something
class DefaultSomething : Something
class MockSomething : Something

ніж

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Останній має кілька питань:

  1. Щось є лише однією реалізацією ISomething, але це те, що має загальну назву, ніби це якось особливе.
  2. MockSomething, схоже, походить від чогось, але реалізує ISomething. Можливо, його слід назвати MockISomething, але я ніколи цього не бачив у дикій природі.
  3. Рефакторинг складніше. Якщо щось зараз є класом, і ви лише пізніше дізнаєтесь, що вам потрібно ввести інтерфейс, цей інтерфейс повинен бути названий однаковим, тому що він повинен бути прозорим для клієнтів, чи є тип конкретним класом, абстрактним класом або інтерфейсом . Щось порушує це.

59
Чому б ви не дотримувались усталеного стандарту, який надає C #? Яка користь від цього, що перевищує витрати на заплутування та дратування кожного програміста C #, який стежить за вами?
Роберт Харві

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

4
Я погоджуюся з вами, але якщо ви проїдете цією дорогою, вам доведеться боротися з кожним лінером, який коли-небудь робився для C #.
RubberDuck

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

2
@ArtB Кілька причин. По-перше, немає мови, де конвенція є приємним коротким маркером на кшталт класу CFoo : Foo. Тож ця опція насправді недоступна. По-друге, ви отримуєте дивну непослідовність, тому що деякі класи матимуть маркер, а інші - ні, залежно від того, чи можуть бути інші реалізації того ж інтерфейсу. CFoo : Fooале SqlStore : Store. Це одна з проблем, які я маю Impl, і це, по суті, лише більш потворний маркер. Можливо, якби була мова з умовою завжди додавати C(або що завгодно), це може бути краще, ніжI
Бен Ааронсон,

27

Мова йде не тільки про іменування конвенцій. C # не підтримує багатократне успадкування, тому це спадкове використання угорської нотації має невелику, хоча й корисну перевагу, коли ви успадковуєте з базового класу та реалізуєте один або кілька інтерфейсів. Так це ...

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

... є кращим перед цим ...

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

ІМО


7
Варто відзначити , що Java акуратно уникає цієї проблеми, використовуючи різні ключові слова для наслідування класу і реалізації інтерфейсу, тобто inheritsпроти implements.
Конрад Рудольф

6
@KonradRudolph ти маєш на увазі extends.
OrangeDog

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

2
Справді не повинно бути ніякої плутанини; якщо ви успадковуєте клас, він ОБОВ'ЯЗКОВО повинен бути першим у списку, перед переліком реалізованих інтерфейсів. Я майже впевнений, що це виконує компілятор.
Енді

1
@Andy ... якщо ти успадковуєш клас, він ОБОВ'ЯЗКОВО повинен бути першим у списку. Це чудово, але без префікса ви б не знали, чи мали ви справу з парою інтерфейсів чи базовим класом та інтерфейсом ...
Роббі Ді

15

Ні-ні-ні.

Конвенції про іменування не є функцією фандуму вашого дядька Боба. Вони не є функцією мови, c # чи іншим чином.

Вони є функцією вашої кодової бази. У значно меншій мірі ваш магазин. Іншими словами, перший у кодовій базі встановлює стандарт.

Тільки тоді ви приймаєте рішення. Після цього просто будьте послідовними. Якщо хтось починає непослідовно, забирайте у нього пістолет-нерв і змушуйте його оновлювати документацію, поки вони не покаяться.

Читаючи нову базу коду, якщо я ніколи не бачу Iпрефікса, я добре, незалежно від мови. Якщо я бачу його на кожному інтерфейсі, я добре. Якщо я іноді бачу його, хтось збирається платити.

Якщо ви опинитесь у розрідженому контексті встановлення цього прецеденту для вашої кодової бази, я закликаю вас врахувати це:

Як клас клієнта, я залишаю за собою право не давати прокляття, про що я говорю. Все, що мені потрібно, - це ім’я та перелік речей, які я можу викликати проти цього імені. Вони можуть не змінитися, якщо я цього не скажу. Я ВЛАСНУЮ цю частину. Те, з чим я розмовляю, інтерфейс чи ні, - це не мій відділ.

Якщо ви підкрадетеся до Iцього імені, клієнту все одно. Якщо немає I, клієнту все одно. Те, про що йдеться, може бути конкретним. Це не може. Оскільки він не закликає newдо нього жодним чином, це не має ніякого значення. Як клієнт, я не знаю. Я не хочу знати.

Тепер як мавпа з кодом дивиться на цей код, навіть у Java, це може бути важливим. Зрештою, якщо мені доведеться змінити реалізацію на інтерфейс, я можу це зробити, не повідомляючи клієнта. Якщо немає Iтрадиції, я можу навіть не перейменувати її. Що може бути проблемою, тому що тепер нам доведеться перекомпілювати клієнта, тому що, хоча джерело не дбає про двійкові. Щось легко пропустити, якщо ти ніколи не змінив ім’я.

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

Справа не в тому, щоб жити з цим вічно. Йдеться про те, щоб не лаяти базу коду, що не відповідає вашому конкретному менталітету, якщо ви не готові вирішити "проблему" скрізь. Якщо у вас немає вагомих причин, я не повинен стати на шлях найменшого опору рівномірності, не приходьте до мене, щоб проповідувати відповідність. У мене є кращі справи.


6
Послідовність є ключовою, але статично набраною мовою та сучасними інструментами рефакторингу досить просто і безпечно змінювати назви. Тож якщо перший розробник зробив поганий вибір у називанні, вам не доведеться жити з ним вічно. Але ви можете змінити найменування у власній базі коду, але ви не можете змінити його в рамках або сторонніх бібліотеках. Оскільки I-префікс (подобається він чи ні) є усталеною конвенцією в усьому світі .net, то краще дотримуватися конвенції, ніж не дотримуватися її.
ЖакБ

Якщо ви використовуєте C #, ви побачите ці Is. Якщо ваш код використовує їх, ви їх побачите всюди. Якщо ваш код не використовує їх, ви побачите їх з рамкового коду, що веде саме до суміші, яку ви не хочете.
Себастьян Редл

8

Тут є одна крихітна різниця між Java та C #, яка є актуальною. У Java кожен член за замовчуванням віртуальний. У C # кожен член за замовчуванням опечатується, за винятком членів інтерфейсу.

Припущення, що впливають на це, керуються принципом - на Яві кожен публічний тип слід вважати не остаточним, відповідно до принципу заміни Ліскова [1]. Якщо у вас є лише одна реалізація, ви назвете клас Parser; якщо ви виявите, що вам потрібно кілька реалізацій, ви просто поміняєте клас на інтерфейс з тим самим іменем і перейменовуєте конкретну реалізацію на щось описове.

У C # головне припущення полягає в тому, що коли ви отримуєте клас (ім'я не починається з I), це клас, який ви хочете. Зауважте, це ніде не є на 100% точним - типовим зустрічним прикладом можуть бути класи типу Stream(які насправді мали бути інтерфейсом або парою інтерфейсів), і кожен має свої вказівки та фони з інших мов. Існують також інші винятки, такі як досить широко використовуваний Baseсуфікс для позначення абстрактного класу - як і в інтерфейсі, ви знаєте, що тип повинен бути поліморфним.

Існує також приємна зручність використання в тому, щоб залишити не-I-префікс ім'я для функціональності, що стосується цього інтерфейсу, не вдаючись до того, щоб зробити інтерфейс абстрактним класом (що зашкодить через відсутність багатократного успадкування класу в C #). Це було популяризовано LINQ, який використовує IEnumerable<T>як інтерфейс, так і Enumerableяк сховище методів, що застосовуються до цього інтерфейсу. Це не потрібно в Java, де інтерфейси можуть містити також реалізацію методів.

Зрештою, Iпрефікс широко використовується у світі C #, а в розширенні - у світі .NET (оскільки на сьогоднішній день більшість коду .NET написано на C #, є сенс дотримуватися рекомендацій C # для більшості публічних інтерфейсів). Це означає, що ви майже напевно будете працювати з бібліотеками та кодом, що слідує за цим позначенням, і є сенс прийняти традицію, щоб запобігти зайвій плутанині - це не так, як якщо опущення префікса зробить ваш код краще :)

Я припускаю, що міркування дядька Боба були приблизно таким:

IBananaє абстрактним поняттям банан. Якщо може бути будь-який клас-реалізатор, який би не мав кращої назви, ніж Bananaабстракція абсолютно безглузда, і вам слід кинути інтерфейс і просто скористатися класом. Якщо є краща назва (скажімо, LongBananaабо AppleBanana), немає причин не використовувати Bananaяк ім'я інтерфейсу. Тому використання Iпрефікса означає, що у вас є марна абстракція, що робить код важче зрозуміти без користі. І оскільки суворий OOP завжди буде вам кодувати проти інтерфейсів, єдине місце, де ви б не побачили Iпрефікс типу, було б на конструкторі - досить безглуздий шум.

Якщо застосувати це до зразкового IParserінтерфейсу, ви можете чітко бачити, що абстракція повністю знаходиться на "безглуздій" території. Або є що - то конкретне про конкретну реалізацію синтаксичного аналізатора (наприклад JsonParser, XmlParser...), або ви повинні просто використовувати клас. Не існує такого поняття, як "реалізація за замовчуванням" (хоча в деяких середовищах це дійсно має сенс - особливо, COM), або існує конкретна реалізація, або ви хочете абстрактний клас або методи розширення для "за замовчуванням". Однак у C #, якщо ваша кодова база вже не містить I-префікса, зберігайте її. Просто робіть class Something: ISomethingміркування щоразу, коли побачите код на зразок - це означає, що хтось не дуже добре стежить за YAGNI та будує розумні абстракції.

[1] - Технічно це не конкретно згадується в роботі Ліскова, але це одна з основ оригінальної статті OOP, і в моєму читанні Ліскова вона цього не оскаржувала. У менш суворій інтерпретації (той, який бере більшість мов OOP), це означає, що будь-який код, що використовує загальнодоступний тип, призначений для заміни (тобто не остаточний / запечатаний), повинен працювати з будь-якою відповідною реалізацією цього типу.


3
Нітпік: LSP не говорить про те, що кожен тип повинен бути не остаточним. Це просто говорить, що для типів, які не є кінцевими, споживачі повинні мати можливість використовувати підкласи прозоро. - І звичайно, у Java теж є кінцеві типи.
Конрад Рудольф

@KonradRudolph Я додав виноску щодо цього; дякую за відгук :)
Luaan

4

Так, наприклад, як слід назвати цих двох? Парсер і ConcreteParser? Парсер і парсер втілення?

В ідеальному світі вам не слід префіксувати своє ім’я короткою рукою ("Я ..."), а просто дозволити ім'я виражати поняття.

Я десь прочитав (і не можу отримати джерело) думки, що ця конвенція ISomething приводить вас до визначення поняття "IDollar", коли вам потрібен клас "Долар", але правильним рішенням буде концепція, яка називається просто "Валюта".

Іншими словами, конвенція дозволяє легко усунути труднощі з називанням речей, а простого - абсолютно невірним .


У реальному світі клієнти вашого коду (який може бути просто "ви з майбутнього") повинні мати можливість прочитати його пізніше і бути знайомим з ним якнайшвидше (або якнайменше зусиль) (дайте клієнтам вашого коду, що вони очікують отримати).

Конвенція 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. Справді, іноді це ПДФА, але це не має нічого спільного з перевагою конкретних типів. Віртуальний, звичайно, не окремий випадок у C #. Здається, ви мали нещастя працювати в кодовій базі C #, написаній любителями. Мені шкода, що це був ваш досвід роботи з мовою. Найкраще пам’ятати, що дев - це не мова, а навпаки.
RubberDuck

@RubberDuck Ха, я ніколи не говорив, що мені це не подобається. Я дуже люблю це, тому що більшість людей не розуміє непрямості . Навіть серед програмістів. Запечатаний за замовчуванням, на мій погляд, краще . І коли я почав із C #, всі були любителями, включаючи команду .NET :) У "справжньому OOP" все повинно бути віртуальним - кожен тип повинен бути замінений на похідний тип, який повинен мати змогу змінити будь-яку поведінку . Але "справжній OOP" був розроблений для спеціалістів, а не для людей, які перейшли від заміни лампочок до програмування, оскільки це менше роботи за більше грошей :)
Luaan

Ні ... Ні. Це не так, як "справжній OOP" працює взагалі. На Яві вам слід витратити час на запечатування методів, які не слід перекривати, не залишаючи це, як Дикий Захід, де кожен може над усім їздити. 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
не все про складання конвенцій та ведення блогів про те, як вони «покращують читабельність»
Ewan

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

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

4
@CodyGray Джоел Спольський написав чудову статтю " Зробити неправильний код невірним щодо угорської нотації та способів, коли це було (неправильно) зрозуміло. Він має аналогічну думку: префікс повинен бути вищим рівнем, а не типовим сховищем даних. Він використовує слово "добрий", пояснюючи: "Я цілеспрямовано використовую слово kind , тому що Симоній помилково використовував тип слова у своїй роботі, і покоління програмістів неправильно зрозуміли, що він мав на увазі".
Джошуа Тейлор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.