Kỹ thuật tư duy lập trình craftman Nhật ký thân mến, 13 tháng 2, 2002. Hôm nay đúng là một ngày xui xẻo Tôi làm hỏng cả chuyện. Tôi rất muốn gây ấn tượng với các ngài cựu học việc ở đây nhưng rút cuộc chỉ làm rối tung cả lên. Ðó là ngày đầu tiên tôi được một chân học việc với ông C. Tôi quả là may mắn có được chân học việc này. Ông C là một tay trùm lớp lang trong vấn đề phát triển phần mềm. Ðấu để giành được chân việc này đúng là nẩy lửa. Các tay học việc của ông C thường trở nên các tay cựu học việc sáng giá. Ðiều này có nghĩa được làm việc với ông C có giá trị rõ ràng. Tôi cứ ngỡ là hôm nay tôi sẽ được gặp ông ta nhưng thay vì đó tôi bị một gã cựu học việc níu tôi qua một bên. Gã bảo ông C luôn luôn dẫn các tay học việc đi xuyên qua phần định hướng trong những ngày đầu. Gã nói ông C nhất quyết cho rằng phần thực tập định hướng là thiết thực với các tay học việc và nó dẫn đến mức chất lượng mã nguồn mà ông ta ta dự tưởng. Tôi náo nức kinh khủng. Ðây là một cơ hội cho họ thấy tôi là một tay lập trình ngon cỡ nào. Thế là tôi bảo Jerry tôi không chờ được nữa. Gã đáp lại sự náo nức của tôi bằng cách bảo tôi thử viết một chương trình đơn giản cho gã. Gã muốn tôi dùng Sieve of Eratosthenes để tính các số nguyên. Gã còn bảo tôi phải chuẩn bị xong chương trình bao gồm trọn bộ các unit tests sẵn sàng để chấm sau buổi ăn trưa. Thật là khoái Tôi có gần 4 tiếng đồng hồ để xào nấu một chương trình giống như Sieve. Tôi quyết tâm thực hiện công tác này một cách hết sức có ấn tượng. Mã dẫn 1 đưa ra những gì tôi đã viết. Tôi nắm chắc là chương trình của tôi được chú thích cẩn thận và trình bày gọn gàng.
The Crafsman Opening Diaster Robert C Martin 13 Tháng 2, 2002 Bài viết lược trích từ chương Principles, Patterns and Practices Agile Software Development Robert C Martin, nhà xuất Prentice Hall, 2002 Nhật ký thân mến, 13 tháng 2, 2002 Hôm ngày xui xẻo - Tôi làm hỏng chuyện Tôi muốn gây ấn tượng với ngài "cựu học việc" rút làm rối tung lên Ðó ngày chân học việc với ông C Tôi may mắn có chân học việc Ông C tay trùm lớp lang vấn đề phát triển phần mềm Ðấu để giành chân việc nẩy lửa Các tay học việc ông C thường trở nên tay "cựu học việc" sáng giá Ðiều có nghĩa làm việc với ông C có giá trị rõ ràng Tôi ngỡ hôm gặp ông ta thay bị gã "cựu học việc" níu qua bên Gã bảo ông C luôn dẫn tay học việc xuyên qua phần định hướng ngày đầu Gã nói ông C cho phần thực tập định hướng thiết thực với tay học việc dẫn đến mức chất lượng mã nguồn mà ông ta ta dự tưởng Tôi náo nức kinh khủng Ðây hội cho họ thấy tay lập trình "ngon" cỡ Thế bảo Jerry không chờ Gã đáp lại náo nức cách bảo thử viết chương trình đơn giản cho gã Gã muốn dùng "Sieve of Eratosthenes" để tính số nguyên Gã bảo phải chuẩn bị xong chương trình bao gồm trọn "unit tests" sẵn sàng để "chấm" sau buổi ăn trưa Thật khoái! Tôi có gần tiếng đồng hồ để "xào nấu" chương trình giống Sieve Tôi tâm thực công tác cách có ấn tượng Mã dẫn đưa viết Tôi nắm chương trình thích cẩn thận trình bày gọn gàng Mã dẫn /** * This class generates prime numbers up to a user-specified maximum * The algorithm used is the Sieve of Eratosthenes ** Eratosthenes of Cyrene, b.c 276 BC, Cyrene, Libya; d.c.194 BC,Alexandria * He was the first man to calculate the circumference of the Earth, * and was also known for working on calendars with leap years and * running the library at Alexandria.
* * The algorithm is quite simple: * Given an array of integers starting at 2, cross out all multiples of * Find the next uncrossed integer, and cross out all of its multiples * Repeat until you have passed the square root of the maximum value * * @authorAlphonse, @version 13 Feb 2002 atp */ import java.util.*; public class GeneratePrimes { /** * @param maxValue is the generation limit */ public static int[] generatePrimes(int maxValue) { if (maxValue >= 2) { // the only valid case // declarations int s = maxValue + 1; // size of array boolean[] f = new boolean[s]; int i; // initialize array to true for (i = 0; i < s; i++) f[i] = true; // get rid of known non-primes f[0] = f[1] = false; // sieve int j; for (i = 2; i < Math.sqrt(s) + 1; i++) { if (f[i]) { // if i is uncrossed, cross its multiples for (j = * i; j < s; j += i) f[j] = false; // multiple is not prime } } // how many primes are there? int count = 0; for (i = 0; i < s; i++) { if (f[i]) count++; // bump count } int[] primes = new int[count]; // move the primes into the result for (i = 0, j = 0; i < s; i++) { if (f[i]) // if prime primes[j++] = i; } return primes; // return the primes } else // maxValue < return new int[0]; // return null array if bad input } } Sau viết "unit test" cho GeneratePrimes Xem mã dẫn Ðoạn mã dùng JUnit framework Jerry dẫn Nó dùng tính chất hướng thống kê; kiểm tra xem "generator" tạo số nguyên tới 0, 2, 100 Trong trường hợp thứ hẳn số nguyên Trong trường hợp thứ nhì hẳn phải có số nguyên phải số Trường hợp thứ ba phải có hai số nguyên chúng phải số Trường hợp cuối phải 25 số nguyên số cuối phải 97 Nếu bước kiểm tra đúng, giả định "generator" làm việc Tôi e khó tin cậy tuyệt đối cách trên, không nghĩ trường hợp "function" bị hỏng mà bước kiểm tra Mã dẫn import junit.framework.*; import java.util.*; public class TestGeneratePrimes extends TestCase { public static void main(String args[]) { Junit.swingui.TestRunner.main( new String[] {"TestGeneratePrimes"}); } public TestGeneratePrimes(String name) { super(name); } public void testPrimes() { int[] nullArray = GeneratePrimes.generatePrimes(0); assertEquals(nullArray.length, 0); int[] minArray = GeneratePrimes.generatePrimes(2); assertEquals(minArray.length, 1); assertEquals(minArray[0], 2); int[] threeArray = GeneratePrimes.generatePrimes(3); assertEquals(threeArray.length, 2); assertEquals(threeArray[0], 2); assertEquals(threeArray[1], 3); int[] centArray = GeneratePrimes.generatePrimes(100); assertEquals(centArray.length, 25); assertEquals(centArray[24], 97); } } Tôi khoảng đồng hồ để làm bước chạy Jerry không muốn gặp sau buổi ăn trưa, thế, dành trọn thời gian lại đọc Design Patterns mà Jerry đưa cho Sau buổi ăn trưa, ghé văn phòng Jerry cho gã biết thực xong chương trình Gã nhìn với nụ cười khó tả, nói: "Ðược lắm, xem thử nào." Gã dẫn phòng thí nghiệm cho ngồi trước máy Gã ngồi bên cạnh yêu cầu đưa chương trình vào máy Thế chuyển mã nguồn từ máy laptop lên Jerry xem xét hai mã nguồn chừng năm phút gã lắc đầu bảo: "Mày đưa cho ông C xem được! Nếu tao để xem này, đuổi cổ tao lẫn mày Ông người kiên nhẫn đâu." Tôi đánh thót phát cố giữ bình tĩnh hỏi gã: "Chớ sai chỗ nào?" Jerry thở dài nói: "Tụi nên xuyên qua mã nguồn với Tao cho mày điểm cách ông C muốn thực nào." "Quá rõ ràng", gã tiếp tục, "cái main function muốn làm ba functions riêng biệt Cái thứ khởi tạo tất biến hàm thiết lập "sieve" Cái thứ nhì thực thi hành "sieve" thứ ba tải kết "sieve" vào dãy số nguyên." Tôi nhận ý gã muốn nói Có ba khái niệm chôn function Tuy vậy, gã muốn phải làm với Gã nhìn lúc, rõ ràng đợi phản ứng Nhưng rốt gã thở dài, lắc đầu The Crafsman Crash Diet Robert C Martin Trong phần trước * Jerry, tay cựu học việc yêu cầu Alphonse, tay học việc, viết chương trình tạo số nguyên dùng "sieve of Etastosthenes" Jerry, nhận thấy Alphonse ứng dụng trọn thuật toán vào function "khổng tượng" nên yêu cầu Alphonse tách theo ba khái niệm: khởi động, ứng tạo chuẩn xuất; Alphonse phải đâu Gã nhìn lúc, rõ ràng đợi làm Nhưng rốt gã thở dài, lắc đầu tiếp tục "Ðể mở rộng ba khái niệm rõ ràng hơn, tao muốn mày tách chúng thành ba methods riêng biệt Ðồng thời vứt hết phụ không cần thiết đặt tên cho class Mày làm xong thứ phải bảo đảm test chạy được." Các bạn thấy điểm làm Mã dẫn Tôi đánh dấu thay đổi chữ đậm, y hệt Martin Fowler trình bày Refactoring ông ta Tôi đổi tên class thành dạng danh từ, vứt hết phụ chuyện Eratosthenes tạo ba methods từ ba khái niệm generatePrimes function Tách ba functions buộc phải đưa số biến hàm function thành static fields class Jerry nói cách làm rõ biến hàm local biến hàm có ảnh hưởng rộng lớn Mã dẫn PrimeGenerator.java, version /** * This class generates prime numbers up to a user-specified * maximum The algorithm used is the Sieve of Eratosthenes * Given an array of integers starting at 2: Find the first * uncrossed integer, and cross out all its multiples Repeat * until the first uncrossed integer exceeds the square root of * the maximum value */ import java.util.*; public class PrimeGenerator { private static int s; private static boolean[] f; private static int[] primes; public static int[] generatePrimes(int maxValue) { if (maxValue < 2) return new int[0]; else { initializeSieve(maxValue); sieve(); loadPrimes(); return primes; // return the primes } } private static void loadPrimes() { int i,j; // how many primes are there? int count = 0; for (i = 0; i < s; i++) { if (f[i]) count++; // bump count } primes = new int[count]; // move the primes into the result for (i = 0, j = 0; i < s; i++) { if (f[i]) // if prime primes[j++] = i; } } private static void sieve() { int i,j; for (i = 2; i < Math.sqrt(s) + 1; i++) { // if i is uncrossed, cross out its multiples if (f[i]) { for (j = * i; j < s; j += i) f[j] = false; // multiple is not prime } } } private static void initializeSieve(int maxValue) { // declarations s = maxValue + 1; // size of array f = new boolean[s]; // initialize array to true for (int i = 0; i < s; i++) f[i] = true; // get rid of known non-primes f[0] = f[1] = false; } } Jerry bảo mã lộn xộn, nên gã giành lấy bàn đánh cách dọn dẹp Mã dẫn minh hoạ gã làm Thoạt tiên gã vứt biến hàm s initializeSieve thay f.length Sau gã đổi tên ba functions (theo kiểu) gã cho có ấn tượng Cuối gã xếp lại "bộ lòng" initializeArrayOfIntegers (từ initializeSieve) dễ đọc chút Các test chạy thường Mã dẫn PrimeGenerator.java, version (partial) public class PrimeGenerator { private static boolean[] f; private static int[] result; public static int[] generatePrimes(int maxValue) { if (maxValue < 2) return new int[0]; else { initializeArrayOfIntegers(maxValue); crossOutMultiples(); putUncrossedIntegersIntoResult(); return result; } } private static void initializeArrayOfIntegers(int maxValue) { f = new boolean[maxValue + 1]; f[0] = f[1] = false; //neither primes nor multiples for (int i = 2; i < f.length; i++) f[i] = true; } Tôi phải công nhận mã rõ chút Trước nghĩ tạo functions có tên sinh động phí thời , chỉnh đổi gã thật làm cho mã nguồn dễ đọc Tiếp theo Jerry trỏ vào crossOutMultiples, nói gã nghĩ cụm if(f[i] == true) làm cho dễ đọc Tôi nghĩ đến điểm chừng phút Ý định cụm dùng để kiểm tra xem i không bị loại trừ; đổi tên f thành unCrossed Jerry nói mã chưa hài lòng với dẫn đến khả phủ định đôi (double negative) unCrossed[i] = false Bởi gã đổi tên dãy số thành dãy isCrossed với số nhỏ Các test chạy Jerry tách phần lặp bên (inner loop) crossOutMultiples function gọi crossOutMultipleOf Gã bảo cụm tương tự if (isCrossed[i] == false) dễ nhầm lẫn nên gã tạo function có tên notCrossed thay cụm if thành if (notCrossed(i)) Kết tiếp gã chạy thử test lại Sau Jerry hỏi ý nghĩa phần số Tôi tốn thời viết phụ giải thích cần phải lặp lại phần số chiều dài dãy số Tôi cố tranh đua với Jerry cách tách phần tính toán thành function, nơi đưa vào phần phụ giải Trong viết phụ nhận số phân tố cực đại số nguyên dãy số Bởi để ứng phó, chọn cách gọi (maxValue) cho biến hàm Cuối bảo đảm tests chạy Kết thay đổi Mã dẫn Mã dẫn PrimeGenerator.java version (partial) public class PrimeGenerator { private static boolean[] isCrossed; private static int[] result; public static int[] generatePrimes(int maxValue) { if (maxValue < 2) return new int[0]; else { initializeArrayOfIntegers(maxValue); crossOutMultiples(); putUncrossedIntegersIntoResult(); return result; } } private static void initializeArrayOfIntegers(int maxValue) { isCrossed = new boolean[maxValue + 1]; for (int i = 2; i < isCrossed.length; i++) isCrossed[i] = false; } private static void crossOutMultiples() { int maxPrimeFactor = calcMaxPrimeFactor(); for (int i = 2; i sqrt of the // size of the array, then q will never be greater than // Thus p is the largest prime factor in the array, and is // also the iteration limit double maxPrimeFactor = Math.sqrt(isCrossed.length) + 1; return (int) maxPrimeFactor; } private static void crossOutMultiplesOf(int i) { for (int multiple = 2*i; multiple < isCrossed.length; multiple += i) isCrossed[multiple] = true; } private static boolean notCrossed(int i) { return isCrossed[i] == false; } Tôi bắt đầu nắm bắt vấn đề nên liền xét lại method putUncrossedIntegersIntoResult Tôi thấy method có hai phần Phần thứ đếm số nguyên không bị loại dãy số, tạo nên dãy kết (bằng chiều dài dãy số) Phần thứ nhì dời số nguyên không bị loại vào dãy kết Bởi thế, bạn thấy Mã dẫn 6, tách phần thứ để hình thành function cho dọp dẹp lặt vặt đôi chút Các tests chạy Jerry thoáng gật đầu Gã có thật khoái điều thực không? Mã dẫn PrimeGenerator.java, version (partial) private static void putUncrossedIntegersIntoResult() { result = new int[numberOfUncrossedIntegers()]; for (int j = 0, i = 2; i < isCrossed.length; i++) if (notCrossed(i)) result[j++] = i; } private static int numberOfUncrossedIntegers() { int count = 0; for (int i = 2; i < isCrossed.length; i++) if (notCrossed(i)) count++; return count; } * Trong nguyên "In the last month's column " tạm dịch thoáng "trong phần trước" cho phù hợp với tinh thần craftsman post lên diễn đàn (không theo tháng mà theo tùy hứng người dịch ;)) The Crafsman Clarity and Collaboration Rober C Martin Lần trước, Jerry, cựu học việc yêu cầu tay học việc Alphonse viết chương trình tạo số nguyên tố dùng phương pháp lượt Eratosthenes (sieve of Eratosthenes) Jerry duyệt giúp Alphonse tách lược (refactor) mã nguồn Anh ta không hài lòng với kết Alphonse Lần trước Alphonse thực xong phần refactoring nghĩ Jerry chấp thuận Jerry thoáng gật đầu Liệu gã có thật khoái điều làm không? Sau Jerry xuyên qua trọn chương trình, đọc lại từ đầu đến cuối thể gã đọc chứng minh hình học Gã bảo bước quan trọng "Ðến bước này, tụi thực refactoring mảnh mã Bây tụi xem thử trọn chương trình nối liền dạng tổng thể" Tôi hỏi: "Jerry, ông làm với mã nguồn ông sao?" Jerry quắc mắt lên nói: "Ở tụi tao làm việc với theo nhóm nên mã riêng tao hết Bộ mày cho mã riêng mày hở?" Tôi trả lời nhỏ nhẻ: "hết nghĩ rồi, ông ảnh hưởng lớn đến mã nguồn này." Gã trả lời: "Cả hai thằng ảnh hưởng đến nó, cách ông C ưa chuộng Ông không khoái làm chủ mã nguồn hết đâu Trả lời riêng cho câu hỏi mày: Ðúng vậy, tụi tao thực nghiệm "rơ" refactoring dọn rác phương pháp ông C." Trong đọc qua mã nguồn, Jerry thấy gã không khoái tên initializeArrayOfIntegers Gã nói: "Cái khởi tạo thực dãy số nguyên, mà dãy booleans Nhưng initializeArrayOfBooleans không cách cải tiến Ðiều thực muốn làm method liệt kê danh sách số nguyên phù hợp để chúng lên sàng, sau lọc loại số số nguyên tố (ie loại bội số)" (Do đó, danh sách lúc đầu không bị gạch chéo, số bị loại sẽ bị gạch chéo (crossed out )) Tôi trả lời: "Tất nhiên!" Thế vớ lấy bàn đánh sửa tên method thành uncrossIntegersUpTo Tôi thấy không khoái tên isCrossed lại dùng cho dãy booleans, nên đổi thành crossedOut Các test chạy Tôi bắt đầu thấy thích trò Jerry không đồng tình Sau Jerry quay lại, hỏi có phải mơ màng theo khói thuốc viết mớ maxPrimeFactor (Xem Mã dẫn 6) Thoạt đầu ngỡ ngàng xem lại đoạn mã phụ nhận thấy gã có lý Eo ôi, thấy thật ngu! Căn bậc (Square root )* chiều dài dãy số không nguyên số Method không tính thừa số nguyên tố cực đại (max prime factor) ** Phần giải sai bét nên, ngượng ngùng viết lại phần phụ để giải thích rõ bậc dùng để làm đổi tên biến , hàm cho thích hợp Các test chạy Mã dẫn TestGeneratePrimes.java (Partial) private static int calcMaxPrimeFactor() { // // // // // We cross out all multiples of p, where p is prime Thus, all crossed out multiples have p and q for factors If p > sqrt of the size of the array, then q will never be greater than Thus p is the largest prime factor in the array, and is also the iteration limit double maxPrimeFactor = Math.sqrt(isCrossed.length) + 1; return (int) maxPrimeFactor; } "dùng +1 làm quái vậy?" Jerry tru tréo lên Tôi nuốt ực, xem lại đoạn mã cuối phát biểu: "Tôi ngại lấy phần nguyên bậc 2, phần thập phân bậc bị đi, vòng lặp bị thiếu." Gã hỏi: "Cho nên mày xả rác đoạn mã với phần gia tăng "+1" mày bị hoảng? Như ngốc quá, dẹp trò gia tăng "+1" thử test lại." Tôi làm trọn test chạy Tôi suy nghĩ lại phần lúc làm run Thế định giới hạn lặp lại thực số "thừa số nguyên tố cực đại" "thừa số nguyên tố"