#c_sharp #исключения #vkontakte_api #captcha
Код примерно следующий: public void AntigateAuthorize(int appId, string email, string password, Settings settings, string antigateKey) { var authorizationCompleted = false; try { Authorize(appId, email, password, settings); authorizationCompleted = true; } catch (CaptchaNeededException exception) { var captchaSid = exception.Sid; var image = GetImageFromUrl(exception.Img.AbsoluteUri); try { const int numberOfAuthorizationRetries = 5; var retryAuthorizationsCount = numberOfAuthorizationRetries; do { var anticaptcha = new AntiCaptcha(antigateKey); try { var captchaKey = anticaptcha?.GetAnswer(image); if (captchaKey != null) { Authorize(appId, email, password, settings, captchaSid, captchaKey); authorizationCompleted = true; } else { Logger.Error("Ответ от сервера Antigate не получен."); } } catch (CaptchaNeededException) { Logger.Trace("Капча распознана неверно."); anticaptcha.FalseCaptcha(); retryAuthorizationsCount--; if (retryAuthorizationsCount == 0) { Logger.Trace("Заданное количество попыток авторизации исчерпано."); throw; } } } while (!authorizationCompleted && retryAuthorizationsCount > 0); } catch (AntigateErrorException aee) { Logger.Error("Ошибка Antigate: {0}", aee.Message); } } catch (VkApiAuthorizationException) { Logger.Error("Не удалось авторизовать приложение. Неправильный логин или пароль."); } catch (VkApiException) { Logger.Error("В процессе попытки авторизации произошла неизвестная ошибка."); } } public static Image GetImageFromUrl(string url) { using (var stream = ((HttpWebResponse)((HttpWebRequest)WebRequest.Create(url)).GetResponse()).GetResponseStream()) return stream == null ? null : Image.FromStream(stream); } Проблема в том, если обрабатывать ошибку CaptchaNeededException однократно, то всё получается, а если в цикле - получается плохо и некрасиво.
Ответы
Ответ 1
Можно попробовать разбить на мелкие функции. Например, так: public void AntigateAuthorize( int appId, string email, string password, Settings settings, string antigateKey) { try { try { Authorize(appId, email, password, settings); } catch (CaptchaNeededException exception) { AuthorizeWithCaptcha(appId, email, password, settings, exception); } } catch (AntigateErrorException) { // ? } catch (VkApiAuthorizationException) { Logger.Error( "Не удалось авторизовать приложение. Неправильный логин или пароль."); } catch (VkApiException) { Logger.Error( "В процессе попытки авторизации произошла неизвестная ошибка."); } } Вспомогательные функции: private void AuthorizeWithCaptchaOnceImpl( int appId, string email, string password, Settings settings, CaptchaNeededException reason, AntiCaptcha anticaptcha) { var captchaSid = reason.Sid; var image = GetImageFromUrl(reason.Img.AbsoluteUri); try { var captchaKey = anticaptcha.GetAnswer(image); if (captchaKey == null) { Logger.Error("Ответ от сервера Antigate не получен."); throw new NoAntigateResponceException(); } Authorize(appId, email, password, settings, captchaSid, captchaKey); } catch (CaptchaNeededException) { Logger.Trace("Капча распознана неверно."); anticaptcha.FalseCaptcha(); throw; } } void AuthorizeWithCaptcha( int appId, string email, string password, Settings settings, CaptchaNeededException exception) { try { var anticaptcha = new AntiCaptcha(antigateKey); const int numberOfAuthorizationRetries = 5; CaptchaNeededException currentException = exception; for (int retry = 0; retry < numberOfAuthorizationRetries; retry++) { try { AuthorizeWithCaptchaOnceImpl( appId, email, password, settings, currentException, anticaptcha); return; } catch (CaptchaNeededException ex) { currentException = ex; } catch (NoAntigateResponceException ex) { // nothing to do, just retry } } throw new RetryNumberExceededException(); } catch (AntigateErrorException aee) { Logger.Error("Ошибка Antigate: {0}", aee.Message); throw; } } Заметьте, что я немного поменял логику. Например, после неудачного распознавания капчи теперь картинка меняется. Также, в результате превышения разрешённого количества попыток распознавания выбрасывается исключение (хотя, возможно, вы захотите вернуть bool из функции).Ответ 2
Есть такая рекомендация: для упрощения читаемости кода блоки if/try/catch/finally/for/foreach/while должны содержать как можно меньше кода, в идеале -- вызов одного метода. Еще одна рекомендация: соблюдать минимальную вложенность кода. Например, у вас есть код: public void Foo() { try { FooInternal(); } catch (Exception1) { ... } catch (Exception2) { ... } } Цель этого метода: вызов некоторого кода и обработка ошибок. При этом метод не перегружен подробностями о том, какой код вызывается. В свою очередь метод FooInternal не будет перегружен информацией о возможных ошибках и их обработке. К вашему коду. Для внешнего блока try эти рекомендации у вас соблюдены: try { Authorize(appId, email, password, settings); authorizationCompleted = true; } Теперь осталось вынести содержимое catch (CaptchaNeededException exception) в отдельный метод. В нем -- в свою очередь вынести содержимое блоков try/catch в отдельные методы. От развесистой структуры блоков try/catch, к сожалению, совсем избавиться не удастся -- такова логика.Ответ 3
Я бы вынес логику отдельно, т.к. в коде присутствуют другие методы не отвечающие за авторизацию, нет следования принципу SRP и не будет reuse. К примеру класс class Antigate Service, метод TryAuthenticate который будет возвращать bool и out AuthenticationResult. По крайней мере мне так видится, избавление в будущем от дублирования И вообще следует избегать ветвления кода(даже если это try/catch)Ответ 4
Переделал сейчас примерно следующим образом (убрал для примера лишние catch): public void AuthorizeEx( int appId, string email, string password, Settings settings, int captchaRecognitionCount = MaxCaptchaRecognitionCount, AntiCaptcha antiCaptcha = null, CaptchaNeededException previousException = null) { if (captchaRecognitionCount < 0) return; var captchaSid = previousException?.Sid; var captchaKey = previousException == null ? null : antiCaptcha?.GetAnswer(GetImageFromUrl(previousException?.Img?.AbsoluteUri)); try { Authorize(appId, email, password, settings, captchaSid, captchaKey); } catch (CaptchaNeededException currentException) { AuthorizeEx(appId, email, password, settings, captchaRecognitionCount - 1, antiCaptcha, currentException); } } private static Image GetImageFromUrl([CanBeNull] string url) { if (url == null) return null; using (var stream = ((WebRequest.Create(url))?.GetResponse())?.GetResponseStream()) { return stream == null ? null : Image.FromStream(stream); } } Итог: код обработки капчи идёт отдельно, красиво. Из минусов - использование рекурсии, но так ли это страшно, если я ограничиваю её уровень? Если нехорошо, то как развернуть?
Комментариев нет:
Отправить комментарий