Занимаясь на курсах или обучаясь по различным туториалам, книгам и статьям, начинающие разработчики не слишком заботятся о том, как называть свои константы, переменные, классы, протоколы и т.п. во время написания кода. А ведь код пишется прежде всего для людей, а не для машины (машина понимает лишь язык нулей и единиц). Соответственно, для того, чтобы работать в команде, нужно позаботиться о том, чтобы код был понятен другим разработчикам (или понятен самому себе спустя несколько месяцев). Понятный код - один из важнейших критериев отбора джунов на работу. Ни один работодатель не захочет брать на работу кодера, у которого в коде "без бутылки не разберешься".
Для того, чтобы понять всю важность нейминга в Swift, приведу пример, с которым я однажды столкнулся в начале своей карьеры. Джунам будет полезно взглянуть на код "со стороны" и уловить ход мыслей ревьювера/коллеги/себя в будущем.
Передо мной стояла задача переделать экран настройки текста. Нужно было убрать/заменить некоторые кнопки, изменить некоторую логику и тп. В целом ничего сложного, но задача заняла гораздо больше времени, чем могла бы, именно из-за проблемного нейминга. Вся верстка была сделана в коде, соотвественно, чтобы выполнить задачу, сначала нужно было разобраться в том, какой код соответствует содержимому на экране.
Итак, перед нами экран редактирования текста.
Нижняя панель с настройками - это отдельный чайлд контроллер, помещенный в специальный контейнер поверх нашего экрана. Вот эту панель мы и рассмотрим.
Видно, что на нём можно нажать на кнопку карандашика, он превращается в розовую кнопку с галочкой, и вместо выравнивания и цвета появляется стиль текста.
Задача: А) убрать шрифты, Б) на их месте расположить кнопки выравнивания и цвета, В) вытащить кнопки стиля, чтобы они всегда были видны, Г) убрать ползунок размера.
Вроде всё просто. ???? Всего-то надо удалить несколько элементов, а остальные переставить. "Ща за 5 минут сделаю!" ????
Смотрим в код. Верстка выглядела вот так:
Немало тут элементов... ???? Количество строк тоже несколько настораживает. Но попробуем угадать, что тут есть что ???? ????
separator (326 строка)- это, судя по названию, линия под заголовком
minSliderImage и maxSliderImage (333 и 335) - это, очевидно картинки по краям слайдера
slider (334) - это сам слайдер внизу
Вот и всё, что ясно из этого экрана ????♂️ Конечно, можно (и нужно!) посмотреть иерархию вьюх в дебаггере и попробовать покликать там по элементам на экране, чтобы стало немного понятнее. Но это дополнительное время, да ещё и не всегда от созерцания иерархии становится всё понятно. Но об этом в конце статьи. А сейчас попытаемся понять по коду что есть что. Погнали по порядку.
collectionView (327 строка) - Судя по экрану, это может быть: A) коллекция из названий шрифтов, а может быть Б) вторая строка с кнопкой карандаша, кнопками alignment и кнопками цвета. Или это В) коллекция, которая появляется, когда нажимаешь кнопку с карандашом.
stackView (328) - Ещё одно малоговорящее название. Мне кажется допустимым такое абстрактное название давать элементам, если они чуть ли не единственные на экране. Но когда верстка сложная, под этим названием может скрываться всё, что угодно.
collectionContainerView (329) - Казалось бы, у нас есть коллекция, так зачем для неё ещё и контейнер? Ну, разные могут быть причины. Может, какое-то особое расположение надо. Ок, пусть так.
viewForBoldButton (330) - Судя по названию, это какая-то отдельная вьюха для кнопки "жирный". Возникает вопрос: "А почему только для неё? Ведь у нас есть ещё и кнопки "italic", "bold italic", "stroke", "stroke bold" и "stroke bold italic". В общем, вопросов пока только прибавляется. ????
boldTextButton (331) - А вот и сама кнопка "жирный". Интересно, зачем её надо отдельно помещать на экран? Разве она не вкупе с остальными должна быть? И самое странное - зачем под неё отдельное вью добавлено строкой выше, но сама кнопка добавляется не на это вью, а просто на вью экрана? ????
bottomStackView (332) - О, уже есть какая-то подсказка! Очевидно, этот стек вью находится где-то внизу. Скорее всего, это должен быть стек вью для слайдера и двух картинок, обозначающих размер шрифта. Так, стоп! А зачем тогда они были добавлены на вью, а не на этот стек вью? Они же должны быть arrangedSubviews этого нижнего стек вью, разве нет? Что-то не сходится... Ладно, едем дальше.
backgroundContainerView (336) - Вроде бы и подсказка есть, да вот что-то не совсем очевидно, что это за бэкграунд такой. Если бы это был какой-нибудь заблюренный фон, то логично было бы тогда все остальные элементы поместить в этот контейнер. Но нет же, они помещены на вью, а сам контейнер помещен поверх них. Так что же это тогда?
Вот вьюхи закончились, но остались ещё вопросы: где кнопки Cancel и Save, где сам заголовок? В итоге мы имеем очень много вопросов.
Что же тут на самом деле?
Спустя какое-то время ковыряния я выяснил следующее:
Я угадал только с сепаратором, слайдером и картинками, обозначающими размер шрифта.
Кнопки Save и Cancel вместе с заголовком лежат на том самом ноунейм "stackView" (пункт 5 из списка выше). Что ж, в таком случае его стоило бы назвать "headerStackView" или "titleStackView" или как минимум "topStackView". Логично?
Ноунейм collectionView - Это всё таки коллекция с названиями шрифтов! Ура! Ну, почему бы её не назвать было как-нибудь "fontNameCollectionView" или хоть как-нибудь иначе, что подсказало бы, что это такое, и где это искать? (В тот момент мысли в голове проносились не столь цензурные ????)
collectionContainerView - Это не контейнер для вышеописанной ноунейм коллекции, как можно было подумать, а контейнер для чайлда, который содержал в себе коллекцию с ячейками, обозначающими стили шрифта. ???? Оставим в стороне это неожиданное решение, разберем только нейминг. Какое-нибудь textStyleCollectionContainerView подошло бы куда лучше. Как считаете?
viewForBoldButton и boldTextButton - как выяснилось, это та самая кнопка с карандашом. Я уже не помню, выяснил ли я тогда, зачем надо было эту кнопку лепить из двух вьюх, и для чего тут какая-то viewForBoldButton, или не выяснил. Но весьма был удивлён, почему бы не назвать эти вьюхи textStyleButton или, например, pencilButton, чтобы хоть как-то опознать их на экране.
bottomStackView - оказалось, что это стек, который состоит из двух объектов: сегмент контрола с капитанским названием segmentControl и стека colorsStackView (Ура, наконец-то что-то понятное!). segmentControl - под этим ничего не значащим названием скрывалась настройка выравнивания текста. Почему стек называется bottomStackView мне непонятно, он ведь не внизу. Наверное, для того, чтобы показать, что это не просто стэк вью, как было в заголовке, а "нижний" стек вью. Чтобы хоть как-то отделить его от ноунейм стек вью из пункта 2. Но на таком сложном экране, как вы поняли, сложно вообще понять, что есть что. Так что такое название вообще не помогает. Вот если бы этот стек назывался alignmentAndColorsStackView, то вопросов бы не возникло. ????
backgroundContainerView - неожиданно, но им оказался контейнер для отображения чайлда с настройками цвета. Вдумайтесь, вот как тут вообще связаны цвет и бэкграунд? Гугл переводчик говорит, что "цвет" - это color. ???? Поэтому данный контейнер должен был называться как минимум colorSettingsContainerView или что-то схожее, что однозначно определит его предназначение. Но назвать его бэкграунд? Как-то странно. Изучив код можно сделать вывод, что такое название скорее всего продиктовано не менее странным названием чайлда, который отвечает за настройки цвета: BottomBackGroundController, который кто-то создал до этого. Редактирование цвета названо как "нижний контроллер бэкграунда". Странно, не так ли? Просто непонятно, причем тут цвет и расположение с бэкграундом.
Вот такая история! Обидно было за потраченное время на задачу, которая того не стоила.
Вероятнее всего, что, как и все мы когда-то, автор кода не сильно задумывался над тем, что кто-то вообще будет в этот код лезть и что-то там править. По неопытности о будущем не сильно задумываешься, тем более, когда горят сроки. Тем не менее этот код очень удачно подвернулся для того, чтобы наглядно показать, каких ошибок следует избегать.
Читая блоги и книги маститых кодеров, я встретился с одной очень интересной мыслью: "Самое сложное в программировании - это правильный нейминг". ???? Не помню, кто так сказал. Возможно, так сказал дядюшка Боб (Роберт Мартин). Если знаете, напишите в комментариях ????
Если сложности с неймингом
Итак, смею надеяться, теперь вы поняли, как сильно может запутать неподходящий или ничего не говорящий нейминг. Так что, уважаемые джуны, именуйте сущности правильно, по смыслу. Это действительно непросто, и порой не всегда быстро приходит в голову удачный нейминг. Иногда просто нет времени долго думать над названием. В таком случае ставьте три слэша над переменной/константой/(подставьте что-то своё) и пишите документацию к сущности при её объявлении. То есть напишите там, для какой цели создана эта штука, где и когда она используется.
Эту документацию потом можно из любого места программы посмотреть, тапнув с зажатым Option на переменную/константу(ну, вы поняли). Прямо как у Apple ко всем её публичным переменным, константам, классам, структурам, энамам и протоколам.
Кстати, удобнее всего пользоваться сочетанием клавиш Cmd+Option+/ при курсоре, расположенном прямо на строке объявления вашей сущности. Тогда Xcode сделает за вас грязную работу:
Вам останется только заполнить соответствующие поля.
Начинайте уже сегодня
По моей инициативе мы с коллегами с некоторых пор всегда пишем документацию ко всем контроллерам/классам/структурам, открытым свойствам и методам. Особенно, если их названия не кристально ясны и документация к ним не будет похожа на заметки от Капитана Очевидности. Чтобы максимально не быть кэпами мы пишем для чего, когда и где используется та или иная штука. Сначала непривычно, но потом это сильно ускоряет работу. Впоследствии, когда натыкаешься на такую документацию, она может сработать как спасательный круг и сэкономит вам и коллегам массу времени.
Если вы (или у вас в команде) так ещё не делается, то начните с себя. Через какое-то время коллеги оценят, и вам будет проще протолкнуть инициативу, чтобы так делали все в вашей команде. Что касается тестовых работ, то такой подход ваши будущие работодатели точно оценят!
Советы по неймингу
Рекомендую ознакомиться с принципами нейминга в наиболее популярном код-стайле от Рея Вандерлиха: https://github.com/raywenderlich/swift-style-guide#naming
Там всё подробно написано. Вот некоторые рекомендации:
Все булевые значения именовать с какой-либо из "приставок": is, has, should, will, can, did, was и т.п. Прям как у Эпл isHidden, hasPrefix и тп. То есть "приставка" всегда в начале: не buttonIsHidden, а isButtonHidden.
Протоколы делегатов именовать с "суффиксом" Delegate. Например, MyAwesomeViewControllerDelegate.
Протоколы из категории "обладающий способностью" называть с "суффиксом" -able или -ible. Например, что-то вроде ConsolePrintable.
Протоколы, описывающие что-то (какой-нибудь сервис или обработчик) должны быть написаны как существительные. Например, протокол какого-нибудь хранилища лучше всего так и назвать Storage (а не StorageProtocol, как любят писать джуны). И тогда ваша сущность, построенная на этом протоколе можно назвать StorageImpl: Storage (от слова "implementation" - реализация). Или какой-нибудь MainStorage или LocalStorage и т.п. Если это тестовое хранилище (моковое), оно может иметь название MockStorage: Storage и т.п.
Про поиск в иерархии вьюх
Не всегда в иерархии (Debug View Hierarchy) становится всё сразу понятным. Потому что в коде, к примеру, ваш стек вью именуется bottomStackView, а в иерархии он будет просто UIStackView. Соответственно, если стек-вьюх на экране много, то трудно понять, где какая. Но есть простой способ: добавить значение в строковое свойство accessibilityIdentifier. Это свойство есть у всех вьюх (вроде бы оно используется в Универсальном доступе при проблемах со зрением, но это неточно), и его очень удобно использовать именно для дебага в иерархии вьюх.
Спасибо за уделённое время! Happy coding! :]
Gargo
Давайте начнем с самого начала:
1)вам часто попадаются джуны, которые пишут UI на constraints чисто кодом? Подозреваю, что очень редко. А если код из вашего примера и встречается, то например, в сочетании с какой-нибудь хитрой анимацией, поэтому вам в любом случае придется вникать, что во что вложено и по какому принципу (названия переменных это никак не отражают)
2)возьмем например, alignmentAndColorsStackView. Даже в вашем примере его содержимое меняется так, что название переменной перестает соответствовать действительности. Вы предлагаете переименовывать переменные каждый раз при изменении UI?
Мой вариант решения вашей проблемы - в UI есть текст (который наверное еще и локализуется) и изображения. Находите сначала контролы, которым эти текст/изображения присваиваются, найти остальные контролы и понять структуру UI после этого становится проще.
Все булевые значения именовать с какой-либо из "приставок": is, has, should, will, can, did, was и т.п. Прям как у Эпл isHidden, hasPrefix и тп. То есть "приставка" всегда в начале: не buttonIsHidden, а isButtonHidden.
по поводу этого - как вы называете переменные, когда по логике нужно вставить "is", но по правилам английского нужно "are"?
Pavel-Laukhin Автор
Спасибо за комментарий!
Если по смыслу лучше подходит «are», я именую с «are». :] А как иначе? Язык Свифт как раз-таки и создавался похожим на просто английский язык, поэтому логично соблюдать правила языка.
UI на констрейнтах чисто кодом — постоянно. А что в этом такого? На мой взгляд, это хороший способ, ведь в коде всё прозрачно и понятно, никакой магии, всё на виду.
Предлагаю ли я переименовывать переменные всякий раз, когда меняется UI? Конечно! А какой смысл, например, оставлять в названии какой-нибудь color, когда теперь эта кнопка отвечает за size? :] Как дядюшка Боб завещал: «Видишь плохо именованную штуку — не проходи мимо, переименуй в более подходящее название». Если политика компании позволяет, конечно. У нас позволяет. :]
Касаемо вашего способа по поиску текстов и картинок там, где они присваиваются. Да, это тоже вариант, спасибо. Иногда и такой способ может выручить. А если картинки и тексты присваиваются не в месте их объявления? А если картинки присваиваются как imageView.image = UIImage(named: «тут какое-то абстрактное название с цифрой, потому что так из Фигмы сохранилось»)? Это мне каждый раз лезть в ассеты и искать там эту картинку? На мой взгляд, всё же быстрее правильно проименовать и/или написать пару строк документации, не заставляя читателя рыться в картинках и искать по всему документу (а то и вовсе снаружи документа), где там присваиваются значения у свойств. Тем более, что локализованые тексты обычно задаются в виде ключа, а содержимое ключа прописано в файле Localizable.string (а там 100500+ ключей, и содержимое порой повторяется… да ну, нет, не стоит так издеваться над проверяющим).
Gargo
UI на констрейнтах чисто кодом — постоянно. А что в этом такого?
Я ничего не имею против такого подхода, просто вряд ли таким занимаются джуны. Тем более что иногда еще и к constraints нужно обращаться в определенном порядке.
Предлагаю ли я переименовывать переменные всякий раз, когда меняется UI? Конечно!
Вам не приходилось отслеживать в git какие-то правки, когда в каждом комите названия переменных меняются - считай рефакторинг?
По поводу текстов и картинок - ну да, зависит еще и от того, сколько их у вас и в каком виде у вас они хранятся.
«тут какое-то абстрактное название с цифрой, потому что так из Фигмы сохранилось»
А Фигма разве умеет гарантировать, что все ключи картинок (включая прошлые и будущие) будут например, уникальными? Работаю обычно с zeplin - там половина изображений называется "cropImage", т.е. постоянно изображения нужно при импорте переименовывать вручную и/или группировать. Кстати может проблема в вашем коде не с названиями переменных, а
По поводу Localizable.strings - а зачем вам повторяющиеся строки в этом файле, если xcode все равно выберет одну из этих строк? А еще потом могут вылезти баги, потому что значения одинаковых ключей могут отличаться, а xcode все равно выберет из нескольких значений одно, причем непредсказуемым образом.
Pavel-Laukhin Автор
Вам не приходилось отслеживать в git какие-то правки, когда в каждом комите названия переменных меняются - считай рефакторинг?
Да, приходилось. Да вроде особых проблем не было с этим. Может, были по началу, но сейчас вроде норм. Может, у нас не такой масштаб, как у вас, поэтому нет особых проблем, не знаю.
А Фигма разве умеет гарантировать, что все ключи картинок (включая прошлые и будущие) будут например, уникальными?
Как дизайнер назовет, так оно и сохраняется из Фигмы :] Не особо народ запаривается на этот счет. То есть, иногда переименовывают, конечно, но всё равно, к сожалению, нет гарантии, что по названию картинки будет понятно, что это.
По поводу Localizable.strings - а зачем вам повторяющиеся строки в этом файле, если xcode все равно выберет одну из этих строк?
Ключи у нас уникальные, а вот значения могут быть разные. Например, сверстали два разных человека два разных экрана. Условно, один верстал онбординг, второй в это время верстал экран подписки. И там, и тут встречается одна и та же фраза на кнопке Продолжить ("Continue"). И вот один разраб в Localizable сделал строчку
"onboardingContinueButton" = "Continue";
, а второй сделал такую же параллельно"subscriptionContinueButton" = "Continue";
. Потом когда-нибудь эти ветки слились в develop, и вот вам два одинаковых текста на разных экранах. А если таких экранов много, то вероятность повторяющегося текста еще больше растет. В общем, я к тому, что по ключам можно искать, как вы предложили, но зачем? Если уж на то пошло, можно вообще не просить никого делать нормальный нейминг, а каждый раз заниматься вот такими вот детективными расследованиями. Но зачем? Цель статьи как раз и заключается в том, чтобы побудить давать более-менее однозначные названия, а не какие-то ноунейм или абстрактные, из-за которых приходится потом тратить лишнее время на поиски. :]