Javaで文字列を使用しないでください?[閉まっている]


73

コードにセマンティクスを持たせないためにJavaで文字列を使用することを思いとどまらせるブログエントリを見つけました。代わりにシンラッパークラスを使用することをお勧めします。これは、このエントリが問題を説明するために提供する前後の例です。

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

プログラミングブログを読んだ私の経験では、90%はナンセンスであるという結論に達しましたが、これが有効なポイントかどうか疑問に思っています。どういうわけか私には正しくないように感じますが、プログラミングのスタイルで何がおかしいのかを正確に特定することはできませんでした。


元のWikiに関する関連する議論:「toString()は何ですか?」
クリストファーハン

5
ああ、あのブログの投稿で体調が悪くなりました。
ロブ

5
私は少なくとも1つの本(Code Completeの初版である可能性があります)を読んで、問題のドメインの名前を持つようにすべてのプリミティブ型を型定義することを推奨しました。同じ考え。
user16764

9
「never」で始まるステートメントは「always」falseです。
フロランツツェライ

1
その男はCTOですか?!
ヒアンジェロ

回答:


88

カプセル化は、プログラムを変更から保護するためにあります。名前の表現は変更されますか?そうでなければ、あなたは時間を浪費していて、YAGNIが適用されます。

編集:私はブログの投稿を読んで、彼は根本的に良いアイデアを持っています。問題は、彼があまりにも遠くまでそれを拡大したことです。おそらく有効ではないので、そのようなものString orderId 本当に悪い"!"£$%^&*())ADAFVFですorderId。これは、String有効なorderIds よりもはるかに多くの可能な値を表すことを意味します。ただし、nameaなどの場合、有効な名前であるかどうかは予測できず、any Stringは有効ですname

最初の例では、可能な入力を有効な入力のみに減らしています。2番目の例では、可能な有効な入力の絞り込みを達成していません。

もう一度編集:無効な入力の場合を考えます。あなたの名前として「ガレスゴブルコック」と書くと、それはばかげて見えるでしょう、それは世界の終わりではないでしょう。無効なOrderIDを入力すると、単に機能しなくなる可能性があります。


5
注文IDを使用して、IDを受け入れて文字列を残すコードにチェックを追加することをお勧めします。クラスは、データを操作するためのメソッドを提供することになっています。一部のクラスが一部のデータの有効性のみをチェックし、その後何もしない場合、これは適切ではないようです。OOPは良いのですが、やりすぎてはいけません。
マルコム

13
@Malcolm:しかし、その後、何度も検証することを除いて、どの文字列が検証されるかを知る方法がありません。
DeadMG

7
「[...]有効な名前であるかどうかを予測することはできず、Stringは有効な名前です。」この手法の長所の1つは、パラメーターがtypeの場合、Name別の無関係なString値を誤って渡すことができないことです。aが期待される場合、a NameのみNameがコンパイルされ、文字列「I oyou you $ 5」は誤って受け入れられません。(これは、あらゆる種類の名前検証とは無関係です!)
Andres F.

6
特定の用途に固有の型を作成すると、セマンティックの豊かさが増し、安全性が高まります。さらに、特定の値を表す型を作成すると、IoCコンテナーを使用してクラスを自動配線するときに非常に役立ちます。特定のクラスに登録されている唯一のBeanである場合に使用する適切なBeanを簡単に解決できます。登録されている多くの文字列の1つである場合は、さらに多くの労力が必要です。
デイサン

3
@DeadMGこれは議論の余地があり、コメントで解決できるものではありません。プリミティブ型の値を誤って配置することは起こると思いますが、インターフェイスを強化することは状況を改善する1つの方法です。
アンドレスF.

46

それはただクレイジーです:)


2
それも私の意見ですが、問題は「Code Complete」でこの提案を覚えているということです。もちろん、その本のすべてが議論の余地のあるわけではありませんが、少なくとも、アイデアを拒否する前に考え直します。
DPM

8
正当な理由なしにいくつかのスローガンに言及しただけです。あなたの意見を詳しく説明していただけますか?いくつかの受け入れパターンと言語機能は、例えば、追加の複雑さのように見えることがあり、まだ(たとえば:静的型付けを)お返しに貴重な何かを提供
アンドレスF.

12
常識は一般的ではありません。
カズドラゴン

1
+1、これに、言語が名前付きパラメーターをサポートし、IDEが優れている場合(C#やVS2010の場合など)に追加すると、クレイジーなパターンを持つ言語の機能の不足を補う必要がなくなります。Xという名前のクラスとYという名前のクラスは、書くことができれば必要ありません。var p = new Point(x: 10, y:20);また、Cinemaが文字列と異なるということではありません。圧力、温度、エネルギーなど、単位が異なり、一部が負にならない物理量を扱う場合は理解できます。ブログの作成者は機能を試す必要があります。
ジョブ

1
「過剰なエンジニアリングをしないでください!」の+1
イヴァン

23

主に著者に同意します。ある場合はどの分野に特有の行動は、注文IDの検証と同様に、私はその型を表すクラスを作成します。彼の2番目のポイントはさらに重要です。住所などの概念を表すフィールドのセットがある場合、その概念のクラスを作成します。Javaでプログラミングしている場合、静的型付けに高い代償を払っています。可能な限りすべての価値を得ることができます。


2
downvoterはコメントできますか?
ケビンクライン

静的型付けに支払う高額はいくらですか?
リチャードティングル

@RichardTingle:コードを変更するたびにコードを再コンパイルしてJVMを再起動する時間。ソース互換でバイナリ非互換の変更を行ったときに失われた時間と再コンパイルが必要なものはそうではなく、「MissingMethodException」が発生します。
ケビンクライン

私は(それは通常、すでに私は、実行を打つ時がまとめたの継続的なコンパイルと何)それは再デプロイが少し遅いようですんいるウェブのWARとの公正なポイントのJavaアプリケーションでの問題を経験したことがありません
リチャード・チンクル

16

しないでください。それはものを複雑にし、あなたはそれを必要としません

...私が2年前にここに書いたであろう答えです。しかし、今、私はよくわかりません。実際、ここ数か月で私は古いコードをこの形式に移行し始めました。これは私がやるべきことではなく、新しい機能の実装や既存の機能の変更に本当に必要だったからです。私はここで他の人がそのコードを見ている自動嫌悪を理解していますが、これは真剣な考えに値するものだと思います。


利点

主な利点は、コードを変更および拡張できることです。使用する場合

class Point {
    int x,y;
    // other point operations
}

数個の整数を渡すだけではなく(残念ながら、多くのインターフェイスが行うことです)、後で別のディメンションを追加するのがはるかに簡単になります。または、タイプをに変更しdoubleます。使用する場合、List<Author> authorsまたはList<Person> authorsその代わりにList<String> authors、後で著者が表すものにより多くの情報を追加する方がはるかに簡単になります。このように書き留めると、私は明白なことを述べているように感じますが、実際には、この方法で文字列を何度も使用することに罪を犯しました。文字列。

私は現在、より多くの情報が必要なので、コード全体に絡み合っている文字列リストをリファクタリングしようとしていますが、痛みを感じます:\

それ以上に、ブログの著者には、より多くの意味情報が含まれており、読者が理解しやすくなっていることに同意します。多くの場合、パラメーターには意味のある名前が付けられ、専用のドキュメント行が提供されますが、フィールドやローカルの場合はそうではありません。

最後の利点は、明らかな理由から型の安全性ですが、私の目には、これはささいなことです。

欠点

書くのに時間がかかります。小さなクラスを書くのは高速で簡単ですが、特にこれらのクラスがたくさん必要な場合楽ではありません。新しいラッパークラスを作成するために3分ごとに停止することに気付いた場合、集中力を損なう可能性もあります。ただし、この努力の状態は通常、コードの一部を書く最初の段階でのみ発生すると思います。私は通常、どのエンティティが関与する必要があるかについてすぐにかなり良いアイデアを得ることができます。

多くの冗長なセッター(または構造)とゲッターが含まれます。ブログの著者はの本当に醜い例与えnew Point(x(10), y(10))代わりのをnew Point(10, 10)、私は使い方もようなものを必要とするかもしれないことを追加したいMath.max(p.x.get(), p.y.get())の代わりにMath.max(p.x, p.y)。そして、長いコードは読みにくいと考えられていることが多く、当然そうです。しかし、正直なところ、多くのコードがオブジェクトを移動し、選択メソッドのみがオブジェクトを作成し、さらにその細かい部分にアクセスする必要はほとんどないと感じています(とにかくOOPyではありません)。

議論の余地あり

これがコードの可読性に役立つかどうかは議論の余地があると思います。はい、より多くの意味情報がありますが、コードは長くなります。はい、各ローカルの役割を理解するのは簡単ですが、ドキュメントを読みに行かない限り、それで何ができるかを理解するのは困難です。


他のほとんどのプログラミングスクールと同じように、これを極端に取るのは不健康だと思います。x座標とy座標をそれぞれ異なるタイプに分離することはありません。十分Countな場合は必要ないと思いますintunsigned intCでの使用は嫌いです。理論的には良いのですが、十分な情報が得られないだけでなく、その魔法の-1をサポートするために後でコードを拡張することを禁止します。時には、シンプルさが必要です。

ブログの投稿は少し極端だと思います。しかし、全体的に、私は痛みを伴う経験から、その背後にある基本的なアイデアが正しいものから作られていることを学びました。

過剰に設計されたコードには深い嫌悪感を抱いています。本当にそうです。しかし、正しく使用すると、この過剰なエンジニアリングとは思わない。


5

それは一種のやり過ぎですが、私が見たほとんどのものは代わりに設計が不十分であるとしばしば思います。

それは「安全」だけではありません。Javaの本当に素晴らしい点の1つは、特定のライブラリメソッド呼び出しが必要とする/期待するものを記憶/構成するのに役立つことです。

私が取り組んだ最悪の(はるかに)Javaライブラリは、Smalltalkが非常に好きな人によって書かれ、GUIライブラリをモデル化して、Smalltalkのように動作するようにしました。しかし、ベースオブジェクトをキャストできるすべてを実際に使用することはできなかったので、メソッドに渡すものを推測し、実行時まで失敗したかどうかを知らないことに戻りました(私が使用するたびに対処しなければならなかった何かC)で働いていた。

別の問題-オブジェクトなしで文字列、整数、コレクション、配列を渡すと、意味のないデータのボールしかありません。これは、「一部のアプリ」が使用するライブラリーの観点から考えると自然に思えますが、アプリ全体を設計するときは、データが定義されている場所ですべてのデータに意味(コード)を割り当て、これらの高レベルオブジェクトの相互作用の条件。オブジェクトの代わりにプリミティブを渡す場合、定義により、定義されている場所とは異なる場所でデータを変更します(これは、セッターとゲッターが本当に好きではない理由でもあります)自分のものではないデータについて)。

最後に、すべてに個別のオブジェクトを定義する場合、常にすべてを検証するのに最適な場所があります。たとえば、郵便番号用のオブジェクトを作成し、後で郵便番号に常に4桁の拡張子が含まれていることを確認する必要がある場合それを置くのに最適な場所。

実際には悪い考えではありません。それについて考えると、私はそれがまったく過剰に設計されているとは言えないでしょう、ほとんどすべての方法で作業するのが簡単です-1つの例外は小さなクラスの増殖ですが、Javaクラスはとても軽量で簡単ですこれはほとんどコストではない(それらは生成されることさえある)と書くこと。

あまりにも多くのクラスが定義されている(プログラミングが難しくなった)よく書かれたJavaプロジェクトを見ることに本当に興味がありますが、あまり多くのクラスを持つことは不可能だと思い始めています。


3

この概念を別の出発点から見る必要があると思います。データベース設計者の観点から見てみましょう。最初の例で渡された型は、便利な方法は言うまでもなく、独自の方法でパラメーターを定義するものではありません。

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

チケットを予約している実際の顧客を指定するには、2つのパラメーターが必要です。同じ名前の2つの異なる映画(リメイクなど)、同じ名前の異なる映画(翻訳など)がある場合があります。例えば、あなたが使用している(映画館の特定のチェーンは異なる支店を持っているかもしれないので、どのように文字列の中で一貫性のある方法でそれに対処しようとしている$chain ($city)$chain in $city、あるいは何か他のものとどのようにあなたはこれがあることを確認してくださいしようとしています最悪の場合、実際には2つのパラメーターを使用して顧客を指定します。名と姓の両方が指定されているという事実は、有効な顧客を保証するものではありません(2つを区別できませんJohn Doe)。

これに対する答えは型を宣言することですが、上記で示したように、これらはめったに薄いラッパーにはなりません。ほとんどの場合、それらはデータストレージとして機能するか、何らかのデータベースと結合されます。そのため、Cinemaオブジェクトには名前、場所などが含まれる可能性があり、そのようにしてあいまいさを取り除きます。それらが薄いラッパーである場合、それらは偶然によるものです。

だから、私見ブログの投稿は「正しい型を渡すようにしてください」と言っているだけで、その作者は特に基本的なデータ型を選ぶために過度に制約された選択をしただけです(間違ったメッセージです)。

提案された代替案の方が優れています。

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

その一方で、私はブログの投稿がすべてを包み込むには行き過ぎだと思います。Count汎用性が高すぎるため、リンゴやオレンジを数え、それらを追加しても、型システムで無意味な操作を行うことができる状況があります。もちろん、ブログと同じロジックを適用して、タイプCountOfOrangesなどを定義することもできますが、それは単純な愚かでもあります。

それが価値があるもののために、私は実際に次のようなものを書くだろう

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

簡単に言えば、無意味な変数を渡すべきではありません。実際のオブジェクトを決定しない値でオブジェクトを実際に指定するのは、クエリを実行するpublic Collection<Film> findFilmsWithTitle(String title)とき(例:)または概念実証をまとめるときだけです。あなたのタイプのシステムは、清潔に保つので、あまりにも一般的であるタイプ(Aで表される例えば映画を使用していないString)、または厳しすぎる/特定/不自然(例えばCount代わりにint)。可能かつ実行可能な場合は常に、オブジェクトを一意かつ明確に定義する型を使用してください。

編集:さらに短い要約。小規模なアプリケーション(概念実証など)の場合:なぜ複雑な設計に煩わされるのですか?Stringまたはintを使用して、それを使用してください。

大規模なアプリケーションの場合:基本的なデータ型を持つ単一のフィールドで構成されるクラスがたくさんある可能性は本当にありますか?そのようなクラスがほとんどない場合は、「通常の」オブジェクトだけがあり、そこには特別なことは何もありません。

文字列をカプセル化するという考えは、不完全な設計に過ぎないと感じています。小さなアプリケーションでは非常に複雑であり、大きなアプリケーションでは十分ではありません。


あなたの主張はわかりますが、著者が述べている以上のことを想定していると思います。私たちが知っている限り、彼のモデルには、パトロン用のストリング、映画用のストリングなどしかありません。この架空の追加機能は本当に問題の核心であるため、彼が主張する際にそれを省くのに非常に「ぼんやり」していたか、または理由だけでより意味的な力を提供する必要があると考えています。繰り返しますが、私たちが知っているすべてのために、それは彼が念頭に置いていた後者です。
DPM

@Jubbat:実際、著者が述べている以上のことを想定しています。しかし、私のポイントは、単純なアプリケーションを使用しているということです。その場合、どちらの方法も非常に複雑です。その規模では、保守性は問題ではなく、セマンティックの区別がコーディング速度の妨げになります。一方、アプリケーションが大きい場合は、型を適切に定義する価値があります(ただし、単純なラッパーである可能性は低いです)。私見、彼の例は説得力がないか、彼がしようとしているポイントを超えて大きな設計上の欠陥を持っています。
エゴン

2

私にとってこれは、C#でリージョンを使用するのと同じです。一般的に、コードを読みやすくするためにこれが必要だと感じた場合、時間を費やすべき大きな問題があります。


2
原因ではなく症状の治療を示唆するために+1。
ジョブ

2

typedefのような機能が強く型付けされた言語では、これは本当に良いアイデアだと思います。

Javaにはこれがないので、これらのことのためにまったく新しいクラスを作成することは、おそらくコストが利益を上回ることを意味します。変数/パラメーターの命名に注意することで、80%の利点を得ることができます。


0

良いでしょう、IF文字列(整数の終わり、そして...文字列についてのみ話す)は最終的なものではなかったので、これらのクラスは意味を持つ(制限された)文字列であり、処理方法を知っている独立したオブジェクトに送信できます基本タイプ(前後に会話することなく)。

そして、例えば「グッドネス」は増加します。すべての名前の制限。

ただし、(ライブラリではなく)アプリを作成する場合は、常にリファクタリングできます。それで、私はそれなしで始めるために前に進みます。


0

例を挙げると、現在開発されているシステムには、(外部システムの使用による)複数の種類の識別子で識別できるさまざまなエンティティがあり、場合によっては同じ種類のエンティティでもあります。すべての識別子は文字列です。したがって、誰かがどの種類のidをパラメーターとして渡す必要があるかを混同した場合、コンパイル時エラーは表示されませんが、実行時にプログラムが爆発します。これは頻繁に起こります。したがって、この原則の主な目的は、変更から保護することではなく(変更にも役立つ)、バグから身を守ることであると言わざるを得ません。とにかく、誰かがAPIを設計する場合、ドメインの概念を反映する必要があるため、ドメイン固有のクラスを定義することは概念的に有用です。すべては開発者の心に秩序があるかどうかに依存します。


@downvoter:説明してください。
thSoft
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.