Fungsi satu baris yang dipanggil hanya sekali

120

Pertimbangkan fungsi tanpa parameter ( edit: tidak harus) yang melakukan satu baris kode, dan dipanggil hanya sekali dalam program (meskipun bukan tidak mungkin akan diperlukan lagi di masa mendatang).

Itu bisa melakukan kueri, memeriksa beberapa nilai, melakukan sesuatu yang melibatkan regex ... apa pun yang tidak jelas atau "diretas".

Alasan di balik ini adalah untuk menghindari evaluasi yang sulit dibaca:

if (getCondition()) {
    // do stuff
}

di mana getCondition()fungsi satu baris.

Pertanyaan saya sederhana: apakah ini praktik yang baik? Tampaknya baik-baik saja bagi saya tetapi saya tidak tahu tentang jangka panjang ...

vemv
sumber
9
Kecuali jika fragmen kode Anda berasal dari bahasa OOP dengan penerima implisit (mis. Ini) ini tentu akan menjadi praktik yang buruk, karena getCondition () kemungkinan besar bergantung pada keadaan global ...
Ingo
2
Dia mungkin bermaksud mengatakan bahwa getCondition () dapat memiliki argumen?
user606723
24
@Ingo - beberapa hal benar-benar memiliki keadaan Global. Waktu saat ini, nama host, nomor port, dll. Semuanya "Global" yang valid. Kesalahan desain adalah membuat Global dari sesuatu yang secara inheren bukan global.
James Anderson
1
Mengapa kamu tidak hanya sebaris getCondition? Jika itu sekecil dan jarang digunakan seperti yang Anda katakan maka memberi nama tidak menyelesaikan apa pun.
davidk01
12
davidk01: keterbacaan kode.
wjl

Jawaban:

240

Tergantung pada satu baris itu. Jika garis dapat dibaca dan ringkas dengan sendirinya, fungsi mungkin tidak diperlukan. Contoh sederhana:

void printNewLine() {
  System.out.println();
}

OTOH, jika fungsinya memberikan nama yang bagus untuk satu baris kode yang berisi mis. Ekspresi yang rumit dan sulit dibaca, itu dibenarkan dengan sempurna (bagi saya). Contoh dibuat (dipecah menjadi beberapa baris untuk dibaca di sini):

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}
Péter Török
sumber
99
+1. Kata ajaib di sini adalah "Kode dokumentasi diri".
Konamiman
8
Sebuah contoh yang bagus tentang apa yang oleh Paman Bob sebut "merangkum kondisi."
Anthony Pegram
12
@Aditya: Tidak ada yang mengatakan itu taxPayerglobal dalam skenario ini. Mungkin kelas ini adalah TaxReturn, dan taxPayermerupakan atribut.
Adam Robinson
2
Selain itu memungkinkan Anda untuk mendokumentasikan fungsi dengan cara yang dapat diambil oleh misalnya javadoc dan dapat dilihat oleh publik.
2
@dallin, Bob Clean Code menunjukkan banyak contoh kode semi-real life. Jika Anda memiliki terlalu banyak fungsi dalam satu kelas, mungkin kelas Anda terlalu besar?
Péter Török
67

Ya, ini dapat digunakan untuk memuaskan praktik terbaik. Sebagai contoh, lebih baik untuk memiliki fungsi yang jelas-bernama melakukan beberapa pekerjaan, bahkan jika itu hanya satu baris panjang, daripada memiliki baris kode dalam fungsi yang lebih besar dan memerlukan komentar satu baris yang menjelaskan apa fungsinya. Selain itu, baris kode yang berdekatan harus melakukan tugas pada tingkat abstraksi yang sama. Contoh tandingan akan menjadi sesuatu seperti

startIgnition();
petrolFlag |= 0x006A;
engageChoke();

Dalam hal ini jelas lebih baik untuk memindahkan garis tengah ke fungsi yang masuk akal.

Kilian Foth
sumber
15
0x006A itu indah: DA dikenal konstan dari bahan bakar berkarbonasi dengan aditif a, b dan c.
Coder
2
+1 Saya semua untuk kode self-documenting :) ngomong-ngomong, saya rasa saya setuju untuk menjaga level abstraksi yang konstan melalui blok, tapi saya tidak bisa menjelaskan alasannya. Maukah Anda memperluas itu?
vemv
3
Jika Anda hanya mengatakan itu petrolFlag |= 0x006A;tanpa mengambil keputusan, akan lebih baik untuk mengatakan petrolFlag |= A_B_C; tanpa fungsi tambahan. Agaknya, engageChoke()hanya boleh dipanggil jika petrolFlagmemenuhi kriteria tertentu dan itu harus dengan jelas mengatakan 'Saya butuh fungsi di sini.' Hanya sedikit nit, jawaban ini pada dasarnya tepat jika tidak :)
Tim Post
9
+1 untuk menunjukkan bahwa kode dalam suatu metode harus dalam level abstraksi yang sama. @vemv, ini adalah praktik yang baik karena membuat kode lebih mudah dipahami, dengan menghindari kebutuhan pikiran Anda untuk melompat-lompat di antara berbagai tingkat abstraksi saat membaca kode. Mengikat tingkat abstraksi beralih ke pemanggilan metode / pengembalian (yaitu struktural "lompatan") adalah cara yang bagus untuk membuat kode lebih lancar dan bersih.
Péter Török
21
Tidak, ini nilai yang sepenuhnya dibuat-buat. Tetapi fakta bahwa Anda bertanya-tanya tentang hal itu hanya menekankan intinya: jika kode itu mengatakan mixLeadedGasoline(), Anda tidak perlu melakukannya!
Kilian Foth
37

Saya pikir dalam banyak kasus fungsi seperti itu adalah gaya yang baik, tetapi Anda dapat mempertimbangkan variabel boolean lokal sebagai alternatif dalam kasus ketika Anda tidak perlu menggunakan kondisi ini di suatu tempat di tempat lain misalnya:

bool someConditionSatisfied = [complex expression];

Ini akan memberi petunjuk kepada pembaca kode dan menyimpan dari memperkenalkan fungsi baru.

cybevnm
sumber
1
Bool sangat bermanfaat jika nama fungsi fungsi akan sulit atau mungkin menyesatkan (misalnya IsEngineReadyUnlessItIsOffOrBusyOrOutOfService).
dbkk
2
Saya mengalami saran ini ide yang buruk: bool dan kondisi mengalihkan perhatian dari bisnis inti fungsi. Juga, gaya yang lebih suka variabel lokal membuat refactoring lebih sulit.
Wolf
1
@ Serigala OTOH, saya lebih suka ini ke pemanggilan fungsi untuk mengurangi "kedalaman abstraksi" kode. IMO, melompat ke fungsi adalah saklar konteks yang lebih besar daripada beberapa baris kode eksplisit dan langsung, terutama jika hanya mengembalikan boolean.
Kache
@Kache Saya pikir itu tergantung pada apakah Anda berorientasi objek kode atau tidak, dalam kasus OOP, penggunaan fungsi anggota membuat desain jauh lebih fleksibel. Itu benar-benar tergantung pada konteksnya ...
Wolf
23

Selain jawaban Peter , jika kondisi itu mungkin perlu diperbarui di beberapa titik di masa depan, dengan membuatnya merangkum dengan cara yang Anda sarankan Anda hanya akan memiliki satu titik edit.

Mengikuti contoh Peter, jika ini

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

menjadi ini

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isMutant() 
        && (taxPayer.getNumberOfThumbs() > 2 
        || (taxPayer.getAge() > 123 && taxPayer.getEmployer().isXMan()));
}

Anda membuat satu pengeditan dan diperbarui secara universal. Dari segi pemeliharaan, ini adalah nilai tambah.

Dari segi kinerja, sebagian besar kompiler yang mengoptimalkan akan menghapus pemanggilan fungsi dan tetap menjalankan blok kode kecil. Mengoptimalkan sesuatu seperti ini sebenarnya dapat mengecilkan ukuran blok (dengan menghapus instruksi yang diperlukan untuk panggilan fungsi, kembali, dll) sehingga biasanya aman untuk dilakukan bahkan dalam kondisi yang dapat mencegah inlining.

Stephen
sumber
2
Saya sudah mengetahui manfaat menjaga satu titik edit - karena itulah pertanyaannya mengatakan "... dipanggil sekali". Meskipun demikian sangat bagus untuk mengetahui tentang optimasi kompiler itu, saya selalu berpikir mereka mengikuti instruksi semacam ini secara harfiah.
vemv
Memisahkan suatu metode untuk menguji suatu kondisi sederhana cenderung menyiratkan bahwa suatu kondisi dapat diubah dengan mengubah metode tersebut. Implikasi seperti itu berguna ketika itu benar, tetapi bisa berbahaya ketika tidak. Misalnya, anggaplah kode perlu membagi benda menjadi benda ringan, benda hijau berat, dan benda non-hijau berat. Objek memiliki properti "looksGreen" cepat yang dapat diandalkan untuk objek berat, tetapi dapat mengembalikan positif palsu dengan objek ringan; mereka juga memiliki fungsi "mengukurSpectralResponse" yang lambat, tetapi akan bekerja dengan andal untuk semua objek.
supercat
Fakta bahwa tidak seemsGreenakan dapat diandalkan dengan benda-benda ringan tidak akan relevan jika kode tidak pernah peduli apakah benda-benda ringan berwarna hijau. Namun, jika definisi "ringan" berubah, sehingga beberapa objek non-hijau yang mengembalikan true untuk seemsGreentidak dilaporkan sebagai "ringan", maka perubahan tersebut ke definisi "ringan" dapat memecahkan kode yang menguji objek. menjadi "hijau". Dalam beberapa kasus, pengujian green-ness dan berat bersama dalam kode dapat membuat hubungan antara tes lebih jelas daripada jika mereka metode yang terpisah.
supercat
5

Selain keterbacaan (atau sebagai pelengkap dari itu) ini memungkinkan untuk menulis fungsi pada tingkat abstraksi yang tepat.

tylermac
sumber
1
Saya khawatir saya tidak mengerti apa yang Anda maksud ...
vemv
1
@ vemv: Saya pikir maksudnya dengan "tingkat abstraksi yang tepat" bahwa Anda tidak berakhir dengan kode yang memiliki berbagai tingkat campuran abstraksi (yaitu apa yang sudah dikatakan Kilian Foth). Anda tidak ingin kode Anda menangani detail yang tampaknya tidak signifikan (mis. Pembentukan semua ikatan pada bahan bakar) ketika kode di sekitarnya mempertimbangkan pandangan yang lebih tinggi tentang situasi yang sedang terjadi (misalnya menjalankan mesin). Mantan mengacaukan yang terakhir dan membuat kode Anda kurang mudah dibaca dan dipelihara karena Anda harus mempertimbangkan semua tingkat abstraksi sekaligus setiap saat.
Egon
4

Tergantung. Terkadang lebih baik untuk merangkum ekspresi dalam fungsi / metode, bahkan jika itu hanya satu baris. Jika sulit dibaca atau Anda membutuhkannya di banyak tempat, maka saya menganggapnya sebagai praktik yang baik. Dalam jangka panjang lebih mudah untuk mempertahankannya, karena Anda telah memperkenalkan satu titik perubahan dan keterbacaan yang lebih baik .

Namun, terkadang itu hanya sesuatu yang tidak Anda butuhkan. Ketika ungkapan itu mudah dibaca dan / atau hanya muncul di satu tempat, maka jangan membungkusnya.

Elang
sumber
3

Saya pikir jika Anda hanya memiliki beberapa dari mereka maka tidak apa-apa tetapi masalah muncul ketika ada banyak dari mereka dalam kode Anda. Dan ketika kompiler berjalan atau interpitor (tergantung pada bahasa yang Anda gunakan) Ini akan berfungsi di memori. Jadi katakanlah Anda memiliki 3 dari mereka, saya tidak berpikir komputer akan melihat tetapi jika Anda mulai memiliki 100 dari hal-hal kecil maka sistem harus mendaftarkan fungsi dalam memori yang hanya dipanggil sekali dan kemudian tidak dihancurkan.

WojonsTech
sumber
Menurut jawaban Stephen, ini mungkin tidak selalu terjadi (walaupun memercayai secara membuta pada sihir penyusun tidak bisa menjadi baik)
vemv
1
ya itu harus dibersihkan tergantung pada banyak fakta. Jika ini adalah bahasa yang diinterpretasikan, sistem akan jatuh untuk fungsi baris tunggal setiap kali kecuali Anda menginstal sesuatu untuk cache yang masih mungkin tidak berhasil. Sekarang ketika datang ke kompiler itu hanya penting pada hari yang lemah dan penempatan planate jika kompiler akan menjadi teman Anda dan membersihkan kekacauan kecil Anda atau jika itu akan berpikir Anda benar-benar membutuhkannya. Saya ingat bahwa jika Anda tahu persis berapa kali sebuah loop akan selalu berjalan beberapa kali lebih baik untuk hanya menyalin dan menempelnya berkali-kali maka untuk loop.
WojonsTech
+1 untuk penyelarasan planet-planet :) tetapi kalimat terakhir Anda terdengar benar-benar gila bagi saya, apakah Anda benar-benar melakukannya?
vemv
Ini benar-benar tergantung. Sebagian besar waktu, saya tidak melakukannya, kecuali saya mendapatkan jumlah pembayaran yang benar untuk memeriksa apakah itu mempercepat kecepatan dan hal-hal lain seperti itu. Tetapi dalam kompiler yang lama lebih baik menyalin dan menempelkannya kemudian membiarkan kompiler untuk mengetahuinya 9/10.
WojonsTech
3

Saya telah melakukan hal yang tepat ini baru-baru ini dalam aplikasi yang telah saya refactoring, untuk membuat eksplisit arti sebenarnya dari kode tanpa komentar:

protected void PaymentButton_Click(object sender, EventArgs e)
    Func<bool> HaveError = () => lblCreditCardError.Text == string.Empty && lblDisclaimer.Text == string.Empty;

    CheckInputs();

    if(HaveError())
        return;

    ...
}
Joshperry
sumber
Hanya penasaran, bahasa apa itu?
vemv
5
@ vemv: Sepertinya C # untuk saya.
Scott Mitchell
Saya juga lebih suka pengidentifikasi ekstra daripada komentar. Tetapi apakah ini benar-benar berbeda dari memperkenalkan variabel lokal untuk mempertahankannya if? Pendekatan lokal (lambda) ini membuat PaymentButton_Clickfungsi (secara keseluruhan) lebih sulit untuk dibaca. Dalam lblCreditCardErrorcontoh Anda tampaknya menjadi anggota, demikian juga HaveErrorpredikat (pribadi) yang valid untuk objek. Saya cenderung downvote ini, tapi saya bukan programmer C #, jadi saya menolak.
Wolf
@ Serigala Hei teman, ya. Saya menulis ini beberapa waktu yang lalu :) Saya pasti melakukan hal-hal yang sangat berbeda sekarang. Bahkan, melihat konten label untuk melihat apakah ada kesalahan membuat saya merasa ngeri ... Mengapa saya tidak mengembalikan bool dari CheckInputs()???
Joshperry
0

Memindahkan satu baris ke metode yang dinamai membuat kode Anda lebih mudah dibaca. Banyak orang lain telah menyebutkan hal itu ("kode dokumentasi diri"). Keuntungan lain dari memindahkannya ke suatu metode adalah membuatnya lebih mudah untuk unit test. Ketika itu diisolasi ke dalam metode itu sendiri, dan unit diuji, Anda dapat yakin bahwa jika / ketika bug ditemukan, itu tidak akan ada dalam metode ini.

Andrew
sumber
0

Sudah ada banyak jawaban bagus, tetapi ada kasus khusus yang layak disebut .

Jika pernyataan satu baris Anda memerlukan komentar , dan Anda dapat mengidentifikasi dengan jelas (yang berarti: nama) tujuannya, pertimbangkan mengekstraksi fungsi sambil meningkatkan komentar ke dalam API doc. Dengan cara ini Anda membuat panggilan fungsi lebih mudah dan cepat untuk dipahami.

Menariknya, hal yang sama dapat dilakukan jika saat ini tidak ada yang dilakukan, tetapi sebuah komentar yang mengingatkan ekspansi yang diperlukan (dalam waktu dekat 1) ), jadi ini,

def sophisticatedHello():
    # todo set up
    say("hello")
    # todo tear down

bisa juga berubah menjadi ini

def sophisticatedHello():
    setUp()
    say("hello")
    tearDown()

1) Anda harus benar-benar yakin tentang ini (lihat prinsip YAGNI )

Serigala
sumber
0

Jika bahasa mendukungnya, saya biasanya menggunakan fungsi anonim berlabel untuk mencapai ini.

someCondition = lambda p: True if [complicated expression involving p] else False
#I explicitly write the function with a ternary to make it clear this is a a predicate
if (someCondition(p)):
    #do stuff...

IMHO ini adalah kompromi yang baik karena memberi Anda manfaat keterbacaan karena tidak memiliki ekspresi rumit yang mengacaukan ifkondisi sambil menghindari mengacaukan namespace global / paket dengan label kecil yang dibuang. Ini memiliki manfaat tambahan bahwa fungsi "definisi" tepat di mana yang digunakan membuatnya mudah untuk memodifikasi dan membaca definisi.

Tidak harus hanya menjadi fungsi predikat. Saya suka membungkus boiler-plate berulang dalam fungsi-fungsi kecil seperti ini juga (Ini bekerja sangat baik untuk generasi daftar pythonic tanpa mengacaukan sintaks braket). Misalnya, contoh berikut yang disederhanakan ketika bekerja dengan PIL dengan python

#goal - I have a list of PIL Image objects and I want them all as grayscale (uint8) numpy arrays
im_2_arr = lambda im: array(im.convert('L')) 
arr_list = [im_2_arr(image) for image in image_list]
kasar
sumber
Mengapa "lambda p: Benar jika [ekspresi rumit melibatkan p] lain Salah" bukannya "lambda p: [ekspresi rumit melibatkan p]"? 8-)
Hans-Peter Störr
@hstoerr di komentarnya tepat di bawah garis itu. Saya ingin membuatnya secara eksplisit diketahui bahwa kita beberapa kondisi adalah predikat. Sementara itu benar-benar tidak perlu, saya menulis banyak skrip ilmiah yang dibaca oleh orang-orang yang tidak banyak kode, saya pribadi berpikir itu lebih tepat untuk memiliki kepekaan ekstra daripada membuat rekan-rekan saya bingung karena mereka tidak tahu itu [] == Falseatau yang lain kesetaraan pythonic serupa yang tidak 'selalu' intuitif. Ini pada dasarnya cara untuk menandai bahwa someCondition sebenarnya merupakan predikat.
crasic
Hanya untuk menghapus kesalahan saya yang jelas [] != Falsetetapi []False sebagai ketika dilemparkan ke bool
crasic
@crasic: ketika saya tidak berharap pembaca saya tahu bahwa [] mengevaluasi ke False, saya lebih suka melakukannya len([complicated expression producing a list]) == 0, daripada menggunakan True if [blah] else Falseyang masih mengharuskan pembaca untuk mengetahui bahwa [] mengevaluasi ke False.
Lie Ryan