การกำหนดในส่วนของเงื่อนไขเป็นเงื่อนไขที่ไม่ดีหรือไม่?


35

สมมติว่าฉันต้องการเขียนฟังก์ชันที่เชื่อมสองสายเข้าด้วยกันใน C วิธีที่ฉันจะเขียนคือ:

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';

}

อย่างไรก็ตามK&Rในหนังสือของพวกเขามีการใช้งานที่แตกต่างกันโดยเฉพาะอย่างยิ่งรวมถึงสภาพของลูปในขณะที่มากที่สุด:

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

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

}

วิธีไหนเป็นที่ต้องการ มันเป็นกำลังใจหรือหมดกำลังใจที่จะเขียนโค้ดในลักษณะที่ K&R ทำหรือไม่? ฉันเชื่อว่าคนอื่นจะอ่านเวอร์ชันของฉันได้ง่ายขึ้น


38
อย่าลืมว่า K&R ได้รับการตีพิมพ์เป็นครั้งแรกในปี 1978 มีการเปลี่ยนแปลงเล็กน้อยในวิธีที่เราใช้รหัสตั้งแต่นั้นมา
corsiKa

28
ความสามารถในการอ่านนั้นแตกต่างกันมากในช่วงเวลาที่ผ่านมาของเครื่องพิมพ์ทางไกลและบรรณาธิการที่มุ่งเน้นสายงาน mushing ทุกสิ่งบนเส้นเดียวที่ใช้จะเป็นมากขึ้นอ่าน
user2357112 รองรับ Monica

15
ฉันตกใจที่พวกเขามีดัชนีและการเปรียบเทียบกับ '\ 0' แทนที่จะเป็นสิ่งที่ชอบwhile (*s++ = *t++); (ฉัน C เป็นสนิมมากฉันต้องมี parens สำหรับการเป็นผู้ดำเนินการหรือไม่) K&R ปล่อยหนังสือเวอร์ชั่นใหม่ของพวกเขาหรือไม่ หนังสือต้นฉบับของพวกเขามีรหัสที่รัดกุมและสำนวนอย่างมาก
user949300

4
ความสามารถในการอ่านเป็นเรื่องส่วนตัวมาก - แม้ในช่วงเวลาที่โทรพิมพ์ คนต่างชอบสไตล์ที่แตกต่าง คำแนะนำมากมายที่เกี่ยวกับการสร้างรหัส ในสมัยนั้นชุดคำสั่งบางชุด (เช่นข้อมูลทั่วไป) สามารถอัดการดำเนินการหลายอย่างไว้ในคำสั่งเดียว นอกจากนี้ในช่วงต้นยุค 80 มีตำนานว่าการใช้วงเล็บสร้างคำแนะนำเพิ่มเติม ฉันต้องสร้างแอสเซมเบลอร์เพื่อพิสูจน์รหัสผู้ตรวจสอบว่ามันเป็นตำนาน
ถ้วย

10
โปรดทราบว่าบล็อกโค้ดสองรายการนั้นไม่เทียบเท่ากัน บล็อกรหัสแรกจะไม่คัดลอกการยกเลิก'\0'จากt( whileออกก่อน) สิ่งนี้จะทำให้sสตริงที่เกิดขึ้นโดยไม่มีการยุติ'\0'(เว้นแต่ว่าตำแหน่งหน่วยความจำนั้นเป็นศูนย์อยู่แล้ว) บล็อกรหัสที่สองจะทำสำเนาของการยกเลิก'\0'ก่อนที่จะออกจากwhileวง
Makyen

คำตอบ:


80

ชอบความคมชัดมากกว่าความฉลาดเสมอ ในปีกลายโปรแกรมเมอร์ที่ดีที่สุดคือคนที่ไม่มีรหัสสามารถเข้าใจได้ "ฉันไม่สามารถเข้าใจรหัสของเขาเขาต้องเป็นอัจฉริยะ"พวกเขากล่าว ปัจจุบันโปรแกรมเมอร์ที่ดีที่สุดคือรหัสที่ใคร ๆ ก็สามารถเข้าใจได้ เวลาของคอมพิวเตอร์ราคาถูกกว่าเวลาของโปรแกรมเมอร์

คนโง่ ๆ สามารถเขียนโค้ดที่คอมพิวเตอร์สามารถเข้าใจได้ โปรแกรมเมอร์ที่ดีเขียนโค้ดที่มนุษย์เข้าใจได้ (M. Fowler)

ดังนั้นไม่ต้องสงสัยเลยว่าฉันจะไปหาตัวเลือก A. และนั่นคือคำตอบที่ชัดเจนของฉัน


8
อุดมการณ์ที่มีสาระสำคัญมาก แต่ความจริงก็คือว่าไม่มีอะไรผิดปกติกับการกำหนดเงื่อนไข เป็นที่นิยมมากกว่าการออกจากลูปหรือการทำซ้ำโค้ดก่อนและข้างในลูป
เส้นทาง Miles Rout

26
@MilesRout มีอยู่ มีบางอย่างผิดปกติกับรหัสใด ๆ ที่มีผลข้างเคียงที่คุณไม่คาดหวังเช่นส่งผ่านอาร์กิวเมนต์ของฟังก์ชันหรือการประเมินเงื่อนไข ไม่ได้ mentionning ที่สามารถจะเข้าใจผิดif (a=b) if (a==b)
Arthur Havlicek

12
@ ลุค: "IDE ของฉันสามารถล้าง X ได้ดังนั้นจึงไม่ใช่ปัญหา" ค่อนข้างไม่น่าเชื่อถือ หากไม่มีปัญหาเหตุใด IDE จึงทำให้ "แก้ไขได้ง่าย"
เควิน

6
@ArthurHavlicek ฉันเห็นด้วยกับจุดทั่วไปของคุณ แต่รหัสที่มีผลข้างเคียงในเงื่อนไขไม่ได้เป็นเรื่องผิดปกติจริงๆ: while ((c = fgetc(file)) != EOF)เป็นคนแรกที่มาถึงใจของฉัน
Daniel Jour

3
+1 "เมื่อพิจารณาว่าการดีบั๊กนั้นยากกว่าการเขียนโปรแกรมตั้งแต่แรกเป็นสองเท่าถ้าคุณฉลาดเท่าที่จะเป็นเมื่อคุณเขียนมันคุณจะดีบั๊กอย่างไร" BWKernighan
Christophe

32

กฎทองเช่นเดียวกับคำตอบของ Tulains Córdovaคือเพื่อให้แน่ใจว่าได้เขียนรหัสที่เข้าใจได้ แต่ฉันไม่เห็นด้วยกับข้อสรุป กฎทองคำนั้นหมายถึงการเขียนโค้ดที่โปรแกรมเมอร์ทั่วไปที่จะลงเอยด้วยการบำรุงรักษาโค้ดของคุณสามารถเข้าใจได้ และคุณเป็นผู้ตัดสินที่ดีที่สุดว่าใครเป็นโปรแกรมเมอร์ทั่วไปที่จะลงเอยด้วยการบำรุงรักษาโค้ดของคุณ

สำหรับโปรแกรมเมอร์ที่ไม่ได้ขึ้นต้นด้วย C เวอร์ชันแรกน่าจะเข้าใจง่ายกว่าด้วยเหตุผลที่คุณรู้แล้ว

สำหรับผู้ที่เติบโตมาพร้อมกับสไตล์ C นั้นรุ่นที่สองอาจเข้าใจง่ายกว่า: สำหรับพวกเขามันเป็นที่เข้าใจกันดีว่ารหัสทำอะไรกับพวกเขาและทำให้คำถามน้อยลงว่าทำไมมันถึงถูกเขียนขึ้นและสำหรับพวกเขา พื้นที่แนวตั้งน้อยหมายความว่าสามารถแสดงบริบทได้มากขึ้นบนหน้าจอ

คุณจะต้องพึ่งพาความรู้สึกที่ดีของคุณเอง คุณต้องการให้ผู้ชมคนใดเข้าใจโค้ดของคุณมากที่สุด รหัสนี้เขียนขึ้นสำหรับ บริษัท หรือไม่? จากนั้นผู้ชมเป้าหมายน่าจะเป็นโปรแกรมเมอร์คนอื่น ๆ ใน บริษัท นั้น นี่เป็นโครงการงานอดิเรกส่วนตัวที่ไม่มีใครทำ แต่ตัวคุณเอง? จากนั้นคุณเป็นกลุ่มเป้าหมายของคุณเอง รหัสนี้คุณต้องการแชร์กับผู้อื่นหรือไม่ จากนั้นคนอื่น ๆ ก็เป็นกลุ่มเป้าหมายของคุณ เลือกรุ่นที่ตรงกับผู้ชมนั้น น่าเสียดายที่ไม่มีวิธีที่จะส่งเสริม


14

แก้ไข:บรรทัดs[i] = '\0';ถูกเพิ่มเข้าไปในเวอร์ชันแรกดังนั้นแก้ไขตามที่อธิบายไว้ในตัวแปร 1 ด้านล่างดังนั้นจึงไม่สามารถใช้กับเวอร์ชันปัจจุบันของรหัสคำถามอีกต่อไป

รุ่นที่สองมีข้อได้เปรียบที่โดดเด่นของการถูกต้องในขณะที่รุ่นแรกไม่ได้ - มันไม่สิ้นสุดสตริงเป้าหมายอย่างถูกต้อง

"การมอบหมายในสภาพ" อนุญาตให้แสดงแนวคิดของ "คัดลอกอักขระทุกตัวก่อนตรวจสอบอักขระว่าง" อย่างรัดกุมและในทางที่ทำให้การเพิ่มประสิทธิภาพสำหรับคอมไพเลอร์ค่อนข้างง่ายขึ้นแม้ว่าวิศวกรซอฟต์แวร์จำนวนมากในทุกวันนี้หารหัสลักษณะนี้อ่านไม่ได้ . หากคุณยืนยันที่จะใช้เวอร์ชันแรกคุณต้องทำเช่นนั้น

  1. เพิ่ม null-terminate หลังจากสิ้นสุดลูปที่สอง (เพิ่มโค้ดเพิ่มเติม แต่คุณสามารถโต้แย้งว่า readabiliy ทำให้มันคุ้มค่า) หรือ
  2. เปลี่ยนร่างกายวนเป็น "ก่อนกำหนดจากนั้นตรวจสอบหรือบันทึกถ่านที่ได้รับมอบหมายแล้วดัชนีเพิ่ม" การตรวจสอบสภาพตรงกลางของลูปหมายถึงการหลุดออกจากลูป (ลดความคมชัด การบันทึก char ที่ได้รับมอบหมายจะหมายถึงการแนะนำตัวแปรชั่วคราว (ลดความชัดเจนและประสิทธิภาพ) ทั้งสองอย่างนี้จะทำลายความได้เปรียบในความคิดของฉัน

ความถูกต้องดีกว่าอ่านง่ายและรัดกุม
user949300

5

คำตอบโดย Tulains Córdovaและ hvd ครอบคลุมด้านความชัดเจน / การอ่านค่อนข้างดี ผมขอโยนขอบเขตอีกเหตุผลหนึ่งเพื่อสนับสนุนการมอบหมายในเงื่อนไข ตัวแปรที่ประกาศในเงื่อนไขนั้นมีอยู่ในขอบเขตของคำสั่งนั้นเท่านั้น คุณไม่สามารถใช้ตัวแปรนั้นหลังจากเกิดอุบัติเหตุ สำหรับวงที่ได้รับการทำเช่นนี้สำหรับทุกเพศทุกวัย และมันสำคัญมากที่ C ++ 17 ที่กำลังมาถึงจะแนะนำไวยากรณ์ที่คล้ายกันสำหรับifและswitch :

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

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

3

ไม่มันเป็นสไตล์ C มาตรฐานและธรรมดามาก ตัวอย่างของคุณไม่ดีเพราะมันควรเป็นแบบวนซ้ำ แต่โดยทั่วไปไม่มีอะไรผิดปกติ

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

ตัวอย่าง (หรือในขณะที่)


7
มีบางอย่างผิดปกติกับมัน `! = NULL` และญาติของมันในเงื่อนไข C เป็น natter เพียงเพื่อปิดปากนักพัฒนาที่ไม่พอใจกับแนวคิดของค่าที่เป็นจริงหรือเท็จ (หรือกลับกัน)
Jonathan Cast

1
@jcast != NULLไม่มีก็ชัดเจนมากขึ้นที่จะรวม
เส้นทาง Miles Rout

1
(x != NULL) != 0ไม่มีก็ชัดเจนมากกว่าที่จะพูด หลังจากที่ทุกคนว่าเป็นสิ่งที่ซีจริงๆตรวจสอบใช่มั้ย?
Jonathan Cast

@ jcast ไม่มันไม่ใช่ การตรวจสอบว่าสิ่งที่ไม่เท่ากันเป็นเท็จไม่ใช่วิธีที่คุณเขียนเงื่อนไขในภาษาใด ๆ
เส้นทาง Miles

"การตรวจสอบว่าสิ่งที่ไม่เท่ากันเป็นเท็จไม่ใช่วิธีที่คุณเขียนเงื่อนไขในภาษาใด ๆ " เผง
Jonathan Cast

2

ในสมัยของ K&R

  • 'C' เป็นรหัสประกอบแบบพกพา
  • มันถูกใช้โดยโปรแกรมเมอร์ที่คิดในการประกอบรหัส
  • คอมไพเลอร์ไม่ได้ทำการปรับให้เหมาะสมมากนัก
  • คอมพิวเตอร์ส่วนใหญ่มี "ชุดคำสั่งที่ซับซ้อน" ตัวอย่างเช่นwhile ((s[i++]=t[j++]) != '\0')จะจับคู่กับคำสั่งเดียวบน CPU ส่วนใหญ่ (ฉันคาดว่า Dec VAC)

มีวัน

  • คนส่วนใหญ่ที่อ่านรหัส C ไม่ใช่โปรแกรมเมอร์รหัสแอสเซมบลี
  • คอมไพเลอร์ C ทำการปรับแต่งมากมายดังนั้นโค้ดที่ง่ายต่อการอ่านจึงน่าจะถูกแปลเป็นรหัสเครื่องเดียวกัน

(หมายเหตุในการใช้วงเล็บปีกกาเสมอ - ชุดที่ 1 ของรหัสใช้พื้นที่มากขึ้นเนื่องจากมีบางส่วนที่ "ไม่จำเป็น" {}ในประสบการณ์ของฉันเหล่านี้มักจะป้องกันรหัสที่ถูกผสานอย่างไม่ถูกต้องจากคอมไพเลอร์และอนุญาตตำแหน่งที่ผิดพลาด ตรวจพบโดยเครื่องมือ)

อย่างไรก็ตามในวันเก่าโค้ดรุ่นที่ 2 จะได้อ่าน (ถ้าฉันเข้าใจถูกต้อง!)

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

2

แม้ความสามารถในการทำเช่นนี้เป็นความคิดที่ดีมาก มันเป็นที่รู้จักกันเรียกขานว่า "ข้อผิดพลาดครั้งสุดท้ายของโลก" เช่น:

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

ในขณะที่คุณไม่น่าจะผิดพลาดที่ค่อนข้างรุนแรงมันง่ายมากที่จะพลาดโดยบังเอิญและทำให้เกิดข้อผิดพลาดที่หายากใน codebase ของคุณ คอมไพเลอร์สมัยใหม่ส่วนใหญ่จะแทรกคำเตือนสำหรับการมอบหมายภายในเงื่อนไข พวกมันอยู่ที่นั่นด้วยเหตุผลและคุณควรระวังพวกเขาและหลีกเลี่ยงการสร้าง


ก่อนคำเตือนเหล่านี้เราจะเขียนCODE_RED = alertเพื่อให้ข้อผิดพลาดคอมไพเลอร์
เอียน

4
@Ian Yoda เงื่อนไขที่เรียกว่า อ่านยากนะ โชคไม่ดีที่จำเป็นสำหรับพวกเขาคือ
Mason Wheeler

หลังจากช่วงเวลาเบื้องต้น "คุ้นเคยกับมัน" สั้น ๆ เงื่อนไขของโยดานั้นไม่ยากที่จะอ่านกว่าคนทั่วไป บางครั้งพวกเขาจะอ่านได้มากขึ้น ตัวอย่างเช่นหากคุณมีลำดับของ ifs / elseif การมีเงื่อนไขที่ถูกทดสอบทางด้านซ้ายสำหรับการเน้นที่สูงขึ้นนั้นเป็นการปรับปรุง IMO เล็กน้อย
user949300

2
@ user949300 สองคำ: Stockholm Syndrome: P
Mason Wheeler

2

ทั้งสองรูปแบบมีรูปแบบถูกต้องและเหมาะสม สิ่งใดที่เหมาะสมกว่านั้นขึ้นอยู่กับแนวทางของ บริษัท เป็นหลัก Modern IDEs จะอำนวยความสะดวกในการใช้งานทั้งสองสไตล์ผ่านการใช้งานซินแทกซ์แบบถ่ายทอดสดที่เน้นบริเวณที่อาจทำให้เกิดความสับสนได้

ตัวอย่างเช่นนิพจน์ต่อไปนี้ถูกเน้นโดยNetbeans :

if($a = someFunction())

บนพื้นฐานของ "การมอบหมายโดยไม่ตั้งใจ"

ป้อนคำอธิบายรูปภาพที่นี่

เพื่อบอก Netbeans อย่างชัดเจนว่า "ใช่ฉันตั้งใจจะทำอย่างนั้นจริงๆ ... " การแสดงออกสามารถห่อเป็นชุดวงเล็บได้

if(($a = someFunction()))

ป้อนคำอธิบายรูปภาพที่นี่

ในตอนท้ายของวันมันทั้งหมด boils ลงไปที่แนวทางรูปแบบ บริษัท และความพร้อมของเครื่องมือที่ทันสมัยเพื่ออำนวยความสะดวกในกระบวนการพัฒนา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.