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

eesb3oybd3gzbmsx-kuv6wdcwgq.jpeg

Недавно я пришел в стартап единственным разрабом. Чуваки плещут идеями и требованиями, и слабо представляют, насколько сложно все это строить. Работы там оказалось столько, что я охренел — пришлось выбить бюджет на небольшую команду. Я быстро схантил троицу крепких кодеров — и автоматом стал для них тимлидом.

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

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

Вот вчера тиммейт прислал на ревью вот такой код

const isAdmin = IdentityService.CurrentUserHasRole(Roles.Admin);


Глаз сразу зацепился за префикс — братан, не используй is для нейминга булеанов. Там же в гитхабе парень ответил — сейчас уберу, это говно от предыдущих разрабов. Вот только этим предыдущим разрабом был я — наговнил когда-то давно и забыл.
Я посмеялся, сделал скрин и закинул в твиттер.

bxuvmvsnzw4nduzz_ay7nmb-92e.png

На меня мигом набросилась толпа людей. Вместо того, чтобы смеяться над ситуацией, они стали объяснять мне, что называть булы с префиксом is — отличная идея. Я не стал спорить — я начал угрожать статьей. Это она.

На самом деле префикс is нужен очень редко


В большинстве случаев мы добавляем is, чтобы донести — значение бинарное, показывает «Да/Нет». Но когда мы говорим о языках со статической типизацией (о других я и говорить не хочу, их никакая читабельность не спасет) — у нас уже есть штука, которая говорит нам, что переменная бинарна. Это её тип. Например, какой-нибудь UI элемент, и у него есть поле — enabled.

UsersPanel.enabled = true;
// или
UsersPanel.isEnabled = true;


Я пишу код в IDE, и она мне показывает тип этого поля — я отлично понимаю семантику и смысл работы с ним, без всяких is.

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

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

Я долго думал, и нашел только один мощный аргумент за префикс is для полей объектов — код будет читабельнее не в IDE. Но если ты решишь писать код таким образом, чтобы он был максимально удобен для работы без IDE — тебе придется написать куда больше символов, чем I и s. В сишарпе например хватает иллюстраций такого подхода.

Там мы фигачим префикс I у интерфейсов, постфикс Exception у исключений,»_» — у приватных полей, постфикс Event у ивентов… Все эти штуки IDE в состоянии различать, и показывать нам разными цветами сама.

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

На деле, любой более менее серьезный код не понять без информации от компилятора, и это будет становиться только хуже — ведь команда C# давно и уверенно шагает в сторону вывода типов. Код, который мы раньше пихали в сорцы, теперь не должен быть написан — компилятор «дописывает» его за меня.

И смещение подходов в том же C# в сторону большей лаконичности как бы говорит мне — братан, рано или поздно ты вообще ничего не поймешь по листингу из гитхаба. Половина того, что есть у тебя в сорцах сейчас, IDE будет выводить и показывать тебе из контекста. И какой тогда смысл цепляться за мелочи вроде префикса I у интерфейса? Чтобы человек зашел в мой сложный код, понял пять процентов из него, зато точно знал где интерфейсы, а где классы (какая кстати ему разница)?

Но в дотнете принято называть интерфейсы с префиксом I. IApi. Эта штука даже не обсуждается. Она пришла из тех времен, когда IDE нихера не умели, и разрабы хотели быстро понять, что перед ними — интерфейс, класс. структура, указатель… Моя сегодняшняя IDE может покрасить, подчеркнуть и обжирить любой символ любого ЯП ровно так, как я этого хочу. Райдер, по моему приказу, пишет интерфейсы курсивом. Зачем мне префикс? Потому что так принято, а принято так, потому что принято — и не умничай.

Но я отрастил достаточно большие яйца, чтобы поступать по другому, когда категорически не согласен. У когда мне говорят, что в дурацком гитхабе будет ничего не понятно — мой поинт простой: ну и хер с ним. В гитхабе разработка не ведется, там отображаются её результаты. Хочешь понимать, где что — клонируй репу, открывай IDE и смотри.
Лично для меня ключевой аргумент один —, а чего это вы, парни, тогда не пишете префиксы вообще для всего?

public class CApi {  // C - класс
  private int _some;  // _ - field
  public IMyType PSome { get; }  // P - property

  public void MDoStuff(){..} // M - method
  public event EventHandler MyEvent; // event, event, event
}


Зашибись же? По листингу кода всё станет понятно. Дерзайте.

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

И это разговор вообще не про красоту кода. Если забить на качественный нейминг — возникнут серьезные и замаскированные названиями проблемы в кодовой базе


Например, те же интерфейсы в сишарпе. Я делаю интерфейс IEmailManager, и единственную имплементацию — EmailManager. Если бы не буква I — мне бы бросился в глаза идиотизм с одинаковыми названиями — пришлось бы писать:

internal class EmailManager : EmailManager


Компиль надавал бы мне по рукам, и поэтому я бы написал подобную хрень:

internal class EmailManager : Interface.EmailManager


И по дороге бы еще сломал башку на нейминге папки, где будут лежать интерфейсы. Я видел, как люди в реальных Java проектах, называли папки Interface и Implementation. От такого нейминга пара шагов до создания директорий вроде '/Logic' или, хе-хе, '/Code'.

Но мне накидали предъяв — смотрите, какой смешной дурак, докапывается до имен папок.

Это один из редких моментов, когда на самом деле я не такой уж и дурак. Наименования — часть архитектуры и дизайна. От них зависит не только то, как код выглядит —, но и то, как он будет работать.

Одно плохое название директории сможет породить — и обязательно породит — плохую архитектуру. Потому что разрабы на проекте не будут каждый раз думать, куда правильно встроить модуль, на каком уровне и слое приложения он будет лежать. Они увидят фолдер, который называется '/Code', подумают такие — ну в моем классе же код, все сходится — и положат его туда. А мы получим кодовую базу, в которой логически не связанные между собой вещи будут прибиты друг к другу гвоздями.


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

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

Мы могли бы создать себе системы тестирования, которые не нуждались бы именно в интерфейсах — так например сделано в тайпскрипте и jest. А ещё мы могли бы не писать хорошо тестируемый код в проектах, где у нас и тестов-то кот наплакал. Но мы выбрали создавать кучу сущностей, которые на самом деле никакого профита проекту не дают — они нужны только для тестирования. Ну и для того, чтобы вымышленный Фаулер из нашей головы не называл нас некомпетентными дурнями.

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

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


А тут ко мне приходят, и говорят — вообще-то, тупой ты наш Фил, всю вот эту сложность принятия решений по семантике можно зашить в одну регулярку для линтера. Такие вещи должен чекать не человек.
Да ни черта подобного. Ну напишешь ты регулярку, которая диприкейтит is в булах — у тебя все ещё останутся сотни случаев, где он максимально уместен. Например в тех же локальных переменных — потому что запись вида

const admin = IdentityService.CurrentUserHasRole(Roles.Admin);


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

const currentUserIsAdmin = IdentityService.CurrentUserHasRole(Roles.Admin);
// или 
const currentUserHasAdminRole = IdentityService.CurrentUserHasRole(Roles.Admin);


Что у нас тут? Антипаттерны «is/has» для булов.

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

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

Так и с кодом — я наговнил странное решение, но пришел в индустрию — и увидел у всех то же самое. Люди даже понаписали больших и умных книжек про то, что так делать — нормально. И оно отпускает.

Стыд от того, что ты, будучи подростком, напился и вызвал проститутку в дом полный родителей и родственников — намного сильней, чем когда полное имя класса у тебя выглядит вот так:

cs
App.Exception.BusinessLayerException.BusinessLayerException : BusinessLayerExceptionBase


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

На правах рекламы


Серверы для разработки — это эпичные от Вдсины.
Используем исключительно быстрые NVMe накопители от Intel и не экономим на железе — только брендовое оборудование и самые современные решения на рынке!

8p3vz47nluspfyc0axlkx88gdua.png

© Habrahabr.ru