Написал короткую заметку про nullable поля

This commit is contained in:
Anton Vakhrushev 2020-11-08 12:03:45 +03:00
parent 7676933e40
commit d37b3473a4

View File

@ -0,0 +1,177 @@
---
title: Доступ к полям класса с null
description: Заметка о том, лучше организовать доступ к полям класса, которые могут содержать значение null
keywords: [чистый код, php, null, поля класса]
---
Нередкая ситуация, когда в классе есть поле, которое может содержать `null`.
```php
class User
{
private Email $email;
private ?string $name;
}
```
Пользователь может указать имя, а может и не указывать,
ограничившись только почтовым адресом.
А далее мы пишем код, которые работает с полем имени.
```php
class User
{
private ?string $name;
public function hasName(): bool
{
return $this->name !== null;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
}
```
И использование этого кода:
```php
/** @var User $user */
if ($user->hasName()) {
do_something_with_name($user->getName());
}
function do_something_with_name(string $name) {}
```
Выглядит хорошо.
Сначала убедились, что имя установлено, а потом использовали его.
Но статический анализатор нам обязательно припомнит, что мы пытаемся передать
в функцию `do_something_with_name` значение типа `string|null`, хотя функция
ожидает значение типа `string`.
И получается дурацкая ситуация, что формально мы должны дописать
еще одну проверку.
```php
/** @var User $user */
if ($user->hasName()) {
$name = $user->getName();
if ($name !== null) {
do_something_with_name($name);
}
}
function do_something_with_name(string $name) {}
```
Статический анализатор наш друг, он помогает находить ошибки и несоответствия
в коде.
И здесь он нашел такое формальное несоответствие типов.
Статический анализатор прав, а мы, как проектировщики интерфейса, не правы.
На самом деле мы смешали два подхода, когда описывали методы в нашем классе:
1. Получить и проверить
2. Проверить и получить
## Получить и проверить
И сразу начнем с примера использования.
```php
$name = $user->getName();
if ($name !== null) {
do_something_with_name($name);
}
```
Сначала мы получаем значение поля, а потом проверяем, соответствует ли это
значение нашим требованиям. Класс при этом будет построен вот так:
```php
class User
{
private ?string $name;
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
}
```
Заметьте, здесь уже нет метода `hasName()`, потому что этот метод перестал быть
нужным. Его роль исполняет метод `getName()`.
## Проверить и получить
Второй подход: сначала проверяем значение, а потом работаем с ним:
```php
if ($user->hasName()) {
do_something_with_name($user->getName());
}
```
Структура класса:
```php
class User
{
private ?string $name;
public function hasName(): bool
{
return $this->name !== null;
}
public function getName(): string
{
if ($this->name === null) {
throw new \LogicException('Name is not set');
}
return $this->name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
}
```
Смотрите отличия.
Метод `hasName()` остается.
А вот метод `getName()` теперь возвращает значение типа `string`.
Он выбросит исключение, если мы попытаемся получить значение,
которое не установлено.
## Использование
Теперь встает вопрос, когда и какой подход следует использовать.
- Если ситуация, когда поле не установлено, скорее исключительная, нежели
обычная, то можно использовать второй подход, а проверку опустить.
Исключение в методе `getName()` позволит обнаружить странное поведение.
- Если в пустом поле нет ничего не обычного, то подход "получить и проверить"
будет удобнее, все равно нужно делать проверку.
В любом случае, нужно смотреть на уместность того или иного подхода в каждом
случае, и не использовать их одновременно.