CodeGym /Java Blog /ランダム /アンチパターンとは何ですか? いくつかの例を見てみましょう(パート 2)
John Squirrels
レベル 41
San Francisco

アンチパターンとは何ですか? いくつかの例を見てみましょう(パート 2)

ランダム グループに公開済み
アンチパターンとは何ですか? いくつかの例を見てみましょう (パート 1) 今日は、最も一般的なアンチパターンのレビューを続けます。最初の部分を見逃した場合は、ここからご覧ください。 アンチパターンとは何ですか?  いくつかの例を見てみましょう (パート 2) - 1したがって、デザインパターンはベストプラクティスです。言い換えれば、これらは特定の問題を解決するための、実績のある優れた方法の例です。一方、アンチパターンは、さまざまな問題を解決する際の落とし穴や間違いのパターン (邪悪なパターン) であるという意味で、その正反対です。次のソフトウェア開発のアンチパターンに進みましょう。

8. ゴールデンハンマー

ゴールデンハンマーは、特定のソリューションが普遍的に適用できるという確信によって定義されるアンチパターンです。例:
  1. 問題に遭遇し、完璧な解決策のパターンを見つけた後、プログラマーは、特定のケースに適切な解決策を探すのではなく、このパターンをあらゆる場所に貼り付け、現在および将来のすべてのプロジェクトに適用しようとします。

  2. かつて一部の開発者は、特定の状況に合わせてキャッシュの独自のバリアントを作成しました (他に適切なものがなかったため)。その後、特別なキャッシュ ロジックを含まない次のプロジェクトでは、既製のライブラリ (Ehcache など) を使用する代わりに、そのバリアントを再度使用しました。その結果、大量のバグと非互換性が発生し、多くの時間を無駄にし、神経をすり減らしました。

    誰でもこのアンチパターンに陥る可能性があります。初心者の場合、デザインパターンについて詳しくないかもしれません。そのため、自分が習得した 1 つの方法ですべての問題を解決しようとする可能性があります。プロフェッショナルについて話している場合、これをプロフェッショナル デフォルメまたはナードビューと呼びます。自分の好みのデザイン パターンがあり、過去にうまく適合していれば将来も同じ結果が保証されると仮定して、正しいものを使用するのではなく、お気に入りを使用します。

    この落とし穴は、実装が悪く、不安定で、維持が困難なことから、プロジェクトの完全な失敗に至るまで、非常に悲しい結果を生み出す可能性があります。すべての病気に効く 1 つの薬がないのと同じように、すべての状況に対応する 1 つの設計パターンもありません。

9. 時期尚早な最適化

時期尚早な最適化は、その名の通りアンチパターンです。
「プログラマーは、コード内の重要ではない箇所について考えたり心配したり、最適化を試みたりすることに膨大な時間を費やしますが、それは後続のデバッグやサポートに悪影響を及ぼすだけです。一般に、たとえば 97% の場合、最適化については忘れるべきです。さらに、 、時期尚早な最適化は諸悪の根源です。そうは言っても、残りの 3% には最大限の注意を払う必要があります。」— ドナルド・クヌース
たとえば、データベースにインデックスを途中で追加する場合などです。なぜそれが悪いのでしょうか?まあ、それは悪いことですが、インデックスはバイナリツリーとして保存されます。その結果、新しい値が追加および削除されるたびにツリーが再計算され、リソースと時間が消費されます。したがって、インデックスは緊急に必要な場合 (大量のデータがあり、クエリに時間がかかりすぎる場合) にのみ、最も重要なフィールド (最も頻繁にクエリされるフィールド) にのみ追加する必要があります。

10. スパゲッティコード

スパゲッティ コードは、例外、条件、ループのラップなど、あらゆる種類の分岐を含む、構造が不十分で混乱し、理解しにくいコードによって定義されるアンチパターンです。以前は、goto 演算子がこのアンチパターンの主な味方でした。Goto ステートメントは実際には使用されなくなり、これにより、関連する多くの困難や問題が幸いにも解消されます。

public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
ひどいですね。残念ながら、これは最も一般的なアンチパターンです :( このようなコードを書いた人でさえ、将来的には理解できなくなるでしょう。コードを見た他の開発者は、「まあ、うまくいくなら、まあいいか —多くの場合、メソッドは最初はシンプルで非常に透明ですが、新しい要件が追加されると、メソッドには徐々に条件ステートメントが増え、このような巨大なメソッドに変わります。というメッセージが表示された場合は、完全にリファクタリングするか、少なくとも最も複雑な部分をリファクタリングする必要があります。通常、プロジェクトをスケジュールするとき、リファクタリングに時間が割り当てられます。たとえば、スプリント時間の 30% がリファクタリングとテストに当てられます。もちろん、これは次のことを前提としています。急ぐ必要はありません(しかし、それがいつ起こるか)。ここで

11. マジックナンバー

マジック ナンバーは、あらゆる種類の定数がその目的や意味の説明なしにプログラム内で使用されるアンチパターンです。つまり、通常、コメントの名前が適切でないか、極端な場合には、コメントの内容やその理由を説明するコメントがありません。スパゲッティ コードと同様、これは最も一般的なアンチパターンの 1 つです。コードを書いていない人は、マジック ナンバーやその仕組みについて手掛かりを持っている場合もあれば、持っていない場合もあります (そして、やがて、作成者自身もそれらを説明できなくなるでしょう)。その結果、数値を変更または削除すると、コードは魔法のようにすべての動作を停止します。たとえば、3673。このアンチパターンに対処するには、コード レビューをお勧めします。コードは、コードの関連セクションに関与していない開発者によって確認される必要があります。彼らの目は新鮮になり、「これは何ですか?なぜあんなことをしたのですか?」という疑問が生まれます。そしてもちろん、説明的な名前を使用するか、コメントを残す必要があります。

12. コピーアンドペーストのプログラミング

コピー アンド ペースト プログラミングは、他人のコードが軽率にコピー アンド ペーストされ、予期しない副作用が生じる可能性があるアンチパターンです。たとえば、完全に理解していない数学的計算や複雑なアルゴリズムを使用したメソッドのコピー アンド ペーストなどです。私たちの特定のケースではうまくいくかもしれませんが、他の状況では問題が発生する可能性があります。配列内の最大数を決定するメソッドが必要だとします。インターネットを探し回っていると、次の解決策を見つけました。

public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
数値 3、6、1、4、2 を含む配列を取得すると、メソッドは 6 を返します。わかりました。そのままにしておきます。しかし、後で 2.5、-7、2、および 3 で構成される配列を取得すると、結果は -7 になります。そしてこの結果はダメです。ここでの問題は、Math.abs() が絶対値を返すことです。これを無視すると大惨事につながりますが、それは特定の状況においてのみです。ソリューションを深く理解していなければ、検証できないケースも多くあります。コピーされたコードは、スタイル的にも、より基本的なアーキテクチャ レベルにおいても、アプリケーションの内部構造を超える場合もあります。このようなコードは、読み取りと保守がより困難になります。そしてもちろん、他人のコードをそのままコピーすることは特別な種類の盗作であることを忘れてはなりません。

13. 車輪の再発明

車輪の再発明はアンチパターンであり、四角い車輪の再発明とも呼ばれます。。本質的に、このテンプレートは、上で検討したコピーアンドペーストのアンチパターンの逆です。このアンチパターンでは、解決策がすでに存在している問題に対して、開発者が独自の解決策を実装します。場合によっては、これらの既存のソリューションがプログラマーが発明したものよりも優れていることがあります。ほとんどの場合、これは時間のロスと生産性の低下につながります。プログラマは解決策をまったく見つけられないか、最善とはほど遠い解決策を見つける可能性があります。そうは言っても、独立したソリューションを作成する可能性を排除することはできません。そうすることは、コピー アンド ペースト プログラミングへの直接的な道となるからです。プログラマは、既製のソリューションを使用するか、カスタム ソリューションを作成するかにかかわらず、発生する特定のプログラミング タスクを適切に解決するためにガイドされる必要があります。よく、このアンチパターンを使用する理由は単に急いでいるからです。その結果、既製のソリューション (検索) を浅く分析することになります。 四角い車輪の再発明は、検討中のアンチパターンがマイナスの結果をもたらすケースです。つまり、プロジェクトにはカスタム ソリューションが必要で、開発者がそれを作成しますが、それは問題です。同時に、優れたオプションはすでに存在しており、他の人はそれをうまく使用しています。結論: 膨大な時間が失われます。まず、機能しないものを作成します。次に、それをリファクタリングして、最後に既存のものに置き換えます。例としては、すでに多数の実装が存在する場合に独自のカスタム キャッシュを実装することが挙げられます。プログラマーとしてどれほど才能があるとしても、四角い車輪を再発明するのは少なくとも時間の無駄であることを覚えておく必要があります。そして、ご存知のとおり、時間は最も貴重なリソースです。

14. ヨーヨー問題

ヨーヨー問題は、過度の断片化 (たとえば、過度に細分化された継承チェーン) によりアプリケーションの構造が過度に複雑になるアンチパターンです。「ヨーヨー問題」は、継承階層が長く複雑で、深くネストされたメソッド呼び出しを作成するプログラムを理解する必要があるときに発生します。その結果、プログラマはプログラムの動作を検査するために、多くの異なるクラスやメソッドの間を移動する必要があります。このアンチパターンの名前は、おもちゃの名前に由来しています。例として、次の継承チェーンを見てみましょう。 Technology インターフェイスがあります。

public interface Technology {
   void turnOn();
}
Transport インターフェイスはそれを継承します。

public interface Transport extends Technology {
   boolean fillUp();
}
そして、もう 1 つのインターフェイス、GroundTransport があります。

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
そしてそこから、抽象 Car クラスを派生します。

public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
次は抽象 Volkswagen クラスです。

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
そして最後に、特定のモデル:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
この連鎖により、私たちは次のような質問に対する答えを探す必要があります。
  1. メソッドはいくつVolkswagenAmarokありますか?

  2. 最大限の抽象化を実現するには、疑問符の代わりにどの型を挿入する必要がありますか。

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
このような質問にすぐに答えるのは難しく、実際に見て調査する必要があり、混乱しがちです。そして、階層がはるかに大きく、長く、複雑で、あらゆる種類のオーバーロードやオーバーライドがある場合はどうなるでしょうか? 過度の断片化により、私たちが持つであろう構造は不明瞭になってしまいます。最善の解決策は、不要な分割を減らすことです。私たちの場合は、テクノロジー → 自動車 → VolkswagenAmarok のままにします。

15. 偶然の複雑さ

不必要な複雑さは、ソリューションに不必要な複雑さをもたらすアンチパターンです。
「どんな愚か者でもコンピュータが理解できるコードを書くことができます。優れたプログラマは人間が理解できるコードを書きます。」— マーティン・ファウラー
では、複雑さとは何でしょうか? プログラム内で実行される各操作の難易度として定義できます。一般に、複雑さは 2 つのタイプに分類できます。1 つ目の複雑さは、システムが持つ機能の数です。これを削減できるのは 1 つの方法だけです。つまり、一部の機能を削除することです。既存の手法を監視する必要があります。メソッドが使用されなくなった場合、またはまだ使用されているが何の価値ももたらさない場合は、メソッドを削除する必要があります。さらに、どこに投資する価値があるのか​​ (多くのコードを再利用する)、どこにノーと言えるのかを理解するために、アプリケーション内のすべてのメソッドがどのように使用されているかを評価する必要があります。2 番目のタイプの複雑さは、不必要な複雑さです。専門的なアプローチによってのみ治すことができます。「クール」なことをする代わりに (この病気にかかりやすいのは若い開発者だけではありません)、最善の解決策は常にシンプルであるため、できるだけシンプルに行う方法を考える必要があります。たとえば、ユーザーなどのいくつかのエンティティの説明を含む小さな関連テーブルがあるとします。 アンチパターンとは何ですか?  いくつかの例を見てみましょう (パート 2) - 3したがって、ユーザーの ID、説明が行われている言語の ID、および説明自体がわかります。同様に、車両、ファイル、計画、顧客テーブル用の補助記述子があります。では、そのようなテーブルに新しい値を挿入するとどうなるでしょうか?

public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
したがって、次の列挙型が得られます。

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
すべてがシンプルで優れているように思えます...しかし、他の方法はどうなのでしょうか? 実際、それらはすべて、多数のswitchステートメントと多数のほぼ同一のデータベース クエリを持ち、クラスを非常に複雑にし、肥大化させます。どうすればこれらすべてを簡単にできるでしょうか? enum を少しアップグレードしましょう。

@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
これで、各タイプにはテーブルの元のフィールドの名前が付けられます。その結果、説明を作成する方法は次のようになります。

private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
便利、簡単、コンパクトだと思いませんか?優れた開発者の指標は、パターンを使用する頻度ではなく、アンチパターンを回避する頻度です。無知は最大の敵です。目で見て敵を知る必要があるからです。さて、今日はこれで終わりです。みんなありがとう!:)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION