Jean muốn tôi đi theo với bà ấy vào phòng đợi cho người hướng dẫn, nhưng tôi cần làm đầu óc mình thoải mái. Vậy nên, tôi đã cáo lỗi và đi ra chỗ đài quan sát hình cầu. Quả cầu sao như một dải màu sọc rực rỡ trải dọc trên nền trời. Đó là một đồng tâm với con tàu của chúng tôi, và vì vậy luôn luôn xuất hiện bên dưới chúng tôi bất cứ khi nào chúng tôi nhìn xuống nền nhà trong suốt của quả cầu. Sự quay của con tàu làm cho quả cầu ngôi sao trông giống như một dòng sông với những ngôi sao đầy màu sắc chảy chầm chậm phía dưới sàn.
Thật khó để tin rằng tôi chỉ vừa mới gặp Jean lần đầu tiên vào sáng nay. Không biết sao lại có cảm giác như là đã gặp rất lâu rồi. Khi tôi nhìn lại thời gian đó, tôi nhận ra rằng chỉ trong vài giờ chúng tôi làm việc cùng nhau, chúng tôi đã hoàn thành được một chút ít công việc, và với những gì đã qua, tôi đã cảm thấy thất vọng. Không hiểu sao, cách bà ấy làm việc khiến tôi cảm thấy như tiến độ đang rất chậm. Jean dường như lại nghĩ rằng tiến độ chậm là một điều tốt. Bà ấy đã nói với tôi nhiều lần về việc dành thời gian và sự cẩn trọng để xây dựng một phần mềm tốt nhất mà chúng tôi có thể làm. Tôi không thể không đồng ý với những gì bà ấy đã nói, và tôi cũng cực kỳ ấn tượng về những gì chúng tôi đã thực sự hoàn thành hôm nay, nhưng, tôi vẫn cảm thấy nó quá chậm.
Tôi đoán, trong các lớp học của tôi ở trên trường, tôi đã quen với việc viết mã thật nhanh và dành nhiều thời gian để gỡ lỗi. Không hiểu sao, việc gỡ lỗi dường như không tốn thời gian với tôi. Bằng cách nào đó, có cảm thấy như tôi làm rất nhanh. Nhưng khi tôi làm việc với Jerry, Jasmine và Jean, chúng tôi đã viết mã chậm hơn rất nhiều. Và chúng tôi đã viết tất cả những lần kiểm thử mà dường như chúng ngốn rất nhiều thời gian. Chúng tôi đã không còn dành nhiều thời gian để gỡ lỗi nữa, và không hiểu sao, điều này đã không làm cho tiến độ trông nhanh hơn. Nó có lẽ đã nhanh hơn, nhưng giờ thật khó nhọc.
Khi tôi lấy tuabin trở lại phòng làm việc của chúng tôi, tôi quyết tâm ngừng cảm giác thất vọng lại. Tiến độ của chúng tôi rất tốt, chất lượng của chúng tôi rất cao, và cảm giác thất vọng của tôi chỉ như là những cái túi cũ mà tôi cần vứt đi.
Khi tôi đến phòng làm việc, một đồng nghiệp trạc tuổi tôi đang ngồi ở chỗ của tôi. Không có bóng dáng của Jean.
“Xin chào”. Tôi nói. “Tôi có thể giúp gì cho bạn?”
“Ừm… Xin chào…ừm, tao là Avery. Jean nói tao nên làm việc với mày trong thời gian còn lại của ngày hôm nay.”
“Ồ. Bà ấy nói thế sao? Ừm…”
“Đúng vậy, bà ấy nói mày sẽ chỉ cho tao cách viết Unit Test.” “Ồ…ừm…thật sao? Mày không biết à?”
“Ừ tao biết chứ… ừm…Jason đã sa thải tao.”
“Jason là người hướng dẫn của mày à? Anh ta đuổi mày thật sao?”
“Ừ…ừm… Cũng không hẳn, anh ấy chỉ hỏi Jean rằng anh ta có thể ngừng làm việc với tao không, vậy nên Jean bảo tao làm việc với mày hôm nay.”
“Sao Jason lại làm vậy được chứ?”
“Tao làm việc trong giờ nghỉ trong khi Jason đã ra ngoài, và viết một loạt mã mà không có bất kỳ đoạn kiểm thử nào. Khi anh ấy trở lại, anh ấy chán không nói nổi và bảo tao xóa nó đi. Tao đã nổi cáu với anh ta và nói với anh ấy tao sẽ không đời nào xóa nó. Cho nên anh ta vừa bỏ đi rồi.”
Tôi cảm thấy hơi ngượng. Không phải ai cũng từng trải qua những thứ thế này sao? “Anh ta vừa đi?” Tôi hỏi.
“Ừ, mắt anh ta trừng hết cả mắt ra, mặt thì đỏ au, rồi ngay lập tức đi ra khỏi phòng làm việc. Điều tiếp theo tao biết là Jean đang bảo tao làm việc với mày trong khi bà ấy tìm cho tao một người hướng dẫn mới. Bà ấy nói mày sẽ hiểu sẽ hiểu thôi.”
Bây giờ tôi cảm thấy còn ngượng hơn. “Ừ, có lẽ, tao đoán là vậy. Mày bắt đầu thực tập từ khi nào?”
“Tuần trước, giống mày thôi. Tao đã làm việc với Jimmy, Joseph vào tuần trước và với Jason hôm nay. Tao thấy ổn khi làm với hai người còn lại, nhưng có điều gì đó về Jason làm tao thấy chán. Tao đoán tao cũng làm anh ấy thấy như vậy.”
“Tao nghĩ điều này cũng bình thường thôi. Vậy chúng ta bắt đầu làm chứ?”
“Tất nhiên rồi”
Tôi đã nói hắn ta về dự án SMCRemote. Tôi đã giải thích những gì Jean và tôi đã làm được trong vài giờ qua. Tôi cũng đã nói với hắn về phần mềm máy khách mà chúng tôi đã làm việc sáng nay và phần mềm máy chủ mà Jean và tôi đã tập hợp lại từ đó. Khi tôi nói xong, hắn nói rằng: “Có vẻ như cậu đã sẵn sàng để máy chủ chạy một trình biên dịch.”
“Đúng vậy, tao nghĩ thế. Nói thử xem, nếu tao viết một kiểm thử, mày sẽ làm thành công chứ?”
“Chắc rồi, tại sao không. Tao đoán tao sẽ phải làm quen với công cụ kiểm thử này.”
“Ừ, tao nghĩ mày nên vậy. Được rồi, đây là bài kiểm tra mà tao đang nghĩ đến”
public void testCompileIsRunInEmptyDirectory() throws Exception { File dummy = new File("dummyFile"); dummy.createNewFile(); CompileFileTransaction cft = new CompileFileTransaction("dummyFile"); dummy.delete(); CompilerResultsTransaction crt = SMCRemoteServer.compile(cft, "ls >files"); File files = new File("files"); assertFalse(files.exists()); crt.write(); assertTrue(files.exists()); BufferedReader reader = new BufferedReader(new FileReader(files)); HashSet lines = new HashSet(); String line; while ((line = reader.readLine()) != null) { lines.add(line); } reader.close(); files.delete(); assertEquals(2, lines.size()); assertTrue(lines.contains("files")); assertTrue(lines.contains("dummyFile")); }
“Ya!” Avery nói. “Được rồi, để tao xem nếu tao hiểu được nó. May đang tạo một tệp giả trống và tải nó vào một ComplierFileTransaction. Sau đó, mày biên dịch nó nhưng sử dụng lệnh ‘ls>files’ thay vì lệnh biên dịch thông thường. Mày muốn hàm biên dịch trả về một ComplierResultsTransaction. Mày muốn giao dịch phải có tệp chứa đầu ra của lệnh ‘ls’. Mày đọc tệp đó và đảm bảo rằng lệnh ‘ls’ không thấy gì ngoài tệp dummyFile và tệp files. Tao đoán điều này chứng tỏ rằng biên dịch được chạy trong một thư mục có nội dung duy nhất là dummyFile.”
Nhân vật Avery này quả không phải kẻ ngốc. “Chính xác, đó là cách tao thấy đoạn kiểm thử hoạt động. Mày có thể làm nó có kết quả thành công được không?”
“Jimmy nói với tao rằng tao nên luôn làm kiểm thử thất bại trước khi cố gắng vượt qua nó. Anh ta nói để làm điều dễ nhất có thể sẽ làm cho lần kiểm thử thất bại. Vì vậy tao đoán sẽ là một cái gì đó như thế này.”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { return new CompilerResultsTransaction(""); }
“Ờ, lần này thất bại vì không có tên tệp cho ComplierResultsTransaction. Nên chúng ta sẽ cần một tệp tin. Chúng ta lấy tệp tin đó bằng cách thực hiện trình biên dịch”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { executeCommand(command); return new CompilerResultsTransaction("files"); }
“Hừm, lần này không được vẫn vì nguyên nhân trên. Tệp files không tồn tại. Nhưng sao lại thế được nhỉ.”
Sau khi tìm hiểu, chúng tôi nhận ra rằng hàm Runtime.exec() không diễn giả lệnh > như là một lệnh điều hướng tệp tin. Để sử nó chúng tôi sửa đoạn kiểm thử như sau:
CompilerResultsTransaction crt = SMCRemoteServer.compile(cft, "sh -c \"ls >files\"");
“Được rồi, lần này kiểm thử không thành công ở dòng assertFalse (files.exists ()). Điều này là do chúng ta đang thực thi lệnh ls trong thư mục hiện tại. Chúng ta cần phải tạo một thư mục con và thực thi nó ở đó. ”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { File wd = makeWorkingDirectory(); //How do I execute the command in the working directory? executeCommand(command); return new CompilerResultsTransaction("files"); }
“Alphonse, tao có thể tạo thư mục làm việc với hàm mà mày và Jean đã viết. Nhưng làm thế nào để tao nhận được lệnh thực hiện trong thư mục đó. Dường như không có cách nào để nói cho Runtime.exec() biết thư mục nào để chạy.”
Avery và tôi đã tìm kiếm các tài liệu, nhưng chúng tôi không thể thấy bất kì cách hiệu quả nào để giả quyết vấn đề này. Sau đó, tôi có một ý tưởng.
“Avery, tại sao chúng ta không đặt tiền tố cho lệnh với một ‘cd workingDirection;’”
“Hừm. Nó có thể chạy được nhưng cũng có nghĩa là chúng ta đặt những thứ sh -c ở sai chỗ. Chúng ta nên đặt nó trong SMCRemoteServer.executeCommand. Tao cá rằng hàm Runtime.exec() không thể đối phó với nhiều lệnh và dấu chấm phẩy.”
“Có lẽ mày đúng. Ngay cả khi mày không làm như thế, hàm executeCommand vẫn là một nơi tốt cho các công cụ sh -c. Chúng ta hãy di chuyển nó”
public static boolean executeCommand(String command) { Runtime rt = Runtime.getRuntime(); try { Process p = rt.exec("sh -c \"" + command + "\""); p.waitFor(); return p.exitValue() == 0; } catch (Exception e) { return false; } }
“Giờ thì làm việc với cd”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { File workingDirectory = makeWorkingDirectory(); String wd = workingDirectory.getName(); executeCommand("cd " + wd + ";" + command); return new CompilerResultsTransaction("files"); }
“Và giờ, lại thêm một lần nó không thành công ở dòng new CompilerResultsTransaction (“ files ”). Và đó là lý do tại sao! Hàm đó không thực hiện trong thư mục con. Chúng ta cần thêm đường dẫn thư mục con vào tên tệp.”
return new CompilerResultsTransaction(wd + "/files");
“Không, nó vẫn không chạy được. Giờ nó thất bại vì ComplierResultsTransaction nghĩ rằng tên này bao gồm đường dẫn thư mục con, và hàm write đang cố ghi lại tệp trở lại vào thư mục con. ”
Tôi thở dài. “Cái này thật rắc rối.”
“Không,” Avery nói, “chúng ta chỉ phải chuyển thư mục con tới hàm khởi tạo CompilerResultsTransaction để nó có thể tải tệp mà không lưu trữ đường dẫn trong tên tệp.”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { File workingDirectory = makeWorkingDirectory(); String wd = workingDirectory.getName(); executeCommand("cd " + wd + ";" + command); return new CompilerResultsTransaction(workingDirectory, "files"); }
public class CompilerResultsTransaction implements Serializable { private FileCarrier resultFile; public CompilerResultsTransaction(File subdirectory, String filename) throws Exception { resultFile = new FileCarrier(subdirectory, filename); } public void write() throws Exception { resultFile.write(); } }
public class FileCarrier implements Serializable { private String fileName; private LinkedList lines = new LinkedList(); private File subdirectory; public FileCarrier(String fileName) throws Exception { this(null, fileName); } public FileCarrier(File subdirectory, String fileName) throws Exception { this.fileName = fileName; this.subdirectory = subdirectory; loadLines(); } private void loadLines() throws IOException { BufferedReader br = makeBufferedReader(); String line; while ((line = br.readLine()) != null) lines.add(line); br.close(); } private BufferedReader makeBufferedReader() throws FileNotFoundException { return new BufferedReader( new InputStreamReader( new FileInputStream(new File(subdirectory, fileName)))); } public void write() throws Exception { PrintStream ps = makePrintStream(); for (Iterator i = lines.iterator(); i.hasNext();) ps.println((String)i.next()); ps.close(); } private PrintStream makePrintStream() throws FileNotFoundException { return new PrintStream( new FileOutputStream(fileName)); } public String getFileName() { return fileName; } }
“Oa! Giờ nhìn nó đẹp hơn rồi đó. Nó không thành công ở dòng assertEquals (2, lines.size ()) của kiểm thử. Và một lần nữa nó khá rõ ràng lý do tại sao. Chúng ta chưa bao giờ viết tập tin đầu vào. Điều đó sẽ dễ sửa thôi! ”
“Không dễ đâu. Chúng ta phải chắc chắn rằng chúng ta viết nó vào thư mục con. ”
“Ah, đúng rồi! Chúng ta phải thêm workingDirectory vào hàm FileCarrier.write. Phát hiện hay lắm!”
public static CompilerResultsTransaction compile(CompileFileTransaction cft, String command) throws Exception { File workingDirectory = makeWorkingDirectory(); String wd = workingDirectory.getName(); cft.sourceFile.write(workingDirectory); executeCommand("cd " + wd + ";" + command); return new CompilerResultsTransaction(workingDirectory, "files"); }
public void write() throws Exception { write(null); } public void write(File subdirectory) throws Exception { PrintStream ps = makePrintStream(subdirectory); for (Iterator i = lines.iterator(); i.hasNext();) ps.println((String)i.next()); ps.close(); } private PrintStream makePrintStream(File subdirectory) throws FileNotFoundException { return new PrintStream( new FileOutputStream(new File(subdirectory,fileName)));
“Bang! Nó đây rồi! Giờ thì nó đã thành công rồi”
“Thật tuyệt!”
Tác giả: Robert C. Martin