Bóc tách vấn đề nhỏ không liên quan
Một kỹ sư không chỉ biết code mà phải biết chia những vấn đề lớn thành những vấn đề nhở và giải quyết lần lượt từng vấn đề một. Hay nói ngắn gọn lại là “Chia để trị”.
Trong bài viết này mình sẽ đưa ra vài lời khuyên để đồng nhất, bóc tách chương trình con. Cụ thể là:
- Nhìn vào một function hoặc môt block (gọi chung là chương trình con), hãy tự hỏi rằng “What is the high-level goalof this code?”
- Kiểm tra xem việc thực thi đoạn code đấy đã giải quyết trực tiếp được vấn đề chưa, hay là nó giải quyết 1 subproblem không liên quan tới goal
- Nếu những dòng code giải quyết subproblem không liên quan đó đủ để tách thành 1 hàm riêng thì ta tách luôn.
Việc viết thanh hàm chác hản chúng ta làm hang ngày nhưng trong bài viết này mình sẽ tập chung vào các viết nhưng chương trình con một cách rõ ràng
Bắt đầu với ví dụ đầu tiên bằng Javascript là hàm tìm vị trí gần nhất từ một tập các điểm bao gồm kinh độ và vĩ độ
// Return which element of 'array' is closest to the given latitude/longitude. // Models the Earth as a perfect sphere. var findClosestLocation = function (lat, lng, array) { var closest; var closest_dist = Number.MAX_VALUE; for (var i = 0; i < array.length; i += 1) { // Convert both points to radians. var lat_rad = radians(lat); var lng_rad = radians(lng); var lat2_rad = radians(array[i].latitude); var lng2_rad = radians(array[i].longitude); // Use the "Spherical Law of Cosines" formula. var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) + Math.cos(lat_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng_rad)); if (dist < closest_dist) { closest = array[i]; closest_dist = dist; } } return closest; };
Trong vòng lăp có một đoạn chương trình con không hê liên quan đến phạm vi bên ngoài của nó chúng ta nên viết nó thành một hàm riêng thay vì phải comment “Convert both points to radians” (Nếu chua nhớ ngay được công thức lượng giác có thể tra google J)
var spherical_distance = function (lat1, lng1, lat2, lng2) { var lat1_rad = radians(lat1); var lng1_rad = radians(lng1); var lat2_rad = radians(lat2); var lng2_rad = radians(lng2); // Use the "Spherical Law of Cosines" formula. return Math.acos(Math.sin(lat1_rad) * Math.sin(lat2_rad) + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.cos(lng2_rad - lng1_rad)); };
Code viền thành hàm spherical_distance() sẽ dễ đọc hơn rất nhiều và không cần comment
Code thuẩn tuý hữu ích
Đây là những chương trình sử lý nhưng công việc cơ bản như sử lý chuỗi, sử dụng bảng băm hay đọc ghi file.
Ví dụ cần đọc nội dung một file: trong php bạn có thể gọi file_get_contents(“filename”) hoặc trong Python open(“filename”).read(). Nhưng trong C++ không được gọn gàng như vậy
ifstream file(file_name); // Calculate the file's size, and allocate a buffer of that size. file.seekg(0, ios::end); const int file_size = file.tellg(); char* file_buf = new char [file_size]; // Read the entire file into the buffer. file.seekg(0, ios::beg); file.read(file_buf, file_size); file.close(); ...
Trường hợp này lên viết lại môt hàm và cớ thể build lai gọn gàng hơn và sử dụng cho mõi Prọject
Code có mục đích khác
Trong nhiều ừng dụng Web, gần như khi chúng ta sử dụng ajax đều phải có một dialog thông bảo phẩn hồi bằng hàm alert();. Và việc sử lý đưa ra thông bào không liên quan đến phần gửi request
ajax_post({ url: 'http://example.com/submit', data: data, on_success: function (response_data) { var str = "{\n"; for (var key in response_data) { str += " " + key + " = " + response_data[key] + "\n"; } alert(str + "}"); // Continue handling 'response_data' ... } });
Và hầu như không ai chịu viết tách khối vòng lắp ra ngoài
var format_pretty = function (obj) { var str = "{\n"; for (var key in obj) { str += " " + key + " = " + obj[key] + "\n"; } return str + "}"; };
Nhưng lợi ích không ngờ
Trong ví dụ phần trên đưa vào hàm format_pretty(); là một ý tưởng hay, đơn gian và thuân tiện. Tuy nhiên thi nhiều lý do tuyệt vời khác. Nó dẽ cái tiến khi phần sử lý này chỉ có một mình. Và giả sử nó có 2 thay đổi như sau:
- Hiển thi biến obj ra object, nếu nó là môt chuỗi văn bản thuần túy hoặc undefined sẽ ném ra ngoại lệ
- Hiển thi giá trị của object. Nếu in trược tiếp ra bao giờ nó cũng là [object Object]
var format_pretty = function (obj, indent) { // Handle null, undefined, strings, and non-objects. if (obj === null) return "null"; if (obj === undefined) return "undefined"; if (typeof obj === "string") return '"' + obj + '"'; if (typeof obj !== "object") return String(obj); if (indent === undefined) indent = ""; // Handle (non-null) objects. var str = "{\n"; for (var key in obj) { str += indent + " " + key + " = "; str += format_pretty(obj[key], indent + " ") + "\n"; } return str + indent + "}"; };
- Thay vi phải sửa tất cả những chỗ có ajax thì chỉ phải sửa duy nhất ở một hàm.
Chia chức năng dự án rành mạch
Ví dụ một người king doanh cần xem lại Website. Đoạn code Phyton này tạo ra mọt đối tượng Business và thiết lập tên, url, ngày tạo
business = Business() business.name = request.POST["name"] url_path_name = business.name.lower() url_path_name = re.sub(r"['.]", "", url_path_name) url_path_name = re.sub(r"[^a-z0-9]+", "-", url_path_name) url_path_name = url_path_name.strip("-") business.url = "/biz/" + url_path_name business.date_created = datetime.datetime.utcnow() business.save_to_database()
name, url, date_create la 3 phần chính cần phải thiết lập. Cần quan tâm đên url thiết lập bằng regular expression nếu tên là “P.GS, TS Phạm Văn Đông” url sẽ được gán là /biz/pham-van-dong. Và ở đây cần tách doạn tạo url ra
CHARS_TO_REMOVE = re.compile(r"['.]+") CHARS_TO_DASH = re.compile(r"[^a-z0-9]+") def make_url_friendly(text): text = text.lower() text = CHARS_TO_REMOVE.sub('', text) text = CHARS_TO_DASH.sub('-', text) return text.strip("-")
Và giờ format code sẽ đều đặn hơn rất nhiều
business = Business() business.name = request.POST["name"] business.url = "/biz/" + make_url_friendly(business.name) business.date_created = datetime.datetime.utcnow() business.save_to_database()
Tạo một interface mà bạn cần
Khi có một đối tượng json { “username”: “…”, “password”: “…” } cần mã hóa và đưa vào URL và sử dụng lớp Cipher để chuỷen thành chuôi ma hóa không phải là một đường dẫn nữa. Nhưng khi cần một URL có độ tin cậy cao hơn
user_info = { "username": "...", "password": "..." } user_str = json.dumps(user_info) cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE) encrypted_bytes = cipher.update(user_str) encrypted_bytes += cipher.final() # flush out the current 128 bit block url = "http://example.com/?user_info=" + base64.urlsafe_b64encode(encrypted_bytes) ...
Nhưng để rõ hơn là chúng ta muốn tạo ra một URL bảo mật hơn thay vi chỉ tạo ra một URL nên viết thành một hàm khác
def url_safe_encrypt(obj): obj_str = json.dumps(obj) cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE) encrypted_bytes = cipher.update(obj_str) encrypted_bytes += cipher.final() # flush out the current 128 bit block return base64.urlsafe_b64encode(encrypted_bytes) user_info = { "username": "...", "password": "..." } url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)
Tổng hợp
Một cách đơn giản về chường này là tách các mã tổng quát từ dự án cụ thể. Khi kết thúc dự án, hầu hết cắc code chung sẽ được say dụng thành thư viện nhỏ và các hàm hỗ trợ
Lý do chính của kỹ thuật này giúp lập trình viên tập chung nhỏ hơn, các vấn đề được xác định rõ tách ra khỏi dự án của bạn. và kết quả la nhưng vấn đền hỏ được giải quyết triệt đẻ, chính xác hơn. Bạn cũng có thể sử dụng lại
Mỗi task một lần
Mã nào nhiều việc cùng một lúc là khó hiểu. Một khối duy nhất của mã có thể được khởi tạo đối tượng mới, làm sạch dữ liệu, phân tích đầu vào, và việc áp dụng logic kinh doanh, tất cả cùng một lúc. Nếu tất cả những mã được đan xen với nhau, nó sẽ khó khăn hơn để hiểu hơn nếu mỗi “task” được bắt đầu và hoàn thành ngày của riêng mình.
Nói một cách khác, bài viết này là nói về “dồn” code của bạn. Sơ đồ dưới đây minh họa quá trình này: phía bên trái hiển thị các nhiệm vụ khác nhau một đoạn mã đang làm, và phía bên phải cho thấy rằng cùng một mã sau khi nó được tổ chức để làm một nhiệm vụ tại một thời điểm.
Quá trình thực hiên mỗi task một lần
- Liệt kê ra tất cả những “task” code của bạn đang làm. Dù là nhở nhất hay lập lại
- Hãy thứ tách các task của bạn thanh nhưng phần hoặc nhiều hàm khác nhau
Task có thể nhỏ
Khi bạn kích vào nú thay đổi (len/xuong) vag ọi đến hàm vote_changed(old_vote, new_vote);
var vote_changed = function (old_vote, new_vote) { var score = get_score(); if (new_vote !== old_vote) { if (new_vote === 'Up') { score += (old_vote === 'Down' ? 2 : 1); } else if (new_vote === 'Down') { score -= (old_vote === 'Up' ? 2 : 1); } else if (new_vote === '') { score += (old_vote === 'Up' ? -1 : 1); } } set_score(score); };
Mặc dù đang khá ngắn, nó làm rất nhiều. Có rất nhiều chi tiết phức tạp, và thật khó để biết trong nháy mắt cho dù có bất kỳ off-by-một trong những sai sót, lỗi chính tả, hoặc lỗi khác.
Code này có thể dường như đang làm một điều duy nhất (cập nhật điểm số), nhưng thực sự có hai nhiệm vụ được thực hiện cùng một lúc:
- old_vote và new_vote được phân tích thành số
- score được update
Chúng ta có thể làm nó dẽ hơn thành 2 task riêng lẻ
Task đầu tiền chúng ta phân tích thành số
var vote_value = function (vote) { if (vote === 'Up') { return +1; } if (vote === 'Down') { return -1; } return 0; };
Task thứ 2 cập nhật score
var vote_changed = function (old_vote, new_vote) { var score = get_score(); score -= vote_value(old_vote); // remove the old vote score += vote_value(new_vote); // add the new vote set_score(score); };
Tách giá trị ra khỏi object
Trong một dự có một đoạn JavaScript dùng để tách vị trí người dùng và ghép thành một chuôi rõ ràng “City, Country” như là “Santa Monica, USA” hoặc “Paris, France.” Chúng ta có một bẳng với vài thông tin khác. Việc phải làm là chọn một thành phố và một Quốc gia và gép chung lại với nhau.
Nếu bất kỳ 3 thành phần xác đinh tỉnh/thành phố ở trên không có giá trị
- Khi chọn từ định danh nhỏ đến lớn