17.5 Team Workflow และ Agile

เฟส 17 · วิศวกรรมซอฟต์แวร์และ Production · เวลาเรียน: 10–20 h

ทำงานเป็นทีม — code review, กลยุทธ์ branch, ticket และการผลิตเกมถูกจัดระเบียบจริงยังไง

ฟีเจอร์ที่วางขายจริงแทบไม่เคยเขียนโดยคนคนเดียวตั้งแต่ต้นจนจบ designer เป็นคนตัดสินใจว่าฟีเจอร์นี้ควรทำอะไรและทำไมมันถึงสำคัญ programmer เป็นคนสร้างระบบที่ทำให้มันทำงานได้จริง artist เป็นคนสร้างสิ่งที่ผู้เล่นมองเห็น technical artist คอยดูแลให้สิ่งที่ artist สร้างรันอยู่ในงบของเกมได้ audio designer ทำให้มันมีเสียงที่ถูกต้อง QA พิสูจน์ว่ามันรอดจากผู้เล่นจริงที่ไม่รู้ว่า "วิธีเล่นที่ถูกต้อง" คืออะไร และ producer คอยติดตามทั้งหมดนี้ให้จบภายในวันที่คนวางแผนได้จริง คุณรู้วิธีเขียนโค้ดอยู่แล้ว บทนี้พูดถึงทุกอย่างที่โค้ดอยู่ข้างใน -- คนที่ส่งงานมาให้คุณ คนที่รับงานจากคุณไปต่อ และกระบวนการที่ควรจะกันไม่ให้ทั้งหมดนี้กลายเป็นความโกลาหล

Git workflow จากบท 17.1, CI pipeline จากบท 17.2, pattern จากบท 17.3 หรือนิสัยการเทสต์จากบท 17.4 ไม่มีความหมายเลย ถ้าทีมตกลงกันไม่ได้ว่าจะสร้างอะไรต่อ อธิบายงานให้ละเอียดพอที่คนอื่นจะหยิบไปทำต่อได้ หรือจับปัญหาในงานของกันและกันไม่ได้ก่อนที่มันจะถูกปล่อยออกไป บทนี้คือชั้นทางสังคม (social layer) ที่บทที่เหลือของบท 17 วางอยู่ด้านบน

this chapter, in one line PEOPLE PROCESS COMMUNICATION PRODUCTION (section 1) (sections 2-6) (sections 7-8) (sections 9-11) who does what, how the team how work gets the milestones a and how a decides what to described and whole game is feature travels build next, in reviewed clearly built toward, and between them what order why deadlines break teams

1. ใครอยู่ในทีมเกม และฟีเจอร์หนึ่งเดินทางผ่านพวกเขายังไง

นี่คือสิ่งที่แต่ละ role ทำจริง ๆ ในแต่ละวัน ก่อนที่เราจะดูฟีเจอร์หนึ่งเดินทางผ่านทุก role เหล่านี้

Producer

งานของ producer ไม่ใช่การตัดสินใจเชิงครีเอทีฟ แต่คือการทำให้การตัดสินใจเชิงครีเอทีฟกลายเป็นเกมที่เสร็จจริงภายในวันที่คนวางแผนได้ ในแต่ละวัน producer จะเป็นคนรัน planning meeting, ติดตามว่า ticket ไหนติดขัด (blocked) อยู่และเพราะอะไร, ไล่ตามคนที่เป็นตัวบล็อก, อัปเดต schedule เมื่อความเป็นจริงไม่ตรงกับแผน (ซึ่งเกิดขึ้นเสมอ) และเจรจาว่าจะตัดอะไรออกเมื่องานมีมากกว่าเวลาที่มี ประโยคที่มีค่าที่สุดของ producer ที่ดีคือ "เราไม่มีเวลาทำทั้งสามอย่างนี้ สองอย่างไหนสำคัญที่สุด" ที่พูดตั้งแต่เนิ่น ๆ ไม่ใช่ "ทำไมยังไม่เสร็จ" ที่พูดตอนสาย

Designer

designer เป็นคนตัดสินใจว่ากติกาของเกมและความรู้สึกแบบวินาทีต่อวินาที (moment-to-moment feel) ควรเป็นยังไง -- jump ควรสูงแค่ไหน การโจมตีหนึ่งครั้งควรสร้างความเสียหายเท่าไหร่ level หนึ่งควรสอนผู้เล่นให้ใช้ ability ใหม่ยังไงก่อนที่จะบังคับให้ผู้เล่นต้องเชี่ยวชาญมัน ในแต่ละวัน designer จะเขียน spec สั้น ๆ อธิบาย intent นั่งอยู่ใน editor ปรับตัวเลข (ความสูงของ jump, cooldown, อัตราดรอปไอเทม) และดูคนอื่นเล่น level หรือระบบของตัวเองแบบสด ๆ จดบันทึกว่าตรงไหนที่พวกเขางงหรือเบื่อ วัตถุดิบของ designer ไม่ใช่โค้ดหรืองานอาร์ต แต่คือความสนใจของผู้เล่นและความสนุกของผู้เล่น ทั้งสองอย่างสังเกตได้เท่านั้น คำนวณล่วงหน้าไม่ได้เลย

Programmer

programmer เป็นคนแปลง intent ของ designer กับ asset ของ artist ให้กลายเป็นระบบที่รันได้จริง ที่ frame rate ที่เกมรับไหว โดยไม่ crash เมื่อผู้เล่นทำอะไรที่ไม่มีใครคาดคิด ในแต่ละวัน คือการเขียน gameplay code, เปิดให้ tuning value ปรับได้ (expose) เพื่อให้ designer ปรับค่าเองได้โดยไม่ต้องขอให้ programmer แก้ตัวเลขแล้ว rebuild ใหม่, review โค้ดของเพื่อนร่วมทีม และแก้บั๊กที่ QA ยื่นเข้ามา ส่วนใหญ่ของสิ่งที่คุณเรียนมาตั้งแต่บทที่ 1 ถึง 16 อยู่ตรงนี้แหละ

Artist

artist สร้างสิ่งที่ผู้เล่นมองเห็นจริง ๆ -- ตัวละคร, สภาพแวดล้อม, prop และ animation ที่ทำให้มันมีชีวิต ในแต่ละวัน คือการ modeling, texturing, rigging และ animating จากนั้น iterate หนักตาม feedback -- พาสแรกของตัวละครแทบไม่ใช่เวอร์ชันที่วางขายจริงเลย และ artist ต้องเตรียมใจทำ asset เดิมซ้ำสามสี่รอบขณะที่ direction ค่อย ๆ นิ่งลง artist ทำงานกับเป้าหมายที่ขยับอยู่ตลอดเวลามากกว่า role ไหนในทีม เพราะ "แบบนี้ดูถูกไหม" เป็นการตัดสินใจที่เปลี่ยนไปเรื่อย ๆ ตามที่ส่วนที่เหลือของเกมค่อย ๆ ประกอบกันขึ้นมารอบตัวมัน

Technical artist

technical artist ยืนอยู่ระหว่างงานอาร์ตกับโค้ด และ role นี้มีอยู่เพราะเครื่องมือของสองสาขานี้ไม่ได้คุยกันเองโดยธรรมชาติ ในแต่ละวัน technical artist เขียน shader, สร้าง editor tool ที่ให้ artist ทำงานเร็วขึ้นโดยไม่ต้องพึ่ง programmer (สคริปต์ที่ auto-generate LOD, ตัว validator ที่แจ้งเตือนเมื่อ texture ใหญ่เกินไปก่อนที่มันจะถูกปล่อยออกไป) และ profile ต้นทุน GPU กับ memory ของสิ่งที่ artist กำลังสร้าง เพื่อไม่ให้ตัวละครที่สวยงามแอบทำ frame rate พัง technical artist อ่านออกมาเป็นครึ่ง artist ครึ่ง programmer และมักเป็นคนที่แปล "ตรงนี้ต้องดูดีขึ้น" ให้กลายเป็น "นี่คือ setting เฉพาะที่กำลังกินคุณภาพภาพของคุณอยู่" แล้วก็แปลกลับไปกลับมา

QA (quality assurance)

งานของ QA คือพิสูจน์ว่า build หนึ่งรอดจากผู้เล่นจริงที่ไม่เคยถูกบอกว่าวิธีเล่นที่ตั้งใจไว้คืออะไร ในแต่ละวัน คือการเล่นเกม -- อย่างตั้งใจ, เป็นระบบ และมักจะเล่นแบบ adversarial (พยายามทำให้มันพัง) -- ยื่น ticket สำหรับทุกอย่างที่ผิดปกติ ตรวจสอบว่า fix ของ programmer แก้ปัญหาได้จริง และรัน regression pass (เล่นซ้ำบั๊กเก่าที่เคยแก้ไปแล้ว) ก่อนถึง milestone เพื่อให้แน่ใจว่าไม่มีอะไรพังซ้ำอีก automated test จากบท 17.4 จับสิ่งที่คุณคิดจะเทสต์ไว้ล่วงหน้า ส่วน QA จับสิ่งที่ไม่มีใครคิดจะเทสต์เลย รวมถึงลำดับการกดปุ่มที่แม่นยำที่ทำให้ระบบพังในแบบที่ไม่มี programmer คนไหนจะจินตนาการออกว่าต้องเขียน unit test มาเช็ค

Audio

audio designer สร้างและ implement เสียง: อัดหรือหาไฟล์เสียงเอฟเฟกต์, แต่งหรือซื้อลิขสิทธิ์เพลง และเชื่อมทั้งสองอย่างเข้ากับเกมเพื่อให้เสียงที่ถูกต้องเล่นในจังหวะที่ถูกต้อง ปกติผ่าน middleware อย่าง Wwise หรือ FMOD (เครื่องมือด้านเสียงที่อยู่ระหว่างไฟล์เสียงดิบกับโค้ดเกม ให้ audio designer ตั้งค่าว่าเสียงจะ trigger, blend และตอบสนองต่อ gameplay state ยังไง โดยไม่ต้องให้ programmer เขียนโค้ดเฉพาะสำหรับเสียงทุกตัว) ในแต่ละวัน คือการตัดต่อและ mix เสียง เชื่อม audio event เข้ากับ gameplay trigger และเหมือน role อื่น ๆ ในลิสต์นี้ คือ iterate ใหม่ทันทีที่ฟีเจอร์เล่นได้จริงแล้วพาสแรกฟังดูผิด

ลองดูฟีเจอร์เล็ก ๆ หนึ่งอันเดินทางผ่านทั้งหมดนี้ สมมติว่า design เรียกร้อง double jump -- ผู้เล่นกระโดดได้อีกครั้งหนึ่ง ระหว่างที่ลอยอยู่กลางอากาศแล้ว

one feature, GD-441 "add double jump", moving across roles DESIGNER writes a one-page spec: why (traversal feels stiff, players keep getting stuck under ledges), what (a second jump, usable once per airborne period, same height as the first), first-pass numbers | v PROGRAMMER implements the jump-count logic and exposes height/cooldown as tunable values; ships it with a placeholder capsule stretch instead of real animation, just to get it playable fast | v DESIGNER (again) plays it grey-boxed (no real art yet), tunes the numbers by feel, decides the second jump should arc slightly forward, not straight up | v ARTIST + TECHNICAL ARTIST artist animates a real second-jump pose; tech artist builds the trail VFX shader and checks its GPU cost is small enough to run on ten enemies plus the player at once | v AUDIO adds a distinct "second jump" sound so a player can tell, without looking, whether the jump they just did was their first or second | v QA plays it badly on purpose: jumps at a ledge corner, jumps the instant they land, jumps while being hit -- finds that jumping during a hit-stun skips the cooldown reset, files a ticket | v PROGRAMMER (again) fixes the hit-stun bug QA found, ships the fix | v DESIGNER (again) final pass in a real level, confirms it now teaches and feels the way the original one-page spec intended

ไม่มีใครในลิสต์นั้นแตะฟีเจอร์ทั้งหมดคนเดียว และไม่มีพาสของใครเป็นคำตัดสินสุดท้าย -- designer กลับมาสองครั้ง programmer กลับมาสองครั้ง และ loop หยุดก็ต่อเมื่อ QA หาอะไรที่คุ้มจะเปิด ticket ไม่เจอแล้วเท่านั้น การไปกลับแบบนี้ไม่ใช่สัญญาณว่า process ล้มเหลว มันคือสิ่งที่ process ควรจะทำ

Tip สตูดิโอเล็ก ๆ มักยุบหลาย role เหล่านี้ให้อยู่ในคนเดียว -- "designer" ที่เขียนสคริปต์ tuning ของตัวเองด้วย หรือ "technical artist" ที่จริง ๆ ก็คือ artist คนเดียวในสตูดิโอที่แตะ shader ด้วย สตูดิโอใหญ่แยกแต่ละอย่างนี้ออกเป็นหลาย role เฉพาะทาง (combat designer กับ level designer เป็นงานคนละอย่างกันในสตูดิโอใหญ่) role เหล่านี้มีอยู่จริงไม่ว่าสตูดิโอจะขนาดไหน มีแค่จำนวนคนที่มันถูกแบ่งออกไปเท่านั้นที่เปลี่ยน

2. ทำไมเกมถึงถูกสร้างแบบ iterative: คุณไม่มีทางรู้ว่ามันสนุกไหมจนกว่าจะได้เล่น

compiler บอกได้ว่าโค้ดถูกต้องไหม แต่ไม่มีอะไรบอกได้ว่า jump หนึ่งรู้สึกดีไหม นอกจากคนที่กระโดดจริงแล้วตอบสนองกับมัน นี่คือข้อเท็จจริงหลักที่ process ทั้งบทนี้มีอยู่เพื่อรับมือ: ความสนุกไม่ใช่สิ่งที่คุณคำนวณได้บนกระดาษ ในที่ประชุม หรือจาก design document ไม่ว่าจะเขียนละเอียดแค่ไหนก็ตาม มันสังเกตได้เท่านั้น ด้วยการสร้างเวอร์ชันที่เล็กที่สุดของสิ่งที่เล่นได้จริง แล้วดูว่าเกิดอะไรขึ้นเมื่อคนจริง ๆ เล่นมัน

นี่คือเหตุผลที่ทีมเกมไม่ได้ design เกมทั้งเกมล่วงหน้าแล้วสร้างมันครั้งเดียวจบ พวกเขาสร้างเวอร์ชันหยาบ ๆ ไม่สวยของชิ้นส่วนหนึ่ง เล่นมัน เรียนรู้อะไรบางอย่างที่จริงแท้ซึ่งการคุยกันเท่าไหร่ก็หาไม่เจอ เปลี่ยนมัน แล้วเล่นอีกครั้ง loop นั้น -- build, play, learn, change -- วนซ้ำจนกว่าสิ่งนั้นจะดี ไม่ใช่จนกว่า schedule จะบอกว่าควรเสร็จ

the iteration loop +-----------+ | BUILD | the smallest playable version -- | | ugly art is fine, missing polish is fine, +-----+-----+ as long as the CORE INTERACTION works | v +-----------+ | PLAY | a real person plays it, ideally someone | | who did not build it and has no idea +-----+-----+ what answer they are "supposed" to find | v +-----------+ | LEARN | where did they get confused, bored, | | stuck, delighted, frustrated -- and why +-----+-----+ | v +-----------+ | CHANGE | adjust ONE thing based on what you just | | watched, not everything at once +-----+------+ | +----------> back to BUILD

สองนิสัยที่ทำให้ loop นี้ใช้งานได้จริงแทนที่จะไม่มีที่สิ้นสุด อย่างแรกคือ prototype (เวอร์ชันหยาบ เร็ว ใช้แล้วทิ้ง สร้างมาเพื่อตอบคำถามเดียวเท่านั้น ไม่ใช่เพื่อวางขาย) อย่างที่สองคือ grey-box (เรขาคณิตของ level ที่บล็อกไว้ด้วยรูปทรงเรียบ ๆ ไม่มี texture -- กล่อง, ทางลาด, ทรงกระบอก -- เพื่อให้ designer เทสต์ได้ว่า layout เล่นดีไหม ก่อนที่ใครจะเสียเวลาอาร์ตทำให้มันดูเป็นอะไรสักอย่าง) ทั้งสองอย่างมีอยู่ด้วยเหตุผลเดียวกัน: งานอาร์ต, polish และโค้ดสุดท้ายมีต้นทุนสูง และไม่มีอันไหนบอกได้เลยว่าไอเดียหลักสนุกไหม การสร้างแบบราคาถูก เทสต์ไว ๆ และใช้ความพยายามด้าน production จริงก็ต่อเมื่อไอเดียหลักพิสูจน์ตัวเองแล้วใน prototype หรือ grey-box คือสิ่งที่กัน loop นี้ไม่ให้ช้าแบบไม่มีที่สิ้นสุด

นี่คือตัวอย่างเล็ก ๆ ที่ลองทำดู สมมติว่า double jump จากหัวข้อ 1 เวอร์ชันแรกที่ปล่อยออกมามีความสูง jump 2 เมตร เท่ากับ jump แรกเป๊ะ ๆ

iteration log -- "double jump รู้สึกอ่อนไป (feels weak)", playtest รอบที่ 1

รอบ 1: height = 2.0m (เท่ากับ jump แรก), ไม่มี forward arc
  โน้ตจาก playtester: "แทบไม่รู้เลยว่าฉัน double-jump ไปแล้ว
  รู้สึกเหมือน jump เดิมสองครั้ง"
  การเปลี่ยน: เพิ่ม forward arc, คง height เท่าเดิม

รอบ 2: height = 2.0m, forward arc = 1.5m
  โน้ตจาก playtester: "ดีขึ้น รู้สึกว่ามันดันตัวไปข้างหน้า แต่
  ก็ยังไม่รู้สึกเหมือน jump ที่สอง เหมือน dash มากกว่า"
  การเปลี่ยน: เพิ่ม height แทนที่จะเพิ่ม arc

รอบ 3: height = 2.6m, forward arc = 0.5m
  โน้ตจาก playtester: "ใช่เลย -- ตอนนี้รู้สึกเหมือน jump ที่สองจริง ๆ
  ใช้มันไปถึงขอบที่ jump แรกไปไม่ถึงได้ด้วย"
  การเปลี่ยน: ไม่มี -- ใช้ตัวเลขนี้วางขายเลย ไปต่อที่ animation กับ VFX

ไม่มีอะไรใน log นั้นเกี่ยวข้องกับ compiler บอกใครว่าผิดเลย ทุกการเปลี่ยนแปลงมาจากการดูคนตอบสนองแล้วปรับตัวแปรทีละตัว สังเกตด้วยว่ารอบ 2 เปลี่ยนสิ่งที่ผิดก่อน -- forward arc ไม่ใช่ height -- และรอบ 3 เท่านั้นที่เจอ fix จริง ๆ การเดาผิดแบบนี้เป็นเรื่องปกติและคาดหวังได้ ซึ่งเป็นเหตุผลที่ iteration แบบถูกและเร็วสำคัญมาก: การเดาผิดที่เสียเวลาแค่บ่ายวันเดียวใน grey box ไม่เป็นไร แต่การเดาผิดแบบเดียวกันที่เพิ่งค้นพบหลังจากอาร์ตและ animation สุดท้ายผ่านไปสามเดือนคือหายนะ

Common mistake คิดว่าโค้ดใช้แล้วทิ้งของ prototype ต้องสะอาด, เทสต์แล้ว และใช้ซ้ำได้ ไม่จำเป็นเลย -- นั่นคือประเด็นทั้งหมดของการเรียกมันว่า prototype โค้ดที่เขียนมาเพื่อตอบคำถาม "นี่สนุกไหม" ให้เร็วที่สุด แล้วลบทิ้งหรือเขียนใหม่เมื่อรู้คำตอบแล้ว กำลังทำหน้าที่ของมันถูกต้อง ถึงแม้ code reviewer จากบท 17.4 จะปฏิเสธทุกบรรทัดของมันก็ตาม ความผิดพลาดคือการลืมเขียนมันใหม่จริง ๆ ก่อนที่มันจะถูกปล่อยออกไป prototype ที่ค่อย ๆ กลายเป็น production code เพราะไม่มีใครมีเวลาทำใหม่ให้ถูกต้อง เป็นต้นตอที่พบบ่อยของระบบที่ยุ่งเหยิงที่สุดในเกมที่วางขายจริง

3. Scrum แบบเข้าใจง่าย: backlog, sprint, stand-up, review, retrospective

Scrum คือวิธีที่สตูดิโอเกมส่วนใหญ่ใช้จัดระเบียบ iteration loop จากหัวข้อ 2 ให้กลายเป็นตารางที่วนซ้ำ มันคือชุด meeting ที่ตายตัวและช่วงเวลาที่ตายตัว (ปกติหนึ่งหรือสองสัปดาห์ เรียกว่า sprint) ที่ทั้งทีม commit ร่วมกัน เพื่อให้คำถาม "เรากำลังจะสร้างอะไรต่อ และเราอยู่ในแทร็กไหม" มีคำตอบที่คาดเดาได้และสม่ำเสมอ แทนที่จะถูกถามแบบสุ่ม ๆ

ระบบนี้ประกอบด้วยห้าชิ้นส่วน:

สอง role ที่ทำให้ระบบนี้เดินต่อไปได้ Product Owner เป็นคนตัดสินใจว่า backlog มีอะไรบ้างและเรียงลำดับยังไง -- ปกติเป็น lead designer หรือ producer คนที่มีอำนาจพูดว่า "อันนี้สำคัญกว่าอันนั้น" Scrum Master รัน meeting, เอาตัวบล็อกออก และปกป้องทีมจากการเพิ่ม scope กลาง sprint -- ไม่ใช่ manager แบบดั้งเดิม แต่เป็นเหมือนกรรมการที่มีหน้าที่รักษาให้ process ซื่อตรง

one scrum cycle (two-week sprint) BACKLOG (everything, roughly ordered by priority) | | Product Owner picks the top slice v SPRINT PLANNING -- team agrees what fits in 2 weeks | v +---------------------------------------------+ | THE SPRINT (10 working days) | | | | day 1 -- stand-up -- day 2 -- stand-up -- ...| | (same 3 questions, every single day) | +---------------------------------------------+ | v SPRINT REVIEW -- show what got built, to anyone watching | v RETROSPECTIVE -- team-only: what should change next sprint | +--------> back to BACKLOG for the next slice

นี่คือ stand-up แบบที่มันฟังดูจริง ๆ สามคนใน meeting สิบนาที ในวันที่ 6 ของ double-jump sprint จากหัวข้อ 1:

STAND-UP -- วันที่ 6 จาก 10

PRODUCER (คนรัน): โอเค รอบเร็ว ๆ Mina เริ่มเลย

MINA (programmer): เมื่อวานทำ jump-count logic เสร็จแล้ว เปิด PR ไว้
วันนี้จะไปหยิบบั๊ก hit-stun ที่ QA เจอเมื่อวาน ไม่มีอะไรบล็อก

PRODUCER: ดี Dao ล่ะ?

DAO (artist): เมื่อวานกับวันนี้เป็น second-jump pose เหมือนกัน --
ใช้เวลานานกว่าที่ประเมินไว้ silhouette มันดูไม่ถูกตอนเคลื่อนไหวเร็ว
เลยกำลังทำตำแหน่งแขนใหม่ น่าจะเสร็จพรุ่งนี้ ไม่มีอะไรบล็อก
แค่ช้ากว่าแผน

PRODUCER: รับทราบ จดไว้แล้ว Fon ล่ะ?

FON (QA): เมื่อวานยื่นไปสาม ticket ตัว hit-stun ที่ Mina พูดถึง
บวกอีกสองตัวเล็ก ๆ วันนี้กำลังรัน regression pass ของ fix จาก
sprint ที่แล้ว มีบล็อกอยู่หนึ่งอย่าง -- ยังไม่มี build ที่ merge
PR ของเมื่อวานเข้าไปแล้ว เลย verify fix ของ Mina ไม่ได้จนกว่ามันจะเข้า

PRODUCER: Mina ping Fon ได้ไหมทันทีที่ PR merge เสร็จ?

MINA: ได้ครับ จะทำทันทีหลัง stand-up

PRODUCER: เยี่ยม ครบทุกคนแล้ว สิบนาที กลับไปทำงานกันต่อ

สังเกตว่า stand-up นั้นไม่ใช่อะไร: ไม่มีใครอธิบายว่าบั๊ก hit-stun ทำงานยังไง ไม่มีใครถกกันว่า second-jump pose ดีหรือเปล่า และไม่มีใครพูดเกินประมาณเก้าสิบวินาที บทสนทนาพวกนั้นมีจริงและสำคัญ แต่มันเกิดขึ้นทีหลัง ในห้องเล็ก ๆ ที่มีแค่ Mina กับ Fon หรือแค่ Dao กับ designer stand-up ที่กลายเป็นการถกเรื่อง design หรือ debugging session จะเลิกเป็นการเช็คสถานะสิบนาทีแล้วเริ่มกินเวลาทั้งเช้าของทุกคน -- การทำให้มันสั้นไม่ใช่พิธีการ มันคือประเด็นทั้งหมดของ meeting นี้เลย

4. ตัวอย่าง sprint สองสัปดาห์แบบเต็ม ๆ พร้อมบอร์ด

ทีมส่วนใหญ่ track sprint บน board: หนึ่งคอลัมน์ต่อหนึ่งสถานะที่ ticket เป็นได้ หนึ่งการ์ดต่อหนึ่ง ticket ย้ายจากซ้ายไปขวาตามที่งานคืบหน้า นี่คือ double-jump sprint จากหัวข้อ 1 สแนปช็อตในวันที่ 6 จาก 10

SPRINT BOARD -- "Traversal improvements" -- day 6 of 10 TO DO IN PROGRESS IN REVIEW DONE ----------------- ----------------- ----------------- ----------------- GD-443 GD-441 GD-442 GD-440 double-jump VFX hit-stun bug jump-count core design spec: [tech artist] fix logic double jump [programmer: [programmer: [designer] Mina] Mina, PR open] ----------------- ----------------- ----------------- ----------------- GD-445 GD-444 GD-439 double-jump sound [artist: Dao, 2nd-jump anim grey-box [audio] second-jump pose (in review) playtest pass pose, running [designer] long] ----------------- ----------------- ----------------- ----------------- GD-438 prototype: is a 2nd jump fun at all? [designer] ----------------- ----------------- ----------------- ----------------- sprint goal: double jump playable end-to-end (logic + placeholder art + basic sound) and merged to the main branch by day 10 burndown (tickets remaining, ideal vs actual): day 1 2 3 4 5 6 7 8 9 10 ideal 6 6 5 4 4 3 2 2 1 0 actual 6 6 6 5 5 5 . . . . (today is day 6 -- running one ticket behind the ideal line)

อ่าน board นั้นแบบที่ producer อ่านมันใน stand-up: ห้า ticket ยังเปิดอยู่ เหลือเวลาสี่วัน หนึ่งในนั้น (2nd-jump animation) ใช้เวลานานเกินไปแล้ว และเส้น burndown (การนับจำนวน ticket ที่ยังไม่ Done ในแต่ละวัน เทียบกับที่ทีมหวังไว้) แสดงว่า sprint ช้ากว่าจังหวะนิดหน่อย ไม่ได้ช้ามาก นั่นคือข้อมูลที่มีประโยชน์ น่าเบื่อ พอดีกับสิ่งที่การเช็คอินสองสัปดาห์ควรผลิตออกมาให้เร็วพอที่จะยังทำอะไรกับมันได้ -- ย้าย ticket ไปให้คนอื่น, ตัด scope หรือแค่ปล่อยให้ Dao ใช้เวลาเพิ่มอีกวัน เพราะที่เหลือทุกอย่างยังอยู่ในแทร็ก

Tip การ์ดควรย้ายไปที่ "In Review" ทันทีที่ pull request ถูกเปิด ไม่ใช่ตอนที่งานรู้สึกว่าเสร็จแล้ว สิ่งนี้เชื่อมตรงกลับไปหานิสัยการทำ code review ในหัวข้อ 8 กับ PR workflow จากบท 17.1 -- ถ้า board แสดง ticket เป็น "In Progress" จนกว่ามันจะ merge เสร็จสมบูรณ์เท่านั้น ทั้งทีมจะเสีย visibility ว่ามีงานเสร็จแล้วรออยู่กี่ชิ้น กำลังรอ reviewer อยู่ เวลารอนั้นมักเป็นต้นตอที่ใหญ่ที่สุดและแก้ไขง่ายที่สุดของ sprint ที่ช้า

5. Scrum พังยังไงบ้างจริง ๆ ในสตูดิโอเกม

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

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

6. Kanban: ทางเลือกที่เบากว่า

Kanban เก็บ board จากหัวข้อ 4 ไว้ แต่ทิ้งกล่องสองสัปดาห์ตายตัวไปเลย ไม่มี sprint ไม่มี sprint planning meeting และมักไม่มี story point -- มีแค่ backlog, board และกฎเกี่ยวกับว่างานเท่าไหร่ที่อนุญาตให้ "in progress" พร้อมกันได้ เรียกว่า WIP limit (work-in-progress limit) งานไหลต่อเนื่อง: ทันทีที่ ticket หนึ่งเสร็จ คนที่ทำมันเสร็จก็ดึงตัวถัดไปจากด้านบนสุดของ backlog แทนที่จะรอ sprint ถัดไปเริ่ม

KANBAN BOARD -- QA triage, ongoing (no sprint boundary) BACKLOG TO DO IN PROGRESS IN REVIEW DONE (WIP: 4) (WIP: 3) (WIP: 2) ---------- --------------- --------------- ------------- --------- (47 more BUG-812 BUG-808 BUG-803 BUG-799 tickets, crash on texture pop UI overlap ... sorted by alt-tab on 21:9 severity) BUG-810 BUG-807 BUG-801 audio cuts enemy stuck BUG-795 out on pause on stairs BUG-809 BUG-805 save icon wrong state BUG-811 (waiting -- IN PROGRESS is already full, 3/3) rule: nobody pulls a new ticket into IN PROGRESS while it already holds 3 -- finish or hand off something first

WIP limit คือกลไกทั้งหมด ถ้าไม่มีมัน ทุกคนจะเริ่มห้าอย่างพร้อมกันแล้วไม่เสร็จสักอย่าง เพราะการเริ่มรู้สึกเหมือนความคืบหน้าถึงแม้จะไม่ใช่ก็ตาม ด้วย WIP limit เท่ากับ 3 บน "In Progress" ticket ที่สี่จะเริ่มไม่ได้เลยจริง ๆ จนกว่าหนึ่งในสาม ticket ปัจจุบันจะย้ายไป Review -- ซึ่งบังคับให้ทีมทำงานให้เสร็จจริง ๆ แทนที่จะกระจายตัวเองไปทั่วทุกอย่างใน backlog พร้อมกัน

Kanban มักเหมาะกับทีมที่งานเข้ามาแบบคาดเดาไม่ได้และต้องการการตอบสนองที่เร็วมากกว่าแผนสองสัปดาห์: QA triage (ความรุนแรงของบั๊กไม่รอ sprint boundary อย่างสุภาพ), live-ops (อีเวนต์หรือปัญหาเซิร์ฟเวอร์ต้องการความสนใจวันนี้ ไม่ใช่ตอน planning meeting ถัดไป) และทีม tools ที่ซัพพอร์ตส่วนที่เหลือของสตูดิโอ (ใครก็ตามที่ติดอยู่ต้องการให้ tool ของตัวเองถูกแก้เดี๋ยวนี้ ไม่ใช่อีกสิบวัน) ทีม feature ที่สร้างอะไรใหญ่และประสานงานกัน -- แบบตัวอย่าง double-jump ที่วิ่งผ่านบทนี้ -- มักยังชอบจังหวะตายตัวของ scrum มากกว่า เพราะ checkpoint สองสัปดาห์ให้ทุกคนมีช่วงเวลาที่แชร์ร่วมกันและคาดเดาได้ในการโชว์งานและวางแผนใหม่ด้วยกัน

scrum vs kanban, side by side aspect scrum kanban -------------------- ------------------------ ------------------------ time-boxed? yes -- fixed sprints no -- continuous flow planning meeting yes, every sprint rarely, or ad hoc limits work via a fixed sprint commitment a WIP limit per column best fit planned feature work unpredictable, reactive work (QA, live-ops, tools support) typical team feature / gameplay team QA, live-ops, tools
Tip สตูดิโอจริงจำนวนมากรันทั้งสองอย่างพร้อมกัน: ทีม feature ใช้ scrum นั่งอยู่ข้าง ๆ ทีม live-ops หรือ QA ที่ใช้ kanban ดึงงานจาก backlog เดียวกัน ทั้งสองอย่างไม่ใช่ศาสนาคู่แข่งกัน -- มันคือสองวิธีจัดตารางงาน และสตูดิโอเลือกอันที่ตรงกับว่างานของทีมนั้น ๆ คาดเดาได้แค่ไหนจริง ๆ

7. เขียน ticket ที่คนอื่นหยิบไปทำต่อได้จริง ๆ

ticket คือหน่วยที่ process ทั้งหมดนี้รันอยู่บน -- มันคือสิ่งที่อยู่บน board ในหัวข้อ 4 สิ่งที่ถูกประมาณสำหรับ sprint และสิ่งที่ programmer, artist หรือ QA tester เปิดขึ้นมาจริง ๆ เพื่อรู้ว่าต้องทำอะไร ticket ที่แย่เสียเวลาจริงของทุกคนที่แตะมัน -- ต้องมีใครสักคนตามหาคนรายงาน ถามว่าหมายความว่ายังไง แล้วรอคำตอบก่อนที่งานจะเริ่มได้ ticket ที่ดีตอบคำถามที่ชัดเจนก่อนที่ใครจะต้องถาม

ticket ที่ดี ไม่ว่าจะอธิบายงานแบบไหน ตอบสี่อย่าง: what ควรเกิดอะไรขึ้น, why ทำไมมันถึงสำคัญ, repro steps ที่แม่นยำถ้าเป็นบั๊ก (ลำดับการกระทำที่แม่นยำที่ทำให้ปัญหาเกิดขึ้น ทุกครั้ง) และ acceptance criteria (ลิสต์เงื่อนไขสั้น ๆ ที่เช็คได้ ที่ต้องเป็นจริงก่อนที่ ticket จะนับว่าเสร็จ)

นี่คือบั๊กจริง เขียนแบบที่มันมักจะเข้ามาครั้งแรก:

Title: jump พังช่วยแก้หน่อย

double jump มันพังบางที ช่วยดูให้หน่อย เกิดขึ้นบ่อยตอนเล่น
ค่อนข้าง urgent นะ

ไม่มีอะไรใน ticket นั้นบอก programmer เลยว่าจะเริ่มตรงไหน "บางที" กับ "บ่อย" ไม่ใช่ repro steps -- ไม่มีลำดับการกระทำให้ทำตาม ไม่มีทางรู้ว่า fix แก้ได้จริงไหม และ "ค่อนข้าง urgent" ก็ไม่ได้ให้ข้อมูลจริงเลยว่ามันแย่แค่ไหน ใครก็ตามที่หยิบอันนี้ไปทำต้องไปตามหาคนรายงานแล้วถามสามสี่คำถามก่อนที่จะเริ่มได้ด้วยซ้ำ ซึ่งเป็นต้นทุนที่ ticket ที่ดีควรจะเลี่ยงได้พอดี

นี่คือบั๊กเดียวกัน เขียนใหม่:

Title: Double jump cooldown ไม่ reset ถ้าผู้เล่นโดนตีกลางอากาศ
ระหว่าง jump ที่สอง

WHAT: cooldown ของ double-jump ผู้เล่นไม่ reset หลังจาก landing
ถ้าผู้เล่นโดนศัตรูโจมตีระหว่างลอยอยู่ในอากาศตอน jump ที่สอง
ผู้เล่นจะติดอยู่ กระโดดสองครั้งไม่ได้จนกว่า level จะ reload

WHY: สิ่งนี้บล็อกผู้เล่นไม่ให้ผ่านทุก encounter ที่คาดว่าจะต้องใช้
double jump ทันทีหลังโดนตี -- ตอนนี้ทำให้ boss fight ใน Level 4
ชนะไม่ได้เลยถ้าไม่ reload

REPRO STEPS:
  1. เริ่ม Level 4 ไปถึง boss arena
  2. Jump หนึ่งครั้ง แล้ว jump อีกครั้ง (double jump) เพื่อ trigger
     สถานะ jump ที่สอง
  3. ระหว่างยังลอยอยู่ใน jump ที่สอง ให้ fireball ของ boss
     โดนผู้เล่น
  4. Land ตามปกติ
  5. ลอง double jump อีกครั้ง -- BUG: jump ที่สองไม่ trigger

EXPECTED: หลัง landing double jump ควรใช้ได้อีกครั้ง เหมือน
การ landing ปกติทุกครั้ง

ACTUAL: cooldown ของ double-jump ค้างอยู่ที่สถานะ "used" ถาวร
หลังจากลำดับเหตุการณ์นี้ จนกว่า level จะ reload

ENVIRONMENT: build 0.14.2, PC, reproduce ได้ 5/5 ครั้ง

SEVERITY: High -- บล็อกความคืบหน้าใน encounter ที่จำเป็น ไม่ใช่
crash มี workaround (reload level) แต่ workaround ไม่ชัดเจน
สำหรับผู้เล่น

ACCEPTANCE CRITERIA:
  - Double jump ใช้ได้อีกครั้งหลัง landing ทุกครั้ง รวมถึง landing
    ที่เกิดขึ้นหลังโดนความเสียหายกลางอากาศด้วย
  - ไม่มี regression ใหม่ในกรณี double-jump ปกติ (ไม่โดนตี) --
    verify กับ repro steps เดิมของ BUG-799 ด้วย

ทุกส่วนของการเขียนใหม่นี้มีอยู่เพื่อประหยัดเวลาของคนอื่น repro steps ทำให้ programmer reproduce บั๊กบนเครื่องตัวเองได้ในสามสิบวินาที แทนที่จะรอคำตอบ why ทำให้ใครก็ตามที่จัด priority backlog ในหัวข้อ 3 แยกมันออกจากบั๊กเล็กน้อยได้แค่มองผ่าน ๆ โดยไม่ต้องมี game-design context โหลดอยู่ในหัว acceptance criteria ทำให้ QA รู้แน่ชัดว่าต้อง re-test อะไรก่อนจะ mark ว่า verified และ programmer รู้แน่ชัดว่าจะหยุดตอนไหน -- ถ้าไม่มี acceptance criteria "เสร็จ" ก็เป็นเรื่องความเห็นส่วนตัว และความเห็นส่วนตัวคือสิ่งที่ ticket มีอยู่เพื่อเอาออกจากสมการพอดี

Common mistake เขียน acceptance criteria คลุมเครือจนเช็คจริงไม่ได้ -- "jump ควรรู้สึกดี" ไม่ใช่ acceptance criteria มันคือ design goal acceptance criterion ที่แท้จริงคือสิ่งที่คนละคนที่ไม่ได้อยู่ในห้องตอนเขียน ticket สามารถดูฟีเจอร์ที่เสร็จแล้วแล้วตอบ yes หรือ no ได้โดยไม่ต้องเดา

8. Code review: ต้องดูอะไรบ้าง และพูดยังไงไม่ให้กลายเป็นเรื่องทะเลาะกัน

Code review คือจุดที่คนที่สองอ่านการเปลี่ยนแปลงก่อนที่มันจะ merge -- pull request workflow เดียวกับบท 17.1 แค่มองจากฝั่งคนแทนฝั่งกลไกของ git หน้าที่ของมันคือจับสิ่งที่ผู้เขียนมองไม่เห็นเพราะใกล้ชิดกับโค้ดของตัวเองเกินไป และกระจายความรู้เกี่ยวกับ codebase ให้อยู่ในหัวมากกว่าคนเดียว

สิ่งที่ reviewer ควรมองหาจริง ๆ

การให้ feedback โดยไม่ทะเลาะกัน

นิสัยที่มีประโยชน์ที่สุดอย่างเดียวคือเล็งทุกคอมเมนต์ไปที่โค้ด ไม่ใช่ไปที่ตัวคน และใช้คำแบบคำถามหรือข้อสังเกตแทนคำสั่ง ลองเทียบสองคอมเมนต์นี้บนบรรทัดเดียวกันเป๊ะ ๆ:

BAD:
"อันนี้ผิดนะ ลืมเช็ค null ตรงนี้"

GOOD:
"ถ้า `target` เป็น null ตอนที่บรรทัดนี้รัน จะเกิดอะไรขึ้น -- เช่น
ศัตรู despawn ไปในเฟรมเดียวกับที่ตรงนี้ทำงานพอดี? อาจจะคุ้มที่จะ
ใส่ null check หรือไม่ก็เราอาจพลาด guard ที่จัดการเรื่องนี้ไว้แล้ว
ที่ไหนสักที่ก่อนหน้านี้ใน call chain"

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

อีกสองนิสัยที่สำคัญเกือบเท่าโทนเสียง อย่างแรก แยก blocking comment (ต้องแก้ก่อนที่จะ approve) ออกจาก nit (nitpick -- ความชอบเรื่อง style เล็ก ๆ ที่ optional คุ้มที่จะแปะป้าย "nit:" เพื่อให้ผู้เขียนรู้ว่าเมินมันได้โดยไม่ต้อง review รอบสอง) อย่างที่สอง พูดอะไรบางอย่างเมื่อโค้ดดี ไม่ใช่แค่ตอนที่มันผิด -- review ที่วิจารณ์ล้วน ๆ ร้อยเปอร์เซ็นต์ ถึงแม้จะใช้คำสุภาพก็ตาม ก็ยังอ่านออกมาเป็นแง่ลบร้อยเปอร์เซ็นต์อยู่ดี

BAD (review ที่มีแต่ปัญหา ไม่มี nit ไหนถูก mark):
"เปลี่ยนชื่อตัวแปรนี้ ฟังก์ชันนี้ยาวเกินไป ใส่ null check ตรงนี้
คอมเมนต์นี้เก่าไปแล้ว ใช้ DamageCalculator ที่มีอยู่แล้วแทนที่จะ
เขียนเองใหม่"

GOOD (ความกังวลเดียวกัน แต่จัดลำดับความสำคัญและแยกกัน):
"เจ๋งมากที่ใช้ hit-stun timer เดิมซ้ำแทนที่จะเพิ่มตัวใหม่ -- ลด
ของที่ต้อง sync กันไปหนึ่งอย่าง

Blocking: ตรงนี้ reimplement คำนวณ damage ที่ DamageCalculator
ทำอยู่แล้วไม่กี่บรรทัดข้างบนใน CombatSystem.cs -- น่าจะเรียกอันนั้น
แทน เพื่อให้สอง path นี้ยังคง consistent กันถ้าสูตรเปลี่ยน

nit: `x` -- อาจเปลี่ยนชื่อเป็น `hitStunRemaining` ก็ได้ ถ้าคุณกำลัง
แตะบรรทัดนี้อยู่แล้ว

nit: คอมเมนต์บรรทัด 40 ยังอธิบาย cooldown logic แบบเก่าอยู่ อาจจะ
เก่าไปแล้ว"

การรับ feedback

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

Tip ถ้า review thread ไปกลับมากกว่าสองสามรอบโดยไม่จบ หยุดพิมพ์แล้วคุยกันแทน -- call สองนาทีแก้ปัญหาได้ในเก้าสิบวินาที สิ่งที่ comment thread อาจลากยาวไปครึ่งวัน และไม่มีใครได้ยินโทนเสียงในข้อความแชทหรอก

9. Milestone: vertical slice, alpha, beta, gold

sprint ตอบคำถาม "ทีมทำอะไรเสร็จในสองสัปดาห์ที่ผ่านมา" milestone ตอบคำถามที่ใหญ่กว่า: "เกมทั้งเกม โดยรวมแล้ว อยู่ในแทร็กไหม" การ production เกมถูกจัดระเบียบรอบ ๆ milestone ที่มีชื่อไม่กี่ตัว และการรู้ว่าแต่ละอันหมายความว่าอะไรจริง ๆ -- ไม่ใช่แค่ชื่อของมัน -- บอกอะไรได้เยอะเกี่ยวกับว่าโปรเจกต์อยู่ในสถานะไหนจริง ๆ เมื่อมีคนพูดถึงมัน

production timeline (simplified -- real schedules vary a lot by studio size) PRE-PRODUCTION PRODUCTION POST ---------------- ------------------------------------------ --------- prototyping, | | | | live-ops, grey-box tests, | | | | patches, answering "is | | | | DLC this fun" (sec 2) | | | | v v v v VERTICAL ALPHA BETA GOLD SLICE (feature (content (final build, (proof of complete) complete / cert passed, quality content ships) bar) lock) phase A: before alpha -- new SYSTEMS get built phase B: alpha to beta -- new CONTENT gets built, no new systems after alpha phase C: beta to gold -- bug fixing, tuning, polish ONLY -- no new content

สังเกตรูปแบบ: แต่ละ milestone เอาออก หมวดหมู่ของงานที่อนุญาต ไม่ใช่แค่เพิ่ม deadline ก่อน alpha ระบบใหม่ยังเล่นได้อยู่ ระหว่าง alpha กับ beta ระบบใหม่หยุด แต่ content ใหม่ยังมาต่อ หลัง beta ทั้งสองอย่างหยุดหมด -- สิ่งเดียวที่เหลือคือทำให้สิ่งที่มีอยู่แล้วถูกต้องและ polish แล้ว ทีมที่ยังออกแบบกลไกใหม่หลัง beta ไม่ใช่แค่ช้ากว่า schedule นิดหน่อย -- มันข้ามความหมายจริงของ milestone ไปแล้ว ซึ่งปกติเป็นปัญหาที่ใหญ่กว่าที่ปฏิทินอย่างเดียวจะแสดงให้เห็นมาก

Common mistake มอง milestone เป็นแค่วันที่บนปฏิทิน แทนที่จะเป็นการเปลี่ยนแปลงจริงของงานที่อนุญาต "เราถึง alpha แล้ว" ควรหมายถึง feature list ถูกล็อกจริง ๆ ไม่ใช่ "วันที่ที่ติดป้าย alpha บน schedule ผ่านไปแล้ว ในขณะที่เรายังเพิ่ม feature ต่อไป" milestone ที่ถึงแค่ในชื่อ โดยเมินกฎที่อยู่เบื้องหลัง ไม่ได้ซื้ออะไรให้ schedule เลย -- มันแค่ย้ายจังหวะที่ทุกคนยอมรับว่าเกมยังไม่ feature complete ให้ไกลออกไปอีก ใกล้ gold เข้าไปอีก ตรงที่มีเวลาเหลือให้ตอบสนองน้อยลงมาก

10. Crunch: ทำไมมันเกิดขึ้น และทำไมมันคือความล้มเหลวของ management

Crunch คือ overtime ต่อเนื่อง -- ช่วงเวลายาว ๆ ของกลางคืนและวันหยุดสุดสัปดาห์ -- ที่ทำเพื่อให้ทัน deadline ปกติกระจุกอยู่ในสัปดาห์ก่อน milestone จากหัวข้อ 9 มันมีประวัติศาสตร์ยาวนานในวงการเกม และนานพอ ๆ กันที่มันถูกพูดถึงราวกับเป็นคุณธรรมส่วนบุคคล: หลักฐานว่าทีมใส่ใจ เครื่องหมายของความทุ่มเท คุ้มค่าที่จะพูดให้ชัดว่าทำไม framing แบบนั้นถึงผิด

deadline หนึ่งมีตัวแปรที่ขยับได้พอดีสามตัว: scope (สร้างเยอะแค่ไหน), schedule (มีเวลาเท่าไหร่) และ people (ทีมมี capacity เท่าไหร่) เมื่อ scope โต -- feature ใหม่ถูกเพิ่มกลาง production หรือการประมาณเดิมของงานที่มีอยู่แล้วมองโลกในแง่ดีเกินไป -- แล้วทั้ง schedule กับจำนวนคนไม่ขยับเพื่อดูดซับมัน ตัวแปรเดียวที่เหลือให้ดัดได้คือจำนวนชั่วโมงที่แต่ละคนใช้ต่อสัปดาห์ การดัดนั้นคือ crunch มันไม่ใช่พลังลึกลับที่โผล่มาใกล้วันวางขาย มันคือสิ่งที่เกิดขึ้น ในทางคณิตศาสตร์ล้วน ๆ เมื่อ scope โตเกิน schedule แล้วไม่มีใครที่มีอำนาจเปลี่ยนอย่างใดอย่างหนึ่งทำอะไรกับมันเลย

three variables, one of them always has to move SCOPE x SCHEDULE x PEOPLE --> the work that gets done if scope grows and schedule + people stay fixed... ...the only thing left that CAN move is hours per person per week ...that is crunch, and it is a DECISION, made (or failed to be made) by whoever controls scope and schedule -- not an act of nature that arrives near a deadline on its own

นี่คือเหตุผลที่ crunch ถูกอธิบายอย่างถูกต้องว่าเป็น management failure มากกว่าความล้มเหลวของทีมหรือสิ่งที่หลีกเลี่ยงไม่ได้: scope กับ schedule คือการตัดสินใจที่ producer, lead และผู้บริหารสตูดิโอทำ ไม่ใช่การตัดสินใจที่ engineer ที่เขียนโค้ดจะทำเองได้ ทีมไม่สามารถ "แค่ทำงานหนักขึ้น" เพื่อหลุดจากปัญหา scope ที่พวกเขาไม่ได้สร้างและไม่มีอำนาจแก้ด้วยการตัด feature และงานวิจัยเกี่ยวกับ overtime ต่อเนื่องก็สอดคล้องกันและไม่น่าฟัง: productivity กับคุณภาพโค้ดทั้งคู่ลดลงอย่างเห็นได้ชัดหลังจากประมาณ 40-50 ชั่วโมงต่อสัปดาห์ และการลดลงนี้ยิ่งสะสมมากขึ้นเมื่อ overtime ยืดยาวขึ้น -- ดังนั้น crunch ไม่ได้ซื้อเวลาที่มันเสียไปกลับคืนมาได้อย่างน่าเชื่อถือด้วยซ้ำ ทีมที่ crunch หกสัปดาห์เพื่อให้ทันวันวางขาย มักวางขายเกมที่มีบั๊กมากกว่าทีมที่ตัด scope แล้ววางขายวันเดียวกันแบบพักผ่อนเพียงพอ เพราะ programmer ที่เหนื่อยเขียนบั๊กมากขึ้น QA ที่เหนื่อยพลาดมากขึ้น และ reviewer ที่เหนื่อยจับได้น้อยลงใน process ของหัวข้อ 8

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

Tip ถ้าคุณเคยได้ยิน "เราไม่ได้ crunch นะ มันแค่ overtime แบบสมัครใจ" ในสตูดิโอที่คนไหนก็ตามที่ปฏิเสธ overtime "แบบสมัครใจ" นั้น ไม่ได้ review score, ขึ้นเงินเดือน หรือชื่อเสียงเท่ากับคนที่อยู่ดึก นั่นคือ crunch ที่แค่เปลี่ยนชื่อ ดูว่าอะไรถูกให้รางวัลจริง ๆ ไม่ใช่ดูว่าเอกสารนโยบายเขียนว่ายังไง

11. คุยกับคนที่งานของเขาไม่ใช่โค้ด

programmer ใช้เวลาส่วนใหญ่ในแต่ละวันคุยกับ programmer คนอื่น ด้วยศัพท์ที่แชร์กันซึ่งสร้างมาหลายปี designer, artist และ producer ไม่ได้แชร์ศัพท์นั้น และการอธิบายข้อจำกัดทางเทคนิคให้พวกเขาฟังด้วยภาษา programmer มักจะฟังดูเหมือน "คอมพิวเตอร์บอกว่าไม่ได้" -- จริง แต่ไม่มีประโยชน์ เพราะมันไม่ให้อะไรที่อีกฝ่ายเอาไปทำต่อได้เลย วิธีแก้คือแปลข้อจำกัดทางเทคนิคให้เป็นเทอมที่อีกฝ่ายคิดอยู่จริง ๆ: มันมีต้นทุนเท่าไหร่ ในหน่วยที่พวกเขาทำอะไรกับมันได้ และทางเลือกที่เป็นจริงมีอะไรบ้าง

สมมติว่า designer ขอ battle ที่มีศัตรูพันตัวอยู่บนหน้าจอพร้อมกัน แต่ละตัวรัน pathfinding เต็มรูปแบบ, physics และตัดสินใจ AI แบบเฉพาะตัว -- ต้นทุนต่อศัตรูแบบเดียวกับที่บท AI กับ physics ของหลักสูตรนี้ครอบคลุมไว้อย่างละเอียด

BAD:
"ทำไม่ได้หรอก pathfinding มัน O(n log n) ต่อ agent แล้ว physics
broad-phase ก็ไม่ scale แบบนั้น เราจะเป่า frame budget ทันที"

GOOD:
"ศัตรูพันตัวที่แต่ละตัวทำ pathfinding กับ physics เต็มรูปแบบทุก
เฟรม จะทำให้เราต่ำกว่า 10 fps ไปเลย -- เล่นไม่ได้ นี่คือสิ่งที่
เป็นไปได้จริง: ศัตรูประมาณ 30 ตัวที่สู้จริงด้วย AI กับ physics
เต็มรูปแบบ บวกอีกหลายร้อยตัวที่อยู่ด้านหลังทำเวอร์ชันปลอมที่ถูก
กว่ามาก -- เคลื่อนที่ง่าย ๆ ไม่มีการตัดสินใจแบบเฉพาะตัว ใกล้เคียง
พอที่ผู้เล่นส่วนใหญ่จะแยกไม่ออก เว้นแต่จะจ้องตัวใดตัวหนึ่งตรง ๆ
แบบนี้จะให้ความรู้สึก 'ฝูงที่ล้นหลาม' ที่อยากได้ไหม หรือ fight
นี้ต้องการศัตรูพันตัวที่ผู้เล่น engage เป็นรายตัวได้จริง ๆ?"

เวอร์ชัน bad ไม่ได้ผิด -- ตัวเลข complexity เป็นเรื่องจริง -- แต่มันตอบคำถามที่ designer ไม่ได้ถาม แล้วทิ้งให้พวกเขาไม่มีอะไรให้ตัดสินใจต่อ เวอร์ชัน good บอกต้นทุนจริงในหน่วยที่ designer คิดอยู่แล้ว (frame rate, "เล่นไม่ได้") เสนอตัวเลขที่เป็นรูปธรรมที่ใช้งานได้จริง และที่สำคัญที่สุด จบด้วยการถามว่าเป้าหมายจริงของ designer คืออะไร บ่อยครั้งข้อจำกัดที่ซื่อตรง ("ศัตรูจำลองเต็มรูปแบบพันตัว") กับเป้าหมายจริง ("มันควรรู้สึกล้นหลาม") ไม่ใช่คำขอเดียวกันเลย และเวอร์ชันที่ถูกกว่าก็ตอบเป้าหมายจริงได้ครบถ้วน

แพทเทิร์นเดียวกันนี้ใช้ได้ในทิศทางตรงข้ามด้วย สมมติว่า artist ส่ง prop ที่มี 500,000 triangles สำหรับสิ่งที่จะปรากฏเป็นของเกะกะพื้นหลัง สามสิบชิ้นพร้อมกัน บนเกมมือถือที่งบตึงมาก:

BAD:
"อันนี้ poly สูงเกินไปมาก จะทำให้ performance พังนะ"

GOOD:
"อันนี้ดูดีมากตอนมองใกล้ ๆ -- แต่เราจะมีประมาณ 30 ชิ้นแบบนี้อยู่
บนหน้าจอพร้อมกัน ในระยะที่ผู้เล่นจะเห็นมันเป็นแค่ silhouette
เป็นหลัก และงบ scene ทั้งหมดของเราบนมือถืออยู่ที่ประมาณ 500k
triangle รวมกัน ตอนนี้ prop ชิ้นเดียวนี้อย่างเดียวจะใช้งบทั้งหมด
ของทั้งหน้าจอไปเลย เราขอเวอร์ชันรายละเอียดต่ำกว่าสำหรับ instance
พื้นหลังได้ไหม แล้วเก็บ detail level นี้ไว้แค่จุดที่เป็น hero
placement ตอนมองใกล้ ๆ? ยินดีเช็คกับ tech artist ว่า triangle
count เท่าไหร่ที่ยังดูดีจริง ๆ ในระยะที่ prop นี้จะถูกมองเห็นตาม
ปกติ"

สังเกตว่ารูปแบบซ้ำเดิม: บอกตัวเลขจริงในบริบทที่อีกฝ่ายทำอะไรกับมันได้ (30 instance, งบ scene ทั้งหมด ไม่ใช่แค่ asset ชิ้นนี้แยกเดี่ยว ๆ) และเสนอทางไปต่อแทนที่จะแค่ปฏิเสธ artist ที่ได้ยิน "poly สูงเกินไป" ไม่ได้เรียนรู้อะไรที่เอาไปใช้ครั้งหน้าได้เลย artist ที่ได้ยิน "นี่คืองบที่มันต้องพอดี และนี่คือเหตุผล" จะตัดสินใจแบบเดียวกันเองได้กับ prop อีกสิบชิ้นถัดไป โดยไม่ต้องให้ programmer review ทุกชิ้นเลย

Common mistake อธิบายข้อจำกัดครั้งเดียว ถูกต้อง แล้วคิดว่ามันจบตลอดไป designer หรือ artist ที่ถูกบอก "ไม่" โดยไม่มีเหตุผลที่ใช้ซ้ำได้ จะถามอีกครั้งเดือนหน้า ไม่ใช่เพราะดื้อ แต่เพราะพวกเขาไม่เคยเรียนรู้งบที่อยู่เบื้องหลังจริง ๆ -- รู้แค่ว่าคำขอเฉพาะครั้งนี้โดนปฏิเสธ การอธิบาย why ในเทอมของพวกเขา คือสิ่งที่ป้องกันบทสนทนาเดียวกันไม่ให้เกิดซ้ำจริง ๆ

12. อภิธานศัพท์ (Glossary)

13. แบบฝึกหัด

Exercise 1 นี่คือ ticket จริง เขียนแบบที่มันถูกยื่นเข้ามาครั้งแรก:
Title: กล้องบั๊กใกล้น้ำ ช่วยเช็คหน่อย

บางทีเวลาอยู่ใกล้น้ำ กล้องทำอะไรแปลก ๆ แล้วค้าง รำคาญนิดหน่อย
ขอบคุณครับ
เขียนใหม่ให้เป็น ticket ที่ดี โดยใช้โครงสร้าง WHAT / WHY / REPRO STEPS / EXPECTED / ACTUAL / ENVIRONMENT / SEVERITY / ACCEPTANCE CRITERIA จากหัวข้อ 7 คุณสามารถคิดรายละเอียดที่สมเหตุสมผลขึ้นมาเองได้ (ชื่อ level, พฤติกรรมกล้อง, เลข build) ตราบใดที่ ticket อ่านแล้วเหมือนสิ่งที่ programmer ทำอะไรกับมันได้ทันที
Show answer

คำตอบจะแตกต่างกันไปตามรายละเอียดที่คิดขึ้นเอง แต่การเขียนใหม่ที่ดีจะมีรูปแบบนี้:

Title: Camera collision ค้างติดกับ geometry ที่มองไม่เห็นเมื่อ
ผู้เล่นยืนอยู่ริมขอบ water plane

WHAT: เมื่อกล้องที่ผู้เล่นควบคุมอยู่ในระยะประมาณ 2 เมตรจาก water
plane และผู้เล่นหมุนกล้องมองย้อนกลับข้ามน้ำไปทางฝั่ง collision
volume ของกล้องจะหยุดขยับและค้างอยู่กับที่ เมินการกดของผู้เล่น
ต่อไป ถึงแม้จะไม่มีสิ่งกีดขวางที่มองเห็นได้เลย

WHY: สิ่งนี้ทำให้มองรอบตัวตามปกติใกล้แหล่งน้ำไม่ได้เลย ซึ่ง
กระทบ level กลางแจ้งส่วนใหญ่ -- ตอนนี้ reproduce ได้แน่นอนที่
ทะเลสาบใน Level 2 และจุดข้ามแม่น้ำใน Level 5

REPRO STEPS:
  1. โหลด Level 2 เดินไปที่ริมทะเลสาบ
  2. ยืนในระยะประมาณ 2 เมตรจากขอบน้ำ หันหน้าเข้าหาน้ำ
  3. หมุนกล้องให้ชี้ย้อนกลับข้ามน้ำไปทางฝั่งหลังผู้เล่น
  4. หมุนต่อไปช้า ๆ -- BUG: เกินมุมหนึ่ง กล้องหยุดตอบสนองต่อ
     input ไปเลย

EXPECTED: กล้องควรหมุนได้อิสระรอบผู้เล่น ไม่ว่าจะห่างจาก water
plane แค่ไหน เหมือนกับพื้นผิวอื่น ๆ

ACTUAL: กล้องหยุดหมุนทันทีที่ข้ามมุมหนึ่งใกล้น้ำ และไม่กลับมา
จนกว่าผู้เล่นจะเดินห่างจากขอบน้ำมากกว่า 2 เมตร

ENVIRONMENT: build 0.21.0, PC, reproduce ได้ 4/4 ครั้งที่ทะเลสาบ
Level 2, 3/4 ครั้งที่แม่น้ำ Level 5

SEVERITY: Medium -- ไม่บล็อกความคืบหน้าหรือ crash แต่ทำให้การ
คุมกล้องพังในสถานการณ์กลางแจ้งทั่วไปที่ผู้เล่นจะเจอบ่อย

ACCEPTANCE CRITERIA:
  - กล้องหมุนได้อิสระใกล้ water plane ใด ๆ ที่มุมไหนก็ตาม ใน
    ทั้งสองจุด repro
  - ไม่มี regression ใหม่ใน camera collision ปกติกับ geometry
    ของ level ที่อื่น

การปรับปรุงหลักเมื่อเทียบกับต้นฉบับคือแบบเดียวกับหัวข้อ 7: repro steps ที่แม่นยำที่ใครก็ตามทำตามได้ภายในไม่ถึงหนึ่งนาที, why ที่เป็นรูปธรรมที่ทำให้ prioritize ได้ถูกต้อง และ acceptance criteria ที่เจาะจงพอที่ QA จะรู้แน่ชัดว่าต้อง re-check อะไรก่อนจะปิด ticket

Exercise 2 ทีมหนึ่งรัน sprint สองสัปดาห์ Story point ที่ commit ไว้: 42 คำนวณจาก "เราคิดว่าจะทำได้เท่าไหร่ถ้าทุกอย่างไปได้ดี" ไม่ใช่จากค่าเฉลี่ยจริงของ sprint ล่าสุด (ซึ่งอยู่ที่ 28) QA ไม่ได้รับ capacity เฉพาะใน sprint plan เลย -- แผนสมมติว่า QA จะ "เทสต์ไปเรื่อย ๆ ตามที่งานเข้ามา" Stand-up ยาวขึ้นเป็นยี่สิบห้านาที เพราะ producer ใช้มันถาม engineer แต่ละคนให้อธิบายเหตุผลของ ticket ที่ช้า พอถึงวันที่ 8 จาก 10 มีแค่ 15 point ในคอลัมน์ DONE ใช้ลิสต์ failure mode ของ scrum จากหัวข้อ 5 ระบุอย่างน้อยสามอย่างที่ผิดพลาดในวิธีวางแผนและรัน sprint นี้ แล้วสำหรับแต่ละอย่าง บอกว่าหัวข้อ 5 แนะนำให้ทำอะไรแทน
Show answer

Backlog ถูกยัดเกินด้วยความหวัง ไม่ใช่หลักฐาน การวางแผน 42 point เทียบกับค่าเฉลี่ยจริงล่าสุดที่ 28 คือความล้มเหลวที่หัวข้อ 5 เรียกชื่อไว้พอดี -- แผนสร้างขึ้นจากความหวัง ไม่ใช่จากสิ่งที่ sprint ล่าสุดพิสูจน์จริงว่าทีมทำเสร็จได้ วิธีแก้ของหัวข้อ 5: วางแผน sprint หน้ารอบค่าเฉลี่ยจริงล่าสุด (ใกล้เคียง 28) ไม่ใช่รอบตัวเลขกรณีที่ดีที่สุด

QA ถูกทิ้งไว้นอกการวางแผน การสมมติว่า QA จะ "เทสต์ไปเรื่อย ๆ ตามที่งานเข้ามา" โดยไม่มี capacity เฉพาะ หมายความว่า sprint plan คิดคำนวณแค่ programmer ทำงานเสร็จเท่านั้น ไม่ได้คิดถึงงานที่ถูก verify จริง วิธีแก้ของหัวข้อ 5: ให้ QA มีเวลาที่ชัดเจนและถูกจัดงบไว้ข้างใน sprint plan เอง แบบเดียวกับที่เวลา programming ของ feature หนึ่งถูกจัดงบไว้

Stand-up กลายเป็นรายงานสถานะให้เจ้านาย stand-up ยี่สิบห้านาทีที่ producer ให้ engineer อธิบายเหตุผลของ ticket ที่ช้า คือ failure mode จากหัวข้อ 5 พอดีเป๊ะ -- มันเลิกเป็นสามคำถามเร็ว ๆ สำหรับทีม แล้วกลายเป็น performance review ต่อหน้าสาธารณะ ซึ่งมักทำให้คนเลิกยอมรับบล็อกจริง ๆ แทนที่จะเปิดเผยมันเร็วขึ้น วิธีแก้ของหัวข้อ 5: คงไว้แค่สามคำถาม แล้วให้บทสนทนา "ทำไมถึงช้า" เกิดขึ้นแบบตัวต่อตัวหลัง stand-up ไม่ใช่ต่อหน้าทั้งทีม

เมื่อรวมทั้งสามปัญหาเข้าด้วยกัน วันที่ 8 จาก 10 ที่มีแค่ 15 จาก 42 point เสร็จ ไม่ใช่เรื่องน่าแปลกใจจริง ๆ -- มันคือผลลัพธ์ที่คาดเดาได้ของ commitment ที่ใหญ่เกินไป ไม่มีเวลา QA จัดงบไว้ และ stand-up ที่กีดกันความซื่อสัตย์เรื่องบล็อกมาตลอด ตามวินัยเรื่อง crunch จากหัวข้อ 10 สิ่งที่ควรทำตอนนี้คือตัด scope สำหรับสองวันที่เหลือ (ตัดสินใจอย่างเปิดเผยว่า ticket ที่ยังไม่เสร็จตัวไหนจะเลื่อนไป sprint หน้า) ไม่ใช่ผลักดัน overtime ที่ไม่ได้วางแผนเพื่อพยายามให้ถึง 42 อยู่ดี

Exercise 3 designer คนหนึ่งขอโหมด battle royale ที่แผนที่ 4 กิโลเมตรทั้งแผนที่ทำลายได้แบบเรียลไทม์เป็นรายชิ้น -- กำแพงไหนก็ได้ อาคารไหนก็ได้ ภูมิประเทศชิ้นไหนก็ได้ -- sync เต็มรูปแบบสำหรับผู้เล่นสูงสุด 100 คนพร้อมกัน อัปเดตทุกเฟรม ใช้วิธีจากหัวข้อ 11 (บอกต้นทุนจริงในเทอมที่ designer คิดอยู่, เสนอทางเลือกที่เป็นรูปธรรม, ถามว่าเป้าหมายจริงคืออะไร) เขียนคำตอบสั้น ๆ ที่คุณส่งเป็นข้อความแชทได้จริง
Show answer

คำตอบจะแตกต่างกันไป แต่คำตอบที่ดีจะมีรูปแบบนี้:

"แบบนั้นจะดูเจ๋งมาก แต่การทำลาย per-object ที่ sync เต็มรูปแบบทั่วทั้งแผนที่ 4 กิโลเมตร อัปเดตทุกเฟรม สำหรับผู้เล่น 100 คน ไม่สมจริงเลย -- client ของผู้เล่นแต่ละคนต้อง sync สถานะที่แม่นยำของชิ้นส่วนที่ทำลายได้เป็นพัน ๆ ชิ้นอย่างต่อเนื่อง แล้ว network traffic อย่างเดียวก็จะถล่ม connection ไปก่อนที่เราจะไปถึงต้นทุนของ rendering หรือ physics ด้วยซ้ำ นี่คือสิ่งที่เป็นไปได้จริง: ชุดโครงสร้างที่ทำลายได้ที่ authored ไว้ล่วงหน้าจำนวนจำกัด (อาจจะ 40-60 จุดทั่วแผนที่ วางไว้ที่ point of interest) บวกกับ scripted destruction event ใหญ่ ๆ ไม่กี่อันที่ผูกกับแมตช์เอง เช่น อาคารถล่มที่ trigger จากพายุใน zone ที่กำลังหด แบบนี้จะให้ความรู้สึก 'แผนที่อันตรายและเปลี่ยนแปลงตลอด' ที่อยากได้ไหม หรือเป้าหมายเจาะจงว่าผู้เล่นต้องทำลายกำแพงไหนก็ได้ตรงไหนก็ได้ที่ตัวเองยืนอยู่?"

สิ่งนี้ทำตามแพทเทิร์นของหัวข้อ 11 เป๊ะ ๆ: มันบอกต้นทุนจริงในหน่วยที่ designer ทำอะไรกับมันได้ (network sync ข้ามผู้เล่น 100 คน ไม่ใช่ notation ของ complexity ดิบ ๆ) เสนอทางเลือกที่เป็นรูปธรรมและวางขายได้จริงแทนการปฏิเสธเปล่า ๆ และจบด้วยการถามว่าเป้าหมายที่แท้จริงคืออะไร -- เพราะ "แผนที่ควรรู้สึกอันตรายและเปลี่ยนแปลง" กับ "กำแพงทุกอันต้องทำลายได้เป็นรายชิ้น" ไม่ใช่คำขอเดียวกัน และเวอร์ชันที่ถูกกว่าอาจตอบเป้าหมายจริงได้ครบถ้วน

ไม่มีอะไรในนี้แทนที่การรู้วิธีเขียนโค้ดได้เลย มันอยู่รอบ ๆ โค้ดต่างหาก designer ที่อธิบายสิ่งที่ต้องการไม่ได้ ticket ที่ไม่บอกว่าทำไมมันถึงสำคัญ review comment ที่เริ่มการทะเลาะแทนที่จะแก้บั๊ก schedule ที่ค่อย ๆ เปลี่ยน "เราหวังว่าจะทำสิ่งนี้เสร็จ" ให้กลายเป็น "เราสัญญาว่าจะทำสิ่งนี้" -- ทุกอย่างในนี้เสียเวลาจริงและคุณภาพจริงของทีม เหมือนกับที่บั๊กหนึ่งจะทำ และไม่มีอันไหนโผล่ใน stack trace เลย programmer ที่ทำงานได้เยอะที่สุดแทบไม่ใช่คนที่เขียนโค้ดฉลาดที่สุดแบบแยกเดี่ยว ๆ พวกเขาคือคนที่ทำให้ designer, artist, tester และ reviewer ทำงานกับพวกเขาได้ง่าย sprint แล้ว sprint เล่า โดยไม่ให้ตัว process เองกลายเป็นสิ่งที่พัง

← กลับไปหน้ารวมบท