เทมเพลต“ metaprogramming” ใน Java เป็นความคิดที่ดีหรือไม่


29

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

น่าเสียดายที่นี่หมายถึงรหัสนี้เป็น PITA ที่จะรักษาไว้ ฉันต้องการลบรหัสที่ซ้ำกันทั้งหมดและเขียนเพียงหนึ่งเทมเพลต อย่างไรก็ตามภาษา, Java, ไม่รองรับเทมเพลตและฉันไม่แน่ใจว่ายาชื่อสามัญเหมาะสำหรับสิ่งนี้

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

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

ประโยชน์ของวิธีนี้มีมากกว่าข้อเสียหรือไม่ ฉันควรจะแทน:

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

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


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

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


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

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

1
สิ่งนี้ฟังดูเหมือนว่าจะได้รับประโยชน์จากตัวแปรหลายรูปแบบ (หรือบางทีแม้แต่ชื่อสามัญ) แทนที่จะเรียกแต่ละฟังก์ชันด้วยชื่อที่แตกต่างกัน
Robert Harvey

2
การตั้งชื่อฟังก์ชั่น func1 ... func12 ดูเหมือนจะบ้า อย่างน้อยชื่อพวกเขา mode1Par2 ฯลฯ ... หรืออาจ myFuncM3P2
949300

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

คำตอบ:


23

นี่เป็นสถานการณ์ที่เลวร้ายมากที่คุณจะต้อง refactor ASAP นี้ - นี้เป็นหนี้ทางเทคนิคในมันเลวร้ายที่สุด - คุณไม่ได้รู้ว่าวิธีการที่สำคัญรหัสจริงๆคือ - เพียงคาดเดาว่ามันเป็นสิ่งที่สำคัญ

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

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

แก้ไข

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

เพียงแค่ใส่: มันไม่ได้เป็น

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


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

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

8
แน่นอนว่าไฟล์ที่สร้างขึ้นโดยอัตโนมัติไม่ได้อยู่ในแหล่งเก็บข้อมูล (ไม่ใช่แหล่งที่มาหลังจากนั้นเป็นรหัสที่คอมไพล์แล้ว) และควรถูกลบโดยant cleanหรือสิ่งที่เทียบเท่าของคุณ ไม่จำเป็นต้องใส่คำปฏิเสธในไฟล์ที่ไม่ได้มี!
Jörg W Mittag

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

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

16

การบำรุงรักษาเกิดขึ้นจริงหรือไม่ก็แค่รบกวนคุณ ถ้ามันรบกวนจิตใจคุณ

ปัญหาด้านประสิทธิภาพเป็นเรื่องจริงหรือไม่หรือนักพัฒนาคนก่อนหน้าคิดว่าเป็นจริงหรือ ปัญหาประสิทธิภาพการทำงานบ่อยกว่าไม่ได้ไม่ใช่ที่ที่พวกเขาคิดว่าจะเป็นแม้ว่าจะใช้ profiler (ฉันใช้เทคนิคนี้ในการค้นหาได้อย่างน่าเชื่อถือ)

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


10

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

นอกจากนี้ถ้าคุณจะพบว่าคุณต้องการฟังก์ชั่นทั้งหมดที่คุณอาจต้องการที่จะใช้Javassistเพื่อสร้างรหัส programatically กับคนอื่น ๆ ได้ชี้ให้เห็นคุณสามารถทำให้มันด้วยMaven


2
นอกจากนี้แม้ว่าจะปรากฎว่าปัญหาด้านประสิทธิภาพเป็นเรื่องจริง แต่การฝึกหัดการศึกษาพวกเขาจะช่วยให้คุณรู้ว่าอะไรเป็นไปได้ด้วยรหัสของคุณ
Carl Manaster

6

ทำไมไม่รวมเทมเพลต preprocessor \ code ไว้ล่วงหน้าสำหรับระบบนี้? ขั้นตอนการสร้างเสริมพิเศษที่กำหนดเองที่ดำเนินการและปล่อยไฟล์ต้นฉบับ Java พิเศษก่อนที่จะรวบรวมรหัสที่เหลือ นี่คือวิธีรวมถึงเว็บไคลเอ็นต์ที่ปิดไฟล์ wsdl และ xsd

แน่นอนว่าคุณจะต้องมีการบำรุงรักษาเครื่องกำเนิด preprocessor \ code แต่คุณจะไม่ต้องกังวลกับการบำรุงรักษาคอร์ของรหัสซ้ำ

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

Java generics ไม่ได้รับประโยชน์ด้านประสิทธิภาพเนื่องจากการลบประเภทในภาษาการคัดเลือกแบบง่ายจะถูกใช้แทน

จากความเข้าใจของฉันเกี่ยวกับแม่แบบ C ++ พวกเขาจะรวบรวมเป็นฟังก์ชั่นต่าง ๆ ต่อการร้องขอแม่แบบแต่ละอัน คุณจะจบลงด้วยการรวบรวมรหัสเวลากลางสำหรับแต่ละประเภทที่คุณเก็บไว้ใน std :: vector เป็นต้น


2

ไม่มีวิธีการที่สติมีความยาวพอที่จะมี 12 ตัวแปร ... และ JITC เกลียดวิธีการที่ยาว - เพียงปฏิเสธที่จะปรับให้เหมาะสมอย่างเหมาะสม ฉันเห็นอัตราเร่งสองเท่าโดยแยกวิธีเป็นสองวิธีที่สั้นกว่า บางทีนี่อาจเป็นวิธีที่จะไป

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

ฉันจะบอกว่าการสร้างรหัสไม่ใช่เรื่องใหญ่สมมติว่ามีเหตุผลที่ดี โอเวอร์เฮดค่อนข้างต่ำและไฟล์ที่ตั้งชื่ออย่างถูกต้องจะนำไปยังแหล่งที่มาทันที เมื่อนานมาแล้วเมื่อฉันสร้างซอร์สโค้ดฉันใส่// DO NOT EDITทุกบรรทัด ... ฉันเดาว่าประหยัดพอ


1

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

มีวิธีพิเศษมากมายเพื่อให้แน่ใจว่าสามารถเพิ่มประสิทธิภาพสำหรับการดำเนินงานทุกประเภท: เข้าร่วมเลือกรวมและอื่น ๆ ... เมื่อมีเงื่อนไขบางประการ

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


1

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


0

คุณสามารถแก้ปัญหาของวิธีการพิเศษโดยใช้ภาษาสกาล่า

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

แต่ยังสกาล่ามีแมโครวากยสัมพันธ์ซึ่งทำให้สามารถทำสิ่งต่าง ๆ มากมายด้วยรหัสในเวลารวบรวมในลักษณะที่ปลอดภัยชนิด

และปัญหาที่พบบ่อยของมวยแบบดั้งเดิมเมื่อใช้ใน generics ก็เป็นไปได้ที่จะแก้ปัญหาใน Scala ด้วยเช่นกัน: มันสามารถสร้างความชำนาญเฉพาะทางสำหรับวิชาพื้นฐานเพื่อหลีกเลี่ยงการชกมวยโดยอัตโนมัติโดยใช้@specializedคำอธิบายประกอบ - สิ่งนี้สร้างด้วยภาษาเอง ดังนั้นโดยพื้นฐานแล้วคุณจะเขียนวิธีการทั่วไปหนึ่งรายการใน Scala และมันจะทำงานด้วยความเร็วของวิธีการพิเศษ และถ้าคุณต้องการ arithmetics ทั่วไปที่รวดเร็วก็สามารถทำได้โดยใช้ Typeclass "pattern" สำหรับการฉีดการดำเนินการและค่าสำหรับประเภทตัวเลขที่แตกต่างกัน

นอกจากนี้สกาล่าก็ยอดเยี่ยมในหลาย ๆ ด้าน และคุณไม่จำเป็นต้องเขียนโค้ดทั้งหมดให้ Scala อีกครั้งเนื่องจาก Scala <-> การทำงานร่วมกันของ Java นั้นยอดเยี่ยม เพียงให้แน่ใจว่าใช้SBT (เครื่องมือสร้าง scala)สำหรับการสร้างโครงการ


-1

หากฟังก์ชันที่มีปัญหาใหญ่ให้เปลี่ยนบิต "mode / กระบวนทัศน์" เป็นอินเทอร์เฟซแล้วส่งผ่านวัตถุที่ใช้ส่วนต่อประสานนั้นเป็นพารามิเตอร์ของฟังก์ชันสามารถทำงานได้ GoF เรียกสิ่งนี้ว่า "กลยุทธ์" รูปแบบ iirc หากฟังก์ชั่นมีขนาดเล็กโอเวอร์เฮดที่เพิ่มขึ้นอาจมีความสำคัญ ... มีใครพูดถึงการทำโปรไฟล์หรือยัง ... คนอื่น ๆ ควรพูดถึงการทำโปรไฟล์


นี้อ่านมากขึ้นเช่นความคิดเห็นกว่าคำตอบ
ริ้น

-2

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


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

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