Как работать с необязательными данными в модели типизированного домена? - PullRequest
0 голосов
/ 04 октября 2019

При разработке приложений / моделей предметной области в целом мне интересно, как лучше всего справиться с неполными или необязательными данными . Типизированные языки, такие как TypeScript и C # , дают нам возможность печатать наши модели . Иногда это мощно, так как накладывает ограничения на нашу модель и может заставить ее быть целочисленной. Однако, как правило, реальные данные неполны , и это, по-видимому, радикально снижает преимущества ограничений на типизацию.

Представьте простую модель данных в примере приложения (внешний интерфейс TypeScript и некоторый внутренний интерфейс)объекта с именем Project , который имеет id , имя и необязательное описание . Данные извлекаются из бэкэнда.

Модель данных определяется во внешнем интерфейсе с использованием интерфейсов:

export interface IProject {
    id: number;
    name: string;
    description: string;
}

Данные извлекаются из бэкэнда следующим образом:

export class ProjectService {
    public getProject(projectId: number): Observable<IProject> {
        const url = 'http://server/api/project/' + projectId;
        return this.httpClient.get<IProject>(url);
    }
}

Пример ответа проекта, который на самом деле имеет описание.

{
    "id": 1
    "name": "My first project",
    "description": "My first project is just a demo"
}

В приложении веб-интерфейса мы отображаем полученные данные. Например, давайте отобразим первые 10 символов описания проекта.

alert(project.description.substr(0,10));

Пока все отлично.

Но представьте, что наш пользователь создал «Проект второй», не заполнив необязательное описание. Теперь сервер может ответить:

{
    "id": 2
    "name": "Project two"
}

или

{
    "id": 2
    "name": "Project two",
    "description" : null
}

Теперь мы получаем исключение нулевой ссылки во внешнем интерфейсе: Невозможно прочитать свойство 'substr' из null . Конечно, можно добавить оператор if, который проверяет, чтобы описание было нулевым:

if(project.description) {
  alert(project.description.substr(0,10));
}

Это работает, но это означает добавление нулевых проверок во всем приложении. Вся база кода будет заполнена этими проверками, а также опасностями маскирования ошибок вместо их предотвращения. Мне это просто не подходит.

Возможным решением может быть всегда возвращать описание, следовательно, возвращать пустую строку, если ни одна не была заполнена.

{
    "id": 2
    "name": "Project two",
    "description": ""
}

Теперь интерфейс делаетбольше не требуются нулевые проверки для описания, но больше невозможно провести различие между явно заполненным пустым описанием и not (-yet) заполненным описанием.

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

1 Ответ

2 голосов
/ 04 октября 2019

В C # 8 вы можете использовать Обнуляемые ссылочные типы , чтобы явно пометить поле description как необязательное 1 :

class Project
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string? Description { get; set; }
}

Здесь мы объявляемDescription как string? - строка, которая может быть нулевой. Затем, всякий раз, когда мы пытаемся вызвать метод на Description, который сначала проверяет его на null, компилятор выдаст нам предупреждение.

C # также даст вам синтаксис для работы с null. Например, если вы хотите получить первые 10 символов описания, но вернуть пустую строку, если описание null, вы можете написать:

project.Description?.Substring(0, 10) ?? "";

(Обратите внимание, что это будет сгенерировано, если ваше описаниене нулевой, но длиной менее 10 символов, поэтому на практике вам понадобится дополнительная логика).

Похоже, что Typescript имеет схожие концепции.


1 Этот класс предупредит вас, потому что Name не может быть обнуляемым, но также не инициализирован. Есть несколько способов решения этой проблемы: установите для него значение по умолчанию "";дать Project конструктор, который устанавливает Name;принудительно разорвать ненулевой контракт на короткое время до десериализации типа путем присвоения null!;некоторые другие.

...