web-dev-qa-db-ja.com

Grokking Java文化-なぜ物事はそんなに重いのですか?それは何のために最適化されますか?

私はPython=でコーディングするのが常でした。今では作業上の理由から、Javaでコーディングしています。私が行うプロジェクトはかなり小さく、おそらくPythonうまく機能しますが、Javaを使用することには有効な非エンジニアリング上の理由があります(詳細については説明できません)。

Java構文は問題ありません。それは別の言語です。しかし、構文とは別に、Javaには、「正しい」と見なされる文化、開発方法のセット、および実践があります。そして今のところ、私はその文化を完全に「成長」させることに失敗しています。私は説明や正しい方向への指針に本当に感謝しています。

最小限の完全な例は、私が始めたスタックオーバーフローの質問で利用できます: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プロジェクトに対応できる状態にあるはずです)ですが、それは非常に弱い推測です。

291

Java言語

Javaが機能する方法に意図を帰すことを試みることによってこれらすべての答えがポイントを欠いていると私は思います。 Pythonと他の多くの言語はまだより簡潔な構文を備えているため、Javaの冗長性はオブジェクト指向であることから生じるものではありません。 Javaの冗長性は、アクセス修飾子のサポートからも得られません。代わりに、それはJavaが設計されて進化した方法です。

Javaはもともと、OOを備えたわずかに改善されたCとして作成されました。したがって、Javaには70年代の構文があります。さらに、Javaは、下位互換性を維持し、時の試練に耐えられるようにするための機能の追加について非常に慎重です。 XMLが大流行した2005年に、XMLリテラルなどのトレンディな機能がJavaに追加されていたら、誰も気にせず、その進化を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ローダーを書きました。彼らは日々これらの怪物を構成し、統合しなければなりませんでした。そしてその結果、コミュニティ全体の文化はこれらのフレームワークの例に倣い、ひどく過剰設計される傾向がありました。

237

他の人が提起していない、あなたが提起したポイントの1つに対する答えがあると思います。

Javaは、いかなる仮定もしないことにより、コンパイル時のプログラマエラー検出のために最適化します

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

この哲学の背後にある理由は、プログラマーが人間だけであることです。私たちが書くことは、常にプログラムが実際に行うことを意図しているとは限りません。 Java言語は、開発者に常に型を明示的に宣言するように強制することにより、これらの問題のいくつかを軽減しようとします。これは、記述されたコードが実際に意図したとおりに機能することを再確認する方法の1つにすぎません。

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

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

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

  1. 戻り値の型としてTriplet<A, B, C>を使用します(これは実際にはJava.utilにあるはずですが、そうでない理由を実際に説明することはできません。特にJDK8がFunctionBiFunctionConsumerBiConsumerなどを導入しているためです... PairTripletは少なくとも理にかなっているようですが、私は割愛します)

  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オブジェクトとして返されることもあります。これは、上記の両方のケースよりもはるかに明確になります。

74
LordOfThePigs

JavaとPythonは私が最もよく使用する2つの言語ですが、私は他の方向から来ています。つまり、Java以前は私はPython=を使用し始めたので、私は助けることができるかもしれません。「なぜ物事がとても重いのか」というより大きな質問への答えは、2つのことになると思います。

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

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

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

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

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を使用していない場合は、開始する必要があります。それはあなたのためにゲッター(そしてあなたがそれらを必要とするならセッター)を生成するので、これはそれほど苦痛ではありません。

あなたのニーズのほとんどを満たすように見えるタイプがすでにあることを指摘しなかったとしたら、私は失望するでしょう here 。それを脇に置いて、あなたが使用しているアプローチは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;
  }
}
50
JimmyJames

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

あなたの不満の1つは、答えとしていくつかの値のセットを返すためだけにクラスを作成する必要があることです。これはあなたのプログラミングの直感が順調に進んでいることを示していると私が思う正当な懸念事項です!しかし、他の回答は、あなたがコミットした原始的な強迫観念の反パターンに固執することによって、足りないものだと思います。また、Javaは、複数のプリミティブを扱うのがPythonの場合と同じように簡単ではありません。この場合、複数の値をネイティブに返し、複数の変数に簡単に割り当てることができます。 。

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

おおよその期間(またはエラーのマージンを含む期間ですが、それを表します)を合計するコードは完全に手続き型ですか、それともオブジェクト性がありますか?おおよその継続時間を合計することに関する優れた設計は、それ自体をテストできるクラス内で、消費するコードの外でそれを行うことを指示することを提案します。この種のドメインオブジェクトを使用すると、コードに肯定的な波及効果があり、行ごとの手順から離れて、単一の高レベルのタスク(多くの責務はあるが)を達成して、単一の責任のクラスに進むのに役立ちます異なる懸念の衝突から解放されます。

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

最後のステートメント

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

補遺

C#の自動プロパティとコンストラクターでget-onlyプロパティを割り当てる機能は、「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になるための適切な候補でもあります。一部のテストでは、構造体に切り替えると、実行時のパフォーマンスが向上するかどうかを確認できます(可能性があります)。

41
ErikE

PythonとJavaは両方とも、設計者の哲学に従って保守性が最適化されていますが、これを実現する方法については非常に異なる考えを持っています。

Pythonはマルチパラダイム言語で、コードの明快さ単純性を最適化します(読み書きが簡単)。

Javaは(伝統的に)単一パラダイムのクラスベースのOO explicitnessおよびconsistencyを最適化する言語)-より多くのコストでさえ詳細コード。

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

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

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

24
JacquesB

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

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

  • 配列があります
  • 配列をリストとして1行で返すことができます: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のようないくつかのシリアル化ツールまたは永続化レイヤーを使用する場合、それらは一般的にゲッター/セッターを使用します(ゲッター/セッターの代わりにフィールドを使用するパラメーターを受け入れる場合があります)。そして、これがこれらの実践が頻繁に使用される理由です。したがって、プラクティスについて知りたい場合は、プラクティスをより効果的に使用するためになぜここにいるのかを理解することをお勧めします。

最後に、多くのシリアライゼーションツールを使用しているため、ゲッターを使用していますが、それらも記述していません。私はロンボクを使用しています。IDEが提供するショートカットを使用しています。

16
Walfrat

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

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

Javaは初心者にとって簡単に習得できるはずです。明示的なものが多いほど、重要な詳細を見逃すことが難しくなります。初心者が把握するのが難しいと思われる魔法が少なくなります。


特定の例については、別のクラスの引数の行は次のとおりです。これらの3つのものが相互に強く関連し、1つの値として返される場合、その「もの」に名前を付ける価値があります。そして、一般的な方法で構造化されたもののグループの名前を導入することは、クラスを定義することを意味します。

Lombokなどのツールを使用して、ボイラープレートを減らすことができます。

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

問題は、リンゴをオレンジと比較することです。型のないタプルを使用したクイック&ダーティpythonの例を示して、実際に one-liner answer を実際に受け取って、複数の値を返すシミュレーション方法を尋ねました。

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

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

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

7
user253752

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

  1. ライブラリコードは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コードはコントラクトの定義に関するものであり、通常はインターフェイスに移動します。

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

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

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

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

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

この解析結果のような構造では、パーサーのcontractが非常に明確に定義されています。 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とdurationを実際に使用したい場合があるので、次のような結果が得られます。

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

それはlotより多くのコードになりつつありますが、コードを1回書くだけで済み、(適切に文書化されていると仮定して)、結局sing yourコードは結果の意味を推測する必要がなく、result[0]を意味するresult[1]のようなことを誤って実行することもありません。あなたはまだcreateインスタンスにかなり簡潔に到達し、それらからデータを取得することはそれほど難しくありません:

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

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

別の答えは、Javaのエンタープライズタイプの性質は、ほとんどの場合、すでに存在する他のライブラリを作成するか、他の人が使用するライブラリを作成することを意味します。あなたの公開API回避できる場合、結果タイプを解読するためにドキュメントを参照するのに多くの時間を必要としません。

writeこれらの構造体は1回だけですが、-createそれらは何度も繰り返します。そのため、その簡潔な作成(取得)が必要です。静的型付けは、それらから取得するデータが期待どおりであることを確認します。

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

7
Joshua Taylor

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

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

おそらく最も些細なことです-他の言語ではタプルを使用しました。タプルが良いアイデアであるかどうかを議論することは、本当の意味ではありませんが、Javaでは重い構造を使用していたので、少し不公平な比較になります。オブジェクトの配列といくつかの型キャストを使用できた可能性があります。

言語構文

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

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

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

言語哲学

しかし、最も重要な要素は、言語が特定の考え方を可能にすることです。時々それは抑圧的に感じるかもしれませんが(私はしばしば特定の機能のサポートを望んでいました)、機能を削除することはそれらを持っていることと同じくらい重要です。あなたはすべてをサポートできますか?もちろん、すべての言語をコンパイルするコンパイラを作成することもできます。つまり、言語はありません-言語のスーパーセットがあり、すべてのプロジェクトは基本的にサブセットを採用します。

もちろん、言語の哲学に反するコードを書くことは可能であり、あなたが観察したように、結果はしばしば醜いです。 Javaにフィールドが2つしかないクラスを持つことは、Scalaでvarを使用すること、関数のプロローグ述語を縮退すること、haskellなどでunsafePerformIOを実行することと同じです。Javaクラスは意味されません軽くなるために-彼らはデータを渡すためにそこにありません。何かが難しいように思えるとき、別の方法があるかどうか後退して確認することはしばしば実りの多いことです。あなたの例では:

期間と単位が分かれているのはなぜですか?期間を宣言できる多くの時間ライブラリがあります。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で「重い構造」である理由についての私の見解は、クラスをデータコンテナーとしてではなく、ロジックの自己完結型セルとして使用するためのものではないということです。

5

私の理解では、中心的な理由は

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

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

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

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

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

これらの目標の1つを最適化しようとすると、他の目標が妨げられる場合があります(つまり、[間違ったことを行うことが難しくなると、正しいことを行うことが難しくなる可能性があります、またはその逆)。

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

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

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

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

これらはほんの一部の例であり、物事を「重く」する(そして、コードをすぐに書き出すのが難しくなる)ことが本当に意図的である場合があることを理解させるためです。 (コードを書くのに多くの労力が必要な場合、コードを書く前にもっと注意深く考える必要があるかもしれないと主張する人もいるでしょう!もちろん、この議論の行はすぐにばかげてきます。)

例:Googleの内部Pythonシステムは、他の人のコードを単純にインポートできないように「負荷がかかる」傾向があります。依存関係を BUILDファイル で宣言する必要があります、コードをインポートするチームは、ライブラリがコードから見えるように宣言されている必要があります。


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

3
ShreevatsaR

JacquesBの回答に同意します

Javaは(伝統的に)単一パラダイムクラスベースのOO明示性と一貫性を最適化する言語です

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

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

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

または ある賢い人がずっと前にそれを置いたように

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

3
artem

Javaカルチャーは、オープンソースとエンタープライズソフトウェアの両方の背景から多大な影響を受けて、時間とともに進化してきました。これは、本当に考えれば奇妙な組み合わせです。エンタープライズソリューションには、重いツールとオープンソースが必要です単純さが要求されます。最終結果はJavaが中間のどこかにあることです。

PythonとJavaは非常に異なっています。

  • Pythonでは、タプルはlanguage機能です。
  • JavaとC#の両方で、タプルはライブラリ機能です(またはそうなるでしょう))。

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

ハッピーミドルグラウンドを提供するC#プラットフォームが行った興味深いソリューションは 匿名オブジェクトC#3. でリリース)のアイデアであり、このかゆみを非常にうまく掻き立てます。残念ながら、Javaにはまだ相当するものがありません。

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

2
Berin Loritsch

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

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

Javaカルチャーでは、どのライブラリが利用可能であるかを認識する傾向があり、それにより、オフザカフプログラミングよりもはるかに多くのことを達成できるようになります。したがって、あなたの特異性をトレードオフすることになります。ハードコアの産業環境で試され、テストされたデザインパターンとスタイル。

しかし、あなたが言うように、これには欠点がないわけではありません:今日、10年以上Javaを使用してきたため、新しいプロジェクトではNode/JavascriptまたはGoを使用する傾向があります。 、そしてマイクロサービススタイルのアーキテクチャでは、これらで十分なことがよくあります。Googleが最初にJavaを多用していたという事実から判断すると、Goの創始者でしたが、同じことをしているのではないでしょうか。でも、今はGoとJavaScriptを使用していますが私は長年に渡ってJavaを使用および理解してきた多くの設計スキルを今でも使用しています。

0
Tom

この場合、クラスを使用する上で重要なことの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));
}

これで、身長と体重が同じソースからのものであることを明確に伝えることができるので、はるかに理にかなっています。

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

0
Pieter B