Apakah boleh mengganti kode yang dioptimalkan dengan kode yang dapat dibaca?


78

Terkadang Anda mengalami situasi di mana Anda harus memperluas / memperbaiki beberapa kode yang ada. Anda melihat bahwa kode lama sangat ramping, tetapi juga sulit diperluas, dan membutuhkan waktu untuk membaca.

Apakah ide yang bagus untuk menggantinya dengan kode modern?

Beberapa waktu yang lalu saya menyukai pendekatan lean, tetapi sekarang, menurut saya lebih baik mengorbankan banyak optimisasi demi abstraksi yang lebih tinggi, antarmuka yang lebih baik dan kode yang lebih mudah dibaca dan dapat diperpanjang.

Kompiler tampaknya menjadi lebih baik juga, jadi hal-hal seperti struct abc = {}diam-diam diubah menjadi memsets, shared_ptrs cukup banyak menghasilkan kode yang sama seperti twiddling pointer mentah, template bekerja sangat baik karena mereka menghasilkan kode super ramping, dan sebagainya.

Tapi tetap saja, terkadang Anda melihat susunan berbasis susunan dan fungsi C lama dengan logika yang tidak jelas, dan biasanya mereka tidak berada di jalur kritis.

Apakah ide yang baik untuk mengubah kode seperti itu jika Anda harus menyentuh sedikit saja?


20
Keterbacaan dan optimalisasi tidak ditentang sebagian besar waktu.
deadalnix

23
Dapatkah keterbacaan meningkat dengan beberapa komentar?
YetAnotherUser

17
Ini mengkhawatirkan bahwa OOP-ification dianggap 'kode modern'
James

7
seperti filosofi slackware: jika tidak rusak jangan perbaiki, setidaknya kamu punya alasan yang sangat, sangat bagus untuk melakukannya
osdamv

5
Dengan kode yang dioptimalkan, maksud Anda kode optimal yang sebenarnya , atau yang disebut kode yang dioptimalkan?
dan04

Jawaban:


115

Dimana?

  • Di beranda situs web skala Google, itu tidak dapat diterima. Simpan barang-barang secepat mungkin.

  • Pada bagian dari aplikasi yang digunakan oleh satu orang setahun sekali, sangat dapat diterima untuk mengorbankan kinerja untuk mendapatkan keterbacaan kode.

Secara umum, apa saja persyaratan non-fungsional untuk bagian kode yang sedang Anda kerjakan? Jika suatu tindakan harus dilakukan di bawah 900 ms. dalam konteks yang diberikan (mesin, memuat, dll.) 80% dari waktu, dan sebenarnya, ia bekerja di bawah 200 ms. 100% dari waktu, tentu saja, membuat kode lebih mudah dibaca bahkan jika itu sedikit mempengaruhi kinerja. Jika di sisi lain tindakan yang sama tidak pernah dilakukan di bawah sepuluh detik, well, Anda sebaiknya mencoba melihat apa yang salah dengan kinerja (atau persyaratan di tempat pertama).

Juga, bagaimana peningkatan keterbacaan akan menurunkan kinerja? Seringkali, pengembang mengadaptasi perilaku yang dekat dengan optimasi prematur: mereka takut untuk meningkatkan keterbacaan, percaya bahwa itu akan secara drastis merusak kinerja, sementara kode yang lebih mudah dibaca akan menghabiskan beberapa mikrodetik lebih banyak melakukan tindakan yang sama.


47
+1! Jika Anda tidak memiliki nomor, dapatkan beberapa nomor. Jika Anda tidak punya waktu untuk mendapatkan angka, Anda tidak punya waktu untuk mengubahnya.
Tacroy

49
Seringkali tidak, pengembang "mengoptimalkan" berdasarkan mitos dan kesalahpahaman, misalnya, dengan mengasumsikan bahwa "C" lebih cepat dari "C ++" dan menghindari fitur C ++ dari perasaan umum bahwa segala sesuatu lebih cepat tanpa angka untuk mendukungnya. Mengingatkan saya pada pengembang C yang saya ikuti yang berpikir gotolebih cepat daripada loop. Ironisnya, pengoptimal bekerja lebih baik dengan loop, jadi dia membuat kode lebih lambat dan lebih sulit dibaca.
Gort the Robot

6
Daripada menambahkan jawaban lain, saya memberi +1 jawaban ini. Jika memahami fragmen kode itu penting, beri komentar dengan baik. Saya bekerja di lingkungan C / C ++ / Assembly dengan kode lama satu dekade dengan puluhan kontributor. Jika kodenya berfungsi, biarkan saja dan kembali bekerja.
Chris K

Itu sebabnya saya cenderung hanya menulis kode yang mudah dibaca. Performa dapat dicapai dengan memotong beberapa hot spot.
Luca

36

Biasanya tidak .

Mengubah kode dapat menyebabkan masalah knock-on yang tidak terduga di tempat lain dalam sistem (yang kadang-kadang bisa tidak diketahui sampai kemudian dalam suatu proyek jika Anda tidak memiliki unit solid dan tes asap di tempat). Saya biasanya pergi dengan "jika tidak rusak, jangan memperbaikinya" mentalitas.

Pengecualian untuk aturan ini adalah jika Anda menerapkan fitur baru yang menyentuh kode ini. Jika, pada saat itu, itu tidak masuk akal dan refactoring benar-benar perlu dilakukan, maka lakukanlah selama waktu refactoring (dan pengujian dan buffer yang cukup untuk menangani masalah knock-on) semuanya diperhitungkan dalam perkiraan.

Tentu saja, profil, profil, profil , terutama jika itu adalah area jalur kritis.


2
Ya, tetapi Anda menganggap optimasi diperlukan. Kami tidak tahu selalu tahu apakah itu, dan kami mungkin ingin menentukan ini dulu.
haylem

2
@Haylem: Tidak, saya berasumsi bahwa kode berfungsi apa adanya. Saya juga berasumsi bahwa refactoring kode akan selalu menyebabkan masalah knock-on di tempat lain dalam sistem (kecuali jika Anda berurusan dengan sepotong kode sepele yang tidak memiliki dependensi eksternal).
Demian Brecht

Ada beberapa kebenaran dalam jawaban ini, dan ironisnya ini adalah karena masalah knock-on jarang didokumentasikan, dipahami, dikomunikasikan, atau bahkan diperhatikan oleh pengembang. Jika pengembang memiliki pemahaman yang lebih mendalam tentang masalah yang telah terjadi di masa lalu, mereka akan tahu apa yang harus diukur , dan akan lebih percaya diri dalam melakukan perubahan kode.
rwong

29

Singkatnya: Tergantung

  • Apakah Anda benar-benar akan membutuhkan atau menggunakan versi refactored / ditingkatkan Anda?

    • Apakah ada keuntungan konkret, langsung atau jangka panjang?
    • Apakah perolehan ini hanya untuk pemeliharaan, atau benar-benar arsitektur?
  • Apakah itu benar-benar perlu dioptimalkan?

    • Mengapa?
    • Apa target perolehan yang Anda butuhkan untuk membidik?

Dalam Rincian

Apakah Anda akan membutuhkan barang yang bersih dan berkilau?

Ada hal-hal yang perlu diwaspadai di sini, dan Anda perlu mengidentifikasi batas antara apa yang nyata, perolehan terukur dan apa yang hanya preferensi pribadi Anda dan kebiasaan buruk potensial untuk menyentuh kode yang seharusnya tidak boleh.

Lebih khusus, ketahui ini:

Ada yang namanya Over-Engineering

Ini anti-pola, dan dilengkapi dengan masalah bawaan:

  • itu mungkin lebih extensible , tetapi mungkin tidak mudah untuk memperluas,
  • itu mungkin tidak sederhana untuk memahami ,
  • terakhir, tapi jelas tidak kalah pentingnya di sini: Anda mungkin memperlambat seluruh kode.

Beberapa juga dapat menyebutkan prinsip KISS sebagai referensi, tetapi ini berlawanan dengan intuisi: apakah cara yang dioptimalkan adalah cara yang sederhana atau cara yang dirancang secara jelas? Jawabannya belum tentu absolut, seperti yang dijelaskan dalam sisanya di bawah ini.

Anda Tidak Akan Membutuhkannya

The Prinsip YAGNI tidak sepenuhnya ortogonal dengan masalah lain, tetapi hal ini membantu untuk bertanya pada diri sendiri pertanyaan: Anda akan membutuhkannya?

Apakah arsitektur yang lebih kompleks benar-benar memberikan manfaat bagi Anda, selain memberikan penampilan yang lebih dapat dipertahankan?

Jika Itu Tidak Rusak, Jangan Perbaiki

Tulis ini di poster besar dan gantung di samping layar Anda atau di area dapur di tempat kerja, atau di ruang rapat pengembang. Tentu saja ada banyak mantra lain yang layak untuk diulangi, tetapi mantra ini penting setiap kali Anda mencoba melakukan "pekerjaan pemeliharaan" dan merasakan dorongan untuk "memperbaikinya".

Wajar bagi kita untuk ingin "meningkatkan" kode atau bahkan hanya menyentuhnya, bahkan secara tidak sadar, ketika kita membacanya untuk mencoba memahaminya. Ini adalah hal yang baik, karena itu berarti kami memiliki pendapat dan mencoba untuk mendapatkan pemahaman yang lebih dalam tentang internal, tetapi juga terikat dengan tingkat keterampilan kami, pengetahuan kami (bagaimana Anda memutuskan apa yang lebih baik atau tidak? Baik, lihat bagian di bawah ini ...), dan semua asumsi yang kami buat tentang apa yang kami pikir kami tahu tentang perangkat lunak ...:

  • sebenarnya,
  • sebenarnya perlu dilakukan,
  • pada akhirnya perlu dilakukan,
  • dan seberapa baik melakukannya.

Apakah itu benar-benar perlu dioptimalkan?

Semua ini mengatakan, mengapa "dioptimalkan" sejak awal? Mereka mengatakan bahwa pengoptimalan prematur adalah akar dari semua kejahatan, dan jika Anda melihat kode yang tidak terdokumentasi dan tampaknya dioptimalkan, biasanya Anda dapat menganggapnya mungkin tidak mengikuti Aturan Pengoptimalan yang tidak terlalu membutuhkan upaya pengoptimalan dan itu hanya keangkuhan pengembang yang biasa. Sekali lagi, mungkin itu hanya milik Anda yang berbicara sekarang.

Jika ya, dalam batas apa ia diterima? Jika ada kebutuhan untuk itu, batas ini ada, dan memberi Anda ruang untuk meningkatkan hal-hal, atau garis keras untuk memutuskan untuk melepaskannya.

Waspadalah terhadap karakteristik yang tidak terlihat. Kemungkinannya, versi "extensible" dari kode ini akan membuat Anda lebih banyak kehabisan memori saat runtime, dan bahkan menghadirkan jejak memori statis yang lebih besar untuk dieksekusi. Fitur-fitur OO yang mengkilap datang dengan biaya tidak intuitif seperti ini, dan mereka mungkin penting bagi program Anda dan lingkungan yang seharusnya dijalankan.

Mengukur, Mengukur, Mengukur

Sebagai orang Google sekarang, ini semua tentang data! Jika Anda dapat mencadangkannya dengan data, maka itu perlu.

Ada kisah yang tidak terlalu tua ini bahwa untuk setiap $ 1 yang dihabiskan dalam pengembangan itu akan diikuti oleh setidaknya $ 1 dalam pengujian dan setidaknya $ 1 dalam dukungan (tapi sungguh, itu jauh lebih banyak).

Ubah dampak banyak hal:

  • Anda mungkin perlu membuat bangunan baru;
  • Anda harus menulis unit test baru (pasti jika tidak ada, dan arsitektur Anda yang lebih luas mungkin menyisakan ruang lebih banyak, karena Anda memiliki lebih banyak permukaan untuk bug);
  • Anda harus menulis tes kinerja baru (untuk memastikan ini tetap stabil di masa depan, dan untuk melihat di mana hambatannya), dan ini sulit dilakukan ;
  • Anda harus mendokumentasikannya (dan lebih dapat diperluas berarti lebih banyak ruang untuk detail);
  • Anda (atau orang lain) perlu mengujinya secara ekstensif di QA;
  • kode (hampir) tidak pernah bebas bug, dan Anda harus mendukungnya.

Jadi bukan hanya konsumsi sumber daya perangkat keras (kecepatan eksekusi atau jejak memori) yang perlu Anda ukur di sini, tetapi juga konsumsi sumber daya tim . Keduanya perlu diprediksi untuk menentukan tujuan sasaran, diukur, dipertanggungjawabkan, dan diadaptasi berdasarkan pembangunan.

Dan bagi Anda manajer, itu berarti memasukkannya ke dalam rencana pengembangan saat ini, jadi jangan berkomunikasi tentang hal itu dan jangan masuk ke pengkodean cow-boy / submarine / black-ops yang marah.


Secara umum...

Ya tapi...

Jangan salah paham, secara umum, saya akan mendukung melakukan apa yang Anda sarankan, dan saya sering menganjurkannya. Tetapi Anda harus menyadari biaya jangka panjang.

Di dunia yang sempurna, ini adalah solusi yang tepat:

  • perangkat keras komputer menjadi lebih baik dari waktu ke waktu,
  • kompiler dan platform runtime menjadi lebih baik dari waktu ke waktu,
  • Anda mendapatkan kode yang hampir sempurna, bersih, dapat dipelihara dan mudah dibaca.

Dalam praktek:

  • Anda dapat memperburuknya

    Anda membutuhkan lebih banyak bola mata untuk melihatnya, dan semakin rumit, semakin banyak bola mata yang Anda butuhkan.

  • Anda tidak dapat memprediksi masa depan

    Anda tidak dapat mengetahui dengan pasti jika Anda akan membutuhkannya dan bahkan jika "ekstensi" yang Anda perlukan akan lebih mudah dan lebih cepat untuk diterapkan dalam bentuk lama, dan jika mereka perlu dioptimalkan secara super. .

  • itu mewakili, dari perspektif manajemen, biaya besar tanpa keuntungan langsung.

Jadikan itu Bagian dari Proses

Anda menyebutkan di sini bahwa ini adalah perubahan yang agak kecil, dan Anda memiliki beberapa masalah spesifik dalam pikiran. Menurut saya biasanya tidak apa-apa dalam kasus ini, tetapi kebanyakan dari kita juga memiliki kisah pribadi tentang perubahan kecil, suntingan yang hampir gagal, yang akhirnya berubah menjadi mimpi buruk pemeliharaan dan tenggat waktu yang nyaris terlewat atau meledak karena Joe Programmer tidak melihat satu pun. alasan di balik kode dan menyentuh sesuatu yang seharusnya tidak.

Jika Anda memiliki proses untuk menangani keputusan seperti itu, Anda mengambil keunggulan pribadi dari mereka:

  • Jika Anda menguji sesuatu dengan benar, Anda akan tahu lebih cepat jika ada yang rusak,
  • Jika Anda mengukurnya, Anda akan tahu jika mereka membaik,
  • Jika Anda meninjaunya, Anda akan tahu jika itu membuat orang lain kesal.

Cakupan Tes, Profiling, dan Pengumpulan Data cukup rumit

Namun, tentu saja, kode pengujian dan metrik Anda mungkin mengalami masalah yang sama dengan yang Anda coba hindari untuk kode Anda yang sebenarnya: apakah Anda menguji hal-hal yang benar, dan apakah itu hal yang tepat untuk masa depan, dan apakah Anda mengukur yang benar sesuatu?

Namun, secara umum, semakin Anda menguji (sampai batas tertentu) dan mengukur, semakin banyak data yang Anda kumpulkan dan semakin aman Anda. Waktu analogi yang buruk: anggap itu seperti mengemudi (atau kehidupan secara umum): Anda bisa menjadi pengemudi terbaik di dunia, jika mobil mogok pada Anda atau seseorang memutuskan untuk bunuh diri dengan mengendarai mobil Anda dengan hari ini, Anda keterampilan mungkin tidak cukup. Ada dua hal lingkungan yang dapat menimpa Anda, dan kesalahan manusia juga penting.

Ulasan Kode adalah Pengujian Lorong Tim Pengembang

Dan saya pikir bagian terakhir adalah kuncinya di sini: lakukan review kode. Anda tidak akan tahu nilai peningkatan Anda jika Anda membuatnya sendiri. Ulasan kode adalah "pengujian lorong" kami: ikuti versi Hukum Linus Raymond untuk mendeteksi bug dan mendeteksi rekayasa berlebihan dan pola-anti lainnya, dan untuk memastikan bahwa kode tersebut sesuai dengan kemampuan tim Anda. Tidak ada gunanya memiliki kode "terbaik" jika tidak ada orang lain tetapi Anda dapat memahami dan memeliharanya, dan itu berlaku untuk optimasi samar dan desain arsitektur 6-lapisan yang mendalam.

Sebagai kata penutup, ingat:

Semua orang tahu bahwa debugging dua kali lebih sulit daripada menulis program di tempat pertama. Jadi, jika Anda sepintar ketika Anda menulisnya, bagaimana Anda akan men-debug-nya? - Brian Kernighan


"Jika Itu Tidak Rusak, Jangan Perbaiki" bertentangan dengan refactoring. Tidak masalah jika sesuatu berfungsi, jika tidak dapat dipelihara, perlu diubah.
Miyamoto Akira

@MiyamotoAkira: itu adalah dua kecepatan. Jika tidak rusak tetapi dapat diterima dan cenderung tidak melihat dukungan, mungkin dapat diterima untuk membiarkannya sendiri daripada memperkenalkan potensi bug baru atau menghabiskan waktu pengembangan untuk itu. Ini semua tentang mengevaluasi manfaat, baik jangka pendek dan jangka panjang, dari refactoring. Tidak ada jawaban yang jelas, itu memang memerlukan beberapa evaluasi.
haylem

sepakat. Saya kira saya tidak suka kalimat (dan filosofi di baliknya) karena saya melihat refactoring sebagai opsi default, dan hanya jika tampaknya akan memakan waktu terlalu lama, atau terlalu sulit maka akan / harus diputuskan untuk tidak ikuti saja. Pikiran Anda, saya telah dibakar oleh orang-orang yang tidak mengubah hal-hal, yang meskipun berfungsi, jelas merupakan solusi yang salah segera setelah Anda harus mempertahankannya atau memperluasnya.
Miyamoto Akira

@MiyamotoAkira: kalimat pendek dan pernyataan pendapat tidak bisa banyak mengungkapkan. Mereka seharusnya ada di wajah Anda, dan dikembangkan di samping, saya kira. Saya sendiri sangat berperan dalam meninjau dan menyentuh kode sesering mungkin, bahkan jika sering tanpa jaring pengaman besar atau tanpa banyak alasan. Jika kotor, Anda bersihkan. Tetapi, sama halnya, saya juga terbakar beberapa kali. Dan masih akan terbakar. Selama ini bukan yang tingkat 3, saya tidak terlalu keberatan, sejauh ini selalu luka bakar jangka pendek untuk keuntungan jangka panjang.
haylem

8

Secara umum, Anda harus fokus pada keterbacaan pertama, dan kinerja jauh di kemudian hari. Sebagian besar waktu, optimasi kinerja tersebut dapat diabaikan, tetapi biaya perawatannya bisa sangat besar.

Tentu saja semua hal "kecil" harus diubah demi kejelasan karena, seperti yang Anda tunjukkan, sebagian besar akan dioptimalkan oleh kompiler.

Adapun optimasi yang lebih besar, mungkin ada peluang bahwa optimasi sebenarnya penting untuk mencapai kinerja yang wajar (meskipun ini bukan kasus yang sering terjadi). Saya akan melakukan perubahan Anda dan kemudian profil kode sebelum dan sesudah perubahan. Jika kode baru memiliki masalah kinerja yang signifikan, Anda selalu dapat memutar kembali ke versi yang dioptimalkan, dan jika tidak, Anda bisa tetap menggunakan versi kode pembersih.

Ubah hanya satu bagian dari kode pada satu waktu dan lihat bagaimana itu mempengaruhi kinerja setelah setiap putaran refactoring.


8

Itu tergantung pada mengapa kode itu dioptimalkan dan apa efek perubahan itu dan apa dampak kode pada kinerja keseluruhan. Itu juga harus bergantung pada apakah Anda memiliki cara yang baik untuk memuat perubahan uji.

Anda tidak boleh melakukan perubahan ini tanpa membuat profil sebelum dan sesudah dan di bawah beban yang mirip dengan apa yang akan terlihat pada produksi. Itu berarti tidak menggunakan sekumpulan kecil data pada mesin pengembang atau menguji ketika hanya satu pengguna yang menggunakan sistem.

Jika pengoptimalannya baru-baru ini, Anda mungkin dapat berbicara dengan pengembang dan mencari tahu apa masalahnya dan seberapa lambat aplikasi tersebut sebelum pengoptimalan. Ini dapat memberi tahu Anda banyak tentang apakah layak untuk melakukan optimasi dan kondisi apa yang diperlukan untuk optimasi (laporan yang mencakup satu tahun penuh misalnya mungkin tidak menjadi lambat hingga September atau Oktober, jika Anda menguji perubahan Anda pada bulan Februari, kelambatan mungkin belum tampak dan tes tidak valid).

Jika optimasi agak lama, metode yang lebih baru mungkin lebih cepat dan lebih mudah dibaca.

Pada akhirnya ini adalah pertanyaan untuk bos Anda. Membutuhkan waktu lama untuk memperbaiki sesuatu yang telah dioptimalkan dan untuk memastikan bahwa perubahan tidak mempengaruhi hasil akhir dan berkinerja baik atau setidaknya dapat diterima dibandingkan dengan cara lama. Dia mungkin ingin Anda menghabiskan waktu di area lain alih-alih mengambil tugas berisiko tinggi untuk menghemat beberapa menit waktu pengkodean. Atau dia mungkin setuju bahwa kodenya sulit dipahami dan perlu sering intervensi dan metode yang lebih baik sekarang tersedia.


6

jika profiling menunjukkan bahwa optimasi tidak diperlukan (tidak dalam bagian kritis) atau bahkan memiliki runtime yang lebih buruk (sebagai akibat dari optimasi prematur yang buruk) maka pastikan ganti dengan kode yang dapat dibaca yang lebih mudah untuk dipertahankan

juga pastikan kode berperilaku sama dengan tes yang sesuai


5

Pikirkan dari sudut pandang bisnis. Berapa biaya perubahan? Berapa banyak waktu yang Anda butuhkan untuk melakukan perubahan dan berapa banyak yang akan Anda hemat dalam jangka panjang dengan membuat kode lebih mudah diperluas atau dipelihara? Sekarang pasang label harga pada waktu itu dan bandingkan dengan uang yang hilang dengan mengurangi kinerja. Mungkin Anda perlu menambah atau memutakhirkan server untuk menebus kinerja yang hilang. Mungkin produk tidak lagi memenuhi persyaratan dan tidak dapat dijual lagi. Mungkin tidak ada kerugian. Mungkin perubahan meningkatkan ketahanan dan menghemat waktu di tempat lain. Sekarang buat keputusan Anda.

Di samping catatan, dalam beberapa kasus, dimungkinkan untuk menyimpan kedua versi fragmen. Anda dapat menulis tes yang menghasilkan nilai input acak dan memverifikasi hasilnya dengan versi lain. Gunakan solusi "pintar" untuk memeriksa hasil dari solusi yang benar-benar dapat dipahami dan jelas benar dan dengan demikian mendapatkan kepastian (tetapi tidak ada bukti) bahwa solusi baru itu setara dengan yang lama. Atau sebaliknya dan periksa hasil dari kode rumit dengan kode verbose dan dengan demikian mendokumentasikan niat di balik peretasan dengan cara yang jelas.


4

Pada dasarnya, Anda bertanya apakah refactoring adalah usaha yang bermanfaat. Jawabannya tentu saja ya.

Tapi...

... kamu harus melakukannya dengan hati-hati. Anda memerlukan unit solid, integrasi, fungsional, dan tes kinerja untuk kode apa pun yang Anda refactoring. Anda harus yakin bahwa mereka benar-benar menguji semua fungsi yang diperlukan. Anda membutuhkan kemampuan untuk menjalankannya dengan mudah dan berulang kali. Setelah memilikinya, Anda harus dapat mengganti komponen dengan komponen baru yang berisi fungsi yang setara.

Martin Fowler menulis buku tentang ini.


3

Anda tidak boleh mengubah kode produksi yang berfungsi tanpa alasan yang baik. "Refactoring" bukan alasan yang cukup kecuali Anda tidak dapat melakukan pekerjaan Anda tanpa refactoring itu. Bahkan jika apa yang Anda lakukan adalah memperbaiki bug di dalam kode yang sulit itu sendiri, Anda harus meluangkan waktu untuk memahaminya, dan membuat perubahan sekecil mungkin. Jika kodenya sulit dipahami, Anda tidak akan dapat memahaminya sepenuhnya, sehingga perubahan yang Anda lakukan akan memiliki efek samping yang tidak dapat diprediksi - bug, dengan kata lain. Semakin besar perubahan, semakin besar kemungkinan Anda menyebabkan masalah.

Akan ada pengecualian untuk ini: jika kode yang tidak dapat dimengerti memiliki serangkaian unit test yang lengkap, Anda dapat melakukan refactor. Karena saya belum pernah melihat atau mendengar kode yang tidak dapat dipahami dengan tes unit lengkap, Anda menulis tes unit terlebih dahulu, mendapatkan persetujuan dari orang-orang yang diperlukan bahwa tes unit itu sebenarnya mewakili apa yang harus dilakukan kode, dan KEMUDIAN membuat perubahan kode . Saya pernah melakukannya sekali atau dua kali; sakit di leher, dan sangat mahal, tetapi pada akhirnya menghasilkan hasil yang baik.


3

Jika itu hanya sepotong kode pendek yang melakukan sesuatu yang relatif sederhana dengan cara yang sulit dipahami, saya akan menggeser "pemahaman cepat" dalam komentar yang diperluas dan / atau implementasi alternatif yang tidak digunakan, seperti

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif

3

Jawabannya adalah, tanpa kehilangan sifat umum, ya. Selalu tambahkan kode modern saat Anda melihat kode sulit dibaca, dan hapus kode buruk dalam kebanyakan kasus. Saya menggunakan proses berikut:

  1. Cari tes kinerja dan informasi profil pendukung. Jika tidak ada tes kinerja, maka apa yang bisa ditegaskan tanpa bukti dapat diberhentikan tanpa bukti. Tegaskan bahwa kode modern Anda lebih cepat dan hapus kode lama. Jika ada yang berdebat (bahkan diri Anda sendiri) minta mereka untuk menulis kode profil untuk membuktikan mana yang lebih cepat.
  2. Jika kode profil ada, tetap tulis kode modern. Sebutkan sesuatu seperti itu <function>_clean(). Lalu, "balas" kode Anda dengan kode yang salah. Jika kode Anda lebih baik, hapus kode lama.
  3. Jika kode lama lebih cepat, tinggalkan kode modern Anda di sana. Ini berfungsi sebagai dokumentasi yang baik untuk apa kode lain dimaksudkan untuk dilakukan, dan karena kode "ras" ada, Anda dapat terus menjalankannya untuk mendokumentasikan karakteristik kinerja dan perbedaan antara dua jalur. Anda juga dapat menguji unit untuk perbedaan dalam perilaku kode. Yang penting, kode modern akan mengalahkan kode "dioptimalkan" suatu hari, dijamin. Anda kemudian dapat menghapus kode yang salah.

QED.


3

Jika saya bisa mengajarkan kepada dunia satu hal (tentang Perangkat Lunak) sebelum saya mati, saya akan mengajarkannya bahwa "Kinerja versus X" adalah Dilema Palsu.

Refactoring biasanya dikenal sebagai anugerah untuk keterbacaan dan keandalan, tetapi dapat dengan mudah mendukung optimasi. Saat Anda menangani peningkatan kinerja sebagai serangkaian refactoring, Anda dapat menghormati Aturan Perkemahan sambil juga membuat aplikasi berjalan lebih cepat. Ini sebenarnya, setidaknya menurut saya, secara etis menjadi kewajiban Anda untuk melakukannya.

Misalnya, penulis pertanyaan ini telah menemukan kode yang gila. Jika orang ini membaca kode saya, mereka akan menemukan bahwa bagian yang gila adalah 3-4 baris. Ada dalam suatu metode dengan sendirinya, dan nama metode dan deskripsi menunjukkan APA yang dilakukan metode tersebut. Metode ini akan berisi 2-6 baris komentar inline yang menggambarkan BAGAIMANA kode gila mendapatkan jawaban yang tepat, meskipun penampilannya dipertanyakan.

Terkotak dengan cara ini, Anda bebas untuk menukar implementasi metode ini sesuka Anda. Memang, mungkin itulah cara saya menulis versi gila untuk memulai. Anda dipersilakan untuk mencoba, atau setidaknya bertanya tentang alternatif. Sebagian besar waktu Anda akan menemukan bahwa implementasi naif terasa lebih buruk (biasanya saya hanya repot untuk peningkatan 2-10x), tetapi kompiler dan perpustakaan selalu berubah, dan siapa tahu apa yang Anda temukan hari ini yang tidak tersedia saat fungsi itu ditulis?


Kunci utama efisiensi dalam banyak kasus adalah membuat kode melakukan pekerjaan sebanyak mungkin dengan cara yang dapat dilakukan secara efisien. Salah satu hal yang mengganggu saya dengan .NET adalah bahwa tidak ada mekanisme yang efisien untuk misalnya menyalin bagian dari satu koleksi ke yang lain. Sebagian besar koleksi menyimpan kelompok besar item berurutan (jika tidak semuanya) dalam array, jadi misal menyalin 5.000 item terakhir dari daftar 50.000 item harus diuraikan menjadi beberapa operasi copy-massal (jika tidak hanya satu) ditambah beberapa lainnya langkah-langkah yang dilakukan paling banyak beberapa kali masing-masing.
supercat

Sayangnya, bahkan dalam kasus-kasus di mana harus memungkinkan untuk operasi tersebut dilakukan secara efisien, seringkali akan perlu untuk memiliki loop "besar" dijalankan untuk 5.000 iterasi (dan dalam beberapa kasus 45.000!). Jika suatu operasi dapat dikurangi menjadi hal-hal seperti salinan array massal, maka mereka dapat dioptimalkan ke derajat ekstrim menghasilkan imbalan besar. Jika setiap iterasi perlu dilakukan selusin langkah, sulit untuk mengoptimalkan salah satu dari mereka dengan sangat baik.
supercat

2

Mungkin bukan ide yang baik untuk menyentuhnya - jika kode ditulis seperti itu untuk alasan kinerja, itu berarti bahwa mengubahnya dapat mengembalikan masalah kinerja yang telah dipecahkan sebelumnya.

Jika Anda tidak memutuskan untuk mengubah hal-hal menjadi lebih mudah dibaca dan dapat diperpanjang: Sebelum Anda membuat perubahan, patokan kode lama di bawah berat beban. Lebih baik lagi jika Anda dapat menemukan dokumen lama atau tiket bermasalah yang menjelaskan masalah kinerja yang seharusnya diperbaiki oleh kode yang tampak aneh ini. Kemudian setelah Anda melakukan perubahan, jalankan pengujian kinerja lagi. Jika tidak jauh berbeda, atau masih dalam parameter yang dapat diterima, maka mungkin OK.

Kadang-kadang dapat terjadi bahwa ketika bagian lain dari suatu sistem berubah, kode yang dioptimalkan kinerja ini tidak lagi membutuhkan optimasi yang berat, tetapi tidak ada cara untuk mengetahui hal itu tanpa pengujian yang ketat.


1
Salah satu orang yang bekerja sama dengan saya sekarang suka mengoptimalkan hal-hal di area yang dikunjungi pengguna sebulan sekali, jika itu sering. Butuh waktu dan tidak jarang menyebabkan masalah lain karena dia suka kode-dan-komit, dan biarkan QA atau fungsi hilir lainnya benar-benar menguji. : / Agar adil, dia umumnya cepat, cepat, dan akurat, tetapi "optimisasi" sen dolar ini hanya membuat segalanya lebih sulit untuk anggota tim lainnya dan kematian permanen mereka akan menjadi Hal yang Baik.
DaveE

@DaveE: Apakah optimisasi ini diterapkan karena atau masalah kinerja nyata? Atau apakah pengembang ini melakukannya hanya karena dia bisa? Saya kira jika Anda tahu optimasi tidak akan berdampak, Anda dapat dengan aman menggantinya dengan kode yang lebih mudah dibaca, tetapi saya hanya akan mempercayai seseorang yang ahli dalam sistem untuk melakukan itu.
FrustratedWithFormsDesigner

Mereka selesai karena dia bisa. Dia sebenarnya biasanya menghemat beberapa siklus, tetapi ketika interaksi pengguna dengan elemen program membutuhkan beberapa detik (15 hingga 300-ish), mencukur sepersepuluh detik runtime untuk mengejar "efisiensi" itu konyol. Terutama ketika orang-orang yang mengikutinya harus mengambil waktu nyata untuk memahami apa yang dia lakukan. Ini adalah aplikasi PowerBuilder yang awalnya dibangun 16 tahun yang lalu, jadi mengingat asal usul hal-hal tersebut, pola pikir mungkin dapat dimengerti, tetapi ia menolak untuk memperbarui pola pikirnya ke realitas saat ini.
DaveE

@ Dave: Saya pikir saya lebih setuju dengan pria yang bekerja dengan Anda daripada Anda. Jika saya tidak diizinkan untuk memperbaiki hal-hal yang lambat tanpa alasan yang jelas saya akan menjadi gila. Jika saya melihat garis C ++ yang berulang kali menggunakan operator + untuk merakit string, atau kode yang terbuka dan membaca / dev / urandom setiap kali melalui loop hanya karena seseorang lupa mengatur bendera, maka saya memperbaikinya. Dengan menjadi fanatik tentang hal ini, saya telah berhasil menjaga kecepatan, ketika orang lain akan membiarkannya meluncur satu mikrodetik pada suatu waktu.
Zan Lynx

1
Kita harus setuju untuk tidak setuju. Menghabiskan satu jam mengubah sesuatu untuk menghemat detik pecahan saat runtime untuk fungsi yang dieksekusi sangat sesekali dan meninggalkan kode dalam bentuk menggaruk kepala untuk pengembang lain adalah ... tidak benar. Jika ini adalah fungsi yang dieksekusi berulang kali di bagian aplikasi yang stres tinggi, bagus & keren. Tapi bukan itu yang saya jelaskan. Ini benar-benar gratis untuk menyelesaikan kode tanpa alasan lain selain untuk mengatakan "Saya membuat hal ini yang dilakukan UserX seminggu sekali fraksional lebih cepat". Sementara itu, kami harus membayar pekerjaan yang perlu dilakukan.
DaveE

2

Masalahnya di sini adalah membedakan "dioptimalkan" dari dibaca dan diperluas, apa yang kita sebagai pengguna lihat sebagai kode yang dioptimalkan dan apa yang dilihat oleh kompiler sebagai dioptimalkan adalah dua hal yang berbeda. Kode yang Anda cari untuk berubah mungkin tidak menjadi hambatan sama sekali, dan oleh karena itu meskipun kodenya "lean" itu bahkan tidak perlu "dioptimalkan". Atau jika kode sudah cukup lama, mungkin ada optimasi yang dilakukan oleh kompiler ke built-in yang membuat menggunakan struktur built-in sederhana yang lebih baru sama atau lebih efisien daripada kode lama.

Dan "lean," kode tidak terbaca tidak selalu dioptimalkan.

Saya dulu dari pola pikir bahwa kode pintar / ramping adalah kode yang baik, tetapi kadang-kadang mengambil keuntungan dari aturan bahasa yang tidak jelas lebih menyakitkan daripada membantu dalam pembuatan kode, saya sudah sering digigit daripada tidak dalam pekerjaan tertanam ketika mencoba untuk menjadi pintar karena kompiler membuat kode pintar Anda menjadi sesuatu yang sama sekali tidak dapat digunakan oleh perangkat keras yang disematkan.


2

Saya tidak akan pernah mengganti kode yang Dioptimalkan dengan kode yang Dapat Dibaca karena saya tidak dapat berkompromi dengan kinerja dan saya akan memilih untuk menggunakan komentar yang tepat di setiap bagian sehingga setiap orang dapat memahami logika yang diterapkan di bagian yang Dioptimalkan yang akan menyelesaikan kedua masalah tersebut.

Oleh karena itu, Kode akan Dioptimalkan + Mengomentari dengan Tepat akan membuatnya terbaca juga.

CATATAN: Anda dapat membuat Kode yang Dioptimalkan dapat dibaca dengan bantuan komentar yang tepat tetapi Anda tidak dapat membuat Kode yang Dapat Dibaca menjadi Kode yang Dioptimalkan.


Saya akan bosan dengan pendekatan ini karena yang diperlukan hanyalah satu orang mengedit kode untuk melupakan agar komentar tetap sinkron. Tiba-tiba setiap ulasan berikutnya akan berjalan berpikir itu melakukan X sambil benar-benar melakukan Y.
John D

2

Berikut adalah contoh untuk melihat perbedaan antara kode sederhana dan kode yang dioptimalkan: https://stackoverflow.com/a/11227902/1396264

menjelang akhir jawaban yang baru saja Diganti:

if (data[c] >= 128)
    sum += data[c];

dengan:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Agar adil saya tidak tahu apa pernyataan jika telah diganti dengan tetapi sebagai penjawab mengatakan beberapa operasi bitwise memberikan hasil yang sama (saya hanya akan mengambil kata-katanya untuk itu) .

Ini dijalankan dalam waktu kurang dari seperempat dari waktu aslinya (11.54dtk vs 2.5dtk)


1

Pertanyaan utama di sini adalah: apakah optimasi diperlukan?

Jika ya, maka Anda tidak dapat menggantinya dengan kode yang lebih lambat dan lebih mudah dibaca. Anda perlu menambahkan komentar dll untuk membuatnya lebih mudah dibaca.

Jika kode tidak harus dioptimalkan maka seharusnya tidak (sampai pada titik yang mempengaruhi keterbacaan) dan Anda dapat faktor ulang untuk membuatnya lebih mudah dibaca.

NAMUN - pastikan Anda tahu persis apa yang kode lakukan dan bagaimana mengujinya secara menyeluruh sebelum Anda mulai mengubah sesuatu. Ini termasuk penggunaan puncak dll. Jika tidak harus membuat satu set kasus uji dan menjalankannya sebelum dan sesudah maka Anda tidak punya waktu untuk melakukan refactoring.


1

Ini adalah cara saya melakukan sesuatu: Pertama saya membuatnya bekerja dalam kode yang mudah dibaca, kemudian saya mengoptimalkannya. Saya menyimpan sumber asli dan mendokumentasikan langkah-langkah optimasi saya.

Kemudian ketika saya perlu menambahkan fitur saya kembali ke kode saya yang dapat dibaca, tambahkan fitur dan ikuti langkah-langkah optimasi yang saya dokumentasikan. Karena Anda mendokumentasikannya, sangat cepat dan mudah untuk mengoptimalkan kode Anda dengan fitur baru.


0

Keterbacaan IMHO lebih penting daripada kode yang dioptimalkan karena dalam kebanyakan kasus optimasi mikro tidak sepadan.

Artikel tentang optimasi mikro yang tidak masuk akal :

Seperti kebanyakan dari kita, saya lelah membaca posting blog tentang optimisasi mikro yang tidak masuk akal seperti mengganti cetak dengan gema, ++ $ i dengan $ i ++, atau dua kali tanda kutip dengan satu tanda kutip. Mengapa? Karena 99,999999% waktu, itu tidak relevan.

"print" menggunakan satu opcode lebih dari "echo" karena sebenarnya mengembalikan sesuatu. Kita dapat menyimpulkan bahwa gema lebih cepat daripada cetak. Tapi satu opcode tidak ada biaya, benar-benar tidak ada.

Saya telah mencoba instalasi WordPress yang baru. Skrip berhenti sebelum diakhiri dengan "Bus Error" di laptop saya, tetapi jumlah opcode sudah lebih dari 2,3 juta. Cukup kata.


0

Optimalisasi relatif. Sebagai contoh:

Pertimbangkan kelas dengan sekelompok anggota BOOL:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Anda mungkin tergoda untuk mengubah bidang BOOL menjadi bitfield:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Karena BOOL diketikkan sebagai INT (yang pada platform Windows adalah integer 32-bit yang ditandatangani), ini membutuhkan enam belas byte dan mengemasnya menjadi satu. Itu penghematan 93%! Siapa yang bisa mengeluh tentang itu?

Asumsi ini:

Karena BOOL diketikkan sebagai INT (yang pada platform Windows adalah integer 32-bit yang ditandatangani), ini membutuhkan enam belas byte dan mengemasnya menjadi satu. Itu penghematan 93%! Siapa yang bisa mengeluh tentang itu?

mengarah ke:

Mengonversi BOOL ke bidang bit-tunggal menghemat tiga byte data tetapi dikenakan biaya delapan byte kode saat anggota tersebut diberi nilai yang tidak konstan. Demikian pula, mengekstraksi nilainya menjadi lebih mahal.

Apa yang dulu

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

menjadi

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

Versi bitfield lebih besar sebesar sembilan byte.

Mari duduk dan melakukan aritmatika. Misalkan masing-masing bidang bitfield ini diakses enam kali dalam kode Anda, tiga kali untuk menulis dan tiga kali untuk membaca. Biaya dalam pertumbuhan kode adalah sekitar 100 byte. Itu tidak akan tepat 102 byte karena pengoptimal mungkin dapat mengambil keuntungan dari nilai-nilai yang sudah ada di register untuk beberapa operasi, dan instruksi tambahan mungkin memiliki biaya tersembunyi dalam hal mengurangi fleksibilitas register. Perbedaan yang sebenarnya mungkin lebih, mungkin lebih sedikit, tetapi untuk perhitungan back-of-the-envelope sebut saja 100. Sementara itu, penghematan memori adalah 15 byte per kelas. Karena itu, titik impas adalah tujuh. Jika program Anda membuat kurang dari tujuh contoh kelas ini, maka biaya kode melebihi penghematan data: Optimasi memori Anda adalah optimasi memori.

Referensi

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.