Best Practices
Введение
На этой странице перечислены наиболее распространенные ошибки, которые, как правило, новички повторяют не один раз, что существенно увеличивает время ревью.
Однако, большинство из этих ошибок совсем не сложные и их легко заметить и исправить самостоятельно.
Это позволит существенно сократить время прохождения ревью и быстрее стать крутым разработчиком ;)
Для самопроверки, обращайтесь к тому разделу, которым вы сейчас занимаетесь.
Критичные пункты для ревью
warning
При нарушении любого из пунктов данного списка ревью будет прервано и отдано на исправление без полной проверки оставшегося кода
- Соответствие кодстайлу
- Если приложение поддерживает не только портретную ориентацию, проверяйте все экраны на соответствие дизайну перед отправкой на ревью
- Не должно быть никакого хардкода текста, который показывается пользователю, надо использовать строки локализации
- Проверяйте, что не добавили в
gitничего лишнего, настройте.gitignore - Код не должен содержать никаких дебажных
printи закомментированных строк, которые не несут никакой смысловой нагрузки - (iOS) Все
@IBOutletдолжны быть приватными - (iOS)
Extensionsдля каждого класса должны находиться строго в отдельных файлах, с соответствующим названием файла —ClassName+Extensions
Общее
Следите за неймингом
У вас не должно быть переменных и методов, которые не несут в названии никакой информации о том, для чего они предназначены.
Например:binding.button.setOnCLickListener { buttonClickAction() }— ни кнопка, ни метод не несут абсолютно никакой информации о том, к чему они относятсяКликабельные элементы UI не должны сами решать, какой метод вьюмодели им вызывать
-
exitButton.setOnClickListener { viewModel.clearUserData() }— UI не должен говорить вьюмодели — чисти данные пользователя -
exitButton.setOnClickListener { viewModel.onExitButtonPressed() }— UI должен говорить вьюмодели — нажата кнопкаexitButton - Публичное API вьюмодели должно быть таким, чтобы по нему явно было понятно, когда его использовать
-
- Используйте аннотации @Throws только в случае, если это попадает в Swift код
- Не создавайте общий класс констант — рано или поздно получится свалка.
Константы должны создаваться именно там, где они имеют смысл Продумывайте имена функций и их содержимое
- Памятка
- Функция должна выполнять какую-то одну конкретную задачу, не должно быть функций, которые делают по 10 дел сразу и называются
doSomething1AndDoSomething2AndDoSomething3
Не реализуйте то, что уже есть в системе
Если что-то в дизайне немного отличается от системного варианта
В большинстве случаев системная реализация подходит.Alert,Toolbar, кнопка "назад" — спросите у руководителя, действительно ли должно быть 1 в 1 как в дизайне.Не работайте с сетевыми сущностями в приложении
- Преобразуйте сетевые сущности в свои — доменные и используйте их во всем приложении
- Если в приложении вы работаете с сетевыми сущностями, то в случае, если на сервере что-то изменят, например изменят имя какого-то поля или поменяют вложенность полей, то вам придется исправлять все места, где используется эта сущность.
А если при получении сетевой сущности вы сразу преобразуете(маппите) ее в доменную, то при изменении сетевой вам нужно будет просто изменить функцию-маппер.
- Если от сервера где-то приходит
null, вы можете как-то заменить его наnot-nullзначение, чтобы удобнее работать в приложении. Например, если приходит полеdescription = null— в маппере можно заменить его на пустую строку - Доменная сущность пишется независимо от серверной, она включает только то, что реально будет использоваться приложением, а не хранит абсолютно все данные, полученные от сервера
Используйте автоформатирование после изменения каждого файла
-
Option+Command+L(Android Studio) -
Control+I+L(Xcode)
-
Нейминг переменных для
StateиActionЕсли вы используетеStateиActions, то либо просто называйте переменные вьюмоделиstateиactions, либо называйте так, чтобы в их имени обязательно фигурировало, что этоstateилиactions.Правила для заданий, начиная с 4ого блока
- Новые зависимости добавляйте сразу в libs.versions.toml в нужный блок
- Не используйте
MRресурсы на платформе, все ресурсы, которые нужны только на платформе получайте на Android черезR, на iOS черезR.swift - В мультиплатформенных ресурсах должны быть только те, которые управляются общей логикой. напоминалка
Не допускайте сильной вложенности кода
- Используйте early return
- Избегайте callback hell
Не используйте форскасты
О том, когда их действительно нужно использовать вы поймете, когда дорастёте до мидла :)Используйте
KeyValueStorageправильноДанные изKeyValueStorageне должны сохраняться в свойства класса, каждый раз надо обращаться кKeyValueStorageи получать актуальное значение.Создавайте кастомные
ExceptionsправильноКастомныеExceptions, которые вы сами кидаете, наследуйте отRuntimeException(), либо от более конкретных его наследников.Также, не забывайте прокидывать в родителя
causeреально произошедшей ошибки, чтобы потом увидеть ее вstackTraceНе забывайте про конфликты имен Kotlin и iOS
- Если у вас в
commonкоде будет свойствоdescription, вот как оно будет доступно на iOS - Одинаковые имена переменных в двух разных интерфейсах
- Функции с одинаковыми именами аргументов, но разными типами
- Классы с одинаковым именем, но в разных пакетах
- Если у вас в
Константы должны иметь такое название, чтобы по нему было понятно, для чего они нужны
- Плохое название константы:
const val REPO_NAME = "repo_name" - Хорошее название константы:
const val REPO_NAME_ARG_KEY = "repo_name"
- Плохое название константы:
Не надо выдавать никакие данные из элемента списка (например по клику)
По клику на элемент списка он должен просто информировать наружу — "на меня нажали"Никогда не обрабатывайте
Error-ы вcatch- У
Throwableдва наследника —ExceptionиError.Exceptionнужно обрабатывать, они исправимы, аError-ы — неисправимы, их обрабатывать не надо. Приложение должно упасть с информацией о том, что пошло не так (вErrorэта информация есть) - изучите документацию
- У
iOS
Logic
Устанавливайте версии подов в
Podfile- Версии установленных подов можно посмотреть после установки подов в файле
Podfile.lock - Если версии подов не будут явно обозначены - то у нового разработчика или у тебя (на другом компе) при установке подов могут подтянуться более новые версии. Есть вероятность, что в этих новых подах что-то будет изменено и проект не скомпилируется. Либо, как сейчас популярно, в библиотеку всунут зловредный код в новой версии, и он тоже скачается :)
- Версии установленных подов можно посмотреть после установки подов в файле
Называйте экраны приложения правильно
В UIKit-экранах используйте суффиксViewController— это системная сущность, которая не обязательно занимает весь экран (их может быть несколько: ваш контроллер,UINavigationController,UITabBarController, модалки). В SwiftUI и на Android (Compose) для экранов приложения используйте суффиксScreen— это абстракция экрана приложения, а не экран девайса.Называйте
@IBOutlet-ы правильно- Все аутлеты должны содержать в название тех данных приложения, которые будут в нем отображаться
- По названию каждого аутлета должно быть однозначно понятно, к какому классу
Viewон относится - Пример плохого нейминга для поля с описанием чего-либо —
@IBOutlet private var label: UILabel! - Хороший нейминг —
@IBOutlet private var descriptionLabel: UITextView!
- Обрабатывайте стейты на iOS правильно
- SplashScreen делать в
LaunchScreen.storyboard, а не вSplashViewController - Не устанавливайте ресурсы для UI элементов в
xib(текст, картинки и тд). Также, все строки локализации должны устанавливаться воViewControllerв отдельной функцииlocalize() - Все изменения конфигурации проекта должны производиться в xcconfig
Не забывайте про циклы сильных ссылок
- Используйте
weak selfтам, где он нужен - Приведет к утечке памяти:
cell.onTap = { self.navigationController?... } - Не приведет к утечке памяти:
cell.onTap = { [weak self] in self?.navigationController?... }
- Используйте
Убирайте текст кнопки "Назад" правильно
- Чтобы у кнопки "Назад" не было текста — нужно у предыдущего экрана выставить в
navigationItem.backButtonTitle = "" -
navControllerберет название для кнопки "Назад" от того экрана, на который будет производиться переход назад
- Чтобы у кнопки "Назад" не было текста — нужно у предыдущего экрана выставить в
Ключи в классах всегда
private static letКлючи не являются уникальными для конкретного экземпляра класса, поэтому их нужно делать статическимиРабота с ресурсами из
R.swift- Когда вы добавили картинки, цвета и другие ресурсы в Assets, вы можете получить к ним доступ через
R.swift, например:R.image.somethingImage() - Некоторые ресурсы, такие как цвета,
R.swiftвозвращаетnullable. В этом случаеguardобработку делать не нужно, потому что разR.swiftпредоставил доступ к переменной, значит смог ее сгенерировать на основе цвета.Поэтому, чтобы узнать о том, что при наличии переменной ресурса самого ресурса нет — можно использовать форскаст. Таким образом, если все-таки произойдет ошибка — мы будем разбираться в причине, а если бы мы обработали
null— то не узнали бы, что такая ошибка может произойти
- Когда вы добавили картинки, цвета и другие ресурсы в Assets, вы можете получить к ним доступ через
UI
Проверяйте имя файла картинки, которую скачали с
Figma- Имя картинки должно явно обозначать, что это за картинка, аналогично с неймингом переменных
- Векторные картинки должны быть
single scale
Используйте кастомные
View- Если какой-то одинаковый набор элементов используется сразу на нескольких экранах — выносите его в отдельную кастомную
UIViewи используйте на нужных экранах - добавление кастомных вьюх должно быть не через код, а через
Interface builder
- Если какой-то одинаковый набор элементов используется сразу на нескольких экранах — выносите его в отдельную кастомную
В
.xibдобавляйте ресурсы только для отладки- Добавляйте текст, элементы списка, картинки, чтобы легче ориентироваться на дизайне экрана. Не добавляйте такой текст, который должен быть в готовом приложении, чтобы потом не забыть поменять.
- Например, для названия кнопки ввода используйте не "Ввод", а "//Ввод", тогда, если бы вы забыли заменить этот текст, то сразу бы увидели это при запуске приложения
Если фон во всем приложении одинаковый — устанавливайте его правильно
- Вариант 1: установить в
AppDelegate,navigationController?.navigationBar.barStyle = .black— сразу после создания контроллера устанавливаем цвет - Вариант 2: в
.plistустановитеUIViewControllerBasedStatusBarAppearance = NOиStatus bar is initially hidden = NO, тогда вообще не придется устанавливатьnavigationBar.barStyle = .black
- Вариант 1: установить в
Android
Logic
- В рамках Composable функций не используйте
lifecycleScopeдля задач, связанных с UI. Для корутин, живущих только во время композиции, используйтеLaunchedEffectилиrememberCoroutineScope(). - Обрабатывайте стейты на Android правильно
- Подписка composable на
ViewModelдолжна происходить прямо внутри composable, через state‑обёртки (collectAsState,observeAsState), а не вручную черезlaunch/collect. - Тема приложения должна задаваться на уровне
Activity(или вApplication/Manifest), а не внутри отдельных composable. - Константы класса должны находиться в
сompanion object, а в свойствах должно быть все то, что уникально для каждого экземпляра класса. Не запутайтесь с удалением `Observer`
- так ничего не удалится
private val request = MutableLiveData<...>()
request.observeForever {
if (it != null) {
...
}
}
request.removeObserver { } - а вот так удалится
private val request = MutableLiveData<...>()
private val requestObserver: Observer<...> = Observer { ... }
request.removeObserver(requestObserver)
- так ничего не удалится
Настраивайте
ToolbarправильноВ Compose не используйте системный `ActionBar`. Верхняя панель должна реализовываться через `TopAppBar` внутри `Scaffold`, что даёт полный контроль и естественную интеграцию с Navigation Compose.
UI
Contentфункцию экрана называйте аналогично названию экрана- Плохой нейминг:
DetailRepoInfoScreenиRepoInfoContent - Хороший нейминг:
DetailInfoScreenиDetailInfoContent
- Плохой нейминг:
- Отступы всегда должны быть кратны 4 (4, 8, 16, 24, 32). Если в дизайне по-другому, задавайте вопросы
- Если какой-то набор элементов используется на нескольких экранах — выносите его в отдельную
Composableфункцию и переиспользуйте - Не передавайте ViewModel глубоко в дерево функций. UI должен зависеть от данных, а не от ViewModel.
State hoisting — выносите состояние наверх
- Composable должны быть максимально «тупыми». Плохо:
@Composable fun Counter() { var count by remember { mutableStateOf(0) } } - Лучше:
@Composable fun Counter(count: Int, onIncrement: () -> Unit)
- Composable должны быть максимально «тупыми». Плохо:
Перед отправкой на ревью
- Проверяйте, чтобы у MR не было конфликтов. Если конфликты есть, вам нужно смержить целевую ветку ("destination", главная/основная ветка) в ветку с вашими изменениями ("source", ваше ответвление от основной ветки) и исправить конфликты
- При повторной отправке на ревью пройдитесь по всем предыдущим комментариям и убедитесь, что все исправили. Отвечайте на каждый комментарий, чтобы ничего не пропустить и чтобы ревьювер сразу понимал, что вы это исправили.
- После каждого коммита выделяйте немного времени, чтобы отсмотреть все изменения на наличие ошибок, перечисленных на этой странице.
- Обязательно обращайте внимание на критичные для ревью пункты. Ошибки в этих пунктах будут прерывать ревью.
- После создания итогового merge request, отсмотрите все сделанные в нем изменения. Благодаря тому, что вы проверяли их после каждого коммита, это не займет у вас слишком много времени.
- Чем чаще вы будете перепроверять себя, тем больше ошибок будете видеть и сможете не допускать их в будущем
Такая самопроверка не только позволит вам не допускать перечисленных ошибок в будущем, но и ускорит ревью вашего merge request, потому что в нем уже не будет части ошибок.