Tag Archives: Ruby

Immutable data ลดปัญหาในการเขียนโปรแกรมของเราได้อย่างไร

ทุกอย่างในโลกการเขียนโปรแกรมมีทั้งข้อดีและข้อเสีย ในโพสนี้ผมจะขอโฟกัสเฉพาะข้อดีของ immutable data นะครับ

มาเริ่มจากการดูโค้ดที่ไม่ได้ใช้ immutable data กันก่อน

def method1(obj1) # ตำแหน่งที่ 1
  # ...
  method2()
  obj1 # ตำแหน่งที่ 2
  # ...
end

โค้ดนี้มันมีความเป็นไปได้ที่ obj1 ณ ตำแหน่งที่ 2 จะถูกเปลี่ยนแปลงไปจากตอนที่เมท็อดรับมันมา ณ​ ตำแหน่งที่ 1 เรื่องนี้มันเป็นสาเหตุหนึ่งที่ทำให้เราต้องพยายามจำโค้ดของเราทั้งหมดว่ามีอะไรเกิดขึ้น ณ จุดใดบ้าง เราต้องรู้ว่าโค้ดก่อนหน้า อย่างเช่น method2 หรือก่อนหน้านั้น ไม่ได้ไปทำอะไรกับ obj1 ของเราในทางอ้อม ถ้าเราเขียนโค้ดดีก็อาจจะไม่มีการเปลี่ยนแปลงให้น่าปวดหัวแบบนั้นเกิดขึ้น แต่มันไม่มีอะไรรับประกันและป้องกันความผิดพลาดนี้ การเขียนโค้ดผิดพลาดมันเป็นเรื่องธรรมดาที่เกิดขึ้นได้อยู่แล้ว

เมื่อเราต้องการ debug โค้ดนี้ เราก็ต้องพยายามใส่ print ให้ใกล้กับตำแหน่งที่เราต้องการเช็คค่ามากที่สุด เพื่อที่เราจะได้มั่นใจได้ว่าเราได้เห็นค่าที่ถูกต้องจริง ๆ แต่มันก็ยังอาจจะไม่จริงเสมอไปเมื่อโปรแกรมของเรามี concurrency เช่น multi thread เข้ามาเกี่ยวข้อง ค่าที่ print ออกมาก็อาจจะยังไม่ใช่ค่าจริง ๆ ณ เวลานั้น ถึงเราจะใช้ debugger ช่วย มันก็มีปัญหาลักษณะเดียวกันอยู่ดี แถม debugger อาจจะไปรบกวนการทำงานของโปรแกรม ทำให้เราไม่เห็นค่าที่ถูกต้องได้อีกด้วย

Immutable data เข้ามาช่วยลดภาระของสมองของเราตรงนี้

(defn func1 [data1] ; ตำแหน่งที่ 3
  ; ...
  (func2)
  data1 ; ตำแหน่งที่ 4
  (let [data1 "something else"] ; ตำแหน่งที่ 5
    data1 ; ตำแหน่งที่ 6
    )
  ; ...
  )

เมื่อเรามาเขียนภาษาที่ data เป็น immutable ปัญหาที่ผมกล่าวมาก็จะหมดไป ค่าของ data1 ในตำแหน่งที่ 4 จะเท่ากับตำแหน่งที่ 3 เสมอ* ถึงแม้ว่าเราอาจจะสามารถเปลี่ยนให้ data1 ไปชี้ที่ค่าอื่นได้ (shadow) ดังเช่นในตำแหน่งที่ 5 ที่ทำให้ค่าของ data1 ในตำแหน่งที่ 6 ไม่เหมือนตำแหน่งที่ 4 แต่มันเป็นเพียงแค่การเปลี่ยน reference โดยที่ค่าของ data1 จากตำแหน่งที่ 3 และ 4 จะไม่ได้ถูกแตะต้อง เพียงแค่เราไม่สามารถเข้าถึงมันได้ในฟังก์ชันนี้แล้วเท่านั้น สังเกตว่าเราสามารถเห็นการเปลี่ยนแปลงได้ชัดเจนเสมอ เพราะมันไม่ได้ถูกซ่อนเอาไว้ เมื่อเป็นเช่นนี้ เราจะได้รับการการันตีว่าไม่ว่าเราจะตรวจค่าของ data1 ณ จุดใด มันจะเป็นค่าเดียวกับที่ถูกส่งมาให้ หรือเป็นค่าที่เราเป็นคนเปลี่ยนเองในฟังก์ชันนี้เสมอ

ผมมีตัวอย่างคลาสิคอีกหนึ่งอัน

def method3
  obj2 = SomeClass.new # ตำแหน่งที่ 7
  method4(obj2)
  obj2 # ตำแหน่งที่ 8
end

เช่นเดียวกันกับตัวอย่างแรก ในตัวอย่างนี้ไม่มีอะไรที่การันตีเราได้ว่า obj2 ในตำแหน่งที่ 8 มีค่าเหมือนกับ obj2 ในตำแหน่งที่ 7 ถ้าเราเขียนโค้ดไม่ดีก็มีโอกาสเกิดขึ้นได้เสมอ

(defn func3 []
  (let [data2 {:key "value"}] ; ตำแหน่งที่ 9
    (func4 data2)
    data2)) ; ตำแหน่งที่ 10

ในขณะที่โค้ดในตัวอย่างสุดท้ายนี้ data2 ในตำแหน่งที่ 9 และ 10 จะมีค่าเหมือนกันเสมอเพราะ data2 มันชี้ไปที่ immutable data ({:key "value"})

จากตั้งอย่างทั้งหมดจะพบว่าการที่เราเขียนโปรแกรมโดยใช้ immutable data ทำให้เราโฟกัสกับฟังก์ชันหรือเมท็อดที่เรากำลังอ่านและเขียนอยู่ได้ สามารถโยนความกังวลว่า object หรือ data ที่อยู่ตรงนี้จะถูกเปลี่ยนแปลงโดยไม่รู้ตัวหรือเปล่าทิ้งไป ลดภาระของสมองของเราเอาไปไว้คิดและจำส่ิงอื่น และลดความผิดพลาดในโปรแกรมของเราไปได้เยอะเลย

*ยกเว้น data1 จะเป็น Java object ที่ mutate ได้ ซึ่งในการเขียนโปรแกรมโดยปกติเราไม่ต้องใช้

Advertisements

เรื่องสำคัญที่หนังสือ OOP ทั่วไปไม่ได้สอน

ดัดแปลงจาก Facebook โพสนี้ https://www.facebook.com/nuttanart/posts/10154374407186081

ซึ่งเป็นโพสต่อเนื่องจากโพสแนะนำหนังสือ 99 Bottles of OOP อันนี้ https://www.facebook.com/nuttanart/posts/10154374324641081

ถึงแม้ผมจะเชียร์หนังสือ 99 Bottles of OOP อย่างไร ผมยังรู้สึกว่ายังมีปัญหาสำคัญมากๆ อันหนึ่งที่ทำให้โค้ดเราเน่าและหนังสือเล่มนี้คงจะไม่ได้พูดถึง หนังสือเล่มอื่นๆที่ผมเคยได้อ่านมาก็ไม่ได้สอน คือ การอยู่ร่วมกันระหว่าง OO กับสิ่งเหล่านี้
– การเชื่อมต่อ database
– การทำ external call
– logging
– อ่าน/เขียนไฟล์
– UI
– ฯลฯ

อาจจะมีแนวคิด Clean Architecture ที่พูดถึงอยู่บ้าง เพียงแต่ว่างบางครั้งเราไปโฟกัสที่ตัว pattern และแต่ละ component มากเกินไปจนลืมว่าจริงๆ แล้วต้นเหตุของปัญหาของเราคืออะไร

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

บางคนอาจจะคิดในใจว่า นี่มัน side-effects นี่หว่า ใช่แล้วครับ ผมคิดว่าอีกปัจจัยหนึ่งที่จะทำให้เราเขียนโค้ด OO ได้ดี ดันเป็นสิ่งที่ OO ไม่ได้สอน แต่มันอยู่ในแนวคิด functional programming ถ้ารู้ว่าจะจัดสิ่งเหล่านี้ให้อยู่ถูกที่ถูกทางได้อย่างไร มันจะทำให้เราจะมีพื้นที่ให้จัดโค้ด OO ให้ดีได้

ลองไปศึกษาเรื่อง side-effects ดูนะครับ แล้วจะพบว่าหลายๆ ปัญหาที่เราเจอกันอยู่ สาเหตุเกิดจากพวกมันนี่แหละ

Update: talk นี้อธิบายเพิ่มเติมได้ดีว่า side-effects คืออะไร และมันสำคัญอย่างไร

Sandi Metz at Keep Ruby Weird 2015 – Be Yourself

“In order to make a best group we can be, then each of you have to act as you are the only one here.”

งานนี้ Sandi Metz เป็น keynote speaker เลยไม่ได้พูดเรื่องโค้ด แต่พูดในลักษณะให้แนวคิดแก่ community และเซต theme งาน

เธอเล่าถึงการทดลองเกี่ยวกับพฤติกรรมมนุษย์ 3 ชิ้น
Asch conformity experiments, Milgram experiment, Latané and Darley(Bystander effect)

แนะนำให้ใส่หูฟังๆ เพราะคุณภาพการอัดเสียงของ video นี้ไม่ค่อยดีเท่าไหร่

ต่อไปนี้จะเป็นการ spoil สิ่งที่ Sandi สรุปจากการทดลอง ดู/ฟังการทดลองก่อนจะสนุกกว่านะครับ

จาก Asch:

  • ถ้าอยากให้กลุ่มคนเห็นพ้องต้องกันเรื่องอะไร ให้ทุกคนพูดความเห็นของตัวเองออกมาตรงๆ โดยให้กลุ่มคนที่ตำแหน่งใหญ่กว่าพูดก่อน
  • ถ้าอยากได้ความหลากหลายทางความคิด ให้คนเขียนความเห็นลงกระดาษก่อน ก่อนที่จะพูดออกมา และให้ทุกคนได้พูดในเวลาที่เท่าเทียมกัน

จาก Milgram:

  • ถ้าต้องการให้คนทำ(ความผิด)ตามคำสั่ง
    • ให้มีคนคอยสั่งเค้าอยู่ใกล้ๆ
    • จับคนนั้นให้ห่างกับผู้ที่ได้รับผลกระทบ (เหมือน Developer กับ User)
    • อย่าให้ได้คุยกับคนที่โดนสั่งแล้วไม่เห็นด้วยคนอื่นแล้ว
  • ถ้าอยากให้คนมีความคิดเป็นของตัวเอง
    • อยู่ให้ห่างจากคนสั่งการ
    • อยู่ให้ใกล้กับผู้รับผลกระทบ
    • ให้กลุ่มคนที่มีความคิดเป็นของตัวเองได้แลกเปลี่ยนพูดคุยกัน

จาก Latané and Darley

  • ถ้าต้องการให้ไม่มีใครตอบสนองคำขอร้อง ให้ขอโดยตรงจากกลุ่ม
  • ถ้าต้องการให้มีคนตอบสนอง ให้ขอไปที่คนโดยตรง

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

“Not conforming with the Group. Be yourself.”

ทำไมผมถึงสนใจ Clojure

พี่รูฟ ถามว่าทำไมผมถึงไม่ใช้ JRuby (ในคอมเมนต์) แล้วผมเขียนตอบไว้ซะยาวเลย เลยขอมาแป๊ะไว้ที่นี่ด้วยๆ ความเสียดาย ด้านล่างนี้คือคำตอบของผม

ไม่รู้สิครับ ไม่ได้อยู่ในการตัดสินใจเลย

ในตอนเริ่ม ทีมมีคนถนัด Ruby กับคนถนัด JavaScript แต่คนทั้งสองกลุ่มตื่นเต้น และอยากเขียน Clojure ก็เลือกกันโดยไม่มีใครมีข้อขัดแย้ง อ่อมันใช้เขียนถอด data format แปลกๆ ของโรงบาลได้ง่ายกว่าด้วย

ส่วนตัวผมทำไมถึงสนใจ หลักๆ ก็คืออยากเปลี่ยนบรรยากาศอะครับ
– เบื่อเขียน class (Kingdom of nouns)
– อยากลองศึกษาว่าข้อดีของการแก้ปัญหาด้วย functional concept เป็นยังไง
– ชอบ persistent data structure มาก ทำให้ไม่รู้สึกผิดเวลาใช้ map, reduce, select, ..
– collection library มันหล่อมาก
– อยากลอง REPL driven development ที่เค้าว่าเร็วๆ เป็นยังไง
– อยากเขียน concurrent programming บ้าง
– ภาษามันไม่เยิ่นเย้อ สั้น แต่ชัดเจน

จากนั้นก็เล่าถึงข้อเสียให้ฟัง

ข้อเสีย
– Startup time ช้า start JVM ปกติเสร็จแล้วยังต้องโหลด standard lib ต่ออีก ปกติ dev บน REPL ก็ไม่เป็นอะไร แต่ถ้าต้องแก้ config หลักๆ บางครั้งก็ต้อง restart
– ต้องรู้ Java ด้วย อ่าน error ได้ เรียกใช้ Java library ได้
– (ตามการคาดเดาของผม) ไม่มีวัน mainstream
— อธิบายให้คนฟังว่า prefix notation ไม่ได้ยากกว่า infix notation นี่ไม่ค่อยจะมีใครเชื่อ และเข้าใจง่ายๆ
— การนับวงเล็บปิดมันไม่ง่าย มี tool ช่วยถ้าใช้เป็นแล้วจะลืมวงเล็บปิดไปเลย แต่ก็ต้องเรียน (เหมือนเรียน vim command)

จากคำถามของพี่เค้าทำให้คิดย้อนกลับไปถึงสาเหตุจริงๆที่หัด Clojure

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

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

เย่!

สรุป The Recipe for the World’s Largest Rails Monolith

เค้าว่าระบบเค้าเป็น Rails app ที่ใหญ่สุดในโลก มาเล่าเรื่องการ scale ให้ฟังอย่างสนุกสนาน
– เขียน auto scaling
– เขียน deploy tool ใหม่
– เขียน activerecord adapter สำหรับ connect multiple databases
– เขียน tool สำหรับเอา spec ไปรันบน aws spot instance
– เขียน database cleaner ใหม่สำหรับ clean เฉพาะ table ที่ถูกแตะตอน test
– เขียน database migration tool ใหม่
– เขียน component framework ใหม่
– เริ่มเขียนปี 2007 ตอน rails 1.x, upgrade มาเรื่อยๆจนตอนนี้ 4.1 แล้ว upgrade โดยการทำ shadow server เปรียบเทียบ output ของ version เก่าและใหม่

ตัวอย่าง source code โปรเจ็ค Rails ที่น่าสนใจ

Ben Orenstein พนักงาน thoughtbot ที่เคยพูดเกี่ยวกับ refactoring ที่งานต่างๆ หลายครั้ง ร่วมมือกับ Chris Hunt พนักงาน Github เขียน web application ที่ชื่อว่า Trailmix ขึ้นมาแทนบริการที่เค้าชื่นชอบที่ชื่อว่า OhLife ซึ่งได้ปิดตัวลงไป

เค้าได้เปิด source เจ้า Trailmix นี้ด้วย จากที่ดูคร่าวๆ ผมว่ามันเป็นโปรเจ็คตัวอย่างที่ดีมากสำหรับ Rails application ทั้งเรื่อง design, testing และที่สำคัญมันเป็น application ที่มีผู้ใช้งานจริงและเก็บเงินจริง ซึ่งปกติจะไม่มีใครยอมเปิด source code กัน ในส่วนท้ายของ Readme ยังระบุถึง practice และ guideline ต่างๆ สำหรับโปรเจ็คโดย link มาจาก guideline ของ thoughtbot ด้วย

ใครที่อยากดูตัวอย่างว่าจะเขียน Rails app ดีๆ ต้องทำยังไง โปรเจ็คนี้น่าจะเป็นโปรเจ็คที่นำไปศึกษาที่ดีเลยครับ ณ เวลาที่ผมเขียน blog นี้อยู่ source code ของเค้าได้คะแนนเต็ม 4.0 ที่ codeclimate

2 คนนี้เค้าเขียนเจ้า Trailmix นี้ขึ้นมาในกิจกรรม codecation ซึ่ง 2 คนได้ ไปอยู่ในบ้านตามสถานที่พักผ่อนต่างๆ เพื่อเขียน code ร่วมกัน ซึ่ง Ben พูดถึงกิจกรรมนี้ใน keynote ของเค้าที่ Nickel City Ruby Conf ส่วน Chris ได้ทำ github page สำหรับกิจกรรมของเค้าด้วย

เทคนิค filter collection หล่อๆ ของ Clojure ด้วย set

โจทย์คือเราต้องการเลือก element จาก list สำหรับ element ที่มีค่าตรงกับค่าที่ระบุ

เริ่มด้วย Ruby กันก่อน เราอาจจะทำด้วยวิธีต่างๆ ดังนี้

(1..5).select { |e| e == 2 || e == 3 } # => [2, 3]

## ใช้ array เมื่อเริ่มมีหลายตัว
to_take = [2,3,5,7]
(1..10).select { |e| to_take.include?(e) } # => [2, 3, 5, 7]

## ใช้ set เพื่อ performance ที่ดีขึ้น
require 'set'
to_take = Set.new([2,3,5,7])
(1..10).select { |e| to_take.member?(e) } # => [2, 3, 5, 7]

มาดูเทคนิคที่ว่านี้บน Clojure กัน

(filter #{2 3 5 7} [1 2 3 4 5 6 7 8 9 10]) ;=> (2 3 5 7)
; หรือถ้าเรียกใช้ range แทน
(filter #{2 3 5 7} (range 1 11)) ;=> (2 3 5 7)

Continue reading