Merancang Perpustakaan Java Mesra Pengguna

1. Gambaran keseluruhan

Java adalah salah satu tonggak dunia sumber terbuka. Hampir setiap projek Java menggunakan projek sumber terbuka yang lain kerana tidak ada yang ingin mencipta semula roda. Namun, berkali-kali kita memerlukan perpustakaan untuk fungsinya tetapi kita tidak tahu bagaimana menggunakannya. Kami menghadapi perkara seperti:

  • Ada apa dengan semua kelas "* Perkhidmatan" ini?
  • Bagaimana saya menunjukkan ini, memerlukan terlalu banyak kebergantungan. Apa itu " selak "?
  • Oh, saya menyatukannya, tetapi sekarang ia mula melemparkan IllegalStateException . Apa yang saya buat salah?

Masalahnya ialah tidak semua pereka perpustakaan memikirkan pengguna mereka. Sebilangan besar hanya memikirkan fungsi, dan ciri, tetapi hanya sedikit yang mempertimbangkan bagaimana API akan digunakan dalam praktik, dan bagaimana kod pengguna akan kelihatan dan diuji.

Artikel ini dilengkapi dengan beberapa nasihat mengenai cara menyelamatkan pengguna kami dari beberapa perjuangan ini - dan tidak, ini bukan melalui menulis dokumentasi. Sudah tentu, keseluruhan buku boleh ditulis mengenai perkara ini (dan beberapa buku telah ditulis); ini adalah beberapa perkara penting yang saya pelajari semasa bekerja di beberapa perpustakaan sendiri.

Saya akan memberi contoh idea di sini menggunakan dua perpustakaan: charles dan jcabi-github

2. Sempadan

Ini semestinya jelas tetapi berkali-kali tidak. Sebelum mula menulis sebaris kod, kita perlu mempunyai jawapan yang jelas untuk beberapa soalan: input apa yang diperlukan? apakah kelas pertama yang akan dilihat oleh pengguna saya? adakah kita memerlukan pelaksanaan dari pengguna? apakah outputnya? Setelah soalan-soalan ini dijawab dengan jelas semuanya menjadi lebih mudah kerana perpustakaan sudah mempunyai lapisan, bentuk.

2.1. Masukan

Ini mungkin topik yang paling penting. Kita harus memastikan bahawa apa yang perlu pengguna berikan ke perpustakaan agar ia dapat menjalankan tugasnya. Dalam beberapa kes, ini adalah masalah yang sangat remeh: hanya String yang mewakili token autentikasi untuk API, tetapi mungkin juga merupakan implementasi antara muka, atau kelas abstrak.

Amalan yang sangat baik adalah mengambil semua pergantungan melalui konstruktor dan menjadikannya pendek, dengan beberapa parameter. Sekiranya kita perlu mempunyai konstruktor dengan lebih dari tiga atau empat parameter, maka kodnya mesti diperbaiki. Dan jika kaedah digunakan untuk menyuntikkan kebergantungan wajib, pengguna kemungkinan besar akan berakhir dengan kekecewaan ketiga yang dijelaskan dalam gambaran keseluruhan.

Juga, kita harus selalu menawarkan lebih daripada satu pembina, memberi alternatif kepada pengguna. Biarkan mereka bekerja dengan String dan Integer atau jangan hadkannya ke FileInputStream , bekerjasama dengan InputStream , sehingga mereka dapat mengirimkan mungkin ByteArrayInputStream ketika pengujian unit dll.

Sebagai contoh, berikut adalah beberapa cara kita dapat menunjukkan titik masuk API Github menggunakan jcabi-github:

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

Ringkas, tanpa keramaian, tidak ada objek konfigurasi yang teduh untuk dimulakan. Dan masuk akal untuk memiliki ketiga-tiga pembangun ini, kerana anda boleh menggunakan laman web Github semasa log keluar, log masuk atau aplikasi dapat mengesahkan bagi pihak anda. Secara semula jadi, beberapa fungsi tidak akan berfungsi jika anda tidak disahkan, tetapi anda mengetahui perkara ini sejak awal.

Sebagai contoh kedua, berikut adalah cara kami bekerja dengan charles, perpustakaan merangkak web:

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

Saya yakin ia cukup jelas. Walau bagaimanapun, semasa menulis ini, saya menyedari dalam versi semasa ada kesilapan: semua pembina memerlukan pengguna untuk menyediakan contoh IgnoredPatterns . Secara lalai, tidak ada corak yang harus diabaikan, tetapi pengguna tidak harus menentukan ini. Saya memutuskan untuk meninggalkannya seperti ini di sini, jadi anda melihat contoh kontra. Saya menganggap bahawa anda akan cuba membuat WebCrawl dan bertanya-tanya "Ada apa dengan IgnoredPatterns itu ?!"

Variabel indexPage adalah URL dari mana permulaan perayapan, pemacu adalah penyemak imbas yang akan digunakan (tidak boleh menggunakan apa-apa kerana kita tidak tahu penyemak imbas mana yang dipasang pada mesin yang sedang berjalan). Pemboleh ubah repo akan dijelaskan di bawah di bahagian seterusnya.

Oleh itu, seperti yang anda lihat dalam contohnya, cubalah untuk menjadikannya mudah, intuitif dan jelas. Masukkan logik dan ketergantungan sedemikian rupa sehingga pengguna tidak menggaru-garu kepalanya ketika melihat pembangun anda.

Sekiranya anda masih mempunyai keraguan, cuba buat permintaan HTTP ke AWS menggunakan aws-sdk-java: anda harus berurusan dengan apa yang disebut AmazonHttpClient, yang menggunakan ClientConfiguration di suatu tempat, kemudian perlu mengambil ExecutionContext di suatu tempat di antara. Akhirnya, anda mungkin dapat melaksanakan permintaan anda dan mendapatkan respons tetapi masih belum tahu apa itu ExecutionContext, misalnya.

2.2. Pengeluaran

Ini kebanyakan untuk perpustakaan yang berkomunikasi dengan dunia luar. Di sini kita harus menjawab pertanyaan "bagaimana output akan ditangani?". Sekali lagi, soalan yang agak lucu, tetapi mudah untuk salah.

Lihat semula kod di atas. Mengapa kita harus menyediakan pelaksanaan Repositori? Mengapa kaedah WebCrawl.crawl () tidak mengembalikan senarai elemen WebPage? Ini jelas bukan tugas perpustakaan untuk mengendalikan halaman yang dirangkak. Bagaimana ia harus tahu apa yang ingin kita lakukan dengan mereka? Sesuatu seperti ini:

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

Tidak ada yang lebih buruk. Pengecualian OutOfMemory boleh terjadi entah dari mana-mana sekiranya laman web yang dirayapi mempunyai, katakanlah, 1000 halaman - perpustakaan memuatkan semuanya dalam memori. Terdapat dua penyelesaian untuk ini:

  • Terus mengembalikan halaman, tetapi menerapkan beberapa mekanisme paging di mana pengguna harus memberikan nombor awal dan akhir. Atau
  • Minta pengguna untuk mengimplementasikan antara muka dengan kaedah yang disebut eksport (Daftar), yang akan dipanggil oleh algoritma setiap kali jumlah halaman maksimum akan dicapai

Pilihan kedua adalah yang terbaik; ia menjadikan semuanya lebih sederhana dan lebih mudah diuji. Fikirkan berapa banyak logik yang harus dilaksanakan di pihak pengguna sekiranya kita mengikuti yang pertama. Seperti ini, Repositori untuk halaman ditentukan (untuk menghantarnya dalam DB atau menulisnya di cakera mungkin) dan tidak ada lagi yang perlu dilakukan setelah memanggil kaedah merangkak ().

Ngomong-ngomong, kod dari bahagian Input di atas adalah segala yang harus kita tulis untuk mendapatkan kandungan laman web (masih dalam ingatan, seperti yang dinyatakan oleh pelaksanaan repo, tetapi itu adalah pilihan kita - kami menyediakan pelaksanaannya sehingga kita mengambil risiko).

Untuk meringkaskan bahagian ini: kita tidak boleh memisahkan tugas kita sepenuhnya dari pekerjaan pelanggan. Kita harus selalu memikirkan apa yang berlaku dengan output yang kita hasilkan. Sama seperti pemandu trak harus membantu membongkar barang daripada hanya membuangnya semasa tiba di tempat tujuan.

3. Antara muka

Sentiasa gunakan antara muka. Pengguna harus berinteraksi dengan kod kami hanya melalui kontrak yang ketat.

Sebagai contoh, dalam jcabi-github perpustakaan kelas RtGithub si satu-satunya pengguna melihat dengan sebenarnya:

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

Coretan di atas menghasilkan tiket di repo eugenp / tutorials. Contoh Repo dan Isu digunakan, tetapi jenis sebenarnya tidak pernah dinyatakan. Kami tidak boleh melakukan perkara seperti ini:

Repo repo = new RtRepo(...)

The above is not possible for a logical reason: we cannot directly create an issue in a Github repo, can we? First, we have to login, then search the repo and only then we can create an issue. Of course, the scenario above could be allowed, but then the user's code would become polluted with a lot of boilerplate code: that RtRepo would probably have to take some kind of authorization object through its constructor, authorize the client and get to the right repo etc.

Interfaces also provide ease of extensibility and backward-compatibility. On one hand, we as developers are bound to respect the already released contracts and on the other, the user can extend the interfaces we offer – he might decorate them or write alternative implementations.

In other words, abstract and encapsulate as much as possible. By using interfaces we can do this in an elegant and non-restrictive manner – we enforce architectural rules while giving the programmer freedom to enhance or change the behaviour we expose.

To end this section, just keep in mind: our library, our rules. We should know exactly how the client's code is going to look like and how he's going to unit test it. If we do not know that, no one will and our library will simply contribute in creating code that is hard to understand and maintain.

4. Third Parties

Keep in mind that a good library is a light-weight library. Your code might solve an issue and be functional, but if the jar adds 10 MB to my build, then it's clear that you lost the blueprints of your project a long time ago. If you need a lot of dependencies you are probably trying to cover too much functionality and should break the project into multiple smaller projects.

Be as transparent as possible, whenever possible do not bind to actual implementations. The best example that comes to mind is: use SLF4J, which is only an API for logging – do not use log4j directly, maybe the user would like to use other loggers.

Document libraries that come through your project transitively and make sure you don't include dangerous dependencies such as xalan or xml-apis (why they are dangerous is not for this article to elaborate).

Bottom line here is: keep your build light, transparent and always know what you are working with. It could save your users more hustle than you could imagine.

5. Conclusion

The article outlines a few simple ideas that can help a project stay on the line with regards to usability. A library, being a component that should find its place in a bigger context, should be powerful in functionality yet offer a smooth and well-crafted interface.

Ini adalah langkah mudah di luar garis dan membuat kekacauan dari reka bentuk. Penyumbang akan selalu tahu bagaimana menggunakannya, tetapi seseorang yang baru pertama kali memerhatikannya mungkin tidak. Produktiviti adalah yang terpenting dan mengikut prinsip ini, pengguna harus dapat mula menggunakan perpustakaan dalam beberapa minit.