文字列との結合はクラスメソッドよりも「緩い」のでしょうか?


18

Swingを使用して、Javaで学校グループプロジェクトを始めています。これは、データベースデスクトップアプリの簡単なGUIです。

教授は去年のプロジェクトのコードを教えてくれたので、彼がどのように仕事をしているかを見ることができました。私の最初の印象は、コードが本来あるべきよりもはるかに複雑であるということですが、プログラマーは、自分が書いただけではないコードを見るとき、これをよく考えると思います。

私は彼のシステムが良いか悪いかの理由を見つけたいと思っています。(私は教授に尋ねたが、彼は後でなぜそれが良いのかを見るだろうと言ったが、それは私を満足させない。)

基本的に、彼の永続可能なオブジェクト、モデル(ビジネスロジック)、およびビュー間のカップリングを避けるために、すべては文字列で行われます。データベースに格納される永続オブジェクトは文字列のハッシュテーブルであり、モデルとビューは互いにサブスクライブし、サブスクライブする「イベント」に文字列キーを提供します。

イベントがトリガーされると、ビューまたはモデルは、そのイベントの処理を決定するすべてのサブスクライバーに文字列を送信します。たとえば、ビューアクションリスナメソッドの1つ(これは永続オブジェクトにbicycleMakeFieldを設定するだけだと思います):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

その呼び出しは、最終的にVehicleモデルのこのメソッドに到達します。

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

教授は、このような方法は、ビューでビジネスロジックオブジェクトのメソッドを単純に呼び出すよりも、拡張性と保守性が高いと言います。彼は、ビューとモデルがお互いの存在を知らないので、ビューとモデルの間に結合がないと言います。

ビューとモデルが機能するには同じ文字列を使用する必要があるため、これはより悪い種類のカップリングだと思います。ビューまたはモデルを削除するか、文字列にタイプミスをすると、コンパイルエラーは発生しません。また、コードが必要以上に長くなります。

私は彼とこれについて議論したいが、彼は彼の業界経験を使って、私、経験の浅い学生がするかもしれない議論に反論する。私が見逃している彼のアプローチにはいくつかの利点がありますか?


明確にするために、ビューをモデルに明らかに結合させることで、上記のアプローチを比較したいと思います。たとえば、車両モデルオブジェクトをビューに渡し、車両の「メイク」を変更するには、次のようにします。

vehicle.make = bicycleMakeField.getText();

これにより、車両のメーカーを1か所で設定するために現在使用されている15行のコードが、読み取り可能な1行のコードに削減されます。(そして、この種の操作はアプリ全体で何百回も行われるため、読みやすさと安全性にとって大きな勝利になると思います。)


更新

私のチームリーダーと私は、静的型付けを使用してフレームワークを望みどおりに再構築し、教授に通知し、最終的に彼にデモを提供しました。彼は私たちに助けを求めない限り、そして私たちのチームの残りの部分を最新に保つことができる限り、私たちのフレームワークを使用するのに十分寛大です-これは私たちにとって公平に思えます。


12
OT。教授は自分自身を更新し、古いコードを使用しないでください。それは2012年だとき学界では2006年からJAVAのバージョンを使用する理由はありません
Farmor

3
最終的に彼の回答が得られたら、投稿してください。
ジェフ

4
コードの品質やテクニックの知恵に関係なく、識別子として使用される文字列の説明から「マジック」という言葉を削除する必要があります。「マジックストリング」とは、システムが論理的ではないことを意味し、ビューを着色し、インストラクターとの議論を害します。「キー」、「名前」、または「識別子」などの単語は、より中立で、おそらくより説明的であり、役に立つ会話につながる可能性が高くなります。
カレブ

1
本当です。彼と話をしている間、私はそれらを魔法の弦とは呼んでいませんが、あなたは正しいです、それをそれより悪い音にする必要はありません。
フィリップ

2
教授が説明するアプローチ(文字列として名前が付けられたイベントを介してリスナーに通知する)は適切です。実際には列挙型の方が理にかなっていますが、これは最大の問題ではありません。私がここで目にする本当の問題は、通知を受け取るオブジェクトがモデルそのものであり、イベントの種類に基づいて手動でディスパッチする必要があるということです。関数が最初のクラスである言語では、イベントへのオブジェクトではなく、関数を登録します。Javaでは、私は1つが、単一の機能をラップオブジェクトのいくつかの種類を使用する必要がありますね
アンドレア・

回答:


32

教授が提案するアプローチは、文字列型として最もよく説明されており、ほぼすべてのレベルで間違っています。

カップリングは、多くのケースをハードワイヤリングするのではなく、依存関係の反転によって最適に削減されます。


3
「ほぼすべてのレベル」と書きますが、この例では(あなたの意見では)適切なケースではない理由を書いていません。知るのは面白いでしょう。
-hakre

1
@hakre:少なくともJavaには適切なケースはありません。私は、「ほとんどすべてのレベルで間違っている」と言いました。なぜなら、間接指定をまったく使用せずに、すべてのコードを1か所に投げるよりも良いからです。ただし、手動ディスパッチの基礎として(グローバル)マジックストリング定数を使用することは、最終的にグローバルオブジェクトを使用する複雑な方法にすぎません。
back2dos

あなたの議論は次のとおりです。適切なケースは決してないので、これもそうではありませんか?それはほとんど議論ではなく、実際にはあまり役に立ちません。
ハクレ

1
@hakre:私の主張は、このアプローチはいくつかの理由で悪いことです(パラグラフ1-これを回避するのに十分な理由は、文字列型の説明で与えられています)。 )。
back2dos

3
@hakreシリアルキラーがあなたを椅子にダクトテープで固定し、チェーンソーを頭の上にかざし、man笑し、ディスパッチロジックに文字列リテラルをどこでも使用するように命じた場合、このアプローチが必要になる状況を想像できます。それ以外は、言語機能に依存してこの種のことを行うことが望ましいです。実際、ワニとニンジャが関係するもう1つのケースを考えることができますが、それはもう少し複雑です。
ロブ14

19

あなたの教授はそれを間違っている。彼は、文字列を介して双方向に通信を強制することでビューとモデルを完全に分離しようとしています(ビューはモデルに依存せず、モデルはビューに依存しません)。これはオブジェクト指向設計とMVCの目的を完全に無効にします。クラスを書いてオブジェクト指向ベースのアーキテクチャを設計するのではなく、単にテキストベースのメッセージに応答する異種のモジュールを書いているように思えます。

教授にやや公平を期すため、彼はJavaで、非常に一般的な.NETインターフェイスと非常によく似たものを作成しようとしてINotifyPropertyChangedいます。これは、変更されたプロパティを指定する文字列を渡すことで、変更イベントをサブスクライバーに通知します 少なくとも.NETでは、このインターフェイスは主にデータモデルから通信してオブジェクトとGUI要素を表示するために使用されます(バインディング)。

正しく行われた場合、このアプローチを取ることにはいくつかの利点があります¹:

  1. ビューはモデルに依存しますが、モデルはビューに依存する必要はありません。これは適切なInversion of Controlです。
  2. Viewコードには、モデルからの変更通知への応答を管理する単一の場所があり、サブスクライブ/サブスクライブ解除する場所は1つだけであるため、参照メモリリークがハングする可能性が低くなります。
  3. イベントパブリッシャーからイベントを追加または削除する場合は、コーディングのオーバーヘッドが少なくなります。.NETには組み込みのパブリッシュ/サブスクライブメカニズムが呼び出さeventsれますが、Javaではクラスまたはオブジェクトを作成し、各イベントのサブスクライブ/サブスクライブ解除コードを記述する必要があります。
  4. このアプローチの最大の欠点は、サブスクライブするコードがパブリッシングコードと同期しなくなる可能性があることです。ただし、これは一部の開発環境でも利点です。モデルで新しいイベントが開発され、それをサブスクライブするためのビューがまだ更新されていない場合、ビューはおそらく機能します。同様に、イベントが削除された場合、いくつかのデッドコードがありますが、それでもコンパイルして実行できます。
  5. 少なくとも.NETでは、ここで非常に効果的にReflectionを使用して、サブスクライバに渡された文字列に応じてゲッターとセッターを自動的に呼び出します。

したがって、要するに(そしてあなたの質問に答えるために)、それはできますが、教授が取っているアプローチは、ビューとモデルの間に少なくとも一方的な依存関係を許可しないことのせいです。それは単に実用的ではなく、過度にアカデミックであり、手元のツールの使用方法の良い例ではありません。


¹Googleを検索しINotifyPropertyChangedて、実装時にマジックストリングを使用しないようにするために人々が見つける100万通りの方法を見つけます。


ここでSOAPについて説明したように聞こえます。:D…(しかし、はい、私はあなたのポイントを得て、あなたは正しいです)
コンラッドルドルフ

11

enumpublic static final Stringマジックストリングの代わりにs(またはs)を使用する必要があります。

あなたの教授はオブジェクト指向を理解していません。たとえば、具体的なVehicleクラスがありますか?
そして、このクラスに自転車のメーカーを理解させるには?

車両は抽象的であり、Bikeと呼ばれる具体的なサブクラスが必要です。私は彼のルールでプレーして良い成績を取り、彼の教えを忘れました。


3
この演習の目的が、より優れたオブジェクト指向の原則を使用してコードを改善することである場合を除きます。その場合は、リファクタリングしてください。
joshin4colours

2
@ joshin4colours StackExchangeがグレーディングしている場合、あなたは正しいでしょう。しかし、グレーダーはアイデアを思いついたのと同じ人なので、彼の実装がより良いことを知っているので、私たち全員に悪い点を与えるでしょう。
ダン・ニーリー

7

マジックストリングが悪いのは良い点だと思います。私のチームの誰かが、数週間前にマジックストリングによって引き起こされた問題をデバッグする必要がありました(しかし、彼らが書いたのは自分のコードであったため、口を閉ざしました)。そしてそれは... 産業で起こった!!

彼のアプローチの利点は、特に小規模なデモプロジェクトの場合、コーディングが高速になる可能性があることです。欠点は、文字列が頻繁に使用されるほど、タイプミスコードが物事を台無しにする可能性があり、タイプミスを起こしやすいことです。あなたがしたい場合や変更する魔法の文字列を、文字列をリファクタリングすることも、文字列触れる可能性があるリファクタリングツールので、変数をリファクタリングよりも硬い含まれ(列として)魔法の文字列をされますが、いない自分自身に魔法の文字列を、とすべきではない触れること。また、新しい開発者が同じ目的で新しいマジックストリングの発明を開始せず、既存のマジックストリングの検索に時間を浪費しないように、辞書またはマジックストリングのインデックスを保持する必要がある場合があります。

このコンテキストでは、Enumはマジックストリングやグローバルストリング定数よりも優れているように見えます。また、IDEは、定数文字列またはEnumを使用するときに、ある種のコードアシスト(オートコンプリート、リファクタリング、すべての参照の検索など)を提供します。マジックストリングを使用する場合、IDEはあまり役に立ちません。


列挙型は文字列リテラルよりも優れていることに同意します。しかし、それでも、enumキーを使用して "stateChangeRequest"を呼び出すか、モデルのメソッドを直接呼び出す方が本当に良いでしょうか?いずれにしても、モデルとビューはその1つのスポットで結合されます。
フィリップ

@Philip:それでは、その時点でモデルとビューを切り離すために、それを行うには何らかのコントローラークラスが必要になるかもしれません...-
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner-はい。MVCアーキテクチャは最も分離されています
-cdeszaq

右、別のレイヤーがそれらを分離するのを助けるならば、私はそれに対して議論しません。ただし、そのレイヤーは静的に型付けされ、文字列または列挙メッセージではなく実際のメソッドを使用して接続できます。
フィリップ

6

「産業経験」を使用することは不十分な議論です。あなたの教授はそれより良い議論を考え出す必要があります:-)。

他の人が述べたように、魔法の文字列の使用は確かに眉をひそめ、列挙型を使用することははるかに安全であると考えられています。列挙型には意味スコープがありますが、マジックストリングにはありません。それとは別に、あなたの教授が関心事を切り離す方法は、今日使用されているよりも古くて壊れやすい技術であると考えられています。

私はこれに答えている間に@backtodosがこれをカバーしていることがわかりますので、Inversion of Control(Dependency Injectionがフォームである)を使用して、やがて業界のSpringフレームワークを実行するという意見を追加します。 ..)は、このタイプのデカップリングを処理するためのより現代的な方法と考えられています。


2
学界が業界よりもはるかに高い要件を持っている必要があることに完全に同意します。学界==最良の理論的方法; 産業==最良の実用的方法
ファーマー

3
残念ながら、学界でこのようなことを教える人の中には、産業界でそれをカットできないためにそうする人もいます。良い面としては、学界にいる人の中には素晴らしい人もいるので、企業のオフィスに隠しておくべきではありません。
FrustratedWithFormsDesigner

4

はっきりさせましょう。あり、必然的にそこにいくつかのカップリングが。そのようなサブスクライブ可能なトピックがあるという事実は、メッセージの残りの性質(「単一の文字列」など)と同様に、結合のポイントです。それを考えると、質問は文字列または型を介して行われたときに結合が大きいかどうかのいずれかになります。その答えは、時間または空間に渡って分配されるカップリングを心配するかどうかによって異なります:メッセージが後で読み込まれる前にファイルに保存される場合、または別のプロセスに送信される場合(特にそれは同じ言語で書かれていません)、文字列を使用すると、物事がはるかに簡単になります。欠点は、型チェックがないことです。失敗は、実行時まで検出されません(その場合でも検出されません)。

Javaの緩和/妥協戦略は、型付きインターフェイスを使用することですが、その型付きインターフェイスは別のパッケージにあります。Java interfaceと基本的な値型(列挙、例外など)のみで構成される必要があります。そのパッケージにはインターフェース実装はありません。その後、誰もがそれに対して実装し、複雑な方法でメッセージを伝える必要がある場合、Javaデリゲートの特定の実装は必要に応じてマジックストリングを使用できます。また、JEEやOSGiなどのよりプロフェッショナルな環境にコードを移行するのがはるかに簡単になり、テストなどの小さなことを大いに支援します…


4

教授が提案しているのは、「マジックストリング」の使用です。変更を容易にするために、クラスを相互に「疎結合」しますが、これはアンチパターンです。文字列は、コード命令を含むまたは制御するために使用されることはめったにないはずです。特定の文字列。

理由は非常に単純です。文字列は、構文や他の値との整合性についてコンパイラでチェックされません(せいぜい、文字列内のエスケープ文字やその他の言語固有のフォーマットに関する適切な形式がチェックされます)。必要なものを文字列に入れることができます。そのため、絶望的に壊れたアルゴリズム/プログラムをコンパイルすることができますが、実行されるまでエラーは発生しません。次のコード(C#)を検討してください。

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

...このコードはすべて最初にコンパイルしますが、エラーは一見して明らかです。この種のプログラミングには多数の例があり、これらはすべてこの種のエラーの影響を受けます。文字列を定数として定義しても(.NETの定数は、使用される各ライブラリのマニフェストに書き込まれるため、次に、定義された値が「グローバルに」変更されるように変更されると、すべてが再コンパイルされます。エラーはコンパイル時に捕捉されないため、実行時テスト中に捕捉する必要があり、適切なコード演習を行う単体テストで100%のコードカバレッジでのみ保証されます。これは通常、最も絶対的なフェイルセーフでのみ見られます、リアルタイムシステム。


2

実際、これらのクラスは依然として密結合されています。今を除いて、それらは、物事が壊れたときにコンパイラがあなたに伝えることができないような方法で密結合されています!

誰かが「BicycleMake」を「BicyclesMake」に変更すると、実行時まで壊れていることをだれも見つけられません。

実行時エラーは、コンパイル時エラーよりも修正に費用がかかります-これはあらゆる種類の悪です。

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