オブジェクト指向の誤ったアプローチについて思ったことを、生成AIを利用してまとめる

導入

前回オブジェクト指向について思ったことを、生成AIを利用してまとめました。 satoazuki.hatenablog.com

オブジェクト指向は、優れたソフトウェア設計を実現するための強力なツールですが、誤ったアプローチによってはアンチパターン(設計上の誤り)を生み出す可能性があります。オブジェクト指向におけるアンチパターンは、手続き型プログラミングよりも制御を複雑にする設計を招く問題が背後に潜んでいます。

このページでは、オブジェクト指向におけるアンチパターンに焦点を当て、それが背後にある問題について探求します。アンチパターンは、正しい設計原則から逸脱し、ソフトウェアの保守性、可読性、拡張性を損なう可能性があります。では、どのようなアンチパタンが存在し、それらがどのような問題を引き起こすのか、詳細に見ていきましょう。

派生クラスと条件文の増加

オブジェクト指向プログラミングにおいて、派生クラスを活用することは一般的です。派生クラスは基底クラスから継承され、独自のふるまいを提供します。しかし、時にはこの設計アプローチが問題を引き起こすことがあります。

派生クラス固有のメソッドを公開利用する設計

ある派生クラスで固有のふるまいを実装し、それを公開したくなることがあるでしょう。おそらくインターフェースクラスのメソッドとは無関係な、その派生クラスの内部状態に固有の利用ケースしたいのだとおもいますが、これは問題を引き起こす可能性があります。

ダウンキャストと条件文の増加

派生クラスが基底クラスと異なる操作を提供するためには、インターフェースクラスのインスタンスが派生クラスから生成しているかを実装者が知る必要があります。 そのためにはインターフェースクラスのインスタンスに対して派生クラスにダウンキャストできるかどうか調べる必要が増加し、コードの制御が複雑化します。

ダウンキャスト条件文を作らない

まずはインターフェースクラスのメソッドに抽象的に実装できるかどうか検討しましょう。 また、基底クラスが派生クラスの固有処理を適切に委譲できるように設計を見直すことも重要です。

条件文が増加する例

interface InterfaceClass
{
    bool Convert(int input);
}

// 派生クラスの定義
class DrivedClass : InterfaceClass
{
    public bool  Convert(int input)
    {
        // メソッドの実装
        return false;
    }

    public void Change()
    {
        // メソッドの実装
        Console.WriteLine("Change method in DrivedClass");
    }
}
class Program
{
    static void Main(string[] args)
    {
        InterfaceClass instance = new DrivedClass();

        // DrivedClass にキャストできるかチェック
        if (instance is DrivedClass derivedInstance) // ダウンキャストできるか条件
        {
            // Change メソッドを呼び出す
            derivedInstance.Change();  // 派生クラス固有のメソッドをわざわざ読んでいる

            // Convertメソッドを呼び出す
            var  doesGet = derivedInstance.Convert(42);
        }
    }
}

継承による処理の見通しの悪化

オブジェクト指向は全体的な手続きと、末端のふるまいを切り分けることによって、可視性やカスタマイズ性を向上させる方法である。特に、全体的な手続きは入力から出力までのデータの流れを抽象化するので、昨今流行りのDXのために役に立つ。 一方で、末端のふるまいを手続きから切り離すということで、設計図が離れてしまう。設計が一か所にまとまっていないという点で見通しが悪化すると主張したい (もちろん、本来このオブジェクト指向手法によって得られる可視性やカスタマイズ性の恩恵が十分にでかい)

実行時の不透明な処理決定

オブジェクト指向では本来無視できる悪化を挙げた。しかし、この悪化の効果を劇的に上げるパターンがあり、それがクラスの継承になる。

共通な公開関数を基底クラスに実装するアンチパターン

クラスの継承を見かけるときは、たいてい今までのクラスのふるまいの一部を変えたいときである。

interface InterfaceClass
{
    bool Convert(int input);]
    bool Revert(int input);
}

class BaseClass
{
    public bool Convert(int input)
    {
           Console.WriteLine("Convert method in BaseClass");
           return true;
    }
    public bool Revert(int input)
   {
           Console.WriteLine("Revert method in BaseClass");
           return true;
   }
}

// 派生クラスの定義
class DrivedClass : InterfaceClass
{
    public bool  Convert(int input)
    {
        Console.WriteLine("Convert method in DrivedClass");
        return false;
    }

}

実行時に処理結果の不透明さ

上の例のような設計したとき、DrivedClass クラスをインスタンス化しそのメソッドを呼ぶと、その実装がBaseClassのふるまいをするか、DrivedClassのふるまいをするか実行時しかわからなくなる。 デバッガーは、①抽象化された手続き②ベースクラス③派生クラス、すべてのソースコードについてブレイクポイントをしかけ、そして実行して止まることを願うかステップ実行ですべての手続きを見ることになる。 (伝統的な手続き型コードであれば一つのファイルさえ注視すればよかった)

オブジェクト指向について思ったことを、生成AIを利用してまとめる

導入

オブジェクト指向プログラミング(OOP)についての基本的な説明

オブジェクト指向プログラミング(OOP)は、プログラミングの世界での新たな冒険で、中級者にとって魅力的なテーマの一つです。私自身、OOPに没頭しているんです。

OOPは、コードを「オブジェクト」と呼ばれる小さな部品に分割し、それぞれのオブジェクトがデータと機能を持つように設計するプログラミングスタイルです。これにより、コードが整理され、読みやすくなります。

OOPの魔法の言葉の一つは「カプセル化」です。これは、オブジェクト内にデータと機能を隠蔽し、他のプログラマーがそれに触れないようにする方法です。つまり、コードが誤って変更される心配が少なく、信頼性が向上します。

また、OOPでは「多相性(ポリモーフィズム)」と呼ばれる特徴もあります。これは、同じ名前のメソッドを異なるオブジェクトで使えるというもので、コードを柔軟に拡張できる利点があります。

最後に、「継承」という概念も大切です。これは、前のプログラマーが作成したクラスを利用して新しいクラスを作る方法です。前のコードを再利用することで、新しいプログラムを効率的に開発できます。

OOPは、コードを整理し、読みやすくするための強力なツールです。カプセル化、多相性、継承を使いこなして、プログラムをより洗練されたものに仕上げることができます。君もぜひこの新しい冒険に挑戦してみてください。きっと楽しいことが待っているはずです!

なぜOOPがコードの可読性向上に貢献するかの紹介

オブジェクト指向プログラミング(OOP)がコードの可読性向上に貢献する理由は、コードの構造化と条件文の削減によって、コードの理解を容易にするからです。

伝統的な手続き型プログラミングでは、コードは上から下に順番に実行されます。このアプローチでは、条件分岐(if文など)が多い場合、コード全体を理解するために上から下まで順に読む必要があり、可読性が低下します。

例えば、3つのif文がある場合、最大で8つの異なる条件のパターンが考えられます。コードの振る舞いを理解しようとすると、これら8つの振る舞いをすべて考慮しなければならず、一つの条件を変更する際にも8つのパターン全てに変更を加える必要があり、制御が複雑になります。

しかし、OOPではオブジェクトという小さな部品にコードを分割し、それぞれのオブジェクトが独自の振る舞いを持ちます。このアプローチにより、条件文の削減が可能となり、コードがよりシンプルで理解しやすくなります。

具体的なオブジェクトが自身の振る舞いを持つため、条件文を使わずにオブジェクトがそれぞれの振る舞いを担当します。これにより、新しい振る舞いを追加する際に、既存のコードに影響を与えずに新しいオブジェクトを追加することができます。

要するに、OOPはコードをより分かりやすく、メンテナンスしやすいものにする手段の一つなのです。条件文の削減とコードの構造化により、コードの可読性が向上し、プログラマーがより効果的にプログラムを理解し、新しい機能を追加できるようになります。

条件文と可読性

伝統的な手続き型プログラミングと条件文の多用

伝統的な手続き型プログラミングでは、コードの記述は順次手続きを実行する形で行われ、条件文(if文やswitch文など)が頻繁に使用されることがあります。これにはいくつかの制約や課題が伴います。

  1. 条件文の多用: 伝統的な手続き型プログラミングでは、プログラムの制御フローを制御するために頻繁に条件文を使用します。例えば、特定の条件に応じて異なるアクションを実行する場合、if文やswitch文が連続して登場することがあります。この結果、コードは条件毎に分岐し、複雑化してしまいます。
  2. 可読性の低下: 条件文が多用されると、コードが長大かつ難解になり、可読性が低下します。プログラマーがコードの挙動を理解し、バグを見つけたり修正したりするのが難しくなります。また、他のプログラマーがコードを理解しにくいため、協力してプロジェクトを進めるのも難しくなります。
  3. 保守性の低下: 大量の条件文を含むコードは、新しい機能を追加したり、既存の機能を修正したりする際に非常に複雑になります。条件文の変更が他の部分に影響を及ぼす可能性が高く、保守性が低下します。また、新しい条件を追加する際に、既存の条件文に手を加える必要が生じ、予期せぬバグの発生リスクが高まります。

このような問題点が、伝統的な手続き型プログラミングにおける条件文の多用に関連しています。これを改善するためにオブジェクト指向プログラミング(OOP)が導入され、コードの構造化と条件文の削減が促進されました。

条件文の過多が可読性に与える影響について説明

条件文の過多が可読性に与える影響は大きく、以下のような点で問題が生じます。

  1. 複雑性の増加: 条件文が多くなると、コードが複雑になります。各条件が相互に依存し、コードの制御フローが多重に入れ子になることがあります。この複雑性は、コード全体の理解を難しくし、バグの発見や修正を困難にします。

  2. 可読性の低下: 大量の条件文があるコードは、可読性が低下します。プログラムが何をしているのか、特定の条件下でどのブロックが実行されるのかを理解するのが難しくなります。可読性が低下すると、コードの保守や協力が難しくなります。

  3. テストの困難さ: 条件文が増えると、テストケースを作成するのが難しくなります。各条件ごとに異なるケースをテストする必要があり、テストカバレッジを高めるためには多くのテストケースを用意する必要があります。これにより、テストの維持が難しくなり、バグが見逃される可能性が高まります。

  4. 拡張性の制限: 大量の条件文を含むコードは、新しい機能を追加する際に制約を生じることがあります。新しい条件を追加すると、既存の条件文に手を加える必要が生じ、他の部分に予期しない影響を及ぼす可能性が高まります。これはコードの拡張性を制限し、新機能の追加を難しくします。

  5. エラーのリスク: 条件文が多いコードでは、個々の条件を正確に記述する必要があります。誤った条件や条件の漏れがあると、バグのリスクが高まります。また、条件の変更や更新時に、すべての条件を適切に更新する必要があり、ヒューマンエラーが発生しやすくなります。

したがって、条件文の過多はコードの複雑性、可読性の低下、テストの難しさ、拡張性の制限、エラーのリスクなど、多くの問題を引き起こす可能性があるため、注意が必要です。このような問題を軽減するために、OOPのようなアプローチが導入され、条件文を削減する努力が行われています。

オブジェクト指向のアプローチ

オブジェクト指向で条件文を減らす方法の概要

手続き型プログラミングでは、条件に基づいて処理を切り替えるためにif文が使用されます。このような条件文をOOPで置き換える要素は以下の通りです:

  1. オブジェクト:

同じ条件文を複数の関数で利用する場合、この条件文自体をオブジェクトとして認識できます。 このオブジェクトは、条件文とそれに関連するデータ・操作の組み合わせを表現します。

  1. カプセル化(Encapsulation):

手続き的なコード内で使用されていた変数やデータは、オブジェクトのプライベートなデータメンバーとしてカプセル化されます。 例えば、複数の関数で利用する変数をオブジェクト内のデータメンバーとして導入します。

  1. 多相性(Polymorphism):

手続き的なif文で実行される共通の処理や固有の処理を、異なるクラスのメソッドとして実装します。 例えば、共通の操作を持つ抽象クラスやインターフェースを定義し、各条件に対応するクラスがこれを実装します。

  1. 抽象クラス(Abstract Class):

手続き型コード内で条件文の外で宣言された変数や処理は、抽象クラスとして表現されます。 この抽象クラスは、条件に対応するクラスによって継承され、共通の特性やメソッドを提供します。

  1. インターフェースクラス(Interface Class):

手続き的な条件文を記述した関数は、インターフェースクラスを用いて実装します。 各クラスが共通のインターフェースを実装することで、条件文内での分岐を回避し、共通の操作を提供します。

  1. 継承(Inheritance):

手続き型コード内の条件文をOOPに移行する場合、各条件に対応するクラスを作成し、抽象クラスやインターフェースを継承します。 各クラスは、継承元のクラスからメソッドをオーバーライドし、特定の条件に応じた処理を提供します。

以上の要素によって、手続き型の条件文をオブジェクト指向の設計に移行させることができます。このアプローチにより、コードの可読性が向上し、新しい条件を追加する際にもシステムを拡張しやすくなります。

具体的な実例

伝統的な手続き型コード

# 伝統的な手続き型のコード

# Function1の定義
    static void Function1(string type)
    {
        int commonParameterA = 0;
        PrepareA(commonParameterA);

        if (type == "A")
        {
            SpecifiedFuncA1();
        }
        else if (type == "B")
        {
            SpecifiedFuncB1();
        }
    }

# Function2の定義
    static void Function2(string type)
    {
        int commonParameterB = 0;
        PrepareB(commonParameterB);

        if (type == "A")
        {
            SpecifiedFuncA2();
        }
        else if (type == "B")
        {
            SpecifiedFuncB2();
        }
    }

オブジェクト指向アプローチを用いたコード1

以下は、同じ機能をOOPスタイルで実現するコード1です。

interface IFunction
{
    void SpecifiedFunction1();
    void SpecifiedFunction2();
}
class FunctionA : IFunction
{
    public void SpecifiedFunction1()
    {
        // Aに特有の処理
    }
    public void SpecifiedFunction2()
    {
        // Aに特有の処理
    }
}

class FunctionB : IFunction
{
    public void SpecifiedFunction1()
    {
        // Bに特有の処理
    }
    public void SpecifiedFunction2()
    {
        // Bに特有の処理
    }
}
 static void Main(string[] args)
    {
        if (inputType == "A")
        {
            function = new FunctionA();
        }
        else if (inputType == "B")
        {
            function = new FunctionB();
        }
 }

# Function1の定義:
void function1(IFunction function)
{
    common_para_1 = 0
    prepare1(common_para_1)
    
   function.SpecifiedFunction1()
}

手続き的実装では複数関数で同じ条件式を使いましたが、オブジェクト指向ではオブジェクトのタイプで一度だけ分岐し、その後は不要です。

この例では、オブジェクト指向のアプローチにおいて、オブジェクト、カプセル化、多相性、インターフェースクラス、継承が結びついています。 しかし、抽象クラスの使用は設計方針に依存します。オブジェクトの種類に関わらず正しい挙動を実現したり、共通の機能を組み込んだりする設計に合わせて実装すると、抽象クラスが出てきます。

interface IFunction
{
    void SpecifiedFunction1();
    void SpecifiedFunction2();
}

abstract class AbstractFunction : IFunction
{
    public void SpecifiedFunction1()
   {
          common_para_1 = 0
          prepare1(common_para_1)

         OnSpecifiedFunction1()
   }
    protected abstract void OnSpecifiedFunction1();

    public void SpecifiedFunction2()
   {
          common_para_2 = 0
          prepare1(common_para_2)

         OnSpecifiedFunction2()
   }
  
    protected abstract void OnSpecifiedFunction2();
}

class FunctionA : AbstractFunction 
{
   protected void OnSpecifiedFunction1();
    {
        // Aに特有の処理
    }
    protected void OnSpecifiedFunction2()
    {
        // Aに特有の処理
    }
}

# Function1の定義:
void function1(IFunction function)
{
   function.SpecifiedFunction1()
}

このように共通の処理を抽象クラスで実現すると、呼び出す関数void function1(IFunction function)は不要になり、 function.SpecifiedFunction1()と呼ぶだけで済むはずです。

まとめ

オブジェクト指向プログラミングが条件文の削減と可読性向上にどのように貢献するかの要点のまとめ

手続き型プログラミングでは、N個の異なる処理をM個の種類で実行する場合、N*M個の条件分岐が必要です。 しかし、OOPを導入すると、最初の種類の分類(M個)だけで済み、各種類ごとに処理(N個)をカプセル化できます。これにより、可読性が向上し、条件文の削減が実現されます。

参考

生成AI

英会話

今日も後輩さんと英会話してきたので、そのまとめと後付けを記す

 

(May the Christian ) god damn it (to hell)くっそ。信仰深い人には不愉快なフレーズ

 

Suck クソ。クソっていう中ではまぁ丁寧なクソらしい

 

what a (fuck)なんじゃこりゃ。なにこれ。 whata って聞こえる。fuckfuckで強調すると下品

what a what なんじゃこりゃ。what aの強調。

f ふぁっく。ふぁっくっていうのは下品なので、それの省略

i run fast ,so i got  first place 速く走って一番になった。fast とfirstの発音が難しい...

 

cast とfast のaは同じ発音。f - aと続けるのが僕は苦手みたい

 

 

本日のランチにて

本日ランチにて学んだことは、二つ

①bitch work

丁寧な意味ではchore,雑用。クソみたいな仕事をbitch work  っていうみたいです。日常英会話なので、公式な時(先輩がいるとき)にはchoreって発言するべし。

②dummy

自分がバカだから、っていうときに、stupidっていうより、dummyて形容すると、面白いらしい。dummyは子供が自分のことを馬鹿って形容するときに使っていて、大人があえてdummyっていうと笑いのツボにはまるらしい。

先輩がいるときにはstupidだけど、dummyって自虐すると受ける(?)。

naitiv  な笑いはよくわからない...

 

 

 

 

How to use "f**k"を読んで

https://www.reddit.com/r/writing/comments/bh9vbf/how_to_use_fuck_from_an_english_class_in_germany/

を勧められたので、読みます。

 

"f**k"の使い方は奥が深いようで、適当に言葉を発するときによく使うのですが、使い方が正しくないといわれました。

 

例えば f**king big sign  と big  f**king signは異なる意味になり、それぞれ「くそでかい広告」「(でかくて)邪魔な広告」となるそうです。後者は道で歩いているときに看板にぶつかったときに使えるそうです。

他にも相手を馬鹿にするときは、Are you f**king in the head?っていうと、お前頭おかしいんちゃう?ってなるそうです。

あるいは、(f**king )shit っていうと、(ほんま)クソとなるようです。

 

このような背景のもと、上記の記事を勧められたので、その中身を読んだメモとして書き記します。

 

怒って: What the "f**k" is this?!

絶望して: What the "f**k" is this?

興味津々に: What the "f**k" is this?

嬉々として: What the "f**k" is this?

いずれも、はぁ?、なんじゃこりゃ、ふざけてやがるって意味らしいく、the "f**k"はWhatを強調する表現のようです。

信じられないをunf**kingbelievableというよりunbef**kinglievableって言った方が、意味が通じやすい(unf**ked & believableとならないから)

 

せっかくなんでいくつか調べてみた

F**k my life ワイの人生ホンマくそ

 

人を侮辱するとき、日本語だと「氏ね」っていう表現で人口減らしたがるけど、

向こうの国だと"f**k""っていう表現で人口増やそうとして、面白いよね