Blending functional programming with imperative. Higher-level of abstraction or functional core.

ดัดแปลงและเพิ่มเติมจากคอมเมนต์คำถามในโพสนี้ https://www.facebook.com/nuttanart/posts/1015437440718608 เป็นคำถามจาก Supanat IBoss Potiwarakorn

ผมได้ยินคนพูดถึง functional core, imperative shell มาซักพักและ แต่ผมก็มักจะได้ยินว่า functional เนี่ยเป็น higher level abstraction เราแอบเอาลำดับขั้นตอนการทำงานต่างๆ ไปแอบไว้ภายใต้ declarative code ผมก็เลยยังรู้สึกสับสนอยู่เล็กน้อย (รู้สึกว่าเห้ย ถ้าเป็น shell มันต้องอยู่บนกว่าสิ)

เท่าที่ผมเข้าใจคือ monad เนี่ยมันคือเหมือน imperative shell ซึ่งจริงๆ แล้วมันก็คือ เรา แอบ state mutation เอาไว้ ไอสองเรื่องที่ผมพูดอ้างถึงบนนี่มันไม่ได้ขัดแย้งกัน แต่มันแค่อธิบายในคนละมุมกันถูกมั้ยฮะ

คำตอบของผม

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

ตอนนี้มีสองเรื่อง
a) functional เป็น declarative (higher-level abstraction) อยู่ด้านบน ส่วน imperative (lower-level abstraction) อยู่ด้านล่าง อย่างเช่นเราเขียน map,reduce โดยไม่ต้องวนลูปเอง
b) imperative อยู่ด้านบน functional อยู่ด้านล่าง ตามแนวคิด Functional Core, Imperative Shell เช่น เอา UI, http request, database ไว้ด้านบน หรืออย่าง Actor  model

เวลาเราเขียนโปรแกรมจริงๆ เราจะไม่ได้คำนึงถึงข้อ a) อะเพราะไม่ใช่หน้าที่เราจะต้องไปสนใจการทำงานตรงนั้น platform มีหน้าที่การันตีให้เรา ด้านล่างของเรามันไม่ functional อยู่แล้ว แต่ platform ทำให้เรารู้สึกว่าเราควบคุมทุกอย่างได้ในลักษณะ functional (input – output) แต่สำหรับ b) เราต้องสนใจเพราะเป็นสิ่งที่เราต้องเป็นคนสั่งให้มันทำ เพราะฉะนั้นแล้วในชั้นของเราที่ใช้ภาษาเขียนเพื่อแก้ปัญหาอื่นๆ เราคงโฟกัสที่ b) มากกว่า ส่วน a) สำหรับเราน่าจะสำคัญแค่ตอนที่เราอธิบายให้คนอื่นฟังว่า functional มันทำงานยังไงแค่นั้นมั้ง

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

ผมชอบประโยคนี้ของ José Valim (ผู้สร้าง Elixir)

I often summarize functional programming as a paradigm that forces us to make the complex parts of our system explicit

ผมคิดว่าการที่เราเอา imperative ซึ่งเป็นส่วนที่ยากขึ้นมาไว้ด้านบน (ไม่ซ่อนอยู่ด้านล่าง) เป็นตัวอย่างที่ดีมากของการทำให้ complex parts explicit

ส่วนเรื่อง monad นี่ไม่รู้เลยอะ(เขียน clojure ไม่ต้องรู้จัก monad 😜) แต่ก็เคยได้ยินมาบ้างเหมือนกันที่ว่ามองมันเป็น imperative shell ได้

เรื่องสำคัญที่หนังสือ 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 คืออะไร และมันสำคัญอย่างไร

เลือกพฤติกรรมของฟังก์ชันด้วยพารามิเตอร์ฟังก์ชัน

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

ผมขอยก merge-with ขึ้นมาเป็นตัวอย่างเพื่ออธิบาย
 
Clojure มีฟังก์ชัน merge ที่ใช้รวม map มากกว่า 2 อันเข้าด้วยกัน โดยหากมี key ที่ซ้ำกันระหว่าง map จะทำการใช้ value จาก map อันสุดท้าย

แต่การเลือกเอาค่าสุดท้ายไม่ได้เป็นสิ่งที่เราต้องการเสมอไป Clojure จึงมีฟังก์ชัน merge-with ให้เราเลือกใช้หากเราต้องการพฤติกรรมเมื่อมี key ซ้ำกันที่แตกต่างออกไปจาก merge ปกติ เช่น
เราสามารถเลือกรวม vector เช่นนี้ได้โดยใช้ concat ช่วย

(merge-with concat {:a [1 2 3]} {:a [4 5 6]})
;=> {:a (1 2 3 4 5 6)}
 หรือจะเลือกเอาค่าที่มากที่สุดโดยการส่ง max เข้าไป
 
(merge-with max {:a 1} {:a 3} {:a 2}) 
;=> {:a 3} 
นอกจากจะมีคู่ merge & merge-with แล้ว ฟังก์ชันอื่นๆ ด้านบนก็มีคู่ในลักษณะเดียวกัน partition & partition-by, split & split-with, assoc & update, assoc-in & update-in, sort & sort-by

จะเห็นได้ว่าการเขียนโปรแกรมในลักษณะนี้ ทำให้เกิดโค้ดที่มีพฤติกรรมใหม่ๆ ได้มากมายไม่จำกัด โดยเราแทบจะไม่ต้องเขียนอะไรเพิ่มขึ้นเลย ผมว่านี่แหละเป็น code reuse ที่เราโปรแกรมเมอร์พยายามทำให้เกิดในโปรแกรมที่เราเขียน และนี่เป็นหนึ่งในสาเหตุที่ทำให้โปรแกรมที่เขียนในลักษณะ functional สามารถสั้นกว่าโปรแกรมที่เขียนในลักษณะอื่นได้ ด้วยการทำให้เกิดการรวมกันของฟังก์ชัน (composability) เพื่อเกิดเป็นโปรแกรมที่มีพฤติกรรมใหม่ๆ ตามต้องการได้ง่าย

หวังว่าตัวอย่างเหล่านี้จะช่วยเพิ่มความเข้าใจในประโยชน์ของ functional programming มากขึ้นนะครับ

Guideline ในการเขียนเทสของผม

ทุกๆครั้งเวลาที่จะเขียนเทสสิ่งที่ผมคิดอยู่เสมอคือ การเขียนเทสในลักษณะใดที่จะทำให้เทสสร้างความมั่นใจให้แก่เราได้สูงสุด ในขณะที่เทสนั้นมีต้นทุนต่ำที่สุด

แนวทางในการเขียนเทสให้ได้แบบนั้น มีดังนี้

  • เทสต้องรันได้เร็ว ถ้ามันช้าเราจะไม่อยากรัน ไม่อยากเขียนเพิ่ม
  • เทสต้องดูแลได้ง่าย ซึ่งหมายถึง
    • เขียนง่าย การ setup ต้องไม่ซับซ้อน
    • อ่านง่าย ชัดเจนว่าต้องการทดสอบอะไร
    • faill เมื่อควรจะ fail จริงๆ
    • ไม่ fail แบบ random
  • เขียนให้น้อยที่สุดที่ตราบที่เรายังมั่นใจเวลาเราจะแก้โค้ด ตราบที่เรายังมั่นใจเวลาเราจะ deploy
    • ยิ่งปริมาณเทสเยอะยิ่งมีแต่วันที่มันจะรันช้าลง
    • ถ้าเราไม่เทสตรงนี้ ความเสียหายคืออะไร เรายอมรับได้มั้ย ถ้าเราพบภายหลังสามารถแก้ไขได้ยากหรือง่ายเพียงใด
    • บางจุดที่ถ้ามันมี error หรือ bug เราจะรู้หรือเห็นได้ทันที ผมก็ไม่เขียนเทส

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.”

Podcast ที่ติดตามอยู่ Oct 2015

พอดีมีน้องมาถามว่ามี video หรือ podcast อะไรเกี่ยวกับ software development ที่น่าสนใจ สำหรับการพัฒนาตัวเองบ้าง ผมก็ไม่รู้ว่าจะแนะนำอะไรดี เพราะเรื่องน่าสนใจมันก็กระจายไปทั่ว และสิ่งที่ผมสนใจอยู่ คนอื่นก็อาจจะไม่ได้สนใจก็ได้ ก็เลยลิส podcast ที่ผมติดตามอยู่ทั้งหมดขึ้นมาให้น้องเค้าไปเลือกเองละกัน

ฟังทุกตอน
Changelog: เกี่ยวกับแวดวง open source ไว้เปิดหูเปิดตาเกี่ยวกับ tool ใหม่ๆ แบบ in depth weekly email เค้าก็ดี
Ruby Rogues: เกี่ยวกับ Ruby ​community เป็นหลัก แต่ก็พูดเรื่อง software dev ทั่ว ๆ ไปด้วยเยอะเหมือนกัน เมื่อก่อนดีมากแต่ drop ลงเพราะ panel เก่งๆ ออกไป
Giant Robots Smashing into other Giant Robots Podcast: เนื้อหาเปลี่ยนไปเรื่อยตามความสนใจของผู้จัดรายการแต่ก่อนเป็น tech จ๋าๆ หลังๆ เป็นเรื่อง product dev มากขึ้น
The Bike Shed: ถกระดับ code คนจัดเป็น Rails contributor เอาสิ่งที่เค้าเจอจากการทำงานแต่ละอาทิตย์มาเล่าให้ฟัง ฟังรู้เรื่องบ้างไม่รู้เรื่องบ้าง หลังๆ มีเรื่อง Rust เยอะขึ้น
Functional Geekery: ครอบคลุมเนื้อหา Functional programming ทั้งหมด
The Cognicast: บริษัทหัวหอกของ Clojure พูดเรื่อง Clojure เป็นหลัก ทั้งแนวคิดและ tool

เลือกฟังบางตอน
CodeNewBie: เป็นแนวการพัฒนาตัวเองในด้าน software dev ในแง่มุมต่าง ๆ
Software Engineering Radio: ตามชื่อ
JavaScript Jabber: ตามชื่อ

เคยฟังบางตอน
Talking Code: software dev & product dev
Turing-Incomplete: software dev หลายๆ เรื่อง
Developer On Fire: เคยฟังแต่ตอน DHH

ผมกับ Amazon Customer Service

กำลังมีปัญหาสั่งของจาก taobao แล้วยังไม่ได้รับ เลยทำให้นึกถึงเหตุการณ์ที่นี้ที่เกิดขึ้นเมื่อ 2 ปีที่แล้ว ช่วงนั้นผมมีโอกาสได้ไปทำงานจากอ๊อฟฟิศของบริษัทที่ซานฟรานซิสโกเป็นระยะสั้น ๆ เพื่อนร่วมงานของผมที่สิงคโปร์ฝากผมซื้อ Kindle และเอากลับไปให้เค้า ด้วยเหตุผิดพลาดนิดหน่อยจึงทำให้ผมได้มีปฏิสัมพันธ์กับฝ่ายบริการลูกค้าของ Amazon อย่างน่าสนใจ เลยอยากจะจดและเล่าให้ฟังก่อนจะลืมมันไปมากกว่านี้ โดยเนื้อหาส่วนมากนำมาจาก email โต้ตอบกันที่ผมยังเก็บไว้อยู่บวกกับสิ่งที่ผมจำได้ อาจจะมีคลาดเคลื่อนและผิดพลาดไปบ้างนะครับ

ผมซึ่ง ณ​ ตอนนั้นไม่มีบัตรเครดิตได้ไปซื้อ Amazon Gift Card และใช้ในการสั่ง Kindle โดยให้มันไปส่งที่อ๊อฟฟิศเพราะบางช่วงเวลาที่ไม่อยู่ที่ที่พักจะไม่มีคนรอรับของ

วันที่ 17 ก.ค.
ผม: สั่ง Kindle ผ่านทางเว็บไซต์ของ Amazon

วันที่ 18 ก.ค.
ระบบ Amazon: แจ้งว่าของได้ถูกส่งออกแล้ว

วันเสาร์ที่ 20 ก.ค.
ระบบ Amazon: แจ้งว่าของได้ถึงมือผู้รับแล้ว

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

วันจันทร์ที่ 22 ก.ค.
ผมไปถึงอ๊อฟฟิศแล้วไม่พบของ ถามใครก็ไม่เห็นผมเลยทำการโทรไปที่ Amazon Customer Service ต่อไปนี้ขอเรียกย่อ ๆ ว่า ACS นะครับ
10:?? – ผม: โทรไปหา Amazon จำเนื้อหาที่คุยไม่ได้ แต่เค้าบอกให้ลองเช็คจากเว็บไซต์ของผู้นำส่งคือ USPS (ไปรษณีย์สหรัฐฯ) ดู
10:58 – ผม: เข้าไปที่ support website ของ Amazon เพราะว่ารอบแรกรู้สึกว่าคุยไม่รู้เรื่อง ถ้าพิมพ์เอาน่าจะง่ายกว่า ผมบอกเค้าไปว่าทางเว็บไซต์ของ USPS บอกว่าของส่งถึงมือผู้รับแล้ว
11:25 – ACS: ส่ง email มาสรุปการสนทนาทางโทรศัพท์ และบอกผมมาว่าให้ผมรอของถึง 5 โมงเย็น ถ้าไม่ได้รับเค้าจะทำการ refund ให้
13:17 – ACS (คนละคน): ตอบที่ผมเข้าไปถามในเว็บไซต์บอกว่าในกรณีนี้เค้าคิดว่าของน่าจะหายแล้ว เค้าตั้งใจจะส่งของมาให้ใหม่แต่พบว่าที่อยู่ของผมมีการเปลี่ยนแปลง* เค้าจึงทำการ request สำหรับการ refund ให้แล้วโดยจะใช้เวลา 2 – 3 วัน ถ้าสั่งใหม่แล้วอยากได้ ship ด่วนใน 1 วันฟรี ให้กดลิงค์นี้…

*ช่วงที่ผมหาของไม่เจอ ผมไป update address นิดหน่อยตรง zipcode โดยแก้ไข block code (code ประจำช่องถนน) จำไม่ได้ว่าทำให้ละเอียดขึ้นหรือลดออก (เข้าใจว่าไม่ต่างกัน)

13:33 – ผม: ตอบเค้ากลับไปว่าผมไม่อยากได้ refund เพราะไม่รู้ว่าจะได้เมื่อไหร่ และกำลังจะออกนอกประเทศในวันที่ 27 แล้วกลัวจะสั่งใหม่ไม่ทัน ช่วยส่งเป็นของใหม่มาให้เลยได้มั้ย ตามที่อยู่ที่เขียนไปให้นี้
15:11 – ระบบ Amazon: แจ้งมาว่า refund สำเร็จ*

*ตรงนี้ผมเข้าใจว่าเพราะเป็น gift card เลยทำการ refund ได้เร็วมาก

15:26 – ผม: ผมตอบกลับไปว่าผมได้รับ refund แล้ว สั่งใหม่ไปแล้วด้วย ไม่ต้องการ ship ฟรี 1 วัน (เพราะตอนนั้นอยู่ในช่วง trial  ของ Amazon Prime ได้ของเร็วอยู่แล้ว)
16:17 – ACS: ตอบผมกลับมาว่า refund ไปแล้ว แก้ไขไม่ได้ให้ผมสั่งใหม่เอง
18:32 – ACS: ตอบกลับมาว่า รับรู้ว่าผมได้รับการ refund และสั่งใหม่แล้ว

วันอังคารที่ 23 ก.ค.
ช่วงสาย ๆ จำเวลาแน่นอนไม่ได้ เพื่อนร่วมงานผมเอา Kindle มาให้บอกว่ามันอยู่ในตู้ไปรษณีย์บริษัท*

* ด้วยความที่ Kindle มันเล็กมากจนใส่ในกล่องไปรษณีย์ของบริษัทประจำตึกได้ (อยู่ในห้องที่รวมตู้รับจดหมาย ๆ ของคนที่อยู่ในตึกนั้น) คนส่งซึ่งเป็นไปรษณีย์ของสหรัฐฯจึงสอดไว้ในตู้เลย

14:11 – ผม: ติดต่อไปที่ support ว่าเจอของแล้ว ขอให้ cancel order ใหม่และไม่ต้อง refund ให้ผมด้วย
17.07 – ACS: ตอบมาว่าไม่ทันแล้วของออกไปแล้ว ก็แค่ไม่ต้องรับของ ของก็จะกลับไป

วันพุธที่ 24 ก.ค.
10:54 – ผม: ตอบกลับไปว่าผมได้ refuse ของตามที่แนะนำแล้ว
11:27 – ACS: ตอบกลับมาว่าโอเค ได้ทำการ refund ให้แล้ว
11:32  – ผม: ตอบกลับไปว่าผมไม่ต้องการ refund ไปอ่านดูการสนทนาก่อนหน้านะ
12:55 – ACS: ตอบกลับมาว่ายกเลิกการ refund แล้ว

เรื่องราวเหมือนจะจบลงแค่นี้ แต่แล้ว

วันที่ 10 ส.ค.
8:55 – ระบบ Amazon: ได้รับของที่คุณคืนมาแล้ว จะทำการ refund ให้
9:25 – ระบบ Amazon: ทำการ refund เรียบร้อย

ผม: Orz

โดยความขี้เกียจที่ต้องไปค้นข้อมูลการสนทนาออกมาอธิบาย และอีกส่วนนึงก็คิดว่าจนถึงตอนนี้ผมคงใช้ค่าแรง man hour ของทีม support ของ Amazon เกินกว่าราคาเครื่อง Kindle ประมาณ $100 ไปแล้ว ถ้ายิ่งตอบกลับไปอีกก็ยิ่งเสียเวลาเค้าไปอีก ยิ่งถ้าเรา request เข้าไปด้วยเหตุการณ์แปลกๆ ที่เค้าไม่เคยเจอเค้าก็จะทำอะไรผิดพลาดเสียเวลาไปกันใหญ่ ผมก็เลยเก็บเครดิตนั้นไว้ แล้วก็เอาไป shopping ของอื่นซะเลย 😀

สิ่งที่ผมได้เรียนรู้จากเรื่องนี้

  • Support policy ของ Amazon สร้างความมั่นใจให้แก่ลูกค้าได้ดีมาก
  • ระบบ support ของเค้ายังมีช่องโหว่อยู่ พอมีหลายคนสลับกันมา อ่าน/ตอบ issue ทำให้เกิดความสับสน โดยเฉพาะการเจอ case แปลกๆ อย่างเช่น การคืนของและไม่ขอ refund