การปรับเปลี่ยนพารามิเตอร์ขาเข้าเป็น antipattern หรือไม่? [ปิด]


60

ฉันกำลังเขียนโปรแกรมใน Java และฉันมักจะทำเช่นนี้:

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

ในที่ทำงานใหม่รูปแบบคือ:

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

สำหรับฉันมันส่งกลิ่นนิดหน่อยเพราะฉันคุ้นเคยกับการไม่เปลี่ยนพารามิเตอร์ขาเข้า การแก้ไขพารามิเตอร์ขาเข้านี้เป็น antipattern หรือไม่ มันมีข้อบกพร่องร้ายแรงบ้างไหม?


4
คำตอบที่ถูกต้องสำหรับสิ่งนี้คือภาษาเฉพาะเนื่องจากสิ่งที่พารามิเตอร์ pass-by-value เปลี่ยนเป็นตัวแปรท้องถิ่นได้อย่างมีประสิทธิภาพ
Blrfl

1
รูปแบบที่สองบางครั้งใช้เป็นการวัดประสิทธิภาพเพื่อนำวัตถุกลับมาใช้ใหม่ สมมติว่าooเป็นวัตถุที่ส่งผ่านไปยังวิธีการไม่ใช่ตัวชี้ที่ตั้งค่าเป็นวัตถุใหม่ เป็นกรณีนี้หรือไม่ หากนี่คือ Java มันอาจเป็นไปได้ว่าถ้ามันเป็น C ++ มันอาจไม่ใช่
Richard Tingle

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

2
รูปแบบที่สองมีประโยชน์เมื่อฟังก์ชันส่งคืนค่าเช่นกันสำหรับ ex, success / fail แต่ไม่มีอะไรผิดปกติกับมัน มันขึ้นอยู่กับตำแหน่งที่คุณต้องการสร้างวัตถุจริงๆ
GrandmasterB

12
ฉันจะไม่ตั้งชื่อฟังก์ชั่นที่ใช้รูปแบบที่แตกต่างกันสองแบบในลักษณะเดียวกัน
Casey

คำตอบ:


70

มันไม่ใช่ปฏิปักษ์ แต่เป็นวิธีปฏิบัติที่ไม่ดี

ความแตกต่างระหว่าง antipattern และการปฏิบัติที่ไม่ดีเพียงอยู่ที่นี่: นิยาม antipattern

รูปแบบการทำงานแบบใหม่ที่คุณแสดงคือการปฏิบัติที่ไม่ดีครั้งที่ร่องรอยหรือ pre-OOP อ้างอิงจาก Clean Code ของลุงบ็อบ

อาร์กิวเมนต์จะถูกตีความอย่างเป็นธรรมชาติมากที่สุดเป็นอินพุตไปยังฟังก์ชัน

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


7
ฉันไม่เห็นว่าทำไมคำจำกัดความที่คุณเชื่อมโยงกับตัดการมองว่าเป็นการต่อต้านแบบแผน
ซามูเอลเอ็ดวินวอร์ด

7
ฉันคิดว่ามันเป็นเพราะเหตุผลที่ว่า "เรากำลังบันทึก CPU / mem โดยไม่สร้างวัตถุใหม่เราควรรีไซเคิลที่เรามีและส่งเป็น ARG" ผิดอย่างเห็นได้ชัดสำหรับทุกคนที่มีพื้นหลัง OOP ที่รุนแรงซึ่งอาจ ไม่ได้เป็นรูปแบบที่เคย - ดังนั้นจึงไม่มีวิธีที่เราจะสามารถพิจารณารูปแบบการต่อต้านตามคำจำกัดความ ...
vaxquis

1
สิ่งที่เกี่ยวกับกรณีที่เมื่อพารามิเตอร์การป้อนข้อมูลเป็นชุดใหญ่ของวัตถุที่ต้องแก้ไข?
Steve Chambers

แม้ว่าฉันจะชอบตัวเลือกที่ 1 แต่ถ้าOtherObjectเป็นอินเทอร์เฟซล่ะ เฉพาะผู้โทร (หวังว่า) จะรู้ว่ามันเป็นรูปแบบที่ต้องการ
user949300

@ SteveChambers ฉันคิดว่าในกรณีนี้การออกแบบชั้นเรียนหรือวิธีการไม่ดีและควรได้รับการปรับโครงสร้างใหม่เพื่อหลีกเลี่ยง
piotr.wittchen

16

การอ้างอิงหนังสือที่มีชื่อเสียงของ Robert C. Martin "Clean Code":

ควรหลีกเลี่ยงอาร์กิวเมนต์เอาต์พุต

ฟังก์ชั่นควรมีอาร์กิวเมนต์จำนวนน้อย

รูปแบบที่สองละเมิดกฎทั้งสองโดยเฉพาะ "อาร์กิวเมนต์เอาท์พุท" หนึ่ง ในแง่นี้มันแย่กว่ารูปแบบแรก


1
ฉันจะบอกว่ามันเป็นการละเมิดข้อแรกข้อโต้แย้งมากมายหมายถึงรหัสที่ยุ่งเหยิง แต่ฉันคิดว่าข้อโต้แย้งสองข้อนั้นโอเคโดยสิ้นเชิง ฉันคิดว่ากฎเกี่ยวกับรหัสที่สะอาดหมายถึงถ้าคุณต้องการข้อมูลที่เข้า 5 หรือ 6 คุณต้องการทำมากเกินไปในวิธีเดียวดังนั้นคุณควร refactor เพื่อเพิ่มการอ่านรหัสและขอบเขต OOP
CsBalazsHungary

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

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

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

2
@emodendroket แต่เพื่อนก็ผู้ชาย!
Pierre Arlaud

15

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

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


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

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

JMonkey เป็นที่ที่ฉันได้เห็นสิ่งนี้บ่อยๆเพราะมันมักจะเป็นเรื่องสำคัญในการแสดง ฉันไม่เคยได้ยินชื่อ "JavaMonkey" มาก่อน
ริชาร์ด

3
@JAB: GC ของ JVM ได้รับการปรับแต่งเป็นพิเศษสำหรับกรณีของวัตถุชั่วคราว วัตถุที่มีอายุสั้นมากจำนวนมากมีความสำคัญต่อการสะสมและในหลาย ๆ กรณีการสร้างชั่วคราวทั้งหมดของคุณอาจถูกรวบรวมด้วยการย้ายตัวชี้เพียงครั้งเดียว
Phoshi

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

10

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


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

@CsBalazsHungary ฉันจะตอบว่าใช่ ตัดสินจากรหัสขนาดเล็กที่คุณให้
ร่าเริง

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

1
@CsBalazsHungary ในกรณีที่มีการโต้แย้งแบบดั้งเดิมฟังก์ชันจะไม่ทำงาน ดังนั้นคุณมีปัญหาที่เลวร้ายยิ่งกว่าการเปลี่ยนข้อโต้แย้ง
ร่าเริง

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

7

มันขึ้นอยู่กับภาษาจริงๆ

ใน Java รูปแบบที่สองอาจเป็น anti-pattern แต่บางภาษาจะปฏิบัติต่อพารามิเตอร์ที่ส่งผ่านต่างกัน ในอาดาและ VHDL สำหรับตัวอย่างเช่นแทนที่จะผ่านค่าหรือการอ้างอิงพารามิเตอร์สามารถมีโหมดin, หรือoutin out

เป็นข้อผิดพลาดในการแก้ไขinพารามิเตอร์หรืออ่านoutพารามิเตอร์ แต่การเปลี่ยนแปลงใด ๆ กับin outพารามิเตอร์จะถูกส่งกลับไปยังผู้เรียก

ดังนั้นทั้งสองรูปแบบใน Ada (เหล่านี้ยังเป็น VHDL ตามกฎหมาย)

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

และ

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

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


3

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

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

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

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

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

Java ไม่ค่อยดีในการจัดการกับเอาต์พุตจำนวนมาก - มันไม่สามารถมีพารามิเตอร์เอาต์พุตอย่างเดียวเช่นบางภาษาหรือมีค่าส่งคืนหลายค่าเช่นอื่น ๆ - แต่ถึงกระนั้นคุณยังสามารถใช้วัตถุส่งคืนได้

เหตุผลความมั่นคงค่อนข้างง่อย แต่น่าเศร้าที่มันเป็นเรื่องธรรมดาที่สุด

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

2

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

ข้อเสียของวิธีนี้คือ:

  1. ไม่สามารถใช้เป็นวิธีการจากโรงงานผู้โทรจะต้องทราบชนิดย่อยที่แน่นอนของOtherObjectความต้องการและสร้างไว้ล่วงหน้า
  2. MyObjectไม่สามารถใช้กับวัตถุที่ต้องใช้พารามิเตอร์ในตัวสร้างของพวกเขาหากพารามิเตอร์เหล่านั้นมาจาก OtherObjectต้อง constructable MyObjectโดยไม่ต้องรู้เกี่ยวกับ

คำตอบจะขึ้นอยู่กับประสบการณ์ของฉันใน c # ฉันหวังว่าตรรกะแปลเป็น Java


ฉันคิดว่าด้วยความรับผิดชอบที่คุณพูดถึงมันก็สร้างวงจรชีวิตของ "วัตถุที่มองเห็นได้ยากขึ้น" ในระบบวิธีการที่ซับซ้อนฉันต้องการแก้ไขวัตถุโดยตรงแทนที่จะเริ่มมองผ่านทุกวิธีที่เราผ่าน
CsBalazsHungary

นี่คือสิ่งที่ฉันคิด การพึ่งพาการฉีด
Lyndon White

ข้อดีอีกอย่างคือถ้า OtherObject เป็นนามธรรมหรืออินเทอร์เฟซ ฟังก์ชั่นไม่มีความคิดว่าจะสร้างประเภทใด แต่ผู้โทรอาจ
user949300

2

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

อย่างไรก็ตามถ้าชื่อเมธอดเป็นConvert(ซึ่งควรเป็นถ้าเราดูที่ส่วน "... ทำการแปลง") ฉันจะบอกว่าวิธีที่สองจะไม่สมเหตุสมผล คุณไม่แปลงค่าเป็นค่าอื่นที่มีอยู่แล้ว IMO

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