ละครเรื่อง EQUUS

EQUUS : ปกติ หรือ ไม่ปกติ

ไม่ปกติตั้งแต่ยังไม่ได้ดู

วันนี้ได้ไปที่โรงละครสดใส พันธุมโกมล เป็นรอบที่ 3 เพื่อจะไปดูเรื่อง EQUUS คือจริงๆชื่อเรื่องยังอ่านไม่ออกเลย โปสเตอร์ก็ไม่ได้น่าสนใจเลยคือแบบ รูปคนตะโกน แต่คำโปรยนี่สิน่าสนใจกับคำที่ว่า

ความ “ปกติ”คืออะไร?
เส้นแบ่งแยกระหว่าง
ความ “ปกติ”และความ “ไม่ปกติ”
อยู่ที่ไหน?

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

เช่นเคยพอลองสังเกตก็แบบเดิมคือคนมาดูส่วนใหญ่เป็นนิสิตจุฬาคงจะเป็นศิษย์ปัจจุบันที่อยู่ในคณะที่เขาจัดแสดงนี่แหละ บางส่วนเป็นศิษย์เก่าที่จบไปแล้ว

เรื่องเริ่มที่ความไม่ปกติ

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

การปลูกฝัง

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

ใช้อะไรตัดสินความไม่ปกติ

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

ทุกอย่างมันมีเหตุผล

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

ขอชื่นชม

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

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

ละครเรื่อง Wannabe Bride

เจ้าสาวรอฤกษ์

บังเอิญ

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

เยือนถิ่นจุฬา

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

สูจิบัตร

ไปถึงหน้างานได้รับบัตรเข้าชมแต่ต้องนั่งรออยู่หน้าโรงจนกว่าจะถึง 19.30 น. ระหว่างรอโรงเปิดก็หยิบเอาสูจิบัตร (มันคงเรียกว่างั้นนะ) มาอ่านดู อ่านก็สนุกดีนะได้รู้ว่ากว่าจะเป็นละครเรื่องนี้ผู้จัดเขาคิดยังไง เขาทำอะไรบ้างกว่าจะได้เป็นละครเรื่องที่กำลังจะเข้าไปดู มีการขอบคุณเพื่อนๆและสิ่งต่างๆมากมาย อ่านไปอ่านมาก็ทำให้นึกถึง กิตติกรรมประกาศในเล่มวิทยานิพนธ์ขึ้นมาเหมือนกัน แต่ต่างกันที่เราจะเขียนอะไรแบบนี้ไม่ได้ จะเขียนภาษาที่เราอยากเขียนไม่ได้เพราะมันต้องสุภาพ มีหลักเกณฑ์ เขียนไม่ถูกหลักเดี๋ยวจะโดนไล่ไปเข้าเล่มใหม่เอาง่ายๆ พานึกไปถึงพวกหน้าแรกๆของ Text book เล่มใหญ่ๆที่เขาจะมีสักหน้าเขียนอะไรสักอย่างเช่น แก่คนที่รัก แก่ลูก แก่คนที่ผ่านมาในชีวิต แด่มิเชล บลาๆๆๆๆๆๆ คิดไปก็อยากจะมีหน้าแบบนี้ให้กับตัวเองบ้างนะ จริงๆการโปรแกรมเมอร์ของเราก็มีของแบบนี้นะมันเรียกว่า easter egg แต่ไม่เคยได้ทำสักทีเพราะแค่ feature หลักของโปรแกรมก็เขียนจะไม่ทันละ ไว้ว่างๆจะลองซ่อนมันไว้สักที่ในโปรแกรมแล้วดูว่าผ่านไปสัก 10 ปี จะมีคนไปเจอบ้างไหม

ท่ามกลางคนแปลกหน้า

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

ละครฟรีแต่สนุก

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

ทางทีว่างเปล่า

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

สุดท้ายต้องขอขอบคุณผู้จัดและทีมงานเรื่อง Wannabe Bride ที่ทำออกมาได้สนุก น่าติดตาม ทำให้ 1.5 ชั่วโมงเป็นเวลาที่น่าจดจำ

สำหรับใครที่สนใจอยากดูละครฟรี สนุก สามารถไปจองได้ที่ https://www.dramaartschulaticket.com/free-events

เธอคนนั้น

ความทรงจำถึงเธอคนนั้น

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

หนังสือหน้าแรก

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

ความสัมพันธ์แบบไหน

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

ความสุขของคนโง่ๆ

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

ของฝาก

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

ดูหนัง

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

ฝันพังทลาย

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

รักมากก็เกลียดมาก

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

ถ้าเธอได้อ่าน

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

Encapsulation

Encapsulation คืออะไร

ภาพจากหนังสือ Object-Oriented Analysis and Design with Applications ถ้าจำไม่ผิด

ช่วงโม้ (ไม่มีสาระ ข้ามได้ไม่ต้องอ่าน)

มีเพื่อนร่วมงานคนหนึ่งมาถามว่า Encapsulation ใช่อันนี้ป่ะแล้วก็อธิบายให้ผมฟัง ผมก็งงๆไม่รู้จะตอบยังไงว่าอะไรคือ Encapsulation จริงๆมันก็อธิบายยากนะ ผมไม่แปลกใจที่คนส่วนใหญ่เกลียด OOP (เคยมีคนเกลียด JAVA เพราะมันเป็น OOP) ตอนเริ่มเขียนผมโปรแกรมผมก็งงๆเหมือนกัน แต่ด้วยความอยากรู้อยากเก่งให้ทันเพื่อนก็เลยลงไปนั่ง Sit in ในคาบวิชา JAVA เพื่อเรียน แต่ตอนเรียน JAVA มันไม่ได้เกี่ยวกับ OOP เลยนะ แค่ประกาศ public private package ต่างกันยังไง import ยังไง ฟังก์ชัน Build in พื้นฐานมีอะไรบ้าง แค่นั้นจริงๆนะ ดังนั้นใครเกลียด JAVA เพราะ OOP ผมว่าไม่เกี่ยวกันนะ

แล้วอะไรคือ Encapsulation ล่ะ

จริงๆ wiki ก็มีอธิบายไว้นะ https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) น่าจะน่าเชื่อถือกว่าที่ผมกำลังจะอธิบาย ถ้าให้ผมอธิบายก็อธิบายคล้ายๆ wiki แหละมันเป็นกระบวนการซ่อนข้อมูล ซ่อนการกระทำไม่ให้คนนอกเห็น อย่างประกาศ private ที่ attribute และ function เออความหมายมันแค่นี้แหละ จบละ

เดี๋ยวๆๆ จริงๆมันมีอะไรมากกว่านั้น

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

ทำไมต้องทำ Encapsulation ทำแล้วดียังไง ถ้าพูดกันตามตรงตอนเขียนโปรแกรมใหม่ๆแม่งทำทำไมวะโคตรไร้สาระเลยนะครับ ประมาณว่าอย่างงี้

1
2
3
4
5
6
7
8
9
10
public class People {
private String name;

public void setName(String name) {
return this.name = name;
}
public String getName() {
return this.name;
}
}

คือมันเป็น private ก็ต้องมานั่งทำ Get Set ค่าซึ่งมันน่ารำคาญมากเพราะ เฮ้ย อะไรกันนักกันหนาหนอ แต่พอเริ่มหาหนังสือ OOP มาอ่านกับได้เรียน OOAD กับอาจารย์จบใหม่จากต่างประเทศที่ไฟแรง เลยค่อยๆเริ่มเข้าใจว่าทำไอ้ Encapsulation ไปทำไม

คนนอกรู้มาก ยิ่งยุ่งยาก

อันนี้เหมือนจะแปลกๆ รู้มากแล้วไม่ดีตรงไหน จริงๆคือมันไม่ดีในทางการเขียนโปรแกรมที่ต้องเชื่อมต่อกับกับหลายส่วนครับ กล่าวคือเมื่อคุณเริ่มรู้ว่า Class ที่คุณทำงานด้วยอะไร ข้างในมันมีอะไรบ้าง คุณก็เริ่มจะไปยุ่งวุ่นวายกับพวกนั้นมากขึ้น ตัวอย่างเช่น ผมซื้อแอร์มาจากนั้น ด้วยความอยากรู้อยากเห็นผมไป Search และรู้ว่ามันทำงานแบบดูดความร้อน (ผมก็ไม่รู้ว่าแอร์ทำงานยังไงเลยขอมั่วว่ามันดูดอากาศร้อนออก) ผมคิดว่าแอร์มันเย็นไม่พอผมเลยไปซื้ออุปกรณ์เพิ่มความเย็นมาซึ่งมันทำงานได้กับแอร์ที่ทำงานแบบดูดความร้อน แอร์เย็นขึ้นมากๆเลย จนวันหนึ่งมีแอร์ตัวใหม่ผลิตออกมาพร้อมบอกว่าใช้ไฟลดลง 90 เปอร์เซ็น (คุณพระ) แถมราคาถูกกว่าของเก่า 95 เปอร์เซ็น ในหัวผมตอนนั้นนี่ไปซื้อแอร์มาเลย เพราะคิดว่ายังไงก็ดี แถมเอามาต่อกับไออุปกรณ์เพิ่มความเย็นตัวเก่ามันจะต้องดีแน่ พอได้แอร์มาติดตั้งเรียบร้อย ผมเอาไอตัวอุปกรณ์พิเศษจากแอร์ตัวเก่า รีบเอามาติดกับแอร์ตัวใหม่ปรากฏว่า “ติดไม่ได้” เอ้าทำไมไม่ได้วะ แอร์ส่วนใหญ่มันก็ระบบดูดความร้อนหมดนี่ (ในความเข้าใจของคนนอกวงการ) พอผมไปแกะดูถึงรู้ว่า แอร์ตัวนี้ทำงานด้วยระบบ ไสยศาสตร์ “เอาผีมาใส่ในแอร์เพื่อให้เกิดความหนาวแบบขนหัวลุกขึ้นมา” ราคามันเลยถูก

ภาพประกอบจากหนังเรื่อง ผีช่องแอร์

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

แอร์แบบดูดความร้อน

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AirCondition {
public int compressorPower = 50;
public void start() {
doCoolWithCompressorPower(this.compressorPower);
}
}

public static void main(String argv[]) {

AirCondition air = new AirCondition();
// เร่งการทำงานของระบบแอร์ที่เป็นแบบดูความร้อน
air.compressorPower = 100;
air.start();
}

แอร์แบบไสยศาสตร์

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class AirCondition {
public float ghostPower = 1.414;
public void start() {
doCoolWithGhostPower(this.ghostPower);
}
}

public static void main(String argv[]) {

AirCondition air = new AirCondition();
// เร่งการทำงานของระบบแอร์ที่เป็นแบบดูความร้อน
air.compressorPower = 100;
air.start();
// Error ไปสิจ๊ะ
}

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

โถ่แก้นิดแก้บ่อยทำบ่น

เคยมีคนบอกผมแบบนี้เหมือนกัน จริงครับตัวอย่างข้างบนมันแก้นิดแก้หน่อย แต่ถ้าโปรแกรมมันมีเรียกใช้แบบนี้สัก 1000 ที่ในโปรแกรม จะเป็นยังไง คุณจะตามแก้มันหมดเหรอ ใช่คุณอาจใช้ Search all แล้ว Replace แต่ถ้ามนันต้องไล่แก้กระบวนการเพิ่มความเย็นให้เป็นแอร์ไสยศาสตร์ล่ะจะทำไง แล้วการคำนวณค่าพลังงานผีมันเป็นต้องตามไปแก้การคำนวณที่ compressorPower ด้วยไหม อะไรมันก็ chain ไปหมด

รองรับการเปลี่ยนแปลงที่เกิดขึ้นได้เสมอ

จากปัญหายิ่งรู้มาก ยิ่งยุ่งยาก จะเห็นผมกระทบและอะไรหลายๆอย่าง จนผู้เชี่ยวชาญเริ่มคิดหลักการอะไรต่างๆขึ้นมาไม่ว่าจะเป็น Abstraction, Dependecy inversion (ผมเคยเขียนไว้แล้วแต่ภาษาจะแปลกๆหน่อย), Law of Demeter ซึ่งการที่จะทำให้ ชิ้นส่วนชิ้นส่วนหนึ่งซ่อนจากภายนอกได้ในระดับที่ห้ามรู้ห้ามยุ่งเลยนั้น(จริงๆแค่บอกให้เข้าใจก็พอทำได้นะ แต่มันคงวุ่นวายว่า เฮ้ย อันไหนตัวแปรที่ไม่ควรยุ่ง) เขาจึงต้องทำ Encapsulation ขึ้นมา แบบประกาศ Private ที่ Attribute หรือ Method แล้วข้างนอกก็จะไม่เห็นแน่นอน พอทำแบบนี้ชีวิตก็จะยากขึ้นเวลาอยากจะทำอะไรเกี่ยวกับการ Class นั้น เราจะต้องทำอะไรอ้อมๆแบบ ต้องเรียกผ่านฟังก์ชัน ต้องส่ง component เข้ามาใน class แต่ไอสิ่งที่ยุ่งยากนี้มันแลกมาด้วยการที่เราสามารถ แก้ปัญหาไอแอร์ไสยศาสตร์ข้างบนได้ในระดับที่ดีมากระบบจะยืดหยุ่น แทนที่จะแก้ Code 1000 ที่ อาจจะแก้แค่ 2 ที่ เวลาเทสก็ไม่ต้องเทสทั้งระบบ เพราะ Code แก้แค่ 2 ส่วน Cost ก็จะลด เงินก็จะเพิ่ม พนักงานจะได้นอนนานขึ้น ไปหาความรู้ได้มากขึ้น

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// เพิ่มช่องทางการเพิ่มความเย็น ให้กับ Air ด้วยฟังก์ชัน addCoolPower ส่วนตัวแปรที่เกี่ยวกับกลไกด้านในเราซ่อนไว้ คนใช้ก็จะเห็นแค่ว่า
// ให้เพิ่ม addCoolPower ได้อย่างเดียว

// แอร์แบบผี
public class AirCondition {
private float ghostPower = 1.414;
public void addCoolPower(int percentCool) {
this.ghosPower = this.ghostPower * percentCool;
}
public void start() {
doCoolWithGhostPower(this.ghostPower);
}
}


// แอร์แบบดูดความร้อน
public class AirCondition {
private int compressorPower = 50;
public void addCoolPower(int percentCool) {
this.compressorPower = percentCool;
}
public void start() {
doCoolWithCompressorPower(this.compressorPower);
}
}



public static void main(String argv[]) {

AirCondition air = new AirCondition();
// เร่งการทำงานของแอร์ แบบไม่เรียกตรงๆ แต่เรียกอ้อมๆผ่าน function จะแอร์แบบผี แอร์แบบดูดความร้อนก็ทำงานได้
air.addCoolPower(100);
air.start();
}

จากตัวอย่างจะเห็นว่า ผมซ่อนพวก compressorPower , ghostPower ไว้ไม่ให้ใครรู้ว่ามีอยู่ คนที่มาเรียกใช้ AIR ทำได้แค่รู้ว่าถ้าต้องการเพิ่มประสิทธิภาพการให้ความเย็นแอร์จะต้องเรียกผ่าน addCoolPower เท่านั้น มันอาจยาก แต่ถ้ามีการเปลี่ยน code ภายในจากแอร์ผีเป็นแอร์ดูดความร้อน หรือ แอร์ดูคดความร้อนเป็นแอร์ผี ผมก็ไม่ได้รับกระทบอะไรเพราะยังเพิ่มประสิทธิภาพ AIR ได้ตามปกติเหมือนเดิม ดังนั้นคุณคงเริ่มเห็นข้อดีของการ ซ่อนพวกการกระทำต่างๆไม่ให้ข้างนอกรู้แล้วใช่ไหมครับ แนวติดของการทำ Encapsulation มีแค่นี้เอง

ตัดจบละกัน

จริงๆมันต่อไปได้อีกหลายเรื่องเลยนะเรื่อง SOLID, Design pattern, Polymorphism, การออกแบบโปรแกรม ทุกเรื่องมันเชื่อมโยงกันหมด แต่เดี๋ยวมันจะยาวไปจนมันเริ่มน่าเบื่อ เลยขอเขียนน้อยๆแต่บ่อยๆดีกว่า (จริงๆยังมีอีกหลายเรื่องที่ผมยังไม่เข้าใจ) สำหรับใครที่อยากอ่านต่อนั้นผมแนะนำหนังสือหลายเล่มเลย

  • Object-Oriented Analysis and Design with Applications

เล่มนี้สนุกดีมีรูปอธิบายตลกดี ใช้เรียนตอนปี 4
Object-Oriented Analysis and Design with Applications

  • Head First Object-Oriented Analysis and Design

เล่มนี้รูปเยอะ มีคำถาม คำตอบระหว่างเล่ม เหมาะกับคนอ่านที่ไม่ชอบรูป (ผมอ่านอันนี้อยู่เหมือนกัน แต่อ่านไม่จบสักที)
Head First Object-Oriented Analysis and Design

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

ประสบการณ์ตอนทำงาน ตอน Purge data

Purge data มันคืออะไรหนอ

ตอนมาเริ่มทำงานใหม่ๆมักจะได้ยินคนอยู่ก่อนพูดถึงคำว่าจะ Purge เมื่อไหร่ ตอนนั้นด้วยความอ่อนด้อยก็งงว่า เฮ้ยเขาคุยอะไรกันวะ ชื่อยังกะหนังไล่ฆ่ากันวันเดียวงั้นแหละ อยู่ๆไปก็ได้เริ่มฟังคำนี้บ่อยขึ้นจนตอนได้รับงานเขียน Web application 1 ตัวมาดูแล เป็นระบบรับส่งข้อมูลธรรมดาแต่ประเด็นคือมันมีคำถามถึงที่หัวหน้าถามมาคือ รู้ไหมว่าต้อง Purge data เมื่อไหร่ ยังไง อ้าวชิบหาย ไม่มีคนบอกกูเลยก็เลยคอตกตอบไปว่า “เอ่อ Pure data มันคืออะไร” หัวหน้าผมก็ใจดีไม่โกรธก็อธิบายไปยาวๆว่ามันคืออะไร จากนี้ผมจะเล่าเรื่อง Purge data แบบมึนๆงงๆให้ฟังนะครับ

ระบบเก็บข้อมูลทุกวัน

เมื่อเรา On production แล้วระบบทำงานมีคนใช้งาน ระบบก็จะเก็บข้อมูลไปเรื่อยๆเกี่ยวกับกิจกรรมหลักของระบบ ถ้าระบบธนาคารก็พวกข้อมูลการฝากถอน โอนเงินต่างๆ ถ้าพวกระบบจัดการ stock ก็บันทึกว่าใครเอาของเข้าของออกจำนวนเท่าไหร่ตอนไหน ไอข้อมูลพวกนี้ศัพท์ในการทำงานเขาเรียกมันว่า Transaction (คนละ Transaction กับเรื่อง ACID ของ Database นะ) ไอข้อมูล Transaction พวกนี้มันจะโตไปเรื่อยๆ เรื่อยๆ เรื่อยๆ และ เรื่อยๆ จน

เวอร์เกินแล้วแค่เก็บข้อมูลเยอะๆมันจะทำให้ระบบมีปัญหาได้ไง จริงๆมันก็ทำให้เกิดได้นะครับ

  • เราไม่สามารถทำงานกับข้อมูลขนาดมหาศาลในงบประมาณจำกัด

ปกติเวลาบริษัทประมูลงานมาเขาก็ต้องกำหนด Space ของ Hardware software มาให้ ดังนั้นถ้าดันไม่กำหนดไว้ว่าจะทำงานกับข้อมูลขนาดเท่าไหร่ แสดงว่าเก็บเรื่อยๆๆๆจน Hard disk เต็มไงล่ะครับ

  • Performance

อะต่อให้มี Harddisk มีไม่จำกัดปัญหาต่อไปก็เรื่องปัญหาเรื่องประสิทธิภาพ ถ้าสมมุติมีข้อมูลขนาด 1 ล้านล้าน row แล้วเกิดอยากหาข้อมูล 1 row โดยหาโดย field ที่ไม่มี Index เพราะเหตุผลเพราะต้องการ Insert update ไวมากๆ (การทำ index จะทำ insert update delete ช้าลง) มันจะเกิดไรขึ้นก็คือ scan 1 ล้านล้าน row ใน Database สิครับ โอเคงั้นเปลี่ยนใหม่ ทำ Index สิครับ ทำแม่งไปเลยอย่าไปกลัว พอทำ Index ตอนข้อมูลเยอะๆ สิ่งที่จะเจอก็คือ นานโว้ยยยยยยยยยยย เคยมีคนในที่ทำงานรอการทำ Index ข้อมูลเป็นวันเลยนะ บางครั้งต้อง Down ระบบเพื่อทำ Index กันเลย บางทีการเลือกใช้ Index ไม่เหมาะสม พวก Index hash ถ้ามันมีข้อมูลเยอะๆ Hash มันจะชน สุดท้ายก็เข้าใกล้การ scan เหมือนกัน

ด้วยปัญหาเหล่านี้จึงทำให้มีข้อตกลงขึ้นมา

ข้อตกลงนี้คือเราตกลงกับลูกค้าว่า เฮ้ยจะเก็บข้อมูลพวก Transaction ไว้นานเท่าไหร่กันหนอ บางธุรกิจก็เก็บไว้ 6 เดือน บางธุรกิจ Transaction เยอะๆหน่อยก็ 3 เดือน อย่างธนาคารจะเก็บข้อมูลพวกข้อมูลฝากถอนไว้ 3 เดือน ถ้าใครไม่อัพสมุดเขาก็รวมเหลือ transaction เดียวให้เลยอันนี้ก็เป็นตัวอย่างที่ดี เดี๋ยวๆๆๆๆๆ อยู่ดีๆมาบอกว่าเกิดข้อตกลงไหนลองบอกข้อดีของการทำไอพวกนี้มาดิ ครับที่ผมคิดได้ก็มีประมาณนี้

  • ลด Cost

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

  • ไม่มีใครดึงข้อมูลเก่าๆมาดูเล่นหรอก

ก็เหมือนเรื่องที่เคยเขียนไปเกี่ยวกับการทำ Pagination ลองนึกธุรกิจนำเข้าส่งออก คือ เขาไม่หยิบข้อมูลเมื่อปีที่แล้วมาดูหรอก เอามาดูทำไม ครึ้มอกครึ้มใจอยากรู้ว่าเขาเอาอะไรเข้ามาเหรอ App นี้มันไว้นำเข้าส่งออกโว้ย App ไม่สนใจข้อมูลเมื่อปีที่แล้วหรอก มันสนแค่ว่าจะเอาของเข้าประเทศได้ไหม มีการแจ้งนำเข้ามาในระยะ 1 เดือนนี้ไหม แล้วมันแจ้งว่านำเข้าอะไรมา พอตรวจแล้วจริงไหม ส่วน APP ที่สนใจข้อมูลลเก่าๆคือพวก App ทำการวิเคราะห์ข้อมูล เพราะมันต้องการนำไปเข้า Algorithm ทางการวิเคราะห์ข้อมูลและหาความสัมพันธ์ ซึ่งเขาต้องการข้อมูลทั้งหมดไปวิเคราะห์ ดังนั้นแยกให้ออกนะว่า App ไหนต้องใช้ข้อมูลตั้งแต่บรรพกาล กับ App ไหนต้องการข้อมูลแค่ช่วงเวลาหนึ่งเท่านั้น พอเลยช่วงเวลาหนึ่งไปมันก็ไร้ประโยชน์

ตัวอย่างระบบที่ใช้ข้อมูลแค่ช่วงเวลาหนึ่งเท่านั้น

ระบบทะเบียนของนักศึกษามหาวิทยาลัยระบบพวกนี้จะเก็บข้อมูลที่เกี่ยวกับนักศึกษาที่กำลังเรียนอยู่เท่านั้น พอนักศึกษารุ่นนี้เรียนจบ เขาก็จะให้มันอยู่ในระบบอีกประมาณ 1 ปี พอหลังจากนั้นเขาก็จะ Purge data ของนักศึกษาที่จบแล้วพวกนี้ออกจากระบบ เพื่อให้ระบบเจอกับงานข้อมูลในระดับที่ตกลงกันไว้ (คงตกลงว่าระบบต้องทำงานได้กับจำนวนนักเรียน 4 ปี + 1 ปีที่จบไปแล้ว)

ที่พูดมาไม่ได้อธิบายเรื่อง Purge data เลย

ไอที่เล่ามาทั้งหมดไม่ใช่การ Purge data เลย แค่อยากเล่าที่มาของการ Purge แค่นั้น (เล่าเพื่อ เหมือนพาไปนั่งรถวนสามรอบแต่จุดหมายอยู่ตรงจุดเริ่มต้น) การ Purge data คือการเอาข้อมูลออกจากระบบนั่นแหละ (เรียกให้หรูเพื่ออะไร) โดยปกติเขาจะลบให้ข้อมูลคงเหลือตามที่ตกลงกันไว้กับลูกค้าเช่น เก็บข้อมูลไว้ในระบบแค่ 3 เดือนล่าสุด ส่วนข้อมูลมันจะไปไหนนั้นก็มีหลายแบบ บางงานที่เป็น Log ที่ไม่สำคัญก็ลบหายไปเลย บางงานข้อมูลสำคัญต้องใช้อ้างอิงกับศาลก็ต้องเก็บไว้นานหน่อยอาจเป็น 10 ปี - 20 ปีแล้วแต่กฏหมาย ไอพวกนี้ เขาก็ยจะ้ายมันไปเก็บไว้ที่อื่นที่ที่ใช้งบประมาณน้อยกว่าเช่น เอาไปเก็บไว้เป็นแผ่นเทป เวลาจะเอาค่อยไปเอาข้อมูลจากแผ่นเทปมา Restore กลับเพื่อเอาข้อมูลกลับมาห

ตัดจบละกัน

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

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

ทำ Private npm ด้วย NEXUS

Source code ของข้า Lib ของข้า

เมื่อทำงานในบริษัทเอกชน Source code ที่เราผลิตขึ้นมาผ่านมันสมอง(ที่ไม่รู้ดีหรือแย่)ถือเป็นทรัพย์สินของบริษัท ซึ่งบางครั้งตัว Source code พวกนี้เป็นความได้เปรียบของบริษัทเช่น ตัว Gen app, Lib ที่เป็น Utility ที่ใช้กันบ่อยๆ ซึ่งมันสามารถ Reuse ใช้กับงานอื่นในบริษัทได้ จากที่ต้องทำงาน 100 เปอร์เซ็น งานใหม่อาจจะเหลือสัก 20 เปอร์เซ็น ซึ่งทางบริษัทไม่อยากจะให้ใครเอาไปใช้ ก็แน่ล่ะเขียนแทบตายอยู่ดีๆอีกบริษัทชุบมือเปิบ Code เราไปใช้ซะงั้น มันเลยตามมาด้วยการเขียน Lib และเก็บไว้ที่เครื่องใครเครื่องมัน นานๆทีก็เอามารวมกันเป็น Lib ของบริษัทนะ แล้วก็ส่งต่อกัน ไอ Lib พวกนี้เนี่ยบางทีมันก็มี Bug ซ่อนอยู่ พอเราใช้งานแล้วเจอก็แก้ ต่างคนต่างแก้ บางคนไม่เจอก็ไม่แก้ บางคนแก้ก็ยิ่งแย่ พอจะมารวม Code อีกที ก็โอ้

ไอตัวคนเขียน Blog นี่ก็เจอเหมือนกัน เขียน DAO ให้คนอื่นใช้ พอสักพักมาเจอ Bug ที่ตัวเองเขียนก็แก้ แต่คนที่ Lib เราไปใช้ก็ไม่รู้ พอเขียนไปเจอ Bug ก็ช็อค กลับไปแก้ส่วนที่ผมแก้ไปแล้วซึ่งจริงๆไม่ต้องแก้แค่เราไม่ได้รวม Code กัน จริงๆปัญหาเหล่านี้ไม่ต้องใช้ Tools ที่ผมกำลังพูดถึงมาแก้ปัญหาก็ได้นะ แค่บริหารจัดการกันดีๆ เช่น 1 เดือนเอา Code มารวมกันแก้ conflict กัน หรือเวลาเจอ Bug ต้องแจ้งใคร แจ้งเสร็จแล้วจะเป็นยังไง แก้เลยหรือส่ง code ให้เขาดูแล้วแก้อะไรประมาณนี้ หรือจริงๆจะเอาขึ้น npm กลางเลยก็ได้นะถ้าไม่ได้หวง หรือ คิดว่าเอาขึ้นไปแล้วไม่มีคนสนใจก็เอาขึ้นไปได้ มีคำคมตอนเรียนวิชา OOAD ตอนปี 4 อาจารย์ประจำวิชาบอกไว้ว่า

Tools ดีแต่คนคนใช้ Design ไม่เป็นมันไม่ได้ช่วยให้ได้ Design ที่ดี แต่มันช่วยให้ได้ขยะเร็วขึ้น

สุดท้ายต่อให้มี Tools ดีเลิศประเสริฐศรีแค่ไหนแต่ไม่คุยไม่บริหารจัดการก็ไม่ได้ช่วยให้อะไรดีขึ้น ดังนั้นลองคุยกันก่อนว่ามี Tools นี้ขึ้นมาเพื่ออะไร ช่วยอะไร มันช่วยให้ชีวิตดีขึ้นยังไง จริงๆตัวคนเขียนเองก็ยังไม่เคยคุยบริษัทตัวเองทำงานด้วยเลยนะว่าจะเอาไอนี่ไปใช้ แต่ลองเล่นมันดูก่อนว่ายากไหม ถ้ายากไปคงไม่เอาไปใช้

เหตุผลที่อยากทำ private repo

อันนี้เป็นความคิดของผมที่อยากเอาไปแก้ปัญหาที่มีอยู่กับบริษัทที่ผมทำงานอยู่

  1. อยากจะจัดการเรื่อง source กลางของบริษัทคือ ตอนนี้บริษัทผมไม่มีเก็บ source code กลางที่เอาไปใช้หลายๆ project ตอนนี้เขาใช้วิธี copy วาง คราวนี้เวลาแก้ก็ต้องไปไล่แก้ทุก project ซึ่งบาง project ก็อาจแก้แล้วที่อื่นพอไปแก้ตรงนี้ก็ส่งผลกระทบ จับมันไว้ที่เดียว แก้ที่เดียวโดนทุก Project ไปเลย

  2. ทำให้สามารถ Reuse lib กลางได้อย่างเต็มที่คือก่อนเขียนอะไรก็ลองมาดูที่ repo กลางก่อนว่ามี lib อะไรที่เขียนไปแล้วบ้างจะได้ไม่ต้องไป Implement ใช้เองเสียเวลา

ตอนนี้ความคิดผมคิดได้เท่านี้แหละ เพราะโดนทำให้เจ็บเพราะเรื่องพวกนี้บ่อย แก้แล้วทำไมไม่ไล่แก้ให้หมดทุก Project กับ เขาเขียนไว้แล้วทำไมไม่ขอมาใช้

ทำไมถึงใช้ Nexus

อย่างแรกเลยคือ “ฟรี” โว้ย คือจะเอาอะไรใหม่ไปให้บริษัทใช้ถ้าเอาแบบเสียเงินไปเขาก็คงจะถามว่าจะใช้ได้จริงไหม ไปๆมาๆจะไม่ได้อนุมัติเอา หรือ พนักงานเฉือนเนื้อตัวเองไปลองแล้วไม่ได้ใช้ แต่ถ้าใช้ของฟรีล้มเหลวก็แค่เสียเวลากับพนักงานได้ความรู้ แต่ใครอยากได้แบบ Enterprise เขาก็มีบริการนะ อย่างที่สองคือมันรองรับตัว package ได้หลายตัวดีได้ทั้ง maven ของ java npm ของ js เห็นแว๊บๆว่าเก็บ docker image ได้ด้วย ตอนแรกกะใช้ Artifactory แต่มันต้องใช้แบบ pro ถึงจะใช้ npm ได้ ก็เลยเออตัวนี้แหละจัดๆไป

ลงเลยสิครับ

ยุคสมัยนี้ใครๆก็ใช้ Docker ดังนั้นเราก็ลงด้วย docker เลยสิครับลงง่าย พังก็ remove container ทิ้งลงใหม่ สำหรับใครที่ไม่เคยใช้ Docker สามารถอ่านได้ที่ https://www.jaynarol.com/what-is-docker/เขาเขียนไว้ดีบวกมีขั้นตอนให้ทำตามดังนั้นไปลองเลยครับไม่ยาก

1
2
# แค่ 1 บรรทัดเอง
docker run -d -p 8081:8081 --name nexus sonatype/nexus3

จริงๆวิธีสอนลงและรายละเอียดอื่นๆไม่ว่าจะเรื่อง build เองหรือ volume ข้อมูลที่เป็น Persistent สามารถดูได้ที่ Dockerhub ของ Nexus3 รายละเอียดพร้อมกว่าเยอะ

ลองสร้าง Repo เลยดีกว่า

เข้าตาม url เครื่องที่ map ไว้ของผมเครื่อง docker อยู่ที่ 192.168.56.101 ดังนั้น url เข้าเครื่อง docker ของผมเป็น http://192.168.56.101:8081 พอเข้าแล้วจะได้หน้าตาประมาณนี้

welcome page

ทำการ Sign in เลย
user : admin
password : admin123

จากนั้นกดไปที่รูปฟันเฟืองจะได้หน้าจออย่างในภาพแล้วกดตามทีวงแดงเลย
create repo

จากนั้นทำการสร้าง repo ของ npm ขึ้นมาได้เลย ซึ่งจริงๆทาง sonatype ได้มีขั้นตอนการสร้างอธิบายไว้แล้วโดยสามารถไปอ่านที่ลิ้งนี้เลย http://blog.sonatype.com/using-nexus-3-as-your-repository-part-2-npm-packages

ขออธิบายคร่าวๆจากเว็บของ sonatype ที่เขาให้สร้าง 1 repo, 1 proxy, 1 group คือ

  • npm-private คือ npm private ของเรานั่นเองมีไว้เก็บ lib เก็บอะไรที่เป็นความลับของเรา
  • npm-proxy อันนี้คือไว้ต่อ npm ตัวฟรีที่เป้น public กล่าวคือถ้ามันหาใน private ไม่เจอมันไปหาที่ npm-proxy ให้ครับ
  • npm-group อันนี้คือการเอาหลายๆ npm-private , npm-proxy หลายๆตัวมารวมกันให้อยู่ใน url เดียว เวลา user ที่เป็น client มาขอใช้จะได้เข้าลิ้งนี้ลิ้งเดียว เดี๋ยวตัว nexus จะไปหาให้เองว่า package เนี้ยมันเป็นของ private หรือมันต้องไปหาที่ proxy

เปิดให้ nexus สามารถ authen ด้วย npm Bearer Token Realm

อันนี้คือเปิดให้สามรถ authen ด้วยวิธีของ npm โดยทำตามรูป ผมติดตรงนี้นานอยู่ จนไป search ว่า ต้องไปเปิดตรงนี้ก่อน เปิดปุ๊บได้ปั๊บ

npm_token

ลอง publish package กันดูดีกว่า

  1. สร้าง folder ขึ้นมาก่อนของผมสร้างชื่อว่า test_npm_repo

  2. เข้าไปใน folder แล้วทำการสั่ง

    1
    npm init

    มันจะถามเกี่ยวกับชื่อ project และอื่นๆ อันนี้เราแค่ลองเราก็กด enter รัวๆไป แต่ถ้าสร้างจริงจังควรอ่านให้ละเอียดและเลือกให้ถูกต้อง เมื่อทำแล้วเราจะได้ไฟล์ชื่อ package.json มา

  3. ทำการสร้างไฟล์ .npmrc

    1
    2
    _auth=YWRtaW46YWRtaW4xMjM=
    email=your_email@email.com

    _auth : มันค่าของ base64 ของ user:password ในที่นี้คือ admin:admin123 สำหรับใครใช้ user password อื่นก็เอามาข้อมูลมาต่อกันตามรูปแบบแล้วเข้า base64 ครับ
    email : ใส่เป็น email ของท่านนะครับ

  4. สร้าง README.md

    สร้างโดยสร้างไฟล์ README.md ข้างในจะเขียนอะไรก็ได้อย่างของผมเขียนว่า Hello world ครับ

    1
    # HELLO WORD
  5. สร้างไฟล์ index.js

    ไฟล์นี้เป็นไฟล์กลางสำหรับเวลาคนอื่น require มันจะมาเรียกไฟล์นี้แล้ว export ออกไปให้ ของผมเอาง่ายๆจะใส่เป็น

    1
    2
    3
    exports.test = function() {
    console.log("HELLO WORLD");
    }
  6. ลอง publish สิครับรออะไร
    อันนี้ผม publish ไปที่ private-repo นะ ไม่ต้อง publish ไปที่ group หรือ proxy

    1
    npm publish --registry  http://192.168.56.101:8081/repository/npm-private/

    ใช้คำสั่งด้านล่าง แต่ตรง url ของผมเป็นอย่างงี้ แต่ของคุณสามารถไป copy ได้จากตรงนี้

    ถ้า publish สำเร็จจะไม่มี Error แดงๆจาก npm และมันก็จะไปขึ้นที่หน้า repo ของเราด้วยอย่างงี้ (ของคุณน่าจะมีแค่อันเดียว พอดีของผมลองทำเป็นหลาย version)

ลอง install ตัว package จาก private repo ดู

  1. สร้าง folder ขึ้นมาสักอันก่อนของผมสร้างชื่อว่า test_download

  2. สร้าง .npmrc ขึ้นมา
    โดยเนื้อข้างในเป็นประมาณนี้

    1
    2
    registry=http://192.168.56.101:8081/repository/npm-group/
    _auth=YWRtaW46YWRtaW4xMjM=

    ตรง registry เปลี่ยนตาม npm-group ของคุณนะครับว่ามันอยู่ที่ไหน วิธีดูคล้ายกับดู private เลย

  3. ลองเลย

    1
    npm install test_npm_repo

    ตัว package จะมาอยู่ในเครื่องแล้ว

ปิดอย่าให้คนนอกมาใช้

สร้างทุกอย่างเสร็จแล้วอันนี้มาถึงสุดท้ายคือปิดไม่ให้คนนอกมาใช้งานกับเราได้ ซึ่งปกติตัว Nexus มันจะเปิด public ให้ใครเข้ามาใช้มาดู repo ได้ ดังนั้นเราต้องปิดนะครับ โดยวิธีปิดคือ ติ๊ก Allow anonymous users to access the server ออกตามภาพ

set privte

ขอตัดจบแค่นี้ก่อนละกันครับ

จริงๆยังมีอีกหลายเรื่องที่ยังไม่พูดถึง (เพราะยังทำไม่เป็น) ไม่ว่าจะเป็นทำยังไงให้เป็น HTTPS การกำหนดสิทธิ์ต่างๆ แต่ผมลองเล่นคร่าวๆมีกำหนดสิทธิ์ให้ใช้เหมือน usermanagement ทั่วไปแต่ยังไม่ลงรายะเอียดเกี่ยวกับสิทธิ์ว่าทำอะไรได้บ้าง

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

ref :
https://www.jaynarol.com/what-is-docker/
http://blog.sonatype.com/using-nexus-3-as-your-repository-part-2-npm-packages
https://help.sonatype.com/display/NXRM3/Node+Packaged+Modules+and+npm+Registries

ประสบการณ์ตอนทำงาน ตอน Limit

Limit เถอะพ่อคุณ

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

ตอนเริ่มฝึกงานใหม่ๆได้รับงานให้เขียน Sql Query หาข้อมูล ด้วยความที่ร้อนวิชาจากการที่ได้เรียนวิชา Database มาใหม่ๆ กับความภูมิใจที่ทำการเคลีย “โจทย์ SQL เกี่ยวกับประธานาธิปดี” มาได้หมด ทำเสร็จเรียบร้อยส่ง Code ไปให้เขาอย่างสบายๆ หลังจากพี่เขาเอาไปลอง Test ก็บอกว่า “เจ๊ง” Server test เกือบดับ

ทำไมต้อง Limit

ความคิดในหัวตอนแรกคือคำว่า “Code เราก็ถูกนี่หว่า” มองยังไงก็ไม่ผิด Logic การ join อะไรก็ถูกหมด จนไปถามว่ามันเกิดอะไรขึ้นทำไมมันเจ๊ง จนได้คำตอบมาว่า “ทำไมไม่ใส่ Limit” ตอนนั้น Stun เลยแบบ เอ้าจะ Limit ทำไมก็มันต้องดึงมาโชว์หมดอยู่แล้วและอื่นๆ พี่ที่ฝึกงานก็เลยสอนว่ามันเป็นแบบนี้

ข้อมูลในงานจริงมีมากกว่าที่เครื่อง Dev หลายเท่า

อันนี้คือเรื่องจริงที่เด็กที่ไม่เคยเจองานจริงมักมองข้ามไปคือตอนเราเริ่ม Dev เราก็แค่สมมุติ Data ขึ้นมาจำนวนหนึ่ง Data พวกนั้นมีแค่ 10 - 20 ตัวซึ่งเรามาใช้เพื่อทดสอบ Logic การทำงานของเราว่าถูกไหม เราลืมมองเรื่อง Performance ไป ลองนึกภาพตามว่าข้อมูลที่ตรงกับเงื่อนไขที่เราต้องการหามี 20 ล้านตัว แต่ละตัวกิน size ประมาณ 30KB รวมข้อมูลที่ต้องดึงมาจาก Database 20 KB * (20 * (10^6)) = 400 GB คุณพระ เครื่อง Server ต้องดึงข้อมูลจาก Database 400 GB ออกมาเข้า RAM ส่งไปต่อไปที่เครื่อง APP โอ้มันจะเหลืออะไรล่ะครับ ถ้าสมมุติว่า DBMS (ตัว Software จัดการข้อมูล) มันมีวิธีดึงข้อมูลห่วยมากคือดึงข้อมูลทั้งหมดขึ้น RAM มันจะเกิดอะไรขึ้น “พังสิครับ” แต่ DBMS ส่วนใหญ่ เขา manage วิธีดึงข้อมูลดีมากคือค่อยๆ ดึง ค่อยๆ ส่ง (แน่นอนว่าทุกเจ้าคงทำแบบนี้) ต่อให้ทำแบบนี้ก็ต้องเสียเวลาส่งข้อมูลผ่าน Link อีกทั้งไอเจ้า APP อะมีจัดการวิธีจัดการ Data ยังไง ถ้าเอาขึ้น RAM หมดก็ “พังสิครับ” นี่เป็นเหตุผลแรกที่ฟังแล้วก็เข้าใจ แต่ปัญหาคือไม่ดึงทีละทั้งหมดต้องดึงแบบไหน จนได้คำตอบข้อต่อมาคือ

1. ค่อยดึงค่อยทำสิ

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ส่วน import ก็ไม่ต้องสนใจมาก
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

// โครงสร้างข้อมูลไม่ต้องสนใจมาก
type City struct {
id int64
Name string
CountryCode string
District string
Population string
}

// ตรงนี้ส่วนที่ Limit ที่บอก
func getCities(dbConnection *sql.DB, limit int, skip int) ([]City, error) {
sql := fmt.Sprintf("SELECT * FROM city LIMIT %d OFFSET %d ", limit, skip)
cities := []City{}
results, err := dbConnection.Query(sql)
if err != nil {
return nil, err
}

for results.Next() {
city := City{}
err := results.Scan(&city.id, &city.Name, &city.CountryCode, &city.District, &city.Population)
if err != nil {
return nil, err
}
cities = append(cities, city)
}
return cities, nil
}

// ฟังก์ชันหลัก
func main() {
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/world")
if err != nil {
fmt.Println("Error")
}
defer db.Close()


// Control flow อยู่ตรงนี้
limit := 10
skip := 0
for {
cities, err := getCities(db, limit, skip)
if err != nil {
fmt.Println("Error")
return
}

if len(cities) == 0 {
break
}
doSomething(cities)
skip += limit
}
}

อย่างตัวอย่างนี้อยากดึงข้อมูลขึ้นมาทีละ 10 ตัวทำแล้วเอาไปทำไรสักอย่างจนเสร็จแล้วค่อยดึงตัวใหม่ขึ้นมา ข้อสำคัญของการทำแบบนี้คืออย่าลืม Order แล้วก็ระวังเรื่อง concurrentcy กล่าวคือถ้าเรา Order ด้วย Field นึงแล้วบังเอิญมีคน Insert หรือ Delete หรือ ทำอะไรก็แล้วแต่ที่ทำให้ผล Order มันเปลี่ยน ข้อมูลเข้ามาระหว่างกำลังทำ Process นี้ลำดับจะเลื่อนแล้วอาจทำให้เกิดการทำงานซ้ำ หรือ ข้ามไปได้ ตัวอย่างเช่น

กรณีปกติ

กรณีนี้คือดึงข้อมูลขึ้นมาทีละ 4 ตัว ดังนั้นรอบแรกจะดึงข้อมูลตัวที่ 1 -4 และ รอบที่ 2 ดึงข้อมูล 5 - 8 ขึ้นมาตามภาพ ตอนนี้ก็ไม่มีอะไร ต่อมาเมื่อระบบมีความซับซ้อน (ซ่อนเงื่อน เพื่อนทรยศ) มากขึ้นมีการทำงานเกี่ยวกับการแก้ไขข้อมูลในขณะเดียวกับการประมวลผลข้อมูลเป้นชุดแบบนี้ก็จะเกิดเหตุการณ์นี้ขึ้น

กรณีทำงานพร้อมๆกัน

จากภาพจะเห็นเลยว่าระหว่างการ select รอบ 1 และ 2 มีอีกงานนึงเข้ามาลบค่า 2 ไปทำให้ลำดับการเรียงผิดไป ทำให้การ Select รอบที่ 2 นั้น 5 ไม่ได้ถูกดึงไปทำ อันนี้เป็นปัญหา Classic ที่คนไม่ค่อยคำนึงถึง หรือเจอไม่ค่อยบ่อย แต่ถ้าเกิดขึ้นแล้วรับรองว่างงแน่นอนเพราะ โปรแกรมทำงานถูกต้องทุกอย่างตามปกติ มันผิดตรง concurrent ที่เข้ามา ในประสบการณ์ทำงานของผมยังไม่เคยเจอนะ หรืออาจจะเจอแต่แค่ไม่มีคนแจ้งมาหรือเขาลองกดใหม่ ปัญหานี้หาอ่านได้ในเรื่อง ACID ซึ่งเป็นมหากาพย์เรื่องยาวเลยทีเดียว ตอนเรียนผมจำได้ว่าเรียนไปเกือบ 2 คาบมั้ง

ถามว่ามีวิธีอื่นไหมที่จะแก้ไอปัญหาพวกนี้ จริงๆมีนะแต่ไม่ได้แก้ได้สมบูรณ์แบบอะไรมากแค่พอขัดในระดับ Dev แก้การเลื่อนจากการลบแต่ไม่แก้ปัญหาเรื่อง Insert ก่อนหน้า คือ

WHERE ด้วยตัวสุดท้ายของรอบนั้น

อันนี้แนะนำกรณีที่ใช้กับการ sort ที่จัดลำดับด้วยอะไรที่เป็น unique วิธีไม่ยากครับ แทนที่จะใช้ skip เราเปลี่ยนมาใช้ตัวสุดท้ายเป็นเงื่อนไขในการค้นหาแทน จากอันนี้ผมทราบแล้วว่า id เป็น unique ผมก็เปลี่ยนฟังก์ชัน getCities แทนที่จะรับ skip ผมเปลี่ยนมารับ id ที่ต้องมากกว่าเข้ามาแทน

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ตรงนี้ส่วนที่ Limit ที่บอก
func getCities(dbConnection *sql.DB, limit int, greaterThanId int) ([]City, error) {
sql := fmt.Sprintf("SELECT * FROM city WHERE id > %d LIMIT %d", greaterThanId, limit)
cities := []City{}
results, err := dbConnection.Query(sql)
if err != nil {
return nil, err
}

for results.Next() {
city := City{}
err := results.Scan(&city.id, &city.Name, &city.CountryCode, &city.District, &city.Population)
if err != nil {
return nil, err
}
cities = append(cities, city)
}
return cities, nil
}

แล้วก็ไปเปลี่ยน main function ให้เรียกแบบใหม่เป็นแบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/world")
if err != nil {
fmt.Println("Error")
}
defer db.Close()


// Control flow อยู่ตรงนี้
greaterThanId := 0
for {
cities, err := getCities(db, 10, greaterThanId)
if err != nil {
fmt.Println("Error")
return
}

if len(cities) == 0 {
break
}
doSomething(cities)
greaterThanId = cities[len(cities) - 1].id
}
}

จาก Code จะเห็นได้ว่าเราเปลี่ยนจากใช้ SKIP มาเป็น greaterThanId มาเป็นเงื่อนไขดังนั้นถ้าเราเก็บตัวสุดท้ายไปเป็นเงื่อนไขค้นหาก็รับรองได้่ว่ามันจะไม่มีทางเอาตัวก่อนหน้าและดีกว่า SKIP ตรงที่มันจะเริ่มหาจากตัวที่มีค่ามากกว่า greaterThanId แต่อันนี้ก็มีปัญหาเหมือนกันนะ คือถ้ามีการ insert ตัวนี้ที่มีค่าน้อยกว่าหรือเท่ากับ greaterThanId เข้าไปตัวนั้นก็จะไม่ถูกทำ ซึ่งปกติเราคงให้ค่า Id มันมีค่ามากขึ้นเรื่อยๆแบบ Auto increment ดังนั้นมันไม่มีทางที่จะทำให้ id มาแทรกอยู่ด้านหน้าได้

2. Pagination

แบ่งหน้าของ google

นี่เป็นคำศัพท์ใหม่ที่รู้ตอนเริ่มทำงานใหม่ๆซึ่งมันก็คือการแบ่งหน้านั่นเอง คือในความเป็นจริงเราไม่ต้องส่งข้อมูลทั้งหมดมาให้ User ดูหรอก ลองนึกภาพตามคุณเข้ามาหน้า Tracking การส่งพัสดุ คำถามคือคุณอยากเห็นการส่งพัสดุของคุณทั้งหมดตั้งแต่เริ่่มเลยไหม หรือ เข้าไปหน้าดูประวัติการฝากถอนเงินของธนาคาร ถามว่าเข้ามาหน้านั้นมันจะต้องเรียงตั้งแต่วันแรกที่เปิดบัญชีจนถึงวันปัจจุบันในหน้าเดียวไหม ใช่ครับไม่มีที่ไหนเขาทำ ปกติเขาก็แสดงเป็นหน้าๆ หน้าละ 10 - 25 รายการ คือถ้ามากกว่านี้มันก็ต้องเริ่ม scroll หน้าละ อีกทั้งไม่มีใครมานั่งไล่หาทีละหน้าหรอก เขาก็ต้องกด search เป็นช่วงเวลา และ เงื่อนไขต่างๆ ให้ได้ผลลัพธ์น้อยๆแล้วค่อยมาหา ลองถามคุณดูว่าใช้ App ใช้อะไรมันเป็นแบบนั้นไหม ซึ่งดังนั้นเราก็ดึงตามเงื่อนไขของผู้ใช้เช่น เอาหน้าที่ 5 ขนาดหน้าละ 20 ก็ไปแปลงเป็น skip limit ได้ไม่ยากใช่ไหมครับ

ก็จบไปสำหรับประสบการณ์ที่ได้มาตอนทำงาน เป็นประสบการณ์ง่อยๆที่เจอมา ไม่รู้ว่าจะมีประโยชน์หรือคุ้มกับเวลาที่อ่านหรือเปล่า

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

ได้โปรด Log เถิด

เรื่อง Log ที่แสนจะธรรมดา

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

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

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

  • Log มีคนเเขียนไว้แล้วไม่ต้อง Implement ใช้เอง

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

งั้นก็โคตรสิ้นหวังเลยสิเขียน Log ไม่ได้เพราะไม่ใช่งานหลัก แต่โลก Programmer ไม่สิ้นคนดี ในหลายๆภาษาเป็นที่นิยมในปัจจุบันจะมีเหล่ากลุ่ม Programmer คนดีช่วยกันพัฒนา Lib Log ชั้นดีขึ้นมา ตัวอย่างเช่น Apache Log4j ที่เขาทำการสร้าง lib Log ของภาษา Java ขึ้นมาและถูกใช้งานอย่างแพร่หลาย เนื่องจากเรียกใช้งานง่ายอีกทั้งยังสามารถต่อเติม Feature ของ Log ให้อีกมากมาย ซึ่งการที่มันถูกใช้อย่างแพร่หลายมันเลยเป็นมาตรฐานแบบกลายๆ (ภาษาฝรั่งเรียกดีแฟคโต้ไรสักอย่างจำไม่ได้แล้ว) เมื่อเราใช้ lib เขา เราก็เหมือนว่าอยู่ในมาตรฐานระดับนึง (มั้งนะ) ดังนั้นก่อนจะเขียน code ภาษาอะไรลองไปค้นหาดูว่ามี lib log ตัวไหนอยู่บ้าง ไม่ต้องสร้างเองนะ

  • Log ให้อ่านรู้เรื่อง

ปัญหาหนึ่งตอนผมเริ่มเขียนใหม่ๆคือเขียน Log ชุ่ยๆ หรือว่าง่ายๆก็ตามตัวอย่าง

1
2
3
[2017-12-13 21:45:38.660] [INFO] app -Add data
[2017-12-13 21:45:39.660] [INFO] app -Add data
[2017-12-13 21:45:40.660] [INFO] app -Add data

มันแปลว่าอะไรไม่เข้าใจขนาด Programmer มาอ่านด้วยกันยังไม่เข้าใจเลยว่ามันแปลว่าอะไรดังนั้นควรเขียนหน่อยว่าทำอะไรอยู่โว้ยยยยย เช่น

1
2
3
[2017-12-13 21:45:38.660] [INFO] app -Add document ref : 32102 to Database complete
[2017-12-13 21:45:39.660] [INFO] app -Add document ref : 32103 to Database complete
[2017-12-13 21:45:40.660] [INFO] app -Add document ref : 32104 to Database complete

อย่างนี้ค่อยพอเข้าใจหน่อยว่าเออกำลังทำอะไรอยู่

  • Log ข้อมูลที่พร้อมในการค้นหา

ตอนแรกที่ผมเริ่มทำงานใหม่ทำถึงไอข้อก่อนหน้านี้ก็โอเคแล้วนะ แต่พอมาเจองานที่ต้องไล่ล่าหาต้นเหตุว่าเกิดจากอะไรแค่ข้อมูลข้างบนมันยังไม่พอ โดยงานส่วนใหญ่ที่ผมเจอจะเป็นงานที่ต้องยุ่งกับ User และ step แบบขั้นตอน มันจะมีพวกคำถามแบบใครคนทำ ทำมาจากที่ไหน คือถ้าเป็น Log ดีๆหน่อยจะบอกชื่อไฟล์ว่ามันสั่งทำงานจาก file ไหน แต่ถ้า Log ไม่ค่อยดีจะไม่บอก พอไม่บอกเราก็จะหาจุดเริ่มต้นที่ควรไปดู source code ได้ โดย หลังจากไล่หาอ่านเรื่องเกี่ยวกับ Best practice เกี่ยวกับ Log ก็เจอเรื่องที่เขาสอนที่ตรงกับงานคือการเขียน log ให้ grep ได้

1
2
[2017-12-02 13:36:58.640] [INFO] e_payment - uid { 1 }  payment_id { 01234567 }  ref1 { 000443 } ref2 { 1 } ref3 { 2017-12-02000001 } : Send request to pay
[2017-12-02 13:37:01.640] [INFO] e_payment - uid { 1 } payment_id { 01234567 } ref1 { 000443 } ref2 { 1 } ref3 { 2017-12-02000001 } : Problem with payment

การทำแบบนี้จะช่วยเราในการระบุข้อมูลเกี่ยวกับ Log ได้ดีขึ้นเพราะเราสามารถ grep ได้ง่าย เช่น ลูกค้ามีปัญหาที่เลข ref1 ซึ่งเป็นเลขที่มนุษย์อ่านออกเราก็สามารถ grep หาว่ามันเกิดอะไรขึ้นบ้าง หรือ ใน error เดียวกัน ฝ่ายเทคจิคแจ้งเกิดปัญหาเราก็สามารถใช้ payment_id ในการค้นหาได้ และสามารถบอกได้ว่าใครเป็นเข้ามาทำ เจ๋งไหม แต่คำเตือนคือ พยายามใส่เฉพาะข้อมูลที่จำเป็น อย่างผมจะเน้นหลักการอะไรที่เป็นตัวอ้างอิงถึงการกระทำนี้ได้ หรือ อ้างอิงถึงสิ่งที่เรากำลังยุ่งอยู่ด้วยได้ผมจะเอามาใส่เช่นตัวอย่าง ผมเอา ref และ payment_id มาใส่ใน log ด้วยเพราะมันใช้อ้างถึงการจ่ายเงินครั้งนี้ได้

  • Log ที่ดีไม่ควรมีงานมาถึง Programmer

ผมเขียนมาถึงไอข้อบนๆเนี่ยก็ดีใจเยอะละนะว่าเขียนได้ดีเลย แต่พองานจริงเข้ามาอีกนิด คือถ้ามี Case (เรียกเหมือนหมอเลย) เกิดขึ้นบ่อยๆเป็ฯเรื่องซ้ำๆบ่อยๆก็จะเริ่มเบื่อ ดังนั้นเราควรทำ Log ที่สามารถดูได้ผ่านตัวผู้ดูแลระบบ หรือ ผ่านตัว User เอง ซึ่งจริงๆคนปกติมนุษย์ทั่วไปเขาจะเรียกกันว่า History ล่ะมั้งขึ้นมา ไอตัว History นี้ควรจะทำขึ้นมาให้ใช้กับงานที่เราคิดว่าต้องการ Tracking มันจริงจังเลยว่าใครเข้ามาทำอะไรกับมันบ้าง หรืองานที่มีผลกระทบเวลาข้อมูลถูกแก้ไข พอเราทำไอตัวนี้ขึ้นมาลูกค้าก็สามารถตรวจสอบได้ว่าไอตัวนี้เกิดปัญหาอะไรขึ้นเองได้ หรือถ้าเขายังโทรมาอีกก็บอกเขาไปว่า

  • Log ตอนไหนและที่ไหนบ้าง

    อันนี้ผมแนะนำถึงอะไรที่เราควร Log ตอนไหนและที่ไหน

    1. ตรง Controlflow ของส่วนที่ติดต่อกับผู้ใช้ หรือง่ายๆตรงไฟล์ Controller ของ Web นั่นแหละ โดยที่เรา Log ตรงนี้ก็เพราะเราต้องการทราบว่าผู้ใช้มาทำอะไรกับระบบเราบ้าง เวลาเกิดปัญหาจะได้รู้ว่าใครเข้ามาทำอะไรผ่านตัว controller นี้
    2. ตรงไหนก็ตามที่ทำงานแบบ Background เพราะเราไม่มีคนมานั่งเฝ้าตอนมันทำงานเวลาเกิดปัญหาก็จะมีแต่ Log นั่่นแหละที่บอกว่ามันทำอะไรไปบ้าง
    3. ตรงส่วนที่มีการรับส่งข้อมูลระหว่างระบบเพราะเราจะได้ใช้ดูว่า เราส่งอะไรไปใช้เขา และ เขาตอบกลับอะไรกลับมา อันนี้ควรทำเป็นอย่างยิ่งเพราะมันมักจะมีปัญหาโต้เถียงกันบ่อยเพราะแต่ละคิดว่าตัวเองไม่ผิด อีกทั้งอาจจะต้องรอการติดต่อจากอีกฝ่าย เคยรอการตอบกลับจากหน่วยงานรัฐ ถาม 10 โมงตอบ 10 โมงอีกวันก็มี ดังนั้นมี Log ส่งให้เขาดูก็จะดีกว่าให้เขาไปหาเองเป็นการช่วยลดเวลาการทำงานของเราด้วย

ตัดจบดีกว่า

สุดท้ายผมขอตัดจบละกันเพราะมันจะยืดยาวจนเกินไป จริงๆมีอีกหลายเรื่องที่อยากเขียนคุยเช่น Level log, ขนาด log , log หลาย line แต่พอมาคิดดูอีกที มันก็มีข้อมูลของต่างประเทศเยอะแยะให้อ่านแถมน่าเชื่อถือกว่าด้วยเลยไม่เขียนดีกว่า

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

Ref :
https://www.codeproject.com/Articles/42354/The-Art-of-Logging

เรื่องของ Exception ตอน 3

Anti pattern Exception ต่อ

ผมเขียนเกี่ยวกับ Exception ไว้ 3 ตอน คุณสามารถกด Link ด้านล่างเพื่ออ่านที่เกี่ยวกับ Exception ตอนต่างๆได้เลย

ว่าจะเขียน Blog ทุกสัปดาห์ไปๆมาๆก็ไม่ได้เขียนเนื่องจากงานเยอะแต่จริงๆคือขี้เกียจเขียน จนมีเพื่อนบอกว่า เฮ้ยเขียนทิ้งไว้ไมไม่กลับมาเขียนต่อ วันนี้เลยมาเขียนต่อให้จบดีกว่า ตอนนี้อยู่หัวข้อเดิมเรื่อง Anti pattern Exception

  • Relying on getCause()

อันนี้น่าจะเจอบ่อยใน JAVA แต่ภาษาอื่นก็ทำคล้ายๆแบบนี้ได้อาการมันประมาณว่าเราดัก Exception จากนั้นดึงเอาสาเหตุของ Exception ออกมาอีกทีหน้าตา Code มันจะประมาณนี้

1
2
3
4
5
6
7
8
9
10
11

try {
idolProfile cherprang = IdolService.getProfile("BNK48","Cherprang");
printToScreen(cherprang);
} catch( CustomException ex) {
if (ex.getCause() instanceof FileNotFoundException ) {
doClearResource();
} else {
throw ex;
}
}

ตอนแรกอ่านมันก็โอเคนะแค่ไปดึงเขาสาเหตุที่เด้ง exception ออกมาตรวจ อีกทีว่ามันคืออะไรแล้วเอาไป Check ใน if แล้วมันจะเป็นอะไรไป แต่พอมาอ่านที่เขาอธิบายมันแล้วก็คิดตามก็เข้าใจ คืออย่างแรกเข้าใจก่อนว่า Code ที่ผมเขียนอธิบายนี้เป็นส่วนเรียกใช้งาน idolService ซึ่งเป็น Code ที่คนอื่นเขียน หรือ อาจจะเป็นผมเขียน โดยผมไปอ่าน Manual และ Source code ของเขาอย่างละเอียดเลยว่ามันจะ Throw อะไรออกมาแล้วผมก็ได้ Code ประมาณนี้ คราวนี้เวลาผ่านไป idolService เกิดการแก้ไข Source code ในส่วน Implementation จากการที่เขาเก็บข้อมูลเป็น File เขาย้ายไปเก็บข้อมูลที Server เวลาดึงข้อมูลเขายิงไป Server เวลาได้ Error ก็จะเปลี่ยนจาก FileNotFoundException เป็นอย่างอื่น คราวนี้ฝันร้ายก็มาเยือนเพราะไม่มีใครรู้ว่ามันมีผลกระทบมาถึง Code ส่วนนี้ด้วย จริงๆรู้แน่ตอนทำ Full loop แต่เขามองว่าแก้ Service เกี่ยวไรกับส่วนแสดงผล ในเมื่อรักษา Interface ทุกอย่างครบ นั่นแหละครับปัญหาทีี่ทำให้การทำอย่างงี้กลายเป็น Anti pattern

  • Destructive Wrapping

ข้อนี้เลวร้ายมากมันคือการทำลาย stack trace ของ Exception ซึ่งการทำไม่ยากเลยทำด้วยวิธีนี้

1
2
3
4
5
6
7

try {
idolProfile cherprang = idolService.getProfile("BNK48","Cherprang");
printToScreen(cherprang);
} catch( CustomException ex) {
throw new idolException("Problem with net idol service" + ex.getMessage());
}

ตอนแรกอ่านก็ไม่เห็นไม่เป็นอะไรเลยนี่พอมาอ่านดีๆ อ้าว stack trace ตอนเราสั่ง print stack มันหายไปเลยมาหยุดตรงนี้พร้อม message ซึ่งบางทีมันอาจไม่ใช่ต้นกำเนิดของสาเหตุด้วย ประมาณดูหนังเจ้าพ่อที่ตำรวจตามจับแก๊งของเจ้าพ่ออยากจะสาวไปหาตัวเจ้าพ่อ กำลังไล่ตามหลักฐานจากโจรคนนึงไล่ไปหาเจ้าพ่อแต่สุดท้ายเจ้าพ่อสั่งฆ่าตัดตอนตอนก่อนถึงตัวเอง ทุกอย่างก็เลยเป็นปริศนาต่อไปเจ้าพ่อก็ยังคงลอยนวล

  • Log and Return Null

อันนี้เลวร้ายพอๆกับข้างบนและผมก็เคยทำด้วยเพราะคิดว่าไม่เป็นไร ตัวอย่าง Code มันจะประมาณนี้

1
2
3
4
5
6
7
8
9
10
11
12
13

public class IdolService {
public IdolProfile getProfile(String group, String name) throws CustomException {
try {
InputStream input = FileUtils.openInputStream(new File("idol.dat"));
return streamToProfile(input, group,name);
} catch (FileNotFoundException ex) {
return null;
} catch (IOException ex) {
throw ex;
}
}
}

ถามว่าทำไมเขียนอย่างนี้คือผมไปอ่านไฟล์ที่ชื่อ “idol.dat” ซึ่งผมการันตีเลยว่าไฟล์นี้มีแน่นอนแล้วผมก็ทิ้ง code นี้ไว้อย่างนี้ จนวันนึง มีคนมาแก้ไขข้อมูลในเครื่อง server ตามรอบการดูแลของเขา เขาเขียน script บางอย่างเกี่ยวกับการ backup แล้วมันดันเขียนผิดไปล้ายไฟล์ “idol.dat” ของผมไปที่อื่น แต่ service ของผมยังทำงานปกติเหมือนไม่มีอะไรเกิดขึ้นทั้งๆที่เกิดปัญหาอยู่ ลูกค้าโทรมาหาผมพร้อมบอกว่า เฮ้ยทำไม Search หาข้อมูลน้องคนนี้ไม่เจอเลย เกิดอะไรขึ้น ตอนนั้นผมคงไปหาข้อมูลใน Log พร้อมกับงงๆว่าทุกอย่างทำงานปกติดีนี่ไม่มี Error ไม่มีอะไรเลย คงต้องใช้เวลานานระดับหนึ่งเลยกว่าผมจะรู้ว่ามันเกิดจาก Source code ตรงนี้ ดังนั้นอย่าทำแบบนี้นะครับอันตรายมากๆ

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

ปล. ผมไม่ได้เป็นแฟนคลับ idol วงไหนนะครับพอดีเห็นเพื่อนๆผมหลายคนชอบแล้วน้องเขาก็ดูน่ารักดีเลยเอามาเป็นมุกเวลามาอ่านบทความนี้จะได้ไม่เซ็งหรือเครียดจนเกินไป

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

Ref :
https://community.oracle.com/docs/DOC-983543#catchingException
https://arxiv.org/pdf/1704.00778.pdf

Dependency Inversion

Dependency คืออะไร

ไม่รู้จะเขียนอะไรจะเขียน Angular4 ก็ยังไม่เคยลอง จะ Webpack ก็เขียนไปแค่ Project เดียว แถมยังใช้ angularjs อยู่ด้วยเลยยังไม่เอามาเขียน ตอนนี้เลยขอกินบุญเก่าเอาเรื่องที่เรียนตอนปี 4 วิชา OOAD (อีกแล้ว) มาเขียนดีกว่า

Dependency คือ เอ่อ… อธิบายยากเหมือนกันนะ ไม่รู้จะอธิบายยังไงให้กลางและเข้าใจง่าย ขอเป็นอธิบายในมุมมองของผมละกัน Dependency เหมือนกับว่าถ้าเราจะใช้ Module นี้เราจะต้องทำอะไรบ้าง เช่น ต้องส่งตัวแปรเข้าไปยังไง ต้องมี Environment แบบไหน ต้องมีเปิด Permission อะไร และอื่นๆที่ทำให้เราสามารถใช้ Module นั้น อ่าถ้าจะให้ยกตัวอย่างก็ประมาณ LEGO อะ ถ้าเราอยากต่อกับ LEGO 4 หัวทิ่มและก็ต้องมี LEGO ที่มีรูให้เสียบ 4 รูตรงกับ LEGO ที่เราจะไปต่อ หรือ ถ้าเป็นนักเล่นเกมส์อยากเล่นเกมส์ก็ต้องไปตรวจสอบ Spec คอมว่ามันต้องลงอะไรบ้าง บางทีต้องไปโหลดส่วน Lib C++, DirectX8, 9, 10 มาลงบ้างเพื่อให้สามารถเล่นเกมส์ได้

เวลาเปลี่ยนอะไรก็เปลี่ยน

วันหนึ่งกำลังเขียน Code แล้วมีคนเดินเข้ามาแล้วบอกว่า “ต้องเปลี่ยนตรงนี้นะ” , “เปลี่ยน DBMS” , “ลูกค้าอยากได้…เลยต้องเปลี่ยนไปใช้…” , “เข้าไม่อยากใช้ Rest เขาอยากใช้ FTP” ในชีวิตคนสายนี้จะต้องได้ยินคำพูดพวกนี้มาบ้างไม่มากก็น้อย มันก็ตลกในหลายๆครั้งที่อาชีพที่เราทำมัน อยากจะเปลียนอะไรก็เปลี่ยนกันง่ายอะไรขนาดนัน อาจเป็นเพราะอาชีพที่เราทำมันเพิ่งมีขึ้นมายังไม่ถึง 100 ปี อะไรๆมันก็เลยไม่นิ่งไม่มีอะไรตายตัวเมื่อเทียบกับสายงานสร้างอื่นๆไม่ว่าจะโยธา เครื่องกล หรืออย่างอื่นที่ศาสตร์ของเขามีมานานมีการลองผิดลองถูกจนได้วิธีการที่ดีที่ถูกต้องแล้ว หรือจริงๆแล้วงานของพวกเราต้องยอมรับกับความเปลี่ยนแปลงได้เสมอ

เมื่อเปลี่ยนก็ต้องแก้

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

Dependency Inversion

เป็นตัวย่อ D จากกฏ SOLID ตัว Dependency Inversion ว่าง่ายๆคือ …. เอออธิบายยากเหมือนกันว่ะ เอาเป็นยกตัวอย่างละกัน ผมเคยเขียน Web appliction ตัวนึงตอนนั้นพัฒนาโดยใช้ Database เป็น NoSQL รูปแบบหนึ่ง คราวนี้ด้วยความอ่อนด้อยด้านประสบการณ์ เวลาเข้าถึงข้อมูลผมก็เขียนแบบให้มันต่อ Database ตรงๆเลย ทำอย่างรวดเร็วว่องไว Project นี้ก็จบไป ผ่านไปไม่นาน คนที่รับ Project นี้ไปทำต่อมาพูดคุยว่าต้องเปลี่ยนจาก NoSQL เป็น SQL เนี่ยจะต้องแก้อะไรบ้าง ตอนนั้นนี่บอกเลยว่าแก้เยอะมากๆ เยอะแบบเยอะที่สุด แถบจะทุกที่เลยเพราะว่าเราเขียนต่อ Database โดยตรงไปทุกส่วน ใจผมตอนนั้นนี่บอกเลยว่า เขียนใหม่อาจจะง่ายกว่าเสียด้วยซ้ำ พออ่านถึงตรงนี้พอจะเริ่มเข้าใจเล็กน้อยแล้วใช่ไหมครับ

ภาพแสดงการ Design ที่ dependency กับ DBMS

ปัญหาของ Code ที่ผมเขียนคือผม Design ออกแบบข้อมูลโดยอ้างอิงจาก DBMS เป็นหลัก พอได้ปุ๊ปก็ออกแบบ Data model ตาม DBMS connection lib return ออกมาให้แบบเพียวๆไปจากนั้น
ก็ให้ส่วน Business logic มีวิธีเรียกใช้ DB และเห็นข้อมูลตาม Data Model จะเห็นว่าอะไรก็ไปขึ้นอยู่กับ DBMS connection lib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

/* RpgUserObject จะประมาณนี้
{
_id : String
name : String
username : String
password : String
item : [Object]
}
*/

// ใน Controller สักที่หนึ่งจากหลายล้านที่

function handleFindRpgUser(req, res) {
var _id = req.headeer("_id");
db.rpgUser.findOne({_id: _id }, function(err, result) {
if (err) {
res.status(500).send("Internal server error");
} else {
res.json(result);
}
});
}

พอมันเกิดอะไรเปลี่ยนแปลงกับ DBMS ไม่ว่าจะเป็นเปลี่ยน (อันนี้ยากแต่ถ้าเปลี่ยนล่ะก็) แต่ที่ผมโดนมาคือ DBMS connection lib มันเปลี่ยน version แล้วเขาก็เปลี่ยนการรูปแบบการ Return แปลว่ามันจะเกิดอะไรขึ้น ทุกส่วนที่ยุ่งเกียวกับ DBMS connection lib จะต้องมีการแก้ไขแน่นอน ดูเส้นได้เลย ผมเลยบอกว่าถ้ามานั่งไล่แก้ซึ่งไม่รู้กี่ที่ กระทบกี่ตำแหน่ง ต้อง Test ใหม่อาจจะ Test ทั้งหมด ถ้าใช้ Automate ก็โชคดีไปแต่ถ้ามีอเปล่าก็…. ไปนึกภาพเอาเองละกัน

กลับหัว

ภาพแสดงการ Design ที่ dependency กับ DBMS

หลังจากเกิดปัญหานี้ผมก็ลองไปค้นหนังสือสมุดที่เคยเรียน หาใน internet ว่ามีวิธีการดีๆอะไรบ้าง จนเจอเรื่อง SOLID แล้วก็เห็นว่าคนส่วนใหญ่เจอปัญหาแบบเดียวกันหมด เขาเลยมาตั้งเป็นหลักการ จากการอ่านคือ ผมไปเขียน Code ไปยึดติดกับส่วนที่เป็น low level ในส่วนที่อธิบายให้เราแก้ Design ทำการสร้างส่วนที่เป็น Abstract หรือ Layer ขึ้นให้มันบอกแค่มันทำอะไรได้ return อะไร อย่างของผมสร้างเป็น Data layer ผมกำหนดไปเลยว่า Data model หน้าตาเป็นยังไง มันสามารถค้นหาด้วยอะไรบ้าง ส่วนวิธีค้นหาเป็นยังไงผมไม่บอกรู้แค่ว่าไปหามาให้ได้ก็พอ หรือง่ายๆเวลา Design ให้พยายาม Design ให้เป็นอะไรแบบ หน้าตาเป็นยังไง เรียกแล้วออกมาได้อะไร ส่วนวิธีการเป็นหน้าที่ของ low level เป็นคนพยายามทำให้ได้ตามรูปแบบที่ hight level กำหนดไว้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

///ไฟล์ RpgUserDataLayer.js
exports.findById(_id, callback) {
// implement how to get data from database
db.rpgUser.findOne({_id: _id }, callback );
};

/// ใน Controller จากหลายล้านที่
function handleFindRpgUser(req, res) {
var RpgUserDataLayer = require("path/to/RpgUserDataLayer.js");

RpgUserDataLayer.findById(req.header("id"), function(req, res) {
if (err) {
res.status(500).send("Internal server error");
} else {
res.json(result);
}
});
}

ไม่เห็นมีไรเปลี่ยนแปลงแถมซับซ้อนขึ้นอีก “หลอกกันนี่นา”

ครับซับซ้อนขึ้นแน่นอนครับ แต่เมื่อเกิดการเปลี่ยนแปลงที่ชั้น DBMS เช่น เปลี่ยนยี่ห้อ เปลี่ยนการ Design model อันนี้ผมยกตัวอย่างเปลียนเป็น SQL จะเกิดอะไรขึ้น สิ่งที่ผมต้องไปแก้ก็แค่ที่เดียวคือ RpgUserDataLayer.js ส่วนที่ Controller อีกหลายล้านที่ผมก็ไม่ต้องแก้เพราะ Data layer ผมไม่ได้เปลี่ยนรูปแบบการรับส่งข้อมูล

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ตัวอย่าง Code ที่เขียนด้วย NodeJs

///ไฟล์ RpgUserDataLayer.js
exports.findById(_id, callback) {
// implement how to get data from database
connection.query("SELECT * FROM RpgUserTable WHERE _id = '" + _id + "'", function (err, resultUser) {
if (err) {
callback(err, null);
} else {
connection.query("SELECT * FROM RpgUserItemTable WHERE _id = '" + _id + "'", function (err, resultUserItem) {
resultUser.item = resultUserItem;
callback(err, resultUser);
});
}
}
};

สรุปเลยดีไหม น่าจะยาวเกินไปละ

ส่วนสรุปเป็นส่วนที่เขียนยากมากๆเลยนะไม่รู้ว่าจะสรุปยังไง เอาเป็นว่า Dependency Inversion คือการกลับความสัมพันธ์ Dependency จากที่เราไปยึดติดขึ้นตรงต่อกับชั้น Low level เราควรกลับหัวความสัมพันธ์ เราควรให้ส่วน Low level ขึ้นตรงกับ Hight level เหมือน code ที่ผมใช้ตัว Low level คือ DBMS connection lib ที่ต้องพยายามเขียน พยายามหาวิธีดึงข้อมูลออกมาให้ตรงกับรูปแบบ Data layer ที่ผม Design ไว้

แหล่งอ้างอิงและเรื่องที่น่าไปอ่านต่อ :
https://en.wikipedia.org/wiki/Dependency_inversion_principle
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
https://en.wikipedia.org/wiki/Coupling_(computer_programming)
https://en.wikipedia.org/wiki/Software_design_pattern
https://sourcemaking.com/

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