หลายคนเข้าใจว่าการแก้โจทย์ Programming คือ การ “เขียนโค้ดให้ได้คำตอบ”
แต่ในความเป็นจริงสิ่งที่สำคัญกว่านั้นคือ "วิธีคิด"

บทความนี้จะพาพวกเราไปเข้าใจแก่นของการแก้โจทย์ programming ที่ไม่ใช่แค่แก้ได้ แต่แก้ได้อย่างมีคุณภาพ และ นำไปใช้ในงานจริงได้


1. เข้าใจปัญหาให้ชัด (Problem Understanding)

ถ้าต้องเลือก ทักษะเดียว ที่สำคัญที่สุดในการแก้โจทย์ Programming มันไม่ใช่ Algorithm หรือ Data Structure แต่ คือ การเข้าใจปัญหาให้ถูกต้องตั้งแต่แรก

เพราะในการทำงานจริง ถ้าหากเรา...

❌ แก้ปัญหาผิด = เสียเวลาทั้งระบบ
✅ เข้าใจปัญหาถูก = ลดความซับซ้อนได้ตั้งแต่ต้น

ดังนั้นก่อนที่เราจะเขียนโค้ด คำถามแรกที่เราควรถามเพื่อค้นหาคำตอบให้ได้ก่อนเลย คือ

  • Input คืออะไร?
  • Output ที่ต้องการคืออะไร?
  • มีข้อจำกัด (constraints) อะไรบ้าง?
  • มี edge cases อะไรที่ต้องระวัง?

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

  • รับค่า n → n เป็นลบได้ไหม?
  • n ใหญ่สุดแค่ไหน?
  • ต้องการ performance ระดับไหน?

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


2. อย่ารีบเขียนโค้ด (Think Before Code)

หนึ่งในความแตกต่างที่สำคัญมากๆ ระหว่างคนที่แก้ปัญหา programming เป็นกับ ไม่เป็น คือ

  • คนที่แก้ปัญหาไม่เป็น: เห็นโจทย์ → เริ่มเขียน loop → แล้วก็ติดไปต่อไม่ได้
  • คนที่แก้ปัญหาเป็น: หยุดคิด → หา pattern

ตัวอย่างโจทย์:

ให้หาผลรวมของเลขคี่ในแต่ละแถว

คนที่แก้ปัญหาไม่เป็น อาจใช้ loop เพื่อรวมค่า

แต่คนที่แก้ปัญหาเป็นจะมองเห็นว่า ผลรวมของแถวที่ n = n³

ซึ่งทำให้ลด complexity จาก O(n) หรือ O(n²) → O(1)

ในกรณีที่เราหา pattern ไม่เจอ วิธีการที่ง่ายที่สุด คือ ลองเขียนตัวอย่างข้อมูล input/output ของแต่ละ case จนกว่าจะมองเห็น pattern แล้วค่อย refactor

ส่งนี้ คือ Test design และการมองหา pattern ที่เหมาะกับวิธีนี้ คือ Test Driven Development (TDD)


3. เลือกวิธีที่เหมาะสม (Trade-off Thinking)

ในการออกแบบโค๊ด จำไว้ว่า ไม่มีวิธีไหนดีที่สุดเสมอ มีแค่ว่า มันเหมาะกับบริบท หรือเปล่า เมื่อก่อนผมก็เป็นคนที่บ้าง Big O พอสมควร ย่อโค๊ดหลายบรรทัดให้เหลือบรรทัดเดียว ตอนนั้น proud มาก แต่เมื่อเวลาผ่านไป 2 เดือน กลับมาอ่านใหม่ งงเด้ เขียนอะไรไป

ดังนั้น ในการเขียน code แต่ละครั้ง สิ่งที่ต้องพิจารณา คือ...

  • Time Complexity (เร็วแค่ไหน)
  • Space Complexity (ใช้ memory เท่าไหร่)
  • Readability (คนอื่นอ่านเข้าใจไหม)

ตัวอย่างโจทย์:

ผลรวมของแถวที่ n = n³
  • ใช้สูตร → เร็ว (O(1)) ได้ แต่ต้องเข้าใจ logic
  • ใช้ loop → เข้าใจง่าย แต่ช้ากว่า

4. คิด Edge Cases เสมอ

โค้ดส่วนใหญ่พังไม่ใช่เพราะ logic หลัก แต่เพราะ “มุมเล็กๆ ที่ไม่ได้คิด” ทุกคนมักออกแบบหรือคิด logic ในมุมของ success case แต่มักลืมคิดในมุมของ fail case โดยเฉพาะ edge cases

ตัวอย่าง edge cases:

  • ค่าต่ำสุด / สูงสุด
  • ค่าที่ไม่คาดคิด
  • input ที่ผิดรูปแบบ
  • n = 0
  • n < 0
  • n ใหญ่มาก (overflow)

Edge case ไม่ใช่เรื่องเล็กๆ เพราะโดยทั่วไปแล้ว มันคือสิ่งที่ทำให้ระบบ fail จริง

ประเภทของ Edge Cases ที่ต้องคิด

  • Boundary Values (ค่าขอบเขต): ค่าต่ำสุด, ค่าสูงสุด → Bug จำนวนมากเกิดที่ “ค่าขอบ” ไม่ใช่ค่าปกติ
  • Empty / Null Input: input = null / input = [] → ในหลายภาษาอาจ crash ทันที หรือ ได้ค่าที่ไม่ถูกต้อง
  • Invalid Input: type ไม่ถูก (string แทน int) → ต้องตัดสินใจว่า จะ throw error? หรือ return default value?
  • Extreme Cases (เคสสุดโต่ง): input ใหญ่มาก, data จำนวนมหาศาล → ต้องคิดต่อว่า performance พอไหม? หรือ memory พอไหม?
  • Duplicate / Unexpected Pattern: ในบางโจทย์อาจมีค่าซ้ำ หรือ มี pattern ที่ไม่ตรง assumption

ผมมี checklist เบื้องต้นไว้ให้:

  1. ค่าต่ำสุดคืออะไร?
  2. ค่าสูงสุดคืออะไร?
  3. มี input ที่ “ผิด” ได้ไหม?
  4. มี input ว่างได้ไหม?
  5. มี case ที่ทำให้ overflow ไหม?

5. เขียนโค้ดให้คนอ่านออก (Readable Code)

โค้ดไม่ได้แค่เขียนให้คอมพิวเตอร์อ่านเท่านั้น แต่ยังต้องเขียนให้ “คน” อ่านออกด้วย อย่างที่ผมเลยบอกไว้ก่อนหน้านี้ ต่อให้ code ที่เราเขียนมันจะเทพแค่ไหน แต่ถ้าเพื่อนร่วมทีมอ่านไม่ออก ก็คงไม่ดีเท่าไหร่ เพราะมันจะกลายเป็นว่า ไม่มีใครกล้าแตะมัน หรืออาจจะถึงขั้นเขียนใหม่เลยก็ได้

ตัวอย่าง เปรียบเทียบ:

❌ ไม่ดี

return n*n*n

✅ ดีกว่า

def sum_odd_row(n: int) -> int:
    return n ** 3

สิ่งสำคัญ:

  • ชื่อ function ชัดเจน
  • แยก responsibility
  • ไม่ clever เกินไป

6. การทดสอบและตรวจสอบ (Validation & Testing)

ในการเขียน code แต่ละครั้งอย่างน้อยควรมีการทดสอบเหล่านี้ นั่นคือ

  • Happy path (เคสปกติ)
  • Edge case
  • Invalid input

ซึ่งเราสามารถเขียน unit testing เพื่อทดสอบได้เลย หลายๆ ครั้งผมมักจะเจอ dev ไม่ค่อยอยากเขียน test กัน เพราะมองว่าเสียเวลา ซึ่งไม่ถูกต้อง เพราะการ test มันไม่ใช่ optional แต่มันเป็นส่วนหนึ่งของการแก้ปัญหา


7. คิดให้ไกลกว่าโจทย์ (Think Beyond Code)

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

ลองถาม:

  • ถ้า function นี้อยู่ใน API จะเกิดอะไรขึ้น?
  • ถ้ามี request จำนวนมาก จะ handle ยังไง?
  • ต้อง cache ไหม?
  • มี concurrency issue ไหม?

สิ่งนี้คือจุดที่แยก “คนเขียนโค้ด” กับ “Software Engineer”


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