Делайте return, как только нашли ответ
Работая с React-проектами, я столкнулся с повторяющейся проблемой: render-функции, которые сложно понять. Я хотел бы рассказать о наиболее частой причине данного усложнения — вложенные if-else операторы в render-функциях, и как этого можно избежать.
Что такое render-функция?
Оттолкнемся от React-документации. Render-функция должна возвращать React Element или нулевое значение. В идеале она должна быть чистой и использовать только this.props и this.state.
Зачем нужен условный оператор в render-функции?
В чистой render-функции условный оператор обычно основан на состоянии/параметрах компонента. Есть две основные причины if-проверок:
- Есть ли у нас все необходимые данные?
if (this.props.user) { // Есть данные, отображаем компонент юзера } else { // Нет данных юзера }
- Различные варианты отображения на основании state/props.
if (this.props.showWarning) { // Дополнительно нужно отобразить компонент с предупреждением }
Но нельзя сказать, что наличие условных операторов в render-функции само по себе плохо или хорошо. Проблема заключается именно в организации таких проверок.
Плохая организация
Давайте перейдем к примеру:
render() { let content; if (this.props.users) { if (this.props.users.length) { content = ( <List> {this.props.users .map(user => <UserCard key={user.id} user={user} />)} </List> ); } else { content = <Warning>Empty</Warning>; } } else { content = <Loading />; } return content; }
Выглядит сложновато. Попробуем упростить:
render() { if (!this.props.users) { return <Loading />; } if (!this.props.users.length) { return <Warning>Empty</Warning>; } return ( <List> {this.props.users .map(user => <UserCard key={user.id} user={user} />)} </List> ); }
Какие преимущества мы получаем?
Читабельность
Основная задача нашей функции — это отображение списка пользователей. И после простого рефакторинга мы с легкостью можем сосредоточиться именно на этой части. Почему? Потому что теперь в ней нет никаких вложений и нам не нужно задумываться: «Все ли данные представлены?». Мы сделали все необходимые проверки в верхней части, поэтому ошибок быть не должно. Это снижает когнитивную нагрузку и позволяет сосредоточиться на оптимистичной части кода (happy path). Мы также можем сказать, что логика визуализации пользователей является изолированной и визуально выделяется, и то же можно сказать о каждой защитной проверке.
Меньше ошибок и погрешностей
Нам не нужно работать с глубокой вложенностью, что уменьшает вероятность ошибки со скобками. Если код становится легкочитаемым и понятным, найти в нем ошибку гораздо проще.
Недостатки?
Нет никаких недостатков! Но не все со мной согласятся. Часто люди видят проблему в том, что количество операций возврата растет, а у функции должна быть одна точка выхода. Но если копнуть, кто и почему это придумал, то в основном все дороги ведут к правилу Single Entry — Single Exit.
Single Entry — Single Exit
Здесь проблема заключается в том, что принцип Single Entry — Single Exit используется для предотвращения входа в функцию оператором GO-TO. Суть отдельного выхода состоит в том, что функция возвращается в единое место, а не прыгает при помощи GO-TO в какую-то другую часть приложения, никогда не достигнув возвратного значения. Кроме того, нигде не сказано, как много возвратных значений у вас может быть. Принцип Single Entry — Single Exit сформулировал Эдсгер В. Дейкстра, который решительно выступает против практики использования операторов GO-TO.
Что еще?
В книге Code complete написано следующее:
«Минимизируйте число возвратов из каждого метода. Тяжело понять логику метода, если при чтении его последних строк вы не подозреваете о возможности выхода из него где-то вверху. По этой причине используйте операторы возврата благоразумно — только если они улучшают читабельность».
Но, кроме этого, в книге также сказано:
«Используйте return, если это повышает читабельность. В некоторых методах при получении ответа хочется сразу вернуть управление вызывающей стороне. Если метод определен так, что обнаружение ошибки не требует никакой дополнительной очистки ресурсов, то отсутствие немедленного возврата означает необходимость писать лишний код. Вот хороший пример ситуации, когда возврат из нескольких частей метода имеет смысл».
Как раз в нашем случае множественный возврат уместен из-за улучшения читабельности.
Также в книге есть отдельная глава о рефакторинге и перечень его видов/причин, один из которых я хотел бы подчеркнуть.
«Возврат из метода сразу после получения ответа вместо установки возвращаемого значения внутри вложенных операторов ifthenelse»
Выход из метода сразу же после нахождения возвращаемого значения часто помогает облегчить чтение кода и снизить вероятность ошибок. Альтернативный вариант — установка возвращаемого значения и следование к выходу из метода через заторы операторов — может оказаться более сложным".
Есть еще два похожих паттерна, которые мне удалось найти, — Guard Clause и Bouncer Pattern. Они выглядят примерно так же, как описанный случай.
Оставляйте свое мнение в комментариях!