21

REST API を提供するサービスの 1 つへのアクセスを簡素化するために、Java SDK を構築しています。この SDK は、サードパーティの開発者が使用するものです。Java 言語により適した SDK でエラー処理を実装するための最適なパターンを見つけるのに苦労しています。

残りのエンドポイントがあるとしましょう: GET /photos/{photoId}. これにより、次の HTTP ステータス コードが返される場合があります。

  • 401 : ユーザーは認証されていません
  • 403 : ユーザーにはこの写真にアクセスする権限がありません
  • 404 : その ID の写真はありません

サービスは次のようになります。

interface RestService {   
    public Photo getPhoto(String photoID);
} 

上記のコードでは、まだエラー処理に対処していません。私は明らかに、sdk のクライアントがどのエラーが発生したかを知り、それから回復できるようにする方法を提供したいと考えています。Java でのエラー処理は Exceptions を使用して行われるので、それで行きましょう。ただし、例外を使用してこれを行う最善の方法は何ですか?

1. エラーに関する情報を含む 1 つの例外を設定します。

public Photo getPhoto(String photoID) throws RestServiceException;

public class RestServiceException extends Exception {
    int statusCode;

    ...
}

SDK のクライアントは、次のようなことができます。

try {
    Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
    swtich(e.getStatusCode()) {
        case 401 : handleUnauthenticated(); break;
        case 403 : handleUnauthorized(); break;
        case 404 : handleNotFound(); break;
    }
}

ただし、主に2つの理由から、このソリューションはあまり好きではありません。

  • メソッドのシグネチャを見ると、開発者はどのようなエラー状況を処理する必要があるかわかりません。
  • 開発者は、HTTP ステータス コードを直接処理し、このメソッドのコンテキストでの意味を知る必要があります (明らかに、正しく使用されている場合、多くの場合、意味はわかっていますが、常にそうであるとは限りません)。

2. エラーのクラス階層を持つ

メソッドのシグネチャはそのままです。

public Photo getPhoto(String photoID) throws RestServiceException;

ただし、エラーの種類ごとに例外を作成します。

public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;

SDK のクライアントは、次のようなことを行うことができます。

try {
    Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
    handleUnauthorized();
}
catch(UnauthorizedException e) {
    handleUnauthenticated();
}
catch(NotFoundException e) {
    handleNotFound();
}

このアプローチでは、開発者はエラーを生成した HTTP ステータス コードについて知る必要がなく、Java 例外を処理するだけで済みます。もう 1 つの利点は、開発者が処理したい例外のみをキャッチできることです (単一の Exception ( RestServiceException) をキャッチしてから、それを処理するかどうかを決定する必要がある以前の状況とは異なります)。

ただし、まだ 1 つの問題があります。メソッドのシグネチャを見ると、開発者は、メソッドのシグネチャにスーパークラスしかないため、処理する必要があるエラーの種類についてまだわかりません。

3.エラーのクラス階層を作成し、メソッドのシグネチャにリストします

さて、ここで頭に浮かぶのは、メソッドのシグネチャを次のように変更することです。

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

ただし、将来、この残りのエンドポイントに新しいエラー状況が追加される可能性があります。これは、メソッドのシグネチャに新しい Exception を追加することを意味し、Java API への重大な変更になります。説明されている状況で API への重大な変更が発生しない、より堅牢なソリューションが必要です。

4.エラーのクラス階層を作成します(未チェックの例外を使用)+メソッドのシグネチャにそれらをリストします

では、チェックされていない例外についてはどうでしょうか。RestServiceException を変更して RuntimeException を拡張すると、次のようになります。

public class RestServiceException extends RuntimeException

そして、メソッドの署名を保持します:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

このようにして、既存のコードを壊すことなく、メソッドのシグネチャに新しい例外を追加できます。ただし、このソリューションでは、開発者は例外をキャッチする必要がなく、ドキュメントを注意深く読むか (そうです!)、メソッドのシグネチャにある例外に気付くまで、処理する必要があるエラー状況があることに気付かないでしょう。 .

この種の状況でのエラー処理のベスト プラクティスは何ですか?

私が言及したものに代わる他の(より良い)代替手段はありますか?

4

5 に答える 5

16

例外処理の代替手段: コールバック

それがより良い代替手段であるかどうかはわかりませんが、コールバックを使用できます。デフォルトの実装を提供することで、一部のメソッドをオプションにすることができます。これを見てください:

    /**
     * Example 1.
     * Some callbacks will be always executed even if they fail or 
     * not, all the request will finish.
     * */
    RestRequest request = RestRequest.get("http://myserver.com/photos/31", 
        Photo.class, new RestCallback(){

            //I know that this error could be triggered, so I override the method.
            @Override
            public void onUnauthorized() {
                //Handle this error, maybe pop up a login windows (?)
            }

            //I always must override this method.
            @Override
            public void onFinish () {
                //Do some UI updates...
            }

        }).send();

コールバック クラスは次のようになります。

public abstract class RestCallback {

    public void onUnauthorized() {
        //Override this method is optional.
    }

    public abstract void onFinish(); //Override this method is obligatory.


    public void onError() {
        //Override this method is optional.
    }

    public void onBadParamsError() {
        //Override this method is optional.
    }

}

このようなことを行うと、リクエストのライフサイクルを定義し、リクエストのすべての状態を管理できます。一部のメソッドを実装するかどうかをオプションにすることができます。いくつかの一般的なエラーを取得し、onError のように、ユーザーに処理を実装する機会を与えることができます。

どの例外が処理するかを明確に定義するにはどうすればよいですか?

あなたが私に尋ねた場合、最良のアプローチは、次のようなリクエストのライフサイクルを描くことです:

サンプル例外ライフサイクル

これは貧弱な例にすぎませんが、すべてのメソッドの実装がオプションであるかどうかにかかわらず、重要なことを覚えておいてください。onAuthenticationErrorが義務的である場合、必ずしもそうonBadUsernameなるわけではなく、その逆もあります。これが、このコールバックを非常に柔軟にするポイントです。

そして、どのように Http クライアントを実装しますか?

私は http クライアントについてあまり知りません。私は常にapache HttpClient を使用しますが、http クライアント間に大きな違いはありません。ほとんどの場合、機能が多少多かったり少なかったりしますが、結局のところ、それらはすべて全く同じで。http メソッドを取得し、URL、パラメーターを入力して送信するだけです。この例では、Apache HttpClient を使用します。

public class RestRequest {
    Gson gson = new Gson();

    public <T> T post(String url, Class<T> clazz,
            List<NameValuePair> parameters, RestCallback callback) {
        // Create a new HttpClient and Post Header
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(url);
        try {
            // Add your data
            httppost.setEntity(new UrlEncodedFormEntity(parameters));
            // Execute HTTP Post Request
            HttpResponse response = httpclient.execute(httppost);
            StringBuilder json = inputStreamToString(response.getEntity()
                    .getContent());
            T gsonObject = gson.fromJson(json.toString(), clazz);
            callback.onSuccess(); // Everything has gone OK
            return gsonObject;

        } catch (HttpResponseException e) {
            // Here are the http error codes!
            callback.onError();
            switch (e.getStatusCode()) {
            case 401:
                callback.onAuthorizationError();
                break;
            case 403:
                callback.onPermissionRefuse();
                break;
            case 404:
                callback.onNonExistingPhoto();
                break;
            }
            e.printStackTrace();
        } catch (ConnectTimeoutException e) {
            callback.onTimeOutError();
            e.printStackTrace();
        } catch (MalformedJsonException e) {
            callback.onMalformedJson();
        }
        return null;
    }

    // Fast Implementation
    private StringBuilder inputStreamToString(InputStream is)
            throws IOException {
        String line = "";
        StringBuilder total = new StringBuilder();

        // Wrap a BufferedReader around the InputStream
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));

        // Read response until the end
        while ((line = rd.readLine()) != null) {
            total.append(line);
        }

        // Return full string
        return total;
    }

}

これは の実装例RestRequestです。これは単純な例の 1 つにすぎません。独自のレスト クライアントを作成する際には、議論すべきトピックがたくさんあります。たとえば、「解析に使用する json ライブラリの種類は?」、「Android と Java のどちらで作業していますか?」などです。(Androidがマルチキャッチ例外などのJava 7の一部の機能をサポートしているかどうかわからないため、これは重要であり、JavaまたはAndroidおよびその逆で利用できないテクノロジーがいくつかあります)。

しかし、私が言える最善の方法は、ユーザーの観点から sdk api をコーディングすることです。残りの要求を行う行が少ないことに注意してください。

お役に立てれば!さよなら :]

于 2013-10-16T00:52:37.267 に答える
2

あなたの提案2と3を組み合わせたライブラリを見てきました。

public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;

このように、を拡張する新しいチェック済み例外を追加するRestServiceExceptionと、メソッドのコントラクトは変更されず、それを使用するコードは引き続きコンパイルされます。

コールバックまたは未チェックの例外ソリューションと比較すると、新しいエラーが一般的なエラーとしてのみであっても、クライアント コードによって確実に処理されるという利点があります。コールバックでは何も起こらず、チェックされていない例外により、クライアント アプリケーションがクラッシュする可能性があります。

于 2013-11-05T04:44:37.403 に答える
0

ソリューションは、ニーズによって異なる場合があります。

  • 将来、予測できない新しい例外タイプが出現する可能性があると想定される場合は、チェックされた例外階層とそのスーパークラスをスローするメソッドを使用した 2 番目のソリューションがRestServiceException最適です。既知のすべてのサブクラスを のように javadoc にリストして、Subclasses: {@link UnauthenticatedException}, ...そこに隠されている可能性のある例外の種類を開発者に知らせる必要があります。一部のメソッドがこのリストから少数の例外しかスローできない場合は、@throws.

  • RestServiceExceptionこのソリューションは、 のすべての外観が、そのサブクラスのいずれかが背後に隠れている可能性があることを意味する場合にも適用できます。ただし、この場合、RestServiceExceptionサブクラスに特定のフィールドとメソッドがない場合は、最初のソリューションが望ましいですが、いくつかの変更があります。

    public class RestServiceException extends Exception {
        private final Type type;
        public Type getType();
        ...
        public static enum Type {
            UNAUTHENTICATED,
            UNAUTHORISED,
            NOT_FOUND;
        }
    }
    
  • RestServiceExceptionまた、 「オール オア ナッシング」ビジネス ロジック内で使用するために、例外自体をラップする未チェックの例外をスローする代替メソッドを作成することをお 勧めします。

    public Photo getPhotoUnchecked(String photoID) {
        try {
            return getPhoto(photoID);
        catch (RestServiceException ex) {
            throw new RestServiceUncheckedException(ex);
        }
    }
    
于 2013-10-16T07:09:26.030 に答える
-1

すべては、API エラー応答がどれだけ有益であるかにかかっています。API のエラー処理が有益であるほど、例外処理も有益になります。例外は、API から返されたエラーと同じくらい有益であると思います。

例:

{ "status":404,"code":2001,"message":"Photo could not be found."}

最初の提案に従って、例外にステータスと API エラー コードの両方が含まれている場合、開発者は何をする必要があるかをよりよく理解し、例外処理に関してより多くのオプションを利用できます。例外に返されたエラー メッセージも含まれている場合、開発者はドキュメントを参照する必要さえありません。

于 2013-10-11T14:24:44.620 に答える