Diễn Đàn Tin Học
 Trang chủ      Tutorial Room      Diễn đàn      Liên hệ - Góp ý
Diễn Đàn Tin Học
Thông tin
Download
Tutorial Room
Sản phẩm
Thống kê
Hiện có 10 người đang trực tuyến.
Google


Diễn Đàn Tin Học » Tutorial Room » Lập trình » Software Engineering » Craftsman-Truyện dài nhiều tập

Craftsman 12 - Three Ugly Lines

  Thông tin      
  Chuyên đề   Lập trình - Software Engineering  
  Dịch giả   hnd  
  Bài gốc   http://www.vninformatics.com/forum/?action=msg&msg=1023492790#1023492790  
  Tựa gốc   Craftsman 12 - Three Ugly Lines  
 

Xin được thay mặt diễn đàn cảm ơn bạn về bài viết này.

 

 

Ba dòng xấu xí
Phần 12.
By Robert C. Martin


Tôi nghỉ giải lao trên đài quan sát. Khi lớp chắn bằng nước đá đi xuyên qua vùng phân tử dày cộm làm cho lớp nước đá nhập nhoè trong những làn chớp xanh và những mô hình chuyển biến khắp bề mặt của lớp chắn - làm tôi nhớ đến Bắc cực quang của trái đất.

Như thường lệ, Jerry đang đợi tôi sau buổi giải lao. Gã nhìn tôi và nói, "OK, hãy gởi một hồ sơ qua socket."

"Ông xem cuộc trưng bày Cherenkov -1- chưa?

"Tuyệt đẹp!" gã cười mỉm. Tôi đoán lâu lâu gã cũng nghỉ giải lao - nhưng thường thường gã đi đâu?

"OK," tôi nói. "Tôi sẽ viết phần test case." Phần đầu tiên tôi gõ là đoạn mã dùng để tạo hồ sơ được gởi qua socket. 

   public void testSendFile() throws Exception {
      File f = new File("testSendFile");
      FileOutputStream stream = new FileOutputStream(f);
      stream.write("I am sending this file.".getBytes());
      stream.close();
  }
 


"Tôi biết ông muốn tạo hồ sơ dữ liệu trong mã test hơn là phụ thuộc vào tình trạng chúng có hiện diện hay không," tôi nói.

"Ðúng thế," Jerry trả lời. "Nhưng mày có thấy mày bị mấy thứ lặp lại không?"

Tôi xem lại phần test và thấy ngay chúng tôi viết đoạn mã gần như trùng lặp với method testCountBytesInFile() mà chúng tôi đã hoàn thành trước giờ giải lao. "Chỉ có bốn dòng code mà thôi," tôi nói.

"Ðúng thế," Jerry đáp. "Nhưng sự trùng lặp nên được vứt bỏ ngay khi có thể được. Không thì mày sẽ ôm một mớ code khổng lồ đầy mập mờ và đầy lỗi."

"Ðược rồi," tôi trả lời, "sửa cái này dễ thôi." Tôi tỉa tót một hàm mới gọi là createTestFile() và thay đổi cả testCountBytesInFile() lẫn testSendFile() để gọi hàm này.
 

   private File createTestFile(String name, String content)
  throws IOException {

       File f = new File(name);
       FileOutputStream stream = new FileOutputStream(f);
       stream.write(content.getBytes());
       stream.close();
       return f;
  }
 


Tôi chạy thử cái test để chắc ăn là không làm hỏng gì cả, rồi tiếp tục viết phần test. Tôi biết nó cần giả lập main(), cho nên tôi gọi những hàm main() cần gọi. Thế rồi tôi thêm vào phần gọi cuối để gởi hồ sơ đi.
 

   public void testSendFile() throws Exception {
      File f = createTestFile("testSendFile", "I am sending this file.");

      c.setFilename("testSendFile");
      assertTrue(c.connect());
      assertTrue(c.prepareFile());
      assertTrue(c.sendFile());

}
 


"Tốt," Jerry gật đầu. "Mày liệu trước là mình sẽ cần một method phía client có tên là sendFile().

"Ðúng vậy," tôi nói. "Method này sẽ gởi hồ sơ nào được chuẩn bị trước."

Tôi trở lại với phần test và bị trở ngại. Làm sao tôi kiểm nghiệm được hồ sơ tôi tạo ra và "gởi đi" thật sự được gởi đến server trong khi chúng tôi chẳng có hồ sơ nào? Phải chăng tôi cần viết cả phần server trước khi tôi có thể kiểm nghiệm chuyện này? Tôi định test gì vậy nhỉ?

Tôi bực dọc ngồi yên trong khi Jerry nhìn tôi chờ đợi. Thế rồi khi tôi xoay qua và giải thích điểm khó khăn. Gã giải thích "Không, mày không cần phải viết cái server," "Chúng ta chỉ test mỗi khả năng gởi hồ sơ của client, chớ chẳng phải khả năng nhận hồ sơ của server."

"Nhưng làm sao tôi gởi hồ sơ trong khi chẳng có server để nhận?"

"Mày có thể tạo ra "stub" server chỉ làm tối thiểu công việc mày cần thôi," Jerry trả lời. "Nó chẳng cần phải thực sự nhận hồ sơ - nó chỉ tiếp báo là mày đã gởi hồ sơ đúng cách."

"Hừm... như thế này chăng?"


         assertTrue(server.fileReceived);


"Như vậy được rồi," Jerry gật đầu. "Bây giờ làm cho cái test đạt đi." Tôi nghĩ về vấn đề này và nhận ra nó không quá khó, thế nên tôi viết một cái server giả chẳng làm gì hết:
 

   class TestSMCRServer implements SocketServer {
      public boolean fileReceived = false;
      public void serve(Socket socket) {
      }
  }
 


Jerry nói, "À, lại thêm trùng lặp!" Tôi xem lại và thấy trước đoạn ngắt, chúng tôi đã ứng hiệu server giả tương tự trong method testConnectToSMSRemoteServer() - thế nên tôi loại bỏ nó.

Thế rồi tôi bắt đầu phần server với method SetUp() trong phần test và đóng nó bằng method TearDown(). Trước khi mỗi method của test được gọi, server khởi động; khi method của test trả ngược về, nó đóng lại.
 

   protected void setUp() throws Exception {
      c = new SMCRemoteClient();

      server = new TestSMCRServer();
      smc = new SocketService(SMCPORT, server);

}

 protected void tearDown() throws Exception {

      smc.close();

}
 


Cuối cùng tôi viết method giả sendFile() trong SMCRemoteClient:


public boolean sendFile() {
      return false;
}


Mấy cái test bị hỏng. Tôi thở dài. "Làm gì bây giờ?"

"Gởi cái hồ sơ đi," gã ra lệnh.

"Chỉ mở hồ sơ ra và tống nó qua socket sao?" Tôi hỏi.

"Không, có lẽ mình cần cho server biết để tiếp nhận hồ sơ - cho nên hãy gởi một thông điệp đơn giản và gởi tiếp theo đó phần hồ sơ." Jerry thay đổi SMCRemoteClient như sau.

"Ðầu tiên, mình cần lấy cái "stream" ra từ socket," gã nói.
 

   public boolean connect() {


     boolean connectionStatus = false;

     try {

    Socket s = new Socket("localhost", 9000);
    is = new BufferedReader(new InputStreamReader(s.getInputStream()));
    os = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
    connectionStatus = true;

    } catch (IOException e) {

       e.printStackTrace();
       connectionStatus = false;

        }

       return connectionStatus;

}
 


"Rồi," Jerry tiếp tục, "để đọc được hồ sơ phải có sẵn."
 

   public boolean prepareFile() {

    boolean filePrepared = false;

    File f = new File(itsFilename);
    if (f.exists()) {
          try {
             itsFileLength = f.length();

        fileReader = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
        filePrepared = true;

          } catch (FileNotFoundException e) {

        filePrepared = false;
        e.printStackTrace();

        }
        }

      return filePrepared;

}
 


"Sau cùng," gã nói, "chúng ta có thể gởi hồ sơ."
 

   public boolean sendFile() {
       boolean fileSent = false;
         try {
         writeSendFileCommand();
         fileSent = true;
       } catch (Exception e) {
         fileSent = false;
       }
       return fileSent;
  }


 private void writeSendFileCommand() throws IOException {
    os.println("Sending");
    os.println(itsFilename);
    os.println(itsFileLength);
    char buffer[] = new char[(int) itsFileLength];
    fileReader.read(buffer);
    os.write(buffer);
    os.flush();
  }
 


"Ôi!" tôi nói. "Gõ ra cả đống mà chẳng thử nghiệm." Jerry nhìn tôi một cách ngượng ngịu. "Ừa, tao cũng run lắm." Gã nhấn nút test và phần test bị hỏng vì server.fileRecieved trả lại sai. "Ui cha!" Jerry nói. "Mình tránh mạch đập Muon -2- đó!"

"Thế," tôi nói, bắt chước giọng thật giống Dr. Watson, "bạn sắp sửa tiến hành hồ sơ với ba dòng. Dòng thứ nhất gồm string "Sending", dòng thứ hai gồm tên hồ sơ và dòng thứ ba gồm chiều dài của hồ sơ. Sau đó, bạn gởi hồ sơ theo dạng chuỗi ký tự."

"Rồi," Jerry mỉm cười. "Tao đã nói với mày trước buổi giải lao là mình cần chiều dài của hồ sơ rồi mà."

"Hừm, tôi đoán thế, Sherlock," tôi nói một cách miễn cưỡng.

"Bây giờ mình chỉ cần nhận hồ sơ từ server giả. Mày muốn thử một phát không?" Jerry hỏi. Tôi khá chắc nên phải làm gì nên đầu tiên tôi đổi cái test để chắc ăn chúng tôi có tên hồ sơ, chiều dài và nội dung hồ sơ:
 

   public void testSendFile() throws Exception {
     File f = createTestFile("testSendFile", "I am sending this file.");
     c.setFilename("testSendFile");
     assertTrue(c.connect());
     assertTrue(c.prepareFile());
     assertTrue(c.sendFile());

     Thread.sleep(50);
     assertTrue(server.fileReceived);
     assertEquals("testSendFile", server.filename);
     assertEquals(23, server.fileLength);
     assertEquals("I am sending this file.", new String(server.content));
         f.delete();

}
 


Kế tiếp tôi đổi cái server giả cho nó phân giải dữ liệu vào và bảo đảm thực tính:
 

   class TestSMCRServer implements SocketServer {

     public String filename = "noFileName";
     public long fileLength = -1;
     public boolean fileReceived = false;
     private PrintStream os;
     private BufferedReader is;
     public char[] content;
     public String command;


public void serve(Socket socket) {

       try {
       os = new PrintStream(socket.getOutputStream());
       is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
       os.println("SMCR Test Server");
       os.flush();
       parse(is.readLine());
       } catch (Exception e) {
       }

}


private void parse(String cmd) throws Exception {
     if (cmd != null) {
       if (cmd.equals("Sending")) {
         filename = is.readLine();
         fileLength = Long.parseLong(is.readLine());
         content = new char[(int)fileLength];
         is.read(content,0,(int)fileLength);
         fileReceived = true;
       }
     }
  }

}
 


Cuối cùng tôi điều chỉnh SMCRemoteClient.connect() để nó đợi thông điệp SMCR được gởi từ server giả:
 

   public boolean connect() {
                ...

     String headerLine = is.readLine();
     connectionStatus = headerLine != null && headerLine.startsWith("SMCR");

        ...
}
 


Tôi không gõ hết những thứ trên cùng một lúc. Tôi thay đổi từng bước nhỏ, chạy test giữa mỗi thay đổi. Tôi biết Jerry có ấn tượng tốt, đặc biệt vì gã còn bị quê chuyện thay đổi to lớn ở trên. Sau cùng, khi mọi test đều đạt, tôi cảm thấy hơi cha nội hơn một tí, tôi đánh liều bằng một nhận xét.

"Jerry," tôi nói. "Ðoạn code này xấu xí quá."

"Ý mày thế nào?" gã hỏi.

"Hèm, gởi ba dòng: tên hồ sơ, chiều dài và dòng "Sending"."

Jerry nhìn tôi một cách nhún nhường. "Cứ cho là mày biết cách hay hơn."

"Tôi nghĩ thế." tôi mỉm cười và bắt đầu gõ....

---------------

-1- Cherenkov display: thuộc nghiên cứu vật lý cao cấp. Một đề tài hết sức thú vị và được nhiều nhóm nghiên cứu quan tâm. Có một pdf phân tích Chrenkov display rất cụ thể ở: www.lip.pt/~varela/projfc/Showers/Auger-3.pdf. Ngoài ra còn có vô số tài liệu về vấn đề này trên Internet cho những ai thích đào sâu. [Back]

-2- Muon: Một "muon" là một phân tố không ổn định trong vùng phản xạ gần bề mặt trái đất. Nó có trọng lượng hơn 207 lần trọng lượng một electron và tồn tại trong cả thể dạng âm hoặc dương. [Back]
 

 

 

Các bài viết mới nhất
Bạn không được phép truy cập vào địa chỉ này!
[Download] - NT Password Recovery Bootdisk
Craftsman 18 - Slow and Steady
Quản lý MySQL Server sử dụng lệnh trên console
Memory-RAM - Một số thuật ngữ và kỹ thuật
Đưa chương trình vào đường dẫn hệ thống
Thay thế BIND với djbdns - phần 1
Phương pháp khôi phục lại password trong hệ thống Win2k/XP/2K3 (Support NTFS)
Cài đặt ActivePython 2.4 trên IIS
Sử Dụng Tiếng Việt Với LaTeX
Cài đặt 1 SMTP server tại nhà với Microsoft IIS
Linux - Vì sao sáng trên bầu trời CNTT
Biên dịch Linux kernel - phần 4
Tự học lập trình Borland Delphi
Thiết kế và Lập trình Web bằng ASP
Cài đặt PHP 4 trên IIS
Giới thiệu về XML-RPC
Sử dụng CSDL MySQL
Một chương trình download manager đơn giản
Giới thiệu - Sơ lược về ngôn ngữ PHP
Các bài viết liên quan
Craftsman 18 - Slow and Steady
Craftsman 17 - Call the Guards
Craftsman 16 - Excess Politesse
Craftsman 15 - Ess Are Pee
Craftsman 14 - Transaction Actions
Craftsman 13 - A Better Solution
Craftsman 12 - Three Ugly Lines
Craftsman 11 - Forget the Main()
Craftsman 10 - Iterations Unbound
Craftsman 9 - Dangerous Threads
Craftsman - 8 : Testing in Synch
Craftsman 7 - Socket Service2
Craftsman 6 - Socket Service
Craftsman 05 - Baby Steps
Craftsman 04 - A Test of Patience
Craftsman 03 - Clarity and Collaboration
Craftsman 02 - Crash Diet
Craftsman 01 - Opening Disaster
Quảng cáo
HOME | TUTORIAL ROOM | FORUM | CONTACT