Optimize….. เดี๋ยวววว ใจเย็นๆ

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

ที่มาเกิดจากโค๊ดหน้าตาแบบนี้ครับ อันนี้เขียนเป็น Java เพื่อให้อ่านเข้าใจง่ายขึ้น

BankTransferInstruction instruction = null;
for(Transaction transaction: transactions) {
  if(instruction == null) {
    instruction = findBankTransferInstruction(transaction);
  }
  transaction.setBankTransferInstruction(instruction);
}

เห็นบั๊กหรือยังครับ ? ถ้ายังลองคิดดูสักห้านาทีนะครับ

โค๊ดที่แก้ไขบั๊กนี้จริง ๆ ง่ายมากเลย แค่นี้เอง

for (Transaction transaction : transactions) {
  BankTransferInstruction instruction = findBankTransferInstruction(transaction);
  transaction.setBankTransferInstruction(instruction);
}

น่าจะเห็นแล้วนะครับว่า โค๊ดที่มีปัญหามีการมองหาคำสั่งการโอนเงินแค่ครั้งแรกครั้งเดียว (คือไม่เป็น null) ส่วนเราต้องการให้หาทุกครั้ง เราก็แค่สั่งให้มันหาใหม่เท่านั้นเอง

วันนี้มีการคุยกับเรื่องนี้ แล้วก็มีคนเสนอว่า เฮ้ย ทำไมเราไม่ optimize เพิ่มเติมอีกสักหน่อย เอาแบบนี้ …

BankTransferInstruction instruction = null;
for(Transaction transaction: transactions) {
  if(instruction == null ||
     instruction.isApplicable(transaction) {
    instruction = findBankTransferInstruction(transaction);
  }
  transaction.setBankTransferInstruction(instruction);
}

เพิ่มเงื่อนไขมาอีกอัน ก็น่าจะทำให้เร็วขึ้นนะ เพราะไม่ต้องหา instruction ใหม่บ่อย ๆ ใช่ไหมครับ ? ถ้าเรามองกันที่ performance อย่างเดียวก็แน่นอนว่ามันเร็วขึ้นอยู่แล้ว (แต่ก็มีกรณีที่ทำให้ช้าลงได้เหมือนกันครับ เช่นถ้า isApplicable() ดันต้องเรียก database ใหม่ จะกลายเป็นว่าในกรณีที่แย่ที่สุดจะต้องเรียก database สองครั้งต่อหนึ่ง transaction)

คำถามคือ มันจำเป็นต้องทำไหม ?

สิ่งแรกที่เราต้องประเมินกันก่อนที่จะทำ optimization คือ ณ.จุดนั้นมันเป็นปัญหาจริง ๆ หรือเปล่า ? อย่างอันนี้ในกรณีทั่ว ๆ ไปของระบบผม ลูกค้าจะไม่ค่อยสร้างคำสั่งซื้อหรือขายพร้อม ๆ กันมาก ๆ คือ ซื้อพร้อม ๆ กันสักสิบรายการก็แฮ่กแล้วครับ ดังนั้นจำนวนมันจะน้อยมาก ๆ ถ้ามันไม่ได้เป็นคอขวดจริง ๆ เราไม่จำเป็นจะต้องไปแก้ครับ

สิ่งที่สองคือ เราจะใช้ effort มากขนาดไหนในการ optimize อย่างกรณีนี้ถ้าเราเขียนฟังก์ชันใหม่ เราก็ต้องทดสอบ isApplicable() ว่าทำงานอย่างถูกต้องจริง ๆ เผลอ ๆ เราต้อง refactor หลาย ๆ ฟังก์ชันเพื่อโค๊ดดูแลรักษาได้ง่ายขึ้น (ใคร copy โค๊ดจาก findBankTransferInstruction() มาใช้ตรง ๆ นี่ผมด่าจริง ๆ นะเออ) ดังนั้นเราก็ต้องทดสอบหลาย ๆ ฟังก์ชันว่าทำงานถูกใหม่ ถ้าโค๊ดใช้ unit test ก็ต้อง refactor ตัว unit test ด้วย อะไรแบบนี้ ซึ่งตรงนี้มันก็ใช้เวลา

ถึงจุดนี้ ถ้าเราตัดสินใจจะทำจริง ๆ สิ่งที่เราต้องทำต่อคือ ลอง benchmark แยกกัน ระหว่างโค๊ดเก่า กับโค๊ดใหม่ ซึ่งในขั้นนี้อาจจะเป็นโค๊ดที่ยังไม่สมบูรณ์ก็ได้ครับ เอามาเทียบกัน แล้วดูว่า ความแตกต่างกันมีมากน้อยแค่ไหน และประเมินต่อว่า เมื่อโค๊ดใหม่เข้าไปในระบบแล้วจะช่วยให้เราประหยัดเวลาไปได้มากน้อยแค่ไหนในระยะเวลาหนึ่ง (สมมติว่าหนึ่งเดือนหรือหนึ่งปีก็ได้) แล้วดูว่าถ้าจะ implement ต่อจนเสร็จ ทดสอบเรียบร้อย ผ่าน Function Testing และ UAT ไปแล้วจะต้องใช้เวลามากน้อยแค่ไหน ใช้งบประมาณเท่าไหร่

ผมอาจจะประเมินคร่าว ๆ ว่า หน้าคำสั่งซื้อขายนี้เนี่ย ผู้ใช้หนึ่งคนอาจจะใช้เวลากับมันประมาณ 1 ชั่วโมงต่อวัน หรือคิดเป็น workload 12.5% ถ้าผมสามารถลดเวลามันลงไป 15 นาที ก็เท่ากับว่าผมสามารถทำให้ efficiency ของผู้ใช้คนนี้เพิ่มขึ้น 25% ของ 1 ชั่วโมงจาก 8 ชั่วโมงต่อวัน ก็จะเป็น 25% * 12.5% = 3.125% ซึ่งผมว่าตัวเลขที่มากกว่า 1% ก็คุ้มค่าที่จะลงทุนนะครับ อันนี้แก้ไปเถอะ

แต่ถ้าแบบ ลดเวลามันลงไปได้ 3 นาที … ก็เท่ากับเพิ่มได้แค่ 0.625% อันนี้ก็ต้องเริ่มดูละว่ามันคุ้มจริง ๆ หรือเปล่า ?

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

อย่างกรณีผมที่ทำงานกับ Legacy Code เป็นหลัก โค๊ดไม่ได้เขียนเป็น procedure มากนัก ภาษาก็เก่า โอกาสที่พลาดสูง และไม่มี unit test เนี่ย ผมมองว่ามันเป็นการเพิ่มปัจจัยเสี่ยงมากเกินไปและผลตอบแทนก็ไม่คุ้ม ก็เลยแย้งไปว่าอย่าทำเลยดีกว่า …

ทั้งนี้การทำ optimization ก่อนที่จะประเมินเรื่องผลตอบแทนและค่าใช้จ่ายนั้น เป็นการทำ premature-optimization รูปแบบนึงครับ (อีกรูปแบบนึงคือโค๊ดยังไม่เป็นรูปเป็นร่างเลยแต่ทำ optimize แล้ว)

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

This site uses Akismet to reduce spam. Learn how your comment data is processed.