it-swarm-id.com

Bagaimana Anda refactor Pernyataan IF bersarang?

Saya sedang menjelajahi blogosphere pemrograman ketika saya melihat posting tentang GOTO:

http://giuliozambon.blogspot.com/2010/12/programmers-tabu.html

Di sini penulis berbicara tentang bagaimana "seseorang harus sampai pada kesimpulan bahwa ada situasi di mana GOTO membuat kode lebih mudah dibaca dan lebih dapat dipelihara" dan kemudian menunjukkan contoh yang mirip dengan ini:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Penulis kemudian mengusulkan bahwa menggunakan GOTO akan membuat kode ini lebih mudah dibaca dan dipelihara.

Saya pribadi dapat memikirkan setidaknya 3 cara berbeda untuk meratakannya dan membuat kode ini lebih mudah dibaca tanpa menggunakan GOTO yang memecah aliran. Inilah dua favorit saya.

1 - Fungsi Kecil Bersarang. Ambil setiap if dan blok kodenya dan mengubahnya menjadi fungsi. Jika pemeriksaan boolean gagal, kembalilah. Jika lewat, panggil fungsi berikutnya dalam rantai. (Wah, itu terdengar sangat mirip rekursi, bisakah Anda melakukannya dalam satu loop dengan pointer fungsi?)

2 - Variabel Sentinal. Bagi saya ini yang paling mudah. Cukup gunakan variabel blnContinueProcessing dan periksa untuk melihat apakah itu masih benar dalam cek Anda. Kemudian jika pemeriksaan gagal, atur variabel ke false.

Berapa banyak cara yang berbeda dapat jenis masalah pengkodean ini di refactored untuk mengurangi sarang dan meningkatkan rawatan?

34
saunderl

Sangat sulit untuk mengatakan tanpa mengetahui bagaimana cek yang berbeda berinteraksi. Refactoring yang ketat mungkin dilakukan. Membuat topologi objek yang mengeksekusi blok yang benar tergantung pada tipenya, juga pola strategi atau pola keadaan mungkin melakukan trik.

Tanpa mengetahui apa yang harus dilakukan, saya akan mempertimbangkan dua kemungkinan refactoring sederhana yang dapat di refactored lebih lanjut dengan mengekstraksi lebih banyak metode.

Yang pertama saya tidak benar-benar suka karena saya selalu suka sebagai titik keluar litle dalam suatu metode (lebih disukai satu)

if (!Check#1)
{ 
    return;
}
CodeBlock#1

if (!Check#2)
{
    return;
}
CodeBlock#2
...

Yang kedua menghapus beberapa pengembalian tetapi juga menambahkan banyak suara. (itu pada dasarnya hanya menghapus sarang)

bool stillValid = Check#1
if (stillValid)
{
  CodeBlock#1
}

stillValid = stillValid && Check#2
if (stillValid)
{
  CodeBlock#2
}

stillValid = stillValid && Check#3
if (stillValid)
{
  CodeBlock#3
}
...

Yang terakhir ini dapat di refactored dengan baik menjadi fungsi dan ketika Anda memberi mereka nama baik, hasilnya mungkin masuk akal ';

bool stillValid = DoCheck1AndCodeBlock1()
stillValid = stillValid && DoCheck2AndCodeBlock2()
stillValid = stillValid && DoCheck3AndCodeBlock3()

public bool DoCheck1AndCodeBlock1()
{
   bool returnValid = Check#1
   if (returnValid)
   {
      CodeBlock#1
   }
   return returnValid
}

Semua dalam semua ada kemungkinan cara pilihan yang lebih baik

33
KeesDijk

Itu disebut "Kode Panah" karena bentuk kode dengan indentasi yang tepat.

Jeff Atwood memiliki posting blog yang bagus tentang Coding Horror tentang cara meratakan panah:

Kode Panah Rata

Baca artikel untuk perawatan lengkap, tetapi di sini adalah poin utama ..

  • Ganti kondisi dengan klausa penjaga
  • Dekomposisi blok bersyarat menjadi fungsi terpisah
  • Ubah cek negatif menjadi cek positif
  • Selalu kembali sesegera mungkin dari fungsi secara oportunistik
40
JohnFx

Saya tahu beberapa orang akan berpendapat bahwa itu kebohongan, tetapi return; adalah refactoring yang jelas, mis.

if (!Check#1)
{ 
        return;
}
CodeBlock#1
if (!Check#2)
{
    return;
}
CodeBlock#2
.
.
.
if (Check#7)
{
    CodeBlock#7
}
else
{
    rest - of - the - program
}

Jika itu benar-benar hanya sekelompok cek penjaga sebelum menjalankan kode, maka ini berfungsi dengan baik. Jika lebih rumit dari itu, maka ini hanya akan membuatnya sedikit lebih sederhana, dan Anda memerlukan salah satu solusi lain.

26
Richard Gadsden

Kode spageti itu sepertinya adalah kandidat yang sempurna untuk mengembalikannya menjadi mesin negara.

10
Pemdas

Ini mungkin sudah disebutkan, tetapi jawaban "masuk" (pun intended) saya untuk "antipattern panah" yang ditunjukkan di sini adalah untuk membalikkan ifs. Uji kebalikan dari kondisi saat ini (cukup mudah dengan bukan operator) dan kembali dari metode jika itu benar. Tidak ada foto (meskipun secara pedantik berbicara, kembalinya void sedikit lebih dari lompatan ke garis kode panggilan, dengan langkah ekstra sepele untuk mengeluarkan bingkai dari tumpukan).

Contoh kasus, inilah aslinya:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Refactored:

if (!Check#1) return;

CodeBlock#1

if (!Check#2) return;

CodeBlock#2

if (!Check#3) return;

CodeBlock#3

if (!Check#4) return;

CodeBlock#4

if (!Check#5) return;

CodeBlock#5

if (!Check#6) return;

CodeBlock#6

if (Check#7)
    CodeBlock#7
else
{
    //rest of the program
}

Pada setiap jika, kami pada dasarnya memeriksa untuk melihat apakah kami harus melanjutkan. Cara kerjanya persis sama seperti aslinya dengan hanya satu tingkat bersarang.

Jika ada sesuatu di luar akhir cuplikan ini yang juga harus dijalankan, ekstrak kode ini ke dalam metodenya sendiri dan panggil dari mana pun kode ini hidup, sebelum melanjutkan ke kode yang akan muncul setelah cuplikan ini. Cuplikan itu sendiri cukup panjang, diberikan LOC aktual yang cukup di setiap blok kode, untuk membenarkan pemisahan beberapa metode lagi, tetapi saya ngelantur.

6
KeithS

Jika Anda secara rutin mendapatkan logika yang benar-benar membutuhkan piramida pemeriksaan if ini, Anda mungkin (secara metaforis) menggunakan kunci pas untuk memalu paku. Anda akan lebih baik melakukan hal semacam itu, logika rumit dalam bahasa yang mendukung logika rumit dan rumit semacam itu dengan struktur yang lebih baik daripada linear if/else if/else- konstruksi gaya.

Bahasa yang mungkin lebih cocok untuk struktur semacam ini dapat mencakup SNOBOL4 (dengan percabangan gaya dual-GOTO yang aneh) atau bahasa logika seperti Prolog dan Mercury (dengan kemampuan unifikasi dan backtracking mereka, belum lagi DCGs mereka untuk ekspresi yang agak ringkas dari keputusan yang rumit) .

Tentu saja jika ini bukan pilihan (karena kebanyakan programmer, tragisnya, bukan polyglots) ide-ide yang dihasilkan orang lain adalah baik seperti menggunakan berbagai OOP - struktur atau memecah klausa menjadi fungsi atau bahkan, jika Anda putus asa, dan tidak keberatan fakta bahwa kebanyakan orang menemukan mereka tidak dapat dibaca, menggunakan mesin negara .

Solusi saya, bagaimanapun, tetap menjangkau bahasa yang memungkinkan Anda untuk mengekspresikan logika apa yang Anda coba ekspresikan (dengan asumsi ini biasa dalam kode Anda) dengan cara yang lebih mudah, lebih mudah dibaca.

Dari di sini :

Cukup sering ketika saya melihat satu set pac-man seandainya saya menemukan bahwa jika saya hanya menggambar sesuatu seperti tabel kebenaran dari semua kondisi yang terlibat saya bisa mencari cara yang lebih baik untuk menyelesaikan masalah.

Dengan begitu Anda juga dapat menilai apakah ada metode yang lebih baik, bagaimana Anda dapat memecahnya lebih lanjut dan (dan ini adalah yang besar dengan kode semacam ini) apakah ada lubang dalam logika.

Setelah melakukan itu, Anda mungkin dapat memecahnya menjadi beberapa pernyataan beralih dan beberapa metode dan menyimpan mook miskin berikutnya yang harus melalui kode banyak masalah.

1
Jim G.

Secara pribadi, saya suka membungkus pernyataan if ini ke dalam fungsi terpisah yang mengembalikan bool jika fungsi berhasil.

Strukturnya terlihat sebagai berikut:


if (DoCheck1AndCodeBlock1() && 
    DoCheck2AndCodeBlock2() && 
    DoCheck3AndCodeBlock3()) 
{
   // ... you may perform the final operation here ....
}

Satu-satunya kelemahan adalah bahwa fungsi-fungsi ini harus menampilkan data menggunakan atribut yang dilewatkan oleh variabel referensi atau anggota.

1
gogisan

Menggunakan OO pendekatan pola komposit di mana daun adalah kondisi sederhana dan komponen penyatuan elemen sederhana membuat kode semacam ini dapat diperluas dan beradaptasi

1
guiman

Jika Anda menginginkan solusi berorientasi objek yang mungkin, kode tersebut terlihat seperti contoh kanonik untuk refactoring menggunakan pola desain rantai tanggung jawab .

0
FinnNk

Membagi mereka menjadi beberapa fungsi mungkin bisa membantu?

Anda memanggil fungsi pertama dan ketika selesai akan memanggil fungsi berikutnya, hal pertama yang akan dilakukan fungsi adalah menguji cek untuk fungsi itu.

0
Toby

Itu sangat tergantung pada ukuran setiap blok kode dan ukuran total, tetapi saya cenderung untuk membelah baik dengan "metode ekstrak" lurus pada blok, atau dengan memindahkan bagian tanpa syarat ke metode yang terpisah. Namun peringatan @KeesDijk yang disebutkan berlaku. Pendekatan sebelumnya memberi Anda serangkaian fungsi yang masing-masing suka

if (Check#1)
{
    CodeBlock#1
    NextFunction
}

Yang dapat bekerja dengan baik tetapi menyebabkan kode gembung dan "metode hanya digunakan sekali" antipattern. Jadi saya biasanya lebih suka pendekatan ini:

if (CheckFunctionOne)
{
    MethodOneWithDescriptiveName
    if (CheckFunctionTwo)
    {
        MethodTwoWithDescriptiveName
        ....

Dengan penggunaan yang tepat dari variabel pribadi dan parameter yang lewat dapat dilakukan. Setelah melirik pemrograman melek bisa membantu di sini.

0
Мסž