การใช้พารามิเตอร์บูลีนเป็นสิ่งผิดปกติหรือไม่


194

ฉันได้เห็นการฝึกฝนเป็นครั้งคราวว่า "รู้สึก" ผิด แต่ฉันไม่สามารถพูดได้ชัดเจนว่ามีอะไรผิดปกติ หรืออาจเป็นเพียงความอคติของฉัน ไปที่นี่:

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

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


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

28
Martin Fowler มีบทความเกี่ยวกับเรื่องนี้: martinfowler.com/bliki/FlagArgument.html
Christoffer Hammarström


@ ChristofferHammarströmลิงค์ที่ดี ฉันจะรวมไว้ในคำตอบของฉันตามที่อธิบายในรายละเอียดมากความคิดเดียวกันกับคำอธิบายของฉัน
อเล็กซ์

1
ฉันไม่เคยเห็นด้วยกับสิ่งที่นิคจะต้องพูด แต่ในกรณีนี้ผมเห็นด้วย 100%: อย่าใช้พารามิเตอร์บูล
Marjan Venema

คำตอบ:


107

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

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

ฟังก์ชั่นที่เหนียวแน่นคืออะไร?

มันเป็นฟังก์ชั่นที่ทำสิ่งหนึ่งและสิ่งหนึ่งเท่านั้น

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

มันจะเป็นการดีกว่าที่จะแยกข้อกังวลของการควบคุมการเข้าถึงออกจากข้อกังวลของภารกิจการกระทำหรือคำสั่ง

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

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

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

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

Ref: Cohesion (วิทยาการคอมพิวเตอร์)


Rob คุณจะให้คำอธิบายว่า Cohesion คืออะไรและใช้ได้อย่างไร?
เรย์

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

1
คำอธิบายที่ดีมากของการทำงานร่วมกันและมันเป็นแอพพลิเคชั่น นี่ควรได้รับคะแนนเสียงเพิ่มขึ้นจริงๆ และฉันก็เห็นด้วยกับปัญหาด้านความปลอดภัยเช่นกัน ... แม้ว่าพวกเขาจะเป็นวิธีการส่วนตัวทั้งหมดมันเป็นช่องโหว่ที่มีโอกาสน้อยกว่า
Ray

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

"unmaintainable"
aaa90210

149

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

แต่ปัญหานี้สามารถแก้ไขได้ง่ายโดยไม่ต้องเปลี่ยนวิธีการพื้นฐาน:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

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

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

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

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


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

33
+1 ฉันใช้ enums สำหรับสิ่งนี้ให้มากที่สุด ผมเคยเห็นฟังก์ชั่นที่พิเศษพารามิเตอร์ได้รับการเพิ่มในภายหลังและบริการโทรเริ่มต้นให้มีลักษณะเหมือนbool DoSomething( myObject, false, false, true, false )เป็นไปไม่ได้ที่จะเข้าใจว่าอาร์กิวเมนต์บูลีนพิเศษนั้นมีความหมายอย่างไรในขณะที่ค่า enum ที่ตั้งชื่อตามความหมายมันเป็นเรื่องง่าย
แกรมเพอร์โรว์

17
โอ้มนุษย์ในที่สุดก็เป็นตัวอย่างที่ดีของวิธีการ FrobnicateBackwards เคยค้นหาสิ่งนี้ตลอดไป
Alex Pritchard

38

สิ่งนี้ไม่ได้ผิดปกติ แต่สามารถแสดงกลิ่นรหัสได้

สถานการณ์พื้นฐานที่ควรหลีกเลี่ยงเกี่ยวกับพารามิเตอร์บูลีนคือ:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

จากนั้นเมื่อโทรคุณมักจะโทรfoo(false)และfoo(true)ขึ้นอยู่กับพฤติกรรมที่แน่นอนที่คุณต้องการ

นี่เป็นปัญหาจริงๆเพราะเป็นกรณีของการติดต่อที่ไม่ดี คุณกำลังสร้างการพึ่งพาระหว่างวิธีที่ไม่จำเป็นจริงๆ

สิ่งที่คุณควรทำในกรณีนี้คือการออกจากdoThisและdoThatเป็นวิธีการแยกและสาธารณะแล้วทำ:

doThis();
doThat();

หรือ

doThis();

วิธีนี้ทำให้คุณตัดสินใจถูกต้องไปยังผู้เรียก (เหมือนกับว่าคุณผ่านพารามิเตอร์บูลีน) โดยไม่ต้องสร้างการเชื่อมต่อ

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

นี่เป็นเพียงตัวอย่างหนึ่งของวิธีแก้ปัญหานี้ตามตัวอย่างที่ฉันเขียน มีอีกหลายกรณีที่จำเป็นต้องใช้แนวทางที่แตกต่าง

มีบทความที่ดีจาก Martin Fowlerอธิบายรายละเอียดเพิ่มเติมในความคิดเดียวกัน

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


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

24
มีหลายสถานการณ์ที่if (flag) doThat()ข้างในfoo()นั้นถูกกฎหมาย การผลักดันการตัดสินใจเกี่ยวกับการกล่าวอ้างdoThat()ที่จะทำซ้ำทุกกองกำลังโทรที่จะต้องถูกลบทิ้งหากภายหลังพบสำหรับวิธีการบางพฤติกรรมที่ยังมีความต้องการที่จะเรียกflag doTheOther()ฉันค่อนข้างจะวางการพึ่งพาระหว่างวิธีการในชั้นเรียนเดียวกันมากกว่าที่จะต้องไปโทรหาผู้โทรทั้งหมดในภายหลัง
Blrfl

1
@Blrfl: ใช่ฉันคิดว่ายิ่ง refactorings ตรงไปตรงมาอาจจะสร้างdoOneและdoBothวิธีการ (สำหรับกรณีที่เป็นเท็จและจริงตามลำดับ) หรือใช้ประเภท enum แยกตามที่แนะนำโดย James Youngman
hugomg

@missingno: คุณยังคงมีปัญหาในการส่งรหัสซ้ำซ้อนออกไปยังผู้โทรเพื่อตัดสินใจdoOne()หรือdoBoth()ตัดสินใจ รูทีนย่อย / ฟังก์ชัน / เมธอดมีอาร์กิวเมนต์เพื่อให้สามารถเปลี่ยนแปลงพฤติกรรมของพวกเขาได้ การใช้ enum สำหรับเงื่อนไขบูลีนอย่างแท้จริงฟังดูคล้ายกับการทำซ้ำตัวเองถ้าชื่อของอาร์กิวเมนต์นั้นอธิบายสิ่งที่มันทำอยู่แล้ว
Blrfl

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

29

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

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


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

13
คำตอบของคุณทำให้ฉันนึกถึงคำพูดที่ฉันไม่สามารถจำแหล่งที่มาของ - "ถ้าฟังก์ชั่นของคุณมี 17 พารามิเตอร์คุณอาจหายไปหนึ่ง"
Joris Timmermans

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

15

ฉันคิดว่าบทความโค้ด Clean Robert C Martins ระบุว่าคุณควรกำจัดอาร์กิวเมนต์บูลีนที่เป็นไปได้เนื่องจากแสดงวิธีการที่ทำมากกว่าหนึ่งสิ่ง วิธีการควรทำสิ่งหนึ่งและสิ่งเดียวที่ฉันคิดว่าเป็นหนึ่งในคำขวัญของเขา


@dreza คุณหมายถึงกฎหมาย Curlys
MattDavey

แน่นอนว่าด้วยประสบการณ์ที่คุณควรรู้เมื่อใดที่จะไม่ต้องโต้แย้ง
gnasher729

8

ฉันคิดว่าสิ่งที่สำคัญที่สุดที่นี่คือการปฏิบัติ

เมื่อบูลีนพิจารณาพฤติกรรมทั้งหมดเพียงแค่สร้างเมธอดที่สอง

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

ตัวอย่างเช่น:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

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


ฉันใช้อาร์กิวเมนต์บูลีนเพื่อควบคุมพฤติกรรมในวิธีส่วนตัวเท่านั้น (ดังที่แสดงไว้ที่นี่) แต่ปัญหา: หากว่า dufus บางคนตัดสินใจที่จะเพิ่มทัศนวิสัยFooInternalในอนาคตแล้วอะไรล่ะ?
ADTC

จริงๆแล้วฉันจะไปอีกเส้นทางหนึ่ง รหัสภายใน FooInternal ควรแบ่งออกเป็น 4 ชิ้น: 2 ฟังก์ชันเพื่อจัดการกับกรณีจริง / เท็จบูลีนหนึ่งรายการสำหรับงานที่เกิดขึ้นก่อนหน้านี้และอีกหนึ่งรายการสำหรับงานที่เกิดขึ้นหลังจากนั้น จากนั้นคุณFoo1จะกลายเป็น: { doWork(); HandleTrueCase(); doMoreWork() }. ในอุดมคติแล้วฟังก์ชันdoWorkและdoMoreWorkแต่ละฟังก์ชันจะแบ่งออกเป็นชิ้น ๆ ที่มีความหมาย (หนึ่งชิ้นหรือมากกว่า) ของการกระทำที่ไม่ต่อเนื่อง (เช่นเป็นฟังก์ชั่นแยก) ไม่ใช่แค่สองฟังก์ชั่นเพื่อการแยก
jpaugh

7

ฉันชอบวิธีการปรับแต่งพฤติกรรมผ่านวิธีการสร้างที่ส่งคืนอินสแตนซ์ที่ไม่เปลี่ยนรูป นี่คือวิธีที่GuavaSplitterใช้:

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

ประโยชน์ของสิ่งนี้คือ:

  • การอ่านที่เหนือกว่า
  • การแยกการกำหนดค่ากับวิธีการดำเนินการที่ชัดเจน
  • ส่งเสริมการทำงานร่วมกันโดยบังคับให้คุณคิดเกี่ยวกับสิ่งที่เป็นสิ่งที่ควรและไม่ควรทำ Splitterในกรณีนี้มันเป็น คุณไม่เคยใส่someVaguelyStringRelatedOperation(List<Entity> myEntities)คลาสที่เรียกว่าSplitterแต่คุณคิดว่าจะใส่มันเป็นวิธีการคงที่ในStringUtilsชั้นเรียน
  • อินสแตนซ์ถูกกำหนดค่าไว้ล่วงหน้าดังนั้นจึงพร้อมใช้งานแบบพึ่งพาได้ ลูกค้าไม่จำเป็นต้องกังวลเกี่ยวกับว่าจะผ่านtrueหรือfalseวิธีการเพื่อให้ได้พฤติกรรมที่ถูกต้อง

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

ฉันชอบความคิดในการแยกการกำหนดค่าและวิธีการแบบแอกตัน!
Sher10ck

ลิงก์ไปยังห้องสมุด Guava เสีย
Josh Noe

4

แน่นอนกลิ่นรหัส ถ้ามันไม่ได้ละเมิดหลักการ Single รับผิดชอบก็อาจละเมิดบอกไม่ถาม พิจารณา:

หากปรากฏว่าไม่ละเมิดหลักการสองข้อใดข้อหนึ่งคุณควรใช้ enum ธงบูลีนเป็นเทียบเท่าบูลของหมายเลขมายากล ทำให้ความรู้สึกมากที่สุดเท่าที่foo(false) bar(42)Enums อาจมีประโยชน์สำหรับรูปแบบกลยุทธ์และมีความยืดหยุ่นในการให้คุณเพิ่มกลยุทธ์อื่น (อย่าลืมตั้งชื่อให้เหมาะสม )

ตัวอย่างเฉพาะของคุณโดยเฉพาะอย่างยิ่งทำให้ฉันรำคาญ เหตุใดสถานะนี้จึงผ่านหลายวิธี นี้เสียงเหมือนคุณต้องแยกพารามิเตอร์ของคุณลงใน subclasses


4

TL; DR: อย่าใช้อาร์กิวเมนต์บูลีน

ดูด้านล่างทำไมพวกเขาถึงไม่ดีและวิธีการแทนที่พวกเขา (ในหน้าตัวหนา)


อาร์กิวเมนต์บูลีนอ่านได้ยากมากและยากต่อการบำรุงรักษา ปัญหาหลักคือวัตถุประสงค์โดยทั่วไปจะชัดเจนเมื่อคุณอ่านวิธีการที่มีการตั้งชื่ออาร์กิวเมนต์ อย่างไรก็ตามโดยทั่วไปการตั้งชื่อพารามิเตอร์ไม่จำเป็นต้องใช้ในภาษาส่วนใหญ่ ดังนั้นคุณจะมีรูปแบบการต่อต้านเหมือนRSACryptoServiceProvider#encrypt(Byte[], Boolean)ที่พารามิเตอร์บูลีนเป็นตัวกำหนดชนิดของการเข้ารหัสที่จะใช้ในฟังก์ชั่น

ดังนั้นคุณจะได้รับสายเช่น:

rsaProvider.encrypt(data, true);

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

rsaProvider.encrypt(data, 1);

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

วิธีที่ดีที่สุดในการแก้ปัญหานี้คือการใช้การแจงนับ หากคุณต้องผ่าน Enum RSAPaddingด้วยค่าสองค่า: OAEPหรือPKCS1_V1_5จากนั้นคุณจะสามารถอ่านรหัสได้ทันที:

rsaProvider.encrypt(data, RSAPadding.OAEP);

บูลีนมีค่าได้สองค่าเท่านั้น ซึ่งหมายความว่าหากคุณมีตัวเลือกที่สามคุณจะต้องปรับโครงสร้างลายเซ็นของคุณใหม่ โดยทั่วไปแล้วสิ่งนี้ไม่สามารถทำได้อย่างง่ายดายหากปัญหาความเข้ากันได้แบบย้อนหลังเป็นสิ่งที่ต้องทำดังนั้นคุณต้องขยายคลาสสาธารณะด้วยวิธีสาธารณะอื่น นี่คือสิ่งที่ Microsoft ทำในที่สุดเมื่อพวกเขาแนะนำRSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)ที่พวกเขาใช้การแจงนับ (หรืออย่างน้อยคลาสที่เลียนแบบการแจงนับ) แทนที่จะเป็นบูลีน

มันอาจจะง่ายกว่าในการใช้วัตถุหรือส่วนต่อประสานแบบเต็มเป็นพารามิเตอร์ในกรณีที่พารามิเตอร์นั้นจำเป็นต้องมีการกำหนดพารามิเตอร์ ในตัวอย่างข้างต้น OAEP การขยายตัวเองสามารถกำหนดพารามิเตอร์ด้วยค่าแฮชที่จะใช้ภายใน โปรดทราบว่าขณะนี้มีแฮชอัลกอริธึม 6 SHA-2 และอัลกอริทึมแฮช 4 SHA-3 ดังนั้นจำนวนค่าการแจงนับอาจระเบิดได้หากคุณใช้การแจงนับครั้งเดียวแทนที่จะเป็นพารามิเตอร์ (นี่อาจเป็นสิ่งต่อไปที่ Microsoft จะหา )


พารามิเตอร์บูลีนอาจระบุว่าวิธีหรือคลาสไม่ได้รับการออกแบบมาอย่างดี เช่นเดียวกับตัวอย่างด้านบน: ไลบรารีการเข้ารหัสใด ๆ ที่นอกเหนือจาก. NET ไม่ได้ใช้แฟล็ก padding ในลายเซ็นเมธอดเลย


ผู้เชี่ยวชาญด้านซอฟต์แวร์เกือบทั้งหมดที่ฉันชอบเตือนต่ออาร์กิวเมนต์บูลีน ตัวอย่างเช่น Joshua Bloch เตือนพวกเขาในหนังสือ "Java ที่มีประสิทธิภาพ" โดยทั่วไปแล้วไม่ควรใช้ คุณสามารถโต้แย้งว่าพวกเขาสามารถนำมาใช้หากกรณีที่มีหนึ่งพารามิเตอร์ที่ง่ายต่อการเข้าใจ แต่ถึงอย่างนั้น: Bit.set(boolean)น่าจะดำเนินการได้ดีกว่าการใช้สองวิธี : และBit.set()Bit.unset()


หากคุณไม่สามารถปรับโครงสร้างโค้ดได้โดยตรงคุณสามารถกำหนดค่าคงที่ให้อย่างน้อยทำให้อ่านง่ายขึ้น:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

สามารถอ่านได้มากกว่า:

cipher.init(key, true);

แม้ว่าคุณจะต้องการ:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

แทน.


3

ฉันประหลาดใจที่ไม่มีใครได้กล่าวถึงชื่อพารามิเตอร์

ปัญหาที่ฉันเห็นด้วยบูลีน - ธงคือพวกเขาเจ็บอ่านง่าย ตัวอย่างเช่นสิ่งที่ไม่trueอยู่ใน

myObject.UpdateTimestamps(true);

ทำ? ฉันไม่รู้. แต่สิ่งที่เกี่ยวกับ:

myObject.UpdateTimestamps(saveChanges: true);

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


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

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

1
คำถามไม่ได้เกี่ยวกับสิ่งที่เป็นที่นิยมของพฤติกรรมที่กำหนดธงว่ามันเป็นเช่นนั้นธงกลิ่นและถ้าเป็นเช่นนั้นทำไม
Ray

2
@ เรย์: ฉันไม่เห็นความแตกต่างระหว่างสองคำถาม ในภาษาที่คุณสามารถบังคับใช้การใช้พารามิเตอร์ที่มีชื่อหรือเมื่อคุณสามารถมั่นใจได้ว่าการใช้พารามิเตอร์ที่กำหนดชื่อจะถูกใช้เสมอ(เช่นวิธีการส่วนตัว)พารามิเตอร์บูลีนจะใช้ได้ หากภาษาที่ตั้งค่าไว้ไม่สามารถบังคับใช้ภาษา (C #) และคลาสเป็นส่วนหนึ่งของ API สาธารณะหรือหากภาษานั้นไม่รองรับพารามิเตอร์ที่มีชื่อ (C ++) ดังนั้นรหัสดังmyFunction(true)กล่าวอาจถูกเขียนขึ้นมาก็คือรหัส - กลิ่น.
BlueRaja - Danny Pflughoeft

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

@Ingo ฉันไม่เห็นด้วย นั่นเป็นปัญหาการเขียนโปรแกรมทั่วไป คุณสามารถกำหนดSaveBigชื่ออื่นได้อย่างง่ายดาย รหัสใด ๆ ที่สามารถทำให้คล้ำขึ้นได้การคลำชนิดนี้ไม่ได้เฉพาะกับพารามิเตอร์ที่มีชื่อ
Maarten Bodewes

1
@Ingo: ถ้าคุณถูกล้อมรอบด้วยคนโง่คุณก็ไปหางานที่อื่น สิ่งนั้นเป็นสิ่งที่คุณมีความคิดเห็นรหัสสำหรับ
gnasher729

1

ฉันไม่สามารถบอกได้เลยว่ามีอะไรผิดปกติ

หากดูเหมือนว่าเป็นกลิ่นรหัสให้รู้สึกเหมือนมีกลิ่นรหัสและ - ดี - มีกลิ่นเหมือนกลิ่นรหัสก็อาจเป็นกลิ่นรหัส

สิ่งที่คุณต้องการทำคือ:

1) หลีกเลี่ยงวิธีการที่มีผลข้างเคียง

2) จัดการกับรัฐที่จำเป็นด้วยส่วนกลางเครื่องจักรรัฐอย่างเป็นทางการ (เช่นนี้ )


1

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

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

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

ข้อมูลเป็นแบบซิงโครนัส แต่คำสั่งแบบอะซิงโครนัสและตรรกะของคำสั่งตามตรรกะบูลีนที่ไม่มีหน่วยความจำ แต่มีคำสั่งตามลำดับตามหน่วยความจำของสถานะถัดไปก่อนหน้าปัจจุบันและที่ต้องการ เพื่อให้การทำงานในภาษาเครื่องมีประสิทธิภาพมากที่สุด (เพียง 64kB) ได้รับการดูแลอย่างดีเพื่อกำหนดทุกขั้นตอนในรูปแบบฮิวริสติก IBM HIPO บางครั้งนั่นหมายถึงการส่งผ่านตัวแปรบูลีนและทำสาขาย่อยที่จัดทำดัชนี

แต่ตอนนี้มีหน่วยความจำและความสะดวกในการ OOK จำนวนมาก Encapsulation เป็นส่วนประกอบที่สำคัญในวันนี้ แต่เป็นการลงโทษเมื่อมีการนับรหัสเป็นไบต์ตามเวลาจริงและรหัสเครื่อง SCADA ..


0

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

สิ่งนี้ช่วยชี้แจงและช่วยเหลือได้หลายวิธี

ใครก็ตามที่อ่านคำกล่าวอ้างจะรู้ว่าผลลัพธ์จะเปลี่ยนไปตามผู้ใช้

ในฟังก์ชันที่เรียกว่าในที่สุดคุณสามารถใช้กฎเกณฑ์ทางธุรกิจที่ซับซ้อนได้ง่ายขึ้นเพราะคุณสามารถเข้าถึงแอตทริบิวต์ของผู้ใช้

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


0

ส่วนใหญ่ฉันจะพิจารณาการเข้ารหัสที่ไม่ดีนี้ ฉันสามารถนึกถึงสองกรณีอย่างไรก็ตามนี่อาจเป็นวิธีปฏิบัติที่ดี เนื่องจากมีคำตอบมากมายที่บอกว่าทำไมมันไม่ดีฉันจึงเสนอสองครั้งเมื่อมันอาจจะดี:

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

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

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


คำเตือนของ enum, SILENT จะทำให้เข้าใจได้ง่ายขึ้นแม้เมื่อใช้คลาส / โครงสร้างตามที่คุณเขียน (เช่นบริบท ) หรือเพียงแค่กำหนดค่าการบันทึกภายนอก - ไม่จำเป็นต้องผ่านอะไรเลย
Maarten Bodewes

-1

บริบทเป็นสิ่งสำคัญ วิธีการดังกล่าวค่อนข้างพบได้ทั่วไปใน iOS เป็นเพียงหนึ่งตัวอย่างที่ใช้บ่อย UINavigationController จัดเตรียมเมธอด-pushViewController:animated:และanimatedพารามิเตอร์คือ BOOL วิธีการนี้จะทำหน้าที่เหมือนกันทั้งสองวิธี แต่มันจะทำให้เกิดการเปลี่ยนแปลงจากมุมมองหนึ่งไปยังอีกมุมมองหนึ่งถ้าคุณผ่านเป็น YES และไม่ใช่ถ้าคุณผ่าน NO ดูเหมือนว่าสมเหตุสมผลทั้งหมด มันเป็นเรื่องงี่เง่าที่จะต้องจัดเตรียมสองวิธีแทนอันนี้เพื่อให้คุณสามารถกำหนดได้ว่าจะใช้แอนิเมชันหรือไม่

มันอาจจะง่ายกว่าที่จะพิสูจน์ความเรียงของสิ่งนี้ใน Objective-C ซึ่งไวยากรณ์การตั้งชื่อเมธอดจัดเตรียมบริบทเพิ่มเติมสำหรับแต่ละพารามิเตอร์มากกว่าที่คุณได้รับในภาษาเช่น C และ Java อย่างไรก็ตามฉันคิดว่าวิธีที่ใช้พารามิเตอร์เดียวสามารถใช้บูลีนได้อย่างง่ายดายและยังคงมีเหตุผล:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?

21
อันที่จริงฉันไม่มีเงื่อนงำสิ่งที่มีความfalseหมายในfile.saveWithEncryptionตัวอย่าง มันหมายความว่ามันจะประหยัดโดยไม่ต้องเข้ารหัส? ถ้าเป็นเช่นนั้นทำไมในโลกถึงมี "การเข้ารหัส" ในชื่อ? ฉันสามารถเข้าใจได้ว่ามีวิธีการเช่นsave(boolean withEncryption)นั้น แต่เมื่อฉันเห็นfile.save(false)มันไม่ชัดเจนเลยว่าพารามิเตอร์บ่งชี้ว่ามันจะมีหรือไม่มีการเข้ารหัส อันที่จริงฉันคิดว่านี่เป็นจุดแรกของ James Youngman เกี่ยวกับการใช้ enum
เรย์

6
สมมุติฐานทางเลือกคือfalseหมายความว่าอย่าเขียนไฟล์ที่มีชื่อเดียวกัน ตัวอย่างที่ทราบฉันรู้ แต่เพื่อให้แน่ใจว่าคุณจะต้องตรวจสอบเอกสาร (หรือรหัส) สำหรับฟังก์ชั่น
James Youngman

5
วิธีการตั้งชื่อsaveWithEncryptionที่บางครั้งไม่ได้บันทึกด้วยการเข้ารหัสเป็นข้อผิดพลาด มันควรจะเป็น possiby file.encrypt().save()หรือเหมือน new EncryptingOutputStream(new FileOutputStream(...)).write(file)Javas
Christoffer Hammarström

2
ที่จริงแล้วโค้ดที่ทำอย่างอื่นนั้นไม่ใช่สิ่งที่มันบอกว่าใช้งานไม่ได้และเป็นข้อผิดพลาด มันไม่ได้บินเพื่อสร้างวิธีการsaveWithEncryption(boolean)ที่บางครั้งบันทึกโดยไม่ต้องเข้ารหัสเช่นเดียวกับที่มันไม่ได้บินเพื่อสร้างวิธีsaveWithoutEncryption(boolean)ที่บางครั้งบันทึกด้วยการเข้ารหัส
Christoffer Hammarström

2
วิธีการไม่ดีเนื่องจากมี "สงสัยเกี่ยวกับความหมายของ 'เท็จ' ที่นี่อย่างชัดเจน อย่างไรก็ตามฉันจะไม่เขียนวิธีการเช่นนั้นในตอนแรก การบันทึกและการเข้ารหัสเป็นการกระทำที่แยกจากกันและวิธีการควรทำสิ่งหนึ่งและทำได้ดี ดูความคิดเห็นก่อนหน้าของฉันสำหรับตัวอย่างที่ดีกว่าวิธีการทำ
Christoffer Hammarström
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.