<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Taskworld Tech - Medium]]></title>
        <description><![CDATA[How Taskworld builds Taskworld - Medium]]></description>
        <link>https://medium.com/taskworld-tech?source=rss----773aef92a29e---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Taskworld Tech - Medium</title>
            <link>https://medium.com/taskworld-tech?source=rss----773aef92a29e---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 18:41:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/taskworld-tech" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[ใช้ User story อย่างไรให้มีความหมาย]]></title>
            <link>https://medium.com/taskworld-tech/%E0%B9%83%E0%B8%8A%E0%B9%89-user-story-%E0%B8%AD%E0%B8%A2%E0%B9%88%E0%B8%B2%E0%B8%87%E0%B9%84%E0%B8%A3%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B8%A1%E0%B8%B5%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%AB%E0%B8%A1%E0%B8%B2%E0%B8%A2-1a980e953b1?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/1a980e953b1</guid>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Tue, 07 Aug 2018 12:26:06 GMT</pubDate>
            <atom:updated>2018-08-07T12:33:12.670Z</atom:updated>
            <content:encoded><![CDATA[<p>เคยคิดกันบ้างมั้ยว่าทำไมเราถึงต้องเขียน User Story ด้วยนะ?</p><p>ในโลกของ Agile เรากำหนดว่า PO หรือ Product owner มีหน้าที่แค่ระบุปัญหาและความสำคัญของปัญหา ส่วนการออกแบบ Solution จะเป็นหน้าที่ของ Cross-functional team ที่จัดการตั้งแต่ต้นจนจบ</p><p>ทีนี้ในการระบุปัญหา เราก็สามารถระบุได้หลายรูปแบบ แต่ทำไม Agilist ถึงมักจะแนะนำเลือกให้เป็นรูปแบบ User story</p><p>ทีนี้ User story ที่มีความหมายควรจะเป็นอย่างไร ผมขอตอบว่า</p><blockquote>User story ที่มีคุณค่า ควรจะสามารถตอบได้ว่า อะไรควรมีและอะไรไม่ควรมีใน Product</blockquote><p>ส่วน User story ที่ไม่ดีเป็นอย่างไร ผมตอบว่า</p><blockquote>User story ที่ไม่ดี คือ User story ที่ตอบได้แค่เพียงว่า อะไรควรมีใน Product</blockquote><p>มาถึงจุดนี้อาจจะงง ผมขอยกตัวอย่างการออกแบบเว็บ Google ว่า ถ้าเขียน User story ให้ Google ควรจะเขียนแบบไหน</p><p>Story ที่ดีจะเป็น</p><p><em>“ในฐานะ User ผมต้องการค้นหาเว็บไซต์ที่มีข้อมูลการซื้อขายบ้าน เพื่อเข้าไปอ่านราคาบ้าน”</em></p><p>ส่วน Story ที่ไม่ดีก็จะเป็น</p><p><em>“ในฐานะ User ผมต้องการค้นหาข้อมูลเว็บไซต์ที่เกี่ยวข้องกับหัวข้อที่ผมสนใจ เพื่ออ่านเว็บไซต์ที่ผมสนใจ”</em></p><p>ถ้าสมมติผมเริ่มเขียน Google ใหม่จาก 0 และผมกำลังทำ MVP ลองตลาด</p><p>Story แรกผมจะรู้ว่า ผมต้องทำให้ค้นหาเว็บไซต์ขายบ้านเจอ อย่างอื่นช่างมัน ไม่จำเป็นต้องมีใน Product ก็ได้</p><p>ส่วน Story หลัง ผมจะไม่รู้ว่าผมต้องทำให้หาได้ดีขนาดไหนใน 1 Sprint กันแน่ อะไรคือข้อมูลที่เกี่ยวข้อง ต้องทำขนาดไหน</p><p>Story แรกผมจะตอบได้ทันทีว่า อะไรที่ไม่เกี่ยวกับการซื้อขายบ้าน ไม่อยู่ใน Sprint</p><p>ส่วน Story หลัง ผมแทบจะตอบไม่ได้เลยว่า อะไรบ้างที่ไม่ควรอยู่ใน Sprint</p><p>มีหน้า Advance Search ให้เลือกค้นหา ก็อยู่ใน Story นี้ได้</p><p>มีระบบ Predicate ก็อยู่ใน Story นี้ได้</p><p>มีระบบ Categorize ก็อยู่ใน Story ได้</p><p>มีระบบค้นหาด้วยรูปภาพ ก็อยู่ใน Story นี้ได้</p><p>จะเห็นว่า Story อันแรก มันบอกได้ทั้งที่ว่าอะไร “ใช่” และอะไรที่ “ไม่ใช่”</p><p>ส่วน Story หลัง มันเป็น Story ที่ มีของที่ใส่แล้วใช่เต็มไปหมด แทบจะเรียกได้ว่าใส่อะไรก็ “ใช่” ได้ทั้งนั้น</p><p>เป็น Unbound story ที่ไม่มีทางสามารถสร้าง Scope ที่ทำสำเร็จเกิดเดโมใน 1 Sprint ได้</p><p>ผมพบว่าในความเป็นจริง หลายครั้งเราจะชอบเขียน Story แบบหลัง</p><p>เราอาจจะคิดว่า “จะบ้าเหรอ เขียน Story แบบแรกมันก็แย่เกิน เราทำระบบค้นหาเว็บไซต์ แต่หาได้แค่บ้านเนี่ยนะ?”</p><p>ความคิดแบบนี้แหละที่มักจะนำมาสู่การเขียน Story ที่กว้าง</p><p>เพราะเราเชื่อว่า ถ้าเขียนเล็กเกินไป ระบบที่ออกแบบมาสุดท้าย มันจะไม่สมบูรณ์</p><p>ผมเห็นด้วยนะ จะเอาแค่ค้นหาบ้านได้แบบนั้นมันก็ไม่ใช่ระบบค้นหาเว็บพอดี</p><p>ถ้า PO เดินมาบอก ผมก็เห็นด้วย ว่ามันไม่พอที่จะเรียกได้ว่า “ระบบค้นหาเว็บไซต์”</p><p>แต่ถึงกระนั้น เราก็ไม่ควรเขียน Story แบบหลังอยู่ดี</p><p>เพราะถ้าเขียน Story แบบหลัง ยังไงเวลาออกแบบ Solution ก็จะบานปลายมาก แทบจะบอกไม่ได้เลยว่าอะไรควรอยู่อะไรไม่ควรอยู่</p><p>มีระบบ Predicate ก็อยู่ใน Story นี้ได้ มีระบบ Categorize ก็อยู่ใน Story ได้ มีระบบโน่นนี่นั่นก็อยู่ใน Story นี้ได้อีกมากมายเหลือเกิน</p><p>แล้วมันก็จะกลายเป็น “เอาตามคนที่มีอิทธิพลคิด” หรือไม่ก็เป็น “เอาตามที่มันง่ายที่สุด”</p><p>อ้าว แล้วจะทำยังไง ถ้าจะทำระบบนึง เขียน Story เล็กก็ไม่ครอบคลุม เป็นระบบที่ไม่สมบูรณ์ ดูกากกรัง แต่พอเขียนแบบกว้างๆ ก็กลายเป็น Unbound ใส่อะไรก็ได้หมดจน Scope ใหญ่เกิน</p><p>ผมพบว่าเรื่องนึงที่เรามักจะลืมคิดกันคือ</p><p><strong><em>1 Feature-set สามารถ Solve ได้ หลาย Story พร้อมกันนะ</em></strong></p><p>ใช่ครับ ให้เราเพิ่ม Story ลงไปให้เยอะๆ แทน</p><p>กลับมาที่ Google</p><p>Google เป็นระบบค้นหาเว็บไซต์ที่มีแค่ Textbox 1 อันกับ Search result ตามมา</p><p>แต่ผมคิดว่า แค่ 2 หน้าจอนี้ มันตอบ Story น่าจะเกินล้าน User story ได้</p><p>ซึ่งก็ไม่ผิดอะไร</p><p>ไม่มีใครกำหนดนี่ว่า 1 User story ต้องทำ 1 Feature หรือ 1 หน้าจอมาตอบ</p><p>ผมว่าเป็นความเคยชินที่น่ากังวลมาก</p><p>ตอนนี้สิ่งนึงที่ผมกำลังพยายามวางไว้เป็น Policy ใน Taskworld คือ</p><ol><li>พยายามหา Solution ที่เล็กและง่ายที่สุดที่จะตอบ User story ทั้งหมดที่เขียนไว้ได้</li><li>ถ้ามี 2 Solution ที่สามารถตอบ User story ได้เหมือนกัน ให้เลือกอันที่สามารถสร้างได้ง่ายกว่าและเล็กกว่าเสมอ (Occam’s Razor)</li><li>เนื่องจากข้อแรก มันเอียงไปทางพยายามปั่น Solution ที่เล็กที่สุด หลายๆ ครั้งก็อาจจะรู้สึกว่า “เห้ย มันน้อยไป มันไม่ครบ มันดูกากเกิน” ถ้ารู้สึกว่า Solution ที่ออกแบบมาไม่ครบถ้วน ยังขาดบางสิ่งบางอย่าง แต่มันดันตอบ Story ได้ครบทุก Story แปลว่าเราอธิบาย Story ไม่หมด ให้เขียน Story เพิ่ม แล้วออกแบบ Solution ใหม่ให้ตอบทุก Story รวมถึงที่เขียนเพิ่มด้วย</li></ol><p>เมื่อทำแบบนี้ User story ก็จะมีความหมาย เพราะมันบอกได้ว่า เราทำแค่ไหน “พอ”</p><p>และถ้า Business ยังรู้สึกว่าเราทำน้อยไป Solution ที่ออกแบบมามันยังขาดมันดูไม่สมบูรณ์ ก็เขียน Story ใหม่มาเพิ่ม เพราะมันสะท้อนว่า Define ปัญหามาไม่ครบถ้วน</p><p>แล้วเราก็เพิ่ม หาจุด “พอ” จุดใหม่ ออกแบบเล็กที่สุดและง่ายที่สุดที่ครอบคลุม Story ใหม่ด้วย</p><p>ถ้ายังเห็นว่ามันเล็กไป ก็วาง Story เพิ่ม แล้วก็วนแบบนี้ไปจนกว่าจะเห็นว่า “โอเค ดูดีแล้ว”</p><p>แต่ถ้าเราเขียน Unbound user story มันจะไม่มีอะไรบอกว่า “แค่ไหนพอ”</p><p>ซึ่งมันทำให้เราจะจบที่ Application ที่เต็มไปด้วย Feature ที่ไม่มี Concrete story รองรับ เป็นแค่อะไรลอยๆ มาเฉยๆ แล้วแต่ไอเดียใคร ใคร Convince เก่งกว่า แต่ตอบไม่ได้ว่า Concrete Story เป็นใคร User ที่จะมาใช้ เขาตั้งใจจะทำอะไรกันแน่</p><p>ถ้าเป็นแบบนี้เป็นไปไม่ได้หรอกที่เราจะสร้าง Demo ทุก Sprint ได้ครับ เพราะ Solution จะบานแน่นอน</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1a980e953b1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/%E0%B9%83%E0%B8%8A%E0%B9%89-user-story-%E0%B8%AD%E0%B8%A2%E0%B9%88%E0%B8%B2%E0%B8%87%E0%B9%84%E0%B8%A3%E0%B9%83%E0%B8%AB%E0%B9%89%E0%B8%A1%E0%B8%B5%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%AB%E0%B8%A1%E0%B8%B2%E0%B8%A2-1a980e953b1">ใช้ User story อย่างไรให้มีความหมาย</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Meaningful Standups]]></title>
            <link>https://medium.com/taskworld-tech/meaningful-standups-19f100b5f440?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/19f100b5f440</guid>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sun, 18 Mar 2018 04:26:28 GMT</pubDate>
            <atom:updated>2018-03-18T04:35:02.145Z</atom:updated>
            <content:encoded><![CDATA[<p>Meaningful Daily Scrum (Standup meeting) happened when react and do something differently, according to the information in daily standups.</p><p>Otherwise, it just become status update. That would be tedious and I prefer just continue with my work.</p><p>This does not mean you should always change the plan, but you need to change the plan when new information arise and there are more optimal way to work.</p><p>If you do standups without changing any plan, let’s say for 1 month, that would mean your team plan is always perfect from the start.</p><p>That mean in your business, you do not need any agility. Planner always get it right from the start.</p><p>If that is the case, then so be it. You should considering stop doing standups and any Agile activity. It has no use.</p><p>Common problem is, it is hard to admit that initial plan is have probability of about 80% to be wrong.</p><p>It is hard to admit that, according to information today, we got it wrong in the start of the sprint.</p><p><strong>It is hard to just say “I got it wrong at the start, let’s change”, very frequent, almost twice a week at average.</strong></p><p>But that was initial assumption of the Agile. If we human are able got software plan right from the start, then why bother do Agile at all?</p><p>Agile culture need to allow everyone, including self, to be truthful and accept underlying assumption that human has limitation when it comes to planning ahead in software development. Planning incorrect and changing a plan in the middle should be accept as nature of software industry, without being interpreted as being incompetency.</p><p>This go both way, from the way management see software development team and expect to have everything planned and worked out, also the way software development team see business team and expected to have everything planned for multiple years.</p><p>Both need to be viewed differently. Both need to accept that there is no such things as perfect plan.</p><p>Otherwise, why bother being Agile. Waterfall and traditional way of management does have its place and can work pretty well too.</p><p>You cannot become Agile without accepting the most fundamental assumption of Agile Methodology, that’s no one, engineer, developers, management, etc, can have perfect plan.</p><p>Every plan will have a pretty high chance to goes wrong.</p><p><strong>Once you accept this assumption, You will be comfortable to say “Hey team, I think we got it wrong, today let’s change something to make it less wrong” every single day in every single standup meeting.</strong></p><p>And that is when daily standups have a meaning. It actually improve the way team work. Team will be very enthusiastic to have standup meeting.</p><p>Another common problem is, even if we know something fishy coming up, and plan is not going as expected, we do not know what to act differently.</p><p>That would come with experience, but at least, Standup meeting allow team to be aware first that something is not going as expected. Hence we can retro and make it better.</p><p>In traditional way of management, we neglect these problem as “accountability”, which basically mean, let the people who accountable for it deal with it.</p><p>That is still ok in Agile, to let the one who accountable handle the case.</p><p>What is not ok is, we should be transparently talk about this issue, instead of just buried those in the hand of the one who responsible for it quitely.</p><p>Talk about it in standups, retro, etc.</p><p>That is when standup have meaning, to surface a problem.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=19f100b5f440" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/meaningful-standups-19f100b5f440">Meaningful Standups</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Step back from requirement]]></title>
            <link>https://medium.com/taskworld-tech/step-back-from-requirement-b767dce1de3e?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/b767dce1de3e</guid>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sun, 07 Jan 2018 11:17:54 GMT</pubDate>
            <atom:updated>2018-01-07T11:26:49.924Z</atom:updated>
            <content:encoded><![CDATA[<p>สมมติเราทำระบบบัญชีระบบนึง มีประเภทของ Transaction 3 แบบ คือ X,Y,Z</p><p>ถ้า Requirement แรกบอกว่าทุกๆ “รายการ Transaction ทุกอันต้องหักเงินในบัญชีลูกค้า”</p><pre>account.debit(transaction.amount);</pre><p>แล้วพอทำไปซักพักเขาบอกว่า “รายการทุกรายการนั่นแหละ ยกเว้น Transaction ประเภท X ต้องหักเงินลูกค้า”</p><p>หลายๆ ครั้ง ฟังแล้วเราอาจจะ Implement เป็น</p><pre>if (transaction.type == TransactionType.X) return;<br>account.debit(transaction.amount);</pre><p>แล้วถ้าตรงข้าม Stakeholder บอกว่า “รายการประเภท Y,Z ให้หักบัญชีลูกค้านะ ส่วนรายการประเภท X ไม่ต้องหักบัญชี”</p><p>หลายๆ ครั้งเขาบอกแบบนี้ เราก็จะ Implement กันแบบนี้</p><pre>switch (transaction.type) {<br>   case TransactionType.X: break;<br>   case TransactionType.Y:<br>   case TransactionType.Z:<br>      account.debit(transaction.amount);<br>      break;<br>}</pre><p>สิ่งที่น่าคิดคือ Requirement 2 ตัวนี้ จริงๆ เหมือนกันทุกประการ ต่างกันแค่วิธีการบอกของลูกค้า</p><p>คำถามคือ</p><ul><li>แบบแรกกับแบบหลัง อันไหนอ่านง่ายกว่ากัน และ Maintain ง่ายกว่ากัน</li><li>ทำไมสิ่งที่เหมือนกันเราถึง Implement เป็น 2 แบบ</li><li>ความง่ายของการ Maintain software ของเราขึ้นอยู่กับลำดับและวิธีเล่าเรื่องของลูกค้า หรืออยู่ในกำมือเรากันแน่</li></ul><p>ทุกคนอาจจะตอบคำถามว่าแบบไหนดีกว่ากันไม่เหมือนกัน และผมจะไม่ขอกล่าวถึงว่าอันไหนดีกว่าอันไหนในบทความนี้</p><p>เพราะไม่ว่าคุณจะคิดว่าอันแรกดีกว่าหรืออันหลังดีกว่าก็ตามแต่</p><p>สิ่งที่ต้องระวังที่ผมอยากสื่อคือ</p><p>เราต้องมีสติรู้ว่า Implement แบบไหนทำให้ Maintain ง่าย</p><p>มิใช่ปล่อยให้ Implementation มันไหลไปตามการเล่าเรื่องของลูกค้า</p><p>ถอยหลังออกมาดูภาพรวมบ้างนะครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b767dce1de3e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/step-back-from-requirement-b767dce1de3e">Step back from requirement</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[On framework dependencies]]></title>
            <link>https://medium.com/taskworld-tech/on-framework-dependencies-865a51ffafb?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/865a51ffafb</guid>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sat, 23 Dec 2017 17:30:36 GMT</pubDate>
            <atom:updated>2017-12-23T17:30:36.003Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="http://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html">Clean Coder Blog</a></p><p>Based on article here, I just want to add that internal frameworks, rules and tech stack do play same effect as standard/framework, maybe even worse. This is one of the biggest trap.</p><p>Abstract away outside framework with internal framework only have benefit when in-house framework is <strong><em>easier</em></strong> to understand and be communicated than third-party framework.</p><p>This normally can be achieved by using domain-specific word and vocabulary among internal devs, and simplify functionality of third-party to just only minimal and specific usage.</p><p>จากบทความที่ลงตะกี้เกี่ยวกับที่ว่า Architecture ที่ดีจะต้องไม่ขึ้นอยู่กับ Interface ของ Framework, tech stack, 9ล9มีเรื่องนึงที่คิดได้และอยากเขียนถึง</p><p>สิ่งนึงที่ต้องระวังและตกหล่มได้ง่าย คือ Framework, Library, Tech stack ในที่นี้มันไม่ได้หมายถึงแค่ Framework ภายนอกเท่านั้น แต่มันรวมถึงสิ่งที่เราคิดกันเองภายในด้วยนะ</p><p>คือ ถ้าเราคิด Structure ของเราเอง in-house เพื่อไปครอบ Framework ข้างนอกเพื่อให้โค้ดเราไม่ขึ้นกับ Framework ภายนอก โอเค ก็ดี แต่มันไม่ได้แปลว่าโค้ดของเราไม่ขึ้นกับ Framework เป็นอิสระจาก Framework อย่าพึ่งเข้าใจไปทางนั้น</p><p><strong><em>มันแปลว่าโค้ดของเราขึ้นอยู่กับ Structure และ Framework ที่เราคิดกันเองใน in-house ต่างหาก!!</em></strong></p><p>ซึ่งมัน “<strong><em>มัก” </em></strong>จะมีประโยชน์ก็เพราะ Framework หรือ Structure ที่เราคิดกันภายใน มีโอกาสที่จะทำความเข้าใจง่ายกว่า มีโอกาสที่จะง่ายต่อการสือสารให้ทีมทำงานภายในเข้าใจกันได้มากกว่าของข้างนอก และการควบคุมการเปลี่ยนแปลงก็ทำได้ง่ายกว่า โอกาสพังเพราะอัพเดทมีน้อยกว่า</p><p>แต่มันไม่ใช่ Benefit เสมอไป ถ้าเราไม่ได้ใช้ Benefit ของมันเต็มที่</p><p>ยกตัวอย่างง่ายๆ สมมติเราบอกว่าเราไม่อยากขึ้นกับ Redux, React เราก็ต้องคิดหาอะไรซักอย่างมาครอบ ให้โค้ดของเราขึ้นอยู่กับสิ่งที่ครอบนั้น ซึ่งวิธีนี้ มันจะมีประโยชน์ต่อเมื่อสิ่งที่ครอบนั้น มันเข้าใจง่ายกว่าตัว Redux, React เอง หรือถ้าตัว React, Redux เองมี Breaking changes บ่อยๆ</p><p>แต่ถ้าเราไม่เข้าใจและออกแบบมันซะซับซ้อนกว่าตัว Redux, React เอง ก็กลายเป็นว่าให้โค้ดเรา Depends on React, Redux อาจจะยังดีซะกว่า</p><p>ถามว่าทำไงให้เข้าใจง่ายกว่าซับซ้อนน้อยกว่า ปกติก็มีสองสิ่งหลักๆ ที่มักจะปรับได้ (โดยส่วนใหญ่ ไม่ใช่ทุกครั้ง)</p><p>อย่างแรก เราใช้คำศัพท์ที่ Domain-specific เกี่ยวกับ Business problem ที่เรากำลังแก้โดยตรง หรือไม่ก็หันไปใช้ศัพท์ที่ใช้กันภายในบ่อยๆ จนคนทำงานในทีม อ่าน Requirement บ่อยๆ คุยกับเพื่อนบ่อยๆ เข้าใจได้ทันทีโดยแทบไม่ต้องศึกษา Document เพิ่มเติม แทนที่จะไปใช้ศัพท์ที่มัน Generic แบบ Standard framework พวก Controller, Component, Factory, etc.. ที่ต้องไปนั่งอ่าน Reference มากมายก่อนที่จะเริ่มใช้</p><p>อย่างที่สองคือเราตัด Functionality ที่ไม่จำเป็นที่เราไม่ใช้ทิ้งไปให้หมด ให้เหลือทางเลือกน้อยจนเพื่อนร่วมทีมไม่สับสน และตัด Use-case ให้ตรงกับสิ่งที่เราใช้งาน</p><p>ข้อแรกทำให้ผมมักจะบ่นบ่อยๆ ว่า Code ที่ Architecture ดี จะมีศัพท์เทคนิคน้อย</p><p>แน่นอนในโค้ดที่ใหญ่และซับซ้อนมากๆ เราหลีกเลี่ยงการสร้าง Concept เฉพาะบางอย่างไม่ได้ และหลีกเลี่ยงการใช้ศัพท์เฉพาะทางไม่ได้หรอก แต่ก็ควรจะพยายาม Minimize ให้มากที่สุด</p><p>ประเด็นหลักที่ผมอยากจะชี้คือ ถ้าเราจะ Abstract 3rd-party dependency ออกไปโดยการสร้างอะไรครอบ มันจะเวิร์คและมีประโยชน์ก็ต่อเมื่อของที่ครอบมันดีกว่าในบริบทของโจทย์ที่เรากำลังแก้ เราแลกความ Generic ของมันไปแล้วทำตัวครอบให้มัน Specific มากขึ้น เพื่อให้เข้าใจง่ายขึ้น ใช้ง่ายขึ้น</p><p>(ถ้ามันดีกว่า In general ก็ Open-source ซะแล้วเตรียมดังได้เลย)</p><p>นั่นคือสิ่งนึงที่น่าพิจารณาเป็นอย่างมาก</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=865a51ffafb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/on-framework-dependencies-865a51ffafb">On framework dependencies</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Avoid reusing data structure]]></title>
            <link>https://medium.com/taskworld-tech/avoid-reusing-data-structure-7deac286b3dd?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/7deac286b3dd</guid>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Mon, 11 Dec 2017 08:52:01 GMT</pubDate>
            <atom:updated>2017-12-11T09:22:43.183Z</atom:updated>
            <content:encoded><![CDATA[<p>ย้อนกลับมาคิด คือที่ผมมักชอบบอกว่าคิดดีๆ ก่อน Abstract โค้ด เพราะที่ผ่านมาผมทำร้ายตัวเองด้วยการ Reuse ของที่ไม่ควร Reuse บ่อยกว่าทำร้ายตัวเองด้วยการเขียนโค้ดซ้ำไปทั่ว</p><p>แล้วจังหวะที่เจ็บเพราะโค้ดซ้ำ มันเจ็บไม่เท่า Abstract โค้ดผิดแล้วต้องรื้อทิ้ง อันนี้โหดร้ายมากๆ</p><p>แต่สิ่งนึงที่เรียนหนักมากคือ Data structure เนี่ย อย่าพยายาม Reuse มากเกินไป เรามักจะมีหล่มนี้</p><p>เช่น ถ้าฟอร์มนึงต้องการแค่ Firstname, Lastname และ Avatar เพื่อเขียนข้อมูล User เล็กๆ ว่าเขาเป็นใคร สิ่งที่ดีที่สุดคือการส่งไปแค่นั้นจริงๆ ไม่ใช่ส่ง User Object ไปทั้งก้อน</p><p>เพราะอะไรถึงควรส่ง Data structure ที่ minimal แบบนั้นเสมอ คือถ้าเราส่ง User Object ทั้งก้อน ส่วนมากแล้วพอฟอร์มตรงนั้นต้องการอะไรเพิ่มเติม (เช่น Link website) Intuition แรกของเราก็คือ “ก็เพิ่มลงใน User object สิ”</p><p>ซึ่งลองนึกดูว่าถ้าเราส่ง User object ไปหลายๆ ที่ทั่วทั้งแอพ แล้วแต่ละส่วนของ UI เติบโตต่างกันมี Requirement changes แตกต่างกัน แปลว่าการเติบโตทั้งหมดที่ไปคนละทิศคนละทางจะมา Reflect ลง User object หมดเลย แล้วถ้าเป็นแบบนี้ สุดท้าย Model เราจะบวมมากๆ กลายเป็น Super class</p><p>แต่ถ้าเราส่ง Minimal data structure เราจะเริ่มคิดละว่า เห้ย เราต้องเติมลงตรง Data structure เล็กๆ ตัวนั้นตัวเดียวเองนี่ แล้วมันอาจจะ Map มาจาก Model หลายๆ ตัวได้ รวมถึงคิดว่าจำเป็นต้องสร้าง Model ใหม่มั้ย เราจะพิจารณาความเป็นไปได้ในการเติมของในที่อื่นๆ อย่างเป็นกลางมากขึ้น</p><p>ทำได้สองอย่างคือ</p><p>1. ส่งให้เล็ก แต่เขียนโค้ดเยอะขึ้นมากๆ ยิ่งถ้า Static type language คือต้องประกาศ Class/Struct เยอะแยะไปหมดเลยเพื่อให้มี Data structure ที่พอดีใช้ ก็มีความน่ารำคาญนะ ผมก็เป็น</p><p>2. ส่งใหญ่ก็ได้ แต่ต้องเท่าทัน Intuition ของตัวเองเวลาคิดว่าต้องเติมของลง User object ต้องแบบ จริงเหรอวะใช่เหรอวะบ่อยๆ ถามตัวเองเยอะๆ เท่าทันว่าเราสร้าง Bias ให้ตัวเองตั้งแต่แรก</p><p>การ Reuse data structure ผิดที่ผิดทางแล้วสร้าง Data structure ตัวนึงที่ถูกใช้หลายที่เกินไป จนมันเริ่มเติบโตพร้อม Requirement changes ทุกทิศทาง นี่คือแบบ สุดยอดของความยากที่จะแก้ไขมากๆ เจ็บหนักมาก</p><p>และเป็นสิ่งที่ต้องระวังเพราะเป็นหล่มที่ตกลงไปได้ง่ายเหลือเกิน</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7deac286b3dd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/avoid-reusing-data-structure-7deac286b3dd">Avoid reusing data structure</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code that tell a story]]></title>
            <link>https://medium.com/taskworld-tech/code-that-telling-story-e81fe6561a47?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/e81fe6561a47</guid>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Wed, 15 Nov 2017 11:00:54 GMT</pubDate>
            <atom:updated>2017-11-15T11:02:47.546Z</atom:updated>
            <content:encoded><![CDATA[<p>วันนี้ผมได้รีวิวโค้ดของน้องคนนึง ซึ่งเขาต้องการแก้โค้ดประมาณนี้</p><pre>function doSomethingA() {<br>  // ............ Some statements</pre><pre>  step2()</pre><pre>  // ............ Another many statements<br>}</pre><p>เขาต้องแก้สร้าง doSomethingB ซึ่งยังทำทุกอย่างเหมือนเดิม เปลี่ยนแค่ step2</p><p>Intuition แรกที่คิดขึ้นมาคือ ในเมื่อ มันต่างกันแค่ Step 2 เราก็ Inject step 2 สิ</p><pre>function createDoSomething({ step2Injected }) {<br>  // ............ Some statements</pre><pre>  step2Injected()</pre><pre>  // ............ Another many statements<br>}</pre><pre>function doSomethingA() {<br>   const doSomething = createDoSomething({ step2Injected = step2 })<br>   return doSomething()<br>}</pre><pre>function doSomethingB() {<br>   const doSomething = createDoSomething({ <br>     step2Injected = step2Special <br>   })<br>   return doSomething()<br>}</pre><p>ซึ่งมันก็ตรงไปมาแหละ</p><p>พอผมอ่าน ผมบอกว่า อ่านยากนะ คือในทางทฤษฎีมันก็ถูกว่าทุกอย่างเหมือนกัน ต่างกันแค่นี้ เราก็ If แล้ว Inject มาสิ</p><p>แต่ มันเสีย “ความต่อเนื่องของเรื่องราว” ไป</p><p>ผมแนะนำให้</p><ol><li>ตั้งชื่อตั้งความหมายให้สิ่งที่ทำก่อน Step2</li><li>ตั้งชื่อตั้งความหมายให้สิ่งที่ทำหลัง Step2</li><li>เล่าเรื่องราวของมันเป็นเรื่องสั้นๆ 3 บรรทัด</li></ol><p>ผลออกมาแบบนี้</p><pre>function step1 () { <br>   ... // Refactor from original doSomethingA<br>}</pre><pre>function step3 () {<br>   ... // Refactor from original doSomethingA<br>}</pre><pre>function doSomethingA () {<br>  step1()<br>  step2()<br>  step3()<br>}</pre><pre>function doSomethingB () {<br>  step1()<br>  step2Special()<br>  step3()<br>}</pre><p>พอเรา Refactor จน doSomethingA, doSomethingB มันเหลือแค่ 3 บรรทัดแล้วแบบนี้ เราเห็นชัดเจนว่ามันต่างกันที่นึง ในระยะ 6 บรรทัด จากเดิมที่เราต้อง Navigate ไประหว่างชุดโค้ดยาวๆ (ในชุดโค้ดแรก createDoSomething มันมีความยาวประมาณ​ 40 บรรทัด)</p><p>ถึงแม้ว่าถ้าดู Requirement และสิ่งที่จะทำคือ “ทำทุกอย่างเหมือนเดิมยกเว้นขั้นตอนที่ 2” การแก้ไขที่ตรงไปตรงมาที่สุดคือ Inject ขั้นที่ 2 เข้าไปผ่าน Dependency injection ก็ตาม</p><p>ผมมองว่าแบบหลังอ่านง่ายกว่าและแก้ง่ายกว่า</p><h4>Moral of story</h4><ol><li>ระวังเรื่องการยึดติดกับ Status quo คือถ้าเรามองว่าเราอยากทำ Requirement ใหม่โดยไม่ Refactor ของเดิม หรือ Refactor ให้น้อยที่สุด ไม่อยากไปทำลายของเก่า เราจะมองไม่เห็นว่ามันสามารถ Breakdown ได้</li><li>โค้ดที่เล่าเรื่องราวได้ใน 3–4 บรรทัด ถึงแม้จะไม่ Systemmatically Precise ว่า “ต่างกันแค่นี้” แต่ก็จะสบายตาและเข้าใจง่ายกว่า ส่งผลให้แก้ไขง่ายกว่า มีโปรแกรมเมอร์อยากแก้เยอะกว่า (ซึ่งทั้งหมดที่เราคุยเรื่อง Software architecturing คือเราต้องการให้โค้ดแก้ง่าย)</li><li>เมื่อใช้ท่ายากทุกครั้งต้องมองเห็นว่ามันสร้าง Learning curve ดังนั้นมอง Trade-off ดีๆ ถ้ามีสองสิ่งที่มีคุณค่าพอๆ กัน ผมมักจะสนับสนุนให้ Prefer code ที่อ่านได้ง่ายและทำความเข้าใจได้ตั้งแต่ Beginner มากกว่าโค้ดที่ยาก</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e81fe6561a47" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/code-that-telling-story-e81fe6561a47">Code that tell a story</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why should we separate business logic out of view? in Layman term]]></title>
            <link>https://medium.com/taskworld-tech/why-should-we-separate-business-logic-out-of-view-in-layman-term-b2d6b60fce2b?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/b2d6b60fce2b</guid>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Mon, 23 Oct 2017 08:46:30 GMT</pubDate>
            <atom:updated>2017-10-23T08:48:18.987Z</atom:updated>
            <content:encoded><![CDATA[<p>ทำไมเราถึงควรแยก Business logic ออกจาก View ตั้งแต่แรกเลย? นี่เป็นเรื่องนึงที่ผมไม่กลัว Premature refactor</p><p>สมมติว่าเราได้รับ Requirement มาว่า</p><p>“ตาราง User จะแสดงเฉพาะ User ที่ Active เท่านั้น”</p><p>ถ้าเราใช้ React เราอาจจะเขียนแบบนี้</p><pre>render () {<br>   const activeUsers = this.props.user.filter(c =&gt; c.active)<br>   return activeUsers.map(user =&gt; (<br>     &lt;UserRow user={user} /&gt;<br>   ))<br>}</pre><p>ซึ่งจริงๆ มันก็ง่ายดีนะและมันแค่บรรทัดเดียวด้วย</p><p>แต่ผมจะบอกว่าไม่ควรทำแบบนี้ในการทำงานเป็นทีม</p><p>ผมแนะนำให้เราสร้าง File ชื่อ User.js ที่รวม Business logic ของ User โดยเฉพาะไว้เลยไฟล์นึง แบบนี้</p><pre>export const isUserActive = (user) =&gt; user.active</pre><p>เราก็จะได้ Render หน้าตาแบบนี้</p><pre>import { isUserActive } from &#39;./user&#39;<br>render () {<br>   const activeUsers = this.props.user.filter(isUserActive)<br>   return activeUsers.map(user =&gt; (<br>     &lt;UserRow user={user} /&gt;<br>   ))<br>}</pre><p>เพราะอะไร ทำไมแค่ของสั้นๆ บรรทัดเดียวผมถึงแนะนำให้ต้องทำเรื่องยุ่งยากอย่างสร้างไฟล์ใหม่ที่มีบรรทัดเดียว import เข้ามา แล้วก็ใช้ด้วย</p><p>เพราะในการทำงานเป็นทีม Application ของเราจะใหญ่มาก แล้วเพื่อนๆ เราก็มีสิทธิ์ที่ได้รับ Requirement ส่วนอื่นๆ ที่ต้องตอบคำถามว่า “เอาเฉพาะคนที่ Active เท่านั้น” เช่นกัน</p><p>สมมติเพื่อนอีกคน ทำอีกส่วนของ Application บอกว่าให้แสดง Avatar ของ User ที่ Active เขาก็จะเขียนแบบนี้</p><pre>render () {<br>   const activeUsers = this.props.user.filter(c =&gt; c.active)<br>   return activeUsers.map(user =&gt; (<br>     &lt;UserAvatar user={user} /&gt;<br>   ))<br>}</pre><p>ลองนึกถึงว่าถ้ามีแบบนี้ซ้อนๆ กันหลายๆ ที่ มีเพื่อนหลายๆ คนต้องทำ Feature ที่ต้องตอบคำถามว่า “ใคร Active อยู่” เราก็จะมี user.filter เต็มไปหมด</p><p>เพื่อนๆ ทุกคนในทีมก็ เขียนแบบนี้แยกกัน โดยที่ไม่รู้ว่าอีกคนเขียนอยู่</p><p>แล้วถ้าวันนึง นิยามของ “ใคร Active อยู่” เปลี่ยนไปล่ะ เช่น เรามีวันหมดอายุแล้ว ใครที่หมดอายุแล้วก็ถือว่า ไม่ Active เช่นกัน</p><p>ถ้าเราทำแบบแรก รวมไว้ที่เดียว เราก็แค่แก้</p><pre>export const isUserActive = (user, now = new Date()) =&gt; user.active &amp;&amp; user.expiry.getTime() &gt; now.getTime()</pre><p>แต่พอเราทำแบบหลัง เราต้องตามแก้ไขกันจนตามไม่หมดทีเดียว</p><p>เรื่องนี้เหมือนเป็นเรื่องเบสิค แต่ผมคิดว่าสำคัญมาก</p><p>ความรู้และคำถามที่เกิดขึ้นใน Application เรา ไม่ว่าจะตอบที่ส่วนไหน ก็ต้องตอบคำถามเหมือนๆ กัน ในกรณีนี้คือ ความรู้ที่ว่า “User คนนี้ Active อยู่มั้ย” ต้องตอบได้เท่ากันหมดทั้ง Application</p><p>การที่จะบรรลุข้อ 1 ได้ เราก็ควรจะรวม “ความรู้และคำถาม” ทั้งหมดที่เกิดขึ้นได้ใน Application ไว้ที่ไหนซักที่นึง หรือตามหลัก DRY ก็จะบอกว่า</p><blockquote>“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”</blockquote><p>อันนี้คือจุดตายจุดแรกที่มักเจอกัน คือ เรามองไม่ออกว่า “User active อยู่มั้ย” เป็นองก์ความรู้ของ Application ที่ควรจะรวมไว้ เพราะมันเปลี่ยนได้ หรือภาษาทางเทคนิคหน่อยจะเรียกว่า Business logic (จริงๆ มันก็คือ If โง่ๆ อันนึงแหละ)</p><p>แต่ด้วยความเป็นมนุษย์ขี้เหม็น ต่อให้เราบอกว่า Centralized แต่ถ้าจุดที่หามันหาเจอยาก หาไม่เจอ ไม่รู้อยู่ตรงไหน มันก็จะเหนี่ยวนำให้เพื่อนร่วมทีมเขียนใหม่ เพราะ .filter แบบนี้ เขียนใหม่ใช้ 5 วิก็เขียนเสร็จ</p><p>ถ้าเวลาที่ต้องใช้ ในการค้นหาให้เจอ ใช้เวลาถึงขั้น 10–20 นาที ต้องเดินไปถามคนนั้นคนนี้ ในขณะที่เขียนใหม่ใช้แค่ 5 วินาที</p><p>ก็มีความเป็นไปได้สูงมากที่ Programmer จะเขียนใหม่ และ Duplicate code</p><p>นี่คือจุดตายจุดที่สอง ที่เราโปรแกรมเมอร์มือฉมังหลายคนจะมองว่าปัญหานี้เกิดจากความ “ไม่ใส่ใจคุณภาพ” แต่ลืมมองไปว่าบางทีเขาอาจจะไม่รู้ว่าต้องหาที่ไหน หาเจอช้า อาจจะมีปัญหาเชิงโครงสร้างและการสื่อสาร</p><p>เพราะโปรแกรมเมอร์มือฉมังจะจำโครงสร้าง Application ได้ แล้วหลายๆ ครั้งเขาจะลืมมองเห็นจากมุมคนอื่นว่า บางคนยังจำไม่ได้ บางคนนึกไม่ออก</p><p>ดังนั้นวิธีที่เราใช้กันที่ Taskworld คือ เรา Group business logic เข้าด้วยกันตามชื่อ Business entity แล้วก็บอกว่า เราตั้งชื่อพวกนี้ใน Format xxx.js เช่น user.js ไปเลย</p><p>ส่วนไฟล์พวก React component หรือไฟล์อื่นๆ เราก็ตั้งชื่อให้มันแตกต่างกันอย่างเห็นได้ชัด เช่น มีลงท้ายด้วย .react มี suffix ว่า Reducer</p><p>เมื่อเราตั้งแบบนี้การ Navigate เจอง่าย แนวโน้ม Probability ที่คนจะเขียนใหม่ก็มีน้อยกว่าการที่คนจะ Reuse ของเก่า</p><p>แต่หลายๆ ครั้ง เราก็มีปัญหาว่าการเปลี่ยนแปลง Structure บางอย่างสื่อสารไม่ทั่วทั้งทีมก็เกิดขึ้นได้เป็นระยะๆ ผมเชื่อว่าทุกที่ก็มีอะไรแบบนี้อยู่บ้าง</p><p>ก็เป็นสิ่งที่เรากำลังพัฒนาให้ดียิ่งขึ้นต่อไป</p><p>ในเรื่องนี้ ผมมองว่ากระบวนท่าไม่สำคัญ คุณจะแบ่ง Folder structure หรือชื่อไฟล์ยังไงผมว่ามันก็มีหลายท่ามากๆ จะใช้ Architecture แบบไหนเลียนแบบ Facebook, Google, Netflix หรือใช้ Standard framework จะเป็น MVC, VIPER, Idiomatic React, Angular หรืออะไรก็ตาม ก็ไม่ได้เป็นประเด็นที่สำคัญที่แท้จริง</p><p>จุดสำคัญในการทำงานเป็นทีมในเรื่องนี้คือ</p><p><strong>รวม Business logic เข้าไว้ในที่ๆ นึงที่ Intuitive พอที่คนทั่วไปในทีม จะติดนิสัยว่า “เห้ย หาตรงนี้ก่อนนะ ไม่เจอค่อยเขียนใหม่” ก่อน Implement if ซักตัว</strong></p><p>ถ้าคุณทำให้เป็นแบบนี้ได้เมื่อไหร่ นั่นแหละคือปลายทาง ไม่ว่าจะเกิดด้วย Framework หรือ Standard อะไรก็ตามที</p><p>ผมชี้ประเด็นที่มักจะเป็นจุดตายในการ “แยก Business logic ให้เป็นระเบียบ”</p><ol><li>ไม่ยอมรวมเพราะมองไม่ออกว่านี่เป็น “องก์ความรู้” ประจำระบบ</li><li>รวมในที่ๆ หายากสำหรับคนทั่วไป มีการแบ่งโครงสร้างที่ซับซ้อน มีคนเข้าใจวิธีการค้นหา Business logic ที่ต้องการเพียงไม่กี่คน</li><li>รวมเสร็จแล้วไม่สามารถสื่อสารให้ทุกคนรู้อย่างทั่วถึงได้ว่าต้องหาตรงไหนก่อน Implement ของใหม่ สื่อสารไม่ทั่วถึง</li></ol><h4>Framework Dependant Architecture</h4><p>เรื่องนึงที่ผมมีความ Skeptic ต่อแนวโน้มทั่วไปของ Developer คือการที่หลายๆ คนเวลาพูดถึง Software architecture ให้ความสำคัญกับการใช้ Framework หรือ Practices ที่เขาเล่าลือกัน แล้วมีน้อยคนจริงๆ ที่เริ่มพูดคุย (อย่างน้อยก็กับผม) ใน Term ของผลลัพธ์ว่า ใช้แล้วกระทบกับทีมอย่างไร ทำไมทีมถึงทำงานดีขึ้น ทำไม Workflow ของ Developer ถึงสบายขึ้น ทำไมโค้ดแก้ไขง่ายขึ้น ต่างจากเดิมยังไง</p><p>จริงๆ การใช้ Framework ที่เป็น Standard มันก็แค่ช่วยลดปัญหาข้อ 2, 3 เท่านั้นเอง เพราะเราสามารถบอกได้ว่า “ลองอ่านคู่มือแถวนี้นะ” แทนที่จะต้องอธิบายเองทุกรอบ ก็ช่วยให้บรรลุเรื่องการสื่อสารได้ง่ายขึ้น</p><p>แล้วคนทำ Framework เขาก็มี Pain ที่เขาเจอมาเยอะ แน่นอนโอกาสที่เขาคิดรอบด้านกว่าเราก็มีสูงเพราะเขาทุ่มเวลาสร้างมันขึ้นมาโดยเฉพาะเลยนี่</p><p>แต่ทว่า ปัญหาฐานจริงๆ มันคือเรื่องของ “ไม่มีความรู้” ครึ่งนึง และ “สื่อสาร” ครึ่งนึง</p><p>ต้องมองตรงนี้ให้ออกครับ ผมพบว่า แทบทุกครั้งที่นำ Framework มาใช้แล้วพังก็เพราะสาเหตุพวกนี้ ตรงข้าม ถ้ามีความรู้และสื่อสารดีมากๆ บางทีคิด Framework กันเอง ใช้กันเองภายใน ยังเวิร์คได้เลยครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b2d6b60fce2b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/why-should-we-separate-business-logic-out-of-view-in-layman-term-b2d6b60fce2b">Why should we separate business logic out of view? in Layman term</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MVP & Clear assumption]]></title>
            <link>https://medium.com/taskworld-tech/mvp-clear-assumption-727bf112dce5?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/727bf112dce5</guid>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sun, 08 Oct 2017 03:49:09 GMT</pubDate>
            <atom:updated>2017-10-08T06:01:05.958Z</atom:updated>
            <content:encoded><![CDATA[<p>ผมพบว่าในการทดลอง MVP หรือการทดสอบ Feature อะไรซักอย่างในตลาด สิ่งที่ผมพบว่ายากมากคือการอธิบายให้คนเข้าใจว่า เราควรตั้ง Assumption ที่เคลียร์</p><p>สมมติวันนี้เราบอกว่าเราออกฟีเจอร์ตัวนี้ออกไปเพื่อทดสอบตลาด เราควรมี Assumption ที่เคลียร์มากๆ เช่น</p><ul><li>ฟีเจอร์นี้ถือว่าสำเร็จก็ต่อเมื่อ คนที่สมัครกับเรา 10 License ขึ้นไป ใช้ฟีเจอร์นี้อย่างน้อยสัปดาห์ละ 2 ครั้ง</li><li>ฟีเจอร์นี้จะสำเร็จก็ต่อเมื่อ เราได้รับความพึงพอใจใน NPS Survey เกิน x คะแนน</li></ul><p>Assumption ควรจะตั้งไว้ให้ชัดเจนและตีผลอะไรไม่ได้นอกจาก “ผ่าน” หรือ “ไม่ผ่าน” เท่านั้น อย่างเช่นสองประโยคด้านบน</p><p>แต่ตรงข้าม ไม่ว่าผลที่ออกมาจะผ่านหรือไม่ผ่านก็ตาม การตีความนั้นตีความได้หลายแบบเสมอ</p><p>ที่ฟีเจอร์ไม่สำเร็จ เราอาจจะทำผิดคิดผิดแต่แรกเลยก็ได้ ไม่โดนใจเลยก็เป็นได้</p><p>ที่ฟีเจอร์ไม่สำเร็จ จริงๆ เราอาจจะทำโดนใจแล้ว แต่เราดีไซน์สีไม่สวยหรือเปล่า</p><p>ที่ฟีเจอร์ไม่สำเร็จ จริงๆ เราอาจจะทำโดนใจแล้ว แต่กระบวนการเก็บ NPS Survey มีปัญหาหรือเปล่า คะแนนเลยน้อย</p><p>พวกนี้มันตีความได้หลายอย่างมาก</p><p>ส่วนว่า Test result ผ่านมั้ย ไม่ผ่านก็คือไม่ผ่าน ไม่มีเร้าหรือ มีแค่ได้หรือไม่ได้เท่านั้น</p><p>ส่วนเหตุผลอะไรถึงไม่ผ่าน ยัง Open for interpretation เสมอ</p><p>ข้อสำคัญที่สุดเลยคือเราต้องไม่กลัวที่จะบอกว่า “ไม่ผ่าน”</p><p>ถ้าเรากลัวที่จะทดสอบไม่ผ่านมากๆ เราจะตั้งสมมติฐานที่ไม่เคลียร์</p><p>เช่น</p><p>“User กลุ่มนี้จะต้องชอบฟีเจอร์นี้”</p><p>แล้วเมื่อไหร่ถึงจะถือว่าชอบ เมื่อไหร่ถึงจะถือว่าไม่ชอบ</p><p>ถ้าคนชอบ 50.01% ถือว่าผ่านมั้ยนะ?</p><p>แต่ตรงข้าม เพื่อให้เกิดกระบวนการการทำงานที่ดี เพื่อให้ทุกคนกล้าที่จะตั้งสมมติฐานที่เคลียร์ เราก็ต้องสร้าง Environment ที่พร้อมจะรับผลอย่างกล้าหาญด้วย</p><p>กระบวนการ MVP, Assumption testing, Lean จึงเกิดไม่ได้ในองค์กรที่ไม่ยอมให้ใครล้มเหลว ไม่ยอมให้มีคำว่า “ไม่ผ่าน” เกิดขึ้น</p><p>สิ่งที่ยากที่สุดสำหรับผมในกระประยุกต์กระบวนการพวกนี้ คือการอธิบายให้เข้าใจว่า</p><p>“ตั้งสมมติฐานให้มันเคลียร์และชัดเจนไปเหอะ ถ้าสุดท้ายไม่ผ่านขึ้นมา ก็แค่ทำให้ดีขึ้นรอบหน้าเอง แค่นั้น มันก็แค่สมมติฐานนะเว้ย ไม่ใช่ KPI อะไร”</p><p>แล้วถ้าคนทำงานเขาไม่เชื่อว่า ไม่ผ่านได้ ไม่เป็นไร ค่อยว่ากันรอบหน้า</p><p>มันก็มักจะจบที่ว่า คนทำงานจะตั้งสมมติฐานให้ไม่เคลียร์ เผื่อไม่ผ่านขึ้นมา จะได้มีทางลง</p><p>ซึ่งวิธีแบบนั้นมันไม่นำพา</p><p>ทางลงมันมีตอนตีผลข้อมูลตอนตีความ เวลาผลสุดท้ายสิ่งที่เราทำมันไม่ผ่าน Assumption ที่ตั้งไว้ มันไม่จำเป็นต้องตีความว่าที่ไม่ผ่านเพราะผลงานมันห่วยไม่ได้เรื่อง คนทำงานมันห่วยไม่ได้เรื่องเสมอไปนี่ครับซะที่ไหน</p><p>แต่ผลการทดสอบ ต้องชัดเจนที่สุดครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=727bf112dce5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/mvp-clear-assumption-727bf112dce5">MVP &amp; Clear assumption</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refactor for readability, not for keystrokes]]></title>
            <link>https://medium.com/taskworld-tech/refactor-for-readability-not-for-keystrokes-614ea026e91d?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/614ea026e91d</guid>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sat, 26 Aug 2017 14:20:40 GMT</pubDate>
            <atom:updated>2017-08-26T14:23:48.855Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>“Optimize code for reading, not for typing.”</blockquote><p>I have face a lot of programmer, who when refactoring, tends to optimize for number of typing strokes, at the cost of understandability</p><p>When we talk about reduce code duplication, we not try to optimized key stroke. The main concept is to centralized the domain knowledge and make changes easier.</p><p>I will give you an example where refactor based on optimizing keystroke can be harmful.</p><pre>Class MyComponent extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><p>We could refactor to:</p><pre>function createReactComponentRender (renderFunc) {<br>  return Class ThisComponent extends React.Component {<br>     render = renderFunc<br>  }<br>}</pre><p>and we can reduce both number of line and number of characters tremendously.</p><p><strong>Fully-React version for 3 components:</strong></p><pre>Class Component1 extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><pre>Class Component2 extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><pre>Class Component3 extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><p><strong>Refactored-React version for 3 components:</strong></p><pre>const Component1 = createReactComponentRender(() =&gt; { ... } )<br>const Component2 = createReactComponentRender(() =&gt; { ... } )<br>const Component3 = createReactComponentRender(() =&gt; { ... } )</pre><p>Wow!!! new version look lot shorter. It should be says cleaner and more precise right?</p><p>I don’t agree at all.</p><p>I would considered Fully-React version to be better code than new version.</p><p><strong><em>Readability:</em></strong></p><p>In term of readability, I would say any React developer can understand code in Fully-React, just by looking at the code. It simple, it is the one that you exactly see on tutorial.</p><p>While in Refactor-React version, one would have to look at definition of createReactComponentRender first to be sure what does it do.</p><p><strong><em>Maintainability:</em></strong></p><p>In term of maintainability, which I perceived as “how easy to make changes in code”</p><p>In this case, most obvious possible changes is to add some lifecycle method to some of components.</p><p>So what changes needs to be made in this case?</p><p><strong>Fully-React</strong></p><pre>Class Component1 extends React.Component<br><strong>  shouldComponentUpdate () {</strong></pre><pre><strong>  }</strong></pre><pre>  render () {<br>    //.... <br>  }<br>}</pre><pre>Class Component2 extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><pre>Class Component3 extends React.Component {<br>  render () {<br>    //.... <br>  }<br>}</pre><p><strong>Refactored-React</strong></p><p>Option 1:</p><pre><strong>Class Component1 extends React.Component<br>  shouldComponentUpdate () {</strong></pre><pre><strong>  }</strong></pre><pre><strong>  render () {<br>    //.... <br>  }<br>}</strong><br>const Component2 = createReactComponentRender(() =&gt; { ... } )<br>const Component3 = createReactComponentRender(() =&gt; { ... } )</pre><p>In this option, the code that needs to be changes become 6 lines, which I would say this become little bit harder to changes than Fully-React version.</p><p>Option 2:</p><pre>function <strong>createReactComponentRenderAndShouldUpdate</strong> (renderFunc, <strong>shouldComponentUpdateFunc = null</strong>) {<br>  return Class ThisComponent extends React.Component {<br>     <strong>shouldComponentUpdate = shouldComponentUpdateFunc </strong><br>     render = renderFunc<br>  }<br>}</pre><pre>const Component1 = createReactComponentRender(() =&gt; { ... }, <strong>() =&gt; { ... }</strong> )<br>const Component2 = createReactComponentRender(() =&gt; { ... } )<br>const Component3 = createReactComponentRender(() =&gt; { ... } )</pre><p>This option need exactly 3 lines changes, which is same number of line as Fully-React version.</p><p>But I would say now we bloat createReactComponentRenderAndShouldUpdate , and we reduce readability by great magnitude.</p><p>This is why I think that Fully-React version is the best one, even if it takes more lines and keystrokes to write than Refactor-React one</p><p>(Actually, we can just snippet)</p><h4>Moral</h4><ol><li>Refactor code for shortest one, for less typing keystrokes one, can hurt maintainability and readability of the code</li><li>Sometimes, duplications of big chunk of code is better handle by snippet rather than refactor to function or even meta-programming.</li><li>When Refactor, try to be less biased toward keystrokes. I know that when we as a programmer, have to type same long boilerplate all the time. we tends to think of refactor and reduce the typing workload. No, we do not refactor for that. We refactor code for readability and maintainability over ease of typing. (Hint: Typing workload can be reduce greatly by just a simple snippet and scaffolding)</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=614ea026e91d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/refactor-for-readability-not-for-keystrokes-614ea026e91d">Refactor for readability, not for keystrokes</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Basic code smell#1: Method ที่ขึ้นกับ Mutation]]></title>
            <link>https://medium.com/taskworld-tech/basic-code-smell-1-method-%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%82%E0%B8%B6%E0%B9%89%E0%B8%99%E0%B8%81%E0%B8%B1%E0%B8%9A-mutation-1450ec9d4805?source=rss----773aef92a29e---4</link>
            <guid isPermaLink="false">https://medium.com/p/1450ec9d4805</guid>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chris]]></dc:creator>
            <pubDate>Sun, 13 Aug 2017 05:52:50 GMT</pubDate>
            <atom:updated>2017-08-13T05:56:03.551Z</atom:updated>
            <content:encoded><![CDATA[<p>บ่อยครั้งมากๆ ที่ผมจะเจอปัญหาแบบนี้ ทั้งในโค้ดตัวเองและโค้ดของคนอื่น แล้วผมพบว่ากว่าผมจะเห็นว่ามันเป็น Code smell ก็ต้องสั่งสมประสบการณ์มาเยอะเหมือนกัน</p><p>สมมติว่าเราต้องการส่ง email ปกติเรามักจะเห็นโค้ดแบบนี้</p><pre>var emailSender = new EmailSender()<br>emailSender.to = &quot;ace@taskworld.com&quot;<br>emailSender.body = &quot;Hello ace&quot;<br>emailSender.send()</pre><p>โค้ดลักษณะนี้เห็นได้ทั่วไปมากๆ</p><p>คำถามคือแล้วถ้าเกิดว่าอยู่ดีๆ มีคนอุตริส่ง email.send() โดยที่ลืมใส่ email.to ล่ะ</p><p>ผมพบว่าปกติระบบก็จะฟ้อง Exception ว่าอย่าลืมใส่ว่าส่งอีเมล์ให้ใคร</p><p>ก็ฟังดูปกติเนอะ เราก็เคยชินกับระบบแบบนี้คลาสแบบนี้กัน ก็ยังดีกว่าอยู่ดีๆ เงียบไปเลยแล้วส่งเมล์ไม่ได้ หรือทำอะไรแปลกๆ อ่ะนะ</p><p>แต่ผมคิดว่ามันดีไม่พอ</p><p>สิ่งที่ดีกว่านั้นคือออกแบบให้โปรแกรมเมอร์ไม่สามารถเขียนโค้ดที่เกิด Runtime exception ได้ตั้งแต่แรกเลย!!!</p><blockquote><em>“Making illegal states unrepresentable”</em></blockquote><p>หา เราทำถึงขนาดนั้นได้ด้วยเหรอ?</p><p>ผมตอบว่าในกรณีนี้ทำได้ครับ</p><p>การเขียนโค้ดแล้วไปมี Exception ที่หลังมันให้ความรู้สึกที่แย่มากๆ ผมจะหงุดหงิดเสมอเวลาที่เขียนโค้ดที่คิดว่าเวิร์คแล้วแล้วอยู่ดีๆ ก็ไปเกิด Runtime exception ตรงที่ไม่น่าจะเกิดเพราะลืมนั่นลืมนี่</p><p>เป็นความรู้สึกที่แย่มากๆ ผมเกลียดมัน ดังนั้น ผมจะพยายามออกแบบให้คนที่ใช้โค้ดของผมไม่สามารถมีหนทางไปเจอ Runtime exception ได้ (หรือถ้าต้องเจอจริงๆ ก็ขอให้น้อยที่สุด)</p><p>ในกรณีนี้ทำได้สองแบบครับ</p><p><strong>Object-Oriented approach</strong></p><pre>var emailSender = new EmailSender(&quot;ace@taskworld.com&quot;, &quot;Hello ace&quot;)<br>emailSender.send()</pre><p>ในกรณีนี้ ผมออกแบบ Class EmailSender ไม่ให้มี Empty Constructor ดังนั้นไม่ว่าใครใช้ก็ต้องใส่ข้อมูลให้ครบก่อนเสมอ</p><p>พอเราบังคับให้ Constructor ต้องมีครบ ก็จะไม่มีใครสามารถสร้าง EmailSender ที่พอกด emailSender.send() แล้วจะเกิด Exception ได้อีกแล้ว</p><p><strong>Functional Approach</strong></p><pre>send(emailServer, &quot;ace@taskworld.com&quot;, &quot;Hello ace&quot;)</pre><p>ง่ายและตรงไปตรงมา ผมส่ง Function บอกว่าต้องใส่ข้อมูลให้ครบ แค่นี้ก็เป็นการยากมากแล้วที่ Developer จะสร้าง Runtime error ได้</p><p>นี่คือเหตุผลนึงที่ผมชอบ Functional programming เนื่องจากมันไม่มี State mutation มีแค่คำสั่งกับ Data transformation ดังนั้นเราสามารถบอกได้ทันทีว่าเราต้องการอะไรบ้าง ส่งมาให้ครบ เราไม่สามารถสร้าง Invalid state ได้แต่แรกแล้ว</p><p><strong>Common Popular Anti-Pattern</strong></p><p>เรื่องที่น่ารำคาญใจสำหรับผมคือ ถึงแม้ Anti-pattern แบบนี้จะเป็นอะไรที่เข้าใจง่ายและหลีกเลี่ยงง่าย แต่เราก็พบ Anti-pattern แบบนี้ได้ทั่วไปมากๆ แม้แต่ระบบที่ออกแบบด้วยบริษัทใหญ่ๆ ก็ตาม</p><p>(ผมถึงได้บอกทุกคนตลอด อย่าเชื่อว่านี่มันเป็น Best practice ออกแบบมาดี เพียงเพราะบริษัทใหญ่ๆ อย่าง F…., G…, M… หรือใครก็ตามออกแบบมาให้เรา ตั้งคำถามด้วยว่าเขาทำมาดีหรือเปล่า)</p><p><strong>ORM</strong></p><pre>user = new User()<br>user.email = &quot;ace@taskworld.com&quot;<br>user.name = &quot;Chris&quot;<br>user.password = Hash(&quot;WeirdPassword&quot;)<br>user.optionalTelephone = &quot;0000000&quot;<br>user.save() // *** Throws if some required fields missing ***</pre><p>นี่คือ Orm ทั่วไปที่พบได้ในเกือบๆ ทุกภาษา</p><p>ถ้าในกรณีนี้ user นั้นต้องการ email, name, password แน่นอนในการสร้าง (คงไม่มี User คนไหนที่ไม่มีอ่ะนะ)</p><p>แทนที่เราจะบอกว่า user.save() คุณไปทำ Validation ด้วยนะ ใครส่งข้อมูลมาไม่ครบให้ Throw runtime exception นะ จะดีกว่ามั้ยถ้าเราออกแบบยังงี้</p><pre>user = new User(&quot;ace@taskworld.com&quot;, &quot;Chris&quot;, Hash(&quot;WeirdPassword&quot;)<br>user.optionalTelephone = &quot;0000000&quot;<br>user.save()</pre><p>ถ้าเราออกแบบยังงี้ ไม่ว่าเราจะกด user.save() ตอนไหน ไม่ว่า Programmer จะมาซนยังไง ตราบใดที่เขาไม่เปลี่ยน Interface ของ Class user ก็ไม่มีทางเกิด Runtime exception จากการใส่ฟิลด์ที่ต้องการไม่ครบได้เลย</p><p>นี่เป็นหลักการที่เข้าใจง่าย แต่ก็พลาดกันบ่อย แม้แต่ผมเองก็ใช้เวลาหลายปีกว่าจะมองเห็นหลักการง่ายๆ ตรงนี้</p><p>สิ่งที่ผมอยากให้จำคือ ครั้งถัดไปที่เราใช้ Library หรือใช้ Class ของคนอื่น หรือใช้ Function ของคนอื่น แล้วมัน Throw runtime exception ขึ้นมาเพราะเรา Config อะไรบางอย่างไม่ครบ</p><p>อย่ามองแต่แค่โทษตัวเองว่าเราโง่เองไม่ยอมอ่านให้ครบก่อนใช้เพียงอย่างเดียว พี่เขาก็บอกแล้วใน Doc แต่ไม่อ่านอย่างเดียว</p><p>แต่อยากให้มองให้เห็นไปถึงว่าการดีไซน์คลาสที่เกิดสิ่งนี้ได้ มันเป็น Code smell แบบนึงด้วยครับ</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1450ec9d4805" width="1" height="1" alt=""><hr><p><a href="https://medium.com/taskworld-tech/basic-code-smell-1-method-%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%82%E0%B8%B6%E0%B9%89%E0%B8%99%E0%B8%81%E0%B8%B1%E0%B8%9A-mutation-1450ec9d4805">Basic code smell#1: Method ที่ขึ้นกับ Mutation</a> was originally published in <a href="https://medium.com/taskworld-tech">Taskworld Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>