1. 概要

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

  • これらすべての「*サービス」クラスとは何ですか?
  • これをインスタンス化するにはどうすればよいですか。依存関係が多すぎます。 「ラッチ」とは何ですか?
  • ああ、私はそれをまとめました、しかし今それはIllegalStateExceptionを投げ始めます。 私は何が間違っているのですか?

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

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

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

2. 境界

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

2.1. 入力

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

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

また、常に複数のコンストラクターを提供し、ユーザーに代替手段を提供する必要があります。 StringIntegerの両方で動作させるか、 FileInputStream に制限せずに、InputStreamで動作させます。ユニットテストなどの場合は、おそらくByteArrayInputStreamを送信してください。

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

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<WebPage> pages = graph.crawl();

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

  • ページを返し続けますが、ユーザーが開始番号と終了番号を指定する必要があるページングメカニズムを実装します。 または
  • export(List)というメソッドを使用してインターフェースを実装するようにユーザーに依頼します )、最大ページ数に達するたびにアルゴリズムが呼び出すこと

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

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

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

3. インターフェース

常にインターフェースを使用してください。 ユーザーは、厳格な契約を通じてのみコードを操作する必要があります。

たとえば、 jcabi-github ライブラリでは、クラスRtGithubsiがユーザーに実際に表示される唯一のクラスです。

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とIssueのインスタンスが使用されますが、実際のタイプが明らかになることはありません。 このようなことはできません。

Repo repo = new RtRepo(...)

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

インターフェイスは、拡張性と下位互換性の容易さも提供します。 一方で、開発者はすでにリリースされたコントラクトを尊重する必要があり、他方では、ユーザーは提供するインターフェースを拡張できます。ユーザーはそれらを装飾したり、代替の実装を作成したりできます。

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

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

4. 第三者

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

可能な限り、実際の実装に拘束されないように、可能な限り透過的にしてください。 頭に浮かぶ最良の例は次のとおりです。ロギング用のAPIにすぎないSLF4Jを使用します。log4jを直接使用しないでください。ユーザーが他のロガーを使用したい場合があります。

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

ここで重要なのは、ビルドを軽量で透明に保ち、何を使用しているかを常に把握することです。 想像以上にユーザーの煩わしさを軽減できます。

5. 結論

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

それはラインを越える簡単なステップであり、デザインを台無しにします。 寄稿者は常にそれを使用する方法を知っていますが、最初にそれに目を向けた新しい人はそうではないかもしれません。 生産性はすべての中で最も重要であり、この原則に従うと、ユーザーは数分でライブラリの使用を開始できるはずです。