Haruskah jika statments menggunakan metode dalam atau luar?


17

Manakah dari desain ini yang lebih baik? Apa pro dan kontra dari masing-masing? Yang mana yang akan Anda gunakan? Saran lain tentang cara menangani metode seperti sangat dihargai.

Masuk akal untuk mengasumsikan bahwa Draw () adalah satu-satunya tempat dari mana metode draw lainnya dipanggil. Ini perlu diperluas ke lebih banyak metode Draw * dan Tampilkan * properti, bukan hanya tiga yang ditampilkan di sini.

public void Draw()
{
    if (ShowAxis)
    {
        DrawAxis();
    }

    if (ShowLegend)
    {
        DrawLegend();
    }

    if (ShowPoints && Points.Count > 0)
    {
        DrawPoints();
    }
}

private void DrawAxis()
{
    // Draw things.
}

private void DrawLegend()
{
    // Draw things.
}

private void DrawPoints()
{
    // Draw things.
}

Atau

public void Draw()
{
    DrawAxis();
    DrawLegend();
    DrawPoints();
}

private void DrawAxis()
{
    if (!ShowAxis)
    {
        return;
    }

    // Draw things.
}

private void DrawLegend()
{
    if (!ShowLegend)
    {
        return;
    }

    // Draw things.
}

private void DrawPoints()
{
    if (!ShowPoints ||  Points.Count <= 0))
    {
        return;
    }

    // Draw things.
}

Untuk referensi lebih lanjut, saya menanyakan ini pada SO - stackoverflow.com/questions/2966216/…
Tesserex

1
Setiap kali saya melihat frasa seperti "Ini perlu diperluas ke lebih banyak lagi ...", saya langsung berpikir "Ini harus menjadi loop".
Paul Butcher

Jawaban:


28

Saya tidak berpikir Anda dapat memiliki aturan selimut untuk hal semacam ini, itu tergantung pada situasinya.

Dalam hal ini, saya akan menyarankan memiliki klausa if di luar metode karena nama-nama metode Draw menyiratkan bahwa mereka hanya menggambar hal-hal tanpa syarat khusus.

Jika Anda menemukan bahwa Anda harus melakukan cek sebelum memanggil metode di banyak tempat, maka Anda mungkin ingin memasukkan cek di dalam metode dan mengganti nama mereka untuk mengklarifikasi bahwa ini terjadi


7

Saya katakan yang kedua.

Metode harus memiliki tingkat abstraksi yang sama.

Dalam contoh kedua, tanggung jawab Draw()metode adalah bertindak seperti pengontrol, memanggil masing-masing metode menggambar individu. Semua kode dalam Draw()metode ini memiliki tingkat abstraksi yang sama. Pedoman ini mulai digunakan dalam skenario penggunaan ulang. Misalnya, jika Anda tergoda untuk menggunakan kembali DrawPoints()metode ini dalam metode publik lain (sebut saja Sketch()), Anda tidak perlu mengulangi klausa penjaga (pernyataan if memutuskan apakah akan menggambar poin).

Dalam contoh pertama, Draw()metode ini bertanggung jawab untuk menentukan apakah akan memanggil masing-masing metode individu, dan kemudian memanggil metode-metode tersebut. Draw()memiliki beberapa metode tingkat rendah yang berfungsi, namun mendelegasikan pekerjaan tingkat rendah lainnya ke metode lain, dan karenanya Draw()memiliki kode pada berbagai tingkat abstraksi. Dalam rangka untuk digunakan kembali DrawPoints()di Sketch(), Anda akan perlu untuk meniru penjaga klausul dalam Sketch()juga.

Ide ini dibahas dalam buku Robert C. Martin "Clean Code", yang sangat saya rekomendasikan.


1
Barang bagus. Untuk membahas poin yang dibuat dalam jawaban lain, saya sarankan mengubah nama DrawAxis()et. Al. untuk menunjukkan bahwa eksekusi mereka adalah kondisional, mungkin TryDrawAxis(). Kalau tidak, Anda perlu menavigasi dari Draw()metode ke setiap submetode individu untuk mengkonfirmasi perilakunya.
Josh Earl

6

Pendapat saya tentang subjek ini cukup kontroversial, tapi tetaplah, karena saya percaya hampir semua orang setuju pada hasil akhirnya. Saya hanya punya pendekatan berbeda untuk sampai ke sana.

Dalam artikel saya Function Hell , saya menjelaskan mengapa saya tidak suka metode pemisahan hanya demi menciptakan metode yang lebih kecil. Saya hanya membagi mereka ketika saya tahu , mereka akan digunakan kembali, atau tentu saja, ketika saya bisa menggunakannya kembali.

OP menyatakan:

Masuk akal untuk mengasumsikan bahwa Draw () adalah satu-satunya tempat dari mana metode draw lainnya dipanggil.

Ini membawa saya ke opsi ketiga (menengah) yang tidak disebutkan. Saya membuat 'blok kode' atau 'paragraf kode' yang orang lain akan buat fungsinya.

public void Draw()
{
    // Draw axis.
    if (ShowAxis)
    {
        // Drawing code ...
    }

    // Draw legend.
    if (ShowLegend)
    {
        // Drawing code ...
    }

    // Draw points.
    if (ShowPoints && Points.Count > 0)
    {
        // Drawing code ...
    }
}

OP juga menyatakan:

Ini perlu diperluas ke lebih banyak metode Draw * dan Tampilkan * properti, bukan hanya tiga yang ditampilkan di sini.

... jadi metode ini akan tumbuh sangat besar dengan cepat. Hampir semua orang setuju ini mengurangi keterbacaan. Menurut pendapat saya solusi yang tepat tidak hanya memecah menjadi beberapa metode, tetapi memecah kode menjadi kelas yang dapat digunakan kembali. Solusi saya mungkin akan terlihat seperti ini.

private void Initialize()
{               
    // Create axis.
    Axe xAxe = new Axe();
    Axe yAxe = new Axe();

    _drawObjects = new List<Drawable>
    {
        xAxe,
        yAxe,
        new Legend(),
        ...
    }
}

public void Draw()
{
    foreach ( Drawable d in _drawObjects )
    {
        d.Draw();
    }
}

Argumen Ofcourse masih perlu disahkan dan semacamnya.


Meskipun saya tidak setuju dengan Anda tentang Function hell, solusi Anda adalah (IMHO) yang benar, dan yang akan muncul dari aplikasi Clean Code & SRP yang tepat.
Paul Butcher

4

Untuk kasus-kasus ketika Anda memeriksa bidang yang mengontrol apakah akan menggambar atau tidak, masuk akal untuk memilikinya Draw(). Ketika keputusan itu menjadi lebih rumit, saya cenderung memilih yang terakhir (atau membaginya). Jika Anda akhirnya membutuhkan lebih banyak fleksibilitas, Anda selalu dapat mengembangkannya.

private void DrawPoints()
{
    if (ShouldDrawPoints())
    {
        DoDrawPoints();
    }
}

protected void ShouldDrawPoints()
{
    return ShowPoints && Points.Count > 0;
}

protected void DoDrawPoints()
{
    // Draw things.
}

Perhatikan metode tambahan dilindungi yang memungkinkan subclass untuk memperluas apa yang mereka butuhkan. Ini juga memungkinkan Anda untuk memaksa menggambar untuk pengujian atau apa pun alasan lain yang mungkin Anda miliki.


Ini bagus: semua yang diulang, ada dalam metode sendiri. Sekarang Anda bahkan dapat menambahkan DrawPointsIfItShould()metode yang pintas if(should){do}:)
Konerak

4

Dari dua alternatif itu, saya lebih suka versi pertama. Alasan saya adalah saya ingin metode untuk melakukan apa yang tersirat namanya, tanpa ada dependensi tersembunyi. DrawLegend harus menggambar legenda, bukan mungkin menggambar legenda.

Jawaban Steven Jeuris lebih baik daripada dua versi dalam pertanyaan.


3

Alih-alih memiliki Show___properti individual untuk setiap bagian, saya mungkin akan mendefinisikan bidang bit. Itu akan menyederhanakannya menjadi:

[Flags]
public enum DrawParts
{
    Axis = 1,
    Legend = 2,
    Points = 4,
    // More...
}

public class MyClass
{
    public void Draw() {
        if (VisibleParts.HasFlag(DrawPart.Axis))   DrawAxis();
        if (VisibleParts.HasFlag(DrawPart.Legend)) DrawLegend();
        if (VisibleParts.HasFlag(DrawPart.Points)) DrawPoints();
        // More...
    }

    public DrawParts VisibleParts { get; set; }
}

Selain itu, saya akan cenderung memeriksa visibilitas dalam metode luar, bukan dalam. Lagi pula, itu DrawLegendbukan DrawLegendIfShown. Tapi itu tergantung pada anggota kelas lainnya. Jika ada tempat lain yang memanggil DrawLegendmetode itu dan mereka perlu memeriksa ShowLegendjuga, saya mungkin hanya memindahkan cek ke DrawLegend.


2

Saya akan pergi dengan opsi pertama - dengan "jika" di luar metode. Ini lebih baik menjelaskan logika yang diikuti, ditambah memberi Anda pilihan untuk benar-benar menggambar sumbu, misalnya, dalam kasus ketika Anda ingin menggambar satu terlepas dari pengaturan. Plus itu menghilangkan overhead panggilan fungsi tambahan (dengan asumsi itu tidak inline), yang dapat terakumulasi jika Anda ingin kecepatan (contoh Anda sepertinya hanya menggambar grafik di mana itu mungkin bukan faktor, tetapi dalam animasi atau game bisa jadi).


1

Secara umum, saya lebih suka memiliki tingkat kode yang lebih rendah menganggap prasyarat terpenuhi dan melakukan pekerjaan apa pun yang seharusnya mereka lakukan, dengan pemeriksaan yang dilakukan lebih tinggi di tumpukan panggilan. Ini memiliki keuntungan samping dari menghemat siklus dengan tidak melakukan pemeriksaan berlebihan, tetapi itu juga berarti Anda dapat menulis kode yang bagus seperti ini:

int do_something()
{
    sometype* x;
    if(!(x = sometype_new())) return -1;
    foo(x);
    bar(x);
    baz(x);
    return 0;
}

dari pada:

int do_something()
{
    sometype x* = sometype_new();
    if(ERROR == foo(x)) return -1;
    if(ERROR == bar(x)) return -1;
    if(ERROR == baz(x)) return -1;
    return 0;
}

Dan tentu saja ini menjadi lebih nastier jika Anda memaksakan keluar tunggal, memiliki memori yang Anda butuhkan untuk bebas, dll.


0

Itu semua tergantung pada konteks:

void someThing(var1, var2)
{
    // If input fails validation then return quickly.
    if (!isValid(var1) || !isValid(var2))
    {   return;
    }


    // Otherwise do what is logical and makes the code easy to read.
    if (doTaskConditionOK())
    {   doTask();
    }

    // Return early if it is logical
    // This is OK in C++ but not C like languages.
    // You have to be careful of cleanup in C like languages while RIAA will do
    // that auto-magically in C++. 
    if (allFinished)
    {   return;
    }

    doAlternativeIfNotFinished();

    // -- Alternative to the above for C
    if (!allFinished)
    {   
        doAlternativeIfNotFinished();
    }

} 
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.