มารู้จัก Smart cast และ Contract ผู้ช่วยในการลด boilerplate codeใน Kotlin กัน

Phisit Mongkolnitayakul
te<h @TDG
Published in
2 min readJul 20, 2020
@LoongDum Koh Samed

A modern programming language that makes developers happier.

Kotlin เป็น modern language ตาม concept ที่เขาเขียนไว้นั้นแหละ 555 มี feature มากมายที่ช่วยลด boilerplate code ให้เราเขียนโปรแกรมได้กระชับยิ่งขึ้นและเปลี่ยนจากการที่ต้องเขียน code ในทุกๆ ขั้นตอนเพื่อให้ได้ผลลัพท์นั้นๆ ให้อยู่ในรูปที่อ่านแล้วเข้าใจได้ง่ายในภาษาของมนุษย์

ส่วนตัวผู้เขียนคิดว่าด้วยความเข้าใจง่ายของตัวภาษาช่วยลดความเสี่ยงจาก bug หรือ exception error ที่เกิดจากเขียน code ที่ซับซ้อนโดยไม่จำเป็นค่อนข้างมากเลยทีเดียว

เอาล่ะ เกริ่นมาซะเยอะเรามาเริ่มเข้าเนื้อหากันเลย

1. Smart cast

เราจะมาทำความรู้จัก Smart cast หนึ่งใน feature ที่จะช่วย cast object โดยอ้างอิงตาม type checking หรือแม้กระทั้งการ check null ผ่าน condition if or else จากนั้น compiler จะทำการ cast object นั้นให้เราอัตโนมัติ

ตัวอย่าง code Smart cast ช่วย cast ‘x’ to String

หากเราสังเกต Smart cast เองก็ค่อยข้างทำงานได้ดีอยู่แล้วแต่ก็ข้อจำกัด บางอย่างที่ทำให้ Smart cast ไม่สามารถทำงาน มี Rules ดังนี้

  1. val local variables — สามารถทำงานได้ปกติ ยกเว้นเมื่อใช้งาน delegate properties
  2. val properties — สามารถทำงานได้ปกติ ยกเว้นเมื่อ 2.1 มีการ custom getter method ของ property นั้นๆ, 2.2 variables ที่ Smart cast จะทำงานได้ต้องอยู่ใน module เดียวกันกับส่วนที่ type checking เท่านั้น
  3. var local variables — Smart cast จะทำงานได้ต้องไม่มีการแก้ไขค่าของ variables ระหว่างมีการ check types ภายใต้ scope ของ method นั้นๆ
  4. var properties —Smart cast ไม่ทำงานทุกกรณีเพราะ compiler มองว่า var อาจถูกแก้ไขค่าได้ตลอดเวลา

Note : ความสามารถของ Smart cast จะหายไปทันที ถ้าหากเรามีการเขียน function แยกออกไปเป็นคนละ function

และนี่เป็นเหตุผลหนึ่งของการมาของ Contract ที่จะช่วยเติมเต็มให้ Smart cast ของ Kotlin นั้น smart ขึ้นไปอีก

2. Contract

หลักการของ Contract คือให้ developer เป็นคนบอก Complier ผ่าน Contract Builder ให้ว่าเฮ้ยย ‘ตัวแปรนี้มันคือ String ไม่ใช่ Int’ หรือ ถ้าหาก function returns value หรือ parameter นี้จะมี type หรือ non-nullable และอีกความสามารถหนึ่งคือใช้เพื่อระบุจำนวนครั้งว่า lambda function ที่จะถูก invoke

Contract Builder

Contract Builder มี function หลักๆ 2 ประเภทคือ return & callsInPlace

— 2.1 Returns() : ใช้เพื่อกำหนดว่าหาก function จบการทำงานโดยไม่มี exception หรือ มีการ returns (‘value’) สอดคล้องตาม value ที่เรากำหนดซึ่งต้องมีการ returns implies(‘expression’) ด้วย

returns('value: Any?') support boolean and null literalsimplies('boolean expression')
ใช้เพื่อระบุที่ผลลัพท์อยู่ในรูปแบบ boolean ที่ implies support เท่านั้นอย่างเช่น
(== null, != null ,is, !is, logic operators (&&, ||, !) implies จะทำการ cooperate กับ complier ในการ improve Smart cast ของเราให้เก่งขึ้นนั้นเอง
ตัวอย่างเปรียบเทียบระหว่าง function ที่มีการใช้ Contract เข้ามาช่วย improve Smart cast

— เราจะพบว่า code ตรงส่วนที่เราใช้ Contract เข้ามาช่วย ทำให้เราไม่ต้อง recheck null หรือ cast variable types อีกรอบ

Note : Complier จะอ้างอิงผลลัพท์จาก expressions ที่ถูก implies ใน Contract หากมีการ implies variables type ที่ผิดจะทำให้เกิด run-time exception ดังนั้นเราใช้ความระมัดระวังและตรวจสอบ expressions หรือเขียน Unit Test ก่อนใช้งานเพื่อความปลอดภัย

— 2.2 CallsInPlace(callable, kind) : ใช้เพื่อระบุว่า Hight Order function จะมีการ invoke lambda function กี่ครั้งเพื่อให้เราสามารถ initial variables ภายใน lambda ได้ ซึ่งปกติแล้ว Compiler จะไม่อนุญาติให้เราทำแบบนั้น

CallsInPlace('callable', 'kind')
callable
: คือ lambda function ที่เราต้องการระบุ
kind : InvocationKind flag ที่เราจะใช้ระบุว่า lambda function นั้นจะถูก invoke กี่ครั้ง

InvocationKind มีทั้งหมด 4 ประเภท

  • UNKNOWN — default for the compiler
  • AT_MOST_ONCE — zero or one invocation(s)
  • EXACTLY_ONCE — guaranteed one-time invocation
  • AT_LEAST_ONCE — one or more invocations
  • AT_LEAST_ONCE — one or more invocations
ตัวอย่างแสดงให้เห็นว่าปกติเราไม่สามารถ Initial variable ภายใน lambda ได้
ตัวอย่างหากเราใช้ CallsInPlace มาช่วยจะทำให้เรา Initial variable ภายใน lambda ได้

เพิ่มเติม

— สำหรับ Kotlin version 1.3 เราต้องประกาศ Contract ไว้เป็น Final function ใน Top-level เท่านั้น (สำหรับ version 1.4 ) นั้นเราจึงจะสามารถประกาศเป็น (Top-level , Member level) ได้

— ปัจจุบัน Kotlin stdlib ได้มีการนำ Contract ไปใช้งานใน functions พื้นฐานต่างๆ แล้วเช่น apply , run , let , isNullOrEmpty จริงๆ มีการใช้งานค่อนข้างเยอะแล้ว แต่ขอยกตัวอย่างแค่นี้เนอะ

หากมีข้อมูลผิดพลาดให้ข้อมูลไม่ครบถ้วนหรือเพื่อนๆ มีข้อแนะนำอะไรสามารถ comment ข้อมูลหรือแสดงความเห็นได้นะ แล้วผมจะรีบแก้ไขข้อมูลให้ถูกต้องครับ สุดท้ายนี้ฝาก ปรบมือ ให้ด้วยนะ ^_^

Credits :

https://github.com/Kotlin/KEEP/blob/master/proposals/kotlin-contracts.md

--

--