Why should we separate business logic out of view? in Layman term

Chris
Taskworld Tech
Published in
2 min readOct 23, 2017

ทำไมเราถึงควรแยก Business logic ออกจาก View ตั้งแต่แรกเลย? นี่เป็นเรื่องนึงที่ผมไม่กลัว Premature refactor

สมมติว่าเราได้รับ Requirement มาว่า

“ตาราง User จะแสดงเฉพาะ User ที่ Active เท่านั้น”

ถ้าเราใช้ React เราอาจจะเขียนแบบนี้

render () {
const activeUsers = this.props.user.filter(c => c.active)
return activeUsers.map(user => (
<UserRow user={user} />
))
}

ซึ่งจริงๆ มันก็ง่ายดีนะและมันแค่บรรทัดเดียวด้วย

แต่ผมจะบอกว่าไม่ควรทำแบบนี้ในการทำงานเป็นทีม

ผมแนะนำให้เราสร้าง File ชื่อ User.js ที่รวม Business logic ของ User โดยเฉพาะไว้เลยไฟล์นึง แบบนี้

export const isUserActive = (user) => user.active

เราก็จะได้ Render หน้าตาแบบนี้

import { isUserActive } from './user'
render () {
const activeUsers = this.props.user.filter(isUserActive)
return activeUsers.map(user => (
<UserRow user={user} />
))
}

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

เพราะในการทำงานเป็นทีม Application ของเราจะใหญ่มาก แล้วเพื่อนๆ เราก็มีสิทธิ์ที่ได้รับ Requirement ส่วนอื่นๆ ที่ต้องตอบคำถามว่า “เอาเฉพาะคนที่ Active เท่านั้น” เช่นกัน

สมมติเพื่อนอีกคน ทำอีกส่วนของ Application บอกว่าให้แสดง Avatar ของ User ที่ Active เขาก็จะเขียนแบบนี้

render () {
const activeUsers = this.props.user.filter(c => c.active)
return activeUsers.map(user => (
<UserAvatar user={user} />
))
}

ลองนึกถึงว่าถ้ามีแบบนี้ซ้อนๆ กันหลายๆ ที่ มีเพื่อนหลายๆ คนต้องทำ Feature ที่ต้องตอบคำถามว่า “ใคร Active อยู่” เราก็จะมี user.filter เต็มไปหมด

เพื่อนๆ ทุกคนในทีมก็ เขียนแบบนี้แยกกัน โดยที่ไม่รู้ว่าอีกคนเขียนอยู่

แล้วถ้าวันนึง นิยามของ “ใคร Active อยู่” เปลี่ยนไปล่ะ เช่น เรามีวันหมดอายุแล้ว ใครที่หมดอายุแล้วก็ถือว่า ไม่ Active เช่นกัน

ถ้าเราทำแบบแรก รวมไว้ที่เดียว เราก็แค่แก้

export const isUserActive = (user, now = new Date()) => user.active && user.expiry.getTime() > now.getTime()

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

เรื่องนี้เหมือนเป็นเรื่องเบสิค แต่ผมคิดว่าสำคัญมาก

ความรู้และคำถามที่เกิดขึ้นใน Application เรา ไม่ว่าจะตอบที่ส่วนไหน ก็ต้องตอบคำถามเหมือนๆ กัน ในกรณีนี้คือ ความรู้ที่ว่า “User คนนี้ Active อยู่มั้ย” ต้องตอบได้เท่ากันหมดทั้ง Application

การที่จะบรรลุข้อ 1 ได้ เราก็ควรจะรวม “ความรู้และคำถาม” ทั้งหมดที่เกิดขึ้นได้ใน Application ไว้ที่ไหนซักที่นึง หรือตามหลัก DRY ก็จะบอกว่า

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”

อันนี้คือจุดตายจุดแรกที่มักเจอกัน คือ เรามองไม่ออกว่า “User active อยู่มั้ย” เป็นองก์ความรู้ของ Application ที่ควรจะรวมไว้ เพราะมันเปลี่ยนได้ หรือภาษาทางเทคนิคหน่อยจะเรียกว่า Business logic (จริงๆ มันก็คือ If โง่ๆ อันนึงแหละ)

แต่ด้วยความเป็นมนุษย์ขี้เหม็น ต่อให้เราบอกว่า Centralized แต่ถ้าจุดที่หามันหาเจอยาก หาไม่เจอ ไม่รู้อยู่ตรงไหน มันก็จะเหนี่ยวนำให้เพื่อนร่วมทีมเขียนใหม่ เพราะ .filter แบบนี้ เขียนใหม่ใช้ 5 วิก็เขียนเสร็จ

ถ้าเวลาที่ต้องใช้ ในการค้นหาให้เจอ ใช้เวลาถึงขั้น 10–20 นาที ต้องเดินไปถามคนนั้นคนนี้ ในขณะที่เขียนใหม่ใช้แค่ 5 วินาที

ก็มีความเป็นไปได้สูงมากที่ Programmer จะเขียนใหม่ และ Duplicate code

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

เพราะโปรแกรมเมอร์มือฉมังจะจำโครงสร้าง Application ได้ แล้วหลายๆ ครั้งเขาจะลืมมองเห็นจากมุมคนอื่นว่า บางคนยังจำไม่ได้ บางคนนึกไม่ออก

ดังนั้นวิธีที่เราใช้กันที่ Taskworld คือ เรา Group business logic เข้าด้วยกันตามชื่อ Business entity แล้วก็บอกว่า เราตั้งชื่อพวกนี้ใน Format xxx.js เช่น user.js ไปเลย

ส่วนไฟล์พวก React component หรือไฟล์อื่นๆ เราก็ตั้งชื่อให้มันแตกต่างกันอย่างเห็นได้ชัด เช่น มีลงท้ายด้วย .react มี suffix ว่า Reducer

เมื่อเราตั้งแบบนี้การ Navigate เจอง่าย แนวโน้ม Probability ที่คนจะเขียนใหม่ก็มีน้อยกว่าการที่คนจะ Reuse ของเก่า

แต่หลายๆ ครั้ง เราก็มีปัญหาว่าการเปลี่ยนแปลง Structure บางอย่างสื่อสารไม่ทั่วทั้งทีมก็เกิดขึ้นได้เป็นระยะๆ ผมเชื่อว่าทุกที่ก็มีอะไรแบบนี้อยู่บ้าง

ก็เป็นสิ่งที่เรากำลังพัฒนาให้ดียิ่งขึ้นต่อไป

ในเรื่องนี้ ผมมองว่ากระบวนท่าไม่สำคัญ คุณจะแบ่ง Folder structure หรือชื่อไฟล์ยังไงผมว่ามันก็มีหลายท่ามากๆ จะใช้ Architecture แบบไหนเลียนแบบ Facebook, Google, Netflix หรือใช้ Standard framework จะเป็น MVC, VIPER, Idiomatic React, Angular หรืออะไรก็ตาม ก็ไม่ได้เป็นประเด็นที่สำคัญที่แท้จริง

จุดสำคัญในการทำงานเป็นทีมในเรื่องนี้คือ

รวม Business logic เข้าไว้ในที่ๆ นึงที่ Intuitive พอที่คนทั่วไปในทีม จะติดนิสัยว่า “เห้ย หาตรงนี้ก่อนนะ ไม่เจอค่อยเขียนใหม่” ก่อน Implement if ซักตัว

ถ้าคุณทำให้เป็นแบบนี้ได้เมื่อไหร่ นั่นแหละคือปลายทาง ไม่ว่าจะเกิดด้วย Framework หรือ Standard อะไรก็ตามที

ผมชี้ประเด็นที่มักจะเป็นจุดตายในการ “แยก Business logic ให้เป็นระเบียบ”

  1. ไม่ยอมรวมเพราะมองไม่ออกว่านี่เป็น “องก์ความรู้” ประจำระบบ
  2. รวมในที่ๆ หายากสำหรับคนทั่วไป มีการแบ่งโครงสร้างที่ซับซ้อน มีคนเข้าใจวิธีการค้นหา Business logic ที่ต้องการเพียงไม่กี่คน
  3. รวมเสร็จแล้วไม่สามารถสื่อสารให้ทุกคนรู้อย่างทั่วถึงได้ว่าต้องหาตรงไหนก่อน Implement ของใหม่ สื่อสารไม่ทั่วถึง

Framework Dependant Architecture

เรื่องนึงที่ผมมีความ Skeptic ต่อแนวโน้มทั่วไปของ Developer คือการที่หลายๆ คนเวลาพูดถึง Software architecture ให้ความสำคัญกับการใช้ Framework หรือ Practices ที่เขาเล่าลือกัน แล้วมีน้อยคนจริงๆ ที่เริ่มพูดคุย (อย่างน้อยก็กับผม) ใน Term ของผลลัพธ์ว่า ใช้แล้วกระทบกับทีมอย่างไร ทำไมทีมถึงทำงานดีขึ้น ทำไม Workflow ของ Developer ถึงสบายขึ้น ทำไมโค้ดแก้ไขง่ายขึ้น ต่างจากเดิมยังไง

จริงๆ การใช้ Framework ที่เป็น Standard มันก็แค่ช่วยลดปัญหาข้อ 2, 3 เท่านั้นเอง เพราะเราสามารถบอกได้ว่า “ลองอ่านคู่มือแถวนี้นะ” แทนที่จะต้องอธิบายเองทุกรอบ ก็ช่วยให้บรรลุเรื่องการสื่อสารได้ง่ายขึ้น

แล้วคนทำ Framework เขาก็มี Pain ที่เขาเจอมาเยอะ แน่นอนโอกาสที่เขาคิดรอบด้านกว่าเราก็มีสูงเพราะเขาทุ่มเวลาสร้างมันขึ้นมาโดยเฉพาะเลยนี่

แต่ทว่า ปัญหาฐานจริงๆ มันคือเรื่องของ “ไม่มีความรู้” ครึ่งนึง และ “สื่อสาร” ครึ่งนึง

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

--

--

Chris
Taskworld Tech

I am a product builder who specializes in programming. Strongly believe in humanist.