Grokking Java culture-なぜそんなに重いのですか?何のために最適化されますか?[閉まっている]


288

私はよくPythonでコーディングしていました。今、仕事上の理由で、私はJavaでコーディングしています。私がやるプロジェクトはかなり小さく、おそらくPythonのほうがうまくいくかもしれませんが、Javaを使用する正当な非エンジニアリングの理由があります(詳細は説明できません)。

Java構文は問題ありません。それは単なる別の言語です。しかし、構文とは別に、Javaには文化、一連の開発メソッド、および「正しい」と見なされるプラクティスがあります。そして今のところ、私はその文化を「グロック」することに完全に失敗しています。だから私は本当に正しい方向に説明やポインタをいただければ幸いです。

最小限の完全な例は、私が始めたStack Overflowの質問にあります:https : //stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339

タスクがあります-(単一の文字列から)解析し、3つの値のセットを処理します。Pythonでは1行(タプル)、PascalまたはCでは5行のレコード/構造体です。

答えによると、構造体に相当するものはJava構文で利用可能であり、トリプルは広く使用されているApacheライブラリで利用可能です。しかし、それを行う「正しい」方法は、ゲッターとセッター。完全な例を提供してくれた人はとても親切でした。それは47行のコードでした(これらの行の一部は空白でした)。

巨大な開発コミュニティは「間違った」ものではないことを理解しています。これは私の理解の問題です。

Pythonのプラクティスは、読みやすさ(その哲学では保守性につながります)とその後の開発速度を最適化します。Cプラクティスは、リソース使用量に対して最適化されます。Javaプラクティスは何のために最適化されますか?私の最善の推測はスケーラビリティです(すべてが数百万のLOCプロジェクトに対応できる状態でなければなりません)が、それは非常に弱い推測です。


1
質問の技術的側面に答えることはありませんが、まだコンテキストにあります:softwareengineering.stackexchange.com/a/63145/13899
Newtopian



3
これが正しい答えだと思うものです。プログラマーではなく、システム管理者のようなプログラミングを考えているため、Javaを愛用しているわけではありません。あなたにとって、ソフトウェアは、最も適切な方法で特定のタスクを達成するために使用するものです。私は20年間Javaコードを書いてきましたが、私が取り組んできたプロジェクトのいくつかは、20、2年のチームを達成してきました。JavaはPythonに代わるものでも、その逆でもありません。両方とも仕事をしていますが、仕事はまったく異なります。
リチャード

1
Javaが事実上の標準である理由は、それが単に正しいからです。ひげが流行する前にひげを生やした真面目な人々によって、初めて正しく作成されました。シェルスクリプトをノックアップしてファイルを操作することに慣れている場合、Javaは耐えられないほど肥大しているように見えます。ただし、クラスター化されたredisキャッシング、3つの課金システム、20MポンドのOracleデータベースクラスターに支えられ、1日5千万人に対応できるデュアル冗長サーバークラスターを作成する場合は、アクセスしてもPythonを使用することはありません。 Pythonのデータベースは25行少ないコードです。
リチャード

回答:


237

Java言語

これらの答えはすべて、Javaが機能する方法に意図を向けようとすることによって、ポイントを失っていると思います。Pythonや他の多くの言語にはまだ簡潔な構文があるため、Javaの冗長性はオブジェクト指向であることには由来しません。Javaの冗長性は、アクセス修飾子のサポートにも起因していません。代わりに、単にJavaがどのように設計され、進化してきたかです。

Javaは元々、OOを備えたわずかに改善されたCとして作成されました。そのため、Javaには70年代の構文があります。さらに、Javaは、下位互換性を保持し、時の試練に耐えられるようにするために、機能の追加について非常に保守的です。XMLが大流行した2005年にJavaがXMLリテラルなどのトレンディな機能を追加した場合、誰も気にせず、10年後にその進化を制限するゴースト機能で言語が肥大化するはずでした。したがって、Javaには概念を簡潔に表現するための多くの最新の構文が欠けています。

ただし、Javaがその構文を採用することを妨げる根本的なものはありません。たとえば、Java 8はラムダとメソッド参照を追加し、多くの状況で冗長性を大幅に減らしました。Javaは、Scalaのケースクラスなどのコンパクトなデータ型宣言のサポートを同様に追加できます。しかし、Javaはそうしていません。カスタム値タイプは水平線上にあり、この機能はそれらを宣言するための新しい構文を導入する場合があることに注意してください。見えると思います。


Javaカルチャー

エンタープライズJava開発の歴史は、今日の文化に大きくつながっています。90年代後半/ 00年代前半、Javaはサーバーサイドのビジネスアプリケーションで非常に人気のある言語になりました。当時、これらのアプリケーションは主にアドホックで記述され、HTTP API、データベース、XMLフィードの処理など、多くの複雑な懸念事項が組み込まれていました。

00年代には、これらのアプリケーションの多くに共通点が多く、Hibernate ORM、Xerces XMLパーサー、JSP、サーブレットAPI、EJBなど、これらの懸念を管理するフレームワークが普及することが明らかになりました。ただし、これらのフレームワークは、自動化するように設定した特定のドメインで作業する労力を減らしましたが、構成と調整が必要でした。当時は、何らかの理由で、最も複雑なユースケースに対応するフレームワークを作成することが一般的であったため、これらのライブラリのセットアップと統合は複雑でした。そして、時間の経過とともに、機能を蓄積するにつれてますます複雑になりました。Javaエンタープライズ開発は、サードパーティのライブラリをプラグインすることによりますますアルゴリズムを作成することではなくなりました。

最終的に、エンタープライズツールの退屈な構成と管理は、フレームワーク(特にSpringフレームワーク)が管理を管理するためにやってくるほど苦痛になりました。すべての構成を1か所に置くことができ、理論が進み、構成ツールが各構成要素を構成し、それらを接続します。残念ながら、これらの「フレームワークフレームワーク」は、ワックスのボール全体にさらに抽象化と複雑さ追加しました

過去数年間で、より多くの軽量ライブラリの人気が高まっています。それにもかかわらず、Javaプログラマーの全世代は、大規模なエンタープライズフレームワークの成長の間に成熟しました。フレームワークを開発するロールモデルは、ファクトリファクトリとプロキシ構成Beanローダーを作成しました。彼らはこれらの怪物を日々設定し、統合しなければなりませんでした。そして結果として、コミュニティ全体の文化はこれらのフレームワークの例に従い、ひどく過剰に設計する傾向がありました。


15
面白いのは、他の言語がいくつかの「java-ism」を取り上げているように見えることです。PHP OOの機能はJavaに大きく影響されており、今日ではJavaScriptは多くのフレームワークと、すべての依存関係を収集して新しいアプリケーションを起動するのがどれほど難しいかという批判を受けることがよくあります。
マーカス

14
@marcusは、「車輪を再発明しないでください」ということをようやく知った人がいるからでしょうか?依存関係は、結局、車輪を再発明しないコストです。
ウォルフラット

9
「Terse」は、しばしば不可解な「巧妙な」コードゴルフ風のワンライナーに変換されます。
Tulainsコルドバ

7
思慮深く、よく書かれた答え。歴史的背景。比較的未ピニオン化。よくできました。
チーツマイスター

10
@TulainsCórdovaは、私たちは(ドナルド・クヌース)「疫病のように...巧妙なトリックを避ける」べきであると同意するが、それは書き込みを意味するものではありませんforループmapより適切な(と読める)だろうが。幸せな媒体があります。
ブライアンマックラッチン

73

他の人によって上げられていない、あなたが上げたポイントの1つに答えがあると思います。

Javaは、仮定を一切行わないことで、コンパイル時のプログラマーのエラー検出を最適化します

一般に、Javaは、プログラマが意図を明示的に表明した後にのみ、ソースコードに関する事実を推測する傾向があります。Javaコンパイラは、コードに関する仮定を一切行わず、推論を使用して冗長コードを削減します。

この哲学の背後にある理由は、プログラマーは人間にすぎないということです。私たちが書くことは、必ずしもプログラムが実際に行うことを意図しているとは限りません。Java言語は、開発者に型を常に明示的に宣言させることにより、これらの問題の一部を軽減しようとします。これは、記述されたコードが実際に意図したとおりに動作するかどうかをダブルチェックする方法にすぎません。

いくつかの他の言語は、事前条件、事後条件、および不変条件をチェックすることにより、そのロジックをさらに推進します(コンパイル時にそれを行うかどうかはわかりませんが)。これらは、プログラマーがコンパイラーに自分の作業を二重にチェックさせるためのさらに極端な方法です。

あなたの場合、これは、コンパイラが返すと思う型を実際に返すことを保証するために、コンパイラにその情報を提供する必要があることを意味します。

Javaには、次の2つの方法があります。

  1. 使用Triplet<A, B, C>本当にであるべきである(戻り値の型としてjava.util、それがない理由を私は本当に説明することはできません。JDK8を紹介し、特に以来FunctionBiFunctionConsumerBiConsumer、などが...それはちょうどそれを思わPairおよびTriplet少なくとも理にかなって。しかし、私は脱線します)

  2. そのために、各フィールドに名前を付けて適切に入力する独自の値タイプを作成します。

どちらの場合でも、コンパイラーは、関数が宣言した型を返すこと、および呼び出し元が各返されたフィールドの型を認識し、それに応じてそれらを使用することを保証できます。

一部の言語は静的型チェックと型推論を同時に提供しますが、それにより、型の不一致の問題の微妙なクラスの扉が開かれたままになります。開発者が特定の型の値を返すことを意図していたが、実際には別の型を返し、コンパイラーがコードを受け入れる場合、関数と呼び出し元の両方が偶然に、意図された両方に適用できるメソッドのみを使用するためおよび実際のタイプ。

明示的な型付けの代わりに型推論が使用されるTypescript(またはフロー型)でこのようなことを検討してください。

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);

もちろん、これはばかげた些細なケースです、コード適切に見えるため、はるかに微妙でデバッグが難しい問題につながる可能性があります。この種の問題はJavaでは完全に回避され、それが言語全体の設計の中心になっています。


適切なオブジェクト指向の原則とドメイン駆動モデリングに従って、リンクされた質問の期間解析ケースもjava.time.Durationオブジェクトとして返される可能性があることに注意してください。これは、上記の両方のケースよりも明確です。


38
Javaがコードの正確さのために最適化することは有効なポイントかもしれませんが、私には(多くの言語のプログラミング、最近は少しJavaのように)言語が失敗するか、非常に効率が悪いようです。確かに、Javaは古く、当時多くのものは存在していませんでしたが、Haskellのような間違いなく優れた正確性チェックを提供する現代的な代替手段があります(あえて言うなら、RustまたはGo)。この目標には、Javaの扱いにくさはすべて不要です。-さらに、これは文化をまったく説明していませんが、ソロモノフはとにかく文化がBSであることを明らかにしました。
-tomsmeding

8
このサンプルは、動的言語で暗黙的な変換を許可するというJavaScriptの決定が愚かであるというだけで、型推論が問題であることを示していません。型推論を備えた健全な動的言語または静的に宣伝された言語では、これは許可されません。両方の種類の例として、Pythonは例外を除いてランタイムをスローし、Haskellはコンパイルしません。
Voo

39
@tomsmeding HaskellはJava よりも5年間長く存在しているため、この議論は特に馬鹿げていることに注意する価値があります。Javaには型システムがあるかもしれませんが、Javaがリリースされたとき、最新の正当性検証さえありませんでした。
アレクシスキング

21
「Javaはコンパイル時のエラー検出のために最適化する」—いいえ。それは提供しますが、他の人が述べたように、それは言葉の意味で確かに最適化されません。さらに、この引数はOPの質問まったく関係ありません。実際にコンパイル時エラー検出用に最適化する他の言語に、同様のシナリオではるかに軽量な構文があるためです。Javaの過剰な冗長性は、コンパイル時の検証とはまったく関係ありません
コンラッドルドルフ

19
@KonradRudolphはい、この答えはまったく無意味ですが、感情的に魅力的だと思います。なぜこれほど多くの票があるのか​​分かりません。Haskell(または、さらに重要なのはIdris)は、Javaよりも正確さに関してはるかに最適化されており、軽量の構文を持つタプルを備えています。さらに、データ型の定義はワンライナーであり、Javaバージョンが取得するすべてのものを取得できます。この答えは、貧弱な言語設計と不必要な冗長性の言い訳ですが、Javaが好きで、他の言語に精通していない場合は良いように聞こえます。
アレクシスキング

50

JavaとPythonは私が最もよく使う2つの言語ですが、私は別の方向から来ています。つまり、私はPythonを使い始める前にJavaの世界に深く関わっていたので、私は手助けができるかもしれません。「なぜこんなに重いのか」という大きな質問に対する答えは、2つのものに帰着すると思います。

  • 2つの間の開発にかかるコストは、長い風船の動物の風船の空気のようなものです。バルーンの一部を絞ると、別の部分が膨らむことができます。Pythonは初期の部分を圧迫する傾向があります。Javaは後半部分を圧縮します。
  • Javaには、この重みの一部を取り除く機能がまだありません。Java 8はこれに大きな影響を与えましたが、文化は変化を完全に消化していません。Javaは、などのさらにいくつかのものを使用できますyield

Javaは、大規模な人々のチームによって長年維持される価値の高いソフトウェアを「最適化」します。私はPythonで何かを書いて1年後にそれを見て、自分のコードに困惑する経験がありました。Javaでは、他の人のコードの小さな断片を見て、それが何をするのかを即座に知ることができます。Pythonでは、実際にそれを行うことはできません。あなたが理解しているように、それはより良いということではありません、彼らはただ異なるコストを持っています。

あなたが言及する特定のケースでは、タプルはありません。簡単な解決策は、パブリック値を持つクラスを作成することです。Javaが最初に登場したとき、人々はこれをかなり定期的に行いました。それを行うことの最初の問題は、それがメンテナンスの頭痛だということです。ロジックやスレッドセーフを追加する必要がある場合、またはポリモーフィズムを使用する場合は、少なくとも、その「タプル風」オブジェクトと対話するすべてのクラスに触れる必要があります。Pythonには、このような解決策などがあります__getattr__ので、それほどひどくはありません。

ただし、これにはいくつかの悪い習慣(IMO)があります。この場合、タプルが必要であれば、なぜそれを可変オブジェクトにするのか疑問に思います。必要なのはゲッターのみです(補足的に、get / setの規則は嫌いですが、それはそうです)。 。つまり、プロジェクト内の参照をクラスに制限することにより、クラスのパブリックインターフェイスを変更せずに、必要に応じて後でリファクタリングできます。以下に、単純な不変オブジェクトを作成する方法の例を示します。

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;
  }
}

これは私が使用する一種のパターンです。IDEを使用していない場合は、開始する必要があります。ゲッター(および必要な場合はセッター)が生成されるため、それほど苦痛はありません。

ここにあなたのニーズのほとんどを満たすように見えるタイプが既にあると指摘しなかったならば、私は失望を感じるでしょう。それはさておき、あなたが使用しているアプローチはJavaの弱点であり、長所ではないため、Javaにはあまり適していません。簡単な改善点は次のとおりです。

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;
  }
}

コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
maple_shaft

2
「より現代的な」Java機能に関してもScalaをご覧になることをお勧めします
...-MikeW

1
@MikeW私は実際には初期のリリースでScalaを使い始め、しばらくの間scalaフォーラムで活動していました。私はそれが言語設計の大きな成果だと思いますが、それは私が探していたものではなく、私はそのコミュニティの四角い釘であったという結論に達しました。おそらくそれ以降、大幅に変更されているので、もう一度見てください。
ジミージェームズ

この回答は、OPによって提起された近似値の側面には対応していません。
-ErikE

@ErikE「概算値」という言葉は質問テキストに表示されません。私が対処しなかった質問の特定の部分を教えていただければ、そうすることを試みることができます。
ジミージェームズ

41

Javaに夢中になる前に、他の投稿の私の答えを読んでください

苦情の1つは、答えとして値のセットを返すためだけにクラスを作成する必要があることです。これは、あなたのプログラミングの直感が順調に進んでいることを示していると思う有効な懸念です!しかし、私はあなたがコミットした原始的な強迫観念のアンチパターンに固執することにより、他の答えがマークを逃していると思います。また、Javaには、Pythonのように複数のプリミティブを簡単に操作できる機能がありません。Pythonでは、複数の値をネイティブに返し、それらを複数の変数に簡単に割り当てることができます。

しかし、ApproximateDuration型が何をするのかを考え始めると、「3つの値を返すために、一見不必要なクラスに」という狭い範囲ではないことに気付きます。このクラスが表す概念は、実際にはコアドメインのビジネス概念の 1つです。つまり、おおよその方法で時間を表現し、それらを比較できる必要があります。これは、アプリケーション、コアのユビキタス言語の一部である必要があり、オブジェクトとドメインを適切にサポートしているため、テスト、モジュール化、再利用可能、および有用です。

おおよその継続時間を合計するコード(または、それを表現するのに誤差のある継続時間)は完全に手続き的ですか、またはそれにオブジェクト性がありますか?おおよその継続時間を合計することに関する適切な設計は、それ自体をテストできるクラス内で、消費するコードの外でそれを行うことを指示することを提案します。この種のドメインオブジェクトを使用すると、コードにプラスの波及効果がもたらされ、単一の高レベルのタスク(多くの責任を伴う)を達成するための行ごとの手続き手順から離れて、単一の責任クラスに向かうのに役立ちますさまざまな懸念の対立から解放されています。

たとえば、継続時間の合計と比較が正しく機能するために実際に必要な精度またはスケールについて詳しく調べ、「約32ミリ秒のエラー」を示す中間フラグが必要であることがわかりました(正方形に近いルートは1000なので、対数的には1から1000までの中間です)。これを表すためにプリミティブを使用するコードに縛られている場合は、コード内のすべての場所を見つけてis_in_seconds,is_under_1ms変更する必要がありますis_in_seconds,is_about_32_ms,is_under_1ms。あらゆるものをあちこちで変えなければなりません!他の場所で消費できるようにエラーのマージンを記録することを担当するクラスを作成すると、ユーザーはエラーのマージンの詳細やそれらの組み合わせに関する詳細を知る必要がなくなり、関連するエラーのマージンを指定できるようになります現時点では。(つまり、クラスに新しいエラーマージンレベルを追加しても、エラーマージンが正しい消費コードは強制的に変更されません。古いエラーマージンはすべて有効であるためです)。

閉会の辞

そのため、SOLIDとGRASPの原理、およびより高度なソフトウェアエンジニアリングに近づくにつれて、Javaの重さに関する不満はなくなるようです。

補遺

C#の自動プロパティと、コンストラクターで取得専用プロパティを割り当てる機能が、「Javaの方法」で必要となるやや厄介なコードをさらにクリーンアップするのに役立つことを完全に無償で不公平に追加します(明示的なプライベートバッキングフィールドとゲッター/セッター関数を使用) :

// 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; }
}

上記のJava実装は次のとおりです。

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;
  }
}

今ではかなりきれいです。不変性の非常に重要で意図的な使用に注意してください。これは、この特定の種類の価値を持つクラスにとって重要なようです。

さらに、このクラスはstruct、値型であるための適切な候補です。いくつかのテストでは、構造体への切り替えに実行時のパフォーマンスの利点があるかどうかが示されます(可能性がある)。


9
これは私のお気に入りの答えだと思います。このようなくだらない使い捨てホルダークラスを大人に昇格させると、関連するすべての責任と熱狂の家になります!すべてがきれいになります!そして、私は通常、問題空間について興味深い何かを同時に学びます。明らかに、過剰なエンジニアリングを回避するスキルがあります...しかし、GOTOの場合と同様に、Goslingはタプル構文を省略したときに愚かではありませんでした。あなたの最後の声明は素晴らしいです。ありがとう!
スーザンW

2
この答えが気に入ったら、ドメイン駆動設計を読んでください。ドメインの概念をコードで表現するというこのトピックについては、多くのことを述べています。
-neontapir

@neontapirうん、ありがとう!名前があることは知っていました!:-)ドメインコンセプトが、インスピレーションを受けたリファクタリングから有機的に現れる場合(このように!)... 。
SusanW

@SusanW同意します。原始的な強迫観念を取り除くリファクタリングは、ドメインの概念を明らかにする優れた方法です。
-neontapir

説明したように、サンプルのJavaバージョンを追加しました。私はC#のすべての魔法の統語論的シュガーに精通していないので、何か足りないものがあれば教えてください。
ジミージェームズ

24

PythonとJavaはどちらも、設計者の哲学に従って保守性が最適化されていますが、これを実現する方法については非常に異なるアイデアがあります。

Pythonは、コードの明快さ単純さ(読み取りと書き込みが容易)を最適化するマルチパラダイム言語です。

Javaは(伝統的に)単一のパラダイムクラスベースのオブジェクト指向言語であり、より冗長なコードを犠牲にしても、明示一貫性を最適化します。

Pythonタプルは、固定数のフィールドを持つデータ構造です。同じ機能は、明示的に宣言されたフィールドを持つ通常のクラスによって実現できます。Pythonでは、特にタプルの組み込み構文サポートにより、コードを大幅に簡素化できるため、クラスの代わりにタプルを提供するのが自然です。

ただし、明示的な宣言済みクラスをすでに使用できるため、このようなショートカットを提供するJavaカルチャとは実際には一致しません。いくつかのコード行を保存し、いくつかの宣言を避けるために、異なる種類のデータ構造を導入する必要はありません。

Javaは、最小限の特別な場合の構文糖で一貫して適用される単一の概念(クラス)を好みますが、Pythonは、特定の目的に最も便利なものを選択できるように、複数のツールと多くの構文糖を提供します。


+1、しかし、このタプルを導入する必要性を発見したScalaなどのクラスを持つ静的に型付けされた言語とどのようにメッシュしますか?タプルは、クラスがある言語であっても、場合によってはきれいなソリューションであると思います。
アンドレスF.

@AndresF .:メッシュとはどういう意味ですか?ScalaはJavaとは異なる言語であり、異なる設計原則とイディオムを持っています。
ジャックB

「しかし、これは実際にJavaの文化とは一致しません[...]」で始まる5番目の段落への返信でそれを意味しました。実際、冗長性はJavaの文化の一部であることに同意しますが、タプルの欠如は「すでに明示的に宣言されたクラスを既に使用している」ためではありません-結局、Scalaには明示的なクラスもあります(そしてより簡潔なケースクラスを導入します)が、タプルも許可します!結局、Javaがタプルを導入しなかったのは、クラスが同じことを(より雑然として)達成できるだけでなく、その構文がCプログラマーに馴染みがなければならず、Cにはタプルがなかったからだと思います。
アンドレスF.

@AndresF .: Scalaには、物事を行う複数の方法に対する嫌悪感はなく、機能的なパラダイムと従来のオブジェクト指向の両方の機能を組み合わせています。そういう意味では、Python哲学に近いです。
ジャックB

@AndresF。はい、JavaはC / C ++に近い構文で始まりましたが、かなり単純化されました。C#はJavaのほぼ正確なコピーとして始まりましたが、タプルを含む多くの機能を長年にわたって追加してきました。ですから、それは言語設計哲学の違いです。(。その後のJavaのバージョンでは、どのクラス同じことを行う可能性があるため、高階関数が最初に拒否されたもののあまり独断的なことに見えるですが、今、彼らはので、多分私達は哲学の変化を見ている導入されている。。)
JacquesB

16

プラクティスを検索しないでください。ベストプラクティスBADで述べたように、通常パターンは良いですか?。ベストプラクティスを求めているのではないことは承知していますが、関連する要素はそこにあると思います。

問題の解決策を探すことは実践よりも優れており、問題はJavaで3つの値を高速に返すタプルではありません。

  • 配列があります
  • 配列をワンライナーのリストとして返すことができます:Arrays.asList(...)
  • より少ない定型文でオブジェクト側を保持したい場合(およびロンボクなし):

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,....){
        ...
    }
 }

ここには、データだけを含む不変オブジェクトがあり、パブリックなので、ゲッターは必要ありません。ただし、ORMなどのシリアル化ツールまたは永続化レイヤーを使用する場合、一般にゲッター/セッターを使用します(また、ゲッター/セッターの代わりにフィールドを使用するパラメーターを受け入れる場合があります)。そして、これがこれらのプラクティスが頻繁に使用される理由です。そのため、プラクティスについて知りたい場合は、それらをよりよく使用するために、なぜここにいるのかを理解する方が良いでしょう。

最後に、多くのシリアル化ツールを使用するためゲッターを使用しますが、どちらも作成しません。lombokを使用します。IDEで提供されるショートカットを使用します。


9
さまざまな言語で出くわす一般的なイディオムを理解することには価値があります。これらは、よりアクセスしやすい事実上の標準になります。これらのイディオムは、何らかの理由で「ベストプラクティス」という見出しに分類される傾向があります。
ベリンロリチュ

3
ねえ、問題に対する賢明な解決策。[gs] etterを備えた本格的なオーバーエンジニアリングされたOOPソリューションの代わりに、少数のパブリックメンバーでクラス(/ struct)を記述することは非常に合理的です。
-tomsmeding

3
これは、Pythonとは異なり、検索またはより簡潔で洗練されたソリューションが推奨されないため、肥大化につながります。しかし、デメリットは、どのJava開発者にとっても肥大化はほぼ同じ種類になるということです。したがって、開発者はより互換性が高く、2つのプロジェクトに参加したり、2つのチームが意図せずに分岐したりするため、保守性がある程度高くなり、スタイルの違いが少なくなります。
ミハイルラメンディク

2
@MikhailRamendik YAGNIとOOPの間のトレードオフです。正統派のOOPは、すべての単一オブジェクトが交換可能であるべきだと言っています。重要なのは、オブジェクトのパブリックインターフェイスのみです。これにより、具体的なオブジェクトにあまり重点を置かず、インターフェイスに重点を置いたコードを作成できます。また、フィールドはインターフェイスの一部にできないため、公開することはありません。これは、することができ、長期的に維持するために、コードがはるかに容易になります。さらに、無効な状態を防ぐことができます。元のサンプルでは、​​相互に排他的な「<ms」と「s」の両方であるオブジェクトを持つことができます。それは良くないね。
ルアーン

2
@PeterMortensenこれは、かなり無関係なことをたくさん行うJavaライブラリです。でもとてもいいです。機能は次のとおりです
マイケル

11

一般的なJavaイディオムについて:

Javaにすべてのクラスがあるのには、さまざまな理由があります。私の知る限り、主な理由は次のとおりです。

Javaは初心者にとって簡単に習得できるものでなければなりません。明示的なものが多いほど、重要な詳細を見落とすのが難しくなります。初心者が把握するのが難しい魔法の発生が少なくなります。


あなたの特定の例について:別のクラスの引数の行はこれです:これらの3つの事柄が1つの値として返されるという互いに十分に関連している場合、その「事」に名前を付ける価値があります。そして、共通の方法で構造化された物事のグループに名前を付けることは、クラスを定義することを意味します。

Lombokなどのツールを使用して定型文を減らすことができます。

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

5
GoogleのAutoValueプロジェクトでもあります: github.com/google/auto/blob/master/value/userguide/index.md
Ivan

これが、Javaプログラムを比較的簡単に保守
トールビョーンラヴンアンデルセン

7

Javaの文化について言えることはたくさんありますが、今直面している場合には、いくつかの重要な側面があると思います。

  1. ライブラリコードは一度書かれますが、より頻繁に使用されます。ライブラリを記述するオーバーヘッドを最小限に抑えるのは良いことですが、おそらく長期的にはライブラリを使用するオーバーヘッドを最小限に抑える方法で記述する方が価値があります。
  2. それは、自己文書化型が優れていることを意味します。メソッド名は、何が起こっているのか、オブジェクトから何を取得しているのかを明確にするのに役立ちます。
  3. 静的型付けは、特定のクラスのエラーを排除するための非常に便利なツールです。それは確かにすべてを修正するわけではありません(Haskellについて、型システムがコードを受け入れるようになると、おそらく正しいと冗談を言うのが好きです)が、特定の種類の間違ったことを不可能にするのは非常に簡単です。
  4. ライブラリコードの記述は、コントラクトの指定に関するものです。引数および結果の型のインターフェイスを定義すると、契約の境界がより明確に定義されます。何かがタプルを受け入れたり生成したりする場合、それが実際に受け取ったり生成したりするタプルの種類であるかどうかは言うまでもなく、そのようなジェネリック型に対する制約はほとんどありません(適切な数の要素さえありますか?彼らはあなたが期待していたタイプの?)。

フィールドを持つ「構造」クラス

他の回答で言及したように、パブリックフィールドを持つクラスを使用できます。これらをfinalにすると、不変クラスを取得し、コンストラクターでそれらを初期化します。

   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;
      }
   }

もちろん、これは特定のクラスに関連付けられていることを意味し、解析結果を生成または消費する必要があるものはすべてこのクラスを使用する必要があります。一部のアプリケーションでは、それで問題ありません。他の人にとっては、それはいくつかの痛みを引き起こす可能性があります。多くのJavaコードは、コントラクトを定義することに関するものであり、通常、これによりインターフェイスにアクセスできます。

別の落とし穴は、クラスベースのアプローチでは、フィールドを公開していることであり、それらのフィールドすべて値を持っている必要があります。たとえば、isLessThanOneMilliがtrueであっても、isSecondsとmillisには常に何らかの値が必要です。isLessThanOneMilliがtrueの場合、millisフィールドの値の解釈はどうなりますか?

インターフェイスとしての「構造」

インターフェイスで許可されている静的メソッドを使用すると、実際には、構文上のオーバーヘッドをあまりかけずに不変の型を比較的簡単に作成できます。たとえば、あなたが話しているような結果構造を次のようなものとして実装できます。

   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;
            }
         };
      }
   }

それはいまだに定型的なものです、私は完全に同意しますが、いくつかの利点もあり、それらはあなたの主要な質問のいくつかに答え始めると思います。

この解析結果のような構造により、パーサーのコントラクトは非常に明確に定義されています。Pythonでは、1つのタプルは別のタプルと実際には区別されません。Javaでは、静的型付けが利用できるため、特定のクラスのエラーは既に除外されています。たとえば、Pythonでタプルを返しているときに、タプル(millis、isSeconds、isLessThanOneMilli)を返したい場合は、誤って実行する可能性があります。

return (true, 500, false)

あなたが意味したとき:

return (500, true, false)

この種のJavaインターフェースでは、コンパイルできません。

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

まったく。あなたはしなければならない:

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

これは一般に静的に型付けされた言語の利点です。

このアプローチにより、取得できる値を制限することもできます。たとえば、getMillis()を呼び出すときに、isLessThanOneMilli()がtrueであるかどうかを確認できます。trueの場合は、IllegalStateException(たとえば)をスローします。

間違ったことをするのを難しくする

上記のインターフェイスの例では、isSeconds引数とisLessThanOneMilli引数が同じ型であるため、誤って引数を入れ替えてしまうという問題がまだあります。

実際には、次のような結果が得られるように、TimeUnitと期間を実際に使用することができます。

   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();
            }
         };
      }
   }

それはあることをなってきた多くのより多くのコードが、あなたは一度だけ、それを書く必要があり、そして、結局人(あなたが適切なものを文書化してきたと仮定した場合)使用して、あなたのコードは、結果が何を意味するのかを推測する必要はありませんし、result[0]彼らが意味するときのようなことを誤ってすることはできませんresult[1]。まだかなり簡単にインスタンスを作成することができ、それらからデータを取得することもそれほど難しくありません:

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

クラスベースのアプローチでも実際にこのようなことを行うことができることに注意してください。さまざまな場合にコンストラクタを指定するだけです。ただし、他のフィールドをどのように初期化するかという問題がまだあり、それらへのアクセスを防ぐことはできません。

別の回答では、Javaのエンタープライズタイプの性質は、多くの場合、すでに存在する他のライブラリを作成している、または他の人が使用するライブラリを作成していることを意味すると述べました。あなたの公開APIは、それを避けることができるならば、結果の種類を解読するためのドキュメントを参考に多くの時間を必要とすべきではありません。

あなただけの書き込みを一度これらの構造をしていますが、作成する彼らに何回も、あなたはまだ(あなたが得る)という簡潔な作成をしたいです。静的型付けにより、それらから取得するデータが期待どおりであることを確認できます。

さて、そうは言っても、単純なタプルまたはリストが多くの意味をなす場所がまだあります。何かの配列を返すことでオーバーヘッドが少なくなる可能性があり、その場合(そしてそのオーバーヘッドが重要であり、プロファイリングで決定します)、内部で値の単純な配列を使用することは理にかなっています。パブリックAPIには、おそらく明確に定義された型があるはずです。


最後に、実際の時間単位とともにUNDER1MSを追加したため、TimeUnitの代わりに独自の列挙を使用しました。ですから、今では3つではなく2つのフィールドしかありません。(理論的には、TimeUnit.NANOSECONDSを誤用する可能性がありますが、それは非常に紛らわしいでしょう。)
ミハイルラメンディック

ゲッターは作成しませんでしたがoriginalTimeUnit=DurationTimeUnit.UNDER1MS、呼び出し元がミリ秒値を読み取ろうとした場合に、ゲッターがどのように例外をスローできるかを確認しました。
ミハイルラメンディク

あなたがそれについて言及したことは知っていますが、Pythonタプルを使用した例は、実際には動的タプル対静的タプルに関するものであることを忘れないでください。タプル自体についてはあまり語っていません。あなた知っていると確信しているように、コンパイルに失敗する静的に型付けされたタプルを持つことができます:)
Andres F.

1
@Veedracどういう意味かわかりません。私のポイントは、より多くの時間が費やされるので、それは、(それは長い時間がかかるにもかかわらず)このようにより冗長ライブラリのコードを記述するためにOKだということでした使用よりもコードを書いて、それを。
ジョシュアテイラー

1
@Veedracはい、そうです。しかし、意味のある再利用コードとそうでないコードに区別があると主張します。たとえば、Javaパッケージでは、クラスの一部はパブリックであり、他の場所から使用されます。これらのAPIは十分に検討する必要があります。内部的には、互いに対話するだけでよいクラスがあります。これらの内部カップリングは、高速で汚れた構造(たとえば、3つの要素を持つと予想されるObject [])が適切な場所です。パブリックAPIの場合は、通常、より意味のある明示的なものが優先されます。
ジョシュアテイラー

7

問題は、リンゴとオレンジを比較することです。型指定されていないタプルを使用したpython&quick&dirtyの例を使用して、複数の値を返すことをシミュレートする方法を尋ねると、実際には1行の回答が返されました。

受け入れられた答えは、正しいビジネスソリューションを提供します。返された値を使用して実用的なことを行う必要がある場合、最初に捨てて正しく実装する必要がある簡単な一時的な回避策はありませんが、永続性、シリアル化/逆シリアル化を含む多数のライブラリと互換性のあるPOJOクラス、計装および可能なあらゆるもの。

これもまったく長くはありません。書く必要があるのはフィールド定義だけです。セッター、ゲッター、hashCode、およびequalsを生成できます。したがって、実際の質問は、ゲッターとセッターが自動生成されないのはなぜかということですが、それは文化的な問題ではなく、構文の問題(構文シュガーの問題、言う人もいます)です。

そして最後に、あなたはまったく重要ではない何かをスピードアップしようと考えすぎています。DTOクラスの作成に費やされる時間は、システムの保守とデバッグに費やされる時間に比べてわずかです。したがって、詳細度を低くするために最適化する人はいません。


2
「誰も」は事実上正しくありません-冗長性を抑えるために最適化するツールがたくさんあります。正規表現は極端な例です。しかし、あなたは「主流のJavaの世界には誰もいない」ことを意味していたかもしれません。そのため、答えを読むことは理にかなっています、ありがとう。冗長性に関する私自身の懸念は、コードの記述に費やした時間ではなく、コードの読み取りに費やした時間です。IDEは読み取り時間を節約しません。しかし、考えは99%の時間はインターフェース定義を読み取るだけなので、簡潔にする必要があるということです?
ミハイルラメンディク

5

あなたが観察していることに貢献する3つの異なる要因があります。

タプルと名前付きフィールド

おそらく最も簡単な-他の言語ではタプルを使用しました。タプルが良いアイデアであるかどうかを議論することは実際にはポイントではありませんが、Javaではより重い構造を使用しているため、少し不公平な比較です。オブジェクトの配列と型キャストを使用することもできます。

言語構文

クラスを宣言する方が簡単ですか?フィールドをパブリックにすることやマップを使用することではなく、Scalaのケースクラスのようなものです。これは、説明したセットアップのすべての利点を提供しますが、より簡潔です。

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

それは可能ですが、コストがかかります。構文がより複雑になります。もちろん、場合によっては価値があるかもしれませんし、ほとんどの場合、あるいは今後5年間はほとんどの場合でさえ価値がありますが、判断する必要があります。ところで、これはあなたが自分で変更できる言語(つまり、Lisp)の素晴らしいところです-構文の単純さにより、これがどのように可能になるかに注意してください。実際に言語を変更しなくても、簡単な構文でより強力なツールを使用できます。たとえば、多くの場合、ScalaではなくJavaで利用可能なリファクタリングオプションがいくつかあります。

言語哲学

しかし、最も重要な要因は、言語が特定の考え方を可能にすることです。ときどき圧迫感を感じるかもしれませんが(特定の機能のサポートを希望することがよくあります)、機能を削除することは、機能を使用することと同じくらい重要です。すべてをサポートしてもらえますか?確かに、しかしすべての単一の言語をコンパイルするコンパイラを書くこともできます。つまり、言語はありません。言語のスーパーセットがあり、すべてのプロジェクトは基本的にサブセットを採用します。

もちろん、言語の哲学に反するコードを書くことは可能であり、あなたが観察したように、結果はしばしばresultsいです。Javaにフィールドを2つだけ持つクラスは、Scalaでvarを使用したり、関数のプロローグ述語を縮退したり、haskellなどでunsafePerformIOを実行したりするのに似ています。 。何かが難しいように思えた場合、一歩下がって別の方法があるかどうかを確認することは実り多いものです。あなたの例では:

期間がユニットから分離されているのはなぜですか?Duration(5、seconds)(構文は異なります)のような期間を宣言できるタイムライブラリがたくさんあります。これにより、はるかに堅牢な方法で必要な処理を実行できます。多分あなたはそれを変換したいでしょう-なぜresult [1](または[2]?)が '時間'で3600を掛けているかをチェックするのはなぜですか?そして、3番目の引数について-その目的は何ですか?ある時点で、「1ms未満」または実際の時間を出力する必要があると思います。これは、当然、時間データに属するロジックです。つまり、次のようなクラスが必要です。

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

}

または実際にデータを処理したいものは何でも、したがってロジックをカプセル化します。

もちろん、この方法が機能しない場合があります-これがタプルの結果を慣用のJavaコードに変換するための魔法のアルゴリズムであるとは言いません!そして、それは非常にくて悪い場合があり、おそらく別の言語を使用すべきだったかもしれません-それは結局のところ非常に多くある理由です!

しかし、Javaでクラスが「重い構造」である理由についての私の見解は、クラスをデータコンテナとしてではなく、ロジックの自己完結型セルとして使用することを意図しているということです。


デュレーションでやりたいこと、実際にそれらを合計してから別のものと比較することです。関係する出力はありません。比較では丸めを考慮する必要があるため、比較時に元の時間単位を知る必要があります。
ミハイルラメンディク

クラスのインスタンスを追加および比較する方法を作成することはおそらく可能ですが、これは非常に重くなります。特に、フロートで除算および乗算する必要があるため(そのUIで確認するパーセンテージがあります)。そのため、今のところ、最終フィールドを持つ不変クラスを作成しましたが、これは良い妥協のように見えます。
ミハイルラメンディク

この特定の質問に関するより重要な部分は、物事のより一般的な側面であり、すべての回答から写真が一緒に来ているようです。
ミハイルラメンディク

@MikhailRamendikおそらくある種のアキュムレータがオプションになるでしょう-しかし、あなたは問題をよく知っています。確かに、この特定の問題を解決することではなく、少し脇道にいってすみません-私の主なポイントは、言語が「単純な」データの使用を思いとどまらせることです。時々 、これはあなたのアプローチを再考役立ちます-他の回intはちょうどint型である
サノスTintinidis

@MikhailRamendik定型的な方法の良いところは、クラスを取得したら、それに動作を追加できることです。そして/またはあなたがしたようにそれを不変にします(これはほとんどの場合良い考えです;あなたは常に合計として新しいオブジェクトを返すことができます)。最終的に、「余分な」クラスは必要なすべての動作をカプセル化し、ゲッターのコストは無視できるようになります。また、必要な場合と必要ない場合があります。lombokまたはautovalueの使用を検討してください。
-maaartinus

5

私の理解では、核となる理由は

  1. インターフェイスは、クラスを抽象化する基本的なJavaの方法です。
  2. Javaはメソッドから単一の値(オブジェクト、配列、ネイティブ値(int / long / double / float / boolean))のみを返すことができます。
  3. インターフェイスにはフィールドを含めることはできず、メソッドのみを含めることができます。フィールドにアクセスする場合は、メソッドを通過する必要があります-したがって、ゲッターとセッターです。
  4. メソッドがインターフェイスを返す場合、実際に返すには実装クラスが必要です。

これにより、「自明でない結果を返すクラスを作成する必要があります」という結果になりますが、これはかなり重いです。インターフェイスの代わりにクラスを使用した場合は、フィールドを作成して直接使用することもできますが、それは特定の実装に結び付けられます。


これに追加するには、次の場合は、あなたが唯一の -理想的に不変小さなコンテキスト(たとえば、クラスのprivateメソッド)でこれを必要とする、それが裸のレコードを使用して大丈夫です。ただし、クラス(またはさらに悪いことに、ライブラリ)のパブリックコントラクトに実際に漏れてはいけません。もちろん、これは将来のコード変更でこれが決して起こらないことを確実にするためだけにレコードに対して決定することができます 多くの場合、よりシンプルなソリューションです。
ルアーン

そして、「タプルはJavaでうまく機能しない」ビューに同意します。ジェネリックを使用すると、これはすぐに厄介になります。
アンデルセン

@ThorbjørnRavnAndersenこれらは、ジェネリックタプルを持つ静的に型付けされた言語であるScalaで非常にうまく機能します。だから多分これはJavaのせいですか?
アンドレスF.

@AndresF。「ジェネリックを使用する場合」の部分に注意してください。Javaがこれを行う方法は、Haskellなどとは異なり、複雑な構造には向いていません。比較するのに十分なScalaの知識はありませんが、Scalaが複数の戻り値を許可しているのは本当ですか?それは大いに役立つかもしれません。
トールビョーンラヴンアンデルセン

@ThorbjørnRavnAndersenScala関数には、タプル型の単一の戻り値があります(たとえば、整数のタプルである値を返すdef f(...): (Int, Int)関数fです)。Javaのジェネリックが問題であるかどうかはわかりません。たとえば、Haskellはタイプ消去も行うことに注意してください。Javaにタプルがないという技術的な理由はないと思います。
アンドレスF.

3

私はJacquesBの回答に同意します

Javaは(伝統的に)明示性と一貫性を最適化する単一パラダイムクラスベースのオブジェクト指向言語です。

ただし、明示性と一貫性は最適化の最終目標ではありません。「pythonは読みやすいように最適化されている」と言うとき、最終目標は「保守性」と「開発速度」であることにすぐに言及します。

明示性と一貫性があり、Javaの方法で行った場合、何を達成しますか?私の考えでは、それはソフトウェアの問題を解決するための予測可能で一貫した統一された方法を提供すると主張する言語として進化したということです。

言い換えれば、Javaカルチャーは、マネージャーがソフトウェア開発を理解していると信じるように最適化されています。

または、ある賢い人がずっと前に言ったように

言語を判断する最善の方法は、支持者によって書かれたコードを見ることです。「Radix enim omnium malorum est cupiditas」-Javaは明らかに金銭指向プログラミング(MOP)の例です。SGIのJavaの主な支持者が私に言ったように:「アレックス、お金があるところに行かなければならない。」しかし、私はお金がどこにあるかは特に行きたくありません-それは通常そこにいい匂いがしません。


3

(この回答は、特にJavaの説明ではありませんが、代わりに「(重い)プラクティスが最適化できるものは何か?」という一般的な質問に対処します。)

次の2つの原則を検討してください。

  1. あなたのプログラムが正しいことをするのは良いことです。正しいことを行うプログラムを簡単に作成できるようにする必要があります。
  2. あなたのプログラムが間違ったことをするのは悪いことです。間違ったことをするプログラムを書くのを難しくすべきです。

これらの目標の1つを最適化しようとすると、他の目標が邪魔される場合があります(つまり、間違ったことをしにくくすると、正しいことをするのが難しくなる、またはその逆)。

特定のケースでどのトレードオフが行われるかは、アプリケーション、問題のプログラマーまたはチームの決定、および(組織または言語コミュニティの)文化に依存します。

たとえば、プログラムのバグや数時間の停止が命の損失(医療システム、航空)または単にお金(たとえばGoogleの広告システムで数百万ドル)をもたらす可能性がある場合、異なるトレードオフを行います(あなたの言語だけでなく、エンジニアリング文化の他の側面でも)、1回限りのスクリプトの場合よりも、「重い」側に傾いている可能性があります。

システムをより「重く」する傾向がある他の例:

  • 多くのチームが長年にわたって大規模なコードベースを使用している場合、大きな懸念事項の1つは、誰かが他の誰かのAPIを誤って使用する可能性があることです。関数の引数が間違った順序で呼び出されたり、予期されるいくつかの前提条件/制約を保証せずに呼び出されたりすると、壊滅的な可能性があります。
  • この特殊なケースとして、チームが特定のAPIまたはライブラリを管理しており、それを変更またはリファクタリングしたいとします。ユーザーがコードをどのように使用できるかについて「制約」が大きいほど、コードを簡単に変更できます。(ここでは、誰もそれを異常な方法で使用できないという実際の保証があることが望ましいことに注意してください。)
  • 開発が複数の人またはチームに分割されている場合、1人の人またはチームにインターフェースを「指定」し、他の人またはチームに実際に実装させることをお勧めします。それが機能するためには、実装が実際に仕様に一致するという実装が完了したときにある程度の信頼を得ることができる必要があります。

これらはほんの一例に過ぎず、物事を「重く」する(そしてコードをすばやく書くのを難しくする)ことが本当に意図的な場合のアイデアをあなたに与えるためです。(コードを書くのに多大な労力が必要な場合、コードを書く前にもっと慎重に考えるようになるかもしれないと主張するかもしれません!もちろん、この議論の行はすぐにばかげています。)

例:Googleの内部Pythonシステムは、他の人のコードを単純にインポートできないため、物事を「重く」する傾向があり、BUILDファイルで依存関係を宣言する必要があります。インポートするコードのチームは、ライブラリをコードなどに表示されます。


:上記のすべては、物事が「重くなる」傾向があるときです。JavaやPython(言語自体、またはその文化)が特定の場合に最適なトレードオフを行うと絶対に言いませ。それはあなたが考えることです。そのようなトレードオフに関する2つの関連リンク:


1
コードの読み取りはどうですか?そこには別のトレードオフがあります。奇妙な呪文や大量の儀式によって重くされたコードは読みにくい。IDEにボイラープレートと不必要なテキスト(およびクラス階層!)があると、コードが実際に何をしているのかがわかりにくくなります。コードを書くのは必ずしも簡単ではないという議論を見ることができます(同意するかどうかはわかりませんが、ある程度のメリットがあります)が、間違いなく読みやすいはずです。そう主張する愚かなことだろう- -明らかに複雑なコードは、およびJavaで構築されてきたことができますが、それはだったおかげで、その冗長性に、または中にもかかわらず、それを?
アンドレスF.

@AndresF。答えを編集して、明確にJavaの問題ではないことを明確にしました(私は大ファンではありません)。しかし、はい、コードを読むときのトレードオフ:一端では、あなたが言ったように「コードが実際に行っていること」を簡単に読めるようにしたいです。もう一方の端では、次のような質問への回答を簡単に確認できるようにしたいと考えています。このコードは他のコードとどのように関連していますか?このコードができる最悪のことは何ですか:他の状態にどのような影響がありますか、その副作用は何ですか?このコードが依存する可能性がある外部要因は何ですか?(もちろん理想的には、両方の質問に迅速な回答が必要です。)
ShreevatsaR

2

Javaの文化は、オープンソースとエンタープライズソフトウェアの両方のバックグラウンドから大きな影響を受けて進化してきました。エンタープライズソリューションには重いツールが必要であり、オープンソースにはシンプルさが求められます。最終的な結果は、Javaが中間のどこかにあるということです。

推奨事項に影響を与えるものの一部は、Pythonで読み取り可能および保守可能と見なされるものであり、Javaは大きく異なります。

  • Pythonでは、タプルは言語機能です。
  • JavaとC#の両方で、タプルはライブラリ機能です(またはライブラリ機能になります)。

標準ライブラリにはTuple <A、B、C、.. n>クラスのセットがあるため、C#のみに言及します。これは、言語がそれらを直接サポートしていない場合の扱いにくいタプルの完璧な例です。ほとんどすべてのインスタンスで、問題を処理するクラスを適切に選択した場合、コードはより読みやすく、保守しやすくなります。リンクされたStack Overflow質問の特定の例では、他の値は戻りオブジェクトの計算されたゲッターとして簡単に表現されます。

C#プラットフォームが行った興味深い解決策は、C#3.0でリリースされた匿名オブジェクトのアイデアです。残念ながら、Javaにはまだ同等のものがありません。

Javaの言語機能が修正されるまで、最も読みやすく保守可能なソリューションは専用オブジェクトを持つことです。これは、1995年の始まりにまで遡る言語の制約によるものです。元の作者は、それを達成することのなかった多くの言語機能を計画しており、下位互換性はJavaの進化を取り巻く主な制約の1つです。


4
C#の最新バージョンでは、新しいタプルはライブラリクラスではなくファーストクラスの市民です。そこに到達するには多くのバージョンが必要でした。
イゴールSoloydenko

最後の3つの段落は、「言語XにはJavaにはない機能があります」と要約できますが、答えに何が追加されているかわかりません。Python to JavaトピックでC#に言及する理由 いいえ、要点がわかりません。2万人以上の担当者から少し奇妙な。
オリビエグレゴワール

1
私が言ったように、「タプルはライブラリ機能であるため、C#のみに言及します」は、メインライブラリにタプルがある場合のJavaでの動作に似ています。使用するのは非常に面倒であり、ほとんどの場合、専用のクラスを用意する方がよい答えです。ただし、匿名オブジェクトは、タプルが設計されているものと同様の悩みを引き起こします。より良いものをサポートする言語がなければ、基本的に最も保守しやすいもので作業する必要があります。
ベリンロリチュ

@IgorSoloydenko、おそらくJava 9に希望があるのでしょうか?これは、ラムダの論理的な次のステップである機能の1つにすぎません。
ベリンロリチュ

1
@IgorSoloydenko私はそれが本当に正しいかわかりません(ファーストクラス市民)-それらはライブラリからクラスのインスタンスを作成およびアンラップする構文拡張であるようであり、クラス、構造体、列挙型としてCLRでファーストクラスをサポートしています。
ピートカーカム

0

この場合にクラスを使用する際の中心的なことの1つは、一緒に行くものは一緒にとどまる必要があるということです。

メソッドの引数については、この議論を逆に行ってきました。BMIを計算する簡単なメソッドを考えてみましょう。

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

この場合、体重と身長が関係しているため、このスタイルに反対します。メソッドは、そうでない場合に2つの別々の値を「通信」します。ある人の体重と別の人の身長でBMIを計算するのはいつですか?意味がありません。

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

これは、身長と体重が同じソースからのものであることを明確に伝えるためです。

複数の値を返す場合も同じです。それらが明確に接続されている場合、複数の値を返さない場合は、きれいな小さなパッケージを返し、オブジェクトを使用します。


4
わかりましたが、グラフを表示する(仮想の)タスクがあると仮定します。BMIは特定の固定高さの重量にどのように依存しますか。次に、各データポイントに対してPersonを作成せずにそれを行うことはできません。そして、極端に言えば、有効な誕生日とパスポート番号なしでPersonクラスのインスタンスを作成することはできません。それで?ところで、私はいくつかのクラスでプロパティを一緒にまとめる正当な理由があります。
アルテム

1
@artemは、インターフェースが機能する次のステップです。したがって、personweightheightインターフェイスはそのようなものになります。あなたは、一緒に行くものが一緒にあるべきであるというポイントを作るために私が与えた例に巻き込まれています。
ピーターB

0

率直に言って、文化は、Javaプログラマーはもともとオブジェクト指向の原則と持続可能なソフトウェア設計の原則が教えられた大学から出てくる傾向があったということです。

ErikEが答えでより多くの言葉で言っているように、あなたは持続可能なコードを書いているようには見えません。あなたの例から私が見るものは、懸念の非常に厄介な絡み合いがあります。

Java文化では、利用可能なライブラリを意識する傾向があります。これにより、すぐに使えるプログラミング以上のことを実現できます。そのため、ハードコアな産業環境で試行され、テストされたデザインパターンとスタイルの特徴を交換することになります。

しかし、あなたが言うように、これは欠点がないわけではありません:今日、Javaを10年以上使用してきたので、新しいプロジェクトにはNode / JavascriptまたはGoのいずれかを使用する傾向があります。どちらもより速い開発を可能にし、マイクロサービススタイルのアーキテクチャではしばしば十分です。Googleが最初はJavaを多用していましたが、Goの創始者であるという事実から判断すると、彼らも同じことをしているのではないかと思います。しかし、現在はGoとJavascriptを使用していますが、Javaの長年の使用と理解から得た多くの設計スキルを使用しています。


1
特に、googleが使用するfoobar募集ツールでは、2つの言語(javaとpython)をソリューションに使用できます。Goがオプションではないことは興味深いと思います。Goでこのプレゼンテーションを行ったところ、JavaとPython(および他の言語)について興味深い点がいくつかありました。たとえば、目立った箇条書きが1つあります。 「解釈と動的型付けを使用する」読む価値があります。
ジミージェームズ

@JimmyJamesは、「専門家の手で、彼らは素晴らしい」と言ってスライドにたどり着きました
トム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.