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 กันก่อนเพราะระบบของเราจะเริ่ม ซับซ้อนมากขึ้้นแล้ว

2 comments

  1. biiterlove

    บอกตามตรงว่าอ่านบบความนี้แล้วผมรู้สึก สะเทือนใจครับ เพราะผมไม่ได้ refactor มานานมากครับ แล้วคาดว่า Technical Debt ตอนนี้ คงซื้อ MacBook Pro ได้แล้วหล่ะครับ ซ้ำหนักเข้าไปอีกคือ ไม่ได้เขียน Test ครับ พอจะมีคำแนะนำในการเริ่มต้น refactor code มั้ยครับพี่

  2. roofimon

    ผมเพิ่งได้หนังสือ “Working Effectively with LEGACY CODE” มาครับ น่าจะเหมาะนะ

Post a comment