ทำไมรหัสสะอาดแนะนำให้หลีกเลี่ยงตัวแปรที่ป้องกัน?


168

รหัสที่ชัดเจนแนะนำให้หลีกเลี่ยงตัวแปรที่ได้รับการป้องกันในส่วน "ระยะทางแนวตั้ง" ของบท "การจัดรูปแบบ":

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

อะไรคือเหตุผล?


7
แสดงตัวอย่างที่คุณคิดว่าคุณต้องการมัน
ริ้น

63
ฉันจะไม่เอาหนังสือพระกิตติคุณเล่มนี้มามากนัก
Rig

40
@Rig คุณกำลังติดกับดูหมิ่นในส่วนเหล่านี้มีความคิดเห็นเช่นนั้น
Ryathal

40
ฉันโอเคกับสิ่งนั้น ฉันอ่านหนังสือแล้วและสามารถมีความคิดเห็นของตัวเองได้
Rig

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

คำตอบ:


277

ตัวแปรที่มีการป้องกันควรหลีกเลี่ยงเพราะ:

  1. พวกเขามักจะนำไปสู่ปัญหาYAGNI ถ้าคุณไม่มีคลาสลูกหลานที่ทำสิ่งต่าง ๆ กับสมาชิกที่ได้รับการป้องกันให้ทำให้เป็นแบบส่วนตัว
  2. พวกเขามักจะนำไปสู่ปัญหาLSP ตัวแปรที่ได้รับการป้องกันโดยทั่วไปจะมีค่าความแปรปรวนภายในซึ่งเกี่ยวข้องกับตัวแปรเหล่านั้น ผู้สืบทอดต้องรักษาคุณสมบัติเหล่านั้นซึ่งผู้คนสามารถทำให้เสียโฉมหรือจงใจละเมิด
  3. พวกเขามีแนวโน้มที่จะละเมิดOCP หากคลาสฐานทำให้สมมติฐานมากเกินไปเกี่ยวกับสมาชิกที่ได้รับความคุ้มครองหรือผู้สืบทอดมีความยืดหยุ่นมากเกินไปกับพฤติกรรมของคลาสมันสามารถนำไปสู่พฤติกรรมของคลาสฐานที่ถูกแก้ไขโดยส่วนขยายนั้น
  4. พวกเขามีแนวโน้มที่จะนำไปสู่การสืบทอดเพื่อขยายมากกว่าองค์ประกอบ สิ่งนี้มีแนวโน้มที่จะนำไปสู่การมีเพศสัมพันธ์ที่เข้มงวดมากขึ้นการละเมิดSRPมากขึ้นการทดสอบที่ยากขึ้นและการฆ่าสิ่งอื่น ๆ ที่อยู่ในการอภิปราย

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


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

2
ดูเหมือนว่าลุงบ๊อบหมายถึงระยะทางแนวตั้งเมื่อมีคนอ้างถึงสมาชิกที่ได้รับความคุ้มครองระหว่างชั้นเรียนและไม่ได้อยู่ในชั้นเรียนเดียวกัน
Robert Harvey

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

2
@ steve314 ฉันไม่สามารถจินตนาการสถานการณ์ที่ชั้นฐานและทั้งหมดของผู้รับมรดกของมันจะเท่านั้นที่เคยอาศัยอยู่ในไฟล์เดียว โดยไม่มีตัวอย่างฉันมีแนวโน้มที่จะคิดว่าเป็นการละเมิดมรดกหรือสิ่งที่เป็นนามธรรม
Telastyn

3
YAGNI = คุณไม่ต้องการมัน LSP = หลักการทดแทน Liskov OCP = เปิด / ปิดหลักการ SRP = หลักการความรับผิดชอบเดี่ยว
Bryan Glazer

54

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


26

ฉันไม่ได้อ่านหนังสือ แต่ฉันสามารถแทงสิ่งที่ลุงบ๊อบหมายถึง

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

ในการแก้ไขปัญหาคุณสามารถสรุปแค็ปซูลตัวแปรในคุณสมบัติที่ได้รับการป้องกันดังนี้:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

สิ่งนี้ช่วยให้nameสามารถตั้งค่าได้อย่างปลอดภัยจากคลาสที่ได้รับโดยใช้อาร์กิวเมนต์ตัวสร้างโดยไม่เปิดเผยรายละเอียดการใช้งานของคลาสฐาน


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

2
+1 ทันที Setter อาจได้รับการปกป้องเช่นกันฉันคิดว่า แต่จุดรหัสฐานจะบังคับใช้หากค่าใหม่นั้นถูกต้องหรือไม่
Andy

เพียงเพื่อเสนอมุมมองตรงข้าม - ฉันทำให้ตัวแปรทั้งหมดของฉันได้รับการปกป้องในชั้นฐาน หากพวกเขาเท่านั้นที่จะเข้าถึงได้ผ่านส่วนต่อประสานสาธารณะแล้วมันไม่ควรจะเป็นชั้นที่ได้รับมันก็ควรจะมีวัตถุ baseclass แต่ฉันยังหลีกเลี่ยงลำดับชั้นที่มีความลึกมากกว่า 2 คลาส
Martin Beckett

2
มีความเป็นใหญ่ความแตกต่างระหว่างprotectedและpublicสมาชิก หากBaseTypeเปิดเผยสมาชิกสาธารณะนั่นหมายความว่าทุกประเภทที่ได้รับจะต้องมีสมาชิกคนเดียวกันทำงานในลักษณะเดียวกันเนื่องจากDerivedTypeอาจมีการส่งตัวอย่างไปยังรหัสที่ได้รับการBaseTypeอ้างอิงและคาดว่าจะใช้สมาชิกนั้น ในทางตรงกันข้ามถ้าBaseTypeตีแผ่protectedสมาชิกแล้วDerivedTypeอาจคาดหวังในการเข้าถึงของสมาชิกที่อยู่ในbaseแต่baseไม่สามารถเป็นอะไรอื่น ๆBaseTypeกว่า
supercat

18

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

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

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


9

คำตอบที่ดีมากอยู่ที่นี่แล้ว แต่สิ่งหนึ่งที่จะเพิ่ม:

ในภาษา OO ที่ทันสมัยส่วนใหญ่มันเป็นนิสัยที่ดี (ใน Java AFAIK ที่จำเป็น) เพื่อให้แต่ละคลาสเป็นไฟล์ของตัวเอง

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


1
ไม่ได้อยู่ในสวิฟท์ น่าสนใจ Swift ไม่มี "ป้องกัน" แต่มี "fileprivate" ซึ่งแทนที่ทั้ง "ป้องกัน" และ "เพื่อน" C ++
gnasher729

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

4

แนวคิดพื้นฐานคือ "ฟิลด์" (ตัวแปรระดับอินสแตนซ์) ที่ประกาศว่าได้รับการป้องกันอาจจะมองเห็นได้มากกว่าที่ควรจะเป็นและ "ป้องกัน" น้อยกว่าที่คุณอาจชอบ ไม่มีตัวดัดแปลงการเข้าถึงใน C / C ++ / Java / C # ที่เทียบเท่ากับ "เข้าถึงได้โดยคลาสลูกภายในแอสเซมบลีเดียวกันเท่านั้น" เท่านั้นจึงทำให้คุณสามารถกำหนดลูกของคุณเองที่สามารถเข้าถึงฟิลด์ในแอสเซมบลีของคุณได้ ไม่อนุญาตให้เด็กสร้างขึ้นในการประกอบอื่น ๆ การเข้าถึงเดียวกัน; C # มีตัวดัดแปลงภายในและที่ได้รับการป้องกัน แต่การรวมมันทำให้การเข้าถึง "ภายในหรือได้รับการป้องกัน" ไม่ใช่ "ภายในและได้รับการป้องกัน" ดังนั้นฟิลด์ใด ๆ ที่มีการป้องกันจะสามารถเข้าถึงได้โดยเด็ก ๆ ไม่ว่าคุณจะเขียนเด็กคนนั้นหรือคนอื่นก็ตาม การป้องกันจึงเป็นประตูเปิดสู่แฮ็กเกอร์

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

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

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


4

มันเป็นการสนทนาที่น่าสนใจ

เครื่องมือที่ดีสามารถลดปัญหา "แนวตั้ง" เหล่านี้ได้หลายอย่าง ในความเป็นจริงในความคิดของฉันความคิดของ "ไฟล์" เป็นจริงสิ่งที่ขัดขวางการพัฒนาซอฟต์แวร์ - โครงการ Light Table กำลังทำงานอยู่ (http://www.chris-granger.com/2012/04/12/light-) โต๊ะ --- แบบใหม่ IDE แนวคิด /)

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

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

ในการใส่อีกวิธีหนึ่ง - ในกรณีใดก็ตามที่ฉันรู้สึกว่าตัวแปรส่วนตัวเหมาะกับดีกว่าตัวที่ได้รับการป้องกันการสืบทอดไม่ใช่วิธีแก้ปัญหาในตอนแรก


0

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

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

อย่างไรก็ตามคุณไม่ควรใช้ litteram ทั้งหมดของโฆษณานี้เพื่อเป็น guildline เท่านั้น หากตัวแปรที่ได้รับการป้องกันนั้นแย่มากพวกเขาจะไม่ถูกวางไว้ในภาษานั้นตั้งแต่แรก สถานที่ที่เหมาะสมสำหรับเขตข้อมูลที่มีการป้องกัน imho อยู่ในชั้นฐานจากกรอบการทดสอบ (ชั้นเรียนทดสอบบูรณาการขยายมัน)


1
อย่าสันนิษฐานว่าเพราะภาษาอนุญาตให้ใช้งานได้ ฉันไม่แน่ใจว่ามีเหตุผลพระเจ้าที่จะอนุญาตให้มีการป้องกันเขตข้อมูล; เราไม่อนุญาตพวกเขาที่นายจ้างของฉัน
Andy

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

คุณอาจต้องการ reword บางโพสต์ของคุณเนื่องจากคุณดูเหมือนว่าจะใช้ตัวแปรและฟิลด์สลับกันได้และทำให้ชัดเจนว่าคุณจะพิจารณาสิ่งต่าง ๆ เช่น const ที่ได้รับการป้องกัน
แอนดี้

3
@Andy มันค่อนข้างชัดเจนว่าตั้งแต่ฉันอ้างถึงตัวแปรที่มีการป้องกันฉันไม่ได้พูดถึงตัวแปรท้องถิ่นหรือตัวแปร แต่ไปที่เขตข้อมูล ฉันยังกล่าวถึงสถานการณ์ที่ตัวแปรป้องกันที่ไม่คงที่มีประโยชน์ imho ผิดสำหรับ บริษัท ที่จะบังคับใช้วิธีที่โปรแกรมเมอร์เขียนรหัส
m3th0dman

1
ถ้าเห็นได้ชัดว่าฉันจะไม่สับสนและทำให้คุณผิดหวัง กฎถูกสร้างขึ้นโดยนักพัฒนาอาวุโสของเราเช่นเดียวกับการตัดสินใจของเราในการเรียกใช้ StyleCop, FxCop และต้องการการทดสอบหน่วยและการตรวจสอบโค้ด
แอนดี้

0

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

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


-1

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

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

ฉันคิดว่าวิธีที่น่าสนใจกว่านั้นคือการทำให้ทุกอย่างเป็นสาธารณะซึ่งจะบังคับให้คุณมีชั้นเรียนเล็ก ๆ

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