ก็คงจะเห็นไปแล้วกับแอปตัวใหม่ที่เพิ่งปล่อยไปอย่าง Hipception แอปแต่งภาพที่เอาไว้ใส่กรอบโซเชียลต่างๆเพื่อเพิ่มความฮิปสเตอร์
ไม่ใช่อะไรจริงจังหรอก เป็นแอปที่ทำเล่นเพื่อระบายความเครียด แต่ก็ทำอย่างใส่ใจนะ เพราะรู้สึกว่าไม่ได้ทำแอปของตัวเองมาก็นานพอสมควรแล้ว (เอาแต่สอน) และช่วงนี้อารมณ์ก็ขึ้นๆลงๆตกไปทางหงอยๆเศร้าๆ ก็เลยใช้เวลา 1 สัปดาห์นั่งทำแอปซะ โดยเข็นเอาเวอร์ชั่นแอนดรอยด์ออกมาก่อน ซึ่งตอนแรกว่าจะพอแค่นั้น ไม่ได้คาดหวังอะไรกับมันมาก แต่ปรากฏว่า Feedback ดีจัด ก็เลยใช้เวลาอีก 9 วันทำเวอร์ชั่น iOS ออกมาเรียบร้อย (กำลังรออนุมัติขึ้น Store อยู่ น่าจะกลางๆสัปดาห์หน้าครับ)
ตอนนี้ทุกอย่างเสร็จแล้ว ใช้งานได้สมบูรณ์ละ เหลือก็แค่อัพเดตใส่โน่นใส่นี่เพิ่มตามกาลเวลา ก็เลยถือโอกาสนี้มาเขียนบันทึกการพัฒนาตั้งแต่ต้นจนจบซะหน่อยครับ
จุดเริ่มต้น
จริงๆมีแอปแต่งภาพแหวกแนวหลายตัวที่อยากจะทำ แต่ที่เลือกทำ Hipception ก่อนเพราะได้รับแรงบันดาลใจมาจากโปรเจค special1 "i'm slowlife ?" ของทีมเด็กๆน้องลาดกระบัง (ลองกดลิงก์เข้าไปดูนะ ไม่เอารูปมาแปะเพราะยังไม่ได้ขอทางนั้น)
ก็เลยตัดสินใจทำแอปเพื่อใส่กรอบจากแอปต่างๆมาแต่งบนภาพซะเลย ฟีเจอร์ไม่มีอะไรมาก ก็เปลี่ยนกรอบได้ ใส่ Effect ให้พื้นหลังและภาพในกรอบได้ ข้อมูลต่างๆบนกรอบเช่นยอดไลค์ก็สามารถแก้ไขได้อย่างอิสระ
ทุกอย่างเน้นใช้งานง่าย ให้สามารถทำได้ภายในเวลาอันสั้นแต่ภาพที่ออกมาต้องแนว (ซึ่งจะทำเป็นโปรเจคใหญ่ๆก็ไม่ได้ไง มันมีเวลาสัปดาห์เดียว ... เจียมเนื้อเจียมตัวๆ)
และนั่นแหละครับ จุดเริ่มต้นเล็กๆของ Hipception เมื่อความเบื่อ ความเครียดและความนี้ดมาบรรจบกันพอดี ...
UX&UI Research
แอปนี้ทำเองทุกอย่างรวมถึงการดีไซน์แอปด้วย ถึงภาพลักษณ์จะเป็น Developer แต่จริงๆแล้วมาทางสายอาร์ทพอสมควร และเป็นคนที่ไม่สามารถทำแอปได้ถ้าดีไซน์คร่าวๆยังไม่เสร็จ (จะหงุดหงิดแหละ ไม่ใช่อะไร) ก็เลยจัดการดีไซน์ก่อนเป็นอย่างแรกเลย Research UX ก่อนสิ รออะไรอยู่!
ส่วนตัวไม่ใช่คนที่มาสายแต่งภาพฟรุ้งฟริ้ง (ดูหน้าตาก็น่าจะรู้) แต่มาสายแต่งภาพแบบเน้น Landscape อย่างพวก Snapseed และเนื่องจากมีแรงบันดาลใจอยากทำแอปแต่งภาพพวกนี้อยู่แล้ว ก็เลยมีโอกาส Research เรื่อง UX ของแอปพวกนี้มาเป็นปีๆ เล่นแอปมาเป็นร้อยตัว การเริ่มต้นโปรเจค Hipception ก็เลยไม่ยาก จับเอา UX/UI ของ Snapseed, Instagram และแอปแต่งรูปหลายๆตัวมายำรวมกันโดยเน้นไปที่ต้องใช้งานง่าย เริ่มแล้วใช้ได้เลยโดยไม่ต้องเปิดคู่มือหรือมี Tutorial
การวางปุ่มต่างๆ ก็ใช้หลักการ Thumb Zone ของ Scott Hurff
ก็เลยวางปุ่มต่างๆที่กดใช้บ่อยๆไว้บริเวณสีเขียว ส่วนบริเวณที่กดน้อยลงมาก็ใส่ในพื้นที่สีเหลือง และปุ่มที่กดเพียงครั้งสองครั้งก็จะใส่ไว้ตรงสีแดง จึงเกิดมาเป็นโครงหน้าอย่างที่เห็นตอนนี้ (Map ที่เห็นเป็นมือถือจอ 5.5 นิ้ว)
เรื่อง UX ที่หนุนความเคยชินของผู้ใช้ ก็จัดมาอย่างครบถ้วน เช่น Pinch Zoom (ใช้นิ้วย่อขยายภาพ) จากที่ทดสอบกับผู้ใช้จริงมา 5 คน พบว่าทุกคนสามารถ Pinch Zoom ได้ทันทีเป็นอย่างแรก และมันคงจะน่าหงุดหงิดมากถ้ามันทำไม่ได้ เพราะผู้ใช้เกือบทุกคนทำมันเป็นอย่างแรกเลย
พวกตำแหน่งปุ่มต่างๆก็วางไว้ให้สอดคล้องกับแพลตฟอร์ม เช่น OK/Cancel ใส่ให้ถูกฝั่งและปรับเปลี่ยนไปตามแพลตฟอร์มด้วย (ตอนเวอร์ชั่น iOS ออกแล้วลองมาเทียบกันดูว่า UX ต่างกันเล็กน้อย เพราะผู้ใช้ทั้งสองฝั่งเคยชินไม่เหมือนกัน)
พูดถึงเรื่อง UI บ้าง ทางด้านธีมแอป เราตัดสินใจไม่เอา Material Design มาใช้ เพราะไม่อยากให้แอปออกไปในแนวมาตรฐานจนเกินไป อยากมีสไตล์ของตัวเอง แต่พวกเรื่องขนาดฟอนต์ก็เป็นไปตาม Material Design Guideline นะ จะเห็นได้จากว่าฟอนต์จะใหญ่หน่อย ส่วน Padding Margin ทั้งหลาย ก็ใช้หลักหาร 8 ลงตัวอยู่ (8/16/24/32) ที่เหลือไม่มีอะไรมาก แปะๆๆๆไป
การออกแบบ UI ก็ใช้ Adaptive Layout ตั้งแต่แรก ซึ่งไม่ใช่อะไรใหม่สำหรับ Android Developer เพราะมันเป็นเรื่องพื้นฐานของแอปแอนดรอยด์ทุกตัวอยู่แล้ว มีมาตั้งแต่วันแรกที่แอนดรอยด์เกิด มันแค่เพิ่งมาจำเป็นใน iOS ตอนช่วงที่ iPhone 6 ออกแหละ (ใครเป็น iOS Developer แล้วยังใช้ Auto Layout ไม่เป็น ควรศึกษาด่วนนะครับ ไม่ใช่เรื่องที่ทำได้ก็ดี แต่เป็นเรื่องที่ต้องทำเลย)
และนี่ก็เป็นโอกาสแรกที่ได้เอาภาพที่ถ่ายเองมาใช้ในแอปตัวเองอย่างเป็นทางการ อย่างแรกที่ทำในแอปเลยแหละ สร้างเสริมกำลังใจ =)
รู้สึกว่ากล้องที่ซื้อใช้คุ้มละ ขายทิ้งเลยละกัน ...
Tools & SDK
ทางด้านการพัฒนา ใช้ Tools และ SDK ดังนี้
Android
IDE: Android Studio เวอร์ชั่นอัพเดตล่าสุดตลอดเวลา
ภาษาที่ใช้: Java
compileSdkVersion: 23
targetSdkVersion: 23 (แน่นอนว่าแอปเลยสนับสนุน Runtime Permission ด้วย)
minSdkVersion: 15 (Android 4.0.3)
Library พื้นฐาน: Android Support Library v23.1.0
iOS
IDE: Xcode 7.1
ภาษาที่ใช้: Swift 2.1
SDK: iOS 9 SDK
Deployment Targer: iOS 8.0
Dependency Manager: Cocoapods
ก็ถือเป็นโปรเจคแรกแหละที่ใช้ Swift ทำขึ้น Production ก็สนุกดีนะ ไม่ได้ทำแอป iOS มาสักพักละ
เลือกเทคนิคที่ใช้ในการแต่งภาพ
เนื่องจากเทคนิคการแต่งภาพมีอยู่หลายวิธี แต่สุดท้ายเลือกใช้วิธียากอย่างการเขียน GL Shader Language (GLSL) แล้วให้ GPU ประมวลผลภาพให้ เพราะถ้าใช้ตัว Graphic API ของแอนดรอยด์หรือ iOS เลย จะมีปัญหาเรื่องประสิทธิภาพ
ถามว่ามันคืออะไร? มันคือการโยนภาพเข้าไปใน GPU และเขียนโปรแกรมให้หน่วยประมวลผลกราฟฟิกประมวลภาพผลพิกเซลต่างๆจนออกมาเป็นผลลัพธ์ แล้วทำไมมันถึงเร็วกว่า? ก็เพราะ GPU ออกแบบมาให้ทำงานทางด้านกราฟฟิกโดยเฉพาะ มันเลยทำงานเรื่องพวกนี้ได้เร็วกว่า CPU มากๆนั่นเอง และหลังจากที่มันประมวลผลภาพเสร็จ ก็เลือกว่าจะเอาไปแสดงบนจอเลย(พรีวิวภาพ) หรือดึงเอาภาพที่ได้กลับมาและเซฟเป็นไฟล์เป็นอันเสร็จ(การบันทึก)
ผลคือพอใช้ GLSL แล้ว ประสิทธิภาพก็ดีมากๆอย่างที่คาดหวัง ทุกอย่างทำออกมาให้เห็นได้แบบ Real Time ไม่ต้องรอ ลองจิ้มๆลากๆปรับโน่นปรับนี่ดูได้ จะเห็นว่ามันเปลี่ยนเลยทันทีไม่มีอิดออด แอปอื่นๆที่ทำงานได้เร็วก็ใช้เทคนิคเดียวกันนี่แหละ ส่วนหน้าตาของ GLSL ก็ประมาณนี้
public static final String NO_VERTEX_SHADER = "" + "attribute mediump vec4 position;\n" + "attribute mediump vec4 inputTextureCoordinate;\n" + "\n" + "varying mediump vec2 textureCoordinate;\n" + "\n" + "void main()\n" + "{\n" + " gl_Position = position;\n" + " textureCoordinate = inputTextureCoordinate.xy;\n" + "}"; public static final String NO_FRAGMENT_SHADER = "" + "precision mediump float;\n" + "varying mediump vec2 textureCoordinate;\n" + "\n" + "uniform mediump sampler2D inputImageTexture;\n" + "\n" + "uniform lowp float alpha;\n" + "\n" + "void main()\n" + "{\n" + " lowp vec4 color = texture2D(inputImageTexture, textureCoordinate);\n" + " gl_FragColor = vec4(color.rgb, color.a * alpha);\n" + "}";
และความยากของโปรเจคนี้ก็อยู่ตรงนี้นี่แหละ การยุ่งกับ OpenGL และ GLSL เป็นอะไรที่มึนหัวและพึ่งจินตนาการมาก ต้องใช้ Math มาคำนวณ Matrix รัวๆ นั่งแปลงไปแปลงกลับระหว่าง Pixel และ (-1, 1) หรือ (0, 1) Debug ก็ไม่ได้ เลยต้องแม่นเรื่อง Concept พอสมควร กระดาษเต็มโต๊ะอ่ะ ทดกันสนุก (เสียดายทิ้งไปหมดแล้ว ไม่ได้ถ่ายไว้เลย) และเวลากว่า 70% ของการทำแอปก็เสียไปกับการงม GLSL นี่แหละ
ก็ต้องขอบคุณ GPUImage ของ CyberAgent ที่ทำไว้ดีมาก เราก็เลยเอาไว้ดูเป็นแนวทาง แต่ไม่ได้ใช้ไลบรารี่ของเค้านะ เขียนขึ้นมาเองใหม่หมด เพราะโค้ดที่เราจะใช้มันมีส่วนต่างค่อนข้างเยอะนั่นเอง แล้วก็อยาก Optimize เพิ่มด้วย
ส่วนพวกฟิลเตอร์ภาพอย่าง Amaro, Hudson อะไรพวกนั้น เราก็ไปดูๆ Instagram Filter ที่มีคนเขียนปล่อยเล่นไว้ใน Github อย่าง yulu/Instagram_Filter หรือ sangmingming/android-instagram-filter เป็นแนวทาง
ผลสุดท้ายออกมาคือดี ทำงานได้เร็ว ไม่กินแบต และพอร์ตไป iOS ได้อย่างรวดเร็ว เพราะใช้โค้ด GLSL เดียวกันได้ (เรื่อง Cross Platform เป็นอีก Factor ที่ตัดสินใจใช้ GLSL ตั้งแต่ตอนแรกเลยแหละ)
และด้วยผลงานอันเฉียบคมของ GPU ทำให้คุณภาพภาพที่ออกมาจึงคมชัดสวยงาม เราก็บันทึกผลที่ได้เป็น JPG Quality 90% เป็นอันเรียบร้อย เย้
CPU และ RAM ความท้าทายของแอปแต่งรูป
ความท้าทายของการทำแอปแต่งรูปทุกชนิดคือข้อจำกัดด้าน CPU และ RAM ซึ่งมีจำกัดเอามากๆบนมือถือ บน iOS ยังไม่ค่อยน่าเป็นห่วง แต่บนแอนดรอยด์นี่สิ ...
เนื่องจากเครื่องจะเล็กจะใหญ่จะแรงจะช้าแค่ไหน ภาพที่ถูกประมวลผลก็เรียกได้ว่าเป็นข้อมูลที่ใหญ่พอสมควรอยู่ดี หลาย MB หละ ถ้าหากเขียนไม่ดีก็รอกันไปอ่ะ ขยับทีนึงรอไป 3 วิถึงจะเห็นผล ตรงนี้ก็แก้ปัญหาด้วยการโยกงานไปให้ GPU ทำแทนตามที่บอกไว้ด้านบน ก็เลยรอดเรื่อง CPU ไปได้
ปัญหาใหญ่สุดๆคงจะอยู่ที่ RAM ละ โดยเฉพาะฝั่งแอนดรอยด์ที่แต่ละเครื่องให้ Heap ไม่เท่ากันเลย (หลักๆก็แปรผันตามจำนวนพิกเซลหน้าจอ จอละเอียดน้อยก็ Heap น้อย จอละเอียดมากก็ Heap เยอะ โชคดีที่ OS รุ่นหลังๆมันขยาย Heap ได้ค่อนข้างเยอะเลยพอจัดการง่ายหน่อย) และข้อจำกัดด้านขนาด Texture ก็มีอยู่ บางเครื่องก็ได้มากสุด 2048x2048 พิกเซล บางเครื่องก็ได้ถึง 4096x4096 พิกเซล
ข้อแรกที่จัดการตัดปัญหาทิ้งไปคือขนาด Texture สุดท้ายเลยจำกัดไว้ที่ 2048x2048 พอ ถึงแม้บางเครื่องจะรัน 4096x4096 ไหวแต่ก็ยังลิมิตไว้ก่อนเนื่องจากมันใหญ่เกินความจำเป็นของจุดประสงค์แอปที่เอาไว้แชร์ออก Social ละ
ซึ่งเพื่อจำกัดปัญหาเป็นวงแคบ Strategy ที่เราทำไปคือตอนแรกลิมิตขนาดภาพไว้ที่ 1280x1280 ก่อน เพื่อดูว่ามีปัญหาอะไรมั้ย หลังจากนั้นสองวันพอเคลียร์ทุกอย่างเสร็จและพบว่าไม่มีปัญหาเรื่อง Memory ส่งมาเลย ก็ขยายออกเป็น 2048x2048 แทนอย่างที่เป็นตอนนี้
ปัญหาที่สองอย่าง Heap นี่ถือเป็นเรื่องน่ากังวลสูงสุดของการทำแอปนี้ละ เพราะภาพที่เอามาประมวลผลค่อนข้างใหญ่ หากไม่มีการจัดการ Memory ที่ดีอย่างเพียงพอ มันมาแน่ครับ OutOfMemoryError
ที่ทำไปคือ
1) ขี้เหนียวมาก ทุกไบต์มีความหมาย อะไรไม่จำเป็นจะไม่ประกาศตัวแปรเลย
2) ตอนพรีวิว เรา Decode ภาพออกมาให้เล็กที่สุดเท่าที่จะเล็กได้ โดยอิงกับขนาดหน้าจอ เพื่อให้การประมวลผลทำได้เร็วและไม่เปลืองแรม ส่วนตอนเรนเดอร์จริงก็โหลดเป็น 2048x2048 เท่ากันทุกเครื่อง หรือถ้าบางเครื่องแรมไม่พอจริงๆก็จะมีการคำนวณสดและลดเหลือ 1280x1280 อัตโนมัติ
3) เมื่อมีการจองเมมเพื่อใช้โหลดรูป ก็จะเอาไปทำงานทันทีและทำลายทิ้งอย่างรวดเร็วไม่เหลือเยื่อใยด้วยคำสั่ง
bitmap.recycle()
สรุปคือต้องมีสติตลอดเวลา ... อะไรจองไว้แล้วต้องรับผิดชอบทำลายเสมอ ห้ามปล่อยปละละเลยเป็นอันขาด โดยส่วนตัวจะยัดคอมเม้นท์ไว้ว่า
// TODO: xxxx
เพื่อช่วยเตือนสติว่าต้องทำอะไรหลังจากจองตรงนี้แล้ว
ถามว่าเราจะตรวจสอบได้ยังไงว่ามี Memory Leak รึเปล่า? สำหรับแอนดรอยด์เครื่องมือที่ใช้ในการตรวจจับคือ Memory Monitor ของ Android Studio นั่นเอง คอยดูการเพิ่มและลดของ Memory ที่ถูกจองไว้และใช้ไป สุดท้ายก็ทำให้หาเจอว่ามี Memory Leak ตรงไหนและไล่เก็บจนหมด
เครื่องมือตัวนี้สำคัญนะ ใครทำแอปแอนดรอยด์อย่าลืมเช็คที่กราฟนี้ทุกหน้าจอ ช่วยได้มากๆๆๆ
ส่วน iOS ไม่มีปัญหาเรื่อง Heap Memory น้อยเพราะสถาปัตยกรรมต่างกัน แอปแต่ละตัวจะสามารถใช้แรมได้เยอะมากกกกกกกก (หลายร้อยเมก) ทำให้เขียนง่ายขึ้นมาหน่อย แต่ยังไงก็ต้องจัดการด้วยเช่นกันเพราะหากของ Memory มั่วซั่วและไม่รู้จักคืนก็ Leak สิ พอ Leak แป๊บนึงแอปก็จะ Crash เราเลยต้องคอยจัดการกับทุกไบต์ที่เราหยิบมาใช้งาน หากไม่ได้ใช้แล้วก็คืนนนนระบบซะนะ (ถึงจะมี ARC แต่ถ้าไม่แม่นเรื่อง Concept ก็พังอยู่ดีนะ)
เรื่องของการตรวจจับ Leak เบื้องต้นสามารถดูจากกราฟ Memory ที่ขึ้นมาตอนรันได้เลย ดูการขึ้นและลง ไม่ใช่ขึ้นแล้วขึ้นเลยไรงี้ อันนี้เป็นกราฟที่มีการเรียกใช้ Memory และกดออกมา แรมก็จะก็กลับไปเท่าเดิม
ส่วนการตรวจจับแบบละเอียด ให้รันในโหมด Profile แล้วเลือก Leak จะคอยบอกเรื่อยๆว่ามี Leak รึเปล่า พอเจอสาเหตุก็ไปไล่เก็บซะ เป็นอันเรียบร้อย ... แต่ไม่ชอบโหมดนี้เลย ทำงานช้าน้ำตาเล็ด
และเพื่อให้แอปอยู่อย่างยั่งยืน แอปตัวนี้ก็เลยดักไว้ด้วยว่าพอแอปเข้าสู่ Background จะคืนทุกอย่างที่คืนได้ให้ระบบ ภาพทั้งหมดที่โหลดไว้ใน Memory ก็ทำลายทิ้งหมด พอกลับสู่ Foreground ก็โหลดใหม่ จากการทดลองบน iPod Touch แอปแทบมีโอกาสโดนฆ่าทิ้งตอนอยู่ Background ต่ำกว่าตอนไม่คืนมากๆอย่างมีนัยสำคัญ
ความน่าเศร้าที่เจอตอนนี้คือ ... โค้ดตัวเองทำมาซะดิบดี ไม่มี Leak เลย แต่ดันมี Leak จาก AdMob ของกูเกิลจ้าาาาและเราทำอะไรไม่ได้ด้วย ... เซ็งจริมๆ
Workflow & Issue Tracking
เนื่องจากทำงานคนเดียวก็เลยไม่ได้ใช้ Issue Tracking Software ใดๆ แต่กลับไปสู่สามัญ ... Post-It และกระดาษจด แปะไว้เต็มคอมพ์ ลุกออกจากคอมพ์ทีต้องสะบัดๆตัวทิ้งเลยทีเดียว นับๆแล้วน่าจะเกิน 50 แผ่นอ่ะ คือคิดอะไรได้ก็จะจดและแปะเลย สุดท้ายก็จะไม่ลืมโน่นลืมนี่และทำงานได้อย่างคล่องแคล่ว
Flow ก็ไม่มีอะไรซับซ้อน ใช้ Kanban แบบสั้น (TODO - DOING - DONE) และเรียบเรียงความสำคัญก่อนหลัง จากนั้นก็ทำทีละส่วนๆๆๆๆ งานไหนติดก็เอากลับไปแปะและหางานที่ไม่เกี่ยวข้องมาทำแทน ไม่ก็อาบน้ำ ไม่ก็นอน ซึ่งไม่น่าเชื่อ พอตื่นมาแล้วปัญหาที่คิดไม่ออกครึ่งวันกลับแก้ได้ใน 5 นาที ...
ส่วนตัวแล้วการเบรคงานออกมาเป็นส่วนย่อยๆถือว่าสำคัญมาก งานที่เราเขียนไว้ในแต่ละใบจะเป็นงานเล็กๆสั้นๆทุกอย่าง อันไหนเริ่มมีหลายอย่างก็จะแตกเป็นหลายๆใบ ซึ่งมันช่วยให้เราสามารถทำงานเสร็จในเวลาอันสั้นได้ เพราะทุกอย่างเหมือนเฟืองชิ้นเล็กๆและสุดท้ายมันก็เอามาประกอบกันจนเป็นชิ้นงานที่สมบูรณ์แบบได้ (ใช้วิธีนี้มา 5-6 ปีได้ละ ตอนทำงานร่วมกับทีมใหญ่ๆ)
แล้วก็มีการใช้ Agile ด้วย โดยมี Standup Meeting ของคนในทีมทุกวัน ตอนเช้าและตอนค่ำๆ ... ซึ่งมีอยู่คนเดียวไง พูดกับตัวเองตลอดเวลา ตอนอาบนงอาบน้ำไรงี้ เอ้อ ...
ล้อเล่นๆ ส่วนตัวแล้วโปรเจคนี้ไม่มี Workflow อะไรที่เป็นรูปเป็นร่างครับ เพราะทำคนเดียว Product Owner, Scrum Master, Team รวมอยู่ในคนคนเดียว ... เลยไม่มี Backlog อะไรมากมาย จับต้องได้มากสุดคือ Kanban นั่นแหละ โปรเจคนี้ทำขำๆ เดี๋ยวมันไม่เสร็จ !
การ Test
บน Android เขียน Automate Test บ้าง แต่ไม่เยอะ ส่วนใหญ่เน้นไปที่ Unit Test (JUnit) สำหรับฟังก์ชั่นการคำนวณ Matrix ที่ทำขึ้นมา และ Instrumentation Test (Test Support Library + Espresso) สำหรับ Custom View ที่ทำมา (เน้นไปเรื่องการทำลาย Component ต่างๆทิ้งแล้วต้องคืน Instance State กลับมาได้) แล้วก็มีทดสอบ Flow เล็กน้อย เช่น ถ้ากด Cancel ค่าจะต้องเหมือนเดิมนะ บลาๆๆๆ
การเขียน Automate Test เหล่านี้ก็ช่วยให้ประหยัดเวลาทดสอบไปได้หลายอย่างเลย ไม่ต้องมานั่งใช้มือจิ้มเองให้เสียเวลา แต่ก็ต้องรันอยู่ดีนะ เพราะไปๆมาๆโค้ดส่วนใหญ่ที่ทำมาดันเทสต์บน JUnit ไม่ได้ เนื่องจากลงลึกเกิน สุดท้ายเลยไปใช้ Instrumentation Test ซะ 90% ได้ ตัดตัวอย่างมาแปะหน่อยนึง สั้นๆ
public class PointUtilsTest extends AndroidTestCase { @Test public void testDistance() throws Exception { PointF p1 = new PointF(10, 10); PointF p2 = new PointF(10, 10); assertEquals(0f, PointUtils.distance(p1, p2), 0f); p1 = new PointF(10, 10); p2 = new PointF(20, 10); assertEquals(10f, PointUtils.distance(p1, p2), 0f); }}
ส่วน iOS ไม่เขียน Automate Test เลยเพราะโค้ดส่วนใหญ่พอร์ตมาจากแอนดรอยด์ ก็เลยใช้ศรัทธาซะเยอะ ...
อย่างไรก็ตาม โปรเจคนี้สารภาพว่าโค้ดเกิน 60% ยังไม่มี Test ก็ประหยัดเวลาระยะสั้นได้นะ แต่ระยะยาวคงไม่ดีนัก งานนี้นอกจาก Instrumentation Test ที่ทำไว้บางส่วน ในส่วนอื่นๆก็เลยใช้มือเทสต์เรื่อยๆ เพราะเชื่อใจในสกิลหัตถเทวะจับอะไรก็พังของตัวเองมากกว่า ... ผลคืออะไรที่พังๆก็โดนเก็บหมดก่อนจะถึงมือ Beta Tester นั่นเอง
และล่าสุดบน iOS ก็ผ่านด่านการทดสอบที่โหดสุดที่มนุษย์มีมาแล้วครับ
น้องเปียโนโปเนียเล่นแล้วแอปไม่พัง!! สบายใจขึ้นไปหลายเปราะเลย
Device หลักที่ใช้ Test
หลายคนเอามือถือแรงๆมาเทสต์แอป แต่หารู้ไม่ว่าในตลาดมือถือ ตัวที่เป็น Mass มันคือ Mid-End ดังนั้นการเอามือถือแรงๆมาเทสต์ถือว่าบาปนัก สปอยล์ตัวเองสุดๆ เราเลยเอามือถือที่ความแรงกลางๆมาเทสต์ ถ้าหากทำงานได้ก็ถือว่าไม่มีปัญหากับทุกเครื่องแล้วแน่นอน
แอนดรอยด์ทดสอบบน AIS Lava 4.0 (Iris 700) และ Samsung Galaxy Note 3 LTE ครับ
ส่วน iOS ก็ทดสอบบน iPod Touch 5th Gen
อ่อ อีกเหตุผลนึงก็คือไม่มีตังค์ซื้อไอโฟนมาเทสต์ ... จบนะ ...
ตรวจจับการ Crash ด้วย Crashlytics
ถึงจะใช้เวลาน้อยในการพัฒนา แต่ก็มี Tester คอยเทสต์อยู่ตลอดเวลา ... ก็ตัวเนยเองนี่แหละ สกิลจับอะไรก็พังช่วยให้บั๊กทั้งหลายมลายหายไปเยอะจนแทบไม่เหลือ
แต่ก็นะ ให้เขียนดียังไง แอปแอนดรอยด์ก็สามารถมีปัญหาได้ อย่าฝันว่าจะ Bug Free หรือ Crash Free เป็นไปไม่ได้
เราก็เลยฝังเจ้าตัวนี้ไปด้วย Crashlytics บริการตรวจจับการ Crash ของแอป เมื่อไรก็ตามที่แอปแครช จะมี Stacktrace ส่งมาให้เราแก้ไขทันที อย่างวันแรกเราแก้ไป 9 เวอร์ชัน และตอนนี้ล่าสุดก็เกือบเป็น Crash-Free Application เรียบร้อยขอรับกระผม
และนี่ก็เป็นเทคนิคที่หลายๆคนถามมาว่าทำไมแอปถึงดูไม่มีบั๊กหรือไม่มีปัญหาเลย อันนี้ตอบได้เลยว่า จริงๆมันมีปัญหาครับ แต่พอมีปัญหาเนยก็แก้และเอาขึ้นใหม่ทันที ดังนั้นผู้ใช้ก็จะไม่รู้เลยว่ามีปัญหานั่นเอง
ก็อย่างที่เนยสอนนักเรียนไปว่า เวลาปล่อยแอปให้ตั้ง War Room ไว้เลย 1 สัปดาห์เพื่อแก้ไขปัญหาทั้งหมดที่ยิงมา จากนั้นก็ปล่อยได้ละ สบาย ปัญหามีได้เสมอ แต่ต้องแก้ให้เร็วครับ สำคัญสุดๆ
และผลพลอยได้จากการฝัง Crashlytics คือการได้ Stat ของผู้ใช้ด้วยฟีเจอร์ Answers ของ Fabric.io ดูได้ว่ามี Daily Active User เท่าไหร่ มี Current Active User เท่าไหร่ ฯลฯ กราฟนี้ช่วยให้เรียนรู้พฤติกรรมคร่าวๆของการกลับเข้าใช้แอปได้ดีมากทีเดียว
ใครทำแอปก็แนะนำให้ใส่ซะครับ Crashlytics ส่วนตัวถือว่าสำคัญระดับ Very High Priority หรือ "ไม่ใส่ไม่ได้" เลย
การเก็บสถิติ
ถึงแอปจะทำเล่นๆ แต่การทำอะไรไปแล้ววัดผลไม่ได้มันไร้ค่า ก็เลยมีการเก็บสถิติไว้หลายอย่างเหมือนกัน โดยใช้ Google Analytics และ Mixpanel เป็นตัวเก็บสถิติหลักๆ
โดย Google Analytics มีไว้ดูภาพรวม คนใช้เยอะแค่ไหน มาจากประเทศอะไร เข้าหน้าจอไหนเยอะน้อย ส่วน Mixpanel เอาไว้ดูพฤติกรรมโดยละเอียด คนกดเข้าเยอะแค่ไหน
ถามว่าใครเป็นพระเอก ต้องบอกว่า Mixpanel ทำงานได้ครอบคลุมกว่าเยอะ แต่มันเป็นบริการที่ต้องเสียเงินถ้าต้องการเก็บ Data Point มากๆ ซึ่ง ... เราไม่มีเงินงะ ก็เลยเก็บไว้ทั้งสองที่ ถ้าถึงจุดที่ Mixpanel ต้องเสียเงิน (200,000 Data Points ต่อเดือน) ก็จะไปดูข้อมูลต่อที่ Google Analytics แทน (10,000,000 Hit ต่อเดือน)
ถ้าใครไม่มีปัญหาเรื่องเงิน ... Mixpanel โลดจ้าาา ไม่ต้องคิดมาก
นอกจากนี้ก็มี Answer โดย Fabric.io ที่โพสต์ให้ดูด้านบนแล้ว ก็เป็นอีกตัวที่เอาไว้ดูข้อมูลคร่าวๆอีกเช่นกัน แต่ก็ไม่สามารถเอามาวิเคราะห์อะไรได้มากนะ
และเราก็สมัคร AppAnnie ไว้ เพื่อให้มันช่วยวิเคราะห์ลำดับในประเทศต่างๆและหาคำเพื่อทำ ASO (App Store Optimization) อันนี้ก็แนะนำเช่นกันๆ ของเล่นเยอะดี
สุดท้ายท้ายสุด ... การไปดู Statistics ใน Google Play ก็สำคัญนะครับ และคอยเช็ครีวิวด้วยตลอดเวลา เพราะเวลามีปัญหาอะไรเค้าจะรีพอร์ทมาทางนี้เสมอ
และผลของการเก็บสถิติเหล่านี้คือ เราสามารถเอาข้อมูลมาทำอะไรแบบนี้ได้ ...
ทำอะไรก็ตาม จงมี Metric ครับ =)
Push Notification คอยแจ้งการอัพเดต
แอปไม่มีอะไรมากแต่ก็จัดเต็มหลายอย่าง อย่าง Push Notification ก็ใส่เข้าไปด้วยเช่นกัน ไม่ได้ทำระบบเอง ขี้เกียจ เลยเลือกใช้ตัวสำเร็จรูปอย่าง Parse Push เพราะบริหารจัดการง่ายดี และประสบการณ์ก่อนหน้านี้กับ Parse Push ถือว่าวิเศษมาก ไม่เคยมีปัญหาใดๆ ก็เลยไม่ไปใช้ตัวอื่นละ
ถามว่าทำไมถึงไม่ควรทำระบบเอง? เพราะการ Push ไม่ได้มีแค่เรื่องการส่งครับ การวิเคราะห์จำนวนการเปิด (Analytics) ก็สำคัญเช่นกัน ซึ่งหากมานั่งทำระบบ Analytics เองคงเสียเวลามาก เราเลยขอแนะนำตัวนี้แหละครับ Parse Push ใช้ง่าย มีทุกอย่างครบและ Cross Platform ด้วย
ค่าใช้จ่ายฟรี 1 ล้านเครื่องแรก และหลังจากนั้นคิด $0.05 ต่อ 1000 เครื่องที่เกินมาต่อเดือน หรือคิดง่ายๆคือ $50 ต่อ 1 ล้านเครื่องถัดไป ถ้าถึงจุดที่ต้องจ่ายเงินแล้วก็ยินดีด้วยครับ มีผู้ใช้เป็นล้านแล้ว ! แต่ก็จ่ายเงินถูกมากกกกกกอยู่ดี ถูกกว่าทำเองเยอะครับ
ประสบการณ์กับการทำแอป Production ด้วย Swift
ถึงจะเล่น Swift มาตั้งแต่มันออกมาวันแรก จนกระทั่งมันเติบโตเป็น Swift 2.1 อย่างทุกวันนี้ แต่ก็ไม่มีโอกาสได้ทำแอป Production ด้วย Swift สักที เจ้า Hipception นี้ถือเป็นตัวแรกเลย (ก่อนหน้านี้มีแอป nuuneoi.com แต่ยังไม่ปล่อย เดี๋ยวปล่อยหลังจากตัวนี้อีกที)
เริ่มจากคำถามเด็ดก่อนเลย "Swift พร้อมจะทำแอป Production รึยัง?"
อันนี้ตอบได้เต็มปากแล้วนะครับว่า "เกินคำว่าพร้อมไปนานแล้ว" นาทีนี้เขียนแอปด้วย Swift ได้เลยครับ ไม่มีปัญหาใดๆละ จะมีก็แค่ Xcode ชอบงอแงเรื่อยๆ แต่ก็กลับมาปกติได้ในเวลาไม่กี่วิ และก็อาจจะมีบางอย่างที่น่าหงุดหงิดหน่อยๆเช่นเรื่องของ Long Expression (โค้ดยาวๆ) เจ้า Swift จะไม่ให้รัน ... ต้องแตกโค้ดออกเป็นบรรทัดย่อยๆถึงจะคอมไพล์ได้ ... คือจริงๆมันหน้าที่ Compiler ไม่ใช่หรอว้าาา แต่ก็เอาแหละ ได้อยู่ๆ
แต่ทิ้ง Objective C ได้ยัง? อันนี้ก็พูดได้อีกเช่นกันว่าห้ามทิ้งงงง ถึงจะเขียนไม่ได้แต่ก็ต้องอ่านได้ครับ เพราะโค้ดตัวอย่างที่มีค่ายังเขียนด้วย Objective C จำนวนมาก เราต้องแปลงเป็น Swift ได้ด้วยตัวเอง หากอ่าน Objective C ไม่ออกก็วอดวายเป็นแน่แท้
อย่างไรก็ตาม อุปสรรคหลักๆที่เจอคือเรื่องของโค้ดในเนต ถึงโค้ดตัวอย่างที่เป็น Swift เริ่มมีให้เห็นกันเยอะมากๆแล้ว แต่ก็นะ จำนวนมากยังเป็น Swift 1.2 อยู่เลย เราก็ต้องแปลงโค้ดกันเอาเอง แต่ก็ไม่ใช่ปัญหาใหญ่เท่าไหร่ ทำให้เก่งขึ้นด้วยซ้ำ
สุดท้ายตอนทำ ipa ออกมาก็แอบตกใจเล็กๆ เพราะไฟล์ใหญ่มากกกกกก 35-37MB แหนะ เทียบกับแอนดรอยด์ มันแค่ 6MB เอง ! แต่พอไปแงะไฟล์ก็พบว่าที่มันใหญ่ๆเนี่ย 75% มาจากไฟล์ Library ของ Swift ทั้งสิ้น สาเหตุเพราะไลบรารี่เหล่านั้นมีการฝัง Bitcode ลงมาด้วย ทำให้บวมมาก แต่พอเอาขึ้น Store มันก็จะ Thinning ลงให้เหลือแค่ 15MB ซึ่งก็โอเคอยู่ ...
ปัญหาก็จะมีแค่ตอนอัพโหลดแหละ แต่ก็ไม่อะไรมาก เนตเร็ว ...
การพัฒนาแอปบน iOS เทียบกับบน Android
โปรเจคนี้ทำทั้งบนแอนดรอยด์และ iOS ซึ่งถามว่าอันไหนง่ายกว่ากัน? ความยากง่ายคงพอๆกันละ บางอย่างที่ง่ายในแอนดรอยด์ ดันไปยากใน iOS ในขณะที่บางอย่างที่ง่ายใน iOS ก็ดันไปยากในแอนดรอยด์ ดังนั้นถือว่าเสมอกันละกัน
แต่สิ่งที่ต่างกันลิบลับเลยคือ "ความกังวลใจ" ในข้อนี้ตอบได้เลยว่า เขียน iOS แล้วกังวลใจน้อยกว่ามากกกกกก เพราะ Fragmentation น้อย โค้ดที่เขียนไปยังไงก็ทำงานได้และทดสอบได้ง่ายกว่า แต่พอเป็นแอนดรอยด์ อะไรก็เกิดขึ้นได้ เลยรู้สึกเสียวๆตลอดเวลา นี่ขนาดเขียนแอนดรอยด์มา 7 ปี ก็ยังรู้สึกกลัวๆอยู่บ้าง ถึงจะมั่นใจระดับนึงแล้วว่าแอปคงไม่มีปัญหาง่ายๆ แต่ก็รู้สึกว่า Fragmentation มันยังขยายตัวออกไปเรื่อยๆอยู่
และนั่นทำให้เรารู้สึกว่าการเขียนแอป iOS นั้นง่ายกว่าแอนดรอยด์พอสมควร สองเรื่องคือ (1) Fragmentation (2) Memory ที่ใช้งานได้ในแอป ซึ่งเราไม่ต้องมานั่งกังวลอะไรกับเรื่องพวกนี้เท่าไหร่เลยบน iOS เขียนไม่ต้องดีมากแอปก็ทำงานได้อ่ะ ประมาณนั้น ในขณะที่ถ้าบนแอนดรอยด์เขียนไม่ดีเพียงนิดเดียว แครชกระจุยจ่ะ
แต่โดยรวมตอนนี้ก็พูดได้เต็มปากละว่า iOS กับ Android มันมาชนกันกว่า 90% ละ ลอกกันไปลอกกันมาจนแนวคิดจะเหมือนกันอยู่แล้ว แต่สิ่งที่ iOS ลอกแอนดรอยด์มาหลายๆอย่างก็ทำมาได้ไม่ดี เช่นระบบ Constraint ทำมาได้ใช้งานยากมากเพราะโดนข้อจำกัดจากการลากๆ จนสุดท้าย Constraint พันตัวเต็มไปหมด ไม่งงก็มึนอ่ะ ผมว่าเป็นเรื่องแรกๆที่แอปเปิ้ลควรจะต้องปรับปรุงเลย มันควรจะจัดการง่ายกว่านี้มากๆครับ
ความทุกข์ขั้นสุดของการเขียน iOS คงเป็นเรื่องของการเอาขึ้น Store แหละ ระบบ iTunes Connect นั้นกากเหลือเกิน กากยิ่งนัก กากจนไม่คิดว่าบริษัทใหญ่ขนาดนี้จะปล่อยให้มันกากอย่างตอนนี้ได้ (และก็แพงมากด้วย)
ขั้นตอนแรกหลังจากอัพโหลดขึ้น Store ก็ต้องรอให้ระบบ Process ไฟล์ให้เรียบร้อย ซึ่งไม่รู้ว่าใช้ Mac Mini 2011 มาโปรเซสไฟล์ให้หรืออย่างไร เพราะใช้เวลาร่วม 12-24 ชั่วโมงกว่าจะโปรเซสเสร็จ! จะส่งให้ External Beta Tester ทดสอบก็ไม่ได้ ต้องรอเป็นวันๆ หลังจากนั้นก็ต้องรออีกโดยเฉลี่ย 7 วันเพื่อให้เค้า Reject มาและ Submit ใหม่ ... ไม่สิ เพื่อให้แอปขึ้น Store นั่นแหละ
เป็นทุกข์หนักระดับที่ทำให้รู้สึกเกลียดเลยหละ ตรงนี้แอนดรอยด์วินสุดๆ ...
สรุปนะสรุป ทั้งสุขและทุกข์มันทั้งสองแพลตฟอร์มอ่ะ แล้วแต่ว่าจะสุขตอนไหนจะทุกข์ตอนไหน 555
แต่โดยรวมนะ ตอน Dev เรายกให้ iOS สุขกว่า อะไรๆก็ง่ายกว่า แต่ตอน Deploy เนี่ย ทุกข์จัดๆ ...
Download
เรื่องราวก็ประมาณนี้ครับ ตอนนี้อยู่บน Play Store ละ สามารถโหลดมาเล่นได้เลย
ส่วน iOS รอหน่อย แอปเปิ้ลยังไม่อนุมัติ ...
อัพโหลดรูปก่อนแต่งขึ้น Cloud จีน
ไม่มีเฟร้ยยยยยยยยยยยยยยยยยย
สรุป
เป็นโปรเจคที่ทำสนุกๆเอาขำๆแต่ก็ได้อะไรเยอะดี ขอบคุณสำหรับ Feedback ดีๆ หากต้องการฟีเจอร์อะไรเพิ่มเติมหรืออยากได้กรอบอะไรเพิ่ม แนะนำได้น้า จะหาใส่เพิ่มเรื่อยๆครับ =)