สิ่งที่ also, apply, let, run และ with ใน Kotlin ไม่ได้บอกเรา
สำหรับบล็อกนี้ไม่อธิบายว่า also, apply, let, run และ with ใช้อย่างไรเพราะน่าหาอ่านได้เต็มไปหมดแล้ว แต่สิ่งที่จะมาโชว์ให้ดูในวันนี้คือ สิ่งที่ document ของ Kotlin ไม่ได้เน้นให้เราซักเท่าไหร่ หรือพูดง่ายๆคือไม่ได้บอกเรานี่แหละ 😂 และเป็นที่มาด้วยว่าหลายๆ tutorial แนะนำให้ใช้ also, apply, let, run และ with เป็นอย่างมาก
ไม่แนะนำใช้แบบนี้อีกต่อไปถ้าคุณหันมาเขียน Kotlin
แล้วมันเพราะอะไรกัน แล้วทำไมถึงใช้ if ธรรมดาไม่ได้ ?
วันนี้เราจะมาเคลียร์กัน
ถ้าเรากดเข้าไปดูในโค้ดของ Kotlin กันซะหน่อย
ก็ดูไม่น่าจะมีอะไรซักเท่าไหร่
เป็นการโยน block เข้ามาใน param แล้วก็เรียก block(this) เพื่อสั่ง run โค้ดที่อยู่ใน block ตามที่เราใส่เข้าไป อย่างเช่น
ตัวแปร string จะถูกโยนเข้ามาใน block และ “len: <string_len>” ก็จะถูกพ่นออกมาที่ logcat นั่นเอง
แต่ๆๆๆ ให้สังเกตที่บรรทัดนี้กัน
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
ตัวนี้แหละคือข้อดีของมันที่หลาย tutorial แนะนำให้ใช้ also, apply, let, run และ with กัน
ก่อนเข้าเรื่องมาดูปัญหากันต่อ
ปัญหาของการใช้ if( string != null){…} ธรรมดา
ถ้าเรามีตัวอย่างโค้ดที่ไม่ใช้ let เลยแบบนี้
NOTE: Timer(thread_name) เป็น function ของ Kotlin ที่สร้าง thread ขึ้นมาใหม่ สามารถกำหนดเวลา delay ได้ด้วย เช่น ณ ที่นี้คือ 0 หมายความว่าให้เริ่มทำงานเลยนั่นเอง
โค้ดชุดนี้เป็นตัวอย่างการทำงานของโค้ดที่มากกว่า 1 thread ขึ้นไป
ใน “thread_name_2” เมื่อ count ≥ 5 เรามีการ assign ค่าให้ string เป็น null เข้าไป ซึ่งในเวลาเดียวกัน “thread_name_1” มีการใช้ตัวแปรที่ชื่อ “string” อยู่ด้วย
ผลลัพธ์ของโค้ดชุดนี้คือ “แอปบึ้ม” เมื่อ “thread_name_2” เริ่ม assign ค่า null ให้กับ string ครับ
เพราะ “thread_name_1” ยังมีการใช้อยู่ถึงเราจะครอบด้วย if( string != null ) ก็ไม่ได้ช่วยอะไรนะครับ ยังไงแอปเราก็บึ้มอยู่ดี
ปล. เป็นแค่ตัวอย่างนะครับจริงๆเขียนแบบนี้มันบาปมากที่เอา if ครอบด้านนอก แต่แค่อย่างจะโชว์บางอย่างให้ดูเพื่อให้เห็นภาพ
ซึ่งนี่คือปัญหาของ
การจัดการกับ “reference” ของ object ที่มากกว่า 1 thread ขึ้นไป
และอาจจะส่งผลเกิดปัญหาต่อไปอีก คือ NullPointerException, Concurrency problem (ในระดับ reference ของ object นะ) และ Race Condition (การชนกันของข้อมูล)
ที่ถูกตามหลักการคือ การทำงานของแต่ละ thread ควรแยกออกจากกันอย่างชัดเจนแล้วค่อยมา update ข้อมูลจริงๆทีหลัง หลังการทำงานแต่ละ thread เสร็จ ถ้าแย่ง update ข้อมูลกันแบบนี้ตายแน่นอนคับ 🤪
งั้นเราลองมา refactor โค้ดกันหน่อย โดยใช้ let แบบนี้
ก็ยังบึ้มอยู่ดี 🤪 เพราะสุดท้ายคุณก็ดันไปเรียก
${string!!.length} ตรงๆอยู่ดี
ถ้า string ถูกเปลี่ยนไปเป็น null ก็ บึ้มเหมือนโค้ดข้างบน เพราะฉะนั้นห้ามเรียกใช้แบบนี้ถ้าอยู่ใน also, apply, let, run หรือ with ครับ
งั้นเรามาดูแบบที่ถูกต้องกัน
การใช้ also, apply, let, run และ with อย่างถูกต้อง
ถ้าเราเข้าไปดูทุก function ของ also, apply, let, run หรือ with จะมีการเรียกแบบนี้อยู่ทุก function
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
มันเป็นการบอกว่า
ทุกๆครั้งที่เราใช้ also, apply, let, run หรือ with นั้น
object ที่ถูกโยนเข้ามาใน block จะถูก read “เพียงครั้งเดียว”และเก็บลง local variable อัตโนมัติก่อนโยนเข้ามาในตัวแปรที่ชื่อ “ it ”
เพราะฉะนั้น การใช้ตัวแปร it จึงเป็นการแยกใช้ reference ของ object อย่างชัดเจน ในแต่ละครั้งที่เราเรียก object.also{…}, object.apply{…}, object.let{…}, object.run{…} หรือ object.with{…} ทำไห้ไม่เกิด Concurrency problem กับ Race Condition อีกต่อไปนั่นเอง
การเปลี่ยน reference ของ string จึงไม่กระทบกับ it ที่อยู่ในแต่ละ let นั่นเอง
เรื่อง object reference เป็นพื้นฐาน OOP ใน Java ที่สำคัญใครงงไปทำความทวนซะนะครับ
โค้ดการใช้ที่ถูกต้องเลยเป็นแบบนี้
แต่ทั้งนี้ทั้งนั้นถ้าแอปไหนใช้เป็น single thread อยู่แล้วไม่ต้องกังวลอะไร แต่ถ้าแอปไหนมีการใช้ มากกว่า 1 thread เราแนะนำให้กลับไป recheck โค้ดตัวเองดูอีกรอบครับ
และนี้คือข้อดีของการใช้ also, apply, let, run และ with ที่ document ของ Kotlin ไม่ได้เน้นให้เราครับ
แถม
- เราไม่สามารถเปลี่ยน reference ให้ตัวแปร it ใหม่ได้เพราะเนื่องจากมันเป็น val
- สุดท้ายแล้ว it มันจะถือ reference ของ object นั้นอยู่ครับเราสามารถแก้ไขค่าข้างใน object นั้นได้อยู่ดี เช่น
เราสามารถสั่ง
it.name = "change name" //ได้ครับค่าใน it จะถูกเปลี่ยนทันทีit = Model(...) //แต่แบบนี้ไม่ได้นะ
ซึ่งการแก้ไขแบบนี้ก็อาจะให้เกิด NullPointerException, Concurrency problem และ Race Condition ได้อยู่ดีๆ ระวังจุดนี้กันด้วยเนาะ
สรุป
การหันมาใช้ also, apply, let, run และ with เราจะไม่เจอปัญหา NullPointerException และสำหรับแอปที่มีการใช้งานมากกว่า 1 thread เราจะไม่เจอปัญหาของ Concurrency problem และ Race Condition (การชนกันของข้อมูล)
นี่แหละครับที่มาว่าหลายๆ tutorial แนะนำให้ใช้ also, apply, let, run และ with เป็นอย่างมาก และเจ้าของบล็อกด้วย
สามารถไปคุยต่อกันได้ที่ kotlin dicuss มีคนไปเปิดประเด็นนี้ไว้อยู่คับ
EXTRA: ไขข้อสงสัยของ also, apply, let, run และ with ใน Kotlin
วันนี้พอแค่นี้เจอกันบล็อกหน้าเนาะ 😎
เข้าไปติดตามกันได้ https://www.facebook.com/thekhaeng.io/
อย่าลืม 👏 ข้างล่าง และ share ให้มนุษย์ Android คนอื่นด้วยหละ 😎