Apakah penugasan dalam kondisi bagian dari persyaratan merupakan praktik yang buruk?


35

Mari kita asumsikan saya ingin menulis fungsi yang menggabungkan dua string dalam C. Cara saya akan menulisnya adalah:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Namun, K&R dalam buku mereka menerapkannya secara berbeda, terutama termasuk sebanyak mungkin dalam bagian loop sementara:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Cara mana yang lebih disukai? Apakah didorong atau tidak disarankan untuk menulis kode seperti yang dilakukan K&R? Saya percaya versi saya akan lebih mudah dibaca oleh orang lain.


38
Jangan lupa, K&R pertama kali diterbitkan pada tahun 1978. Ada beberapa perubahan kecil dalam cara kami membuat kode sejak saat itu.
corsiKa

28
Keterbacaan sangat berbeda pada zaman teleprinters dan editor yang berorientasi garis. Menyerahkan semua hal itu ke dalam satu baris yang dulu lebih mudah dibaca.
user2357112 mendukung Monica

15
Saya terkejut bahwa mereka memiliki indeks dan perbandingan untuk '\ 0' dan bukannya sesuatu seperti while (*s++ = *t++); (C saya sangat berkarat, apakah saya perlu parens di sana untuk diutamakan operator?) Apakah K&R merilis versi baru dari buku mereka? Buku asli mereka memiliki kode yang sangat ringkas dan idiomatik.
user949300

4
Keterbacaan adalah hal yang sangat pribadi - bahkan di zaman teletype. Orang yang berbeda lebih suka gaya yang berbeda. Banyak instruksi yang harus dilakukan berkaitan dengan pembuatan kode. Pada masa itu, beberapa set instruksi (mis. Data Umum) dapat menjejalkan beberapa operasi menjadi satu instruksi. Juga, pada awal 80-an, ada mitos bahwa menggunakan tanda kurung menghasilkan lebih banyak instruksi. Saya harus membuat assembler untuk membuktikan kepada pengkaji kode bahwa itu adalah mitos.
Piala

10
Perhatikan bahwa dua blok kode tidak setara. Blok kode pertama tidak akan menyalin terminasi '\0'dari t( whilekeluar pertama). Ini akan meninggalkan sstring yang dihasilkan tanpa berakhir '\0'(kecuali lokasi memori sudah memusatkan perhatian). Blok kode kedua akan membuat salinan terminating '\0'sebelum keluar dari whileloop.
Makyen

Jawaban:


80

Selalu lebih suka kejelasan daripada kepintaran. Di masa lalu, programmer terbaik adalah kode yang tidak dapat dimengerti oleh siapa pun. "Aku tidak bisa memahami kodenya, dia pasti jenius , " kata mereka. Saat ini programmer terbaik adalah kode yang siapa pun dapat mengerti. Waktu komputer lebih murah sekarang daripada waktu programmer.

Orang bodoh mana pun dapat menulis kode yang dapat dimengerti komputer. Pemrogram yang baik menulis kode yang dapat dimengerti manusia. (M. Fowler)

Jadi, tidak diragukan lagi, saya akan memilih opsi A. Dan itu adalah jawaban pasti saya.


8
Ideologi sangat bernas, tetapi kenyataannya tetap bahwa tidak ada yang salah dengan penugasan dalam kondisi. Jauh lebih baik untuk keluar awal dari loop atau kode duplikat sebelum dan di dalam loop.
Rute Mil

26
@MilesRout Ada. Ada yang salah dengan kode apa pun yang memiliki efek samping di mana Anda tidak menduganya, yaitu meneruskan argumen fungsi atau mengevaluasi kondisional. Bahkan tidak menyebutkan yang if (a=b)bisa dengan mudah dikira if (a==b).
Arthur Havlicek

12
@ Lukas: "IDE saya dapat membersihkan X, oleh karena itu bukan masalah" agak tidak meyakinkan. Jika itu bukan masalah, mengapa IDE membuatnya sangat mudah untuk "diperbaiki?"
Kevin

6
@ArthurHavlicek Saya setuju dengan poin umum Anda, tetapi kode dengan efek samping di conditional benar-benar tidak biasa: while ((c = fgetc(file)) != EOF)sebagai yang pertama muncul di pikiran saya.
Daniel Jour

3
+1 "Mengingat bahwa debugging dua kali lebih sulit daripada menulis program di tempat pertama, jika Anda sepintar ketika Anda menulisnya, bagaimana Anda bisa men-debug itu?" BWKernighan
Christophe

32

Aturan emas, sama seperti dalam jawaban Tulains Córdova, adalah memastikan untuk menulis kode yang dapat dipahami. Tapi saya tidak setuju dengan kesimpulannya. Aturan emas itu berarti menulis kode yang bisa dimengerti oleh programmer tipikal yang mempertahankan kode Anda. Dan Anda adalah hakim terbaik siapa programmer khas yang akhirnya akan mempertahankan kode Anda.

Untuk programmer yang tidak memulai dengan C, versi pertama mungkin lebih mudah dipahami, karena alasan yang Anda sudah tahu.

Bagi mereka yang tumbuh dengan gaya C, versi kedua mungkin lebih mudah untuk dipahami: bagi mereka, sama-sama dapat dimengerti apa yang dilakukan oleh kode, bagi mereka, itu meninggalkan lebih sedikit pertanyaan mengapa ditulis seperti apa adanya, dan bagi mereka , lebih sedikit ruang vertikal berarti lebih banyak konteks dapat ditampilkan di layar.

Anda harus mengandalkan akal sehat Anda sendiri. Kepada audiens mana Anda ingin membuat kode Anda paling mudah dipahami? Apakah kode ini ditulis untuk perusahaan? Maka target audiens mungkin adalah programmer lain di perusahaan itu. Apakah ini proyek hobi pribadi yang tidak akan dikerjakan oleh siapa pun kecuali Anda sendiri? Maka Anda adalah target audiens Anda sendiri. Apakah kode ini ingin Anda bagikan dengan orang lain? Lalu yang lainnya adalah audiens target Anda. Pilih versi yang cocok dengan audiens itu. Sayangnya, tidak ada satu pun cara yang disukai untuk mendorong.


14

EDIT: Baris s[i] = '\0';ditambahkan ke versi pertama, dengan demikian memperbaikinya seperti yang dijelaskan dalam varian 1 di bawah ini, jadi ini tidak berlaku untuk versi kode pertanyaan saat ini lagi.

Versi kedua memiliki keunggulan khas menjadi benar , sementara yang pertama tidak - itu tidak null-mengakhiri string target dengan benar.

"Penugasan dalam kondisi" memungkinkan mengekspresikan konsep "salin setiap karakter sebelum memeriksa karakter nol" dengan sangat ringkas dan dengan cara yang membuat pengoptimalan untuk kompiler menjadi lebih mudah, meskipun banyak insinyur perangkat lunak saat ini menemukan gaya kode ini kurang dapat dibaca . Jika Anda bersikeras menggunakan versi pertama, Anda harus melakukannya juga

  1. tambahkan null-termination setelah akhir dari loop kedua (menambahkan lebih banyak kode, tetapi Anda bisa berpendapat bahwa readabiliy membuatnya berharga) atau
  2. ubah loop body menjadi "assign pertama, lalu centang atau simpan char yang ditugaskan, lalu increment index". Memeriksa kondisi di tengah-tengah loop berarti memecah loop (mengurangi kejernihan, disukai oleh sebagian besar puritan). Menyimpan char yang ditugaskan berarti memperkenalkan variabel sementara (mengurangi kejelasan dan efisiensi). Keduanya akan memusnahkan keuntungan menurut saya.

Benar lebih baik daripada mudah dibaca dan ringkas.
user949300

5

Jawaban oleh Tulains Córdova dan hvd mencakup aspek kejelasan / keterbacaan dengan cukup baik. Izinkan saya memberikan pelingkupan sebagai alasan lain yang mendukung penugasan dalam kondisi. Variabel yang dideklarasikan dalam kondisi hanya tersedia dalam lingkup pernyataan itu. Anda tidak dapat menggunakan variabel itu setelah itu secara tidak sengaja. The untuk lingkaran telah melakukan hal ini untuk usia. Dan itu cukup penting bahwa C ++ 17 yang akan datang memperkenalkan sintaksis yang sama untuk jika dan beralih :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope

3

Tidak. Ini adalah gaya C yang sangat standar dan normal. Contoh Anda buruk, karena seharusnya hanya untuk perulangan, tetapi secara umum tidak ada yang salah dengan itu

if ((a = f()) != NULL)
    ...

misalnya (atau dengan sementara).


7
Ada yang salah dengan itu; `! = NULL` dan kerabatnya dalam kondisi C lebih baik, hanya ada untuk menenangkan pengembang yang tidak nyaman dengan konsep nilai menjadi benar atau salah (atau sebaliknya).
Jonathan Cast

1
@cast Tidak, ini lebih eksplisit untuk disertakan != NULL.
Miles Rout

1
Tidak, itu lebih eksplisit untuk dikatakan (x != NULL) != 0. Bagaimanapun, itulah yang benar - benar diperiksa oleh C , bukan?
Jonathan Cast

@ siaran Tidak, tidak. Memeriksa apakah ada sesuatu yang tidak sama dengan salah bukanlah bagaimana Anda menulis kondisional dalam bahasa apa pun.
Miles Rout

"Memeriksa apakah ada sesuatu yang tidak sama dengan salah bukanlah bagaimana Anda menulis kondisional dalam bahasa apa pun." Persis.
Jonathan Cast

2

Pada zaman K&R

  • 'C' adalah kode perakitan portabel
  • Itu digunakan oleh programmer yang berpikir dalam kode assembly
  • Kompiler tidak melakukan banyak optimasi
  • Sebagian besar komputer memiliki "set instruksi kompleks", misalnya while ((s[i++]=t[j++]) != '\0')akan memetakan ke satu instruksi pada sebagian besar CPU (saya harapkan VAC Desember)

Ada hari

  • Kebanyakan orang yang membaca kode C bukan pemrogram kode assembly
  • Kompiler C melakukan banyak optimasi, karenanya kode yang lebih mudah dibaca kemungkinan akan diterjemahkan ke dalam kode mesin yang sama.

(Catatan tentang selalu menggunakan kawat gigi - set kode pertama mengambil lebih banyak ruang karena memiliki beberapa "tidak dibutuhkan" {}, dalam pengalaman saya ini sering mencegah kode yang telah digabung dengan buruk dari kompiler dan memungkinkan kesalahan dengan penempatan ";" yang salah menjadi terdeteksi oleh alat.)

Namun di masa lalu kode versi ke-2 akan membaca. (Jika saya benar!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}

2

Bahkan bisa melakukan ini sama sekali adalah ide yang sangat buruk. Bahasa sehari-hari dikenal sebagai "The Last Last Bug," seperti:

if (alert = CODE_RED)
{
   launch_nukes();
}

Meskipun Anda tidak akan membuat kesalahan yang cukup parah, sangat mudah untuk secara tidak sengaja mengacaukan dan menyebabkan kesalahan yang sulit ditemukan dalam basis kode Anda. Sebagian besar kompiler modern akan menyisipkan peringatan untuk tugas di dalam kondisi. Mereka ada di sana karena suatu alasan, dan Anda sebaiknya memperhatikan mereka dan menghindari konstruksi ini.


Sebelum peringatan ini, kami akan menulis CODE_RED = alertsehingga memberi kesalahan kompiler.
Ian

4
@Ian Yoda Conditional yang disebut. Sulit dibaca. Sayangnya kebutuhan mereka adalah.
Mason Wheeler

Setelah periode pengantar yang sangat singkat "membiasakan diri", kondisi Yoda tidak lebih sulit dibaca daripada yang normal. Terkadang lebih mudah dibaca . Sebagai contoh, jika Anda memiliki urutan ifs / elseifs, memiliki kondisi yang diuji terhadap di sebelah kiri untuk penekanan yang lebih tinggi adalah sedikit perbaikan IMO.
user949300

2
@ user949300 Dua kata: Sindrom Stockholm: P
Mason Wheeler

2

Kedua gaya terbentuk dengan baik, benar, dan tepat. Mana yang lebih tepat tergantung pada pedoman gaya perusahaan Anda. IDE modern akan memfasilitasi penggunaan kedua gaya melalui penggunaan sintaks langsung yang secara eksplisit menyoroti area yang mungkin menjadi sumber kebingungan.

Misalnya, ekspresi berikut disorot oleh Netbeans :

if($a = someFunction())

dengan alasan "penugasan tak disengaja".

masukkan deskripsi gambar di sini

Untuk secara eksplisit memberi tahu Netbeans bahwa "ya, aku benar-benar bermaksud melakukan itu ...", ungkapan itu dapat dibungkus dalam satu set tanda kurung.

if(($a = someFunction()))

masukkan deskripsi gambar di sini

Pada akhirnya, itu semua bermuara pada pedoman gaya perusahaan dan ketersediaan alat-alat modern untuk memfasilitasi proses pengembangan.

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.