JavaRush /Blog Java /Random-VI /Hướng dẫn về phong cách lập trình chung
pandaFromMinsk
Mức độ
Минск

Hướng dẫn về phong cách lập trình chung

Xuất bản trong nhóm
Bài viết này là một phần của khóa học hàn lâm "Java nâng cao." Khóa học này được thiết kế để giúp bạn tìm hiểu cách sử dụng hiệu quả các tính năng của Java. Tài liệu bao gồm các chủ đề “nâng cao” như tạo đối tượng, cạnh tranh, tuần tự hóa, phản ánh, v.v. Khóa học sẽ dạy bạn cách thành thạo các kỹ thuật Java một cách hiệu quả. Chi tiết tại đây .
Nội dung
1. Giới thiệu 2. Phạm vi biến 3. Trường lớp và biến cục bộ 4. Đối số phương thức và biến cục bộ 5. Đóng hộp và mở hộp 6. Giao diện 7. Chuỗi 8. Quy ước đặt tên 9. Thư viện chuẩn 10. Tính bất biến 11. Kiểm tra 12. Tiếp theo. .. 13. Tải mã nguồn
1. Giới thiệu
Trong phần hướng dẫn này, chúng ta sẽ tiếp tục thảo luận về các nguyên tắc chung về phong cách lập trình tốt và thiết kế đáp ứng trong Java. Chúng ta đã thấy một số nguyên tắc này trong các chương trước của hướng dẫn, nhưng nhiều mẹo thực tế sẽ được đưa ra nhằm mục đích cải thiện kỹ năng của nhà phát triển Java.
2. Phạm vi thay đổi
Trong Phần thứ ba ("Cách thiết kế các lớp và giao diện") chúng ta đã thảo luận về cách có thể áp dụng khả năng hiển thị và khả năng truy cập cho các thành viên của lớp và giao diện, với các hạn chế về phạm vi nhất định. Tuy nhiên, chúng ta vẫn chưa thảo luận về các biến cục bộ được sử dụng trong việc triển khai phương thức. Trong ngôn ngữ Java, mọi biến cục bộ, một khi được khai báo, đều có phạm vi. Biến này hiển thị từ nơi nó được khai báo cho đến thời điểm hoàn thành việc thực thi phương thức (hoặc khối mã). Nói chung, quy tắc duy nhất cần tuân theo là khai báo một biến cục bộ càng gần nơi nó sẽ được sử dụng càng tốt. Hãy để tôi xem một ví dụ điển hình: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } Trong cả hai đoạn mã, phạm vi của các biến được giới hạn ở các khối thực thi nơi các biến này được khai báo. Khi khối hoàn thành, phạm vi kết thúc và biến trở nên vô hình. Điều này có vẻ rõ ràng hơn, nhưng với việc phát hành Java 8 và giới thiệu lambdas, nhiều thành ngữ nổi tiếng của ngôn ngữ này sử dụng các biến cục bộ đang trở nên lỗi thời. Hãy để tôi đưa ra một ví dụ từ ví dụ trước bằng cách sử dụng lambdas thay vì vòng lặp: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); Có thể thấy rằng biến cục bộ đã trở thành một đối số cho hàm, đến lượt nó được chuyển làm đối số cho phương thức forEach .
3. Trường lớp và biến cục bộ
Mỗi phương thức trong Java thuộc về một lớp cụ thể (hoặc, trong trường hợp Java8, một giao diện trong đó phương thức được khai báo là phương thức mặc định). Do đó, giữa các biến cục bộ là các trường của một lớp hoặc các phương thức được sử dụng trong quá trình triển khai, có khả năng xảy ra xung đột tên. Trình biên dịch Java biết cách chọn biến chính xác trong số các biến có sẵn, mặc dù có nhiều nhà phát triển dự định sử dụng biến đó. Các IDE Java hiện đại thực hiện rất tốt công việc thông báo cho nhà phát triển khi những xung đột như vậy sắp xảy ra, thông qua các cảnh báo của trình biên dịch và đánh dấu biến. Nhưng vẫn tốt hơn nếu bạn nghĩ về những điều như vậy trong khi viết mã. Tôi khuyên bạn nên xem một ví dụ: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } Ví dụ này trông khá dễ dàng nhưng lại là một cái bẫy. Phương thức CalculateValue giới thiệu một giá trị biến cục bộ và thao tác trên giá trị đó, ẩn trường lớp có cùng tên. Dòng này value += value; phải là tổng giá trị của trường lớp và biến cục bộ, nhưng thay vào đó, một việc khác đang được thực hiện. Việc triển khai đúng cách sẽ trông như thế này (sử dụng từ khóa this): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Mặc dù ví dụ này có vẻ đơn giản ở một số khía cạnh, nhưng nó chứng minh một điểm quan trọng là trong một số trường hợp, có thể mất hàng giờ để gỡ lỗi và sửa lỗi.
4. Đối số phương thức và biến cục bộ
Một cạm bẫy khác mà các nhà phát triển Java thiếu kinh nghiệm thường rơi vào là sử dụng các đối số phương thức làm biến cục bộ. Java cho phép bạn gán lại giá trị cho các đối số không cố định (tuy nhiên, điều này không ảnh hưởng đến giá trị ban đầu): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } Đoạn mã trên không tinh tế nhưng nó làm rất tốt việc phát hiện ra vấn đề: đối số str được gán một giá trị khác (và về cơ bản được sử dụng như một biến cục bộ). Trong mọi trường hợp (không có ngoại lệ), bạn có thể và nên làm mà không cần có ví dụ này (ví dụ: bằng cách khai báo các đối số dưới dạng hằng số). Ví dụ: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } Bằng cách tuân theo quy tắc đơn giản này, việc theo dõi mã đã cho và tìm ra nguồn gốc của vấn đề sẽ dễ dàng hơn, ngay cả khi đưa vào các biến cục bộ.
5. Đóng gói và giải nén
Boxing và unboxing là tên của một kỹ thuật được sử dụng trong Java để chuyển đổi các kiểu nguyên thủy ( int, long, double, v.v. ) thành các trình bao bọc kiểu tương ứng ( Integer, Long, Double , v.v.). Trong Phần 4 của hướng dẫn Cách thức và Thời điểm sử dụng Generics, bạn đã thấy điều này trong thực tế khi tôi nói về việc gói các kiểu nguyên thủy làm tham số kiểu của generics. Mặc dù trình biên dịch Java cố gắng hết sức để ẩn những chuyển đổi như vậy bằng cách thực hiện tự động đóng hộp, nhưng đôi khi điều này ít hơn mong đợi và tạo ra kết quả không mong muốn. Hãy xem một ví dụ: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); Đoạn mã trên biên dịch tốt. Tuy nhiên, nó sẽ ném ra một ngoại lệ NullPointerException trên dòng // блок nơi nó đang chuyển đổi giữa Longlong . Lời khuyên cho trường hợp như vậy là nên sử dụng các kiểu nguyên thủy (tuy nhiên, chúng ta đã biết rằng điều này không phải lúc nào cũng có thể thực hiện được).
6. Giao diện
Trong Phần 3 của hướng dẫn, "Cách thiết kế các lớp và giao diện", chúng ta đã thảo luận về giao diện và lập trình hợp đồng, nhấn mạnh rằng các giao diện nên được ưu tiên hơn các lớp cụ thể bất cứ khi nào có thể. Mục đích của phần này là khuyến khích bạn xem xét các giao diện trước tiên bằng cách chứng minh điều này bằng các ví dụ thực tế. Các giao diện không bị ràng buộc với việc triển khai cụ thể (ngoại trừ các phương thức mặc định). Chúng chỉ là những hợp đồng và, chẳng hạn, chúng mang lại rất nhiều sự tự do và linh hoạt trong cách thực hiện hợp đồng. Tính linh hoạt này trở nên quan trọng hơn khi việc triển khai liên quan đến các hệ thống hoặc dịch vụ bên ngoài. Hãy xem ví dụ về một giao diện đơn giản và khả năng triển khai của nó: public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } Đoạn mã ở trên hiển thị một mẫu giao diện điển hình và cách triển khai nó. Việc triển khai này sử dụng dịch vụ HTTP bên ngoài ( http://api.geonames.org/ ) để truy xuất múi giờ của một vị trí cụ thể. Tuy nhiên, bởi vì hợp đồng phụ thuộc vào giao diện, rất dễ dàng để giới thiệu một cách triển khai giao diện khác, chẳng hạn như sử dụng cơ sở dữ liệu hoặc thậm chí là một tệp phẳng thông thường. Với chúng, các giao diện rất hữu ích trong việc thiết kế mã có thể kiểm tra được. Ví dụ: không phải lúc nào cũng thực tế khi gọi các dịch vụ bên ngoài trong mỗi lần kiểm tra, do đó, thay vào đó, nên triển khai một cách triển khai thay thế, đơn giản nhất (chẳng hạn như sơ khai): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } Việc triển khai này có thể được sử dụng ở bất kỳ nơi nào cần có giao diện TimezoneService , tách biệt tập lệnh kiểm tra khỏi sự phụ thuộc vào các thành phần bên ngoài. Nhiều ví dụ tuyệt vời về việc sử dụng hiệu quả các giao diện như vậy được gói gọn trong thư viện chuẩn Java. Bộ sưu tập, danh sách, bộ - những giao diện này có nhiều cách triển khai có thể được hoán đổi liền mạch và có thể được hoán đổi cho nhau khi hợp đồng tận dụng. Ví dụ: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. Dây
Chuỗi là một trong những loại được sử dụng nhiều nhất trong cả Java và các ngôn ngữ lập trình khác. Ngôn ngữ Java đơn giản hóa nhiều thao tác chuỗi thông thường bằng cách hỗ trợ các thao tác nối và so sánh ngay lập tức. Ngoài ra, thư viện chuẩn còn chứa nhiều lớp giúp cho các thao tác chuỗi trở nên hiệu quả. Đây chính xác là những gì chúng ta sẽ thảo luận trong phần này. Trong Java, chuỗi là đối tượng bất biến được biểu thị bằng mã hóa UTF-16. Mỗi khi bạn nối các chuỗi (hoặc thực hiện bất kỳ thao tác nào sửa đổi chuỗi gốc), một phiên bản mới của lớp String sẽ được tạo . Do đó, thao tác nối có thể trở nên rất kém hiệu quả, khiến nhiều phiên bản trung gian của lớp String được tạo ra (nói chung là tạo rác). Nhưng thư viện chuẩn Java có chứa hai lớp rất hữu ích với mục đích làm cho thao tác chuỗi trở nên thuận tiện. Đây là StringBuilderStringBuffer (điểm khác biệt duy nhất giữa chúng là StringBuffer an toàn cho luồng trong khi StringBuilder thì ngược lại). Chúng ta hãy xem một vài ví dụ về một trong các lớp này đang được sử dụng: final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); Mặc dù sử dụng StringBuilder/StringBuffer là cách được khuyến nghị để thao tác với các chuỗi, nhưng nó có thể trông quá mức cần thiết trong kịch bản đơn giản nhất là nối hai hoặc ba chuỗi, sao cho toán tử phép cộng thông thường ( ("+"), ví dụ: Thông String userId = "user:" + new Random().nextInt( 100 ); thường, giải pháp thay thế tốt nhất để đơn giản hóa việc ghép nối là sử dụng định dạng chuỗi cũng như Thư viện chuẩn Java để giúp cung cấp phương thức trợ giúp String.format tĩnh . Điều này hỗ trợ một bộ công cụ xác định định dạng phong phú, bao gồm số, ký hiệu, ngày/giờ, v.v. (Tham khảo tài liệu tham khảo để biết chi tiết đầy đủ) Phương String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12% thức String.format cung cấp một cách tiếp cận rõ ràng và nhẹ nhàng để tạo chuỗi từ nhiều loại dữ liệu khác nhau. Điều đáng chú ý là các IDE Java hiện đại có thể phân tích đặc tả định dạng từ các đối số được truyền cho phương thức String.format và cảnh báo các nhà phát triển nếu phát hiện bất kỳ sự không khớp nào.
8. Quy ước đặt tên
Java là ngôn ngữ không buộc các nhà phát triển phải tuân thủ nghiêm ngặt bất kỳ quy ước đặt tên nào, nhưng cộng đồng đã phát triển một bộ quy tắc đơn giản giúp mã Java trông nhất quán cả trong thư viện chuẩn và trong bất kỳ dự án Java nào khác:
  • tên gói được viết thường: org.junit, com.fasterxml.jackson, javax.json
  • Tên các lớp, bảng liệt kê, giao diện, chú thích được viết bằng chữ in hoa: StringBuilder, Runnable, @Override
  • tên của các trường hoặc phương thức (ngoại trừ static Final ) được chỉ định bằng ký hiệu lạc đà: isEmpty, format, addAll
  • trường cuối cùng tĩnh hoặc tên hằng số liệt kê được viết hoa, phân tách bằng dấu gạch dưới ("_"): LOG, MIN_RADIX, INSTANCE.
  • các biến cục bộ hoặc đối số phương thức được nhập theo ký hiệu lạc đà: str, newLength, minCapacity
  • Tên loại tham số cho generics được biểu thị bằng một chữ cái viết hoa: T, U, E
Bằng cách tuân theo các quy ước đơn giản này, mã bạn viết sẽ có phong cách ngắn gọn và không thể phân biệt được với một thư viện hoặc khung khác và sẽ có cảm giác như được phát triển bởi cùng một người (một trong những trường hợp hiếm hoi khi các quy ước thực sự hoạt động).
9. Thư viện chuẩn
Cho dù bạn đang thực hiện loại dự án Java nào thì các thư viện chuẩn Java vẫn là những người bạn tốt nhất của bạn. Vâng, thật khó để không đồng ý rằng chúng có một số góc cạnh thô ráp và các quyết định thiết kế kỳ lạ, tuy nhiên, 99% đó là mã chất lượng cao được viết bởi các chuyên gia. Nó đáng để khám phá. Mỗi bản phát hành Java đều mang lại nhiều tính năng mới cho các thư viện hiện có (với một số vấn đề có thể xảy ra với các tính năng cũ) và cũng bổ sung thêm nhiều thư viện mới. Java 5 mang đến một thư viện tương tranh mới như một phần của gói java.util.concurrent . Java 6 đã giới thiệu hỗ trợ tập lệnh (ít được biết đến hơn) ( gói javax.script ) và API trình biên dịch Java (như một phần của gói javax.tools ). Java 7 mang lại nhiều cải tiến cho java.util.concurrent , giới thiệu thư viện I/O mới trong gói java.nio.file và hỗ trợ các ngôn ngữ động trong java.lang.invoke . Và cuối cùng, Java 8 đã thêm ngày/giờ được chờ đợi từ lâu trong gói java.time . Nền tảng Java đang phát triển và điều quan trọng là nó phải phát triển cùng với những thay đổi trên. Bất cứ khi nào bạn cân nhắc việc đưa thư viện hoặc khung công tác của bên thứ ba vào dự án của mình, hãy đảm bảo rằng chức năng được yêu cầu chưa có trong các thư viện Java tiêu chuẩn (tất nhiên, có nhiều cách triển khai thuật toán chuyên biệt và hiệu suất cao đi trước các thuật toán trong thư viện chuẩn, nhưng trong hầu hết các trường hợp, chúng thực sự không cần thiết).
10. Tính bất biến
Tính bất biến xuyên suốt hướng dẫn và trong phần này vẫn như một lời nhắc nhở: hãy xem xét nó một cách nghiêm túc. Nếu một lớp bạn thiết kế hoặc một phương thức bạn triển khai có thể cung cấp sự đảm bảo về tính bất biến, thì nó có thể được sử dụng trong hầu hết các trường hợp ở mọi nơi mà không sợ bị sửa đổi cùng lúc. Điều này sẽ làm cho cuộc sống của bạn với tư cách là một nhà phát triển (và hy vọng là cuộc sống của các thành viên trong nhóm của bạn) dễ dàng hơn.
11. Kiểm tra
Việc thực hành phát triển dựa trên thử nghiệm (TDD) cực kỳ phổ biến trong cộng đồng Java, nâng cao tiêu chuẩn về chất lượng mã. Với tất cả những lợi ích mà TDD mang lại, thật đáng buồn khi thấy thư viện chuẩn Java ngày nay không bao gồm bất kỳ framework kiểm thử hay công cụ hỗ trợ nào. Tuy nhiên, thử nghiệm đã trở thành một phần cần thiết trong quá trình phát triển Java hiện đại và trong phần này chúng ta sẽ xem xét một số kỹ thuật cơ bản sử dụng khung công tác JUnit . Về cơ bản, trong JUnit, mỗi bài kiểm tra là một tập hợp các câu lệnh về trạng thái hoặc hành vi dự kiến ​​của một đối tượng. Bí quyết để viết các bài kiểm tra tốt là giữ cho chúng đơn giản và ngắn gọn, kiểm tra từng thứ một. Như một bài tập, chúng ta hãy viết một tập hợp các bài kiểm tra để xác minh rằng String.format là một hàm từ phần chuỗi trả về kết quả mong muốn. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } Cả hai bài kiểm tra đều trông rất dễ đọc và việc thực hiện chúng là các phiên bản. Ngày nay, một dự án Java trung bình chứa hàng trăm trường hợp thử nghiệm, cung cấp cho nhà phát triển phản hồi nhanh chóng về các hồi quy hoặc tính năng trong quá trình phát triển.
12. Tiếp theo
Phần hướng dẫn này hoàn thành một loạt các cuộc thảo luận liên quan đến thực hành lập trình bằng Java và các sách hướng dẫn sử dụng ngôn ngữ lập trình này. Lần tới chúng ta sẽ quay lại các tính năng của ngôn ngữ, khám phá thế giới Java về các ngoại lệ, loại của chúng, cách thức và thời điểm sử dụng chúng.
13. Tải mã nguồn
Đây là bài học về các nguyên tắc phát triển chung từ khóa học Java nâng cao. Mã nguồn của bài học có thể được tải xuống tại đây .
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION