JavaRush /Blog Java /Random-VI /Nghỉ giải lao #85. Ba bài học Java tôi đã học được một cá...

Nghỉ giải lao #85. Ba bài học Java tôi đã học được một cách khó khăn. Cách sử dụng nguyên tắc SOLID trong mã

Xuất bản trong nhóm

Ba bài học Java tôi đã học được một cách khó khăn

Nguồn: Học Java trung bình rất khó. Tôi đã học được từ những sai lầm của mình. Bây giờ bạn cũng có thể học hỏi từ những sai lầm và kinh nghiệm cay đắng của tôi, những điều mà bạn không nhất thiết phải có. Nghỉ giải lao #85.  Ba bài học Java tôi đã học được một cách khó khăn.  Cách sử dụng nguyên tắc SOLID trong code - 1

1. Lambdas có thể gây rắc rối.

Lambdas thường vượt quá 4 dòng mã và lớn hơn mong đợi. Điều này tạo gánh nặng cho trí nhớ làm việc. Bạn có cần thay đổi một biến từ lambda không? Bạn không thể làm điều đó. Tại sao? Nếu lambda có thể truy cập các biến trang cuộc gọi thì vấn đề về luồng có thể phát sinh. Vì vậy bạn không thể thay đổi biến từ lambda. Nhưng con đường Hạnh phúc trong lambda hoạt động tốt. Sau khi chạy lỗi, bạn sẽ nhận được phản hồi này:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Thật khó để theo dõi dấu vết ngăn xếp lambda. Những cái tên khó hiểu và khó theo dõi và gỡ lỗi. Nhiều lambdas hơn = nhiều dấu vết ngăn xếp hơn. Cách tốt nhất để gỡ lỗi lambdas là gì? Sử dụng kết quả trung gian.
map(elem -> {
 int result = elem.getResult();
 return result;
});
Một cách hay khác là sử dụng các kỹ thuật gỡ lỗi IntelliJ nâng cao. Sử dụng TAB để chọn mã bạn muốn gỡ lỗi và kết hợp nó với các kết quả trung gian. “Khi chúng ta dừng lại ở một dòng chứa lambda, nếu chúng ta nhấn F7 (bước vào), thì IntelliJ sẽ đánh dấu đoạn cần được gỡ lỗi. Chúng tôi có thể chuyển khối sang gỡ lỗi bằng cách sử dụng Tab và sau khi quyết định điều đó, hãy nhấn F7 lần nữa.” Làm cách nào để truy cập các biến trang cuộc gọi từ lambda? Bạn chỉ có thể truy cập các biến cuối cùng hoặc thực sự là cuối cùng. Bạn cần tạo một trình bao bọc xung quanh các biến vị trí cuộc gọi. Sử dụng AtomicType hoặc sử dụng loại của riêng bạn. Bạn có thể thay đổi trình bao bọc biến đã tạo bằng lambda. Làm thế nào để giải quyết vấn đề dấu vết ngăn xếp? Sử dụng các chức năng được đặt tên. Bằng cách này, bạn có thể nhanh chóng tìm thấy mã có trách nhiệm, kiểm tra logic và giải quyết vấn đề. Sử dụng các hàm được đặt tên để loại bỏ dấu vết ngăn xếp khó hiểu. Lambda tương tự có được lặp lại không? Đặt nó trong một chức năng được đặt tên. Bạn sẽ có một điểm tham chiếu duy nhất. Mỗi lambda có một hàm được tạo ra, điều này gây khó khăn cho việc theo dõi.
lambda$yourNamedFunction
lambda$0
Các hàm được đặt tên giải quyết một vấn đề khác. Lambda lớn. Các hàm được đặt tên chia nhỏ các lambda lớn, tạo các đoạn mã nhỏ hơn và tạo các hàm có thể cắm được.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. Vấn đề với danh sách

Bạn cần làm việc với các danh sách ( Lists ). Bạn cần HashMap cho dữ liệu. Đối với các vai trò, bạn sẽ cần TreeMap . Danh sách cứ kéo dài. Và không có cách nào bạn có thể tránh làm việc với các bộ sưu tập. Làm thế nào để lập một danh sách? Bạn cần loại danh sách nào? Nó nên bất biến hay có thể thay đổi? Tất cả những câu trả lời này đều ảnh hưởng đến tương lai mã của bạn. Hãy chọn trước danh sách phù hợp để sau này bạn không hối hận. Arrays::asList tạo danh sách “từ đầu đến cuối”. Bạn không thể làm gì với danh sách này? Bạn không thể thay đổi kích thước nó. Anh ấy là không thể thay đổi. Bạn có thể làm gì ở đây? Chỉ định các phần tử, sắp xếp hoặc các thao tác khác không ảnh hưởng đến kích thước. Hãy sử dụng Arrays::asList một cách cẩn thận vì kích thước của nó là không thay đổi nhưng nội dung của nó thì không. new ArrayList() tạo một danh sách “có thể thay đổi” mới. Danh sách đã tạo hỗ trợ những hoạt động nào? Thế thôi, và đây là lý do để bạn phải cẩn thận. Tạo danh sách có thể thay đổi từ danh sách không thể thay đổi bằng cách sử dụng new ArrayList() . List::of tạo ra một bộ sưu tập "bất biến". Kích thước và nội dung của nó không thay đổi trong những điều kiện nhất định. Nếu nội dung là dữ liệu nguyên thủy, chẳng hạn như int , thì danh sách này không thể thay đổi. Hãy xem ví dụ sau.
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### Danh sách KIỂM TRAOF với nội dung có thể thay đổi ### a123
Bạn cần tạo các đối tượng bất biến và chèn chúng vào List::of . Tuy nhiên, List::of không cung cấp bất kỳ sự đảm bảo nào về tính bất biến. List::of cung cấp tính bất biến, độ tin cậy và khả năng đọc. Biết khi nào nên sử dụng cấu trúc có thể thay đổi và khi nào nên sử dụng cấu trúc bất biến. Danh sách các đối số không nên thay đổi phải nằm trong danh sách bất biến. Một danh sách có thể thay đổi có thể là một danh sách có thể thay đổi. Hiểu bộ sưu tập nào bạn cần để tạo mã đáng tin cậy.

3. Chú thích làm bạn chậm lại

Bạn có sử dụng chú thích không? Bạn có hiểu họ không? Bạn có biết họ làm gì không? Nếu bạn cho rằng Logged Annotation phù hợp với mọi phương pháp thì bạn đã nhầm. Tôi đã sử dụng Logged để ghi lại các đối số phương thức. Thật ngạc nhiên, nó không hoạt động.
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Có gì sai với mã này? Có rất nhiều thông tin cấu hình ở đây. Bạn sẽ gặp phải điều này nhiều lần. Cấu hình được trộn với mã thông thường. Bản thân nó không tệ, nhưng nó thu hút sự chú ý của bạn. Chú thích là cần thiết để giảm mã soạn sẵn. Bạn không cần phải viết logic ghi nhật ký cho từng điểm cuối. Không cần thiết lập giao dịch, hãy sử dụng @Transactional . Chú thích giảm mẫu bằng cách trích xuất mã. Không có người chiến thắng rõ ràng ở đây vì cả hai đều tham gia trò chơi. Tôi vẫn sử dụng XML và chú thích. Khi tìm thấy mẫu lặp lại, tốt nhất bạn nên chuyển logic vào chú thích. Ví dụ: ghi nhật ký là một tùy chọn chú thích tốt. Đạo đức: đừng lạm dụng chú thích và đừng quên XML.

Phần thưởng: Bạn có thể gặp vấn đề với Tùy chọn

Bạn sẽ sử dụng orElse từ Tùy chọn . Hành vi không mong muốn xảy ra khi bạn không truyền hằng số orElse . Bạn nên lưu ý điều này để ngăn chặn những rắc rối trong tương lai. Hãy xem xét một vài ví dụ. Khi getValue(x) trả về một giá trị, getValue(y) sẽ được thực thi . Phương thức trong orElse được thực thi nếu getValue(x) trả về giá trị Tùy chọn không trống .
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
Sử dụng orElseGet . Nó sẽ không thực thi mã cho các Tùy chọn không trống .
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

Phần kết luận

Học Java rất khó. Bạn không thể học Java trong 24 giờ. Rèn luyện kỹ năng của bạn. Hãy dành thời gian, học hỏi và vượt trội trong công việc của bạn.

Cách sử dụng nguyên tắc SOLID trong mã

Nguồn: Cleanthecode Viết mã đáng tin cậy đòi hỏi các nguyên tắc RẮN. Đến một lúc nào đó tất cả chúng ta đều phải học cách lập trình. Và hãy thành thật nhé. Chúng tôi thật ngu ngốc. Và mã của chúng tôi giống nhau. Cảm ơn Chúa, chúng tôi có RẮN. Nghỉ giải lao #85.  Ba bài học Java tôi đã học được một cách khó khăn.  Cách sử dụng nguyên tắc SOLID trong code - 2

Nguyên tắc RẮN

Vậy làm thế nào để bạn viết mã SOLID? Nó thực sự đơn giản. Bạn chỉ cần tuân theo năm quy tắc sau:
  • Nguyên tắc trách nhiệm duy nhất
  • Nguyên tắc đóng mở
  • Nguyên lý thay thế Liskov
  • Nguyên tắc tách giao diện
  • Nguyên tắc đảo ngược phụ thuộc
Đừng lo lắng! Những nguyên tắc này đơn giản hơn nhiều so với vẻ ngoài của chúng!

Nguyên tắc trách nhiệm duy nhất

Trong cuốn sách của mình, Robert C. Martin mô tả nguyên tắc này như sau: “Một giai cấp chỉ nên có một lý do để thay đổi.” Chúng ta hãy cùng nhau xem xét hai ví dụ.

1. Những điều không nên làm

Chúng tôi có một lớp tên là Người dùng cho phép người dùng thực hiện những việc sau:
  • Đăng ký tài khoản
  • Đăng nhập
  • Nhận thông báo khi đăng nhập lần đầu
Lớp này bây giờ có một số trách nhiệm. Nếu quá trình đăng ký thay đổi thì lớp User sẽ thay đổi. Điều tương tự sẽ xảy ra nếu quá trình đăng nhập hoặc quá trình thông báo thay đổi. Điều này có nghĩa là lớp học bị quá tải. Anh ấy có quá nhiều trách nhiệm. Cách dễ nhất để khắc phục điều này là chuyển trách nhiệm sang các lớp của bạn để lớp Người dùng chỉ chịu trách nhiệm kết hợp các lớp. Nếu quá trình sau đó thay đổi, bạn có một lớp riêng biệt, rõ ràng cần thay đổi.

2. Phải làm gì

Hãy tưởng tượng một lớp sẽ hiển thị thông báo cho người dùng mới, FirstUseNotification . Nó sẽ bao gồm ba chức năng:
  • Kiểm tra xem thông báo đã được hiển thị chưa
  • Hiện thông báo
  • Đánh dấu thông báo là đã được hiển thị
Lớp học này có nhiều lý do để thay đổi? KHÔNG. Lớp này có một chức năng rõ ràng - hiển thị thông báo cho người dùng mới. Điều này có nghĩa là lớp có một lý do để thay đổi. Cụ thể, nếu mục tiêu này thay đổi. Vì vậy, lớp này không vi phạm nguyên tắc trách nhiệm duy nhất. Tất nhiên, có một số điều có thể thay đổi: cách thông báo được đánh dấu là đã đọc có thể thay đổi hoặc cách thông báo xuất hiện. Tuy nhiên, vì mục đích của lớp học rất rõ ràng và cơ bản nên điều này là ổn.

Nguyên tắc đóng mở

Nguyên tắc đóng mở được đưa ra bởi Bertrand Meyer: “Các đối tượng phần mềm (lớp, mô-đun, hàm, v.v.) phải mở để mở rộng nhưng đóng để sửa đổi”. Nguyên tắc này thực ra rất đơn giản. Bạn phải viết mã của mình để có thể thêm các tính năng mới vào mã đó mà không cần thay đổi mã nguồn. Điều này giúp ngăn chặn tình huống bạn cần thay đổi các lớp phụ thuộc vào lớp đã sửa đổi của bạn. Tuy nhiên, nguyên tắc này khó thực hiện hơn nhiều. Meyer đề xuất sử dụng tính kế thừa. Nhưng nó dẫn đến một kết nối mạnh mẽ. Chúng ta sẽ thảo luận điều này trong Nguyên tắc tách giao diện và Nguyên tắc đảo ngược phụ thuộc. Vì vậy Martin đã nghĩ ra một cách tiếp cận tốt hơn: sử dụng tính đa hình. Thay vì kế thừa thông thường, cách tiếp cận này sử dụng các lớp cơ sở trừu tượng. Bằng cách này, các thông số kỹ thuật kế thừa có thể được sử dụng lại trong khi không cần thực hiện. Giao diện có thể được viết một lần và sau đó đóng lại để thực hiện thay đổi. Các chức năng mới sau đó phải triển khai giao diện này và mở rộng nó.

Nguyên lý thay thế Liskov

Nguyên tắc này được phát minh bởi Barbara Liskov, người đoạt giải Turing vì những đóng góp của bà cho ngôn ngữ lập trình và phương pháp phần mềm. Trong bài viết của mình, cô đã xác định nguyên tắc của mình như sau: “Các đối tượng trong một chương trình phải có thể thay thế được bằng các thể hiện của kiểu con của chúng mà không ảnh hưởng đến việc thực thi chính xác của chương trình”. Hãy xem nguyên tắc này với tư cách là một lập trình viên. Hãy tưởng tượng chúng ta có một hình vuông. Nó có thể là một hình chữ nhật, nghe có vẻ hợp lý vì hình vuông là một hình dạng đặc biệt của hình chữ nhật. Đây chính là lúc nguyên lý thay thế Liskov phát huy tác dụng. Bất cứ nơi nào bạn muốn thấy một hình chữ nhật trong mã của mình, thì hình vuông cũng có thể xuất hiện. Bây giờ hãy tưởng tượng hình chữ nhật của bạn có các phương thức SetWidthSetHeight . Điều này có nghĩa là hình vuông cũng cần những phương pháp này. Thật không may, điều này không có ý nghĩa gì cả. Điều này có nghĩa là nguyên tắc thay thế Liskov bị vi phạm ở đây.

Nguyên tắc tách giao diện

Giống như tất cả các nguyên tắc, nguyên tắc tách giao diện đơn giản hơn nhiều so với vẻ ngoài của nó: “Nhiều giao diện dành riêng cho khách hàng sẽ tốt hơn một giao diện có mục đích chung”. Giống như nguyên tắc trách nhiệm duy nhất, mục tiêu là giảm tác dụng phụ và số lượng thay đổi cần thiết. Tất nhiên, không ai cố tình viết mã như vậy. Nhưng rất dễ gặp phải. Bạn có nhớ hình vuông trong nguyên tắc trước không? Bây giờ hãy tưởng tượng rằng chúng ta quyết định thực hiện kế hoạch của mình: chúng ta tạo ra một hình vuông từ một hình chữ nhật. Bây giờ chúng tôi đang buộc hình vuông triển khai setWidthsetHeight , những việc này có thể không làm được gì cả. Nếu họ làm vậy, có lẽ chúng ta sẽ phá vỡ thứ gì đó vì chiều rộng và chiều cao sẽ không như chúng ta mong đợi. Thật may mắn cho chúng ta, điều này có nghĩa là chúng ta không còn vi phạm nguyên tắc thay thế Liskov nữa, vì giờ đây chúng ta cho phép sử dụng hình vuông ở bất cứ nơi nào chúng ta sử dụng hình chữ nhật. Tuy nhiên, điều này lại tạo ra một vấn đề mới: hiện nay chúng ta đang vi phạm nguyên tắc phân chia các giao diện. Chúng ta buộc lớp dẫn xuất phải triển khai chức năng mà nó chọn không sử dụng.

Nguyên tắc đảo ngược phụ thuộc

Nguyên tắc cuối cùng rất đơn giản: các mô-đun cấp cao phải được tái sử dụng và không bị ảnh hưởng bởi những thay đổi đối với các mô-đun cấp thấp.
  • A. Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Cả hai đều phải phụ thuộc vào sự trừu tượng (chẳng hạn như giao diện).
  • B. Sự trừu tượng không nên phụ thuộc vào chi tiết. Các chi tiết (triển khai cụ thể) phải phụ thuộc vào sự trừu tượng hóa.
Điều này có thể đạt được bằng cách triển khai một sự trừu tượng hóa để phân tách các mô-đun cấp cao và cấp thấp. Tên của nguyên tắc gợi ý rằng hướng của sự phụ thuộc thay đổi, nhưng thực tế không phải vậy. Nó chỉ phân tách sự phụ thuộc bằng cách đưa ra sự trừu tượng hóa giữa chúng. Kết quả là bạn sẽ nhận được hai phần phụ thuộc:
  • Mô-đun cấp cao, tùy thuộc vào sự trừu tượng
  • Mô-đun cấp thấp tùy thuộc vào cùng một mức độ trừu tượng
Điều này có vẻ khó khăn nhưng trên thực tế nó sẽ tự động xảy ra nếu bạn áp dụng đúng nguyên tắc đóng/mở và nguyên tắc thay thế Liskov. Đó là tất cả! Bây giờ bạn đã học được năm nguyên tắc cốt lõi làm nền tảng cho SOLID. Với năm nguyên tắc này, bạn có thể làm cho mã của mình trở nên tuyệt vời!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION