Refactoring toward smaller methods

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

public String evaluate() {
   String result = templateText;
   for (Entry<String, String> entry : variables.entrySet()) {
      String regex = "\\$\\{" + entry.getKey() + "\\}";
      result = result.replaceAll(regex, entry.getValue());
   }
   checkForMissingValues(result);
   return result;
}
private void checkForMissingValues(String result) {
   if (result.matches(".*\\$\\{.+\\}.*")) {
      throw new MissingValueException();
   }
}

Listing 2.16 Extracting the check for missing variables into a method

ดูดีขึ้นมาหน่อย แต่ยังไม่ดีพอ evaluate ยังขาดในเรื่องของ balance ไปแต่ก่อนจะไปไกลกว่านี้เราควรรู้จักคำว่า balance กันก่อนว่ามันคืออะไร

Keeping methods in balance

คุณสมบัติที่สำคัญอีกอย่างหนึ่งของ method คือมันต้องอ่านได้ง่ายและต้องมีความชัดเจนในตัวเองว่ามันทำอะไรดังนั้นเราลองกลับไปดู evaluate กันอีกครั้งว่ายังเหลืออะไรที่ต้องแก้ไขบ้าง

public String evaluate() {
   String result = templateText;
   for (Entry<String, String> entry : variables.entrySet()) {
      String regex = "\\$\\{" + entry.getKey() + "\\}";
      result = result.replaceAll(regex, entry.getValue());
   }
   checkForMissingValues(result);
   return result;
}

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

public String evaluate() {
   String result = replaceVariables();
   checkForMissingValues(result);
   return result;
}
private String replaceVariables() {
   String result = templateText;
   for (Entry<String, String> entry : variables.entrySet()) {
      String regex = "\\$\\{" + entry.getKey() + "\\}";
      result = result.replaceAll(regex, entry.getValue());
   }
   return result;
}
private void checkForMissingValues(String result) {
   if (result.matches(".*\\$\\{.+\\}.*")) {
   }
}

หลักจากปรับโค้ดแล้วเรา run test ทันใดเราจะพบว่าไม่มีข้อผิดพลาดใดๆ ลองนึกภาพดูว่าถ้าเราไม่มี test คอยเป็นเหมือนไฟส่องทางเราจะกล้าทำเยอะขนาดนี้ไหม :) นี่เป้นตัวอย่างที่ดีที่แสดงให้เห็นว่าการทำ test เพื่อคุ้มครองโค้ดตัวเองมันดีแค่ไหน แต่งานของเรายังไม่เสร็จแต่เพียงเท่านี้เรายังต้องเพิ่ม exception message ที่มีความหมายอ่านง่ายๆให้กับระบบของเรา

Expecting details from an exception
ก่อนหน้านี้เราเขียน test สำหรับเรื่อง missing value ไปแล้วและเราสร้าง MissingValueException ไว้แต่สิ่งที่เราพบคือเราไม่ได้ระบุลงไปให้แน่ชัดว่า variable ตัวไหนที่หายไปซึ่งถ้าเรายังปล่อยให้เป็นแบบนี้เราอาจงงเองในอนาคตได้ว่า variable ไหนกันแน่ที่มันเพี้ยนพังไป ดังนั้นทำเลยอย่าช้า

@Test
public void missingValueRaisesException() throws Exception {
   try {
      new Template("${foo}").evaluate();
      fail("evaluate() should throw an exception if " + "a variable was left without a value!");
   } catch (MissingValueException expected) {
      assertEquals("No value for ${foo}", expected.getMessage());
      Exception should name missing variable
   }
}

Listing 2.18 Testing for an expected exception

หลังจากแก้ไขไปเราก็ทำตามขั้นตอนของเราไปแล้วเราก็ผ่าน test ไปได้ด้วยดี ส่วนต่อไปที่เราจะทำคือเราจะใช้ java.util.regex กันเล็กน้อย ตามแบบโค้ดด้านล่าง

import java.util.regex.Pattern;
import java.util.regex.Matcher;
private void checkForMissingValues(String result) {
   Matcher m = Pattern.compile("\\$\\{.+\\}").matcher(result);
   if (m.find()) {
      throw new MissingValueException("No value for " + m.group());
   }
}

ต่อไปก็เพิ่ม constructor สำหรับ MissingValueException เข้าไปอีกหน่อย

public class MissingValueException extends RuntimeException {
   public MissingValueException(String message) {
      super(message);
   }
}

มาถึงตรงนี้เราได้ปรับโค้ดชุดแรกของเรามาได้ดีระดับหนึ่งแล้ว โดยเราไม่เคยต้องกลัวว่ามันจะพังแล้วแก้ไขไม่ได้ เรามี Test คุ้มภัยครับ -/\-

Adding a bit of error handling

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

Expecting an exception
คำถามคือเราจะเขียน Unit Test ที่ให้สำหรับตรวจสอบว่า exception ที่เราคาดหวังถูก throw ออกมาจากโค้ดบล๊อก try – catch ที่เราเขียนไว้ ตัวอย่างของโค้ดในส่วน 2.14 คือรูปแบบพื้นฐานที่เราใช้สำหรับการทดสอบ exception-throwing ใน JUnit

   @Test
   public void missingValueRaisesException() throws Exception {
       try {
           new Template("${foo}").evaluate();
           fail("evaluate() should throw an exception if "
                   + "a variable was left without a value!");
       } catch (MissingValueException expected) {
       }
   }

สิ่งที่เรานิยมทำกันคือการใส่ fail เมธอดลงไปหลังจากการพยายามเรียก evaluate() ที่จะทำให้เกิด exception โดยการใส่ fail ไว้ด้านหลังแบบนี้แปลว่า “ถ้าไอ้โค้ด new Template(“${foo}”).evaluate(); สามารถทำงานผ่านฉลุยข้ามไปอีกบรรทัดได้แสดงว่ามีบางอย่างผิดปกติไปจากที่มันควรจะเป็นแล้วพี่น้อง” และส่วนต่อไปที่เราจะใส่ครอบไว้เพื่อจับการทำงานคือการ catch exception ที่เราคาดว่าจะเกิดจากการเรียก evaluate แบบผิดๆนี้ ส่วน error อื่นๆที่ไม่เกี่ยวข้องเราจะไม่สนใจมัน เพราะถ้ามี error อื่นๆเกิดขึ้นมันจะหมายความว่าเราทำอะไรผิดเพิ่มขึ้นไปอีกและ exception อื่นๆเหล่านั้นจะถูกจับได้โดยตัว JUnit เอง
ตอนนี้เราได้ test ที่ fail มาอีกหนึ่งอันแล้วต่อไปเราจะเริ่มแก้มันไปตามขั้นตอน โดยสิ่งแรกคือทำให้มัน compile ได้ก่อนดังนั้นสิ่งแรกที่เราจะทำคือการสร้าง MissingValueException class

public class MissingValueException extends RuntimeException {
       // this is all we need for now
}

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

public String evaluate() {
       String result = templateText;
       for (Entry<String, String> entry : variables.entrySet()) {
           String regex = "\\$\\{" + entry.getKey() + "\\}";
           result = result.replaceAll(regex, entry.getValue());
       }
       if (result.matches(".*\\$\\{.+\\}.*")) {
          throw new MissingValueException();
       }
       return result;
}

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

Test Driven Development Workshop No. 1

เรื่องของเรื่องเริ่มจากเราได้มีการพูดคุยเรื่อง TDD ในที่สาธารณะกันมาเป็นปีๆว่าดีอย่างนั้นดีอย่างนี้แต่ “จะเริ่มยังไง ทำได้จริงหรือ?” คำถามเหล่านี้ตามมามากมายและเนื่องด้วยเหตุผลมากมายทำให้ยังไม่สามารถทำ TDD Workshop ได้สักที จนกระทั่งเรื่องมาเกิดจริงๆหลังที่ผมได้รับโอกาสให้ไปร่วมบรรยายที่ Software Park ในหัวข้อเรื่อง “เขียนโค้ดอย่างไร ให้สำเร็จ” ที่จัดโดยชมรม Thailand SPIN ซึ่งก็เป็นอีกครั้งที่ผมไปในฐานะคนที่บูชาลัทธิ TDD และนี่เองก็เป็นเวลาที่ผมได้พบกับ “คุณ Karan Sivarat” และหลังจากจบสัมมนาเราก็ยังคงพูดคุยกันเรื่องแนวคิดการทำ TDD แต่เนื่อด้วยเวลามรจำกัดผมก็เลยชวนไปงาน Bug Dat 2011 และคุณ Karan ก็มาจริงๆแล้วก็คงโดนมนต์สะกดแห่ง TDD เข้าไป :)
หลังจากนั้นอีกไม่นานก็ได้รับเทียบเชิญจากคุณ Karan มาว่าสนใจจะเชิญขบวนการมนุษย์ไฟฟ้าห้าสีไปเสวนาเรือง TDD ที่บริษัท GoSoft หนึ่งครั้ง เมื่อได้รับเทียบเชิญมาเราเหล่ามนุษย์ไฟฟ้ามีหรือที่จะบอกว่า “ไม่” มีแต่อยากจะไปเลยซะเดี๋ยวนั้นแต่เนื่องจากเป็น Session ที่เป็นทางการมากเราเหล่ามนุษย์ไฟฟ้าจึงต้องเตรียมกันไปเยอะเพื่อการจัดหนักโดยรายการของที่เตรียมไปมีดังนี้

  • เพื่อให้เห็นภาพในองค์รวมถึงประโยชน์ของ TDD เราต้องเข้าใจแอจไจล์ก่อนแบบกว้างๆ
  • จากนั้นก็มาเข้าใจ eXtreme Programming ที่ถือว่าเป็น Method หลักหนึ่งในแอจไจล์
  • เราก็เขียนโค้ดกันทั้งบ่ายเพื่อให้เห็นว่า TDD ทำได้จริงและมันส์จริง โดยโจทย์คือเราจะเขียนระบบฝากเงินโอนเงินแบบง่ายๆที่ค่อยๆเพิ่ม requirement เข้ามาทีละนิดละนิด ค่อยๆให้ Test ไป Drive ให้เกิด Code
  • สุดท้ายเราปิดท้ายด้วย AOP ท่ามหัศจรรย์
  •  

    เช้าวันงานผู้คนมากมายเดินทางมาเข้า session นี้เยอะจริงๆมีมาจากหลายแผนกมาก Developer, Project Manager, Quality Control, System Integration ทำให้เราเห็น Skill ที่หลากหลายของพนักงานที่นี่นอกจากนี้พนักงานที่นีีเองก็น่ารักเป็นกันเองมาก นอกจากนั้นมีบางกลุ่มเข้าเส้นมากนั่งเขียนโค้ดตามตัวอย่างแบบไม่ดูเฉลย เล่นเอาทีมงานประทับใจกันไปมากมายและอีกสิ่งหนึ่งที่สำคัญคือ spirit งานเกือบทุกอย่างนี่ทำให้ 7-11 นั้นเป็นคนไทยทำทั้งหมดทำเองกับมือขนาดของบริษัทไม่ได้ใหญ่ไปกว่าแผนกไอทีของบรฺาัทใหญ่ๆเลยแต่ที่นี่ลงมือเองทำเองดังนั้นสิ่งที่เราชาวมนุษย์ไฟฟ้าได้กลับมาคือโจทย์และคำถามจากคนที่เจอปัญหาจริงๆซึ่งเป็นเรื่องที่สนุกมากๆครับ
    แต่เนื่องด้วยเวลาอันจำกัดก็ทำให้เนื้อหาบางส่วนต้องถูกโยนใส่เครื่องเร่งเวลาบางคนอาจจะตามไม่ทันก็ขออภัยไว้ ณ ที่นี้ด้วยครับหวังว่าคงจะมีโอกาสกลับไปเติมส่วนที่ขาดให้เต็มได้ในอนาคตนะครับโดยเฉพาะเรื่อง Working With Legacy Code :)

     

    Let’s not forget to refactor

    ที่ผ่านมาเราเขียนโค้ดไปเพียบๆแต่ยังไม่ได้ refactor เลยเพราะอะไรก็เพราะโค้ดเราเมพส์ขนาดหาที่ปรับปรุง ไม่ได้ อาจจะเป็นอย่างนั้นหรืออาจเป็นเพราะไอ้ที่เราโค้ดไปนั้นมันง่ายจัดไม่มีอะไรซับซ้อน แต่ก่อนที่เราจะทำ อะไรไปมากกว่านี้เราควรหันกลับมาดู test code ของเราก่อนมันแปลกๆอยู่หลายจุด เรามาดูกันเลยว่าตรง ไหนที่น่าจะโดนเปลี่ยนบ้าง โค้ดด้านล่างคือ test ที่เราเพิ่งเขียนเสร็จกันไป
    Listing 2.12 Our test code up to this point—can you spot anything to refactor?

    public class TestTemplate {
       @Test
       public void oneVariable() throws Exception {
          Template template = new Template("Hello, ${name}");
          template.set("name", "Reader");
          assertEquals("Hello, Reader", template.evaluate());
       }
       @Test
       public void differentTemplate() throws Exception {
          template = new Template("Hi, ${name}");
          template.set("name", "someone else");
          assertEquals("Hi, someone else", template.evaluate());
       }
       @Test
       public void multipleVariables() throws Exception {
          Template template = new Template("${one}, ${two}, ${three}");
          template.set("one", "1");
          template.set("two", "2");
          template.set("three", "3");
          assertEquals("1, 2, 3", template.evaluate());
       }
       @Test
       public void unknownVariablesAreIgnored() throws Exception {
          Template template = new Template("Hello, ${name}");
          template.set("name", "Reader");
          template.set("doesnotexist", "Hi");
          assertEquals("Hello, Reader", template.evaluate());
       }
    }
    

    ลองตั้งสติกันดูสิว่ามีอะไรต้องแก้ไขไหมเช่นมี Duplication. Semantic redundancy. หรืออะไรก็ ตามที่น่าจะโดนรือ จดไว้เดี๋ยวเรามาดูกันว่าเราคิดตรงกันไหม

    Potential refactorings in test code
    หลังจากนั่งทางในอยู่สักพักเราจะพบจุดที่ต้องแก้สองสามจุด เริ่มจาก Template คลาสที่เรา test มันอยู่เรา ควรจะแยกมันออกมาเป็น instance variable แทนที่เราจะประกาศมันร่ำไป ตัวที่สองที่จะต้องถูกแก้คือ evaluate mothod ที่ถูกเราเรียกจิกใช้งานเยี่ยงทาสเพื่อส่งเข้าไป assertEquals ส่วนที่สามคือเรา initiate Template class ด้วย text เดิมๆสองที่ซ้ำกัน สิ่งเหล่านี้ต้องถูกกำจัดออกไป
    อย่างไรก็ตามเราใช้ template text สองตัวที่แตกต่างกัน เราควรจะทำเยี่ยงไรดี เราควรจะสร้าง instance ขึ้นมาสองตัวเลยไหมถ้าใช่เราก็ต้องแยก class ออกมาเป็นสอง class เพื่อให้แต่ละคลาสมีหน้าที่รับผิดชอบแต่ละ fixture ที่ชัดเจน

    โน๊ต: สำหรับบางคนที่ไม่ชินกับคำว่า fixture จริงๆแล้วมันไม่มีอะไรเลยมันเป็นชุดของ object ( state ) ที่เรา initial มันขึ้นมาเพื่อใช้สำหรับทดสอบ สำหรับ JUnit นั้น fixture เป็นชุดของ instance variable และ state ของ test class instance อย่างไรก็ตามเราจะพูดคุยกันเรื่อง fixture ในบทที่ 4

    อย่างไรก็ตามทางออกอีกทางคือการแยก class ด้วยการใช้ TestTemplate ซึ่งเราน่าใช้วิธีที่สองนี้กัน เดี๋ยวเรามาดูกันก่อนว่าทำยังไงเหตุผลเดี๋ยวจามมา

    Removing a redundant test
    ส่วนต่อไปที่ยังซ้ำกันอยู่คือการซ้ำกันอยู่ของ oneVariable, differentTemplate และ multipleVariables ดังนั้นสิ่งแรกที่เราจะทำคือ ถล่ม single-variable ให้กระจุยออกไปก่อน จากนั้นเราจะเพิ่ม test เข้าไปเพื่อให้ มันตรวจสอบได้ว่าเรายังสามารถ set ค่าให้ตัวแปรและ re-evaluate ค่าของ template ได้
    ที่เท่กว่านั้นเรายังสามารถกำหนดให้ unknownVariablesAreIgnored ไปใช้ template เดียวกับ multipleVariables. หลังจากแก้ไขไป test template แรกเราจะมีหน้าตาเป็นแบบนี้
    Listing 2.13 Test code after removing a redundant test and unifying the fixture

    import org.junit.Test;
    import org.junit.Before;
    import static org.junit.Assert.*;
    public class TestTemplate {
    
    private Template template;
    
       @Before
       public void setUp() throws Exception {
          template = new Template("${one}, ${two}, ${three}");
          template.set("one", "1");
          template.set("two", "2");
          template.set("three", "3");
       }
    
       @Test
       public void multipleVariables() throws Exception {
          assertTemplateEvaluatesTo("1, 2, 3");
       }
    
       @Test
       public void unknownVariablesAreIgnored() throws Exception {
          template.set("doesnotexist", "whatever");
          assertTemplateEvaluatesTo("1, 2, 3");
       }
    
       @Test
       private void assertTemplateEvaluatesTo(String expected) {
          assertEquals(expected, template.evaluate());
       }
    }
    

    เป็นไงครับหล่อขึ้นเป็นกอง Test Template ที่เราลดความซ้ำซอนลงไป ทำให้ test ของเรามีแต่งานที่มันจะ ต้องทำเท่านั้น ต่อไปเราจะเริ่มเดินหน้าเพื่อเพิ่ม feature เข้าไปให้ Template ของเราแต่ก่อนจะไป เราน่าจะไปจัดการเรื่อง Error Handling กันก่อนเพราะระบบของเราจะเริ่ม ซับซ้อนมากขึ้้นแล้ว

    Breadth-first หรือ depth-first

    ส่วนต่อไปที่เราจะทำคือเราจะหากระบวนท่าในการจัดการปัญหาที่เราเผชิญหน้าอยู่ ดังนั้นเราควรจะย้อนวันเวลากลับไป สมัยเป็นเกรียนมหาลัย ตอนที่เรานั่งหลับในวิชา Data Structure ถ้ายังจำได้เราจะพบว่าการ transverse tree สามารถทำได้มากกว่า 1 วิธีคือ breadth-first และ depth-first วิธีการนี้ใช้ได้กับ TDD เช่นกันตามรูป 2.3 และ 2.4 คือ
    ถ้าเราตัดสินใจแก้ปัญหาด้วยกระบวนท่า breadth-first ผลคือเราจะเขียน test เพื่อทดสอบการทำงานของ interface ของคลาส Template โดยเราจะ fake การทำงานข้างในมันทั้งหมดและหลัจากนั้นเราจะค่อยๆเขียน test เพื่อทดสอบการ ทำงานของ function ข้างในทีละตัวไปตามภาพ 2.3 มองจากซ้ายไปขวา

    ภาพ 2.3

    ภาพ 2.4
    ในทางกลับกันถ้าเราเลือกไปท่า depth-first เราจะ test-implement จากระดับล่างมาก่อนดังนั้น เราจะต้องเขียนรายละเอียด ของ template parsing logic ก่อนแล้วตามมาด้วยการเขียน rendering แล้วสุดท้ายจะกระโดดขึ้นไปเขียน Template Engine เป็นที่สุดท้าย
    ในกรณีการทำงานของเราเรื่อง template engine ที่ต้องจัดการกับ “Hello ${name}” นั้นเราสามารถที่จะ fake การทำงานระดับล่าง(parsing template) ไว้ได้ก่อนแล้วหลังจากนั้นเราค่อยๆเพิ่มความสามารถของมันเข้าไปทุกครั้งที่ เราเพิ่ม test เข้าไปดังนั้นเราเลือก breadth-first แล้วจะทำยังไงล่ะนี่
    Faking details a little longer
    สิ่งที่เราต้องทำคือเราจะ Fake รายละเอียดดังนั้น อันดับแรกเราต้องหาที่สำหรับเก็บ Template และ ค่าตัวแปรก่อน สิ่งที่เราต้องทำคือหาวิธีแทนค่าเพื่อให้ผ่าน test ไปก่อนโดยเราจะทำได้ตามนี้

    public class Template {
       private String variableValue;
       private String templateText;
       public Template(String templateText) {
          this.templateText = templateText;
       }
       public void set(String variable, String value) {
          this.variableValue = value;
       }
       public String evaluate() {
          return templateText.replaceAll("\\$\\{name\\}", variableValue);
       }
    }
    

    เมื่อเราใส่ test นี้เข้าไปแน่นอนว่า แดง อีกแล้วเพราะอะไรเราคงเห็นกันจะจะอยุ่ว่า Reg-Ex ของเรามันจ้องจะหา ${name} อย่างเดียวถ้าเจออย่างอื่นมันจะออกอาการไม่ตอบสนองทันที ดังนั้นคำถามต่อไปคือเราจะรับมือกับโจทย์ข้อนี้ยังไง
    ทางออกแรกที่คิดออกคือเขียนแบบ search-and-replace ไล่ไปเป็นคู่ๆ แต่การเขียนแบบนี้อาจมีปัญหาที่ บางอย่างคือมันอาจจะช้าและอาจมีปัญหากับตัวแปรที่เราต้องการเข้าไปแทนค่าที่มีค่าเป็น ${variable} แต่คงยังไม่มีทางออกไหนที่ดีไปกว่านี้อีกแล้ว ณ ตอนนี้เราจะเลือกทางนี้ไปก่อนเพราะเราต้องการเดินไปข้างหน้า แต่อย่างไรก็ตามเพื่อให้แน่ใจว่าสิ่งที่เราจะ test นั้นสามารถทำงานได้ถูกต้องเราสามารถเขียนออกมาเป็นประโยคได้ดังนี้
    Evaluate template “${one}, ${two}, ${three}” with values “1”, “${foo}”, and “3”, respectively, and verify that the template engine renders the result as “1, ${foo}, 3”.
    การเขียนออกมาเป็นประโยคแบบนี้จะช่วยให้เราสามารถมีสมาธิอยู่กับสิ่งที่เราทำความคิดเราจะไม่กระโดด ข้ามไปข้ามมา โดยผลของการใช้วิธีการ search-and-replace จะทำให้เราได้โค้ดออกมาใหม่เป็นแบบนี้
    New and improved details

    import java.util.Map;
    import java.util.HashMap;
    import static java.util.Map.Entry;
    public class Template {
       private Map<String, String> variables;
       private String templateText;
       public Template(String templateText) {
          this.variables = new HashMap<String, String>();
          this.templateText = templateText;
       }
       public void set(String name, String value) {
          this.variables.put(name, value);
       }
       public String evaluate() {
          String result = templateText;
          for (Entry<String, String> entry : variables.entrySet()) {
             String regex = "\\$\\{" + entry.getKey() + "\\}";
             result = result.replaceAll(regex, entry.getValue());
          }
          return result;
       }
    }
    

    และแน่นอนว่าเรา test ผ่านแล้ว และสิ่งต่อไปที่เราจะทำคือการ Refactor แต่อย่างไรก็ตาม เรายังไม่เห็นว่าโค้ดจะสามารถถูกปรับไปได้มากกว่านี้ ดังนั้นจะขอข้ามไปทำอย่างอื่นก่อนจะดีกว่า
    ■ เราต้อง evaluate template “Hello, ${name}” ด้วยการไม่ส่งค่า name มาให้และเราควรจะได้รับ MissingValueError
    ■ เราต้อง evaluate template “Hello, ${name}” ด้วยการส่งค่า “Hi” และ “Reader” แต่เนื่องจากเราส่งค่าตัวแปรไปเกินสิ่งที่เราควรได้รับกลับมาคือ “Hello Reader” เท่านั้น
    ■ ข้อนี้เป็นข้อที่เราจดไว้ก่อนเขียนโค้ด search-and-replace โดยเราจะ evaluate template ที่เป็น
    “${one}, ${two}, ${three}” ด้วยค่า “1”, “${foo}”, and “3” เราก็ควรจะได้ “1”, “${foo}”, and “3”

    Testing for a special case
    โค้ดส่วนต่อไปคือเราจะ test สามรายการด้านบนที่เรา list ไว้โดยหน้าตา test จะเป็นแบบนี้

    @Test
    public void unknownVariablesAreIgnored() throws Exception {
       Template template = new Template("Hello, ${name}");
       template.set("name", "Reader");
       template.set("doesnotexist", "Hi");
       assertEquals("Hello, Reader", template.evaluate());
    }
    

    และแน่นอนผ่าน ฉลุย แต่หน้าที่ของเรายังไม่จบและสิ่งที่เราต้องทำต่อไปคือ Refactoring นั่นเอง

    เลือก test แรก

    เกริ่น นำรำวงมานานหลายตอนมาถึงตอนนี้เป็นตอนที่จะได้เห็นโค้ดจริงๆสักที โดยอ้างถึงตอนที่แล้วเราตั้งใจกันว่าจะเขียน สิ่งที่เรียกว่า template engine ดังนั้นเพื่อให้สอดคล้องกับแนวคิดเรื่อง TDD นั้นเราจะค่อยๆทำไปทีละนิดเราจะไม่ทำที เดียวหมด ดังนั้นสิ่งแรกที่เราจะทำคือการเน้นไปในเรื่องของ business logic ของตัว template emgine ก่อนโดยที่ให้ตัด ส่วนอื่นๆทิ้งไปก่อนแล้ว template engine คืออะไร

    “The template engine we’re talking about needs to be capable of reading in a template,which is basically static text with an arbitrary number of variable placeholders mixed in. The variables are marked up using a specific syntax; before the template engine is asked to render the template, it needs to be given the values for the named variables.(ขี้เกียจแปล)”

    น่าจะเคลียร์นะครับ ดังนั้นสิ่งที่เราต้องทำคือเปลี่ยนมันให้เป็นชุดของ test และหลังจากนั้นเลือกมาหนึ่งอันเพื่อ implement

    2.2.1 Creating a list of tests
    อย่างไรก็ตามก่อนที่เราจะมี list ของ test ได้เราต้องมี requirement ก่อนและไอ้ข้างล่างคือ requirement ที่เรามี

    ■ ระบบจะต้องแทนค่าตัวแปรต่างๆใน template เช่น ${firstname} และ ${lastname} ด้วยค่าที่เราต้องการ ณ จังหวะ runtime
    ■ ถ้าระบบพยายามส่ง email ออกไปโดยที่ยังมีตัวแปรบางตัวไม่ถูกแทนค่า จะต้องเกิด error ในบัดดล
    ■ ระบบจะไม่สนใจตัวแปรถูกส่งมาแต่ไม่สามารถหาได้ใน template file
    ■ ระบบรองรับ Latin-1 character set
    ■ ระบบรองรับ Latin-1 character set สำหรับการตั้งชื่อตัวแปร
    ■ และอื่นๆ

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

    ■ ถ้าเรามี tempalte แบบนี้ “Hello, ${name}” แล้วเราแทนค่า ${name} ด้วย “Reader” เราจะได้ stringที่มีค่าเป็น “Hello, Reader”.
    ■ ถ้าเรามี template แบบนี้ “${greeting}, ${name}” wแล้วเราแทนค่า “Hi” และ “Reader”, ผลที่ได้ควรจะเป็น “Hi, Reader”.
    ■ ถ้าเรามี template แบบนี้ “Hello, ${name}” แต่ไม่มีการแทนค่า “name” จะส่งผลให้เกิด MissingValueError.
    ■ ถ้าเรามี template แบบนี้ “Hello, ${name}” แล้วเราแทนค่า “Hi” และ “Reader” ให้กับตัวแปรที่ชื่อ “doesnotexist” และ “name”, ผลที่จะได้คือ “Hello, Reader”.
    ■ และอื่นๆ

    เห็น ความต่างไหมครัฟ ถ้าไม่เห็นก็ไปนอนก่อนนะครับเพราะมันโคตรจะต่างกันเลย เพราะ test จะเป็นสิ่งที่จับต้องได้มากๆ ภาษาที่ใช้ก็ชัดเจนจนเราไม่สงสัยว่าอะไรคือ “raise en error” เพราะมันคือการ throw exception นั่นเองและเราก็รู้ด้วยว่า message อะไรที่เราจะใส่ลงไปใน exception นั้นๆ ซึ่งจาก test เราจะใช้ “MessingValueError” แต่ไม่เจาะจงรายละเอียดของ message ดังนั้นด้วย test list นี้เรารู้ว่าเราต้องการอะไร ดังนั้นส่วนต่อไปคือเราจะต้องเลือกว่าอะไรคือสิ่งแรกที่เราจะเลือกทำ

    2.2.2 Writing the first failing test
    มาดูตัวอย่างกันโดยเราจะเลือก test ที่ง่ายที่สุดมาทำก่อนเพื่อความมั่นใจ
    ถ้าเรามี tempalte แบบนี้ “Hello, ${name}” แล้วเราแทนค่า ${name} ด้วย “Reader” เราจะได้ stringที่มีค่าเป็น “Hello, Reader”.
    และเพราะเราต้องการทำงานแบบ test first ดังนั้นสิ่งแรกที่เราจะเขียนคือ test ที่มีแค่โครงก่อนหน้าตาแบบนี้

    Listing 2.1 Creating a skeleton for our tests

    public class TestTemplate {
    }
    

    ต่อไปเราจะใส่ method ที่ทำหน้าที่แทนค่าหนึ่งค่า
    Listing 2.2 Adding a test method

    import org.junit.Test;
    public class TestTemplate {
       @Test
       public void oneVariable() throws Exception {
       }
    }
    

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

    Listing 2.3 Writing the actual test

    import org.junit.Test;
    import static org.junit.Assert.*;
    public class TestTemplate {
       @Test
       public void oneVariable() throws Exception {
          Template template = new Template("Hello, ${name}");
          template.set("name", "Reader");
          assertEquals("Hello, Reader", template.evaluate());
       }
    }
    

    เรา สร้าง Template คลาสก่อนเลยและใส่พารามิเตอร์เข้าไปเป็น String ที่ระบุรูปแบบของ Template จากนั้นเราเลือกที่จะใช้ method ชื่อ set เพื่อกำหนดค่า “Reader” ให้กับ “name” หลังจากนั้นเราก็ assert ค่าที่ได้จาก Template คลาสว่าตรงกับสิ่งที่เราต้องการหรือไม่
    อย่า เพิ่งไปไกลกว่านี้นะครับ หยุดก่อนแล้วดูว่าไอ้ที่เราเพิ่งเขียนไปนั้นตรงกับที่เราคิดไว้ไหม การใช้งานคลาส Template เป็นไปตามที่เราคาดหวังไหมนี่คือ “Programming by Intention” อยากให้สิ่งที่เราต้องการสร้างถูกใช้งาน อย่างไรจงเขียนไว้ใน test
    มา ถึงตรงนี้เราได้ test มาแล้วแต่เราจะเห็นว่า compiler จะบ่นเราว่าไม่มีคลาสชื่อ Template นะ ดังนั้นสิ่งแรกที่เราต้อง ทำคือเพิงมันเข้าไป จากนั้น error ยังไม่หายไปเพราะยังเหลืออีกสองสิ่งที่เรายังไม่ได้ทำเพิ่มนั่นก็คือ set(String, String ) และ evaluate() ดังนั้นเพื่อไม่ให้เป็นการเสียเวลาเราก็เพิ่มเข้าไปใน code ของเราเลย
    Listing 2.4 Satisfying the compiler by adding empty methods and constructors

    public class Template {
       public Template(String templateText) {
       }
       public void set(String variable, String value) {
       }
       public String evaluate() {
          return null;
       }
    }
    

    สุดท้ายเมื่อทุกอย่างเข้าที่หน้าที่ของเราคือการ run test :)
    Running the test
    run test ครั้งแรกแน่นอนว่า fail แน่นอนไม่ต้องเดาเพราะเรายังไม่ได้ใส่กลไกอะไรลงไป method เลยแม้แต่นิดเดียว เพราะเราจะค่อยๆทำไป หัวข้อก่อนหน้านี้เราทำแค่ให้มันไม่ แดง บน IDE ไปก่อน นี่เป็นขั้นตอนแรกที่มักทำกันเสมอเพื่อการก้าวเดินทีละขั้น ขั้นเล็กๆ ดังนั้นเมือเรา rub test ครั้งแรกมันต้อง fail ดังนั้นต่อไปเราจะเริ่มใส่กลไกให้มันทีละนิด

    A failing test is progress
    มา ถึงตรงนี้บางคนยังสงสัยว่านี่เราอำอะไรลงไป เรายังไม่ได้เขียนโค้ดส่วนที่เป็นกลไกการทำงานจริงๆที่เราต้องการจะ test เลย อย่าเพิ่งสงสัยอ่านตรงนี้ก่อนสิ่ง ที่เรามีในมือตอนนี้คือ test ที่สามารถบอกเราได้อย่างชัดเจนว่าเมื่อไหร่สิ่งที่เราทำจะสามารถเรียกได้ว่า เสร็จโดยหลัง จากนี้ไม่ช้าก็เร็ว แต่ไอ้ test ที่ว่ามันไม่ได้บอกเราเป็น % ว่า “ตอนนี้คุณสำเร็จไป 90%” หรือ “อีก 5 นาทีจะเสร็จ” อะไรเทือกๆนี้ไม่มีนะใน test แต่สิ่งที่เราจะรู้ได้คือเมื่อ test ทั้งหมด run แล้ว “ผ่าน” ดังนั้นเรารู้อยู่แล้วว่าเรายังไม่ได้สร้าง ใส่รายละเอียดลงไปใน Template คลาสอีกสองอย่างดังนั้นเราจะค่อยๆปรับ Template คลาสไปเรื่อยๆจนเข้าที่การ run test ในตัวอย่างที่ 2.2 ทำให้เรารู้ว่าเราได้ว่า null มาซึ่งมันไม่ตรงกับสิ่งที่เราคาดหวังไว้เำพราะมันควรจะได้ “Hello, Reader” มาแทนดังนั้นตอนนี้เราอยู่ในสถานะ fail

    2.2.3 Making the first test pass
    ก้าวต่อไปที่เราจะทำคือการทำให้ test ผ่านให้เร็วที่สุดเท่าที่จะทำได้ก่อน ดังนั้นตอนนี้เรามีโครงของ Template คลาสที่มีลักษณะแบบนี้

    public class Template {
    	public Template(String templateText) {
    	}
    
    	public void set(String variable, String value) {
    	}
    
    	public String evaluate() {
    		return null;
    	}
    }
    

    ดัง นั้นขั้นตอนนี้เป็นขั้นตอนการตัดสินใจที่สำคัญมาก คำถามคือเราจะผ่าน test นี้ไปได้อย่างไรให้เร็วที่สุด? เพราะถ้าเราต้องมานั่งเขียนโค้ดเพื่อทำ String Replacement สำหรับ “Hello, ${name}” คงจะใช้เวลาหลายนาทีอยู่ แต่เราต้องการเร็วกว่านั้นนี่ ดังนั้นทางออกมีไม่มาก ไม่มากจริง และอย่าเพิ่งตกใจ ทางออกคือแบบนี้ครับ
    Listing 2.6 Passing as quickly as possible with a hard-coded return statement

    public class Template {
    	public Template(String templateText) {
    	}
    
    	public void set(String variable, String value) {
    	}
    
    	public String evaluate() {
    		return "Hello, Reader";
    	}
    }
    

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

    2.2.4 Writing another test

    ตอน นี้เราติดอยู่ตรงที่เรา hardcode ค่าที่ได้จาก evaluate method ซึ่งมันผิดชัดๆแต่ถ้าวัดด้วย unit test แล้วมันถูกดังนั้นการที่เราจะแก้ไขเรื่องนี้ได้ก็คือเราต้องสร้าง test อีกตัวขึ้นมา

    public class TestTemplate {
    	@Test
    	public void oneVariable() throws Exception {
    		Template template = new Template("Hello, ${name}");
    		template.set("name", "Reader");
    		assertEquals("Hello, Reader", template.evaluate());
    	}
    
    	@Test
    	public void differentValue() throws Exception {
    		Template template = new Template("Hello, ${name}");
    		template.set("name", "someone else");
    		assertEquals("Hello, someone else", template.evaluate());
    	}
    }
    

    test ที่สองที่เราใส่เข้าไปเป็น test ปราบมาร เพราะมันจะตรวจว่า put(String, String) และ evaluate() ทำงานได้ถูกต้อง หรือไม่เพราะเราใส่ตัวแปรอื่นลงไปค่าที่ได้ต้องแปรเปลี่ยนตามไปด้วย ดังนั้น hardcode ที่เราเพิ่งใส่ลงไปไม่รอดแล้วต้อง โดนระเบิดไปเพราะมันจะไม่มีทางผ่าน test ที่สองได้
    การ เขียน test ในลักษณะนี้มีชื่อเรียกนะครับ เท่ด้วย เราเรียกมันว่า Triangulation (Triangulation เป็นศัพท์ที่มาจากวิชาการสำรวจทางภูมิศาสตร์ (Physical Survey) และการนำร่องในการเดินเรือหรือขับเครื่องบิน (Navigation) โดยการหาพิกัดตำแหน่งที่ต้องการโดยการวัดมุมระหว่างตำแหน่งที่อยู่กับจุด อ้างอิงที่ทราบพิกัดอย่างน้อย 2 จุด จุดตัดของมุมทั้ง 2 จะเป็นจุดพิกัดที่ทำให้เราทราบตำแหน่งที่อยู่ หากจุดอ้างอิงทั้ง 2 จุดไม่มีพิกัด ตำแหน่งที่จุดที่ต้องการจะไม่สามารถบอกจุดที่แน่นอนได้บอกได้เพียงความ สัมพันธ์ของตำแหน่งที่ต้องการกับจุดอ้างอิงทั้ง 2 เท่านั้น) เรามี test ที่สองขึ้นมาเพื่อช่วยกำหนดทิศทางที่ถูกต้องของ Tempate คลาสเรื่องการแทนที่ตัวแปร และการทำงานแบบนี้เราจะค่อยๆเดินไปข้างหน้าเราจะแก้ปัญหาเรื่อง over engineering ได้ไปแบอ้อมๆด้วยเช่นกัน เพราะเราไม่ได้เขียนโค้ดเผื่อ เราเขียนแค่พอดี แต่อย่างไรก็ตามการผ่าน test ที่สองนั้นเราก็ทำแค่พอดีเช่นกันดังนั้นเราจะได้ Template คลาสหน้าตาใหม่ออกมาแบบนี้

    Listing 2.8 Making the second test pass by storing and returning the set value

    public class Template {
    private String variableValue;
    	public Template(String templateText) {
    	}
    	public void set(String variable, String value) {
    		this.variableValue = value;
    	}
    	public String evaluate() {
    		return "Hello, " + variableValue;
    	}
    }
    

    เรา ทำให้ test ผ่านไปได้อีกครั้งและการผ่านครั้งนี้เราก็ลงแรงน้อยๆเท่าที่จำเป็นอีกเมือ นเดิม แต่เราก็เห็นกับตาอยู่ว่ามัน ไม่ดีเพราะยังมี hardcode อยู่ดังนั้นเราต้องทำ triangulation ต่อไปเพื่อผลักให้เราเดินไปข้างหน้าอีกนิดโดยเราจะ ถอด test ที่ชื่อ differentValue ออกแล้วใส่ test ตัวใหม่ลงไปโดยมันจะต้องรับมือกับ template แบบต่างๆได้มากกว่าคำว่า Hello เราต้องส่งอะไรเข้ามาก็ได้ตามที่เราต้องการ
    Listing 2.9 Applying triangulation for the static template text

    public class TestTemplate {
    	@Test
    	public void oneVariable() throws Exception {
    		Template template = new Template("Hello, ${name}");
    		template.set("name", "Reader");
    		assertEquals("Hello, Reader", template.evaluate());
    	}
    
    	@Test
    	public void differentTemplate() throws Exception {
    		Template template = new Template("Hi, ${name}");
    		template.set("name", "someone else");
    		assertEquals("Hi, someone else", template.evaluate());
    	}
    }
    

    เมื่อ เรา run test อ่ะแน่นอนว่ามันต้อง red เพราะเรายังมี hardcode อยู่ในโค้ดดังนั้นตอนนี้เราต้องเขียน parsing text แล้ว ดังนั้นเราอาจต้องกระโดดไปคุยเรื่อง parsing logic ก่อนแล้วเราค่อยวนกลับมาเรื่อง test ต่อ ดังนั้นถึงตรงนี้เราคง ต้องคุยกันเรื่อง breadth และ depth กันแล้ว

    เริ่มต้นกับ Test Driven Development

    ใคร เคยหลงทางในสนามบินบ้าง? คำตอบคือน่าจะยากมากเพราะอะไรเพราะในสนามบินเราจะพบกับการใช้ป้ายสัญลักษณ์ มากมาย ทุกครั้งที่เราเริ่มงงว่าจะเดินไปทางไหนเราจะเริ่มมองหาป้ายที่บอกทางไป Gate ของเราก่อนเลยเช่น “Gate 42: continue straight forward.” ตัวอย่างนี้สามารถเอามาประยุกต์ใช้กับการพัฒนาซอฟท์แวร์ในชีวิตประจำวันได้ เช่นกันลอง หลับตาคิดดูว่าถ้าเราอยู่ที่สนามบิน Heatthrow เราก้าวผ่านด่านตรวจคนแล้วพบป้ายใหญ่ๆที่เขียนว่า “โชคดี” แล้วก็ไม่เห็น ป้ายอะไรอีกเลยเราจะเดินไปทางไหน? หลงแน่นอน !!! ดังนั้นป้ายบอกทางเป็นสิ่งจำเป็นในสนามบินเช่นเดียวกันในโลกของการทำซอฟท์ แวร์เราพบว่าการทำ Test Driven Development ที่มีข้อ ปฏิบัติที่เรียบง่ายสามข้อนั้นเป็นเหมือนกับป้ายบอกทางหลายร้อยป้ายที่มี อยู่ที่สนามบินที่ช่วยนำเรา ไปในทิศทางที่ถูกต้องและสามารถไปถึงจุดหมาย ได้ตรงเวลาและอย่างที่เราได้อ่านกันมาหลายรอบแล้วว่าการทำ TDD คือการกลับการบวนการทำงานจาก design-code-test ที่ไม่สนุกเสียใหม่ให้เป็น test-code-refactor อันแสนสนุกแทนและ ใครจะเชื่อว่าไอ้กระบวนการอันแสนธรรมดาสามข้อนี้จะช่วยให้เราสร้างซอฟท์แวร์ ที่มีคุณภาพสูงออกมาได้อย่างที่เรา ไม่เคยทำได้มาก่อน นอกจากคุณภาพจะดีแล้วเรายังได้ระบบทีีถูกปรับเข้าหา requirement ตลอดเวลาทำให้ไม่หลงหรือพลาดไปจากสิ่งที่ลูกค้าต้องการ ค่าใช้จ่ายในการซ่อม defect ก็ต่ำกว่าเพราะมีกระบวนการตรวจสอบความถูกต้องที่ดีกว่า (automate test suite) นอกจากนี้มันยังช่วยให้เรามี productivity ที่สูงขึ้นอีกด้วยและเหนือสิ่งอื่นในการทำงานจะเต็มไปด้วยความมันส์
    ใน บทนี้เราจะได้เรียนรู้ต่อไปอีกว่าอะไรคือ TDD และอะไรคือกุญแจแห่งความสำเร็จของ TDD เราจะได้ลงมือเขียน test-first กันนิดหน่อยเพื่อให้เห้นภาพการทำงานแบบ TDD

    Figure 2.1 The TDD cycle is a three-step process of writing a test, making it pass by writing just enough production code, and finally improving the design by disciplined refactorings.
    เนื่อง จากขั้นแรกของการทำ TDD คือทำ test fail ก่อนนั่นคือการสร้างกระบวนการทดสอบพฤติกรรมของระบบที่เราอยากได้ ดังนั้นอย่ารอช้ามาลงมือทำกันแต่เหนือสิ่งอื่นใดเราน่าจะมารู้เรื่อง requirement กันก่อน

    2.1 จาก requirements สู่ tests
    จินตนาการ ดูครับว่าเรากำลังทำระบบย่อยของระบบที่เรียกว่า corporate collaboration โดยส่วนที่เราทำนั้นรับผิดชอบใน การทำอีเมล์เทมเพลตสำหรับช่วยเลขาผู้บริหารให้สามารถส่งข้อมูลที่สำคัญๆไป ให้ใครก็ได้แบบง่ายๆด้วยการคลิ้กสองครั้ง เออแล้วเราจะ test ยังไง? เราจะได้คำตอบหลังจากอ่านเนื้อหาของบทนี้จบแล้ว โดยสิ่งแรกที่เราต้องทำคือการแตก requirement ออกเป็นส่วนย่อยๆที่เล็กลงและจับต้องได้

    2.1.1 ระเบิด requirements
    เมื่อ เรามี requirement สิ่งแรกที่เรามักทำกันคือแตกมันออกเป็นส่วนเล็กๆเป็น “งานที่เราต้องทำ เราเรียกมันว่า task” เมื่อเราทำมันเสร็จแล้วมันจะเป็นส่วนที่เติมเต็ม requirement ของเรา เออเราคิดแบบนี้ไม่ได้นะเราต้องคิดอีกแบบดังนั้นลืมไอ้ของเก่าที่คิดไว้ก่อน แล้วคิดใหม่คราวนี้เราย่อยมันออกเป็นชุดของ test เล็กๆ เมื่อเราให้ผ่านได้เราจะเติมเต็ม requirement ของเราได้ “นึกออกไหมครับ” น่าจะพอได้นะ มันอาจจะยากใน ครั้งแรกแต่เดี๋ยวจะค่อยๆชินไปเอง
    “A mail template without any variable data is sent off as is.”
    “The placeholder for the recipient’s first name in a greeting should get replaced with each individual recipient’s name, respectively.”
    ค่อยๆทำไปเราจะได้ test ที่สามารถทดสอบพฤติกรรมของแอพพลิเคชั่นของเราได้เพื่อให้เราทำงานได้ง่ายขึ้นเราลองมาดูความแตกต่างระหว่าง Task กับ Test ก่อน จากตาราง 2.1 เราจะเห็นการเปรียบ เทียบระหว่าง task กับ test เรื่องหลักๆเลยคือการแตกงานออกมาเป็น Task แล้วเอามาเรียงกันเราจะไม่สามารถตรวจวัด ความคืบหน้าของงานได้เลย ในทางกลับกันถ้าเราแตกออกเป็น test แทนเราจะเห็นความต่อเนื่องและความก้าว หน้าของงานที่ทำได้อย่างง่ายดาย

    Table 2.1 Alternative decompositions of a mail-template subsystem

    เรา จะเห็นว่าการแตกงานออกมาเป็น test สุดยอดกว่าการแตกออกเป็น task เพราะอะไรเพราะ task นั้นคิดง่ายแต่ไม่ได้มี ความเชื่อมโยงอะไรกับงานเลยและก็ไม่ได้เป็นตัวนำทางที่ดีด้วยแถมดีไม่ดีทำ แล้วหลงอีกต่างหาก Task นั้นบอกแค่ว่าเราควรทำอะไร โดยทีี่ไม่บอกว่าเมื่อทำเสร็วแล้วเป็นยังไง แต่ไม่ต้องกลัวเดี๋ยวจะมีเนื้อหาที่ครอบคลุมและ อธิบายได้เรื่องนี้อีกในภายหลังแต่ก่อนอื่นเรามารู้สองเรื่องที่สำคัญๆก่อน

    2.1.2 What are good tests made of?
    มา ถึงตอนนี้เราได้รู้กันแล้วว่า test ดีกว่า task :) แต่ว่าไอ้ test ที่เราจะเขียนเราก็ต้องคิดให้ดีเหมือนกันว่าการเขียน test ที่ดีคืออะไร ถ้าเราลองไปศึกษาดูเราจะพบว่ามีการให้คำนิยามเกี่ยวกับการเขียน test ไว้มากมายและก็มีเทคนิคมากมาย ที่จะใช้สร้าง test อย่างไรก็ตามถ้าเรากลั่นสิ่งเหล่านั้นออกมาเราจะพบว่า test ใดๆก็ตามจะถือว่าเป็น test ที่ดีก็ต่อเมื่อมัน มีคุณสมบัติสองข้อต่อไปนี้

    ■ A good test is atomic.

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

    2.1.3 Working from a test list
    ดัง นั้นจุดเริ่มต้นของงานเราคือเราต้องมีชุดของ test ที่เราต้องการทำก่อนจากนั้นเราจะมาเลือกกันว่า test ไหนจะเป็นผู้ โชคดีที่จะถูกทำก่อนโดยมากแล้วตัวที่เราจะเลือกก่อนคือตัวที่เราคิดว่ามัน ง่ายที่สุดเพื่อให้เราเดินหน้าได้เร็วที่สุดโดยใช้ แรงน้อยที่สุด และเมื่อเราเลือก test แรกที่จะถูกทำแล้วสิ่งต่อไปที่เราต้องทำคือ “ลืม” ไปก่อนว่า test อื่นๆยังมีอยู่เราจะ โฟกัสอยู่ที่ test ตัวนี้ที่จะถูกทำหลังจากนั้นเราจะเริ่มเขียน test code แล้วก็ compile และ execute มันเพื่อดูมัน fail :) ก่อนที่เราจะเริ่มเขียน production code หลายคนก็ยังคงงงอยู่ว่าเป็นไปได้หรอที่เราจะเขียน test สำหรับ code ที่ยังไม่เคยมีอยู่? เรามาดูกันว่ามันทำกันยังไงและทำไม

    2.1.4 Programming by intention
    มัน ทำได้ไง? เราจะจินตนาการอะไร? ถ้าเราจินตนาการถึง production code จากมุมมองของ test มันจะเป็นการโกหก ตัวเองหรือป่าว? ใช่มันคือการโกหกตัวเองแต่มันเป้นสิ่งที่ดีเพราะการที่เราเริ่มเขียน test ก่อนจะทำให้เราจินตนาการถึง production code ที่ถูกใช้งานได้ง่ายมาก เพราะไม่งั้นจะ test ยาก :) การคิดในลักษณะนี้ทำให้เรามั่นใจได้ว่า production code ของเราจะถูกสร้างออกมาให้ใช้งานได้ง่ายเพราะโดยสามัญสำนึกแล้วเราจะ จินตนาการถึงสิ่งที่ค่อนข้างธรรมดา และเข้าใจง่ายเสมอ นี่คือพลังของจินตนาการ และการทำงานแบบนี้มีชื่อเหมือนกันนะครับ “Programming by Intention”
    ผล ของการทำงานตามแนวคิดของ Programming By Intension คือเราสามารถโฟกัสตัวเองไว้ที่จุดที่เราต้องการ test ขณะนั้นแทนที่เราจะด้น code มั่วไปเรื่อย นอกจากนี้ Programming By Intension ยังส่งผลให้โค้ด flow ขึ้นทำให้โค้ด อ่านง่าย ดังนั้นอย่ารอช้าตอนหน้ามาลองทำกันเลย

    เวิร์คแน่นอน(Making sure the software still works)

    จาก แนวคิดของ TDD ที่มุ่งเน้นว่าเราต้องสามารถสร้างซอฟท์แวร์ที่ทำงานได้จริงตั้งแต่วันแรกของ การทำงาน ไอ้ตอนคิดน่ะ ง่ายแต่ทำน่ะยากไม่ใช่เล่นเพราะเราจะเห็นว่าเราต้องแก้ไข design ตลอดเวลาทุกวัน มันมีคำถามว่าของที่เราใส่เข้าไปใหม่ นั้นไม่ไปป่วนโค้ดเดิมและนี่คือความน่ากลัวของการขยับโค้ด แบบที่ไม่มีกรอบหรือตัววัดที่ชัดเจนแต่เราไม่ได้ทำแบบนั้น เราทำ TDD เรามีเครื่องมือที่สามารถตรวจสอบความถูกต้องได้ทำให้เรามั่นใจได้ว่างานของ เรายังถูกต้องอยู่เสมอ
    การ ทำงานแบบนี้อาจจะดูขัดแย้งกับชาวบ้านตรงที่ คนทั่วไปอาจคิดว่า test ไม่น่าจะมาเป็นภาระขัดขวางการทำงานขนาดนี้ แต่อย่างไรก็ตามเราพบว่าการทำ test แบบ manual นั้นเป็นเรื่องที่หลอนมากและทำได้ช้ามากและเราพบเสมอว่าถ้าเรา ปล่อยให้ developer รับหน้าที่ test เราจะพบกับการ test แบบพิศดารคือมันจะ test เฉพาะสิ่งที่มันแน่ใจว่าผ่านแต่อะไรที่ test ยุ่งยากๆมันจะข้ามไปทำเป็นไม่เห็นดังนั้นถ้าเราคิดและทำแบบเดิดเราก็จะพบ ปัญหาเดิมๆ ดังนั้นเราจะเปลี่ยนใหม่ เปลี่ยนให้การ test เป็นแบบ automate ที่สุดเพราะต้องการแก้ปัญหาแบบเก่าๆเดิมๆ
    หลาย คนคงชินและคุ้นเคยกับการทำ regression มันเป็นเรื่องที่เกิดขึ้นเสมอในการพัฒนาซอฟท์แวร์เช่นถ้าเราเพิ่ม feature ใหม่เข้าไปแล้วระบบพัง นั่นหมายความว่าระบบเราถอยหลังจากสถานะที่ทำงานได้ดีกลับไปหาสถานะที่ไม่ สามารถทำงาน ได้เหมือนภาพประกอบที่ 1.11 การเกิด regression นั้นอยู่ดีๆมันไม่ได้เกิดขึ้นเองแต่เป็นเราๆเหล่าโปรแกรมเมอร์นี่แหละที่ เป็นคนทำให้เกิดขึ้นด้วยการใส่อะไรใหม่ๆลงไปในระบบ

    ภาพ 1.11 จากภาพจะเห็นว่า test harness จะทำหน้าที่เหมือนกรอบที่คอยกำหนดลักษณะของ code base เรา ดังนั้นเมื่อไหร่ก็ตามที่เราเพิ่มฟีเจอร์ใหม่เข้าไปใน code base แล้วทำให้กรอบแตกออก(test แล้วพัง) นั่นหมายความว่าเราทำอะไรผิดเข้าให้แล้ว

    แต่ด้วยความเป็นมืออาชีพเราต้องการบางอย่างที่สามารถเข้ามาช่วยเราและทำให้เรารู้ตัวเร็วที่สุดเท่าที่จะทำได้ว่าสิ่งที่เราูทำ อยู่นั้นนมันกำลังพังระบบเดิมของเราอยู่และสิ่งเดียวที่ช่วยเราได้คือ automate test โดยที่ automate test จะทำหน้าที่เหมือนผู้ตรวจการที่จะแจ้งเราทันทีที่เกิดข้อผิดพลาดขึ้นในการทำ งานของ test นั่นหมายความว่าเราทำจะทำ regression test อยู่เสมอทำบ่อยๆตลอดโปเจคเพื่อทำให้เรามั่นใจว่าข้อผิดพลาดเดิมๆจะไม่เกิด ขึ้นหรือไเมื่อเราแก้ไขระบบจะไม่ทำให้ข้อผิดพลาดใหม่เกิดขึ้นในจุดอื่น
    อย่าง ไรก็ตาม test harness นี้จำเป็นจะต้องมีคุณลักษณะพิเศษที่เราต้องพิจรณาเช่น test จะต้องทำงานอย่างง่ายๆเพราะถ้ามันทำยากเราจะท้อใจและอาจจะข้ามขั้นตอนการทำ ไปและเราก็ทำการ check in โค้ดเขาไปในระบบโดยหวังว่ามันจะไม่ไปกระทบส่วนที่มีอยู่แล้ว ข้อที่สองคือ test ต้องทำงานได้เร็ว ถ้ามันทำงานช้าเราจะไม่สามารถทำได้บ่อยๆและเมื่อเราไม่ตรวจสอบบ่อยๆก็มีความ เสี่ยงที่ระบบจะเกิดข้อผิดพลาดได้มาก
    อีก สิ่งที่ทำให้เราทำงานได้ง่ายขึ้นคือการรับ feedback กลับมาให้เร็วเป็นอีกปัจจัยที่ช่วยให้เราทำงานได้เร็วขึ้น feedback ที่เราได้จาก test ก็เป็นอีกเรื่องที่เราได้ประโยชน์เพราะยิ่งเรา run test บ่อยๆเราจะยิ่งรู้ว่าข้อผิดพลาดของเราอยู่ที่บริเวณ ไหนเพราะเรากำลัง test ส่วนเล็กแต่บ่อยๆแทนที่เราจะเขียนโค้ดไปสักสามชั่วโมงแล้ว test ทีนั่นแปลว่าเราต้อง test code บริมาณมหาศาลที่เราเพิ่มเข้าไปในระบบและถ้ามันพลาดขึ้นมาเราจะเริ่มงงว่าตรง ไหนกันแน่ที่มันผิด

    การรับ feedback อย่างรวดเร็ว
    บาง ครั้งบางหนเราจะพบว่าการ run test นั้นไม่ใช่เรื่องที่จะทำได้เร็วๆเพราะอะไรเพราะเราต้องเราโปรแกรมของเราไป ต่อ เข้า กับระบบภายนอกต่างๆเช่น database, share drive, web server หรือแม้กระทั่ง Internet ซึ่งระบบเหล่านั้นอาจจะทำ งานได้ช้านและส่งผลให้งานของเราช้าลงไปด้วยซึ่งการรอคอยที่นานจะส่งผลให้เรา ทำงานสะดุดไม่ต่อเนื่อง
    การ แก้ไขปัญหาเรื่องนี้คือเราควรจะ run sub-test แทนที่จะ run test ทั้งหมดเพราะเราสามารถทดสอบส่วนที่เราต้องการ ได้อย่างรวดเร็วแล้วปล่อยให้การ run test ทั้งหมดให้เป็นหน้าที่ของ Continuous Integration แทนเพราะเมื่อเราแยก งานเป็นส่วนเล็กๆแล้วเราก็จะสามารถทำงานได้เร็วมากขึ้น ว่าแต่ไอ้ Continuous Integration Server คืออะไรเอาแบบ บ้านๆถ้าเรามี Continuous Integration ไม่ได้แปลว่าเราทำ Continuous Integration เพราะ CI Server มันคือเซิร์ฟเวอร์ หนึ่งเครื่องที่มีหน้าที่สร้างรายงานออกมาให้เราดูว่าการ run test ทั้งหมดโดยภาพรวมเป็นอย่างไร แต่อย่างไรก็ตามถ้า developer ไม่ยอมเอาโค้ดกลับมาไว้ที่ Repository มันก็ไม่มีประโยชน์อะไรที่จะตั้ง CI Server ไว้

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

    เขยิบทีละนิด (Evolutionary Design)

    เนื้อหาแปลมาจาก Test Driven PRACTICAL TDD AND ACCEPTANCE TDD FOR JAVA DEVELOPERS จากบทความที่แล้วเราได้เห็นจังหวะของการทำ TDD มาบทนี้เราจะมาดูเรื่อง Evolutionary Design กัน มาจะกล่าวบทไปถึงแอจไจล์เสียเล็กน้อยว่าหนึ่งในแนวคิดหลักของแอจไจล์คือทีมต้องพยายามทำให้งานที่เราทำอยู่ในสภาพ ที่เรียกว่า deployable ได้เร็วที่สุด(ซึ่งมันจะสะท้อนไปสู่การทำงานของปัจเจกคือการทำงานควรจะเขยิบ ทีละนิด) และทีมจะต้องทำให้สภาพ deployable นั้นคงอยู่ตลอดจนจบโปรเจคหรือแบบเข้าใจง่ายคือถ้าเช้ามาเราทำงานอยู่สบายๆ อยู่ๆเจ้านายโทรมาว่าลูกค้าอยากดูเดโมเราต้องสามารถดึงโค้ดของเมื่อวานที่ stable แล้วออกมา build และ deploy ได้เลยอย่างรวดเร็วและด้วยแนวคิดนี้จำทำให้เรามั่นใจว่าเรามีงานที่พร้อมส่ง และทำงานได้จริงเสมอ(อย่างหล่อส์) โดยที่ของที่เรามีมันอาจจะไม่ครบถ้วนทุกฟีเจอร์ที่ลูกค้าต้องการแต่อย่าง น้อยก็มีอะไรที่ทำงานได้จริงๆนะ
    Continue reading

    จังหวะรัก test-code-refactor

    เนื้อหาแปลมาจาก Test Driven PRACTICAL TDD AND ACCEPTANCE TDD FOR JAVA DEVELOPERS
    จากบทความที่แล้วเราได้เห็นแล้วว่า TDD เป็นเทคนิคการเขียนโปรแกรม ย้ำเทคนิคการเขียนโปรแกรมที่ถูกสร้างขึ้นมาบน หลักการง่ายๆ
    จงเขียนโค้ดเพื่อ ซ่อม test ที่ fail เท่านั้น
    เขียน test ก่อนเสมอและหลังจากนั้นเราจะเขียน code เพื่อซ่อม test ซึ่งเป็นหลักการที่กลับด้านกับสิ่งที่เราเคยทำมา นั่นคือเราจะชินกับการ design จากนั้น implement design และสุดท้ายเรา test เพื่อหาบั๊กที่เราได้สร้างขึ้นในระหว่างที่เรา implement ตามภาพ 1.3

    ภาพ 1.3 TDD กลับด้านการทำงานแบบเดิมๆทีีมีลำดับ design-code-test. โดยการทำงานจะกลับด้านเป็นtest-code-design

    ภาพ 1.4 Test-code-refactor เป็นมนตราของ test-driven developers มันอธิบายวิธีการทำงานได้ชัดเจนกว่าและดูเท่กว่า

    เขียน test ก่อนตามด้วย code แล้วค่อย design หรือที่เรียกว่า “disgn afterward” มันดูงงๆแปลกๆไหมทำไม design ทำทีหลังอ่ะงง โรงเรียนไม่ได้สอนแบบนี้? จริงๆแล้วคำว่า design ที่ถูกใช้ในที่นี้ไม่ใช่คำว่า design ที่เราใช้กันมาแต่เก่า ดังนั้นเราจะไม่เรียกมันว่า design เราจะเรียกสัตว์ประหลาดตัวนี้ว่า refactor มันคือกระบวนการของการเปลี่ยน design ปัจจุบันให้เปลี่ยนไปเป็น design ที่ดีกว่าเก่าดังนั้นเมื่อเราเปลี่ยนชื่อมันไปแล้วภาพการทำงานของเราจะ เปลี่ยนไปเป็นภาพ 1.4 แทนคือ test-code-refactor โอวมันคือ magic การเปลี่ยนขั้นตอนการทำงานให้เป็น test-code-refactor จะช่วยให้ เราสร้างงานที่มีคุณภาพสูงขึ้นมากในภาพรวม โดยจะเริ่มที่ปัจเจคก่อนแล้วขยายไปที่ทีม โปรเจค และ องค์กร ดังนั้นก๋อนจะหลุดไปไกลกว่านี้เราควรมาเข้าใจการทำงานทั้งสามส่วนนี้ก่อน
    เขียน test ก่อน
    เมื่อ เราเริ่มลงมือเขียน test ในวัฏจักรของ TDD นั้นเราต้องพึงระลึกเสมอว่าเราไม่เขียนแค่ test นะ (งงดีไหมตกลงทำอะไรอยู่) จริงๆแล้วเรากำลังตัดสินใจเรื่องการออกแบบ API-มันคือ interface ที่เราจะใช้เพื่อเข้าไปใช้งาน function ต่างๆที่เรากำลังจะ test ดังนั้นการที่เราถูกบังคับให้เขียน test ก่อนเราจะถูกบังคับให้ คิดเสมอว่าไอ้ code ที่กำลังจะถูกสร้างนั้นจะถูกใช้งานยังไง เหมือนกับเรากำลังคิดว่าเราจะสร้าง jigswa ตัวต่อไปยังไงตามรูป 1.5

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

    ลอง จินตนาการถึงความยากในการสร้าง jigsaw ขึ้นมาหนึ่งตัวว่า มันจะออกมาหน้าตาเป็นไง ถ้าเราไม่รู้ว่าไอ้ตัวที่เราจะ สร้างขึ้นมามันจะไปต่อกับตัวไหน? จะง่ายกว่าไหมถ้าเห็นว่าเราจะสร้างเพื่อต่อเข้ากับอะไร และอย่างที่กล่าวไปแล้วว่าการเขียน test มันไม่ใช่แค่การเขียน test มันเป็นการออกแบบ interface ดังนั้นเราอาจจะเคยได้ยินเหล่าผู้เชี่ยวชาญเรื่อง user-interface เสวนาว่าการออกแบบ user interface นั้นมีความสำคัญขนาดไหนต่อคนใช้งาน ทำไมเรื่องเหล่านี้ไม่ค่อยมีการพูดถึงเมื่อเราลงมาคุยกันในระดับของการเขียนโค้ดเพราะคำถามคือโปรแกรมเมอร์เองก็ถือเป็นผู้ใช้ประเภทหนึ่งใช่หรือไม่? เราใช้ซอฟท์แวร์ในรูปแบบของ API ไง :) ดังนั้นแนวคิดเรื่อง test first จะนำมาซึ่งความเปลี่ยนแปลงอย่างใหญ่หลวง ยกตัวอย่างเช่นบางครั้งถ้าเรามีความจำเป็นที่จะต้องใช้ library ของใครสักคนแล้วเราเปิดดูเอกสารของ API นั้นเราจะพบว่า เราหาทางใช้มันไม่ถูก ไม่รู้จะใช้มันยังไง เรื่องนี้เกิดได้ยังไงมันเกิดขึ้นเพราะคนออกแบบ API นั้นไม่ได้ออกแบบมันจากมุมมองของคนใช้ แต่ออกแบบบมันจากมุมของโปรแกรมเมอร์ที่ต้องสร้าง API
    ใน การออกแบบ interface จะมีหลักสำคัญอยู่ 1 ข้อคือ การวัดผลเรื่องของประสิทธิภาพและความถูกต้องของการ design จะทำได้ก็ต่อเมื่อเราได้ลองใช้มันแล้วเท่านั้นและ ด้วยการเขียน test ก่อนเราจะมั่นใจว่าเราจะได้ code ที่มีประสิทธิภาพ, modular และ testable (ก็แหงละเขียน test ก่อน code มันจะไม่ testable ได้ไง ขยายความต่อได้ว่า ตราบใดที่ code นั้น testable มันจึงคงอยู่ได้ แต่ถ้ามันไม่ testable มันก็ไม่ควรจะอยู่ตรงนั้น มันต้องหายไป) TDD จะช่วยเราให้สร้างงานที่ฝรั่งเรียกว่า usable API
    และเนื่องจากการออกแบบ software นั้นไม่ใช่แค่เรื่องของการวาง โครงสร้างมันเป็นเรื่องของความเหมาะสมด้วยเพราะเราสร้างซอฟท์แวร์เพื่อตอบ สนองความต้องการในวันนี้ก่อน เราจะสร้างสิ่งที่เราต้องการใช้มันเท่านั้น ยกตัวอย่างให้เห็นภาพ ในการสร้างรถเราคงจะไม่ออกแบบเครื่องยนต์ให้มีวาวล์ สำรองไว้สักสองวาวล์เผื่อเอาไว้ใช้ในยามจำเป็นที่เราต้องการจะเร่งความเร็ว รถแบบกระทันหัน ถ้าเราสร้างซอฟท์แวร์ออก มาในลักษณะนี้เราอาจจะพบกับสิ่งที่เรียกว่า over engineering software มันคือการสร้างเผื่ออนาคตที่ไม่จำเป็น ทำไมเราต้องใส่ของที่เราไม่มีความจำเป็นต้องใช้วันนี้เข้าไปในสิ่งที่เราจะ สร้างเพื่อวันนี้ มันแพงเกินไป ดังนั้น TDD เป็นกระบวนทำงานที่ทำให้เรามั่นใจว่าสิ่งที่เราจะเขียนนั้นเป็นสิ่งที่เรา ต้องการ ณ วันนี้ ไม่ใช่เพื่อพรุ่งนี้ และ เมื่อวาน
    theme ที่เน้นไปที่เรื่องของปัจจุบันนี้ยังถูกฉายซ้ำเข้าไปที่ลำดับการทำงานขั้น ต่อไปคือคือ เขียนโค้ดแค่พอดี คือการเขียนแค่พอดีให้ test ผ่านเท่านั้นแล้วทำไมต้องใช้คำว่า แค่พอดี ก็เพราะ test ที่เราเขียนขึ้นมานั้นเป็น test ที่ fail มันเป็น test ที่ชี้ให้เห็นช่องว่างระหว่างสิ่งที่ code ทำอยู่ ณ ขณะนั้นกับสิ่งที่เราคาดหวังให้มันจะเป็นในอนาคต ซึ่งช่องว่างนี้เป็นช่องว่างเล็กๆที่เราสามารถปิดมันได้ภายในเวลาไม่กี่นาที นั่นหมายความว่า code ของเราจะต้องไม่อยู่ในสภาพที่มีช่องว่างเป็นเวลานานๆ
    อีก หนึ่งแนวคิดพื้นฐานของการทำ test-first development คือ test ที่เราสร้างขึ้นจะแสดงสิ่งที่เราต้องการจะ implement และเพื่อวัดความก้าวหน้าในการพัฒนาโปรแกรม เวลาเรา implement เราจะไม่สักแต่ code ไปเรื่อยๆโดยไม่สนใจ requirement แต่สิ่งที่เราควรทำคือความชัดเจนว่าเรากำลังมีความคืบหน้าดังนั้น เราต้องเขียน code ให้ผ่าน test เพื่อแสดงความคืบหน้าออกมาอย่างชัดเจน
    การ เขียนโค้ดแค่พอดีผ่านนั้นก็ไม่ได้ผิดอะไร เพราะจุดหมายหลักของเราคือทำให้ผ่าน test เร็วที่สุดเท่าที่จะทำได้ซึ่งนั่น หมายความว่าสิ่งที่เรา code ชุดแรกที่ออกมานั้นอาจจะยังไม่ดีที่สุด แต่มันก็โอเคแล้ว การปรับ code ให้ดีขึ้นนั้นจะถูกทำในกระบวนการต่อไป ซึ่งเราสามารถทำได้ด้วยความมั่นใจสูงมากเพราะเรามี test ที่เป็นเมือนกับหลัก ยึดความถูกต้องของฟังก์ชั่น เราจะแก้ยังไงก็ได้ตราบเท่าที่เรายัง test ผ่านซึ่งกระบวนการปรับปรุง code นี้เราเรียกว่า refactoring ซึ่งถือว่าเป็นกระบวนการสุดท้ายของ test-code-refactor เพราะขั้นตอนนี้เป็นขั้นตอนที่เราจะมองย้อนกลับ ไปว่าเราจะแก้ไขสิ่งที่เราทำไปแล้วให้ดีขึ้นกว่าเดิมได้อย่างไรบ้าง แต่ก็มีคำถามว่าเราไม่ทำ refactor ได้ไหม คำตอบคือได้แต่สิ่งที่เราจะได้มาคือ production ที่มี code อุบาทว์ซึ่งการมี code อุบาทว์ก็ไม่ผิดอะไรอย่างน้อยมันก็ test ผ่านแต่การมี code อุบาทว์มันไม่ส่งเสริมให้เรามีพลังในการทำงานต่อไปข้างหน้า ดังนั้นเพื่อให้ทุกอย่างดูสวยงามเราไม่ควรละเลยขั้นตอนของการทำ refactor อย่างเด็ดขาด
    ตอนต่อไปเราจะมาว่ากันด้วยเรื่องการ “เขยิบทีละนิด”