กระบวนทัศน์“ ทำสิ่งเดียว” เมื่อไรจะกลายเป็นอันตราย


21

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

รุ่น 1:

void printFile(const string & filePath) {
  fstream file(filePath, ios::in);
  string line;
  while (std::getline(file, line)) {
    cout << line << endl;
  }
}

ฉันรู้ว่าขอแนะนำให้ฟังก์ชั่นทำสิ่งหนึ่งที่ระดับหนึ่งของสิ่งที่เป็นนามธรรม สำหรับฉันแม้ว่าโค้ดด้านบนจะทำสิ่งหนึ่งและค่อนข้างปรมาณู

หนังสือบางเล่ม (เช่น Clean Code ของ Robert C. Martin) ดูเหมือนจะแนะนำให้แบ่งรหัสด้านบนออกเป็นฟังก์ชันแยกต่างหาก

รุ่น 2:

void printFile(const string & filePath) {
  fstream file(filePath, ios::in);
  printLines(file);
}

void printLines(fstream & file) {
  string line;
  while (std::getline(file, line)) {
    printLine(line);
  }
}

void printLine(const string & line) {
  cout << line << endl;
}

ฉันเข้าใจสิ่งที่พวกเขาต้องการบรรลุ (เปิดไฟล์ / อ่านบรรทัด / พิมพ์บรรทัด) แต่มันไม่ได้เกินความจริงหรือ?

รุ่นดั้งเดิมนั้นเรียบง่ายและในบางแง่มุมก็ทำสิ่งหนึ่งแล้ว - พิมพ์ไฟล์

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

ในกรณีนี้จะดีกว่าไหมหากมีรหัสในที่เดียว

กระบวนทัศน์ "สิ่งที่หนึ่ง" กลายเป็นอันตราย ณ จุดใด


13
การฝึกเขียนโปรแกรมแบบนี้ขึ้นอยู่กับกรณีเป็นกรณีไป ไม่เคยมีวิธีการเดียว
iammilind

1
@Alex - คำตอบที่ได้รับการยอมรับไม่มีอะไรเกี่ยวข้องกับคำถาม ฉันพบว่าแปลกจริงๆ
ChaosPandion

2
ฉันทราบว่าเวอร์ชันที่คุณปรับโครงสร้างใหม่นั้นกลับหัวกลับหางซึ่งทำให้อ่านไม่ได้ อ่านหนังสือลงไฟล์ที่คุณคาดหวังที่จะเห็นprintFile, และในที่สุดก็printLines printLine
Anthony Pegram

1
@Kev ฉันไม่เห็นด้วยอีกครั้งโดยเฉพาะอย่างยิ่งกับการจัดหมวดหมู่นั้น มันไม่โอ้อวดมันเป็นประเด็น! มันเป็น OP ที่บอกว่ารุ่นที่สองโดยเฉพาะอาจไม่สามารถอ่านได้ มันเป็น OP ที่อ้างถึง Clean Code โดยเฉพาะว่าเป็นแรงบันดาลใจให้กับเวอร์ชั่นที่สอง ความคิดเห็นของฉันเป็นหลักว่ารหัสสะอาดจะไม่ให้เขาเขียนรหัสแบบนั้น คำสั่งซื้อมีความสำคัญต่อการอ่านได้จริงคุณอ่านไฟล์เหมือนอ่านบทความในหนังสือพิมพ์รับรายละเอียดมากขึ้นเรื่อย ๆ จนกว่าคุณจะไม่สนใจ
Anthony Pegram

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

คำตอบ:


15

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

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

IMO ฟังก์ชั่นที่ประกอบด้วยโค้ดเพียงบรรทัดเดียวนั้นไม่ค่อยมีปัญหาเลย คุณprintLine()ไม่มีข้อได้เปรียบมากกว่าการใช้std::cout << line << '\n'1โดยตรง ถ้าฉันเห็นprintLine()ฉันต้องสมมติว่ามันทำในสิ่งที่ชื่อพูดหรือค้นหาและตรวจสอบ ถ้าผมเห็นฉันรู้ทันทีว่ามันไม่เพราะนี่เป็นวิธีที่เป็นที่ยอมรับของการแสดงผลเนื้อหาของสตริงสายไปstd::cout << line << '\n'std::cout

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

void copyLines(std::istream& is, std::ostream& os)
{
  std::string line;
  while( std::getline(is, line) );
    os << line << '\n';
  }
}

อัลกอริทึมดังกล่าวสามารถนำกลับมาใช้ในบริบทอื่นได้เช่นกัน

จากนั้นคุณสามารถใส่ทุกอย่างที่เฉพาะเจาะจงลงในเคสนี้ใช้ในฟังก์ชั่นที่เรียกอัลกอริทึมทั่วไปนี้:

void printFile(const std::string& filePath) {
  std::ifstream file(filePath.c_str());
  printLines(file, std::cout);
}

1 ทราบว่าผมใช้มากกว่า'\n' ควรจะเป็นทางเลือกเริ่มต้นสำหรับการแสดงผลขึ้นบรรทัดใหม่ , เป็นกรณีที่แปลกstd::endl'\n'std::endl


2
+1 - ฉันเห็นด้วยเป็นส่วนใหญ่ แต่ฉันคิดว่ามันมีอะไรมากกว่า "ความรู้สึกไส้" ปัญหาคือเมื่อคนตัดสิน "สิ่งหนึ่ง" โดยนับรายละเอียดการใช้งาน สำหรับฉันแล้วฟังก์ชั่นควรใช้ (และชื่ออธิบาย) สิ่งที่เป็นนามธรรมชัดเจน คุณไม่ควรตั้งชื่อฟังก์ชั่น "do_x_and_y" การนำไปใช้สามารถและควรทำสิ่งต่าง ๆ (ง่ายกว่า) - และสิ่งที่เรียบง่ายแต่ละอย่างนั้นอาจถูกจำแนกออกเป็นหลาย ๆ สิ่งที่ง่ายกว่าเป็นต้น มันเป็นเพียงแค่การแยกย่อยการทำงานด้วยกฎพิเศษ - ซึ่งแต่ละฟังก์ชั่น (และชื่อ) ควรอธิบายแนวคิด / ภารกิจ / สิ่งที่ชัดเจน
Steve314

@ Steve314: ฉันไม่ได้ระบุรายละเอียดการใช้งานเป็นไปได้ การคัดลอกบรรทัดจากสตรีมหนึ่งไปยังอีกสตรีมอย่างชัดเจนเป็นสิ่งที่เป็นนามธรรม หรือมันคืออะไร? และง่ายต่อการหลีกเลี่ยงdo_x_and_y()โดยการตั้งชื่อฟังก์ชั่นdo_everything()แทน ใช่นั่นเป็นตัวอย่างที่งี่เง่า แต่ก็แสดงให้เห็นว่ากฎนี้ล้มเหลวที่จะป้องกันตัวอย่างที่แย่ที่สุดของการออกแบบที่ไม่ดี IMO นี่คือการตัดสินใจความรู้สึกที่ดีเท่ากับหนึ่งที่ถูกกำหนดโดยอนุสัญญา ไม่อย่างนั้นถ้ามันเป็นเป้าหมายคุณก็สามารถสร้างเมตริกขึ้นมาได้ซึ่งคุณไม่สามารถทำได้
sbi

1
ฉันไม่ได้ตั้งใจจะโต้แย้ง - เพียงเพื่อแนะนำการเพิ่ม ฉันเดาว่าสิ่งที่ฉันลืมที่จะพูดคือจากคำถามการสลายตัวไปสู่printLineฯลฯ นั้นถูกต้อง - แต่ละอันนั้นเป็นนามธรรมเดียว - แต่นั่นไม่ได้หมายความว่าจำเป็น printFileมีอยู่แล้ว "สิ่งเดียว" แม้ว่าคุณสามารถแยกย่อยออกเป็น abstractions ระดับล่างที่แยกจากกันได้ แต่คุณไม่จำเป็นต้องแยกย่อยในทุกระดับของนามธรรม แต่ละฟังก์ชั่นจะต้องทำ "สิ่งเดียว" แต่ไม่ใช่ทุกสิ่งที่เป็นไปได้ "สิ่งเดียว" ที่จะต้องมีฟังก์ชั่น การย้ายความซับซ้อนมากเกินไปไปยังกราฟการโทรอาจเป็นปัญหาได้
Steve314

7

การมีฟังก์ชั่นทำได้เพียง "สิ่งเดียว" คือหนทางสู่การสิ้นสุดที่ต้องการสองประการไม่ใช่พระบัญญัติจากพระเจ้า:

  1. หากฟังก์ชั่นของคุณทำ "สิ่งเดียว" มันจะช่วยให้คุณหลีกเลี่ยงการทำซ้ำโค้ดและ API bloat เพราะคุณจะสามารถเขียนฟังก์ชั่นเพื่อทำสิ่งที่ซับซ้อนมากกว่าที่ทำแทนการระเบิดแบบ combinatorial ในระดับที่สูงขึ้น .

  2. การมีฟังก์ชั่นทำแค่ "สิ่งเดียว" อาจทำให้โค้ดอ่านง่ายขึ้น ขึ้นอยู่กับว่าคุณจะได้รับความชัดเจนและความสะดวกในการให้เหตุผลมากขึ้นหรือไม่โดยการแยกสิ่งต่าง ๆ ออกจากการสูญเสียความฟุ่มเฟื่อยทางอ้อมและค่าใช้จ่ายทางความคิดของโครงสร้างที่อนุญาตให้คุณแยกแยะสิ่งต่างๆ

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

หากคุณต้องการควบคุมสิ่งที่เป็นนามธรรมในระดับที่ต่ำกว่าและจบลงด้วยการทำซ้ำอย่างละเอียดและการระเบิดแบบ combinatorial (เช่นprintLinesสำหรับชื่อไฟล์และแยกจากกันอย่างสมบูรณ์printLinesสำหรับfstreamวัตถุprintLinesคอนโซลและprintLinesไฟล์) printLinesทำอะไรมากกว่าหนึ่งอย่างในระดับ ของสิ่งที่เป็นนามธรรมที่คุณสนใจ


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

@ ส่วนบุคคล Nexus: ฉันค่อนข้างเห็นด้วยกับปัญหาการทดสอบ แต่ IMHO มันโง่ที่จะทดสอบรายละเอียดการใช้งาน สำหรับฉันการทดสอบหน่วยควรทดสอบ "สิ่งเดียว" ตามที่กำหนดไว้ในคำตอบของฉัน สิ่งที่ละเอียดยิ่งขึ้นนั้นทำให้การทดสอบของคุณเปราะ (เนื่องจากการเปลี่ยนแปลงรายละเอียดการใช้งานจะต้องมีการทดสอบของคุณเปลี่ยน) และรหัสของคุณ verbose ทางอ้อม ฯลฯ (เพราะคุณจะเพิ่มทางอ้อมเพื่อรองรับการทดสอบ)
dsimcha

6

ในระดับนี้มันไม่สำคัญ การใช้งานฟังก์ชั่นเดียวนั้นชัดเจนและเข้าใจได้อย่างสมบูรณ์ อย่างไรก็ตามการเพิ่มความซับซ้อนอีกเล็กน้อยทำให้น่าสนใจมากที่จะแยกการวนซ้ำออกจากการกระทำ ตัวอย่างเช่นสมมติว่าคุณต้องการพิมพ์บรรทัดจากชุดไฟล์ที่ระบุโดยรูปแบบเช่น "* .txt" จากนั้นฉันจะแยกการทำซ้ำออกจากแอ็คชัน:

printLines(FileSet files) {
   files.each({ 
       file -> file.eachLine({ 
           line -> printLine(line); 
       })
   })
}

ตอนนี้การวนซ้ำไฟล์สามารถทดสอบแยกกันได้

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


4
ฉันคิดว่าคุณถูกจับ หากเราต้องการความคิดเห็นเพื่ออธิบายบรรทัดมันก็ถึงเวลาที่จะแยกวิธี
Roger CS Wernersson

5

แตกวิธีเมื่อคุณรู้สึกว่าจำเป็นต้องมีความคิดเห็นเพื่ออธิบายสิ่งต่าง ๆ

เขียนวิธีการที่ทำในสิ่งที่ชื่อของพวกเขาพูดอย่างชัดเจนหรือบอกเล่าเรื่องราวด้วยการเรียกวิธีการตั้งชื่ออย่างชาญฉลาด


3

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

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

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

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


2
ฉันตอบคำถามนี้เพราะฉันชอบตรรกะแม้ว่าฉันจะไม่เห็นด้วยก็ตาม! จัดเตรียมโครงสร้างบนพื้นฐานของความคิดที่ซับซ้อนเกี่ยวกับสิ่งที่อาจเกิดขึ้นในอนาคตว่ามีประสิทธิผล รหัสตัวประกอบเมื่อคุณต้องการ อย่าทำสิ่งที่เป็นนามธรรมจนกว่าคุณจะต้อง รหัสสมัยใหม่เต็มไปด้วยคนพยายามที่จะปฏิบัติตามกฎทาสแทนเพียงเขียนรหัสที่ผลงานและการปรับตัวมันอย่างไม่เต็มใจ โปรแกรมเมอร์ที่ดีคือขี้เกียจ
Yttrill

ขอบคุณสำหรับความคิดเห็น หมายเหตุฉันไม่ได้เรียกร้องให้งดออกเสียงก่อนกำหนดเพียงแค่แบ่งการดำเนินการทางตรรกะเพื่อให้ง่ายขึ้นในภายหลัง
Michael Brown

2

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

ฟังก์ชั่นยูทิลิตี้ทั่วไป:

void printLine(ostream& output, const string & line) { 
    output << line << endl; 
} 

void printLines(istream& input, ostream& output) { 
    string line; 
    while (getline(input, line)) {
        printLine(output, line); 
    } 
} 

ฟังก์ชั่นเฉพาะโดเมน:

void printFile(const string & filePath, ostream& output = std::cout) { 
    fstream file(filePath, ios::in); 
    printLines(file, output); 
} 

ตอนนี้printLinesและprintLineสามารถทำงานได้ไม่เพียงfstreamแต่กับสตรีมใด ๆ


2
ฉันไม่เห็นด้วย. ที่printLine()ฟังก์ชั่นไม่มีค่า ดูคำตอบของฉัน
sbi

1
ทีนี้ถ้าเราเก็บ printLine () จากนั้นเราสามารถเพิ่มเครื่องมือตกแต่งซึ่งเพิ่มหมายเลขบรรทัดหรือการระบายสีไวยากรณ์ ต้องบอกว่าฉันจะไม่แยกวิธีการเหล่านั้นจนกว่าฉันจะพบเหตุผล
Roger CS Wernersson

2

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

คำตอบที่แท้จริงของคำถามจึงต้องมีความสามารถที่ดีในการ "มองเห็น" อนาคตเช่น:

  • ตอนนี้ฉันจำเป็นต้องทำAและB
  • ความน่าจะเป็นคืออะไรในอนาคตอันใกล้ฉันจะต้องทำเช่นนั้นA-และB+(เช่นสิ่งที่ดูเหมือน A และ B แต่แตกต่างกันเล็กน้อย)
  • อะไรคือสิ่งที่น่าจะเป็นในอนาคตที่ไกลมากขึ้นที่ A + จะกลายเป็นA*หรือA*-?

หากความน่าจะเป็นนั้นค่อนข้างสูงมันจะเป็นโอกาสที่ดีถ้าในขณะที่คิดเกี่ยวกับ A และ B- ฉันก็คิดถึงความหลากหลายที่เป็นไปได้ของพวกเขาด้วยดังนั้นเพื่อแยกส่วนที่พบบ่อยออกไป

หากความน่าจะเป็นนั้นต่ำมาก (ตัวแปรใด ๆ ก็ตามที่Aไม่มีความหมายมากกว่าAตัวของมันเอง) ให้ศึกษาวิธีการสลายตัว A ต่อไปจะทำให้เสียเวลามากขึ้น

เป็นตัวอย่างให้ฉันบอกคุณเรื่องจริง:

ในช่วงชีวิตที่ผ่านมาของฉันเป็นครูผมค้นพบว่า -ON มากที่สุดของ projects- นักเรียนจริงทั้งหมดของพวกเขาให้การทำงานของตัวเองเพื่อคำนวณความยาวของสตริง C ที่

หลังจากการตรวจสอบฉันพบว่าเป็นปัญหาบ่อยครั้งนักเรียนทุกคนมาถึงแนวคิดที่จะใช้ฟังก์ชั่นสำหรับสิ่งนั้น หลังจากบอกพวกเขาว่ามีฟังก์ชั่นห้องสมุดสำหรับเรื่องนั้น ( strlen) หลายคนตอบว่าเนื่องจากปัญหานั้นง่ายและไม่สำคัญมันจึงมีประสิทธิภาพมากกว่าสำหรับพวกเขาที่เขียนฟังก์ชั่นของตัวเอง (รหัส 2 บรรทัด) มากกว่าการค้นหาคู่มือห้องสมุด C (เป็นปี 1984 ลืมเว็บและ google!) ตามลำดับตัวอักษรที่เข้มงวดเพื่อดูว่ามีฟังก์ชั่นที่พร้อมใช้งานหรือไม่

นี่คือตัวอย่างที่กระบวนทัศน์ "ไม่ต้องพลิกโฉมพวงมาลัย" ก็อาจเป็นอันตรายได้หากไม่มีแคตตาล็อกล้อที่มีประสิทธิภาพ!


2

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

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

ที่ดีและฟังก์ชั่นเชิงเส้นค่อนข้างเล็ก ๆ น้อย ๆ ของคุณก็จะสิ้นสุดลงในระเบียบที่ซับซ้อนที่ขอร้องที่จะ splitted เข้าสู่ฟังก์ชั่นที่แยกต่างหาก


2

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

จากโพสต์ต้นฉบับ

void printLine(const string & line) {
  cout << line << endl;
}

หากคุณคล่องแคล่วพอคุณอาจสังเกตเห็นว่า printLine ยังคงทำสองสิ่ง: เขียนบรรทัดลงใน cout และเพิ่มอักขระ "end line" บางคนอาจต้องการจัดการกับสิ่งนั้นโดยการสร้างฟังก์ชั่นใหม่:

void printLine(const string & line) {
  reallyPrintLine(line);
  addEndLine();
}

void reallyPrintLine(const string & line) {
  cout << line;
}

void addEndLine() {
  cout << endl;
}

โอ้ไม่ตอนนี้เราได้ทำให้ปัญหาแย่ลงไปอีก! ตอนนี้มันก็ยิ่งเป็นไปได้ที่ printLine จะทำสองสิ่ง !!! 1! มันไม่ได้ทำให้เกิดความโง่เขลามากนักในการสร้าง "การทำงานรอบ ๆ " ที่ไร้สาระที่สุดซึ่งสามารถจินตนาการได้เพียงเพื่อกำจัดปัญหาที่หลีกเลี่ยงไม่ได้ที่การพิมพ์บรรทัดประกอบด้วยการพิมพ์บรรทัดเองและเพิ่มอักขระสิ้นสุดบรรทัด

void printLine(const string & line) {
  for (int i=0; i<2; i++)
    reallyPrintLine(line, i);
}

void reallyPrintLine(const string & line, int action) {
  cout << (action==0?line:endl);
}

1

คำตอบสั้น ๆ ... มันขึ้นอยู่กับ

คิดเกี่ยวกับสิ่งนี้: ถ้าในอนาคตคุณจะไม่ต้องการพิมพ์เฉพาะเอาต์พุตมาตรฐาน แต่เป็นไฟล์

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

อย่างไรก็ตามหากคุณมั่นใจว่าคุณต้องการเพียงแค่เอาต์พุตในคอนโซลมันก็ไม่สมเหตุสมผลนัก การเขียน "กระดาษห่อ" มากกว่าcout <<ดูเหมือนจะไร้ประโยชน์


1
แต่การพูดอย่างเคร่งครัดฟังก์ชัน printLine ไม่ใช่ระดับที่เป็นนามธรรมของการทำซ้ำข้ามบรรทัดหรือไม่

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

1

เหตุผลทั้งหมดที่มีหนังสือที่อุทิศบทเพื่อคุณธรรมของ "ทำสิ่งหนึ่ง" คือยังมีนักพัฒนาออกมีที่เขียนฟังก์ชั่นที่มีความยาว 4 หน้าและรังเงื่อนไข 6 ระดับ หากรหัสของคุณเรียบง่ายและชัดเจนว่าคุณทำถูกแล้ว


0

ตามที่ผู้โพสต์คนอื่น ๆ ให้ความเห็นการทำสิ่งหนึ่งเป็นเรื่องของขนาด

ฉันขอแนะนำว่าแนวคิด One Thing คือการหยุดคนเขียนโค้ดด้วยผลข้างเคียง นี่คือสุดขั้วโดยการมีเพศสัมพันธ์ตามลำดับที่วิธีการจะต้องมีการเรียกในลำดับเฉพาะเพื่อให้ได้ผลลัพธ์ 'ถูกต้อง'

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