В выражении с несколькими потенциально выбрасывающими вызовами где должна находиться отметка try, чтобы ком...

В выражении с несколькими потенциально выбрасывающими вызовами где должна находиться отметка try, чтобы компилятор разрешил всё выражение?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

try применяется к выражению, которое следует за ним. Если оно охватывает только один вызов, остальные потенциально выбрасывающие вызовы в той же внешней операции должны быть отмечены отдельно либо включены в явно сгруппированное выражение.

Например, в последовательном сложении вызовов безопаснее явно отметить каждый источник ошибки: try first() + try second(). Запись try (first() + second()) группирует всю операцию и позволяет одной отметке покрыть оба вызова.

Исторический контекст

Модель ошибок Swift построена вокруг явного распространения ошибки: сигнатура throws показывает, что функция может завершиться не обычным значением, а ошибкой. Отдельная отметка try делает место такого перехода заметным непосредственно в выражении.

Это решает проблему неявных исключений, когда потенциально опасный вызов трудно обнаружить при чтении кода. Компилятор проверяет, что каждый путь к выбрасывающей операции находится внутри контекста try или передаёт ошибку дальше из throws-функции.

Постановка проблемы

Сложные выражения часто объединяют несколько вызовов: получение данных, преобразование и вычисление. Если разработчик мысленно распространяет одну отметку try на всю строку, но фактическая область выражения выбрана неверно, компилятор сообщит о необработанном вызове.

Неверная группировка особенно опасна при рефакторинге: добавление второго вызова в арифметическое выражение, аргументы функции или цепочку преобразований может потребовать новой отметки try. Это не runtime-ошибка, а статическая проверка, поэтому программа не соберётся до явного исправления.

Подробное решение

try является префиксом выражения, а не универсальным разрешением на все последующие операции в строке. В записи try first() + second() оно относится к вызову first(), тогда как second() остаётся вне этого выражения и также должен быть обработан.

Группировка меняет область действия: try (first() + second()) помечает всё выражение в скобках. Такой вариант допустим, если обе операции должны иметь общий путь распространения ошибки; отдельные отметки обычно лучше показывают, какой именно вызов потенциально выбрасывает ошибку.

Минимальный пример:

enum Failure: Error { case unavailable } func first() throws -> Int { 1 } func second() throws -> Int { 2 } func total() throws -> Int { let explicit = try first() + try second() let grouped = try (first() + second()) return explicit + grouped }

В обоих выражениях ошибка любого вызова распространяется из total(), потому что сама функция объявлена с throws. Если убрать try перед second() в первом варианте, компилятор отклонит код. Скобки не перехватывают ошибку и не изменяют её тип — они только задают выражение, к которому относится try.

Это отличается от do-catch: try лишь разрешает потенциальный выброс в текущем месте, а do-catch определяет, будет ли ошибка обработана здесь или пойдёт выше. Если нужно сохранить источник ошибки и передать её вызывающему коду, предпочтительнее оставить функцию throws, а не заменять вызов на try?.

Ситуация из практики

Сервис рассчитывает итоговую стоимость через два независимых вызова: загрузку базовой цены и получение скидки. Вариант с отдельными try явно показывает две точки отказа и облегчает диагностику; его минус — более многословная запись.

Группировка через try (price() - discount()) короче и полезна, когда выражение логически едино. Однако при добавлении побочных эффектов или сложной логики такая запись хуже показывает границы операций, поэтому в production-коде обычно сначала сохраняют промежуточные значения, а затем вычисляют итог.

Выбранное решение — отдельные локальные значения с отдельным try для каждого вызова. Оно позволяет легко добавить контекст, измерить длительность каждой операции и проверить, какая именно ошибка пришла, не меняя семантику распространения. Результат — прозрачный код и сохранение исходной ошибки до уровня, где принято решение о восстановлении или отображении сообщения.

Что кандидаты часто упускают

  1. Покрывает ли try все вызовы справа от него?

Нет. Он покрывает только выражение, к которому синтаксически относится. В try first() + second() второй вызов находится за пределами отмеченного вызова, поэтому для него нужна отдельная отметка или группировка всего выражения.

  1. Изменяет ли группировка через скобки тип или причину ошибки?

Нет. Скобки влияют на область выражения, но не преобразуют ошибку и не скрывают её. Любая ошибка, выброшенная внутри сгруппированного выражения, распространяется дальше с тем же значением, если её не перехватили или не преобразовали.

  1. Можно ли заменить несколько try на try?, чтобы упростить выражение?

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