ユーザーフレンドリーなJavaライブラリの設計

ユーザーフレンドリーなJavaライブラリの設計

1. 概要

Javaは、オープンソースの世界の柱の1つです。 誰も車輪を再発明したくないので、ほとんどすべてのJavaプロジェクトは他のオープンソースプロジェクトを使用します。 ただし、その機能のためにライブラリが必要になることは何度もありますが、その使用方法がわかりません。 私たちは次のようなものに遭遇します:

  • これらすべての「* Service」クラスとは何ですか?

  • これをインスタンス化するにはどうすればいいですか、依存関係が多すぎます。 「latch」とは何ですか?

  • ああ、私はそれをまとめました、しかし今それはIllegalStateExceptionを投げ始めます。 何がおかしいのですか?

問題は、すべてのライブラリデザイナーがユーザーについて考えるわけではないことです。 ほとんどの人は機能と機能についてのみ考えていますが、APIが実際にどのように使用されるか、ユーザーのコードがどのように表示され、テストされるかについて考える人はほとんどいません。

この記事には、これらの苦労の一部をユーザーに保存する方法に関するアドバイスがいくつか含まれています。いいえ、ドキュメントを作成することではありません。 もちろん、この主題について本全体を書くこともできます(そして、いくつかは書かれています)。これらは、私自身がいくつかのライブラリに取り組んでいる間に学んだ重要なポイントの一部です。

ここでは、charlesjcabi-githubの2つのライブラリを使用してアイデアを例示します。

2. 境界線

これは明らかなはずですが、多くの場合そうではありません。 コードの行を書き始める前に、いくつかの質問に対する明確な答えが必要です。どのような入力が必要ですか? ユーザーに表示される最初のクラスは何ですか? ユーザーからの実装が必要ですか? 出力は何ですか? これらの質問が明確に回答されると、ライブラリには既に裏地、形状があるため、すべてが簡単になります。

2.1. 入力

これはおそらく最も重要なトピックです。 図書館が仕事をするために、ユーザーが図書館に何を提供する必要があるかを明確にする必要があります。 場合によっては、これは非常に些細な問題です。APIの認証トークンを表す文字列である場合もありますが、インターフェイスまたは抽象クラスの実装である場合もあります。

非常に良い方法は、いくつかのパラメーターを使用して、コンストラクターを介してすべての依存関係を取得し、これらを短くすることです。 3つまたは4つ以上のパラメーターを持つコンストラクターが必要な場合は、コードを明確にリファクタリングする必要があります。 また、必須の依存関係を注入するためにメソッドが使用される場合、ユーザーは、概要で説明されている3番目のフラストレーションに陥ります。

また、常に複数のコンストラクターを提供し、ユーザーに選択肢を提供する必要があります。 StringIntegerの両方で動作するようにするか、FileInputStreamに制限せずに、InputStreamで動作するようにします。これにより、ユニットテストなど。

たとえば、jcabi-githubを使用してGithub APIエントリポイントをインスタンス化できるいくつかの方法を次に示します。

Github noauth = new RtGithub();
Github basicauth = new RtGithub("username", "password");
Github oauth = new RtGithub("token");

初期化するための単純な、ハッスル、日陰の構成オブジェクトはありません。 また、ログアウト、ログイン中にGithub Webサイトを使用したり、アプリがユーザーに代わって認証したりできるため、これら3つのコンストラクターを用意することは理にかなっています。 当然、認証されていないと機能しない機能もありますが、これは最初から知っています。

2番目の例として、Webクロールライブラリであるcharlesを使用する方法を次に示します。

WebDriver driver = new FirefoxDriver();
Repository repo = new InMemoryRepository();
String indexPage = "http://www.amihaiemil.com/index.html";
WebCrawl graph = new GraphCrawl(
  indexPage, driver, new IgnoredPatterns(), repo
);
graph.crawl();

それも一目瞭然だと思います。 ただし、これを書いているときに、現在のバージョンには間違いがあることに気付きました。すべてのコンストラクターは、ユーザーにIgnoredPatternsのインスタンスを提供するように要求します。 デフォルトでは、パターンは無視されませんが、ユーザーはこれを指定する必要はありません。 ここにこのままにしておくことにしたので、反例を参照してください。 WebCrawlをインスタンス化しようとして、「そのIgnoredPatternsとは何ですか?!」と思うと思います。

変数indexPageはクロールを開始するURL、ドライバーは使用するブラウザーです(実行中のマシンにどのブラウザーがインストールされているかわからないため、デフォルトでは何もできません)。 レポ変数については、次のセクションで説明します。

そのため、例でわかるように、シンプルで直感的でわかりやすいものにしてください。 ユーザーがコンストラクターを見るときに頭を悩ませないように、ロジックと依存関係をカプセル化します。

それでも疑問がある場合は、aws-sdk-javaを使用してAWSにHTTPリクエストを送信してみてください。ClientConfigurationをどこかで使用するいわゆるAmazonHttpClientを処理し、その間のどこかでExecutionContextを取得する必要があります。 最後に、リクエストを実行してレスポンスを取得することはできますが、ExecutionContextが何であるかはまだわかりません。

2.2. 出力

これは主に、外界と通信するライブラリ用です。 ここで、「出力の処理方法」という質問に答える必要があります。 繰り返しになりますが、かなり面白い質問ですが、間違えるのは簡単です。

上記のコードをもう一度見てください。 リポジトリの実装を提供する必要があるのはなぜですか? WebCrawl.crawl()メソッドがWebPage要素のリストを返さないのはなぜですか? クロールされたページを処理するのは明らかに図書館の仕事ではありません。 私たちが彼らと何をしたいのか、それをどのように知る必要がありますか? このようなもの:

WebCrawl graph = new GraphCrawl(...);
List pages = graph.crawl();

何も悪化することはありません。 クロールされたサイトに1000ページがある場合、OutOfMemory例外がどこからともなく発生する可能性があります。ライブラリは、それらすべてをメモリにロードします。 これには2つの解決策があります。

  • ページを返し続けますが、ユーザーが開始番号と終了番号を指定する必要があるページングメカニズムを実装します。 Or

  • export(List )というメソッドでインターフェースを実装するようユーザーに依頼します。アルゴリズムは、最大ページ数に達するたびに呼び出します。

2番目のオプションは断然最高です。両側で物事を簡単に保ち、よりテスト可能です。 最初のロジックを使用した場合、ユーザー側でどの程度のロジックを実装する必要があるかを考えてください。 このように、ページのリポジトリが指定され(DBに送信するか、ディスクに書き込むため)、メソッドcrawl()を呼び出した後に他に何もする必要はありません。

ちなみに、上記の入力セクションのコードは、Webサイトのコンテンツを取得するために記述する必要のあるすべてのものです(レポの実装が言うように、まだメモリ内ですが、それは私たちの選択です-私たちはそれを提供しました)実装するため、リスクを負います)。

このセクションを要約すると、私たちは自分の仕事をクライアントの仕事から完全に分離してはなりません。 作成した出力で何が起こるかを常に考える必要があります。 トラックの運転手が目的地に到着したときに商品を単に捨てるのではなく、荷を解くのを手伝うべきです。

3. インターフェース

常にインターフェイスを使用します。 ユーザーは厳密な契約を介してのみコードと対話する必要があります。

たとえば、jcabi-githubライブラリでは、クラスRtGithubは、ユーザーが実際に目にする唯一のものです。

Repo repo = new RtGithub("oauth_token").repos().get(
  new Coordinates.Simple("eugenp/tutorials"));
Issue issue = repo.issues()
  .create("Example issue", "Created with jcabi-github");

上記のスニペットは、eugenp/tutorials repoにチケットを作成します。 リポジトリおよび発行のインスタンスが使用されますが、実際のタイプは明らかになりません。 このようなことはできません。

Repo repo = new RtRepo(...)

上記は論理的な理由で不可能です。Githubリポジトリに問題を直接作成することはできませんか? まず、ログインし、次にレポを検索する必要があります。その後でのみ問題を作成できます。 もちろん、上記のシナリオは許可されますが、ユーザーのコードは多くの定型コードで汚染されます。そのRtRepoは、コンストラクターを介して何らかの承認オブジェクトを取得し、クライアントを承認して、右のレポなどに。

インターフェイスは、拡張性と下位互換性の容易さも提供します。 開発者はすでにリリースされたコントラクトを尊重しなければならない一方で、ユーザーは提供するインターフェイスを拡張することができます。つまり、デコレーションしたり、代替実装を作成したりできます。

言い換えれば、可能な限り抽象化し、カプセル化します。 インターフェイスを使用することで、これをエレガントで制限のない方法で行うことができます。アーキテクチャルールを適用しながら、プログラマが公開する動作を自由に拡張または変更できます。

このセクションを終了するには、ライブラリ、ルールを念頭に置いてください。 クライアントのコードがどのように見えるか、そしてクライアントがそれをユニットテストする方法を正確に知る必要があります。 それがわからない場合、だれもライブラリーは理解も維持も困難なコードの作成に貢献しません。

4. 第三者

優れたライブラリは軽量のライブラリであることに注意してください。 あなたのコードは問題を解決して機能するかもしれませんが、jarが私のビルドに10 MBを追加する場合、あなたがずっと前にプロジェクトの青写真を失ったことは明らかです。 多くの依存関係が必要な場合、多すぎる機能をカバーしようとしているため、プロジェクトを複数の小さなプロジェクトに分割する必要があります。

可能な限り、実際の実装にバインドしないでください。 頭に浮かぶ最良の例は次のとおりです。SLF4Jを使用します。これはロギング用のAPIのみです。log4jを直接使用しないでください。他のロガーを使用したい場合があります。

プロジェクトを推移的に通過するドキュメントライブラリを作成し、xalanxml-apisなどの危険な依存関係が含まれていないことを確認します(危険な理由については、この記事では詳しく説明しません)。

ここで一番下の行は、ビルドを軽く、透明に保ち、常に作業対象を把握することです。 ユーザーが想像する以上に手間を省くことができます。

5. 結論

この記事では、使いやすさに関してプロジェクトを順調に進めるのに役立ついくつかの簡単なアイデアの概要を説明します。 ライブラリは、より大きなコンテキストでその場所を見つける必要があるコンポーネントであり、機能が強力でありながら、スムーズで適切に作成されたインターフェイスを提供する必要があります。

これは簡単なステップであり、設計を混乱させます。 寄稿者はいつでもそれを使用する方法を知っていますが、最初にそれを目にした人は知らないかもしれません。 生産性が最も重要であり、この原則に従って、ユーザーは数分でライブラリの使用を開始できるはずです。