Selama proyek baru-baru ini yang saya kerjakan, saya harus menggunakan banyak fungsi yang terlihat seperti ini:
static bool getGPS(double plane_latitude, double plane_longitude, double plane_altitude,
double plane_roll, double plane_pitch, double plane_heading,
double gimbal_roll, double gimbal_pitch, double gimbal_yaw,
int target_x, int target_y, double zoom,
int image_width_pixels, int image_height_pixels,
double & Target_Latitude, double & Target_Longitude, double & Target_Height);
Jadi saya ingin mengubahnya agar terlihat seperti ini:
static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)
Bagi saya ini tampaknya jauh lebih mudah dibaca dan aman daripada metode pertama. Tetapi apakah masuk akal untuk membuat PixelCoordinate
dan PixelSize
kelas? Atau lebih baik saya menggunakan std::pair<int,int>
masing-masing saja. Dan apakah masuk akal untuk memiliki ZoomLevel
kelas, atau haruskah saya menggunakan saja double
?
Intuisi saya di balik menggunakan kelas untuk semuanya didasarkan pada asumsi ini:
- Jika ada kelas untuk semuanya, tidak mungkin untuk lulus
ZoomLevel
di manaWeight
objek diharapkan, sehingga akan lebih sulit untuk memberikan argumen yang salah ke suatu fungsi - Demikian juga, beberapa operasi ilegal akan menyebabkan kesalahan kompilasi, seperti menambahkan a
GPSCoordinate
ke yangZoomLevel
lainGPSCoordinate
- Operasi legal akan mudah untuk diwakili dan typesafe. yaitu mengurangi dua
GPSCoordinate
s akan menghasilkan aGPSDisplacement
Namun, sebagian besar kode C ++ yang saya lihat menggunakan banyak tipe primitif, dan saya membayangkan pasti ada alasan bagus untuk itu. Apakah ide yang bagus untuk menggunakan objek untuk apa pun, atau apakah ada kelemahan yang tidak saya sadari?
Jawaban:
Iya tentu saja. Fungsi / metode yang terlalu banyak argumen adalah bau kode , dan menunjukkan setidaknya salah satu dari yang berikut:
Jika yang terakhir adalah kasusnya (dan contoh Anda tentu saja menyarankan demikian), inilah saatnya untuk melakukan "abstraksi" celana mewah yang dibicarakan anak-anak keren itu, beberapa dekade yang lalu. Bahkan, saya akan melangkah lebih jauh dari contoh Anda dan melakukan sesuatu seperti:
(Saya tidak terbiasa dengan GPS jadi ini mungkin bukan abstraksi yang benar; misalnya, saya tidak mengerti apa yang harus dilakukan zoom dengan fungsi yang disebut
getGPS
.)Tolong lebih suka kelas seperti
PixelCoordinate
lebihstd::pair<T, T>
, bahkan jika keduanya memiliki data yang sama. Ini menambah nilai semantik, ditambah sedikit keamanan tipe tambahan (kompiler akan menghentikan Anda dari meneruskanScreenCoordinate
kePixelCoordinate
meskipun keduanyapair
s.sumber
Penafian: Saya lebih suka C ke C ++
Alih-alih kelas, saya akan menggunakan struct. Poin ini sangat bagus, karena struct dan kelas hampir identik (lihat di sini untuk grafik sederhana, atau pertanyaan SO ini ), tapi saya pikir ada kelebihan dalam diferensiasi.
Pemrogram C akrab dengan struct sebagai blok data yang memiliki makna lebih besar ketika dikelompokkan, sedangkan ketika saya melihat clasas, saya berasumsi ada beberapa keadaan internal dan metode untuk memanipulasi keadaan itu. Koordinat GPS tidak memiliki status, hanya data.
Dari pertanyaan Anda, tampaknya inilah yang sebenarnya Anda cari, wadah umum, bukan struktur stateful. Seperti yang disebutkan orang lain, saya tidak akan repot menempatkan Zoom ke kelasnya sendiri; Saya pribadi benci kelas yang dibangun untuk menampung tipe data (profesor saya suka membuat
Height
danId
kelas).Saya rasa ini bukan masalah. Jika Anda mengurangi jumlah parameter dengan mengelompokkan hal-hal yang jelas telah Anda lakukan, tajuk seharusnya cukup untuk mencegah masalah tersebut. Jika Anda benar-benar khawatir, pisahkan primitif dengan struct, yang seharusnya memberi Anda kompilasi kesalahan untuk kesalahan umum. Sangat mudah untuk menukar dua parameter di sebelah satu sama lain, tetapi lebih sulit jika mereka terpisah lebih jauh.
Penggunaan berlebihan operator yang baik.
Juga penggunaan berlebihan operator.
Saya pikir Anda berada di jalur yang benar. Hal lain yang perlu dipertimbangkan adalah bagaimana ini sesuai dengan sisa program.
sumber
Menggunakan tipe khusus memiliki keuntungan bahwa jauh lebih sulit untuk mengacaukan urutan argumen. Versi pertama dari fungsi contoh Anda, misalnya, dimulai dengan rantai 9 ganda. Ketika seseorang menggunakan fungsi ini, sangat mudah untuk mengacaukan pesanan dan melewatkan sudut gimbal di tempat koordinat GPS dan sebaliknya. Ini dapat menyebabkan bug yang sangat aneh dan sulit dilacak. Saat Anda hanya memberikan tiga argumen dengan tipe yang berbeda, kesalahan ini dapat ditangkap oleh kompiler.
Saya sebenarnya akan mempertimbangkan untuk melangkah lebih jauh dan memperkenalkan jenis-jenis yang secara teknis identik tetapi memiliki makna semantik yang berbeda. Misalnya dengan memiliki kelas
PlaneAngle3d
dan kelas yang terpisahGimbalAngle3d
, meskipun keduanya merupakan kumpulan dari tiga ganda. Ini tidak memungkinkan untuk menggunakan yang satu sebagai yang lain, kecuali disengaja.Saya akan merekomendasikan menggunakan typedef yang hanya alias untuk tipe primitif (
typedef double ZoomLevel
) tidak hanya karena alasan itu, tetapi juga untuk yang lain: Mudah memungkinkan Anda untuk mengubah jenisnya nanti. Ketika Anda mendapatkan ide suatu hari bahwa menggunakanfloat
bisa sama baiknyadouble
tetapi bisa lebih hemat memori dan CPU, Anda hanya perlu mengubahnya di satu tempat, dan bukan di dozend.sumber
Sudah ada jawaban bagus di sini, tetapi ada satu hal yang ingin saya tambahkan:
Menggunakan
PixelCoordinate
danPixelSize
kelas membuat hal-hal menjadi sangat spesifik. Seorang jenderalCoordinate
atauPoint2D
kelas mungkin lebih sesuai dengan kebutuhan Anda. APoint2D
harus berupa kelas yang mengimplementasikan operasi titik / vektor paling umum. Anda bisa mengimplementasikan kelas semacam itu bahkan secara umum (diPoint2D<T>
mana T mungkinint
ataudouble
atau sesuatu yang lain).Operasi titik / vektor 2D sangat umum untuk banyak tugas pemrograman geometri 2D sehingga menurut pengalaman saya keputusan seperti itu akan meningkatkan peluang penggunaan kembali operasi 2D yang ada. Anda akan menghindari kebutuhan untuk-menerapkan kembali mereka untuk setiap
PixelCoordinate
,GPSCoordinate
, "whatsover" kelas -Coordinate lagi dan lagi.sumber
struct PixelCoordinate:Point2D<int>
jika Anda ingin jenis keamanan yang tepatIni [lebih mudah dibaca]. Itu juga ide yang bagus.
Tetapi apakah masuk akal untuk membuat kelas PixelCoordinate dan PixelSize?
Jika mereka mewakili konsep yang berbeda, itu masuk akal (yaitu, "ya.").
Anda bisa mulai dengan melakukan
typedef std::pair<int,int> PixelCoordinate, PixelSize;
. Dengan cara ini, Anda menutupi semantik (Anda bekerja dengan koordinat piksel dan ukuran piksel, bukan dengan std :: pasang dalam kode klien Anda) dan jika Anda perlu memperluas, Anda cukup mengganti typedef dengan kelas dan kode klien Anda harus membutuhkan perubahan minimal.Anda bisa mengetiknya juga, tapi saya akan menggunakan
double
sampai saya membutuhkan fungsionalitas yang terkait dengannya yang tidak ada untuk double (misalnya, memilikiZoomLevel::default
nilai akan mengharuskan Anda mendefinisikan kelas, tetapi sampai Anda memiliki sesuatu seperti itu, pertahankan dua kali lipat).Mereka adalah argumen yang kuat (Anda harus selalu berusaha untuk membuat antarmuka Anda mudah digunakan dengan benar dan sulit digunakan secara tidak benar).
Ada alasannya; dalam banyak kasus, alasan itu tidak baik. Salah satu alasan yang valid untuk melakukan ini mungkin kinerja, tetapi itu berarti bahwa Anda harus melihat kode semacam ini sebagai pengecualian, bukan sebagai aturan.
Secara umum adalah ide yang baik untuk memastikan bahwa jika nilai Anda selalu muncul bersama (dan tidak masuk akal), Anda harus mengelompokkannya (untuk alasan yang sama seperti yang Anda sebutkan dalam posting Anda).
Artinya, jika Anda dengan koordinat yang selalu memiliki x, y, dan z, maka jauh lebih baik memiliki
coordinate
kelas daripada mengirimkan tiga parameter di mana-mana.sumber