Build Dumb, Refactor Smart: Cara Memijat Masalah dari Kode Ruby on Rails

Diterbitkan: 2022-03-11
Daniel Lewis telah menjadi pengembang Ruby on Rails profesional selama lebih dari 4 tahun, mengerjakan sekitar selusin aplikasi web dengan lalu lintas tinggi, banyak di antaranya melalui Toptal.

Terkadang, klien memberi kami permintaan fitur yang sebenarnya tidak kami sukai. Bukannya kami tidak menyukai klien kami—kami mencintai klien kami. Bukannya kami tidak menyukai fitur tersebut—sebagian besar fitur yang diminta klien selaras dengan tujuan dan pendapatan bisnis mereka.

Terkadang, kami tidak menyukai permintaan fitur karena cara termudah untuk menyelesaikannya adalah dengan menulis kode yang buruk, dan kami tidak memiliki solusi elegan di atas kepala kami. Ini akan membuat banyak dari kita pengembang Rails melakukan pencarian tanpa hasil melalui RubyToolbox, GitHub, blog pengembang, dan StackOverflow mencari permata atau plugin atau kode contoh yang akan membuat kita merasa lebih baik tentang diri kita sendiri.

Terkadang, kami tidak menyukai permintaan fitur karena cara termudah untuk menyelesaikannya adalah dengan menulis kode yang buruk.
Menciak

Refactor Ruby on Rails Code

Nah, saya di sini untuk memberitahu Anda: tidak apa-apa untuk menulis kode yang buruk. Terkadang, kode Rails yang buruk lebih mudah untuk diubah menjadi kode yang indah daripada solusi yang dipikirkan dengan buruk yang diterapkan di bawah krisis waktu.

Ini adalah proses refactoring Rails yang ingin saya ikuti ketika mengatasi masalah dari solusi plester saya yang mengerikan:

Proses refactoring Ruby on Rails ini adalah yang saya ikuti untuk mengatasi masalah Rails.

Untuk perspektif alternatif, inilah log komit Git untuk fitur yang telah difaktorkan ulang selangkah demi selangkah:

Log ini menunjukkan refactor Rails yang telah dilakukan langkah demi langkah.

Dan inilah artikel menarik lainnya tentang refactoring skala besar dari seorang rekan di jaringan Toptal.

Mari kita lihat bagaimana hal itu dilakukan.

Pemandangan

Langkah 1. Mulai di Tampilan

Katakanlah kita sedang memulai tiket untuk fitur baru. Klien memberi tahu kami: "Pengunjung harus dapat melihat daftar Proyek aktif di halaman selamat datang."

Tiket ini memerlukan perubahan yang terlihat, jadi tempat yang masuk akal untuk mulai bekerja adalah di Tampilan. Masalahnya mudah dan salah satu yang kita semua telah dilatih untuk memecahkan beberapa kali. Saya akan menyelesaikannya dengan Cara yang Salah dan mendemonstrasikan cara memfaktorkan ulang solusi saya ke area yang sesuai. Memecahkan masalah Cara yang Salah dapat membantu kita mengatasi masalah karena tidak mengetahui solusi yang tepat.

Untuk memulai, asumsikan kita memiliki model yang disebut Project dengan atribut boolean yang disebut active . Kami ingin mendapatkan daftar semua Projects di mana active sama dengan true , sehingga kami dapat menggunakan Project.where(active: true) , dan mengulangnya dengan each blok.

 app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name

Saya tahu apa yang Anda katakan: "Itu tidak akan pernah lolos dari tinjauan kode" atau "Klien saya pasti akan memecat saya karena ini." Ya, solusi ini memecahkan masalah pemisahan Model-View-Controller, mungkin mengakibatkan panggilan basis data menyimpang yang sulit dilacak, dan mungkin menjadi sulit untuk dipertahankan di masa mendatang. Tetapi pertimbangkan nilai melakukannya dengan Cara yang Salah :

  1. Anda bisa mendapatkan perubahan ini pada pementasan dalam waktu kurang dari 15 menit.
  2. Jika dibiarkan, blok ini mudah di-cache.
  3. Memperbaiki masalah Rails ini sangat mudah (dapat diberikan kepada pengembang junior).

Langkah 2. Sebagian

Setelah melakukannya dengan Cara yang Salah , saya merasa buruk tentang diri saya dan ingin mengisolasi kode buruk saya. Jika perubahan ini jelas hanya menjadi perhatian lapisan View, saya dapat memperbaiki rasa malu saya menjadi sebagian.

 app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name

Itu sedikit lebih baik. Jelas, kami masih membuat kesalahan dengan kueri Model dalam Tampilan, tetapi setidaknya ketika pengelola datang kemudian dan melihat bagian saya yang mengerikan, mereka akan memiliki cara langsung untuk mengatasi masalah kode Rails itu secara khusus. Memperbaiki sesuatu yang jelas bodoh selalu lebih mudah daripada memperbaiki abstraksi buggy yang diterapkan dengan buruk.

Memperbaiki sesuatu yang jelas bodoh selalu lebih mudah daripada memperbaiki abstraksi buggy yang diterapkan dengan buruk.
Menciak

Langkah 3. Pembantu

Pembantu di Rails adalah cara membuat DSL (Bahasa Khusus Domain) untuk bagian Tampilan Anda. Anda harus menulis ulang kode Anda menggunakan content_tag alih-alih haml atau HTML, tetapi Anda mendapatkan keuntungan karena diizinkan untuk memanipulasi struktur data tanpa membuat pengembang lain memelototi Anda selama 15 baris kode Tampilan yang tidak dicetak.

Jika saya menggunakan pembantu di sini, saya mungkin akan memperbaiki tag li .

 app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end

Pengendali

Langkah 4. Pindahkan ke Controller

Mungkin kode buruk Anda bukan hanya masalah Tampilan. Jika kode Anda masih berbau, cari kueri yang dapat Anda transisikan dari Tampilan ke Pengontrol.

 app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end

Langkah 5. Filter Pengontrol

Alasan paling jelas untuk memindahkan kode ke Controller before_filter atau after_filter adalah untuk kode yang telah Anda duplikasi dalam beberapa tindakan Controller. Anda juga dapat memindahkan kode ke filter Pengontrol jika Anda ingin memisahkan tujuan tindakan Pengontrol dari persyaratan tampilan Anda.

 app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end

Langkah 6. Pengontrol Aplikasi

Anggaplah Anda memerlukan kode Anda untuk muncul di setiap halaman, atau Anda ingin membuat fungsi pembantu Controller tersedia untuk semua pengontrol, Anda dapat memindahkan fungsi Anda ke ApplicationController. Jika perubahannya bersifat global, Anda mungkin ingin memodifikasi tata letak aplikasi Anda juga.

 app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end

Modelnya

Langkah 7. Refactoring ke Model

Seperti moto MVC: Model Gemuk, Pengendali Kurus, dan Tampilan Bodoh . Kami diharapkan untuk memfaktorkan ulang semua yang kami bisa ke dalam Model, dan memang benar bahwa fungsionalitas yang paling kompleks pada akhirnya akan menjadi asosiasi model dan metode model. Kita harus selalu menghindari melakukan pemformatan/melihat sesuatu dalam Model, tetapi mengubah data menjadi jenis data lain diperbolehkan. Dalam hal ini, hal terbaik untuk melakukan refactor ke dalam Model adalah klausa where(active: true) , yang dapat kita ubah menjadi scope. Menggunakan cakupan sangat berharga bukan hanya karena membuat panggilan terlihat lebih cantik, tetapi jika kita memutuskan untuk menambahkan atribut seperti delete atau outdated , kita dapat memodifikasi cakupan ini alih-alih memburu semua klausa where kita.

 app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)

Langkah 8. Filter Model

Kami tidak memiliki penggunaan khusus untuk filter before_save atau after_save filters Model dalam kasus ini, tetapi langkah selanjutnya yang biasanya saya ambil adalah memindahkan fungsionalitas dari Controllers dan metode Model ke dalam filter Model.

Katakanlah kami memiliki atribut lain, num_views . Jika num_views > 50 , Proyek menjadi tidak aktif. Kami dapat mengatasi masalah ini di Tampilan, tetapi membuat perubahan basis data dalam Tampilan tidak tepat. Kita bisa menyelesaikannya di Controller, tapi Controller kita harus setipis mungkin! Kita bisa menyelesaikannya dengan mudah di Model.

 app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end

Catatan: Anda harus menghindari memanggil self.save dalam filter Model, karena hal ini menyebabkan kejadian penyimpanan rekursif, dan lapisan manipulasi database aplikasi Anda harus tetap menjadi Controller.

Langkah 9. Perpustakaan

Kadang-kadang, fitur Anda cukup besar sehingga dapat menjamin perpustakaannya sendiri. Anda mungkin ingin memindahkannya ke file perpustakaan karena digunakan kembali di banyak tempat, atau cukup besar sehingga Anda ingin melakukan pengembangan secara terpisah.

Tidak apa-apa untuk menyimpan file library di direktori lib/ , tetapi saat mereka berkembang, Anda dapat mentransfernya ke RubyGem asli! Keuntungan utama memindahkan kode Anda ke perpustakaan adalah Anda dapat menguji perpustakaan secara terpisah dari model Anda.

Bagaimanapun, dalam kasus Daftar Proyek, kita dapat membenarkan memindahkan scope :active dari model Project ke file perpustakaan, dan membawanya kembali ke Ruby:

 app/models/project.rb: class Project < ActiveRecord::Base include Activeable before_filter :deactivate_if_over_num_views end lib/activeable.rb: module Activeable def self.included(k) k.scope :active, k.where(active: true) end def deactivate_if_over_num_views if num_views > 50 self.active = false end end end

Catatan: metode self.included dipanggil ketika kelas Model Rails dimuat dan melewati ruang lingkup kelas sebagai variabel k .

Kesimpulan

Dalam tutorial refactoring Ruby on Rails ini, kami telah mengambil waktu kurang dari 15 menit dan menerapkan solusi dan memasangnya di staging untuk pengujian pengguna, siap untuk diterima ke dalam set fitur atau dihapus. Pada akhir proses pemfaktoran ulang, kami memiliki sepotong kode yang menjabarkan kerangka kerja untuk menerapkan item yang dapat diaktifkan dan dapat diaktifkan di beberapa Model yang akan melewati proses peninjauan yang paling ketat sekalipun.

Dalam proses refactoring Rails Anda sendiri, jangan ragu untuk melewati beberapa langkah ke bawah pipa jika Anda yakin melakukannya (misalnya, melompat dari View ke Controller, atau Controller ke Model). Hanya perlu diingat bahwa aliran kode dari View ke Model.

Jangan takut terlihat bodoh. Apa yang membedakan bahasa modern dari aplikasi rendering template CGI lama bukanlah karena kami melakukan segalanya dengan Cara yang Benar setiap saat—tetapi kami meluangkan waktu untuk memfaktorkan ulang, menggunakan kembali, dan membagikan upaya kami.

Terkait: Pemotongan stempel waktu: Kisah ActiveRecord Ruby on Rails