วัตถุประสงค์ของการใช้วงเล็บปีกกา (เช่น {}) สำหรับบรรทัดเดียวคือถ้าหรือวนรอบคืออะไร?


321

ฉันกำลังอ่านบันทึกการบรรยายของอาจารย์ C ++ ของฉันและเขาเขียนสิ่งต่อไปนี้:

  1. ใช้การเยื้อง // ตกลง
  2. ไม่ต้องพึ่งพาลำดับความสำคัญของโอเปอเรเตอร์ - ใช้เครื่องหมายวงเล็บ // OK
  3. ใช้บล็อก {} ทุกครั้ง - แม้สำหรับบรรทัดเดียว // ไม่เป็นไรทำไม ???
  4. วัตถุ Const ที่ด้านซ้ายของการเปรียบเทียบ // ตกลง
  5. ใช้เครื่องหมายที่ไม่ได้ลงชื่อสำหรับตัวแปรที่> = 0 // เคล็ดลับดี
  6. ตั้งค่าตัวชี้เป็น NULL หลังจากลบ - การป้องกันการลบสองครั้ง // ไม่เลว

เทคนิคที่สามไม่ชัดเจนสำหรับฉัน: ฉันจะได้อะไรจากการวางหนึ่งบรรทัดใน { ... } ?

ตัวอย่างเช่นใช้รหัสแปลก ๆ นี้:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

และแทนที่ด้วย:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

ประโยชน์ของการใช้รุ่นที่ 1 คืออะไร


256
ความสามารถในการอ่านและการบำรุงรักษา ไม่ชัดเจนในทันทีว่าบล็อกคำสั่ง 'j ++' นั้นเป็นของอะไรและการเพิ่มโค้ดหลังจากนั้นจะไม่เชื่อมโยงกับคำสั่ง if
ป่าและต้นไม้

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

30
ฉันไม่เห็นด้วยกับ 6 เพราะจะซ่อนการลบซ้ำและซ่อนข้อผิดพลาดเชิงตรรกะ
Luchian Grigore

28
# 5 อาจมีเล่ห์เหลี่ยม - พิจารณาลูปนี้: for (unsigned i = 100; i >= 0; --i).
อาร์ชี

36
Btw, (i % 2 == 0)ขัดแย้ง (2) คุณจะอาศัยความสำคัญประกอบและความหมายเป็นหลักสูตรที่มากกว่า((i % 2) == 0) (i % (2 == 0))ฉันจะจำแนกกฎที่ 2 ว่า "ความเชื่อมั่นที่ถูกต้อง แต่" เสมอ "ผิด"
Steve Jessop

คำตอบ:


509

ลองแก้ไขด้วยiเมื่อเราเพิ่มj:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

ไม่นะ! มาจาก Python มันดูโอเค แต่ที่จริงแล้วมันไม่ได้เป็นมันเทียบเท่ากับ:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

แน่นอนว่านี่เป็นความผิดพลาดที่ไร้สาระ แต่สิ่งที่แม้แต่โปรแกรมเมอร์ที่มีประสบการณ์ก็สามารถทำได้

อีกเหตุผลที่ดีมากมีการชี้ให้เห็นในคำตอบของ ta.speot.is

หนึ่งในสามที่ฉันคิดได้คือซ้อนกันif:

if (cond1)
   if (cond2) 
      doSomething();

ตอนนี้ถือว่าตอนนี้คุณต้องการdoSomethingElse()เมื่อcond1ไม่เป็นไปตาม (คุณลักษณะใหม่) ดังนั้น:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

ซึ่งเป็นสิ่งที่ผิดอย่างเห็นได้ชัดตั้งแต่ร่วมงานกับภายในelseif


แก้ไข: เนื่องจากสิ่งนี้ได้รับความสนใจฉันจะอธิบายมุมมองของฉันให้ชัดเจน คำถามที่ฉันตอบคือ:

ประโยชน์ของการใช้รุ่นที่ 1 คืออะไร

ซึ่งฉันได้อธิบายไว้ มีประโยชน์บางอย่าง แต่ IMO กฎ "เสมอ" จะไม่นำมาใช้เสมอ ดังนั้นฉันจึงไม่สนับสนุนอย่างเต็มที่

ใช้บล็อก {} ทุกครั้ง - แม้สำหรับบรรทัดเดียว // ไม่ตกลงทำไม ???

ฉันไม่ได้พูดเสมอใช้{}บล็อก ถ้ามันเป็นเงื่อนไขและพฤติกรรมที่เรียบง่าย หากคุณสงสัยว่ามีใครบางคนอาจเข้ามาภายหลังและเปลี่ยนรหัสของคุณเพื่อเพิ่มฟังก์ชันการทำงานให้ทำ


5
@Science_Fiction: จริง แต่ถ้าคุณเพิ่มi++ มาก่อน j++ตัวแปรทั้งสองจะยังคงอยู่ในขอบเขตเมื่อพวกเขาใช้
Mike Seymour

26
นี่ฟังดูสมเหตุสมผลมาก แต่ก็ไม่สนใจความจริงที่ว่าบรรณาธิการแก้ไขการเยื้องไม่ใช่คุณและมันจะเยื้องi++;ในลักษณะที่แสดงทันทีว่ามันไม่ได้เป็นส่วนหนึ่งของลูป (ในอดีตนี่อาจเป็นเหตุผลที่สมเหตุสมผลและฉันเคยเห็นปัญหาดังกล่าวประมาณ 20 ปีที่แล้วไม่ใช่ตั้งแต่นั้น)
James Kanze

43
@ James: นั่นไม่ใช่ "ความจริง" แม้ว่ามันเป็นเวิร์กโฟลว์ของคุณ และขั้นตอนการทำงานของผู้คนจำนวนมาก แต่ไม่ใช่ของทุกคน ผมไม่คิดว่ามันจำเป็นต้องมีข้อผิดพลาดในการรักษา c ++ แหล่งที่มาเป็นไฟล์ข้อความธรรมดามากกว่าการส่งออกของบรรณาธิการ WYSIWYG (vi / emacs / Visual Studio) ที่บังคับใช้การจัดรูปแบบกฎ ดังนั้นกฎนี้คือผู้ไม่เชื่อเรื่องพระเจ้าเกินกว่าที่คุณต้องการ แต่ไม่เกินกว่าสิ่งที่ผู้คนใช้เพื่อแก้ไข C ++ ดังนั้น "การป้องกัน"
Steve Jessop

31
@JamesKanze คุณเชื่อมั่นในสมมติฐานที่ว่าทุกคนทำงานใน IDEs ที่ทรงพลังหรือไม่? CI ล่าสุดเขียนไว้ในนาโน แม้ว่าสิ่งหนึ่งที่สิ่งแรกที่ฉันมักจะปิดใน IDE คือการเยื้องอัตโนมัติ - เนื่องจาก IDE มีแนวโน้มที่จะเข้าสู่เวิร์กโฟลว์ที่ไม่ใช่เชิงเส้นของฉันพยายามแก้ไข 'ความผิดพลาด' ของฉันตามข้อมูลที่ไม่สมบูรณ์ . IDEs ไม่ค่อยดีในการเยื้องอัตโนมัติของโปรแกรมตามธรรมชาติ โปรแกรมเมอร์เหล่านั้นที่ใช้คุณสมบัติดังกล่าวมักจะรวมสไตล์ของพวกเขาเข้ากับ IDE ของพวกเขาซึ่งก็ดีถ้าคุณใช้ IDE เพียงอันเดียว แต่ไม่ได้ผลมากนักถ้าคุณทำงานข้ามหลาย ๆ อย่าง
Rushyo

10
“ นี่เป็นความผิดพลาดที่ไร้สาระ แต่สิ่งที่แม้แต่โปรแกรมเมอร์ที่มีประสบการณ์ก็สามารถทำได้” - อย่างที่ฉันพูดในคำตอบฉันไม่เชื่อ ฉันคิดว่ามันเป็นกรณีที่วางแผนไว้อย่างสมบูรณ์ซึ่งไม่ก่อให้เกิดปัญหาในความเป็นจริง
Konrad Rudolph

324

มันง่ายมากที่จะตั้งใจเปลี่ยนแปลงการควบคุมการไหลที่มีความคิดเห็นถ้าคุณไม่ได้ใช้และ{ }ตัวอย่างเช่น:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

หากคุณแสดงความคิดเห็นdo_something_else()ด้วยความคิดเห็นบรรทัดเดียวคุณจะพบกับสิ่งนี้:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

มันรวบรวม แต่must_always_do_this()ไม่ได้เรียกเสมอ

เราพบปัญหานี้ในฐานรหัสของเราซึ่งมีคนเข้าไปปิดการใช้งานบางฟังก์ชั่นอย่างรวดเร็วก่อนที่จะปล่อย โชคดีที่เราจับได้ในการตรวจสอบรหัส


3
โอ้โห !! เป็นพฤติกรรมที่กำหนดไว้ซึ่งmust_always_do_this();จะดำเนินการหากคุณแสดงความคิดเห็น // do_something_else ();
Mr.Anubis

1
@Supr เพราะมันเป็นครั้งแรกที่เขียนเขาก็บอกว่ามันเป็นเรื่องยากที่จะทำลายการไหลที่ถูกต้องถ้าคุณใช้การจัดฟันและจากนั้นให้ตัวอย่างของวิธีการที่ง่ายก็คือการทำลายโดยไม่ต้องรหัสวงเล็บอย่างถูกต้อง
SeanC

20
ฉันวิ่งเข้าไปในนี้เมื่อวันก่อน if(debug) \n //print(info);. โดยทั่วไปนำห้องสมุดทั้งหมดออก
Kevin

4
Fortunately we caught it in code review.อุ๊ย! ฟังดูผิดนะ Fortunately we caught it in unit tests.จะดีกว่านี้มาก!
BЈовић

4
@ BЈовић แต่ถ้ารหัสถูกทดสอบหน่วย? ใจกระเด้ง (ล้อเล่นมันเป็นแอปรุ่นเก่าไม่มีการทดสอบหน่วย)
ta.speot.is

59

ฉันมีข้อสงสัยเกี่ยวกับความสามารถของอาจารย์ พิจารณาคะแนนของเขา:

  1. ตกลง
  2. ใครบ้างที่จะเขียน (หรือต้องการอ่าน) (b*b) - ((4*a)*c)บ้างที่ ? ลำดับความสำคัญบางอย่างเห็นได้ชัด (หรือควรเป็น) และเครื่องหมายวงเล็บพิเศษก็เพิ่มความสับสน (ในทางกลับกันคุณ _should_ ใช้วงเล็บในกรณีที่ไม่ชัดเจนแม้ว่าคุณจะรู้ว่าไม่จำเป็นก็ตาม)
  3. เรียงจาก มีอนุสัญญาการแพร่กระจายสองแบบสำหรับการจัดรูปแบบเงื่อนไขและลูป:
    ถ้า (cond) {
        รหัส;
    }
    
    และ:
    ถ้า (cond)
    {
        รหัส;
    }
    
    ในตอนแรกฉันเห็นด้วยกับเขา ช่องเปิด{ไม่สามารถมองเห็นได้ดังนั้นจึงเป็นการดีที่สุดที่จะสันนิษฐานว่ามี อย่างไรก็ตามในครั้งที่สองฉัน (และคนส่วนใหญ่ที่ฉันเคยทำงานด้วย) ไม่มีปัญหาในการละเว้นวงเล็บปีกกาสำหรับคำสั่งเดียว (แน่นอนว่าการเยื้องนั้นเป็นระบบและคุณใช้สไตล์นี้อย่างสม่ำเสมอ (และโปรแกรมเมอร์ที่เก่งมาก ๆ เขียนโค้ดที่อ่านได้ดีมากไม่ต้องใส่เครื่องหมายวงเล็บแม้เมื่อทำการฟอร์แมตด้วยวิธีแรก)
  4. NO สิ่งที่ชอบif ( NULL == ptr )น่าเกลียดพอที่จะขัดขวางการอ่าน เขียนการเปรียบเทียบอย่างสังหรณ์ใจ (ซึ่งในหลายกรณีส่งผลให้ค่าคงที่ด้านขวา) 4 ของเขาคือคำแนะนำที่ไม่ดี; สิ่งใดก็ตามที่ทำให้โค้ดผิดธรรมชาติทำให้อ่านไม่ได้
  5. NO แต่intจะสงวนไว้สำหรับกรณีพิเศษเท่านั้น สำหรับโปรแกรมเมอร์ C และ C ++ ที่มีประสบการณ์การใช้unsignedสัญญาณตัวดำเนินการบิต C ++ ไม่มีประเภทที่แท้จริง (หรือประเภทย่อยที่มีประสิทธิภาพอื่น ๆ ); unsignedไม่ทำงานสำหรับค่าตัวเลขเนื่องจากกฎการส่งเสริมการขาย ค่าตัวเลขที่ไม่มีการดำเนินการทางคณิตศาสตร์จะทำให้ความรู้สึกเช่นหมายเลข serial unsignedสันนิษฐานว่าอาจจะเป็น ฉันจะเถียงกับมันอย่างไรเพราะมันส่งข้อความผิด: การทำงานระดับบิตไม่สมเหตุสมผลเช่นกัน กฎพื้นฐานคือประเภทอินทิกรัลคือint_unless_ มีเหตุผลที่สำคัญสำหรับการใช้ประเภทอื่น
  6. NO การทำสิ่งนี้อย่างเป็นระบบทำให้เข้าใจผิดและไม่ได้ป้องกันอะไรเลย ในรหัส OO เข้มงวดdelete this;มักจะเป็นกรณีที่พบบ่อยที่สุด (และคุณไม่สามารถตั้งค่าthisการNULL) และส่วนใหญ่เป็นอย่างอื่นdeleteอยู่ใน destructors ดังนั้นคุณจึงไม่สามารถเข้าถึงตัวชี้ภายหลังอยู่แล้ว และการตั้งค่าให้NULLไม่ทำอะไรกับพอยน์เตอร์อื่น ๆ ที่ลอยอยู่รอบ ๆ การตั้งค่าตัวชี้อย่างเป็นระบบเพื่อNULLความปลอดภัยที่ผิดพลาดและไม่ได้ซื้ออะไรให้คุณเลย

ดูรหัสในการอ้างอิงทั่วไป Stroustrup ละเมิดกฎทุกข้อที่คุณได้รับยกเว้นตัวอย่างแรก

ฉันขอแนะนำให้คุณหาอาจารย์คนอื่น คนที่รู้จริง ๆ ว่าเขากำลังพูดถึงอะไร


13
หมายเลข 4 อาจน่าเกลียด แต่มีจุดประสงค์อยู่ กำลังพยายามป้องกันหาก (ptr = NULL) ฉันไม่คิดว่าฉันเคยใช้delete thisมันเป็นเรื่องธรรมดามากกว่าที่ฉันเคยเห็นหรือไม่? ฉันไม่คิดว่าการตั้งค่าตัวชี้เป็น NULL หลังการใช้งานนั้นเป็นสิ่งที่ไม่ดี แต่ YMMV อาจเป็นเพียงฉัน แต่แนวทางส่วนใหญ่ของเขาก็ไม่ได้แย่ขนาดนั้น
Firedragon

16
@Firedragon: คอมไพเลอร์ส่วนใหญ่จะเตือนเกี่ยวกับเว้นแต่คุณจะเขียนเป็นif (ptr = NULL) if ((ptr = NULL))ต้องเห็นด้วยกับเจมส์ Kanze ว่าความน่าเกลียดของการมีNULLครั้งแรกทำให้มันไม่แน่นอนสำหรับฉัน
Leo

34
@ JamesKanze: ฉันต้องบอกว่าฉันไม่เห็นด้วยกับสิ่งที่คุณได้กล่าวมาที่นี่ - แม้ว่าฉันจะชื่นชมและเคารพข้อโต้แย้งของคุณที่มาถึงพวกเขา สำหรับโปรแกรมเมอร์ C และ C ++ ที่มีประสบการณ์การใช้ตัวดำเนินการบิตสัญญาณที่ไม่ได้ลงนาม - ฉันไม่เห็นด้วยเลย: การใช้ตัวดำเนินการบิตส่งสัญญาณการใช้ตัวดำเนินการบิต สำหรับฉันแล้วการใช้unsignedเป็นการระบุถึงความทะเยอทะยานในส่วนของโปรแกรมเมอร์ว่าตัวแปรควรแสดงถึงจำนวนบวกเท่านั้น การผสมกับหมายเลขที่เซ็นชื่อมักจะทำให้เกิดคำเตือนของคอมไพเลอร์ซึ่งอาจเป็นสิ่งที่ผู้สอนตั้งใจจะทำ
องค์ประกอบ 10

18
สำหรับโปรแกรมเมอร์ C และ C ++ ที่มีประสบการณ์การใช้ตัวดำเนินการบิตสัญญาณที่ไม่ได้ลงนามหรือไม่ size_tใคร
ta.speot.is

16
@ James Kanze พิจารณาจุดประสงค์ คุณกำลังเปรียบเทียบโค้ดที่ผลิตโดยโปรแกรมเมอร์ที่มีประสบการณ์กับตัวอย่างการสอน กฎเหล่านี้จัดทำโดยวิทยากรเพราะเป็นข้อผิดพลาดประเภทที่เขาเห็นนักเรียนทำ ด้วยประสบการณ์นักเรียนสามารถผ่อนคลายหรือไม่สนใจสิ่งเหล่านี้
Joshua Shane Liberman

46

คำตอบอื่น ๆ ทั้งหมดปกป้องกฎของอาจารย์ของคุณ 3

ให้ฉันบอกว่าฉันเห็นด้วยกับคุณ: กฎซ้ำซ้อนและฉันจะไม่แนะนำ มันเป็นความจริงที่ว่าในทางทฤษฎีจะป้องกันข้อผิดพลาดหากคุณเพิ่มวงเล็บปีกกาไว้เสมอ ในทางกลับกันฉันไม่เคยพบปัญหานี้ในชีวิตจริง : ตรงกันข้ามกับคำตอบอื่น ๆ ที่บอกเป็นนัยฉันไม่เคยลืมที่จะใส่วงเล็บปีกกาเมื่อพวกเขาจำเป็น หากคุณใช้การเยื้องที่เหมาะสมจะเห็นได้ชัดทันทีว่าคุณจำเป็นต้องเพิ่มวงเล็บปีกกาอีกครั้งเมื่อมีการเยื้องมากกว่าหนึ่งคำสั่ง

คำตอบของ“ ส่วนประกอบที่ 10” จะเน้นเฉพาะกรณีที่นึกออกเท่านั้นซึ่งนี่อาจนำไปสู่ข้อผิดพลาดได้ แต่ในทางกลับกันการแทนที่รหัสผ่านการแสดงออกปกติมักรับประกันความสนใจอย่างมากอยู่ดี

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

พิจารณาฟังก์ชั่นที่มีคำสั่งป้องกันจำนวนมากในตอนเริ่มต้น (และใช่ต่อไปนี้คือรหัส C ++ ที่ไม่ดี แต่ในภาษาอื่นอาจเป็นสถานการณ์ทั่วไป)

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

นี่เป็นรหัสที่แย่มากและฉันขอยืนยันอย่างยิ่งว่าสิ่งต่อไปนี้สามารถอ่านได้อย่างมากมาย:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

ในทำนองเดียวกันลูปซ้อนแบบสั้นจะได้ประโยชน์จากการไม่ใส่วงเล็บปีกกา:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

เปรียบเทียบกับ:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

รหัสแรกกระชับ รหัสที่สองป่อง

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

กล่าวโดยย่อ: อย่าเขียนรหัสที่ไม่จำเป็นซึ่งกินพื้นที่หน้าจอ


26
หากคุณไม่เชื่อในการเขียนรหัสที่ใช้พื้นที่หน้าจอโดยไม่จำเป็นคุณไม่มีธุรกิจที่จะเปิดวงเล็บปีกกาในบรรทัดของตัวเอง ฉันอาจจะต้องหลบและหนีจากการล้างแค้นของ GNU แต่อย่างจริงจัง - ไม่ว่าคุณต้องการให้โค้ดของคุณมีขนาดกะทัดรัดในแนวตั้งหรือไม่ และถ้าคุณทำอย่าทำสิ่งที่ออกแบบมาเพื่อทำให้โค้ดของคุณเล็กลงในแนวตั้ง แต่อย่างที่คุณพูดเมื่อแก้ไขแล้วคุณยังต้องการลบวงเล็บปีกกาซ้ำซ้อน หรืออาจจะเขียนif (a == nullptr) { throw null_ptr_error("a"); }เป็นหนึ่งบรรทัด
Steve Jessop

6
@ Steve เป็นเรื่องของความเป็นจริงผมไม่ใส่รั้งเปิดในบรรทัดก่อนหน้านี้ด้วยเหตุผลอย่างที่คุณระบุ ฉันใช้สไตล์อื่นที่นี่เพื่อทำให้ชัดเจนยิ่งขึ้นว่าความแตกต่างนั้นรุนแรงมากเพียงใด
Konrad Rudolph

9
+1 ฉันเห็นด้วยอย่างยิ่งว่าตัวอย่างแรกของคุณอ่านง่ายกว่าโดยไม่ต้องใช้เครื่องมือจัดฟัน ในตัวอย่างที่สองสไตล์การเข้ารหัสส่วนบุคคลของฉันคือการใช้เครื่องมือจัดฟันด้านนอก for-loop และไม่อยู่ด้านใน ฉันไม่เห็นด้วยกับ @SteveJessop เกี่ยวกับการเป็นหนึ่งในสุดขีดหรืออื่น ๆ เกี่ยวกับรหัสขนาดกะทัดรัดในแนวตั้ง ฉันไม่ได้ใช้เครื่องมือจัดฟันแบบพิเศษเพื่อลดช่องว่างในแนวดิ่ง แต่ฉันจะใช้เครื่องมือจัดฟันแบบเปิดในบรรทัดใหม่เพราะฉันเห็นว่าการมองเห็นขอบเขตได้ง่ายขึ้นเมื่อมีการจัดฟันแบบเรียงแถว เป้าหมายคือความสามารถในการอ่านและบางครั้งก็หมายถึงการใช้พื้นที่ในแนวดิ่งมากกว่าในขณะที่บางครั้งก็หมายถึงการใช้พื้นที่น้อยลง
Travesty3

22
"ฉันไม่เคยเจอปัญหานี้ในชีวิตจริง": โชคดีคุณ สิ่งนี้ไม่เพียงเผาคุณ แต่ให้ระดับ 90% ที่สามแก่คุณ (และนั่นเป็นเพียงการจัดการสองสามชั้นที่ต้องการการแก้ไขในช่วงเย็น)
ริชาร์ด

9
@ Richard ฉันไม่ได้ซื้อมัน ตามที่ฉันได้อธิบายไว้ในการแชทแม้ว่าข้อผิดพลาดนี้จะเกิดขึ้น (ซึ่งฉันคิดว่าไม่น่าจะเกิดขึ้น) มันเป็นเรื่องเล็กน้อยที่จะแก้ไขเมื่อคุณดูที่การติดตามสแต็กเพราะเห็นได้ชัดว่าข้อผิดพลาดนั้นเกิดจากการดูรหัส การอ้างสิทธิ์ที่เกินจริงของคุณนั้นไม่มีมูลความจริงเลย
Konrad Rudolph

40

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

ตัวอย่างปัญหาที่พบบ่อยที่สุดที่ฉันพบคือ:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

ดังนั้นเมื่อฉันมาและต้องการที่จะเพิ่มคำสั่งนั้นฉันสามารถจบลงด้วยสิ่งนี้ถ้าฉันไม่ระวัง:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

เนื่องจากใช้เวลาประมาณ 1 วินาทีในการเพิ่มเครื่องมือจัดฟันและสามารถช่วยให้คุณประหยัดเวลาในการดีบักอย่างน้อยสองสามนาทีทำไมคุณไม่ลองใช้ตัวเลือกที่ไม่ชัดเจน ดูเหมือนว่าเศรษฐกิจที่ผิดสำหรับฉัน


16
ปัญหาในตัวอย่างนี้ไม่ใช่การเยื้องที่ไม่เหมาะสมและเส้นที่ยาวเกินไปแทนที่จะเป็นการจัดฟันใช่หรือไม่
Honza Brabec

7
ใช่ แต่การปฏิบัติตามแนวทางการออกแบบ / การเข้ารหัสที่ปลอดภัยเท่านั้นโดยสมมติว่าผู้คนกำลังทำตามแนวทางอื่น ๆ (เช่นไม่มีเส้นที่ยาวเกินไป) ดูเหมือนว่าจะถามถึงปัญหา หากมีการจัดฟันอยู่ในช่วงเริ่มต้นมันจะเป็นไปไม่ได้ที่จะจบลงด้วย if-block ที่ไม่ถูกต้องในสถานการณ์นี้
แยม

16
จะเพิ่มการจัดฟันอย่างไร ( if(really long...editor){ do_foo;}ช่วยให้คุณหลีกเลี่ยงกรณีนี้ดูเหมือนว่าปัญหาจะยังคงเหมือนเดิมโดยส่วนตัวแล้วฉันชอบที่จะหลีกเลี่ยงการจัดฟันเมื่อไม่จำเป็น แต่ที่ไม่มีเวลาทำอะไร อันเนื่องมาจากสองบรรทัดพิเศษในรหัส
Grizzly

1
จุดดี - ฉันสันนิษฐานว่าการบังคับใช้การจัดฟันก็จะส่งผลให้พวกเขาถูกวางไว้ในที่ที่เหมาะสม แต่แน่นอนว่ามีคนตั้งใจแน่วแน่ที่จะทำให้สิ่งต่าง ๆ เป็นเรื่องยาก ฉันคิดว่าคนส่วนใหญ่จะไม่แม้ว่า
แยม

2
สิ่งแรกและสิ่งสุดท้ายที่ฉันทำเมื่อสัมผัสไฟล์คือกดปุ่มรูปแบบอัตโนมัติ มันกำจัดปัญหาส่วนใหญ่เหล่านี้ได้
Jonathan Allen

20

2c ของฉัน:

ใช้การเยื้อง

อย่างชัดเจน

ไม่ต้องพึ่งพาลำดับความสำคัญของโอเปอเรเตอร์ - ใช้วงเล็บเสมอ

ฉันจะไม่ใช้คำว่า "ไม่เคยและ" เสมอ "แต่โดยทั่วไปฉันเห็นว่ากฎนี้มีประโยชน์ในบางภาษา (Lisp, Smalltalk) นี่เป็นปัญหาที่ไม่ใช่ประเด็น

ใช้บล็อก {} ทุกครั้ง - แม้สำหรับบรรทัดเดียว

ฉันไม่เคยทำอย่างนั้นและไม่เคยมีปัญหาเดียว แต่ฉันเห็นได้ว่ามันดีสำหรับนักเรียนอย่างไร ถ้าพวกเขาศึกษา Python มาก่อน

วัตถุ Const ที่ด้านซ้ายของการเปรียบเทียบ

เงื่อนไขโยดา? ไม่ได้โปรด มันเจ็บการอ่าน เพียงใช้ระดับการเตือนสูงสุดเมื่อคุณรวบรวมรหัส

ใช้ไม่ได้ลงนามสำหรับตัวแปรที่>> 0

ตกลง. ตลกพอฉันเคยได้ยิน Stroustrup ไม่เห็นด้วย

ตั้งค่าตัวชี้เป็น NULL หลังจากลบ - การป้องกันการลบซ้ำ

คำแนะนำที่ไม่ดี! ไม่มีตัวชี้ที่ชี้ไปยังวัตถุที่ถูกลบหรือไม่มีอยู่


5
+1 เพียงเพื่อจุดสุดท้ายเพียงอย่างเดียว ตัวชี้แบบ raw ไม่มีธุรกิจที่เป็นเจ้าของหน่วยความจำอยู่แล้ว
Konrad Rudolph

2
เกี่ยวกับการใช้งานที่ไม่ได้ลงชื่อ: ไม่เพียง แต่ Stroustrup แต่ K&R (ใน C), Herb Sutter และ (ฉันคิดว่า) Scott Meyers ในความเป็นจริงฉันไม่เคยได้ยินใครที่เข้าใจกฎของ C ++ เถียงว่าใช้ไม่ได้ลงชื่อ
James Kanze

2
@ JamesKanze ในความเป็นจริงในโอกาสเดียวกันฉันได้ยินความคิดเห็นของ Stroustrup (การประชุมที่บอสตันในปี 2008) Herb Sutter อยู่ที่นั่นและไม่เห็นด้วยกับ Bjarne
Nemanja Trifunovic

3
เพียงเพื่อทำให้ " unsignedเสีย" หนึ่งในปัญหาคือเมื่อ C ++ เปรียบเทียบประเภทที่เซ็นชื่อและขนาดที่ไม่ได้ลงนามคล้ายกันมันจะแปลงเป็นประเภทที่ไม่ได้ลงชื่อก่อนทำการเปรียบเทียบ ซึ่งส่งผลให้เกิดการเปลี่ยนแปลงในมูลค่า การแปลงไปสู่การเซ็นชื่อนั้นไม่จำเป็นต้องดีกว่ามากนัก การเปรียบเทียบควรเกิดขึ้นจริง ๆ "ราวกับว่า" ค่าทั้งสองถูกแปลงเป็นประเภทที่มีขนาดใหญ่กว่าซึ่งสามารถเป็นตัวแทนของค่าทั้งหมดในประเภทใดประเภทหนึ่ง
James Kanze

1
@SteveJessop unsignedฉันคิดว่าคุณต้องใช้มันในบริบทของฟังก์ชั่นการกลับมาของ ฉันแน่ใจว่าเขาไม่มีปัญหากับการexp(double)คืนค่ามากกว่าMAX_INT:-) แต่อีกครั้งปัญหาที่แท้จริงคือการแปลงโดยนัย int i = exp( 1e6 );ถูกต้องสมบูรณ์ C ++ Stroustrup เสนอให้ลดการแปลงโดยปริยายที่สูญเสียไปโดยปริยาย แต่คณะกรรมการไม่สนใจ (คำถามที่น่าสนใจ: จะunsigned-> intถูกพิจารณาว่าเป็นความสูญเสียฉันจะพิจารณาทั้งunsigned-> intและint-> unsignedlossy ซึ่งจะไปได้ไกลในการunsignedตกลง
James Kanze

18

มันใช้งานง่ายและเข้าใจง่ายขึ้น มันทำให้เจตนาชัดเจน

และมันก็ทำให้มั่นใจได้ว่ารหัสไม่ทำลายเมื่อผู้ใช้ใหม่โดยไม่รู้ตัวอาจพลาด{, }ในขณะที่เพิ่มงบรหัสใหม่


Makes the intent clear+1 นี่อาจเป็นเหตุผลที่รัดกุมและแม่นยำที่สุด
Qix - MONICA ถูกยกเลิก

13

เพื่อเพิ่มข้อเสนอแนะที่สมเหตุสมผลมากขึ้นตัวอย่างหนึ่งฉันพบในขณะที่ refactoring รหัสบางส่วนที่สิ่งนี้กลายเป็นสิ่งสำคัญดังนี้: ฉันกำลังแก้ไข codebase ขนาดใหญ่มากเพื่อเปลี่ยนจาก API หนึ่งไปสู่อีก API แรกมีการเรียกเพื่อตั้งรหัส บริษัท ดังนี้:

setCompIds( const std::string& compId, const std::string& compSubId );

ในขณะที่การเปลี่ยนต้องการการโทรสองสาย:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

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

if ( condition )
   setCompIds( compId, compSubId );

สำหรับสิ่งนี้:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

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

ฉันสังเกตว่าastyleตอนนี้มีตัวเลือก--add-bracketsที่อนุญาตให้คุณเพิ่มวงเล็บเหลี่ยมที่ไม่มีและฉันขอแนะนำอย่างยิ่งถ้าคุณพบว่าตัวเองอยู่ในตำแหน่งเดียวกันกับที่ฉันเคยเป็น


5
ฉันเคยเห็นเอกสารที่มีเหรียญ "Microsoftligent" ที่ยอดเยี่ยม ใช่เป็นไปได้ที่จะทำผิดพลาดอย่างมีนัยสำคัญกับการค้นหาทั่วโลกและแทนที่ นั่นหมายถึงการค้นหาและแทนที่ทั่วโลกจะต้องมีการใช้อย่างชาญฉลาดไม่ต้องใช้ไมโครซอฟท์
Pete Becker

4
ฉันรู้ว่านี่ไม่ใช่ post-ชันสูตรของฉันที่จะดำเนินการ แต่ถ้าคุณกำลังจะทำการแทนที่ข้อความบนซอร์สโค้ดคุณควรทำตามกฎเดียวกันกับที่คุณใช้สำหรับการแทนที่ข้อความชนิดที่ดี ในภาษา: macros คุณไม่ควรเขียนแมโคร#define FOO() func1(); \ func2(); (ที่มี linebreak หลังจากแบ็กสแลช) การค้นหาและแทนที่จะเหมือนกัน ที่กล่าวว่าฉันได้เห็น "ใช้การจัดฟัน" ขั้นสูงเป็นกฎสไตล์อย่างแม่นยำเพราะช่วยให้คุณไม่ต้องห่อมาโครหลายคำสั่งdo .. while(0)ทั้งหมดไว้ แต่ฉันไม่เห็นด้วย
Steve Jessop

2
Btw นั่นคือ "เป็นที่ยอมรับ" ในแง่ที่ว่า knotweed ภาษาญี่ปุ่นเป็นที่ยอมรับ: ฉันไม่ได้บอกว่าเราควรออกไปใช้วิธีการใช้มาโครและการแทนที่ข้อความ แต่ฉันบอกว่าเมื่อเราทำเช่นนี้ สิ่งที่เราควรจะทำมันในทางที่ผลงานมากกว่าการทำสิ่งที่ทำงานเฉพาะในกรณีที่กฎสไตล์โดยเฉพาะอย่างยิ่งถูกบังคับประสบความสำเร็จบนฐานรหัสทั้ง :-)
สตีฟเจสซอพ

1
@ SteveJessop One สามารถโต้แย้งการจัดฟันและเข็มขัด หากคุณต้องใช้มาโครดังกล่าว (และเราเคยทำมาก่อนหน้า C ++ และinline) คุณควรตั้งเป้าหมายให้พวกเขาทำงานเหมือนฟังก์ชั่นให้มากที่สุดโดยใช้do { ... } while(0)เคล็ดลับหากจำเป็น (และวงเล็บจำนวนมาก แต่ก็ยังไม่จำเป็น อย่าหยุดใช้วงเล็บปีกกาทุกที่ถ้านั่นคือสไตล์บ้าน (FWIW: ฉันเคยทำงานในสถานที่ที่มีรูปแบบบ้านที่แตกต่างกันครอบคลุมสไตล์ทั้งหมดที่กล่าวถึงที่นี่ฉันไม่เคยพบปัญหาร้ายแรงใด ๆ เลย)
James Kanze

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

8

ฉันใช้{}ทุกที่ยกเว้นบางกรณีที่เห็นได้ชัด บรรทัดเดียวเป็นหนึ่งในกรณี:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

มันอาจทำร้ายคุณเมื่อคุณเพิ่มวิธีการบางอย่างก่อนที่จะกลับมา การเยื้องระบุว่าการส่งคืนกำลังดำเนินการเมื่อตรงตามเงื่อนไข แต่จะกลับคืนเสมอ

ตัวอย่างอื่น ๆ ใน C # ด้วยการใช้ statment

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

ซึ่งเทียบเท่ากับ

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}

1
เพียงใช้เครื่องหมายจุลภาคในusingคำสั่งและคุณไม่จำเป็นต้อง :)
Ry-

1
@minitech นั้นใช้งานไม่ได้ที่นี่คุณสามารถใช้เครื่องหมายจุลภาคเมื่อประเภทเท่ากันไม่ใช่ประเภทไม่เท่ากัน วิธีการของ Lukas ในการทำเช่นนี้เป็นวิธีที่ยอมรับได้ IDE ยังจัดรูปแบบสิ่งนี้แตกต่างกัน (สังเกตว่าไม่มีการเยื้องอัตโนมัติของวินาทีusing)
Konrad Rudolph

8

ตัวอย่างที่เกี่ยวข้องที่สุดที่ฉันนึกได้:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

คู่ไหนifจะelseถูกจับคู่กับ? การเยื้องก็หมายความว่าด้านนอกifได้รับelseแต่นั่นไม่ใช่วิธีการรวบรวมที่จะเห็น ภายใน ifจะได้รับelseและด้านนอกifไม่ได้ คุณจะต้องรู้ว่า (หรือดูว่ามันมีพฤติกรรมแบบนั้นในโหมดแก้ไขจุดบกพร่อง) เพื่อคิดออกโดยการตรวจสอบว่าทำไมรหัสนี้อาจทำให้ความคาดหวังของคุณล้มเหลว มันจะสับสนมากขึ้นถ้าคุณรู้จัก Python ในกรณีนั้นคุณรู้ว่าการเยื้องกำหนดบล็อคโค้ดดังนั้นคุณคาดหวังให้การประเมินเป็นไปตามการเยื้อง อย่างไรก็ตาม C # ไม่ได้ให้ข้อมูลเกี่ยวกับช่องว่าง

ตอนนี้ที่กล่าวว่าฉันไม่เห็นด้วยกับกฎ "มักใช้วงเล็บ" นี้บนใบหน้าของมัน มันทำให้รหัสมีเสียงดังในแนวตั้งมากลดความสามารถในการอ่านผ่านอย่างรวดเร็ว ถ้าคำสั่งคือ:

if(someCondition)
   DoSomething();

... จากนั้นควรเขียนเช่นนี้ คำสั่ง "ใช้วงเล็บเหลี่ยมเสมอ" ดูเหมือนว่า "ล้อมรอบการดำเนินการทางคณิตศาสตร์ด้วยวงเล็บเสมอ" ที่จะเปลี่ยนคำสั่งที่ง่ายมากa * b + c / dในการ((a * b) + (c / d))แนะนำความเป็นไปได้ที่จะหายไปใกล้ชิด (ความหายนะของ coder จำนวนมาก) และเพื่ออะไร ลำดับของการดำเนินการเป็นที่รู้จักกันดีและมีผลบังคับใช้ดังนั้นวงเล็บจึงซ้ำซ้อน คุณจะใช้วงเล็บในการบังคับใช้ลำดับการดำเนินการที่แตกต่างจากปกติจะใช้: a * (b+c) / dตัวอย่างเช่น เครื่องมือจัดฟันแบบบล็อกมีความคล้ายคลึงกัน ใช้พวกมันเพื่อกำหนดสิ่งที่คุณต้องการจะทำในกรณีที่มันแตกต่างจากค่าเริ่มต้นและไม่ใช่ "ชัดเจน" (ส่วนตัว แต่มักจะเป็นสามัญสำนึก)


3
@ AlexBrown ... ซึ่งเป็นจุดของฉัน กฎที่ระบุไว้ใน OP คือ "ใช้เครื่องหมายวงเล็บเสมอแม้แต่บรรทัดเดียว" ซึ่งฉันไม่เห็นด้วยกับเหตุผลที่ฉันระบุ วงเล็บจะช่วยในตัวอย่างโค้ดแรก ๆ เพราะโค้ดจะไม่ทำงานในลักษณะที่เว้าแหว่ง คุณต้องใช้วงเล็บเพื่อจับคู่elseกับอันแรกifแทนที่จะเป็นอันที่สอง โปรดลบ downvote
KeithS

5

เมื่อมองดูคำตอบที่ไม่มีใครระบุไว้อย่างชัดเจนว่าฉันฝึกนิสัยแบบไหนเล่าเรื่องราวของรหัสของคุณ:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

กลายเป็น:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

วางj++บนบรรทัดเดียวกันเป็นถ้าควรส่งสัญญาณไปยังคนอื่น"ฉันต้องการเพียงบล็อกนี้เพื่อเพิ่มที่เคยj " ของ coursethis เป็นเพียงที่คุ้มค่าหากสายเป็นที่ง่ายที่สุดเพราะการวางจุดพักที่นี่เป็นชานเมืองกล่าวถึงไม่ได้ไปจะมีประโยชน์มาก

ในความเป็นจริงฉันเพิ่งทำงานข้ามส่วนหนึ่งของ Twitter Storm API ที่มีรหัส 'เรียงลำดับ' ใน java นี่คือตัวอย่างโค้ดแบบ relvant ที่เรียกใช้โค้ดในหน้า 43 ของสไลด์โชว์นี้ :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

บล็อก for loop มีสองสิ่งในนั้นดังนั้นฉันจะไม่อินไลน์โค้ด ฉันไม่เคย :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

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

ถ้าคุณต้องการส่งโทรเลขถึงคนอื่นมันเป็นงานบรรทัดเดียวเท่านั้นที่ใช้ตัวดำเนินการแบบไตรภาคหรือ?:แบบฟอร์ม:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

แต่นี่คือการเขียนโค้ดกอล์ฟและฉันคิดว่าไม่ใช่แนวปฏิบัติที่ดี (มันไม่ชัดเจนสำหรับฉันถ้าฉันควรวาง j ++ ไว้ที่ด้านหนึ่งของ:หรือไม่) NBฉันไม่ได้เรียกผู้ประกอบการ ternary ใน C ++ ก่อนหน้านี้ผมไม่ทราบว่างานนี้แต่มันไม่อยู่

ในระยะสั้น:

ลองจินตนาการว่าผู้อ่านของคุณ (เช่นคนที่ดูแลรหัส) ตีความเรื่องราวของคุณอย่างไร (รหัส) ทำให้ชัดเจนสำหรับพวกเขาที่สุด หากคุณรู้ว่าผู้ฝึกหัด / นักศึกษากำลังทำสิ่งนี้อยู่บางทีอาจจะมาก{}ที่สุดเท่าที่จะทำได้เพื่อไม่ให้สับสน


1
(1) การวางคำสั่งบนบรรทัดเดียวกันทำให้อ่านน้อยลงไม่มาก โดยเฉพาะอย่างยิ่งความคิดง่ายๆเช่นการเพิ่มขึ้นจะถูกมองข้าม อย่าวางไว้บนบรรทัดใหม่ (2) แน่นอนว่าคุณสามารถใส่forลูปของคุณในบรรทัดเดียวทำไมจึงไม่ควรทำงานนี้ มันทำงานได้ด้วยเหตุผลเดียวกับที่คุณสามารถละเว้นวงเล็บปีกกา; ขึ้นบรรทัดใหม่ไม่สำคัญใน C ++ (3) ตัวอย่างโอเปอเรเตอร์ที่มีเงื่อนไขของคุณนอกเหนือจากความน่ากลัวแล้วคือ C ++ ที่ไม่ถูกต้อง
Konrad Rudolph

@ KonradRudolph ขอบคุณฉันเป็นสนิมเล็กน้อยที่ C ++ ฉันไม่เคยบอกว่า (1) อ่านได้มากขึ้น แต่มันจะส่งสัญญาณว่าส่วนของรหัสนั้นหมายถึงการออนไลน์หนึ่งบรรทัด (2) ความคิดเห็นของฉันมีมากขึ้นที่ฉันจะไม่สามารถอ่านและรู้ว่ามันใช้งานได้ไม่ว่าจะทั้งหมดหรือตามที่ตั้งใจไว้ มันเป็นตัวอย่างของสิ่งที่ไม่ควรทำด้วยเหตุผลนี้ (3) ขอบคุณฉันไม่ได้เขียน C ++ ในเวลานาน ฉันจะแก้ไขมันตอนนี้
Pureferret

2
นอกจากนี้การใส่มากกว่าหนึ่งนิพจน์ในหนึ่งบรรทัดทำให้ยากต่อการดีบักโค้ด คุณวางเบรกพอยต์กับการแสดงครั้งที่ 2 ในบรรทัดนั้นได้อย่างไร
Piotr Perak

5

เพราะเมื่อคุณมีสองข้อความที่ไม่มี{}จึงเป็นเรื่องง่ายที่จะพลาดปัญหา สมมติว่ารหัสมีลักษณะเช่นนี้

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

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

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

if (something) goto fail;

แต่ไม่ใช่อย่างใดอย่างหนึ่งดังต่อไปนี้

if (something)
    goto fail;

เผง อย่าเพิ่งขึ้นบรรทัดใหม่ + + (ไม่จำเป็นโดยสมบูรณ์) การเยื้องและคุณก้าวเท้าเลี่ยงปัญหานี้อย่างสมบูรณ์ว่าทุกคนมักจะนำขึ้นอย่างรวดเร็ว
Alexander - Reinstate Monica

4

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


4

wrt 6: ปลอดภัยกว่าเพราะการลบตัวชี้โมฆะนั้นเป็นแบบไม่ใช้งาน ดังนั้นหากคุณบังเอิญผ่านเส้นทางนั้นสองครั้งคุณจะไม่ทำให้หน่วยความจำเสียหายทำให้หน่วยความจำที่ว่างหรือถูกจัดสรรให้กับสิ่งอื่น

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

ในกรณีส่วนใหญ่คุณสามารถหลีกเลี่ยงความต้องการนี้ได้โดยใช้ auto_ptrs


6
หากคุณบังเอิญผ่านเส้นทางนั้นสองครั้งคุณจะพบข้อผิดพลาดในการเขียนโปรแกรม การตั้งตัวชี้เป็น null เพื่อทำให้ข้อผิดพลาดนี้เป็นอันตรายน้อยลงไม่สามารถแก้ปัญหาพื้นฐานได้
Pete Becker

1
เห็นด้วย แต่ฉันเคยเห็นสิ่งนี้แนะนำมาก่อนและฉันเชื่อว่ามันอยู่ในมาตรฐานการเขียนโปรแกรมมืออาชีพ ฉันแสดงความคิดเห็นเพิ่มเติมเกี่ยวกับสาเหตุที่อาจารย์ผู้โพสต์มากับมันแทนที่จะเป็นเรื่องดี
Tom Tanner

2
ติดตามสิ่งที่ Pete Becker พูดว่า: มันไม่ได้แก้ปัญหาพื้นฐาน แต่อาจปกปิดได้ (มีบางกรณีที่คุณจะตั้งค่าตัวชี้เป็นNULLหลังจากลบมันถ้าNULLเป็นค่าที่ถูกต้องสำหรับตัวชี้ที่จะมีในสถานการณ์เหล่านั้นเช่นตัวชี้ชี้ไปที่ค่าแคชและNULLบ่งชี้แคชที่ไม่ถูกต้อง แต่เมื่อคุณเห็นการตั้งค่าบางคน ตัวชี้ไปNULLเป็นบรรทัดสุดท้ายใน destructor คุณสงสัยว่าถ้าเขารู้ว่า C ++).
เจมส์ Kanze

4

ฉันชอบคำตอบที่ได้รับการยอมรับของ Luchian อันที่จริงฉันได้เรียนรู้วิธีที่ยากที่เขาพูดถูกดังนั้นฉันจึงใช้เหล็กดัดฟันเสมอแม้กระทั่งบล็อกแถวเดียว อย่างไรก็ตามส่วนตัวฉันให้ข้อยกเว้นเมื่อเขียนตัวกรองตามที่คุณอยู่ในตัวอย่างของคุณ นี้:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

ดูรกจัง มันแยก for loop และคำสั่ง if เป็นการกระทำที่แยกเมื่อความตั้งใจของคุณคือการกระทำเดียว: การนับจำนวนเต็มทั้งหมดหารด้วย 2 ในภาษาที่มีความหมายมากกว่านี้สามารถเขียนได้ดังนี้:

j = [1..100].filter(_%2 == 0).Count

ในภาษาที่ไม่มีการปิดตัวกรองไม่สามารถแสดงในคำสั่งเดียว แต่ต้องเป็นคำสั่งวนซ้ำตามด้วยคำสั่ง if อย่างไรก็ตามมันยังคงเป็นหนึ่งการกระทำในใจของโปรแกรมเมอร์และฉันเชื่อว่าควรจะสะท้อนให้เห็นในรหัสเช่น:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}

7
ฉันชอบวิธีที่ทุกคนจัดการเพื่อละเว้นfor (int i = 0; i < 100; i += 2);การโต้เถียงเกี่ยวกับการเยื้อง ;-) อาจมี bunfight แยกทั้งหมดที่เราสามารถมีวิธี "ดีที่สุด" เพื่อแสดงตรรกะ "สำหรับแต่ละiในช่วงที่แน่นอนด้วยคุณสมบัติบางอย่าง" ใน C ++ โดยไม่ต้องห่วงโดยใช้การรวมฝันร้ายบางส่วนของขั้นตอนวิธีการมาตรฐานและfilter_iterator / หรือ counting_iterator
Steve Jessop

1
นอกจากนี้ถ้าเรามีสิ่งนั้นเราก็ไม่เห็นด้วยกับวิธีการเยื้องคำเดียวที่เกิดขึ้น
Steve Jessop

2
@ Steve มันเป็นเพียงตัวอย่าง มีรูปแบบการใช้งานที่ถูกกฎหมายมากมาย เห็นได้ชัดว่าถ้าคุณต้องการนับจำนวนจาก 1 ถึง 100 ซึ่งหารด้วย 2 ทั้งหมดที่คุณต้องทำคือ 100/2
MikeFHay

2
แน่นอนฉันรู้ว่านั่นเป็นเหตุผลที่ฉันสรุปว่า "สำหรับแต่ละคนiในช่วงหนึ่งด้วยคุณสมบัติที่แน่นอน" เป็นเรื่องปกติที่ SO นั้นผู้คนจะเพิกเฉยต่อคำถามที่เกิดขึ้นจริงเพื่อสนับสนุนวิธีการที่แตกต่างไปจากตัวอย่างที่ให้ แต่การเยื้องเป็นสิ่งสำคัญดังนั้นเราจึงไม่ ;-)
Steve Jessop

4

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

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong

5
ตัวเลือกที่สองจะให้ผลผลิตรวบรวมข้อผิดพลาดหรือไม่เนื่องจากไม่เกี่ยวข้องกับelse if
Luchian Grigore

ปัญหาที่ไม่สามารถมองเห็นได้อย่างหนึ่งของ inline ก็คือ IDEs ส่วนใหญ่ที่เป็นค่าเริ่มต้นจะเปลี่ยนเป็นรูปแบบการเยื้อง
Honza Brabec

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

3

ถ้าคุณเป็นคอมไพเลอร์มันก็ไม่ได้สร้างความแตกต่างเลย ทั้งสองเหมือนกัน

แต่สำหรับโปรแกรมเมอร์คนแรกนั้นมีความชัดเจนอ่านง่ายและมีข้อผิดพลาดน้อยลง


2
นอกเหนือจากการเปิด{ในสายของตัวเองต่อไป
Rob

3

อีกตัวอย่างของการเพิ่มเครื่องหมายปีกกา เมื่อฉันค้นหาข้อผิดพลาดและพบรหัสดังกล่าว:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

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

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

เป็นผลให้ SetAnotherVariableUnconditionnaly ถูกดำเนินการเมื่อ SomeConditionIsMet () แต่คนที่แต่งตัวประหลาดอย่างรวดเร็วไม่ได้สังเกตเห็นเพราะทุกบรรทัดมีขนาดใกล้เคียงกันและแม้เมื่อเงื่อนไขการส่งคืนถูกเยื้องในแนวตั้ง

หากการส่งคืนแบบมีเงื่อนไขจัดรูปแบบดังนี้:

if (!SomeConditionIsMet())
{
    return;
}

มันชัดเจนมากและ Fast Coder จะค้นหาได้อย่างรวดเร็ว


หาก coder ที่รวดเร็วของคุณไม่สามารถรบกวนreturnคำสั่งที่เน้นไวยากรณ์ภายในเนื้อหาของฟังก์ชันก่อนที่จะเพิ่มบางสิ่งลงไปคุณไม่ควรปล่อยให้ coder ที่เร็วเข้าใกล้โค้ดของคุณ คุณจะไม่หยุดยั้งคนเช่นนี้จากการหมุนรอบโค้ดของคุณโดยการใส่เครื่องหมายวงเล็บ
cmaster - คืนสถานะโมนิกา

@cmaster เขาไม่ได้ทำงานกับเราอีกต่อไป อย่างไรก็ตามการเน้นไวยากรณ์เป็นสิ่งที่ดี แต่จำไว้ว่ามีบุคคลที่ไม่เห็นชัดเจน (ฉันเห็นแม้แต่โพสต์จากโปรแกรมเมอร์ตาบอดเมื่อปีที่แล้ว)
Artemix

2

ฉันคิดว่าอันแรกจะชัดเจนจากนั้นก็วินาที มันให้ความรู้สึกในการปิดคำสั่งโดยมีรหัสน้อยเมื่อรหัสที่ซับซ้อน{...}ช่วยได้มากแม้ว่ามันจะเป็นendifหรือbegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

1

เป็นการดีที่สุดที่จะตั้งค่าตัวชี้เป็น NULL เมื่อคุณทำเสร็จแล้ว

นี่คือตัวอย่างทำไม:

Class A ทำสิ่งต่อไปนี้:

  1. จัดสรรบล็อกของหน่วยความจำ
  2. จากนั้นอีกไม่นานมันจะลบบล็อคหน่วยความจำนี้ แต่ไม่ได้ตั้งค่าตัวชี้เป็น NULL

Class B ทำสิ่งต่อไปนี้

  1. จัดสรรหน่วยความจำ (และในกรณีนี้มันจะได้รับบล็อกหน่วยความจำเดียวกันที่ถูกลบโดยคลาส A. )

ณ จุดนี้ทั้งคลาส A และคลาส B มีพอยน์เตอร์ชี้ไปที่บล็อกหน่วยความจำเท่าที่คลาส A เกี่ยวข้องบล็อกหน่วยความจำนี้ไม่มีอยู่เพราะมันเสร็จสิ้นแล้ว

พิจารณาปัญหาต่อไปนี้:

เกิดอะไรขึ้นถ้ามีข้อผิดพลาดทางตรรกะใน Class A ซึ่งส่งผลให้มันเขียนไปยังหน่วยความจำที่ตอนนี้เป็นของ Class B?

ในอินสแตนซ์เฉพาะนี้คุณจะไม่ได้รับข้อผิดพลาดในการยกเว้นการเข้าถึงที่ไม่ถูกต้องเนื่องจากที่อยู่หน่วยความจำถูกต้องตามกฎหมายขณะที่คลาส A ทั้งหมดกำลังทำให้ข้อมูลคลาส B เสียหาย

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

หากคุณตั้งค่าตัวชี้หน่วยความจำที่ลบไปเป็น NULL คุณจะได้รับข้อผิดพลาดข้อยกเว้นทันทีที่เกิดข้อผิดพลาดเชิงตรรกะในคลาส A พยายามเขียนไปยังตัวชี้ NULL

หากคุณกังวลเกี่ยวกับข้อผิดพลาดทางตรรกะพร้อมการลบซ้ำสองครั้งเมื่อพอยน์เตอร์เป็น NULL เป็นครั้งที่สองให้เพิ่มการยืนยันสำหรับสิ่งนี้

นอกจากนี้: หากคุณกำลังจะลงคะแนนโปรดอธิบาย


2
หากมีข้อผิดพลาดทางตรรกะควรแก้ไขแทนที่จะปิดบังมัน
uınbɐɥs

@Barmar, OP บอกว่า ... 6. ตั้งค่าตัวชี้เป็น NULL หลังจากลบ - การป้องกันการลบสองครั้ง // ไม่เลว บางคนตอบว่าไม่ได้ตั้งไว้ที่ Null และฉันกำลังบอกว่าทำไมมันควรตั้งเป็น NULL ส่วนไหนของ 6. คำตอบของฉันในการตั้งค่า NULL ไม่เหมาะกับ 6?
John

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

1

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

elseรายละเอียดที่น่าสนใจประการแรกคือการที่ผู้เสนอส่วนใหญ่ของกฎที่เห็นด้วยกับการละเมิดมันในกรณีของ กล่าวอีกนัยหนึ่งพวกเขาไม่ต้องการตรวจสอบรหัสดังกล่าว:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

แต่หากพวกเขาเห็นมันพวกเขาอาจแนะนำให้เขียนแบบนั้น:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

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

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

รายละเอียดที่สาม (ซึ่งมักถูกลืมโดยฝ่ายตรงข้ามของกฎนั้น) เป็นลักษณะที่สับสนของคำแถลงว่าง ๆ ที่แสดงด้วยเครื่องหมายอัฒภาคเดี่ยว นักพัฒนาส่วนใหญ่ที่มีประสบการณ์บางอย่างสับสนในไม่ช้าก็เร็วโดยเซมิโคลอนหายไปเพียงอย่างเดียวหรือโดยคำสั่งที่ว่างเปล่าที่เขียนโดยใช้อัฒภาค การจัดฟันแบบสองปีกแทนที่จะใช้เครื่องหมายอัฒภาคเดียวจะทำให้มองเห็นได้ง่ายขึ้น


1

ฉันต้องยอมรับว่าไม่ได้ใช้{}สำหรับบรรทัดเดียวเสมอไป แต่เป็นการปฏิบัติที่ดี

  • สมมติว่าคุณเขียนโค้ดโดยไม่มีวงเล็บเหลี่ยมที่มีลักษณะดังนี้:

    สำหรับ (int i = 0; i <100; ++ i) สำหรับ (int j = 0; j <100; ++ j) DoSingleStuff ();

และหลังจากเวลาที่คุณต้องการเพิ่มในjวงอื่น ๆ และคุณเพียงแค่ทำอย่างนั้นโดยการจัดตำแหน่งและลืมที่จะเพิ่มวงเล็บ

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

    if (... ) {// SomeStuff ... {// เราไม่มีถ้าในขณะที่ ฯลฯ // SomeOtherStuff} // SomeMoreStuff}

  • วิธีที่สามที่จะใช้มันคล้ายกับที่สอง ไม่เพียง แต่จะทำให้กองสะอาด แต่เพื่อเปิดฟังก์ชั่นบางอย่าง หากคุณใช้mutexในฟังก์ชั่นที่ยาวนานมักจะดีกว่าที่จะล็อคและปลดล็อคก่อนเข้าถึงข้อมูลและหลังจากอ่าน / เขียนเสร็จแล้ว หมายเหตุวิธีนี้ใช้ถ้าคุณมีของคุณเองclassหรือstructกับconstructorและdestructorเพื่อล็อคหน่วยความจำ

  • อะไรที่มากกว่า:

    ถ้า (... ) ถ้า (... ) SomeStuff (); อื่น SomeOtherStuff (); // ไปที่สองถ้า, แต่ alligment แสดงว่ามันเป็นอันแรก ...

ทั้งหมดในทุกสิ่งฉันไม่สามารถพูดได้ว่าอะไรคือวิธีที่ดีที่สุดที่จะใช้{}สำหรับบรรทัดเดียว แต่มันไม่มีอะไรเลวร้ายที่จะทำเช่นนั้น

การแก้ไขที่สำคัญ หากคุณเขียนวงเล็บรหัสการรวบรวมสำหรับบรรทัดเดียวไม่ได้ทำอะไรเลย แต่ถ้ารหัสของคุณจะถูกตีความมันจะทำให้รหัสช้าลงเล็กน้อยมาก น้อยมาก


1

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

if (condition)
  statement;

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

if (condition)
{
  statement;
  statement;
}

จากนั้นจะสามารถมองเห็นได้อย่างชัดเจนว่าifข้อความใดควบคุมข้อความเดี่ยวและบรรทัดใดควบคุมหลายบรรทัด อย่างไรก็ตามหากข้อความหลายบรรทัดifถูกเขียนเป็น:

if (condition) {
  statement;
  statement;
}

โอกาสของคนที่พยายามขยายคำแถลงคำเดียวifโดยไม่เพิ่มการจัดฟันที่จำเป็นอาจสูงกว่ามาก

คำสั่งบรรทัดifคำสั่งบนบรรทัดถัดไปอาจเป็นปัญหาเช่นกันหาก codebase ใช้งานฟอร์มอย่างมีนัยสำคัญ

if (condition) statement;

การตั้งค่าของฉันเองคือการมีคำสั่งในบรรทัดของตัวเองโดยทั่วไปช่วยเพิ่มความชัดเจนยกเว้นในกรณีที่มีหลายifคำสั่งที่มีบล็อกควบคุมที่คล้ายกันเช่น

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

ในกรณีนี้โดยทั่วไปฉันจะนำหน้าและปฏิบัติตามifคำแถลงกลุ่มดังกล่าวพร้อมกับบรรทัดว่างเปล่าเพื่อแยกพวกเขาออกจากรหัสอื่น การมีข้อความมากมายที่เริ่มต้นด้วยifการเยื้องเดียวกันจะทำให้เห็นภาพชัดเจนว่ามีบางสิ่งผิดปกติ

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