📚

ChatGPTと学ぶFlutter(Dart)でのSOLID原則:品質の高いコードへの道

2024/04/14に公開

はじめに

私はエンジニアとして約5年の経験がありますが、自分の書くコードの品質を常に向上させたいと日々考えています。
現職でのコードの質に対するフィードバックを受けて、さらに学び、改善するための方法を模索しているところです。

読んでほしい人

  • 高品質なコードを目指すエンジニア
  • SOLID原則については聞いたことがあるが、学び方がわからないエンジニア
  • 受験勉強や定期テストの勉強のような回答があるもの勉強が比較的得意なエンジニア

そもそもSOLID原則とは?

SOLIDの原則とは、高品質なソフトウェアを設計するための5つのガイドラインです。
これらの原則を適用することで、コードは以下のように改善されます。

  • 変更に強い
    • 新しい機能の追加や既存の機能の変更が容易
  • 理解しやすい
    • 他の開発者がコードをすぐに理解しやすくなる
  • 再利用可能
    • 一度書いたコードを他のプロジェクトでも使い回せる

各原則の概要

次の5つの原則の頭文字を取り、SOLIDと呼ばれている

  • 単一責任の原則(SRP: Single Responsibility Principle)

    一つのクラスは一つの責任のみ持つべきです。これにより、そのクラスが変更を受ける理由は一つのみになります。

    サンプルコード
    // ユーザープロファイル情報を保持するクラス
    class UserProfile {
        String name;
        String email;
      
        UserProfile(this.name, this.email);
      }
      
    // ユーザープロファイルの保存と読み込みを担当するクラス
    class UserProfileManager {
      void saveUserProfile(UserProfile userProfile) {
          // ユーザープロファイルをデータベースに保存するロジック
          print("${userProfile.name} のプロファイルを保存しました。");
        }
      
        void loadUserProfile(String name) {
          // データベースからユーザープロファイルを読み込むロジック
          print("$name のプロファイルを読み込みました。");
        }
      }
      
      void main() {
        UserProfile userProfile = UserProfile("Yamada Taro", "taro@example.com");
        UserProfileManager userProfileManager = UserProfileManager();
        
        userProfileManager.saveUserProfile(userProfile);
        userProfileManager.loadUserProfile(userProfile.name);
      }
    
    
    
  • オープン・クローズドの原則(OCP: Open-Closed Principle)

    システムを拡張しやすくしつつ、既存のコードを変更しないようにします。拡張性を持たせることで、将来の変更が容易になります。

    サンプルコード
        abstract class IReportGenerator {
          String generateReport(List<String> data);
        }
        
        class PDFReportGenerator implements IReportGenerator {
          
          String generateReport(List<String> data) {
            return "PDF report: ${data.join(", ")}";
          }
        }
        
        class HTMLReportGenerator implements IReportGenerator {
          
          String generateReport(List<String> data) {
            return "<html><body>${data.join("<br>")}</body></html>";
          }
        }
        
        
        
        class ReportGenerator {
          IReportGenerator _generator;
        
          ReportGenerator(this._generator);
        
          String generateReport(List<String> data) {
            return _generator.generateReport(data);
          }
        }
    
    
  • リスコフの置換原則(LSP: Liskov Substitution Principle)

    サブクラスがそのスーパークラスの代わりとして機能できることを要求します。つまり、サブクラスのインスタンスは、プログラムの正確さを損なうことなく、スーパークラスのインスタンスの代わりに使用できるべきです。この原則に従うことで、コードの互換性と再利用性が向上します。

    サンプルコード
     abstract class Bird {
       // 他のメソッドやフィールド
     }
     
     abstract class FlyingBird implements Bird {
        void fly();
     }
     
     class Duck extends FlyingBird {
       
       void fly() {
         print("アヒルが飛んでいます");
       }
     }
     
     class Ostrich extends Bird {
       void run() {
         print("ダチョウは走る");
       }
     }
     
     void makeBirdFly(FlyingBird bird) {
       bird.fly();
     }
     
     void makeBirdRun(Ostrich ostrich) {
       ostrich.run();
     }
     
     void main() {
       Duck duck = Duck();
       Ostrich ostrich = Ostrich();
     
       makeBirdFly(duck);
       makeBirdRun(ostrich); 
     }
    
    
  • インターフェイス分離の原則(ISP: Interface Segregation Principle)

    クラスが使用しないインターフェースは実装強制されるべきではないという考えに基づいています。つまり、非常に大きな一つのインターフェースよりも、特定のクライアントに必要なメソッドだけを提供する小さなインターフェースを多数用意するほうがより効果的です。

    サンプルコード
    abstract class IPrinter {
     
       void printDocument();
     }
     
     abstract class IScanner {
       void scan();
     }
     
     abstract class IFax {
       void fax();
     }
     
     class MultiFunctionPrinter implements IPrinter, IScanner, IFax {
       
       void printDocument() {
         print("印刷します。");
       }
     
       
       void scan() {
         print("スキャンします。");
       }
     
       
       void fax() {
         print("ファックスを送信します。");
       }
     }
     
     class OldFashionedPrinter implements IPrinter {
       
       void printDocument() {
         print("印刷します。");
       }
     
      
     }
     
     void main() {
       MultiFunctionPrinter mfp = MultiFunctionPrinter();
       OldFashionedPrinter ofp = OldFashionedPrinter();
     
       mfp.printDocument();
       mfp.scan();
       mfp.fax();
     
       ofp.printDocument();
      
     }
    
    
  • 依存性逆転の原則 (DIP: Dependency Inversion Principle)

    高レベルのモジュールが低レベルのモジュールに依存すべきではなく、両者は抽象化に依存すべきだという考え方です。これにより、モジュール間の依存性が減り、コードの変更が容易になります。

    サンプルコード
     abstract class DataStorage {
       void saveData(String data);
     }
     
     class LocalStorage implements DataStorage {
       
       void saveData(String data) {
         print("Data saved in local storage: $data");
       }
     }
     
     class UserManager {
       final DataStorage storage;
     
       UserManager(this.storage);
     
       void saveUserData(String userData) {
         storage.saveData(userData);
       }
     }
     
     void main() {
       DataStorage localStorage = LocalStorage();
       UserManager userManager = UserManager(localStorage);
       userManager.saveUserData("User data for Alice");
     }
     
    

学習の過程

言っていることはわかるけども😩

多くの技術書や記事を読んでも、「理解はできるが実際には応用が難しい」と感じていました。
そこで、ChatGPTの力を借りて、具体的なコード例を通じて原則を学ぶことにしました。

ChatGPTを活用🧑‍🏫

ソフトウェア開発では、「正解」が常に変わることを理解していますが、SOLID原則の基本的な「守」の段階でさえ理解するのが難しいことがあります。これがどれだけ基本的でも、理論だけでなく実際にコードを書く際にこれらの原則をどのように適用すればいいのか、始めの一歩が分からないのです。

そこで、ChatGPTの力を借りることにしました。具体的には、SOLID原則に違反しているコードを生成してもらい、その修正を試みることから始めました。このプロセスを通じて、私は理論だけでなく実践的な技術も学ぶことができました。

最初は、ただのアイデアでしたが、ChatGPTと共に問題点を洗い出し、適切な修正を加えることで、原則が実際にどのように機能するのかを理解することができました。この方法は、単に理解するだけでなく、「なぜ」その修正が必要なのかを深く考える良い練習になりました。

どんな風にやったのか?

↓ こんな感じです!

このようにして先生とトライアンドエラーを繰り返して勉強を行いました。

結果としての成果

3日間、合計6時間取り組んだ結果、先生の出す問題にはそこそこ対応ができるようになりましたので、守破離の「守」を若干は身についてきたのではないかと実感しています。
ただこれだけやっていても完全に身に付くことはないことは理解しておりますので、ここでの学びを個人開発並びに業務で発揮していくことで「守」のフェーズを身につけられると思いますのでこれからも精進いたします。

終わりに:これを他のエンジニアさんにも共有したい

私としてはChatGPT先生との練習は非常にためになったと思っているので、是非SOLIDで悩まれているエンジニアさんにも挑戦してみていただきたいです。

手軽に皆さんが挑戦できるようにGPTsにして、ストアに公開しました!

作成したGPTs

https://chat.openai.com/g/g-kbtWczztH-solid-learning-assistant

プログラミング言語とレベルと1~5の間で指定してもらえるとそれに沿った問題が生成されますので、是非活用ください。
Lv3あたりまで難なくクリアできるようになると最低限は身につけられた感覚を得られると思います!

フィードバックや改善案があれば、ぜひコメントで教えてください。

一緒に成長しましょう!

Discussion