Grokking văn hóa Java - tại sao mọi thứ rất nặng nề? Nó tối ưu hóa để làm gì? [đóng cửa]


288

Tôi đã sử dụng mã trong Python rất nhiều. Bây giờ, vì lý do công việc, tôi viết mã bằng Java. Các dự án tôi làm khá nhỏ và có thể Python sẽ hoạt động tốt hơn, nhưng có những lý do phi kỹ thuật hợp lệ để sử dụng Java (tôi không thể đi sâu vào chi tiết).

Cú pháp Java không có vấn đề gì; nó chỉ là một ngôn ngữ khác Nhưng ngoài cú pháp, Java có một nền văn hóa, một tập hợp các phương thức phát triển và các thực tiễn được coi là "chính xác". Và bây giờ tôi hoàn toàn thất bại trong việc "mò mẫm" văn hóa đó. Vì vậy, tôi thực sự sẽ đánh giá cao giải thích hoặc con trỏ đi đúng hướng.

Một ví dụ hoàn chỉnh tối thiểu có sẵn trong câu hỏi Stack Overflow mà tôi đã bắt đầu: https://stackoverflow.com/questions/43619566/retinating-a-result-with-several-values-the-java-way/43620339

Tôi có một nhiệm vụ - phân tích cú pháp (từ một chuỗi đơn) và xử lý một bộ ba giá trị. Trong Python, nó là một lớp lót (tuple), trong Pascal hoặc C là bản ghi / cấu trúc 5 lớp.

Theo các câu trả lời, tương đương với một cấu trúc có sẵn trong cú pháp Java và bộ ba có sẵn trong thư viện Apache được sử dụng rộng rãi - nhưng cách thực hiện "chính xác" thực sự là bằng cách tạo một lớp riêng cho giá trị, hoàn thành với getters và setters. Ai đó đã rất tử tế khi cung cấp một ví dụ hoàn chỉnh. Đó là 47 dòng mã (tốt, một số trong những dòng này là khoảng trống).

Tôi hiểu rằng một cộng đồng phát triển khổng lồ dường như không "sai". Vì vậy, đây là một vấn đề với sự hiểu biết của tôi.

Các thực hành Python tối ưu hóa cho khả năng đọc (trong triết lý đó, dẫn đến khả năng duy trì) và sau đó, tốc độ phát triển. Thực hành C tối ưu hóa cho việc sử dụng tài nguyên. Các thực hành Java tối ưu hóa để làm gì? Dự đoán tốt nhất của tôi là khả năng mở rộng (mọi thứ nên ở trạng thái sẵn sàng cho dự án hàng triệu LỘC), nhưng đó là một phỏng đoán rất yếu.


1
sẽ không trả lời các khía cạnh kỹ thuật của câu hỏi nhưng vẫn trong ngữ cảnh: softwareengineering.stackexchange.com/a/63145/13899
Newtopian

74
Bạn có thể thưởng thức bài tiểu luận của Steve Yegge Thi hành tại Vương quốc Danh từ
Đại tá Panic


3
Đây là những gì tôi nghĩ là câu trả lời đúng. Bạn không mò mẫm Java vì bạn nghĩ về lập trình như một sysadmin chứ không phải lập trình viên. Đối với bạn, phần mềm là thứ mà bạn sử dụng để đạt được một nhiệm vụ cụ thể theo cách thức phù hợp nhất. Tôi đã viết mã Java được 20 năm và một số dự án tôi đã thực hiện phải mất một nhóm 20, 2 năm để đạt được. Java không phải là sự thay thế cho Python cũng như ngược lại. Cả hai đều làm công việc của họ, nhưng công việc của họ là hoàn toàn khác nhau.
Richard

1
Lý do mà Java là tiêu chuẩn thực tế là vì nó đơn giản là đúng. Nó được tạo ra ngay từ lần đầu tiên, bởi những người nghiêm túc có râu trước khi râu là mốt. Nếu bạn đã sử dụng để gõ các tập lệnh shell để thao tác các tệp, thì Java dường như không thể chịu đựng được. Nhưng nếu bạn muốn tạo một cụm máy chủ dự phòng kép có khả năng phục vụ 50 triệu người mỗi ngày, được hỗ trợ bởi bộ nhớ đệm redis cụm, 3 hệ thống thanh toán và cụm cơ sở dữ liệu oracle 20 triệu bảng .. Bạn sẽ không sử dụng python, ngay cả khi truy cập cơ sở dữ liệu trong Python là 25 dòng mã.
Richard

Câu trả lời:


237

Ngôn ngữ Java

Tôi tin rằng tất cả các câu trả lời này đều thiếu điểm bằng cách cố gắng đưa ra ý định cho cách thức hoạt động của Java. Mức độ chi tiết của Java không xuất phát từ việc nó được hướng đối tượng, vì Python và nhiều ngôn ngữ khác chưa có cú pháp chặt chẽ hơn. Độ dài của Java cũng không đến từ sự hỗ trợ của các bộ sửa đổi truy cập. Thay vào đó, nó chỉ đơn giản là cách Java được thiết kế và phát triển.

Java ban đầu được tạo ra như một C cải tiến một chút với OO. Như vậy Java có cú pháp từ những năm 70. Hơn nữa, Java rất thận trọng trong việc thêm các tính năng để duy trì khả năng tương thích ngược và cho phép nó đứng vững trước thử thách của thời gian. Có phải Java đã thêm các tính năng hợp thời như chữ XML vào năm 2005 khi XML là cơn thịnh nộ mà ngôn ngữ sẽ bị làm mờ đi với các tính năng ma mà không ai quan tâm và điều đó hạn chế sự phát triển của nó 10 năm sau đó. Do đó, Java đơn giản là thiếu rất nhiều cú pháp hiện đại để diễn đạt các khái niệm chặt chẽ hơn.

Tuy nhiên, không có gì cơ bản ngăn Java chấp nhận cú pháp đó. Ví dụ, Java 8 đã thêm lambdas và các tham chiếu phương thức, giúp giảm đáng kể tính dài dòng trong nhiều tình huống. Java tương tự có thể thêm hỗ trợ cho các khai báo kiểu dữ liệu nhỏ gọn như các lớp trường hợp của Scala. Nhưng Java đơn giản là không làm như vậy. Xin lưu ý rằng các loại giá trị tùy chỉnh nằm ở đường chân trời và tính năng này có thể giới thiệu một cú pháp mới để khai báo chúng. Tôi cho rằng chúng ta sẽ thấy.


Văn hóa Java

Lịch sử phát triển Java của doanh nghiệp phần lớn đã đưa chúng ta đến với văn hóa mà chúng ta thấy ngày nay. Vào cuối những năm 90 / đầu 00, Java đã trở thành một ngôn ngữ cực kỳ phổ biến cho các ứng dụng kinh doanh phía máy chủ. Trước đó, các ứng dụng đó phần lớn được viết đặc biệt và kết hợp nhiều mối quan tâm phức tạp, chẳng hạn như API HTTP, cơ sở dữ liệu và xử lý các nguồn cấp dữ liệu XML.

Trong những năm 00, rõ ràng là nhiều ứng dụng trong số này có rất nhiều điểm chung và khung để quản lý các mối quan tâm này, như ORM Hibernate, trình phân tích cú pháp Xerces XML, API và API servlet và EJB, đã trở nên phổ biến. Tuy nhiên, trong khi các khung này làm giảm nỗ lực làm việc trong miền cụ thể mà chúng đặt để tự động hóa, chúng yêu cầu cấu hình và phối hợp. Vào thời điểm đó, vì bất kỳ lý do gì, người ta thường viết các khung để phục vụ cho trường hợp sử dụng phức tạp nhất và do đó các thư viện này rất phức tạp để thiết lập và tích hợp. Và theo thời gian, chúng ngày càng phức tạp khi chúng tích lũy các tính năng. Phát triển doanh nghiệp Java dần trở nên ngày càng nhiều hơn về việc kết hợp các thư viện bên thứ ba và ít hơn về việc viết các thuật toán.

Cuối cùng, cấu hình và quản lý công cụ doanh nghiệp tẻ nhạt đã trở nên đau đớn đến mức các khung, đặc biệt là khung Spring, đã xuất hiện để quản lý việc quản lý. Bạn có thể đặt tất cả cấu hình của mình ở một nơi, theo lý thuyết, và công cụ cấu hình sau đó sẽ cấu hình các mảnh và nối chúng lại với nhau. Thật không may, các "khung khung" đã thêm sự trừu tượng và phức tạp hơn trên đỉnh của toàn bộ quả bóng sáp.

Trong vài năm qua, các thư viện nhẹ hơn đã trở nên phổ biến. Tuy nhiên, toàn bộ thế hệ lập trình viên Java đã đến tuổi trong quá trình phát triển các khung công tác nặng. Các mô hình vai trò của họ, những người phát triển các khung công tác, đã viết các nhà máy sản xuất và bộ nạp đậu cấu hình proxy. Họ đã phải cấu hình và tích hợp những điều quái dị này hàng ngày. Và kết quả là văn hóa của cộng đồng nói chung đã theo gương của những khuôn khổ này và có xu hướng quá kỹ sư.


15
Điều thú vị là các ngôn ngữ khác dường như đang chọn một số "java-isms". Các tính năng OO của PHP được lấy cảm hứng rất nhiều từ Java và ngày nay JavaScript thường bị chỉ trích vì số lượng khung công tác khổng lồ và việc thu thập tất cả các phụ thuộc và bắt đầu một ứng dụng mới khó đến mức nào.
marcus

14
@marcus có lẽ bởi vì một số người cuối cùng đã học được điều "không phát minh lại bánh xe"?
Rốt cuộc

9
"Terse" thường được dịch thành phức tạp, "thông minh" , code-golf-ish one-liners.
Tulains Córdova

7
Chu đáo, trả lời tốt bằng văn bản. Bối cảnh lịch sử. Tương đối không quan tâm. Làm rất tốt
Cheezmeister

10
@ TulainsCórdova Tôi đồng ý rằng chúng ta nên "tránh ... những mánh khóe thông minh như bệnh dịch" (Donald Knuth), nhưng điều đó không có nghĩa là viết một forvòng lặp khi mapthích hợp hơn (và có thể đọc được). Có một phương tiện hạnh phúc.
Brian McCutchon

73

Tôi tin rằng tôi có câu trả lời cho một trong những điểm bạn nêu ra mà chưa được người khác nêu ra.

Java tối ưu hóa để phát hiện lỗi lập trình viên thời gian biên dịch bằng cách không bao giờ đưa ra bất kỳ giả định nào

Nói chung, Java có xu hướng chỉ suy luận sự thật về mã nguồn sau khi lập trình viên đã thể hiện rõ ràng ý định của mình. Trình biên dịch Java không bao giờ đưa ra bất kỳ giả định nào về mã và sẽ chỉ sử dụng suy luận để giảm mã dự phòng.

Lý do đằng sau triết lý này là lập trình viên chỉ là con người. Những gì chúng tôi viết không phải luôn luôn là những gì chúng tôi thực sự có ý định chương trình để làm. Ngôn ngữ Java cố gắng giảm thiểu một số vấn đề đó bằng cách buộc nhà phát triển luôn luôn tuyên bố rõ ràng các loại của họ. Đó chỉ là một cách để kiểm tra kỹ xem mã được viết có thực sự đúng như dự định hay không.

Một số ngôn ngữ khác thúc đẩy logic đó hơn nữa bằng cách kiểm tra các điều kiện trước, điều kiện hậu và bất biến (mặc dù tôi không chắc họ làm điều đó vào thời gian biên dịch). Đây thậm chí là những cách cực đoan hơn để lập trình viên có trình biên dịch kiểm tra lại công việc của chính mình.

Trong trường hợp của bạn, điều này có nghĩa là để trình biên dịch đảm bảo rằng bạn thực sự trả về các loại mà bạn nghĩ rằng bạn đang trả về, bạn cần cung cấp thông tin đó cho trình biên dịch.

Trong Java có hai cách để làm điều đó:

  1. Sử dụng Triplet<A, B, C>như kiểu trả về (mà thực sự phải ở trong java.util, và tôi có thể không thực sự giải thích lý do tại sao nó không phải là. Đặc biệt là kể từ khi JDK8 giới thiệu Function, BiFunction, Consumer, BiConsumer, vv ... Nó chỉ có vẻ mà PairTripletít nhất sẽ có ý nghĩa. Nhưng tôi lạc đề )

  2. Tạo loại giá trị của riêng bạn cho mục đích đó, trong đó mỗi trường được đặt tên và nhập đúng.

Trong cả hai trường hợp đó, trình biên dịch có thể đảm bảo rằng hàm của bạn trả về các kiểu đã khai báo và người gọi nhận ra đâu là loại của mỗi trường được trả về và sử dụng chúng cho phù hợp.

Một số ngôn ngữ cung cấp kiểm tra kiểu tĩnh VÀ suy luận kiểu cùng một lúc, nhưng điều đó khiến cánh cửa mở ra cho một lớp tinh tế của các vấn đề không khớp loại. Trường hợp nhà phát triển dự định trả về một giá trị của một loại nhất định, nhưng thực tế trả về một loại khác và trình biên dịch VẪN chấp nhận mã bởi vì điều đó xảy ra, do tình cờ cả hàm và người gọi chỉ sử dụng các phương thức có thể được áp dụng cho cả hai dự định và các loại thực tế.

Xem xét một cái gì đó như thế này trong Bản mô tả (hoặc loại luồng) trong đó sử dụng suy luận kiểu thay vì gõ rõ ràng.

function parseDurationInMillis(csvLine){
    // here the developer intends to return a Number, 
    // but actually it's a String
    return csv.firstField();
}

// Compiler infers that parseDurationInMillis is String, so it does
// string concatenation and infers that plusTwoSeconds is String
// Developer actually intended Number
var plusTwoSeconds = 2000 + parseDurationInMillis(csvLine);

Đây là một trường hợp tầm thường ngớ ngẩn, tất nhiên, nhưng nó có thể tinh tế hơn rất nhiều và dẫn đến các vấn đề khó gỡ lỗi, bởi vì mã có vẻ đúng. Loại vấn đề này hoàn toàn tránh được trong Java và đó là những gì toàn bộ ngôn ngữ được thiết kế xung quanh.


Lưu ý rằng tuân theo các nguyên tắc Hướng đối tượng phù hợp và mô hình hướng theo miền, trường hợp phân tích thời lượng trong câu hỏi được liên kết cũng có thể được trả về dưới dạng java.time.Durationđối tượng, điều này sẽ rõ ràng hơn cả hai trường hợp trên.


38
Nói rằng Java tối ưu hóa tính chính xác của mã có thể là một điểm hợp lệ, nhưng đối với tôi (lập trình rất nhiều ngôn ngữ, gần đây là một chút của java) rằng ngôn ngữ đó bị lỗi hoặc cực kỳ kém hiệu quả khi làm như vậy. Được cho là, Java đã cũ và rất nhiều thứ không tồn tại trước đó, nhưng có những lựa chọn thay thế hiện đại cung cấp khả năng kiểm tra tính chính xác tốt hơn, như Haskell (và tôi có dám nói không? Rust hay Go). Tất cả sự khó sử dụng của Java là không cần thiết cho mục tiêu này. - Và bên cạnh đó, điều này không ở tất cả các giải thích nền văn hóa, nhưng Solomonoff tiết lộ rằng văn hóa là BS anyway.
tomsmeding

8
Mẫu đó không chứng minh rằng suy luận kiểu là vấn đề chỉ là quyết định của JavaScript cho phép chuyển đổi ngầm trong ngôn ngữ động là ngu ngốc. Bất kỳ ngôn ngữ động lành mạnh hoặc ngôn ngữ được mời chào tĩnh với loại suy luận sẽ không cho phép điều này. Một ví dụ cho cả hai loại: python sẽ ném thời gian chạy ngoại trừ, Haskell sẽ không biên dịch.
Voo

39
@tomsmeding Điều đáng chú ý là lập luận này đặc biệt ngớ ngẩn vì Haskell đã tồn tại lâu hơn Java 5 năm . Java có thể có một hệ thống loại, nhưng nó thậm chí không có trạng thái xác minh tính chính xác khi nó được phát hành.
Alexis King

21
Java tối ưu hóa để phát hiện lỗi thời gian biên dịch. - Không. Nó cung cấp cho nó nhưng, như những người khác đã lưu ý, nó chắc chắn không tối ưu hóa cho nó theo bất kỳ ý nghĩa nào của từ này. Bên cạnh đó, lập luận này là hoàn toàn không liên quan đến câu hỏi OP vì ngôn ngữ khác mà làm thực sự tối ưu hóa để phát hiện lỗi thời gian biên dịch có nhiều trọng lượng nhẹ hơn cú pháp cho các kịch bản tương tự. Độ dài quá mức của Java hoàn toàn không liên quan gì đến việc xác minh thời gian biên dịch của nó.
Konrad Rudolph

19
@KonradRudolph Vâng, câu trả lời này hoàn toàn vô lý, mặc dù tôi cho rằng nó hấp dẫn về mặt cảm xúc. Tôi không chắc tại sao nó có nhiều phiếu như vậy. Haskell (hoặc có lẽ thậm chí còn quan trọng hơn, Idris) tối ưu hóa chính xác hơn nhiều so với Java và nó có các cú pháp với một cú pháp nhẹ. Hơn nữa, việc xác định kiểu dữ liệu là một lớp lót và bạn có được mọi thứ mà phiên bản Java sẽ nhận được. Câu trả lời này là một cái cớ cho thiết kế ngôn ngữ kém và tính dài dòng không cần thiết, nhưng nó có vẻ tốt nếu bạn thích Java và không quen thuộc với các ngôn ngữ khác.
Alexis King

50

Java và Python là hai ngôn ngữ tôi sử dụng nhiều nhất nhưng tôi đến từ hướng khác. Đó là, tôi đã ở sâu trong thế giới Java trước khi tôi bắt đầu sử dụng Python để tôi có thể giúp đỡ. Tôi nghĩ rằng câu trả lời cho câu hỏi lớn hơn về "tại sao mọi thứ quá nặng" lại có đến 2 điều:

  • Các chi phí xung quanh sự phát triển giữa hai người giống như không khí trong một khinh khí cầu động vật dài. Bạn có thể bóp một phần của quả bóng và một phần khác phồng lên. Python có xu hướng ép phần đầu. Java siết chặt phần sau.
  • Java vẫn thiếu một số tính năng sẽ loại bỏ một phần trọng lượng này. Java 8 đã tạo ra một sự thay đổi lớn trong vấn đề này nhưng văn hóa vẫn chưa hoàn toàn tiêu hóa các thay đổi. Java có thể sử dụng một vài thứ nữa như yield.

Java 'tối ưu hóa' cho phần mềm giá trị cao sẽ được duy trì trong nhiều năm bởi các nhóm lớn người. Tôi đã có kinh nghiệm viết nội dung bằng Python và nhìn vào nó một năm sau đó và bị cản trở bởi chính mã của tôi. Trong Java, tôi có thể xem các đoạn mã nhỏ của người khác và ngay lập tức biết nó làm gì. Trong Python bạn không thể thực sự làm điều đó. Không phải là tốt hơn như bạn nhận ra, họ chỉ có chi phí khác nhau.

Trong trường hợp cụ thể mà bạn đề cập, không có bộ dữ liệu. Giải pháp dễ dàng là tạo ra một lớp với các giá trị công khai. Khi Java lần đầu tiên ra mắt, mọi người đã làm điều này khá thường xuyên. Vấn đề đầu tiên khi làm điều đó là vấn đề đau đầu về bảo trì. Nếu bạn cần thêm một số logic hoặc an toàn luồng hoặc muốn sử dụng đa hình, ít nhất bạn sẽ cần phải chạm vào mọi lớp tương tác với đối tượng 'tuple-esque' đó. Trong Python có các giải pháp cho việc này, __getattr__v.v. vì vậy nó không quá tệ.

Có một số thói quen xấu (IMO) xung quanh điều này mặc dù. Trong trường hợp này nếu bạn muốn một tuple, tôi đặt câu hỏi tại sao bạn lại biến nó thành một đối tượng có thể thay đổi. Bạn chỉ cần getters (trên một ghi chú bên cạnh, tôi ghét quy ước get / set nhưng nó là như vậy.) Tôi nghĩ rằng một lớp trần (có thể thay đổi hoặc không) có thể hữu ích trong bối cảnh riêng tư hoặc gói riêng trong Java . Đó là, bằng cách giới hạn các tham chiếu trong dự án vào lớp, bạn có thể cấu trúc lại sau này khi cần mà không thay đổi giao diện chung của lớp. Đây là một ví dụ về cách bạn có thể tạo một đối tượng bất biến đơn giản:

public class Blah 
{
  public static Blah blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    return new Blah(number, isInSeconds, lessThanOneMillis);
  }

  private final long number;
  private final boolean isInSeconds;
  private final boolean lessThanOneMillis;

  public Blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    this.number = number;
    this.isInSeconds = isInSeconds;
    this.lessThanOneMillis = lessThanOneMillis;
  }

  public long getNumber()
  {
    return number;
  }

  public boolean isInSeconds()
  {
    return isInSeconds;
  }

  public boolean isLessThanOneMillis()
  {
    return lessThanOneMillis;
  }
}

Đây là một loại mô hình tôi sử dụng. Nếu bạn không sử dụng IDE, bạn nên bắt đầu. Nó sẽ tạo ra các getters (và setters nếu bạn cần chúng) cho bạn để điều này không quá đau đớn.

Tôi sẽ cảm thấy hối hận nếu tôi không chỉ ra rằng đã có một loại sẽ xuất hiện để đáp ứng hầu hết các nhu cầu của bạn ở đây . Đặt điều đó sang một bên, cách tiếp cận bạn đang sử dụng không phù hợp với Java vì nó phù hợp với điểm yếu của nó, không phải điểm mạnh. Đây là một cải tiến đơn giản:

public class Blah 
{
  public static Blah fromSeconds(long number)
  {
    return new Blah(number * 1000_000);
  }

  public static Blah fromMills(long number)
  {
    return new Blah(number * 1000);
  }

  public static Blah fromNanos(long number)
  {
    return new Blah(number);
  }

  private final long nanos;

  private Blah(long nanos)
  {
    this.nanos = nanos;
  }

  public long getNanos()
  {
    return nanos;
  }

  public long getMillis()
  {
    return getNanos() / 1000; // or round, whatever your logic is
  }

  public long getSeconds()
  {
    return getMillis() / 1000; // or round, whatever your logic is
  }

  /* I don't really know what this is about but I hope you get the idea */
  public boolean isLessThanOneMillis()
  {
    return getMillis() < 1;
  }
}

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

2
Tôi có thể đề nghị bạn cũng nhìn vào Scala đối với các tính năng Java "hiện đại hơn" ...
MikeW

1
@MikeW Tôi thực sự bắt đầu với Scala cách trở lại trong các phiên bản đầu tiên và đã hoạt động trên các diễn đàn scala một thời gian. Tôi nghĩ rằng đó là một thành tựu tuyệt vời trong thiết kế ngôn ngữ nhưng tôi đã đi đến kết luận rằng đó không thực sự là thứ tôi đang tìm kiếm và tôi là một chốt vuông trong cộng đồng đó. Tôi nên xem lại lần nữa vì có lẽ nó đã thay đổi đáng kể kể từ thời điểm đó.
JimmyJames

Câu trả lời này không đề cập đến khía cạnh giá trị gần đúng được đưa ra bởi OP.
ErikE

@ErikE Các từ "giá trị gần đúng" không xuất hiện trong văn bản câu hỏi. Nếu bạn có thể chỉ cho tôi phần cụ thể của câu hỏi tôi không giải quyết, tôi có thể cố gắng làm điều đó.
JimmyJames

41

Trước khi bạn quá tức giận với Java, xin vui lòng đọc câu trả lời của tôi trong bài viết khác của bạn .

Một trong những phàn nàn của bạn là cần phải tạo một lớp chỉ để trả về một số bộ giá trị làm câu trả lời. Đây là một mối quan tâm hợp lệ mà tôi nghĩ cho thấy trực giác lập trình của bạn đang đi đúng hướng! Tuy nhiên, tôi nghĩ rằng các câu trả lời khác bị mất dấu bằng cách gắn bó với kiểu chống ám ảnh nguyên thủy mà bạn đã cam kết. Và Java không dễ dàng làm việc với nhiều nguyên thủy như Python có, nơi bạn có thể trả về nhiều giá trị nguyên bản và gán chúng cho nhiều biến một cách dễ dàng.

Nhưng một khi bạn bắt đầu nghĩ về những gì một ApproximateDurationloại làm cho bạn, bạn nhận ra rằng nó không nằm trong phạm vi hẹp đến mức "chỉ là một lớp dường như không cần thiết để trả về ba giá trị". Khái niệm được đại diện bởi lớp này thực sự là một trong những khái niệm kinh doanh tên miền cốt lõi của bạn. Thethe cần có khả năng biểu diễn thời gian một cách gần đúng và so sánh chúng. Điều này cần phải là một phần của ngôn ngữ phổ biến trong cốt lõi của ứng dụng của bạn, với sự hỗ trợ đối tượng và tên miền tốt cho nó, để nó có thể được kiểm tra, mô-đun, có thể tái sử dụng và hữu ích.

Là mã của bạn tổng hợp các khoảng thời gian gần đúng với nhau (hoặc thời lượng có biên độ sai số, tuy nhiên bạn đại diện cho điều đó) hoàn toàn theo thủ tục hoặc có bất kỳ đối tượng nào đối với nó không? Tôi sẽ đề xuất rằng thiết kế tốt xung quanh việc tổng hợp các khoảng thời gian gần đúng cùng nhau sẽ ra lệnh thực hiện nó bên ngoài bất kỳ mã tiêu thụ nào, trong một lớp mà chính nó có thể được kiểm tra. Tôi nghĩ rằng việc sử dụng loại đối tượng miền này sẽ có hiệu ứng gợn tích cực trong mã của bạn, giúp bạn tránh xa các bước thủ tục theo từng dòng để hoàn thành một nhiệm vụ cấp cao duy nhất (mặc dù có nhiều trách nhiệm), đối với các lớp trách nhiệm đơn lẻ đó là miễn phí từ các xung đột của mối quan tâm khác nhau.

Ví dụ: giả sử bạn tìm hiểu thêm về độ chính xác hoặc tỷ lệ thực sự cần thiết để tính tổng và so sánh thời lượng của bạn để hoạt động chính xác và bạn phát hiện ra rằng bạn cần một cờ trung gian để chỉ ra "lỗi khoảng 32 mili giây" (gần với hình vuông gốc của 1000, do đó, một nửa logarit giữa 1 và 1000). Nếu bạn đã tự ràng buộc mình với mã sử dụng nguyên thủy để thể hiện điều này, bạn sẽ phải tìm mọi nơi trong mã nơi bạn có is_in_seconds,is_under_1msvà thay đổi nó thànhis_in_seconds,is_about_32_ms,is_under_1ms. Mọi thứ sẽ phải thay đổi khắp nơi! Tạo một lớp có trách nhiệm ghi lại biên độ lỗi để có thể tiêu thụ ở nơi khác giúp người tiêu dùng của bạn không biết chi tiết về mức độ lỗi của vấn đề hoặc bất cứ điều gì về cách họ kết hợp và cho phép họ chỉ định biên độ lỗi có liên quan tại thời điểm này. (Nghĩa là, không có mã tiêu thụ nào có lề lỗi chính xác bị buộc phải thay đổi khi bạn thêm một mức độ lỗi mới trong lớp, vì tất cả các lề lỗi cũ vẫn còn hiệu lực).

Tuyên bố đóng cửa

Khiếu nại về độ nặng của Java dường như biến mất khi bạn tiến gần hơn đến các nguyên tắc của RẮN và GRASP và công nghệ phần mềm tiên tiến hơn.

Phụ lục

Tôi sẽ bổ sung hoàn toàn một cách vô cớ và không công bằng rằng các thuộc tính tự động và khả năng gán các thuộc tính chỉ có trong các hàm tạo của C # giúp làm sạch hơn nữa mã hơi lộn xộn mà "cách Java" sẽ yêu cầu (với các trường sao lưu riêng và hàm getter / setter rõ ràng) :

// Warning: C# code!
public sealed class ApproximateDuration {
   public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
      LowMilliseconds = lowMilliseconds;
      HighMilliseconds = highMilliseconds;
   }
   public int LowMilliseconds { get; }
   public int HighMilliseconds { get; }
}

Đây là một triển khai Java ở trên:

public final class ApproximateDuration {
  private final int lowMilliseconds;
  private final int highMilliseconds;

  public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
    this.lowMilliseconds = lowMilliseconds;
    this.highMilliseconds = highMilliseconds;
  }

  public int getLowMilliseconds() {
    return lowMilliseconds;
  }

  public int getHighMilliseconds() {
    return highMilliseconds;
  }
}

Bây giờ là khá sạch sẽ. Lưu ý việc sử dụng bất biến rất quan trọng và có chủ ý - điều này có vẻ rất quan trọng đối với loại lớp mang giá trị đặc biệt này.

Đối với vấn đề đó, lớp này cũng là một ứng cử viên structxứng đáng để trở thành một loại giá trị. Một số thử nghiệm sẽ cho thấy việc chuyển sang cấu trúc có mang lại lợi ích hiệu suất trong thời gian chạy hay không (có thể).


9
Tôi nghĩ rằng đây là câu trả lời yêu thích của tôi. Tôi thấy rằng khi tôi thúc đẩy một lớp người nắm giữ tào lao như thế này thành một thứ gì đó trưởng thành, nó sẽ trở thành ngôi nhà cho tất cả các trách nhiệm liên quan và zing! mọi thứ trở nên sạch sẽ hơn Và tôi thường học được điều gì đó thú vị về không gian vấn đề cùng một lúc. Rõ ràng là có một kỹ năng tránh kỹ thuật quá mức ... nhưng Gosling đã không ngu ngốc khi anh ta bỏ qua cú pháp tuple, giống như với GOTO. Tuyên bố kết thúc của bạn là tuyệt vời. Cảm ơn!
SusanW

2
Nếu bạn thích câu trả lời này, hãy đọc về Thiết kế hướng tên miền. Nó có rất nhiều điều để nói về chủ đề này đại diện cho các khái niệm miền trong mã.
neontapir

@neontapir Đúng, cảm ơn! Tôi biết có một cái tên cho nó! :-) Mặc dù tôi phải nói rằng tôi thích nó khi một khái niệm miền xuất hiện hữu cơ từ một chút tái cấu trúc được truyền cảm hứng (như thế này!) ... giống như khi bạn khắc phục vấn đề trọng lực thế kỷ 19 của mình bằng cách khám phá Sao Hải Vương .. .
SusanW

@SusanW Tôi đồng ý. Tái cấu trúc để loại bỏ nỗi ám ảnh nguyên thủy có thể là một cách tuyệt vời để khám phá các khái niệm miền!
neontapir

Tôi đã thêm một phiên bản Java của ví dụ như đã thảo luận. Tôi không có đủ đường tổng hợp ma thuật trong C # vì vậy nếu thiếu thứ gì đó, hãy cho tôi biết.
JimmyJames

24

Cả Python và Java đều được tối ưu hóa cho khả năng bảo trì theo triết lý của các nhà thiết kế của họ, nhưng họ có những ý tưởng rất khác nhau về cách đạt được điều này.

Python là một ngôn ngữ đa mô hình, tối ưu hóa cho sự rõ ràngđơn giản của mã (dễ đọc và viết).

Java (theo truyền thống) là một ngôn ngữ OO dựa trên lớp mô hình duy nhất, tối ưu hóa cho sự minh chứng và tính nhất quán - ngay cả với chi phí của mã dài hơn.

Một tuple Python là một cấu trúc dữ liệu với một số trường cố định. Chức năng tương tự có thể đạt được bởi một lớp thông thường với các trường được khai báo rõ ràng. Trong Python, việc cung cấp các bộ dữ liệu thay thế cho các lớp là điều tự nhiên vì nó cho phép bạn đơn giản hóa mã rất nhiều, đặc biệt là nhờ hỗ trợ cú pháp tích hợp cho các bộ dữ liệu.

Nhưng điều này không thực sự phù hợp với văn hóa Java để cung cấp các phím tắt như vậy, vì bạn đã có thể sử dụng các lớp khai báo rõ ràng. Không cần phải giới thiệu một loại cấu trúc dữ liệu khác chỉ để lưu một số dòng mã và tránh một số khai báo.

Java thích một khái niệm duy nhất (các lớp) được áp dụng nhất quán với tối thiểu đường cú pháp trong trường hợp đặc biệt, trong khi Python cung cấp nhiều công cụ và nhiều đường cú pháp để cho phép bạn chọn thuận tiện nhất cho bất kỳ mục đích cụ thể nào.


+1, nhưng làm thế nào để lưới này với các ngôn ngữ được nhập tĩnh với các lớp, chẳng hạn như Scala, tuy nhiên vẫn thấy cần phải giới thiệu các bộ dữ liệu? Tôi nghĩ rằng tuples chỉ là giải pháp gọn gàng hơn trong một số trường hợp, ngay cả đối với các ngôn ngữ có lớp.
Andres F.

@AndresF.: Ý của bạn là gì bởi lưới? Scala là một ngôn ngữ khác với Java và có các nguyên tắc và thành ngữ thiết kế khác nhau.
JacquesB

Tôi có nghĩa là nó trả lời đoạn 5 của bạn, bắt đầu bằng "nhưng điều này không thực sự phù hợp với văn hóa Java [...]". Thật vậy, tôi đồng ý tính dài dòng là một phần của văn hóa Java, nhưng việc thiếu các bộ dữ liệu không thể là do họ "đã sử dụng các lớp được khai báo rõ ràng" - sau tất cả, Scala cũng có các lớp rõ ràng (và giới thiệu các lớp trường hợp ngắn gọn hơn ), nhưng nó cũng cho phép bộ dữ liệu! Cuối cùng, tôi nghĩ rằng có khả năng Java đã không giới thiệu các bộ dữ liệu không chỉ bởi vì các lớp có thể đạt được điều tương tự (với sự lộn xộn hơn), mà còn bởi vì cú pháp của nó phải quen thuộc với các lập trình viên C và C không có bộ dữ liệu.
Andres F.

@AndresF.: Scala không có ác cảm với nhiều cách làm, nó kết hợp các tính năng từ cả mô hình chức năng và từ OO cổ điển. Nó gần gũi hơn với triết lý Python theo cách đó.
JacquesB

@AndresF.: Có Java bắt đầu với cú pháp gần với C / C ++, nhưng đơn giản hóa rất nhiều. C # khởi đầu là một bản sao gần như chính xác của Java, nhưng đã thêm rất nhiều tính năng trong những năm qua bao gồm các bộ dữ liệu. Vì vậy, nó thực sự là một sự khác biệt trong triết lý thiết kế ngôn ngữ. (Các phiên bản sau này của Java có vẻ ít giáo điều hơn. Hàm bậc cao hơn ban đầu bị từ chối vì bạn có thể làm giống như các lớp nào, nhưng bây giờ chúng đã được giới thiệu. Vì vậy, có lẽ chúng ta đang thấy một sự thay đổi trong triết học.)
JacquesB

16

Đừng tìm kiếm thực hành; nó thường là một ý tưởng tồi, như đã nói trong BAD thực hành tốt nhất, mô hình TỐT? . Tôi biết bạn không yêu cầu thực hành tốt nhất, nhưng tôi vẫn nghĩ rằng bạn sẽ tìm thấy một số yếu tố có liên quan trong đó.

Tìm kiếm một giải pháp cho vấn đề của bạn tốt hơn so với thực tiễn và vấn đề của bạn không phải là nhanh chóng để trả về ba giá trị trong Java nhanh:

  • Có Mảng
  • Bạn có thể trả về một mảng dưới dạng một danh sách trong một lớp lót: Arrays.asList (...)
  • Nếu bạn muốn giữ phía đối tượng với ít nồi hơi hơn có thể (và không có lombok):

class MyTuple {
    public final long value_in_milliseconds;
    public final boolean is_in_seconds;
    public final boolean is_under_1ms;
    public MyTuple(long value_in_milliseconds,....){
        ...
    }
 }

Ở đây bạn có một đối tượng bất biến chỉ chứa dữ liệu của bạn và công khai nên không cần getters. Lưu ý rằng tuy nhiên nếu bạn sử dụng một số công cụ tuần tự hóa hoặc lớp kiên trì như ORM, chúng thường sử dụng getter / setter (và có thể chấp nhận tham số để sử dụng các trường thay vì getter / setter). Và đây là lý do tại sao những thực hành này được sử dụng rất nhiều. Vì vậy, nếu bạn muốn biết về thực tiễn, tốt hơn là nên hiểu tại sao họ ở đây để sử dụng chúng tốt hơn.

Cuối cùng: Tôi sử dụng getters vì tôi sử dụng nhiều công cụ tuần tự hóa, nhưng tôi cũng không viết chúng; Tôi sử dụng lombok: Tôi sử dụng các phím tắt được cung cấp bởi IDE của tôi.


9
Vẫn còn giá trị trong việc hiểu các thành ngữ phổ biến mà bạn gặp trong các ngôn ngữ khác nhau, vì chúng trở thành một tiêu chuẩn thực tế dễ tiếp cận hơn. Những thành ngữ này có xu hướng rơi vào tiêu đề "thực tiễn tốt nhất" vì bất kỳ lý do gì.
Berin Loritsch

3
Này, một giải pháp hợp lý cho vấn đề. Có vẻ khá hợp lý khi chỉ viết một lớp (/ struct) với một vài thành viên công cộng thay vì giải pháp OOP được thiết kế quá mức đầy đủ với [gs] etters.
tomsmeding

3
Điều này không dẫn đến sự phình to bởi vì, không giống như Python, việc tìm kiếm hoặc một giải pháp ngắn hơn và thanh lịch hơn không được khuyến khích. Nhưng nhược điểm là sự phình to sẽ ít nhiều giống với bất kỳ nhà phát triển Java nào. Do đó, có một mức độ duy trì cao hơn bởi vì các nhà phát triển có thể thay thế cho nhau hơn và nếu hai dự án được tham gia hoặc hai nhóm vô tình phân kỳ, sẽ có ít sự khác biệt về phong cách để chiến đấu.
Mikhail Ramendik

2
@MikhailRamendik Đó là sự đánh đổi giữa YAGNI và OOP. Chính thống OOP nói rằng mọi đối tượng đơn lẻ nên được thay thế; điều duy nhất quan trọng là giao diện chung của đối tượng. Điều này cho phép bạn viết mã ít tập trung vào các đối tượng cụ thể và nhiều hơn trên các giao diện; và vì các trường không thể là một phần của giao diện, bạn không bao giờ để lộ chúng. Điều này có thể làm cho mã dễ dàng hơn nhiều để duy trì trong thời gian dài. Ngoài ra, nó cho phép bạn ngăn chặn các trạng thái không hợp lệ. Trong mẫu ban đầu của bạn, có thể có một đối tượng cả "<ms" và "s", loại trừ lẫn nhau. Thật tồi tệ.
Luaan

2
@PeterMortensen Đó là một thư viện Java chứa rất nhiều thứ không liên quan. Nó rất tốt mặc dù. Dưới đây là các tính năng
Michael

11

Về thành ngữ Java nói chung:

Có nhiều lý do tại sao Java có các lớp cho mọi thứ. Theo như kiến ​​thức của tôi, lý do chính là:

Java nên dễ học cho người mới bắt đầu. Càng nhiều thứ rõ ràng, càng khó bỏ lỡ các chi tiết quan trọng. Ít ma thuật xảy ra sẽ khó cho người mới bắt đầu nắm bắt.


Như ví dụ cụ thể của bạn: dòng đối số cho một lớp riêng biệt là thế này: nếu ba điều đó đủ mạnh liên quan đến nhau mà chúng được trả về như một giá trị, thì đáng để đặt tên cho "điều đó". Và giới thiệu một tên cho một nhóm những thứ được cấu trúc theo cách phổ biến có nghĩa là xác định một lớp.

Bạn có thể giảm bản tóm tắt bằng các công cụ như Lombok:

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}

5
Đây cũng là dự án AutoValue của Google: github.com/google/auto/blob/master/value/userguide/index.md
Ivan

Đây cũng là lý do tại sao các chương trình Java có thể được duy trì
Thorbjørn Ravn Andersen

7

Có rất nhiều điều có thể nói về văn hóa Java, nhưng tôi nghĩ rằng trong trường hợp bạn phải đối mặt ngay bây giờ, có một vài khía cạnh quan trọng:

  1. Mã thư viện được viết một lần nhưng được sử dụng thường xuyên hơn nhiều. Mặc dù thật tuyệt khi giảm thiểu chi phí viết thư viện, nhưng về lâu dài có thể đáng viết hơn theo cách giảm thiểu chi phí sử dụng thư viện.
  2. Điều đó có nghĩa là các kiểu tự viết tài liệu rất hay: tên phương thức giúp làm rõ những gì đang xảy ra và những gì bạn đang thoát ra khỏi một đối tượng.
  3. Gõ tĩnh là một công cụ rất hữu ích để loại bỏ các lớp lỗi nhất định. Nó chắc chắn không sửa tất cả mọi thứ (mọi người thích nói đùa về Haskell rằng một khi bạn có được hệ thống loại chấp nhận mã của mình, điều đó có thể đúng), nhưng nó rất dễ biến những điều sai nhất định thành không thể.
  4. Viết mã thư viện là về việc chỉ định hợp đồng. Xác định giao diện cho loại đối số và loại kết quả của bạn làm cho ranh giới của hợp đồng của bạn được xác định rõ ràng hơn. Nếu một cái gì đó chấp nhận hoặc tạo ra một tuple, thì không thể nói đó là loại tuple mà bạn thực sự nên nhận hay sản xuất, và có rất ít cách thức ràng buộc đối với một loại chung như vậy (thậm chí nó có đúng số lượng phần tử không? Họ thuộc loại mà bạn đang mong đợi?).

Các lớp "Struct" với các trường

Như các câu trả lời khác đã đề cập, bạn chỉ có thể sử dụng một lớp với các trường công khai. Nếu bạn thực hiện những điều cuối cùng này, thì bạn sẽ có được một lớp bất biến và bạn sẽ khởi tạo chúng với hàm tạo:

   class ParseResult0 {
      public final long millis;
      public final boolean isSeconds;
      public final boolean isLessThanOneMilli;

      public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
         this.millis = millis;
         this.isSeconds = isSeconds;
         this.isLessThanOneMilli = isLessThanOneMilli;
      }
   }

Tất nhiên, điều này có nghĩa là bạn bị ràng buộc với một lớp cụ thể và bất kỳ thứ gì cần sản xuất hoặc tiêu thụ kết quả phân tích đều phải sử dụng lớp này. Đối với một số ứng dụng, điều đó tốt. Đối với những người khác, điều đó có thể gây ra một số nỗi đau. Nhiều mã Java là về việc xác định các hợp đồng và điều đó thường sẽ đưa bạn vào các giao diện.

Một điều đáng tiếc nữa là với cách tiếp cận dựa trên lớp, bạn đang phơi bày các trường và tất cả các trường đó phải có giá trị. Ví dụ, isSeconds và millis luôn phải có một số giá trị, ngay cả khi isLessThanOneMilli là đúng. Việc giải thích giá trị của trường millis là gì khi isLessThanOneMilli là đúng?

"Cấu trúc" là Giao diện

Với các phương thức tĩnh được phép trong các giao diện, thực sự tương đối dễ dàng để tạo các loại bất biến mà không cần nhiều chi phí cú pháp. Ví dụ, tôi có thể triển khai loại cấu trúc kết quả mà bạn đang nói như một thứ như thế này:

   interface ParseResult {
      long getMillis();

      boolean isSeconds();

      boolean isLessThanOneMilli();

      static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
         return new ParseResult() {
            @Override
            public boolean isSeconds() {
               return isSeconds;
            }

            @Override
            public boolean isLessThanOneMilli() {
               return isLessThanOneMill;
            }

            @Override
            public long getMillis() {
               return millis;
            }
         };
      }
   }

Tôi vẫn hoàn toàn đồng ý, tôi hoàn toàn đồng ý, nhưng cũng có một số lợi ích và tôi nghĩ rằng họ bắt đầu trả lời một số câu hỏi chính của bạn.

Với cấu trúc như kết quả phân tích cú pháp này, hợp đồng của trình phân tích cú pháp của bạn được xác định rất rõ ràng. Trong Python, một tuple không thực sự khác biệt với một tuple khác. Trong Java, gõ tĩnh có sẵn, vì vậy chúng tôi đã loại trừ một số loại lỗi nhất định. Chẳng hạn, nếu bạn trả lại một tuple bằng Python và bạn muốn trả lại tuple (millis, isSeconds, isLessThanOneMilli), bạn có thể vô tình làm:

return (true, 500, false)

khi bạn có nghĩa là:

return (500, true, false)

Với loại giao diện Java này, bạn không thể biên dịch:

return ParseResult.from(true, 500, false);

ở tất cả. Bạn phải làm:

return ParseResult.from(500, true, false);

Đó là một lợi ích của ngôn ngữ gõ tĩnh nói chung.

Cách tiếp cận này cũng bắt đầu cung cấp cho bạn khả năng hạn chế những giá trị bạn có thể nhận được. Chẳng hạn, khi gọi getMillis (), bạn có thể kiểm tra xem liệuLessThanOneMilli () có đúng không, và nếu có, hãy ném IllegalStateException (ví dụ), vì trường hợp đó không có giá trị ý nghĩa của millis.

Làm cho nó khó để làm điều sai

Trong ví dụ về giao diện ở trên, bạn vẫn gặp vấn đề là bạn có thể vô tình trao đổi các đối số isSeconds và isLessThanOneMilli, vì chúng có cùng loại.

Trong thực tế, bạn thực sự có thể muốn sử dụng TimeUnit và thời lượng, để bạn có kết quả như sau:

   interface Duration {
      TimeUnit getTimeUnit();

      long getDuration();

      static Duration from(TimeUnit unit, long duration) {
         return new Duration() {
            @Override
            public TimeUnit getTimeUnit() {
               return unit;
            }

            @Override
            public long getDuration() {
               return duration;
            }
         };
      }
   }

   interface ParseResult2 {

      boolean isLessThanOneMilli();

      Duration getDuration();

      static ParseResult2 from(TimeUnit unit, long duration) {
         Duration d = Duration.from(unit, duration);
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return false;
            }

            @Override
            public Duration getDuration() {
               return d;
            }
         };
      }

      static ParseResult2 lessThanOneMilli() {
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return true;
            }

            @Override
            public Duration getDuration() {
               throw new IllegalStateException();
            }
         };
      }
   }

Đó là việc nhận được nhiều mã hơn, nhưng bạn chỉ cần viết một lần và (giả sử bạn đã ghi lại đúng thứ), những người cuối cùng sử dụng mã của bạn không phải đoán kết quả có nghĩa là gì, và không thể vô tình làm những việc như result[0]khi chúng có ý nghĩa result[1]. Bạn vẫn có thể tạo các cá thể khá ngắn gọn và việc lấy dữ liệu từ chúng cũng không quá khó:

  ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
  ParseResult2 y = ParseResult2.lessThanOneMilli();

Lưu ý rằng bạn thực sự có thể làm một cái gì đó như thế này với cách tiếp cận dựa trên lớp, quá. Chỉ cần xác định các nhà xây dựng cho các trường hợp khác nhau. Tuy nhiên, bạn vẫn có vấn đề về việc khởi tạo các trường khác để làm gì và bạn không thể ngăn truy cập vào chúng.

Một câu trả lời khác đề cập rằng bản chất kiểu Java của doanh nghiệp có nghĩa là phần lớn thời gian, bạn đang soạn thư viện khác đã tồn tại hoặc viết thư viện cho người khác sử dụng. API công khai của bạn không cần nhiều thời gian tư vấn tài liệu để giải mã các loại kết quả nếu có thể tránh được.

Bạn chỉ viết các cấu trúc này một lần, nhưng bạn tạo chúng nhiều lần, vì vậy bạn vẫn muốn tạo ra súc tích đó (mà bạn nhận được). Việc gõ tĩnh đảm bảo rằng dữ liệu bạn nhận được từ chúng là những gì bạn mong đợi.

Bây giờ, tất cả những gì đã nói, vẫn còn những nơi mà các bộ hoặc danh sách đơn giản có thể có nhiều ý nghĩa. Có thể có ít chi phí hơn trong việc trả lại một mảng của một cái gì đó, và nếu đó là trường hợp (và chi phí đó là đáng kể, mà bạn xác định bằng cách định hình), thì sử dụng một mảng đơn giản các giá trị bên trong có thể có ý nghĩa. API công khai của bạn vẫn có thể có các loại được xác định rõ ràng.


Cuối cùng, tôi đã sử dụng enum của riêng mình thay vì TimeUnit, vì tôi vừa thêm UNDER1MS cùng với các đơn vị thời gian thực tế. Vì vậy, bây giờ tôi chỉ có hai lĩnh vực, không phải ba. (Về lý thuyết, tôi có thể sử dụng sai TimeUnit.NANOSECONDS, nhưng điều đó sẽ rất khó hiểu.)
Mikhail Ramendik

Tôi đã không tạo ra một getter, nhưng bây giờ tôi thấy cách một getter sẽ cho phép tôi ném một ngoại lệ nếu, với originalTimeUnit=DurationTimeUnit.UNDER1MS, người gọi đã cố gắng đọc giá trị mili giây.
Mikhail Ramendik

Tôi biết bạn đã đề cập đến nó, nhưng hãy nhớ ví dụ của bạn với các bộ dữ liệu python thực sự là về các bộ dữ liệu động so với các bộ gõ tĩnh; nó không thực sự nói nhiều về bản thân. Bạn có thể đã gõ tĩnh các bộ dữ liệu sẽ không biên dịch được, vì tôi chắc chắn bạn biết :)
Andres F.

1
@Veedrac Tôi không chắc ý của bạn là gì. Quan điểm của tôi là việc viết mã thư viện giống như thế này là ổn (mặc dù mất nhiều thời gian hơn), vì sẽ mất nhiều thời gian sử dụng mã hơn là viết mã .
Joshua Taylor

1
@Veedrac Vâng, điều đó đúng. Nhưng tôi muốn duy trì rằng có một sự phân biệt giữa mã đó sẽ được tái sử dụng có ý nghĩa, và mã mà sẽ không. Ví dụ, trong gói Java, một số lớp là công khai và được sử dụng từ các vị trí khác. Những API đó nên được suy nghĩ kỹ. Trong nội bộ, có những lớp chỉ có thể nói chuyện với nhau. Các khớp nối bên trong đó là nơi mà các cấu trúc nhanh và bẩn (ví dụ: một Vật thể [] dự kiến ​​có ba yếu tố) có thể ổn. Đối với API công khai, thường nên ưu tiên một cái gì đó có ý nghĩa và rõ ràng hơn.
Joshua Taylor

7

Vấn đề là bạn so sánh táo với cam . Bạn đã hỏi làm thế nào để mô phỏng trả về nhiều hơn một giá trị đơn lẻ, đưa ra một ví dụ về con trăn nhanh và bẩn với bộ dữ liệu chưa được xử lý và bạn thực sự đã nhận được câu trả lời thực tế .

Câu trả lời được chấp nhận cung cấp một giải pháp kinh doanh chính xác. Không có cách giải quyết tạm thời nhanh chóng mà bạn sẽ phải vứt bỏ và thực hiện chính xác ngay lần đầu tiên bạn cần làm bất cứ điều gì thiết thực với giá trị được trả về, nhưng một lớp POJO tương thích với một tập hợp lớn các thư viện, bao gồm kiên trì, tuần tự hóa / giải tuần tự hóa, dụng cụ và bất cứ điều gì có thể.

Điều này cũng không dài chút nào. Điều duy nhất bạn cần viết là các định nghĩa trường. Setters, getters, hashCode và bằng có thể được tạo ra. Vì vậy, câu hỏi thực tế của bạn là, tại sao các getters và setters không được tạo tự động nhưng đó là một vấn đề cú pháp (vấn đề đường cú pháp, một số người sẽ nói) và không phải là vấn đề văn hóa.

Và cuối cùng, bạn đang suy nghĩ quá mức khi cố gắng tăng tốc một thứ không quan trọng chút nào. Thời gian dành cho việc viết các lớp DTO là không đáng kể so với thời gian dành cho việc duy trì và gỡ lỗi hệ thống. Do đó, không ai tối ưu hóa cho độ dài ít hơn.


2
"Không ai" thực sự không chính xác - có vô số công cụ tối ưu hóa để ít chi tiết hơn, các biểu thức chính quy là một ví dụ cực đoan. Nhưng bạn có thể có nghĩa là "không ai trong thế giới Java chính thống", và trong đó việc đọc câu trả lời có rất nhiều ý nghĩa, cảm ơn. Mối quan tâm riêng của tôi với tính dài dòng không phải là thời gian viết mã mà là thời gian đọc nó; IDE sẽ không tiết kiệm thời gian đọc; Nhưng có vẻ như ý tưởng là 99% người ta chỉ đọc định nghĩa giao diện để THẬT nên ngắn gọn?
Mikhail Ramendik

5

Có ba yếu tố khác nhau góp phần vào những gì bạn đang quan sát.

Tuples so với các trường được đặt tên

Có lẽ là tầm thường nhất - trong các ngôn ngữ khác mà bạn đã sử dụng một tuple. Việc tranh luận liệu các bộ dữ liệu có phải là một ý tưởng hay không thực sự là vấn đề - nhưng trong Java bạn đã sử dụng một cấu trúc nặng hơn để so sánh hơi không công bằng: bạn có thể đã sử dụng một mảng các đối tượng và một số kiểu truyền.

Cú pháp ngôn ngữ

Có thể dễ dàng hơn để khai báo lớp? Tôi không nói về việc công khai các trường hoặc sử dụng bản đồ mà giống như các lớp trường hợp của Scala, cung cấp tất cả các lợi ích của thiết lập mà bạn đã mô tả nhưng ngắn gọn hơn nhiều:

case class Foo(duration: Int, unit: String, tooShort: Boolean)

Chúng ta có thể có điều đó - nhưng có một chi phí: cú pháp trở nên phức tạp hơn. Tất nhiên nó có thể có giá trị đối với một số trường hợp, hoặc thậm chí cho hầu hết các trường hợp, hoặc thậm chí cho hầu hết các trường hợp trong 5 năm tới - nhưng nó cần phải được đánh giá. Nhân tiện, đây là một trong những điều tuyệt vời với các ngôn ngữ bạn có thể tự sửa đổi (ví dụ như lisp) - và lưu ý cách điều này trở nên khả thi do tính đơn giản của cú pháp. Ngay cả khi bạn không thực sự sửa đổi ngôn ngữ, một cú pháp đơn giản cho phép các công cụ mạnh hơn; ví dụ rất nhiều lần tôi bỏ lỡ một số tùy chọn tái cấu trúc có sẵn cho Java nhưng không phải cho Scala.

Triết lý ngôn ngữ

Nhưng yếu tố quan trọng nhất là một ngôn ngữ sẽ cho phép một cách suy nghĩ nhất định. Đôi khi nó có thể cảm thấy ngột ngạt (tôi thường mong muốn được hỗ trợ cho một tính năng nhất định) nhưng việc xóa các tính năng cũng quan trọng như có chúng. Bạn có thể hỗ trợ tất cả mọi thứ? Chắc chắn, nhưng sau đó bạn có thể chỉ cần viết một trình biên dịch biên dịch mọi ngôn ngữ. Nói cách khác, bạn sẽ không có ngôn ngữ - bạn sẽ có một siêu ngôn ngữ và mọi dự án về cơ bản sẽ áp dụng một tập hợp con.

Tất nhiên có thể viết mã đi ngược lại triết lý của ngôn ngữ và, như bạn quan sát, kết quả thường xấu. Có một lớp chỉ có một vài trường trong Java gần giống với việc sử dụng var trong Scala, làm biến đổi các biến vị ngữ prolog thành các hàm, thực hiện một unsafePerformIO trong haskell, v.v. Các lớp Java không có nghĩa là ánh sáng - chúng không ở đó để truyền dữ liệu xung quanh . Khi một cái gì đó có vẻ khó khăn, thường lùi lại và xem liệu có cách nào khác không. Trong ví dụ của bạn:

Tại sao có thời hạn tách biệt với các đơn vị? Có rất nhiều thư viện thời gian cho phép bạn khai báo thời lượng - đại loại như Thời lượng (5, giây) (cú pháp sẽ thay đổi), sau đó sẽ cho phép bạn làm bất cứ điều gì bạn muốn theo cách mạnh mẽ hơn nhiều. Có thể bạn muốn chuyển đổi nó - tại sao kiểm tra xem kết quả [1] (hoặc [2]?) Là 'giờ' và nhân với 3600? Và đối với đối số thứ ba - mục đích của nó là gì? Tôi đoán rằng tại một số thời điểm, bạn sẽ phải in "dưới 1ms" hoặc thời gian thực tế - đó là một số logic tự nhiên thuộc về dữ liệu thời gian. Tức là bạn nên có một lớp học như thế này:

class TimeResult {
    public TimeResult(duration, unit, tooShort)
    public String getResult() {
        if tooShort:
           return "too short"
        else:
           return format(duration)
}

}

hoặc bất cứ điều gì bạn thực sự muốn làm với dữ liệu, do đó gói gọn logic.

Tất nhiên, có thể có một trường hợp theo cách này sẽ không hoạt động - Tôi không nói rằng đây là thuật toán kỳ diệu để chuyển đổi kết quả tuple thành mã Java thành ngữ! Và có thể có những trường hợp nó rất xấu và xấu và có lẽ bạn nên sử dụng một ngôn ngữ khác - đó là lý do tại sao có rất nhiều thứ!

Nhưng quan điểm của tôi về lý do tại sao các lớp là "cấu trúc nặng" trong Java là bạn không có ý sử dụng chúng làm nơi chứa dữ liệu mà là các ô logic tự chứa.


Những gì tôi muốn làm với thời lượng thực sự là về việc tổng hợp chúng và sau đó so sánh với thời lượng khác. Không có đầu ra liên quan. Việc so sánh cần tính đến làm tròn, vì vậy tôi cần biết các đơn vị thời gian ban đầu tại thời điểm so sánh.
Mikhail Ramendik

Có lẽ có thể tạo ra một cách thêm và so sánh các thể hiện của lớp tôi, nhưng điều này có thể trở nên cực kỳ nặng nề. Đặc biệt là vì tôi cũng cần chia và nhân với số float (có tỷ lệ phần trăm để xác minh trong UI đó). Vì vậy, bây giờ tôi đã tạo ra một lớp bất biến với các trường cuối cùng, trông giống như một sự thỏa hiệp tốt đẹp.
Mikhail Ramendik

Phần quan trọng hơn về câu hỏi cụ thể này là khía cạnh tổng quát hơn của mọi thứ, và từ tất cả các câu trả lời, một bức tranh dường như được kết hợp với nhau.
Mikhail Ramendik

@MikhailRamendik có lẽ một số loại tích lũy sẽ là một lựa chọn - nhưng bạn biết rõ vấn đề hơn. Quả thực không phải là giải quyết vấn đề đặc biệt này, xin lỗi nếu tôi có một chút lạc lõng - điểm chính của tôi là ngôn ngữ không khuyến khích việc sử dụng dữ liệu "đơn giản". đôi khi, điều này giúp bạn suy nghĩ lại về cách tiếp cận của bạn - lần khác, một int chỉ là một int
Thanos Tintinidis

@MikhailRamendik Điều hay ho về cách thức sôi nổi là một khi bạn có một lớp học, bạn có thể thêm hành vi vào nó. Và / hoặc làm cho nó bất biến như bạn đã làm (đây là một ý tưởng tốt trong hầu hết thời gian; bạn luôn có thể trả về một đối tượng mới dưới dạng tổng). Cuối cùng, lớp "không cần thiết" của bạn có thể gói gọn tất cả các hành vi bạn cần và sau đó chi phí cho các nhân viên thu được không đáng kể. Hơn nữa, bạn có thể hoặc không cần chúng. Cân nhắc sử dụng lombok hoặc autovalue .
maaartinus

5

Theo hiểu biết của tôi, lý do cốt lõi là

  1. Các giao diện là cách Java cơ bản để trừu tượng hóa các lớp.
  2. Java chỉ có thể trả về một giá trị duy nhất từ ​​một phương thức - một đối tượng hoặc một mảng hoặc một giá trị gốc (int / long / double / float / boolean).
  3. Các giao diện không thể chứa các trường, chỉ có các phương thức. Nếu bạn muốn truy cập vào một trường, bạn phải trải qua một phương thức - do đó getters và setters.
  4. Nếu một phương thức trả về một giao diện, bạn phải có một lớp thực hiện để thực sự trả về.

Điều này cung cấp cho bạn "Bạn phải viết một lớp để trả về bất kỳ kết quả không tầm thường nào", đến lượt nó khá nặng. Nếu bạn đã sử dụng một lớp thay vì giao diện, bạn chỉ có thể có các trường và sử dụng chúng trực tiếp, nhưng điều đó ràng buộc bạn với một triển khai cụ thể.


Để thêm vào điều này: nếu bạn chỉ cần điều này trong một bối cảnh nhỏ (giả sử, một phương thức riêng tư trong một lớp), thì tốt nhất là sử dụng một bản ghi trần - lý tưởng là bất biến. Nhưng nó thực sự thực sự không bao giờ nên rò rỉ vào hợp đồng công khai của lớp (hoặc thậm chí tệ hơn, thư viện!). Tất nhiên, bạn có thể quyết định chống lại các bản ghi để đảm bảo rằng điều này không bao giờ xảy ra với các thay đổi mã trong tương lai; đó thường là giải pháp đơn giản hơn.
Luaan

Và tôi đồng ý với quan điểm "Tuples không hoạt động tốt trong Java". Nếu bạn sử dụng thuốc generic thì sẽ rất nhanh hết khó chịu.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Chúng hoạt động khá tốt trong Scala, một ngôn ngữ được gõ tĩnh với các tổng quát bộ dữ liệu. Vì vậy, có lẽ đây là lỗi của Java?
Andres F.

@AresresF. Xin lưu ý phần "Nếu bạn sử dụng thuốc generic". Cách Java thực hiện điều này không cho vay các công trình phức tạp trái ngược với ví dụ như Haskell. Tôi không biết Scala đủ tốt để làm một phép so sánh, nhưng có thật là Scala cho phép nhiều giá trị trả về không? Điều đó có thể giúp khá nhiều.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Các hàm Scala có một giá trị trả về duy nhất có thể thuộc loại tuple (ví dụ: def f(...): (Int, Int)hàm ftrả về giá trị xảy ra là một tuple của số nguyên). Tôi không chắc rằng thuốc generic trong Java là vấn đề với nó; xin lưu ý rằng Haskell cũng không xóa kiểu, ví dụ. Tôi không nghĩ có một lý do kỹ thuật tại sao Java không có bộ dữ liệu.
Andres F.

3

Tôi đồng ý với câu trả lời của JacquesB

Java (theo truyền thống) là một ngôn ngữ OO dựa trên lớp mô hình duy nhất, tối ưu hóa cho sự minh chứng và nhất quán

Nhưng thám hiểm và tính nhất quán không phải là mục tiêu cuối cùng để tối ưu hóa. Khi bạn nói 'python được tối ưu hóa cho khả năng đọc', bạn ngay lập tức đề cập rằng mục tiêu cuối cùng là 'khả năng duy trì' và 'tốc độ phát triển'.

Bạn đạt được gì khi bạn có sự minh chứng và nhất quán, thực hiện theo cách Java? Tôi nghĩ rằng nó được phát triển như một ngôn ngữ tuyên bố sẽ cung cấp cách thức thống nhất, nhất quán, có thể dự đoán để giải quyết bất kỳ vấn đề phần mềm nào.

Nói cách khác, văn hóa Java được tối ưu hóa để khiến các nhà quản lý tin rằng họ hiểu được sự phát triển phần mềm.

Hoặc, như một người khôn ngoan đã đặt nó từ rất lâu rồi ,

Cách tốt nhất để đánh giá một ngôn ngữ là nhìn vào mã được viết bởi những người đề xuất nó. "Radix enim omnium malorum est cupiditas" - và Java rõ ràng là một ví dụ về lập trình hướng tiền (MOP). Như người đề xuất chính của Java tại SGI nói với tôi: "Alex, bạn phải đi đến nơi có tiền". Nhưng tôi không đặc biệt muốn đi đến nơi có tiền - nó thường không có mùi thơm ở đó.


3

(Câu trả lời này không phải là một lời giải thích cho Java nói riêng, mà thay vào đó, nó giải quyết câu hỏi chung về những gì thực hành [nặng] có thể tối ưu hóa cho?

Hãy xem xét hai nguyên tắc sau:

  1. Thật tốt khi chương trình của bạn làm đúng. Chúng ta nên làm cho nó dễ dàng để viết các chương trình làm đúng.
  2. Thật tệ khi chương trình của bạn làm sai. Chúng ta nên làm cho việc viết chương trình làm sai điều đó trở nên khó khăn hơn.

Cố gắng tối ưu hóa một trong những mục tiêu này đôi khi có thể cản trở mục tiêu kia (nghĩa là làm cho việc làm sai trở nên khó khăn hơn cũng có thể làm cho việc thực hiện đúng hoặc ngược lại khó khăn hơn ).

Sự đánh đổi nào được thực hiện trong bất kỳ trường hợp cụ thể nào tùy thuộc vào ứng dụng, quyết định của các lập trình viên hoặc nhóm được đề cập và văn hóa (của tổ chức hoặc cộng đồng ngôn ngữ).

Ví dụ: nếu một lỗi hoặc mất vài giờ trong chương trình của bạn có thể dẫn đến mất mạng (hệ thống y tế, hàng không) hoặc thậm chí chỉ là tiền (như hàng triệu đô la trong các hệ thống quảng cáo của Google), bạn sẽ thực hiện các sự đánh đổi khác nhau (không phải chỉ bằng ngôn ngữ của bạn mà còn ở các khía cạnh khác của văn hóa kỹ thuật) so với ngôn ngữ một lần: nó có khả năng nghiêng về phía "nặng".

Các ví dụ khác có xu hướng làm cho hệ thống của bạn "nặng" hơn:

  • Khi bạn có một cơ sở mã lớn được làm việc bởi nhiều nhóm trong nhiều năm, một mối quan tâm lớn là ai đó có thể sử dụng API của người khác không chính xác. Một hàm được gọi với các đối số theo thứ tự sai hoặc được gọi mà không đảm bảo một số điều kiện tiên quyết / ràng buộc mà nó mong đợi, có thể là thảm họa.
  • Trong trường hợp đặc biệt này, giả sử nhóm của bạn duy trì một API hoặc thư viện cụ thể và muốn thay đổi hoặc cấu trúc lại nó. Người dùng của bạn càng bị ràng buộc nhiều hơn về cách sử dụng mã của bạn, thì việc thay đổi mã đó càng dễ dàng hơn. (Lưu ý rằng ở đây sẽ có các đảm bảo thực tế ở đây rằng không ai có thể sử dụng nó theo cách khác thường.)
  • Nếu sự phát triển được phân chia giữa nhiều người hoặc nhiều nhóm, có vẻ như một ý tưởng tốt là có một người hoặc nhóm Nhóm chỉ định giao diện, và để những người khác thực sự thực hiện nó. Để nó hoạt động, bạn cần có khả năng tự tin khi thực hiện xong, việc triển khai thực sự phù hợp với đặc điểm kỹ thuật.

Đây chỉ là một số ví dụ, để cung cấp cho bạn ý tưởng về các trường hợp khiến mọi thứ trở nên nặng nề (và khiến bạn khó viết ra một số mã nhanh chóng) có thể thực sự có chủ ý. (Người ta thậm chí có thể lập luận rằng nếu viết mã đòi hỏi nhiều nỗ lực, nó có thể khiến bạn phải suy nghĩ kỹ hơn trước khi viết mã! Tất nhiên dòng lập luận này nhanh chóng trở nên lố bịch.)

Một ví dụ: Hệ thống Python nội bộ của Google có xu hướng tạo ra những thứ nặng nề mà bạn không thể nhập mã của người khác, bạn phải khai báo sự phụ thuộc trong tệp BUILD , nhóm có mã bạn muốn nhập cần phải khai báo thư viện của họ là hiển thị mã của bạn, v.v.


Lưu ý : Tất cả những điều trên chỉ là khi mọi thứ có xu hướng trở nên "nặng nề". Tôi tuyệt đối không cho rằng Java hoặc Python (chính ngôn ngữ hoặc văn hóa của họ) tạo ra sự đánh đổi tối ưu cho bất kỳ trường hợp cụ thể nào; đó là để bạn suy nghĩ Hai liên kết liên quan đến sự đánh đổi đó:


1
Còn về đọc mã? Có một sự đánh đổi khác ở đó. Mã bị giảm trọng lượng bởi những câu thần chú kỳ quái và những nghi thức thâm thúy khó đọc hơn; với bản tóm tắt và văn bản không cần thiết (và phân cấp lớp!) trong IDE của bạn, việc xem mã thực sự đang làm gì trở nên khó khăn hơn. Tôi có thể thấy lập luận rằng mã không nhất thiết phải dễ viết (không chắc là tôi có đồng ý với nó không, nhưng nó có một số giá trị), nhưng nó chắc chắn phải dễ đọc . Rõ ràng mã phức tạp có thể và đã được xây dựng bằng Java - thật ngu ngốc khi yêu cầu khác - nhưng có phải nhờ vào tính dài dòng của nó hay mặc dù vậy?
Andres F.

@AresresF. Tôi đã chỉnh sửa câu trả lời để làm cho nó rõ ràng hơn không phải về Java cụ thể (trong đó tôi không phải là một người hâm mộ tuyệt vời). Nhưng vâng, sự đánh đổi khi đọc mã: ở một đầu bạn muốn có thể dễ dàng đọc được những gì mã thực sự đang làm ra như bạn đã nói. Ở đầu bên kia, bạn muốn có thể dễ dàng nhìn thấy câu trả lời cho các câu hỏi như: mã này liên quan đến mã khác như thế nào? Điều tồi tệ nhất mà mã này có thể làm: tác động của nó đến trạng thái khác là gì, tác dụng phụ của nó là gì? Những yếu tố bên ngoài mà mã này có thể phụ thuộc vào? (Tất nhiên lý tưởng là chúng tôi muốn có câu trả lời nhanh cho cả hai bộ câu hỏi.)
ShreevatsaR

2

Văn hóa Java đã phát triển theo thời gian với những ảnh hưởng nặng nề từ cả nền tảng phần mềm doanh nghiệp và nguồn mở - đây là một sự pha trộn kỳ lạ nếu bạn thực sự nghĩ về nó. Các giải pháp doanh nghiệp đòi hỏi các công cụ nặng và nguồn mở đòi hỏi sự đơn giản. Kết quả cuối cùng là Java ở đâu đó ở giữa.

Một phần của những gì đang ảnh hưởng đến khuyến nghị là những gì được coi là có thể đọc và duy trì được trong Python và Java rất khác nhau.

  • Trong Python, bộ dữ liệu là một tính năng ngôn ngữ .
  • Trong cả Java và C #, tuple là (hoặc sẽ là) một tính năng thư viện .

Tôi chỉ đề cập đến C # vì thư viện tiêu chuẩn có một tập hợp các lớp Tuple <A, B, C, .. n> và đó là một ví dụ hoàn hảo về việc các bộ dữ liệu khó sử dụng nếu ngôn ngữ không hỗ trợ chúng trực tiếp. Trong hầu hết mọi trường hợp, mã của bạn trở nên dễ đọc và dễ bảo trì hơn nếu bạn có các lớp được chọn tốt để xử lý vấn đề. Trong ví dụ cụ thể trong câu hỏi Stack Overflow được liên kết của bạn, các giá trị khác sẽ dễ dàng được biểu thị dưới dạng các biểu đồ được tính toán trên đối tượng trả về.

Một giải pháp thú vị mà nền tảng C # đã làm mang đến một nền tảng hạnh phúc là ý tưởng về các đối tượng ẩn danh (được phát hành trong C # 3.0 ) giúp loại bỏ ngứa này khá tốt. Thật không may, Java chưa có tương đương.

Cho đến khi các tính năng ngôn ngữ của Java được sửa đổi, giải pháp dễ đọc và dễ bảo trì nhất là có một đối tượng chuyên dụng. Đó là do những hạn chế trong ngôn ngữ bắt đầu từ năm 1995. Các tác giả ban đầu có nhiều tính năng ngôn ngữ được lên kế hoạch mà không bao giờ thực hiện được và tính tương thích ngược là một trong những hạn chế chính xung quanh sự tiến hóa của Java theo thời gian.


4
Trong phiên bản mới nhất của c #, các bộ dữ liệu mới là công dân hạng nhất chứ không phải lớp thư viện. Phải mất quá nhiều phiên bản để đạt được điều đó.
Igor Soloydenko

3 đoạn cuối có thể được tóm tắt là "Ngôn ngữ X có tính năng mà bạn tìm kiếm mà Java không có" và tôi không thấy những gì họ thêm vào câu trả lời. Tại sao lại đề cập đến C # trong một chủ đề Python to Java? Không, tôi chỉ không nhìn thấy điểm. Một chút kỳ lạ từ một anh chàng 20k + rep.
Olivier Grégoire

1
Như tôi đã nói, "Tôi chỉ đề cập đến C # vì Tuples là một tính năng của thư viện" tương tự như cách nó sẽ hoạt động trong Java nếu nó có Tuples trong thư viện chính. Nó rất khó sử dụng, và câu trả lời tốt hơn là hầu như luôn luôn có một lớp dành riêng. Mặc dù vậy, các đối tượng ẩn danh gây ra một vết ngứa tương tự như những gì được thiết kế cho. Không có sự hỗ trợ ngôn ngữ cho một cái gì đó tốt hơn, về cơ bản bạn phải làm việc với những gì có thể duy trì nhất.
Berin Loritsch

@IgorSoloydenko, có thể có hy vọng cho Java 9 không? Đây chỉ là một trong những tính năng là bước tiếp theo hợp lý cho lambdas.
Berin Loritsch

1
@IgorSoloydenko Tôi không chắc điều đó hoàn toàn đúng (công dân hạng nhất) - chúng dường như là phần mở rộng cú pháp để tạo và hủy bỏ các thể hiện của các lớp từ thư viện, thay vào đó có hỗ trợ lớp đầu tiên trong CLR như lớp, struct, enum.
Pete Kirkham

0

Tôi nghĩ một trong những điều cốt lõi của việc sử dụng một lớp học trong trường hợp này là những gì đi cùng nhau nên ở lại với nhau.

Tôi đã có cuộc thảo luận này theo cách khác, về các đối số phương pháp: Hãy xem xét một phương pháp đơn giản để tính toán BMI:

CalculateBMI(weight,height)
{
  System.out.println("BMI: " + (( weight / height ) x 703));
}

Trong trường hợp này tôi sẽ tranh luận về phong cách này vì cân nặng và chiều cao có liên quan. Phương thức "truyền đạt" đó là hai giá trị riêng biệt khi chúng không. Khi nào bạn sẽ tính chỉ số BMI với cân nặng của một người và chiều cao của người khác? Nó sẽ không có ý nghĩa.

CalculateBMI(Person)
{
  System.out.println("BMI: " + (( Person.weight / Person.height ) x 703));
}

Có ý nghĩa hơn rất nhiều bởi vì bây giờ bạn truyền đạt rõ ràng rằng chiều cao và cân nặng đến từ cùng một nguồn.

Điều tương tự cũng xảy ra khi trả về nhiều giá trị. Nếu chúng được kết nối rõ ràng thì trả về một gói nhỏ gọn gàng và sử dụng một đối tượng, nếu chúng không trả về nhiều giá trị.


4
Ok, nhưng giả sử sau đó bạn có một nhiệm vụ (giả thuyết) để hiển thị biểu đồ: làm thế nào BMI phụ thuộc vào cân nặng cho một số chiều cao cố định cụ thể. Sau đó, bạn không thể làm điều đó mà không tạo Người cho mỗi điểm dữ liệu. Và, đưa nó đến mức cực đoan, bạn không thể tạo một thể hiện của lớp Người mà không có ngày sinh và số hộ chiếu hợp lệ. Giờ thì sao? Tôi đã không downvote btw, có những lý do hợp lệ cho việc kết hợp các thuộc tính với nhau trong một số lớp.
artem

1
@artem đó là bước tiếp theo nơi giao diện phát huy tác dụng. Vì vậy, một giao diện cá nhân có thể là một cái gì đó như thế. Bạn đang bị cuốn vào ví dụ mà tôi đã đưa ra để chỉ ra rằng những gì đi cùng nhau nên được cùng nhau.
Pieter B

0

Thẳng thắn mà nói, văn hóa là các lập trình viên Java ban đầu có xu hướng ra khỏi các trường đại học nơi các nguyên tắc Định hướng đối tượng và nguyên tắc thiết kế phần mềm bền vững được dạy.

Như ErikE nói bằng nhiều từ hơn trong câu trả lời của anh ấy, bạn dường như không viết mã bền vững. Những gì tôi thấy từ ví dụ của bạn là có một mối lo ngại rất khó xử.

Trong văn hóa Java, bạn sẽ có xu hướng nhận thức được những thư viện nào có sẵn và điều đó sẽ cho phép bạn đạt được nhiều hơn so với lập trình ngoài luồng của bạn. Vì vậy, bạn sẽ giao dịch các đặc điểm riêng của mình cho các mẫu và kiểu dáng thiết kế đã được thử và kiểm tra trong các thiết lập công nghiệp lõi cứng.

Nhưng như bạn nói, điều này không có nhược điểm: ngày nay, đã sử dụng Java hơn 10 năm, tôi có xu hướng sử dụng Node / Javascript hoặc Go cho các dự án mới, vì cả hai đều cho phép phát triển nhanh hơn và với các kiến ​​trúc kiểu microservice thường đủ. Đánh giá về thực tế Google trước tiên đã sử dụng Java rất nhiều, nhưng đã là người khởi tạo Go, tôi đoán họ có thể đang làm như vậy. Nhưng ngay cả khi tôi sử dụng Go và Javascript bây giờ, tôi vẫn sử dụng nhiều kỹ năng thiết kế tôi có được sau nhiều năm sử dụng và hiểu Java.


1
Đáng chú ý, công cụ tuyển dụng foobar mà google sử dụng cho phép hai ngôn ngữ được sử dụng cho các giải pháp: java và python. Tôi thấy thú vị rằng Go không phải là một lựa chọn. Tôi đã tình cờ thấy bài thuyết trình này trên Go và nó có một số điểm thú vị về Java và Python (cũng như các ngôn ngữ khác.) Ví dụ, có một điểm nhấn nổi bật: "Có lẽ do đó, các lập trình viên không phải là chuyên gia đã nhầm lẫn" dễ dàng sử dụng "với giải thích và gõ động." Đáng đọc.
JimmyJames

@JimmyJames vừa nhận được slide nói rằng 'trong tay các chuyên gia, họ thật tuyệt vời' - tóm tắt tốt về vấn đề trong câu hỏi
Tom
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.