Variabel sementara vs persyaratan panjang garis

10

Saya telah membaca Refactoring Martin Fowler . Ini umumnya sangat baik tetapi salah satu rekomendasi Fowler tampaknya menyebabkan sedikit masalah.

Fowler menyarankan Anda mengganti variabel sementara dengan kueri, jadi alih-alih ini:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

Anda menarik ke dalam metode pembantu:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

Secara umum saya setuju kecuali bahwa salah satu alasan saya menggunakan variabel sementara adalah ketika sebuah garis terlalu panjang. Sebagai contoh:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Jika saya mencoba menguraikan itu, garis akan lebih panjang dari 80 karakter.

Bergantian saya berakhir dengan rantai kode, yang sendiri tidak begitu mudah dibaca:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Apa sajakah strategi untuk mendamaikan keduanya?

Kevin Burke
sumber
10
80 karakter sekitar 1/3 dari 1 monitor saya. apakah Anda yakin bahwa tetap menggunakan 80 baris char masih bermanfaat bagi Anda?
jk.
10
ya, lihat misalnya programmers.stackexchange.com/questions/604/…
Kevin Burke
Contoh Anda $hostdan $uricontoh dibuat-buat - kecuali tuan rumah sedang dibaca dari pengaturan atau input lain, saya lebih suka mereka berada di baris yang sama, bahkan jika itu membungkus atau pergi dari tepi.
Izkata
5
Tidak perlu begitu dogmatis. Buku ini adalah daftar teknik yang dapat digunakan ketika mereka membantu, bukan seperangkat aturan yang harus Anda terapkan di mana-mana, setiap saat. Intinya adalah untuk membuat kode Anda lebih mudah dikelola & lebih mudah dibaca. Jika refactor tidak melakukan ini, Anda tidak menggunakannya.
Sean McSomething
Sementara saya pikir batas 80 karakter sedikit berlebihan, batas yang sama (100?) Masuk akal. Sebagai contoh, saya sering suka pemrograman pada monitor berorientasi potret, jadi garis panjang ekstra dapat mengganggu (setidaknya jika mereka umum).
Thomas Eding

Jawaban:

16

Cara
1. Pembatasan panjang garis ada sehingga Anda dapat melihat + memahami lebih banyak kode. Mereka masih valid.
2. Tekankan penilaian atas konvensi buta .
3. Hindari variabel temp kecuali mengoptimalkan kinerja .
4. Hindari menggunakan indentasi yang dalam untuk penyelarasan dalam pernyataan multi-baris.
5. Pisahkan pernyataan panjang menjadi beberapa baris di sepanjang batas gagasan :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

Reasoning
Sumber utama masalah (debugging) saya dengan variabel temp adalah bahwa mereka cenderung bisa berubah. Yaitu, saya akan menganggap mereka adalah satu nilai ketika saya menulis kode, tetapi jika fungsinya kompleks, beberapa bagian kode lainnya mengubah statusnya di tengah jalan. (Atau sebaliknya, di mana keadaan variabel tetap sama tetapi hasil kueri telah berubah).

Pertimbangkan bertahan dengan kueri kecuali Anda mengoptimalkan kinerja . Ini membuat logika apa pun yang Anda gunakan untuk menghitung nilai itu di satu tempat.

Contoh yang Anda berikan (Java dan ... PHP?) Keduanya memungkinkan untuk pernyataan multi-baris. Jika garisnya panjang, hancurkan. Sumber jquery membawa ini ke ekstrem. (Pernyataan pertama berjalan ke baris 69!) Bukan berarti saya setuju, tetapi ada cara lain untuk membuat kode Anda dapat dibaca daripada menggunakan temp vars.

Beberapa Contoh
1. Panduan Gaya PEP 8 untuk python (bukan contoh tercantik)
2. Paul M Jones pada Panduan Gaya Pear (tengah argumen jalan)
3. Panjang jalur Oracle + konvensi pembungkus (strategi yang berguna untuk menjaga hingga 80 karakter)
4. Praktek-praktek MDN Java (menekankan penilaian programmer atas konvensi)

Zachary Yates
sumber
1
Bagian lain dari masalah adalah bahwa variabel sementara sering melebihi nilainya. Bukan masalah di blok lingkup kecil, tapi di blok yang lebih besar, ya, masalah besar.
Ross Patterson
8
Jika Anda khawatir tentang sementara yang sedang dimodifikasi, beri konstelasi padanya.
Thomas Eding
3

Saya pikir, argumen terbaik untuk menggunakan metode pembantu bukan variabel sementara adalah keterbacaan manusia. Jika Anda, sebagai manusia, memiliki lebih banyak masalah dalam membaca rantai-metode-penolong daripada varialbe sementara, saya tidak melihat alasan mengapa Anda harus mengekstraknya.

(tolong koreksi saya jika saya salah)

jam
sumber
3

Saya tidak berpikir Anda harus benar-benar mengikuti 80 pedoman karakter atau bahwa variabel temp lokal harus diekstraksi. Tetapi antrean panjang dan temps lokal harus diselidiki untuk cara yang lebih baik untuk mengekspresikan ide yang sama. Pada dasarnya, mereka mengindikasikan bahwa fungsi atau garis yang diberikan terlalu rumit dan kita perlu memecahnya. Tetapi kita perlu berhati-hati, karena memecah tugas menjadi beberapa bagian dengan cara yang buruk hanya membuat situasi lebih rumit. Jadi saya akan memecah hal-hal menjadi komponen resuable dan sederhana.

Biarkan saya melihat contoh yang Anda posting.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Pengamatan saya adalah bahwa semua panggilan twilio api akan dimulai dengan "https://api.twilio.com/2010-04-1/", dan dengan demikian ada fungsi yang dapat digunakan kembali yang sangat jelas:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

Bahkan, saya pikir satu-satunya alasan untuk menghasilkan URL adalah untuk membuat permintaan, jadi saya akan lakukan:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

Sebenarnya, banyak url yang sebenarnya dimulai dengan "Akun / $ akunSid", jadi saya mungkin akan mengekstraknya juga:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

Dan jika kita menjadikan api twilio sebagai objek yang menyimpan nomor akun, kita dapat melakukan sesuatu seperti:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

Menggunakan objek $ twilio memiliki manfaat membuat pengujian unit lebih mudah. Saya dapat memberikan objek objek $ twilio berbeda yang tidak benar-benar memanggil kembali ke twilio, yang akan lebih cepat dan tidak akan melakukan hal-hal aneh untuk twilio.

Mari kita lihat yang lain

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Di sini saya akan memikirkan:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

atau

$params = MustacheOptions::build($bagcheck->getFlatParams());

atau

$params = MustacheOptions::build(flatParams($backCheck));

Tergantung pada yang merupakan idiom yang lebih dapat digunakan kembali.

Winston Ewert
sumber
1

Sebenarnya, saya tidak setuju dengan Pak Fowler yang terkemuka dalam hal ini dalam kasus umum.

Keuntungan mengekstraksi metode dari kode yang sebelumnya diuraikan adalah penggunaan kembali kode; kode dalam metode sekarang terputus dari penggunaan awalnya, dan sekarang dapat digunakan di tempat lain dalam kode tanpa disalin dan ditempelkan (yang mengharuskan perubahan di banyak tempat jika logika umum kode yang disalin harus diubah) .

Namun, dari nilai konseptual yang sama, sering lebih besar adalah "penggunaan kembali nilai". Mr. Fowler menyebut metode yang diekstrak ini untuk menggantikan variabel temp "queries". Nah, apa yang lebih efisien; meminta database setiap kali Anda membutuhkan nilai tertentu, atau meminta sekali dan menyimpan hasilnya (dengan asumsi bahwa nilai tersebut cukup statis sehingga Anda tidak akan mengharapkannya berubah)?

Untuk hampir semua perhitungan di luar yang relatif sepele dalam contoh Anda, dalam kebanyakan bahasa lebih murah untuk menyimpan hasil dari satu perhitungan daripada terus menghitungnya. Oleh karena itu, rekomendasi umum untuk menghitung ulang berdasarkan permintaan tidak jujur; biaya lebih banyak waktu pengembang dan lebih banyak waktu CPU, dan menghemat jumlah memori sepele, yang dalam kebanyakan sistem modern adalah sumber daya termurah dari ketiganya.

Sekarang, metode helper, bersama dengan kode lain, dapat dibuat "malas". Itu akan, ketika dijalankan pertama kali, menginisialisasi variabel. Semua panggilan lebih lanjut akan mengembalikan variabel itu sampai metode tersebut diberitahu secara eksplisit untuk melakukan perhitungan ulang. Ini bisa menjadi parameter untuk metode ini, atau tanda yang ditetapkan oleh kode lain yang mengubah nilai apa pun yang dihitung oleh metode ini:

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

Sekarang, untuk perhitungan sepele ini, itu bahkan lebih banyak pekerjaan yang dilakukan daripada disimpan, dan jadi saya biasanya merekomendasikan tetap dengan variabel temp; namun, untuk perhitungan yang lebih rumit yang biasanya ingin Anda hindari berjalan berulang kali, dan yang Anda perlukan di banyak tempat dalam kode, ini adalah cara Anda akan melakukannya.

KeithS
sumber
1

Metode helper ada di tempat, tetapi Anda harus berhati-hati memastikan konsistensi data, dan peningkatan yang tidak perlu dalam lingkup variabel.

Misalnya, contoh Anda sendiri mengutip:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Jelas keduanya _quantitydan _itemPricemerupakan variabel global (atau setidaknya tingkat kelas) dan oleh karena itu ada potensi bagi mereka untuk dimodifikasi di luargetPrice()

Karenanya ada potensi panggilan pertama basePrice()untuk mengembalikan nilai yang berbeda dari panggilan kedua!

Oleh karena itu, saya akan menyarankan bahwa fungsi pembantu bisa berguna untuk mengisolasi matematika yang kompleks, tetapi sebagai pengganti variabel lokal, Anda harus berhati-hati.


Anda juga harus menghindari reductio ad absurdum - haruskah perhitungan discountFactordihadiahkan ke suatu metode? Jadi contoh Anda menjadi:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Partisi di luar level tertentu sebenarnya membuat kode kurang dibaca.

Andrew
sumber
+1 untuk membuat kode lebih mudah dibaca. Pemartisian berlebih dapat menyembunyikan masalah bisnis yang coba dipecahkan oleh kode sumber. Mungkin ada kasus khusus di mana kupon diterapkan di getPrice (), tetapi jika itu tersembunyi jauh di dalam rangkaian fungsi panggilan maka aturan bisnis juga disembunyikan.
Reactgular
0

Jika Anda kebetulan bekerja dalam bahasa dengan parameter bernama (ObjectiveC, Python, Ruby, dll), vars temp kurang berguna.

Namun, dalam contoh basePrice Anda, kueri mungkin perlu beberapa saat untuk dieksekusi dan Anda mungkin ingin menyimpan hasilnya dalam variabel temp untuk digunakan di masa mendatang.

Seperti Anda, saya menggunakan variabel temp untuk pertimbangan kejelasan dan panjang garis.

Saya juga melihat programmer melakukan hal berikut dalam PHP. Ini menarik dan bagus untuk debugging, tapi agak aneh.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs
Dimitry
sumber
0

Alasan di balik rekomendasi ini adalah bahwa Anda ingin dapat menggunakan perhitungan yang sama di tempat lain dalam aplikasi Anda. Lihat Ganti Temp dengan Query di katalog pola refactoring:

Metode baru kemudian dapat digunakan dalam metode lain

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

Jadi, dalam contoh host dan URI Anda, saya hanya akan menerapkan rekomendasi ini jika saya berencana untuk menggunakan kembali definisi URI atau host yang sama.

Jika ini masalahnya, karena namespace, saya tidak akan mendefinisikan metode global uri () atau host (), tetapi nama dengan informasi lebih lanjut, seperti twilio_host (), atau archive_request_uri ().

Lalu, untuk masalah panjang garis, saya melihat beberapa opsi:

  • Buat variabel lokal, seperti uri = archive_request_uri().

Dasar Pemikiran: Dalam metode saat ini, Anda ingin URI menjadi yang disebutkan. Definisi URI masih difaktorkan.

  • Tentukan metode lokal, seperti uri() { return archive_request_uri() }

Jika Anda sering menggunakan rekomendasi Fowler, Anda akan tahu bahwa metode uri () adalah pola yang sama.

Jika karena pilihan bahasa Anda perlu mengakses metode lokal dengan 'mandiri', saya akan merekomendasikan solusi pertama, untuk meningkatkan ekspresif (dengan Python, saya akan mendefinisikan fungsi uri di dalam metode saat ini).

Marc-Emmanuel Coupvent des Gra
sumber