Автор поста приводит идею того, что метод, возвращающий ссылочный тип, объект которого создается из некоего репозитория, должен, тем или иным образом, гарантировать, что возвращаемый объект не будет null. При этом в одном из примеров он использует контракты, что противоречит их принципам. Я хочу разобрать ошибочность этого подхода.
У нас есть метод GetItem
, который достает объект из некоего репозитория и должен, по замыслу автора, гарантировать, что объект будет не null.
public Item GetItem(int itemId) { return dataAccessor.getItemById(itemId); }
В случае, если в репозитории не оказалось нужного объекта, dataAccessor
вернет null, который будет отдан клиенту и тот, не проверив значение на null, получит NullReferenceException. Так как-же можно гарантировать, что этого не произойдет?
Можно втиснуть проверку на null внутрь метода и в случае, если объект не найден, бросить Exception. Но этим мы просто переименовываем NullReferenceException в, к примеру, ItemNotFoundException. Более того, теперь клиент обязан добавлять try-catch блок при каждом обращении к этому методу. Писатель клиента будет очень рад.
Еще вариант, это добавить пост-условие в метод. Вот так:
public Item GetItem(int itemId) { Contract.Ensures(Contract.Result<Item>() != null); return dataAccessor.GetItemById(itemId); }
Тут все становится совсем плохо и котят в мире становится меньше. Прочитав контракт, любой человек должен понять, что этот метод вернет объект всегда, при любом предоставленном идентификаторе. Но это невозможно. Метод не может гарантировать, что объект с этим Id есть в репозитории. Значит нужно добавить пред-условие, которое поможет бедолаге и спихнет все проблемы клиенту:
public Item GetItem(int itemId) { Contract.Requires(dataAccessor.GetItemById(itemId) != null) Contract.Ensures(Contract.Result<Item>() != null); return dataAccessor.GetItemById(itemId); }
Весело, не правда ли? Что бы достать объект из репозитория, нужно сначала вытащить объект из репозитория, для чего нужно вытащить объект из репозитория, для чего…
Правда в том, что ложки нет никто не может гарантировать существование объекта в репозитории. Единственный способ гарантировать существование любого объекта это создать его и зажать его ссылку. Все остальное – плохой дизайн. Data access layer должен достать объект из репозитория и передать клиенту. Он не знает как обработать отсутствие объекта и вообще не в курсе плохо ли это. За это отвечает уровень логики.
Невозможность гарантировать существования объекта вне поля видимости, приводит ко второй идее приведенной в статье по ссылке вверху. Использование nullable контейнера для ссылочных типов. Я перефразирую: nullable reference. Масло масляное. Было странно узнать про популярность этого паттерна.
Ссылочный тип может ссылаться на null. Зачем засовывать один ссылочный тип в другой и добавлять в контейнер свойство HasValue, для меня решительно не понятно. Для проверки на HasValue? Что мешает обратится к содержимому объекту без этой проверки? Можно точно так-же безалаберно не проверить на null через неравенство. Более того, этот паттерн не только бесполезен, но и вреден. Взгляните на следующий код:
public Maybe<Item> SomeMethod() { var mbItem = GetMbItem(); if(mbItem.HasValue) { var item = mbItem.Value; ModifyItem(ref item); } return mbItem; }
В случае если метод ModifyItem подменил объект, то метод SomeMethod вернет контейнер со старым объектом. Иди потом, ищи этот баг.
В общем, я считаю практику, показанную в той статье, вредной. Для проверки ссылки на null, в C# есть стандартные средства, а для того, чтобы не допускать NullReferenceException, нужно быть внимательным. Усложнять систему для этого совсем необязательно. Работая с репозиториями нужно следить за целостностью данных и от этого не убежишь. Если в репозитории нет объекта, который там обязательно должен быть, это намного-намного хуже.
ссылка на оригинал статьи http://habrahabr.ru/post/200884/
Добавить комментарий