Mục lục
ToggleSự thật chức năng các hàm, lớp làm gì chỉ có thể được tìm thấy ở một nơi: code. Chỉ có code mới thực sự có thể cho bạn biết nó đang có gì và làm gì. Đây là nguồn thông tin thực sự chính xác duy nhất. Do đó, mặc dù comment là đôi khi là cần thiết, nhưng chúng ta sẽ tìm cách để tối thiểu nó trong code của mình để tránh gây hoang mang thông tin :v. Comment thường được khuyên là không nên cho vào trong code
Don’t comment bad code – rewrite it
Không có gì hữu ích như việc đặt comment tốt. Không có gì có thể làm module trở lên lộn xộn bằng việc đặt comment giáo điều phù phiếm. Không có gì có thể gây nguy hiểm hơn những comment đối trá, sai lệch thông tin.
Bản thân comment không hẳn luôn là tốt, mà quan trọng comment cần dùng đúng cách để đạt hiệu quả, bằng không nó sẽ làm rối tung tất cả.
Nếu chúng ta khéo léo trong việc sử dụng ngôn ngữ trong lập trình ( đặt tên biến, hàm class) thì có thể chúng ta cũng không phải dùng quá nhiều comment trong bài code.
Theo tác giả, việc sử dụng comment trong code còn thể hiện 1 việc thất bại trong việc diễn giải code. Hay nói cách khác, chúng ta không thể diễn giải code đủ tốt để mọi người hiểu mà thay vào đó lại comment bù đắp thiếu sót đó.
Sự thật chỉ tìm thấy được duy nhất trong các đoạn mã, không phải các đoạn comment. Chỉ có Code mới nói cho ta biết chính xác làm gì. ĐIều này cũng có thể hiểu rằng, chúng ta luôn có quá trình nâng cấp, tối ưu lại mã nguồn. Code thì có thể thay đổi liên tục như vậy, nhưng comment thì không hẳn.
Thà rằng chúng ta không có comment cho đoạn mã còn hơn là 1 comment sai lệch thông tin.
Một trong những lý do thúc đẩy việc viết comment chính là do code lởm. Chúng ta viết những module và biết rằng nó thật khó hiểu và vô tổ chức. Và giải pháp đưa ra là:”Oh,tôi sẽ thêm comment vào đoạn code đô”
Nhưng không, Điều chúng ta nên làm là tối ưu nó thay vì viết comment.
Đoạn code rõ ràng và biểu cảm với 1 vài comment thì tốt hơn là đống code lộn xộn, phức tạp kèm với 1 đống comment.
Thay vì giành thờ igian của bạn vào viết comment cho đoạn code hỗn độn đó, bạn nên dùng thời gian để tối ưu lại mã code đó.
Để code tự giải thích ý nghĩa của chính nó thay vì phải viết comment
// Check to see if the employee is eligible for full benefits if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) có thể được viết lại là với hàm kiểm tra được tách riêng ra như sau if (employee.isEligibleForFullBenefits())
Chúng ta sẽ chỉ mất 1 vài giây để hiểu được ý định code thông qua tên hàm. Trong nhiều trường hợp chỉ đơn giản là tạo ra hàm nói điều tương tự như đoạn comment bạn muốn viết
Đôi lúc comment thì là cần thiết và mang lại nhiều lợi ích. Tuy nhiên nhớ rằng comment code tốt là comment khi mà bạn không thể tìm được cách viết code để diễn giải như thế nào.
Đôi khi tùy thược vào các tiêu chuẩn của công ty mà bắt buộc chúng ta phải viết các đoạn comment vì mục đích pháp lý. Ví dụ như về copyright, tác giả là cần thiết và hợp lý khi đặt ở đầu mỗi file source code.
Nếu có thể hãy đẫn link tham khảo, tên các giấy phép tiêu chuẩn, hay tài liệu cụ thể bên ngoài thay vì đặt tất cả điều khoản và điều kiện pháp lý vào trong comment.
Điều này đôi khi khá hữu dụng trong việc cung cấp thông tin cho người đọc. Ví dụ như bình luận dưới đây nhằm giải nghĩa cho giá trị trả về từ một abstract method
// Returns an instance of the Responder being tested. protected abstract Responder responderInstance();
Comment này đôi khi sẽ hữu dụng, nhưng sẽ tốt hơn nếu chúng ta có thể đặt tên hàm mô tả thông tin . Ví dụ có thể đặt tên hàm là responderBeingTested
// format matched kk:mm:ss EEE, MMM dd, yyyy Pattern timeMatcher = Pattern.compile( "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");
Trong trường hợp này, comment cho chúng ta biết biểu thức chính quy sử dụng với mục đích xác định thời gian theo 1 định dạng có sẵn. Tuy là thế, nó cũng có thể trở nên tốt hơn và rõ ràng hơn nếu đoạn code này được chuyển tới 1 class đặc biệt chuyên xử lý thời gian. Lúc đó có thể comment cũng không còn nhiều tác dụng nữa.
Đôi khi comment không chỉ là thông tin hữu ích về việc thực hiện hay cung cấp thông tin mục đích sau mỗi quyết định. Ví dụ như dưới đây, tác giả quyết định muốn sắp xếp các đối tượng tạo ra bởi class đích của anh ấy được cao hơn các đối tượng khác.
public int compareTo(Object o) { if (o instanceof WikiPagePath) { WikiPagePath p = (WikiPagePath) o; String compressedName = StringUtil.join(names, ""); String compressedArgumentName = StringUtil.join(p.names, ""); return compressedName.compareTo(compressedArgumentName); } return 1; // we are greater because we are the right type. }
Một ví dụ khác, bạn có thể không đồng ý rằng giải pháp dưới đây là giải pháp tốt, nhưng ít nhất bạn hiểu rằng nó đang cố gắng làm điều gì đó.
public void testConcurrentAddWidgets() throws Exception { WidgetBuilder widgetBuilder = new WidgetBuilder(new Class[] { BoldWidget.class }); String text = "'''bold text'''"; ParentWidget parent = new BoldWidget(new MockWidgetRoot(), "'''bold text'''"); AtomicBoolean failFlag = new AtomicBoolean(); failFlag.set(false); //This is our best attempt to get a race condition //by creating large number of threads. for (int i = 0; i < 25000; i++) { WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(widgetBuilder, text, parent, failFlag); Thread thread = new Thread(widgetBuilderThread); thread.start(); } assertEquals(false, failFlag.get()); }
Đôi khi nó giúp dịch nghĩa của 1 số tham số tối nghĩa hay trả về giá trị có thể đọc được. Nói chung, điều này giúp ích trong các trường hợp chúng ta sử dụng các thư viện tiêu chuẩn, hay chúng ta chỉnh sửa, nhưng đoạn mã code gốc không chấp nhận việc này.
public void testCompareTo() throws Exception { WikiPagePath a = PathParser.parse("PageA"); WikiPagePath ab = PathParser.parse("PageA.PageB"); WikiPagePath b = PathParser.parse("PageB"); WikiPagePath aa = PathParser.parse("PageA.PageA"); WikiPagePath bb = PathParser.parse("PageB.PageB"); WikiPagePath ba = PathParser.parse("PageB.PageA"); assertTrue(a.compareTo(a) == 0); // a == a assertTrue(a.compareTo(b) != 0); // a != b assertTrue(ab.compareTo(ab) == 0); // ab == ab assertTrue(a.compareTo(b) == -1); // a < b assertTrue(aa.compareTo(ab) == -1); // aa < ab assertTrue(ba.compareTo(bb) == -1); // ba < bb assertTrue(b.compareTo(a) == 1); // b > a assertTrue(ab.compareTo(aa) == 1); // ab > aa assertTrue(bb.compareTo(ba) == 1); // bb > ba }
Có rủi ro đáng kể ở đây, tất nhiên rồi, rằng 1 nhận xét rõ nghĩa là không chính xác. Thông qua ví dụ trên đây, nó giải thích 2 điều, tại sao làm rõ nghĩa là cần thiết, và tại sao nó lại là rủi ro. Do vậy trước khi viết comment giống như vậy, hãy chắc chắn rằng không có cách nào tốt hơn để làm rõ nghĩa.
Đôi khi sử dụng comment để cảnh báo cho các lập trình viên khác về 1 số hậu quả nhất định:
// Don't run unless you // have some time to kill. public void _testWithReallyBigFile() { writeLinesToFile(10000000); response.setBody(testFile); response.readyToSend(this); String responseString = output.toString(); assertSubString("Content-Length: 1000000000", responseString); assertTrue(bytesSent > 1000000000); }
Hoặc ví dụ khác như:
public static SimpleDateFormat makeStandardHttpDateFormat() { //SimpleDateFormat is not thread safe, //so we need to create each instance independently. SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); df.setTimeZone(TimeZone.getTimeZone("GMT")); return df; }
Có nhiều cách tốt hơn để giải quyết vấn đề này, nhưng trong trường hợp này comment là chấp nhận được. Nó ngăn chặn được việc khởi tạo đối tượng thông qua qua hàm tĩnh của 1 số lập trình viên khác.
Đôi khi có TODO Comment là việc hoàn toàn hợp lý. Trong trường hợp này, bình luận giải nghĩa tại sao hàm chức năng chưa được triển khai, và nó sẽ làm gì trong tương lai.
//TODO-MdM these are not needed // We expect this to go away when we do the checkout model protected VersionInfo makeVersion() throws Exception { return null; }
TODOs là những việc mà lập trình viên nghĩ rằng nên hoàn thành, nhưng vì 1 số lý do nào đó mà họ chưa thể hoàn thành nó.
Nó có thể là:
Vì bất kì điều gì, việc có TODOs comment không phải là code lởm trong hệ thống.
Điều này thường được dùng để khuếch đại sự quan trọng của 1 điều gì đó mà nhìn vào dường như nó không mấy quan trọng.
String listItemContent = match.group(3).trim(); // the trim is real important. It removes the starting // spaces that could cause the item to be recognized // as another list. new ListItemWidget(this, listItemContent, this.level + 1); return buildList(text.substring(match.end()));
Hầu hết các comment tệ đều là những comment đưa ra làm biện minh, lý do cho code xấu hay giải thích cho những quyết định không đủ thuyết phục.
Nếu bạn quyết định viết 1 comment, hãy giành thời gian để chắc chằn rằng đó là comment tốt nhất bạn có thể viết được.
public void loadProperties() { try { String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE; FileInputStream propertiesStream = new FileInputStream(propertiesPath); loadedProperties.load(propertiesStream); } catch (IOException e) { // No properties files means all defaults are loaded } }
Trong đoạn catch có ý nghĩa gì? Rõ ràng nó có ý nghĩa nào đó với tác giả, nhưng có lẽ không hẳn là ý tốt với mọi người khác.
Rõ ràng rằng, nếu chúng ta nhận đc lỗi IOException, có nghĩa là không có file properties nào được load, và trong trường hợp này, mặc định đã được nạp vào hệ thống.
Nhưng ai đã tải các thông tin mặc định đó? Nó được tải trước khi gọi loadProperties.load hay loadProperties.load đã tải mặc định, những vẫn ném ra ngoại lệ để chúng ta biết ?
Thực sự comment này làm cho chúng ta cảm thấy bối rối vì sự không rõ ràng cũng như mục đích thật sự của nó. Chúng ta không biết thực sự tác giả đoạn mã này muốn gì? hay là để cảm thấy an tâm thay vì để khối catch bị trống, hay họ có dự định hoàn thiện đoạn mã trong catch ở 1 lúc nào đó?
Để hiểu rõ điều này chúng ta cần tìm hiểu vào các phần khác của hệ thống để xem các đoạn mã liên quan. Tuy vậy bất kì comment nào bắt chúng ta phải tìm hiểu cả ở những phần mã khác, thì rõ ràng việc đoạn comment đó tồn tại thực sự là điều không sáng suốt.
Utility method that returns when this.closed is true. Throws an exception // if the timeout is reached. public synchronized void waitForClose(final long timeoutMillis) throws Exception { if (!closed) { wait(timeoutMillis); if (!closed) throw new Exception("MockResponseSender could not be closed"); } }
Như đoạn mã trên đây, đoạn comment có khi còn tốn thời gian để đọc hơn là đọc code bên dưới. Không rõ mục đích thật sự của bình luận này là gì, nó không giải nghĩa cho đoạn mã, cũng chẳng cung cấp mục đích hay lý do nào cả.
Như ví dụ trên, đó không chỉ là 1 bình luận dư thừa mà còn gây hiểu lầm cho người đọc nữa.
Ta thấy hàm này chỉ trả về giá trị khi this.closed là true, ném ra lỗi khi this.closed không phải true – theo như Comment. Nhưng mà thực tế rằng: hàm này sẽ không trả về khi this.closed trở thành true, Nó chỉ trả về if this.closed là true, nó sẽ đợi 1 khoảng thời gian và ném lỗi ra ngoài nếu this.closed tiếp tục không trở thành true.
Bản chất của comment này như đã nói nó khó hiểu hơn so với chính bản thân đoạn mã. Bởi lẽ, có thể 1 lập trình viên khi gọi hàm này sẽ kì vọng trả về true ngay khi gọi hàm thay vì phải đợi 1 khoảng thời gian. Thậm chí sẽ mất thời gian nếu như chúng ta là người chưa có nhiều kinh nghiệm, chung ta phải debug xem tại sao code lại chạy chậm như vậy.
Theo tác giả việc thêm các document trước mỗi block code thì thật sự rối rắm, nó có thể cũng chẳng làm rõ nghĩa hơn cho đoạn code mà lại làm code trở nên hỗn loạn và bối rối người đọc
Tuy vậy theo cá nhân mình việc có thêm document đôi khi khá thuận tiện trong việc biết tham số vào và giá trị trả về, đặc biệt khi code trên các IDE như kiểu visual Studio, gõ tên hàm sẽ đọc được nội dung tham số. Điều này đôi khi giúp ích khá tốt cho việc lập trình.
Một số người có thói quen tạo thêm comment ở đầu mỗi module mỗi lần bắt đầu sửa hay thay đổi chúng. Những comment này giống như kiểu nhật ký, log cho mỗi thay đổi xảy ra vậy.
Những comment như vậy không mang nhiều ý nghĩa trong quá trình phát triển cho lắm, và có lẽ nó nên được xóa sẽ tốt hơn.
Những comment này không cung cấp được thông tin gì mới cả ngoài việc gây nhiễu mà thôi
/** * Default constructor. */ protected AnnualDateRule() {} /** The day of the month. */ private int dayOfMonth; Ví dụ dưới đây là 1 minh chứng khác private void startSending() { try { doSending(); } catch (SocketException e) { // normal. someone stopped the request. } catch (Exception e) { try { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch (Exception e1) { //Give me a break! } } }
Đoạn comment đầu trong khối catch thứ nhất, dường như khá hợp lý, tuy vậy trong đoạn thứ 2 thì có vẻ thực sự đang làm nhiễu đoạn mã hơn.
Để làm gọn lại đoạn mã này, chúng ta cần tách nó thành các khối xử lý khác nhau như dưới đây.
/** * Default constructor. */ protected AnnualDateRule() {} /** The day of the month. */ private int dayOfMonth; Ví dụ dưới đây là 1 minh chứng khác private void startSending() { try { doSending(); } catch (SocketException e) { // normal. someone stopped the request. } catch (Exception e) { try { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch (Exception e1) { //Give me a break! } } }
/** The name. */ private String name; /** The version. */ private String version; /** The licenceName. */ private String licenceName; /** The version. */ private String info;
Mục đích của các comment trên đây là gì? chưa kể đến việc những comment này còn bị lạm dụng khi copy paste tạo ra những nhầm lẫn – thông tin nhiễu cho các người đọc khác nữa.
// does the module from the global list <mod> depend on the // subsystem we are part of? if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
Có thể sửa lại thành:
ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if (moduleDependees.contains(ourSubSystem))
Tác giả của comment trên có thể đã viết nó trước khi viết đoạn mã thực hiện. Tuy nhiên họ nên refactoring code để có đoạn mã sạch sẽ và rõ nghĩa hơn.
Một số lập trình viên thích đánh dấu các vị trí cần thiết trong đoạn mã code bằng các comment. ví dụ như
// Actions //////////////////////////////////
Rất hiếm khi đoạn code sẽ mô tả đầy đủ dưới những đoạn comment như vậy. Thường thì việc này giống với việc code ẩu, bừa bãi hơn, nó cũng cần được loại bỏ khỏi các đoạn mã để trở nên gọn gàng sạch sẽ hơn.
Đôi khi các lập trình viên đặt những comment báo hiệu cho việc kết thúc hàm, đóng đoạn mã. Nhưng việc này có 1 vài ý nghĩa cho các function dài với nhiều cấp độ lồng nhau hơn. Nhưng như chúng ta đã biết, giải pháp cho việc này cần tách nhỏ hàm chức năng với mục đích cụ thể. Khi đó các hàm sẽ ngắn gọn và không cần những comment đóng hàm như kia nữa.
Một mặt khác, các IDE hiện tại đều hỗ trợ việc xác định đóng mở đoạn mã một cách khá thông mình. Chính vì thế chúng ta cũng không cần thiết các comment này làm gì cả.
public class wc { public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line; int lineCount = 0; int charCount = 0; int wordCount = 0; try { while ((line = in .readLine()) != null) { lineCount++; charCount += line.length(); String words[] = line.split("\\W"); wordCount += words.length; } //while System.out.println("wordCount = " + wordCount); System.out.println("lineCount = " + lineCount); System.out.println("charCount = " + charCount); } // try catch (IOException e) { System.err.println("Error:" + e.getMessage()); } //catch } //main }
/* Added by Rick */
Đâu đó chúng ta sẽ hay thấy những đoạn comment như trên. Đoạn này muốn nói rằng ai đã thêm những dòng mã vào? Việc này có thể giúp ích trong 1 vài trường hợp khi muốn tìm hiểu người nào đã dựng lên đoạn code đó. Tuy vậy thường thì các dự án phát triển dài hơi, các đoạn mã như kia sẽ thường tồn tại rất lâu. thậm chí cho đến cả khi nó không còn chính xác nữa (đoạn mã đc sửa đổi, chỉnh sửa bởi nhiều người khác)
Chúng ta nên sử dụng các hệ thống quản lý mã nguồn như github để xử lý việc này thay vì comment trong code.
Chúng ta đôi lúc cũng thấy các đoạn mã như
InputStreamResponse response = new InputStreamResponse(); response.setBody(formatter.getResultStream(), formatter.getByteCount()); // InputStream resultsStream = formatter.getResultStream(); // StreamReader reader = new StreamReader(resultsStream); // response.setContent(reader.read(formatter.getByteCount()));
Đừng làm điều này trong code của bạn.
Bởi lẽ khi người khác nhìn vào, thật khó để họ quyết định xem có nên xóa nó hay không. Họ nghĩa rằng nó tồn tại ở đây là có lý do, và không nên xóa nó.
Hiện tại có nhiều hệ thống hỗ trợ quản lý mã nguồn một cách mạnh mẽ. Chúng sẽ ghi nhớ các thay đổi cho chúng ta. Cứ mạnh dạn xóa đoạn code dư thừa, bất kì khi nào cần chúng ta có thể lấy lại nó dựa vào hệ thống quản lý mã nguồn.
/** * Task to run fit tests. * This task runs fitnesse tests and publishes the results. * * * Usage: * <taskdef name="execute-fitnesse-tests" * classname="fitnesse.ant.ExecuteFitnesseTestsTask" * classpathref="classpath" /> * OR * <taskdef classpathref="classpath" * resource="tasks.properties" /> * * <execute-fitnesse-tests * suitepage="FitNesse.SuiteAcceptanceTests" * fitnesseport="8082" * resultsdir="${results.dir}" * resultshtmlpage="fit-results.html" * classpathref="classpath" /> * */
Các đoạn comment dưới dạng mã HTML như trên có lẽ không nên xuất hiện. nó quá khó để chúng ta đọc khi mà chúng ta làm việc chủ yếu với text editor hay các IDE – công cụ mà không hỗ trợ preview html trong khi đoạn đọc code.
/** * Port on which fitnesse would run. Defaults to <b>8082</b>. * * @param fitnessePort */ public void setFitnessePort(int fitnessePort) { this.fitnessePort = fitnessePort; }
Hãy chắc chắn rằng, khi bạn viết comment, bạn đang muốn nói về đoạn mã ngay gần nó. Như đoạn mã trên, comment có ghi rằng giá trị mặc định cho port là 8082, tuy vậy nó không hề liên quan đến đoạn code, và cũng không được định nghĩa ở đây. Cũng như không có gì chắc chắn cho việc khi đoạn mã gốc định nghĩa port này thay đổi, thì comment này cũng được cập nhật tương ứng.
Chúng ta không nên đưa các cuộc thảo luận, hay các mô tả chi tiết vào các đoạn comment của mình.
ý nghĩa là comment là giúp đoạn code trở lên rõ ràng và sáng nghãi hơn. Nếu bạn khó khăn trong viết comment, ít nhất bạn cũng mong chờ rằng những người đọc khi nhìn comment và code có thể hiểu rằng comment đang nói về điều gì
/* * start with an array that is big enough to hold all the pixels * (plus filter bytes), and an extra 200 bytes for header info */ this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
Đoạn comment mang lại cho ta những khó hiểu khác ngoài đoạn code. như là: filter byte? tại sao lại là 200 bytes. Comment này để giải thích lại đoạn mã bên dưới, nhưng có lẽ chính bản thân người đọc cũng không hiểu comment này nói về cái gì.
Bình luận: