Apakah kelas 'Util' memprihatinkan? [Tutup]

14

Kadang-kadang saya membuat kelas 'Util' yang utamanya berfungsi untuk menampung metode dan nilai yang tampaknya tidak benar-benar dimiliki di tempat lain. Tetapi setiap kali saya membuat salah satu kelas ini, saya pikir "uh-oh, saya akan menyesal nanti ...", karena saya membaca di suatu tempat bahwa itu buruk.

Tetapi di sisi lain, tampaknya ada dua kasus yang menarik (setidaknya bagi saya) untuk mereka:

  1. rahasia implementasi yang digunakan di banyak kelas dalam satu paket
  2. menyediakan fungsionalitas yang berguna untuk menambah kelas, tanpa mengacaukan antarmuka

Apakah saya sedang menuju kehancuran? Apa kamu bilang !! Haruskah saya refactor?


sumber
11
Hentikan penggunaan Utilatas nama kelas Anda. Masalah terpecahkan.
yannis
3
@MattFenwick Yannis punya poin bagus. Memberi nama sebuah kelas SomethingUtilagak malas dan hanya mengaburkan tujuan sebenarnya dari kelas - sama dengan kelas yang bernama SomethingManageratau SomethingService. Jika kelas itu memiliki satu tanggung jawab, itu harus mudah untuk memberinya nama yang bermakna. Jika tidak, itulah masalah sebenarnya yang harus dihadapi ...
MattDavey
1
@MDavey poin bagus, itu mungkin pilihan kata yang buruk untuk digunakan Util, meskipun jelas saya tidak berharap bahwa itu akan terpaku pada dan sisa pertanyaan diabaikan ...
1
Jika Anda membuat beberapa kelas * Util yang dipisahkan oleh objek yang mereka manipulasi, maka saya pikir tidak masalah. Saya tidak melihat masalah dengan memiliki StringUtil, ListUtil, dll. Kecuali Anda berada di C # maka Anda harus menggunakan metode extention.
marco-fiset
4
Saya sering memiliki serangkaian fungsi yang digunakan untuk tujuan terkait. Mereka tidak benar-benar memetakan dengan baik ke kelas. Saya tidak memerlukan kelas ImageResizer, saya hanya perlu fungsi ResizeImage. Jadi saya akan meletakkan fungsi terkait ke kelas statis seperti ImageTools. Untuk fungsi yang belum dalam pengelompokan apa pun, saya memiliki kelas statis Util untuk menampungnya, tetapi begitu saya memiliki beberapa yang cukup terkait satu sama lain untuk masuk dalam satu kelas, saya akan memindahkannya. Saya tidak melihat cara OO yang lebih baik untuk menangani ini.
Philip

Jawaban:

26

Desain OO modern menerima bahwa tidak semuanya adalah objek. Beberapa hal adalah perilaku, atau formula, dan beberapa di antaranya tidak memiliki keadaan. Ada baiknya memodelkan hal-hal ini sebagai fungsi murni untuk mendapatkan manfaat dari desain itu.

Java dan C # (dan lainnya) mengharuskan Anda membuat kelas util dan melompati lingkaran itu untuk melakukannya. Mengganggu, tetapi bukan akhir dari dunia; dan tidak terlalu merepotkan dari perspektif desain.

Telastyn
sumber
Ya, itu yang saya pikirkan. Terima kasih atas tanggapannya!
Setuju, meskipun harus disebutkan bahwa .NET sekarang menawarkan metode ekstensi dan kelas parsial sebagai alternatif dari pendekatan ini. Jika Anda menggunakan item ini secara ekstrem, Anda akan mendapatkan Ruby mixins - bahasa yang tidak membutuhkan kelas utilitas.
neontapir
2
@neontapir - Kecuali penerapan metode ekstensi pada dasarnya memaksa Anda untuk membuat kelas util dengan metode ekstensi ...
Telastyn
Benar. Ada dua tempat di mana kelas Util menghalangi, satu di kelas itu sendiri dan yang lainnya adalah callsite. Dengan membuat metode yang terlihat seperti ada pada objek yang disempurnakan, metode ekstensi mengatasi masalah lokasi panggilan. Saya setuju, masih ada masalah keberadaan kelas Util.
neontapir
16

Tidak pernah berkata tidak"

Saya tidak berpikir itu selalu buruk, itu hanya buruk jika Anda melakukannya dengan buruk dan menyalahgunakannya.

Kita Semua Membutuhkan Alat dan Utilitas

Sebagai permulaan, kita semua menggunakan beberapa perpustakaan yang kadang-kadang dianggap hampir di mana-mana dan harus dimiliki. Misalnya, di dunia Java, Google Guava atau beberapa Apache Commons ( Apache Commons Lang , Apache Commons Collections , dll ...).

Jadi jelas ada kebutuhan untuk ini.

Hindari Kata-Kata Sulit, Duplikasi, dan Perkenalan Bug

Jika Anda berpikir tentang ini cukup banyak hanya sekelompok besar dari Utilkelas - kelas ini Anda jelaskan, kecuali seseorang berusaha keras untuk membuat mereka (relatif) benar, dan mereka telah diuji - waktu dan sangat terbelalak oleh orang lain.

Jadi saya akan mengatakan aturan pertama ketika merasakan gatal untuk menulis Utilkelas adalah untuk memeriksa bahwa Utilkelas sebenarnya belum ada.

Satu-satunya argumen balasan yang saya lihat adalah ketika Anda ingin membatasi dependensi Anda karena:

  • Anda ingin membatasi jejak memori dependensi Anda,
  • atau Anda ingin mengontrol dengan ketat apa yang diizinkan untuk digunakan pengembang (terjadi dalam tim besar yang obsesif, atau ketika kerangka kerja tertentu dikenal memiliki kelas super-jelek yang benar-benar dihindari di suatu tempat).

Tetapi kedua hal ini dapat diatasi dengan mengemas ulang lib menggunakan ProGuard atau yang setara, atau memisahkannya sendiri (untuk pengguna Maven , plugin maven-shade-plugin menawarkan beberapa pola penyaringan untuk mengintegrasikan ini sebagai bagian dari build Anda).

Jadi, jika itu dalam lib dan cocok dengan use case Anda, dan tidak ada tolok ukur yang memberi tahu Anda sebaliknya, gunakan itu. Jika sedikit berbeda dari apa yang Anda miliki, perluas (jika mungkin) atau perluas, atau dalam upaya terakhir tulis ulang.

Konvensi Penamaan

Namun, sejauh ini dalam jawaban ini saya menyebut mereka Utilseperti Anda. Jangan sebutkan itu.

Beri mereka nama yang berarti. Ambil Google Guava sebagai (sangat, sangat) contoh yang baik dari apa yang harus dilakukan, dan bayangkan bahwa com.google.guavanamespace sebenarnya adalah utilroot Anda .

Panggil paket Anda util, paling buruk, tetapi bukan kelas. Jika berurusan dengan Stringobjek dan manipulasi konstruksi string, sebut saja Strings, bukan StringUtils(maaf, Apache Commons Lang - Saya masih suka dan menggunakan Anda!). Jika itu melakukan sesuatu yang spesifik, pilih nama kelas tertentu (seperti Splitteratau Joiner).

Unit-Test

Jika Anda harus menggunakan utilitas ini untuk menulis, pastikan untuk mengujinya. Hal yang baik tentang utilitas adalah bahwa mereka biasanya merupakan komponen yang mandiri, yang mengambil input spesifik dan mengembalikan output spesifik. Itulah konsepnya. Jadi tidak ada alasan untuk tidak mengujinya.

Selain itu, pengujian unit akan memungkinkan Anda untuk menentukan dan mendokumentasikan kontrak API mereka. Jika tes gagal, Anda mengubah sesuatu dengan cara yang salah, atau itu berarti Anda mencoba mengubah kontrak API Anda (atau bahwa tes awal Anda adalah omong kosong - belajarlah darinya, dan jangan lakukan lagi) .

Desain API

Keputusan desain yang akan Anda ambil untuk API ini akan mengikuti Anda sejak lama, mungkin. Jadi, meski tidak menghabiskan berjam-jam untuk menulis Splitter-kloning, berhati-hatilah dengan cara Anda mendekati masalah ini.

Tanyakan kepada diri Anda beberapa pertanyaan:

  • Apakah metode utilitas Anda menjamin kelas sendiri, atau apakah metode statis cukup baik, jika masuk akal untuk menjadi bagian dari kelompok metode yang sama berguna?
  • Apakah Anda memerlukan metode pabrik untuk membuat objek dan membuat API Anda lebih mudah dibaca?
  • Berbicara tentang keterbacaan, apakah Anda memerlukan API Lancar , pembangun , dll ...?

Anda ingin utilitas ini mencakup banyak kasus penggunaan, menjadi kuat, stabil, terdokumentasi dengan baik, mengikuti prinsip paling tidak mengejutkan, dan mandiri. Idealnya, setiap sub-paket utilitas Anda, atau setidaknya seluruh paket utilitas Anda, dapat diekspor ke bundel agar mudah digunakan kembali.

Seperti biasa, pelajari dari para raksasa di sini:

Ya, banyak dari ini memiliki penekanan pada koleksi dan struktur data, tapi jangan bilang itu bukan di mana atau untuk apa Anda biasanya akan menerapkan sebagian besar utilitas Anda, secara langsung atau tidak langsung.

haylem
sumber
+1 untukIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova
+1 Untuk desain API di .Net, bacalah buku oleh arsitek .Net. Pedoman Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Reusable .NET Libraries
MarkJ
Mengapa Anda menyebutnya Strings, bukan StringUtil? String bagiku terdengar seperti objek koleksi.
bdrx
@bdrx: bagi saya objek koleksi akan baik StringsCollectionTypeHerejika Anda menginginkan implementasi konkret. Atau nama yang lebih spesifik jika string ini memiliki makna khusus dalam konteks aplikasi Anda. Untuk kasus khusus ini, Guava menggunakan Stringsuntuk helper terkait-string, yang bertentangan dengan StringUtilsCommons Lang. Saya merasa ini benar-benar dapat diterima, itu hanya berarti bagi saya bahwa kelas menangani Stringobjek atau adalah kelas tujuan umum untuk mengelola Strings.
haylem
@bdrx: Secara umum NameUtilshal-hal menggangguku tanpa akhir karena jika ia duduk di bawah nama paket yang diberi label dengan jelas, saya sudah tahu itu adalah kelas utilitas (dan jika tidak, akan mencari tahu dengan cepat dari melihat API). Ini sebagai menjengkelkan bagi saya sebagai orang menyatakan hal-hal sebagai SomethingInterface, ISomething, atau SomethingImpl. Saya agak OK dengan artefak seperti itu ketika coding di C dan tidak menggunakan IDE. Hari ini saya secara umum tidak membutuhkan hal-hal seperti itu.
haylem
8

Kelas Util tidak kohesif dan, umumnya, desain buruk karena kelas harus memiliki alasan tunggal untuk berubah (Prinsip Responsabilitas Tunggal).

Namun, saya telah melihat "util" kelas dalam sangat Java API, seperti: Math, Collectionsdan Arrays.

Pada kenyataannya, ini adalah kelas utilitas tetapi semua metode mereka terkait dengan satu tema, satu memiliki operasi matematika, satu memiliki metode untuk memanipulasi koleksi dan yang lainnya untuk memanipulasi array.

Cobalah tidak, tidak memiliki metode yang sama sekali tidak terkait dalam kelas utilitas. Jika itu masalahnya, kemungkinan Anda juga bisa menempatkannya di tempat yang seharusnya.

Jika harus menggunakan kelas maka cobalah untuk dipisahkan dengan tema seperti Java Math, Collectionsdan Arrays. Setidaknya, itu menunjukkan beberapa niat desain bahkan ketika mereka hanya ruang nama.

I untuk satu, selalu menghindari kelas utilitas dan tidak pernah memiliki kebutuhan untuk membuatnya.

Tulains Córdova
sumber
Terima kasih atas tanggapannya. Jadi, meskipun kelas util adalah paket-privat, mereka masih berbau desain?
tidak semuanya termasuk dalam kelas. jika Anda terjebak dengan bahasa yang bersikeras bahwa itu berlaku maka kelas utilitas akan terjadi.
jk.
1
Nah, kelas utilitas tidak memiliki prinsip desain untuk memberi tahu Anda berapa banyak metode yang terlalu banyak, karena pada awalnya tidak ada keterpaduan. Bagaimana Anda bisa tahu cara berhenti? Setelah 30 metode? 40? 100? Prinsip-prinsip OOP, di sisi lain memberi Anda gambaran tentang kapan suatu metode bukan milik kelas tertentu, dan ketika kelas itu melakukan terlalu banyak. Itu disebut keterpaduan. Tetapi seperti yang saya katakan dalam jawaban saya, orang-orang yang merancang Java menggunakan kelas utilitas. Anda tidak bisa melakukannya juga. Saya tidak membuat kelas utilitas dan belum berada dalam situasi ketika sesuatu tidak termasuk dalam kelas normal.
Tulains Córdova
1
Kelas Utils biasanya bukan kelas mereka hanya ruang nama yang berisi banyak fungsi, biasanya murni. Fungsi murni sangat kohesif yang bisa Anda dapatkan.
jk.
1
@ jk maksudku Utils ... Ngomong-ngomong muncul dengan nama suka Mathatau Arraysmenunjukkan setidaknya beberapa niat desain.
Tulains Córdova
3

Sangat dapat diterima untuk menggunakan kelas util , meskipun saya lebih suka istilah itu ClassNameHelper. .NET BCL bahkan memiliki kelas pembantu di dalamnya. Hal yang paling penting untuk diingat, adalah mendokumentasikan tujuan kelas secara menyeluruh, serta setiap metode pembantu individu, dan untuk membuatnya menjadi kode yang dapat dikelola dengan kualitas tinggi.

Dan jangan terbawa oleh kelas pembantu.

David Anderson
sumber
0

Saya menggunakan pendekatan dua tingkat. Kelas "Global" dalam paket "util" (folder). Untuk sesuatu yang masuk ke kelas "Global" atau paket "util", itu harus:

  1. Sederhana,
  2. Tidak berubah,
  3. Tidak bergantung pada hal lain dalam aplikasi Anda

Contoh yang lulus tes ini:

  • Format tanggal dan desimal di seluruh sistem
  • Data global yang tidak dapat diubah, seperti EARLIEST_YEAR (yang didukung sistem) atau IS_TEST_MODE atau semacamnya benar-benar global dan tidak berubah (saat sistem berjalan) nilainya.
  • Metode pembantu yang sangat kecil yang mengatasi kesenjangan dalam bahasa, kerangka kerja, atau alat bantu Anda

Berikut adalah contoh metode pembantu yang sangat kecil, benar-benar independen dari aplikasi lainnya:

public static String ordinal(final int i) {
    return (i == 1) ? "1st" :
           (i == 2) ? "2nd" :
           (i == 3) ? "3rd" :
           new StringBuilder().append(i).append("th").toString();
}

Melihat ini saya bisa melihat bug bahwa 21 harus "21", 22 harus "22", dll. Tapi itu intinya.

Jika salah satu dari metode penolong itu tumbuh atau menjadi rumit, ia harus dipindahkan ke kelasnya sendiri dalam paket util. Jika dua atau lebih kelas pembantu dalam paket util terkait satu sama lain, mereka harus dipindahkan ke paket mereka sendiri. Jika metode konstan atau helper ternyata terkait dengan bagian spesifik aplikasi Anda, itu harus dipindahkan ke sana.

Anda harus dapat membenarkan mengapa tidak ada tempat yang lebih baik untuk semua yang Anda tempel di kelas Globals atau paket util dan Anda harus membersihkannya secara berkala menggunakan tes di atas. Jika tidak, Anda hanya membuat kekacauan.

GlenPeterson
sumber