<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prayoch Rujira</title>
    <description>The latest articles on DEV Community by Prayoch Rujira (@j4cksw).</description>
    <link>https://dev.to/j4cksw</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F236435%2Fc5cac98e-cdfe-4e04-907a-af880e6d4218.png</url>
      <title>DEV Community: Prayoch Rujira</title>
      <link>https://dev.to/j4cksw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/j4cksw"/>
    <language>en</language>
    <item>
      <title>บันทึกเกี่ยวกับ test coverage</title>
      <dc:creator>Prayoch Rujira</dc:creator>
      <pubDate>Tue, 25 Mar 2025 14:16:38 +0000</pubDate>
      <link>https://dev.to/j4cksw/banthuekekiiywkab-test-coverage-4o7d</link>
      <guid>https://dev.to/j4cksw/banthuekekiiywkab-test-coverage-4o7d</guid>
      <description>&lt;p&gt;สวัสดีครับ วันนี่มีมิตรสหายมาสอบถามเกี่ยวกับ test coverage ในภาษา Go เลยเอามาเขียนบล็อกเก็บไว้ด้วย&lt;/p&gt;

&lt;p&gt;ในภาษา Go เราสามารถรันเทสด้วยคำสั่งนี้พร้อมกับ option -cover เพื่อดู code coverage ได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; go test ./calculator -cover
ok      coverage/calculator     0.359s  coverage: 100.0% of statements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;และยังสามารถสร้างเป็น HTML report ได้ ด้วยคำสั่ง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; go test ./... -covermode=atomic -coverprofile=coverage.out
&amp;gt; go tool cover -html=coverage.out -o coverage.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมือเปิดดูไฟล์ HTML ที่ได้จะสามารถดูได้ว่าบรรทัดไหนบ้างที่ถูกใช้งานจากการรัน unit test&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhe2qcqoaehd36crzm2nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhe2qcqoaehd36crzm2nb.png" alt="Image description" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;สำหรับผู้ที่ใช้ VSCode ก็สามารถเปิด code coverage ได้เช่นกัน ด้วยการกด cmd(ctrl) + shift + p  แล้วเลือกคำสั่งนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxytrwwmjoqtqxvyjinr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxytrwwmjoqtqxvyjinr.png" alt="Image description" width="800" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeluuxpn6fwodyyyti0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeluuxpn6fwodyyyti0o.png" alt="Image description" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;อย่างไรก็ตาม แม้เราจะมี code coverage ที่ 100% แล้วก็ไม่ได้แปลว่า unit test นั้นครบทุก case แล้ว&lt;br&gt;
ตัวอย่างเช่น function นี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func Divide(a, b int) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return float64(a) / float64(b), nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;กับ unit test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func TestDivide(t *testing.T) {
    tests := []struct {
        name        string
        a, b        int
        expected    float64
        expectError bool
    }{
        {"valid division", 6, 2, 3.0, false},
        {"division by zero", 6, 0, 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := Divide(tt.a, tt.b)
            if tt.expectError {
                if err == nil {
                    t.Error("Divide() expected error but got none")
                }
                return
            }
            if err != nil {
                t.Errorf("Divide() unexpected error: %v", err)
                return
            }
            if result != tt.expected {
                t.Errorf("Divide(%d, %d) = %f; want %f", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมื่อเรา run test ก็จะได้ 100% แน่นอนเพราะว่า test ของเราวิ่งเข้าครบทุกเงื่อนไข&lt;/p&gt;

&lt;p&gt;แต่ test coverage ไม่สามารถเตือนเราได้ว่า input เป็นเลขลบได้นะ! ดังนั้น เราควรจะเพิ่ม case เลขติดลบเข้าไปด้วย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func TestDivide(t *testing.T) {
    tests := []struct {
        name        string
        a, b        int
        expected    float64
        expectError bool
    }{
        {"valid division", 6, 2, 3.0, false},
        {"division by zero", 6, 0, 0, true},
        {"negative numbers", -6, 2, -3.0, false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := Divide(tt.a, tt.b)
            if tt.expectError {
                if err == nil {
                    t.Error("Divide() expected error but got none")
                }
                return
            }
            if err != nil {
                t.Errorf("Divide() unexpected error: %v", err)
                return
            }
            if result != tt.expected {
                t.Errorf("Divide(%d, %d) = %f; want %f", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เพราะฉะนั้น &lt;strong&gt;&lt;em&gt;Code coverage ไม่เท่ากับ Test case coverage&lt;/em&gt;&lt;/strong&gt; นะครับ&lt;/p&gt;

&lt;p&gt;เพื่อป้องกันปัญหานี้การช่วยกันออกแบบ test case จึ่งมีความสำคัญ ลองทำ peer review โดยให้เพื่อนในทีม หรือ QA หรือ Product มาช่วยให้คำแนะนำก็ได้ แน่นอนว่าไม่ได้ให้มารีวิวโค้ด แต่ให้มาช่วยดู data ที่จะ test นั้นถูกต้อง เหมาะสม หรือไม่&lt;/p&gt;

&lt;p&gt;หรือสมัยนี้สามารถใช้  AI เข้ามาช่วยวิเคราะห์ให้ได้เลยว่า test นั้นครบทุก edge case แล้วหรือยัง จะได้ออกมาเพียบ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        // Basic cases
        {"positive numbers", 6, 2, 3.0, false},
        {"negative numbers", -6, 2, -3.0, false},
        {"both negative", -6, -2, 3.0, false},
        {"numerator negative", -6, 2, -3.0, false},
        {"denominator negative", 6, -2, -3.0, false},

        // Zero cases
        {"division by zero", 6, 0, 0, true},
        {"zero numerator", 0, 5, 0.0, false},
        {"zero denominator", 5, 0, 0, true},
        {"both zero", 0, 0, 0, true},

        // One cases
        {"divide by one", 5, 1, 5.0, false},
        {"one by number", 1, 5, 0.2, false},
        {"one by one", 1, 1, 1.0, false},

        // Large numbers
        {"large positive", 1000000, 1000, 1000.0, false},
        {"large negative", -1000000, 1000, -1000.0, false},

        // Decimal results
        {"decimal result", 5, 2, 2.5, false},
        {"small decimal", 1, 3, 0.3333333333333333, false},

        // Edge cases
        {"max int32", 2147483647, 1, 2147483647.0, false},
        {"min int32", -2147483648, 1, -2147483648.0, false},
        {"max by max", 2147483647, 2147483647, 1.0, false},
        {"min by min", -2147483648, -2147483648, 1.0, false},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;หรือจะไปให้ไกลกว่านั้น ก็ลองใช้เทคนิคอื่นๆพวก monkey test หรือ permutation test เข้ามาช่วยในการทำการทดสอบอีกระดับก็ได้ครับ ลองศึกษาดูจาก link ต่อไปนี้ก่อนก็ได้&lt;br&gt;
&lt;a href="https://www.geeksforgeeks.org/monkey-software-testing/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/monkey-software-testing/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=8hQG7QlcLBk&amp;amp;ab_channel=GopherAcademy" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=8hQG7QlcLBk&amp;amp;ab_channel=GopherAcademy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code coverage เป็นเครื่องมือที่มีประโยชน์มากในการพัฒนา software น่าเสียดายมากนะครับถ้าเอามาใช้เป็นแค่ quality gate เฉยๆ แต่ระหว่างทางเราไม่ได้เใช้ประโยชน์อะไรจากมันเลย&lt;/p&gt;

&lt;p&gt;ลองเอามาใช้เป็นเครื่องมือทำให้การเขียน code ของเราสนุกมากขึ้นดูนะครับ &lt;/p&gt;

</description>
      <category>unittest</category>
    </item>
    <item>
      <title>ทำความรู้จักกับ Meta Programming ใน Typescript</title>
      <dc:creator>Prayoch Rujira</dc:creator>
      <pubDate>Mon, 05 Aug 2024 13:37:48 +0000</pubDate>
      <link>https://dev.to/j4cksw/thamkhwaamruucchakkab-meta-programming-ain-typescript-51on</link>
      <guid>https://dev.to/j4cksw/thamkhwaamruucchakkab-meta-programming-ain-typescript-51on</guid>
      <description>&lt;p&gt;ก่อนอื่นเลย เราไปดูความหมายของ Meta programming จากใน wikipedia กันก่อน&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Metaprogramming is a computer programming technique in which computer programs have the ability to treat other programs as their data. It means that a program can be designed to read, generate, analyse, or transform other programs, and even modify itself, while running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;แปลเองแล้วงง ลองให้ AI แปลดูซิ&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Metaprogramming คือ เทคนิคในการเขียนโปรแกรมคอมพิวเตอร์ ซึ่งโปรแกรมสามารถจัดการกับโปรแกรมอื่น ๆ เหมือนข้อมูลของตัวเองได้ นั่นหมายถึงโปรแกรมสามารถออกแบบให้สามารถอ่าน สร้าง วิเคราะห์ หรือแปลงโปรแกรมอื่น ๆ และ ในบางกรณี สามารถปรับเปลี่ยนตัวเองขณะที่กำลังทำงานได้&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ไม่ได้ช่วยเท่าไหร่เลย ผมลองอธิบายแบบบ้านๆดีกว่า &lt;br&gt;
อย่างเวลาเราเขียนโปรแกรม เราก็จะประกาศตัวแปร หรือ function หรือ class ขึ้นมาเพื่อแก้ปัญหาอะไรสักอย่าง ในภาษาอย่าง Java หรือ Typescript ก็ถือเป็น object ประเภทหนึ่ง เราสามารถเข้าถึงข้อมูลบางอย่างได้ เช่น ชื่อ หรือถ้าฟังก์ชั่นก็จะดู input arguments ได้ เป็นต้น ซึ่งการเข้าถึงข้อมูลพวกนี้เรียกว่า metadata ก็เลยเป็นที่มาของ meta programming นี่แหละครับ&lt;/p&gt;

&lt;p&gt;ยกตัวอย่างที่เราพอได้ใช้กันอยู่บ้าง เช่นการใช้ Object.hasOwnProperty()&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tvpqyozf4rtuledpaa4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tvpqyozf4rtuledpaa4.png" alt="Object.hasOwnProperty" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เราสามารถที่จะเรียกดูรายชื่อ proterty ใน object ได้ด้วยการใช้ Object.keys&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqsh4z237oojx8ho33j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqsh4z237oojx8ho33j7.png" alt="Object.keys" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เข้าถึง property descriptor ด้วย Object.getOwnPerpertyDescriptor() &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwz143lv2ejg5wnlswp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwz143lv2ejg5wnlswp0.png" alt="Object.getOwnPerpertyDescriptor" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แก้ไขข้อกำหนดบางอย่างของตัว property ด้วย Object.defineProperty() อย่างเช่น ไม่อนุญาตให้แก้ไข&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvgkl8roerv3m1lp6gu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvgkl8roerv3m1lp6gu9.png" alt="Object.defineProperty" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ลองเล่นกับ Decorator&lt;/strong&gt;&lt;br&gt;
ปกติแล้ว เราจะเป็นผู้ใช้งาน decorator กันซะมากกว่า ในหลายๆ framework เช่น Angular (แล้วเราก็เรียกมันว่า magic 😂)&lt;br&gt;
ซึ่ง magic ที่ว่านี่ก็คือการใช้ประโยชน์จากการเข้าถึง meta data ได้นั่นแหละ&lt;/p&gt;

&lt;p&gt;ลองมาดูหน้าตาของ decorator ใน TypeScript กัน ก่อนอื่นต้องรู้ก่อนว่าจะเอา decorator ไปวางไว้ที่ไหน เช่น class หรือ method&lt;/p&gt;

&lt;p&gt;เอาง่ายๆก็ method ก่อน สมมติว่าเราจะสร้าง decorator ชื่อ log&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgscbokl5q031atyyc2oc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgscbokl5q031atyyc2oc.png" alt="method decorator" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;วิธีใช้งานก็เอาไปวางบนหัว method แบบนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn34e6xnl34upqjchrmt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn34e6xnl34upqjchrmt.png" alt="using method decorator" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แน่นอนว่าตัว decorator ยังไม่ได้ทำอะไรเลย ดังนั้นเรามาดูกันต่อว่าทำอะไรกับมันได้บ้าง&lt;/p&gt;

&lt;p&gt;สมมติว่า เราอยากจะทำการ log ทุกครั้งที่มีการเรียก method เราก็จะต้องทำการ decorate ก่อน ที่จะเรียก method จริงๆ&lt;br&gt;
ที่นี้จะทำได้ยังไง ก็ผ่านตัว descriptor นั่นแหละ หน้าตามันก็คล้ายๆกับที่เล่าไปในตอนแรก&lt;br&gt;
ใน descriptor มันจะมี property ตัวนึงชื่อ value ให้เราสามารถย่ำยีได้ตามใจชอบ ดังนั้น เราก็จะสร้าง function มาครอบของเดิม เพื่อให้มัน log ก่อน แล้วค่อยไปเรียกตัว method จริงๆ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4wwha1srtrzv6vkqzqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4wwha1srtrzv6vkqzqp.png" alt="implement decorator" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ลองใช้งานดู ก็จะพบว่า จะมีการ log ทุกครั้งที่เรียกฟังก์ชั่น&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nyehqxdl2cb9eukua1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nyehqxdl2cb9eukua1u.png" alt="log result #1" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แน่นอนว่า เราสามารถ เข้าถึงได้มากกว่าชื่อ functtion จะ log input arguments ก็ทำได้เช่นกัน&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvex9bfat91shgaicm3iz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvex9bfat91shgaicm3iz.png" alt="logging input parameters" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ที่นี้ก้ไม่ต้องคอยใส่ log เข้าไปทุก method แล้ว&lt;/p&gt;

&lt;p&gt;อันนี้ก็ตัวอย่างที่เราเอาไปใช้ได้นะครับ เรื่อง metaprogramming ยังมีให้ศึกษาอีกเยอะมากเลย เป็นเครื่องมืออีกอันที่หยิบมาใช้ทำให้ชีวิตดีขึ้นได้ครับ ทำให้เข้าใจมากขึ้นเวลาไปใช้ framework ต่างๆด้วย ว่า magic แต่ละอันมันมาได้ยังไงด้วย 😎&lt;/p&gt;

&lt;p&gt;จบไว้ประมาณนี้ก่อนะครับ ถ้ามีโอกาสจะมาเล่าเคสที่ซับซ้อนมากขึ้นอีกนิด&lt;/p&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>ทำ Software ไม่ได้มีแต่การปั่น Feature หรอกนะ</title>
      <dc:creator>Prayoch Rujira</dc:creator>
      <pubDate>Mon, 20 Feb 2023 14:43:05 +0000</pubDate>
      <link>https://dev.to/j4cksw/tham-software-aimaidmiiaetkaarpan-feature-hrkna-9oe</link>
      <guid>https://dev.to/j4cksw/tham-software-aimaidmiiaetkaarpan-feature-hrkna-9oe</guid>
      <description>&lt;p&gt;สวัสดีครับ เนื่องจากวันที่ 19 กุมภาพันธ์  2566 ที่ผ่านมา พี่ปุ๋ยชวนไป(โดนฆ่า)พูดที่งาน National coding day จัดโดยสมาคมโปรแกรมเมอร์ไทย ที่ Bitec&lt;/p&gt;

&lt;p&gt;เนื้อหาไม่ได้มีอะไรมาก เป็นการพูดคุยเกี่ยวกับเรื่องที่พบเจอได้ทั่วๆไปในการพัฒนา Software จนมันกลายเป็น meme ที่เอามาล้อกันสนุกสนาน แต่น้ำตามันตกอยู่ข้างใน&lt;/p&gt;

&lt;p&gt;แน่นอนว่ากว่าผมจะรู้ว่าต้องพูดอะไร ก็ตอนที่ขึ้นเวทีไปแล้วนั่นเอง&lt;/p&gt;

&lt;p&gt;แต่นั่นไม่ใช่ประเด็น จนต้องเอามาเขียน ที่ผมอยากจะเขึยนก็คือ ตอนปิดท้ายผมรู้สึกว่าตัวเองพูดรวบรัดไปหน่อย รู้สึกว่าสื่อสารออกไปได้ไม่ครบถ้วน เลยมาเรียบเรียงขยายความใหม่ น่าจะดีกว่า&lt;/p&gt;

&lt;p&gt;เริ่มที่รูปนี้ จากใน Slide นั่นแหละ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fof10e8pk6xmpz47inix4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fof10e8pk6xmpz47inix4.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;คำถามคือ เราปล่อยให้ Bug มันขยายร่างขึ้นไปเรื่อยๆได้ยังไง&lt;br&gt;
หรือผมลองตั้งคำถามใหม่&lt;br&gt;
เราตั้งใจปล่อยให้บั๊กมันโตขึ้นเรื่อยๆหรือเปล่า?&lt;br&gt;
มี Developer คนไหนตั้งใจ commit code ที่เป็น Bug เข้าระบบบ้าง&lt;br&gt;
มี Tester คนไหนตั้งใจเขียน test case ที่ไม่มีวันทดสอบผ่านบ้าง&lt;br&gt;
มี Designer คนไหน ตั้งใจสร้างของที่มันทำจริงๆไม่ได้ ใส่เข้าไปใน Figma บ้าง&lt;br&gt;
มี Infra คนไหน ตั้งใจวางขั้นตอนให้มัน deploy ยากๆบ้าง&lt;br&gt;
มี User คนไหน ตั้งใจให้ requirement ที่ไม่มีวันทำได้จริงบ้าง&lt;br&gt;
และ อื่นๆอีกมากมาย&lt;/p&gt;

&lt;p&gt;ผมคิดว่าจริงๆแล้ว ก็ไม่มีใครอยากทำร้ายกันหรอก ทุกคนก็อยากทำสิ่งดีๆทั้งนั้น ถ้างั้นแล้ว ปัญหามันมาจากอะไรล่ะ&lt;/p&gt;

&lt;p&gt;ผมเดาว่า เป็นเพราะ ทุกคนมีเป้าหมายที่แตกต่างกัน เมื่อต่างคนต่างมุ่งหมายที่จะกระทำเป้าหมายของตัวเองให้สำเร็จเท่านั้น&lt;/p&gt;

&lt;p&gt;ซึ่งมันเป็นเป้าหมายเดียวกันกับ software ที่กำลังทำกันอยู่หรือไม่?&lt;/p&gt;

&lt;p&gt;ทำไมบั๊กมันถึงเติบโตขึ้นได้เรื่อยๆ สาเหตุหนึ่ง ก็เพราะว่าเรามี deadline ที่จะต้องทำ feature ทั้งหมดให้เสร็จตามที่ commit ไว้&lt;br&gt;
ดังนั้น หลายๆครั้ง เราก็เลือกที่จะละเลยเรืองบางเรื่องไปก่อน เพื่อที่จะส่งมอบงานได้ทันเวลา&lt;br&gt;
แล้วการต้องส่งมอบให้ได้ทันเวลา โดยที่ระบบมีปัญหา ใช้งานไม่ได มันเป็นเป้าหมายของใครกันล่ะ? ไม่น่ามีนะ&lt;br&gt;
และเราก็ได้เรียนรู้ครั้งแล้วครั้งเล่าว่าธรรมชาติของการทำ Software มันไม่ได้เป้นแบบนั้น&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65n7xcbqrswaud5vae1u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65n7xcbqrswaud5vae1u.jpeg" alt=" " width="500" height="735"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ผมคิดว่า DevOps หรือ practice หลายๆตัวมันก็เกิดมาจากจุดนี้แหละ นั่นคือ ไม่ว่าคุณจะอยู่ตรงไหนของการทำ Software คุณจะต้องตระหนักรู้ถึงการที่ software ของคุณน่ะ มันจะถูกเอาไป Operate อย่างไร เราต้องทำอะไรหรือเตรียมตัวอย่างไรเพื่อ support สิ่งนั้น แล้ว practice หรือ tools ต่างๆ ค่อยตามมา&lt;/p&gt;

&lt;p&gt;ขอให้สนุกกับการทำ Software มีความสุขทั้งฝั่งผู้ใช้งาน และฝั่งผู้พัฒนา ครับ&lt;/p&gt;

</description>
      <category>showdev</category>
    </item>
    <item>
      <title>บันทึกการ Refactor</title>
      <dc:creator>Prayoch Rujira</dc:creator>
      <pubDate>Wed, 25 May 2022 15:39:59 +0000</pubDate>
      <link>https://dev.to/j4cksw/banthuekkaar-refactor-m8k</link>
      <guid>https://dev.to/j4cksw/banthuekkaar-refactor-m8k</guid>
      <description>&lt;p&gt;ในช่วงเดือนที่ผ่านมา ผมมีโอกาสได้เข้าไปช่วยเหลือให้คำแนะนำเกี่ยวกับการ   Refactor ให้กับทีมที่กำลังทำแอปตัวหนึ่งมา เลยเอามาบันทึกและแบ่งปันไว้ตรงนี้ครับ&lt;/p&gt;

&lt;p&gt;โดยโจทย์ที่ได้เจอก็คือ Code ถูกเขียนมาสักระยะแล้ว ประมาณ 3-4 เดือน โดยที่ไม่ได้มีการ Apply Design principles ใดๆเลย เนื่องจาก ต้องเผาให้ทันส่งนั่นเอง&lt;/p&gt;

&lt;p&gt;ดังนั้น code smell ที่จะได้เจอแน่ๆ ก็คือ God class หรือ class ที่มันใหญ่มากๆ มีการทำงานหลายๆอย่างอยู่ข้างในนั้น บางที่ก็เรียก Monster class หรือ Kaiju class นะ อันนี้ก็แล้วแต่จะสรรหามาเรียกกัน แต่โดยรวมแล้วไม่ต่างกัน คืือมันร้ายกาจมาก เวลามันโมโหขึ้นมาก็อาจจะทำให้โปรเจคล่มได้เลยนะ&lt;/p&gt;

&lt;p&gt;ยกตัวอย่างให้เห็นภาพ ตอนแรกที่เราเริ่มทำแอป เพื่อจะแยกส่วนแสดงผมกับส่วนของการประมวลผลหรือควบคุม UI ออกจากกัน ก็อาจจะมี View กับ ViewModel คู่กันแบบ 1:1 ใช่ไหมครับ และในเวลาต่อมา เราก็จะเริ่มเพิ่มfeature ที่ 2,3,4 เข้าไปเรื่อยๆ ในช่วงแรก มันยังเล็กๆอยู่ เราอาจจะยังไม่เห็นปัญหามากนัก เราจะเอาของใส่ใว้ตรงไหนล่ะ ก็ ViewModel ตัวเดิมนั่นไง ต่อมาเราก็เพิ่ม View ขึ้นมาอีกอันนึง แต่ feature มันก็ใกล้ๆกันนะ เราก็เอา feature ของ view ที่สองเอามาใส่กับ ViewModel ตัวเดิม แล้วเราก็ทำอย่างนี้ไปเรื่อยๆๆ หลังจากนั้น พอมีตรงไหนที่อยากจะ reuse feature ใน viewmodel ก็เอาไปเรียกใช้ได้เลย ทำให้ ViewModel ตัวเดียว อาจจะมีการเรียกใช้จากทุกหน้าในแอปก็เป็นได้&lt;/p&gt;

&lt;p&gt;ซึ่งมันก็ดูจะสะดวกสบายดี เอาทุกๆอย่าง รวมไว้ใน class ก้อนเดียว เวลาเรียกใช้ก็ง่ายๆเลย ปัญหามันคืออะไรล่ะ &lt;/p&gt;

&lt;p&gt;คำถามคือ มันทำให้งานของเรายากขึ้นเรื่อยๆหรือเปล่า?&lt;/p&gt;

&lt;p&gt;งานของโปรแกรมเมอร์คืออออ&lt;br&gt;
1.อ่านโค้ด&lt;br&gt;
กับ 2.เขียน(แก้)โค้ด&lt;/p&gt;

&lt;p&gt;เราทำข้อ 1 เพื่อหาจุดที่เราจะต้องแก้แล้วถึงทำข้อ 2 เพื่อให้ software ของเรามันเป็นไปตาม Requirement ถูกมั้ยครับ&lt;/p&gt;

&lt;p&gt;ไอ้ตัว God class เนื่องจากมีการทำงานที่อัดแน่นอยู่ข้างในตัวมัน ดังนั้นสิ่งที่ตามมาก็คือปริมาณของโค้ดที่เราต้องอ่าน ซึ่งมันสามารถทำคอมโบกับ code smell อีกตัวที่ชื่อ long method ได้อีก ก้คือ method ที่ยาววววว มากๆ อาจจะเกิน 10-20 บรรทัดได้ และยังมีโอกาสเจอตัวอื่นๆได้อีกเพียบ&lt;/p&gt;

&lt;p&gt;ยิ่งปริมาณโค้ดเยอะ ก็ยิ่งใช้เวลานานในการทำความเข้าใจ และเมื่อเวลาผ่านไป เราจะใช้เวลาอ่านโค้ดนานกว่าเขียนโค้ดมากขึ้นไปเรื่อยๆ และนี่ก็คือสาเหตุหนึ่งที่ทำไม productivity ของเหล่าโปรแกรมเมอร์มันลดลงๆ&lt;/p&gt;

&lt;p&gt;ปัญหาต่อมาคือ แก้ไปแล้วรู้ได้ยังไงว่ามันทำงานถูก&lt;/p&gt;

&lt;p&gt;การเกิด God class ส่วนหนึ่งเกิดจากการที่เราไม่มี unit test&lt;br&gt;
เพราะถ้าเราเขียน unit test ไว้ตั้งแต่เนิ่นๆ เราจะรู้สึกได้ไวมากๆ ว่า class มันเริ่มมีปัญหา มันชักจะเริ่มเขียน test ยาก ทำให้เราต้องกลับมาดู design ของเราว่ามันมีอะไรผิดไป และเราควรจะเริ่มแตกปัญหาออกมาเป็น class เล็กๆ เพื่อให้สามารถเขียน unit test ได้ง่ายๆ ซึ่ง God class ที่ผมเจอมาคือมักจะไม่มี unit test มันจึงเป็นเรื่องที่ต้องออกแรงเยอะมากถ้าเราจะแก้มัน&lt;/p&gt;

&lt;p&gt;อันดับแรก ต้องเริ่มที่ understanding เสมอ&lt;/p&gt;

&lt;p&gt;ก็คือทำความเข้าใจของที่อยู่ข้างในนั้นก่อน ซึ่งแน่นอนว่าไม่ง่าย สิ่งที่ผมทำก็คือ ถามว่า ไอ้นี่มันทดสอบยังไง ทั้งแบบ manual และเอามาเขียนเป็น acceptance test กับ integration test ก่อน อาจจะดูง่ายๆ แต่ใช้เวลานานมากนะครับ มันคือการลองผิดลองถูก explore มันไปเรื่อยๆ crosss check กับหลายๆคนเลยว่ามันถูกมั้ย จะให้ดีก็เอา QA มา pair ด้วยเลย ซึ่งไม่ค่อยมีโอกาสเท่าไหร่หรอก &lt;/p&gt;

&lt;p&gt;พอเข้าใจโค้ดระดับหนึ่งแล้ว ผมก็จะมองหาจุดที่จะ improve สำหรับ God class ก็คือเราจะแยกชิ้นส่วนมันออกมาเป็น class เล็กๆ ได้อย่างไร&lt;/p&gt;

&lt;p&gt;เทคนิคที่ใช้ก็คือมองหา รอยต่อ(Seams) ภายใน class ยกตัวอย่างแบบง่ายๆเช่น กลุ่มของ method หรือ ตัวแปร ที่ไม่ได้มีความเกี่ยวของกัน อันนี้สามารถจับแยกเป็นคนละ class ได้เลย แต่ก็ไม่ได้ง่ายแบบนั้นเสมอไป ตรงนี้ต้องใช้ความชำนาญสักหน่อยครับ และต้องใช้ design principle หรือ pattern ต่างๆเข้ามาแก้ปัญหา&lt;/p&gt;

&lt;p&gt;พอหารอยต่อได้แล้ว ก็จะเอามา design แล้วลงมือเขียน unit test แล้วค่อยๆ refactor ตามขั้นตอนนี้ไปเรื่อยๆ&lt;/p&gt;

&lt;p&gt;แล้วจะต้อง refactor ไปถึงไหน? อันนี้ก็แล้วแต่ตกลงกัน ยกตัวอย่างเช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test coverage ถึงค่าที่กำหนด เช่น 90% (Code เกือบทั้งหมด มี unit test)&lt;/li&gt;
&lt;li&gt;ให้ทีมทำ code review แล้วไม่พบ issue ที่ยอมรับไม่ได้&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;การ Refactor แบบล้มยักษ์แบบนี้ไม่ควรเกิดขึ้นบ่อยๆ หรือไม่ควรเกิดเลย เพราะมันสิ้นเปลืองเพราะเราก็ต้องใช้เวลาของ developer ในการทำ แล้วในมุมของ business มันก็ถือว่าไม่เกิด progress ใดๆ แทนที่จะได้เอาเวลาไปทำ feature ใหม่ๆ กลับต้องเอาเวลามาใช้หนี้ทางเทคนิคเหล่านี้ แต่มันก็เป็นสิ่งที่ละเลยไม่ได้ถ้าจะทำงานกันไปยาวๆ แต่จากประสบการณ์ของผม ยิ่งโปรเจคเร่งรัดมากเท่าไหร่ ก็มีโอกาสเจอมากเท่านั้นแหละ&lt;/p&gt;

&lt;p&gt;ดังนั้น ป้องกันไม่ให้มันเกิดจะดีที่สุดนะครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;เพิ่มเติม&lt;/strong&gt;&lt;br&gt;
Code smells (Code ที่ไม่ดีเป็นอย่างไร)&lt;br&gt;
&lt;a href="https://refactoring.guru/refactoring/smells"&gt;https://refactoring.guru/refactoring/smells&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refactoring recipes (วิธีการ Refactor แบบต่างๆ)&lt;br&gt;
&lt;a href="https://refactoring.guru/refactoring/techniques"&gt;https://refactoring.guru/refactoring/techniques&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seams (การหารอยต่อใน code)&lt;br&gt;
&lt;a href="https://www.informit.com/articles/article.aspx?p=359417&amp;amp;seqNum=2"&gt;https://www.informit.com/articles/article.aspx?p=359417&amp;amp;seqNum=2&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>บันทึก การ Redirect ของ LIFF App</title>
      <dc:creator>Prayoch Rujira</dc:creator>
      <pubDate>Fri, 21 Jan 2022 05:31:14 +0000</pubDate>
      <link>https://dev.to/j4cksw/banthuek-kaar-redirect-khng-liff-app-2jl4</link>
      <guid>https://dev.to/j4cksw/banthuek-kaar-redirect-khng-liff-app-2jl4</guid>
      <description>&lt;p&gt;ช่วงนี้วุ่นๆกับการทำ LIFF แล้วติดปัญหาเวลามี parameter แล้วงงกับพฤติกรรมของมันเวลา Redirect&lt;br&gt;
ทาง Line เค้าก็ทำ Video กับ Document อธิบายไว้แล้วแหละ แต่ก็ยังไม่ค่อยเข้าใจอยู่ดี เลยต้องลองด้วยตัวเอง&lt;/p&gt;

&lt;p&gt;โจทย์ก็คือ เรามี LIFF Url แบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://liff.line.me/1656449211-9Kym0GQ7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เวลาใช้จริงเราก็อยากจะแนบ query parameter เข้าไปด้วย แบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://liff.line.me/1656449211-9Kym0GQ7?template_id=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;เมื่อเราเปิด LIFF จากใน line จะเจอว่า มีการเรียกมาที่ /?liff.state=%3Ftemplate_id%3D1 &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gjmeel0fxd33yaagmpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gjmeel0fxd33yaagmpj.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เมื่อทำการ initialize สำเร็จ จะมีการเรียกเข้ามาอีกครั้งที่ /?template_id=1 และ สามารถอ่านค่า query param ได้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki5scyen0ktn16gw14jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki5scyen0ktn16gw14jc.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;สรุปว่า query param ไม่ได้หายไปไหน แต่ต้องอ่านให้ถูก ถ้าจะอ่านจาก redirect ครั้งแรก มันจะถูก encode มาใน liff.state&lt;br&gt;
แต่ถ้าจะอ่านจาก redirect ครั้งที่สอง ก็อ่านจาก URL ตามปกติ&lt;/p&gt;

</description>
      <category>line</category>
      <category>liff</category>
    </item>
  </channel>
</rss>
