Нужно ли проверять продукт или ошибку в методе loadData, если результат уже обработан
Что проверяют
Понимаете ли вы, что пара необязательных значений в колбэке допускает состояния, которых по контракту не бывает, и умеете ли убрать их типом. Заодно смотрят, где у вас проходит граница между сетевым слоем и экраном.
Как отвечать
Если сигнатура выглядит как (Product?, Error?) -> Void, то у результата четыре состояния, и два из них бессмысленны: пришло и то и другое либо не пришло ничего. Компилятор их не запрещает, поэтому появляется третья ветка else, которую никто не знает, как обрабатывать. Правильный ответ не «добавить ещё проверок», а сделать неверное состояние непредставимым:
func loadData(completion: @escaping (Result<Product, LoadError>) -> Void)
// или в современном виде
func loadData() async throws -> ProductТогда switch по Result покрывает ровно два случая, и полноту контролирует компилятор.
Дальше про слои. Если нижний уровень уже разобрал ответ, проверил код HTTP, декодировал тело и превратил сбой в доменную ошибку, то экрану перепроверять нечего: он получает результат и переводит его в состояние интерфейса. Дублирующий if error != nil поверх Result это шум.
Что проверять обязательно в любом случае: обновление интерфейса идёт на главном потоке (@MainActor), ответ отменённого или устаревшего запроса не применяется, замыкание не держит контроллер сильной ссылкой.
Где спотыкаются
Оставляют старую сигнатуру с двумя опционалами и городят лестницу if let. Третья ветка либо молча игнорируется, либо показывает пользователю выдуманную ошибку, хотя настоящая проблема в типе.
Тащат разбор транспорта в контроллер или во вью-модель. Экран начинает знать про коды ответа и про формат JSON, и любая смена контракта сервера правит слой представления.
Забывают про поток и отмену. Пользователь уходит с экрана, ответ приходит позже и обновляет то, чего уже нет, либо результат старого запроса перекрывает свежий. В версии на async это снимается хранением задачи и её отменой при новой загрузке.