Apa yang akan membantu ketika refactoring metode besar untuk memastikan bahwa saya tidak merusak apa pun?

10

Saya saat ini refactoring bagian dari basis kode besar tanpa unit test apa pun. Saya mencoba untuk memperbaiki kode dengan cara kasar, yaitu dengan mencoba menebak apa yang dilakukan kode dan perubahan apa yang tidak akan mengubah artinya, tetapi tanpa hasil: secara acak memecah fitur-fitur di sekitar basis kode.

Perhatikan bahwa refactoring mencakup pemindahan kode C # lama ke gaya yang lebih fungsional (kode lama tidak menggunakan salah satu fitur .NET Framework 3 dan yang lebih baru, termasuk LINQ), menambahkan generik di mana kode dapat mengambil manfaat darinya, dll.

Saya tidak dapat menggunakan metode formal , mengingat berapa biayanya.

Di sisi lain, saya berasumsi bahwa setidaknya "Setiap kode warisan refactored harus datang dengan unit test" harus diikuti dengan ketat, tidak peduli berapa biayanya. Masalahnya adalah ketika saya memperbaiki sebagian kecil metode pribadi 500 LOC, menambahkan tes unit tampaknya menjadi tugas yang sulit.

Apa yang bisa membantu saya mengetahui unit test mana yang relevan untuk bagian kode tertentu? Saya menduga bahwa analisis statis dari kode akan membantu, tetapi apa alat dan teknik yang dapat saya gunakan untuk:

  • Tahu persis tes unit apa yang harus saya buat,

  • Dan / atau tahu jika perubahan yang saya lakukan memengaruhi kode asli dengan cara yang dieksekusi berbeda dari sekarang?

Arseni Mourzenko
sumber
Apa alasan Anda bahwa tes unit penulisan akan menambah waktu untuk proyek ini? Banyak pendukung akan tidak setuju, tetapi juga bergantung pada kemampuan Anda untuk menulisnya.
JeffO
Saya tidak mengatakan itu akan menambah waktu keseluruhan untuk proyek ini. Yang ingin saya katakan adalah bahwa itu akan meningkatkan jangka pendek (yaitu waktu langsung yang saya habiskan saat ini ketika refactoring kode).
Arseni Mourzenko
1
Anda tidak akan mau menggunakan formal methods in software developmentkarena itu digunakan untuk membuktikan kebenaran suatu program menggunakan logika predikat dan tidak akan memiliki penerapan untuk refactoring basis kode besar. Metode formal biasanya digunakan untuk membuktikan kode bekerja dengan benar di bidang-bidang seperti aplikasi medis. Anda benar itu mahal untuk melakukan itu sebabnya itu tidak sering digunakan.
Mushy
Alat yang bagus seperti opsi refactor di ReSharper membuat tugas seperti itu jauh lebih mudah. Dalam situasi seperti ini, itu sepadan dengan uang yang dikeluarkan.
billy.bob
1
Bukan jawaban penuh tetapi teknik bodoh yang saya temukan sangat efektif ketika semua teknik refactoring lain gagal saya: Buat kelas baru, pisahkan fungsi menjadi fungsi terpisah dengan kode persis sudah ada di sana, hanya rusak setiap 50 atau lebih baris, mempromosikan penduduk setempat yang dibagikan secara lintas fungsi kepada anggota, maka fungsi individu tersebut masuk ke dalam kepala saya dengan lebih baik dan memberi saya kemampuan untuk melihat anggota yang bagian-bagiannya di-threaded melalui keseluruhan logika. Ini bukan tujuan akhir, hanya cara aman untuk mendapatkan kekacauan warisan agar siap untuk refactoring dengan aman.
Jimmy Hoffa

Jawaban:

12

Saya memiliki tantangan serupa. Buku Bekerja dengan Kode Legacy adalah sumber yang bagus, tetapi ada asumsi bahwa Anda dapat menggunakan tes unit untuk mendukung pekerjaan Anda. Terkadang itu tidak mungkin.

Dalam pekerjaan arkeologi saya (istilah saya untuk pemeliharaan kode warisan seperti ini), saya mengikuti pendekatan yang sama dengan apa yang Anda uraikan.

  • Mulailah dengan pemahaman yang kuat tentang apa yang rutin dilakukan saat ini.
  • Pada saat yang sama, identifikasi apa yang seharusnya dilakukan oleh rutinitas tersebut . Banyak yang berpikir peluru ini dan yang sebelumnya sama, tetapi ada perbedaan yang halus. Sering kali, jika rutin melakukan apa yang seharusnya dilakukan maka Anda tidak akan menerapkan perubahan perawatan.
  • Jalankan beberapa sampel melalui rutin dan pastikan Anda menekan kasus batas, jalur kesalahan yang relevan, bersama dengan jalur utama. Pengalaman saya adalah bahwa kerusakan agunan (kerusakan fitur) berasal dari kondisi batas yang tidak dilaksanakan dengan cara yang persis sama.
  • Setelah contoh-contoh kasus, identifikasi apa yang sedang berlangsung yang tidak perlu perlu dipertahankan. Sekali lagi, saya telah menemukan bahwa efek samping seperti ini yang menyebabkan kerusakan tambahan di tempat lain.

Pada titik ini, Anda harus memiliki daftar kandidat tentang apa yang telah diekspos dan / atau dimanipulasi oleh rutinitas itu. Beberapa dari manipulasi itu kemungkinan besar tidak disengaja. Sekarang saya menggunakan findstrdan IDE untuk memahami area apa yang mungkin mereferensikan item dalam daftar kandidat. Saya akan meluangkan waktu untuk memahami bagaimana referensi itu bekerja dan apa sifatnya.

Akhirnya, setelah saya menipu diri sendiri untuk berpikir bahwa saya memahami dampak dari rutinitas asli, saya akan melakukan perubahan saya satu per satu dan menjalankan kembali langkah analisis yang saya uraikan di atas untuk memverifikasi bahwa perubahan itu berfungsi seperti yang saya harapkan itu bekerja. Saya secara khusus mencoba untuk menghindari mengubah banyak hal sekaligus karena saya menemukan ini meledak pada saya ketika saya mencoba dan memverifikasi dampaknya. Kadang-kadang Anda bisa lolos dengan beberapa perubahan, tetapi jika saya bisa mengikuti rute satu per satu, itu adalah pilihan saya.

Singkatnya, pendekatan saya mirip dengan apa yang Anda paparkan. Ini banyak pekerjaan persiapan; kemudian berhati-hati, perubahan individu; dan kemudian memverifikasi, memverifikasi, memverifikasi.


sumber
2
+1 untuk penggunaan "arkeologi" saja. Itu istilah yang sama yang saya gunakan untuk menggambarkan kegiatan ini dan saya pikir itu cara yang bagus untuk menjelaskannya (juga berpikir jawabannya bagus - saya tidak terlalu dangkal)
Erik Dietrich
10

Apa yang akan membantu ketika refactoring metode besar untuk memastikan bahwa saya tidak merusak apa pun?

Jawaban singkat: langkah kecil.

Masalahnya adalah ketika saya memperbaiki sebagian kecil metode pribadi 500 LOC, menambahkan tes unit tampaknya menjadi tugas yang sulit.

Pertimbangkan langkah-langkah ini:

  1. Pindahkan implementasi ke fungsi (pribadi) yang berbeda dan delegasikan panggilan.

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. Tambahkan kode logging (pastikan logging tidak gagal) di fungsi asli Anda, untuk semua input dan output.

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

    Jalankan aplikasi Anda dan lakukan apa pun yang Anda bisa dengannya (penggunaan yang valid, penggunaan yang tidak valid, penggunaan tipikal, penggunaan yang tidak biasa, dll).

  3. Anda sekarang memiliki max(call_count)set input dan output untuk menulis tes Anda dengan; Anda bisa menulis satu tes yang mengulang semua set parameter / hasil yang Anda miliki dan menjalankannya dalam satu lingkaran. Anda juga dapat menulis tes tambahan yang menjalankan kombinasi tertentu (untuk digunakan untuk memeriksa dengan cepat melewati set i / o tertentu).

  4. Pindah // 500 LOC herekembali ke ugly500locfungsi Anda (dan hapus fungsionalitas logging).

  5. Mulai mengekstraksi fungsi dari fungsi besar (tidak melakukan apa-apa lagi, cukup ekstrak fungsi) dan jalankan tes. Setelah ini, Anda harus memiliki lebih sedikit fungsi untuk refactor, daripada fungsi 500LOC.

  6. Hidup bahagia selamanya.

utnapistim
sumber
3

Biasanya Tes Unit adalah cara yang harus dilakukan.

Buat tes yang diperlukan yang membuktikan bahwa saat ini berfungsi seperti yang diharapkan. Luangkan waktu Anda dan tes terakhir harus membuat Anda percaya diri pada hasilnya.

Apa yang bisa membantu saya mengetahui unit test mana yang relevan untuk bagian kode tertentu?

Anda sedang dalam proses refactoring sepotong kode, Anda harus tahu persis apa yang dilakukannya dan apa dampaknya. Jadi pada dasarnya Anda perlu menguji semua zona yang terkena dampak. Ini akan membawa Anda banyak waktu ... tapi itu hasil yang diharapkan dari setiap proses refactoring.

Kemudian Anda dapat memisahkan semuanya tanpa masalah.

AFAIK, tidak ada teknik anti peluru untuk ini ... Anda hanya perlu metodis (pada metode apa pun Anda merasa nyaman), banyak waktu dan banyak kesabaran! :)

Ceria dan semoga berhasil!

Alex

AlexCode
sumber
Alat cakupan kode sangat penting di sini. Mengonfirmasi bahwa Anda telah menempuh setiap jalur melalui metode kompleks besar melalui inspeksi adalah sulit. Alat yang menunjukkan bahwa secara kolektif KitchenSinkMethodTest01 () ... KitchenSinkMethodTest17 () mencakup garis 1-45, 48-220, 245-399, dan 488-500 tetapi tidak menyentuh kode di antara; akan membuat mencari tahu tes tambahan apa yang perlu Anda tulis jauh lebih sederhana.
Dan Is Fiddling By Firelight