Metode tunggal dengan banyak parameter vs banyak metode yang harus dipanggil secara berurutan

16

Saya punya beberapa data mentah, saya perlu melakukan banyak hal untuk (menggeser, memutar, menskalakan sepanjang sumbu tertentu, memutarnya ke posisi akhir) dan saya tidak yakin apa cara terbaik untuk melakukan ini untuk menjaga keterbacaan kode. Di satu sisi, saya dapat membuat metode tunggal dengan banyak parameter (10+) untuk melakukan apa yang saya butuhkan, tetapi ini adalah mimpi buruk membaca kode. Di sisi lain, saya dapat membuat beberapa metode dengan 1-3 parameter masing-masing, tetapi metode ini perlu dipanggil dalam urutan yang sangat spesifik untuk mendapatkan hasil yang benar. Saya telah membaca bahwa yang terbaik adalah metode untuk melakukan satu hal dan melakukannya dengan baik, tetapi sepertinya memiliki banyak metode yang perlu dipanggil agar membuka kode untuk bug yang sulit ditemukan.

Apakah ada paradigma pemrograman yang bisa saya gunakan yang akan meminimalkan bug dan membuat kode lebih mudah dibaca?

tomsrobots
sumber
3
Masalah terbesar bukanlah 'tidak memanggil mereka secara berurutan', tetapi 'tidak mengetahui' bahwa Anda (atau lebih tepatnya, seorang programmer masa depan) harus memanggil mereka secara berurutan. Pastikan setiap programmer pemeliharaan mengetahui detailnya (Ini akan sangat tergantung pada bagaimana Anda mendokumentasikan persyaratan, desain, dan spesifikasi). Gunakan tes Unit, komentar dan berikan fungsi pembantu yang mengambil semua parameter dan memanggil yang lain
mattnz
Sama seperti komentar tidak langsung, antarmuka yang lancar dan pola perintah mungkin berguna. Namun, terserah Anda (sebagai pemilik) dan pengguna perpustakaan Anda (pelanggan) untuk memutuskan desain apa yang terbaik. Seperti yang ditunjukkan orang lain, perlu untuk berkomunikasi dengan pengguna bahwa operasi tidak komutatif (bahwa mereka peka terhadap urutan eksekusi), yang tanpanya pengguna Anda tidak akan pernah mengetahui cara menggunakannya dengan benar.
rwong
Contoh operasi non-komutatif: transformasi gambar (rotasi, penskalaan dan pemangkasan), perkalian matriks, dll.
rwong
Mungkin Anda bisa menggunakan currying: ini akan membuat tidak mungkin menerapkan metode / fungsi dalam urutan yang salah.
Giorgio
Metode apa yang Anda kerjakan di sini? Maksud saya, saya pikir standarnya adalah untuk melewatkan objek transformasi (seperti Java Affine Transform untuk hal-hal 2D) yang Anda berikan ke beberapa metode yang menerapkannya. Isi transformasi berbeda tergantung pada urutan Anda memanggil operasi awal di atasnya, dengan desain (jadi "Anda menyebutnya sesuai urutan yang Anda butuhkan", bukan "sesuai urutan yang saya inginkan").
Clockwork-Muse

Jawaban:

24

Waspadalah terhadap penggabungan waktu . Namun, ini tidak selalu menjadi masalah.

Jika Anda harus melakukan langkah-langkah secara berurutan, maka langkah 1 menghasilkan beberapa objek yang diperlukan untuk langkah 2 (misalnya aliran file atau struktur data lainnya). Ini saja membutuhkan bahwa fungsi kedua harus dipanggil setelah yang pertama, bahkan tidak mungkin untuk memanggil mereka dalam urutan yang salah secara tidak sengaja.

Dengan membagi fungsionalitas Anda menjadi potongan-potongan berukuran gigitan, setiap bagian lebih mudah dipahami, dan tentu saja lebih mudah untuk diuji secara terpisah. Jika Anda memiliki fungsi 100 garis besar dan ada sesuatu di tengah-tengah, bagaimana tes gagal Anda memberi tahu Anda apa yang salah? Jika salah satu dari lima metode garis Anda rusak, unit test Anda yang gagal mengarahkan Anda segera ke satu bagian kode yang perlu diperhatikan.

Ini adalah bagaimana kode yang kompleks harus melihat:

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

Kapan saja selama proses konversi data mentah menjadi widget yang selesai, setiap fungsi mengembalikan sesuatu yang diperlukan oleh langkah selanjutnya dalam proses. Seseorang tidak dapat membentuk paduan dari terak, seseorang harus mencium (memurnikan) itu terlebih dahulu. Seseorang mungkin tidak membuat widget tanpa izin yang tepat (misalnya baja) sebagai input.

Detail spesifik dari setiap langkah terkandung dalam fungsi individual yang dapat diuji: alih-alih unit menguji seluruh proses penambangan batu dan membuat widget, uji setiap langkah spesifik. Sekarang Anda memiliki cara mudah untuk memastikan bahwa jika proses "buat widget" Anda gagal, Anda dapat mempersempit alasan spesifik.

Selain manfaat menguji dan membuktikan kebenaran, menulis kode dengan cara ini jauh lebih mudah dibaca. Tidak ada yang bisa memahami daftar parameter besar . Memecahnya menjadi potongan-potongan kecil, dan menunjukkan apa arti setiap potongan kecil: itu grokkable .

Komunitas
sumber
2
Terima kasih, saya pikir ini adalah cara yang baik untuk mengatasi masalah tersebut. Meskipun meningkatkan jumlah objek (dan itu mungkin terasa tidak perlu), itu memaksa ketertiban sambil mempertahankan keterbacaan.
tomsrobots
10

Argumen "harus dijalankan dalam urutan" masih bisa diperdebatkan karena hampir semua kode Anda harus dijalankan dalam urutan yang benar. Lagipula, Anda tidak bisa menulis ke file, lalu membukanya lalu menutupnya, bukan?

Anda harus berkonsentrasi pada apa yang membuat kode Anda paling mudah dikelola. Ini biasanya berarti fungsi menulis yang kecil dan mudah dipahami. Setiap fungsi harus memiliki tujuan tunggal dan tidak memiliki efek samping yang tidak terduga.

Dave Nay
sumber
5

Saya akan membuat » ImageProcesssor « (atau nama apa pun yang sesuai dengan proyek Anda) dan objek konfigurasi ProcessConfiguration , yang menampung semua parameter yang diperlukan.

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

Di dalam imageprocessor Anda merangkum seluruh proses di belakang satu mehtod process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

Metode ini menyebut metode transformasional dalam urutan yang benar shift(), rotate(). Setiap metode mendapat parameter yang sesuai dari ProcessConfiguration yang diteruskan .

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

Saya menggunakan antarmuka cairan

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

yang memungkinkan inisialisasi bagus (seperti yang terlihat di atas).

Keuntungan yang jelas, merangkum parameter yang diperlukan dalam satu objek. Tanda tangan metode Anda menjadi dapat dibaca:

private void shift(Image i, ProcessConfiguration c)

Ini adalah tentang pergeseran sebuah gambar dan parameter rinci entah bagaimana dikonfigurasi .

Atau, Anda bisa membuat ProcessingPipeline :

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

Pemanggilan metode ke metode processImageakan membuat pipa seperti itu dan membuat transparan apa dan di mana urutan dilakukan: shift , rotate

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}
Thomas Junk
sumber
3

Sudahkah Anda mempertimbangkan untuk menggunakan semacam kari ? Bayangkan Anda memiliki kelas Processeedan kelas Processor:

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

Sekarang Anda dapat mengganti kelas Processordengan dua kelas Processor1dan Processor2:

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

Anda kemudian dapat memanggil operasi dalam urutan yang benar menggunakan:

new Processor1(processee).process(a1).process(a2);

Anda dapat menerapkan pola ini beberapa kali jika Anda memiliki lebih dari dua parameter. Anda juga dapat mengelompokkan argumen seperti yang Anda inginkan, yaitu Anda tidak perlu meminta masing-masing processmetode untuk mengambil satu argumen.

Giorgio
sumber
Kami memiliki ide yang hampir sama;) Satu-satunya perbedaan adalah, bahwa Pipeline Anda memberlakukan urutan pemrosesan yang ketat.
Thomas Junk
@ThomasJunk: Sejauh yang saya mengerti, ini adalah persyaratan: "metode ini perlu dipanggil dalam urutan yang sangat spesifik untuk mendapatkan hasil yang benar". Memiliki urutan eksekusi yang ketat terdengar sangat mirip dengan komposisi fungsi.
Giorgio
Dan saya juga. Tetapi, jika pemrosesan pesanan berubah, Anda harus melakukan banyak refactoring;)
Thomas Junk
@ThomasJunk: Benar. Itu sangat tergantung pada aplikasinya. Jika langkah-langkah pemrosesan dapat ditukar sangat sering, maka mungkin pendekatan Anda lebih baik.
Giorgio