Miglior approccio per la propagazione degli errori nel livello di accesso al servizio di un'app mobile multipiattaforma

6

Abbiamo iniziato a lavorare su un'app mobile utilizzando Xamarin. C'è una libreria di base che deve essere condivisa tra le app Android e iOS. Nella libreria principale ci saranno diverse funzioni per comunicare con un servizio web. Ho avuto una discussione con mio fratello su quale sarebbe l'approccio migliore, entrambi abbiamo un'opinione diversa, quindi vorremmo sentire le vostre opinioni in merito. E forse c'è un approccio ancora migliore a cui entrambi non avevamo ancora pensato.

Il servizio web con cui parliamo ha alcuni codici di stato HTTP predefiniti per determinati errori. Ad esempio, quando si esegue la verifica di un account e le informazioni non sono corrette, il servizio Web potrebbe restituire un codice di stato 400 e alcuni altri errori potrebbero comportare un codice di stato diverso.

Il mio approccio era il seguente:

public async Task<string> VerifyAccountAsync (string email, string password) {
        // prevent null values
        email = email ?? "";
        password = password ?? "";

        var queryString = String.Format ("/VerifyAccount?email={0}&password={1}", email, password);
        var request = WebRequest.Create (_accountUrlString + queryString);
        string result = null;

        try {
                using (var response = await request.GetResponseAsync ()) {
                        using (var stream = response.GetResponseStream ()) {
                                using (var reader = new StreamReader (stream)) {
                                        result = reader.ReadToEnd ();
                                }
                        }
                }
        } catch (WebException ex) {
                // a protocol exception means we got a response, but the status code implies some error occured.
                if (ex.Status == WebExceptionStatus.ProtocolError) {
                        var httpResponse = (HttpWebResponse)ex.Response;

                        switch (httpResponse.StatusCode) {
                        case HttpStatusCode.BadRequest: // 400
                                throw new DepotServiceException (httpResponse, "User could not be found.");
                        case HttpStatusCode.InternalServerError: // 500
                                throw new DepotServiceException (httpResponse, "Some error");
                        default: // TODO: use the response stream to set the error message.
                                throw new DepotServiceException (httpResponse, "Undefined error ...");
                        }                                                                
                } else {
                        // some other exception occured, we'll just rethrow it (should not be ignored)
                        throw ex;
                }
        }

        return result;
}

Il mio ragionamento era il seguente:

  • Poiché il servizio Web utilizza alcuni codici di stato predefiniti per determinati errori, dovremmo utilizzare un'eccezione tipizzata (DepotServiceException) per rendere più chiaro per il consumatore della libreria principale qual è l'origine dell'errore.
  • Gestire le eccezioni in questo modo nella libreria principale, impedirà di dover utilizzare le costruzioni if / else nei livelli visivi iPhone / Android. Nel livello visivo non dovremo controllare quale fosse il codice di stato, per mostrare il messaggio di errore appropriato.

L'approccio di mio fratello è il seguente:

public async Task<DepotServiceResult> RetrieveUrl(string url) {
        DepotServiceResult serviceResult = new DepotServiceResult();
        WebRequest request = WebRequest.Create(url);

        try {
                using (var response = await request.GetResponseAsync()) {
                        var webresponse = (HttpWebResponse)response;
                        using (var stream = response.GetResponseStream()) {
                                serviceResult.statuscode = webresponse.StatusCode;
                                using (var streamReader = new StreamReader(stream)) {
                                        serviceResult.response = streamReader.ReadToEnd();
                                }
                        }
                }
        } catch (WebException ex) {
                serviceResult.errorDescription = ex.ToString();
        }

        return serviceResult;
}


public async Task<DepotServiceResult> VerifyAccountAsync(string email, string password) {
        var url = _urlBase + "api/account/VerifyAccount?email={0}&password={1}";
        email = WebUtility.HtmlEncode (email);
        password = WebUtility.HtmlEncode (password);

        url = String.Format (url, email, password);
        return await RetrieveUrl(url);
}

public class DepotServiceResult {
     public string errorDescription;
     public HttpStatusCode statuscode;
     public string response;
}

Mio fratello preferisce il suo approccio perché non vuole che il livello principale della app maneggi l'errore. Preferisce gestire l'errore nel livello dell'interfaccia utente dell'app. Pertanto, per il suo approccio, ha scelto di creare un oggetto DepotServiceResult che contenga l'intero risultato dell'operazione, inclusi eventuali errori in caso di errore.

Personalmente, credo che il mio approccio sia il migliore, anche se ho difficoltà a spiegare perché è l'approccio migliore. Quindi, se qualcuno fosse d'accordo con me, potrei avere delle buone ragioni sul perché è l'approccio migliore? Certo, mio fratello preferisce il suo approccio e se il suo approccio è migliore, mi piacerebbe sicuramente sentire un buon ragionamento sul perché è il migliore.

Se c'è un approccio ancora migliore rispetto alle nostre preferenze, ci piacerebbe sentirlo anche.

    
posta Wolfgang Schreurs 15.01.2014 - 17:54
fonte

2 risposte

1

Il tuo è migliore nel fatto che tratti il tuo corelib come un'API. Lanciare eccezioni significative verso l'alto all'applicazione.

È meglio essere precisi codificando le voci email e password.

Non vedo davvero la necessità di DepotServiceResult se non per evitare una costosa gestione delle eccezioni (che in questo caso dovrebbe includere una proprietà booleana HasError).

    
risposta data 15.01.2014 - 23:58
fonte
1

La differenza principale sembra essere che il tuo codice converte le risposte di errore in eccezioni mentre tuo fratello le restituisce semplicemente. Credo che le eccezioni dovrebbero essere utilizzate per indicare bug di programmazione o situazioni di emergenza che minacciano l'integrità di un pezzo del sistema. Da questa prospettiva, la libreria probabilmente non dovrebbe lanciare un'eccezione perché non ci sono errori nel codice e nessuna emergenza per quanto ne sa. Piuttosto, un servizio esterno è inattivo o un utente ha inserito input non validi, entrambi sono in pratica situazioni "normali".

Sono d'accordo con tuo fratello.

    
risposta data 16.01.2014 - 00:34
fonte

Leggi altre domande sui tag